]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/assets/iD/iD/mapillary-js/mapillary.module.js
Update to iD v2.21.0
[rails.git] / vendor / assets / iD / iD / mapillary-js / mapillary.module.js
index b08ff0b342c6a0f8092579867e82e6a37512425f..3fbef9486ed80363bcebbb125e1545559f7dcaea 100644 (file)
@@ -17,495 +17,496 @@ PERFORMANCE OF THIS SOFTWARE.
 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]; };
+        function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
     return extendStatics(d, b);
 };
 
 function __extends(d, b) {
+    if (typeof b !== "function" && b !== null)
+        throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
     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';
+function __awaiter(thisArg, _arguments, P, generator) {
+    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
+    return new (P || (P = Promise))(function (resolve, reject) {
+        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
+        step((generator = generator.apply(thisArg, _arguments || [])).next());
+    });
 }
 
-/** 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);
+function __generator(thisArg, body) {
+    var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
+    return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
+    function verb(n) { return function (v) { return step([n, v]); }; }
+    function step(op) {
+        if (f) throw new TypeError("Generator is already executing.");
+        while (_) try {
+            if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
+            if (y = 0, t) op = [op[0] & 2, t.value];
+            switch (op[0]) {
+                case 0: case 1: t = op; break;
+                case 4: _.label++; return { value: op[1], done: false };
+                case 5: _.label++; y = op[1]; op = [0]; continue;
+                case 7: op = _.ops.pop(); _.trys.pop(); continue;
+                default:
+                    if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
+                    if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
+                    if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
+                    if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
+                    if (t[2]) _.ops.pop();
+                    _.trys.pop(); continue;
+            }
+            op = body.call(thisArg, _);
+        } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
+        if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
+    }
 }
 
-/** 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;
+function __values(o) {
+    var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
+    if (m) return m.call(o);
+    if (o && typeof o.length === "number") return {
+        next: function () {
+            if (o && i >= o.length) o = void 0;
+            return { value: o && o[i++], done: !o };
         }
-        else {
-            hostReportError(err);
+    };
+    throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
+}
+
+function __read(o, n) {
+    var m = typeof Symbol === "function" && o[Symbol.iterator];
+    if (!m) return o;
+    var i = m.call(o), r, ar = [], e;
+    try {
+        while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
+    }
+    catch (error) { e = { error: error }; }
+    finally {
+        try {
+            if (r && !r.done && (m = i["return"])) m.call(i);
         }
-    },
-    complete: function () { }
-};
+        finally { if (e) throw e.error; }
+    }
+    return ar;
+}
 
-/** PURE_IMPORTS_START  PURE_IMPORTS_END */
-var isArray$1 = /*@__PURE__*/ (function () { return Array.isArray || (function (x) { return x && typeof x.length === 'number'; }); })();
+function __spreadArray(to, from) {
+    for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
+        to[j] = from[i];
+    return to;
+}
+
+function __await(v) {
+    return this instanceof __await ? (this.v = v, this) : new __await(v);
+}
+
+function __asyncGenerator(thisArg, _arguments, generator) {
+    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
+    var g = generator.apply(thisArg, _arguments || []), i, q = [];
+    return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
+    function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
+    function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
+    function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
+    function fulfill(value) { resume("next", value); }
+    function reject(value) { resume("throw", value); }
+    function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
+}
+
+function __asyncValues(o) {
+    if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
+    var m = o[Symbol.asyncIterator], i;
+    return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
+    function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
+    function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
+}
 
-/** PURE_IMPORTS_START  PURE_IMPORTS_END */
-function isObject$1(x) {
-    return x !== null && typeof x === 'object';
+function isFunction(value) {
+    return typeof value === 'function';
 }
 
-/** 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  ') : '';
+function createErrorClass(createImpl) {
+    var _super = function (instance) {
+        Error.call(instance);
+        instance.stack = new Error().stack;
+    };
+    var ctorFunc = createImpl(_super);
+    ctorFunc.prototype = Object.create(Error.prototype);
+    ctorFunc.prototype.constructor = ctorFunc;
+    return ctorFunc;
+}
+
+var UnsubscriptionError = createErrorClass(function (_super) {
+    return function UnsubscriptionErrorImpl(errors) {
+        _super(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;
+    };
+});
+
+function arrRemove(arr, item) {
+    if (arr) {
+        var index = arr.indexOf(item);
+        0 <= index && arr.splice(index, 1);
     }
-    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) {
+var Subscription = (function () {
+    function Subscription(initialTeardown) {
+        this.initialTeardown = initialTeardown;
         this.closed = false;
-        this._parentOrParents = null;
-        this._subscriptions = null;
-        if (unsubscribe) {
-            this._ctorUnsubscribe = true;
-            this._unsubscribe = unsubscribe;
-        }
+        this._parentage = null;
+        this._teardowns = null;
     }
     Subscription.prototype.unsubscribe = function () {
+        var e_1, _a, e_2, _b;
         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)) {
+        if (!this.closed) {
+            this.closed = true;
+            var _parentage = this._parentage;
+            if (_parentage) {
+                this._parentage = null;
+                if (Array.isArray(_parentage)) {
                     try {
-                        sub.unsubscribe();
-                    }
-                    catch (e) {
-                        errors = errors || [];
-                        if (e instanceof UnsubscriptionError) {
-                            errors = errors.concat(flattenUnsubscriptionErrors(e.errors));
+                        for (var _parentage_1 = __values(_parentage), _parentage_1_1 = _parentage_1.next(); !_parentage_1_1.done; _parentage_1_1 = _parentage_1.next()) {
+                            var parent_1 = _parentage_1_1.value;
+                            parent_1.remove(this);
                         }
-                        else {
-                            errors.push(e);
+                    }
+                    catch (e_1_1) { e_1 = { error: e_1_1 }; }
+                    finally {
+                        try {
+                            if (_parentage_1_1 && !_parentage_1_1.done && (_a = _parentage_1.return)) _a.call(_parentage_1);
                         }
+                        finally { if (e_1) throw e_1.error; }
                     }
                 }
+                else {
+                    _parentage.remove(this);
+                }
             }
-        }
-        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;
+            var initialTeardown = this.initialTeardown;
+            if (isFunction(initialTeardown)) {
+                try {
+                    initialTeardown();
                 }
-                else if (this.closed) {
-                    subscription.unsubscribe();
-                    return subscription;
+                catch (e) {
+                    errors = e instanceof UnsubscriptionError ? e.errors : [e];
+                }
+            }
+            var _teardowns = this._teardowns;
+            if (_teardowns) {
+                this._teardowns = null;
+                try {
+                    for (var _teardowns_1 = __values(_teardowns), _teardowns_1_1 = _teardowns_1.next(); !_teardowns_1_1.done; _teardowns_1_1 = _teardowns_1.next()) {
+                        var teardown_1 = _teardowns_1_1.value;
+                        try {
+                            execTeardown(teardown_1);
+                        }
+                        catch (err) {
+                            errors = errors !== null && errors !== void 0 ? errors : [];
+                            if (err instanceof UnsubscriptionError) {
+                                errors = __spreadArray(__spreadArray([], __read(errors)), __read(err.errors));
+                            }
+                            else {
+                                errors.push(err);
+                            }
+                        }
+                    }
                 }
-                else if (!(subscription instanceof Subscription)) {
-                    var tmp = subscription;
-                    subscription = new Subscription();
-                    subscription._subscriptions = [tmp];
+                catch (e_2_1) { e_2 = { error: e_2_1 }; }
+                finally {
+                    try {
+                        if (_teardowns_1_1 && !_teardowns_1_1.done && (_b = _teardowns_1.return)) _b.call(_teardowns_1);
+                    }
+                    finally { if (e_2) throw e_2.error; }
                 }
-                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;
+            if (errors) {
+                throw new UnsubscriptionError(errors);
             }
-            subscription._parentOrParents = [_parentOrParents, this];
         }
-        else if (_parentOrParents.indexOf(this) === -1) {
-            _parentOrParents.push(this);
-        }
-        else {
-            return subscription;
+    };
+    Subscription.prototype.add = function (teardown) {
+        var _a;
+        if (teardown && teardown !== this) {
+            if (this.closed) {
+                execTeardown(teardown);
+            }
+            else {
+                if (teardown instanceof Subscription) {
+                    if (teardown.closed || teardown._hasParent(this)) {
+                        return;
+                    }
+                    teardown._addParent(this);
+                }
+                (this._teardowns = (_a = this._teardowns) !== null && _a !== void 0 ? _a : []).push(teardown);
+            }
         }
-        var subscriptions = this._subscriptions;
-        if (subscriptions === null) {
-            this._subscriptions = [subscription];
+    };
+    Subscription.prototype._hasParent = function (parent) {
+        var _parentage = this._parentage;
+        return _parentage === parent || (Array.isArray(_parentage) && _parentage.includes(parent));
+    };
+    Subscription.prototype._addParent = function (parent) {
+        var _parentage = this._parentage;
+        this._parentage = Array.isArray(_parentage) ? (_parentage.push(parent), _parentage) : _parentage ? [_parentage, parent] : parent;
+    };
+    Subscription.prototype._removeParent = function (parent) {
+        var _parentage = this._parentage;
+        if (_parentage === parent) {
+            this._parentage = null;
         }
-        else {
-            subscriptions.push(subscription);
+        else if (Array.isArray(_parentage)) {
+            arrRemove(_parentage, parent);
         }
-        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.prototype.remove = function (teardown) {
+        var _teardowns = this._teardowns;
+        _teardowns && arrRemove(_teardowns, teardown);
+        if (teardown instanceof Subscription) {
+            teardown._removeParent(this);
         }
     };
-    Subscription.EMPTY = (function (empty) {
+    Subscription.EMPTY = (function () {
+        var empty = new Subscription();
         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); }, []);
+var EMPTY_SUBSCRIPTION = Subscription.EMPTY;
+function isSubscription(value) {
+    return (value instanceof Subscription ||
+        (value && 'closed' in value && isFunction(value.remove) && isFunction(value.add) && isFunction(value.unsubscribe)));
+}
+function execTeardown(teardown) {
+    if (isFunction(teardown)) {
+        teardown();
+    }
+    else {
+        teardown.unsubscribe();
+    }
 }
 
-/** PURE_IMPORTS_START  PURE_IMPORTS_END */
-var rxSubscriber = /*@__PURE__*/ (function () {
-    return typeof Symbol === 'function'
-        ? /*@__PURE__*/ Symbol('rxSubscriber')
-        : '@@rxSubscriber_' + /*@__PURE__*/ Math.random();
-})();
+var config = {
+    onUnhandledError: null,
+    onStoppedNotification: null,
+    Promise: undefined,
+    useDeprecatedSynchronousErrorHandling: false,
+    useDeprecatedNextContext: false,
+};
+
+var timeoutProvider = {
+    setTimeout: function () {
+        var args = [];
+        for (var _i = 0; _i < arguments.length; _i++) {
+            args[_i] = arguments[_i];
+        }
+        var delegate = timeoutProvider.delegate;
+        return ((delegate === null || delegate === void 0 ? void 0 : delegate.setTimeout) || setTimeout).apply(void 0, __spreadArray([], __read(args)));
+    },
+    clearTimeout: function (handle) {
+        var delegate = timeoutProvider.delegate;
+        return ((delegate === null || delegate === void 0 ? void 0 : delegate.clearTimeout) || clearTimeout)(handle);
+    },
+    delegate: undefined,
+};
+
+function reportUnhandledError(err) {
+    timeoutProvider.setTimeout(function () {
+        {
+            throw err;
+        }
+    });
+}
+
+function noop() { }
+
+var COMPLETE_NOTIFICATION = (function () { return createNotification('C', undefined, undefined); })();
+function errorNotification(error) {
+    return createNotification('E', undefined, error);
+}
+function nextNotification(value) {
+    return createNotification('N', value, undefined);
+}
+function createNotification(kind, value, error) {
+    return {
+        kind: kind,
+        value: value,
+        error: error,
+    };
+}
+
+var context = null;
+function errorContext(cb) {
+    if (config.useDeprecatedSynchronousErrorHandling) {
+        var isRoot = !context;
+        if (isRoot) {
+            context = { errorThrown: false, error: null };
+        }
+        cb();
+        if (isRoot) {
+            var _a = context, errorThrown = _a.errorThrown, error = _a.error;
+            context = null;
+            if (errorThrown) {
+                throw error;
+            }
+        }
+    }
+    else {
+        cb();
+    }
+}
 
-/** PURE_IMPORTS_START tslib,_util_isFunction,_Observer,_Subscription,_internal_symbol_rxSubscriber,_config,_util_hostReportError PURE_IMPORTS_END */
-var Subscriber = /*@__PURE__*/ (function (_super) {
+var Subscriber = (function (_super) {
     __extends(Subscriber, _super);
-    function Subscriber(destinationOrNext, error, complete) {
+    function Subscriber(destination) {
         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;
+        if (destination) {
+            _this.destination = destination;
+            if (isSubscription(destination)) {
+                destination.add(_this);
+            }
+        }
+        else {
+            _this.destination = EMPTY_OBSERVER;
         }
         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;
+        return new SafeSubscriber(next, error, complete);
     };
     Subscriber.prototype.next = function (value) {
-        if (!this.isStopped) {
+        if (this.isStopped) {
+            handleStoppedNotification(nextNotification(value), this);
+        }
+        else {
             this._next(value);
         }
     };
     Subscriber.prototype.error = function (err) {
-        if (!this.isStopped) {
+        if (this.isStopped) {
+            handleStoppedNotification(errorNotification(err), this);
+        }
+        else {
             this.isStopped = true;
             this._error(err);
         }
     };
     Subscriber.prototype.complete = function () {
-        if (!this.isStopped) {
+        if (this.isStopped) {
+            handleStoppedNotification(COMPLETE_NOTIFICATION, this);
+        }
+        else {
             this.isStopped = true;
             this._complete();
         }
     };
     Subscriber.prototype.unsubscribe = function () {
-        if (this.closed) {
-            return;
+        if (!this.closed) {
+            this.isStopped = true;
+            _super.prototype.unsubscribe.call(this);
+            this.destination = null;
         }
-        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();
+        try {
+            this.destination.error(err);
+        }
+        finally {
+            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;
+        try {
+            this.destination.complete();
+        }
+        finally {
+            this.unsubscribe();
+        }
     };
     return Subscriber;
 }(Subscription));
-var SafeSubscriber = /*@__PURE__*/ (function (_super) {
+var SafeSubscriber = (function (_super) {
     __extends(SafeSubscriber, _super);
-    function SafeSubscriber(_parentSubscriber, observerOrNext, error, complete) {
+    function SafeSubscriber(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;
+            (next = observerOrNext.next, error = observerOrNext.error, complete = observerOrNext.complete);
+            var context_1;
+            if (_this && config.useDeprecatedNextContext) {
+                context_1 = Object.create(observerOrNext);
+                context_1.unsubscribe = function () { return _this.unsubscribe(); };
             }
             else {
-                hostReportError(err);
+                context_1 = observerOrNext;
             }
+            next = next === null || next === void 0 ? void 0 : next.bind(context_1);
+            error = error === null || error === void 0 ? void 0 : error.bind(context_1);
+            complete = complete === null || complete === void 0 ? void 0 : complete.bind(context_1);
         }
-    };
-    SafeSubscriber.prototype.__tryOrSetError = function (parent, fn, value) {
-        if (!config.useDeprecatedSynchronousErrorHandling) {
-            throw new Error('bad call');
+        _this.destination = {
+            next: next ? wrapForErrorHandling(next) : noop,
+            error: wrapForErrorHandling(error !== null && error !== void 0 ? error : defaultErrorHandler),
+            complete: complete ? wrapForErrorHandling(complete) : noop,
+        };
+        return _this;
+    }
+    return SafeSubscriber;
+}(Subscriber));
+function wrapForErrorHandling(handler, instance) {
+    return function () {
+        var args = [];
+        for (var _i = 0; _i < arguments.length; _i++) {
+            args[_i] = arguments[_i];
         }
         try {
-            fn.call(this._context, value);
+            handler.apply(void 0, __spreadArray([], __read(args)));
         }
         catch (err) {
-            if (config.useDeprecatedSynchronousErrorHandling) {
-                parent.syncErrorValue = err;
-                parent.syncErrorThrown = true;
-                return true;
-            }
-            else {
-                hostReportError(err);
-                return true;
+            {
+                reportUnhandledError(err);
             }
         }
-        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);
+function defaultErrorHandler(err) {
+    throw err;
 }
+function handleStoppedNotification(notification, subscriber) {
+    var onStoppedNotification = config.onStoppedNotification;
+    onStoppedNotification && timeoutProvider.setTimeout(function () { return onStoppedNotification(notification, subscriber); });
+}
+var EMPTY_OBSERVER = {
+    closed: true,
+    next: noop,
+    error: defaultErrorHandler,
+    complete: noop,
+};
 
-/** PURE_IMPORTS_START  PURE_IMPORTS_END */
-var observable = /*@__PURE__*/ (function () { return typeof Symbol === 'function' && Symbol.observable || '@@observable'; })();
+var observable = (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;
@@ -518,10 +519,8 @@ function pipeFromArray(fns) {
     };
 }
 
-/** PURE_IMPORTS_START _util_canReportError,_util_toSubscriber,_symbol_observable,_util_pipe,_config PURE_IMPORTS_END */
-var Observable = /*@__PURE__*/ (function () {
+var Observable = (function () {
     function Observable(subscribe) {
-        this._isScalar = false;
         if (subscribe) {
             this._subscribe = subscribe;
         }
@@ -533,41 +532,27 @@ var Observable = /*@__PURE__*/ (function () {
         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;
+        var _this = this;
+        var subscriber = isSubscriber(observerOrNext) ? observerOrNext : new SafeSubscriber(observerOrNext, error, complete);
+        errorContext(function () {
+            var _a = _this, operator = _a.operator, source = _a.source;
+            subscriber.add(operator
+                ?
+                    operator.call(subscriber, source)
+                : source
+                    ?
+                        _this._subscribe(subscriber)
+                    :
+                        _this._trySubscribe(subscriber));
+        });
+        return subscriber;
     };
     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);
-            }
+            sink.error(err);
         }
     };
     Observable.prototype.forEach = function (next, promiseCtor) {
@@ -581,16 +566,14 @@ var Observable = /*@__PURE__*/ (function () {
                 }
                 catch (err) {
                     reject(err);
-                    if (subscription) {
-                        subscription.unsubscribe();
-                    }
+                    subscription === null || subscription === void 0 ? void 0 : subscription.unsubscribe();
                 }
             }, reject, resolve);
         });
     };
     Observable.prototype._subscribe = function (subscriber) {
-        var source = this.source;
-        return source && source.subscribe(subscriber);
+        var _a;
+        return (_a = this.source) === null || _a === void 0 ? void 0 : _a.subscribe(subscriber);
     };
     Observable.prototype[observable] = function () {
         return this;
@@ -600,9 +583,6 @@ var Observable = /*@__PURE__*/ (function () {
         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) {
@@ -610,7 +590,7 @@ var Observable = /*@__PURE__*/ (function () {
         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); });
+            _this.subscribe(function (x) { return (value = x); }, function (err) { return reject(err); }, function () { return resolve(value); });
         });
     };
     Observable.create = function (subscribe) {
@@ -619,155 +599,280 @@ var Observable = /*@__PURE__*/ (function () {
     return Observable;
 }());
 function getPromiseCtor(promiseCtor) {
-    if (!promiseCtor) {
-        promiseCtor = Promise;
-    }
-    if (!promiseCtor) {
-        throw new Error('no Promise impl found');
-    }
-    return promiseCtor;
+    var _a;
+    return (_a = promiseCtor !== null && promiseCtor !== void 0 ? promiseCtor : config.Promise) !== null && _a !== void 0 ? _a : Promise;
+}
+function isObserver(value) {
+    return value && isFunction(value.next) && isFunction(value.error) && isFunction(value.complete);
+}
+function isSubscriber(value) {
+    return (value && value instanceof Subscriber) || (isObserver(value) && isSubscription(value));
 }
 
-/** PURE_IMPORTS_START  PURE_IMPORTS_END */
-var ObjectUnsubscribedErrorImpl = /*@__PURE__*/ (function () {
-    function ObjectUnsubscribedErrorImpl() {
-        Error.call(this);
-        this.message = 'object unsubscribed';
-        this.name = 'ObjectUnsubscribedError';
-        return this;
+function hasLift(source) {
+    return isFunction(source === null || source === void 0 ? void 0 : source.lift);
+}
+function operate(init) {
+    return function (source) {
+        if (hasLift(source)) {
+            return source.lift(function (liftedSource) {
+                try {
+                    return init(liftedSource, this);
+                }
+                catch (err) {
+                    this.error(err);
+                }
+            });
+        }
+        throw new TypeError('Unable to lift unknown Observable type');
+    };
+}
+
+var OperatorSubscriber = (function (_super) {
+    __extends(OperatorSubscriber, _super);
+    function OperatorSubscriber(destination, onNext, onComplete, onError, onFinalize) {
+        var _this = _super.call(this, destination) || this;
+        _this.onFinalize = onFinalize;
+        _this._next = onNext
+            ? function (value) {
+                try {
+                    onNext(value);
+                }
+                catch (err) {
+                    destination.error(err);
+                }
+            }
+            : _super.prototype._next;
+        _this._error = onError
+            ? function (err) {
+                try {
+                    onError(err);
+                }
+                catch (err) {
+                    destination.error(err);
+                }
+                finally {
+                    this.unsubscribe();
+                }
+            }
+            : _super.prototype._error;
+        _this._complete = onComplete
+            ? function () {
+                try {
+                    onComplete();
+                }
+                catch (err) {
+                    destination.error(err);
+                }
+                finally {
+                    this.unsubscribe();
+                }
+            }
+            : _super.prototype._complete;
+        return _this;
     }
-    ObjectUnsubscribedErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype);
-    return ObjectUnsubscribedErrorImpl;
-})();
-var ObjectUnsubscribedError = ObjectUnsubscribedErrorImpl;
+    OperatorSubscriber.prototype.unsubscribe = function () {
+        var _a;
+        var closed = this.closed;
+        _super.prototype.unsubscribe.call(this);
+        !closed && ((_a = this.onFinalize) === null || _a === void 0 ? void 0 : _a.call(this));
+    };
+    return OperatorSubscriber;
+}(Subscriber));
+
+function refCount() {
+    return operate(function (source, subscriber) {
+        var connection = null;
+        source._refCount++;
+        var refCounter = new OperatorSubscriber(subscriber, undefined, undefined, undefined, function () {
+            if (!source || source._refCount <= 0 || 0 < --source._refCount) {
+                connection = null;
+                return;
+            }
+            var sharedConnection = source._connection;
+            var conn = connection;
+            connection = null;
+            if (sharedConnection && (!conn || sharedConnection === conn)) {
+                sharedConnection.unsubscribe();
+            }
+            subscriber.unsubscribe();
+        });
+        source.subscribe(refCounter);
+        if (!refCounter.closed) {
+            connection = source.connect();
+        }
+    });
+}
 
-/** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */
-var SubjectSubscription = /*@__PURE__*/ (function (_super) {
-    __extends(SubjectSubscription, _super);
-    function SubjectSubscription(subject, subscriber) {
+var ConnectableObservable = (function (_super) {
+    __extends(ConnectableObservable, _super);
+    function ConnectableObservable(source, subjectFactory) {
         var _this = _super.call(this) || this;
-        _this.subject = subject;
-        _this.subscriber = subscriber;
-        _this.closed = false;
+        _this.source = source;
+        _this.subjectFactory = subjectFactory;
+        _this._subject = null;
+        _this._refCount = 0;
+        _this._connection = null;
+        if (hasLift(source)) {
+            _this.lift = source.lift;
+        }
         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;
+    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();
         }
-        var subscriberIndex = observers.indexOf(this.subscriber);
-        if (subscriberIndex !== -1) {
-            observers.splice(subscriberIndex, 1);
+        return this._subject;
+    };
+    ConnectableObservable.prototype._teardown = function () {
+        this._refCount = 0;
+        var _connection = this._connection;
+        this._subject = this._connection = null;
+        _connection === null || _connection === void 0 ? void 0 : _connection.unsubscribe();
+    };
+    ConnectableObservable.prototype.connect = function () {
+        var _this = this;
+        var connection = this._connection;
+        if (!connection) {
+            connection = this._connection = new Subscription();
+            var subject_1 = this.getSubject();
+            connection.add(this.source.subscribe(new OperatorSubscriber(subject_1, undefined, function () {
+                _this._teardown();
+                subject_1.complete();
+            }, function (err) {
+                _this._teardown();
+                subject_1.error(err);
+            }, function () { return _this._teardown(); })));
+            if (connection.closed) {
+                this._connection = null;
+                connection = Subscription.EMPTY;
+            }
         }
+        return connection;
     };
-    return SubjectSubscription;
-}(Subscription));
+    ConnectableObservable.prototype.refCount = function () {
+        return refCount()(this);
+    };
+    return ConnectableObservable;
+}(Observable));
 
-/** 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) {
+var ObjectUnsubscribedError = createErrorClass(function (_super) {
+    return function ObjectUnsubscribedErrorImpl() {
+        _super(this);
+        this.name = 'ObjectUnsubscribedError';
+        this.message = 'object unsubscribed';
+    };
+});
+
+var Subject = (function (_super) {
     __extends(Subject, _super);
     function Subject() {
         var _this = _super.call(this) || this;
-        _this.observers = [];
         _this.closed = false;
+        _this.observers = [];
         _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) {
+    Subject.prototype._throwIfClosed = function () {
         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.next = function (value) {
+        var _this = this;
+        errorContext(function () {
+            var e_1, _a;
+            _this._throwIfClosed();
+            if (!_this.isStopped) {
+                var copy = _this.observers.slice();
+                try {
+                    for (var copy_1 = __values(copy), copy_1_1 = copy_1.next(); !copy_1_1.done; copy_1_1 = copy_1.next()) {
+                        var observer = copy_1_1.value;
+                        observer.next(value);
+                    }
+                }
+                catch (e_1_1) { e_1 = { error: e_1_1 }; }
+                finally {
+                    try {
+                        if (copy_1_1 && !copy_1_1.done && (_a = copy_1.return)) _a.call(copy_1);
+                    }
+                    finally { if (e_1) throw e_1.error; }
+                }
             }
-        }
+        });
     };
     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;
+        var _this = this;
+        errorContext(function () {
+            _this._throwIfClosed();
+            if (!_this.isStopped) {
+                _this.hasError = _this.isStopped = true;
+                _this.thrownError = err;
+                var observers = _this.observers;
+                while (observers.length) {
+                    observers.shift().error(err);
+                }
+            }
+        });
     };
     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;
+        var _this = this;
+        errorContext(function () {
+            _this._throwIfClosed();
+            if (!_this.isStopped) {
+                _this.isStopped = true;
+                var observers = _this.observers;
+                while (observers.length) {
+                    observers.shift().complete();
+                }
+            }
+        });
     };
     Subject.prototype.unsubscribe = function () {
-        this.isStopped = true;
-        this.closed = true;
+        this.isStopped = this.closed = true;
         this.observers = null;
     };
+    Object.defineProperty(Subject.prototype, "observed", {
+        get: function () {
+            var _a;
+            return ((_a = this.observers) === null || _a === void 0 ? void 0 : _a.length) > 0;
+        },
+        enumerable: false,
+        configurable: true
+    });
     Subject.prototype._trySubscribe = function (subscriber) {
-        if (this.closed) {
-            throw new ObjectUnsubscribedError();
-        }
-        else {
-            return _super.prototype._trySubscribe.call(this, subscriber);
-        }
+        this._throwIfClosed();
+        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;
+        this._throwIfClosed();
+        this._checkFinalizedStatuses(subscriber);
+        return this._innerSubscribe(subscriber);
+    };
+    Subject.prototype._innerSubscribe = function (subscriber) {
+        var _a = this, hasError = _a.hasError, isStopped = _a.isStopped, observers = _a.observers;
+        return hasError || isStopped
+            ? EMPTY_SUBSCRIPTION
+            : (observers.push(subscriber), new Subscription(function () { return arrRemove(observers, subscriber); }));
+    };
+    Subject.prototype._checkFinalizedStatuses = function (subscriber) {
+        var _a = this, hasError = _a.hasError, thrownError = _a.thrownError, isStopped = _a.isStopped;
+        if (hasError) {
+            subscriber.error(thrownError);
         }
-        else if (this.isStopped) {
+        else if (isStopped) {
             subscriber.complete();
-            return Subscription.EMPTY;
-        }
-        else {
-            this.observers.push(subscriber);
-            return new SubjectSubscription(this, subscriber);
         }
     };
     Subject.prototype.asObservable = function () {
@@ -780,7 +885,7 @@ var Subject = /*@__PURE__*/ (function (_super) {
     };
     return Subject;
 }(Observable));
-var AnonymousSubject = /*@__PURE__*/ (function (_super) {
+var AnonymousSubject = (function (_super) {
     __extends(AnonymousSubject, _super);
     function AnonymousSubject(destination, source) {
         var _this = _super.call(this) || this;
@@ -789,179 +894,25 @@ var AnonymousSubject = /*@__PURE__*/ (function (_super) {
         return _this;
     }
     AnonymousSubject.prototype.next = function (value) {
-        var destination = this.destination;
-        if (destination && destination.next) {
-            destination.next(value);
-        }
+        var _a, _b;
+        (_b = (_a = this.destination) === null || _a === void 0 ? void 0 : _a.next) === null || _b === void 0 ? void 0 : _b.call(_a, value);
     };
     AnonymousSubject.prototype.error = function (err) {
-        var destination = this.destination;
-        if (destination && destination.error) {
-            this.destination.error(err);
-        }
+        var _a, _b;
+        (_b = (_a = this.destination) === null || _a === void 0 ? void 0 : _a.error) === null || _b === void 0 ? void 0 : _b.call(_a, err);
     };
     AnonymousSubject.prototype.complete = function () {
-        var destination = this.destination;
-        if (destination && destination.complete) {
-            this.destination.complete();
-        }
+        var _a, _b;
+        (_b = (_a = this.destination) === null || _a === void 0 ? void 0 : _a.complete) === null || _b === void 0 ? void 0 : _b.call(_a);
     };
     AnonymousSubject.prototype._subscribe = function (subscriber) {
-        var source = this.source;
-        if (source) {
-            return this.source.subscribe(subscriber);
-        }
-        else {
-            return Subscription.EMPTY;
-        }
+        var _a, _b;
+        return (_b = (_a = this.source) === null || _a === void 0 ? void 0 : _a.subscribe(subscriber)) !== null && _b !== void 0 ? _b : EMPTY_SUBSCRIPTION;
     };
     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) {
+var BehaviorSubject = (function (_super) {
     __extends(BehaviorSubject, _super);
     function BehaviorSubject(_value) {
         var _this = _super.call(this) || this;
@@ -972,47 +923,117 @@ var BehaviorSubject = /*@__PURE__*/ (function (_super) {
         get: function () {
             return this.getValue();
         },
-        enumerable: true,
+        enumerable: false,
         configurable: true
     });
     BehaviorSubject.prototype._subscribe = function (subscriber) {
         var subscription = _super.prototype._subscribe.call(this, subscriber);
-        if (subscription && !subscription.closed) {
-            subscriber.next(this._value);
-        }
+        !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;
+        var _a = this, hasError = _a.hasError, thrownError = _a.thrownError, _value = _a._value;
+        if (hasError) {
+            throw thrownError;
         }
+        this._throwIfClosed();
+        return _value;
     };
     BehaviorSubject.prototype.next = function (value) {
-        _super.prototype.next.call(this, this._value = 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;
+var dateTimestampProvider = {
+    now: function () {
+        return (dateTimestampProvider.delegate || Date).now();
+    },
+    delegate: undefined,
+};
+
+var ReplaySubject = (function (_super) {
+    __extends(ReplaySubject, _super);
+    function ReplaySubject(_bufferSize, _windowTime, _timestampProvider) {
+        if (_bufferSize === void 0) { _bufferSize = Infinity; }
+        if (_windowTime === void 0) { _windowTime = Infinity; }
+        if (_timestampProvider === void 0) { _timestampProvider = dateTimestampProvider; }
+        var _this = _super.call(this) || this;
+        _this._bufferSize = _bufferSize;
+        _this._windowTime = _windowTime;
+        _this._timestampProvider = _timestampProvider;
+        _this._buffer = [];
+        _this._infiniteTimeWindow = true;
+        _this._infiniteTimeWindow = _windowTime === Infinity;
+        _this._bufferSize = Math.max(1, _bufferSize);
+        _this._windowTime = Math.max(1, _windowTime);
+        return _this;
     }
-    Action.prototype.schedule = function (state, delay) {
-        return this;
+    ReplaySubject.prototype.next = function (value) {
+        var _a = this, isStopped = _a.isStopped, _buffer = _a._buffer, _infiniteTimeWindow = _a._infiniteTimeWindow, _timestampProvider = _a._timestampProvider, _windowTime = _a._windowTime;
+        if (!isStopped) {
+            _buffer.push(value);
+            !_infiniteTimeWindow && _buffer.push(_timestampProvider.now() + _windowTime);
+        }
+        this._trimBuffer();
+        _super.prototype.next.call(this, value);
     };
-    return Action;
+    ReplaySubject.prototype._subscribe = function (subscriber) {
+        this._throwIfClosed();
+        this._trimBuffer();
+        var subscription = this._innerSubscribe(subscriber);
+        var _a = this, _infiniteTimeWindow = _a._infiniteTimeWindow, _buffer = _a._buffer;
+        var copy = _buffer.slice();
+        for (var i = 0; i < copy.length && !subscriber.closed; i += _infiniteTimeWindow ? 1 : 2) {
+            subscriber.next(copy[i]);
+        }
+        this._checkFinalizedStatuses(subscriber);
+        return subscription;
+    };
+    ReplaySubject.prototype._trimBuffer = function () {
+        var _a = this, _bufferSize = _a._bufferSize, _timestampProvider = _a._timestampProvider, _buffer = _a._buffer, _infiniteTimeWindow = _a._infiniteTimeWindow;
+        var adjustedBufferSize = (_infiniteTimeWindow ? 1 : 2) * _bufferSize;
+        _bufferSize < Infinity && adjustedBufferSize < _buffer.length && _buffer.splice(0, _buffer.length - adjustedBufferSize);
+        if (!_infiniteTimeWindow) {
+            var now = _timestampProvider.now();
+            var last = 0;
+            for (var i = 1; i < _buffer.length && _buffer[i] <= now; i += 2) {
+                last = i;
+            }
+            last && _buffer.splice(0, last + 1);
+        }
+    };
+    return ReplaySubject;
+}(Subject));
+
+var Action = (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) {
+var intervalProvider = {
+    setInterval: function () {
+        var args = [];
+        for (var _i = 0; _i < arguments.length; _i++) {
+            args[_i] = arguments[_i];
+        }
+        var delegate = intervalProvider.delegate;
+        return ((delegate === null || delegate === void 0 ? void 0 : delegate.setInterval) || setInterval).apply(void 0, __spreadArray([], __read(args)));
+    },
+    clearInterval: function (handle) {
+        var delegate = intervalProvider.delegate;
+        return ((delegate === null || delegate === void 0 ? void 0 : delegate.clearInterval) || clearInterval)(handle);
+    },
+    delegate: undefined,
+};
+
+var AsyncAction = (function (_super) {
     __extends(AsyncAction, _super);
     function AsyncAction(scheduler, work) {
         var _this = _super.call(this, scheduler, work) || this;
@@ -1022,9 +1043,7 @@ var AsyncAction = /*@__PURE__*/ (function (_super) {
         return _this;
     }
     AsyncAction.prototype.schedule = function (state, delay) {
-        if (delay === void 0) {
-            delay = 0;
-        }
+        if (delay === void 0) { delay = 0; }
         if (this.closed) {
             return this;
         }
@@ -1039,20 +1058,16 @@ var AsyncAction = /*@__PURE__*/ (function (_super) {
         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.requestAsyncId = function (scheduler, _id, delay) {
+        if (delay === void 0) { delay = 0; }
+        return intervalProvider.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) {
+    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);
+        intervalProvider.clearInterval(id);
         return undefined;
     };
     AsyncAction.prototype.execute = function (state, delay) {
@@ -1068,144 +1083,78 @@ var AsyncAction = /*@__PURE__*/ (function (_super) {
             this.id = this.recycleAsyncId(this.scheduler, this.id, null);
         }
     };
-    AsyncAction.prototype._execute = function (state, delay) {
+    AsyncAction.prototype._execute = function (state, _delay) {
         var errored = false;
-        var errorValue = undefined;
+        var errorValue;
         try {
             this.work(state);
         }
         catch (e) {
             errored = true;
-            errorValue = !!e && e || new Error(e);
+            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);
+    AsyncAction.prototype.unsubscribe = function () {
+        if (!this.closed) {
+            var _a = this, id = _a.id, scheduler = _a.scheduler;
+            var actions = scheduler.actions;
+            this.work = this.state = this.scheduler = null;
+            this.pending = false;
+            arrRemove(actions, this);
+            if (id != null) {
+                this.id = this.recycleAsyncId(scheduler, id, null);
+            }
+            this.delay = null;
+            _super.prototype.unsubscribe.call(this);
         }
-        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;
+var Scheduler = (function () {
+    function Scheduler(schedulerActionCtor, now) {
+        if (now === void 0) { now = Scheduler.now; }
+        this.schedulerActionCtor = schedulerActionCtor;
         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);
+        if (delay === void 0) { delay = 0; }
+        return new this.schedulerActionCtor(this, work).schedule(state, delay);
     };
-    Scheduler.now = function () { return Date.now(); };
+    Scheduler.now = dateTimestampProvider.now;
     return Scheduler;
 }());
 
-/** PURE_IMPORTS_START tslib,_Scheduler PURE_IMPORTS_END */
-var AsyncScheduler = /*@__PURE__*/ (function (_super) {
+var AsyncScheduler = (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;
+        if (now === void 0) { now = Scheduler.now; }
+        var _this = _super.call(this, SchedulerAction, now) || this;
         _this.actions = [];
-        _this.active = false;
-        _this.scheduled = undefined;
+        _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) {
+        if (this._active) {
             actions.push(action);
             return;
         }
         var error;
-        this.active = true;
+        this._active = true;
         do {
-            if (error = action.execute(action.state, action.delay)) {
+            if ((error = action.execute(action.state, action.delay))) {
                 break;
             }
-        } while (action = actions.shift());
-        this.active = false;
+        } while ((action = actions.shift()));
+        this._active = false;
         if (error) {
-            while (action = actions.shift()) {
+            while ((action = actions.shift())) {
                 action.unsubscribe();
             }
             throw error;
@@ -1214,21 +1163,10 @@ var AsyncScheduler = /*@__PURE__*/ (function (_super) {
     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;
+var asyncScheduler = new AsyncScheduler(AsyncAction);
+var async = asyncScheduler;
 
-/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */
-var EMPTY$1 = /*@__PURE__*/ new Observable(function (subscriber) { return subscriber.complete(); });
+var EMPTY$1 = new Observable(function (subscriber) { return subscriber.complete(); });
 function empty(scheduler) {
     return scheduler ? emptyScheduled(scheduler) : EMPTY$1;
 }
@@ -1236,639 +1174,29 @@ 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 () {
+        return scheduler.schedule(function () {
             if (i === input.length) {
                 subscriber.complete();
-                return;
             }
-            subscriber.next(input[i++]);
-            if (!subscriber.closed) {
-                sub.add(this.schedule());
+            else {
+                subscriber.next(input[i++]);
+                if (!subscriber.closed) {
+                    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));
+    return isFunction(value === null || value === void 0 ? void 0 : value.then);
 }
-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();
@@ -1884,54 +1212,50 @@ function scheduleObservable(input, scheduler) {
     });
 }
 
-/** 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 scheduler.schedule(function () {
             return input.then(function (value) {
-                sub.add(scheduler.schedule(function () {
+                subscriber.add(scheduler.schedule(function () {
                     subscriber.next(value);
-                    sub.add(scheduler.schedule(function () { return subscriber.complete(); }));
+                    subscriber.add(scheduler.schedule(function () { return subscriber.complete(); }));
                 }));
             }, function (err) {
-                sub.add(scheduler.schedule(function () { return subscriber.error(err); }));
+                subscriber.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');
+function getSymbolIterator() {
+    if (typeof Symbol !== 'function' || !Symbol.iterator) {
+        return '@@iterator';
     }
+    return Symbol.iterator;
+}
+var iterator = getSymbolIterator();
+
+function caughtSchedule(subscriber, scheduler, execute, delay) {
+    if (delay === void 0) { delay = 0; }
+    var subscription = scheduler.schedule(function () {
+        try {
+            execute.call(this);
+        }
+        catch (err) {
+            subscriber.error(err);
+        }
+    }, delay);
+    subscriber.add(subscription);
+    return subscription;
+}
+
+function scheduleIterable(input, scheduler) {
     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 () {
+        subscriber.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;
-                }
+            caughtSchedule(subscriber, scheduler, function () {
+                var _a = iterator$1.next(), value = _a.value, done = _a.done;
                 if (done) {
                     subscriber.complete();
                 }
@@ -1939,2084 +1263,1402 @@ function scheduleIterable(input, scheduler) {
                     subscriber.next(value);
                     this.schedule();
                 }
+            });
+        }));
+        return function () { return isFunction(iterator$1 === null || iterator$1 === void 0 ? void 0 : iterator$1.return) && iterator$1.return(); };
+    });
+}
+
+function scheduleAsyncIterable(input, scheduler) {
+    if (!input) {
+        throw new Error('Iterable cannot be null');
+    }
+    return new Observable(function (subscriber) {
+        var sub = new Subscription();
+        sub.add(scheduler.schedule(function () {
+            var iterator = input[Symbol.asyncIterator]();
+            sub.add(scheduler.schedule(function () {
+                var _this = this;
+                iterator.next().then(function (result) {
+                    if (result.done) {
+                        subscriber.complete();
+                    }
+                    else {
+                        subscriber.next(result.value);
+                        _this.schedule();
+                    }
+                });
             }));
         }));
         return sub;
     });
 }
 
-/** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */
 function isInteropObservable(input) {
-    return input && typeof input[observable] === 'function';
+    return isFunction(input[observable]);
 }
 
-/** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */
 function isIterable(input) {
-    return input && typeof input[iterator] === 'function';
+    return isFunction(input === null || input === void 0 ? void 0 : input[iterator]);
+}
+
+function isAsyncIterable(obj) {
+    return Symbol.asyncIterator && isFunction(obj === null || obj === void 0 ? void 0 : obj[Symbol.asyncIterator]);
+}
+
+function createInvalidObservableTypeError(input) {
+    return new TypeError("You provided " + (input !== null && typeof input === 'object' ? 'an invalid object' : "'" + input + "'") + " where a stream was expected. You can provide an Observable, Promise, ReadableStream, Array, AsyncIterable, or Iterable.");
+}
+
+function readableStreamLikeToAsyncGenerator(readableStream) {
+    return __asyncGenerator(this, arguments, function readableStreamLikeToAsyncGenerator_1() {
+        var reader, _a, value, done;
+        return __generator(this, function (_b) {
+            switch (_b.label) {
+                case 0:
+                    reader = readableStream.getReader();
+                    _b.label = 1;
+                case 1:
+                    _b.trys.push([1, , 9, 10]);
+                    _b.label = 2;
+                case 2:
+                    return [4, __await(reader.read())];
+                case 3:
+                    _a = _b.sent(), value = _a.value, done = _a.done;
+                    if (!done) return [3, 5];
+                    return [4, __await(void 0)];
+                case 4: return [2, _b.sent()];
+                case 5: return [4, __await(value)];
+                case 6: return [4, _b.sent()];
+                case 7:
+                    _b.sent();
+                    return [3, 2];
+                case 8: return [3, 10];
+                case 9:
+                    reader.releaseLock();
+                    return [7];
+                case 10: return [2];
+            }
+        });
+    });
+}
+function isReadableStreamLike(obj) {
+    return isFunction(obj === null || obj === void 0 ? void 0 : obj.getReader);
+}
+
+function scheduleReadableStreamLike(input, scheduler) {
+    return scheduleAsyncIterable(readableStreamLikeToAsyncGenerator(input), scheduler);
 }
 
-/** 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)) {
+        if (isArrayLike(input)) {
+            return scheduleArray(input, scheduler);
+        }
+        if (isPromise(input)) {
             return schedulePromise(input, scheduler);
         }
-        else if (isArrayLike(input)) {
-            return scheduleArray(input, scheduler);
+        if (isAsyncIterable(input)) {
+            return scheduleAsyncIterable(input, scheduler);
         }
-        else if (isIterable(input) || typeof input === 'string') {
+        if (isIterable(input)) {
             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;
+        if (isReadableStreamLike(input)) {
+            return scheduleReadableStreamLike(input, scheduler);
         }
-        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);
+    throw createInvalidObservableTypeError(input);
 }
 
-/** 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)); };
+function from(input, scheduler) {
+    return scheduler ? scheduled(input, scheduler) : innerFrom(input);
 }
-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;
+function innerFrom(input) {
+    if (input instanceof Observable) {
+        return input;
     }
-    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);
+    if (input != null) {
+        if (isInteropObservable(input)) {
+            return fromInteropObservable(input);
         }
-        catch (err) {
-            this.destination.error(err);
-            return;
+        if (isArrayLike(input)) {
+            return fromArrayLike(input);
         }
-        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);
+        if (isPromise(input)) {
+            return fromPromise(input);
         }
-    };
-    MergeMapSubscriber.prototype._complete = function () {
-        this.hasCompleted = true;
-        if (this.active === 0 && this.buffer.length === 0) {
-            this.destination.complete();
+        if (isAsyncIterable(input)) {
+            return fromAsyncIterable(input);
         }
-        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());
+        if (isIterable(input)) {
+            return fromIterable(input);
         }
-        else if (this.active === 0 && this.hasCompleted) {
-            this.destination.complete();
+        if (isReadableStreamLike(input)) {
+            return fromReadableStreamLike(input);
         }
-    };
-    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);
+    throw createInvalidObservableTypeError(input);
 }
-
-/** PURE_IMPORTS_START _mergeAll PURE_IMPORTS_END */
-function concatAll() {
-    return mergeAll(1);
+function fromInteropObservable(obj) {
+    return new Observable(function (subscriber) {
+        var obs = obj[observable]();
+        if (isFunction(obs.subscribe)) {
+            return obs.subscribe(subscriber);
+        }
+        throw new TypeError('Provided object does not correctly implement Symbol.observable');
+    });
 }
-
-/** 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));
+function fromArrayLike(array) {
+    return new Observable(function (subscriber) {
+        for (var i = 0; i < array.length && !subscriber.closed; i++) {
+            subscriber.next(array[i]);
+        }
+        subscriber.complete();
+    });
 }
-
-/** 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); }));
-    }
+function fromPromise(promise) {
     return new Observable(function (subscriber) {
-        function handler(e) {
-            if (arguments.length > 1) {
-                subscriber.next(Array.prototype.slice.call(arguments));
-            }
-            else {
-                subscriber.next(e);
+        promise
+            .then(function (value) {
+            if (!subscriber.closed) {
+                subscriber.next(value);
+                subscriber.complete();
             }
-        }
-        setupSubscription(target, eventName, handler, subscriber, options);
+        }, function (err) { return subscriber.error(err); })
+            .then(null, reportUnhandledError);
     });
 }
-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);
+function fromIterable(iterable) {
+    return new Observable(function (subscriber) {
+        var e_1, _a;
+        try {
+            for (var iterable_1 = __values(iterable), iterable_1_1 = iterable_1.next(); !iterable_1_1.done; iterable_1_1 = iterable_1.next()) {
+                var value = iterable_1_1.value;
+                subscriber.next(value);
+                if (subscriber.closed) {
+                    return;
+                }
+            }
         }
-    }
-    else {
-        throw new TypeError('Invalid event target');
-    }
-    subscriber.add(unsubscribe);
-}
-function isNodeStyleEventEmitter(sourceObj) {
-    return sourceObj && typeof sourceObj.addListener === 'function' && typeof sourceObj.removeListener === 'function';
+        catch (e_1_1) { e_1 = { error: e_1_1 }; }
+        finally {
+            try {
+                if (iterable_1_1 && !iterable_1_1.done && (_a = iterable_1.return)) _a.call(iterable_1);
+            }
+            finally { if (e_1) throw e_1.error; }
+        }
+        subscriber.complete();
+    });
 }
-function isJQueryStyleEventEmitter(sourceObj) {
-    return sourceObj && typeof sourceObj.on === 'function' && typeof sourceObj.off === 'function';
+function fromAsyncIterable(asyncIterable) {
+    return new Observable(function (subscriber) {
+        process(asyncIterable, subscriber).catch(function (err) { return subscriber.error(err); });
+    });
 }
-function isEventTarget(sourceObj) {
-    return sourceObj && typeof sourceObj.addEventListener === 'function' && typeof sourceObj.removeEventListener === 'function';
+function fromReadableStreamLike(readableStream) {
+    return fromAsyncIterable(readableStreamLikeToAsyncGenerator(readableStream));
+}
+function process(asyncIterable, subscriber) {
+    var asyncIterable_1, asyncIterable_1_1;
+    var e_2, _a;
+    return __awaiter(this, void 0, void 0, function () {
+        var value, e_2_1;
+        return __generator(this, function (_b) {
+            switch (_b.label) {
+                case 0:
+                    _b.trys.push([0, 5, 6, 11]);
+                    asyncIterable_1 = __asyncValues(asyncIterable);
+                    _b.label = 1;
+                case 1: return [4, asyncIterable_1.next()];
+                case 2:
+                    if (!(asyncIterable_1_1 = _b.sent(), !asyncIterable_1_1.done)) return [3, 4];
+                    value = asyncIterable_1_1.value;
+                    subscriber.next(value);
+                    if (subscriber.closed) {
+                        return [2];
+                    }
+                    _b.label = 3;
+                case 3: return [3, 1];
+                case 4: return [3, 11];
+                case 5:
+                    e_2_1 = _b.sent();
+                    e_2 = { error: e_2_1 };
+                    return [3, 11];
+                case 6:
+                    _b.trys.push([6, , 9, 10]);
+                    if (!(asyncIterable_1_1 && !asyncIterable_1_1.done && (_a = asyncIterable_1.return))) return [3, 8];
+                    return [4, _a.call(asyncIterable_1)];
+                case 7:
+                    _b.sent();
+                    _b.label = 8;
+                case 8: return [3, 10];
+                case 9:
+                    if (e_2) throw e_2.error;
+                    return [7];
+                case 10: return [7];
+                case 11:
+                    subscriber.complete();
+                    return [2];
+            }
+        });
+    });
 }
 
-/** PURE_IMPORTS_START _isArray PURE_IMPORTS_END */
-function isNumeric(val) {
-    return !isArray$1(val) && (val - parseFloat(val) + 1) >= 0;
+function internalFromArray(input, scheduler) {
+    return scheduler ? scheduleArray(input, scheduler) : fromArrayLike(input);
 }
 
-/** PURE_IMPORTS_START _Observable,_util_isScheduler,_operators_mergeAll,_fromArray PURE_IMPORTS_END */
-function merge() {
-    var observables = [];
+function isScheduler(value) {
+    return value && isFunction(value.schedule);
+}
+
+function last$1(arr) {
+    return arr[arr.length - 1];
+}
+function popResultSelector(args) {
+    return isFunction(last$1(args)) ? args.pop() : undefined;
+}
+function popScheduler(args) {
+    return isScheduler(last$1(args)) ? args.pop() : undefined;
+}
+function popNumber(args, defaultValue) {
+    return typeof last$1(args) === 'number' ? args.pop() : defaultValue;
+}
+
+function of() {
+    var args = [];
     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];
+        args[_i] = arguments[_i];
     }
-    return mergeAll(concurrent)(fromArray(observables, scheduler));
+    var scheduler = popScheduler(args);
+    return scheduler ? scheduleArray(args, scheduler) : internalFromArray(args);
 }
 
-/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
-function filter(predicate, thisArg) {
-    return function filterOperatorFunction(source) {
-        return source.lift(new FilterOperator(predicate, thisArg));
-    };
+function throwError(errorOrErrorFactory, scheduler) {
+    var errorFactory = isFunction(errorOrErrorFactory) ? errorOrErrorFactory : function () { return errorOrErrorFactory; };
+    var init = function (subscriber) { return subscriber.error(errorFactory()); };
+    return new Observable(scheduler ? function (subscriber) { return scheduler.schedule(init, 0, subscriber); } : init);
 }
-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);
-        }
+
+var EmptyError = createErrorClass(function (_super) { return function EmptyErrorImpl() {
+    _super(this);
+    this.name = 'EmptyError';
+    this.message = 'no elements in sequence';
+}; });
+
+function isValidDate(value) {
+    return value instanceof Date && !isNaN(value);
+}
+
+var TimeoutError = createErrorClass(function (_super) {
+    return function TimeoutErrorImpl(info) {
+        if (info === void 0) { info = null; }
+        _super(this);
+        this.message = 'Timeout has occurred';
+        this.name = 'TimeoutError';
+        this.info = info;
     };
-    return FilterSubscriber;
-}(Subscriber));
+});
+function timeout(config, schedulerArg) {
+    var _a = (isValidDate(config)
+        ? { first: config }
+        : typeof config === 'number'
+            ? { each: config }
+            : config), first = _a.first, each = _a.each, _b = _a.with, _with = _b === void 0 ? timeoutErrorFactory : _b, _c = _a.scheduler, scheduler = _c === void 0 ? schedulerArg !== null && schedulerArg !== void 0 ? schedulerArg : asyncScheduler : _c, _d = _a.meta, meta = _d === void 0 ? null : _d;
+    if (first == null && each == null) {
+        throw new TypeError('No timeout provided.');
+    }
+    return operate(function (source, subscriber) {
+        var originalSourceSubscription;
+        var timerSubscription;
+        var lastValue = null;
+        var seen = 0;
+        var startTimer = function (delay) {
+            timerSubscription = caughtSchedule(subscriber, scheduler, function () {
+                originalSourceSubscription.unsubscribe();
+                innerFrom(_with({
+                    meta: meta,
+                    lastValue: lastValue,
+                    seen: seen,
+                })).subscribe(subscriber);
+            }, delay);
+        };
+        originalSourceSubscription = source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            timerSubscription === null || timerSubscription === void 0 ? void 0 : timerSubscription.unsubscribe();
+            seen++;
+            subscriber.next((lastValue = value));
+            each > 0 && startTimer(each);
+        }, undefined, undefined, function () {
+            if (!(timerSubscription === null || timerSubscription === void 0 ? void 0 : timerSubscription.closed)) {
+                timerSubscription === null || timerSubscription === void 0 ? void 0 : timerSubscription.unsubscribe();
+            }
+            lastValue = null;
+        }));
+        startTimer(first != null ? (typeof first === 'number' ? first : +first - scheduler.now()) : each);
+    });
+}
+function timeoutErrorFactory(info) {
+    throw new TimeoutError(info);
+}
 
-/** 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 map(project, thisArg) {
+    return operate(function (source, subscriber) {
+        var index = 0;
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            subscriber.next(project.call(thisArg, value, index++));
+        }));
     });
 }
-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();
+
+var isArray$6 = Array.isArray;
+function callOrApply(fn, args) {
+    return isArray$6(args) ? fn.apply(void 0, __spreadArray([], __read(args))) : fn(args);
+}
+function mapOneOrManyArgs(fn) {
+    return map(function (args) { return callOrApply(fn, args); });
+}
+
+var isArray$5 = Array.isArray;
+var getPrototypeOf = Object.getPrototypeOf, objectProto = Object.prototype, getKeys = Object.keys;
+function argsArgArrayOrObject(args) {
+    if (args.length === 1) {
+        var first_1 = args[0];
+        if (isArray$5(first_1)) {
+            return { args: first_1, keys: null };
+        }
+        if (isPOJO(first_1)) {
+            var keys = getKeys(first_1);
+            return {
+                args: keys.map(function (key) { return first_1[key]; }),
+                keys: keys,
+            };
+        }
     }
-    state.index = index + 1;
-    this.schedule(state, period);
+    return { args: args, keys: null };
+}
+function isPOJO(obj) {
+    return obj && typeof obj === 'object' && getPrototypeOf(obj) === objectProto;
 }
 
-/** PURE_IMPORTS_START tslib,_fromArray,_util_isArray,_Subscriber,_.._internal_symbol_iterator,_innerSubscribe PURE_IMPORTS_END */
-function zip() {
-    var observables = [];
+function createObject(keys, values) {
+    return keys.reduce(function (result, key, i) { return ((result[key] = values[i]), result); }, {});
+}
+
+function combineLatest() {
+    var args = [];
     for (var _i = 0; _i < arguments.length; _i++) {
-        observables[_i] = arguments[_i];
-    }
-    var resultSelector = observables[observables.length - 1];
-    if (typeof resultSelector === 'function') {
-        observables.pop();
+        args[_i] = arguments[_i];
     }
-    return fromArray(observables, undefined).lift(new ZipOperator(resultSelector));
+    var scheduler = popScheduler(args);
+    var resultSelector = popResultSelector(args);
+    var _a = argsArgArrayOrObject(args), observables = _a.args, keys = _a.keys;
+    if (observables.length === 0) {
+        return from([], scheduler);
+    }
+    var result = new Observable(combineLatestInit(observables, scheduler, keys
+        ?
+            function (values) { return createObject(keys, values); }
+        :
+            identity));
+    return resultSelector ? result.pipe(mapOneOrManyArgs(resultSelector)) : result;
+}
+function combineLatestInit(observables, scheduler, valueTransform) {
+    if (valueTransform === void 0) { valueTransform = identity; }
+    return function (subscriber) {
+        maybeSchedule(scheduler, function () {
+            var length = observables.length;
+            var values = new Array(length);
+            var active = length;
+            var remainingFirstValues = length;
+            var _loop_1 = function (i) {
+                maybeSchedule(scheduler, function () {
+                    var source = from(observables[i], scheduler);
+                    var hasFirstValue = false;
+                    source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+                        values[i] = value;
+                        if (!hasFirstValue) {
+                            hasFirstValue = true;
+                            remainingFirstValues--;
+                        }
+                        if (!remainingFirstValues) {
+                            subscriber.next(valueTransform(values.slice()));
+                        }
+                    }, function () {
+                        if (!--active) {
+                            subscriber.complete();
+                        }
+                    }));
+                }, subscriber);
+            };
+            for (var i = 0; i < length; i++) {
+                _loop_1(i);
+            }
+        }, subscriber);
+    };
 }
-var ZipOperator = /*@__PURE__*/ (function () {
-    function ZipOperator(resultSelector) {
-        this.resultSelector = resultSelector;
+function maybeSchedule(scheduler, execute, subscription) {
+    if (scheduler) {
+        subscription.add(scheduler.schedule(execute));
     }
-    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;
+    else {
+        execute();
     }
-    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));
+}
+
+function mergeInternals(source, subscriber, project, concurrent, onBeforeNext, expand, innerSubScheduler, additionalTeardown) {
+    var buffer = [];
+    var active = 0;
+    var index = 0;
+    var isComplete = false;
+    var checkComplete = function () {
+        if (isComplete && !buffer.length && !active) {
+            subscriber.complete();
         }
     };
-    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());
+    var outerNext = function (value) { return (active < concurrent ? doInnerSub(value) : buffer.push(value)); };
+    var doInnerSub = function (value) {
+        expand && subscriber.next(value);
+        active++;
+        var innerComplete = false;
+        innerFrom(project(value, index++)).subscribe(new OperatorSubscriber(subscriber, function (innerValue) {
+            onBeforeNext === null || onBeforeNext === void 0 ? void 0 : onBeforeNext(innerValue);
+            if (expand) {
+                outerNext(innerValue);
             }
             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;
+                subscriber.next(innerValue);
             }
-            if (result.done) {
-                destination.complete();
-                return;
+        }, function () {
+            innerComplete = true;
+        }, undefined, function () {
+            if (innerComplete) {
+                try {
+                    active--;
+                    var _loop_1 = function () {
+                        var bufferedValue = buffer.shift();
+                        innerSubScheduler ? subscriber.add(innerSubScheduler.schedule(function () { return doInnerSub(bufferedValue); })) : doInnerSub(bufferedValue);
+                    };
+                    while (buffer.length && active < concurrent) {
+                        _loop_1();
+                    }
+                    checkComplete();
+                }
+                catch (err) {
+                    subscriber.error(err);
+                }
             }
-            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);
+    source.subscribe(new OperatorSubscriber(subscriber, outerNext, function () {
+        isComplete = true;
+        checkComplete();
+    }));
+    return function () {
+        additionalTeardown === null || additionalTeardown === void 0 ? void 0 : additionalTeardown();
     };
-    return ZipSubscriber;
-}(Subscriber));
-var StaticIterator = /*@__PURE__*/ (function () {
-    function StaticIterator(iterator) {
-        this.iterator = iterator;
-        this.nextResult = iterator.next();
+}
+
+function mergeMap(project, resultSelector, concurrent) {
+    if (concurrent === void 0) { concurrent = Infinity; }
+    if (isFunction(resultSelector)) {
+        return mergeMap(function (a, i) { return map(function (b, ii) { return resultSelector(a, b, i, ii); })(innerFrom(project(a, i))); }, concurrent);
     }
-    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;
+    else if (typeof resultSelector === 'number') {
+        concurrent = resultSelector;
     }
-    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 };
+    return operate(function (source, subscriber) { return mergeInternals(source, subscriber, project, concurrent); });
+}
+
+function mergeAll(concurrent) {
+    if (concurrent === void 0) { concurrent = Infinity; }
+    return mergeMap(identity, concurrent);
+}
+
+function concatAll() {
+    return mergeAll(1);
+}
+
+function concat() {
+    var args = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        args[_i] = arguments[_i];
+    }
+    return concatAll()(internalFromArray(args, popScheduler(args)));
+}
+
+var nodeEventEmitterMethods = ['addListener', 'removeListener'];
+var eventTargetMethods = ['addEventListener', 'removeEventListener'];
+var jqueryMethods = ['on', 'off'];
+function fromEvent(target, eventName, options, resultSelector) {
+    if (isFunction(options)) {
+        resultSelector = options;
+        options = undefined;
+    }
+    if (resultSelector) {
+        return fromEvent(target, eventName, options).pipe(mapOneOrManyArgs(resultSelector));
+    }
+    var _a = __read(isEventTarget(target)
+        ? eventTargetMethods.map(function (methodName) { return function (handler) { return target[methodName](eventName, handler, options); }; })
+        :
+            isNodeStyleEventEmitter(target)
+                ? nodeEventEmitterMethods.map(toCommonHandlerRegistry(target, eventName))
+                : isJQueryStyleEventEmitter(target)
+                    ? jqueryMethods.map(toCommonHandlerRegistry(target, eventName))
+                    : [], 2), add = _a[0], remove = _a[1];
+    if (!add) {
+        if (isArrayLike(target)) {
+            return mergeMap(function (subTarget) { return fromEvent(subTarget, eventName, options); })(internalFromArray(target));
+        }
+    }
+    if (!add) {
+        throw new TypeError('Invalid event target');
+    }
+    return new Observable(function (subscriber) {
+        var handler = function () {
+            var args = [];
+            for (var _i = 0; _i < arguments.length; _i++) {
+                args[_i] = arguments[_i];
+            }
+            return subscriber.next(1 < args.length ? args : args[0]);
+        };
+        add(handler);
+        return function () { return remove(handler); };
+    });
+}
+function toCommonHandlerRegistry(target, eventName) {
+    return function (methodName) { return function (handler) { return target[methodName](eventName, handler); }; };
+}
+function isNodeStyleEventEmitter(target) {
+    return isFunction(target.addListener) && isFunction(target.removeListener);
+}
+function isJQueryStyleEventEmitter(target) {
+    return isFunction(target.on) && isFunction(target.off);
+}
+function isEventTarget(target) {
+    return isFunction(target.addEventListener) && isFunction(target.removeEventListener);
+}
+
+function timer(dueTime, intervalOrScheduler, scheduler) {
+    if (dueTime === void 0) { dueTime = 0; }
+    if (scheduler === void 0) { scheduler = async; }
+    var intervalDuration = -1;
+    if (intervalOrScheduler != null) {
+        if (isScheduler(intervalOrScheduler)) {
+            scheduler = intervalOrScheduler;
         }
         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();
+            intervalDuration = intervalOrScheduler;
         }
-        else {
-            this.destination.complete();
+    }
+    return new Observable(function (subscriber) {
+        var due = isValidDate(dueTime) ? +dueTime - scheduler.now() : dueTime;
+        if (due < 0) {
+            due = 0;
         }
-    };
-    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 n = 0;
+        return scheduler.schedule(function () {
+            if (!subscriber.closed) {
+                subscriber.next(n++);
+                if (0 <= intervalDuration) {
+                    this.schedule(undefined, intervalDuration);
+                }
+                else {
+                    subscriber.complete();
+                }
+            }
+        }, due);
+    });
 }
-var AuditOperator = /*@__PURE__*/ (function () {
-    function AuditOperator(durationSelector) {
-        this.durationSelector = durationSelector;
+
+function merge() {
+    var args = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        args[_i] = arguments[_i];
     }
-    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;
+    var scheduler = popScheduler(args);
+    var concurrent = popNumber(args, Infinity);
+    var sources = args;
+    return !sources.length
+        ?
+            EMPTY$1
+        : sources.length === 1
+            ?
+                innerFrom(sources[0])
+            :
+                mergeAll(concurrent)(internalFromArray(sources, scheduler));
+}
+
+var isArray$4 = Array.isArray;
+function argsOrArgArray(args) {
+    return args.length === 1 && isArray$4(args[0]) ? args[0] : args;
+}
+
+function filter(predicate, thisArg) {
+    return operate(function (source, subscriber) {
+        var index = 0;
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) { return predicate.call(thisArg, value, index++) && subscriber.next(value); }));
+    });
+}
+
+function zip() {
+    var args = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        args[_i] = arguments[_i];
     }
-    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();
+    var resultSelector = popResultSelector(args);
+    var sources = argsOrArgArray(args);
+    return sources.length
+        ? new Observable(function (subscriber) {
+            var buffers = sources.map(function () { return []; });
+            var completed = sources.map(function () { return false; });
+            subscriber.add(function () {
+                buffers = completed = null;
+            });
+            var _loop_1 = function (sourceIndex) {
+                innerFrom(sources[sourceIndex]).subscribe(new OperatorSubscriber(subscriber, function (value) {
+                    buffers[sourceIndex].push(value);
+                    if (buffers.every(function (buffer) { return buffer.length; })) {
+                        var result = buffers.map(function (buffer) { return buffer.shift(); });
+                        subscriber.next(resultSelector ? resultSelector.apply(void 0, __spreadArray([], __read(result))) : result);
+                        if (buffers.some(function (buffer, i) { return !buffer.length && completed[i]; })) {
+                            subscriber.complete();
+                        }
+                    }
+                }, function () {
+                    completed[sourceIndex] = true;
+                    !buffers[sourceIndex].length && subscriber.complete();
+                }));
+            };
+            for (var sourceIndex = 0; !subscriber.closed && sourceIndex < sources.length; sourceIndex++) {
+                _loop_1(sourceIndex);
             }
-            else {
-                this.add(this.throttled = innerSubscription);
+            return function () {
+                buffers = completed = null;
+            };
+        })
+        : EMPTY$1;
+}
+
+function audit(durationSelector) {
+    return operate(function (source, subscriber) {
+        var hasValue = false;
+        var lastValue = null;
+        var durationSubscriber = null;
+        var isComplete = false;
+        var endDuration = function () {
+            durationSubscriber === null || durationSubscriber === void 0 ? void 0 : durationSubscriber.unsubscribe();
+            durationSubscriber = null;
+            if (hasValue) {
+                hasValue = false;
+                var value = lastValue;
+                lastValue = null;
+                subscriber.next(value);
             }
-        }
-    };
-    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));
+            isComplete && subscriber.complete();
+        };
+        var cleanupDuration = function () {
+            durationSubscriber = null;
+            isComplete && subscriber.complete();
+        };
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            hasValue = true;
+            lastValue = value;
+            if (!durationSubscriber) {
+                innerFrom(durationSelector(value)).subscribe((durationSubscriber = new OperatorSubscriber(subscriber, endDuration, cleanupDuration)));
+            }
+        }, function () {
+            isComplete = true;
+            (!hasValue || !durationSubscriber || durationSubscriber.closed) && subscriber.complete();
+        }));
+    });
+}
 
-/** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */
 function auditTime(duration, scheduler) {
-    if (scheduler === void 0) {
-        scheduler = async;
-    }
+    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);
+    if (startBufferEvery === void 0) { startBufferEvery = null; }
+    startBufferEvery = startBufferEvery !== null && startBufferEvery !== void 0 ? startBufferEvery : bufferSize;
+    return operate(function (source, subscriber) {
+        var buffers = [];
+        var count = 0;
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            var e_1, _a, e_2, _b;
+            var toEmit = null;
+            if (count++ % startBufferEvery === 0) {
+                buffers.push([]);
             }
-        }
-    };
-    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);
+            try {
+                for (var buffers_1 = __values(buffers), buffers_1_1 = buffers_1.next(); !buffers_1_1.done; buffers_1_1 = buffers_1.next()) {
+                    var buffer = buffers_1_1.value;
+                    buffer.push(value);
+                    if (bufferSize <= buffer.length) {
+                        toEmit = toEmit !== null && toEmit !== void 0 ? toEmit : [];
+                        toEmit.push(buffer);
+                    }
+                }
             }
-        }
-        _super.prototype._complete.call(this);
-    };
-    return BufferSkipCountSubscriber;
-}(Subscriber));
+            catch (e_1_1) { e_1 = { error: e_1_1 }; }
+            finally {
+                try {
+                    if (buffers_1_1 && !buffers_1_1.done && (_a = buffers_1.return)) _a.call(buffers_1);
+                }
+                finally { if (e_1) throw e_1.error; }
+            }
+            if (toEmit) {
+                try {
+                    for (var toEmit_1 = __values(toEmit), toEmit_1_1 = toEmit_1.next(); !toEmit_1_1.done; toEmit_1_1 = toEmit_1.next()) {
+                        var buffer = toEmit_1_1.value;
+                        arrRemove(buffers, buffer);
+                        subscriber.next(buffer);
+                    }
+                }
+                catch (e_2_1) { e_2 = { error: e_2_1 }; }
+                finally {
+                    try {
+                        if (toEmit_1_1 && !toEmit_1_1.done && (_b = toEmit_1.return)) _b.call(toEmit_1);
+                    }
+                    finally { if (e_2) throw e_2.error; }
+                }
+            }
+        }, function () {
+            var e_3, _a;
+            try {
+                for (var buffers_2 = __values(buffers), buffers_2_1 = buffers_2.next(); !buffers_2_1.done; buffers_2_1 = buffers_2.next()) {
+                    var buffer = buffers_2_1.value;
+                    subscriber.next(buffer);
+                }
+            }
+            catch (e_3_1) { e_3 = { error: e_3_1 }; }
+            finally {
+                try {
+                    if (buffers_2_1 && !buffers_2_1.done && (_a = buffers_2.return)) _a.call(buffers_2);
+                }
+                finally { if (e_3) throw e_3.error; }
+            }
+            subscriber.complete();
+        }, undefined, function () {
+            buffers = null;
+        }));
+    });
+}
 
-/** PURE_IMPORTS_START tslib,_Subscription,_innerSubscribe PURE_IMPORTS_END */
 function bufferWhen(closingSelector) {
-    return function (source) {
-        return source.lift(new BufferWhenOperator(closingSelector));
-    };
+    return operate(function (source, subscriber) {
+        var buffer = null;
+        var closingSubscriber = null;
+        var openBuffer = function () {
+            closingSubscriber === null || closingSubscriber === void 0 ? void 0 : closingSubscriber.unsubscribe();
+            var b = buffer;
+            buffer = [];
+            b && subscriber.next(b);
+            innerFrom(closingSelector()).subscribe((closingSubscriber = new OperatorSubscriber(subscriber, openBuffer, noop)));
+        };
+        openBuffer();
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) { return buffer === null || buffer === void 0 ? void 0 : buffer.push(value); }, function () {
+            buffer && subscriber.next(buffer);
+            subscriber.complete();
+        }, undefined, function () { return (buffer = closingSubscriber = null); }));
+    });
 }
-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);
+    return operate(function (source, subscriber) {
+        var innerSub = null;
+        var syncUnsub = false;
+        var handledResult;
+        innerSub = source.subscribe(new OperatorSubscriber(subscriber, undefined, undefined, function (err) {
+            handledResult = innerFrom(selector(err, catchError(selector)(source)));
+            if (innerSub) {
+                innerSub.unsubscribe();
+                innerSub = null;
+                handledResult.subscribe(subscriber);
             }
-            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);
+            else {
+                syncUnsub = true;
             }
+        }));
+        if (syncUnsub) {
+            innerSub.unsubscribe();
+            innerSub = null;
+            handledResult.subscribe(subscriber);
         }
+    });
+}
+
+function scanInternals(accumulator, seed, hasSeed, emitOnNext, emitBeforeComplete) {
+    return function (source, subscriber) {
+        var hasState = hasSeed;
+        var state = seed;
+        var index = 0;
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            var i = index++;
+            state = hasState
+                ?
+                    accumulator(state, value, i)
+                :
+                    ((hasState = true), value);
+            emitOnNext && subscriber.next(state);
+        }, emitBeforeComplete &&
+            (function () {
+                hasState && subscriber.next(state);
+                subscriber.complete();
+            })));
     };
-    return CatchSubscriber;
-}(SimpleOuterSubscriber));
+}
+
+function reduce(accumulator, seed) {
+    return operate(scanInternals(accumulator, seed, arguments.length >= 2, false, true));
+}
 
-/** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */
 function concatMap(project, resultSelector) {
-    return mergeMap(project, resultSelector, 1);
+    return isFunction(resultSelector) ? mergeMap(project, resultSelector, 1) : mergeMap(project, 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)); };
+function fromSubscribable(subscribable) {
+    return new Observable(function (subscriber) { return subscribable.subscribe(subscriber); });
 }
-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;
+
+var DEFAULT_CONFIG = {
+    connector: function () { return new Subject(); },
+};
+function connect(selector, config) {
+    if (config === void 0) { config = DEFAULT_CONFIG; }
+    var connector = config.connector;
+    return operate(function (source, subscriber) {
+        var subject = connector();
+        from(selector(fromSubscribable(subject))).subscribe(subscriber);
+        subscriber.add(source.subscribe(subject));
+    });
+}
+
+function debounceTime(dueTime, scheduler) {
+    if (scheduler === void 0) { scheduler = asyncScheduler; }
+    return operate(function (source, subscriber) {
+        var activeTask = null;
+        var lastValue = null;
+        var lastTime = null;
+        var emit = function () {
+            if (activeTask) {
+                activeTask.unsubscribe();
+                activeTask = null;
+                var value = lastValue;
+                lastValue = null;
+                subscriber.next(value);
+            }
+        };
+        function emitWhenIdle() {
+            var targetTime = lastTime + dueTime;
+            var now = scheduler.now();
+            if (now < targetTime) {
+                activeTask = this.schedule(undefined, targetTime - now);
+                subscriber.add(activeTask);
+                return;
+            }
+            emit();
         }
-    };
-    return DebounceTimeSubscriber;
-}(Subscriber));
-function dispatchNext(subscriber) {
-    subscriber.debouncedNext();
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            lastValue = value;
+            lastTime = scheduler.now();
+            if (!activeTask) {
+                activeTask = scheduler.schedule(emitWhenIdle, dueTime);
+                subscriber.add(activeTask);
+            }
+        }, function () {
+            emit();
+            subscriber.complete();
+        }, undefined, function () {
+            lastValue = activeTask = null;
+        }));
+    });
 }
 
-/** 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)); };
+    return operate(function (source, subscriber) {
+        var hasValue = false;
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            hasValue = true;
+            subscriber.next(value);
+        }, function () {
+            if (!hasValue) {
+                subscriber.next(defaultValue);
+            }
+            subscriber.complete();
+        }));
+    });
 }
-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);
+function take(count) {
+    return count <= 0
+        ?
+            function () { return EMPTY$1; }
+        : operate(function (source, subscriber) {
+            var seen = 0;
+            source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+                if (++seen <= count) {
+                    subscriber.next(value);
+                    if (count <= seen) {
+                        subscriber.complete();
+                    }
+                }
+            }));
+        });
 }
 
-/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
-function distinctUntilChanged(compare, keySelector) {
-    return function (source) { return source.lift(new DistinctUntilChangedOperator(compare, keySelector)); };
+function distinctUntilChanged(comparator, keySelector) {
+    if (keySelector === void 0) { keySelector = identity; }
+    comparator = comparator !== null && comparator !== void 0 ? comparator : defaultCompare$3;
+    return operate(function (source, subscriber) {
+        var previousKey;
+        var first = true;
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            var currentKey = keySelector(value);
+            if (first || !comparator(previousKey, currentKey)) {
+                first = false;
+                previousKey = currentKey;
+                subscriber.next(value);
+            }
+        }));
+    });
 }
-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;
+function defaultCompare$3(a, b) {
+    return a === b;
+}
+
+function throwIfEmpty(errorFactory) {
+    if (errorFactory === void 0) { errorFactory = defaultErrorFactory; }
+    return operate(function (source, subscriber) {
+        var hasValue = false;
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            hasValue = true;
+            subscriber.next(value);
+        }, function () { return (hasValue ? subscriber.complete() : subscriber.error(errorFactory())); }));
+    });
+}
+function defaultErrorFactory() {
+    return new EmptyError();
+}
+
+function expand(project, concurrent, scheduler) {
+    if (concurrent === void 0) { concurrent = Infinity; }
+    concurrent = (concurrent || 0) < 1 ? Infinity : concurrent;
+    return operate(function (source, subscriber) {
+        return mergeInternals(source, subscriber, project, concurrent, undefined, true, scheduler);
+    });
+}
+
+function finalize(callback) {
+    return operate(function (source, subscriber) {
         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;
+            source.subscribe(subscriber);
         }
-        if (!result) {
-            this.key = key;
-            this.destination.next(value);
+        finally {
+            subscriber.add(callback);
         }
+    });
+}
+
+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(); }));
     };
-    return DistinctUntilChangedSubscriber;
-}(Subscriber));
+}
 
-/** PURE_IMPORTS_START tslib,_util_EmptyError,_Subscriber PURE_IMPORTS_END */
-function throwIfEmpty(errorFactory) {
-    if (errorFactory === void 0) {
-        errorFactory = defaultErrorFactory;
-    }
+function takeLast(count) {
+    return count <= 0
+        ? function () { return EMPTY$1; }
+        : operate(function (source, subscriber) {
+            var buffer = [];
+            source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+                buffer.push(value);
+                count < buffer.length && buffer.shift();
+            }, function () {
+                var e_1, _a;
+                try {
+                    for (var buffer_1 = __values(buffer), buffer_1_1 = buffer_1.next(); !buffer_1_1.done; buffer_1_1 = buffer_1.next()) {
+                        var value = buffer_1_1.value;
+                        subscriber.next(value);
+                    }
+                }
+                catch (e_1_1) { e_1 = { error: e_1_1 }; }
+                finally {
+                    try {
+                        if (buffer_1_1 && !buffer_1_1.done && (_a = buffer_1.return)) _a.call(buffer_1);
+                    }
+                    finally { if (e_1) throw e_1.error; }
+                }
+                subscriber.complete();
+            }, undefined, function () {
+                buffer = null;
+            }));
+        });
+}
+
+function last(predicate, defaultValue) {
+    var hasDefaultValue = arguments.length >= 2;
     return function (source) {
-        return source.lift(new ThrowIfEmptyOperator(errorFactory));
+        return source.pipe(predicate ? filter(function (v, i) { return predicate(v, i, source); }) : identity, takeLast(1), hasDefaultValue ? defaultIfEmpty(defaultValue) : throwIfEmpty(function () { return new EmptyError(); }));
     };
 }
-var ThrowIfEmptyOperator = /*@__PURE__*/ (function () {
-    function ThrowIfEmptyOperator(errorFactory) {
-        this.errorFactory = errorFactory;
+
+function multicast(subjectOrSubjectFactory, selector) {
+    var subjectFactory = isFunction(subjectOrSubjectFactory) ? subjectOrSubjectFactory : function () { return subjectOrSubjectFactory; };
+    if (isFunction(selector)) {
+        return connect(selector, {
+            connector: subjectFactory,
+        });
     }
-    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;
+    return function (source) { return new ConnectableObservable(source, subjectFactory); };
+}
+
+function pairwise() {
+    return operate(function (source, subscriber) {
+        var prev;
+        var hasPrev = false;
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            var p = prev;
+            prev = value;
+            hasPrev && subscriber.next([p, value]);
+            hasPrev = true;
+        }));
+    });
+}
+
+function pluck() {
+    var properties = [];
+    for (var _i = 0; _i < arguments.length; _i++) {
+        properties[_i] = arguments[_i];
     }
-    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();
+    var length = properties.length;
+    if (length === 0) {
+        throw new Error('list of properties cannot be empty.');
+    }
+    return map(function (x) {
+        var currentProp = x;
+        for (var i = 0; i < length; i++) {
+            var p = currentProp === null || currentProp === void 0 ? void 0 : currentProp[properties[i]];
+            if (typeof p !== 'undefined') {
+                currentProp = p;
             }
-            catch (e) {
-                err = e;
+            else {
+                return undefined;
             }
-            this.destination.error(err);
         }
-        else {
-            return this.destination.complete();
-        }
-    };
-    return ThrowIfEmptySubscriber;
-}(Subscriber));
-function defaultErrorFactory() {
-    return new EmptyError();
+        return currentProp;
+    });
 }
 
-/** 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));
-        }
-    };
+function publish(selector) {
+    return selector ? function (source) { return connect(selector)(source); } : function (source) { return multicast(new Subject())(source); };
 }
-var TakeOperator = /*@__PURE__*/ (function () {
-    function TakeOperator(total) {
-        this.total = total;
-        if (this.total < 0) {
-            throw new ArgumentOutOfRangeError;
-        }
+
+function publishReplay(bufferSize, windowTime, selectorOrScheduler, timestampProvider) {
+    if (selectorOrScheduler && !isFunction(selectorOrScheduler)) {
+        timestampProvider = selectorOrScheduler;
     }
-    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 selector = isFunction(selectorOrScheduler) ? selectorOrScheduler : undefined;
+    return function (source) { return multicast(new ReplaySubject(bufferSize, windowTime, timestampProvider), selector)(source); };
 }
-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;
+function retry(configOrCount) {
+    if (configOrCount === void 0) { configOrCount = Infinity; }
+    var config;
+    if (configOrCount && typeof configOrCount === 'object') {
+        config = configOrCount;
     }
-    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);
+    else {
+        config = {
+            count: configOrCount,
         };
     }
-    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;
+    var _a = config.count, count = _a === void 0 ? Infinity : _a, delay = config.delay, _b = config.resetOnSuccess, resetOnSuccess = _b === void 0 ? false : _b;
+    return count <= 0
+        ? identity
+        : operate(function (source, subscriber) {
+            var soFar = 0;
+            var innerSub;
+            var subscribeForRetry = function () {
+                var syncUnsub = false;
+                innerSub = source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+                    if (resetOnSuccess) {
+                        soFar = 0;
+                    }
+                    subscriber.next(value);
+                }, undefined, function (err) {
+                    if (soFar++ < count) {
+                        var resub_1 = function () {
+                            if (innerSub) {
+                                innerSub.unsubscribe();
+                                innerSub = null;
+                                subscribeForRetry();
+                            }
+                            else {
+                                syncUnsub = true;
+                            }
+                        };
+                        if (delay != null) {
+                            var notifier = typeof delay === 'number' ? timer(delay) : innerFrom(delay(err, soFar));
+                            var notifierSubscriber_1 = new OperatorSubscriber(subscriber, function () {
+                                notifierSubscriber_1.unsubscribe();
+                                resub_1();
+                            }, function () {
+                                subscriber.complete();
+                            });
+                            notifier.subscribe(notifierSubscriber_1);
+                        }
+                        else {
+                            resub_1();
+                        }
+                    }
+                    else {
+                        subscriber.error(err);
+                    }
+                }));
+                if (syncUnsub) {
+                    innerSub.unsubscribe();
+                    innerSub = null;
+                    subscribeForRetry();
+                }
             };
-        }
-        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;
-    };
+            subscribeForRetry();
+        });
 }
-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;
+function sample(notifier) {
+    return operate(function (source, subscriber) {
+        var hasValue = false;
+        var lastValue = null;
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            hasValue = true;
+            lastValue = value;
+        }));
+        var emit = function () {
+            if (hasValue) {
+                hasValue = false;
+                var value = lastValue;
+                lastValue = null;
+                subscriber.next(value);
             }
-        }
-        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); };
+        };
+        notifier.subscribe(new OperatorSubscriber(subscriber, emit, noop));
+    });
 }
 
-/** 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);
+function scan(accumulator, seed) {
+    return operate(scanInternals(accumulator, seed, arguments.length >= 2, true));
+}
+
+function share(options) {
+    if (options === void 0) { options = {}; }
+    var _a = options.connector, connector = _a === void 0 ? function () { return new Subject(); } : _a, _b = options.resetOnError, resetOnError = _b === void 0 ? true : _b, _c = options.resetOnComplete, resetOnComplete = _c === void 0 ? true : _c, _d = options.resetOnRefCountZero, resetOnRefCountZero = _d === void 0 ? true : _d;
+    return function (wrapperSource) {
+        var connection = null;
+        var resetConnection = null;
+        var subject = null;
+        var refCount = 0;
+        var hasCompleted = false;
+        var hasErrored = false;
+        var cancelReset = function () {
+            resetConnection === null || resetConnection === void 0 ? void 0 : resetConnection.unsubscribe();
+            resetConnection = null;
+        };
+        var reset = function () {
+            cancelReset();
+            connection = subject = null;
+            hasCompleted = hasErrored = false;
+        };
+        var resetAndUnsubscribe = function () {
+            var conn = connection;
+            reset();
+            conn === null || conn === void 0 ? void 0 : conn.unsubscribe();
+        };
+        return operate(function (source, subscriber) {
+            refCount++;
+            if (!hasErrored && !hasCompleted) {
+                cancelReset();
             }
-            else if (count > -1) {
-                this.count = count - 1;
+            var dest = (subject = subject !== null && subject !== void 0 ? subject : connector());
+            subscriber.add(function () {
+                refCount--;
+                if (refCount === 0 && !hasErrored && !hasCompleted) {
+                    resetConnection = handleReset(resetAndUnsubscribe, resetOnRefCountZero);
+                }
+            });
+            dest.subscribe(subscriber);
+            if (!connection) {
+                connection = new SafeSubscriber({
+                    next: function (value) { return dest.next(value); },
+                    error: function (err) {
+                        hasErrored = true;
+                        cancelReset();
+                        resetConnection = handleReset(reset, resetOnError, err);
+                        dest.error(err);
+                    },
+                    complete: function () {
+                        hasCompleted = true;
+                        cancelReset();
+                        resetConnection = handleReset(reset, resetOnComplete);
+                        dest.complete();
+                    },
+                });
+                from(source).subscribe(connection);
             }
-            source.subscribe(this._unsubscribeAndRecycle());
-        }
+        })(wrapperSource);
     };
-    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;
+function handleReset(reset, on) {
+    var args = [];
+    for (var _i = 2; _i < arguments.length; _i++) {
+        args[_i - 2] = arguments[_i];
     }
-    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;
+    if (on === true) {
+        reset();
+        return null;
     }
-    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)); };
+    if (on === false) {
+        return null;
+    }
+    return on.apply(void 0, __spreadArray([], __read(args))).pipe(take(1))
+        .subscribe(function () { return reset(); });
 }
 
-/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
 function skip(count) {
-    return function (source) { return source.lift(new SkipOperator(count)); };
+    return filter(function (_, index) { return count <= index; });
 }
-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)); };
+    return operate(function (source, subscriber) {
+        var taking = false;
+        var index = 0;
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) { return (taking || (taking = !predicate(value, index++))) && subscriber.next(value); }));
+    });
 }
-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 = [];
+    var values = [];
     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); };
+        values[_i] = arguments[_i];
     }
+    var scheduler = popScheduler(values);
+    return operate(function (source, subscriber) {
+        (scheduler ? concat(values, source, scheduler) : concat(values, source)).subscribe(subscriber);
+    });
 }
 
-/** 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)); };
+    return operate(function (source, subscriber) {
+        var innerSubscriber = null;
+        var index = 0;
+        var isComplete = false;
+        var checkComplete = function () { return isComplete && !innerSubscriber && subscriber.complete(); };
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            innerSubscriber === null || innerSubscriber === void 0 ? void 0 : innerSubscriber.unsubscribe();
+            var innerIndex = 0;
+            var outerIndex = index++;
+            innerFrom(project(value, outerIndex)).subscribe((innerSubscriber = new OperatorSubscriber(subscriber, function (innerValue) { return subscriber.next(resultSelector ? resultSelector(value, innerValue, outerIndex, innerIndex++) : innerValue); }, function () {
+                innerSubscriber = null;
+                checkComplete();
+            })));
+        }, function () {
+            isComplete = true;
+            checkComplete();
+        }));
+    });
 }
-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)); };
+    return operate(function (source, subscriber) {
+        innerFrom(notifier).subscribe(new OperatorSubscriber(subscriber, function () { return subscriber.complete(); }, noop));
+        !subscriber.closed && source.subscribe(subscriber);
+    });
 }
-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));
-    };
+    if (inclusive === void 0) { inclusive = false; }
+    return operate(function (source, subscriber) {
+        var index = 0;
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            var result = predicate(value, index++);
+            (result || inclusive) && subscriber.next(value);
+            !result && subscriber.complete();
+        }));
+    });
 }
-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);
+function tap(observerOrNext, error, complete) {
+    var tapObserver = isFunction(observerOrNext) || error || complete
+        ?
+            { next: observerOrNext, error: error, complete: complete }
+        : observerOrNext;
+    return tapObserver
+        ? operate(function (source, subscriber) {
+            var _a;
+            (_a = tapObserver.subscribe) === null || _a === void 0 ? void 0 : _a.call(tapObserver);
+            var isUnsub = true;
+            source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+                var _a;
+                (_a = tapObserver.next) === null || _a === void 0 ? void 0 : _a.call(tapObserver, value);
+                subscriber.next(value);
+            }, function () {
+                var _a;
+                isUnsub = false;
+                (_a = tapObserver.complete) === null || _a === void 0 ? void 0 : _a.call(tapObserver);
+                subscriber.complete();
+            }, function (err) {
+                var _a;
+                isUnsub = false;
+                (_a = tapObserver.error) === null || _a === void 0 ? void 0 : _a.call(tapObserver, err);
+                subscriber.error(err);
+            }, function () {
+                var _a, _b;
+                if (isUnsub) {
+                    (_a = tapObserver.unsubscribe) === null || _a === void 0 ? void 0 : _a.call(tapObserver);
+                }
+                (_b = tapObserver.finalize) === null || _b === void 0 ? void 0 : _b.call(tapObserver);
+            }));
+        })
+        :
+            identity;
 }
 
-/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */
 function withLatestFrom() {
-    var args = [];
+    var inputs = [];
     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);
-        }
+        inputs[_i] = arguments[_i];
+    }
+    var project = popResultSelector(inputs);
+    return operate(function (source, subscriber) {
+        var len = inputs.length;
+        var otherValues = new Array(len);
+        var hasValue = inputs.map(function () { return false; });
+        var ready = false;
+        var _loop_1 = function (i) {
+            innerFrom(inputs[i]).subscribe(new OperatorSubscriber(subscriber, function (value) {
+                otherValues[i] = value;
+                if (!ready && !hasValue[i]) {
+                    hasValue[i] = true;
+                    (ready = hasValue.every(identity)) && (hasValue = null);
+                }
+            }, noop));
+        };
         for (var i = 0; i < len; i++) {
-            var observable = observables[i];
-            _this.add(subscribeToResult(_this, observable, undefined, i));
+            _loop_1(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);
+        source.subscribe(new OperatorSubscriber(subscriber, function (value) {
+            if (ready) {
+                var values = __spreadArray([value], __read(otherValues));
+                subscriber.next(project ? project.apply(void 0, __spreadArray([], __read(values))) : values);
             }
-        }
-    };
-    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
@@ -4098,8 +2740,12 @@ class FilterCreator {
     }
 }
 
-// threejs.org/license
-const REVISION = '125';
+/**
+ * @license
+ * Copyright 2010-2021 Three.js Authors
+ * SPDX-License-Identifier: MIT
+ */
+const REVISION = '134';
 const CullFaceNone = 0;
 const CullFaceBack = 1;
 const CullFaceFront = 2;
@@ -4183,6 +2829,7 @@ const RGBFormat = 1022;
 const RGBAFormat = 1023;
 const LuminanceFormat = 1024;
 const LuminanceAlphaFormat = 1025;
+const RGBEFormat = RGBAFormat;
 const DepthFormat = 1026;
 const DepthStencilFormat = 1027;
 const RedFormat = 1028;
@@ -4267,11 +2914,9 @@ const GLSL3 = '300 es';
  * https://github.com/mrdoob/eventdispatcher.js/
  */
 
-function EventDispatcher() {}
-
-Object.assign( EventDispatcher.prototype, {
+class EventDispatcher {
 
-       addEventListener: function ( type, listener ) {
+       addEventListener( type, listener ) {
 
                if ( this._listeners === undefined ) this._listeners = {};
 
@@ -4289,9 +2934,9 @@ Object.assign( EventDispatcher.prototype, {
 
                }
 
-       },
+       }
 
-       hasEventListener: function ( type, listener ) {
+       hasEventListener( type, listener ) {
 
                if ( this._listeners === undefined ) return false;
 
@@ -4299,9 +2944,9 @@ Object.assign( EventDispatcher.prototype, {
 
                return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;
 
-       },
+       }
 
-       removeEventListener: function ( type, listener ) {
+       removeEventListener( type, listener ) {
 
                if ( this._listeners === undefined ) return;
 
@@ -4320,9 +2965,9 @@ Object.assign( EventDispatcher.prototype, {
 
                }
 
-       },
+       }
 
-       dispatchEvent: function ( event ) {
+       dispatchEvent( event ) {
 
                if ( this._listeners === undefined ) return;
 
@@ -4342,11 +2987,20 @@ Object.assign( EventDispatcher.prototype, {
 
                        }
 
+                       event.target = null;
+
                }
 
        }
 
-} );
+}
+
+let _seed = 1234567;
+
+const DEG2RAD$1 = Math.PI / 180;
+const RAD2DEG$1 = 180 / Math.PI;
+
+//
 
 const _lut = [];
 
@@ -4356,234 +3010,263 @@ for ( let i = 0; i < 256; i ++ ) {
 
 }
 
-let _seed = 1234567;
+const hasRandomUUID = typeof crypto !== 'undefined' && 'randomUUID' in crypto;
 
-const MathUtils = {
+function generateUUID() {
 
-       DEG2RAD: Math.PI / 180,
-       RAD2DEG: 180 / Math.PI,
+       if ( hasRandomUUID ) {
 
-       generateUUID: function () {
+               return crypto.randomUUID().toUpperCase();
 
-               // 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 ] + '-' +
+       // TODO Remove this code when crypto.randomUUID() is available everywhere
+       // 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 ) );
-
-       },
+       // .toUpperCase() here flattens concatenated strings to save heap memory space.
+       return uuid.toUpperCase();
 
-       // compute euclidian modulo of m % n
-       // https://en.wikipedia.org/wiki/Modulo_operation
+}
 
-       euclideanModulo: function ( n, m ) {
+function clamp$1( value, min, max ) {
 
-               return ( ( n % m ) + m ) % m;
+       return Math.max( min, Math.min( max, value ) );
 
-       },
+}
 
-       // Linear mapping from range <a1, a2> to range <b1, b2>
+// compute euclidian modulo of m % n
+// https://en.wikipedia.org/wiki/Modulo_operation
+function euclideanModulo( n, m ) {
 
-       mapLinear: function ( x, a1, a2, b1, b2 ) {
+       return ( ( n % m ) + m ) % m;
 
-               return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+}
 
-       },
+// Linear mapping from range <a1, a2> to range <b1, b2>
+function mapLinear( x, a1, a2, b1, b2 ) {
 
-       // https://en.wikipedia.org/wiki/Linear_interpolation
+       return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
 
-       lerp: function ( x, y, t ) {
+}
 
-               return ( 1 - t ) * x + t * y;
+// https://www.gamedev.net/tutorials/programming/general-and-gameplay-programming/inverse-lerp-a-super-useful-yet-often-overlooked-function-r5230/
+function inverseLerp( x, y, value ) {
 
-       },
+       if ( x !== y ) {
 
-       // http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/
+               return ( value - x ) / ( y - x );
 
-       damp: function ( x, y, lambda, dt ) {
+       } else {
 
-               return MathUtils.lerp( x, y, 1 - Math.exp( - lambda * dt ) );
+               return 0;
 
-       },
+       }
 
-       // https://www.desmos.com/calculator/vcsjnyz7x4
+}
 
-       pingpong: function ( x, length = 1 ) {
+// https://en.wikipedia.org/wiki/Linear_interpolation
+function lerp( x, y, t ) {
 
-               return length - Math.abs( MathUtils.euclideanModulo( x, length * 2 ) - length );
+       return ( 1 - t ) * x + t * y;
 
-       },
+}
 
-       // http://en.wikipedia.org/wiki/Smoothstep
+// http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/
+function damp( x, y, lambda, dt ) {
 
-       smoothstep: function ( x, min, max ) {
+       return lerp( x, y, 1 - Math.exp( - lambda * dt ) );
 
-               if ( x <= min ) return 0;
-               if ( x >= max ) return 1;
+}
 
-               x = ( x - min ) / ( max - min );
+// https://www.desmos.com/calculator/vcsjnyz7x4
+function pingpong( x, length = 1 ) {
 
-               return x * x * ( 3 - 2 * x );
+       return length - Math.abs( euclideanModulo( x, length * 2 ) - length );
 
-       },
+}
 
-       smootherstep: function ( x, min, max ) {
+// http://en.wikipedia.org/wiki/Smoothstep
+function smoothstep( x, min, max ) {
 
-               if ( x <= min ) return 0;
-               if ( x >= max ) return 1;
+       if ( x <= min ) return 0;
+       if ( x >= max ) return 1;
 
-               x = ( x - min ) / ( max - min );
+       x = ( x - min ) / ( max - min );
 
-               return x * x * x * ( x * ( x * 6 - 15 ) + 10 );
+       return x * x * ( 3 - 2 * x );
 
-       },
+}
 
-       // Random integer from <low, high> interval
+function smootherstep( x, min, max ) {
 
-       randInt: function ( low, high ) {
+       if ( x <= min ) return 0;
+       if ( x >= max ) return 1;
 
-               return low + Math.floor( Math.random() * ( high - low + 1 ) );
+       x = ( x - min ) / ( max - min );
 
-       },
+       return x * x * x * ( x * ( x * 6 - 15 ) + 10 );
 
-       // Random float from <low, high> interval
+}
 
-       randFloat: function ( low, high ) {
+// Random integer from <low, high> interval
+function randInt( low, high ) {
 
-               return low + Math.random() * ( high - low );
+       return low + Math.floor( Math.random() * ( high - low + 1 ) );
 
-       },
+}
 
-       // Random float from <-range/2, range/2> interval
+// Random float from <low, high> interval
+function randFloat( low, high ) {
 
-       randFloatSpread: function ( range ) {
+       return low + Math.random() * ( high - low );
 
-               return range * ( 0.5 - Math.random() );
+}
 
-       },
+// Random float from <-range/2, range/2> interval
+function randFloatSpread( range ) {
 
-       // Deterministic pseudo-random float in the interval [ 0, 1 ]
+       return range * ( 0.5 - Math.random() );
 
-       seededRandom: function ( s ) {
+}
 
-               if ( s !== undefined ) _seed = s % 2147483647;
+// Deterministic pseudo-random float in the interval [ 0, 1 ]
+function seededRandom( s ) {
 
-               // Park-Miller algorithm
+       if ( s !== undefined ) _seed = s % 2147483647;
 
-               _seed = _seed * 16807 % 2147483647;
+       // Park-Miller algorithm
 
-               return ( _seed - 1 ) / 2147483646;
+       _seed = _seed * 16807 % 2147483647;
 
-       },
+       return ( _seed - 1 ) / 2147483646;
 
-       degToRad: function ( degrees ) {
+}
 
-               return degrees * MathUtils.DEG2RAD;
+function degToRad( degrees ) {
 
-       },
+       return degrees * DEG2RAD$1;
 
-       radToDeg: function ( radians ) {
+}
 
-               return radians * MathUtils.RAD2DEG;
+function radToDeg( radians ) {
 
-       },
+       return radians * RAD2DEG$1;
 
-       isPowerOfTwo: function ( value ) {
+}
 
-               return ( value & ( value - 1 ) ) === 0 && value !== 0;
+function isPowerOfTwo( value ) {
 
-       },
+       return ( value & ( value - 1 ) ) === 0 && value !== 0;
 
-       ceilPowerOfTwo: function ( value ) {
+}
 
-               return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) );
+function ceilPowerOfTwo( 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 ) );
+function floorPowerOfTwo( 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
+function setQuaternionFromProperEuler( q, a, b, c, order ) {
 
-               // 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
+       // Intrinsic Proper Euler Angles - see https://en.wikipedia.org/wiki/Euler_angles
 
-               const cos = Math.cos;
-               const sin = Math.sin;
+       // 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 c2 = cos( b / 2 );
-               const s2 = sin( b / 2 );
+       const cos = Math.cos;
+       const sin = Math.sin;
 
-               const c13 = cos( ( a + c ) / 2 );
-               const s13 = sin( ( a + c ) / 2 );
+       const c2 = cos( b / 2 );
+       const s2 = sin( b / 2 );
 
-               const c1_3 = cos( ( a - c ) / 2 );
-               const s1_3 = sin( ( a - c ) / 2 );
+       const c13 = cos( ( a + c ) / 2 );
+       const s13 = sin( ( a + c ) / 2 );
 
-               const c3_1 = cos( ( c - a ) / 2 );
-               const s3_1 = sin( ( c - a ) / 2 );
+       const c1_3 = cos( ( a - c ) / 2 );
+       const s1_3 = sin( ( a - c ) / 2 );
 
-               switch ( order ) {
+       const c3_1 = cos( ( c - a ) / 2 );
+       const s3_1 = sin( ( c - a ) / 2 );
 
-                       case 'XYX':
-                               q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 );
-                               break;
+       switch ( order ) {
 
-                       case 'YZY':
-                               q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 );
-                               break;
+               case 'XYX':
+                       q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 );
+                       break;
 
-                       case 'ZXZ':
-                               q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 );
-                               break;
+               case 'YZY':
+                       q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 );
+                       break;
 
-                       case 'XZX':
-                               q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 );
-                               break;
+               case 'ZXZ':
+                       q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 );
+                       break;
 
-                       case 'YXY':
-                               q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 );
-                               break;
+               case 'XZX':
+                       q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 );
+                       break;
 
-                       case 'ZYZ':
-                               q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 );
-                               break;
+               case 'YXY':
+                       q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 );
+                       break;
 
-                       default:
-                               console.warn( 'THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order );
+               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 );
 
        }
 
-};
+}
+
+var MathUtils = /*#__PURE__*/Object.freeze({
+       __proto__: null,
+       DEG2RAD: DEG2RAD$1,
+       RAD2DEG: RAD2DEG$1,
+       generateUUID: generateUUID,
+       clamp: clamp$1,
+       euclideanModulo: euclideanModulo,
+       mapLinear: mapLinear,
+       inverseLerp: inverseLerp,
+       lerp: lerp,
+       damp: damp,
+       pingpong: pingpong,
+       smoothstep: smoothstep,
+       smootherstep: smootherstep,
+       randInt: randInt,
+       randFloat: randFloat,
+       randFloatSpread: randFloatSpread,
+       seededRandom: seededRandom,
+       degToRad: degToRad,
+       radToDeg: radToDeg,
+       isPowerOfTwo: isPowerOfTwo,
+       ceilPowerOfTwo: ceilPowerOfTwo,
+       floorPowerOfTwo: floorPowerOfTwo,
+       setQuaternionFromProperEuler: setQuaternionFromProperEuler
+});
 
 class Vector2 {
 
        constructor( x = 0, y = 0 ) {
 
-               Object.defineProperty( this, 'isVector2', { value: true } );
-
                this.x = x;
                this.y = y;
 
@@ -5052,14 +3735,21 @@ class Vector2 {
 
        }
 
+       *[ Symbol.iterator ]() {
+
+               yield this.x;
+               yield this.y;
+
+       }
+
 }
 
+Vector2.prototype.isVector2 = true;
+
 class Matrix3 {
 
        constructor() {
 
-               Object.defineProperty( this, 'isMatrix3', { value: true } );
-
                this.elements = [
 
                        1, 0, 0,
@@ -5102,12 +3792,6 @@ class Matrix3 {
 
        }
 
-       clone() {
-
-               return new this.constructor().fromArray( this.elements );
-
-       }
-
        copy( m ) {
 
                const te = this.elements;
@@ -5262,7 +3946,7 @@ class Matrix3 {
 
        getNormalMatrix( matrix4 ) {
 
-               return this.setFromMatrix4( matrix4 ).copy( this ).invert().transpose();
+               return this.setFromMatrix4( matrix4 ).invert().transpose();
 
        }
 
@@ -5390,13 +4074,79 @@ class Matrix3 {
 
        }
 
+       clone() {
+
+               return new this.constructor().fromArray( this.elements );
+
+       }
+
+}
+
+Matrix3.prototype.isMatrix3 = true;
+
+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;
+
+}
+
+function createElementNS( name ) {
+
+       return document.createElementNS( 'http://www.w3.org/1999/xhtml', name );
+
+}
+
+/**
+  * cyrb53 hash for string from: https://stackoverflow.com/a/52171480
+  *
+  * Public Domain, @bryc - https://stackoverflow.com/users/815680/bryc
+  *
+  * It is roughly similar to the well-known MurmurHash/xxHash algorithms. It uses a combination
+  * of multiplication and Xorshift to generate the hash, but not as thorough. As a result it's
+  * faster than either would be in JavaScript and significantly simpler to implement. Keep in
+  * mind this is not a secure algorithm, if privacy/security is a concern, this is not for you.
+  *
+  * @param {string} str
+  * @param {number} seed, default 0
+  * @returns number
+  */
+function hashString( str, seed = 0 ) {
+
+       let h1 = 0xdeadbeef ^ seed, h2 = 0x41c6ce57 ^ seed;
+
+       for ( let i = 0, ch; i < str.length; i ++ ) {
+
+               ch = str.charCodeAt( i );
+
+               h1 = Math.imul( h1 ^ ch, 2654435761 );
+
+               h2 = Math.imul( h2 ^ ch, 1597334677 );
+
+       }
+
+       h1 = Math.imul( h1 ^ ( h1 >>> 16 ), 2246822507 ) ^ Math.imul( h2 ^ ( h2 >>> 13 ), 3266489909 );
+
+       h2 = Math.imul( h2 ^ ( h2 >>> 16 ), 2246822507 ) ^ Math.imul( h1 ^ ( h1 >>> 13 ), 3266489909 );
+
+       return 4294967296 * ( 2097151 & h2 ) + ( h1 >>> 0 );
+
 }
 
 let _canvas;
 
-const ImageUtils = {
+class ImageUtils {
 
-       getDataURL: function ( image ) {
+       static getDataURL( image ) {
 
                if ( /^data:/i.test( image.src ) ) {
 
@@ -5418,7 +4168,7 @@ const ImageUtils = {
 
                } else {
 
-                       if ( _canvas === undefined ) _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+                       if ( _canvas === undefined ) _canvas = createElementNS( 'canvas' );
 
                        _canvas.width = image.width;
                        _canvas.height = image.height;
@@ -5441,6 +4191,8 @@ const ImageUtils = {
 
                if ( canvas.width > 2048 || canvas.height > 2048 ) {
 
+                       console.warn( 'THREE.ImageUtils.getDataURL: Image converted to jpg for performance reasons', image );
+
                        return canvas.toDataURL( 'image/jpeg', 0.6 );
 
                } else {
@@ -5451,81 +4203,80 @@ const ImageUtils = {
 
        }
 
-};
+}
 
 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 ) {
+class Texture extends EventDispatcher {
 
-       Object.defineProperty( this, 'id', { value: textureId ++ } );
+       constructor( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = 1, encoding = LinearEncoding ) {
 
-       this.uuid = MathUtils.generateUUID();
+               super();
 
-       this.name = '';
+               Object.defineProperty( this, 'id', { value: textureId ++ } );
 
-       this.image = image;
-       this.mipmaps = [];
+               this.uuid = generateUUID();
 
-       this.mapping = mapping;
+               this.name = '';
 
-       this.wrapS = wrapS;
-       this.wrapT = wrapT;
+               this.image = image;
+               this.mipmaps = [];
 
-       this.magFilter = magFilter;
-       this.minFilter = minFilter;
+               this.mapping = mapping;
 
-       this.anisotropy = anisotropy;
+               this.wrapS = wrapS;
+               this.wrapT = wrapT;
 
-       this.format = format;
-       this.internalFormat = null;
-       this.type = type;
+               this.magFilter = magFilter;
+               this.minFilter = minFilter;
 
-       this.offset = new Vector2( 0, 0 );
-       this.repeat = new Vector2( 1, 1 );
-       this.center = new Vector2( 0, 0 );
-       this.rotation = 0;
+               this.anisotropy = anisotropy;
 
-       this.matrixAutoUpdate = true;
-       this.matrix = new Matrix3();
+               this.format = format;
+               this.internalFormat = null;
+               this.type = type;
 
-       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)
+               this.offset = new Vector2( 0, 0 );
+               this.repeat = new Vector2( 1, 1 );
+               this.center = new Vector2( 0, 0 );
+               this.rotation = 0;
 
-       // 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.matrixAutoUpdate = true;
+               this.matrix = new Matrix3();
 
-       this.version = 0;
-       this.onUpdate = null;
+               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;
 
-Texture.DEFAULT_IMAGE = undefined;
-Texture.DEFAULT_MAPPING = UVMapping;
+               this.userData = {};
 
-Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+               this.version = 0;
+               this.onUpdate = null;
 
-       constructor: Texture,
+               this.isRenderTargetTexture = false;
 
-       isTexture: true,
+       }
 
-       updateMatrix: function () {
+       updateMatrix() {
 
                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 () {
+       clone() {
 
                return new this.constructor().copy( this );
 
-       },
+       }
 
-       copy: function ( source ) {
+       copy( source ) {
 
                this.name = source.name;
 
@@ -5560,11 +4311,13 @@ Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
                this.unpackAlignment = source.unpackAlignment;
                this.encoding = source.encoding;
 
+               this.userData = JSON.parse( JSON.stringify( source.userData ) );
+
                return this;
 
-       },
+       }
 
-       toJSON: function ( meta ) {
+       toJSON( meta ) {
 
                const isRootObject = ( meta === undefined || typeof meta === 'string' );
 
@@ -5617,7 +4370,7 @@ Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
 
                        if ( image.uuid === undefined ) {
 
-                               image.uuid = MathUtils.generateUUID(); // UGH
+                               image.uuid = generateUUID(); // UGH
 
                        }
 
@@ -5666,6 +4419,8 @@ Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
 
                }
 
+               if ( JSON.stringify( this.userData ) !== '{}' ) output.userData = this.userData;
+
                if ( ! isRootObject ) {
 
                        meta.textures[ this.uuid ] = output;
@@ -5674,15 +4429,15 @@ Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
 
                return output;
 
-       },
+       }
 
-       dispose: function () {
+       dispose() {
 
                this.dispatchEvent( { type: 'dispose' } );
 
-       },
+       }
 
-       transformUv: function ( uv ) {
+       transformUv( uv ) {
 
                if ( this.mapping !== UVMapping ) return uv;
 
@@ -5762,17 +4517,18 @@ Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
 
        }
 
-} );
-
-Object.defineProperty( Texture.prototype, 'needsUpdate', {
-
-       set: function ( value ) {
+       set needsUpdate( value ) {
 
                if ( value === true ) this.version ++;
 
        }
 
-} );
+}
+
+Texture.DEFAULT_IMAGE = undefined;
+Texture.DEFAULT_MAPPING = UVMapping;
+
+Texture.prototype.isTexture = true;
 
 function serializeImage( image ) {
 
@@ -5812,8 +4568,6 @@ 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;
@@ -6460,8 +5214,19 @@ class Vector4 {
 
        }
 
+       *[ Symbol.iterator ]() {
+
+               yield this.x;
+               yield this.y;
+               yield this.z;
+               yield this.w;
+
+       }
+
 }
 
+Vector4.prototype.isVector4 = true;
+
 /*
  In options, we can specify:
  * Texture parameters for an auto-generated target texture
@@ -6469,29 +5234,26 @@ class Vector4 {
 */
 class WebGLRenderTarget extends EventDispatcher {
 
-       constructor( width, height, options ) {
+       constructor( width, height, options = {} ) {
 
                super();
 
-               Object.defineProperty( this, 'isWebGLRenderTarget', { value: true } );
-
                this.width = width;
                this.height = height;
+               this.depth = 1;
 
                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.isRenderTargetTexture = true;
 
-               this.texture.image = {};
-               this.texture.image.width = width;
-               this.texture.image.height = height;
+               this.texture.image = { width: width, height: height, depth: 1 };
 
                this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false;
+               this.texture.internalFormat = options.internalFormat !== undefined ? options.internalFormat : null;
                this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter;
 
                this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true;
@@ -6500,15 +5262,29 @@ class WebGLRenderTarget extends EventDispatcher {
 
        }
 
-       setSize( width, height ) {
+       setTexture( texture ) {
 
-               if ( this.width !== width || this.height !== height ) {
+               texture.image = {
+                       width: this.width,
+                       height: this.height,
+                       depth: this.depth
+               };
+
+               this.texture = texture;
+
+       }
+
+       setSize( width, height, depth = 1 ) {
+
+               if ( this.width !== width || this.height !== height || this.depth !== depth ) {
 
                        this.width = width;
                        this.height = height;
+                       this.depth = depth;
 
                        this.texture.image.width = width;
                        this.texture.image.height = height;
+                       this.texture.image.depth = depth;
 
                        this.dispose();
 
@@ -6529,10 +5305,12 @@ class WebGLRenderTarget extends EventDispatcher {
 
                this.width = source.width;
                this.height = source.height;
+               this.depth = source.depth;
 
                this.viewport.copy( source.viewport );
 
                this.texture = source.texture.clone();
+               this.texture.image = { ...this.texture.image }; // See #20328.
 
                this.depthBuffer = source.depthBuffer;
                this.stencilBuffer = source.stencilBuffer;
@@ -6550,12 +5328,112 @@ class WebGLRenderTarget extends EventDispatcher {
 
 }
 
+WebGLRenderTarget.prototype.isWebGLRenderTarget = true;
+
+class WebGLMultipleRenderTargets extends WebGLRenderTarget {
+
+       constructor( width, height, count ) {
+
+               super( width, height );
+
+               const texture = this.texture;
+
+               this.texture = [];
+
+               for ( let i = 0; i < count; i ++ ) {
+
+                       this.texture[ i ] = texture.clone();
+
+               }
+
+       }
+
+       setSize( width, height, depth = 1 ) {
+
+               if ( this.width !== width || this.height !== height || this.depth !== depth ) {
+
+                       this.width = width;
+                       this.height = height;
+                       this.depth = depth;
+
+                       for ( let i = 0, il = this.texture.length; i < il; i ++ ) {
+
+                               this.texture[ i ].image.width = width;
+                               this.texture[ i ].image.height = height;
+                               this.texture[ i ].image.depth = depth;
+
+                       }
+
+                       this.dispose();
+
+               }
+
+               this.viewport.set( 0, 0, width, height );
+               this.scissor.set( 0, 0, width, height );
+
+               return this;
+
+       }
+
+       copy( source ) {
+
+               this.dispose();
+
+               this.width = source.width;
+               this.height = source.height;
+               this.depth = source.depth;
+
+               this.viewport.set( 0, 0, this.width, this.height );
+               this.scissor.set( 0, 0, this.width, this.height );
+
+               this.depthBuffer = source.depthBuffer;
+               this.stencilBuffer = source.stencilBuffer;
+               this.depthTexture = source.depthTexture;
+
+               this.texture.length = 0;
+
+               for ( let i = 0, il = source.texture.length; i < il; i ++ ) {
+
+                       this.texture[ i ] = source.texture[ i ].clone();
+
+               }
+
+               return this;
+
+       }
+
+}
+
+WebGLMultipleRenderTargets.prototype.isWebGLMultipleRenderTargets = true;
+
+class WebGLMultisampleRenderTarget extends WebGLRenderTarget {
+
+       constructor( width, height, options ) {
+
+               super( width, height, options );
+
+               this.samples = 4;
+
+       }
+
+       copy( source ) {
+
+               super.copy.call( this, source );
+
+               this.samples = source.samples;
+
+               return this;
+
+       }
+
+}
+
+WebGLMultisampleRenderTarget.prototype.isWebGLMultisampleRenderTarget = true;
+
 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;
@@ -6565,7 +5443,8 @@ class Quaternion {
 
        static slerp( qa, qb, qm, t ) {
 
-               return qm.copy( qa ).slerp( qb, t );
+               console.warn( 'THREE.Quaternion: Static .slerp() has been deprecated. Use qm.slerpQuaternions( qa, qb, t ) instead.' );
+               return qm.slerpQuaternions( qa, qb, t );
 
        }
 
@@ -6583,6 +5462,26 @@ class Quaternion {
                        z1 = src1[ srcOffset1 + 2 ],
                        w1 = src1[ srcOffset1 + 3 ];
 
+               if ( t === 0 ) {
+
+                       dst[ dstOffset + 0 ] = x0;
+                       dst[ dstOffset + 1 ] = y0;
+                       dst[ dstOffset + 2 ] = z0;
+                       dst[ dstOffset + 3 ] = w0;
+                       return;
+
+               }
+
+               if ( t === 1 ) {
+
+                       dst[ dstOffset + 0 ] = x1;
+                       dst[ dstOffset + 1 ] = y1;
+                       dst[ dstOffset + 2 ] = z1;
+                       dst[ dstOffset + 3 ] = w1;
+                       return;
+
+               }
+
                if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) {
 
                        let s = 1 - t;
@@ -6895,11 +5794,11 @@ class Quaternion {
 
                // assumes direction vectors vFrom and vTo are normalized
 
-               const EPS = 0.000001;
-
                let r = vFrom.dot( vTo ) + 1;
 
-               if ( r < EPS ) {
+               if ( r < Number.EPSILON ) {
+
+                       // vFrom and vTo point in opposite directions
 
                        r = 0;
 
@@ -6936,7 +5835,7 @@ class Quaternion {
 
        angleTo( q ) {
 
-               return 2 * Math.acos( Math.abs( MathUtils.clamp( this.dot( q ), - 1, 1 ) ) );
+               return 2 * Math.acos( Math.abs( clamp$1( this.dot( q ), - 1, 1 ) ) );
 
        }
 
@@ -7133,6 +6032,35 @@ class Quaternion {
 
        }
 
+       slerpQuaternions( qa, qb, t ) {
+
+               this.copy( qa ).slerp( qb, t );
+
+       }
+
+       random() {
+
+               // Derived from http://planning.cs.uiuc.edu/node198.html
+               // Note, this source uses w, x, y, z ordering,
+               // so we swap the order below.
+
+               const u1 = Math.random();
+               const sqrt1u1 = Math.sqrt( 1 - u1 );
+               const sqrtu1 = Math.sqrt( u1 );
+
+               const u2 = 2 * Math.PI * Math.random();
+
+               const u3 = 2 * Math.PI * Math.random();
+
+               return this.set(
+                       sqrt1u1 * Math.cos( u2 ),
+                       sqrtu1 * Math.sin( u3 ),
+                       sqrtu1 * Math.cos( u3 ),
+                       sqrt1u1 * Math.sin( u2 ),
+               );
+
+       }
+
        equals( quaternion ) {
 
                return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );
@@ -7186,12 +6114,12 @@ class Quaternion {
 
 }
 
+Quaternion.prototype.isQuaternion = true;
+
 class Vector3 {
 
        constructor( x = 0, y = 0, z = 0 ) {
 
-               Object.defineProperty( this, 'isVector3', { value: true } );
-
                this.x = x;
                this.y = y;
                this.z = z;
@@ -7417,13 +6345,13 @@ class Vector3 {
 
                }
 
-               return this.applyQuaternion( _quaternion.setFromEuler( euler ) );
+               return this.applyQuaternion( _quaternion$4.setFromEuler( euler ) );
 
        }
 
        applyAxisAngle( axis, angle ) {
 
-               return this.applyQuaternion( _quaternion.setFromAxisAngle( axis, angle ) );
+               return this.applyQuaternion( _quaternion$4.setFromAxisAngle( axis, angle ) );
 
        }
 
@@ -7725,9 +6653,9 @@ class Vector3 {
 
        projectOnPlane( planeNormal ) {
 
-               _vector.copy( this ).projectOnVector( planeNormal );
+               _vector$c.copy( this ).projectOnVector( planeNormal );
 
-               return this.sub( _vector );
+               return this.sub( _vector$c );
 
        }
 
@@ -7736,7 +6664,7 @@ class Vector3 {
                // 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 ) ) );
+               return this.sub( _vector$c.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) );
 
        }
 
@@ -7750,7 +6678,7 @@ class Vector3 {
 
                // clamp, to handle numerical problems
 
-               return Math.acos( MathUtils.clamp( theta, - 1, 1 ) );
+               return Math.acos( clamp$1( theta, - 1, 1 ) );
 
        }
 
@@ -7898,19 +6826,43 @@ class Vector3 {
 
        }
 
+       randomDirection() {
+
+               // Derived from https://mathworld.wolfram.com/SpherePointPicking.html
+
+               const u = ( Math.random() - 0.5 ) * 2;
+               const t = Math.random() * Math.PI * 2;
+               const f = Math.sqrt( 1 - u ** 2 );
+
+               this.x = f * Math.cos( t );
+               this.y = f * Math.sin( t );
+               this.z = u;
+
+               return this;
+
+       }
+
+       *[ Symbol.iterator ]() {
+
+               yield this.x;
+               yield this.y;
+               yield this.z;
+
+       }
+
 }
 
-const _vector = /*@__PURE__*/ new Vector3();
-const _quaternion = /*@__PURE__*/ new Quaternion();
+Vector3.prototype.isVector3 = true;
 
-class Box3 {
+const _vector$c = /*@__PURE__*/ new Vector3();
+const _quaternion$4 = /*@__PURE__*/ new Quaternion();
 
-       constructor( min, max ) {
+class Box3 {
 
-               Object.defineProperty( this, 'isBox3', { value: true } );
+       constructor( min = new Vector3( + Infinity, + Infinity, + Infinity ), max = new Vector3( - Infinity, - Infinity, - Infinity ) ) {
 
-               this.min = ( min !== undefined ) ? min : new Vector3( + Infinity, + Infinity, + Infinity );
-               this.max = ( max !== undefined ) ? max : new Vector3( - Infinity, - Infinity, - Infinity );
+               this.min = min;
+               this.max = max;
 
        }
 
@@ -8005,7 +6957,7 @@ class Box3 {
 
        setFromCenterAndSize( center, size ) {
 
-               const halfSize = _vector$1.copy( size ).multiplyScalar( 0.5 );
+               const halfSize = _vector$b.copy( size ).multiplyScalar( 0.5 );
 
                this.min.copy( center ).sub( halfSize );
                this.max.copy( center ).add( halfSize );
@@ -8056,26 +7008,12 @@ class Box3 {
 
        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 );
 
        }
@@ -8124,10 +7062,10 @@ class Box3 {
 
                        }
 
-                       _box.copy( geometry.boundingBox );
-                       _box.applyMatrix4( object.matrixWorld );
+                       _box$3.copy( geometry.boundingBox );
+                       _box$3.applyMatrix4( object.matrixWorld );
 
-                       this.union( _box );
+                       this.union( _box$3 );
 
                }
 
@@ -8164,13 +7102,6 @@ class Box3 {
                // 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 ),
@@ -8191,10 +7122,10 @@ class Box3 {
        intersectsSphere( sphere ) {
 
                // Find the point on the AABB closest to the sphere center.
-               this.clampPoint( sphere.center, _vector$1 );
+               this.clampPoint( sphere.center, _vector$b );
 
                // If that point is inside the sphere, the AABB and sphere intersect.
-               return _vector$1.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );
+               return _vector$b.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );
 
        }
 
@@ -8258,14 +7189,14 @@ class Box3 {
                _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 );
+               _v0$2.subVectors( triangle.a, _center );
+               _v1$7.subVectors( triangle.b, _center );
+               _v2$3.subVectors( triangle.c, _center );
 
                // compute edge vectors for triangle
-               _f0.subVectors( _v1, _v0 );
-               _f1.subVectors( _v2, _v1 );
-               _f2.subVectors( _v0, _v2 );
+               _f0.subVectors( _v1$7, _v0$2 );
+               _f1.subVectors( _v2$3, _v1$7 );
+               _f2.subVectors( _v0$2, _v2$3 );
 
                // 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
@@ -8275,7 +7206,7 @@ class Box3 {
                        _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 ) ) {
+               if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$3, _extents ) ) {
 
                        return false;
 
@@ -8283,7 +7214,7 @@ class Box3 {
 
                // test 3 face normals from the aabb
                axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ];
-               if ( ! satForAxes( axes, _v0, _v1, _v2, _extents ) ) {
+               if ( ! satForAxes( axes, _v0$2, _v1$7, _v2$3, _extents ) ) {
 
                        return false;
 
@@ -8294,26 +7225,19 @@ class Box3 {
                _triangleNormal.crossVectors( _f0, _f1 );
                axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ];
 
-               return satForAxes( axes, _v0, _v1, _v2, _extents );
+               return satForAxes( axes, _v0$2, _v1$7, _v2$3, _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 );
+               const clampedPoint = _vector$b.copy( point ).clamp( this.min, this.max );
 
                return clampedPoint.sub( point ).length();
 
@@ -8321,16 +7245,9 @@ class Box3 {
 
        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;
+               target.radius = this.getSize( _vector$b ).length() * 0.5;
 
                return target;
 
@@ -8395,31 +7312,7 @@ class Box3 {
 
 }
 
-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;
-
-}
+Box3.prototype.isBox3 = true;
 
 const _points = [
        /*@__PURE__*/ new Vector3(),
@@ -8432,15 +7325,15 @@ const _points = [
        /*@__PURE__*/ new Vector3()
 ];
 
-const _vector$1 = /*@__PURE__*/ new Vector3();
+const _vector$b = /*@__PURE__*/ new Vector3();
 
-const _box = /*@__PURE__*/ new Box3();
+const _box$3 = /*@__PURE__*/ new Box3();
 
 // triangle centered vertices
 
-const _v0 = /*@__PURE__*/ new Vector3();
-const _v1 = /*@__PURE__*/ new Vector3();
-const _v2 = /*@__PURE__*/ new Vector3();
+const _v0$2 = /*@__PURE__*/ new Vector3();
+const _v1$7 = /*@__PURE__*/ new Vector3();
+const _v2$3 = /*@__PURE__*/ new Vector3();
 
 // triangle edge vectors
 
@@ -8453,14 +7346,43 @@ const _extents = /*@__PURE__*/ new Vector3();
 const _triangleNormal = /*@__PURE__*/ new Vector3();
 const _testAxis = /*@__PURE__*/ new Vector3();
 
-const _box$1 = /*@__PURE__*/ new Box3();
+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 _box$2 = /*@__PURE__*/ new Box3();
+const _v1$6 = /*@__PURE__*/ new Vector3();
+const _toFarthestPoint = /*@__PURE__*/ new Vector3();
+const _toPoint = /*@__PURE__*/ new Vector3();
 
 class Sphere {
 
-       constructor( center, radius ) {
+       constructor( center = new Vector3(), radius = - 1 ) {
 
-               this.center = ( center !== undefined ) ? center : new Vector3();
-               this.radius = ( radius !== undefined ) ? radius : - 1;
+               this.center = center;
+               this.radius = radius;
 
        }
 
@@ -8483,7 +7405,7 @@ class Sphere {
 
                } else {
 
-                       _box$1.setFromPoints( points ).getCenter( center );
+                       _box$2.setFromPoints( points ).getCenter( center );
 
                }
 
@@ -8501,12 +7423,6 @@ class Sphere {
 
        }
 
-       clone() {
-
-               return new this.constructor().copy( this );
-
-       }
-
        copy( sphere ) {
 
                this.center.copy( sphere.center );
@@ -8567,13 +7483,6 @@ class Sphere {
 
                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 ) ) {
@@ -8589,13 +7498,6 @@ class Sphere {
 
        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
@@ -8628,29 +7530,78 @@ class Sphere {
 
        }
 
+       expandByPoint( point ) {
+
+               // from https://github.com/juj/MathGeoLib/blob/2940b99b99cfe575dd45103ef20f4019dee15b54/src/Geometry/Sphere.cpp#L649-L671
+
+               _toPoint.subVectors( point, this.center );
+
+               const lengthSq = _toPoint.lengthSq();
+
+               if ( lengthSq > ( this.radius * this.radius ) ) {
+
+                       const length = Math.sqrt( lengthSq );
+                       const missingRadiusHalf = ( length - this.radius ) * 0.5;
+
+                       // Nudge this sphere towards the target point. Add half the missing distance to radius,
+                       // and the other half to position. This gives a tighter enclosure, instead of if
+                       // the whole missing distance were just added to radius.
+
+                       this.center.add( _toPoint.multiplyScalar( missingRadiusHalf / length ) );
+                       this.radius += missingRadiusHalf;
+
+               }
+
+               return this;
+
+       }
+
+       union( sphere ) {
+
+               // from https://github.com/juj/MathGeoLib/blob/2940b99b99cfe575dd45103ef20f4019dee15b54/src/Geometry/Sphere.cpp#L759-L769
+
+               // To enclose another sphere into this sphere, we only need to enclose two points:
+               // 1) Enclose the farthest point on the other sphere into this sphere.
+               // 2) Enclose the opposite point of the farthest point into this sphere.
+
+               _toFarthestPoint.subVectors( sphere.center, this.center ).normalize().multiplyScalar( sphere.radius );
+
+               this.expandByPoint( _v1$6.copy( sphere.center ).add( _toFarthestPoint ) );
+               this.expandByPoint( _v1$6.copy( sphere.center ).sub( _toFarthestPoint ) );
+
+               return this;
+
+       }
+
        equals( sphere ) {
 
                return sphere.center.equals( this.center ) && ( sphere.radius === this.radius );
 
        }
 
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
 }
 
-const _vector$2 = /*@__PURE__*/ new Vector3();
+const _vector$a = /*@__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();
+const _normal$1 = /*@__PURE__*/ new Vector3();
 
 class Ray {
 
-       constructor( origin, direction ) {
+       constructor( origin = new Vector3(), direction = new Vector3( 0, 0, - 1 ) ) {
 
-               this.origin = ( origin !== undefined ) ? origin : new Vector3();
-               this.direction = ( direction !== undefined ) ? direction : new Vector3( 0, 0, - 1 );
+               this.origin = origin;
+               this.direction = direction;
 
        }
 
@@ -8663,12 +7614,6 @@ class Ray {
 
        }
 
-       clone() {
-
-               return new this.constructor().copy( this );
-
-       }
-
        copy( ray ) {
 
                this.origin.copy( ray.origin );
@@ -8680,13 +7625,6 @@ class Ray {
 
        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 );
 
        }
@@ -8701,7 +7639,7 @@ class Ray {
 
        recast( t ) {
 
-               this.origin.copy( this.at( t, _vector$2 ) );
+               this.origin.copy( this.at( t, _vector$a ) );
 
                return this;
 
@@ -8709,13 +7647,6 @@ class Ray {
 
        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 );
@@ -8738,7 +7669,7 @@ class Ray {
 
        distanceSqToPoint( point ) {
 
-               const directionDistance = _vector$2.subVectors( point, this.origin ).dot( this.direction );
+               const directionDistance = _vector$a.subVectors( point, this.origin ).dot( this.direction );
 
                // point behind the ray
 
@@ -8748,9 +7679,9 @@ class Ray {
 
                }
 
-               _vector$2.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
+               _vector$a.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
 
-               return _vector$2.distanceToSquared( point );
+               return _vector$a.distanceToSquared( point );
 
        }
 
@@ -8875,9 +7806,9 @@ class Ray {
 
        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;
+               _vector$a.subVectors( sphere.center, this.origin );
+               const tca = _vector$a.dot( this.direction );
+               const d2 = _vector$a.dot( _vector$a ) - tca * tca;
                const radius2 = sphere.radius * sphere.radius;
 
                if ( d2 > radius2 ) return null;
@@ -9047,7 +7978,7 @@ class Ray {
 
        intersectsBox( box ) {
 
-               return this.intersectBox( box, _vector$2 ) !== null;
+               return this.intersectBox( box, _vector$a ) !== null;
 
        }
 
@@ -9059,14 +7990,14 @@ class Ray {
 
                _edge1.subVectors( b, a );
                _edge2.subVectors( c, a );
-               _normal.crossVectors( _edge1, _edge2 );
+               _normal$1.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 DdN = this.direction.dot( _normal$1 );
                let sign;
 
                if ( DdN > 0 ) {
@@ -9112,7 +8043,7 @@ class Ray {
                }
 
                // Line intersects triangle, check if ray does.
-               const QdN = - sign * _diff.dot( _normal );
+               const QdN = - sign * _diff.dot( _normal$1 );
 
                // t < 0, no intersection
                if ( QdN < 0 ) {
@@ -9141,14 +8072,18 @@ class Ray {
 
        }
 
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
 }
 
 class Matrix4 {
 
        constructor() {
 
-               Object.defineProperty( this, 'isMatrix4', { value: true } );
-
                this.elements = [
 
                        1, 0, 0, 0,
@@ -9273,9 +8208,9 @@ class Matrix4 {
                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();
+               const scaleX = 1 / _v1$5.setFromMatrixColumn( m, 0 ).length();
+               const scaleY = 1 / _v1$5.setFromMatrixColumn( m, 1 ).length();
+               const scaleZ = 1 / _v1$5.setFromMatrixColumn( m, 2 ).length();
 
                te[ 0 ] = me[ 0 ] * scaleX;
                te[ 1 ] = me[ 1 ] * scaleX;
@@ -9818,13 +8753,13 @@ class Matrix4 {
 
        }
 
-       makeShear( x, y, z ) {
+       makeShear( xy, xz, yx, yz, zx, zy ) {
 
                this.set(
 
-                       1, y, z, 0,
-                       x, 1, z, 0,
-                       x, y, 1, 0,
+                       1, yx, zx, 0,
+                       xy, 1, zy, 0,
+                       xz, yz, 1, 0,
                        0, 0, 0, 1
 
                );
@@ -9873,9 +8808,9 @@ class Matrix4 {
 
                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();
+               let sx = _v1$5.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length();
+               const sy = _v1$5.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length();
+               const sz = _v1$5.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length();
 
                // if determine is negative, we need to invert one scale
                const det = this.determinant();
@@ -9886,25 +8821,25 @@ class Matrix4 {
                position.z = te[ 14 ];
 
                // scale the rotation part
-               _m1.copy( this );
+               _m1$2.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$2.elements[ 0 ] *= invSX;
+               _m1$2.elements[ 1 ] *= invSX;
+               _m1$2.elements[ 2 ] *= invSX;
 
-               _m1.elements[ 4 ] *= invSY;
-               _m1.elements[ 5 ] *= invSY;
-               _m1.elements[ 6 ] *= invSY;
+               _m1$2.elements[ 4 ] *= invSY;
+               _m1$2.elements[ 5 ] *= invSY;
+               _m1$2.elements[ 6 ] *= invSY;
 
-               _m1.elements[ 8 ] *= invSZ;
-               _m1.elements[ 9 ] *= invSZ;
-               _m1.elements[ 10 ] *= invSZ;
+               _m1$2.elements[ 8 ] *= invSZ;
+               _m1$2.elements[ 9 ] *= invSZ;
+               _m1$2.elements[ 10 ] *= invSZ;
 
-               quaternion.setFromRotationMatrix( _m1 );
+               quaternion.setFromRotationMatrix( _m1$2 );
 
                scale.x = sx;
                scale.y = sy;
@@ -10017,20 +8952,23 @@ class Matrix4 {
 
 }
 
-const _v1$1 = /*@__PURE__*/ new Vector3();
-const _m1 = /*@__PURE__*/ new Matrix4();
+Matrix4.prototype.isMatrix4 = true;
+
+const _v1$5 = /*@__PURE__*/ new Vector3();
+const _m1$2 = /*@__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();
 
+const _matrix$1 = /*@__PURE__*/ new Matrix4();
+const _quaternion$3 = /*@__PURE__*/ new Quaternion();
+
 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;
@@ -10090,12 +9028,12 @@ class Euler {
 
        }
 
-       set( x, y, z, order ) {
+       set( x, y, z, order = this._order ) {
 
                this._x = x;
                this._y = y;
                this._z = z;
-               this._order = order || this._order;
+               this._order = order;
 
                this._onChangeCallback();
 
@@ -10122,9 +9060,7 @@ class Euler {
 
        }
 
-       setFromRotationMatrix( m, order, update ) {
-
-               const clamp = MathUtils.clamp;
+       setFromRotationMatrix( m, order = this._order, update = true ) {
 
                // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
 
@@ -10133,13 +9069,11 @@ class Euler {
                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 ) );
+                               this._y = Math.asin( clamp$1( m13, - 1, 1 ) );
 
                                if ( Math.abs( m13 ) < 0.9999999 ) {
 
@@ -10157,7 +9091,7 @@ class Euler {
 
                        case 'YXZ':
 
-                               this._x = Math.asin( - clamp( m23, - 1, 1 ) );
+                               this._x = Math.asin( - clamp$1( m23, - 1, 1 ) );
 
                                if ( Math.abs( m23 ) < 0.9999999 ) {
 
@@ -10175,7 +9109,7 @@ class Euler {
 
                        case 'ZXY':
 
-                               this._x = Math.asin( clamp( m32, - 1, 1 ) );
+                               this._x = Math.asin( clamp$1( m32, - 1, 1 ) );
 
                                if ( Math.abs( m32 ) < 0.9999999 ) {
 
@@ -10193,7 +9127,7 @@ class Euler {
 
                        case 'ZYX':
 
-                               this._y = Math.asin( - clamp( m31, - 1, 1 ) );
+                               this._y = Math.asin( - clamp$1( m31, - 1, 1 ) );
 
                                if ( Math.abs( m31 ) < 0.9999999 ) {
 
@@ -10211,7 +9145,7 @@ class Euler {
 
                        case 'YZX':
 
-                               this._z = Math.asin( clamp( m21, - 1, 1 ) );
+                               this._z = Math.asin( clamp$1( m21, - 1, 1 ) );
 
                                if ( Math.abs( m21 ) < 0.9999999 ) {
 
@@ -10229,7 +9163,7 @@ class Euler {
 
                        case 'XZY':
 
-                               this._z = Math.asin( - clamp( m12, - 1, 1 ) );
+                               this._z = Math.asin( - clamp$1( m12, - 1, 1 ) );
 
                                if ( Math.abs( m12 ) < 0.9999999 ) {
 
@@ -10253,7 +9187,7 @@ class Euler {
 
                this._order = order;
 
-               if ( update !== false ) this._onChangeCallback();
+               if ( update === true ) this._onChangeCallback();
 
                return this;
 
@@ -10261,15 +9195,15 @@ class Euler {
 
        setFromQuaternion( q, order, update ) {
 
-               _matrix.makeRotationFromQuaternion( q );
+               _matrix$1.makeRotationFromQuaternion( q );
 
-               return this.setFromRotationMatrix( _matrix, order, update );
+               return this.setFromRotationMatrix( _matrix$1, order, update );
 
        }
 
-       setFromVector3( v, order ) {
+       setFromVector3( v, order = this._order ) {
 
-               return this.set( v.x, v.y, v.z, order || this._order );
+               return this.set( v.x, v.y, v.z, order );
 
        }
 
@@ -10277,9 +9211,9 @@ class Euler {
 
                // WARNING: this discards revolution information -bhouston
 
-               _quaternion$1.setFromEuler( this );
+               _quaternion$3.setFromEuler( this );
 
-               return this.setFromQuaternion( _quaternion$1, newOrder );
+               return this.setFromQuaternion( _quaternion$3, newOrder );
 
        }
 
@@ -10339,12 +9273,11 @@ class Euler {
 
 }
 
+Euler.prototype.isEuler = true;
+
 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() {
@@ -10399,119 +9332,115 @@ class Layers {
 
 let _object3DId = 0;
 
-const _v1$2 = new Vector3();
-const _q1 = new Quaternion();
-const _m1$1 = new Matrix4();
-const _target = new Vector3();
+const _v1$4 = /*@__PURE__*/ new Vector3();
+const _q1 = /*@__PURE__*/ new Quaternion();
+const _m1$1 = /*@__PURE__*/ new Matrix4();
+const _target = /*@__PURE__*/ new Vector3();
 
-const _position = new Vector3();
-const _scale = new Vector3();
-const _quaternion$2 = new Quaternion();
+const _position$3 = /*@__PURE__*/ new Vector3();
+const _scale$2 = /*@__PURE__*/ new Vector3();
+const _quaternion$2 = /*@__PURE__*/ new Quaternion();
 
-const _xAxis = new Vector3( 1, 0, 0 );
-const _yAxis = new Vector3( 0, 1, 0 );
-const _zAxis = new Vector3( 0, 0, 1 );
+const _xAxis = /*@__PURE__*/ new Vector3( 1, 0, 0 );
+const _yAxis = /*@__PURE__*/ new Vector3( 0, 1, 0 );
+const _zAxis = /*@__PURE__*/ new Vector3( 0, 0, 1 );
 
 const _addedEvent = { type: 'added' };
 const _removedEvent = { type: 'removed' };
 
-function Object3D() {
+class Object3D extends EventDispatcher {
 
-       Object.defineProperty( this, 'id', { value: _object3DId ++ } );
+       constructor() {
 
-       this.uuid = MathUtils.generateUUID();
+               super();
 
-       this.name = '';
-       this.type = 'Object3D';
+               Object.defineProperty( this, 'id', { value: _object3DId ++ } );
 
-       this.parent = null;
-       this.children = [];
+               this.uuid = generateUUID();
 
-       this.up = Object3D.DefaultUp.clone();
+               this.name = '';
+               this.type = 'Object3D';
 
-       const position = new Vector3();
-       const rotation = new Euler();
-       const quaternion = new Quaternion();
-       const scale = new Vector3( 1, 1, 1 );
+               this.parent = null;
+               this.children = [];
 
-       function onRotationChange() {
+               this.up = Object3D.DefaultUp.clone();
 
-               quaternion.setFromEuler( rotation, false );
+               const position = new Vector3();
+               const rotation = new Euler();
+               const quaternion = new Quaternion();
+               const scale = new Vector3( 1, 1, 1 );
 
-       }
+               function onRotationChange() {
 
-       function onQuaternionChange() {
+                       quaternion.setFromEuler( rotation, false );
 
-               rotation.setFromQuaternion( quaternion, undefined, false );
+               }
 
-       }
+               function onQuaternionChange() {
 
-       rotation._onChange( onRotationChange );
-       quaternion._onChange( onQuaternionChange );
+                       rotation.setFromQuaternion( quaternion, undefined, false );
 
-       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;
+               rotation._onChange( onRotationChange );
+               quaternion._onChange( onQuaternionChange );
 
-       this.layers = new Layers();
-       this.visible = true;
+               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.castShadow = false;
-       this.receiveShadow = false;
+               this.matrix = new Matrix4();
+               this.matrixWorld = new Matrix4();
 
-       this.frustumCulled = true;
-       this.renderOrder = 0;
+               this.matrixAutoUpdate = Object3D.DefaultMatrixAutoUpdate;
+               this.matrixWorldNeedsUpdate = false;
 
-       this.animations = [];
+               this.layers = new Layers();
+               this.visible = true;
 
-       this.userData = {};
+               this.castShadow = false;
+               this.receiveShadow = false;
 
-}
+               this.frustumCulled = true;
+               this.renderOrder = 0;
 
-Object3D.DefaultUp = new Vector3( 0, 1, 0 );
-Object3D.DefaultMatrixAutoUpdate = true;
+               this.animations = [];
 
-Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+               this.userData = {};
 
-       constructor: Object3D,
+       }
 
-       isObject3D: true,
+       onBeforeRender( /* renderer, scene, camera, geometry, material, group */ ) {}
 
-       onBeforeRender: function () {},
-       onAfterRender: function () {},
+       onAfterRender( /* renderer, scene, camera, geometry, material, group */ ) {}
 
-       applyMatrix4: function ( matrix ) {
+       applyMatrix4( matrix ) {
 
                if ( this.matrixAutoUpdate ) this.updateMatrix();
 
@@ -10519,47 +9448,47 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                this.matrix.decompose( this.position, this.quaternion, this.scale );
 
-       },
+       }
 
-       applyQuaternion: function ( q ) {
+       applyQuaternion( q ) {
 
                this.quaternion.premultiply( q );
 
                return this;
 
-       },
+       }
 
-       setRotationFromAxisAngle: function ( axis, angle ) {
+       setRotationFromAxisAngle( axis, angle ) {
 
                // assumes axis is normalized
 
                this.quaternion.setFromAxisAngle( axis, angle );
 
-       },
+       }
 
-       setRotationFromEuler: function ( euler ) {
+       setRotationFromEuler( euler ) {
 
                this.quaternion.setFromEuler( euler, true );
 
-       },
+       }
 
-       setRotationFromMatrix: function ( m ) {
+       setRotationFromMatrix( m ) {
 
                // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
 
                this.quaternion.setFromRotationMatrix( m );
 
-       },
+       }
 
-       setRotationFromQuaternion: function ( q ) {
+       setRotationFromQuaternion( q ) {
 
                // assumes q is normalized
 
                this.quaternion.copy( q );
 
-       },
+       }
 
-       rotateOnAxis: function ( axis, angle ) {
+       rotateOnAxis( axis, angle ) {
 
                // rotate object on axis in object space
                // axis is assumed to be normalized
@@ -10570,9 +9499,9 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                return this;
 
-       },
+       }
 
-       rotateOnWorldAxis: function ( axis, angle ) {
+       rotateOnWorldAxis( axis, angle ) {
 
                // rotate object on axis in world space
                // axis is assumed to be normalized
@@ -10584,70 +9513,70 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                return this;
 
-       },
+       }
 
-       rotateX: function ( angle ) {
+       rotateX( angle ) {
 
                return this.rotateOnAxis( _xAxis, angle );
 
-       },
+       }
 
-       rotateY: function ( angle ) {
+       rotateY( angle ) {
 
                return this.rotateOnAxis( _yAxis, angle );
 
-       },
+       }
 
-       rotateZ: function ( angle ) {
+       rotateZ( angle ) {
 
                return this.rotateOnAxis( _zAxis, angle );
 
-       },
+       }
 
-       translateOnAxis: function ( axis, distance ) {
+       translateOnAxis( axis, distance ) {
 
                // translate object by distance along axis in object space
                // axis is assumed to be normalized
 
-               _v1$2.copy( axis ).applyQuaternion( this.quaternion );
+               _v1$4.copy( axis ).applyQuaternion( this.quaternion );
 
-               this.position.add( _v1$2.multiplyScalar( distance ) );
+               this.position.add( _v1$4.multiplyScalar( distance ) );
 
                return this;
 
-       },
+       }
 
-       translateX: function ( distance ) {
+       translateX( distance ) {
 
                return this.translateOnAxis( _xAxis, distance );
 
-       },
+       }
 
-       translateY: function ( distance ) {
+       translateY( distance ) {
 
                return this.translateOnAxis( _yAxis, distance );
 
-       },
+       }
 
-       translateZ: function ( distance ) {
+       translateZ( distance ) {
 
                return this.translateOnAxis( _zAxis, distance );
 
-       },
+       }
 
-       localToWorld: function ( vector ) {
+       localToWorld( vector ) {
 
                return vector.applyMatrix4( this.matrixWorld );
 
-       },
+       }
 
-       worldToLocal: function ( vector ) {
+       worldToLocal( vector ) {
 
                return vector.applyMatrix4( _m1$1.copy( this.matrixWorld ).invert() );
 
-       },
+       }
 
-       lookAt: function ( x, y, z ) {
+       lookAt( x, y, z ) {
 
                // This method does not support objects having non-uniformly-scaled parent(s)
 
@@ -10665,15 +9594,15 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                this.updateWorldMatrix( true, false );
 
-               _position.setFromMatrixPosition( this.matrixWorld );
+               _position$3.setFromMatrixPosition( this.matrixWorld );
 
                if ( this.isCamera || this.isLight ) {
 
-                       _m1$1.lookAt( _position, _target, this.up );
+                       _m1$1.lookAt( _position$3, _target, this.up );
 
                } else {
 
-                       _m1$1.lookAt( _target, _position, this.up );
+                       _m1$1.lookAt( _target, _position$3, this.up );
 
                }
 
@@ -10687,9 +9616,9 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                }
 
-       },
+       }
 
-       add: function ( object ) {
+       add( object ) {
 
                if ( arguments.length > 1 ) {
 
@@ -10731,9 +9660,9 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                return this;
 
-       },
+       }
 
-       remove: function ( object ) {
+       remove( object ) {
 
                if ( arguments.length > 1 ) {
 
@@ -10760,9 +9689,23 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                return this;
 
-       },
+       }
 
-       clear: function () {
+       removeFromParent() {
+
+               const parent = this.parent;
+
+               if ( parent !== null ) {
+
+                       parent.remove( this );
+
+               }
+
+               return this;
+
+       }
+
+       clear() {
 
                for ( let i = 0; i < this.children.length; i ++ ) {
 
@@ -10779,9 +9722,9 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
                return this;
 
 
-       },
+       }
 
-       attach: function ( object ) {
+       attach( object ) {
 
                // adds object as a child of this, while maintaining the object's world transform
 
@@ -10799,27 +9742,27 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                object.applyMatrix4( _m1$1 );
 
-               object.updateWorldMatrix( false, false );
-
                this.add( object );
 
+               object.updateWorldMatrix( false, true );
+
                return this;
 
-       },
+       }
 
-       getObjectById: function ( id ) {
+       getObjectById( id ) {
 
                return this.getObjectByProperty( 'id', id );
 
-       },
+       }
 
-       getObjectByName: function ( name ) {
+       getObjectByName( name ) {
 
                return this.getObjectByProperty( 'name', name );
 
-       },
+       }
 
-       getObjectByProperty: function ( name, value ) {
+       getObjectByProperty( name, value ) {
 
                if ( this[ name ] === value ) return this;
 
@@ -10838,65 +9781,37 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                return undefined;
 
-       },
-
-       getWorldPosition: function ( target ) {
-
-               if ( target === undefined ) {
-
-                       console.warn( 'THREE.Object3D: .getWorldPosition() target is now required' );
-                       target = new Vector3();
+       }
 
-               }
+       getWorldPosition( target ) {
 
                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();
+       }
 
-               }
+       getWorldQuaternion( target ) {
 
                this.updateWorldMatrix( true, false );
 
-               this.matrixWorld.decompose( _position, target, _scale );
+               this.matrixWorld.decompose( _position$3, target, _scale$2 );
 
                return target;
 
-       },
-
-       getWorldScale: function ( target ) {
-
-               if ( target === undefined ) {
-
-                       console.warn( 'THREE.Object3D: .getWorldScale() target is now required' );
-                       target = new Vector3();
+       }
 
-               }
+       getWorldScale( target ) {
 
                this.updateWorldMatrix( true, false );
 
-               this.matrixWorld.decompose( _position, _quaternion$2, target );
+               this.matrixWorld.decompose( _position$3, _quaternion$2, target );
 
                return target;
 
-       },
-
-       getWorldDirection: function ( target ) {
-
-               if ( target === undefined ) {
-
-                       console.warn( 'THREE.Object3D: .getWorldDirection() target is now required' );
-                       target = new Vector3();
+       }
 
-               }
+       getWorldDirection( target ) {
 
                this.updateWorldMatrix( true, false );
 
@@ -10904,11 +9819,11 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize();
 
-       },
+       }
 
-       raycast: function () {},
+       raycast() {}
 
-       traverse: function ( callback ) {
+       traverse( callback ) {
 
                callback( this );
 
@@ -10920,9 +9835,9 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                }
 
-       },
+       }
 
-       traverseVisible: function ( callback ) {
+       traverseVisible( callback ) {
 
                if ( this.visible === false ) return;
 
@@ -10936,9 +9851,9 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                }
 
-       },
+       }
 
-       traverseAncestors: function ( callback ) {
+       traverseAncestors( callback ) {
 
                const parent = this.parent;
 
@@ -10950,17 +9865,17 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                }
 
-       },
+       }
 
-       updateMatrix: function () {
+       updateMatrix() {
 
                this.matrix.compose( this.position, this.quaternion, this.scale );
 
                this.matrixWorldNeedsUpdate = true;
 
-       },
+       }
 
-       updateMatrixWorld: function ( force ) {
+       updateMatrixWorld( force ) {
 
                if ( this.matrixAutoUpdate ) this.updateMatrix();
 
@@ -10992,9 +9907,9 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                }
 
-       },
+       }
 
-       updateWorldMatrix: function ( updateParents, updateChildren ) {
+       updateWorldMatrix( updateParents, updateChildren ) {
 
                const parent = this.parent;
 
@@ -11030,9 +9945,9 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                }
 
-       },
+       }
 
-       toJSON: function ( meta ) {
+       toJSON( meta ) {
 
                // meta is a string when called from JSON.stringify
                const isRootObject = ( meta === undefined || typeof meta === 'string' );
@@ -11090,6 +10005,7 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
                        object.type = 'InstancedMesh';
                        object.count = this.count;
                        object.instanceMatrix = this.instanceMatrix.toJSON();
+                       if ( this.instanceColor !== null ) object.instanceColor = this.instanceColor.toJSON();
 
                }
 
@@ -11107,7 +10023,29 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                }
 
-               if ( this.isMesh || this.isLine || this.isPoints ) {
+               if ( this.isScene ) {
+
+                       if ( this.background ) {
+
+                               if ( this.background.isColor ) {
+
+                                       object.background = this.background.toJSON();
+
+                               } else if ( this.background.isTexture ) {
+
+                                       object.background = this.background.toJSON( meta ).uuid;
+
+                               }
+
+                       }
+
+                       if ( this.environment && this.environment.isTexture ) {
+
+                               object.environment = this.environment.toJSON( meta ).uuid;
+
+                       }
+
+               } else if ( this.isMesh || this.isLine || this.isPoints ) {
 
                        object.geometry = serialize( meta.geometries, this.geometry );
 
@@ -11246,15 +10184,15 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
                }
 
-       },
+       }
 
-       clone: function ( recursive ) {
+       clone( recursive ) {
 
                return new this.constructor().copy( this, recursive );
 
-       },
+       }
 
-       copy: function ( source, recursive = true ) {
+       copy( source, recursive = true ) {
 
                this.name = source.name;
 
@@ -11297,234 +10235,17 @@ Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
        }
 
-} );
-
-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 );
+}
 
-       }
+Object3D.DefaultUp = new Vector3( 0, 1, 0 );
+Object3D.DefaultMatrixAutoUpdate = true;
 
-}
+Object3D.prototype.isObject3D = true;
 
 const _v0$1 = /*@__PURE__*/ new Vector3();
 const _v1$3 = /*@__PURE__*/ new Vector3();
-const _v2$1 = /*@__PURE__*/ new Vector3();
-const _v3 = /*@__PURE__*/ new Vector3();
+const _v2$2 = /*@__PURE__*/ new Vector3();
+const _v3$1 = /*@__PURE__*/ new Vector3();
 
 const _vab = /*@__PURE__*/ new Vector3();
 const _vac = /*@__PURE__*/ new Vector3();
@@ -11535,23 +10256,16 @@ const _vcp = /*@__PURE__*/ new Vector3();
 
 class Triangle {
 
-       constructor( a, b, c ) {
+       constructor( a = new Vector3(), b = new Vector3(), c = new Vector3() ) {
 
-               this.a = ( a !== undefined ) ? a : new Vector3();
-               this.b = ( b !== undefined ) ? b : new Vector3();
-               this.c = ( c !== undefined ) ? c : new Vector3();
+               this.a = a;
+               this.b = b;
+               this.c = c;
 
        }
 
        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 );
@@ -11573,23 +10287,16 @@ class Triangle {
 
                _v0$1.subVectors( c, a );
                _v1$3.subVectors( b, a );
-               _v2$1.subVectors( point, a );
+               _v2$2.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 dot02 = _v0$1.dot( _v2$2 );
                const dot11 = _v1$3.dot( _v1$3 );
-               const dot12 = _v1$3.dot( _v2$1 );
+               const dot12 = _v1$3.dot( _v2$2 );
 
                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 ) {
 
@@ -11610,20 +10317,20 @@ class Triangle {
 
        static containsPoint( point, a, b, c ) {
 
-               this.getBarycoord( point, a, b, c, _v3 );
+               this.getBarycoord( point, a, b, c, _v3$1 );
 
-               return ( _v3.x >= 0 ) && ( _v3.y >= 0 ) && ( ( _v3.x + _v3.y ) <= 1 );
+               return ( _v3$1.x >= 0 ) && ( _v3$1.y >= 0 ) && ( ( _v3$1.x + _v3$1.y ) <= 1 );
 
        }
 
        static getUV( point, p1, p2, p3, uv1, uv2, uv3, target ) {
 
-               this.getBarycoord( point, p1, p2, p3, _v3 );
+               this.getBarycoord( point, p1, p2, p3, _v3$1 );
 
                target.set( 0, 0 );
-               target.addScaledVector( uv1, _v3.x );
-               target.addScaledVector( uv2, _v3.y );
-               target.addScaledVector( uv3, _v3.z );
+               target.addScaledVector( uv1, _v3$1.x );
+               target.addScaledVector( uv2, _v3$1.y );
+               target.addScaledVector( uv3, _v3$1.z );
 
                return target;
 
@@ -11659,6 +10366,16 @@ class Triangle {
 
        }
 
+       setFromAttributeAndIndices( attribute, i0, i1, i2 ) {
+
+               this.a.fromBufferAttribute( attribute, i0 );
+               this.b.fromBufferAttribute( attribute, i1 );
+               this.c.fromBufferAttribute( attribute, i2 );
+
+               return this;
+
+       }
+
        clone() {
 
                return new this.constructor().copy( this );
@@ -11686,13 +10403,6 @@ class Triangle {
 
        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 );
 
        }
@@ -11705,13 +10415,6 @@ class Triangle {
 
        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 );
 
        }
@@ -11748,13 +10451,6 @@ class Triangle {
 
        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;
 
@@ -11842,6 +10538,492 @@ class Triangle {
 
 }
 
+let materialId = 0;
+
+class Material extends EventDispatcher {
+
+       constructor() {
+
+               super();
+
+               Object.defineProperty( this, 'id', { value: materialId ++ } );
+
+               this.uuid = generateUUID();
+
+               this.name = '';
+               this.type = 'Material';
+
+               this.fog = true;
+
+               this.blending = NormalBlending;
+               this.side = FrontSide;
+               this.vertexColors = false;
+
+               this.opacity = 1;
+               this.format = RGBAFormat;
+               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.alphaToCoverage = false;
+               this.premultipliedAlpha = false;
+
+               this.visible = true;
+
+               this.toneMapped = true;
+
+               this.userData = {};
+
+               this.version = 0;
+
+               this._alphaTest = 0;
+
+       }
+
+       get alphaTest() {
+
+               return this._alphaTest;
+
+       }
+
+       set alphaTest( value ) {
+
+               if ( this._alphaTest > 0 !== value > 0 ) {
+
+                       this.version ++;
+
+               }
+
+               this._alphaTest = value;
+
+       }
+
+       onBuild( /* shaderobject, renderer */ ) {}
+
+       onBeforeRender( /* renderer, scene, camera, geometry, object, group */ ) {}
+
+       onBeforeCompile( /* shaderobject, renderer */ ) {}
+
+       customProgramCacheKey() {
+
+               return this.onBeforeCompile.toString();
+
+       }
+
+       setValues( 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( 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 !== undefined ) data.sheen = this.sheen;
+               if ( this.sheenColor && this.sheenColor.isColor ) data.sheenColor = this.sheenColor.getHex();
+               if ( this.sheenRoughness !== undefined ) data.sheenRoughness = this.sheenRoughness;
+               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.specularIntensity !== undefined ) data.specularIntensity = this.specularIntensity;
+               if ( this.specularColor && this.specularColor.isColor ) data.specularColor = this.specularColor.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;
+                       data.lightMapIntensity = this.lightMapIntensity;
+
+               }
+
+               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.specularIntensityMap && this.specularIntensityMap.isTexture ) data.specularIntensityMap = this.specularIntensityMap.toJSON( meta ).uuid;
+               if ( this.specularColorMap && this.specularColorMap.isTexture ) data.specularColorMap = this.specularColorMap.toJSON( meta ).uuid;
+
+               if ( this.envMap && this.envMap.isTexture ) {
+
+                       data.envMap = this.envMap.toJSON( meta ).uuid;
+
+                       if ( this.combine !== undefined ) data.combine = this.combine;
+
+               }
+
+               if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity;
+               if ( this.reflectivity !== undefined ) data.reflectivity = this.reflectivity;
+               if ( this.refractionRatio !== undefined ) data.refractionRatio = this.refractionRatio;
+
+               if ( this.gradientMap && this.gradientMap.isTexture ) {
+
+                       data.gradientMap = this.gradientMap.toJSON( meta ).uuid;
+
+               }
+
+               if ( this.transmission !== undefined ) data.transmission = this.transmission;
+               if ( this.transmissionMap && this.transmissionMap.isTexture ) data.transmissionMap = this.transmissionMap.toJSON( meta ).uuid;
+               if ( this.thickness !== undefined ) data.thickness = this.thickness;
+               if ( this.thicknessMap && this.thicknessMap.isTexture ) data.thicknessMap = this.thicknessMap.toJSON( meta ).uuid;
+               if ( this.attenuationDistance !== undefined ) data.attenuationDistance = this.attenuationDistance;
+               if ( this.attenuationColor !== undefined ) data.attenuationColor = this.attenuationColor.getHex();
+
+               if ( this.size !== undefined ) data.size = this.size;
+               if ( this.shadowSide !== null ) data.shadowSide = this.shadowSide;
+               if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation;
+
+               if ( this.blending !== NormalBlending ) data.blending = this.blending;
+               if ( this.side !== FrontSide ) data.side = this.side;
+               if ( this.vertexColors ) data.vertexColors = true;
+
+               if ( this.opacity < 1 ) data.opacity = this.opacity;
+               if ( this.format !== RGBAFormat ) data.format = this.format;
+               if ( this.transparent === true ) data.transparent = this.transparent;
+
+               data.depthFunc = this.depthFunc;
+               data.depthTest = this.depthTest;
+               data.depthWrite = this.depthWrite;
+               data.colorWrite = this.colorWrite;
+
+               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.alphaToCoverage === true ) data.alphaToCoverage = this.alphaToCoverage;
+               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.flatShading === true ) data.flatShading = this.flatShading;
+
+               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() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       copy( source ) {
+
+               this.name = source.name;
+
+               this.fog = source.fog;
+
+               this.blending = source.blending;
+               this.side = source.side;
+               this.vertexColors = source.vertexColors;
+
+               this.opacity = source.opacity;
+               this.format = source.format;
+               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.alphaToCoverage = source.alphaToCoverage;
+               this.premultipliedAlpha = source.premultipliedAlpha;
+
+               this.visible = source.visible;
+
+               this.toneMapped = source.toneMapped;
+
+               this.userData = JSON.parse( JSON.stringify( source.userData ) );
+
+               return this;
+
+       }
+
+       dispose() {
+
+               this.dispatchEvent( { type: 'dispose' } );
+
+       }
+
+       set needsUpdate( value ) {
+
+               if ( value === true ) this.version ++;
+
+       }
+
+}
+
+Material.prototype.isMaterial = true;
+
 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,
@@ -11897,8 +11079,6 @@ class Color {
 
        constructor( r, g, b ) {
 
-               Object.defineProperty( this, 'isColor', { value: true } );
-
                if ( g === undefined && b === undefined ) {
 
                        // r is THREE.Color, hex or string
@@ -11965,9 +11145,9 @@ class Color {
        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 );
+               h = euclideanModulo( h, 1 );
+               s = clamp$1( s, 0, 1 );
+               l = clamp$1( l, 0, 1 );
 
                if ( s === 0 ) {
 
@@ -12108,7 +11288,7 @@ class Color {
        setColorName( style ) {
 
                // color keywords
-               const hex = _colorKeywords[ style ];
+               const hex = _colorKeywords[ style.toLowerCase() ];
 
                if ( hex !== undefined ) {
 
@@ -12232,13 +11412,6 @@ class Color {
 
                // 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 );
@@ -12381,9 +11554,9 @@ class Color {
                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 );
+               const h = lerp( _hslA.h, _hslB.h, alpha );
+               const s = lerp( _hslA.s, _hslB.s, alpha );
+               const l = lerp( _hslA.l, _hslB.l, alpha );
 
                this.setHSL( h, s, l );
 
@@ -12446,56 +11619,104 @@ class Color {
 }
 
 Color.NAMES = _colorKeywords;
+
+Color.prototype.isColor = true;
 Color.prototype.r = 1;
 Color.prototype.g = 1;
 Color.prototype.b = 1;
 
-class Face3 {
+/**
+ * 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>,
+ * }
+ */
+
+class MeshBasicMaterial extends Material {
 
-       constructor( a, b, c, normal, color, materialIndex = 0 ) {
+       constructor( parameters ) {
 
-               this.a = a;
-               this.b = b;
-               this.c = c;
+               super();
 
-               this.normal = ( normal && normal.isVector3 ) ? normal : new Vector3();
-               this.vertexNormals = Array.isArray( normal ) ? normal : [];
+               this.type = 'MeshBasicMaterial';
 
-               this.color = ( color && color.isColor ) ? color : new Color();
-               this.vertexColors = Array.isArray( color ) ? color : [];
+               this.color = new Color( 0xffffff ); // emissive
 
-               this.materialIndex = materialIndex;
+               this.map = null;
 
-       }
+               this.lightMap = null;
+               this.lightMapIntensity = 1.0;
 
-       clone() {
+               this.aoMap = null;
+               this.aoMapIntensity = 1.0;
 
-               return new this.constructor().copy( this );
+               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.setValues( parameters );
 
        }
 
        copy( source ) {
 
-               this.a = source.a;
-               this.b = source.b;
-               this.c = source.c;
+               super.copy( source );
 
-               this.normal.copy( source.normal );
                this.color.copy( source.color );
 
-               this.materialIndex = source.materialIndex;
+               this.map = source.map;
 
-               for ( let i = 0, il = source.vertexNormals.length; i < il; i ++ ) {
+               this.lightMap = source.lightMap;
+               this.lightMapIntensity = source.lightMapIntensity;
 
-                       this.vertexNormals[ i ] = source.vertexNormals[ i ].clone();
+               this.aoMap = source.aoMap;
+               this.aoMapIntensity = source.aoMapIntensity;
 
-               }
+               this.specularMap = source.specularMap;
 
-               for ( let i = 0, il = source.vertexColors.length; i < il; i ++ ) {
+               this.alphaMap = source.alphaMap;
 
-                       this.vertexColors[ i ] = source.vertexColors[ i ].clone();
+               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;
 
                return this;
 
@@ -12503,646 +11724,89 @@ class Face3 {
 
 }
 
-let materialId = 0;
+MeshBasicMaterial.prototype.isMeshBasicMaterial = true;
 
-function Material() {
+const _vector$9 = /*@__PURE__*/ new Vector3();
+const _vector2$1 = /*@__PURE__*/ new Vector2();
 
-       Object.defineProperty( this, 'id', { value: materialId ++ } );
+class BufferAttribute {
 
-       this.uuid = MathUtils.generateUUID();
+       constructor( array, itemSize, normalized ) {
 
-       this.name = '';
-       this.type = 'Material';
+               if ( Array.isArray( array ) ) {
 
-       this.fog = true;
+                       throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );
 
-       this.blending = NormalBlending;
-       this.side = FrontSide;
-       this.flatShading = false;
-       this.vertexColors = false;
+               }
 
-       this.opacity = 1;
-       this.transparent = false;
+               this.name = '';
 
-       this.blendSrc = SrcAlphaFactor;
-       this.blendDst = OneMinusSrcAlphaFactor;
-       this.blendEquation = AddEquation;
-       this.blendSrcAlpha = null;
-       this.blendDstAlpha = null;
-       this.blendEquationAlpha = null;
+               this.array = array;
+               this.itemSize = itemSize;
+               this.count = array !== undefined ? array.length / itemSize : 0;
+               this.normalized = normalized === true;
 
-       this.depthFunc = LessEqualDepth;
-       this.depthTest = true;
-       this.depthWrite = true;
+               this.usage = StaticDrawUsage;
+               this.updateRange = { offset: 0, count: - 1 };
 
-       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.version = 0;
 
-       this.clippingPlanes = null;
-       this.clipIntersection = false;
-       this.clipShadows = false;
+       }
 
-       this.shadowSide = null;
+       onUploadCallback() {}
 
-       this.colorWrite = true;
+       set needsUpdate( value ) {
 
-       this.precision = null; // override the renderer's default precision for this material
+               if ( value === true ) this.version ++;
 
-       this.polygonOffset = false;
-       this.polygonOffsetFactor = 0;
-       this.polygonOffsetUnits = 0;
+       }
 
-       this.dithering = false;
+       setUsage( value ) {
 
-       this.alphaTest = 0;
-       this.premultipliedAlpha = false;
+               this.usage = value;
 
-       this.visible = true;
+               return this;
 
-       this.toneMapped = true;
+       }
 
-       this.userData = {};
+       copy( source ) {
 
-       this.version = 0;
+               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;
 
-Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+               return this;
 
-       constructor: Material,
+       }
 
-       isMaterial: true,
+       copyAt( index1, attribute, index2 ) {
 
-       onBeforeCompile: function ( /* shaderobject, renderer */ ) {},
+               index1 *= this.itemSize;
+               index2 *= attribute.itemSize;
 
-       customProgramCacheKey: function () {
+               for ( let i = 0, l = this.itemSize; i < l; i ++ ) {
 
-               return this.onBeforeCompile.toString();
+                       this.array[ index1 + i ] = attribute.array[ index2 + i ];
 
-       },
+               }
 
-       setValues: function ( values ) {
+               return this;
 
-               if ( values === undefined ) return;
+       }
 
-               for ( const key in values ) {
+       copyArray( array ) {
 
-                       const newValue = values[ key ];
+               this.array.set( array );
 
-                       if ( newValue === undefined ) {
+               return this;
 
-                               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 ) {
+       copyColorsArray( colors ) {
 
                const array = this.array;
                let offset = 0;
@@ -13166,9 +11830,9 @@ Object.assign( BufferAttribute.prototype, {
 
                return this;
 
-       },
+       }
 
-       copyVector2sArray: function ( vectors ) {
+       copyVector2sArray( vectors ) {
 
                const array = this.array;
                let offset = 0;
@@ -13191,9 +11855,9 @@ Object.assign( BufferAttribute.prototype, {
 
                return this;
 
-       },
+       }
 
-       copyVector3sArray: function ( vectors ) {
+       copyVector3sArray( vectors ) {
 
                const array = this.array;
                let offset = 0;
@@ -13217,9 +11881,9 @@ Object.assign( BufferAttribute.prototype, {
 
                return this;
 
-       },
+       }
 
-       copyVector4sArray: function ( vectors ) {
+       copyVector4sArray( vectors ) {
 
                const array = this.array;
                let offset = 0;
@@ -13244,9 +11908,9 @@ Object.assign( BufferAttribute.prototype, {
 
                return this;
 
-       },
+       }
 
-       applyMatrix3: function ( m ) {
+       applyMatrix3( m ) {
 
                if ( this.itemSize === 2 ) {
 
@@ -13263,10 +11927,10 @@ Object.assign( BufferAttribute.prototype, {
 
                        for ( let i = 0, l = this.count; i < l; i ++ ) {
 
-                               _vector$3.fromBufferAttribute( this, i );
-                               _vector$3.applyMatrix3( m );
+                               _vector$9.fromBufferAttribute( this, i );
+                               _vector$9.applyMatrix3( m );
 
-                               this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+                               this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z );
 
                        }
 
@@ -13274,127 +11938,127 @@ Object.assign( BufferAttribute.prototype, {
 
                return this;
 
-       },
+       }
 
-       applyMatrix4: function ( m ) {
+       applyMatrix4( 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$9.x = this.getX( i );
+                       _vector$9.y = this.getY( i );
+                       _vector$9.z = this.getZ( i );
 
-                       _vector$3.applyMatrix4( m );
+                       _vector$9.applyMatrix4( m );
 
-                       this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+                       this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z );
 
                }
 
                return this;
 
-       },
+       }
 
-       applyNormalMatrix: function ( m ) {
+       applyNormalMatrix( 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$9.x = this.getX( i );
+                       _vector$9.y = this.getY( i );
+                       _vector$9.z = this.getZ( i );
 
-                       _vector$3.applyNormalMatrix( m );
+                       _vector$9.applyNormalMatrix( m );
 
-                       this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+                       this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z );
 
                }
 
                return this;
 
-       },
+       }
 
-       transformDirection: function ( m ) {
+       transformDirection( 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$9.x = this.getX( i );
+                       _vector$9.y = this.getY( i );
+                       _vector$9.z = this.getZ( i );
 
-                       _vector$3.transformDirection( m );
+                       _vector$9.transformDirection( m );
 
-                       this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+                       this.setXYZ( i, _vector$9.x, _vector$9.y, _vector$9.z );
 
                }
 
                return this;
 
-       },
+       }
 
-       set: function ( value, offset = 0 ) {
+       set( value, offset = 0 ) {
 
                this.array.set( value, offset );
 
                return this;
 
-       },
+       }
 
-       getX: function ( index ) {
+       getX( index ) {
 
                return this.array[ index * this.itemSize ];
 
-       },
+       }
 
-       setX: function ( index, x ) {
+       setX( index, x ) {
 
                this.array[ index * this.itemSize ] = x;
 
                return this;
 
-       },
+       }
 
-       getY: function ( index ) {
+       getY( index ) {
 
                return this.array[ index * this.itemSize + 1 ];
 
-       },
+       }
 
-       setY: function ( index, y ) {
+       setY( index, y ) {
 
                this.array[ index * this.itemSize + 1 ] = y;
 
                return this;
 
-       },
+       }
 
-       getZ: function ( index ) {
+       getZ( index ) {
 
                return this.array[ index * this.itemSize + 2 ];
 
-       },
+       }
 
-       setZ: function ( index, z ) {
+       setZ( index, z ) {
 
                this.array[ index * this.itemSize + 2 ] = z;
 
                return this;
 
-       },
+       }
 
-       getW: function ( index ) {
+       getW( index ) {
 
                return this.array[ index * this.itemSize + 3 ];
 
-       },
+       }
 
-       setW: function ( index, w ) {
+       setW( index, w ) {
 
                this.array[ index * this.itemSize + 3 ] = w;
 
                return this;
 
-       },
+       }
 
-       setXY: function ( index, x, y ) {
+       setXY( index, x, y ) {
 
                index *= this.itemSize;
 
@@ -13403,9 +12067,9 @@ Object.assign( BufferAttribute.prototype, {
 
                return this;
 
-       },
+       }
 
-       setXYZ: function ( index, x, y, z ) {
+       setXYZ( index, x, y, z ) {
 
                index *= this.itemSize;
 
@@ -13415,9 +12079,9 @@ Object.assign( BufferAttribute.prototype, {
 
                return this;
 
-       },
+       }
 
-       setXYZW: function ( index, x, y, z, w ) {
+       setXYZW( index, x, y, z, w ) {
 
                index *= this.itemSize;
 
@@ -13428,218 +12092,131 @@ Object.assign( BufferAttribute.prototype, {
 
                return this;
 
-       },
+       }
 
-       onUpload: function ( callback ) {
+       onUpload( callback ) {
 
                this.onUploadCallback = callback;
 
                return this;
 
-       },
+       }
 
-       clone: function () {
+       clone() {
 
                return new this.constructor( this.array, this.itemSize ).copy( this );
 
-       },
+       }
 
-       toJSON: function () {
+       toJSON() {
 
-               return {
+               const data = {
                        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;
-
+               if ( this.name !== '' ) data.name = this.name;
+               if ( this.usage !== StaticDrawUsage ) data.usage = this.usage;
+               if ( this.updateRange.offset !== 0 || this.updateRange.count !== - 1 ) data.updateRange = this.updateRange;
 
-function Uint8ClampedBufferAttribute( array, itemSize, normalized ) {
+               return data;
 
-       BufferAttribute.call( this, new Uint8ClampedArray( array ), itemSize, normalized );
+       }
 
 }
 
-Uint8ClampedBufferAttribute.prototype = Object.create( BufferAttribute.prototype );
-Uint8ClampedBufferAttribute.prototype.constructor = Uint8ClampedBufferAttribute;
-
-
-function Int16BufferAttribute( array, itemSize, normalized ) {
+BufferAttribute.prototype.isBufferAttribute = true;
 
-       BufferAttribute.call( this, new Int16Array( array ), itemSize, normalized );
-
-}
+class Uint16BufferAttribute extends BufferAttribute {
 
-Int16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
-Int16BufferAttribute.prototype.constructor = Int16BufferAttribute;
+       constructor( array, itemSize, normalized ) {
 
+               super( new Uint16Array( array ), itemSize, normalized );
 
-function Uint16BufferAttribute( array, itemSize, normalized ) {
-
-       BufferAttribute.call( this, new Uint16Array( array ), itemSize, normalized );
+       }
 
 }
 
-Uint16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
-Uint16BufferAttribute.prototype.constructor = Uint16BufferAttribute;
+class Uint32BufferAttribute extends BufferAttribute {
 
+       constructor( array, itemSize, normalized ) {
 
-function Int32BufferAttribute( array, itemSize, normalized ) {
+               super( new Uint32Array( array ), itemSize, normalized );
 
-       BufferAttribute.call( this, new Int32Array( array ), itemSize, normalized );
+       }
 
 }
 
-Int32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
-Int32BufferAttribute.prototype.constructor = Int32BufferAttribute;
-
+class Float16BufferAttribute extends BufferAttribute {
 
-function Uint32BufferAttribute( array, itemSize, normalized ) {
+       constructor( array, itemSize, normalized ) {
 
-       BufferAttribute.call( this, new Uint32Array( array ), itemSize, normalized );
+               super( new Uint16Array( 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;
+class Float32BufferAttribute extends BufferAttribute {
 
+       constructor( array, itemSize, normalized ) {
 
-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 ];
+               super( new Float32Array( array ), itemSize, normalized );
 
        }
 
-       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() {
+const _m1 = /*@__PURE__*/ new Matrix4();
+const _obj = /*@__PURE__*/ new Object3D();
+const _offset = /*@__PURE__*/ new Vector3();
+const _box$1 = /*@__PURE__*/ new Box3();
+const _boxMorphTargets = /*@__PURE__*/ new Box3();
+const _vector$8 = /*@__PURE__*/ new Vector3();
 
-       Object.defineProperty( this, 'id', { value: _id ++ } );
+class BufferGeometry extends EventDispatcher {
 
-       this.uuid = MathUtils.generateUUID();
+       constructor() {
 
-       this.name = '';
-       this.type = 'BufferGeometry';
+               super();
 
-       this.index = null;
-       this.attributes = {};
+               Object.defineProperty( this, 'id', { value: _id ++ } );
 
-       this.morphAttributes = {};
-       this.morphTargetsRelative = false;
+               this.uuid = generateUUID();
 
-       this.groups = [];
+               this.name = '';
+               this.type = 'BufferGeometry';
 
-       this.boundingBox = null;
-       this.boundingSphere = null;
+               this.index = null;
+               this.attributes = {};
 
-       this.drawRange = { start: 0, count: Infinity };
+               this.morphAttributes = {};
+               this.morphTargetsRelative = false;
 
-       this.userData = {};
+               this.groups = [];
 
-}
+               this.boundingBox = null;
+               this.boundingSphere = null;
 
-BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+               this.drawRange = { start: 0, count: Infinity };
 
-       constructor: BufferGeometry,
+               this.userData = {};
 
-       isBufferGeometry: true,
+       }
 
-       getIndex: function () {
+       getIndex() {
 
                return this.index;
 
-       },
+       }
 
-       setIndex: function ( index ) {
+       setIndex( index ) {
 
                if ( Array.isArray( index ) ) {
 
@@ -13653,37 +12230,37 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return this;
 
-       },
+       }
 
-       getAttribute: function ( name ) {
+       getAttribute( name ) {
 
                return this.attributes[ name ];
 
-       },
+       }
 
-       setAttribute: function ( name, attribute ) {
+       setAttribute( name, attribute ) {
 
                this.attributes[ name ] = attribute;
 
                return this;
 
-       },
+       }
 
-       deleteAttribute: function ( name ) {
+       deleteAttribute( name ) {
 
                delete this.attributes[ name ];
 
                return this;
 
-       },
+       }
 
-       hasAttribute: function ( name ) {
+       hasAttribute( name ) {
 
                return this.attributes[ name ] !== undefined;
 
-       },
+       }
 
-       addGroup: function ( start, count, materialIndex = 0 ) {
+       addGroup( start, count, materialIndex = 0 ) {
 
                this.groups.push( {
 
@@ -13693,22 +12270,22 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                } );
 
-       },
+       }
 
-       clearGroups: function () {
+       clearGroups() {
 
                this.groups = [];
 
-       },
+       }
 
-       setDrawRange: function ( start, count ) {
+       setDrawRange( start, count ) {
 
                this.drawRange.start = start;
                this.drawRange.count = count;
 
-       },
+       }
 
-       applyMatrix4: function ( matrix ) {
+       applyMatrix4( matrix ) {
 
                const position = this.attributes.position;
 
@@ -13756,69 +12333,79 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return this;
 
-       },
+       }
 
-       rotateX: function ( angle ) {
+       applyQuaternion( q ) {
+
+               _m1.makeRotationFromQuaternion( q );
+
+               this.applyMatrix4( _m1 );
+
+               return this;
+
+       }
+
+       rotateX( angle ) {
 
                // rotate geometry around world x-axis
 
-               _m1$2.makeRotationX( angle );
+               _m1.makeRotationX( angle );
 
-               this.applyMatrix4( _m1$2 );
+               this.applyMatrix4( _m1 );
 
                return this;
 
-       },
+       }
 
-       rotateY: function ( angle ) {
+       rotateY( angle ) {
 
                // rotate geometry around world y-axis
 
-               _m1$2.makeRotationY( angle );
+               _m1.makeRotationY( angle );
 
-               this.applyMatrix4( _m1$2 );
+               this.applyMatrix4( _m1 );
 
                return this;
 
-       },
+       }
 
-       rotateZ: function ( angle ) {
+       rotateZ( angle ) {
 
                // rotate geometry around world z-axis
 
-               _m1$2.makeRotationZ( angle );
+               _m1.makeRotationZ( angle );
 
-               this.applyMatrix4( _m1$2 );
+               this.applyMatrix4( _m1 );
 
                return this;
 
-       },
+       }
 
-       translate: function ( x, y, z ) {
+       translate( x, y, z ) {
 
                // translate geometry
 
-               _m1$2.makeTranslation( x, y, z );
+               _m1.makeTranslation( x, y, z );
 
-               this.applyMatrix4( _m1$2 );
+               this.applyMatrix4( _m1 );
 
                return this;
 
-       },
+       }
 
-       scale: function ( x, y, z ) {
+       scale( x, y, z ) {
 
                // scale geometry
 
-               _m1$2.makeScale( x, y, z );
+               _m1.makeScale( x, y, z );
 
-               this.applyMatrix4( _m1$2 );
+               this.applyMatrix4( _m1 );
 
                return this;
 
-       },
+       }
 
-       lookAt: function ( vector ) {
+       lookAt( vector ) {
 
                _obj.lookAt( vector );
 
@@ -13828,9 +12415,9 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return this;
 
-       },
+       }
 
-       center: function () {
+       center() {
 
                this.computeBoundingBox();
 
@@ -13840,9 +12427,9 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return this;
 
-       },
+       }
 
-       setFromPoints: function ( points ) {
+       setFromPoints( points ) {
 
                const position = [];
 
@@ -13857,9 +12444,9 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return this;
 
-       },
+       }
 
-       computeBoundingBox: function () {
+       computeBoundingBox() {
 
                if ( this.boundingBox === null ) {
 
@@ -13894,20 +12481,20 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
                                for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
 
                                        const morphAttribute = morphAttributesPosition[ i ];
-                                       _box$2.setFromBufferAttribute( morphAttribute );
+                                       _box$1.setFromBufferAttribute( morphAttribute );
 
                                        if ( this.morphTargetsRelative ) {
 
-                                               _vector$4.addVectors( this.boundingBox.min, _box$2.min );
-                                               this.boundingBox.expandByPoint( _vector$4 );
+                                               _vector$8.addVectors( this.boundingBox.min, _box$1.min );
+                                               this.boundingBox.expandByPoint( _vector$8 );
 
-                                               _vector$4.addVectors( this.boundingBox.max, _box$2.max );
-                                               this.boundingBox.expandByPoint( _vector$4 );
+                                               _vector$8.addVectors( this.boundingBox.max, _box$1.max );
+                                               this.boundingBox.expandByPoint( _vector$8 );
 
                                        } else {
 
-                                               this.boundingBox.expandByPoint( _box$2.min );
-                                               this.boundingBox.expandByPoint( _box$2.max );
+                                               this.boundingBox.expandByPoint( _box$1.min );
+                                               this.boundingBox.expandByPoint( _box$1.max );
 
                                        }
 
@@ -13927,9 +12514,9 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
-       },
+       }
 
-       computeBoundingSphere: function () {
+       computeBoundingSphere() {
 
                if ( this.boundingSphere === null ) {
 
@@ -13956,7 +12543,7 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                        const center = this.boundingSphere.center;
 
-                       _box$2.setFromBufferAttribute( position );
+                       _box$1.setFromBufferAttribute( position );
 
                        // process morph attributes if present
 
@@ -13969,16 +12556,16 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                                        if ( this.morphTargetsRelative ) {
 
-                                               _vector$4.addVectors( _box$2.min, _boxMorphTargets.min );
-                                               _box$2.expandByPoint( _vector$4 );
+                                               _vector$8.addVectors( _box$1.min, _boxMorphTargets.min );
+                                               _box$1.expandByPoint( _vector$8 );
 
-                                               _vector$4.addVectors( _box$2.max, _boxMorphTargets.max );
-                                               _box$2.expandByPoint( _vector$4 );
+                                               _vector$8.addVectors( _box$1.max, _boxMorphTargets.max );
+                                               _box$1.expandByPoint( _vector$8 );
 
                                        } else {
 
-                                               _box$2.expandByPoint( _boxMorphTargets.min );
-                                               _box$2.expandByPoint( _boxMorphTargets.max );
+                                               _box$1.expandByPoint( _boxMorphTargets.min );
+                                               _box$1.expandByPoint( _boxMorphTargets.max );
 
                                        }
 
@@ -13986,7 +12573,7 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                        }
 
-                       _box$2.getCenter( center );
+                       _box$1.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
@@ -13995,9 +12582,9 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                        for ( let i = 0, il = position.count; i < il; i ++ ) {
 
-                               _vector$4.fromBufferAttribute( position, i );
+                               _vector$8.fromBufferAttribute( position, i );
 
-                               maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) );
+                               maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$8 ) );
 
                        }
 
@@ -14012,16 +12599,16 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                                        for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) {
 
-                                               _vector$4.fromBufferAttribute( morphAttribute, j );
+                                               _vector$8.fromBufferAttribute( morphAttribute, j );
 
                                                if ( morphTargetsRelative ) {
 
                                                        _offset.fromBufferAttribute( position, j );
-                                                       _vector$4.add( _offset );
+                                                       _vector$8.add( _offset );
 
                                                }
 
-                                               maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) );
+                                               maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$8 ) );
 
                                        }
 
@@ -14039,15 +12626,9 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
-       },
-
-       computeFaceNormals: function () {
-
-               // backwards compatibility
-
-       },
+       }
 
-       computeTangents: function () {
+       computeTangents() {
 
                const index = this.index;
                const attributes = this.attributes;
@@ -14210,9 +12791,9 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
-       },
+       }
 
-       computeVertexNormals: function () {
+       computeVertexNormals() {
 
                const index = this.index;
                const positionAttribute = this.getAttribute( 'position' );
@@ -14302,9 +12883,9 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
-       },
+       }
 
-       merge: function ( geometry, offset ) {
+       merge( geometry, offset ) {
 
                if ( ! ( geometry && geometry.isBufferGeometry ) ) {
 
@@ -14349,25 +12930,25 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return this;
 
-       },
+       }
 
-       normalizeNormals: function () {
+       normalizeNormals() {
 
                const normals = this.attributes.normal;
 
                for ( let i = 0, il = normals.count; i < il; i ++ ) {
 
-                       _vector$4.fromBufferAttribute( normals, i );
+                       _vector$8.fromBufferAttribute( normals, i );
 
-                       _vector$4.normalize();
+                       _vector$8.normalize();
 
-                       normals.setXYZ( i, _vector$4.x, _vector$4.y, _vector$4.z );
+                       normals.setXYZ( i, _vector$8.x, _vector$8.y, _vector$8.z );
 
                }
 
-       },
+       }
 
-       toNonIndexed: function () {
+       toNonIndexed() {
 
                function convertBufferAttribute( attribute, indices ) {
 
@@ -14381,7 +12962,15 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                        for ( let i = 0, l = indices.length; i < l; i ++ ) {
 
-                               index = indices[ i ] * itemSize;
+                               if ( attribute.isInterleavedBufferAttribute ) {
+
+                                       index = indices[ i ] * attribute.data.stride + attribute.offset;
+
+                               } else {
+
+                                       index = indices[ i ] * itemSize;
+
+                               }
 
                                for ( let j = 0; j < itemSize; j ++ ) {
 
@@ -14459,9 +13048,9 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return geometry2;
 
-       },
+       }
 
-       toJSON: function () {
+       toJSON() {
 
                const data = {
                        metadata: {
@@ -14492,6 +13081,8 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
+               // for simplicity the code assumes attributes are not shared across geometries, see #15811
+
                data.data = { attributes: {} };
 
                const index = this.index;
@@ -14511,11 +13102,7 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                        const attribute = attributes[ key ];
 
-                       const attributeData = attribute.toJSON( data.data );
-
-                       if ( attribute.name !== '' ) attributeData.name = attribute.name;
-
-                       data.data.attributes[ key ] = attributeData;
+                       data.data.attributes[ key ] = attribute.toJSON( data.data );
 
                }
 
@@ -14532,11 +13119,7 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                                const attribute = attributeArray[ i ];
 
-                               const attributeData = attribute.toJSON( data.data );
-
-                               if ( attribute.name !== '' ) attributeData.name = attribute.name;
-
-                               array.push( attributeData );
+                               array.push( attribute.toJSON( data.data ) );
 
                        }
 
@@ -14578,39 +13161,15 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                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;
+       }
 
-                }
+       clone() {
 
                 return new this.constructor().copy( this );
-                */
-
-               return new BufferGeometry().copy( this );
 
-       },
+       }
 
-       copy: function ( source ) {
+       copy( source ) {
 
                // reset
 
@@ -14711,63 +13270,65 @@ BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                this.userData = source.userData;
 
+               // geometry generator parameters
+
+               if ( source.parameters !== undefined ) this.parameters = Object.assign( {}, source.parameters );
+
                return this;
 
-       },
+       }
 
-       dispose: function () {
+       dispose() {
 
                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();
+BufferGeometry.prototype.isBufferGeometry = true;
 
-const _tempA = new Vector3();
-const _tempB = new Vector3();
-const _tempC = new Vector3();
+const _inverseMatrix$2 = /*@__PURE__*/ new Matrix4();
+const _ray$2 = /*@__PURE__*/ new Ray();
+const _sphere$3 = /*@__PURE__*/ new Sphere();
 
-const _morphA = new Vector3();
-const _morphB = new Vector3();
-const _morphC = new Vector3();
+const _vA$1 = /*@__PURE__*/ new Vector3();
+const _vB$1 = /*@__PURE__*/ new Vector3();
+const _vC$1 = /*@__PURE__*/ new Vector3();
 
-const _uvA = new Vector2();
-const _uvB = new Vector2();
-const _uvC = new Vector2();
+const _tempA = /*@__PURE__*/ new Vector3();
+const _tempB = /*@__PURE__*/ new Vector3();
+const _tempC = /*@__PURE__*/ new Vector3();
 
-const _intersectionPoint = new Vector3();
-const _intersectionPointWorld = new Vector3();
+const _morphA = /*@__PURE__*/ new Vector3();
+const _morphB = /*@__PURE__*/ new Vector3();
+const _morphC = /*@__PURE__*/ new Vector3();
 
-function Mesh( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) {
+const _uvA$1 = /*@__PURE__*/ new Vector2();
+const _uvB$1 = /*@__PURE__*/ new Vector2();
+const _uvC$1 = /*@__PURE__*/ new Vector2();
 
-       Object3D.call( this );
+const _intersectionPoint = /*@__PURE__*/ new Vector3();
+const _intersectionPointWorld = /*@__PURE__*/ new Vector3();
 
-       this.type = 'Mesh';
+class Mesh extends Object3D {
 
-       this.geometry = geometry;
-       this.material = material;
+       constructor( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) {
 
-       this.updateMorphTargets();
+               super();
 
-}
+               this.type = 'Mesh';
 
-Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
+               this.geometry = geometry;
+               this.material = material;
 
-       constructor: Mesh,
+               this.updateMorphTargets();
 
-       isMesh: true,
+       }
 
-       copy: function ( source ) {
+       copy( source ) {
 
-               Object3D.prototype.copy.call( this, source );
+               super.copy( source );
 
                if ( source.morphTargetInfluences !== undefined ) {
 
@@ -14786,9 +13347,9 @@ Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
 
                return this;
 
-       },
+       }
 
-       updateMorphTargets: function () {
+       updateMorphTargets() {
 
                const geometry = this.geometry;
 
@@ -14831,9 +13392,9 @@ Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
 
                }
 
-       },
+       }
 
-       raycast: function ( raycaster, intersects ) {
+       raycast( raycaster, intersects ) {
 
                const geometry = this.geometry;
                const material = this.material;
@@ -14845,21 +13406,21 @@ Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
 
                if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
 
-               _sphere.copy( geometry.boundingSphere );
-               _sphere.applyMatrix4( matrixWorld );
+               _sphere$3.copy( geometry.boundingSphere );
+               _sphere$3.applyMatrix4( matrixWorld );
 
-               if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return;
+               if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return;
 
                //
 
-               _inverseMatrix.copy( matrixWorld ).invert();
-               _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
+               _inverseMatrix$2.copy( matrixWorld ).invert();
+               _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 );
 
                // Check boundingBox before continuing
 
                if ( geometry.boundingBox !== null ) {
 
-                       if ( _ray.intersectsBox( geometry.boundingBox ) === false ) return;
+                       if ( _ray$2.intersectsBox( geometry.boundingBox ) === false ) return;
 
                }
 
@@ -14888,7 +13449,7 @@ Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
                                                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 ) );
+                                               const end = Math.min( index.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) );
 
                                                for ( let j = start, jl = end; j < jl; j += 3 ) {
 
@@ -14896,7 +13457,7 @@ Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
                                                        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 );
+                                                       intersection = checkBufferGeometryIntersection( this, groupMaterial, raycaster, _ray$2, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
 
                                                        if ( intersection ) {
 
@@ -14921,7 +13482,7 @@ Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
                                                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 );
+                                               intersection = checkBufferGeometryIntersection( this, material, raycaster, _ray$2, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
 
                                                if ( intersection ) {
 
@@ -14946,7 +13507,7 @@ Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
                                                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 ) );
+                                               const end = Math.min( position.count, Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) ) );
 
                                                for ( let j = start, jl = end; j < jl; j += 3 ) {
 
@@ -14954,7 +13515,7 @@ Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
                                                        const b = j + 1;
                                                        const c = j + 2;
 
-                                                       intersection = checkBufferGeometryIntersection( this, groupMaterial, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
+                                                       intersection = checkBufferGeometryIntersection( this, groupMaterial, raycaster, _ray$2, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
 
                                                        if ( intersection ) {
 
@@ -14979,7 +13540,7 @@ Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
                                                const b = i + 1;
                                                const c = i + 2;
 
-                                               intersection = checkBufferGeometryIntersection( this, material, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
+                                               intersection = checkBufferGeometryIntersection( this, material, raycaster, _ray$2, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
 
                                                if ( intersection ) {
 
@@ -15002,7 +13563,9 @@ Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
 
        }
 
-} );
+}
+
+Mesh.prototype.isMesh = true;
 
 function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) {
 
@@ -15037,13 +13600,13 @@ function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point
 
 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 );
+       _vA$1.fromBufferAttribute( position, a );
+       _vB$1.fromBufferAttribute( position, b );
+       _vC$1.fromBufferAttribute( position, c );
 
        const morphInfluences = object.morphTargetInfluences;
 
-       if ( material.morphTargets && morphPosition && morphInfluences ) {
+       if ( morphPosition && morphInfluences ) {
 
                _morphA.set( 0, 0, 0 );
                _morphB.set( 0, 0, 0 );
@@ -15068,54 +13631,61 @@ function checkBufferGeometryIntersection( object, material, raycaster, ray, posi
 
                        } else {
 
-                               _morphA.addScaledVector( _tempA.sub( _vA ), influence );
-                               _morphB.addScaledVector( _tempB.sub( _vB ), influence );
-                               _morphC.addScaledVector( _tempC.sub( _vC ), influence );
+                               _morphA.addScaledVector( _tempA.sub( _vA$1 ), influence );
+                               _morphB.addScaledVector( _tempB.sub( _vB$1 ), influence );
+                               _morphC.addScaledVector( _tempC.sub( _vC$1 ), influence );
 
                        }
 
                }
 
-               _vA.add( _morphA );
-               _vB.add( _morphB );
-               _vC.add( _morphC );
+               _vA$1.add( _morphA );
+               _vB$1.add( _morphB );
+               _vC$1.add( _morphC );
 
        }
 
        if ( object.isSkinnedMesh ) {
 
-               object.boneTransform( a, _vA );
-               object.boneTransform( b, _vB );
-               object.boneTransform( c, _vC );
+               object.boneTransform( a, _vA$1 );
+               object.boneTransform( b, _vB$1 );
+               object.boneTransform( c, _vC$1 );
 
        }
 
-       const intersection = checkIntersection( object, material, raycaster, ray, _vA, _vB, _vC, _intersectionPoint );
+       const intersection = checkIntersection( object, material, raycaster, ray, _vA$1, _vB$1, _vC$1, _intersectionPoint );
 
        if ( intersection ) {
 
                if ( uv ) {
 
-                       _uvA.fromBufferAttribute( uv, a );
-                       _uvB.fromBufferAttribute( uv, b );
-                       _uvC.fromBufferAttribute( uv, c );
+                       _uvA$1.fromBufferAttribute( uv, a );
+                       _uvB$1.fromBufferAttribute( uv, b );
+                       _uvC$1.fromBufferAttribute( uv, c );
 
-                       intersection.uv = Triangle.getUV( _intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() );
+                       intersection.uv = Triangle.getUV( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() );
 
                }
 
                if ( uv2 ) {
 
-                       _uvA.fromBufferAttribute( uv2, a );
-                       _uvB.fromBufferAttribute( uv2, b );
-                       _uvC.fromBufferAttribute( uv2, c );
+                       _uvA$1.fromBufferAttribute( uv2, a );
+                       _uvB$1.fromBufferAttribute( uv2, b );
+                       _uvC$1.fromBufferAttribute( uv2, c );
 
-                       intersection.uv2 = Triangle.getUV( _intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() );
+                       intersection.uv2 = Triangle.getUV( _intersectionPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() );
 
                }
 
-               const face = new Face3( a, b, c );
-               Triangle.getNormal( _vA, _vB, _vC, face.normal );
+               const face = {
+                       a: a,
+                       b: b,
+                       c: c,
+                       normal: new Vector3(),
+                       materialIndex: 0
+               };
+
+               Triangle.getNormal( _vA$1, _vB$1, _vC$1, face.normal );
 
                intersection.face = face;
 
@@ -15282,6 +13852,12 @@ class BoxGeometry extends BufferGeometry {
 
        }
 
+       static fromJSON( data ) {
+
+               return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments );
+
+       }
+
 }
 
 /**
@@ -15303,7 +13879,7 @@ function cloneUniforms( src ) {
                        if ( property && ( property.isColor ||
                                property.isMatrix3 || property.isMatrix4 ||
                                property.isVector2 || property.isVector3 || property.isVector4 ||
-                               property.isTexture ) ) {
+                               property.isTexture || property.isQuaternion ) ) {
 
                                dst[ u ][ p ] = property.clone();
 
@@ -15364,222 +13940,206 @@ var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0
  *  wireframe: <boolean>,
  *  wireframeLinewidth: <float>,
  *
- *  lights: <bool>,
- *
- *  skinning: <bool>,
- *  morphTargets: <bool>,
- *  morphNormals: <bool>
+ *  lights: <bool>
  * }
  */
 
-function ShaderMaterial( parameters ) {
-
-       Material.call( this );
-
-       this.type = 'ShaderMaterial';
+class ShaderMaterial extends Material {
 
-       this.defines = {};
-       this.uniforms = {};
+       constructor( parameters ) {
 
-       this.vertexShader = default_vertex;
-       this.fragmentShader = default_fragment;
+               super();
 
-       this.linewidth = 1;
+               this.type = 'ShaderMaterial';
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
+               this.defines = {};
+               this.uniforms = {};
 
-       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.vertexShader = default_vertex;
+               this.fragmentShader = default_fragment;
 
-       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.linewidth = 1;
 
-       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
-       };
+               this.wireframe = false;
+               this.wireframeLinewidth = 1;
 
-       // 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.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.index0AttributeName = undefined;
-       this.uniformsNeedUpdate = false;
-
-       this.glslVersion = null;
+               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
+               };
 
-       if ( parameters !== undefined ) {
+               // 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 ]
+               };
 
-               if ( parameters.attributes !== undefined ) {
+               this.index0AttributeName = undefined;
+               this.uniformsNeedUpdate = false;
 
-                       console.error( 'THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.' );
+               this.glslVersion = null;
 
-               }
+               if ( parameters !== undefined ) {
 
-               this.setValues( parameters );
+                       if ( parameters.attributes !== undefined ) {
 
-       }
+                               console.error( 'THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.' );
 
-}
+                       }
 
-ShaderMaterial.prototype = Object.create( Material.prototype );
-ShaderMaterial.prototype.constructor = ShaderMaterial;
+                       this.setValues( parameters );
 
-ShaderMaterial.prototype.isShaderMaterial = true;
+               }
 
-ShaderMaterial.prototype.copy = function ( source ) {
+       }
 
-       Material.prototype.copy.call( this, source );
+       copy( source ) {
 
-       this.fragmentShader = source.fragmentShader;
-       this.vertexShader = source.vertexShader;
+               super.copy( source );
 
-       this.uniforms = cloneUniforms( source.uniforms );
+               this.fragmentShader = source.fragmentShader;
+               this.vertexShader = source.vertexShader;
 
-       this.defines = Object.assign( {}, source.defines );
+               this.uniforms = cloneUniforms( source.uniforms );
 
-       this.wireframe = source.wireframe;
-       this.wireframeLinewidth = source.wireframeLinewidth;
+               this.defines = Object.assign( {}, source.defines );
 
-       this.lights = source.lights;
-       this.clipping = source.clipping;
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
 
-       this.skinning = source.skinning;
+               this.lights = source.lights;
+               this.clipping = source.clipping;
 
-       this.morphTargets = source.morphTargets;
-       this.morphNormals = source.morphNormals;
+               this.extensions = Object.assign( {}, source.extensions );
 
-       this.extensions = Object.assign( {}, source.extensions );
+               this.glslVersion = source.glslVersion;
 
-       this.glslVersion = source.glslVersion;
+               return this;
 
-       return this;
+       }
 
-};
+       toJSON( meta ) {
 
-ShaderMaterial.prototype.toJSON = function ( meta ) {
+               const data = super.toJSON( meta );
 
-       const data = Material.prototype.toJSON.call( this, meta );
+               data.glslVersion = this.glslVersion;
+               data.uniforms = {};
 
-       data.glslVersion = this.glslVersion;
-       data.uniforms = {};
+               for ( const name in this.uniforms ) {
 
-       for ( const name in this.uniforms ) {
+                       const uniform = this.uniforms[ name ];
+                       const value = uniform.value;
 
-               const uniform = this.uniforms[ name ];
-               const value = uniform.value;
+                       if ( value && value.isTexture ) {
 
-               if ( value && value.isTexture ) {
+                               data.uniforms[ name ] = {
+                                       type: 't',
+                                       value: value.toJSON( meta ).uuid
+                               };
 
-                       data.uniforms[ name ] = {
-                               type: 't',
-                               value: value.toJSON( meta ).uuid
-                       };
+                       } else if ( value && value.isColor ) {
 
-               } else if ( value && value.isColor ) {
+                               data.uniforms[ name ] = {
+                                       type: 'c',
+                                       value: value.getHex()
+                               };
 
-                       data.uniforms[ name ] = {
-                               type: 'c',
-                               value: value.getHex()
-                       };
+                       } else if ( value && value.isVector2 ) {
 
-               } else if ( value && value.isVector2 ) {
+                               data.uniforms[ name ] = {
+                                       type: 'v2',
+                                       value: value.toArray()
+                               };
 
-                       data.uniforms[ name ] = {
-                               type: 'v2',
-                               value: value.toArray()
-                       };
+                       } else if ( value && value.isVector3 ) {
 
-               } else if ( value && value.isVector3 ) {
+                               data.uniforms[ name ] = {
+                                       type: 'v3',
+                                       value: value.toArray()
+                               };
 
-                       data.uniforms[ name ] = {
-                               type: 'v3',
-                               value: value.toArray()
-                       };
+                       } else if ( value && value.isVector4 ) {
 
-               } else if ( value && value.isVector4 ) {
+                               data.uniforms[ name ] = {
+                                       type: 'v4',
+                                       value: value.toArray()
+                               };
 
-                       data.uniforms[ name ] = {
-                               type: 'v4',
-                               value: value.toArray()
-                       };
+                       } else if ( value && value.isMatrix3 ) {
 
-               } else if ( value && value.isMatrix3 ) {
+                               data.uniforms[ name ] = {
+                                       type: 'm3',
+                                       value: value.toArray()
+                               };
 
-                       data.uniforms[ name ] = {
-                               type: 'm3',
-                               value: value.toArray()
-                       };
+                       } else if ( value && value.isMatrix4 ) {
 
-               } else if ( value && value.isMatrix4 ) {
+                               data.uniforms[ name ] = {
+                                       type: 'm4',
+                                       value: value.toArray()
+                               };
 
-                       data.uniforms[ name ] = {
-                               type: 'm4',
-                               value: value.toArray()
-                       };
+                       } else {
 
-               } else {
+                               data.uniforms[ name ] = {
+                                       value: value
+                               };
 
-                       data.uniforms[ name ] = {
-                               value: value
-                       };
+                               // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far
 
-                       // 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;
 
-       if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines;
+               data.vertexShader = this.vertexShader;
+               data.fragmentShader = this.fragmentShader;
 
-       data.vertexShader = this.vertexShader;
-       data.fragmentShader = this.fragmentShader;
+               const extensions = {};
 
-       const extensions = {};
+               for ( const key in this.extensions ) {
 
-       for ( const key in this.extensions ) {
+                       if ( this.extensions[ key ] === true ) extensions[ key ] = true;
 
-               if ( this.extensions[ key ] === true ) extensions[ key ] = true;
-
-       }
+               }
 
-       if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions;
+               if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions;
 
-       return data;
+               return data;
 
-};
+       }
 
-function Camera$1() {
+}
 
-       Object3D.call( this );
+ShaderMaterial.prototype.isShaderMaterial = true;
 
-       this.type = 'Camera';
+class Camera$1 extends Object3D {
 
-       this.matrixWorldInverse = new Matrix4();
+       constructor() {
 
-       this.projectionMatrix = new Matrix4();
-       this.projectionMatrixInverse = new Matrix4();
+               super();
 
-}
+               this.type = 'Camera';
 
-Camera$1.prototype = Object.assign( Object.create( Object3D.prototype ), {
+               this.matrixWorldInverse = new Matrix4();
 
-       constructor: Camera$1,
+               this.projectionMatrix = new Matrix4();
+               this.projectionMatrixInverse = new Matrix4();
 
-       isCamera: true,
+       }
 
-       copy: function ( source, recursive ) {
+       copy( source, recursive ) {
 
-               Object3D.prototype.copy.call( this, source, recursive );
+               super.copy( source, recursive );
 
                this.matrixWorldInverse.copy( source.matrixWorldInverse );
 
@@ -15588,16 +14148,9 @@ Camera$1.prototype = Object.assign( Object.create( Object3D.prototype ), {
 
                return this;
 
-       },
-
-       getWorldDirection: function ( target ) {
-
-               if ( target === undefined ) {
-
-                       console.warn( 'THREE.Camera: .getWorldDirection() target is now required' );
-                       target = new Vector3();
+       }
 
-               }
+       getWorldDirection( target ) {
 
                this.updateWorldMatrix( true, false );
 
@@ -15605,64 +14158,62 @@ Camera$1.prototype = Object.assign( Object.create( Object3D.prototype ), {
 
                return target.set( - e[ 8 ], - e[ 9 ], - e[ 10 ] ).normalize();
 
-       },
+       }
 
-       updateMatrixWorld: function ( force ) {
+       updateMatrixWorld( force ) {
 
-               Object3D.prototype.updateMatrixWorld.call( this, force );
+               super.updateMatrixWorld( force );
 
                this.matrixWorldInverse.copy( this.matrixWorld ).invert();
 
-       },
+       }
 
-       updateWorldMatrix: function ( updateParents, updateChildren ) {
+       updateWorldMatrix( updateParents, updateChildren ) {
 
-               Object3D.prototype.updateWorldMatrix.call( this, updateParents, updateChildren );
+               super.updateWorldMatrix( updateParents, updateChildren );
 
                this.matrixWorldInverse.copy( this.matrixWorld ).invert();
 
-       },
+       }
 
-       clone: function () {
+       clone() {
 
                return new this.constructor().copy( this );
 
        }
 
-} );
-
-function PerspectiveCamera( fov = 50, aspect = 1, near = 0.1, far = 2000 ) {
+}
 
-       Camera$1.call( this );
+Camera$1.prototype.isCamera = true;
 
-       this.type = 'PerspectiveCamera';
+class PerspectiveCamera extends Camera$1 {
 
-       this.fov = fov;
-       this.zoom = 1;
+       constructor( fov = 50, aspect = 1, near = 0.1, far = 2000 ) {
 
-       this.near = near;
-       this.far = far;
-       this.focus = 10;
+               super();
 
-       this.aspect = aspect;
-       this.view = null;
+               this.type = 'PerspectiveCamera';
 
-       this.filmGauge = 35;    // width of the film (default in millimeters)
-       this.filmOffset = 0;    // horizontal film offset (same unit as gauge)
+               this.fov = fov;
+               this.zoom = 1;
 
-       this.updateProjectionMatrix();
+               this.near = near;
+               this.far = far;
+               this.focus = 10;
 
-}
+               this.aspect = aspect;
+               this.view = null;
 
-PerspectiveCamera.prototype = Object.assign( Object.create( Camera$1.prototype ), {
+               this.filmGauge = 35;    // width of the film (default in millimeters)
+               this.filmOffset = 0;    // horizontal film offset (same unit as gauge)
 
-       constructor: PerspectiveCamera,
+               this.updateProjectionMatrix();
 
-       isPerspectiveCamera: true,
+       }
 
-       copy: function ( source, recursive ) {
+       copy( source, recursive ) {
 
-               Camera$1.prototype.copy.call( this, source, recursive );
+               super.copy( source, recursive );
 
                this.fov = source.fov;
                this.zoom = source.zoom;
@@ -15679,7 +14230,7 @@ PerspectiveCamera.prototype = Object.assign( Object.create( Camera$1.prototype )
 
                return this;
 
-       },
+       }
 
        /**
         * Sets the FOV by focal length in respect to the current .filmGauge.
@@ -15689,47 +14240,47 @@ PerspectiveCamera.prototype = Object.assign( Object.create( Camera$1.prototype )
         *
         * Values for focal length and film gauge must have the same unit.
         */
-       setFocalLength: function ( focalLength ) {
+       setFocalLength( 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.fov = RAD2DEG$1 * 2 * Math.atan( vExtentSlope );
                this.updateProjectionMatrix();
 
-       },
+       }
 
        /**
         * Calculates the focal length from the current .fov and .filmGauge.
         */
-       getFocalLength: function () {
+       getFocalLength() {
 
-               const vExtentSlope = Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov );
+               const vExtentSlope = Math.tan( DEG2RAD$1 * 0.5 * this.fov );
 
                return 0.5 * this.getFilmHeight() / vExtentSlope;
 
-       },
+       }
 
-       getEffectiveFOV: function () {
+       getEffectiveFOV() {
 
-               return MathUtils.RAD2DEG * 2 * Math.atan(
-                       Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov ) / this.zoom );
+               return RAD2DEG$1 * 2 * Math.atan(
+                       Math.tan( DEG2RAD$1 * 0.5 * this.fov ) / this.zoom );
 
-       },
+       }
 
-       getFilmWidth: function () {
+       getFilmWidth() {
 
                // film not completely covered in portrait format (aspect < 1)
                return this.filmGauge * Math.min( this.aspect, 1 );
 
-       },
+       }
 
-       getFilmHeight: function () {
+       getFilmHeight() {
 
                // 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
@@ -15766,7 +14317,7 @@ PerspectiveCamera.prototype = Object.assign( Object.create( Camera$1.prototype )
         *
         *   Note there is no reason monitors have to be the same size or in a grid.
         */
-       setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {
+       setViewOffset( fullWidth, fullHeight, x, y, width, height ) {
 
                this.aspect = fullWidth / fullHeight;
 
@@ -15794,9 +14345,9 @@ PerspectiveCamera.prototype = Object.assign( Object.create( Camera$1.prototype )
 
                this.updateProjectionMatrix();
 
-       },
+       }
 
-       clearViewOffset: function () {
+       clearViewOffset() {
 
                if ( this.view !== null ) {
 
@@ -15806,12 +14357,12 @@ PerspectiveCamera.prototype = Object.assign( Object.create( Camera$1.prototype )
 
                this.updateProjectionMatrix();
 
-       },
+       }
 
-       updateProjectionMatrix: function () {
+       updateProjectionMatrix() {
 
                const near = this.near;
-               let top = near * Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov ) / this.zoom;
+               let top = near * Math.tan( DEG2RAD$1 * 0.5 * this.fov ) / this.zoom;
                let height = 2 * top;
                let width = this.aspect * height;
                let left = - 0.5 * width;
@@ -15836,11 +14387,11 @@ PerspectiveCamera.prototype = Object.assign( Object.create( Camera$1.prototype )
 
                this.projectionMatrixInverse.copy( this.projectionMatrix ).invert();
 
-       },
+       }
 
-       toJSON: function ( meta ) {
+       toJSON( meta ) {
 
-               const data = Object3D.prototype.toJSON.call( this, meta );
+               const data = super.toJSON( meta );
 
                data.object.fov = this.fov;
                data.object.zoom = this.zoom;
@@ -15860,65 +14411,75 @@ PerspectiveCamera.prototype = Object.assign( Object.create( Camera$1.prototype )
 
        }
 
-} );
+}
+
+PerspectiveCamera.prototype.isPerspectiveCamera = true;
 
 const fov = 90, aspect = 1;
 
-function CubeCamera( near, far, renderTarget ) {
+class CubeCamera extends Object3D {
 
-       Object3D.call( this );
+       constructor( near, far, renderTarget ) {
 
-       this.type = 'CubeCamera';
+               super();
 
-       if ( renderTarget.isWebGLCubeRenderTarget !== true ) {
+               this.type = 'CubeCamera';
 
-               console.error( 'THREE.CubeCamera: The constructor now expects an instance of WebGLCubeRenderTarget as third parameter.' );
-               return;
+               if ( renderTarget.isWebGLCubeRenderTarget !== true ) {
 
-       }
+                       console.error( 'THREE.CubeCamera: The constructor now expects an instance of WebGLCubeRenderTarget as third parameter.' );
+                       return;
+
+               }
 
-       this.renderTarget = renderTarget;
+               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 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 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 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 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 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 );
+               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 ) {
+       }
+
+       update( renderer, scene ) {
 
                if ( this.parent === null ) this.updateMatrixWorld();
 
+               const renderTarget = this.renderTarget;
+
+               const [ cameraPX, cameraNX, cameraPY, cameraNY, cameraPZ, cameraNZ ] = this.children;
+
                const currentXrEnabled = renderer.xr.enabled;
                const currentRenderTarget = renderer.getRenderTarget();
 
@@ -15952,57 +14513,38 @@ function CubeCamera( near, far, renderTarget ) {
 
                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 );
+class CubeTexture extends Texture {
 
-       this.flipY = false;
+       constructor( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) {
 
-       // 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;
-
-}
+               images = images !== undefined ? images : [];
+               mapping = mapping !== undefined ? mapping : CubeReflectionMapping;
 
-CubeTexture.prototype = Object.create( Texture.prototype );
-CubeTexture.prototype.constructor = CubeTexture;
+               super( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
 
-CubeTexture.prototype.isCubeTexture = true;
+               this.flipY = false;
 
-Object.defineProperty( CubeTexture.prototype, 'images', {
+       }
 
-       get: function () {
+       get images() {
 
                return this.image;
 
-       },
+       }
 
-       set: function ( value ) {
+       set images( value ) {
 
                this.image = value;
 
        }
 
-} );
+}
+
+CubeTexture.prototype.isCubeTexture = true;
 
 class WebGLCubeRenderTarget extends WebGLRenderTarget {
 
@@ -16018,11 +14560,21 @@ class WebGLCubeRenderTarget extends WebGLRenderTarget {
 
                super( size, size, options );
 
-               Object.defineProperty( this, 'isWebGLCubeRenderTarget', { value: true } );
-
                options = options || {};
 
+               // 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 isRenderTargetTexture controls this conversion. The flip is not required when using WebGLCubeRenderTarget.texture
+               // as a cube texture (this is detected when isRenderTargetTexture is set to true for cube textures).
+
                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.isRenderTargetTexture = true;
+
+               this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false;
+               this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter;
 
                this.texture._needsFlipEnvMap = false;
 
@@ -16137,45 +14689,217 @@ class WebGLCubeRenderTarget extends WebGLRenderTarget {
 
 }
 
-function DataTexture( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {
+WebGLCubeRenderTarget.prototype.isWebGLCubeRenderTarget = true;
+
+const _vector1 = /*@__PURE__*/ new Vector3();
+const _vector2 = /*@__PURE__*/ new Vector3();
+const _normalMatrix = /*@__PURE__*/ new Matrix3();
+
+class Plane {
 
-       Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
+       constructor( normal = new Vector3( 1, 0, 0 ), constant = 0 ) {
 
-       this.image = { data: data || null, width: width || 1, height: height || 1 };
+               // normal is assumed to be normalized
 
-       this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
-       this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;
+               this.normal = normal;
+               this.constant = constant;
 
-       this.generateMipmaps = false;
-       this.flipY = false;
-       this.unpackAlignment = 1;
+       }
 
-       this.needsUpdate = true;
+       set( normal, constant ) {
 
-}
+               this.normal.copy( normal );
+               this.constant = constant;
 
-DataTexture.prototype = Object.create( Texture.prototype );
-DataTexture.prototype.constructor = DataTexture;
+               return this;
 
-DataTexture.prototype.isDataTexture = true;
+       }
 
-const _sphere$1 = /*@__PURE__*/ new Sphere();
-const _vector$5 = /*@__PURE__*/ new Vector3();
+       setComponents( x, y, z, w ) {
 
-class Frustum {
+               this.normal.set( x, y, z );
+               this.constant = w;
 
-       constructor( p0, p1, p2, p3, p4, p5 ) {
+               return this;
 
-               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()
+       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;
+
+       }
+
+       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 ) {
+
+               return target.copy( this.normal ).multiplyScalar( - this.distanceToPoint( point ) ).add( point );
+
+       }
+
+       intersectLine( line, target ) {
+
+               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 null;
+
+               }
+
+               const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator;
+
+               if ( t < 0 || t > 1 ) {
+
+                       return null;
+
+               }
+
+               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 ) {
+
+               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 );
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+}
+
+Plane.prototype.isPlane = true;
+
+const _sphere$2 = /*@__PURE__*/ new Sphere();
+const _vector$7 = /*@__PURE__*/ new Vector3();
+
+class Frustum {
+
+       constructor( p0 = new Plane(), p1 = new Plane(), p2 = new Plane(), p3 = new Plane(), p4 = new Plane(), p5 = new Plane() ) {
+
+               this.planes = [ p0, p1, p2, p3, p4, p5 ];
 
        }
 
@@ -16194,12 +14918,6 @@ class Frustum {
 
        }
 
-       clone() {
-
-               return new this.constructor().copy( this );
-
-       }
-
        copy( frustum ) {
 
                const planes = this.planes;
@@ -16240,19 +14958,19 @@ class Frustum {
 
                if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
 
-               _sphere$1.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld );
+               _sphere$2.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld );
 
-               return this.intersectsSphere( _sphere$1 );
+               return this.intersectsSphere( _sphere$2 );
 
        }
 
        intersectsSprite( sprite ) {
 
-               _sphere$1.center.set( 0, 0, 0 );
-               _sphere$1.radius = 0.7071067811865476;
-               _sphere$1.applyMatrix4( sprite.matrixWorld );
+               _sphere$2.center.set( 0, 0, 0 );
+               _sphere$2.radius = 0.7071067811865476;
+               _sphere$2.applyMatrix4( sprite.matrixWorld );
 
-               return this.intersectsSphere( _sphere$1 );
+               return this.intersectsSphere( _sphere$2 );
 
        }
 
@@ -16288,11 +15006,11 @@ class Frustum {
 
                        // 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;
+                       _vector$7.x = plane.normal.x > 0 ? box.max.x : box.min.x;
+                       _vector$7.y = plane.normal.y > 0 ? box.max.y : box.min.y;
+                       _vector$7.z = plane.normal.z > 0 ? box.max.z : box.min.z;
 
-                       if ( plane.distanceToPoint( _vector$5 ) < 0 ) {
+                       if ( plane.distanceToPoint( _vector$7 ) < 0 ) {
 
                                return false;
 
@@ -16322,6 +15040,12 @@ class Frustum {
 
        }
 
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
 }
 
 function WebGLAnimation() {
@@ -16444,6 +15168,10 @@ function WebGLAttributes( gl, capabilities ) {
 
                        type = 5121;
 
+               } else if ( array instanceof Uint8ClampedArray ) {
+
+                       type = 5121;
+
                }
 
                return {
@@ -16638,15 +15366,23 @@ class PlaneGeometry extends BufferGeometry {
 
        }
 
+       static fromJSON( data ) {
+
+               return new PlaneGeometry( data.width, data.height, data.widthSegments, data.heightSegments );
+
+       }
+
 }
 
 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 alphatest_fragment = "#ifdef USE_ALPHATEST\n\tif ( diffuseColor.a < alphaTest ) discard;\n#endif";
+
+var alphatest_pars_fragment = "#ifdef USE_ALPHATEST\n\tuniform float alphaTest;\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_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.roughness );\n\t#endif\n#endif";
 
 var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif";
 
@@ -16654,9 +15390,9 @@ 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 bsdfs = "vec3 BRDF_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 f0, const in float f90, const in float dotVH ) {\n\tfloat fresnel = exp2( ( - 5.55473 * dotVH - 6.98316 ) * dotVH );\n\treturn f0 * ( 1.0 - fresnel ) + ( f90 * fresnel );\n}\nfloat V_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_GGX( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 f0, const in float f90, const in float roughness ) {\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( f0, f90, dotVH );\n\tfloat V = V_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\tfloat D = D_GGX( alpha, dotNH );\n\treturn F * ( V * 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}\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_BlinnPhong( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotVH = saturate( dot( viewDir, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, 1.0, dotVH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie( float roughness, float dotNH ) {\n\tfloat alpha = pow2( roughness );\n\tfloat invAlpha = 1.0 / alpha;\n\tfloat cos2h = dotNH * dotNH;\n\tfloat sin2h = max( 1.0 - cos2h, 0.0078125 );\n\treturn ( 2.0 + invAlpha ) * pow( sin2h, invAlpha * 0.5 ) / ( 2.0 * PI );\n}\nfloat V_Neubelt( float dotNV, float dotNL ) {\n\treturn saturate( 1.0 / ( 4.0 * ( dotNL + dotNV - dotNL * dotNV ) ) );\n}\nvec3 BRDF_Sheen( const in vec3 lightDir, const in vec3 viewDir, const in vec3 normal, vec3 sheenColor, const in float sheenRoughness ) {\n\tvec3 halfDir = normalize( lightDir + viewDir );\n\tfloat dotNL = saturate( dot( normal, lightDir ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat D = D_Charlie( sheenRoughness, dotNH );\n\tfloat V = V_Neubelt( dotNV, dotNL );\n\treturn sheenColor * ( D * V );\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 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, float faceDirection ) {\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 ) * faceDirection;\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";
 
@@ -16666,15 +15402,15 @@ var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 v
 
 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_fragment = "#if defined( USE_COLOR_ALPHA )\n\tdiffuseColor *= vColor;\n#elif defined( USE_COLOR )\n\tdiffuseColor.rgb *= vColor;\n#endif";
 
-var color_pars_fragment = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif";
+var color_pars_fragment = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif defined( 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_pars_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvarying vec4 vColor;\n#elif 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 color_vertex = "#if defined( USE_COLOR_ALPHA )\n\tvColor = vec4( 1.0 );\n#elif defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor *= color;\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 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 max3( const in vec3 v ) { return max( max( v.x, v.y ), v.z ); }\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 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 USE_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}\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";
 
@@ -16692,7 +15428,7 @@ 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_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\tenvColor = envMapTexelToLinear( envColor );\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#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";
 
@@ -16702,41 +15438,41 @@ var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || def
 
 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_vertex = "#ifdef USE_FOG\n\tvFogDepth = - mvPosition.z;\n#endif";
 
-var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float fogDepth;\n#endif";
+var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float vFogDepth;\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_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * vFogDepth * vFogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, vFogDepth );\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 fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float vFogDepth;\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_fragment = "#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel = texture2D( lightMap, vUv2 );\n\tvec3 lightMapIrradiance = lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tlightMapIrradiance *= PI;\n\t#endif\n\treflectedLight.indirectDiffuse += lightMapIrradiance;\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_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.normal );\n#ifdef DOUBLE_SIDED\n\tvIndirectBack += getAmbientLightIrradiance( ambientLightColor );\n\tvIndirectBack += getLightProbeIrradiance( lightProbe, backGeometry.normal );\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\tgetPointLightInfo( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = 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\tgetSpotLightInfo( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = 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\tgetDirectionalLightInfo( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = 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.normal );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry.normal );\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 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 vec3 normal ) {\n\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\treturn irradiance;\n}\nfloat getDistanceAttenuation( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n\t#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\t\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\t\tif ( cutoffDistance > 0.0 ) {\n\t\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t\t}\n\t\treturn distanceFalloff;\n\t#else\n\t\tif ( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\t\treturn pow( saturate( - lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t\t}\n\t\treturn 1.0;\n\t#endif\n}\nfloat getSpotAttenuation( const in float coneCosine, const in float penumbraCosine, const in float angleCosine ) {\n\treturn smoothstep( coneCosine, penumbraCosine, angleCosine );\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 getDirectionalLightInfo( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tlight.color = directionalLight.color;\n\t\tlight.direction = directionalLight.direction;\n\t\tlight.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 getPointLightInfo( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tlight.color = pointLight.color;\n\t\tlight.color *= getDistanceAttenuation( lightDistance, pointLight.distance, pointLight.decay );\n\t\tlight.visible = ( light.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 getSpotLightInfo( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight light ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tlight.direction = normalize( lVector );\n\t\tfloat angleCos = dot( light.direction, spotLight.direction );\n\t\tfloat spotAttenuation = getSpotAttenuation( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\tif ( spotAttenuation > 0.0 ) {\n\t\t\tfloat lightDistance = length( lVector );\n\t\t\tlight.color = spotLight.color * spotAttenuation;\n\t\t\tlight.color *= getDistanceAttenuation( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tlight.visible = ( light.color != vec3( 0.0 ) );\n\t\t} else {\n\t\t\tlight.color = vec3( 0.0 );\n\t\t\tlight.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 vec3 normal ) {\n\t\tfloat dotNL = dot( normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\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 envmap_physical_pars_fragment = "#if defined( USE_ENVMAP )\n\t#ifdef ENVMAP_MODE_REFRACTION\n\t\tuniform float refractionRatio;\n\t#endif\n\tvec3 getIBLIrradiance( const in vec3 normal ) {\n\t\t#if defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\n\t}\n\tvec3 getIBLRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness ) {\n\t\t#if defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec3 reflectVec;\n\t\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\t\treflectVec = reflect( - viewDir, normal );\n\t\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t\t#else\n\t\t\t\treflectVec = refract( - viewDir, normal, refractionRatio );\n\t\t\t#endif\n\t\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t\treturn envMapColor.rgb * envMapIntensity;\n\t\t#else\n\t\t\treturn vec3( 0.0 );\n\t\t#endif\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_toon_pars_fragment = "varying vec3 vViewPosition;\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\treflectedLight.directDiffuse += irradiance * BRDF_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_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_phong_pars_fragment = "varying vec3 vViewPosition;\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\treflectedLight.directDiffuse += irradiance * BRDF_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_BlinnPhong( directLight.direction, geometry.viewDir, geometry.normal, 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_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_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.roughness = max( roughnessFactor, 0.0525 );material.roughness += geometryRoughness;\nmaterial.roughness = min( material.roughness, 1.0 );\n#ifdef IOR\n\t#ifdef SPECULAR\n\t\tfloat specularIntensityFactor = specularIntensity;\n\t\tvec3 specularColorFactor = specularColor;\n\t\t#ifdef USE_SPECULARINTENSITYMAP\n\t\t\tspecularIntensityFactor *= texture2D( specularIntensityMap, vUv ).a;\n\t\t#endif\n\t\t#ifdef USE_SPECULARCOLORMAP\n\t\t\tspecularColorFactor *= specularColorMapTexelToLinear( texture2D( specularColorMap, vUv ) ).rgb;\n\t\t#endif\n\t\tmaterial.specularF90 = mix( specularIntensityFactor, 1.0, metalnessFactor );\n\t#else\n\t\tfloat specularIntensityFactor = 1.0;\n\t\tvec3 specularColorFactor = vec3( 1.0 );\n\t\tmaterial.specularF90 = 1.0;\n\t#endif\n\tmaterial.specularColor = mix( min( pow2( ( ior - 1.0 ) / ( ior + 1.0 ) ) * specularColorFactor, vec3( 1.0 ) ) * specularIntensityFactor, diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( 0.04 ), diffuseColor.rgb, metalnessFactor );\n\tmaterial.specularF90 = 1.0;\n#endif\n#ifdef USE_CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\tmaterial.clearcoatF0 = vec3( 0.04 );\n\tmaterial.clearcoatF90 = 1.0;\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 = sheenColor;\n\t#ifdef USE_SHEENCOLORMAP\n\t\tmaterial.sheenColor *= sheenColorMapTexelToLinear( texture2D( sheenColorMap, vUv ) ).rgb;\n\t#endif\n\tmaterial.sheenRoughness = clamp( sheenRoughness, 0.07, 1.0 );\n\t#ifdef USE_SHEENROUGHNESSMAP\n\t\tmaterial.sheenRoughness *= texture2D( sheenRoughnessMap, vUv ).a;\n\t#endif\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_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat roughness;\n\tvec3 specularColor;\n\tfloat specularF90;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat clearcoat;\n\t\tfloat clearcoatRoughness;\n\t\tvec3 clearcoatF0;\n\t\tfloat clearcoatF90;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\tvec3 sheenColor;\n\t\tfloat sheenRoughness;\n\t#endif\n};\nvec3 clearcoatSpecular = vec3( 0.0 );\nvec2 DFGApprox( const in vec3 normal, const in vec3 viewDir, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\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\tvec2 fab = vec2( - 1.04, 1.04 ) * a004 + r.zw;\n\treturn fab;\n}\nvec3 EnvironmentBRDF( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\treturn specularColor * fab.x + specularF90 * fab.y;\n}\nvoid computeMultiscattering( const in vec3 normal, const in vec3 viewDir, const in vec3 specularColor, const in float specularF90, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n\tvec2 fab = DFGApprox( normal, viewDir, roughness );\n\tvec3 FssEss = specularColor * fab.x + specularF90 * fab.y;\n\tfloat Ess = fab.x + fab.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}\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.roughness;\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#ifdef USE_CLEARCOAT\n\t\tfloat dotNLcc = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = dotNLcc * directLight.color;\n\t\tclearcoatSpecular += ccIrradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.clearcoatNormal, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\t#ifdef USE_SHEEN\n\t\treflectedLight.directSpecular += irradiance * BRDF_Sheen( directLight.direction, geometry.viewDir, geometry.normal, material.sheenColor, material.sheenRoughness );\n\t#endif\n\treflectedLight.directSpecular += irradiance * BRDF_GGX( directLight.direction, geometry.viewDir, geometry.normal, material.specularColor, material.specularF90, material.roughness );\n\treflectedLight.directDiffuse += irradiance * BRDF_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_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 USE_CLEARCOAT\n\t\tclearcoatSpecular += clearcoatRadiance * EnvironmentBRDF( geometry.clearcoatNormal, geometry.viewDir, material.clearcoatF0, material.clearcoatF90, material.clearcoatRoughness );\n\t#endif\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\tcomputeMultiscattering( geometry.normal, geometry.viewDir, material.specularColor, material.specularF90, material.roughness, singleScattering, multiScattering );\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - ( singleScattering + multiScattering ) );\n\treflectedLight.indirectSpecular += 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_begin = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef USE_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\tgetPointLightInfo( 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\tgetSpotLightInfo( 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\tgetDirectionalLightInfo( 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.normal );\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.normal );\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_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 += getIBLIrradiance( geometry.normal );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getIBLRadiance( geometry.viewDir, geometry.normal, material.roughness );\n\t#ifdef USE_CLEARCOAT\n\t\tclearcoatRadiance += getIBLRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness );\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";
 
@@ -16760,25 +15496,33 @@ var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_META
 
 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 morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\tif ( morphTargetInfluences[ i ] > 0.0 ) objectNormal += getMorph( gl_VertexID, i, 1, 2 ) * morphTargetInfluences[ i ];\n\t\t}\n\t#else\n\t\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\t\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\t\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\t\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n\t#endif\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_pars_vertex = "#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tuniform float morphTargetInfluences[ MORPHTARGETS_COUNT ];\n\t\tuniform sampler2DArray morphTargetsTexture;\n\t\tuniform vec2 morphTargetsTextureSize;\n\t\tvec3 getMorph( const in int vertexIndex, const in int morphTargetIndex, const in int offset, const in int stride ) {\n\t\t\tfloat texelIndex = float( vertexIndex * stride + offset );\n\t\t\tfloat y = floor( texelIndex / morphTargetsTextureSize.x );\n\t\t\tfloat x = texelIndex - y * morphTargetsTextureSize.x;\n\t\t\tvec3 morphUV = vec3( ( x + 0.5 ) / morphTargetsTextureSize.x, y / morphTargetsTextureSize.y, morphTargetIndex );\n\t\t\treturn texture( morphTargetsTexture, morphUV ).xyz;\n\t\t}\n\t#else\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\tuniform float morphTargetInfluences[ 8 ];\n\t\t#else\n\t\t\tuniform float morphTargetInfluences[ 4 ];\n\t\t#endif\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 morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\t#ifdef MORPHTARGETS_TEXTURE\n\t\tfor ( int i = 0; i < MORPHTARGETS_COUNT; i ++ ) {\n\t\t\t#ifndef USE_MORPHNORMALS\n\t\t\t\tif ( morphTargetInfluences[ i ] > 0.0 ) transformed += getMorph( gl_VertexID, i, 0, 1 ) * morphTargetInfluences[ i ];\n\t\t\t#else\n\t\t\t\tif ( morphTargetInfluences[ i ] > 0.0 ) transformed += getMorph( gl_VertexID, i, 0, 2 ) * morphTargetInfluences[ i ];\n\t\t\t#endif\n\t\t}\n\t#else\n\t\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\t\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\t\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\t\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t\t#ifndef USE_MORPHNORMALS\n\t\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t\t#endif\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_begin = "float faceDirection = gl_FrontFacing ? 1.0 : - 1.0;\n#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 * faceDirection;\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 * faceDirection;\n\t\t\tbitangent = bitangent * faceDirection;\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 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 * faceDirection;\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, faceDirection );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( - vViewPosition, normal, dHdxy_fwd(), faceDirection );\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 normal_pars_fragment = "#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";
 
-var clearcoat_normal_fragment_begin = "#ifdef CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif";
+var normal_pars_vertex = "#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";
 
-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 normal_vertex = "#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";
+
+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, float faceDirection ) {\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\tvec3 N = surf_norm;\n\t\tvec3 q1perp = cross( q1, N );\n\t\tvec3 q0perp = cross( N, q0 );\n\t\tvec3 T = q1perp * st0.x + q0perp * st1.x;\n\t\tvec3 B = q1perp * st0.y + q0perp * st1.y;\n\t\tfloat det = max( dot( T, T ), dot( B, B ) );\n\t\tfloat scale = ( det == 0.0 ) ? 0.0 : faceDirection * inversesqrt( det );\n\t\treturn normalize( T * ( mapN.x * scale ) + B * ( mapN.y * scale ) + N * mapN.z );\n\t}\n#endif";
+
+var clearcoat_normal_fragment_begin = "#ifdef USE_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, faceDirection );\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 output_fragment = "#ifdef OPAQUE\ndiffuseColor.a = 1.0;\n#endif\n#ifdef USE_TRANSMISSION\ndiffuseColor.a *= transmissionAlpha + 0.1;\n#endif\ngl_FragColor = vec4( outgoingLight, diffuseColor.a );";
+
+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";
 
@@ -16814,11 +15558,11 @@ var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D spe
 
 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 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 transmission_fragment = "#ifdef USE_TRANSMISSION\n\tfloat transmissionAlpha = 1.0;\n\tfloat transmissionFactor = transmission;\n\tfloat thicknessFactor = thickness;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\ttransmissionFactor *= texture2D( transmissionMap, vUv ).r;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tthicknessFactor *= texture2D( thicknessMap, vUv ).g;\n\t#endif\n\tvec3 pos = vWorldPosition;\n\tvec3 v = normalize( cameraPosition - pos );\n\tvec3 n = inverseTransformDirection( normal, viewMatrix );\n\tvec4 transmission = getIBLVolumeRefraction(\n\t\tn, v, roughnessFactor, material.diffuseColor, material.specularColor, material.specularF90,\n\t\tpos, modelMatrix, viewMatrix, projectionMatrix, ior, thicknessFactor,\n\t\tattenuationColor, attenuationDistance );\n\ttotalDiffuse = mix( totalDiffuse, transmission.rgb, transmissionFactor );\n\ttransmissionAlpha = mix( transmissionAlpha, transmission.a, transmissionFactor );\n#endif";
 
-var transmissionmap_pars_fragment = "#ifdef USE_TRANSMISSIONMAP\n\tuniform sampler2D transmissionMap;\n#endif";
+var transmission_pars_fragment = "#ifdef USE_TRANSMISSION\n\tuniform float transmission;\n\tuniform float thickness;\n\tuniform float attenuationDistance;\n\tuniform vec3 attenuationColor;\n\t#ifdef USE_TRANSMISSIONMAP\n\t\tuniform sampler2D transmissionMap;\n\t#endif\n\t#ifdef USE_THICKNESSMAP\n\t\tuniform sampler2D thicknessMap;\n\t#endif\n\tuniform vec2 transmissionSamplerSize;\n\tuniform sampler2D transmissionSamplerMap;\n\tuniform mat4 modelMatrix;\n\tuniform mat4 projectionMatrix;\n\tvarying vec3 vWorldPosition;\n\tvec3 getVolumeTransmissionRay( vec3 n, vec3 v, float thickness, float ior, mat4 modelMatrix ) {\n\t\tvec3 refractionVector = refract( - v, normalize( n ), 1.0 / ior );\n\t\tvec3 modelScale;\n\t\tmodelScale.x = length( vec3( modelMatrix[ 0 ].xyz ) );\n\t\tmodelScale.y = length( vec3( modelMatrix[ 1 ].xyz ) );\n\t\tmodelScale.z = length( vec3( modelMatrix[ 2 ].xyz ) );\n\t\treturn normalize( refractionVector ) * thickness * modelScale;\n\t}\n\tfloat applyIorToRoughness( float roughness, float ior ) {\n\t\treturn roughness * clamp( ior * 2.0 - 2.0, 0.0, 1.0 );\n\t}\n\tvec4 getTransmissionSample( vec2 fragCoord, float roughness, float ior ) {\n\t\tfloat framebufferLod = log2( transmissionSamplerSize.x ) * applyIorToRoughness( roughness, ior );\n\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\treturn texture2DLodEXT( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n\t\t#else\n\t\t\treturn texture2D( transmissionSamplerMap, fragCoord.xy, framebufferLod );\n\t\t#endif\n\t}\n\tvec3 applyVolumeAttenuation( vec3 radiance, float transmissionDistance, vec3 attenuationColor, float attenuationDistance ) {\n\t\tif ( attenuationDistance == 0.0 ) {\n\t\t\treturn radiance;\n\t\t} else {\n\t\t\tvec3 attenuationCoefficient = -log( attenuationColor ) / attenuationDistance;\n\t\t\tvec3 transmittance = exp( - attenuationCoefficient * transmissionDistance );\t\t\treturn transmittance * radiance;\n\t\t}\n\t}\n\tvec4 getIBLVolumeRefraction( vec3 n, vec3 v, float roughness, vec3 diffuseColor, vec3 specularColor, float specularF90,\n\t\tvec3 position, mat4 modelMatrix, mat4 viewMatrix, mat4 projMatrix, float ior, float thickness,\n\t\tvec3 attenuationColor, float attenuationDistance ) {\n\t\tvec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );\n\t\tvec3 refractedRayExit = position + transmissionRay;\n\t\tvec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );\n\t\tvec2 refractionCoords = ndcPos.xy / ndcPos.w;\n\t\trefractionCoords += 1.0;\n\t\trefractionCoords /= 2.0;\n\t\tvec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );\n\t\tvec3 attenuatedColor = applyVolumeAttenuation( transmittedLight.rgb, length( transmissionRay ), attenuationColor, attenuationDistance );\n\t\tvec3 F = EnvironmentBRDF( n, v, specularColor, specularF90, roughness );\n\t\treturn vec4( ( 1.0 - F ) * attenuatedColor * diffuseColor, transmittedLight.a );\n\t}\n#endif";
 
 var uv_pars_fragment = "#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )\n\tvarying vec2 vUv;\n#endif";
 
@@ -16832,76 +15576,77 @@ var uv2_pars_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tat
 
 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 worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP ) || defined ( USE_TRANSMISSION )\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}";
+const vertex$g = "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 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}";
+const fragment$g = "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 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}";
+const vertex$f = "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 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}";
+const fragment$f = "#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 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}";
+const vertex$e = "#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 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}";
+const fragment$e = "#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 <alphatest_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 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}";
+const vertex$d = "#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 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}";
+const fragment$d = "#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 <alphatest_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 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}";
+const vertex$c = "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 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}";
+const fragment$c = "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 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}";
+const vertex$b = "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 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}";
+const fragment$b = "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\t#include <output_fragment>\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\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}";
+const vertex$a = "#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#if defined ( USE_ENVMAP ) || defined ( USE_SKINNING )\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinbase_vertex>\n\t\t#include <skinnormal_vertex>\n\t\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 <clipping_planes_vertex>\n\t#include <worldpos_vertex>\n\t#include <envmap_vertex>\n\t#include <fog_vertex>\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}";
+const fragment$a = "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 <alphatest_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\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\t#include <output_fragment>\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_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}";
+const vertex$9 = "#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 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}";
+const fragment$9 = "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 <alphatest_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_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_Lambert( diffuseColor.rgb ) * getShadowMask();\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include <envmap_fragment>\n\t#include <output_fragment>\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_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}";
+const vertex$8 = "#define MATCAP\nvarying vec3 vViewPosition;\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 <normal_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#include <normal_vertex>\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 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}";
+const fragment$8 = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\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 <alphatest_pars_fragment>\n#include <fog_pars_fragment>\n#include <normal_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\t#include <output_fragment>\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_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}";
+const vertex$7 = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <normal_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\t#include <normal_vertex>\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 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}";
+const fragment$7 = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#include <packing>\n#include <uv_pars_fragment>\n#include <normal_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 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}";
+const vertex$6 = "#define PHONG\nvarying vec3 vViewPosition;\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 <normal_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 <normal_vertex>\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 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}";
+const fragment$6 = "#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 <alphatest_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 <normal_pars_fragment>\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\t#include <output_fragment>\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_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}";
+const vertex$5 = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifdef USE_TRANSMISSION\n\tvarying vec3 vWorldPosition;\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 <normal_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 <normal_vertex>\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#ifdef USE_TRANSMISSION\n\tvWorldPosition = worldPosition.xyz;\n#endif\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}";
+const fragment$5 = "#define STANDARD\n#ifdef PHYSICAL\n\t#define IOR\n\t#define SPECULAR\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef IOR\n\tuniform float ior;\n#endif\n#ifdef SPECULAR\n\tuniform float specularIntensity;\n\tuniform vec3 specularColor;\n\t#ifdef USE_SPECULARINTENSITYMAP\n\t\tuniform sampler2D specularIntensityMap;\n\t#endif\n\t#ifdef USE_SPECULARCOLORMAP\n\t\tuniform sampler2D specularColorMap;\n\t#endif\n#endif\n#ifdef USE_CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheenColor;\n\tuniform float sheenRoughness;\n\t#ifdef USE_SHEENCOLORMAP\n\t\tuniform sampler2D sheenColorMap;\n\t#endif\n\t#ifdef USE_SHEENROUGHNESSMAP\n\t\tuniform sampler2D sheenRoughnessMap;\n\t#endif\n#endif\nvarying vec3 vViewPosition;\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 <alphatest_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_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 <normal_pars_fragment>\n#include <lights_physical_pars_fragment>\n#include <transmission_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#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 <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 totalDiffuse = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse;\n\tvec3 totalSpecular = reflectedLight.directSpecular + reflectedLight.indirectSpecular;\n\t#include <transmission_fragment>\n\tvec3 outgoingLight = totalDiffuse + totalSpecular + totalEmissiveRadiance;\n\t#ifdef USE_CLEARCOAT\n\t\tfloat dotNVcc = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\tvec3 Fcc = F_Schlick( material.clearcoatF0, material.clearcoatF90, dotNVcc );\n\t\toutgoingLight = outgoingLight * ( 1.0 - clearcoat * Fcc ) + clearcoatSpecular * clearcoat;\n\t#endif\n\t#include <output_fragment>\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 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}";
+const vertex$4 = "#define TOON\nvarying vec3 vViewPosition;\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 <normal_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 <normal_vertex>\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_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}";
+const fragment$4 = "#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 <alphatest_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 <normal_pars_fragment>\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\t#include <output_fragment>\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 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}";
+const vertex$3 = "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 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}";
+const fragment$3 = "uniform vec3 diffuse;\nuniform float opacity;\n#include <common>\n#include <color_pars_fragment>\n#include <map_particle_pars_fragment>\n#include <alphatest_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\t#include <output_fragment>\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\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}";
+const vertex$2 = "#include <common>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\nvoid main() {\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 <worldpos_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\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}";
+const fragment$2 = "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 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}";
+const vertex$1 = "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}";
 
-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 fragment$1 = "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 <alphatest_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\t#include <output_fragment>\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n}";
 
 const ShaderChunk = {
        alphamap_fragment: alphamap_fragment,
        alphamap_pars_fragment: alphamap_pars_fragment,
        alphatest_fragment: alphatest_fragment,
+       alphatest_pars_fragment: alphatest_pars_fragment,
        aomap_fragment: aomap_fragment,
        aomap_pars_fragment: aomap_pars_fragment,
        begin_vertex: begin_vertex,
@@ -16964,10 +15709,14 @@ const ShaderChunk = {
        morphtarget_vertex: morphtarget_vertex,
        normal_fragment_begin: normal_fragment_begin,
        normal_fragment_maps: normal_fragment_maps,
+       normal_pars_fragment: normal_pars_fragment,
+       normal_pars_vertex: normal_pars_vertex,
+       normal_vertex: normal_vertex,
        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,
+       output_fragment: output_fragment,
        packing: packing,
        premultiplied_alpha_fragment: premultiplied_alpha_fragment,
        project_vertex: project_vertex,
@@ -16987,8 +15736,8 @@ const ShaderChunk = {
        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,
+       transmission_fragment: transmission_fragment,
+       transmission_pars_fragment: transmission_pars_fragment,
        uv_pars_fragment: uv_pars_fragment,
        uv_pars_vertex: uv_pars_vertex,
        uv_vertex: uv_vertex,
@@ -16997,38 +15746,38 @@ const ShaderChunk = {
        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
+       background_vert: vertex$g,
+       background_frag: fragment$g,
+       cube_vert: vertex$f,
+       cube_frag: fragment$f,
+       depth_vert: vertex$e,
+       depth_frag: fragment$e,
+       distanceRGBA_vert: vertex$d,
+       distanceRGBA_frag: fragment$d,
+       equirect_vert: vertex$c,
+       equirect_frag: fragment$c,
+       linedashed_vert: vertex$b,
+       linedashed_frag: fragment$b,
+       meshbasic_vert: vertex$a,
+       meshbasic_frag: fragment$a,
+       meshlambert_vert: vertex$9,
+       meshlambert_frag: fragment$9,
+       meshmatcap_vert: vertex$8,
+       meshmatcap_frag: fragment$8,
+       meshnormal_vert: vertex$7,
+       meshnormal_frag: fragment$7,
+       meshphong_vert: vertex$6,
+       meshphong_frag: fragment$6,
+       meshphysical_vert: vertex$5,
+       meshphysical_frag: fragment$5,
+       meshtoon_vert: vertex$4,
+       meshtoon_frag: fragment$4,
+       points_vert: vertex$3,
+       points_frag: fragment$3,
+       shadow_vert: vertex$2,
+       shadow_frag: fragment$2,
+       sprite_vert: vertex$1,
+       sprite_frag: fragment$1
 };
 
 /**
@@ -17039,7 +15788,7 @@ const UniformsLib = {
 
        common: {
 
-               diffuse: { value: new Color( 0xeeeeee ) },
+               diffuse: { value: new Color( 0xffffff ) },
                opacity: { value: 1.0 },
 
                map: { value: null },
@@ -17047,6 +15796,7 @@ const UniformsLib = {
                uv2Transform: { value: new Matrix3() },
 
                alphaMap: { value: null },
+               alphaTest: { value: 0 }
 
        },
 
@@ -17060,7 +15810,8 @@ const UniformsLib = {
 
                envMap: { value: null },
                flipEnvMap: { value: - 1 },
-               reflectivity: { value: 1.0 },
+               reflectivity: { value: 1.0 }, // basic, lambert, phong
+               ior: { value: 1.5 }, // standard, physical
                refractionRatio: { value: 0.98 },
                maxMipLevel: { value: 0 }
 
@@ -17216,24 +15967,26 @@ const UniformsLib = {
 
        points: {
 
-               diffuse: { value: new Color( 0xeeeeee ) },
+               diffuse: { value: new Color( 0xffffff ) },
                opacity: { value: 1.0 },
                size: { value: 1.0 },
                scale: { value: 1.0 },
                map: { value: null },
                alphaMap: { value: null },
+               alphaTest: { value: 0 },
                uvTransform: { value: new Matrix3() }
 
        },
 
        sprite: {
 
-               diffuse: { value: new Color( 0xeeeeee ) },
+               diffuse: { value: new Color( 0xffffff ) },
                opacity: { value: 1.0 },
                center: { value: new Vector2( 0.5, 0.5 ) },
                rotation: { value: 0.0 },
                map: { value: null },
                alphaMap: { value: null },
+               alphaTest: { value: 0 },
                uvTransform: { value: new Matrix3() }
 
        }
@@ -17427,8 +16180,8 @@ const ShaderLib = {
                        }
                ] ),
 
-               vertexShader: ShaderChunk.normal_vert,
-               fragmentShader: ShaderChunk.normal_frag
+               vertexShader: ShaderChunk.meshnormal_vert,
+               fragmentShader: ShaderChunk.meshnormal_frag
 
        },
 
@@ -17530,9 +16283,23 @@ ShaderLib.physical = {
                        clearcoatRoughnessMap: { value: null },
                        clearcoatNormalScale: { value: new Vector2( 1, 1 ) },
                        clearcoatNormalMap: { value: null },
-                       sheen: { value: new Color( 0x000000 ) },
+                       sheen: { value: 0 },
+                       sheenColor: { value: new Color( 0x000000 ) },
+                       sheenColorMap: { value: null },
+                       sheenRoughness: { value: 0 },
+                       sheenRoughnessMap: { value: null },
                        transmission: { value: 0 },
                        transmissionMap: { value: null },
+                       transmissionSamplerSize: { value: new Vector2() },
+                       transmissionSamplerMap: { value: null },
+                       thickness: { value: 0 },
+                       thicknessMap: { value: null },
+                       attenuationDistance: { value: 0 },
+                       attenuationColor: { value: new Color( 0x000000 ) },
+                       specularIntensity: { value: 0 },
+                       specularIntensityMap: { value: null },
+                       specularColor: { value: new Color( 1, 1, 1 ) },
+                       specularColorMap: { value: null },
                }
        ] ),
 
@@ -17553,8 +16320,9 @@ function WebGLBackground( renderer, cubemaps, state, objects, premultipliedAlpha
        let currentBackgroundVersion = 0;
        let currentTonemapping = null;
 
-       function render( renderList, scene, camera, forceClear ) {
+       function render( renderList, scene ) {
 
+               let forceClear = false;
                let background = scene.isScene === true ? scene.background : null;
 
                if ( background && background.isTexture ) {
@@ -17592,7 +16360,7 @@ function WebGLBackground( renderer, cubemaps, state, objects, premultipliedAlpha
 
                }
 
-               if ( background && ( background.isCubeTexture || background.isWebGLCubeRenderTarget || background.mapping === CubeUVReflectionMapping ) ) {
+               if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) {
 
                        if ( boxMesh === undefined ) {
 
@@ -17634,16 +16402,8 @@ function WebGLBackground( renderer, cubemaps, state, objects, premultipliedAlpha
 
                        }
 
-                       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;
+                       boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background.isRenderTargetTexture === false ) ? - 1 : 1;
 
                        if ( currentBackground !== background ||
                                currentBackgroundVersion !== background.version ||
@@ -18086,9 +16846,16 @@ function WebGLBindingStates( gl, extensions, attributes, capabilities ) {
 
                        const programAttribute = programAttributes[ name ];
 
-                       if ( programAttribute >= 0 ) {
+                       if ( programAttribute.location >= 0 ) {
+
+                               let geometryAttribute = geometryAttributes[ name ];
+
+                               if ( geometryAttribute === undefined ) {
+
+                                       if ( name === 'instanceMatrix' && object.instanceMatrix ) geometryAttribute = object.instanceMatrix;
+                                       if ( name === 'instanceColor' && object.instanceColor ) geometryAttribute = object.instanceColor;
 
-                               const geometryAttribute = geometryAttributes[ name ];
+                               }
 
                                if ( geometryAttribute !== undefined ) {
 
@@ -18113,85 +16880,85 @@ function WebGLBindingStates( gl, extensions, attributes, capabilities ) {
 
                                                if ( data && data.isInstancedInterleavedBuffer ) {
 
-                                                       enableAttributeAndDivisor( programAttribute, data.meshPerAttribute );
-
-                                                       if ( geometry._maxInstanceCount === undefined ) {
+                                                       for ( let i = 0; i < programAttribute.locationSize; i ++ ) {
 
-                                                               geometry._maxInstanceCount = data.meshPerAttribute * data.count;
+                                                               enableAttributeAndDivisor( programAttribute.location + i, data.meshPerAttribute );
 
                                                        }
 
-                                               } else {
-
-                                                       enableAttribute( programAttribute );
+                                                       if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) {
 
-                                               }
+                                                               geometry._maxInstanceCount = data.meshPerAttribute * data.count;
 
-                                               gl.bindBuffer( 34962, buffer );
-                                               vertexAttribPointer( programAttribute, size, type, normalized, stride * bytesPerElement, offset * bytesPerElement );
+                                                       }
 
-                                       } else {
+                                               } else {
 
-                                               if ( geometryAttribute.isInstancedBufferAttribute ) {
+                                                       for ( let i = 0; i < programAttribute.locationSize; i ++ ) {
 
-                                                       enableAttributeAndDivisor( programAttribute, geometryAttribute.meshPerAttribute );
+                                                               enableAttribute( programAttribute.location + i );
 
-                                                       if ( geometry._maxInstanceCount === undefined ) {
+                                                       }
 
-                                                               geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count;
+                                               }
 
-                                                       }
+                                               gl.bindBuffer( 34962, buffer );
 
-                                               } else {
+                                               for ( let i = 0; i < programAttribute.locationSize; i ++ ) {
 
-                                                       enableAttribute( programAttribute );
+                                                       vertexAttribPointer(
+                                                               programAttribute.location + i,
+                                                               size / programAttribute.locationSize,
+                                                               type,
+                                                               normalized,
+                                                               stride * bytesPerElement,
+                                                               ( offset + ( size / programAttribute.locationSize ) * i ) * bytesPerElement
+                                                       );
 
                                                }
 
-                                               gl.bindBuffer( 34962, buffer );
-                                               vertexAttribPointer( programAttribute, size, type, normalized, 0, 0 );
+                                       } else {
 
-                                       }
+                                               if ( geometryAttribute.isInstancedBufferAttribute ) {
 
-                               } else if ( name === 'instanceMatrix' ) {
+                                                       for ( let i = 0; i < programAttribute.locationSize; i ++ ) {
 
-                                       const attribute = attributes.get( object.instanceMatrix );
+                                                               enableAttributeAndDivisor( programAttribute.location + i, geometryAttribute.meshPerAttribute );
 
-                                       // TODO Attribute may not be available on context restore
+                                                       }
 
-                                       if ( attribute === undefined ) continue;
+                                                       if ( object.isInstancedMesh !== true && geometry._maxInstanceCount === undefined ) {
 
-                                       const buffer = attribute.buffer;
-                                       const type = attribute.type;
+                                                               geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count;
 
-                                       enableAttributeAndDivisor( programAttribute + 0, 1 );
-                                       enableAttributeAndDivisor( programAttribute + 1, 1 );
-                                       enableAttributeAndDivisor( programAttribute + 2, 1 );
-                                       enableAttributeAndDivisor( programAttribute + 3, 1 );
+                                                       }
 
-                                       gl.bindBuffer( 34962, buffer );
+                                               } else {
 
-                                       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 );
+                                                       for ( let i = 0; i < programAttribute.locationSize; i ++ ) {
 
-                               } else if ( name === 'instanceColor' ) {
+                                                               enableAttribute( programAttribute.location + i );
 
-                                       const attribute = attributes.get( object.instanceColor );
+                                                       }
 
-                                       // TODO Attribute may not be available on context restore
+                                               }
 
-                                       if ( attribute === undefined ) continue;
+                                               gl.bindBuffer( 34962, buffer );
 
-                                       const buffer = attribute.buffer;
-                                       const type = attribute.type;
+                                               for ( let i = 0; i < programAttribute.locationSize; i ++ ) {
 
-                                       enableAttributeAndDivisor( programAttribute, 1 );
+                                                       vertexAttribPointer(
+                                                               programAttribute.location + i,
+                                                               size / programAttribute.locationSize,
+                                                               type,
+                                                               normalized,
+                                                               size * bytesPerElement,
+                                                               ( size / programAttribute.locationSize ) * i * bytesPerElement
+                                                       );
 
-                                       gl.bindBuffer( 34962, buffer );
+                                               }
 
-                                       gl.vertexAttribPointer( programAttribute, 3, type, false, 12, 0 );
+                                       }
 
                                } else if ( materialDefaultAttributeValues !== undefined ) {
 
@@ -18202,19 +16969,19 @@ function WebGLBindingStates( gl, extensions, attributes, capabilities ) {
                                                switch ( value.length ) {
 
                                                        case 2:
-                                                               gl.vertexAttrib2fv( programAttribute, value );
+                                                               gl.vertexAttrib2fv( programAttribute.location, value );
                                                                break;
 
                                                        case 3:
-                                                               gl.vertexAttrib3fv( programAttribute, value );
+                                                               gl.vertexAttrib3fv( programAttribute.location, value );
                                                                break;
 
                                                        case 4:
-                                                               gl.vertexAttrib4fv( programAttribute, value );
+                                                               gl.vertexAttrib4fv( programAttribute.location, value );
                                                                break;
 
                                                        default:
-                                                               gl.vertexAttrib1fv( programAttribute, value );
+                                                               gl.vertexAttrib1fv( programAttribute.location, value );
 
                                                }
 
@@ -18415,9 +17182,9 @@ function WebGLCapabilities( gl, extensions, parameters ) {
 
                if ( maxAnisotropy !== undefined ) return maxAnisotropy;
 
-               const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
+               if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) {
 
-               if ( extension !== null ) {
+                       const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
 
                        maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT );
 
@@ -18476,6 +17243,8 @@ function WebGLCapabilities( gl, extensions, parameters ) {
 
        }
 
+       const drawBuffers = isWebGL2 || extensions.has( 'WEBGL_draw_buffers' );
+
        const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true;
 
        const maxTextures = gl.getParameter( 34930 );
@@ -18489,7 +17258,7 @@ function WebGLCapabilities( gl, extensions, parameters ) {
        const maxFragmentUniforms = gl.getParameter( 36349 );
 
        const vertexTextures = maxVertexTextures > 0;
-       const floatFragmentTextures = isWebGL2 || !! extensions.get( 'OES_texture_float' );
+       const floatFragmentTextures = isWebGL2 || extensions.has( 'OES_texture_float' );
        const floatVertexTextures = vertexTextures && floatFragmentTextures;
 
        const maxSamples = isWebGL2 ? gl.getParameter( 36183 ) : 0;
@@ -18498,6 +17267,8 @@ function WebGLCapabilities( gl, extensions, parameters ) {
 
                isWebGL2: isWebGL2,
 
+               drawBuffers: drawBuffers,
+
                getMaxAnisotropy: getMaxAnisotropy,
                getMaxPrecision: getMaxPrecision,
 
@@ -18708,7 +17479,7 @@ function WebGLCubeMaps( renderer ) {
 
        function get( texture ) {
 
-               if ( texture && texture.isTexture ) {
+               if ( texture && texture.isTexture && texture.isRenderTargetTexture === false ) {
 
                        const mapping = texture.mapping;
 
@@ -18725,7 +17496,6 @@ function WebGLCubeMaps( renderer ) {
 
                                        if ( image && image.height > 0 ) {
 
-                                               const currentRenderList = renderer.getRenderList();
                                                const currentRenderTarget = renderer.getRenderTarget();
 
                                                const renderTarget = new WebGLCubeRenderTarget( image.height / 2 );
@@ -18733,7 +17503,6 @@ function WebGLCubeMaps( renderer ) {
                                                cubemaps.set( texture, renderTarget );
 
                                                renderer.setRenderTarget( currentRenderTarget );
-                                               renderer.setRenderList( currentRenderList );
 
                                                texture.addEventListener( 'dispose', onTextureDispose );
 
@@ -18787,7262 +17556,7358 @@ function WebGLCubeMaps( renderer ) {
 
 }
 
-function WebGLExtensions( gl ) {
-
-       const extensions = {};
-
-       function getExtension( name ) {
+class OrthographicCamera extends Camera$1 {
 
-               if ( extensions[ name ] !== undefined ) {
+       constructor( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) {
 
-                       return extensions[ name ];
+               super();
 
-               }
+               this.type = 'OrthographicCamera';
 
-               let extension;
+               this.zoom = 1;
+               this.view = null;
 
-               switch ( name ) {
+               this.left = left;
+               this.right = right;
+               this.top = top;
+               this.bottom = bottom;
 
-                       case 'WEBGL_depth_texture':
-                               extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' );
-                               break;
+               this.near = near;
+               this.far = far;
 
-                       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;
+               this.updateProjectionMatrix();
 
-                       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;
+       copy( source, recursive ) {
 
-                       default:
-                               extension = gl.getExtension( name );
+               super.copy( 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;
 
-               extensions[ name ] = extension;
+               this.zoom = source.zoom;
+               this.view = source.view === null ? null : Object.assign( {}, source.view );
 
-               return extension;
+               return this;
 
        }
 
-       return {
+       setViewOffset( fullWidth, fullHeight, x, y, width, height ) {
 
-               has: function ( name ) {
+               if ( this.view === null ) {
 
-                       return getExtension( name ) !== null;
+                       this.view = {
+                               enabled: true,
+                               fullWidth: 1,
+                               fullHeight: 1,
+                               offsetX: 0,
+                               offsetY: 0,
+                               width: 1,
+                               height: 1
+                       };
 
-               },
+               }
 
-               init: function ( capabilities ) {
+               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;
 
-                       if ( capabilities.isWebGL2 ) {
+               this.updateProjectionMatrix();
 
-                               getExtension( 'EXT_color_buffer_float' );
+       }
 
-                       } else {
+       clearViewOffset() {
 
-                               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' );
+               if ( this.view !== null ) {
 
-                       }
+                       this.view.enabled = false;
 
-                       getExtension( 'OES_texture_float_linear' );
-                       getExtension( 'EXT_color_buffer_half_float' );
+               }
 
-               },
+               this.updateProjectionMatrix();
 
-               get: function ( name ) {
+       }
 
-                       const extension = getExtension( name );
+       updateProjectionMatrix() {
 
-                       if ( extension === null ) {
+               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;
 
-                               console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' );
+               let left = cx - dx;
+               let right = cx + dx;
+               let top = cy + dy;
+               let bottom = cy - dy;
 
-                       }
+               if ( this.view !== null && this.view.enabled ) {
 
-                       return extension;
+                       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();
 
-function WebGLGeometries( gl, attributes, info, bindingStates ) {
+       }
 
-       const geometries = {};
-       const wireframeAttributes = new WeakMap();
+       toJSON( meta ) {
 
-       function onGeometryDispose( event ) {
+               const data = super.toJSON( meta );
 
-               const geometry = event.target;
+               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 ( geometry.index !== null ) {
+               if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );
 
-                       attributes.remove( geometry.index );
+               return data;
 
-               }
+       }
 
-               for ( const name in geometry.attributes ) {
+}
 
-                       attributes.remove( geometry.attributes[ name ] );
+OrthographicCamera.prototype.isOrthographicCamera = true;
 
-               }
+class RawShaderMaterial extends ShaderMaterial {
 
-               geometry.removeEventListener( 'dispose', onGeometryDispose );
+       constructor( parameters ) {
 
-               delete geometries[ geometry.id ];
+               super( parameters );
 
-               const attribute = wireframeAttributes.get( geometry );
+               this.type = 'RawShaderMaterial';
 
-               if ( attribute ) {
+       }
 
-                       attributes.remove( attribute );
-                       wireframeAttributes.delete( geometry );
+}
 
-               }
+RawShaderMaterial.prototype.isRawShaderMaterial = true;
 
-               bindingStates.releaseStatesOfGeometry( geometry );
+const LOD_MIN = 4;
+const LOD_MAX = 8;
+const SIZE_MAX = Math.pow( 2, LOD_MAX );
+
+// The standard deviations (radians) associated with the extra mips. These are
+// chosen to approximate a Trowbridge-Reitz distribution function times the
+// geometric shadowing function. These sigma values squared must match the
+// variance #defines in cube_uv_reflection_fragment.glsl.js.
+const EXTRA_LOD_SIGMA = [ 0.125, 0.215, 0.35, 0.446, 0.526, 0.582 ];
+
+const TOTAL_LODS = LOD_MAX - LOD_MIN + 1 + EXTRA_LOD_SIGMA.length;
+
+// The maximum length of the blur for loop. Smaller sigmas will use fewer
+// samples and exit early, but not recompile the shader.
+const MAX_SAMPLES = 20;
+
+const ENCODINGS = {
+       [ LinearEncoding ]: 0,
+       [ sRGBEncoding ]: 1,
+       [ RGBEEncoding ]: 2,
+       [ RGBM7Encoding ]: 3,
+       [ RGBM16Encoding ]: 4,
+       [ RGBDEncoding ]: 5,
+       [ GammaEncoding ]: 6
+};
 
-               if ( geometry.isInstancedBufferGeometry === true ) {
+const _flatCamera = /*@__PURE__*/ new OrthographicCamera();
+const { _lodPlanes, _sizeLods, _sigmas } = /*@__PURE__*/ _createPlanes();
+const _clearColor = /*@__PURE__*/ new Color();
+let _oldTarget = null;
+
+// Golden Ratio
+const PHI = ( 1 + Math.sqrt( 5 ) ) / 2;
+const INV_PHI = 1 / PHI;
+
+// Vertices of a dodecahedron (except the opposites, which represent the
+// same axis), used as axis directions evenly spread on a sphere.
+const _axisDirections = [
+       /*@__PURE__*/ new Vector3( 1, 1, 1 ),
+       /*@__PURE__*/ new Vector3( - 1, 1, 1 ),
+       /*@__PURE__*/ new Vector3( 1, 1, - 1 ),
+       /*@__PURE__*/ new Vector3( - 1, 1, - 1 ),
+       /*@__PURE__*/ new Vector3( 0, PHI, INV_PHI ),
+       /*@__PURE__*/ new Vector3( 0, PHI, - INV_PHI ),
+       /*@__PURE__*/ new Vector3( INV_PHI, 0, PHI ),
+       /*@__PURE__*/ new Vector3( - INV_PHI, 0, PHI ),
+       /*@__PURE__*/ new Vector3( PHI, INV_PHI, 0 ),
+       /*@__PURE__*/ new Vector3( - PHI, INV_PHI, 0 ) ];
 
-                       delete geometry._maxInstanceCount;
+/**
+ * This class generates a Prefiltered, Mipmapped Radiance Environment Map
+ * (PMREM) from a cubeMap environment texture. This allows different levels of
+ * blur to be quickly accessed based on material roughness. It is packed into a
+ * special CubeUV format that allows us to perform custom interpolation so that
+ * we can support nonlinear formats such as RGBE. Unlike a traditional mipmap
+ * chain, it only goes down to the LOD_MIN level (above), and then creates extra
+ * even more filtered 'mips' at the same LOD_MIN resolution, associated with
+ * higher roughness levels. In this way we maintain resolution to smoothly
+ * interpolate diffuse lighting while limiting sampling computation.
+ *
+ * Paper: Fast, Accurate Image-Based Lighting
+ * https://drive.google.com/file/d/15y8r_UpKlU9SvV4ILb0C3qCPecS8pvLz/view
+*/
 
-               }
+class PMREMGenerator {
 
-               //
+       constructor( renderer ) {
 
-               info.memory.geometries --;
+               this._renderer = renderer;
+               this._pingPongRenderTarget = null;
+
+               this._blurMaterial = _getBlurShader( MAX_SAMPLES );
+               this._equirectShader = null;
+               this._cubemapShader = null;
+
+               this._compileMaterial( this._blurMaterial );
 
        }
 
-       function get( object, geometry ) {
+       /**
+        * Generates a PMREM from a supplied Scene, which can be faster than using an
+        * image if networking bandwidth is low. Optional sigma specifies a blur radius
+        * in radians to be applied to the scene before PMREM generation. Optional near
+        * and far planes ensure the scene is rendered in its entirety (the cubeCamera
+        * is placed at the origin).
+        */
+       fromScene( scene, sigma = 0, near = 0.1, far = 100 ) {
 
-               if ( geometries[ geometry.id ] === true ) return geometry;
+               _oldTarget = this._renderer.getRenderTarget();
+               const cubeUVRenderTarget = this._allocateTargets();
 
-               geometry.addEventListener( 'dispose', onGeometryDispose );
+               this._sceneToCubeUV( scene, near, far, cubeUVRenderTarget );
+               if ( sigma > 0 ) {
 
-               geometries[ geometry.id ] = true;
+                       this._blur( cubeUVRenderTarget, 0, 0, sigma );
 
-               info.memory.geometries ++;
+               }
 
-               return geometry;
+               this._applyPMREM( cubeUVRenderTarget );
+               this._cleanup( cubeUVRenderTarget );
+
+               return cubeUVRenderTarget;
 
        }
 
-       function update( geometry ) {
+       /**
+        * Generates a PMREM from an equirectangular texture, which can be either LDR
+        * (RGBFormat) or HDR (RGBEFormat). The ideal input image size is 1k (1024 x 512),
+        * as this matches best with the 256 x 256 cubemap output.
+        */
+       fromEquirectangular( equirectangular ) {
 
-               const geometryAttributes = geometry.attributes;
+               return this._fromTexture( equirectangular );
 
-               // Updating index buffer in VAO now. See WebGLBindingStates.
+       }
 
-               for ( const name in geometryAttributes ) {
+       /**
+        * Generates a PMREM from an cubemap texture, which can be either LDR
+        * (RGBFormat) or HDR (RGBEFormat). The ideal input cube size is 256 x 256,
+        * as this matches best with the 256 x 256 cubemap output.
+        */
+       fromCubemap( cubemap ) {
 
-                       attributes.update( geometryAttributes[ name ], 34962 );
+               return this._fromTexture( cubemap );
 
-               }
+       }
 
-               // morph targets
+       /**
+        * Pre-compiles the cubemap shader. You can get faster start-up by invoking this method during
+        * your texture's network fetch for increased concurrency.
+        */
+       compileCubemapShader() {
 
-               const morphAttributes = geometry.morphAttributes;
+               if ( this._cubemapShader === null ) {
 
-               for ( const name in morphAttributes ) {
+                       this._cubemapShader = _getCubemapShader();
+                       this._compileMaterial( this._cubemapShader );
 
-                       const array = morphAttributes[ name ];
+               }
 
-                       for ( let i = 0, l = array.length; i < l; i ++ ) {
+       }
 
-                               attributes.update( array[ i ], 34962 );
+       /**
+        * Pre-compiles the equirectangular shader. You can get faster start-up by invoking this method during
+        * your texture's network fetch for increased concurrency.
+        */
+       compileEquirectangularShader() {
 
-                       }
+               if ( this._equirectShader === null ) {
+
+                       this._equirectShader = _getEquirectShader();
+                       this._compileMaterial( this._equirectShader );
 
                }
 
        }
 
-       function updateWireframeAttribute( geometry ) {
-
-               const indices = [];
-
-               const geometryIndex = geometry.index;
-               const geometryPosition = geometry.attributes.position;
-               let version = 0;
+       /**
+        * Disposes of the PMREMGenerator's internal memory. Note that PMREMGenerator is a static class,
+        * so you should not need more than one PMREMGenerator object. If you do, calling dispose() on
+        * one of them will cause any others to also become unusable.
+        */
+       dispose() {
 
-               if ( geometryIndex !== null ) {
+               this._blurMaterial.dispose();
 
-                       const array = geometryIndex.array;
-                       version = geometryIndex.version;
+               if ( this._cubemapShader !== null ) this._cubemapShader.dispose();
+               if ( this._equirectShader !== null ) this._equirectShader.dispose();
 
-                       for ( let i = 0, l = array.length; i < l; i += 3 ) {
+               for ( let i = 0; i < _lodPlanes.length; i ++ ) {
 
-                               const a = array[ i + 0 ];
-                               const b = array[ i + 1 ];
-                               const c = array[ i + 2 ];
+                       _lodPlanes[ i ].dispose();
 
-                               indices.push( a, b, b, c, c, a );
+               }
 
-                       }
+       }
 
-               } else {
+       // private interface
 
-                       const array = geometryPosition.array;
-                       version = geometryPosition.version;
+       _cleanup( outputTarget ) {
 
-                       for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {
+               this._pingPongRenderTarget.dispose();
+               this._renderer.setRenderTarget( _oldTarget );
+               outputTarget.scissorTest = false;
+               _setViewport( outputTarget, 0, 0, outputTarget.width, outputTarget.height );
 
-                               const a = i + 0;
-                               const b = i + 1;
-                               const c = i + 2;
+       }
 
-                               indices.push( a, b, b, c, c, a );
+       _fromTexture( texture ) {
 
-                       }
+               _oldTarget = this._renderer.getRenderTarget();
+               const cubeUVRenderTarget = this._allocateTargets( texture );
+               this._textureToCubeUV( texture, cubeUVRenderTarget );
+               this._applyPMREM( cubeUVRenderTarget );
+               this._cleanup( cubeUVRenderTarget );
 
-               }
+               return cubeUVRenderTarget;
 
-               const attribute = new ( arrayMax( indices ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );
-               attribute.version = version;
+       }
 
-               // Updating index buffer in VAO now. See WebGLBindingStates
+       _allocateTargets( texture ) { // warning: null texture is valid
 
-               //
+               const params = {
+                       magFilter: NearestFilter,
+                       minFilter: NearestFilter,
+                       generateMipmaps: false,
+                       type: UnsignedByteType,
+                       format: RGBEFormat,
+                       encoding: _isLDR( texture ) ? texture.encoding : RGBEEncoding,
+                       depthBuffer: false
+               };
 
-               const previousAttribute = wireframeAttributes.get( geometry );
+               const cubeUVRenderTarget = _createRenderTarget( params );
+               cubeUVRenderTarget.depthBuffer = texture ? false : true;
+               this._pingPongRenderTarget = _createRenderTarget( params );
+               return cubeUVRenderTarget;
 
-               if ( previousAttribute ) attributes.remove( previousAttribute );
+       }
 
-               //
+       _compileMaterial( material ) {
 
-               wireframeAttributes.set( geometry, attribute );
+               const tmpMesh = new Mesh( _lodPlanes[ 0 ], material );
+               this._renderer.compile( tmpMesh, _flatCamera );
 
        }
 
-       function getWireframeAttribute( geometry ) {
+       _sceneToCubeUV( scene, near, far, cubeUVRenderTarget ) {
 
-               const currentAttribute = wireframeAttributes.get( geometry );
+               const fov = 90;
+               const aspect = 1;
+               const cubeCamera = new PerspectiveCamera( fov, aspect, near, far );
+               const upSign = [ 1, - 1, 1, 1, 1, 1 ];
+               const forwardSign = [ 1, 1, 1, - 1, - 1, - 1 ];
+               const renderer = this._renderer;
 
-               if ( currentAttribute ) {
+               const originalAutoClear = renderer.autoClear;
+               const outputEncoding = renderer.outputEncoding;
+               const toneMapping = renderer.toneMapping;
+               renderer.getClearColor( _clearColor );
 
-                       const geometryIndex = geometry.index;
+               renderer.toneMapping = NoToneMapping;
+               renderer.outputEncoding = LinearEncoding;
+               renderer.autoClear = false;
 
-                       if ( geometryIndex !== null ) {
+               const backgroundMaterial = new MeshBasicMaterial( {
+                       name: 'PMREM.Background',
+                       side: BackSide,
+                       depthWrite: false,
+                       depthTest: false,
+               } );
 
-                               // if the attribute is obsolete, create a new one
+               const backgroundBox = new Mesh( new BoxGeometry(), backgroundMaterial );
 
-                               if ( currentAttribute.version < geometryIndex.version ) {
+               let useSolidColor = false;
+               const background = scene.background;
 
-                                       updateWireframeAttribute( geometry );
+               if ( background ) {
 
-                               }
+                       if ( background.isColor ) {
+
+                               backgroundMaterial.color.copy( background );
+                               scene.background = null;
+                               useSolidColor = true;
 
                        }
 
                } else {
 
-                       updateWireframeAttribute( geometry );
+                       backgroundMaterial.color.copy( _clearColor );
+                       useSolidColor = true;
 
                }
 
-               return wireframeAttributes.get( geometry );
+               for ( let i = 0; i < 6; i ++ ) {
 
-       }
+                       const col = i % 3;
+                       if ( col == 0 ) {
 
-       return {
+                               cubeCamera.up.set( 0, upSign[ i ], 0 );
+                               cubeCamera.lookAt( forwardSign[ i ], 0, 0 );
 
-               get: get,
-               update: update,
+                       } else if ( col == 1 ) {
 
-               getWireframeAttribute: getWireframeAttribute
+                               cubeCamera.up.set( 0, 0, upSign[ i ] );
+                               cubeCamera.lookAt( 0, forwardSign[ i ], 0 );
 
-       };
+                       } else {
 
-}
+                               cubeCamera.up.set( 0, upSign[ i ], 0 );
+                               cubeCamera.lookAt( 0, 0, forwardSign[ i ] );
 
-function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) {
+                       }
 
-       const isWebGL2 = capabilities.isWebGL2;
+                       _setViewport( cubeUVRenderTarget,
+                               col * SIZE_MAX, i > 2 ? SIZE_MAX : 0, SIZE_MAX, SIZE_MAX );
+                       renderer.setRenderTarget( cubeUVRenderTarget );
 
-       let mode;
+                       if ( useSolidColor ) {
 
-       function setMode( value ) {
+                               renderer.render( backgroundBox, cubeCamera );
 
-               mode = value;
+                       }
 
-       }
+                       renderer.render( scene, cubeCamera );
 
-       let type, bytesPerElement;
+               }
 
-       function setIndex( value ) {
+               backgroundBox.geometry.dispose();
+               backgroundBox.material.dispose();
 
-               type = value.type;
-               bytesPerElement = value.bytesPerElement;
+               renderer.toneMapping = toneMapping;
+               renderer.outputEncoding = outputEncoding;
+               renderer.autoClear = originalAutoClear;
+               scene.background = background;
 
        }
 
-       function render( start, count ) {
+       _setEncoding( uniform, texture ) {
 
-               gl.drawElements( mode, count, type, start * bytesPerElement );
+               if ( this._renderer.capabilities.isWebGL2 === true && texture.format === RGBAFormat && texture.type === UnsignedByteType && texture.encoding === sRGBEncoding ) {
 
-               info.update( count, mode, 1 );
+                       uniform.value = ENCODINGS[ LinearEncoding ];
 
-       }
+               } else {
 
-       function renderInstances( start, count, primcount ) {
+                       uniform.value = ENCODINGS[ texture.encoding ];
 
-               if ( primcount === 0 ) return;
+               }
 
-               let extension, methodName;
+       }
 
-               if ( isWebGL2 ) {
+       _textureToCubeUV( texture, cubeUVRenderTarget ) {
 
-                       extension = gl;
-                       methodName = 'drawElementsInstanced';
+               const renderer = this._renderer;
 
-               } else {
+               const isCubeTexture = ( texture.mapping === CubeReflectionMapping || texture.mapping === CubeRefractionMapping );
 
-                       extension = extensions.get( 'ANGLE_instanced_arrays' );
-                       methodName = 'drawElementsInstancedANGLE';
+               if ( isCubeTexture ) {
 
-                       if ( extension === null ) {
+                       if ( this._cubemapShader == null ) {
 
-                               console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );
-                               return;
+                               this._cubemapShader = _getCubemapShader();
 
                        }
 
-               }
+               } else {
 
-               extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount );
+                       if ( this._equirectShader == null ) {
 
-               info.update( count, mode, primcount );
+                               this._equirectShader = _getEquirectShader();
 
-       }
+                       }
 
-       //
+               }
 
-       this.setMode = setMode;
-       this.setIndex = setIndex;
-       this.render = render;
-       this.renderInstances = renderInstances;
+               const material = isCubeTexture ? this._cubemapShader : this._equirectShader;
+               const mesh = new Mesh( _lodPlanes[ 0 ], material );
 
-}
+               const uniforms = material.uniforms;
 
-function WebGLInfo( gl ) {
+               uniforms[ 'envMap' ].value = texture;
 
-       const memory = {
-               geometries: 0,
-               textures: 0
-       };
+               if ( ! isCubeTexture ) {
 
-       const render = {
-               frame: 0,
-               calls: 0,
-               triangles: 0,
-               points: 0,
-               lines: 0
-       };
+                       uniforms[ 'texelSize' ].value.set( 1.0 / texture.image.width, 1.0 / texture.image.height );
 
-       function update( count, mode, instanceCount ) {
+               }
 
-               render.calls ++;
+               this._setEncoding( uniforms[ 'inputEncoding' ], texture );
+               this._setEncoding( uniforms[ 'outputEncoding' ], cubeUVRenderTarget.texture );
 
-               switch ( mode ) {
+               _setViewport( cubeUVRenderTarget, 0, 0, 3 * SIZE_MAX, 2 * SIZE_MAX );
 
-                       case 4:
-                               render.triangles += instanceCount * ( count / 3 );
-                               break;
+               renderer.setRenderTarget( cubeUVRenderTarget );
+               renderer.render( mesh, _flatCamera );
 
-                       case 1:
-                               render.lines += instanceCount * ( count / 2 );
-                               break;
+       }
 
-                       case 3:
-                               render.lines += instanceCount * ( count - 1 );
-                               break;
+       _applyPMREM( cubeUVRenderTarget ) {
 
-                       case 2:
-                               render.lines += instanceCount * count;
-                               break;
+               const renderer = this._renderer;
+               const autoClear = renderer.autoClear;
+               renderer.autoClear = false;
 
-                       case 0:
-                               render.points += instanceCount * count;
-                               break;
+               for ( let i = 1; i < TOTAL_LODS; i ++ ) {
 
-                       default:
-                               console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode );
-                               break;
+                       const sigma = Math.sqrt( _sigmas[ i ] * _sigmas[ i ] - _sigmas[ i - 1 ] * _sigmas[ i - 1 ] );
 
-               }
+                       const poleAxis = _axisDirections[ ( i - 1 ) % _axisDirections.length ];
 
-       }
+                       this._blur( cubeUVRenderTarget, i - 1, i, sigma, poleAxis );
 
-       function reset() {
+               }
 
-               render.frame ++;
-               render.calls = 0;
-               render.triangles = 0;
-               render.points = 0;
-               render.lines = 0;
+               renderer.autoClear = autoClear;
 
        }
 
-       return {
-               memory: memory,
-               render: render,
-               programs: null,
-               autoReset: true,
-               reset: reset,
-               update: update
-       };
-
-}
+       /**
+        * This is a two-pass Gaussian blur for a cubemap. Normally this is done
+        * vertically and horizontally, but this breaks down on a cube. Here we apply
+        * the blur latitudinally (around the poles), and then longitudinally (towards
+        * the poles) to approximate the orthogonally-separable blur. It is least
+        * accurate at the poles, but still does a decent job.
+        */
+       _blur( cubeUVRenderTarget, lodIn, lodOut, sigma, poleAxis ) {
 
-function numericalSort( a, b ) {
+               const pingPongRenderTarget = this._pingPongRenderTarget;
 
-       return a[ 0 ] - b[ 0 ];
+               this._halfBlur(
+                       cubeUVRenderTarget,
+                       pingPongRenderTarget,
+                       lodIn,
+                       lodOut,
+                       sigma,
+                       'latitudinal',
+                       poleAxis );
 
-}
+               this._halfBlur(
+                       pingPongRenderTarget,
+                       cubeUVRenderTarget,
+                       lodOut,
+                       lodOut,
+                       sigma,
+                       'longitudinal',
+                       poleAxis );
 
-function absNumericalSort( a, b ) {
+       }
 
-       return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] );
+       _halfBlur( targetIn, targetOut, lodIn, lodOut, sigmaRadians, direction, poleAxis ) {
 
-}
+               const renderer = this._renderer;
+               const blurMaterial = this._blurMaterial;
 
-function WebGLMorphtargets( gl ) {
+               if ( direction !== 'latitudinal' && direction !== 'longitudinal' ) {
 
-       const influencesList = {};
-       const morphInfluences = new Float32Array( 8 );
+                       console.error(
+                               'blur direction must be either latitudinal or longitudinal!' );
 
-       const workInfluences = [];
+               }
 
-       for ( let i = 0; i < 8; i ++ ) {
+               // Number of standard deviations at which to cut off the discrete approximation.
+               const STANDARD_DEVIATIONS = 3;
 
-               workInfluences[ i ] = [ i, 0 ];
+               const blurMesh = new Mesh( _lodPlanes[ lodOut ], blurMaterial );
+               const blurUniforms = blurMaterial.uniforms;
 
-       }
+               const pixels = _sizeLods[ lodIn ] - 1;
+               const radiansPerPixel = isFinite( sigmaRadians ) ? Math.PI / ( 2 * pixels ) : 2 * Math.PI / ( 2 * MAX_SAMPLES - 1 );
+               const sigmaPixels = sigmaRadians / radiansPerPixel;
+               const samples = isFinite( sigmaRadians ) ? 1 + Math.floor( STANDARD_DEVIATIONS * sigmaPixels ) : MAX_SAMPLES;
 
-       function update( object, geometry, material, program ) {
+               if ( samples > MAX_SAMPLES ) {
 
-               const objectInfluences = object.morphTargetInfluences;
+                       console.warn( `sigmaRadians, ${
+                               sigmaRadians}, is too large and will clip, as it requested ${
+                               samples} samples when the maximum is set to ${MAX_SAMPLES}` );
 
-               // 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;
+               const weights = [];
+               let sum = 0;
 
-               let influences = influencesList[ geometry.id ];
+               for ( let i = 0; i < MAX_SAMPLES; ++ i ) {
 
-               if ( influences === undefined ) {
+                       const x = i / sigmaPixels;
+                       const weight = Math.exp( - x * x / 2 );
+                       weights.push( weight );
 
-                       // initialise list
+                       if ( i == 0 ) {
 
-                       influences = [];
+                               sum += weight;
 
-                       for ( let i = 0; i < length; i ++ ) {
+                       } else if ( i < samples ) {
 
-                               influences[ i ] = [ i, 0 ];
+                               sum += 2 * weight;
 
                        }
 
-                       influencesList[ geometry.id ] = influences;
-
                }
 
-               // Collect influences
+               for ( let i = 0; i < weights.length; i ++ ) {
 
-               for ( let i = 0; i < length; i ++ ) {
-
-                       const influence = influences[ i ];
-
-                       influence[ 0 ] = i;
-                       influence[ 1 ] = objectInfluences[ i ];
+                       weights[ i ] = weights[ i ] / sum;
 
                }
 
-               influences.sort( absNumericalSort );
+               blurUniforms[ 'envMap' ].value = targetIn.texture;
+               blurUniforms[ 'samples' ].value = samples;
+               blurUniforms[ 'weights' ].value = weights;
+               blurUniforms[ 'latitudinal' ].value = direction === 'latitudinal';
 
-               for ( let i = 0; i < 8; i ++ ) {
+               if ( poleAxis ) {
 
-                       if ( i < length && influences[ i ][ 1 ] ) {
+                       blurUniforms[ 'poleAxis' ].value = poleAxis;
 
-                               workInfluences[ i ][ 0 ] = influences[ i ][ 0 ];
-                               workInfluences[ i ][ 1 ] = influences[ i ][ 1 ];
+               }
 
-                       } else {
+               blurUniforms[ 'dTheta' ].value = radiansPerPixel;
+               blurUniforms[ 'mipInt' ].value = LOD_MAX - lodIn;
 
-                               workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER;
-                               workInfluences[ i ][ 1 ] = 0;
+               this._setEncoding( blurUniforms[ 'inputEncoding' ], targetIn.texture );
+               this._setEncoding( blurUniforms[ 'outputEncoding' ], targetIn.texture );
 
-                       }
+               const outputSize = _sizeLods[ lodOut ];
+               const x = 3 * Math.max( 0, SIZE_MAX - 2 * outputSize );
+               const y = ( lodOut === 0 ? 0 : 2 * SIZE_MAX ) + 2 * outputSize * ( lodOut > LOD_MAX - LOD_MIN ? lodOut - LOD_MAX + LOD_MIN : 0 );
 
-               }
+               _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize );
+               renderer.setRenderTarget( targetOut );
+               renderer.render( blurMesh, _flatCamera );
 
-               workInfluences.sort( numericalSort );
+       }
 
-               const morphTargets = material.morphTargets && geometry.morphAttributes.position;
-               const morphNormals = material.morphNormals && geometry.morphAttributes.normal;
+}
 
-               let morphInfluencesSum = 0;
+function _isLDR( texture ) {
 
-               for ( let i = 0; i < 8; i ++ ) {
+       if ( texture === undefined || texture.type !== UnsignedByteType ) return false;
 
-                       const influence = workInfluences[ i ];
-                       const index = influence[ 0 ];
-                       const value = influence[ 1 ];
+       return texture.encoding === LinearEncoding || texture.encoding === sRGBEncoding || texture.encoding === GammaEncoding;
 
-                       if ( index !== Number.MAX_SAFE_INTEGER && value ) {
+}
 
-                               if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) {
+function _createPlanes() {
 
-                                       geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] );
+       const _lodPlanes = [];
+       const _sizeLods = [];
+       const _sigmas = [];
 
-                               }
+       let lod = LOD_MAX;
 
-                               if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) {
+       for ( let i = 0; i < TOTAL_LODS; i ++ ) {
 
-                                       geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] );
+               const sizeLod = Math.pow( 2, lod );
+               _sizeLods.push( sizeLod );
+               let sigma = 1.0 / sizeLod;
 
-                               }
+               if ( i > LOD_MAX - LOD_MIN ) {
 
-                               morphInfluences[ i ] = value;
-                               morphInfluencesSum += value;
+                       sigma = EXTRA_LOD_SIGMA[ i - LOD_MAX + LOD_MIN - 1 ];
 
-                       } else {
+               } else if ( i == 0 ) {
 
-                               if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) {
+                       sigma = 0;
 
-                                       geometry.deleteAttribute( 'morphTarget' + i );
+               }
 
-                               }
+               _sigmas.push( sigma );
 
-                               if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) {
+               const texelSize = 1.0 / ( sizeLod - 1 );
+               const min = - texelSize / 2;
+               const max = 1 + texelSize / 2;
+               const uv1 = [ min, min, max, min, max, max, min, min, max, max, min, max ];
 
-                                       geometry.deleteAttribute( 'morphNormal' + i );
+               const cubeFaces = 6;
+               const vertices = 6;
+               const positionSize = 3;
+               const uvSize = 2;
+               const faceIndexSize = 1;
 
-                               }
+               const position = new Float32Array( positionSize * vertices * cubeFaces );
+               const uv = new Float32Array( uvSize * vertices * cubeFaces );
+               const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces );
 
-                               morphInfluences[ i ] = 0;
+               for ( let face = 0; face < cubeFaces; face ++ ) {
 
-                       }
+                       const x = ( face % 3 ) * 2 / 3 - 1;
+                       const y = face > 2 ? 0 : - 1;
+                       const coordinates = [
+                               x, y, 0,
+                               x + 2 / 3, y, 0,
+                               x + 2 / 3, y + 1, 0,
+                               x, y, 0,
+                               x + 2 / 3, y + 1, 0,
+                               x, y + 1, 0
+                       ];
+                       position.set( coordinates, positionSize * vertices * face );
+                       uv.set( uv1, uvSize * vertices * face );
+                       const fill = [ face, face, face, face, face, face ];
+                       faceIndex.set( fill, faceIndexSize * vertices * face );
 
                }
 
-               // 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;
+               const planes = new BufferGeometry();
+               planes.setAttribute( 'position', new BufferAttribute( position, positionSize ) );
+               planes.setAttribute( 'uv', new BufferAttribute( uv, uvSize ) );
+               planes.setAttribute( 'faceIndex', new BufferAttribute( faceIndex, faceIndexSize ) );
+               _lodPlanes.push( planes );
+
+               if ( lod > LOD_MIN ) {
+
+                       lod --;
 
-               program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
-               program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences );
+               }
 
        }
 
-       return {
+       return { _lodPlanes, _sizeLods, _sigmas };
 
-               update: update
+}
 
-       };
+function _createRenderTarget( params ) {
+
+       const cubeUVRenderTarget = new WebGLRenderTarget( 3 * SIZE_MAX, 3 * SIZE_MAX, params );
+       cubeUVRenderTarget.texture.mapping = CubeUVReflectionMapping;
+       cubeUVRenderTarget.texture.name = 'PMREM.cubeUv';
+       cubeUVRenderTarget.scissorTest = true;
+       return cubeUVRenderTarget;
 
 }
 
-function WebGLObjects( gl, geometries, attributes, info ) {
+function _setViewport( target, x, y, width, height ) {
 
-       let updateMap = new WeakMap();
+       target.viewport.set( x, y, width, height );
+       target.scissor.set( x, y, width, height );
 
-       function update( object ) {
+}
 
-               const frame = info.render.frame;
+function _getBlurShader( maxSamples ) {
 
-               const geometry = object.geometry;
-               const buffergeometry = geometries.get( object, geometry );
+       const weights = new Float32Array( maxSamples );
+       const poleAxis = new Vector3( 0, 1, 0 );
+       const shaderMaterial = new RawShaderMaterial( {
 
-               // Update once per frame
+               name: 'SphericalGaussianBlur',
 
-               if ( updateMap.get( buffergeometry ) !== frame ) {
+               defines: { 'n': maxSamples },
 
-                       geometries.update( buffergeometry );
+               uniforms: {
+                       'envMap': { value: null },
+                       'samples': { value: 1 },
+                       'weights': { value: weights },
+                       'latitudinal': { value: false },
+                       'dTheta': { value: 0 },
+                       'mipInt': { value: 0 },
+                       'poleAxis': { value: poleAxis },
+                       'inputEncoding': { value: ENCODINGS[ LinearEncoding ] },
+                       'outputEncoding': { value: ENCODINGS[ LinearEncoding ] }
+               },
 
-                       updateMap.set( buffergeometry, frame );
+               vertexShader: _getCommonVertexShader(),
 
-               }
+               fragmentShader: /* glsl */`
 
-               if ( object.isInstancedMesh ) {
+                       precision mediump float;
+                       precision mediump int;
 
-                       if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) {
+                       varying vec3 vOutputDirection;
 
-                               object.addEventListener( 'dispose', onInstancedMeshDispose );
+                       uniform sampler2D envMap;
+                       uniform int samples;
+                       uniform float weights[ n ];
+                       uniform bool latitudinal;
+                       uniform float dTheta;
+                       uniform float mipInt;
+                       uniform vec3 poleAxis;
 
-                       }
+                       ${ _getEncodings() }
 
-                       attributes.update( object.instanceMatrix, 34962 );
+                       #define ENVMAP_TYPE_CUBE_UV
+                       #include <cube_uv_reflection_fragment>
 
-                       if ( object.instanceColor !== null ) {
+                       vec3 getSample( float theta, vec3 axis ) {
 
-                               attributes.update( object.instanceColor, 34962 );
+                               float cosTheta = cos( theta );
+                               // Rodrigues' axis-angle rotation
+                               vec3 sampleDirection = vOutputDirection * cosTheta
+                                       + cross( axis, vOutputDirection ) * sin( theta )
+                                       + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );
+
+                               return bilinearCubeUV( envMap, sampleDirection, mipInt );
 
                        }
 
-               }
+                       void main() {
 
-               return buffergeometry;
+                               vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );
 
-       }
+                               if ( all( equal( axis, vec3( 0.0 ) ) ) ) {
 
-       function dispose() {
+                                       axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );
 
-               updateMap = new WeakMap();
+                               }
 
-       }
+                               axis = normalize( axis );
 
-       function onInstancedMeshDispose( event ) {
+                               gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );
+                               gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );
 
-               const instancedMesh = event.target;
+                               for ( int i = 1; i < n; i++ ) {
 
-               instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose );
+                                       if ( i >= samples ) {
 
-               attributes.remove( instancedMesh.instanceMatrix );
+                                               break;
 
-               if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor );
+                                       }
 
-       }
+                                       float theta = dTheta * float( i );
+                                       gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );
+                                       gl_FragColor.rgb += weights[ i ] * getSample( theta, axis );
 
-       return {
+                               }
 
-               update: update,
-               dispose: dispose
+                               gl_FragColor = linearToOutputTexel( gl_FragColor );
 
-       };
+                       }
+               `,
 
-}
+               blending: NoBlending,
+               depthTest: false,
+               depthWrite: false
 
-function DataTexture2DArray( data = null, width = 1, height = 1, depth = 1 ) {
+       } );
 
-       Texture.call( this, null );
+       return shaderMaterial;
 
-       this.image = { data, width, height, depth };
+}
 
-       this.magFilter = NearestFilter;
-       this.minFilter = NearestFilter;
+function _getEquirectShader() {
 
-       this.wrapR = ClampToEdgeWrapping;
+       const texelSize = new Vector2( 1, 1 );
+       const shaderMaterial = new RawShaderMaterial( {
 
-       this.generateMipmaps = false;
-       this.flipY = false;
+               name: 'EquirectangularToCubeUV',
 
-       this.needsUpdate = true;
+               uniforms: {
+                       'envMap': { value: null },
+                       'texelSize': { value: texelSize },
+                       'inputEncoding': { value: ENCODINGS[ LinearEncoding ] },
+                       'outputEncoding': { value: ENCODINGS[ LinearEncoding ] }
+               },
 
-}
+               vertexShader: _getCommonVertexShader(),
 
-DataTexture2DArray.prototype = Object.create( Texture.prototype );
-DataTexture2DArray.prototype.constructor = DataTexture2DArray;
-DataTexture2DArray.prototype.isDataTexture2DArray = true;
+               fragmentShader: /* glsl */`
 
-function DataTexture3D( data = null, width = 1, height = 1, depth = 1 ) {
+                       precision mediump float;
+                       precision mediump int;
 
-       // 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
+                       varying vec3 vOutputDirection;
 
-       Texture.call( this, null );
+                       uniform sampler2D envMap;
+                       uniform vec2 texelSize;
 
-       this.image = { data, width, height, depth };
+                       ${ _getEncodings() }
 
-       this.magFilter = NearestFilter;
-       this.minFilter = NearestFilter;
+                       #include <common>
 
-       this.wrapR = ClampToEdgeWrapping;
+                       void main() {
 
-       this.generateMipmaps = false;
-       this.flipY = false;
+                               gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );
 
-       this.needsUpdate = true;
+                               vec3 outputDirection = normalize( vOutputDirection );
+                               vec2 uv = equirectUv( outputDirection );
 
+                               vec2 f = fract( uv / texelSize - 0.5 );
+                               uv -= f * texelSize;
+                               vec3 tl = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;
+                               uv.x += texelSize.x;
+                               vec3 tr = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;
+                               uv.y += texelSize.y;
+                               vec3 br = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;
+                               uv.x -= texelSize.x;
+                               vec3 bl = envMapTexelToLinear( texture2D ( envMap, uv ) ).rgb;
 
-}
-
-DataTexture3D.prototype = Object.create( Texture.prototype );
-DataTexture3D.prototype.constructor = DataTexture3D;
-DataTexture3D.prototype.isDataTexture3D = true;
+                               vec3 tm = mix( tl, tr, f.x );
+                               vec3 bm = mix( bl, br, f.x );
+                               gl_FragColor.rgb = mix( tm, bm, f.y );
 
-/**
- * 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
- *
- */
+                               gl_FragColor = linearToOutputTexel( gl_FragColor );
 
-const emptyTexture = new Texture();
-const emptyTexture2dArray = new DataTexture2DArray();
-const emptyTexture3d = new DataTexture3D();
-const emptyCubeTexture = new CubeTexture();
+                       }
+               `,
 
-// --- Utilities ---
+               blending: NoBlending,
+               depthTest: false,
+               depthWrite: false
 
-// Array Caches (provide typed arrays for temporary by size)
+       } );
 
-const arrayCacheF32 = [];
-const arrayCacheI32 = [];
+       return shaderMaterial;
 
-// Float32Array caches used for uploading Matrix uniforms
+}
 
-const mat4array = new Float32Array( 16 );
-const mat3array = new Float32Array( 9 );
-const mat2array = new Float32Array( 4 );
+function _getCubemapShader() {
 
-// Flattening for arrays of vectors and matrices
+       const shaderMaterial = new RawShaderMaterial( {
 
-function flatten( array, nBlocks, blockSize ) {
+               name: 'CubemapToCubeUV',
 
-       const firstElem = array[ 0 ];
+               uniforms: {
+                       'envMap': { value: null },
+                       'inputEncoding': { value: ENCODINGS[ LinearEncoding ] },
+                       'outputEncoding': { value: ENCODINGS[ LinearEncoding ] }
+               },
 
-       if ( firstElem <= 0 || firstElem > 0 ) return array;
-       // unoptimized: ! isNaN( firstElem )
-       // see http://jacksondunstan.com/articles/983
+               vertexShader: _getCommonVertexShader(),
 
-       const n = nBlocks * blockSize;
-       let r = arrayCacheF32[ n ];
+               fragmentShader: /* glsl */`
 
-       if ( r === undefined ) {
+                       precision mediump float;
+                       precision mediump int;
 
-               r = new Float32Array( n );
-               arrayCacheF32[ n ] = r;
+                       varying vec3 vOutputDirection;
 
-       }
+                       uniform samplerCube envMap;
 
-       if ( nBlocks !== 0 ) {
+                       ${ _getEncodings() }
 
-               firstElem.toArray( r, 0 );
+                       void main() {
 
-               for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) {
+                               gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );
+                               gl_FragColor.rgb = envMapTexelToLinear( textureCube( envMap, vec3( - vOutputDirection.x, vOutputDirection.yz ) ) ).rgb;
+                               gl_FragColor = linearToOutputTexel( gl_FragColor );
 
-                       offset += blockSize;
-                       array[ i ].toArray( r, offset );
+                       }
+               `,
 
-               }
+               blending: NoBlending,
+               depthTest: false,
+               depthWrite: false
 
-       }
+       } );
 
-       return r;
+       return shaderMaterial;
 
 }
 
-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 _getCommonVertexShader() {
 
-}
+       return /* glsl */`
 
-function copyArray( a, b ) {
+               precision mediump float;
+               precision mediump int;
 
-       for ( let i = 0, l = b.length; i < l; i ++ ) {
+               attribute vec3 position;
+               attribute vec2 uv;
+               attribute float faceIndex;
 
-               a[ i ] = b[ i ];
+               varying vec3 vOutputDirection;
 
-       }
+               // RH coordinate system; PMREM face-indexing convention
+               vec3 getDirection( vec2 uv, float face ) {
 
-}
+                       uv = 2.0 * uv - 1.0;
 
-// Texture unit allocation
+                       vec3 direction = vec3( uv, 1.0 );
 
-function allocTexUnits( textures, n ) {
+                       if ( face == 0.0 ) {
 
-       let r = arrayCacheI32[ n ];
+                               direction = direction.zyx; // ( 1, v, u ) pos x
 
-       if ( r === undefined ) {
+                       } else if ( face == 1.0 ) {
 
-               r = new Int32Array( n );
-               arrayCacheI32[ n ] = r;
+                               direction = direction.xzy;
+                               direction.xz *= -1.0; // ( -u, 1, -v ) pos y
 
-       }
+                       } else if ( face == 2.0 ) {
 
-       for ( let i = 0; i !== n; ++ i ) {
+                               direction.x *= -1.0; // ( -u, v, 1 ) pos z
 
-               r[ i ] = textures.allocateTextureUnit();
+                       } else if ( face == 3.0 ) {
 
-       }
+                               direction = direction.zyx;
+                               direction.xz *= -1.0; // ( -1, v, -u ) neg x
 
-       return r;
+                       } else if ( face == 4.0 ) {
 
-}
+                               direction = direction.xzy;
+                               direction.xy *= -1.0; // ( -u, -1, v ) neg y
 
-// --- Setters ---
+                       } else if ( face == 5.0 ) {
 
-// Note: Defining these methods externally, because they come in a bunch
-// and this way their names minify.
+                               direction.z *= -1.0; // ( u, v, -1 ) neg z
 
-// Single scalar
+                       }
 
-function setValueV1f( gl, v ) {
+                       return direction;
 
-       const cache = this.cache;
+               }
 
-       if ( cache[ 0 ] === v ) return;
+               void main() {
 
-       gl.uniform1f( this.addr, v );
+                       vOutputDirection = getDirection( uv, faceIndex );
+                       gl_Position = vec4( position, 1.0 );
 
-       cache[ 0 ] = v;
+               }
+       `;
 
 }
 
-// Single float vector (from flat array or THREE.VectorN)
+function _getEncodings() {
 
-function setValueV2f( gl, v ) {
+       return /* glsl */`
 
-       const cache = this.cache;
+               uniform int inputEncoding;
+               uniform int outputEncoding;
 
-       if ( v.x !== undefined ) {
+               #include <encodings_pars_fragment>
 
-               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) {
+               vec4 inputTexelToLinear( vec4 value ) {
 
-                       gl.uniform2f( this.addr, v.x, v.y );
+                       if ( inputEncoding == 0 ) {
 
-                       cache[ 0 ] = v.x;
-                       cache[ 1 ] = v.y;
+                               return value;
 
-               }
+                       } else if ( inputEncoding == 1 ) {
 
-       } else {
+                               return sRGBToLinear( value );
 
-               if ( arraysEqual( cache, v ) ) return;
+                       } else if ( inputEncoding == 2 ) {
 
-               gl.uniform2fv( this.addr, v );
+                               return RGBEToLinear( value );
 
-               copyArray( cache, v );
+                       } else if ( inputEncoding == 3 ) {
 
-       }
+                               return RGBMToLinear( value, 7.0 );
 
-}
+                       } else if ( inputEncoding == 4 ) {
 
-function setValueV3f( gl, v ) {
+                               return RGBMToLinear( value, 16.0 );
 
-       const cache = this.cache;
+                       } else if ( inputEncoding == 5 ) {
 
-       if ( v.x !== undefined ) {
+                               return RGBDToLinear( value, 256.0 );
 
-               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) {
+                       } else {
 
-                       gl.uniform3f( this.addr, v.x, v.y, v.z );
+                               return GammaToLinear( value, 2.2 );
 
-                       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 ) {
+               vec4 linearToOutputTexel( vec4 value ) {
 
-                       gl.uniform3f( this.addr, v.r, v.g, v.b );
+                       if ( outputEncoding == 0 ) {
 
-                       cache[ 0 ] = v.r;
-                       cache[ 1 ] = v.g;
-                       cache[ 2 ] = v.b;
+                               return value;
 
-               }
+                       } else if ( outputEncoding == 1 ) {
 
-       } else {
+                               return LinearTosRGB( value );
 
-               if ( arraysEqual( cache, v ) ) return;
+                       } else if ( outputEncoding == 2 ) {
 
-               gl.uniform3fv( this.addr, v );
+                               return LinearToRGBE( value );
 
-               copyArray( cache, v );
+                       } else if ( outputEncoding == 3 ) {
 
-       }
+                               return LinearToRGBM( value, 7.0 );
 
-}
+                       } else if ( outputEncoding == 4 ) {
 
-function setValueV4f( gl, v ) {
+                               return LinearToRGBM( value, 16.0 );
 
-       const cache = this.cache;
+                       } else if ( outputEncoding == 5 ) {
 
-       if ( v.x !== undefined ) {
+                               return LinearToRGBD( value, 256.0 );
 
-               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) {
+                       } else {
 
-                       gl.uniform4f( this.addr, v.x, v.y, v.z, v.w );
+                               return LinearToGamma( value, 2.2 );
 
-                       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 );
+               vec4 envMapTexelToLinear( vec4 color ) {
 
-               copyArray( cache, v );
+                       return inputTexelToLinear( color );
 
-       }
+               }
+       `;
 
 }
 
-// Single matrix (from flat array or MatrixN)
-
-function setValueM2( gl, v ) {
-
-       const cache = this.cache;
-       const elements = v.elements;
+function WebGLCubeUVMaps( renderer ) {
 
-       if ( elements === undefined ) {
+       let cubeUVmaps = new WeakMap();
 
-               if ( arraysEqual( cache, v ) ) return;
+       let pmremGenerator = null;
 
-               gl.uniformMatrix2fv( this.addr, false, v );
+       function get( texture ) {
 
-               copyArray( cache, v );
+               if ( texture && texture.isTexture && texture.isRenderTargetTexture === false ) {
 
-       } else {
+                       const mapping = texture.mapping;
 
-               if ( arraysEqual( cache, elements ) ) return;
+                       const isEquirectMap = ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping );
+                       const isCubeMap = ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping );
 
-               mat2array.set( elements );
+                       if ( isEquirectMap || isCubeMap ) {
 
-               gl.uniformMatrix2fv( this.addr, false, mat2array );
+                               // equirect/cube map to cubeUV conversion
 
-               copyArray( cache, elements );
+                               if ( cubeUVmaps.has( texture ) ) {
 
-       }
+                                       return cubeUVmaps.get( texture ).texture;
 
-}
+                               } else {
 
-function setValueM3( gl, v ) {
+                                       const image = texture.image;
 
-       const cache = this.cache;
-       const elements = v.elements;
+                                       if ( ( isEquirectMap && image && image.height > 0 ) || ( isCubeMap && image && isCubeTextureComplete( image ) ) ) {
 
-       if ( elements === undefined ) {
+                                               const currentRenderTarget = renderer.getRenderTarget();
 
-               if ( arraysEqual( cache, v ) ) return;
+                                               if ( pmremGenerator === null ) pmremGenerator = new PMREMGenerator( renderer );
 
-               gl.uniformMatrix3fv( this.addr, false, v );
+                                               const renderTarget = isEquirectMap ? pmremGenerator.fromEquirectangular( texture ) : pmremGenerator.fromCubemap( texture );
+                                               cubeUVmaps.set( texture, renderTarget );
 
-               copyArray( cache, v );
+                                               renderer.setRenderTarget( currentRenderTarget );
 
-       } else {
+                                               texture.addEventListener( 'dispose', onTextureDispose );
 
-               if ( arraysEqual( cache, elements ) ) return;
+                                               return renderTarget.texture;
 
-               mat3array.set( elements );
+                                       } else {
 
-               gl.uniformMatrix3fv( this.addr, false, mat3array );
+                                               // image not yet ready. try the conversion next frame
 
-               copyArray( cache, elements );
+                                               return null;
 
-       }
+                                       }
 
-}
+                               }
 
-function setValueM4( gl, v ) {
+                       }
 
-       const cache = this.cache;
-       const elements = v.elements;
+               }
 
-       if ( elements === undefined ) {
+               return texture;
 
-               if ( arraysEqual( cache, v ) ) return;
+       }
 
-               gl.uniformMatrix4fv( this.addr, false, v );
+       function isCubeTextureComplete( image ) {
 
-               copyArray( cache, v );
+               let count = 0;
+               const length = 6;
 
-       } else {
+               for ( let i = 0; i < length; i ++ ) {
 
-               if ( arraysEqual( cache, elements ) ) return;
+                       if ( image[ i ] !== undefined ) count ++;
 
-               mat4array.set( elements );
+               }
 
-               gl.uniformMatrix4fv( this.addr, false, mat4array );
+               return count === length;
 
-               copyArray( cache, elements );
 
        }
 
-}
+       function onTextureDispose( event ) {
 
-// Single texture (2D / Cube)
+               const texture = event.target;
 
-function setValueT1( gl, v, textures ) {
+               texture.removeEventListener( 'dispose', onTextureDispose );
 
-       const cache = this.cache;
-       const unit = textures.allocateTextureUnit();
+               const cubemapUV = cubeUVmaps.get( texture );
 
-       if ( cache[ 0 ] !== unit ) {
+               if ( cubemapUV !== undefined ) {
 
-               gl.uniform1i( this.addr, unit );
-               cache[ 0 ] = unit;
+                       cubeUVmaps.delete( texture );
+                       cubemapUV.dispose();
 
-       }
+               }
 
-       textures.safeSetTexture2D( v || emptyTexture, unit );
+       }
 
-}
+       function dispose() {
 
-function setValueT2DArray1( gl, v, textures ) {
+               cubeUVmaps = new WeakMap();
 
-       const cache = this.cache;
-       const unit = textures.allocateTextureUnit();
+               if ( pmremGenerator !== null ) {
 
-       if ( cache[ 0 ] !== unit ) {
+                       pmremGenerator.dispose();
+                       pmremGenerator = null;
 
-               gl.uniform1i( this.addr, unit );
-               cache[ 0 ] = unit;
+               }
 
        }
 
-       textures.setTexture2DArray( v || emptyTexture2dArray, unit );
+       return {
+               get: get,
+               dispose: dispose
+       };
 
 }
 
-function setValueT3D1( gl, v, textures ) {
-
-       const cache = this.cache;
-       const unit = textures.allocateTextureUnit();
+function WebGLExtensions( gl ) {
 
-       if ( cache[ 0 ] !== unit ) {
+       const extensions = {};
 
-               gl.uniform1i( this.addr, unit );
-               cache[ 0 ] = unit;
+       function getExtension( name ) {
 
-       }
+               if ( extensions[ name ] !== undefined ) {
 
-       textures.setTexture3D( v || emptyTexture3d, unit );
+                       return extensions[ name ];
 
-}
+               }
 
-function setValueT6( gl, v, textures ) {
+               let extension;
 
-       const cache = this.cache;
-       const unit = textures.allocateTextureUnit();
+               switch ( name ) {
 
-       if ( cache[ 0 ] !== unit ) {
+                       case 'WEBGL_depth_texture':
+                               extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' );
+                               break;
 
-               gl.uniform1i( this.addr, unit );
-               cache[ 0 ] = unit;
+                       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;
 
-       textures.safeSetTextureCube( v || emptyCubeTexture, unit );
+                       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 );
 
-// Integer / Boolean vectors or arrays thereof (always flat arrays)
+               }
 
-function setValueV1i( gl, v ) {
+               extensions[ name ] = extension;
 
-       const cache = this.cache;
+               return extension;
 
-       if ( cache[ 0 ] === v ) return;
+       }
 
-       gl.uniform1i( this.addr, v );
+       return {
 
-       cache[ 0 ] = v;
+               has: function ( name ) {
 
-}
+                       return getExtension( name ) !== null;
 
-function setValueV2i( gl, v ) {
+               },
 
-       const cache = this.cache;
+               init: function ( capabilities ) {
 
-       if ( arraysEqual( cache, v ) ) return;
+                       if ( capabilities.isWebGL2 ) {
 
-       gl.uniform2iv( this.addr, v );
+                               getExtension( 'EXT_color_buffer_float' );
 
-       copyArray( cache, v );
+                       } 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' );
 
-function setValueV3i( gl, v ) {
+                       }
 
-       const cache = this.cache;
+                       getExtension( 'OES_texture_float_linear' );
+                       getExtension( 'EXT_color_buffer_half_float' );
 
-       if ( arraysEqual( cache, v ) ) return;
+               },
 
-       gl.uniform3iv( this.addr, v );
+               get: function ( name ) {
 
-       copyArray( cache, v );
+                       const extension = getExtension( name );
 
-}
+                       if ( extension === null ) {
 
-function setValueV4i( gl, v ) {
+                               console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' );
 
-       const cache = this.cache;
+                       }
 
-       if ( arraysEqual( cache, v ) ) return;
+                       return extension;
 
-       gl.uniform4iv( this.addr, v );
+               }
 
-       copyArray( cache, v );
+       };
 
 }
 
-// uint
+function WebGLGeometries( gl, attributes, info, bindingStates ) {
 
-function setValueV1ui( gl, v ) {
+       const geometries = {};
+       const wireframeAttributes = new WeakMap();
 
-       const cache = this.cache;
+       function onGeometryDispose( event ) {
 
-       if ( cache[ 0 ] === v ) return;
+               const geometry = event.target;
 
-       gl.uniform1ui( this.addr, v );
+               if ( geometry.index !== null ) {
 
-       cache[ 0 ] = v;
+                       attributes.remove( geometry.index );
 
-}
+               }
 
-// Helper to pick the right setter for the singular case
+               for ( const name in geometry.attributes ) {
 
-function getSingularSetter( type ) {
+                       attributes.remove( geometry.attributes[ name ] );
 
-       switch ( type ) {
+               }
 
-               case 0x1406: return setValueV1f; // FLOAT
-               case 0x8b50: return setValueV2f; // _VEC2
-               case 0x8b51: return setValueV3f; // _VEC3
-               case 0x8b52: return setValueV4f; // _VEC4
+               geometry.removeEventListener( 'dispose', onGeometryDispose );
 
-               case 0x8b5a: return setValueM2; // _MAT2
-               case 0x8b5b: return setValueM3; // _MAT3
-               case 0x8b5c: return setValueM4; // _MAT4
+               delete geometries[ geometry.id ];
 
-               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
+               const attribute = wireframeAttributes.get( geometry );
 
-               case 0x1405: return setValueV1ui; // UINT
+               if ( attribute ) {
 
-               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;
+                       attributes.remove( attribute );
+                       wireframeAttributes.delete( geometry );
 
-               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;
+               bindingStates.releaseStatesOfGeometry( geometry );
 
-               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;
+               if ( geometry.isInstancedBufferGeometry === true ) {
 
-       }
+                       delete geometry._maxInstanceCount;
 
-}
+               }
 
-// Array of scalars
-function setValueV1fArray( gl, v ) {
+               //
 
-       gl.uniform1fv( this.addr, v );
+               info.memory.geometries --;
 
-}
+       }
 
-// Integer / Boolean vectors or arrays thereof (always flat arrays)
-function setValueV1iArray( gl, v ) {
+       function get( object, geometry ) {
 
-       gl.uniform1iv( this.addr, v );
+               if ( geometries[ geometry.id ] === true ) return geometry;
 
-}
+               geometry.addEventListener( 'dispose', onGeometryDispose );
 
-function setValueV2iArray( gl, v ) {
+               geometries[ geometry.id ] = true;
 
-       gl.uniform2iv( this.addr, v );
+               info.memory.geometries ++;
 
-}
+               return geometry;
 
-function setValueV3iArray( gl, v ) {
+       }
 
-       gl.uniform3iv( this.addr, v );
+       function update( geometry ) {
 
-}
+               const geometryAttributes = geometry.attributes;
 
-function setValueV4iArray( gl, v ) {
+               // Updating index buffer in VAO now. See WebGLBindingStates.
 
-       gl.uniform4iv( this.addr, v );
+               for ( const name in geometryAttributes ) {
 
-}
+                       attributes.update( geometryAttributes[ name ], 34962 );
 
+               }
 
-// Array of vectors (flat or from THREE classes)
+               // morph targets
 
-function setValueV2fArray( gl, v ) {
+               const morphAttributes = geometry.morphAttributes;
 
-       const data = flatten( v, this.size, 2 );
+               for ( const name in morphAttributes ) {
 
-       gl.uniform2fv( this.addr, data );
+                       const array = morphAttributes[ name ];
 
-}
+                       for ( let i = 0, l = array.length; i < l; i ++ ) {
 
-function setValueV3fArray( gl, v ) {
+                               attributes.update( array[ i ], 34962 );
 
-       const data = flatten( v, this.size, 3 );
+                       }
 
-       gl.uniform3fv( this.addr, data );
+               }
 
-}
+       }
 
-function setValueV4fArray( gl, v ) {
+       function updateWireframeAttribute( geometry ) {
 
-       const data = flatten( v, this.size, 4 );
+               const indices = [];
 
-       gl.uniform4fv( this.addr, data );
+               const geometryIndex = geometry.index;
+               const geometryPosition = geometry.attributes.position;
+               let version = 0;
 
-}
+               if ( geometryIndex !== null ) {
 
-// Array of matrices (flat or from THREE clases)
+                       const array = geometryIndex.array;
+                       version = geometryIndex.version;
 
-function setValueM2Array( gl, v ) {
+                       for ( let i = 0, l = array.length; i < l; i += 3 ) {
 
-       const data = flatten( v, this.size, 4 );
+                               const a = array[ i + 0 ];
+                               const b = array[ i + 1 ];
+                               const c = array[ i + 2 ];
 
-       gl.uniformMatrix2fv( this.addr, false, data );
+                               indices.push( a, b, b, c, c, a );
 
-}
+                       }
 
-function setValueM3Array( gl, v ) {
+               } else {
 
-       const data = flatten( v, this.size, 9 );
+                       const array = geometryPosition.array;
+                       version = geometryPosition.version;
 
-       gl.uniformMatrix3fv( this.addr, false, data );
-
-}
+                       for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {
 
-function setValueM4Array( gl, v ) {
+                               const a = i + 0;
+                               const b = i + 1;
+                               const c = i + 2;
 
-       const data = flatten( v, this.size, 16 );
+                               indices.push( a, b, b, c, c, a );
 
-       gl.uniformMatrix4fv( this.addr, false, data );
+                       }
 
-}
+               }
 
-// Array of textures (2D / Cube)
+               const attribute = new ( arrayMax( indices ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );
+               attribute.version = version;
 
-function setValueT1Array( gl, v, textures ) {
+               // Updating index buffer in VAO now. See WebGLBindingStates
 
-       const n = v.length;
+               //
 
-       const units = allocTexUnits( textures, n );
+               const previousAttribute = wireframeAttributes.get( geometry );
 
-       gl.uniform1iv( this.addr, units );
+               if ( previousAttribute ) attributes.remove( previousAttribute );
 
-       for ( let i = 0; i !== n; ++ i ) {
+               //
 
-               textures.safeSetTexture2D( v[ i ] || emptyTexture, units[ i ] );
+               wireframeAttributes.set( geometry, attribute );
 
        }
 
-}
-
-function setValueT6Array( gl, v, textures ) {
-
-       const n = v.length;
-
-       const units = allocTexUnits( textures, n );
+       function getWireframeAttribute( geometry ) {
 
-       gl.uniform1iv( this.addr, units );
+               const currentAttribute = wireframeAttributes.get( geometry );
 
-       for ( let i = 0; i !== n; ++ i ) {
+               if ( currentAttribute ) {
 
-               textures.safeSetTextureCube( v[ i ] || emptyCubeTexture, units[ i ] );
+                       const geometryIndex = geometry.index;
 
-       }
+                       if ( geometryIndex !== null ) {
 
-}
+                               // if the attribute is obsolete, create a new one
 
-// Helper to pick the right setter for a pure (bottom-level) array
+                               if ( currentAttribute.version < geometryIndex.version ) {
 
-function getPureArraySetter( type ) {
+                                       updateWireframeAttribute( geometry );
 
-       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
+               } else {
 
-               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
+                       updateWireframeAttribute( geometry );
 
-               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;
+               return wireframeAttributes.get( geometry );
 
        }
 
-}
-
-// --- Uniform Classes ---
+       return {
 
-function SingleUniform( id, activeInfo, addr ) {
+               get: get,
+               update: update,
 
-       this.id = id;
-       this.addr = addr;
-       this.cache = [];
-       this.setValue = getSingularSetter( activeInfo.type );
+               getWireframeAttribute: getWireframeAttribute
 
-       // this.path = activeInfo.name; // DEBUG
+       };
 
 }
 
-function PureArrayUniform( id, activeInfo, addr ) {
+function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) {
 
-       this.id = id;
-       this.addr = addr;
-       this.cache = [];
-       this.size = activeInfo.size;
-       this.setValue = getPureArraySetter( activeInfo.type );
+       const isWebGL2 = capabilities.isWebGL2;
 
-       // this.path = activeInfo.name; // DEBUG
+       let mode;
 
-}
+       function setMode( value ) {
 
-PureArrayUniform.prototype.updateCache = function ( data ) {
+               mode = value;
 
-       const cache = this.cache;
+       }
 
-       if ( data instanceof Float32Array && cache.length !== data.length ) {
+       let type, bytesPerElement;
 
-               this.cache = new Float32Array( data.length );
+       function setIndex( value ) {
 
-       }
+               type = value.type;
+               bytesPerElement = value.bytesPerElement;
 
-       copyArray( cache, data );
+       }
 
-};
+       function render( start, count ) {
 
-function StructuredUniform( id ) {
+               gl.drawElements( mode, count, type, start * bytesPerElement );
 
-       this.id = id;
+               info.update( count, mode, 1 );
 
-       this.seq = [];
-       this.map = {};
+       }
 
-}
+       function renderInstances( start, count, primcount ) {
 
-StructuredUniform.prototype.setValue = function ( gl, value, textures ) {
+               if ( primcount === 0 ) return;
 
-       const seq = this.seq;
+               let extension, methodName;
 
-       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+               if ( isWebGL2 ) {
 
-               const u = seq[ i ];
-               u.setValue( gl, value[ u.id ], textures );
+                       extension = gl;
+                       methodName = 'drawElementsInstanced';
 
-       }
+               } else {
 
-};
+                       extension = extensions.get( 'ANGLE_instanced_arrays' );
+                       methodName = 'drawElementsInstancedANGLE';
 
-// --- Top-level ---
+                       if ( extension === null ) {
 
-// Parser - builds up the property tree from the path strings
+                               console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );
+                               return;
 
-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 ) {
+               extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount );
 
-       container.seq.push( uniformObject );
-       container.map[ uniformObject.id ] = uniformObject;
+               info.update( count, mode, primcount );
 
-}
+       }
 
-function parseUniform( activeInfo, addr, container ) {
+       //
 
-       const path = activeInfo.name,
-               pathLength = path.length;
+       this.setMode = setMode;
+       this.setIndex = setIndex;
+       this.render = render;
+       this.renderInstances = renderInstances;
 
-       // reset RegExp object, because of the early exit of a previous run
-       RePathPart.lastIndex = 0;
+}
 
-       while ( true ) {
+function WebGLInfo( gl ) {
 
-               const match = RePathPart.exec( path ),
-                       matchEnd = RePathPart.lastIndex;
+       const memory = {
+               geometries: 0,
+               textures: 0
+       };
 
-               let id = match[ 1 ];
-               const idIsIndex = match[ 2 ] === ']',
-                       subscript = match[ 3 ];
+       const render = {
+               frame: 0,
+               calls: 0,
+               triangles: 0,
+               points: 0,
+               lines: 0
+       };
 
-               if ( idIsIndex ) id = id | 0; // convert to integer
+       function update( count, mode, instanceCount ) {
 
-               if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) {
+               render.calls ++;
 
-                       // bare name or "pure" bottom-level array "[0]" suffix
+               switch ( mode ) {
 
-                       addUniform( container, subscript === undefined ?
-                               new SingleUniform( id, activeInfo, addr ) :
-                               new PureArrayUniform( id, activeInfo, addr ) );
+                       case 4:
+                               render.triangles += instanceCount * ( count / 3 );
+                               break;
 
-                       break;
+                       case 1:
+                               render.lines += instanceCount * ( count / 2 );
+                               break;
 
-               } else {
+                       case 3:
+                               render.lines += instanceCount * ( count - 1 );
+                               break;
 
-                       // step into inner node / create it in case it doesn't exist
+                       case 2:
+                               render.lines += instanceCount * count;
+                               break;
 
-                       const map = container.map;
-                       let next = map[ id ];
+                       case 0:
+                               render.points += instanceCount * count;
+                               break;
 
-                       if ( next === undefined ) {
+                       default:
+                               console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode );
+                               break;
 
-                               next = new StructuredUniform( id );
-                               addUniform( container, next );
+               }
 
-                       }
+       }
 
-                       container = next;
+       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
+       };
+
 }
 
-// Root Container
+class DataTexture2DArray extends Texture {
 
-function WebGLUniforms( gl, program ) {
+       constructor( data = null, width = 1, height = 1, depth = 1 ) {
 
-       this.seq = [];
-       this.map = {};
+               super( null );
 
-       const n = gl.getProgramParameter( program, 35718 );
+               this.image = { data, width, height, depth };
 
-       for ( let i = 0; i < n; ++ i ) {
+               this.magFilter = NearestFilter;
+               this.minFilter = NearestFilter;
 
-               const info = gl.getActiveUniform( program, i ),
-                       addr = gl.getUniformLocation( program, info.name );
+               this.wrapR = ClampToEdgeWrapping;
 
-               parseUniform( info, addr, this );
+               this.generateMipmaps = false;
+               this.flipY = false;
+               this.unpackAlignment = 1;
+
+               this.needsUpdate = true;
 
        }
 
 }
 
-WebGLUniforms.prototype.setValue = function ( gl, name, value, textures ) {
+DataTexture2DArray.prototype.isDataTexture2DArray = true;
 
-       const u = this.map[ name ];
+function numericalSort( a, b ) {
 
-       if ( u !== undefined ) u.setValue( gl, value, textures );
+       return a[ 0 ] - b[ 0 ];
 
-};
+}
 
-WebGLUniforms.prototype.setOptional = function ( gl, object, name ) {
+function absNumericalSort( a, b ) {
 
-       const v = object[ name ];
+       return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] );
 
-       if ( v !== undefined ) this.setValue( gl, name, v );
+}
 
-};
+function denormalize( morph, attribute ) {
 
+       let denominator = 1;
+       const array = attribute.isInterleavedBufferAttribute ? attribute.data.array : attribute.array;
 
-// Static interface
+       if ( array instanceof Int8Array ) denominator = 127;
+       else if ( array instanceof Int16Array ) denominator = 32767;
+       else if ( array instanceof Int32Array ) denominator = 2147483647;
+       else console.error( 'THREE.WebGLMorphtargets: Unsupported morph attribute data type: ', array );
 
-WebGLUniforms.upload = function ( gl, seq, values, textures ) {
+       morph.divideScalar( denominator );
 
-       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+}
 
-               const u = seq[ i ],
-                       v = values[ u.id ];
+function WebGLMorphtargets( gl, capabilities, textures ) {
 
-               if ( v.needsUpdate !== false ) {
+       const influencesList = {};
+       const morphInfluences = new Float32Array( 8 );
+       const morphTextures = new WeakMap();
+       const morph = new Vector3();
 
-                       // note: always updating when .needsUpdate is undefined
-                       u.setValue( gl, v.value, textures );
+       const workInfluences = [];
 
-               }
+       for ( let i = 0; i < 8; i ++ ) {
 
-       }
+               workInfluences[ i ] = [ i, 0 ];
 
-};
+       }
 
-WebGLUniforms.seqWithValue = function ( seq, values ) {
+       function update( object, geometry, material, program ) {
 
-       const r = [];
+               const objectInfluences = object.morphTargetInfluences;
 
-       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+               if ( capabilities.isWebGL2 === true ) {
 
-               const u = seq[ i ];
-               if ( u.id in values ) r.push( u );
+                       // instead of using attributes, the WebGL 2 code path encodes morph targets
+                       // into an array of data textures. Each layer represents a single morph target.
 
-       }
+                       const numberOfMorphTargets = geometry.morphAttributes.position.length;
 
-       return r;
+                       let entry = morphTextures.get( geometry );
 
-};
+                       if ( entry === undefined || entry.count !== numberOfMorphTargets ) {
 
-function WebGLShader( gl, type, string ) {
+                               if ( entry !== undefined ) entry.texture.dispose();
 
-       const shader = gl.createShader( type );
+                               const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
 
-       gl.shaderSource( shader, string );
-       gl.compileShader( shader );
+                               const morphTargets = geometry.morphAttributes.position;
+                               const morphNormals = geometry.morphAttributes.normal || [];
 
-       return shader;
+                               const numberOfVertices = geometry.attributes.position.count;
+                               const numberOfVertexData = ( hasMorphNormals === true ) ? 2 : 1; // (v,n) vs. (v)
 
-}
+                               let width = numberOfVertices * numberOfVertexData;
+                               let height = 1;
 
-let programIdCount = 0;
+                               if ( width > capabilities.maxTextureSize ) {
 
-function addLineNumbers( string ) {
+                                       height = Math.ceil( width / capabilities.maxTextureSize );
+                                       width = capabilities.maxTextureSize;
 
-       const lines = string.split( '\n' );
+                               }
 
-       for ( let i = 0; i < lines.length; i ++ ) {
+                               const buffer = new Float32Array( width * height * 4 * numberOfMorphTargets );
 
-               lines[ i ] = ( i + 1 ) + ': ' + lines[ i ];
+                               const texture = new DataTexture2DArray( buffer, width, height, numberOfMorphTargets );
+                               texture.format = RGBAFormat; // using RGBA since RGB might be emulated (and is thus slower)
+                               texture.type = FloatType;
 
-       }
+                               // fill buffer
 
-       return lines.join( '\n' );
+                               const vertexDataStride = numberOfVertexData * 4;
 
-}
+                               for ( let i = 0; i < numberOfMorphTargets; i ++ ) {
 
-function getEncodingComponents( encoding ) {
+                                       const morphTarget = morphTargets[ i ];
+                                       const morphNormal = morphNormals[ i ];
 
-       switch ( encoding ) {
+                                       const offset = width * height * 4 * i;
 
-               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 )' ];
+                                       for ( let j = 0; j < morphTarget.count; j ++ ) {
 
-       }
+                                               morph.fromBufferAttribute( morphTarget, j );
 
-}
+                                               if ( morphTarget.normalized === true ) denormalize( morph, morphTarget );
 
-function getShaderErrors( gl, shader, type ) {
+                                               const stride = j * vertexDataStride;
 
-       const status = gl.getShaderParameter( shader, 35713 );
-       const log = gl.getShaderInfoLog( shader ).trim();
+                                               buffer[ offset + stride + 0 ] = morph.x;
+                                               buffer[ offset + stride + 1 ] = morph.y;
+                                               buffer[ offset + stride + 2 ] = morph.z;
+                                               buffer[ offset + stride + 3 ] = 0;
 
-       if ( status && log === '' ) return '';
+                                               if ( hasMorphNormals === true ) {
 
-       // --enable-privileged-webgl-extension
-       // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) );
+                                                       morph.fromBufferAttribute( morphNormal, j );
 
-       const source = gl.getShaderSource( shader );
+                                                       if ( morphNormal.normalized === true ) denormalize( morph, morphNormal );
 
-       return 'THREE.WebGLShader: gl.getShaderInfoLog() ' + type + '\n' + log + addLineNumbers( source );
+                                                       buffer[ offset + stride + 4 ] = morph.x;
+                                                       buffer[ offset + stride + 5 ] = morph.y;
+                                                       buffer[ offset + stride + 6 ] = morph.z;
+                                                       buffer[ offset + stride + 7 ] = 0;
 
-}
+                                               }
 
-function getTexelDecodingFunction( functionName, encoding ) {
+                                       }
 
-       const components = getEncodingComponents( encoding );
-       return 'vec4 ' + functionName + '( vec4 value ) { return ' + components[ 0 ] + 'ToLinear' + components[ 1 ] + '; }';
+                               }
 
-}
+                               entry = {
+                                       count: numberOfMorphTargets,
+                                       texture: texture,
+                                       size: new Vector2( width, height )
+                               };
 
-function getTexelEncodingFunction( functionName, encoding ) {
+                               morphTextures.set( geometry, entry );
 
-       const components = getEncodingComponents( encoding );
-       return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }';
+                       }
 
-}
+                       //
 
-function getToneMappingFunction( functionName, toneMapping ) {
+                       let morphInfluencesSum = 0;
 
-       let toneMappingName;
+                       for ( let i = 0; i < objectInfluences.length; i ++ ) {
 
-       switch ( toneMapping ) {
+                               morphInfluencesSum += objectInfluences[ i ];
 
-               case LinearToneMapping:
-                       toneMappingName = 'Linear';
-                       break;
+                       }
 
-               case ReinhardToneMapping:
-                       toneMappingName = 'Reinhard';
-                       break;
+                       const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;
 
-               case CineonToneMapping:
-                       toneMappingName = 'OptimizedCineon';
-                       break;
+                       program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
+                       program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences );
 
-               case ACESFilmicToneMapping:
-                       toneMappingName = 'ACESFilmic';
-                       break;
+                       program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures );
+                       program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size );
 
-               case CustomToneMapping:
-                       toneMappingName = 'Custom';
-                       break;
 
-               default:
-                       console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping );
-                       toneMappingName = 'Linear';
+               } else {
 
-       }
+                       // 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
 
-       return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';
+                       const length = objectInfluences === undefined ? 0 : objectInfluences.length;
 
-}
+                       let influences = influencesList[ geometry.id ];
 
-function generateExtensions( parameters ) {
+                       if ( influences === undefined || influences.length !== length ) {
 
-       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' : ''
-       ];
+                               // initialise list
 
-       return chunks.filter( filterEmptyLine ).join( '\n' );
+                               influences = [];
 
-}
+                               for ( let i = 0; i < length; i ++ ) {
 
-function generateDefines( defines ) {
+                                       influences[ i ] = [ i, 0 ];
 
-       const chunks = [];
+                               }
 
-       for ( const name in defines ) {
+                               influencesList[ geometry.id ] = influences;
 
-               const value = defines[ name ];
+                       }
 
-               if ( value === false ) continue;
+                       // Collect influences
 
-               chunks.push( '#define ' + name + ' ' + value );
+                       for ( let i = 0; i < length; i ++ ) {
 
-       }
+                               const influence = influences[ i ];
 
-       return chunks.join( '\n' );
+                               influence[ 0 ] = i;
+                               influence[ 1 ] = objectInfluences[ i ];
 
-}
+                       }
 
-function fetchAttributeLocations( gl, program ) {
+                       influences.sort( absNumericalSort );
 
-       const attributes = {};
+                       for ( let i = 0; i < 8; i ++ ) {
 
-       const n = gl.getProgramParameter( program, 35721 );
+                               if ( i < length && influences[ i ][ 1 ] ) {
 
-       for ( let i = 0; i < n; i ++ ) {
+                                       workInfluences[ i ][ 0 ] = influences[ i ][ 0 ];
+                                       workInfluences[ i ][ 1 ] = influences[ i ][ 1 ];
 
-               const info = gl.getActiveAttrib( program, i );
-               const name = info.name;
+                               } else {
 
-               // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i );
+                                       workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER;
+                                       workInfluences[ i ][ 1 ] = 0;
 
-               attributes[ name ] = gl.getAttribLocation( program, name );
+                               }
 
-       }
+                       }
 
-       return attributes;
+                       workInfluences.sort( numericalSort );
 
-}
+                       const morphTargets = geometry.morphAttributes.position;
+                       const morphNormals = geometry.morphAttributes.normal;
 
-function filterEmptyLine( string ) {
+                       let morphInfluencesSum = 0;
 
-       return string !== '';
+                       for ( let i = 0; i < 8; i ++ ) {
 
-}
+                               const influence = workInfluences[ i ];
+                               const index = influence[ 0 ];
+                               const value = influence[ 1 ];
 
-function replaceLightNums( string, parameters ) {
+                               if ( index !== Number.MAX_SAFE_INTEGER && value ) {
 
-       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 );
+                                       if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) {
 
-}
+                                               geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] );
 
-function replaceClippingPlaneNums( string, parameters ) {
+                                       }
 
-       return string
-               .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes )
-               .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) );
+                                       if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) {
 
-}
+                                               geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] );
 
-// Resolve Includes
+                                       }
 
-const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm;
+                                       morphInfluences[ i ] = value;
+                                       morphInfluencesSum += value;
 
-function resolveIncludes( string ) {
+                               } else {
 
-       return string.replace( includePattern, includeReplacer );
+                                       if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) {
 
-}
+                                               geometry.deleteAttribute( 'morphTarget' + i );
 
-function includeReplacer( match, include ) {
+                                       }
 
-       const string = ShaderChunk[ include ];
+                                       if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) {
 
-       if ( string === undefined ) {
+                                               geometry.deleteAttribute( 'morphNormal' + i );
 
-               throw new Error( 'Can not resolve #include <' + include + '>' );
+                                       }
 
-       }
+                                       morphInfluences[ i ] = 0;
 
-       return resolveIncludes( string );
+                               }
 
-}
+                       }
 
-// Unroll Loops
+                       // 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;
 
-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;
+                       program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
+                       program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences );
 
-function unrollLoops( string ) {
+               }
 
-       return string
-               .replace( unrollLoopPattern, loopReplacer )
-               .replace( deprecatedUnrollLoopPattern, deprecatedLoopReplacer );
+       }
 
-}
+       return {
 
-function deprecatedLoopReplacer( match, start, end, snippet ) {
+               update: update
 
-       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 ) {
+function WebGLObjects( gl, geometries, attributes, info ) {
 
-       let string = '';
+       let updateMap = new WeakMap();
 
-       for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) {
+       function update( object ) {
 
-               string += snippet
-                       .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' )
-                       .replace( /UNROLLED_LOOP_INDEX/g, i );
+               const frame = info.render.frame;
 
-       }
+               const geometry = object.geometry;
+               const buffergeometry = geometries.get( object, geometry );
 
-       return string;
+               // Update once per frame
 
-}
+               if ( updateMap.get( buffergeometry ) !== frame ) {
 
-//
+                       geometries.update( buffergeometry );
 
-function generatePrecision( parameters ) {
+                       updateMap.set( buffergeometry, frame );
 
-       let precisionstring = 'precision ' + parameters.precision + ' float;\nprecision ' + parameters.precision + ' int;';
+               }
 
-       if ( parameters.precision === 'highp' ) {
+               if ( object.isInstancedMesh ) {
 
-               precisionstring += '\n#define HIGH_PRECISION';
+                       if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) {
 
-       } else if ( parameters.precision === 'mediump' ) {
+                               object.addEventListener( 'dispose', onInstancedMeshDispose );
 
-               precisionstring += '\n#define MEDIUM_PRECISION';
+                       }
 
-       } else if ( parameters.precision === 'lowp' ) {
+                       attributes.update( object.instanceMatrix, 34962 );
 
-               precisionstring += '\n#define LOW_PRECISION';
+                       if ( object.instanceColor !== null ) {
 
-       }
+                               attributes.update( object.instanceColor, 34962 );
 
-       return precisionstring;
+                       }
 
-}
+               }
 
-function generateShadowMapTypeDefine( parameters ) {
+               return buffergeometry;
 
-       let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC';
+       }
 
-       if ( parameters.shadowMapType === PCFShadowMap ) {
+       function dispose() {
 
-               shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF';
+               updateMap = new WeakMap();
 
-       } else if ( parameters.shadowMapType === PCFSoftShadowMap ) {
+       }
 
-               shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT';
+       function onInstancedMeshDispose( event ) {
 
-       } else if ( parameters.shadowMapType === VSMShadowMap ) {
+               const instancedMesh = event.target;
 
-               shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM';
+               instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose );
+
+               attributes.remove( instancedMesh.instanceMatrix );
+
+               if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor );
 
        }
 
-       return shadowMapTypeDefine;
+       return {
+
+               update: update,
+               dispose: dispose
+
+       };
 
 }
 
-function generateEnvMapTypeDefine( parameters ) {
+class DataTexture3D extends Texture {
 
-       let envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
+       constructor( data = null, width = 1, height = 1, depth = 1 ) {
 
-       if ( parameters.envMap ) {
+               // 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
 
-               switch ( parameters.envMapMode ) {
+               super( null );
 
-                       case CubeReflectionMapping:
-                       case CubeRefractionMapping:
-                               envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
-                               break;
+               this.image = { data, width, height, depth };
 
-                       case CubeUVReflectionMapping:
-                       case CubeUVRefractionMapping:
-                               envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV';
-                               break;
+               this.magFilter = NearestFilter;
+               this.minFilter = NearestFilter;
 
-               }
+               this.wrapR = ClampToEdgeWrapping;
 
-       }
+               this.generateMipmaps = false;
+               this.flipY = false;
+               this.unpackAlignment = 1;
 
-       return envMapTypeDefine;
+               this.needsUpdate = true;
+
+       }
 
 }
 
-function generateEnvMapModeDefine( parameters ) {
+DataTexture3D.prototype.isDataTexture3D = true;
 
-       let envMapModeDefine = 'ENVMAP_MODE_REFLECTION';
+/**
+ * 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
+ *
+ */
 
-       if ( parameters.envMap ) {
+const emptyTexture = new Texture();
+const emptyTexture2dArray = new DataTexture2DArray();
+const emptyTexture3d = new DataTexture3D();
+const emptyCubeTexture = new CubeTexture();
 
-               switch ( parameters.envMapMode ) {
+// --- Utilities ---
 
-                       case CubeRefractionMapping:
-                       case CubeUVRefractionMapping:
+// Array Caches (provide typed arrays for temporary by size)
 
-                               envMapModeDefine = 'ENVMAP_MODE_REFRACTION';
-                               break;
+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 );
 
-       return envMapModeDefine;
+// Flattening for arrays of vectors and matrices
 
-}
+function flatten( array, nBlocks, blockSize ) {
 
-function generateEnvMapBlendingDefine( parameters ) {
+       const firstElem = array[ 0 ];
 
-       let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE';
+       if ( firstElem <= 0 || firstElem > 0 ) return array;
+       // unoptimized: ! isNaN( firstElem )
+       // see http://jacksondunstan.com/articles/983
 
-       if ( parameters.envMap ) {
+       const n = nBlocks * blockSize;
+       let r = arrayCacheF32[ n ];
 
-               switch ( parameters.combine ) {
+       if ( r === undefined ) {
 
-                       case MultiplyOperation:
-                               envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY';
-                               break;
+               r = new Float32Array( n );
+               arrayCacheF32[ n ] = r;
 
-                       case MixOperation:
-                               envMapBlendingDefine = 'ENVMAP_BLENDING_MIX';
-                               break;
+       }
 
-                       case AddOperation:
-                               envMapBlendingDefine = 'ENVMAP_BLENDING_ADD';
-                               break;
+       if ( nBlocks !== 0 ) {
+
+               firstElem.toArray( r, 0 );
+
+               for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) {
+
+                       offset += blockSize;
+                       array[ i ].toArray( r, offset );
 
                }
 
        }
 
-       return envMapBlendingDefine;
+       return r;
 
 }
 
-function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
+function arraysEqual( a, b ) {
 
-       const gl = renderer.getContext();
+       if ( a.length !== b.length ) return false;
 
-       const defines = parameters.defines;
+       for ( let i = 0, l = a.length; i < l; i ++ ) {
 
-       let vertexShader = parameters.vertexShader;
-       let fragmentShader = parameters.fragmentShader;
+               if ( a[ i ] !== b[ i ] ) return false;
 
-       const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters );
-       const envMapTypeDefine = generateEnvMapTypeDefine( parameters );
-       const envMapModeDefine = generateEnvMapModeDefine( parameters );
-       const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters );
+       }
 
+       return true;
 
-       const gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0;
+}
 
-       const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters );
+function copyArray( a, b ) {
 
-       const customDefines = generateDefines( defines );
+       for ( let i = 0, l = b.length; i < l; i ++ ) {
 
-       const program = gl.createProgram();
+               a[ i ] = b[ i ];
 
-       let prefixVertex, prefixFragment;
-       let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : '';
+       }
 
-       if ( parameters.isRawShaderMaterial ) {
+}
 
-               prefixVertex = [
+// Texture unit allocation
 
-                       customDefines
+function allocTexUnits( textures, n ) {
 
-               ].filter( filterEmptyLine ).join( '\n' );
+       let r = arrayCacheI32[ n ];
 
-               if ( prefixVertex.length > 0 ) {
+       if ( r === undefined ) {
 
-                       prefixVertex += '\n';
+               r = new Int32Array( n );
+               arrayCacheI32[ n ] = r;
 
-               }
+       }
 
-               prefixFragment = [
+       for ( let i = 0; i !== n; ++ i ) {
 
-                       customExtensions,
-                       customDefines
+               r[ i ] = textures.allocateTextureUnit();
 
-               ].filter( filterEmptyLine ).join( '\n' );
+       }
 
-               if ( prefixFragment.length > 0 ) {
+       return r;
 
-                       prefixFragment += '\n';
+}
 
-               }
+// --- Setters ---
 
-       } else {
+// Note: Defining these methods externally, because they come in a bunch
+// and this way their names minify.
 
-               prefixVertex = [
+// Single scalar
 
-                       generatePrecision( parameters ),
+function setValueV1f( gl, v ) {
 
-                       '#define SHADER_NAME ' + parameters.shaderName,
+       const cache = this.cache;
 
-                       customDefines,
+       if ( cache[ 0 ] === v ) return;
 
-                       parameters.instancing ? '#define USE_INSTANCING' : '',
-                       parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '',
+       gl.uniform1f( this.addr, v );
 
-                       parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '',
+       cache[ 0 ] = v;
 
-                       '#define GAMMA_FACTOR ' + gammaFactorDefine,
+}
 
-                       '#define MAX_BONES ' + parameters.maxBones,
-                       ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
-                       ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',
+// Single float vector (from flat array or THREE.VectorN)
 
-                       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' : '',
+function setValueV2f( gl, v ) {
 
-                       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' : '',
+       const cache = this.cache;
 
-                       parameters.vertexTangents ? '#define USE_TANGENT' : '',
-                       parameters.vertexColors ? '#define USE_COLOR' : '',
-                       parameters.vertexUvs ? '#define USE_UV' : '',
-                       parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',
+       if ( v.x !== undefined ) {
 
-                       parameters.flatShading ? '#define FLAT_SHADED' : '',
+               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) {
 
-                       parameters.skinning ? '#define USE_SKINNING' : '',
-                       parameters.useVertexTexture ? '#define BONE_TEXTURE' : '',
+                       gl.uniform2f( this.addr, v.x, v.y );
 
-                       parameters.morphTargets ? '#define USE_MORPHTARGETS' : '',
-                       parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '',
-                       parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
-                       parameters.flipSided ? '#define FLIP_SIDED' : '',
+                       cache[ 0 ] = v.x;
+                       cache[ 1 ] = v.y;
 
-                       parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
-                       parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
+               }
 
-                       parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '',
+       } else {
 
-                       parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
-                       ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
+               if ( arraysEqual( cache, v ) ) return;
 
-                       'uniform mat4 modelMatrix;',
-                       'uniform mat4 modelViewMatrix;',
-                       'uniform mat4 projectionMatrix;',
-                       'uniform mat4 viewMatrix;',
-                       'uniform mat3 normalMatrix;',
-                       'uniform vec3 cameraPosition;',
-                       'uniform bool isOrthographic;',
+               gl.uniform2fv( this.addr, v );
 
-                       '#ifdef USE_INSTANCING',
+               copyArray( cache, v );
 
-                       '       attribute mat4 instanceMatrix;',
+       }
 
-                       '#endif',
+}
 
-                       '#ifdef USE_INSTANCING_COLOR',
+function setValueV3f( gl, v ) {
 
-                       '       attribute vec3 instanceColor;',
+       const cache = this.cache;
 
-                       '#endif',
+       if ( v.x !== undefined ) {
 
-                       'attribute vec3 position;',
-                       'attribute vec3 normal;',
-                       'attribute vec2 uv;',
+               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) {
 
-                       '#ifdef USE_TANGENT',
+                       gl.uniform3f( this.addr, v.x, v.y, v.z );
 
-                       '       attribute vec4 tangent;',
+                       cache[ 0 ] = v.x;
+                       cache[ 1 ] = v.y;
+                       cache[ 2 ] = v.z;
 
-                       '#endif',
+               }
 
-                       '#ifdef USE_COLOR',
+       } else if ( v.r !== undefined ) {
 
-                       '       attribute vec3 color;',
+               if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) {
 
-                       '#endif',
+                       gl.uniform3f( this.addr, v.r, v.g, v.b );
 
-                       '#ifdef USE_MORPHTARGETS',
+                       cache[ 0 ] = v.r;
+                       cache[ 1 ] = v.g;
+                       cache[ 2 ] = v.b;
 
-                       '       attribute vec3 morphTarget0;',
-                       '       attribute vec3 morphTarget1;',
-                       '       attribute vec3 morphTarget2;',
-                       '       attribute vec3 morphTarget3;',
+               }
 
-                       '       #ifdef USE_MORPHNORMALS',
+       } else {
 
-                       '               attribute vec3 morphNormal0;',
-                       '               attribute vec3 morphNormal1;',
-                       '               attribute vec3 morphNormal2;',
-                       '               attribute vec3 morphNormal3;',
+               if ( arraysEqual( cache, v ) ) return;
 
-                       '       #else',
+               gl.uniform3fv( this.addr, v );
 
-                       '               attribute vec3 morphTarget4;',
-                       '               attribute vec3 morphTarget5;',
-                       '               attribute vec3 morphTarget6;',
-                       '               attribute vec3 morphTarget7;',
+               copyArray( cache, v );
 
-                       '       #endif',
+       }
 
-                       '#endif',
+}
 
-                       '#ifdef USE_SKINNING',
+function setValueV4f( gl, v ) {
 
-                       '       attribute vec4 skinIndex;',
-                       '       attribute vec4 skinWeight;',
+       const cache = this.cache;
 
-                       '#endif',
+       if ( v.x !== undefined ) {
 
-                       '\n'
+               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) {
 
-               ].filter( filterEmptyLine ).join( '\n' );
+                       gl.uniform4f( this.addr, v.x, v.y, v.z, v.w );
 
-               prefixFragment = [
+                       cache[ 0 ] = v.x;
+                       cache[ 1 ] = v.y;
+                       cache[ 2 ] = v.z;
+                       cache[ 3 ] = v.w;
 
-                       customExtensions,
+               }
 
-                       generatePrecision( parameters ),
+       } else {
 
-                       '#define SHADER_NAME ' + parameters.shaderName,
+               if ( arraysEqual( cache, v ) ) return;
 
-                       customDefines,
+               gl.uniform4fv( this.addr, v );
 
-                       parameters.alphaTest ? '#define ALPHATEST ' + parameters.alphaTest + ( parameters.alphaTest % 1 ? '' : '.0' ) : '', // add '.0' if integer
+               copyArray( cache, v );
 
-                       '#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' : '',
+// Single matrix (from flat array or THREE.MatrixN)
 
-                       parameters.sheen ? '#define USE_SHEEN' : '',
-                       parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '',
+function setValueM2( gl, v ) {
 
-                       parameters.vertexTangents ? '#define USE_TANGENT' : '',
-                       parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '',
-                       parameters.vertexUvs ? '#define USE_UV' : '',
-                       parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',
+       const cache = this.cache;
+       const elements = v.elements;
 
-                       parameters.gradientMap ? '#define USE_GRADIENTMAP' : '',
+       if ( elements === undefined ) {
 
-                       parameters.flatShading ? '#define FLAT_SHADED' : '',
+               if ( arraysEqual( cache, v ) ) return;
 
-                       parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
-                       parameters.flipSided ? '#define FLIP_SIDED' : '',
+               gl.uniformMatrix2fv( this.addr, false, v );
 
-                       parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
-                       parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
+               copyArray( cache, v );
 
-                       parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '',
+       } else {
 
-                       parameters.physicallyCorrectLights ? '#define PHYSICALLY_CORRECT_LIGHTS' : '',
+               if ( arraysEqual( cache, elements ) ) return;
 
-                       parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
-                       ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
+               mat2array.set( elements );
 
-                       ( ( parameters.extensionShaderTextureLOD || parameters.envMap ) && parameters.rendererExtensionShaderTextureLod ) ? '#define TEXTURE_LOD_EXT' : '',
+               gl.uniformMatrix2fv( this.addr, false, mat2array );
 
-                       'uniform mat4 viewMatrix;',
-                       'uniform vec3 cameraPosition;',
-                       'uniform bool isOrthographic;',
+               copyArray( cache, elements );
 
-                       ( 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 ),
+function setValueM3( gl, v ) {
 
-                       parameters.depthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '',
+       const cache = this.cache;
+       const elements = v.elements;
 
-                       '\n'
+       if ( elements === undefined ) {
 
-               ].filter( filterEmptyLine ).join( '\n' );
+               if ( arraysEqual( cache, v ) ) return;
 
-       }
+               gl.uniformMatrix3fv( this.addr, false, v );
 
-       vertexShader = resolveIncludes( vertexShader );
-       vertexShader = replaceLightNums( vertexShader, parameters );
-       vertexShader = replaceClippingPlaneNums( vertexShader, parameters );
+               copyArray( cache, v );
 
-       fragmentShader = resolveIncludes( fragmentShader );
-       fragmentShader = replaceLightNums( fragmentShader, parameters );
-       fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters );
+       } else {
 
-       vertexShader = unrollLoops( vertexShader );
-       fragmentShader = unrollLoops( fragmentShader );
+               if ( arraysEqual( cache, elements ) ) return;
 
-       if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) {
+               mat3array.set( elements );
 
-               // GLSL 3.0 conversion for built-in materials and ShaderMaterial
+               gl.uniformMatrix3fv( this.addr, false, mat3array );
 
-               versionString = '#version 300 es\n';
+               copyArray( cache, elements );
 
-               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;
+}
 
-       }
+function setValueM4( gl, v ) {
 
-       const vertexGlsl = versionString + prefixVertex + vertexShader;
-       const fragmentGlsl = versionString + prefixFragment + fragmentShader;
+       const cache = this.cache;
+       const elements = v.elements;
 
-       // console.log( '*VERTEX*', vertexGlsl );
-       // console.log( '*FRAGMENT*', fragmentGlsl );
+       if ( elements === undefined ) {
 
-       const glVertexShader = WebGLShader( gl, 35633, vertexGlsl );
-       const glFragmentShader = WebGLShader( gl, 35632, fragmentGlsl );
+               if ( arraysEqual( cache, v ) ) return;
 
-       gl.attachShader( program, glVertexShader );
-       gl.attachShader( program, glFragmentShader );
+               gl.uniformMatrix4fv( this.addr, false, v );
 
-       // Force a particular attribute to index 0.
+               copyArray( cache, v );
 
-       if ( parameters.index0AttributeName !== undefined ) {
+       } else {
 
-               gl.bindAttribLocation( program, 0, parameters.index0AttributeName );
+               if ( arraysEqual( cache, elements ) ) return;
 
-       } else if ( parameters.morphTargets === true ) {
+               mat4array.set( elements );
 
-               // programs with morphTargets displace position out of attribute 0
-               gl.bindAttribLocation( program, 0, 'position' );
+               gl.uniformMatrix4fv( this.addr, false, mat4array );
+
+               copyArray( cache, elements );
 
        }
 
-       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();
+// Single integer / boolean
 
-               let runnable = true;
-               let haveDiagnostics = true;
+function setValueV1i( gl, v ) {
 
-               if ( gl.getProgramParameter( program, 35714 ) === false ) {
+       const cache = this.cache;
 
-                       runnable = false;
+       if ( cache[ 0 ] === v ) return;
 
-                       const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' );
-                       const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' );
+       gl.uniform1i( this.addr, v );
 
-                       console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), '35715', gl.getProgramParameter( program, 35715 ), 'gl.getProgramInfoLog', programLog, vertexErrors, fragmentErrors );
+       cache[ 0 ] = v;
 
-               } else if ( programLog !== '' ) {
+}
 
-                       console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog );
+// Single integer / boolean vector (from flat array)
 
-               } else if ( vertexLog === '' || fragmentLog === '' ) {
+function setValueV2i( gl, v ) {
 
-                       haveDiagnostics = false;
+       const cache = this.cache;
 
-               }
+       if ( arraysEqual( cache, v ) ) return;
 
-               if ( haveDiagnostics ) {
+       gl.uniform2iv( this.addr, v );
 
-                       this.diagnostics = {
+       copyArray( cache, v );
 
-                               runnable: runnable,
+}
 
-                               programLog: programLog,
+function setValueV3i( gl, v ) {
 
-                               vertexShader: {
+       const cache = this.cache;
 
-                                       log: vertexLog,
-                                       prefix: prefixVertex
+       if ( arraysEqual( cache, v ) ) return;
 
-                               },
+       gl.uniform3iv( this.addr, v );
 
-                               fragmentShader: {
+       copyArray( cache, v );
 
-                                       log: fragmentLog,
-                                       prefix: prefixFragment
+}
 
-                               }
+function setValueV4i( gl, v ) {
 
-                       };
+       const cache = this.cache;
 
-               }
+       if ( arraysEqual( cache, v ) ) return;
 
-       }
+       gl.uniform4iv( this.addr, v );
 
-       // Clean up
+       copyArray( cache, v );
 
-       // Crashes in iOS9 and iOS10. #18402
-       // gl.detachShader( program, glVertexShader );
-       // gl.detachShader( program, glFragmentShader );
+}
 
-       gl.deleteShader( glVertexShader );
-       gl.deleteShader( glFragmentShader );
+// Single unsigned integer
 
-       // set up caching for uniform locations
+function setValueV1ui( gl, v ) {
 
-       let cachedUniforms;
+       const cache = this.cache;
 
-       this.getUniforms = function () {
+       if ( cache[ 0 ] === v ) return;
 
-               if ( cachedUniforms === undefined ) {
+       gl.uniform1ui( this.addr, v );
 
-                       cachedUniforms = new WebGLUniforms( gl, program );
+       cache[ 0 ] = v;
 
-               }
+}
 
-               return cachedUniforms;
+// Single unsigned integer vector (from flat array)
 
-       };
+function setValueV2ui( gl, v ) {
 
-       // set up caching for attribute locations
+       const cache = this.cache;
 
-       let cachedAttributes;
+       if ( arraysEqual( cache, v ) ) return;
 
-       this.getAttributes = function () {
+       gl.uniform2uiv( this.addr, v );
 
-               if ( cachedAttributes === undefined ) {
+       copyArray( cache, v );
 
-                       cachedAttributes = fetchAttributeLocations( gl, program );
+}
 
-               }
+function setValueV3ui( gl, v ) {
 
-               return cachedAttributes;
+       const cache = this.cache;
 
-       };
+       if ( arraysEqual( cache, v ) ) return;
 
-       // free resource
+       gl.uniform3uiv( this.addr, v );
 
-       this.destroy = function () {
+       copyArray( cache, v );
 
-               bindingStates.releaseStatesOfProgram( this );
+}
 
-               gl.deleteProgram( program );
-               this.program = undefined;
+function setValueV4ui( gl, v ) {
 
-       };
+       const cache = this.cache;
 
-       //
+       if ( arraysEqual( cache, v ) ) return;
 
-       this.name = parameters.shaderName;
-       this.id = programIdCount ++;
-       this.cacheKey = cacheKey;
-       this.usedTimes = 1;
-       this.program = program;
-       this.vertexShader = glVertexShader;
-       this.fragmentShader = glFragmentShader;
+       gl.uniform4uiv( this.addr, v );
 
-       return this;
+       copyArray( cache, v );
 
 }
 
-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'
-       };
+// Single texture (2D / Cube)
 
-       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 setValueT1( gl, v, textures ) {
 
-       function getMaxBones( object ) {
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
 
-               const skeleton = object.skeleton;
-               const bones = skeleton.bones;
+       if ( cache[ 0 ] !== unit ) {
 
-               if ( floatVertexTextures ) {
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
 
-                       return 1024;
+       }
 
-               } else {
+       textures.safeSetTexture2D( v || emptyTexture, unit );
 
-                       // 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 );
+function setValueT3D1( gl, v, textures ) {
 
-                       const maxBones = Math.min( nVertexMatrices, bones.length );
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
 
-                       if ( maxBones < bones.length ) {
+       if ( cache[ 0 ] !== unit ) {
 
-                               console.warn( 'THREE.WebGLRenderer: Skeleton has ' + bones.length + ' bones. This GPU supports ' + maxBones + '.' );
-                               return 0;
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
 
-                       }
+       }
 
-                       return maxBones;
+       textures.setTexture3D( v || emptyTexture3d, unit );
 
-               }
+}
 
-       }
+function setValueT6( gl, v, textures ) {
 
-       function getTextureEncodingFromMap( map ) {
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
 
-               let encoding;
+       if ( cache[ 0 ] !== unit ) {
 
-               if ( map && map.isTexture ) {
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
 
-                       encoding = map.encoding;
+       }
 
-               } else if ( map && map.isWebGLRenderTarget ) {
+       textures.safeSetTextureCube( v || emptyCubeTexture, unit );
 
-                       console.warn( 'THREE.WebGLPrograms.getTextureEncodingFromMap: don\'t use render targets as textures. Use their .texture property instead.' );
-                       encoding = map.texture.encoding;
+}
 
-               } else {
+function setValueT2DArray1( gl, v, textures ) {
 
-                       encoding = LinearEncoding;
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
 
-               }
+       if ( cache[ 0 ] !== unit ) {
 
-               return encoding;
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
 
        }
 
-       function getParameters( material, lights, shadows, scene, object ) {
-
-               const fog = scene.fog;
-               const environment = material.isMeshStandardMaterial ? scene.environment : null;
+       textures.setTexture2DArray( v || emptyTexture2dArray, unit );
 
-               const envMap = cubemaps.get( material.envMap || environment );
+}
 
-               const shaderID = shaderIDs[ material.type ];
+// Helper to pick the right setter for the singular case
 
-               // heuristics to create shader parameters according to lights in the scene
-               // (not to blow over maxLights budget)
+function getSingularSetter( type ) {
 
-               const maxBones = object.isSkinnedMesh ? getMaxBones( object ) : 0;
+       switch ( type ) {
 
-               if ( material.precision !== null ) {
+               case 0x1406: return setValueV1f; // FLOAT
+               case 0x8b50: return setValueV2f; // _VEC2
+               case 0x8b51: return setValueV3f; // _VEC3
+               case 0x8b52: return setValueV4f; // _VEC4
 
-                       precision = capabilities.getMaxPrecision( material.precision );
+               case 0x8b5a: return setValueM2; // _MAT2
+               case 0x8b5b: return setValueM3; // _MAT3
+               case 0x8b5c: return setValueM4; // _MAT4
 
-                       if ( precision !== material.precision ) {
+               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
 
-                               console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' );
+               case 0x1405: return setValueV1ui; // UINT
+               case 0x8dc6: return setValueV2ui; // _VEC2
+               case 0x8dc7: return setValueV3ui; // _VEC3
+               case 0x8dc8: return setValueV4ui; // _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 setValueT1;
 
-               }
+               case 0x8b5f: // SAMPLER_3D
+               case 0x8dcb: // INT_SAMPLER_3D
+               case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D
+                       return setValueT3D1;
 
-               let vertexShader, fragmentShader;
+               case 0x8b60: // SAMPLER_CUBE
+               case 0x8dcc: // INT_SAMPLER_CUBE
+               case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE
+               case 0x8dc5: // SAMPLER_CUBE_SHADOW
+                       return setValueT6;
 
-               if ( shaderID ) {
+               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;
 
-                       const shader = ShaderLib[ shaderID ];
+       }
 
-                       vertexShader = shader.vertexShader;
-                       fragmentShader = shader.fragmentShader;
+}
 
-               } else {
 
-                       vertexShader = material.vertexShader;
-                       fragmentShader = material.fragmentShader;
+// Array of scalars
 
-               }
+function setValueV1fArray( gl, v ) {
 
-               const currentRenderTarget = renderer.getRenderTarget();
+       gl.uniform1fv( this.addr, v );
 
-               const parameters = {
+}
 
-                       isWebGL2: isWebGL2,
+// Array of vectors (from flat array or array of THREE.VectorN)
 
-                       shaderID: shaderID,
-                       shaderName: material.type,
+function setValueV2fArray( gl, v ) {
 
-                       vertexShader: vertexShader,
-                       fragmentShader: fragmentShader,
-                       defines: material.defines,
+       const data = flatten( v, this.size, 2 );
 
-                       isRawShaderMaterial: material.isRawShaderMaterial === true,
-                       glslVersion: material.glslVersion,
+       gl.uniform2fv( this.addr, data );
 
-                       precision: precision,
+}
 
-                       instancing: object.isInstancedMesh === true,
-                       instancingColor: object.isInstancedMesh === true && object.instanceColor !== null,
+function setValueV3fArray( gl, v ) {
 
-                       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,
+       const data = flatten( v, this.size, 3 );
 
-                       gradientMap: !! material.gradientMap,
+       gl.uniform3fv( this.addr, data );
 
-                       sheen: !! material.sheen,
+}
 
-                       transmissionMap: !! material.transmissionMap,
+function setValueV4fArray( gl, v ) {
 
-                       combine: material.combine,
+       const data = flatten( v, this.size, 4 );
 
-                       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,
+       gl.uniform4fv( this.addr, data );
 
-                       fog: !! fog,
-                       useFog: material.fog,
-                       fogExp2: ( fog && fog.isFogExp2 ),
+}
 
-                       flatShading: material.flatShading,
+// Array of matrices (from flat array or array of THREE.MatrixN)
 
-                       sizeAttenuation: material.sizeAttenuation,
-                       logarithmicDepthBuffer: logarithmicDepthBuffer,
+function setValueM2Array( gl, v ) {
 
-                       skinning: material.skinning && maxBones > 0,
-                       maxBones: maxBones,
-                       useVertexTexture: floatVertexTextures,
+       const data = flatten( v, this.size, 4 );
 
-                       morphTargets: material.morphTargets,
-                       morphNormals: material.morphNormals,
-                       maxMorphTargets: renderer.maxMorphTargets,
-                       maxMorphNormals: renderer.maxMorphNormals,
+       gl.uniformMatrix2fv( this.addr, false, data );
 
-                       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,
+function setValueM3Array( gl, v ) {
 
-                       numClippingPlanes: clipping.numPlanes,
-                       numClipIntersection: clipping.numIntersection,
+       const data = flatten( v, this.size, 9 );
 
-                       dithering: material.dithering,
+       gl.uniformMatrix3fv( this.addr, false, data );
 
-                       shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0,
-                       shadowMapType: renderer.shadowMap.type,
+}
 
-                       toneMapping: material.toneMapped ? renderer.toneMapping : NoToneMapping,
-                       physicallyCorrectLights: renderer.physicallyCorrectLights,
+function setValueM4Array( gl, v ) {
 
-                       premultipliedAlpha: material.premultipliedAlpha,
+       const data = flatten( v, this.size, 16 );
 
-                       alphaTest: material.alphaTest,
-                       doubleSided: material.side === DoubleSide,
-                       flipSided: material.side === BackSide,
+       gl.uniformMatrix4fv( this.addr, false, data );
 
-                       depthPacking: ( material.depthPacking !== undefined ) ? material.depthPacking : false,
+}
 
-                       index0AttributeName: material.index0AttributeName,
+// Array of integer / boolean
 
-                       extensionDerivatives: material.extensions && material.extensions.derivatives,
-                       extensionFragDepth: material.extensions && material.extensions.fragDepth,
-                       extensionDrawBuffers: material.extensions && material.extensions.drawBuffers,
-                       extensionShaderTextureLOD: material.extensions && material.extensions.shaderTextureLOD,
+function setValueV1iArray( gl, v ) {
 
-                       rendererExtensionFragDepth: isWebGL2 || extensions.has( 'EXT_frag_depth' ),
-                       rendererExtensionDrawBuffers: isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ),
-                       rendererExtensionShaderTextureLod: isWebGL2 || extensions.has( 'EXT_shader_texture_lod' ),
+       gl.uniform1iv( this.addr, v );
 
-                       customProgramCacheKey: material.customProgramCacheKey()
+}
 
-               };
+// Array of integer / boolean vectors (from flat array)
 
-               return parameters;
+function setValueV2iArray( gl, v ) {
 
-       }
+       gl.uniform2iv( this.addr, v );
 
-       function getProgramCacheKey( parameters ) {
+}
 
-               const array = [];
+function setValueV3iArray( gl, v ) {
 
-               if ( parameters.shaderID ) {
+       gl.uniform3iv( this.addr, v );
 
-                       array.push( parameters.shaderID );
+}
 
-               } else {
+function setValueV4iArray( gl, v ) {
 
-                       array.push( parameters.fragmentShader );
-                       array.push( parameters.vertexShader );
+       gl.uniform4iv( this.addr, v );
 
-               }
+}
 
-               if ( parameters.defines !== undefined ) {
+// Array of unsigned integer
 
-                       for ( const name in parameters.defines ) {
+function setValueV1uiArray( gl, v ) {
 
-                               array.push( name );
-                               array.push( parameters.defines[ name ] );
+       gl.uniform1uiv( this.addr, v );
 
-                       }
+}
 
-               }
+// Array of unsigned integer vectors (from flat array)
 
-               if ( parameters.isRawShaderMaterial === false ) {
+function setValueV2uiArray( gl, v ) {
 
-                       for ( let i = 0; i < parameterNames.length; i ++ ) {
+       gl.uniform2uiv( this.addr, v );
 
-                               array.push( parameters[ parameterNames[ i ] ] );
+}
 
-                       }
+function setValueV3uiArray( gl, v ) {
 
-                       array.push( renderer.outputEncoding );
-                       array.push( renderer.gammaFactor );
+       gl.uniform3uiv( this.addr, v );
 
-               }
+}
 
-               array.push( parameters.customProgramCacheKey );
+function setValueV4uiArray( gl, v ) {
 
-               return array.join();
+       gl.uniform4uiv( this.addr, v );
 
-       }
+}
 
-       function getUniforms( material ) {
 
-               const shaderID = shaderIDs[ material.type ];
-               let uniforms;
+// Array of textures (2D / Cube)
 
-               if ( shaderID ) {
+function setValueT1Array( gl, v, textures ) {
 
-                       const shader = ShaderLib[ shaderID ];
-                       uniforms = UniformsUtils.clone( shader.uniforms );
+       const n = v.length;
 
-               } else {
+       const units = allocTexUnits( textures, n );
 
-                       uniforms = material.uniforms;
+       gl.uniform1iv( this.addr, units );
 
-               }
+       for ( let i = 0; i !== n; ++ i ) {
 
-               return uniforms;
+               textures.safeSetTexture2D( v[ i ] || emptyTexture, units[ i ] );
 
        }
 
-       function acquireProgram( parameters, cacheKey ) {
-
-               let program;
+}
 
-               // Check if code has been already compiled
-               for ( let p = 0, pl = programs.length; p < pl; p ++ ) {
+function setValueT6Array( gl, v, textures ) {
 
-                       const preexistingProgram = programs[ p ];
+       const n = v.length;
 
-                       if ( preexistingProgram.cacheKey === cacheKey ) {
+       const units = allocTexUnits( textures, n );
 
-                               program = preexistingProgram;
-                               ++ program.usedTimes;
+       gl.uniform1iv( this.addr, units );
 
-                               break;
+       for ( let i = 0; i !== n; ++ i ) {
 
-                       }
+               textures.safeSetTextureCube( v[ i ] || emptyCubeTexture, units[ i ] );
 
-               }
+       }
 
-               if ( program === undefined ) {
+}
 
-                       program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates );
-                       programs.push( program );
+// Helper to pick the right setter for a pure (bottom-level) array
 
-               }
+function getPureArraySetter( type ) {
 
-               return program;
+       switch ( type ) {
 
-       }
+               case 0x1406: return setValueV1fArray; // FLOAT
+               case 0x8b50: return setValueV2fArray; // _VEC2
+               case 0x8b51: return setValueV3fArray; // _VEC3
+               case 0x8b52: return setValueV4fArray; // _VEC4
 
-       function releaseProgram( program ) {
+               case 0x8b5a: return setValueM2Array; // _MAT2
+               case 0x8b5b: return setValueM3Array; // _MAT3
+               case 0x8b5c: return setValueM4Array; // _MAT4
 
-               if ( -- program.usedTimes === 0 ) {
+               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
 
-                       // Remove from unordered set
-                       const i = programs.indexOf( program );
-                       programs[ i ] = programs[ programs.length - 1 ];
-                       programs.pop();
+               case 0x1405: return setValueV1uiArray; // UINT
+               case 0x8dc6: return setValueV2uiArray; // _VEC2
+               case 0x8dc7: return setValueV3uiArray; // _VEC3
+               case 0x8dc8: return setValueV4uiArray; // _VEC4
 
-                       // Free WebGL resources
-                       program.destroy();
+               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;
 
        }
 
-       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();
+// --- Uniform Classes ---
 
-       function get( object ) {
+function SingleUniform( id, activeInfo, addr ) {
 
-               let map = properties.get( object );
+       this.id = id;
+       this.addr = addr;
+       this.cache = [];
+       this.setValue = getSingularSetter( activeInfo.type );
 
-               if ( map === undefined ) {
+       // this.path = activeInfo.name; // DEBUG
 
-                       map = {};
-                       properties.set( object, map );
+}
 
-               }
+function PureArrayUniform( id, activeInfo, addr ) {
 
-               return map;
+       this.id = id;
+       this.addr = addr;
+       this.cache = [];
+       this.size = activeInfo.size;
+       this.setValue = getPureArraySetter( activeInfo.type );
 
-       }
+       // this.path = activeInfo.name; // DEBUG
 
-       function remove( object ) {
+}
 
-               properties.delete( object );
+PureArrayUniform.prototype.updateCache = function ( data ) {
 
-       }
+       const cache = this.cache;
 
-       function update( object, key, value ) {
+       if ( data instanceof Float32Array && cache.length !== data.length ) {
 
-               properties.get( object )[ key ] = value;
+               this.cache = new Float32Array( data.length );
 
        }
 
-       function dispose() {
-
-               properties = new WeakMap();
+       copyArray( cache, data );
 
-       }
+};
 
-       return {
-               get: get,
-               remove: remove,
-               update: update,
-               dispose: dispose
-       };
+function StructuredUniform( id ) {
 
-}
+       this.id = id;
 
-function painterSortStable( a, b ) {
+       this.seq = [];
+       this.map = {};
 
-       if ( a.groupOrder !== b.groupOrder ) {
+}
 
-               return a.groupOrder - b.groupOrder;
+StructuredUniform.prototype.setValue = function ( gl, value, textures ) {
 
-       } else if ( a.renderOrder !== b.renderOrder ) {
+       const seq = this.seq;
 
-               return a.renderOrder - b.renderOrder;
+       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
 
-       } else if ( a.program !== b.program ) {
+               const u = seq[ i ];
+               u.setValue( gl, value[ u.id ], textures );
 
-               return a.program.id - b.program.id;
+       }
 
-       } else if ( a.material.id !== b.material.id ) {
+};
 
-               return a.material.id - b.material.id;
+// --- Top-level ---
 
-       } else if ( a.z !== b.z ) {
+// Parser - builds up the property tree from the path strings
 
-               return a.z - b.z;
+const RePathPart = /(\w+)(\])?(\[|\.)?/g;
 
-       } else {
+// 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.
 
-               return a.id - b.id;
+function addUniform( container, uniformObject ) {
 
-       }
+       container.seq.push( uniformObject );
+       container.map[ uniformObject.id ] = uniformObject;
 
 }
 
-function reversePainterSortStable( a, b ) {
+function parseUniform( activeInfo, addr, container ) {
 
-       if ( a.groupOrder !== b.groupOrder ) {
+       const path = activeInfo.name,
+               pathLength = path.length;
 
-               return a.groupOrder - b.groupOrder;
+       // reset RegExp object, because of the early exit of a previous run
+       RePathPart.lastIndex = 0;
 
-       } else if ( a.renderOrder !== b.renderOrder ) {
+       while ( true ) {
 
-               return a.renderOrder - b.renderOrder;
+               const match = RePathPart.exec( path ),
+                       matchEnd = RePathPart.lastIndex;
 
-       } else if ( a.z !== b.z ) {
+               let id = match[ 1 ];
+               const idIsIndex = match[ 2 ] === ']',
+                       subscript = match[ 3 ];
 
-               return b.z - a.z;
+               if ( idIsIndex ) id = id | 0; // convert to integer
 
-       } else {
+               if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) {
 
-               return a.id - b.id;
+                       // 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 {
 
-function WebGLRenderList( properties ) {
+                       // step into inner node / create it in case it doesn't exist
 
-       const renderItems = [];
-       let renderItemsIndex = 0;
+                       const map = container.map;
+                       let next = map[ id ];
 
-       const opaque = [];
-       const transparent = [];
+                       if ( next === undefined ) {
 
-       const defaultProgram = { id: - 1 };
+                               next = new StructuredUniform( id );
+                               addUniform( container, next );
 
-       function init() {
+                       }
 
-               renderItemsIndex = 0;
+                       container = next;
 
-               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
-                       };
+// Root Container
 
-                       renderItems[ renderItemsIndex ] = renderItem;
+function WebGLUniforms( gl, program ) {
 
-               } else {
+       this.seq = [];
+       this.map = {};
 
-                       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;
+       const n = gl.getProgramParameter( program, 35718 );
 
-               }
+       for ( let i = 0; i < n; ++ i ) {
 
-               renderItemsIndex ++;
+               const info = gl.getActiveUniform( program, i ),
+                       addr = gl.getUniformLocation( program, info.name );
 
-               return renderItem;
+               parseUniform( info, addr, this );
 
        }
 
-       function push( object, geometry, material, groupOrder, z, group ) {
-
-               const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
+}
 
-               ( material.transparent === true ? transparent : opaque ).push( renderItem );
+WebGLUniforms.prototype.setValue = function ( gl, name, value, textures ) {
 
-       }
+       const u = this.map[ name ];
 
-       function unshift( object, geometry, material, groupOrder, z, group ) {
+       if ( u !== undefined ) u.setValue( gl, value, textures );
 
-               const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
+};
 
-               ( material.transparent === true ? transparent : opaque ).unshift( renderItem );
+WebGLUniforms.prototype.setOptional = function ( gl, object, name ) {
 
-       }
+       const v = object[ name ];
 
-       function sort( customOpaqueSort, customTransparentSort ) {
+       if ( v !== undefined ) this.setValue( gl, name, v );
 
-               if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable );
-               if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable );
+};
 
-       }
 
-       function finish() {
+// Static interface
 
-               // Clear references from inactive renderItems in the list
+WebGLUniforms.upload = function ( gl, seq, values, textures ) {
 
-               for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) {
+       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
 
-                       const renderItem = renderItems[ i ];
+               const u = seq[ i ],
+                       v = values[ u.id ];
 
-                       if ( renderItem.id === null ) break;
+               if ( v.needsUpdate !== false ) {
 
-                       renderItem.id = null;
-                       renderItem.object = null;
-                       renderItem.geometry = null;
-                       renderItem.material = null;
-                       renderItem.program = null;
-                       renderItem.group = null;
+                       // note: always updating when .needsUpdate is undefined
+                       u.setValue( gl, v.value, textures );
 
                }
 
        }
 
-       return {
-
-               opaque: opaque,
-               transparent: transparent,
-
-               init: init,
-               push: push,
-               unshift: unshift,
-               finish: finish,
+};
 
-               sort: sort
-       };
+WebGLUniforms.seqWithValue = function ( seq, values ) {
 
-}
+       const r = [];
 
-function WebGLRenderLists( properties ) {
+       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
 
-       let lists = new WeakMap();
+               const u = seq[ i ];
+               if ( u.id in values ) r.push( u );
 
-       function get( scene, camera ) {
+       }
 
-               const cameras = lists.get( scene );
-               let list;
+       return r;
 
-               if ( cameras === undefined ) {
+};
 
-                       list = new WebGLRenderList( properties );
-                       lists.set( scene, new WeakMap() );
-                       lists.get( scene ).set( camera, list );
+function WebGLShader( gl, type, string ) {
 
-               } else {
+       const shader = gl.createShader( type );
 
-                       list = cameras.get( camera );
-                       if ( list === undefined ) {
+       gl.shaderSource( shader, string );
+       gl.compileShader( shader );
 
-                               list = new WebGLRenderList( properties );
-                               cameras.set( camera, list );
+       return shader;
 
-                       }
+}
 
-               }
+let programIdCount = 0;
 
-               return list;
+function addLineNumbers( string ) {
 
-       }
+       const lines = string.split( '\n' );
 
-       function dispose() {
+       for ( let i = 0; i < lines.length; i ++ ) {
 
-               lists = new WeakMap();
+               lines[ i ] = ( i + 1 ) + ': ' + lines[ i ];
 
        }
 
-       return {
-               get: get,
-               dispose: dispose
-       };
+       return lines.join( '\n' );
 
 }
 
-function UniformsCache() {
-
-       const lights = {};
-
-       return {
-
-               get: function ( light ) {
+function getEncodingComponents( encoding ) {
 
-                       if ( lights[ light.id ] !== undefined ) {
+       switch ( encoding ) {
 
-                               return lights[ light.id ];
+               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 )' ];
 
-                       }
+       }
 
-                       let uniforms;
+}
 
-                       switch ( light.type ) {
+function getShaderErrors( gl, shader, type ) {
 
-                               case 'DirectionalLight':
-                                       uniforms = {
-                                               direction: new Vector3(),
-                                               color: new Color()
-                                       };
-                                       break;
+       const status = gl.getShaderParameter( shader, 35713 );
+       const errors = gl.getShaderInfoLog( shader ).trim();
 
-                               case 'SpotLight':
-                                       uniforms = {
-                                               position: new Vector3(),
-                                               direction: new Vector3(),
-                                               color: new Color(),
-                                               distance: 0,
-                                               coneCos: 0,
-                                               penumbraCos: 0,
-                                               decay: 0
-                                       };
-                                       break;
+       if ( status && errors === '' ) return '';
 
-                               case 'PointLight':
-                                       uniforms = {
-                                               position: new Vector3(),
-                                               color: new Color(),
-                                               distance: 0,
-                                               decay: 0
-                                       };
-                                       break;
+       // --enable-privileged-webgl-extension
+       // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) );
 
-                               case 'HemisphereLight':
-                                       uniforms = {
-                                               direction: new Vector3(),
-                                               skyColor: new Color(),
-                                               groundColor: new Color()
-                                       };
-                                       break;
+       return type.toUpperCase() + '\n\n' + errors + '\n\n' + addLineNumbers( gl.getShaderSource( shader ) );
 
-                               case 'RectAreaLight':
-                                       uniforms = {
-                                               color: new Color(),
-                                               position: new Vector3(),
-                                               halfWidth: new Vector3(),
-                                               halfHeight: new Vector3()
-                                       };
-                                       break;
+}
 
-                       }
+function getTexelDecodingFunction( functionName, encoding ) {
 
-                       lights[ light.id ] = uniforms;
+       const components = getEncodingComponents( encoding );
+       return 'vec4 ' + functionName + '( vec4 value ) { return ' + components[ 0 ] + 'ToLinear' + components[ 1 ] + '; }';
 
-                       return uniforms;
+}
 
-               }
+function getTexelEncodingFunction( functionName, encoding ) {
 
-       };
+       const components = getEncodingComponents( encoding );
+       return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }';
 
 }
 
-function ShadowUniformsCache() {
+function getToneMappingFunction( functionName, toneMapping ) {
 
-       const lights = {};
+       let toneMappingName;
 
-       return {
+       switch ( toneMapping ) {
 
-               get: function ( light ) {
+               case LinearToneMapping:
+                       toneMappingName = 'Linear';
+                       break;
 
-                       if ( lights[ light.id ] !== undefined ) {
+               case ReinhardToneMapping:
+                       toneMappingName = 'Reinhard';
+                       break;
 
-                               return lights[ light.id ];
+               case CineonToneMapping:
+                       toneMappingName = 'OptimizedCineon';
+                       break;
 
-                       }
+               case ACESFilmicToneMapping:
+                       toneMappingName = 'ACESFilmic';
+                       break;
 
-                       let uniforms;
+               case CustomToneMapping:
+                       toneMappingName = 'Custom';
+                       break;
 
-                       switch ( light.type ) {
+               default:
+                       console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping );
+                       toneMappingName = 'Linear';
 
-                               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;
+       return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';
 
-                               case 'PointLight':
-                                       uniforms = {
-                                               shadowBias: 0,
-                                               shadowNormalBias: 0,
-                                               shadowRadius: 1,
-                                               shadowMapSize: new Vector2(),
-                                               shadowCameraNear: 1,
-                                               shadowCameraFar: 1000
-                                       };
-                                       break;
+}
 
-                               // TODO (abelnation): set RectAreaLight shadow uniforms
+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.transmission ) && parameters.rendererExtensionShaderTextureLod ? '#extension GL_EXT_shader_texture_lod : enable' : ''
+       ];
 
-                       lights[ light.id ] = uniforms;
+       return chunks.filter( filterEmptyLine ).join( '\n' );
 
-                       return uniforms;
+}
 
-               }
+function generateDefines( defines ) {
 
-       };
+       const chunks = [];
 
-}
+       for ( const name in defines ) {
 
+               const value = defines[ name ];
 
+               if ( value === false ) continue;
 
-let nextVersion = 0;
+               chunks.push( '#define ' + name + ' ' + value );
 
-function shadowCastingLightsFirst( lightA, lightB ) {
+       }
 
-       return ( lightB.castShadow ? 1 : 0 ) - ( lightA.castShadow ? 1 : 0 );
+       return chunks.join( '\n' );
 
 }
 
-function WebGLLights( extensions, capabilities ) {
+function fetchAttributeLocations( gl, program ) {
 
-       const cache = new UniformsCache();
+       const attributes = {};
 
-       const shadowCache = ShadowUniformsCache();
+       const n = gl.getProgramParameter( program, 35721 );
 
-       const state = {
+       for ( let i = 0; i < n; i ++ ) {
 
-               version: 0,
+               const info = gl.getActiveAttrib( program, i );
+               const name = info.name;
 
-               hash: {
-                       directionalLength: - 1,
-                       pointLength: - 1,
-                       spotLength: - 1,
-                       rectAreaLength: - 1,
-                       hemiLength: - 1,
+               let locationSize = 1;
+               if ( info.type === 35674 ) locationSize = 2;
+               if ( info.type === 35675 ) locationSize = 3;
+               if ( info.type === 35676 ) locationSize = 4;
 
-                       numDirectionalShadows: - 1,
-                       numPointShadows: - 1,
-                       numSpotShadows: - 1
-               },
+               // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i );
 
-               ambient: [ 0, 0, 0 ],
-               probe: [],
-               directional: [],
-               directionalShadow: [],
-               directionalShadowMap: [],
-               directionalShadowMatrix: [],
-               spot: [],
-               spotShadow: [],
-               spotShadowMap: [],
-               spotShadowMatrix: [],
-               rectArea: [],
-               rectAreaLTC1: null,
-               rectAreaLTC2: null,
-               point: [],
-               pointShadow: [],
-               pointShadowMap: [],
-               pointShadowMatrix: [],
-               hemi: []
+               attributes[ name ] = {
+                       type: info.type,
+                       location: gl.getAttribLocation( program, name ),
+                       locationSize: locationSize
+               };
 
-       };
+       }
 
-       for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() );
+       return attributes;
 
-       const vector3 = new Vector3();
-       const matrix4 = new Matrix4();
-       const matrix42 = new Matrix4();
+}
 
-       function setup( lights ) {
+function filterEmptyLine( string ) {
 
-               let r = 0, g = 0, b = 0;
+       return string !== '';
 
-               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;
+function replaceLightNums( string, parameters ) {
 
-               let numDirectionalShadows = 0;
-               let numPointShadows = 0;
-               let numSpotShadows = 0;
+       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 );
 
-               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;
+function replaceClippingPlaneNums( string, parameters ) {
 
-                       const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null;
+       return string
+               .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes )
+               .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) );
 
-                       if ( light.isAmbientLight ) {
+}
 
-                               r += color.r * intensity;
-                               g += color.g * intensity;
-                               b += color.b * intensity;
+// Resolve Includes
 
-                       } else if ( light.isLightProbe ) {
+const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm;
 
-                               for ( let j = 0; j < 9; j ++ ) {
+function resolveIncludes( string ) {
 
-                                       state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity );
+       return string.replace( includePattern, includeReplacer );
 
-                               }
+}
 
-                       } else if ( light.isDirectionalLight ) {
+function includeReplacer( match, include ) {
 
-                               const uniforms = cache.get( light );
+       const string = ShaderChunk[ include ];
 
-                               uniforms.color.copy( light.color ).multiplyScalar( light.intensity );
+       if ( string === undefined ) {
 
-                               if ( light.castShadow ) {
+               throw new Error( 'Can not resolve #include <' + include + '>' );
 
-                                       const shadow = light.shadow;
+       }
 
-                                       const shadowUniforms = shadowCache.get( light );
+       return resolveIncludes( string );
 
-                                       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;
+// Unroll Loops
 
-                                       numDirectionalShadows ++;
+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 ) {
 
-                               state.directional[ directionalLength ] = uniforms;
+       return string
+               .replace( unrollLoopPattern, loopReplacer )
+               .replace( deprecatedUnrollLoopPattern, deprecatedLoopReplacer );
 
-                               directionalLength ++;
+}
 
-                       } else if ( light.isSpotLight ) {
+function deprecatedLoopReplacer( match, start, end, snippet ) {
 
-                               const uniforms = cache.get( light );
+       console.warn( 'WebGLProgram: #pragma unroll_loop shader syntax is deprecated. Please use #pragma unroll_loop_start syntax instead.' );
+       return loopReplacer( match, start, end, snippet );
 
-                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
+}
 
-                               uniforms.color.copy( color ).multiplyScalar( intensity );
-                               uniforms.distance = distance;
+function loopReplacer( match, start, end, snippet ) {
 
-                               uniforms.coneCos = Math.cos( light.angle );
-                               uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) );
-                               uniforms.decay = light.decay;
+       let string = '';
 
-                               if ( light.castShadow ) {
+       for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) {
 
-                                       const shadow = light.shadow;
+               string += snippet
+                       .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' )
+                       .replace( /UNROLLED_LOOP_INDEX/g, i );
 
-                                       const shadowUniforms = shadowCache.get( light );
+       }
 
-                                       shadowUniforms.shadowBias = shadow.bias;
-                                       shadowUniforms.shadowNormalBias = shadow.normalBias;
-                                       shadowUniforms.shadowRadius = shadow.radius;
-                                       shadowUniforms.shadowMapSize = shadow.mapSize;
+       return string;
 
-                                       state.spotShadow[ spotLength ] = shadowUniforms;
-                                       state.spotShadowMap[ spotLength ] = shadowMap;
-                                       state.spotShadowMatrix[ spotLength ] = light.shadow.matrix;
+}
 
-                                       numSpotShadows ++;
+//
 
-                               }
+function generatePrecision( parameters ) {
 
-                               state.spot[ spotLength ] = uniforms;
+       let precisionstring = 'precision ' + parameters.precision + ' float;\nprecision ' + parameters.precision + ' int;';
 
-                               spotLength ++;
+       if ( parameters.precision === 'highp' ) {
 
-                       } else if ( light.isRectAreaLight ) {
+               precisionstring += '\n#define HIGH_PRECISION';
 
-                               const uniforms = cache.get( light );
+       } else if ( parameters.precision === 'mediump' ) {
 
-                               // (a) intensity is the total visible light emitted
-                               //uniforms.color.copy( color ).multiplyScalar( intensity / ( light.width * light.height * Math.PI ) );
+               precisionstring += '\n#define MEDIUM_PRECISION';
 
-                               // (b) intensity is the brightness of the light
-                               uniforms.color.copy( color ).multiplyScalar( intensity );
+       } else if ( parameters.precision === 'lowp' ) {
 
-                               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
-                               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
+               precisionstring += '\n#define LOW_PRECISION';
 
-                               state.rectArea[ rectAreaLength ] = uniforms;
+       }
 
-                               rectAreaLength ++;
+       return precisionstring;
 
-                       } else if ( light.isPointLight ) {
+}
 
-                               const uniforms = cache.get( light );
+function generateShadowMapTypeDefine( parameters ) {
 
-                               uniforms.color.copy( light.color ).multiplyScalar( light.intensity );
-                               uniforms.distance = light.distance;
-                               uniforms.decay = light.decay;
+       let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC';
 
-                               if ( light.castShadow ) {
+       if ( parameters.shadowMapType === PCFShadowMap ) {
 
-                                       const shadow = light.shadow;
+               shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF';
 
-                                       const shadowUniforms = shadowCache.get( light );
+       } else if ( parameters.shadowMapType === PCFSoftShadowMap ) {
 
-                                       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;
+               shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT';
 
-                                       state.pointShadow[ pointLength ] = shadowUniforms;
-                                       state.pointShadowMap[ pointLength ] = shadowMap;
-                                       state.pointShadowMatrix[ pointLength ] = light.shadow.matrix;
+       } else if ( parameters.shadowMapType === VSMShadowMap ) {
 
-                                       numPointShadows ++;
+               shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM';
 
-                               }
+       }
 
-                               state.point[ pointLength ] = uniforms;
+       return shadowMapTypeDefine;
 
-                               pointLength ++;
+}
 
-                       } else if ( light.isHemisphereLight ) {
+function generateEnvMapTypeDefine( parameters ) {
 
-                               const uniforms = cache.get( light );
+       let envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
 
-                               uniforms.skyColor.copy( light.color ).multiplyScalar( intensity );
-                               uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity );
+       if ( parameters.envMap ) {
 
-                               state.hemi[ hemiLength ] = uniforms;
+               switch ( parameters.envMapMode ) {
 
-                               hemiLength ++;
+                       case CubeReflectionMapping:
+                       case CubeRefractionMapping:
+                               envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
+                               break;
 
-                       }
+                       case CubeUVReflectionMapping:
+                       case CubeUVRefractionMapping:
+                               envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV';
+                               break;
 
                }
 
-               if ( rectAreaLength > 0 ) {
-
-                       if ( capabilities.isWebGL2 ) {
-
-                               // WebGL 2
-
-                               state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
-                               state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
+       }
 
-                       } else {
+       return envMapTypeDefine;
 
-                               // WebGL 1
+}
 
-                               if ( extensions.has( 'OES_texture_float_linear' ) === true ) {
+function generateEnvMapModeDefine( parameters ) {
 
-                                       state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
-                                       state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
+       let envMapModeDefine = 'ENVMAP_MODE_REFLECTION';
 
-                               } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) {
+       if ( parameters.envMap ) {
 
-                                       state.rectAreaLTC1 = UniformsLib.LTC_HALF_1;
-                                       state.rectAreaLTC2 = UniformsLib.LTC_HALF_2;
+               switch ( parameters.envMapMode ) {
 
-                               } else {
+                       case CubeRefractionMapping:
+                       case CubeUVRefractionMapping:
 
-                                       console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' );
+                               envMapModeDefine = 'ENVMAP_MODE_REFRACTION';
+                               break;
 
-                               }
+               }
 
-                       }
+       }
 
-               }
+       return envMapModeDefine;
 
-               state.ambient[ 0 ] = r;
-               state.ambient[ 1 ] = g;
-               state.ambient[ 2 ] = b;
+}
 
-               const hash = state.hash;
+function generateEnvMapBlendingDefine( parameters ) {
 
-               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 ) {
+       let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE';
 
-                       state.directional.length = directionalLength;
-                       state.spot.length = spotLength;
-                       state.rectArea.length = rectAreaLength;
-                       state.point.length = pointLength;
-                       state.hemi.length = hemiLength;
+       if ( parameters.envMap ) {
 
-                       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;
+               switch ( parameters.combine ) {
 
-                       hash.directionalLength = directionalLength;
-                       hash.pointLength = pointLength;
-                       hash.spotLength = spotLength;
-                       hash.rectAreaLength = rectAreaLength;
-                       hash.hemiLength = hemiLength;
+                       case MultiplyOperation:
+                               envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY';
+                               break;
 
-                       hash.numDirectionalShadows = numDirectionalShadows;
-                       hash.numPointShadows = numPointShadows;
-                       hash.numSpotShadows = numSpotShadows;
+                       case MixOperation:
+                               envMapBlendingDefine = 'ENVMAP_BLENDING_MIX';
+                               break;
 
-                       state.version = nextVersion ++;
+                       case AddOperation:
+                               envMapBlendingDefine = 'ENVMAP_BLENDING_ADD';
+                               break;
 
                }
 
        }
 
-       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 ];
+       return envMapBlendingDefine;
 
-                       if ( light.isDirectionalLight ) {
+}
 
-                               const uniforms = state.directional[ directionalLength ];
+function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
 
-                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
-                               vector3.setFromMatrixPosition( light.target.matrixWorld );
-                               uniforms.direction.sub( vector3 );
-                               uniforms.direction.transformDirection( viewMatrix );
+       // TODO Send this event to Three.js DevTools
+       // console.log( 'WebGLProgram', cacheKey );
 
-                               directionalLength ++;
+       const gl = renderer.getContext();
 
-                       } else if ( light.isSpotLight ) {
+       const defines = parameters.defines;
 
-                               const uniforms = state.spot[ spotLength ];
+       let vertexShader = parameters.vertexShader;
+       let fragmentShader = parameters.fragmentShader;
 
-                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
-                               uniforms.position.applyMatrix4( viewMatrix );
+       const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters );
+       const envMapTypeDefine = generateEnvMapTypeDefine( parameters );
+       const envMapModeDefine = generateEnvMapModeDefine( parameters );
+       const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters );
 
-                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
-                               vector3.setFromMatrixPosition( light.target.matrixWorld );
-                               uniforms.direction.sub( vector3 );
-                               uniforms.direction.transformDirection( viewMatrix );
 
-                               spotLength ++;
+       const gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0;
 
-                       } else if ( light.isRectAreaLight ) {
+       const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters );
 
-                               const uniforms = state.rectArea[ rectAreaLength ];
+       const customDefines = generateDefines( defines );
 
-                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
-                               uniforms.position.applyMatrix4( viewMatrix );
+       const program = gl.createProgram();
 
-                               // extract local rotation of light to derive width/height half vectors
-                               matrix42.identity();
-                               matrix4.copy( light.matrixWorld );
-                               matrix4.premultiply( viewMatrix );
-                               matrix42.extractRotation( matrix4 );
+       let prefixVertex, prefixFragment;
+       let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : '';
 
-                               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
-                               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
+       if ( parameters.isRawShaderMaterial ) {
 
-                               uniforms.halfWidth.applyMatrix4( matrix42 );
-                               uniforms.halfHeight.applyMatrix4( matrix42 );
+               prefixVertex = [
 
-                               rectAreaLength ++;
+                       customDefines
 
-                       } else if ( light.isPointLight ) {
+               ].filter( filterEmptyLine ).join( '\n' );
 
-                               const uniforms = state.point[ pointLength ];
+               if ( prefixVertex.length > 0 ) {
 
-                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
-                               uniforms.position.applyMatrix4( viewMatrix );
+                       prefixVertex += '\n';
 
-                               pointLength ++;
+               }
 
-                       } else if ( light.isHemisphereLight ) {
+               prefixFragment = [
 
-                               const uniforms = state.hemi[ hemiLength ];
+                       customExtensions,
+                       customDefines
 
-                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
-                               uniforms.direction.transformDirection( viewMatrix );
-                               uniforms.direction.normalize();
+               ].filter( filterEmptyLine ).join( '\n' );
 
-                               hemiLength ++;
+               if ( prefixFragment.length > 0 ) {
 
-                       }
+                       prefixFragment += '\n';
 
                }
 
-       }
-
-       return {
-               setup: setup,
-               setupView: setupView,
-               state: state
-       };
-
-}
-
-function WebGLRenderState( extensions, capabilities ) {
-
-       const lights = new WebGLLights( extensions, capabilities );
+       } else {
 
-       const lightsArray = [];
-       const shadowsArray = [];
+               prefixVertex = [
 
-       function init() {
+                       generatePrecision( parameters ),
 
-               lightsArray.length = 0;
-               shadowsArray.length = 0;
+                       '#define SHADER_NAME ' + parameters.shaderName,
 
-       }
+                       customDefines,
 
-       function pushLight( light ) {
+                       parameters.instancing ? '#define USE_INSTANCING' : '',
+                       parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '',
 
-               lightsArray.push( light );
+                       parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '',
 
-       }
+                       '#define GAMMA_FACTOR ' + gammaFactorDefine,
 
-       function pushShadow( shadowLight ) {
+                       '#define MAX_BONES ' + parameters.maxBones,
+                       ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
+                       ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',
 
-               shadowsArray.push( shadowLight );
+                       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' : '',
 
-       function setupLights() {
+                       parameters.displacementMap && parameters.supportsVertexTextures ? '#define USE_DISPLACEMENTMAP' : '',
 
-               lights.setup( lightsArray );
+                       parameters.specularMap ? '#define USE_SPECULARMAP' : '',
+                       parameters.specularIntensityMap ? '#define USE_SPECULARINTENSITYMAP' : '',
+                       parameters.specularColorMap ? '#define USE_SPECULARCOLORMAP' : '',
 
-       }
+                       parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',
+                       parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',
+                       parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
 
-       function setupLightsView( camera ) {
+                       parameters.transmission ? '#define USE_TRANSMISSION' : '',
+                       parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '',
+                       parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '',
 
-               lights.setupView( lightsArray, camera );
+                       parameters.sheenColorMap ? '#define USE_SHEENCOLORMAP' : '',
+                       parameters.sheenRoughnessMap ? '#define USE_SHEENROUGHNESSMAP' : '',
 
-       }
+                       parameters.vertexTangents ? '#define USE_TANGENT' : '',
+                       parameters.vertexColors ? '#define USE_COLOR' : '',
+                       parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '',
+                       parameters.vertexUvs ? '#define USE_UV' : '',
+                       parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',
 
-       const state = {
-               lightsArray: lightsArray,
-               shadowsArray: shadowsArray,
+                       parameters.flatShading ? '#define FLAT_SHADED' : '',
 
-               lights: lights
-       };
+                       parameters.skinning ? '#define USE_SKINNING' : '',
+                       parameters.useVertexTexture ? '#define BONE_TEXTURE' : '',
 
-       return {
-               init: init,
-               state: state,
-               setupLights: setupLights,
-               setupLightsView: setupLightsView,
+                       parameters.morphTargets ? '#define USE_MORPHTARGETS' : '',
+                       parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '',
+                       ( parameters.morphTargets && parameters.isWebGL2 ) ? '#define MORPHTARGETS_TEXTURE' : '',
+                       ( parameters.morphTargets && parameters.isWebGL2 ) ? '#define MORPHTARGETS_COUNT ' + parameters.morphTargetsCount : '',
+                       parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
+                       parameters.flipSided ? '#define FLIP_SIDED' : '',
 
-               pushLight: pushLight,
-               pushShadow: pushShadow
-       };
+                       parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
+                       parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
 
-}
+                       parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '',
 
-function WebGLRenderStates( extensions, capabilities ) {
+                       parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
+                       ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
 
-       let renderStates = new WeakMap();
+                       'uniform mat4 modelMatrix;',
+                       'uniform mat4 modelViewMatrix;',
+                       'uniform mat4 projectionMatrix;',
+                       'uniform mat4 viewMatrix;',
+                       'uniform mat3 normalMatrix;',
+                       'uniform vec3 cameraPosition;',
+                       'uniform bool isOrthographic;',
 
-       function get( scene, renderCallDepth = 0 ) {
+                       '#ifdef USE_INSTANCING',
 
-               let renderState;
+                       '       attribute mat4 instanceMatrix;',
 
-               if ( renderStates.has( scene ) === false ) {
+                       '#endif',
 
-                       renderState = new WebGLRenderState( extensions, capabilities );
-                       renderStates.set( scene, [] );
-                       renderStates.get( scene ).push( renderState );
+                       '#ifdef USE_INSTANCING_COLOR',
 
-               } else {
+                       '       attribute vec3 instanceColor;',
 
-                       if ( renderCallDepth >= renderStates.get( scene ).length ) {
+                       '#endif',
 
-                               renderState = new WebGLRenderState( extensions, capabilities );
-                               renderStates.get( scene ).push( renderState );
+                       'attribute vec3 position;',
+                       'attribute vec3 normal;',
+                       'attribute vec2 uv;',
 
-                       } else {
+                       '#ifdef USE_TANGENT',
 
-                               renderState = renderStates.get( scene )[ renderCallDepth ];
+                       '       attribute vec4 tangent;',
 
-                       }
+                       '#endif',
 
-               }
+                       '#if defined( USE_COLOR_ALPHA )',
 
-               return renderState;
+                       '       attribute vec4 color;',
 
-       }
+                       '#elif defined( USE_COLOR )',
 
-       function dispose() {
+                       '       attribute vec3 color;',
 
-               renderStates = new WeakMap();
+                       '#endif',
 
-       }
+                       '#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )',
 
-       return {
-               get: get,
-               dispose: dispose
-       };
+                       '       attribute vec3 morphTarget0;',
+                       '       attribute vec3 morphTarget1;',
+                       '       attribute vec3 morphTarget2;',
+                       '       attribute vec3 morphTarget3;',
 
-}
+                       '       #ifdef USE_MORPHNORMALS',
 
-/**
- * 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>
- * }
- */
+                       '               attribute vec3 morphNormal0;',
+                       '               attribute vec3 morphNormal1;',
+                       '               attribute vec3 morphNormal2;',
+                       '               attribute vec3 morphNormal3;',
 
-function MeshDepthMaterial( parameters ) {
+                       '       #else',
 
-       Material.call( this );
+                       '               attribute vec3 morphTarget4;',
+                       '               attribute vec3 morphTarget5;',
+                       '               attribute vec3 morphTarget6;',
+                       '               attribute vec3 morphTarget7;',
 
-       this.type = 'MeshDepthMaterial';
+                       '       #endif',
 
-       this.depthPacking = BasicDepthPacking;
+                       '#endif',
 
-       this.skinning = false;
-       this.morphTargets = false;
+                       '#ifdef USE_SKINNING',
 
-       this.map = null;
+                       '       attribute vec4 skinIndex;',
+                       '       attribute vec4 skinWeight;',
 
-       this.alphaMap = null;
+                       '#endif',
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+                       '\n'
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
+               ].filter( filterEmptyLine ).join( '\n' );
 
-       this.fog = false;
+               prefixFragment = [
 
-       this.setValues( parameters );
+                       customExtensions,
 
-}
+                       generatePrecision( parameters ),
 
-MeshDepthMaterial.prototype = Object.create( Material.prototype );
-MeshDepthMaterial.prototype.constructor = MeshDepthMaterial;
+                       '#define SHADER_NAME ' + parameters.shaderName,
 
-MeshDepthMaterial.prototype.isMeshDepthMaterial = true;
+                       customDefines,
 
-MeshDepthMaterial.prototype.copy = function ( source ) {
+                       '#define GAMMA_FACTOR ' + gammaFactorDefine,
 
-       Material.prototype.copy.call( this, source );
+                       ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
+                       ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',
 
-       this.depthPacking = source.depthPacking;
+                       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' : '',
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
+                       parameters.clearcoat ? '#define USE_CLEARCOAT' : '',
+                       parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '',
+                       parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '',
+                       parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '',
 
-       this.map = source.map;
+                       parameters.specularMap ? '#define USE_SPECULARMAP' : '',
+                       parameters.specularIntensityMap ? '#define USE_SPECULARINTENSITYMAP' : '',
+                       parameters.specularColorMap ? '#define USE_SPECULARCOLORMAP' : '',
+                       parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',
+                       parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',
 
-       this.alphaMap = source.alphaMap;
+                       parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
+                       parameters.alphaTest ? '#define USE_ALPHATEST' : '',
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+                       parameters.sheen ? '#define USE_SHEEN' : '',
+                       parameters.sheenColorMap ? '#define USE_SHEENCOLORMAP' : '',
+                       parameters.sheenRoughnessMap ? '#define USE_SHEENROUGHNESSMAP' : '',
 
-       this.wireframe = source.wireframe;
-       this.wireframeLinewidth = source.wireframeLinewidth;
+                       parameters.transmission ? '#define USE_TRANSMISSION' : '',
+                       parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '',
+                       parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '',
 
-       return this;
+                       parameters.vertexTangents ? '#define USE_TANGENT' : '',
+                       parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '',
+                       parameters.vertexAlphas ? '#define USE_COLOR_ALPHA' : '',
+                       parameters.vertexUvs ? '#define USE_UV' : '',
+                       parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',
 
-};
+                       parameters.gradientMap ? '#define USE_GRADIENTMAP' : '',
 
-/**
- * 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>
- *
- * }
- */
+                       parameters.flatShading ? '#define FLAT_SHADED' : '',
 
-function MeshDistanceMaterial( parameters ) {
+                       parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
+                       parameters.flipSided ? '#define FLIP_SIDED' : '',
 
-       Material.call( this );
+                       parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
+                       parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
 
-       this.type = 'MeshDistanceMaterial';
+                       parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '',
 
-       this.referencePosition = new Vector3();
-       this.nearDistance = 1;
-       this.farDistance = 1000;
+                       parameters.physicallyCorrectLights ? '#define PHYSICALLY_CORRECT_LIGHTS' : '',
 
-       this.skinning = false;
-       this.morphTargets = false;
+                       parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
+                       ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
 
-       this.map = null;
+                       ( ( parameters.extensionShaderTextureLOD || parameters.envMap ) && parameters.rendererExtensionShaderTextureLod ) ? '#define TEXTURE_LOD_EXT' : '',
 
-       this.alphaMap = null;
+                       'uniform mat4 viewMatrix;',
+                       'uniform vec3 cameraPosition;',
+                       'uniform bool isOrthographic;',
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+                       ( 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 ) : '',
 
-       this.fog = false;
+                       parameters.dithering ? '#define DITHERING' : '',
+                       parameters.format === RGBFormat ? '#define OPAQUE' : '',
 
-       this.setValues( parameters );
+                       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.specularColorMap ? getTexelDecodingFunction( 'specularColorMapTexelToLinear', parameters.specularColorMapEncoding ) : '',
+                       parameters.sheenColorMap ? getTexelDecodingFunction( 'sheenColorMapTexelToLinear', parameters.sheenColorMapEncoding ) : '',
+                       parameters.lightMap ? getTexelDecodingFunction( 'lightMapTexelToLinear', parameters.lightMapEncoding ) : '',
+                       getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputEncoding ),
 
-}
+                       parameters.depthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '',
 
-MeshDistanceMaterial.prototype = Object.create( Material.prototype );
-MeshDistanceMaterial.prototype.constructor = MeshDistanceMaterial;
+                       '\n'
 
-MeshDistanceMaterial.prototype.isMeshDistanceMaterial = true;
+               ].filter( filterEmptyLine ).join( '\n' );
 
-MeshDistanceMaterial.prototype.copy = function ( source ) {
+       }
 
-       Material.prototype.copy.call( this, source );
+       vertexShader = resolveIncludes( vertexShader );
+       vertexShader = replaceLightNums( vertexShader, parameters );
+       vertexShader = replaceClippingPlaneNums( vertexShader, parameters );
 
-       this.referencePosition.copy( source.referencePosition );
-       this.nearDistance = source.nearDistance;
-       this.farDistance = source.farDistance;
+       fragmentShader = resolveIncludes( fragmentShader );
+       fragmentShader = replaceLightNums( fragmentShader, parameters );
+       fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters );
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
+       vertexShader = unrollLoops( vertexShader );
+       fragmentShader = unrollLoops( fragmentShader );
 
-       this.map = source.map;
+       if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) {
 
-       this.alphaMap = source.alphaMap;
+               // GLSL 3.0 conversion for built-in materials and ShaderMaterial
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+               versionString = '#version 300 es\n';
 
-       return this;
+               prefixVertex = [
+                       'precision mediump sampler2DArray;',
+                       '#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;
 
-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}";
+       const vertexGlsl = versionString + prefixVertex + vertexShader;
+       const fragmentGlsl = versionString + prefixFragment + fragmentShader;
 
-function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
+       // console.log( '*VERTEX*', vertexGlsl );
+       // console.log( '*FRAGMENT*', fragmentGlsl );
 
-       let _frustum = new Frustum();
+       const glVertexShader = WebGLShader( gl, 35633, vertexGlsl );
+       const glFragmentShader = WebGLShader( gl, 35632, fragmentGlsl );
 
-       const _shadowMapSize = new Vector2(),
-               _viewportSize = new Vector2(),
+       gl.attachShader( program, glVertexShader );
+       gl.attachShader( program, glFragmentShader );
 
-               _viewport = new Vector4(),
+       // Force a particular attribute to index 0.
 
-               _depthMaterials = [],
-               _distanceMaterials = [],
+       if ( parameters.index0AttributeName !== undefined ) {
 
-               _materialCache = {};
+               gl.bindAttribLocation( program, 0, parameters.index0AttributeName );
 
-       const shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide };
+       } else if ( parameters.morphTargets === true ) {
 
-       const shadowMaterialVertical = new ShaderMaterial( {
+               // programs with morphTargets displace position out of attribute 0
+               gl.bindAttribLocation( program, 0, 'position' );
 
-               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 }
-               },
+       gl.linkProgram( program );
 
-               vertexShader: vsm_vert,
+       // check for link errors
+       if ( renderer.debug.checkShaderErrors ) {
 
-               fragmentShader: vsm_frag
+               const programLog = gl.getProgramInfoLog( program ).trim();
+               const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim();
+               const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim();
 
-       } );
+               let runnable = true;
+               let haveDiagnostics = true;
 
-       const shadowMaterialHorizontal = shadowMaterialVertical.clone();
-       shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1;
+               if ( gl.getProgramParameter( program, 35714 ) === false ) {
 
-       const fullScreenTri = new BufferGeometry();
-       fullScreenTri.setAttribute(
-               'position',
-               new BufferAttribute(
-                       new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ),
-                       3
-               )
-       );
+                       runnable = false;
 
-       const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical );
+                       const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' );
+                       const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' );
 
-       const scope = this;
+                       console.error(
+                               'THREE.WebGLProgram: Shader Error ' + gl.getError() + ' - ' +
+                               'VALIDATE_STATUS ' + gl.getProgramParameter( program, 35715 ) + '\n\n' +
+                               'Program Info Log: ' + programLog + '\n' +
+                               vertexErrors + '\n' +
+                               fragmentErrors
+                       );
 
-       this.enabled = false;
+               } else if ( programLog !== '' ) {
 
-       this.autoUpdate = true;
-       this.needsUpdate = false;
+                       console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog );
 
-       this.type = PCFShadowMap;
+               } else if ( vertexLog === '' || fragmentLog === '' ) {
 
-       this.render = function ( lights, scene, camera ) {
+                       haveDiagnostics = false;
 
-               if ( scope.enabled === false ) return;
-               if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;
+               }
 
-               if ( lights.length === 0 ) return;
+               if ( haveDiagnostics ) {
 
-               const currentRenderTarget = _renderer.getRenderTarget();
-               const activeCubeFace = _renderer.getActiveCubeFace();
-               const activeMipmapLevel = _renderer.getActiveMipmapLevel();
+                       this.diagnostics = {
 
-               const _state = _renderer.state;
+                               runnable: runnable,
 
-               // 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 );
+                               programLog: programLog,
 
-               // render depth map
+                               vertexShader: {
 
-               for ( let i = 0, il = lights.length; i < il; i ++ ) {
+                                       log: vertexLog,
+                                       prefix: prefixVertex
 
-                       const light = lights[ i ];
-                       const shadow = light.shadow;
+                               },
 
-                       if ( shadow === undefined ) {
+                               fragmentShader: {
 
-                               console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' );
-                               continue;
+                                       log: fragmentLog,
+                                       prefix: prefixFragment
 
-                       }
+                               }
 
-                       if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue;
+                       };
 
-                       _shadowMapSize.copy( shadow.mapSize );
+               }
 
-                       const shadowFrameExtents = shadow.getFrameExtents();
+       }
 
-                       _shadowMapSize.multiply( shadowFrameExtents );
+       // Clean up
 
-                       _viewportSize.copy( shadow.mapSize );
+       // Crashes in iOS9 and iOS10. #18402
+       // gl.detachShader( program, glVertexShader );
+       // gl.detachShader( program, glFragmentShader );
 
-                       if ( _shadowMapSize.x > maxTextureSize || _shadowMapSize.y > maxTextureSize ) {
+       gl.deleteShader( glVertexShader );
+       gl.deleteShader( glFragmentShader );
 
-                               if ( _shadowMapSize.x > maxTextureSize ) {
+       // set up caching for uniform locations
 
-                                       _viewportSize.x = Math.floor( maxTextureSize / shadowFrameExtents.x );
-                                       _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x;
-                                       shadow.mapSize.x = _viewportSize.x;
+       let cachedUniforms;
 
-                               }
+       this.getUniforms = function () {
 
-                               if ( _shadowMapSize.y > maxTextureSize ) {
+               if ( cachedUniforms === undefined ) {
 
-                                       _viewportSize.y = Math.floor( maxTextureSize / shadowFrameExtents.y );
-                                       _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y;
-                                       shadow.mapSize.y = _viewportSize.y;
+                       cachedUniforms = new WebGLUniforms( gl, program );
 
-                               }
+               }
 
-                       }
+               return cachedUniforms;
 
-                       if ( shadow.map === null && ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
+       };
 
-                               const pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat };
+       // set up caching for attribute locations
 
-                               shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
-                               shadow.map.texture.name = light.name + '.shadowMap';
+       let cachedAttributes;
 
-                               shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+       this.getAttributes = function () {
 
-                               shadow.camera.updateProjectionMatrix();
+               if ( cachedAttributes === undefined ) {
 
-                       }
+                       cachedAttributes = fetchAttributeLocations( gl, program );
 
-                       if ( shadow.map === null ) {
+               }
 
-                               const pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat };
+               return cachedAttributes;
 
-                               shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
-                               shadow.map.texture.name = light.name + '.shadowMap';
+       };
 
-                               shadow.camera.updateProjectionMatrix();
+       // free resource
 
-                       }
+       this.destroy = function () {
 
-                       _renderer.setRenderTarget( shadow.map );
-                       _renderer.clear();
+               bindingStates.releaseStatesOfProgram( this );
 
-                       const viewportCount = shadow.getViewportCount();
+               gl.deleteProgram( program );
+               this.program = undefined;
 
-                       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
-                               );
+       this.name = parameters.shaderName;
+       this.id = programIdCount ++;
+       this.cacheKey = cacheKey;
+       this.usedTimes = 1;
+       this.program = program;
+       this.vertexShader = glVertexShader;
+       this.fragmentShader = glFragmentShader;
 
-                               _state.viewport( _viewport );
+       return this;
 
-                               shadow.updateMatrices( light, vp );
+}
 
-                               _frustum = shadow.getFrustum();
+function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) {
 
-                               renderObject( scene, camera, shadow.camera, light, this.type );
+       const programs = [];
 
-                       }
+       const isWebGL2 = capabilities.isWebGL2;
+       const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer;
+       const floatVertexTextures = capabilities.floatVertexTextures;
+       const maxVertexUniforms = capabilities.maxVertexUniforms;
+       const vertexTextures = capabilities.vertexTextures;
 
-                       // do blur pass for VSM
+       let precision = capabilities.precision;
 
-                       if ( ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
+       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'
+       };
 
-                               VSMPass( shadow, camera );
+       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',
+               'clearcoat', 'clearcoatMap', 'clearcoatRoughnessMap', 'clearcoatNormalMap',
+               'displacementMap', 'specularMap', , 'roughnessMap', 'metalnessMap', 'gradientMap',
+               'alphaMap', 'alphaTest', 'combine', 'vertexColors', 'vertexAlphas', 'vertexTangents', 'vertexUvs', 'uvsVertexOnly', 'fog', 'useFog', 'fogExp2',
+               'flatShading', 'sizeAttenuation', 'logarithmicDepthBuffer', 'skinning',
+               'maxBones', 'useVertexTexture', 'morphTargets', 'morphNormals', 'morphTargetsCount', 'premultipliedAlpha',
+               'numDirLights', 'numPointLights', 'numSpotLights', 'numHemiLights', 'numRectAreaLights',
+               'numDirLightShadows', 'numPointLightShadows', 'numSpotLightShadows',
+               'shadowMapEnabled', 'shadowMapType', 'toneMapping', 'physicallyCorrectLights',
+               'doubleSided', 'flipSided', 'numClippingPlanes', 'numClipIntersection', 'depthPacking', 'dithering', 'format',
+               'specularIntensityMap', 'specularColorMap', 'specularColorMapEncoding',
+               'transmission', 'transmissionMap', 'thicknessMap',
+               'sheen', 'sheenColorMap', 'sheenColorMapEncoding', 'sheenRoughnessMap'
+       ];
 
-                       }
+       function getMaxBones( object ) {
 
-                       shadow.needsUpdate = false;
+               const skeleton = object.skeleton;
+               const bones = skeleton.bones;
 
-               }
+               if ( floatVertexTextures ) {
 
-               scope.needsUpdate = false;
+                       return 1024;
 
-               _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel );
+               } 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)
 
-       function VSMPass( shadow, camera ) {
+                       const nVertexUniforms = maxVertexUniforms;
+                       const nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 );
 
-               const geometry = _objects.update( fullScreenMesh );
+                       const maxBones = Math.min( nVertexMatrices, bones.length );
 
-               // vertical pass
+                       if ( maxBones < bones.length ) {
 
-               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 );
+                               console.warn( 'THREE.WebGLRenderer: Skeleton has ' + bones.length + ' bones. This GPU supports ' + maxBones + '.' );
+                               return 0;
 
-               // 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 );
+                       return maxBones;
 
-       }
+               }
 
-       function getDepthMaterialVariant( useMorphing, useSkinning, useInstancing ) {
+       }
 
-               const index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;
+       function getTextureEncodingFromMap( map ) {
 
-               let material = _depthMaterials[ index ];
+               let encoding;
 
-               if ( material === undefined ) {
+               if ( map && map.isTexture ) {
 
-                       material = new MeshDepthMaterial( {
+                       encoding = map.encoding;
 
-                               depthPacking: RGBADepthPacking,
+               } else if ( map && map.isWebGLRenderTarget ) {
 
-                               morphTargets: useMorphing,
-                               skinning: useSkinning
+                       console.warn( 'THREE.WebGLPrograms.getTextureEncodingFromMap: don\'t use render targets as textures. Use their .texture property instead.' );
+                       encoding = map.texture.encoding;
 
-                       } );
+               } else {
 
-                       _depthMaterials[ index ] = material;
+                       encoding = LinearEncoding;
 
                }
 
-               return material;
-
-       }
-
-       function getDistanceMaterialVariant( useMorphing, useSkinning, useInstancing ) {
+               if ( isWebGL2 && map && map.isTexture && map.format === RGBAFormat && map.type === UnsignedByteType && map.encoding === sRGBEncoding ) {
 
-               const index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;
+                       encoding = LinearEncoding; // disable inline decode for sRGB textures in WebGL 2
 
-               let material = _distanceMaterials[ index ];
+               }
 
-               if ( material === undefined ) {
+               return encoding;
 
-                       material = new MeshDistanceMaterial( {
+       }
 
-                               morphTargets: useMorphing,
-                               skinning: useSkinning
+       function getParameters( material, lights, shadows, scene, object ) {
 
-                       } );
+               const fog = scene.fog;
+               const environment = material.isMeshStandardMaterial ? scene.environment : null;
 
-                       _distanceMaterials[ index ] = material;
+               const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment );
 
-               }
+               const shaderID = shaderIDs[ material.type ];
 
-               return material;
+               // heuristics to create shader parameters according to lights in the scene
+               // (not to blow over maxLights budget)
 
-       }
+               const maxBones = object.isSkinnedMesh ? getMaxBones( object ) : 0;
 
-       function getDepthMaterial( object, geometry, material, light, shadowCameraNear, shadowCameraFar, type ) {
+               if ( material.precision !== null ) {
 
-               let result = null;
+                       precision = capabilities.getMaxPrecision( material.precision );
 
-               let getMaterialVariant = getDepthMaterialVariant;
-               let customMaterial = object.customDepthMaterial;
+                       if ( precision !== material.precision ) {
 
-               if ( light.isPointLight === true ) {
+                               console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' );
 
-                       getMaterialVariant = getDistanceMaterialVariant;
-                       customMaterial = object.customDistanceMaterial;
+                       }
 
                }
 
-               if ( customMaterial === undefined ) {
+               let vertexShader, fragmentShader;
 
-                       let useMorphing = false;
+               if ( shaderID ) {
 
-                       if ( material.morphTargets === true ) {
+                       const shader = ShaderLib[ shaderID ];
 
-                               useMorphing = geometry.morphAttributes && geometry.morphAttributes.position && geometry.morphAttributes.position.length > 0;
+                       vertexShader = shader.vertexShader;
+                       fragmentShader = shader.fragmentShader;
 
-                       }
+               } else {
 
-                       let useSkinning = false;
+                       vertexShader = material.vertexShader;
+                       fragmentShader = material.fragmentShader;
 
-                       if ( object.isSkinnedMesh === true ) {
+               }
 
-                               if ( material.skinning === true ) {
+               const currentRenderTarget = renderer.getRenderTarget();
 
-                                       useSkinning = true;
+               const useAlphaTest = material.alphaTest > 0;
+               const useClearcoat = material.clearcoat > 0;
 
-                               } else {
+               const parameters = {
 
-                                       console.warn( 'THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:', object );
+                       isWebGL2: isWebGL2,
 
-                               }
+                       shaderID: shaderID,
+                       shaderName: material.type,
 
-                       }
+                       vertexShader: vertexShader,
+                       fragmentShader: fragmentShader,
+                       defines: material.defines,
 
-                       const useInstancing = object.isInstancedMesh === true;
+                       isRawShaderMaterial: material.isRawShaderMaterial === true,
+                       glslVersion: material.glslVersion,
 
-                       result = getMaterialVariant( useMorphing, useSkinning, useInstancing );
+                       precision: precision,
 
-               } else {
+                       instancing: object.isInstancedMesh === true,
+                       instancingColor: object.isInstancedMesh === true && object.instanceColor !== null,
 
-                       result = customMaterial;
+                       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,
 
-               }
+                       clearcoat: useClearcoat,
+                       clearcoatMap: useClearcoat && !! material.clearcoatMap,
+                       clearcoatRoughnessMap: useClearcoat && !! material.clearcoatRoughnessMap,
+                       clearcoatNormalMap: useClearcoat && !! material.clearcoatNormalMap,
 
-               if ( _renderer.localClippingEnabled &&
-                               material.clipShadows === true &&
-                               material.clippingPlanes.length !== 0 ) {
+                       displacementMap: !! material.displacementMap,
+                       roughnessMap: !! material.roughnessMap,
+                       metalnessMap: !! material.metalnessMap,
+                       specularMap: !! material.specularMap,
+                       specularIntensityMap: !! material.specularIntensityMap,
+                       specularColorMap: !! material.specularColorMap,
+                       specularColorMapEncoding: getTextureEncodingFromMap( material.specularColorMap ),
 
-                       // in this case we need a unique material instance reflecting the
-                       // appropriate state
+                       alphaMap: !! material.alphaMap,
+                       alphaTest: useAlphaTest,
 
-                       const keyA = result.uuid, keyB = material.uuid;
+                       gradientMap: !! material.gradientMap,
 
-                       let materialsForVariant = _materialCache[ keyA ];
+                       sheen: material.sheen > 0,
+                       sheenColorMap: !! material.sheenColorMap,
+                       sheenColorMapEncoding: getTextureEncodingFromMap( material.sheenColorMap ),
+                       sheenRoughnessMap: !! material.sheenRoughnessMap,
 
-                       if ( materialsForVariant === undefined ) {
+                       transmission: material.transmission > 0,
+                       transmissionMap: !! material.transmissionMap,
+                       thicknessMap: !! material.thicknessMap,
 
-                               materialsForVariant = {};
-                               _materialCache[ keyA ] = materialsForVariant;
+                       combine: material.combine,
 
-                       }
+                       vertexTangents: ( !! material.normalMap && !! object.geometry && !! object.geometry.attributes.tangent ),
+                       vertexColors: material.vertexColors,
+                       vertexAlphas: material.vertexColors === true && !! object.geometry && !! object.geometry.attributes.color && object.geometry.attributes.color.itemSize === 4,
+                       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 || !! material.thicknessMap || !! material.specularIntensityMap || !! material.specularColorMap || !! material.sheenColorMap || material.sheenRoughnessMap,
+                       uvsVertexOnly: ! ( !! material.map || !! material.bumpMap || !! material.normalMap || !! material.specularMap || !! material.alphaMap || !! material.emissiveMap || !! material.roughnessMap || !! material.metalnessMap || !! material.clearcoatNormalMap || material.transmission > 0 || !! material.transmissionMap || !! material.thicknessMap || !! material.specularIntensityMap || !! material.specularColorMap || !! material.sheen > 0 || !! material.sheenColorMap || !! material.sheenRoughnessMap ) && !! material.displacementMap,
 
-                       let cachedMaterial = materialsForVariant[ keyB ];
+                       fog: !! fog,
+                       useFog: material.fog,
+                       fogExp2: ( fog && fog.isFogExp2 ),
 
-                       if ( cachedMaterial === undefined ) {
+                       flatShading: !! material.flatShading,
 
-                               cachedMaterial = result.clone();
-                               materialsForVariant[ keyB ] = cachedMaterial;
+                       sizeAttenuation: material.sizeAttenuation,
+                       logarithmicDepthBuffer: logarithmicDepthBuffer,
 
-                       }
+                       skinning: object.isSkinnedMesh === true && maxBones > 0,
+                       maxBones: maxBones,
+                       useVertexTexture: floatVertexTextures,
 
-                       result = cachedMaterial;
+                       morphTargets: !! object.geometry && !! object.geometry.morphAttributes.position,
+                       morphNormals: !! object.geometry && !! object.geometry.morphAttributes.normal,
+                       morphTargetsCount: ( !! object.geometry && !! object.geometry.morphAttributes.position ) ? object.geometry.morphAttributes.position.length : 0,
 
-               }
+                       numDirLights: lights.directional.length,
+                       numPointLights: lights.point.length,
+                       numSpotLights: lights.spot.length,
+                       numRectAreaLights: lights.rectArea.length,
+                       numHemiLights: lights.hemi.length,
 
-               result.visible = material.visible;
-               result.wireframe = material.wireframe;
+                       numDirLightShadows: lights.directionalShadowMap.length,
+                       numPointLightShadows: lights.pointShadowMap.length,
+                       numSpotLightShadows: lights.spotShadowMap.length,
 
-               if ( type === VSMShadowMap ) {
+                       numClippingPlanes: clipping.numPlanes,
+                       numClipIntersection: clipping.numIntersection,
 
-                       result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side;
+                       format: material.format,
+                       dithering: material.dithering,
 
-               } else {
+                       shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0,
+                       shadowMapType: renderer.shadowMap.type,
 
-                       result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ];
+                       toneMapping: material.toneMapped ? renderer.toneMapping : NoToneMapping,
+                       physicallyCorrectLights: renderer.physicallyCorrectLights,
 
-               }
+                       premultipliedAlpha: material.premultipliedAlpha,
 
-               result.clipShadows = material.clipShadows;
-               result.clippingPlanes = material.clippingPlanes;
-               result.clipIntersection = material.clipIntersection;
+                       doubleSided: material.side === DoubleSide,
+                       flipSided: material.side === BackSide,
 
-               result.wireframeLinewidth = material.wireframeLinewidth;
-               result.linewidth = material.linewidth;
+                       depthPacking: ( material.depthPacking !== undefined ) ? material.depthPacking : false,
 
-               if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) {
-
-                       result.referencePosition.setFromMatrixPosition( light.matrixWorld );
-                       result.nearDistance = shadowCameraNear;
-                       result.farDistance = shadowCameraFar;
-
-               }
-
-               return result;
+                       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,
 
-       function renderObject( object, camera, shadowCamera, light, type ) {
+                       rendererExtensionFragDepth: isWebGL2 || extensions.has( 'EXT_frag_depth' ),
+                       rendererExtensionDrawBuffers: isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ),
+                       rendererExtensionShaderTextureLod: isWebGL2 || extensions.has( 'EXT_shader_texture_lod' ),
 
-               if ( object.visible === false ) return;
+                       customProgramCacheKey: material.customProgramCacheKey()
 
-               const visible = object.layers.test( camera.layers );
+               };
 
-               if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) {
+               return parameters;
 
-                       if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) {
+       }
 
-                               object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
+       function getProgramCacheKey( parameters ) {
 
-                               const geometry = _objects.update( object );
-                               const material = object.material;
+               const array = [];
 
-                               if ( Array.isArray( material ) ) {
+               if ( parameters.shaderID ) {
 
-                                       const groups = geometry.groups;
+                       array.push( parameters.shaderID );
 
-                                       for ( let k = 0, kl = groups.length; k < kl; k ++ ) {
+               } else {
 
-                                               const group = groups[ k ];
-                                               const groupMaterial = material[ group.materialIndex ];
+                       array.push( hashString( parameters.fragmentShader ) );
+                       array.push( hashString( parameters.vertexShader ) );
 
-                                               if ( groupMaterial && groupMaterial.visible ) {
+               }
 
-                                                       const depthMaterial = getDepthMaterial( object, geometry, groupMaterial, light, shadowCamera.near, shadowCamera.far, type );
+               if ( parameters.defines !== undefined ) {
 
-                                                       _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group );
+                       for ( const name in parameters.defines ) {
 
-                                               }
+                               array.push( name );
+                               array.push( parameters.defines[ name ] );
 
-                                       }
+                       }
 
-                               } else if ( material.visible ) {
+               }
 
-                                       const depthMaterial = getDepthMaterial( object, geometry, material, light, shadowCamera.near, shadowCamera.far, type );
+               if ( parameters.isRawShaderMaterial === false ) {
 
-                                       _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null );
+                       for ( let i = 0; i < parameterNames.length; i ++ ) {
 
-                               }
+                               array.push( parameters[ parameterNames[ i ] ] );
 
                        }
 
-               }
-
-               const children = object.children;
+                       array.push( renderer.outputEncoding );
+                       array.push( renderer.gammaFactor );
 
-               for ( let i = 0, l = children.length; i < l; i ++ ) {
+               }
 
-                       renderObject( children[ i ], camera, shadowCamera, light, type );
+               array.push( parameters.customProgramCacheKey );
 
-               }
+               return array.join();
 
        }
 
-}
-
-function WebGLState( gl, extensions, capabilities ) {
+       function getUniforms( material ) {
 
-       const isWebGL2 = capabilities.isWebGL2;
+               const shaderID = shaderIDs[ material.type ];
+               let uniforms;
 
-       function ColorBuffer() {
+               if ( shaderID ) {
 
-               let locked = false;
+                       const shader = ShaderLib[ shaderID ];
+                       uniforms = UniformsUtils.clone( shader.uniforms );
 
-               const color = new Vector4();
-               let currentColorMask = null;
-               const currentColorClear = new Vector4( 0, 0, 0, 0 );
+               } else {
 
-               return {
+                       uniforms = material.uniforms;
 
-                       setMask: function ( colorMask ) {
+               }
 
-                               if ( currentColorMask !== colorMask && ! locked ) {
+               return uniforms;
 
-                                       gl.colorMask( colorMask, colorMask, colorMask, colorMask );
-                                       currentColorMask = colorMask;
+       }
 
-                               }
+       function acquireProgram( parameters, cacheKey ) {
 
-                       },
+               let program;
 
-                       setLocked: function ( lock ) {
+               // Check if code has been already compiled
+               for ( let p = 0, pl = programs.length; p < pl; p ++ ) {
 
-                               locked = lock;
+                       const preexistingProgram = programs[ p ];
 
-                       },
+                       if ( preexistingProgram.cacheKey === cacheKey ) {
 
-                       setClear: function ( r, g, b, a, premultipliedAlpha ) {
+                               program = preexistingProgram;
+                               ++ program.usedTimes;
 
-                               if ( premultipliedAlpha === true ) {
+                               break;
 
-                                       r *= a; g *= a; b *= a;
+                       }
 
-                               }
+               }
 
-                               color.set( r, g, b, a );
+               if ( program === undefined ) {
 
-                               if ( currentColorClear.equals( color ) === false ) {
+                       program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates );
+                       programs.push( program );
 
-                                       gl.clearColor( r, g, b, a );
-                                       currentColorClear.copy( color );
+               }
 
-                               }
+               return program;
 
-                       },
+       }
 
-                       reset: function () {
+       function releaseProgram( program ) {
 
-                               locked = false;
+               if ( -- program.usedTimes === 0 ) {
 
-                               currentColorMask = null;
-                               currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state
+                       // Remove from unordered set
+                       const i = programs.indexOf( program );
+                       programs[ i ] = programs[ programs.length - 1 ];
+                       programs.pop();
 
-                       }
+                       // Free WebGL resources
+                       program.destroy();
 
-               };
+               }
 
        }
 
-       function DepthBuffer() {
-
-               let locked = false;
-
-               let currentDepthMask = null;
-               let currentDepthFunc = null;
-               let currentDepthClear = null;
+       return {
+               getParameters: getParameters,
+               getProgramCacheKey: getProgramCacheKey,
+               getUniforms: getUniforms,
+               acquireProgram: acquireProgram,
+               releaseProgram: releaseProgram,
+               // Exposed for resource monitoring & error feedback via renderer.info:
+               programs: programs
+       };
 
-               return {
+}
 
-                       setTest: function ( depthTest ) {
+function WebGLProperties() {
 
-                               if ( depthTest ) {
+       let properties = new WeakMap();
 
-                                       enable( 2929 );
+       function get( object ) {
 
-                               } else {
+               let map = properties.get( object );
 
-                                       disable( 2929 );
+               if ( map === undefined ) {
 
-                               }
+                       map = {};
+                       properties.set( object, map );
 
-                       },
+               }
 
-                       setMask: function ( depthMask ) {
+               return map;
 
-                               if ( currentDepthMask !== depthMask && ! locked ) {
+       }
 
-                                       gl.depthMask( depthMask );
-                                       currentDepthMask = depthMask;
+       function remove( object ) {
 
-                               }
+               properties.delete( object );
 
-                       },
+       }
 
-                       setFunc: function ( depthFunc ) {
+       function update( object, key, value ) {
 
-                               if ( currentDepthFunc !== depthFunc ) {
+               properties.get( object )[ key ] = value;
 
-                                       if ( depthFunc ) {
+       }
 
-                                               switch ( depthFunc ) {
+       function dispose() {
 
-                                                       case NeverDepth:
+               properties = new WeakMap();
 
-                                                               gl.depthFunc( 512 );
-                                                               break;
+       }
 
-                                                       case AlwaysDepth:
+       return {
+               get: get,
+               remove: remove,
+               update: update,
+               dispose: dispose
+       };
 
-                                                               gl.depthFunc( 519 );
-                                                               break;
+}
 
-                                                       case LessDepth:
+function painterSortStable( a, b ) {
 
-                                                               gl.depthFunc( 513 );
-                                                               break;
+       if ( a.groupOrder !== b.groupOrder ) {
 
-                                                       case LessEqualDepth:
+               return a.groupOrder - b.groupOrder;
 
-                                                               gl.depthFunc( 515 );
-                                                               break;
+       } else if ( a.renderOrder !== b.renderOrder ) {
 
-                                                       case EqualDepth:
+               return a.renderOrder - b.renderOrder;
 
-                                                               gl.depthFunc( 514 );
-                                                               break;
+       } else if ( a.program !== b.program ) {
 
-                                                       case GreaterEqualDepth:
+               return a.program.id - b.program.id;
 
-                                                               gl.depthFunc( 518 );
-                                                               break;
+       } else if ( a.material.id !== b.material.id ) {
 
-                                                       case GreaterDepth:
+               return a.material.id - b.material.id;
 
-                                                               gl.depthFunc( 516 );
-                                                               break;
+       } else if ( a.z !== b.z ) {
 
-                                                       case NotEqualDepth:
+               return a.z - b.z;
 
-                                                               gl.depthFunc( 517 );
-                                                               break;
+       } else {
 
-                                                       default:
+               return a.id - b.id;
 
-                                                               gl.depthFunc( 515 );
+       }
 
-                                               }
+}
 
-                                       } else {
+function reversePainterSortStable( a, b ) {
 
-                                               gl.depthFunc( 515 );
+       if ( a.groupOrder !== b.groupOrder ) {
 
-                                       }
+               return a.groupOrder - b.groupOrder;
 
-                                       currentDepthFunc = depthFunc;
+       } else if ( a.renderOrder !== b.renderOrder ) {
 
-                               }
+               return a.renderOrder - b.renderOrder;
 
-                       },
+       } else if ( a.z !== b.z ) {
 
-                       setLocked: function ( lock ) {
+               return b.z - a.z;
 
-                               locked = lock;
+       } else {
 
-                       },
+               return a.id - b.id;
 
-                       setClear: function ( depth ) {
+       }
 
-                               if ( currentDepthClear !== depth ) {
+}
 
-                                       gl.clearDepth( depth );
-                                       currentDepthClear = depth;
 
-                               }
+function WebGLRenderList( properties ) {
 
-                       },
+       const renderItems = [];
+       let renderItemsIndex = 0;
 
-                       reset: function () {
+       const opaque = [];
+       const transmissive = [];
+       const transparent = [];
 
-                               locked = false;
+       const defaultProgram = { id: - 1 };
 
-                               currentDepthMask = null;
-                               currentDepthFunc = null;
-                               currentDepthClear = null;
+       function init() {
 
-                       }
+               renderItemsIndex = 0;
 
-               };
+               opaque.length = 0;
+               transmissive.length = 0;
+               transparent.length = 0;
 
        }
 
-       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;
+       function getNextRenderItem( object, geometry, material, groupOrder, z, group ) {
 
-               return {
+               let renderItem = renderItems[ renderItemsIndex ];
+               const materialProperties = properties.get( material );
 
-                       setTest: function ( stencilTest ) {
+               if ( renderItem === undefined ) {
 
-                               if ( ! locked ) {
+                       renderItem = {
+                               id: object.id,
+                               object: object,
+                               geometry: geometry,
+                               material: material,
+                               program: materialProperties.program || defaultProgram,
+                               groupOrder: groupOrder,
+                               renderOrder: object.renderOrder,
+                               z: z,
+                               group: group
+                       };
 
-                                       if ( stencilTest ) {
+                       renderItems[ renderItemsIndex ] = renderItem;
 
-                                               enable( 2960 );
+               } else {
 
-                                       } 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;
 
-                                               disable( 2960 );
+               }
 
-                                       }
+               renderItemsIndex ++;
 
-                               }
+               return renderItem;
 
-                       },
+       }
 
-                       setMask: function ( stencilMask ) {
+       function push( object, geometry, material, groupOrder, z, group ) {
 
-                               if ( currentStencilMask !== stencilMask && ! locked ) {
+               const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
 
-                                       gl.stencilMask( stencilMask );
-                                       currentStencilMask = stencilMask;
+               if ( material.transmission > 0.0 ) {
 
-                               }
+                       transmissive.push( renderItem );
 
-                       },
+               } else if ( material.transparent === true ) {
 
-                       setFunc: function ( stencilFunc, stencilRef, stencilMask ) {
+                       transparent.push( renderItem );
 
-                               if ( currentStencilFunc !== stencilFunc ||
-                                    currentStencilRef !== stencilRef ||
-                                    currentStencilFuncMask !== stencilMask ) {
+               } else {
 
-                                       gl.stencilFunc( stencilFunc, stencilRef, stencilMask );
+                       opaque.push( renderItem );
 
-                                       currentStencilFunc = stencilFunc;
-                                       currentStencilRef = stencilRef;
-                                       currentStencilFuncMask = stencilMask;
+               }
 
-                               }
+       }
 
-                       },
+       function unshift( object, geometry, material, groupOrder, z, group ) {
 
-                       setOp: function ( stencilFail, stencilZFail, stencilZPass ) {
+               const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
 
-                               if ( currentStencilFail !== stencilFail ||
-                                    currentStencilZFail !== stencilZFail ||
-                                    currentStencilZPass !== stencilZPass ) {
+               if ( material.transmission > 0.0 ) {
 
-                                       gl.stencilOp( stencilFail, stencilZFail, stencilZPass );
+                       transmissive.unshift( renderItem );
 
-                                       currentStencilFail = stencilFail;
-                                       currentStencilZFail = stencilZFail;
-                                       currentStencilZPass = stencilZPass;
+               } else if ( material.transparent === true ) {
 
-                               }
+                       transparent.unshift( renderItem );
 
-                       },
+               } else {
 
-                       setLocked: function ( lock ) {
+                       opaque.unshift( renderItem );
 
-                               locked = lock;
+               }
 
-                       },
+       }
 
-                       setClear: function ( stencil ) {
+       function sort( customOpaqueSort, customTransparentSort ) {
 
-                               if ( currentStencilClear !== stencil ) {
+               if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable );
+               if ( transmissive.length > 1 ) transmissive.sort( customTransparentSort || reversePainterSortStable );
+               if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable );
 
-                                       gl.clearStencil( stencil );
-                                       currentStencilClear = stencil;
+       }
 
-                               }
+       function finish() {
 
-                       },
+               // Clear references from inactive renderItems in the list
 
-                       reset: function () {
+               for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) {
 
-                               locked = false;
+                       const renderItem = renderItems[ i ];
 
-                               currentStencilMask = null;
-                               currentStencilFunc = null;
-                               currentStencilRef = null;
-                               currentStencilFuncMask = null;
-                               currentStencilFail = null;
-                               currentStencilZFail = null;
-                               currentStencilZPass = null;
-                               currentStencilClear = null;
+                       if ( renderItem.id === null ) break;
 
-                       }
+                       renderItem.id = null;
+                       renderItem.object = null;
+                       renderItem.geometry = null;
+                       renderItem.material = null;
+                       renderItem.program = null;
+                       renderItem.group = null;
 
-               };
+               }
 
        }
 
-       //
-
-       const colorBuffer = new ColorBuffer();
-       const depthBuffer = new DepthBuffer();
-       const stencilBuffer = new StencilBuffer();
-
-       let enabledCapabilities = {};
+       return {
 
-       let currentProgram = null;
+               opaque: opaque,
+               transmissive: transmissive,
+               transparent: transparent,
 
-       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;
+               init: init,
+               push: push,
+               unshift: unshift,
+               finish: finish,
 
-       let currentFlipSided = null;
-       let currentCullFace = null;
+               sort: sort
+       };
 
-       let currentLineWidth = null;
+}
 
-       let currentPolygonOffsetFactor = null;
-       let currentPolygonOffsetUnits = null;
+function WebGLRenderLists( properties ) {
 
-       const maxTextures = gl.getParameter( 35661 );
+       let lists = new WeakMap();
 
-       let lineWidthAvailable = false;
-       let version = 0;
-       const glVersion = gl.getParameter( 7938 );
+       function get( scene, renderCallDepth ) {
 
-       if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) {
+               let list;
 
-               version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] );
-               lineWidthAvailable = ( version >= 1.0 );
+               if ( lists.has( scene ) === false ) {
 
-       } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) {
+                       list = new WebGLRenderList( properties );
+                       lists.set( scene, [ list ] );
 
-               version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] );
-               lineWidthAvailable = ( version >= 2.0 );
+               } else {
 
-       }
+                       if ( renderCallDepth >= lists.get( scene ).length ) {
 
-       let currentTextureSlot = null;
-       let currentBoundTextures = {};
+                               list = new WebGLRenderList( properties );
+                               lists.get( scene ).push( list );
 
-       const currentScissor = new Vector4();
-       const currentViewport = new Vector4();
+                       } else {
 
-       function createTexture( type, target, count ) {
+                               list = lists.get( scene )[ renderCallDepth ];
 
-               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 ++ ) {
+               return list;
 
-                       gl.texImage2D( target + i, 0, 6408, 1, 1, 0, 6408, 5121, data );
+       }
 
-               }
+       function dispose() {
 
-               return texture;
+               lists = new WeakMap();
 
        }
 
-       const emptyTextures = {};
-       emptyTextures[ 3553 ] = createTexture( 3553, 3553, 1 );
-       emptyTextures[ 34067 ] = createTexture( 34067, 34069, 6 );
+       return {
+               get: get,
+               dispose: dispose
+       };
 
-       // init
+}
 
-       colorBuffer.setClear( 0, 0, 0, 1 );
-       depthBuffer.setClear( 1 );
-       stencilBuffer.setClear( 0 );
+function UniformsCache() {
 
-       enable( 2929 );
-       depthBuffer.setFunc( LessEqualDepth );
+       const lights = {};
 
-       setFlipSided( false );
-       setCullFace( CullFaceBack );
-       enable( 2884 );
+       return {
 
-       setBlending( NoBlending );
+               get: function ( light ) {
 
-       //
+                       if ( lights[ light.id ] !== undefined ) {
 
-       function enable( id ) {
+                               return lights[ light.id ];
 
-               if ( enabledCapabilities[ id ] !== true ) {
+                       }
 
-                       gl.enable( id );
-                       enabledCapabilities[ id ] = true;
+                       let uniforms;
 
-               }
+                       switch ( light.type ) {
 
-       }
-
-       function disable( id ) {
-
-               if ( enabledCapabilities[ id ] !== false ) {
-
-                       gl.disable( id );
-                       enabledCapabilities[ id ] = false;
+                               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;
 
-       function useProgram( program ) {
+                               case 'HemisphereLight':
+                                       uniforms = {
+                                               direction: new Vector3(),
+                                               skyColor: new Color(),
+                                               groundColor: new Color()
+                                       };
+                                       break;
 
-               if ( currentProgram !== program ) {
+                               case 'RectAreaLight':
+                                       uniforms = {
+                                               color: new Color(),
+                                               position: new Vector3(),
+                                               halfWidth: new Vector3(),
+                                               halfHeight: new Vector3()
+                                       };
+                                       break;
 
-                       gl.useProgram( program );
+                       }
 
-                       currentProgram = program;
+                       lights[ light.id ] = uniforms;
 
-                       return true;
+                       return uniforms;
 
                }
 
-               return false;
-
-       }
-
-       const equationToGL = {
-               [ AddEquation ]: 32774,
-               [ SubtractEquation ]: 32778,
-               [ ReverseSubtractEquation ]: 32779
        };
 
-       if ( isWebGL2 ) {
+}
 
-               equationToGL[ MinEquation ] = 32775;
-               equationToGL[ MaxEquation ] = 32776;
+function ShadowUniformsCache() {
 
-       } else {
+       const lights = {};
 
-               const extension = extensions.get( 'EXT_blend_minmax' );
+       return {
 
-               if ( extension !== null ) {
+               get: function ( light ) {
 
-                       equationToGL[ MinEquation ] = extension.MIN_EXT;
-                       equationToGL[ MaxEquation ] = extension.MAX_EXT;
+                       if ( lights[ light.id ] !== undefined ) {
 
-               }
+                               return lights[ light.id ];
 
-       }
+                       }
 
-       const factorToGL = {
-               [ ZeroFactor ]: 0,
-               [ OneFactor ]: 1,
-               [ SrcColorFactor ]: 768,
-               [ SrcAlphaFactor ]: 770,
-               [ SrcAlphaSaturateFactor ]: 776,
-               [ DstColorFactor ]: 774,
-               [ DstAlphaFactor ]: 772,
-               [ OneMinusSrcColorFactor ]: 769,
-               [ OneMinusSrcAlphaFactor ]: 771,
-               [ OneMinusDstColorFactor ]: 775,
-               [ OneMinusDstAlphaFactor ]: 773
-       };
+                       let uniforms;
 
-       function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {
+                       switch ( light.type ) {
 
-               if ( blending === NoBlending ) {
+                               case 'DirectionalLight':
+                                       uniforms = {
+                                               shadowBias: 0,
+                                               shadowNormalBias: 0,
+                                               shadowRadius: 1,
+                                               shadowMapSize: new Vector2()
+                                       };
+                                       break;
 
-                       if ( currentBlendingEnabled ) {
+                               case 'SpotLight':
+                                       uniforms = {
+                                               shadowBias: 0,
+                                               shadowNormalBias: 0,
+                                               shadowRadius: 1,
+                                               shadowMapSize: new Vector2()
+                                       };
+                                       break;
 
-                               disable( 3042 );
-                               currentBlendingEnabled = false;
+                               case 'PointLight':
+                                       uniforms = {
+                                               shadowBias: 0,
+                                               shadowNormalBias: 0,
+                                               shadowRadius: 1,
+                                               shadowMapSize: new Vector2(),
+                                               shadowCameraNear: 1,
+                                               shadowCameraFar: 1000
+                                       };
+                                       break;
+
+                               // TODO (abelnation): set RectAreaLight shadow uniforms
 
                        }
 
-                       return;
+                       lights[ light.id ] = uniforms;
+
+                       return uniforms;
 
                }
 
-               if ( ! currentBlendingEnabled ) {
+       };
 
-                       enable( 3042 );
-                       currentBlendingEnabled = true;
+}
 
-               }
 
-               if ( blending !== CustomBlending ) {
 
-                       if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) {
+let nextVersion = 0;
 
-                               if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) {
+function shadowCastingLightsFirst( lightA, lightB ) {
 
-                                       gl.blendEquation( 32774 );
+       return ( lightB.castShadow ? 1 : 0 ) - ( lightA.castShadow ? 1 : 0 );
 
-                                       currentBlendEquation = AddEquation;
-                                       currentBlendEquationAlpha = AddEquation;
+}
 
-                               }
+function WebGLLights( extensions, capabilities ) {
 
-                               if ( premultipliedAlpha ) {
+       const cache = new UniformsCache();
 
-                                       switch ( blending ) {
+       const shadowCache = ShadowUniformsCache();
 
-                                               case NormalBlending:
-                                                       gl.blendFuncSeparate( 1, 771, 1, 771 );
-                                                       break;
+       const state = {
 
-                                               case AdditiveBlending:
-                                                       gl.blendFunc( 1, 1 );
-                                                       break;
+               version: 0,
 
-                                               case SubtractiveBlending:
-                                                       gl.blendFuncSeparate( 0, 0, 769, 771 );
-                                                       break;
+               hash: {
+                       directionalLength: - 1,
+                       pointLength: - 1,
+                       spotLength: - 1,
+                       rectAreaLength: - 1,
+                       hemiLength: - 1,
 
-                                               case MultiplyBlending:
-                                                       gl.blendFuncSeparate( 0, 768, 0, 770 );
-                                                       break;
+                       numDirectionalShadows: - 1,
+                       numPointShadows: - 1,
+                       numSpotShadows: - 1
+               },
 
-                                               default:
-                                                       console.error( 'THREE.WebGLState: Invalid blending: ', blending );
-                                                       break;
+               ambient: [ 0, 0, 0 ],
+               probe: [],
+               directional: [],
+               directionalShadow: [],
+               directionalShadowMap: [],
+               directionalShadowMatrix: [],
+               spot: [],
+               spotShadow: [],
+               spotShadowMap: [],
+               spotShadowMatrix: [],
+               rectArea: [],
+               rectAreaLTC1: null,
+               rectAreaLTC2: null,
+               point: [],
+               pointShadow: [],
+               pointShadowMap: [],
+               pointShadowMatrix: [],
+               hemi: []
 
-                                       }
+       };
 
-                               } else {
+       for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() );
 
-                                       switch ( blending ) {
+       const vector3 = new Vector3();
+       const matrix4 = new Matrix4();
+       const matrix42 = new Matrix4();
 
-                                               case NormalBlending:
-                                                       gl.blendFuncSeparate( 770, 771, 1, 771 );
-                                                       break;
+       function setup( lights, physicallyCorrectLights ) {
 
-                                               case AdditiveBlending:
-                                                       gl.blendFunc( 770, 1 );
-                                                       break;
+               let r = 0, g = 0, b = 0;
 
-                                               case SubtractiveBlending:
-                                                       gl.blendFunc( 0, 769 );
-                                                       break;
+               for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 );
 
-                                               case MultiplyBlending:
-                                                       gl.blendFunc( 0, 768 );
-                                                       break;
+               let directionalLength = 0;
+               let pointLength = 0;
+               let spotLength = 0;
+               let rectAreaLength = 0;
+               let hemiLength = 0;
 
-                                               default:
-                                                       console.error( 'THREE.WebGLState: Invalid blending: ', blending );
-                                                       break;
+               let numDirectionalShadows = 0;
+               let numPointShadows = 0;
+               let numSpotShadows = 0;
 
-                                       }
+               lights.sort( shadowCastingLightsFirst );
 
-                               }
+               // artist-friendly light intensity scaling factor
+               const scaleFactor = ( physicallyCorrectLights !== true ) ? Math.PI : 1;
 
-                               currentBlendSrc = null;
-                               currentBlendDst = null;
-                               currentBlendSrcAlpha = null;
-                               currentBlendDstAlpha = null;
+               for ( let i = 0, l = lights.length; i < l; i ++ ) {
 
-                               currentBlending = blending;
-                               currentPremultipledAlpha = premultipliedAlpha;
+                       const light = lights[ i ];
 
-                       }
+                       const color = light.color;
+                       const intensity = light.intensity;
+                       const distance = light.distance;
 
-                       return;
+                       const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null;
 
-               }
+                       if ( light.isAmbientLight ) {
 
-               // custom blending
+                               r += color.r * intensity * scaleFactor;
+                               g += color.g * intensity * scaleFactor;
+                               b += color.b * intensity * scaleFactor;
 
-               blendEquationAlpha = blendEquationAlpha || blendEquation;
-               blendSrcAlpha = blendSrcAlpha || blendSrc;
-               blendDstAlpha = blendDstAlpha || blendDst;
+                       } else if ( light.isLightProbe ) {
 
-               if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) {
+                               for ( let j = 0; j < 9; j ++ ) {
 
-                       gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] );
+                                       state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity );
 
-                       currentBlendEquation = blendEquation;
-                       currentBlendEquationAlpha = blendEquationAlpha;
+                               }
 
-               }
+                       } else if ( light.isDirectionalLight ) {
 
-               if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) {
+                               const uniforms = cache.get( light );
 
-                       gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] );
+                               uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor );
 
-                       currentBlendSrc = blendSrc;
-                       currentBlendDst = blendDst;
-                       currentBlendSrcAlpha = blendSrcAlpha;
-                       currentBlendDstAlpha = blendDstAlpha;
+                               if ( light.castShadow ) {
 
-               }
+                                       const shadow = light.shadow;
 
-               currentBlending = blending;
-               currentPremultipledAlpha = null;
+                                       const shadowUniforms = shadowCache.get( light );
 
-       }
+                                       shadowUniforms.shadowBias = shadow.bias;
+                                       shadowUniforms.shadowNormalBias = shadow.normalBias;
+                                       shadowUniforms.shadowRadius = shadow.radius;
+                                       shadowUniforms.shadowMapSize = shadow.mapSize;
 
-       function setMaterial( material, frontFaceCW ) {
+                                       state.directionalShadow[ directionalLength ] = shadowUniforms;
+                                       state.directionalShadowMap[ directionalLength ] = shadowMap;
+                                       state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix;
 
-               material.side === DoubleSide
-                       ? disable( 2884 )
-                       : enable( 2884 );
+                                       numDirectionalShadows ++;
 
-               let flipSided = ( material.side === BackSide );
-               if ( frontFaceCW ) flipSided = ! flipSided;
+                               }
 
-               setFlipSided( flipSided );
+                               state.directional[ directionalLength ] = uniforms;
 
-               ( 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 );
+                               directionalLength ++;
 
-               depthBuffer.setFunc( material.depthFunc );
-               depthBuffer.setTest( material.depthTest );
-               depthBuffer.setMask( material.depthWrite );
-               colorBuffer.setMask( material.colorWrite );
+                       } else if ( light.isSpotLight ) {
 
-               const stencilWrite = material.stencilWrite;
-               stencilBuffer.setTest( stencilWrite );
-               if ( stencilWrite ) {
+                               const uniforms = cache.get( light );
 
-                       stencilBuffer.setMask( material.stencilWriteMask );
-                       stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask );
-                       stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass );
+                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
 
-               }
+                               uniforms.color.copy( color ).multiplyScalar( intensity * scaleFactor );
+                               uniforms.distance = distance;
 
-               setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+                               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;
 
-       function setFlipSided( flipSided ) {
+                                       const shadowUniforms = shadowCache.get( light );
 
-               if ( currentFlipSided !== flipSided ) {
+                                       shadowUniforms.shadowBias = shadow.bias;
+                                       shadowUniforms.shadowNormalBias = shadow.normalBias;
+                                       shadowUniforms.shadowRadius = shadow.radius;
+                                       shadowUniforms.shadowMapSize = shadow.mapSize;
 
-                       if ( flipSided ) {
+                                       state.spotShadow[ spotLength ] = shadowUniforms;
+                                       state.spotShadowMap[ spotLength ] = shadowMap;
+                                       state.spotShadowMatrix[ spotLength ] = light.shadow.matrix;
 
-                               gl.frontFace( 2304 );
+                                       numSpotShadows ++;
 
-                       } else {
+                               }
 
-                               gl.frontFace( 2305 );
+                               state.spot[ spotLength ] = uniforms;
 
-                       }
+                               spotLength ++;
 
-                       currentFlipSided = flipSided;
+                       } 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 ) );
 
-       function setCullFace( cullFace ) {
+                               // (b) intensity is the brightness of the light
+                               uniforms.color.copy( color ).multiplyScalar( intensity );
 
-               if ( cullFace !== CullFaceNone ) {
+                               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
+                               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
 
-                       enable( 2884 );
+                               state.rectArea[ rectAreaLength ] = uniforms;
 
-                       if ( cullFace !== currentCullFace ) {
+                               rectAreaLength ++;
 
-                               if ( cullFace === CullFaceBack ) {
+                       } else if ( light.isPointLight ) {
 
-                                       gl.cullFace( 1029 );
+                               const uniforms = cache.get( light );
 
-                               } else if ( cullFace === CullFaceFront ) {
+                               uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor );
+                               uniforms.distance = light.distance;
+                               uniforms.decay = light.decay;
 
-                                       gl.cullFace( 1028 );
+                               if ( light.castShadow ) {
 
-                               } else {
+                                       const shadow = light.shadow;
 
-                                       gl.cullFace( 1032 );
+                                       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;
 
-               } else {
+                                       numPointShadows ++;
 
-                       disable( 2884 );
+                               }
 
-               }
+                               state.point[ pointLength ] = uniforms;
 
-               currentCullFace = cullFace;
+                               pointLength ++;
 
-       }
+                       } else if ( light.isHemisphereLight ) {
 
-       function setLineWidth( width ) {
+                               const uniforms = cache.get( light );
 
-               if ( width !== currentLineWidth ) {
+                               uniforms.skyColor.copy( light.color ).multiplyScalar( intensity * scaleFactor );
+                               uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity * scaleFactor );
 
-                       if ( lineWidthAvailable ) gl.lineWidth( width );
+                               state.hemi[ hemiLength ] = uniforms;
 
-                       currentLineWidth = width;
+                               hemiLength ++;
 
-               }
+                       }
 
-       }
+               }
 
-       function setPolygonOffset( polygonOffset, factor, units ) {
+               if ( rectAreaLength > 0 ) {
 
-               if ( polygonOffset ) {
+                       if ( capabilities.isWebGL2 ) {
 
-                       enable( 32823 );
+                               // WebGL 2
 
-                       if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) {
+                               state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
+                               state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
 
-                               gl.polygonOffset( factor, units );
+                       } else {
 
-                               currentPolygonOffsetFactor = factor;
-                               currentPolygonOffsetUnits = units;
+                               // WebGL 1
 
-                       }
+                               if ( extensions.has( 'OES_texture_float_linear' ) === true ) {
 
-               } else {
+                                       state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
+                                       state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
 
-                       disable( 32823 );
+                               } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) {
 
-               }
+                                       state.rectAreaLTC1 = UniformsLib.LTC_HALF_1;
+                                       state.rectAreaLTC2 = UniformsLib.LTC_HALF_2;
 
-       }
+                               } else {
 
-       function setScissorTest( scissorTest ) {
+                                       console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' );
 
-               if ( scissorTest ) {
+                               }
 
-                       enable( 3089 );
+                       }
 
-               } else {
+               }
 
-                       disable( 3089 );
+               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 ) {
 
-       // texture
+                       state.directional.length = directionalLength;
+                       state.spot.length = spotLength;
+                       state.rectArea.length = rectAreaLength;
+                       state.point.length = pointLength;
+                       state.hemi.length = hemiLength;
 
-       function activeTexture( webglSlot ) {
+                       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;
 
-               if ( webglSlot === undefined ) webglSlot = 33984 + maxTextures - 1;
+                       hash.directionalLength = directionalLength;
+                       hash.pointLength = pointLength;
+                       hash.spotLength = spotLength;
+                       hash.rectAreaLength = rectAreaLength;
+                       hash.hemiLength = hemiLength;
 
-               if ( currentTextureSlot !== webglSlot ) {
+                       hash.numDirectionalShadows = numDirectionalShadows;
+                       hash.numPointShadows = numPointShadows;
+                       hash.numSpotShadows = numSpotShadows;
 
-                       gl.activeTexture( webglSlot );
-                       currentTextureSlot = webglSlot;
+                       state.version = nextVersion ++;
 
                }
 
        }
 
-       function bindTexture( webglType, webglTexture ) {
+       function setupView( lights, camera ) {
 
-               if ( currentTextureSlot === null ) {
+               let directionalLength = 0;
+               let pointLength = 0;
+               let spotLength = 0;
+               let rectAreaLength = 0;
+               let hemiLength = 0;
 
-                       activeTexture();
+               const viewMatrix = camera.matrixWorldInverse;
 
-               }
+               for ( let i = 0, l = lights.length; i < l; i ++ ) {
 
-               let boundTexture = currentBoundTextures[ currentTextureSlot ];
+                       const light = lights[ i ];
 
-               if ( boundTexture === undefined ) {
+                       if ( light.isDirectionalLight ) {
 
-                       boundTexture = { type: undefined, texture: undefined };
-                       currentBoundTextures[ currentTextureSlot ] = boundTexture;
+                               const uniforms = state.directional[ directionalLength ];
 
-               }
+                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+                               vector3.setFromMatrixPosition( light.target.matrixWorld );
+                               uniforms.direction.sub( vector3 );
+                               uniforms.direction.transformDirection( viewMatrix );
 
-               if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {
+                               directionalLength ++;
 
-                       gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );
+                       } else if ( light.isSpotLight ) {
 
-                       boundTexture.type = webglType;
-                       boundTexture.texture = webglTexture;
-
-               }
-
-       }
+                               const uniforms = state.spot[ spotLength ];
 
-       function unbindTexture() {
+                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
+                               uniforms.position.applyMatrix4( viewMatrix );
 
-               const boundTexture = currentBoundTextures[ currentTextureSlot ];
+                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+                               vector3.setFromMatrixPosition( light.target.matrixWorld );
+                               uniforms.direction.sub( vector3 );
+                               uniforms.direction.transformDirection( viewMatrix );
 
-               if ( boundTexture !== undefined && boundTexture.type !== undefined ) {
+                               spotLength ++;
 
-                       gl.bindTexture( boundTexture.type, null );
+                       } else if ( light.isRectAreaLight ) {
 
-                       boundTexture.type = undefined;
-                       boundTexture.texture = undefined;
+                               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 );
 
-       function compressedTexImage2D() {
+                               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
+                               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
 
-               try {
+                               uniforms.halfWidth.applyMatrix4( matrix42 );
+                               uniforms.halfHeight.applyMatrix4( matrix42 );
 
-                       gl.compressedTexImage2D.apply( gl, arguments );
+                               rectAreaLength ++;
 
-               } catch ( error ) {
+                       } else if ( light.isPointLight ) {
 
-                       console.error( 'THREE.WebGLState:', error );
+                               const uniforms = state.point[ pointLength ];
 
-               }
+                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
+                               uniforms.position.applyMatrix4( viewMatrix );
 
-       }
+                               pointLength ++;
 
-       function texImage2D() {
+                       } else if ( light.isHemisphereLight ) {
 
-               try {
+                               const uniforms = state.hemi[ hemiLength ];
 
-                       gl.texImage2D.apply( gl, arguments );
+                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+                               uniforms.direction.transformDirection( viewMatrix );
+                               uniforms.direction.normalize();
 
-               } catch ( error ) {
+                               hemiLength ++;
 
-                       console.error( 'THREE.WebGLState:', error );
+                       }
 
                }
 
        }
 
-       function texImage3D() {
+       return {
+               setup: setup,
+               setupView: setupView,
+               state: state
+       };
 
-               try {
+}
 
-                       gl.texImage3D.apply( gl, arguments );
+function WebGLRenderState( extensions, capabilities ) {
 
-               } catch ( error ) {
+       const lights = new WebGLLights( extensions, capabilities );
 
-                       console.error( 'THREE.WebGLState:', error );
+       const lightsArray = [];
+       const shadowsArray = [];
 
-               }
+       function init() {
+
+               lightsArray.length = 0;
+               shadowsArray.length = 0;
 
        }
 
-       //
+       function pushLight( light ) {
 
-       function scissor( scissor ) {
+               lightsArray.push( light );
 
-               if ( currentScissor.equals( scissor ) === false ) {
+       }
 
-                       gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w );
-                       currentScissor.copy( scissor );
+       function pushShadow( shadowLight ) {
 
-               }
+               shadowsArray.push( shadowLight );
 
        }
 
-       function viewport( viewport ) {
+       function setupLights( physicallyCorrectLights ) {
 
-               if ( currentViewport.equals( viewport ) === false ) {
+               lights.setup( lightsArray, physicallyCorrectLights );
 
-                       gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w );
-                       currentViewport.copy( viewport );
+       }
 
-               }
+       function setupLightsView( camera ) {
+
+               lights.setupView( lightsArray, camera );
 
        }
 
-       //
+       const state = {
+               lightsArray: lightsArray,
+               shadowsArray: shadowsArray,
 
-       function reset() {
+               lights: lights
+       };
 
-               enabledCapabilities = {};
+       return {
+               init: init,
+               state: state,
+               setupLights: setupLights,
+               setupLightsView: setupLightsView,
 
-               currentTextureSlot = null;
-               currentBoundTextures = {};
+               pushLight: pushLight,
+               pushShadow: pushShadow
+       };
 
-               currentProgram = null;
+}
 
-               currentBlendingEnabled = null;
-               currentBlending = null;
-               currentBlendEquation = null;
-               currentBlendSrc = null;
-               currentBlendDst = null;
-               currentBlendEquationAlpha = null;
-               currentBlendSrcAlpha = null;
-               currentBlendDstAlpha = null;
-               currentPremultipledAlpha = false;
+function WebGLRenderStates( extensions, capabilities ) {
 
-               currentFlipSided = null;
-               currentCullFace = null;
+       let renderStates = new WeakMap();
 
-               currentLineWidth = null;
+       function get( scene, renderCallDepth = 0 ) {
 
-               currentPolygonOffsetFactor = null;
-               currentPolygonOffsetUnits = null;
+               let renderState;
 
-               colorBuffer.reset();
-               depthBuffer.reset();
-               stencilBuffer.reset();
+               if ( renderStates.has( scene ) === false ) {
 
-       }
+                       renderState = new WebGLRenderState( extensions, capabilities );
+                       renderStates.set( scene, [ renderState ] );
 
-       return {
+               } else {
 
-               buffers: {
-                       color: colorBuffer,
-                       depth: depthBuffer,
-                       stencil: stencilBuffer
-               },
+                       if ( renderCallDepth >= renderStates.get( scene ).length ) {
 
-               enable: enable,
-               disable: disable,
+                               renderState = new WebGLRenderState( extensions, capabilities );
+                               renderStates.get( scene ).push( renderState );
 
-               useProgram: useProgram,
+                       } else {
 
-               setBlending: setBlending,
-               setMaterial: setMaterial,
+                               renderState = renderStates.get( scene )[ renderCallDepth ];
 
-               setFlipSided: setFlipSided,
-               setCullFace: setCullFace,
+                       }
 
-               setLineWidth: setLineWidth,
-               setPolygonOffset: setPolygonOffset,
+               }
 
-               setScissorTest: setScissorTest,
+               return renderState;
 
-               activeTexture: activeTexture,
-               bindTexture: bindTexture,
-               unbindTexture: unbindTexture,
-               compressedTexImage2D: compressedTexImage2D,
-               texImage2D: texImage2D,
-               texImage3D: texImage3D,
+       }
 
-               scissor: scissor,
-               viewport: viewport,
+       function dispose() {
 
-               reset: reset
+               renderStates = new WeakMap();
 
+       }
+
+       return {
+               get: get,
+               dispose: dispose
        };
 
 }
 
-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;
+/**
+ * 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>
+ * }
+ */
 
-       const _videoTextures = new WeakMap();
-       let _canvas;
+class MeshDepthMaterial extends Material {
 
-       // 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).
+       constructor( parameters ) {
 
-       let useOffscreenCanvas = false;
+               super();
 
-       try {
+               this.type = 'MeshDepthMaterial';
 
-               useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined'
-                       && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null;
+               this.depthPacking = BasicDepthPacking;
 
-       } catch ( err ) {
+               this.map = null;
 
-               // Ignore any errors
+               this.alphaMap = null;
 
-       }
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-       function createCanvas( width, height ) {
+               this.wireframe = false;
+               this.wireframeLinewidth = 1;
 
-               // Use OffscreenCanvas when available. Specially needed in web workers
+               this.fog = false;
 
-               return useOffscreenCanvas ?
-                       new OffscreenCanvas( width, height ) :
-                       document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+               this.setValues( parameters );
 
        }
 
-       function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) {
+       copy( source ) {
 
-               let scale = 1;
+               super.copy( source );
 
-               // handle case if texture exceeds max size
+               this.depthPacking = source.depthPacking;
 
-               if ( image.width > maxSize || image.height > maxSize ) {
+               this.map = source.map;
 
-                       scale = maxSize / Math.max( image.width, image.height );
+               this.alphaMap = source.alphaMap;
 
-               }
+               this.displacementMap = source.displacementMap;
+               this.displacementScale = source.displacementScale;
+               this.displacementBias = source.displacementBias;
 
-               // only perform resize if necessary
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
 
-               if ( scale < 1 || needsPowerOfTwo === true ) {
+               return this;
 
-                       // 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;
+MeshDepthMaterial.prototype.isMeshDepthMaterial = true;
 
-                               const width = floor( scale * image.width );
-                               const height = floor( scale * image.height );
+/**
+ * parameters = {
+ *
+ *  referencePosition: <float>,
+ *  nearDistance: <float>,
+ *  farDistance: <float>,
+ *
+ *  map: new THREE.Texture( <Image> ),
+ *
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *
+ *  displacementMap: new THREE.Texture( <Image> ),
+ *  displacementScale: <float>,
+ *  displacementBias: <float>
+ *
+ * }
+ */
 
-                               if ( _canvas === undefined ) _canvas = createCanvas( width, height );
+class MeshDistanceMaterial extends Material {
 
-                               // cube textures can't reuse the same canvas
+       constructor( parameters ) {
 
-                               const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas;
+               super();
 
-                               canvas.width = width;
-                               canvas.height = height;
+               this.type = 'MeshDistanceMaterial';
 
-                               const context = canvas.getContext( '2d' );
-                               context.drawImage( image, 0, 0, width, height );
+               this.referencePosition = new Vector3();
+               this.nearDistance = 1;
+               this.farDistance = 1000;
 
-                               console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').' );
+               this.map = null;
 
-                               return canvas;
+               this.alphaMap = null;
 
-                       } else {
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-                               if ( 'data' in image ) {
+               this.fog = false;
 
-                                       console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );
+               this.setValues( parameters );
 
-                               }
+       }
 
-                               return image;
+       copy( source ) {
 
-                       }
+               super.copy( source );
 
-               }
+               this.referencePosition.copy( source.referencePosition );
+               this.nearDistance = source.nearDistance;
+               this.farDistance = source.farDistance;
 
-               return image;
+               this.map = source.map;
 
-       }
+               this.alphaMap = source.alphaMap;
 
-       function isPowerOfTwo( image ) {
+               this.displacementMap = source.displacementMap;
+               this.displacementScale = source.displacementScale;
+               this.displacementBias = source.displacementBias;
 
-               return MathUtils.isPowerOfTwo( image.width ) && MathUtils.isPowerOfTwo( image.height );
+               return this;
 
        }
 
-       function textureNeedsPowerOfTwo( texture ) {
+}
 
-               if ( isWebGL2 ) return false;
+MeshDistanceMaterial.prototype.isMeshDistanceMaterial = true;
 
-               return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) ||
-                       ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter );
+const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}";
 
-       }
+const fragment = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include <packing>\nvoid main() {\n\tconst float samples = float( VSM_SAMPLES );\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat uvStride = samples <= 1.0 ? 0.0 : 2.0 / ( samples - 1.0 );\n\tfloat uvStart = samples <= 1.0 ? 0.0 : - 1.0;\n\tfor ( float i = 0.0; i < samples; i ++ ) {\n\t\tfloat uvOffset = uvStart + i * uvStride;\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( uvOffset, 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, uvOffset ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean / samples;\n\tsquared_mean = squared_mean / samples;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}";
 
-       function textureNeedsGenerateMipmaps( texture, supportsMips ) {
+function WebGLShadowMap( _renderer, _objects, _capabilities ) {
 
-               return texture.generateMipmaps && supportsMips &&
-                       texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter;
+       let _frustum = new Frustum();
 
-       }
+       const _shadowMapSize = new Vector2(),
+               _viewportSize = new Vector2(),
 
-       function generateMipmap( target, texture, width, height ) {
+               _viewport = new Vector4(),
 
-               _gl.generateMipmap( target );
+               _depthMaterial = new MeshDepthMaterial( { depthPacking: RGBADepthPacking } ),
+               _distanceMaterial = new MeshDistanceMaterial(),
 
-               const textureProperties = properties.get( texture );
+               _materialCache = {},
 
-               // 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;
+               _maxTextureSize = _capabilities.maxTextureSize;
 
-       }
+       const shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide };
 
-       function getInternalFormat( internalFormatName, glFormat, glType ) {
+       const shadowMaterialVertical = new ShaderMaterial( {
+               defines: {
+                       VSM_SAMPLES: 8
+               },
+               uniforms: {
+                       shadow_pass: { value: null },
+                       resolution: { value: new Vector2() },
+                       radius: { value: 4.0 }
+               },
 
-               if ( isWebGL2 === false ) return glFormat;
+               vertexShader: vertex,
+               fragmentShader: fragment
 
-               if ( internalFormatName !== null ) {
+       } );
 
-                       if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ];
+       const shadowMaterialHorizontal = shadowMaterialVertical.clone();
+       shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1;
 
-                       console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' );
+       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 );
 
-               let internalFormat = glFormat;
+       const scope = this;
 
-               if ( glFormat === 6403 ) {
+       this.enabled = false;
 
-                       if ( glType === 5126 ) internalFormat = 33326;
-                       if ( glType === 5131 ) internalFormat = 33325;
-                       if ( glType === 5121 ) internalFormat = 33321;
+       this.autoUpdate = true;
+       this.needsUpdate = false;
 
-               }
+       this.type = PCFShadowMap;
 
-               if ( glFormat === 6407 ) {
+       this.render = function ( lights, scene, camera ) {
 
-                       if ( glType === 5126 ) internalFormat = 34837;
-                       if ( glType === 5131 ) internalFormat = 34843;
-                       if ( glType === 5121 ) internalFormat = 32849;
+               if ( scope.enabled === false ) return;
+               if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;
 
-               }
+               if ( lights.length === 0 ) return;
 
-               if ( glFormat === 6408 ) {
+               const currentRenderTarget = _renderer.getRenderTarget();
+               const activeCubeFace = _renderer.getActiveCubeFace();
+               const activeMipmapLevel = _renderer.getActiveMipmapLevel();
 
-                       if ( glType === 5126 ) internalFormat = 34836;
-                       if ( glType === 5131 ) internalFormat = 34842;
-                       if ( glType === 5121 ) internalFormat = 32856;
+               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 );
 
-               if ( internalFormat === 33325 || internalFormat === 33326 ||
-                       internalFormat === 34842 || internalFormat === 34836 ) {
+               // render depth map
 
-                       extensions.get( 'EXT_color_buffer_float' );
+               for ( let i = 0, il = lights.length; i < il; i ++ ) {
 
-               }
+                       const light = lights[ i ];
+                       const shadow = light.shadow;
 
-               return internalFormat;
+                       if ( shadow === undefined ) {
 
-       }
+                               console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' );
+                               continue;
 
-       // Fallback filters for non-power-of-2 textures
+                       }
 
-       function filterFallback( f ) {
+                       if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue;
 
-               if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) {
+                       _shadowMapSize.copy( shadow.mapSize );
 
-                       return 9728;
+                       const shadowFrameExtents = shadow.getFrameExtents();
 
-               }
+                       _shadowMapSize.multiply( shadowFrameExtents );
 
-               return 9729;
+                       _viewportSize.copy( shadow.mapSize );
 
-       }
+                       if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) {
 
-       //
+                               if ( _shadowMapSize.x > _maxTextureSize ) {
 
-       function onTextureDispose( event ) {
+                                       _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x );
+                                       _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x;
+                                       shadow.mapSize.x = _viewportSize.x;
 
-               const texture = event.target;
+                               }
 
-               texture.removeEventListener( 'dispose', onTextureDispose );
+                               if ( _shadowMapSize.y > _maxTextureSize ) {
 
-               deallocateTexture( texture );
+                                       _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y );
+                                       _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y;
+                                       shadow.mapSize.y = _viewportSize.y;
 
-               if ( texture.isVideoTexture ) {
+                               }
 
-                       _videoTextures.delete( texture );
+                       }
 
-               }
+                       if ( shadow.map === null && ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
 
-               info.memory.textures --;
+                               const pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat };
 
-       }
+                               shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+                               shadow.map.texture.name = light.name + '.shadowMap';
 
-       function onRenderTargetDispose( event ) {
+                               shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
 
-               const renderTarget = event.target;
+                               shadow.camera.updateProjectionMatrix();
 
-               renderTarget.removeEventListener( 'dispose', onRenderTargetDispose );
+                       }
 
-               deallocateRenderTarget( renderTarget );
+                       if ( shadow.map === null ) {
 
-               info.memory.textures --;
+                               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();
 
-       function deallocateTexture( texture ) {
+                       }
 
-               const textureProperties = properties.get( texture );
+                       _renderer.setRenderTarget( shadow.map );
+                       _renderer.clear();
 
-               if ( textureProperties.__webglInit === undefined ) return;
+                       const viewportCount = shadow.getViewportCount();
 
-               _gl.deleteTexture( textureProperties.__webglTexture );
+                       for ( let vp = 0; vp < viewportCount; vp ++ ) {
 
-               properties.remove( texture );
+                               const viewport = shadow.getViewport( vp );
 
-       }
+                               _viewport.set(
+                                       _viewportSize.x * viewport.x,
+                                       _viewportSize.y * viewport.y,
+                                       _viewportSize.x * viewport.z,
+                                       _viewportSize.y * viewport.w
+                               );
 
-       function deallocateRenderTarget( renderTarget ) {
+                               _state.viewport( _viewport );
 
-               const renderTargetProperties = properties.get( renderTarget );
-               const textureProperties = properties.get( renderTarget.texture );
+                               shadow.updateMatrices( light, vp );
 
-               if ( ! renderTarget ) return;
+                               _frustum = shadow.getFrustum();
 
-               if ( textureProperties.__webglTexture !== undefined ) {
+                               renderObject( scene, camera, shadow.camera, light, this.type );
 
-                       _gl.deleteTexture( textureProperties.__webglTexture );
+                       }
 
-               }
+                       // do blur pass for VSM
 
-               if ( renderTarget.depthTexture ) {
+                       if ( ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
 
-                       renderTarget.depthTexture.dispose();
+                               VSMPass( shadow, camera );
+
+                       }
+
+                       shadow.needsUpdate = false;
 
                }
 
-               if ( renderTarget.isWebGLCubeRenderTarget ) {
+               scope.needsUpdate = false;
 
-                       for ( let i = 0; i < 6; i ++ ) {
+               _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel );
 
-                               _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] );
-                               if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] );
+       };
 
-                       }
+       function VSMPass( shadow, camera ) {
 
-               } else {
+               const geometry = _objects.update( fullScreenMesh );
 
-                       _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 );
+               if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) {
 
-               }
+                       shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples;
+                       shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples;
 
-               properties.remove( renderTarget.texture );
-               properties.remove( renderTarget );
+                       shadowMaterialVertical.needsUpdate = true;
+                       shadowMaterialHorizontal.needsUpdate = true;
 
-       }
+               }
 
-       //
+               // vertical pass
 
-       let textureUnits = 0;
+               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 );
 
-       function resetTextureUnits() {
+               // horizontal pass
 
-               textureUnits = 0;
+               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 allocateTextureUnit() {
+       function getDepthMaterial( object, geometry, material, light, shadowCameraNear, shadowCameraFar, type ) {
 
-               const textureUnit = textureUnits;
+               let result = null;
 
-               if ( textureUnit >= maxTextures ) {
+               const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial;
 
-                       console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + maxTextures );
+               if ( customMaterial !== undefined ) {
 
-               }
+                       result = customMaterial;
 
-               textureUnits += 1;
+               } else {
 
-               return textureUnit;
+                       result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial;
 
-       }
-
-       //
-
-       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 ) {
+               if ( ( _renderer.localClippingEnabled && material.clipShadows === true && material.clippingPlanes.length !== 0 ) ||
+                       ( material.displacementMap && material.displacementScale !== 0 ) ||
+                       ( material.alphaMap && material.alphaTest > 0 ) ) {
 
-                               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined' );
+                       // in this case we need a unique material instance reflecting the
+                       // appropriate state
 
-                       } else if ( image.complete === false ) {
+                       const keyA = result.uuid, keyB = material.uuid;
 
-                               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' );
+                       let materialsForVariant = _materialCache[ keyA ];
 
-                       } else {
+                       if ( materialsForVariant === undefined ) {
 
-                               uploadTexture( textureProperties, texture, slot );
-                               return;
+                               materialsForVariant = {};
+                               _materialCache[ keyA ] = materialsForVariant;
 
                        }
 
-               }
-
-               state.activeTexture( 33984 + slot );
-               state.bindTexture( 3553, textureProperties.__webglTexture );
-
-       }
+                       let cachedMaterial = materialsForVariant[ keyB ];
 
-       function setTexture2DArray( texture, slot ) {
+                       if ( cachedMaterial === undefined ) {
 
-               const textureProperties = properties.get( texture );
+                               cachedMaterial = result.clone();
+                               materialsForVariant[ keyB ] = cachedMaterial;
 
-               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+                       }
 
-                       uploadTexture( textureProperties, texture, slot );
-                       return;
+                       result = cachedMaterial;
 
                }
 
-               state.activeTexture( 33984 + slot );
-               state.bindTexture( 35866, textureProperties.__webglTexture );
-
-       }
+               result.visible = material.visible;
+               result.wireframe = material.wireframe;
 
-       function setTexture3D( texture, slot ) {
+               if ( type === VSMShadowMap ) {
 
-               const textureProperties = properties.get( texture );
+                       result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side;
 
-               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+               } else {
 
-                       uploadTexture( textureProperties, texture, slot );
-                       return;
+                       result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ];
 
                }
 
-               state.activeTexture( 33984 + slot );
-               state.bindTexture( 32879, textureProperties.__webglTexture );
+               result.alphaMap = material.alphaMap;
+               result.alphaTest = material.alphaTest;
 
-       }
+               result.clipShadows = material.clipShadows;
+               result.clippingPlanes = material.clippingPlanes;
+               result.clipIntersection = material.clipIntersection;
 
-       function setTextureCube( texture, slot ) {
+               result.displacementMap = material.displacementMap;
+               result.displacementScale = material.displacementScale;
+               result.displacementBias = material.displacementBias;
 
-               const textureProperties = properties.get( texture );
+               result.wireframeLinewidth = material.wireframeLinewidth;
+               result.linewidth = material.linewidth;
 
-               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+               if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) {
 
-                       uploadCubeTexture( textureProperties, texture, slot );
-                       return;
+                       result.referencePosition.setFromMatrixPosition( light.matrixWorld );
+                       result.nearDistance = shadowCameraNear;
+                       result.farDistance = shadowCameraFar;
 
                }
 
-               state.activeTexture( 33984 + slot );
-               state.bindTexture( 34067, textureProperties.__webglTexture );
+               return result;
 
        }
 
-       const wrappingToGL = {
-               [ RepeatWrapping ]: 10497,
-               [ ClampToEdgeWrapping ]: 33071,
-               [ MirroredRepeatWrapping ]: 33648
-       };
-
-       const filterToGL = {
-               [ NearestFilter ]: 9728,
-               [ NearestMipmapNearestFilter ]: 9984,
-               [ NearestMipmapLinearFilter ]: 9986,
+       function renderObject( object, camera, shadowCamera, light, type ) {
 
-               [ LinearFilter ]: 9729,
-               [ LinearMipmapNearestFilter ]: 9985,
-               [ LinearMipmapLinearFilter ]: 9987
-       };
+               if ( object.visible === false ) return;
 
-       function setTextureParameters( textureType, texture, supportsMips ) {
+               const visible = object.layers.test( camera.layers );
 
-               if ( supportsMips ) {
+               if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) {
 
-                       _gl.texParameteri( textureType, 10242, wrappingToGL[ texture.wrapS ] );
-                       _gl.texParameteri( textureType, 10243, wrappingToGL[ texture.wrapT ] );
+                       if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) {
 
-                       if ( textureType === 32879 || textureType === 35866 ) {
+                               object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
 
-                               _gl.texParameteri( textureType, 32882, wrappingToGL[ texture.wrapR ] );
+                               const geometry = _objects.update( object );
+                               const material = object.material;
 
-                       }
+                               if ( Array.isArray( material ) ) {
 
-                       _gl.texParameteri( textureType, 10240, filterToGL[ texture.magFilter ] );
-                       _gl.texParameteri( textureType, 10241, filterToGL[ texture.minFilter ] );
+                                       const groups = geometry.groups;
 
-               } else {
+                                       for ( let k = 0, kl = groups.length; k < kl; k ++ ) {
 
-                       _gl.texParameteri( textureType, 10242, 33071 );
-                       _gl.texParameteri( textureType, 10243, 33071 );
+                                               const group = groups[ k ];
+                                               const groupMaterial = material[ group.materialIndex ];
 
-                       if ( textureType === 32879 || textureType === 35866 ) {
+                                               if ( groupMaterial && groupMaterial.visible ) {
 
-                               _gl.texParameteri( textureType, 32882, 33071 );
+                                                       const depthMaterial = getDepthMaterial( object, geometry, groupMaterial, light, shadowCamera.near, shadowCamera.far, type );
 
-                       }
+                                                       _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group );
 
-                       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.' );
+                                       }
 
-                       }
+                               } else if ( material.visible ) {
 
-                       _gl.texParameteri( textureType, 10240, filterFallback( texture.magFilter ) );
-                       _gl.texParameteri( textureType, 10241, filterFallback( texture.minFilter ) );
+                                       const depthMaterial = getDepthMaterial( object, geometry, material, light, shadowCamera.near, shadowCamera.far, type );
 
-                       if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) {
+                                       _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null );
 
-                               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 ) {
+               const children = object.children;
 
-                               _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) );
-                               properties.get( texture ).__currentAnisotropy = texture.anisotropy;
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
 
-                       }
+                       renderObject( children[ i ], camera, shadowCamera, light, type );
 
                }
 
        }
 
-       function initTexture( textureProperties, texture ) {
+}
 
-               if ( textureProperties.__webglInit === undefined ) {
+function WebGLState( gl, extensions, capabilities ) {
 
-                       textureProperties.__webglInit = true;
+       const isWebGL2 = capabilities.isWebGL2;
 
-                       texture.addEventListener( 'dispose', onTextureDispose );
+       function ColorBuffer() {
 
-                       textureProperties.__webglTexture = _gl.createTexture();
+               let locked = false;
 
-                       info.memory.textures ++;
+               const color = new Vector4();
+               let currentColorMask = null;
+               const currentColorClear = new Vector4( 0, 0, 0, 0 );
 
-               }
+               return {
 
-       }
+                       setMask: function ( colorMask ) {
 
-       function uploadTexture( textureProperties, texture, slot ) {
+                               if ( currentColorMask !== colorMask && ! locked ) {
 
-               let textureType = 3553;
+                                       gl.colorMask( colorMask, colorMask, colorMask, colorMask );
+                                       currentColorMask = colorMask;
 
-               if ( texture.isDataTexture2DArray ) textureType = 35866;
-               if ( texture.isDataTexture3D ) textureType = 32879;
+                               }
 
-               initTexture( textureProperties, texture );
+                       },
 
-               state.activeTexture( 33984 + slot );
-               state.bindTexture( textureType, textureProperties.__webglTexture );
+                       setLocked: function ( lock ) {
 
-               _gl.pixelStorei( 37440, texture.flipY );
-               _gl.pixelStorei( 37441, texture.premultiplyAlpha );
-               _gl.pixelStorei( 3317, texture.unpackAlignment );
+                               locked = lock;
 
-               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 );
+                       setClear: function ( r, g, b, a, premultipliedAlpha ) {
 
-               let glType = utils.convert( texture.type ),
-                       glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType );
+                               if ( premultipliedAlpha === true ) {
 
-               setTextureParameters( textureType, texture, supportsMips );
+                                       r *= a; g *= a; b *= a;
 
-               let mipmap;
-               const mipmaps = texture.mipmaps;
+                               }
 
-               if ( texture.isDepthTexture ) {
+                               color.set( r, g, b, a );
 
-                       // populate depth texture with dummy data
+                               if ( currentColorClear.equals( color ) === false ) {
 
-                       glInternalFormat = 6402;
+                                       gl.clearColor( r, g, b, a );
+                                       currentColorClear.copy( color );
 
-                       if ( isWebGL2 ) {
+                               }
 
-                               if ( texture.type === FloatType ) {
+                       },
 
-                                       glInternalFormat = 36012;
+                       reset: function () {
 
-                               } else if ( texture.type === UnsignedIntType ) {
+                               locked = false;
 
-                                       glInternalFormat = 33190;
+                               currentColorMask = null;
+                               currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state
 
-                               } else if ( texture.type === UnsignedInt248Type ) {
+                       }
 
-                                       glInternalFormat = 35056;
+               };
 
-                               } else {
+       }
 
-                                       glInternalFormat = 33189; // WebGL2 requires sized internalformat for glTexImage2D
+       function DepthBuffer() {
 
-                               }
+               let locked = false;
 
-                       } else {
+               let currentDepthMask = null;
+               let currentDepthFunc = null;
+               let currentDepthClear = null;
 
-                               if ( texture.type === FloatType ) {
+               return {
 
-                                       console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' );
+                       setTest: function ( depthTest ) {
 
-                               }
+                               if ( depthTest ) {
 
-                       }
+                                       enable( 2929 );
 
-                       // validation checks for WebGL 1
+                               } else {
 
-                       if ( texture.format === DepthFormat && glInternalFormat === 6402 ) {
+                                       disable( 2929 );
 
-                               // 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 );
+                       setMask: function ( depthMask ) {
 
-                               }
+                               if ( currentDepthMask !== depthMask && ! locked ) {
 
-                       }
+                                       gl.depthMask( depthMask );
+                                       currentDepthMask = depthMask;
 
-                       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 ) {
+                       setFunc: function ( depthFunc ) {
 
-                                       console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' );
+                               if ( currentDepthFunc !== depthFunc ) {
 
-                                       texture.type = UnsignedInt248Type;
-                                       glType = utils.convert( texture.type );
+                                       if ( depthFunc ) {
 
-                               }
+                                               switch ( depthFunc ) {
 
-                       }
+                                                       case NeverDepth:
 
-                       //
+                                                               gl.depthFunc( 512 );
+                                                               break;
 
-                       state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null );
+                                                       case AlwaysDepth:
 
-               } else if ( texture.isDataTexture ) {
+                                                               gl.depthFunc( 519 );
+                                                               break;
 
-                       // 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
+                                                       case LessDepth:
 
-                       if ( mipmaps.length > 0 && supportsMips ) {
+                                                               gl.depthFunc( 513 );
+                                                               break;
 
-                               for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+                                                       case LessEqualDepth:
 
-                                       mipmap = mipmaps[ i ];
-                                       state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+                                                               gl.depthFunc( 515 );
+                                                               break;
 
-                               }
+                                                       case EqualDepth:
 
-                               texture.generateMipmaps = false;
-                               textureProperties.__maxMipLevel = mipmaps.length - 1;
+                                                               gl.depthFunc( 514 );
+                                                               break;
 
-                       } else {
+                                                       case GreaterEqualDepth:
 
-                               state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data );
-                               textureProperties.__maxMipLevel = 0;
+                                                               gl.depthFunc( 518 );
+                                                               break;
 
-                       }
+                                                       case GreaterDepth:
 
-               } else if ( texture.isCompressedTexture ) {
+                                                               gl.depthFunc( 516 );
+                                                               break;
 
-                       for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+                                                       case NotEqualDepth:
 
-                               mipmap = mipmaps[ i ];
+                                                               gl.depthFunc( 517 );
+                                                               break;
 
-                               if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {
+                                                       default:
 
-                                       if ( glFormat !== null ) {
+                                                               gl.depthFunc( 515 );
 
-                                               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()' );
+                                               gl.depthFunc( 515 );
 
                                        }
 
-                               } else {
-
-                                       state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+                                       currentDepthFunc = depthFunc;
 
                                }
 
-                       }
+                       },
 
-                       textureProperties.__maxMipLevel = mipmaps.length - 1;
+                       setLocked: function ( lock ) {
 
-               } else if ( texture.isDataTexture2DArray ) {
+                               locked = lock;
 
-                       state.texImage3D( 35866, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
-                       textureProperties.__maxMipLevel = 0;
+                       },
 
-               } else if ( texture.isDataTexture3D ) {
+                       setClear: function ( depth ) {
 
-                       state.texImage3D( 32879, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
-                       textureProperties.__maxMipLevel = 0;
+                               if ( currentDepthClear !== depth ) {
 
-               } else {
+                                       gl.clearDepth( depth );
+                                       currentDepthClear = depth;
 
-                       // 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 ) {
+                       reset: function () {
 
-                               for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+                               locked = false;
 
-                                       mipmap = mipmaps[ i ];
-                                       state.texImage2D( 3553, i, glInternalFormat, glFormat, glType, mipmap );
+                               currentDepthMask = null;
+                               currentDepthFunc = null;
+                               currentDepthClear = null;
 
-                               }
+                       }
 
-                               texture.generateMipmaps = false;
-                               textureProperties.__maxMipLevel = mipmaps.length - 1;
+               };
 
-                       } else {
+       }
 
-                               state.texImage2D( 3553, 0, glInternalFormat, glFormat, glType, image );
-                               textureProperties.__maxMipLevel = 0;
+       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;
 
-               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+               return {
 
-                       generateMipmap( textureType, texture, image.width, image.height );
+                       setTest: function ( stencilTest ) {
 
-               }
+                               if ( ! locked ) {
 
-               textureProperties.__version = texture.version;
+                                       if ( stencilTest ) {
 
-               if ( texture.onUpdate ) texture.onUpdate( texture );
+                                               enable( 2960 );
 
-       }
+                                       } else {
 
-       function uploadCubeTexture( textureProperties, texture, slot ) {
+                                               disable( 2960 );
 
-               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 );
+                       setMask: function ( stencilMask ) {
 
-               const isCompressed = ( texture && ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ) );
-               const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture );
+                               if ( currentStencilMask !== stencilMask && ! locked ) {
 
-               const cubeImage = [];
+                                       gl.stencilMask( stencilMask );
+                                       currentStencilMask = stencilMask;
 
-               for ( let i = 0; i < 6; i ++ ) {
+                               }
 
-                       if ( ! isCompressed && ! isDataTexture ) {
+                       },
 
-                               cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, maxCubemapSize );
+                       setFunc: function ( stencilFunc, stencilRef, stencilMask ) {
 
-                       } else {
+                               if ( currentStencilFunc !== stencilFunc ||
+                                    currentStencilRef !== stencilRef ||
+                                    currentStencilFuncMask !== stencilMask ) {
 
-                               cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ];
+                                       gl.stencilFunc( stencilFunc, stencilRef, stencilMask );
 
-                       }
+                                       currentStencilFunc = stencilFunc;
+                                       currentStencilRef = stencilRef;
+                                       currentStencilFuncMask = stencilMask;
 
-               }
+                               }
 
-               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 );
+                       setOp: function ( stencilFail, stencilZFail, stencilZPass ) {
 
-               let mipmaps;
+                               if ( currentStencilFail !== stencilFail ||
+                                    currentStencilZFail !== stencilZFail ||
+                                    currentStencilZPass !== stencilZPass ) {
 
-               if ( isCompressed ) {
+                                       gl.stencilOp( stencilFail, stencilZFail, stencilZPass );
 
-                       for ( let i = 0; i < 6; i ++ ) {
+                                       currentStencilFail = stencilFail;
+                                       currentStencilZFail = stencilZFail;
+                                       currentStencilZPass = stencilZPass;
 
-                               mipmaps = cubeImage[ i ].mipmaps;
+                               }
 
-                               for ( let j = 0; j < mipmaps.length; j ++ ) {
+                       },
 
-                                       const mipmap = mipmaps[ j ];
+                       setLocked: function ( lock ) {
 
-                                       if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {
+                               locked = lock;
 
-                                               if ( glFormat !== null ) {
+                       },
 
-                                                       state.compressedTexImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+                       setClear: function ( stencil ) {
 
-                                               } else {
+                               if ( currentStencilClear !== stencil ) {
 
-                                                       console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' );
+                                       gl.clearStencil( stencil );
+                                       currentStencilClear = stencil;
 
-                                               }
+                               }
 
-                                       } else {
+                       },
 
-                                               state.texImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+                       reset: function () {
 
-                                       }
+                               locked = false;
 
-                               }
+                               currentStencilMask = null;
+                               currentStencilFunc = null;
+                               currentStencilRef = null;
+                               currentStencilFuncMask = null;
+                               currentStencilFail = null;
+                               currentStencilZFail = null;
+                               currentStencilZPass = null;
+                               currentStencilClear = null;
 
                        }
 
-                       textureProperties.__maxMipLevel = mipmaps.length - 1;
+               };
 
-               } else {
+       }
 
-                       mipmaps = texture.mipmaps;
+       //
 
-                       for ( let i = 0; i < 6; i ++ ) {
+       const colorBuffer = new ColorBuffer();
+       const depthBuffer = new DepthBuffer();
+       const stencilBuffer = new StencilBuffer();
 
-                               if ( isDataTexture ) {
+       let enabledCapabilities = {};
 
-                                       state.texImage2D( 34069 + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data );
+       let xrFramebuffer = null;
+       let currentBoundFramebuffers = {};
 
-                                       for ( let j = 0; j < mipmaps.length; j ++ ) {
+       let currentProgram = null;
 
-                                               const mipmap = mipmaps[ j ];
-                                               const mipmapImage = mipmap.image[ i ].image;
+       let currentBlendingEnabled = false;
+       let currentBlending = null;
+       let currentBlendEquation = null;
+       let currentBlendSrc = null;
+       let currentBlendDst = null;
+       let currentBlendEquationAlpha = null;
+       let currentBlendSrcAlpha = null;
+       let currentBlendDstAlpha = null;
+       let currentPremultipledAlpha = false;
 
-                                               state.texImage2D( 34069 + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data );
+       let currentFlipSided = null;
+       let currentCullFace = null;
 
-                                       }
+       let currentLineWidth = null;
 
-                               } else {
+       let currentPolygonOffsetFactor = null;
+       let currentPolygonOffsetUnits = null;
 
-                                       state.texImage2D( 34069 + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] );
-
-                                       for ( let j = 0; j < mipmaps.length; j ++ ) {
+       const maxTextures = gl.getParameter( 35661 );
 
-                                               const mipmap = mipmaps[ j ];
+       let lineWidthAvailable = false;
+       let version = 0;
+       const glVersion = gl.getParameter( 7938 );
 
-                                               state.texImage2D( 34069 + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] );
+       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 );
 
-                       textureProperties.__maxMipLevel = mipmaps.length;
+       }
 
-               }
+       let currentTextureSlot = null;
+       let currentBoundTextures = {};
 
-               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+       const scissorParam = gl.getParameter( 3088 );
+       const viewportParam = gl.getParameter( 2978 );
 
-                       // We assume images for cube map have the same size.
-                       generateMipmap( 34067, texture, image.width, image.height );
+       const currentScissor = new Vector4().fromArray( scissorParam );
+       const currentViewport = new Vector4().fromArray( viewportParam );
 
-               }
+       function createTexture( type, target, count ) {
 
-               textureProperties.__version = texture.version;
+               const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4.
+               const texture = gl.createTexture();
 
-               if ( texture.onUpdate ) texture.onUpdate( texture );
+               gl.bindTexture( type, texture );
+               gl.texParameteri( type, 10241, 9728 );
+               gl.texParameteri( type, 10240, 9728 );
 
-       }
+               for ( let i = 0; i < count; i ++ ) {
 
-       // Render targets
+                       gl.texImage2D( target + i, 0, 6408, 1, 1, 0, 6408, 5121, data );
 
-       // 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 );
+               return texture;
 
        }
 
-       // Setup storage for internal depth/stencil buffers and bind to correct framebuffer
-       function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) {
+       const emptyTextures = {};
+       emptyTextures[ 3553 ] = createTexture( 3553, 3553, 1 );
+       emptyTextures[ 34067 ] = createTexture( 34067, 34069, 6 );
 
-               _gl.bindRenderbuffer( 36161, renderbuffer );
+       // init
 
-               if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
+       colorBuffer.setClear( 0, 0, 0, 1 );
+       depthBuffer.setClear( 1 );
+       stencilBuffer.setClear( 0 );
 
-                       let glInternalFormat = 33189;
+       enable( 2929 );
+       depthBuffer.setFunc( LessEqualDepth );
 
-                       if ( isMultisample ) {
+       setFlipSided( false );
+       setCullFace( CullFaceBack );
+       enable( 2884 );
 
-                               const depthTexture = renderTarget.depthTexture;
+       setBlending( NoBlending );
 
-                               if ( depthTexture && depthTexture.isDepthTexture ) {
+       //
 
-                                       if ( depthTexture.type === FloatType ) {
+       function enable( id ) {
 
-                                               glInternalFormat = 36012;
+               if ( enabledCapabilities[ id ] !== true ) {
 
-                                       } else if ( depthTexture.type === UnsignedIntType ) {
+                       gl.enable( id );
+                       enabledCapabilities[ id ] = true;
 
-                                               glInternalFormat = 33190;
+               }
 
-                                       }
+       }
 
-                               }
+       function disable( id ) {
 
-                               const samples = getRenderTargetSamples( renderTarget );
+               if ( enabledCapabilities[ id ] !== false ) {
 
-                               _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+                       gl.disable( id );
+                       enabledCapabilities[ id ] = false;
 
-                       } else {
+               }
 
-                               _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
+       }
 
-                       }
+       function bindXRFramebuffer( framebuffer ) {
 
-                       _gl.framebufferRenderbuffer( 36160, 36096, 36161, renderbuffer );
+               if ( framebuffer !== xrFramebuffer ) {
 
-               } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+                       gl.bindFramebuffer( 36160, framebuffer );
 
-                       if ( isMultisample ) {
+                       xrFramebuffer = framebuffer;
 
-                               const samples = getRenderTargetSamples( renderTarget );
+               }
 
-                               _gl.renderbufferStorageMultisample( 36161, samples, 35056, renderTarget.width, renderTarget.height );
+       }
 
-                       } else {
+       function bindFramebuffer( target, framebuffer ) {
 
-                               _gl.renderbufferStorage( 36161, 34041, renderTarget.width, renderTarget.height );
+               if ( framebuffer === null && xrFramebuffer !== null ) framebuffer = xrFramebuffer; // use active XR framebuffer if available
 
-                       }
+               if ( currentBoundFramebuffers[ target ] !== framebuffer ) {
 
+                       gl.bindFramebuffer( target, framebuffer );
 
-                       _gl.framebufferRenderbuffer( 36160, 33306, 36161, renderbuffer );
+                       currentBoundFramebuffers[ target ] = framebuffer;
 
-               } else {
+                       if ( isWebGL2 ) {
 
-                       const glFormat = utils.convert( renderTarget.texture.format );
-                       const glType = utils.convert( renderTarget.texture.type );
-                       const glInternalFormat = getInternalFormat( renderTarget.texture.internalFormat, glFormat, glType );
+                               // 36009 is equivalent to 36160
 
-                       if ( isMultisample ) {
+                               if ( target === 36009 ) {
 
-                               const samples = getRenderTargetSamples( renderTarget );
+                                       currentBoundFramebuffers[ 36160 ] = framebuffer;
 
-                               _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+                               }
 
-                       } else {
+                               if ( target === 36160 ) {
 
-                               _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
+                                       currentBoundFramebuffers[ 36009 ] = framebuffer;
+
+                               }
 
                        }
 
+                       return true;
+
                }
 
-               _gl.bindRenderbuffer( 36161, null );
+               return false;
 
        }
 
-       // Setup resources for a Depth Texture for a FBO (needs an extension)
-       function setupDepthTexture( framebuffer, renderTarget ) {
+       function useProgram( program ) {
 
-               const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget );
-               if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' );
+               if ( currentProgram !== program ) {
 
-               _gl.bindFramebuffer( 36160, framebuffer );
+                       gl.useProgram( program );
 
-               if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) {
+                       currentProgram = program;
 
-                       throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' );
+                       return true;
 
                }
 
-               // 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;
-
-               }
+               return false;
 
-               setTexture2D( renderTarget.depthTexture, 0 );
+       }
 
-               const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture;
+       const equationToGL = {
+               [ AddEquation ]: 32774,
+               [ SubtractEquation ]: 32778,
+               [ ReverseSubtractEquation ]: 32779
+       };
 
-               if ( renderTarget.depthTexture.format === DepthFormat ) {
+       if ( isWebGL2 ) {
 
-                       _gl.framebufferTexture2D( 36160, 36096, 3553, webglDepthTexture, 0 );
+               equationToGL[ MinEquation ] = 32775;
+               equationToGL[ MaxEquation ] = 32776;
 
-               } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) {
+       } else {
 
-                       _gl.framebufferTexture2D( 36160, 33306, 3553, webglDepthTexture, 0 );
+               const extension = extensions.get( 'EXT_blend_minmax' );
 
-               } else {
+               if ( extension !== null ) {
 
-                       throw new Error( 'Unknown depthTexture format' );
+                       equationToGL[ MinEquation ] = extension.MIN_EXT;
+                       equationToGL[ MaxEquation ] = extension.MAX_EXT;
 
                }
 
        }
 
-       // Setup GL resources for a non-texture depth buffer
-       function setupDepthRenderbuffer( renderTarget ) {
-
-               const renderTargetProperties = properties.get( renderTarget );
+       const factorToGL = {
+               [ ZeroFactor ]: 0,
+               [ OneFactor ]: 1,
+               [ SrcColorFactor ]: 768,
+               [ SrcAlphaFactor ]: 770,
+               [ SrcAlphaSaturateFactor ]: 776,
+               [ DstColorFactor ]: 774,
+               [ DstAlphaFactor ]: 772,
+               [ OneMinusSrcColorFactor ]: 769,
+               [ OneMinusSrcAlphaFactor ]: 771,
+               [ OneMinusDstColorFactor ]: 775,
+               [ OneMinusDstAlphaFactor ]: 773
+       };
 
-               const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
+       function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {
 
-               if ( renderTarget.depthTexture ) {
+               if ( blending === NoBlending ) {
 
-                       if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' );
+                       if ( currentBlendingEnabled === true ) {
 
-                       setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget );
+                               disable( 3042 );
+                               currentBlendingEnabled = false;
 
-               } else {
+                       }
 
-                       if ( isCube ) {
+                       return;
 
-                               renderTargetProperties.__webglDepthbuffer = [];
+               }
 
-                               for ( let i = 0; i < 6; i ++ ) {
+               if ( currentBlendingEnabled === false ) {
 
-                                       _gl.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer[ i ] );
-                                       renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer();
-                                       setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false );
+                       enable( 3042 );
+                       currentBlendingEnabled = true;
 
-                               }
+               }
 
-                       } else {
+               if ( blending !== CustomBlending ) {
 
-                               _gl.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer );
-                               renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer();
-                               setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false );
+                       if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) {
 
-                       }
+                               if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) {
 
-               }
+                                       gl.blendEquation( 32774 );
 
-               _gl.bindFramebuffer( 36160, null );
+                                       currentBlendEquation = AddEquation;
+                                       currentBlendEquationAlpha = AddEquation;
 
-       }
+                               }
 
-       // Set up GL resources for the render target
-       function setupRenderTarget( renderTarget ) {
+                               if ( premultipliedAlpha ) {
 
-               const renderTargetProperties = properties.get( renderTarget );
-               const textureProperties = properties.get( renderTarget.texture );
+                                       switch ( blending ) {
 
-               renderTarget.addEventListener( 'dispose', onRenderTargetDispose );
+                                               case NormalBlending:
+                                                       gl.blendFuncSeparate( 1, 771, 1, 771 );
+                                                       break;
 
-               textureProperties.__webglTexture = _gl.createTexture();
+                                               case AdditiveBlending:
+                                                       gl.blendFunc( 1, 1 );
+                                                       break;
 
-               info.memory.textures ++;
+                                               case SubtractiveBlending:
+                                                       gl.blendFuncSeparate( 0, 0, 769, 771 );
+                                                       break;
 
-               const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
-               const isMultisample = ( renderTarget.isWebGLMultisampleRenderTarget === true );
-               const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2;
+                                               case MultiplyBlending:
+                                                       gl.blendFuncSeparate( 0, 768, 0, 770 );
+                                                       break;
 
-               // Handles WebGL2 RGBFormat fallback - #18858
+                                               default:
+                                                       console.error( 'THREE.WebGLState: Invalid blending: ', blending );
+                                                       break;
 
-               if ( isWebGL2 && renderTarget.texture.format === RGBFormat && ( renderTarget.texture.type === FloatType || renderTarget.texture.type === HalfFloatType ) ) {
+                                       }
 
-                       renderTarget.texture.format = RGBAFormat;
+                               } else {
 
-                       console.warn( 'THREE.WebGLRenderer: Rendering to textures with RGB format is not supported. Using RGBA format instead.' );
+                                       switch ( blending ) {
 
-               }
+                                               case NormalBlending:
+                                                       gl.blendFuncSeparate( 770, 771, 1, 771 );
+                                                       break;
 
-               // Setup framebuffer
+                                               case AdditiveBlending:
+                                                       gl.blendFunc( 770, 1 );
+                                                       break;
 
-               if ( isCube ) {
+                                               case SubtractiveBlending:
+                                                       gl.blendFunc( 0, 769 );
+                                                       break;
 
-                       renderTargetProperties.__webglFramebuffer = [];
+                                               case MultiplyBlending:
+                                                       gl.blendFunc( 0, 768 );
+                                                       break;
 
-                       for ( let i = 0; i < 6; i ++ ) {
+                                               default:
+                                                       console.error( 'THREE.WebGLState: Invalid blending: ', blending );
+                                                       break;
 
-                               renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer();
+                                       }
 
-                       }
+                               }
 
-               } else {
+                               currentBlendSrc = null;
+                               currentBlendDst = null;
+                               currentBlendSrcAlpha = null;
+                               currentBlendDstAlpha = null;
 
-                       renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();
+                               currentBlending = blending;
+                               currentPremultipledAlpha = premultipliedAlpha;
 
-                       if ( isMultisample ) {
+                       }
 
-                               if ( isWebGL2 ) {
+                       return;
 
-                                       renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer();
-                                       renderTargetProperties.__webglColorRenderbuffer = _gl.createRenderbuffer();
+               }
 
-                                       _gl.bindRenderbuffer( 36161, renderTargetProperties.__webglColorRenderbuffer );
+               // custom blending
 
-                                       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 );
+               blendEquationAlpha = blendEquationAlpha || blendEquation;
+               blendSrcAlpha = blendSrcAlpha || blendSrc;
+               blendDstAlpha = blendDstAlpha || blendDst;
 
-                                       _gl.bindFramebuffer( 36160, renderTargetProperties.__webglMultisampledFramebuffer );
-                                       _gl.framebufferRenderbuffer( 36160, 36064, 36161, renderTargetProperties.__webglColorRenderbuffer );
-                                       _gl.bindRenderbuffer( 36161, null );
+               if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) {
 
-                                       if ( renderTarget.depthBuffer ) {
+                       gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] );
 
-                                               renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer();
-                                               setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true );
+                       currentBlendEquation = blendEquation;
+                       currentBlendEquationAlpha = blendEquationAlpha;
 
-                                       }
+               }
 
-                                       _gl.bindFramebuffer( 36160, null );
+               if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) {
 
+                       gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] );
 
-                               } else {
+                       currentBlendSrc = blendSrc;
+                       currentBlendDst = blendDst;
+                       currentBlendSrcAlpha = blendSrcAlpha;
+                       currentBlendDstAlpha = blendDstAlpha;
 
-                                       console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+               }
 
-                               }
+               currentBlending = blending;
+               currentPremultipledAlpha = null;
 
-                       }
+       }
 
-               }
+       function setMaterial( material, frontFaceCW ) {
 
-               // Setup color buffer
+               material.side === DoubleSide
+                       ? disable( 2884 )
+                       : enable( 2884 );
 
-               if ( isCube ) {
+               let flipSided = ( material.side === BackSide );
+               if ( frontFaceCW ) flipSided = ! flipSided;
 
-                       state.bindTexture( 34067, textureProperties.__webglTexture );
-                       setTextureParameters( 34067, renderTarget.texture, supportsMips );
+               setFlipSided( flipSided );
 
-                       for ( let i = 0; i < 6; i ++ ) {
+               ( 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 );
 
-                               setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, 36064, 34069 + i );
+               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 ) {
 
-                       if ( textureNeedsGenerateMipmaps( renderTarget.texture, supportsMips ) ) {
+                       stencilBuffer.setMask( material.stencilWriteMask );
+                       stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask );
+                       stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass );
 
-                               generateMipmap( 34067, renderTarget.texture, renderTarget.width, renderTarget.height );
+               }
 
-                       }
+               setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
 
-                       state.bindTexture( 34067, null );
+               material.alphaToCoverage === true
+                       ? enable( 32926 )
+                       : disable( 32926 );
 
-               } else {
+       }
 
-                       state.bindTexture( 3553, textureProperties.__webglTexture );
-                       setTextureParameters( 3553, renderTarget.texture, supportsMips );
-                       setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, 36064, 3553 );
+       //
 
-                       if ( textureNeedsGenerateMipmaps( renderTarget.texture, supportsMips ) ) {
+       function setFlipSided( flipSided ) {
 
-                               generateMipmap( 3553, renderTarget.texture, renderTarget.width, renderTarget.height );
+               if ( currentFlipSided !== flipSided ) {
 
-                       }
+                       if ( flipSided ) {
 
-                       state.bindTexture( 3553, null );
+                               gl.frontFace( 2304 );
 
-               }
+                       } else {
 
-               // Setup depth and stencil buffers
+                               gl.frontFace( 2305 );
 
-               if ( renderTarget.depthBuffer ) {
+                       }
 
-                       setupDepthRenderbuffer( renderTarget );
+                       currentFlipSided = flipSided;
 
                }
 
        }
 
-       function updateRenderTargetMipmap( renderTarget ) {
+       function setCullFace( cullFace ) {
 
-               const texture = renderTarget.texture;
-               const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2;
+               if ( cullFace !== CullFaceNone ) {
 
-               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+                       enable( 2884 );
 
-                       const target = renderTarget.isWebGLCubeRenderTarget ? 34067 : 3553;
-                       const webglTexture = properties.get( texture ).__webglTexture;
+                       if ( cullFace !== currentCullFace ) {
 
-                       state.bindTexture( target, webglTexture );
-                       generateMipmap( target, texture, renderTarget.width, renderTarget.height );
-                       state.bindTexture( target, null );
+                               if ( cullFace === CullFaceBack ) {
 
-               }
+                                       gl.cullFace( 1029 );
 
-       }
+                               } else if ( cullFace === CullFaceFront ) {
 
-       function updateMultisampleRenderTarget( renderTarget ) {
+                                       gl.cullFace( 1028 );
 
-               if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+                               } else {
 
-                       if ( isWebGL2 ) {
+                                       gl.cullFace( 1032 );
 
-                               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;
+               } else {
 
-                               if ( renderTarget.depthBuffer ) mask |= 256;
-                               if ( renderTarget.stencilBuffer ) mask |= 1024;
+                       disable( 2884 );
 
-                               _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, 9728 );
+               }
 
-                               _gl.bindFramebuffer( 36160, renderTargetProperties.__webglMultisampledFramebuffer ); // see #18905
+               currentCullFace = cullFace;
 
-                       } else {
+       }
 
-                               console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+       function setLineWidth( width ) {
 
-                       }
+               if ( width !== currentLineWidth ) {
+
+                       if ( lineWidthAvailable ) gl.lineWidth( width );
+
+                       currentLineWidth = width;
 
                }
 
        }
 
-       function getRenderTargetSamples( renderTarget ) {
+       function setPolygonOffset( polygonOffset, factor, units ) {
 
-               return ( isWebGL2 && renderTarget.isWebGLMultisampleRenderTarget ) ?
-                       Math.min( maxSamples, renderTarget.samples ) : 0;
+               if ( polygonOffset ) {
 
-       }
+                       enable( 32823 );
 
-       function updateVideoTexture( texture ) {
+                       if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) {
 
-               const frame = info.render.frame;
+                               gl.polygonOffset( factor, units );
 
-               // Check the last frame we updated the VideoTexture
+                               currentPolygonOffsetFactor = factor;
+                               currentPolygonOffsetUnits = units;
 
-               if ( _videoTextures.get( texture ) !== frame ) {
+                       }
 
-                       _videoTextures.set( texture, frame );
-                       texture.update();
+               } else {
+
+                       disable( 32823 );
 
                }
 
        }
 
-       // backwards compatibility
+       function setScissorTest( scissorTest ) {
 
-       let warnedTexture2D = false;
-       let warnedTextureCube = false;
+               if ( scissorTest ) {
 
-       function safeSetTexture2D( texture, slot ) {
+                       enable( 3089 );
 
-               if ( texture && texture.isWebGLRenderTarget ) {
+               } else {
 
-                       if ( warnedTexture2D === false ) {
+                       disable( 3089 );
 
-                               console.warn( 'THREE.WebGLTextures.safeSetTexture2D: don\'t use render targets as textures. Use their .texture property instead.' );
-                               warnedTexture2D = true;
+               }
 
-                       }
+       }
 
-                       texture = texture.texture;
+       // texture
 
-               }
+       function activeTexture( webglSlot ) {
 
-               setTexture2D( texture, slot );
+               if ( webglSlot === undefined ) webglSlot = 33984 + maxTextures - 1;
 
-       }
+               if ( currentTextureSlot !== webglSlot ) {
 
-       function safeSetTextureCube( texture, slot ) {
+                       gl.activeTexture( webglSlot );
+                       currentTextureSlot = webglSlot;
 
-               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;
+       function bindTexture( webglType, webglTexture ) {
 
-                       }
+               if ( currentTextureSlot === null ) {
 
-                       texture = texture.texture;
+                       activeTexture();
 
                }
 
+               let boundTexture = currentBoundTextures[ currentTextureSlot ];
 
-               setTextureCube( texture, slot );
+               if ( boundTexture === undefined ) {
 
-       }
+                       boundTexture = { type: undefined, texture: undefined };
+                       currentBoundTextures[ currentTextureSlot ] = boundTexture;
 
-       //
+               }
 
-       this.allocateTextureUnit = allocateTextureUnit;
-       this.resetTextureUnits = resetTextureUnits;
+               if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {
 
-       this.setTexture2D = setTexture2D;
-       this.setTexture2DArray = setTexture2DArray;
-       this.setTexture3D = setTexture3D;
-       this.setTextureCube = setTextureCube;
-       this.setupRenderTarget = setupRenderTarget;
-       this.updateRenderTargetMipmap = updateRenderTargetMipmap;
-       this.updateMultisampleRenderTarget = updateMultisampleRenderTarget;
+                       gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );
 
-       this.safeSetTexture2D = safeSetTexture2D;
-       this.safeSetTextureCube = safeSetTextureCube;
+                       boundTexture.type = webglType;
+                       boundTexture.texture = webglTexture;
 
-}
+               }
 
-function WebGLUtils( gl, extensions, capabilities ) {
+       }
 
-       const isWebGL2 = capabilities.isWebGL2;
+       function unbindTexture() {
 
-       function convert( p ) {
+               const boundTexture = currentBoundTextures[ currentTextureSlot ];
 
-               let extension;
+               if ( boundTexture !== undefined && boundTexture.type !== undefined ) {
 
-               if ( p === UnsignedByteType ) return 5121;
-               if ( p === UnsignedShort4444Type ) return 32819;
-               if ( p === UnsignedShort5551Type ) return 32820;
-               if ( p === UnsignedShort565Type ) return 33635;
+                       gl.bindTexture( boundTexture.type, null );
 
-               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;
+                       boundTexture.type = undefined;
+                       boundTexture.texture = undefined;
 
-               if ( p === HalfFloatType ) {
+               }
 
-                       if ( isWebGL2 ) return 5131;
+       }
 
-                       extension = extensions.get( 'OES_texture_half_float' );
+       function compressedTexImage2D() {
 
-                       if ( extension !== null ) {
+               try {
 
-                               return extension.HALF_FLOAT_OES;
+                       gl.compressedTexImage2D.apply( gl, arguments );
 
-                       } else {
+               } catch ( error ) {
 
-                               return null;
-
-                       }
+                       console.error( 'THREE.WebGLState:', error );
 
                }
 
-               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 ) {
+       function texImage2D() {
 
-                               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;
+               try {
 
-                       } else {
+                       gl.texImage2D.apply( gl, arguments );
 
-                               return null;
+               } catch ( error ) {
 
-                       }
+                       console.error( 'THREE.WebGLState:', error );
 
                }
 
-               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 ) {
+       function texImage3D() {
 
-                               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;
+               try {
 
-                       } else {
+                       gl.texImage3D.apply( gl, arguments );
 
-                               return null;
+               } catch ( error ) {
 
-                       }
+                       console.error( 'THREE.WebGLState:', error );
 
                }
 
-               if ( p === RGB_ETC1_Format ) {
+       }
 
-                       extension = extensions.get( 'WEBGL_compressed_texture_etc1' );
+       //
 
-                       if ( extension !== null ) {
+       function scissor( scissor ) {
 
-                               return extension.COMPRESSED_RGB_ETC1_WEBGL;
+               if ( currentScissor.equals( scissor ) === false ) {
 
-                       } else {
+                       gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w );
+                       currentScissor.copy( scissor );
 
-                               return null;
+               }
 
-                       }
+       }
 
-               }
+       function viewport( viewport ) {
 
-               if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) {
+               if ( currentViewport.equals( viewport ) === false ) {
 
-                       extension = extensions.get( 'WEBGL_compressed_texture_etc' );
+                       gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w );
+                       currentViewport.copy( viewport );
 
-                       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;
+       }
 
-                       }
+       //
 
-               }
+       function reset() {
 
-               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 ) {
+               // reset state
 
-                       extension = extensions.get( 'WEBGL_compressed_texture_astc' );
+               gl.disable( 3042 );
+               gl.disable( 2884 );
+               gl.disable( 2929 );
+               gl.disable( 32823 );
+               gl.disable( 3089 );
+               gl.disable( 2960 );
+               gl.disable( 32926 );
 
-                       if ( extension !== null ) {
+               gl.blendEquation( 32774 );
+               gl.blendFunc( 1, 0 );
+               gl.blendFuncSeparate( 1, 0, 1, 0 );
 
-                               // TODO Complete?
+               gl.colorMask( true, true, true, true );
+               gl.clearColor( 0, 0, 0, 0 );
 
-                               return p;
+               gl.depthMask( true );
+               gl.depthFunc( 513 );
+               gl.clearDepth( 1 );
 
-                       } else {
+               gl.stencilMask( 0xffffffff );
+               gl.stencilFunc( 519, 0, 0xffffffff );
+               gl.stencilOp( 7680, 7680, 7680 );
+               gl.clearStencil( 0 );
 
-                               return null;
+               gl.cullFace( 1029 );
+               gl.frontFace( 2305 );
 
-                       }
+               gl.polygonOffset( 0, 0 );
 
-               }
+               gl.activeTexture( 33984 );
 
-               if ( p === RGBA_BPTC_Format ) {
+               gl.bindFramebuffer( 36160, null );
 
-                       extension = extensions.get( 'EXT_texture_compression_bptc' );
+               if ( isWebGL2 === true ) {
 
-                       if ( extension !== null ) {
+                       gl.bindFramebuffer( 36009, null );
+                       gl.bindFramebuffer( 36008, null );
 
-                               // TODO Complete?
+               }
 
-                               return p;
+               gl.useProgram( null );
 
-                       } else {
+               gl.lineWidth( 1 );
 
-                               return null;
+               gl.scissor( 0, 0, gl.canvas.width, gl.canvas.height );
+               gl.viewport( 0, 0, gl.canvas.width, gl.canvas.height );
 
-                       }
+               // reset internals
 
-               }
+               enabledCapabilities = {};
 
-               if ( p === UnsignedInt248Type ) {
+               currentTextureSlot = null;
+               currentBoundTextures = {};
 
-                       if ( isWebGL2 ) return 34042;
+               xrFramebuffer = null;
+               currentBoundFramebuffers = {};
 
-                       extension = extensions.get( 'WEBGL_depth_texture' );
+               currentProgram = null;
 
-                       if ( extension !== null ) {
+               currentBlendingEnabled = false;
+               currentBlending = null;
+               currentBlendEquation = null;
+               currentBlendSrc = null;
+               currentBlendDst = null;
+               currentBlendEquationAlpha = null;
+               currentBlendSrcAlpha = null;
+               currentBlendDstAlpha = null;
+               currentPremultipledAlpha = false;
 
-                               return extension.UNSIGNED_INT_24_8_WEBGL;
+               currentFlipSided = null;
+               currentCullFace = null;
 
-                       } else {
+               currentLineWidth = null;
 
-                               return null;
+               currentPolygonOffsetFactor = null;
+               currentPolygonOffsetUnits = null;
 
-                       }
+               currentScissor.set( 0, 0, gl.canvas.width, gl.canvas.height );
+               currentViewport.set( 0, 0, gl.canvas.width, gl.canvas.height );
 
-               }
+               colorBuffer.reset();
+               depthBuffer.reset();
+               stencilBuffer.reset();
 
        }
 
-       return { convert: convert };
+       return {
 
-}
+               buffers: {
+                       color: colorBuffer,
+                       depth: depthBuffer,
+                       stencil: stencilBuffer
+               },
 
-function ArrayCamera( array = [] ) {
+               enable: enable,
+               disable: disable,
 
-       PerspectiveCamera.call( this );
+               bindFramebuffer: bindFramebuffer,
+               bindXRFramebuffer: bindXRFramebuffer,
 
-       this.cameras = array;
+               useProgram: useProgram,
 
-}
+               setBlending: setBlending,
+               setMaterial: setMaterial,
 
-ArrayCamera.prototype = Object.assign( Object.create( PerspectiveCamera.prototype ), {
+               setFlipSided: setFlipSided,
+               setCullFace: setCullFace,
 
-       constructor: ArrayCamera,
+               setLineWidth: setLineWidth,
+               setPolygonOffset: setPolygonOffset,
 
-       isArrayCamera: true
+               setScissorTest: setScissorTest,
 
-} );
+               activeTexture: activeTexture,
+               bindTexture: bindTexture,
+               unbindTexture: unbindTexture,
+               compressedTexImage2D: compressedTexImage2D,
+               texImage2D: texImage2D,
+               texImage3D: texImage3D,
 
-function Group() {
+               scissor: scissor,
+               viewport: viewport,
 
-       Object3D.call( this );
+               reset: reset
 
-       this.type = 'Group';
+       };
 
 }
 
-Group.prototype = Object.assign( Object.create( Object3D.prototype ), {
+function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) {
 
-       constructor: Group,
+       const isWebGL2 = capabilities.isWebGL2;
+       const maxTextures = capabilities.maxTextures;
+       const maxCubemapSize = capabilities.maxCubemapSize;
+       const maxTextureSize = capabilities.maxTextureSize;
+       const maxSamples = capabilities.maxSamples;
 
-       isGroup: true
+       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).
 
-function WebXRController() {
+       let useOffscreenCanvas = false;
 
-       this._targetRay = null;
-       this._grip = null;
-       this._hand = null;
+       try {
 
-}
+               useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined'
+                       && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null;
 
-Object.assign( WebXRController.prototype, {
+       } catch ( err ) {
 
-       constructor: WebXRController,
+               // Ignore any errors
 
-       getHandSpace: function () {
+       }
 
-               if ( this._hand === null ) {
+       function createCanvas( width, height ) {
 
-                       this._hand = new Group();
-                       this._hand.matrixAutoUpdate = false;
-                       this._hand.visible = false;
+               // Use OffscreenCanvas when available. Specially needed in web workers
 
-                       this._hand.joints = {};
-                       this._hand.inputState = { pinching: false };
+               return useOffscreenCanvas ?
+                       new OffscreenCanvas( width, height ) : createElementNS( 'canvas' );
 
-               }
+       }
 
-               return this._hand;
+       function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) {
 
-       },
+               let scale = 1;
 
-       getTargetRaySpace: function () {
+               // handle case if texture exceeds max size
 
-               if ( this._targetRay === null ) {
+               if ( image.width > maxSize || image.height > maxSize ) {
 
-                       this._targetRay = new Group();
-                       this._targetRay.matrixAutoUpdate = false;
-                       this._targetRay.visible = false;
+                       scale = maxSize / Math.max( image.width, image.height );
 
                }
 
-               return this._targetRay;
+               // only perform resize if necessary
 
-       },
+               if ( scale < 1 || needsPowerOfTwo === true ) {
 
-       getGripSpace: function () {
+                       // only perform resize for certain image types
 
-               if ( this._grip === null ) {
+                       if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
+                               ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
+                               ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {
 
-                       this._grip = new Group();
-                       this._grip.matrixAutoUpdate = false;
-                       this._grip.visible = false;
+                               const floor = needsPowerOfTwo ? floorPowerOfTwo : Math.floor;
 
-               }
+                               const width = floor( scale * image.width );
+                               const height = floor( scale * image.height );
 
-               return this._grip;
+                               if ( _canvas === undefined ) _canvas = createCanvas( width, height );
 
-       },
+                               // cube textures can't reuse the same canvas
 
-       dispatchEvent: function ( event ) {
+                               const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas;
 
-               if ( this._targetRay !== null ) {
+                               canvas.width = width;
+                               canvas.height = height;
 
-                       this._targetRay.dispatchEvent( event );
+                               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 + ').' );
 
-               if ( this._grip !== null ) {
+                               return canvas;
 
-                       this._grip.dispatchEvent( event );
+                       } else {
 
-               }
+                               if ( 'data' in image ) {
 
-               if ( this._hand !== null ) {
+                                       console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );
 
-                       this._hand.dispatchEvent( event );
+                               }
 
-               }
+                               return image;
 
-               return this;
+                       }
 
-       },
+               }
 
-       disconnect: function ( inputSource ) {
+               return image;
 
-               this.dispatchEvent( { type: 'disconnected', data: inputSource } );
+       }
 
-               if ( this._targetRay !== null ) {
+       function isPowerOfTwo$1( image ) {
 
-                       this._targetRay.visible = false;
+               return isPowerOfTwo( image.width ) && isPowerOfTwo( image.height );
 
-               }
+       }
 
-               if ( this._grip !== null ) {
+       function textureNeedsPowerOfTwo( texture ) {
 
-                       this._grip.visible = false;
+               if ( isWebGL2 ) return false;
 
-               }
+               return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) ||
+                       ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter );
 
-               if ( this._hand !== null ) {
+       }
 
-                       this._hand.visible = false;
+       function textureNeedsGenerateMipmaps( texture, supportsMips ) {
 
-               }
+               return texture.generateMipmaps && supportsMips &&
+                       texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter;
 
-               return this;
+       }
 
-       },
+       function generateMipmap( target, texture, width, height, depth = 1 ) {
 
-       update: function ( inputSource, frame, referenceSpace ) {
+               _gl.generateMipmap( target );
 
-               let inputPose = null;
-               let gripPose = null;
-               let handPose = null;
+               const textureProperties = properties.get( texture );
 
-               const targetRay = this._targetRay;
-               const grip = this._grip;
-               const hand = this._hand;
+               textureProperties.__maxMipLevel = Math.log2( Math.max( width, height, depth ) );
 
-               if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) {
+       }
 
-                       if ( hand && inputSource.hand ) {
+       function getInternalFormat( internalFormatName, glFormat, glType, encoding ) {
 
-                               handPose = true;
+               if ( isWebGL2 === false ) return glFormat;
 
-                               for ( const inputjoint of inputSource.hand.values() ) {
+               if ( internalFormatName !== null ) {
 
-                                       // Update the joints groups with the XRJoint poses
-                                       const jointPose = frame.getJointPose( inputjoint, referenceSpace );
+                       if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ];
 
-                                       if ( hand.joints[ inputjoint.jointName ] === undefined ) {
+                       console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' );
 
-                                               // 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 );
+               }
 
-                                       }
+               let internalFormat = glFormat;
 
-                                       const joint = hand.joints[ inputjoint.jointName ];
+               if ( glFormat === 6403 ) {
 
-                                       if ( jointPose !== null ) {
+                       if ( glType === 5126 ) internalFormat = 33326;
+                       if ( glType === 5131 ) internalFormat = 33325;
+                       if ( glType === 5121 ) internalFormat = 33321;
 
-                                               joint.matrix.fromArray( jointPose.transform.matrix );
-                                               joint.matrix.decompose( joint.position, joint.rotation, joint.scale );
-                                               joint.jointRadius = jointPose.radius;
+               }
 
-                                       }
+               if ( glFormat === 6407 ) {
 
-                                       joint.visible = jointPose !== null;
+                       if ( glType === 5126 ) internalFormat = 34837;
+                       if ( glType === 5131 ) internalFormat = 34843;
+                       if ( glType === 5121 ) internalFormat = 32849;
 
-                               }
+               }
 
-                               // Custom events
+               if ( glFormat === 6408 ) {
 
-                               // Check pinchz
-                               const indexTip = hand.joints[ 'index-finger-tip' ];
-                               const thumbTip = hand.joints[ 'thumb-tip' ];
-                               const distance = indexTip.position.distanceTo( thumbTip.position );
+                       if ( glType === 5126 ) internalFormat = 34836;
+                       if ( glType === 5131 ) internalFormat = 34842;
+                       if ( glType === 5121 ) internalFormat = ( encoding === sRGBEncoding ) ? 35907 : 32856;
 
-                               const distanceToPinch = 0.02;
-                               const threshold = 0.005;
+               }
 
-                               if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) {
+               if ( internalFormat === 33325 || internalFormat === 33326 ||
+                       internalFormat === 34842 || internalFormat === 34836 ) {
 
-                                       hand.inputState.pinching = false;
-                                       this.dispatchEvent( {
-                                               type: 'pinchend',
-                                               handedness: inputSource.handedness,
-                                               target: this
-                                       } );
+                       extensions.get( 'EXT_color_buffer_float' );
 
-                               } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) {
+               }
 
-                                       hand.inputState.pinching = true;
-                                       this.dispatchEvent( {
-                                               type: 'pinchstart',
-                                               handedness: inputSource.handedness,
-                                               target: this
-                                       } );
+               return internalFormat;
 
-                               }
+       }
 
-                       } else {
+       // Fallback filters for non-power-of-2 textures
 
-                               if ( targetRay !== null ) {
+       function filterFallback( f ) {
 
-                                       inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace );
+               if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) {
 
-                                       if ( inputPose !== null ) {
+                       return 9728;
 
-                                               targetRay.matrix.fromArray( inputPose.transform.matrix );
-                                               targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale );
+               }
 
-                                       }
+               return 9729;
 
-                               }
+       }
 
-                               if ( grip !== null && inputSource.gripSpace ) {
+       //
 
-                                       gripPose = frame.getPose( inputSource.gripSpace, referenceSpace );
+       function onTextureDispose( event ) {
 
-                                       if ( gripPose !== null ) {
+               const texture = event.target;
 
-                                               grip.matrix.fromArray( gripPose.transform.matrix );
-                                               grip.matrix.decompose( grip.position, grip.rotation, grip.scale );
+               texture.removeEventListener( 'dispose', onTextureDispose );
 
-                                       }
+               deallocateTexture( texture );
 
-                               }
+               if ( texture.isVideoTexture ) {
 
-                       }
+                       _videoTextures.delete( texture );
 
                }
 
-               if ( targetRay !== null ) {
-
-                       targetRay.visible = ( inputPose !== null );
+               info.memory.textures --;
 
-               }
+       }
 
-               if ( grip !== null ) {
+       function onRenderTargetDispose( event ) {
 
-                       grip.visible = ( gripPose !== null );
+               const renderTarget = event.target;
 
-               }
+               renderTarget.removeEventListener( 'dispose', onRenderTargetDispose );
 
-               if ( hand !== null ) {
+               deallocateRenderTarget( renderTarget );
 
-                       hand.visible = ( handPose !== null );
+       }
 
-               }
+       //
 
-               return this;
+       function deallocateTexture( texture ) {
 
-       }
+               const textureProperties = properties.get( texture );
 
-} );
+               if ( textureProperties.__webglInit === undefined ) return;
 
-function WebXRManager( renderer, gl ) {
+               _gl.deleteTexture( textureProperties.__webglTexture );
 
-       const scope = this;
+               properties.remove( texture );
 
-       let session = null;
+       }
 
-       let framebufferScaleFactor = 1.0;
+       function deallocateRenderTarget( renderTarget ) {
 
-       let referenceSpace = null;
-       let referenceSpaceType = 'local-floor';
+               const texture = renderTarget.texture;
 
-       let pose = null;
+               const renderTargetProperties = properties.get( renderTarget );
+               const textureProperties = properties.get( texture );
 
-       const controllers = [];
-       const inputSourcesMap = new Map();
+               if ( ! renderTarget ) return;
 
-       //
+               if ( textureProperties.__webglTexture !== undefined ) {
 
-       const cameraL = new PerspectiveCamera();
-       cameraL.layers.enable( 1 );
-       cameraL.viewport = new Vector4();
+                       _gl.deleteTexture( textureProperties.__webglTexture );
 
-       const cameraR = new PerspectiveCamera();
-       cameraR.layers.enable( 2 );
-       cameraR.viewport = new Vector4();
+                       info.memory.textures --;
 
-       const cameras = [ cameraL, cameraR ];
+               }
 
-       const cameraVR = new ArrayCamera();
-       cameraVR.layers.enable( 1 );
-       cameraVR.layers.enable( 2 );
+               if ( renderTarget.depthTexture ) {
 
-       let _currentDepthNear = null;
-       let _currentDepthFar = null;
+                       renderTarget.depthTexture.dispose();
 
-       //
+               }
 
-       this.enabled = false;
+               if ( renderTarget.isWebGLCubeRenderTarget ) {
 
-       this.isPresenting = false;
+                       for ( let i = 0; i < 6; i ++ ) {
 
-       this.getController = function ( index ) {
+                               _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] );
+                               if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] );
 
-               let controller = controllers[ index ];
+                       }
 
-               if ( controller === undefined ) {
+               } else {
 
-                       controller = new WebXRController();
-                       controllers[ index ] = controller;
+                       _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 );
 
                }
 
-               return controller.getTargetRaySpace();
+               if ( renderTarget.isWebGLMultipleRenderTargets ) {
 
-       };
+                       for ( let i = 0, il = texture.length; i < il; i ++ ) {
 
-       this.getControllerGrip = function ( index ) {
+                               const attachmentProperties = properties.get( texture[ i ] );
 
-               let controller = controllers[ index ];
+                               if ( attachmentProperties.__webglTexture ) {
 
-               if ( controller === undefined ) {
+                                       _gl.deleteTexture( attachmentProperties.__webglTexture );
 
-                       controller = new WebXRController();
-                       controllers[ index ] = controller;
+                                       info.memory.textures --;
 
-               }
+                               }
 
-               return controller.getGripSpace();
+                               properties.remove( texture[ i ] );
 
-       };
+                       }
 
-       this.getHand = function ( index ) {
+               }
 
-               let controller = controllers[ index ];
+               properties.remove( texture );
+               properties.remove( renderTarget );
 
-               if ( controller === undefined ) {
+       }
 
-                       controller = new WebXRController();
-                       controllers[ index ] = controller;
+       //
 
-               }
+       let textureUnits = 0;
 
-               return controller.getHandSpace();
+       function resetTextureUnits() {
 
-       };
+               textureUnits = 0;
 
-       //
+       }
 
-       function onSessionEvent( event ) {
+       function allocateTextureUnit() {
 
-               const controller = inputSourcesMap.get( event.inputSource );
+               const textureUnit = textureUnits;
 
-               if ( controller ) {
+               if ( textureUnit >= maxTextures ) {
 
-                       controller.dispatchEvent( { type: event.type, data: event.inputSource } );
+                       console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + maxTextures );
 
                }
 
-       }
+               textureUnits += 1;
 
-       function onSessionEnd() {
+               return textureUnit;
 
-               inputSourcesMap.forEach( function ( controller, inputSource ) {
+       }
 
-                       controller.disconnect( inputSource );
+       //
 
-               } );
+       function setTexture2D( texture, slot ) {
 
-               inputSourcesMap.clear();
+               const textureProperties = properties.get( texture );
 
-               _currentDepthNear = null;
-               _currentDepthFar = null;
+               if ( texture.isVideoTexture ) updateVideoTexture( texture );
 
-               //
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
 
-               renderer.setFramebuffer( null );
-               renderer.setRenderTarget( renderer.getRenderTarget() ); // Hack #15830
-               animation.stop();
+                       const image = texture.image;
 
-               scope.isPresenting = false;
+                       if ( image === undefined ) {
 
-               scope.dispatchEvent( { type: 'sessionend' } );
+                               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined' );
 
-       }
+                       } else if ( image.complete === false ) {
 
-       this.setFramebufferScaleFactor = function ( value ) {
+                               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' );
 
-               framebufferScaleFactor = value;
+                       } else {
 
-               if ( scope.isPresenting === true ) {
+                               uploadTexture( textureProperties, texture, slot );
+                               return;
 
-                       console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' );
+                       }
 
                }
 
-       };
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 3553, textureProperties.__webglTexture );
+
+       }
 
-       this.setReferenceSpaceType = function ( value ) {
+       function setTexture2DArray( texture, slot ) {
 
-               referenceSpaceType = value;
+               const textureProperties = properties.get( texture );
 
-               if ( scope.isPresenting === true ) {
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
 
-                       console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' );
+                       uploadTexture( textureProperties, texture, slot );
+                       return;
 
                }
 
-       };
-
-       this.getReferenceSpace = function () {
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 35866, textureProperties.__webglTexture );
 
-               return referenceSpace;
+       }
 
-       };
+       function setTexture3D( texture, slot ) {
 
-       this.getSession = function () {
+               const textureProperties = properties.get( texture );
 
-               return session;
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
 
-       };
+                       uploadTexture( textureProperties, texture, slot );
+                       return;
 
-       this.setSession = async function ( value ) {
+               }
 
-               session = value;
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 32879, textureProperties.__webglTexture );
 
-               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 );
+       function setTextureCube( texture, slot ) {
 
-                       const attributes = gl.getContextAttributes();
+               const textureProperties = properties.get( texture );
 
-                       if ( attributes.xrCompatible !== true ) {
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
 
-                               await gl.makeXRCompatible();
+                       uploadCubeTexture( textureProperties, texture, slot );
+                       return;
 
-                       }
+               }
 
-                       const layerInit = {
-                               antialias: attributes.antialias,
-                               alpha: attributes.alpha,
-                               depth: attributes.depth,
-                               stencil: attributes.stencil,
-                               framebufferScaleFactor: framebufferScaleFactor
-                       };
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 34067, textureProperties.__webglTexture );
+
+       }
+
+       const wrappingToGL = {
+               [ RepeatWrapping ]: 10497,
+               [ ClampToEdgeWrapping ]: 33071,
+               [ MirroredRepeatWrapping ]: 33648
+       };
 
-                       // eslint-disable-next-line no-undef
-                       const baseLayer = new XRWebGLLayer( session, gl, layerInit );
+       const filterToGL = {
+               [ NearestFilter ]: 9728,
+               [ NearestMipmapNearestFilter ]: 9984,
+               [ NearestMipmapLinearFilter ]: 9986,
 
-                       session.updateRenderState( { baseLayer: baseLayer } );
+               [ LinearFilter ]: 9729,
+               [ LinearMipmapNearestFilter ]: 9985,
+               [ LinearMipmapLinearFilter ]: 9987
+       };
 
-                       referenceSpace = await session.requestReferenceSpace( referenceSpaceType );
+       function setTextureParameters( textureType, texture, supportsMips ) {
 
-                       animation.setContext( session );
-                       animation.start();
+               if ( supportsMips ) {
 
-                       scope.isPresenting = true;
+                       _gl.texParameteri( textureType, 10242, wrappingToGL[ texture.wrapS ] );
+                       _gl.texParameteri( textureType, 10243, wrappingToGL[ texture.wrapT ] );
 
-                       scope.dispatchEvent( { type: 'sessionstart' } );
+                       if ( textureType === 32879 || textureType === 35866 ) {
 
-               }
+                               _gl.texParameteri( textureType, 32882, wrappingToGL[ texture.wrapR ] );
 
-       };
+                       }
 
-       function onInputSourcesChange( event ) {
+                       _gl.texParameteri( textureType, 10240, filterToGL[ texture.magFilter ] );
+                       _gl.texParameteri( textureType, 10241, filterToGL[ texture.minFilter ] );
 
-               const inputSources = session.inputSources;
+               } else {
 
-               // Assign inputSources to available controllers
+                       _gl.texParameteri( textureType, 10242, 33071 );
+                       _gl.texParameteri( textureType, 10243, 33071 );
 
-               for ( let i = 0; i < controllers.length; i ++ ) {
+                       if ( textureType === 32879 || textureType === 35866 ) {
 
-                       inputSourcesMap.set( inputSources[ i ], controllers[ i ] );
+                               _gl.texParameteri( textureType, 32882, 33071 );
 
-               }
+                       }
+
+                       if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) {
 
-               // Notify disconnected
+                               console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.' );
 
-               for ( let i = 0; i < event.removed.length; i ++ ) {
+                       }
 
-                       const inputSource = event.removed[ i ];
-                       const controller = inputSourcesMap.get( inputSource );
+                       _gl.texParameteri( textureType, 10240, filterFallback( texture.magFilter ) );
+                       _gl.texParameteri( textureType, 10241, filterFallback( texture.minFilter ) );
 
-                       if ( controller ) {
+                       if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) {
 
-                               controller.dispatchEvent( { type: 'disconnected', data: inputSource } );
-                               inputSourcesMap.delete( inputSource );
+                               console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.' );
 
                        }
 
                }
 
-               // Notify connected
+               if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) {
 
-               for ( let i = 0; i < event.added.length; i ++ ) {
+                       const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
 
-                       const inputSource = event.added[ i ];
-                       const controller = inputSourcesMap.get( inputSource );
+                       if ( texture.type === FloatType && extensions.has( 'OES_texture_float_linear' ) === false ) return; // verify extension for WebGL 1 and WebGL 2
+                       if ( isWebGL2 === false && ( texture.type === HalfFloatType && extensions.has( 'OES_texture_half_float_linear' ) === false ) ) return; // verify extension for WebGL 1 only
 
-                       if ( controller ) {
+                       if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) {
 
-                               controller.dispatchEvent( { type: 'connected', data: inputSource } );
+                               _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) );
+                               properties.get( texture ).__currentAnisotropy = texture.anisotropy;
 
                        }
 
@@ -26050,6600 +24915,6640 @@ function WebXRManager( renderer, gl ) {
 
        }
 
-       //
-
-       const cameraLPos = new Vector3();
-       const cameraRPos = new Vector3();
+       function initTexture( textureProperties, texture ) {
 
-       /**
-        * 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 ) {
+               if ( textureProperties.__webglInit === undefined ) {
 
-               cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
-               cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );
+                       textureProperties.__webglInit = true;
 
-               const ipd = cameraLPos.distanceTo( cameraRPos );
+                       texture.addEventListener( 'dispose', onTextureDispose );
 
-               const projL = cameraL.projectionMatrix.elements;
-               const projR = cameraR.projectionMatrix.elements;
+                       textureProperties.__webglTexture = _gl.createTexture();
 
-               // 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 ];
+                       info.memory.textures ++;
 
-               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();
+       function uploadTexture( textureProperties, texture, slot ) {
 
-               // 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;
+               let textureType = 3553;
 
-               camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );
+               if ( texture.isDataTexture2DArray ) textureType = 35866;
+               if ( texture.isDataTexture3D ) textureType = 32879;
 
-       }
+               initTexture( textureProperties, texture );
 
-       function updateCamera( camera, parent ) {
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( textureType, textureProperties.__webglTexture );
 
-               if ( parent === null ) {
+               _gl.pixelStorei( 37440, texture.flipY );
+               _gl.pixelStorei( 37441, texture.premultiplyAlpha );
+               _gl.pixelStorei( 3317, texture.unpackAlignment );
+               _gl.pixelStorei( 37443, 0 );
 
-                       camera.matrixWorld.copy( camera.matrix );
+               const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo$1( texture.image ) === false;
+               const image = resizeImage( texture.image, needsPowerOfTwo, false, maxTextureSize );
 
-               } else {
+               const supportsMips = isPowerOfTwo$1( image ) || isWebGL2,
+                       glFormat = utils.convert( texture.format );
 
-                       camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix );
+               let glType = utils.convert( texture.type ),
+                       glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
 
-               }
+               setTextureParameters( textureType, texture, supportsMips );
 
-               camera.matrixWorldInverse.copy( camera.matrixWorld ).invert();
+               let mipmap;
+               const mipmaps = texture.mipmaps;
 
-       }
+               if ( texture.isDepthTexture ) {
 
-       this.getCamera = function ( camera ) {
+                       // populate depth texture with dummy data
 
-               cameraVR.near = cameraR.near = cameraL.near = camera.near;
-               cameraVR.far = cameraR.far = cameraL.far = camera.far;
+                       glInternalFormat = 6402;
 
-               if ( _currentDepthNear !== cameraVR.near || _currentDepthFar !== cameraVR.far ) {
+                       if ( isWebGL2 ) {
 
-                       // Note that the new renderState won't apply until the next frame. See #18320
+                               if ( texture.type === FloatType ) {
 
-                       session.updateRenderState( {
-                               depthNear: cameraVR.near,
-                               depthFar: cameraVR.far
-                       } );
+                                       glInternalFormat = 36012;
 
-                       _currentDepthNear = cameraVR.near;
-                       _currentDepthFar = cameraVR.far;
+                               } else if ( texture.type === UnsignedIntType ) {
 
-               }
+                                       glInternalFormat = 33190;
 
-               const parent = camera.parent;
-               const cameras = cameraVR.cameras;
+                               } else if ( texture.type === UnsignedInt248Type ) {
 
-               updateCamera( cameraVR, parent );
+                                       glInternalFormat = 35056;
 
-               for ( let i = 0; i < cameras.length; i ++ ) {
+                               } else {
 
-                       updateCamera( cameras[ i ], parent );
+                                       glInternalFormat = 33189; // WebGL2 requires sized internalformat for glTexImage2D
 
-               }
+                               }
 
-               // update camera and its children
+                       } else {
 
-               camera.matrixWorld.copy( cameraVR.matrixWorld );
-               camera.matrix.copy( cameraVR.matrix );
-               camera.matrix.decompose( camera.position, camera.quaternion, camera.scale );
+                               if ( texture.type === FloatType ) {
 
-               const children = camera.children;
+                                       console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' );
 
-               for ( let i = 0, l = children.length; i < l; i ++ ) {
+                               }
 
-                       children[ i ].updateMatrixWorld( true );
+                       }
 
-               }
+                       // validation checks for WebGL 1
 
-               // update projection matrix for proper view frustum culling
+                       if ( texture.format === DepthFormat && glInternalFormat === 6402 ) {
 
-               if ( cameras.length === 2 ) {
+                               // 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 ) {
 
-                       setProjectionFromUnion( cameraVR, cameraL, cameraR );
+                                       console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' );
 
-               } else {
+                                       texture.type = UnsignedShortType;
+                                       glType = utils.convert( texture.type );
 
-                       // assume single camera setup (AR)
+                               }
 
-                       cameraVR.projectionMatrix.copy( cameraL.projectionMatrix );
+                       }
 
-               }
+                       if ( texture.format === DepthStencilFormat && glInternalFormat === 6402 ) {
 
-               return cameraVR;
+                               // 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 ) {
 
-       // Animation Loop
+                                       console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' );
 
-       let onAnimationFrameCallback = null;
+                                       texture.type = UnsignedInt248Type;
+                                       glType = utils.convert( texture.type );
 
-       function onAnimationFrame( time, frame ) {
+                               }
 
-               pose = frame.getViewerPose( referenceSpace );
+                       }
 
-               if ( pose !== null ) {
+                       //
 
-                       const views = pose.views;
-                       const baseLayer = session.renderState.baseLayer;
+                       state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null );
 
-                       renderer.setFramebuffer( baseLayer.framebuffer );
+               } else if ( texture.isDataTexture ) {
 
-                       let cameraVRNeedsUpdate = false;
+                       // 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
 
-                       // check if it's necessary to rebuild cameraVR's camera list
+                       if ( mipmaps.length > 0 && supportsMips ) {
 
-                       if ( views.length !== cameraVR.cameras.length ) {
+                               for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
 
-                               cameraVR.cameras.length = 0;
-                               cameraVRNeedsUpdate = true;
+                                       mipmap = mipmaps[ i ];
+                                       state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
 
-                       }
+                               }
 
-                       for ( let i = 0; i < views.length; i ++ ) {
+                               texture.generateMipmaps = false;
+                               textureProperties.__maxMipLevel = mipmaps.length - 1;
 
-                               const view = views[ i ];
-                               const viewport = baseLayer.getViewport( view );
+                       } else {
 
-                               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 );
+                               state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data );
+                               textureProperties.__maxMipLevel = 0;
 
-                               if ( i === 0 ) {
+                       }
 
-                                       cameraVR.matrix.copy( camera.matrix );
+               } else if ( texture.isCompressedTexture ) {
 
-                               }
+                       for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
 
-                               if ( cameraVRNeedsUpdate === true ) {
+                               mipmap = mipmaps[ i ];
 
-                                       cameraVR.cameras.push( camera );
+                               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()' );
 
-               const inputSources = session.inputSources;
+                                       }
 
-               for ( let i = 0; i < controllers.length; i ++ ) {
+                               } else {
 
-                       const controller = controllers[ i ];
-                       const inputSource = inputSources[ i ];
+                                       state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
 
-                       controller.update( inputSource, frame, referenceSpace );
+                               }
 
-               }
+                       }
 
-               if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame );
+                       textureProperties.__maxMipLevel = mipmaps.length - 1;
 
-       }
+               } else if ( texture.isDataTexture2DArray ) {
 
-       const animation = new WebGLAnimation();
-       animation.setAnimationLoop( onAnimationFrame );
+                       state.texImage3D( 35866, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
+                       textureProperties.__maxMipLevel = 0;
 
-       this.setAnimationLoop = function ( callback ) {
+               } else if ( texture.isDataTexture3D ) {
 
-               onAnimationFrameCallback = callback;
+                       state.texImage3D( 32879, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
+                       textureProperties.__maxMipLevel = 0;
 
-       };
+               } else {
 
-       this.dispose = function () {};
+                       // 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
 
-Object.assign( WebXRManager.prototype, EventDispatcher.prototype );
+                       if ( mipmaps.length > 0 && supportsMips ) {
 
-function WebGLMaterials( properties ) {
+                               for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
 
-       function refreshFogUniforms( uniforms, fog ) {
+                                       mipmap = mipmaps[ i ];
+                                       state.texImage2D( 3553, i, glInternalFormat, glFormat, glType, mipmap );
 
-               uniforms.fogColor.value.copy( fog.color );
+                               }
 
-               if ( fog.isFog ) {
+                               texture.generateMipmaps = false;
+                               textureProperties.__maxMipLevel = mipmaps.length - 1;
 
-                       uniforms.fogNear.value = fog.near;
-                       uniforms.fogFar.value = fog.far;
+                       } else {
 
-               } else if ( fog.isFogExp2 ) {
+                               state.texImage2D( 3553, 0, glInternalFormat, glFormat, glType, image );
+                               textureProperties.__maxMipLevel = 0;
 
-                       uniforms.fogDensity.value = fog.density;
+                       }
 
                }
 
-       }
+               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
 
-       function refreshMaterialUniforms( uniforms, material, pixelRatio, height ) {
+                       generateMipmap( textureType, texture, image.width, image.height );
 
-               if ( material.isMeshBasicMaterial ) {
+               }
 
-                       refreshUniformsCommon( uniforms, material );
+               textureProperties.__version = texture.version;
 
-               } else if ( material.isMeshLambertMaterial ) {
+               if ( texture.onUpdate ) texture.onUpdate( texture );
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsLambert( uniforms, material );
+       }
 
-               } else if ( material.isMeshToonMaterial ) {
+       function uploadCubeTexture( textureProperties, texture, slot ) {
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsToon( uniforms, material );
+               if ( texture.image.length !== 6 ) return;
 
-               } else if ( material.isMeshPhongMaterial ) {
+               initTexture( textureProperties, texture );
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsPhong( uniforms, material );
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 34067, textureProperties.__webglTexture );
 
-               } else if ( material.isMeshStandardMaterial ) {
+               _gl.pixelStorei( 37440, texture.flipY );
+               _gl.pixelStorei( 37441, texture.premultiplyAlpha );
+               _gl.pixelStorei( 3317, texture.unpackAlignment );
+               _gl.pixelStorei( 37443, 0 );
 
-                       refreshUniformsCommon( uniforms, material );
+               const isCompressed = ( texture && ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ) );
+               const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture );
 
-                       if ( material.isMeshPhysicalMaterial ) {
+               const cubeImage = [];
+
+               for ( let i = 0; i < 6; i ++ ) {
 
-                               refreshUniformsPhysical( uniforms, material );
+                       if ( ! isCompressed && ! isDataTexture ) {
+
+                               cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, maxCubemapSize );
 
                        } else {
 
-                               refreshUniformsStandard( uniforms, material );
+                               cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ];
 
                        }
 
-               } else if ( material.isMeshMatcapMaterial ) {
+               }
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsMatcap( uniforms, material );
+               const image = cubeImage[ 0 ],
+                       supportsMips = isPowerOfTwo$1( image ) || isWebGL2,
+                       glFormat = utils.convert( texture.format ),
+                       glType = utils.convert( texture.type ),
+                       glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
 
-               } else if ( material.isMeshDepthMaterial ) {
+               setTextureParameters( 34067, texture, supportsMips );
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsDepth( uniforms, material );
+               let mipmaps;
 
-               } else if ( material.isMeshDistanceMaterial ) {
+               if ( isCompressed ) {
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsDistance( uniforms, material );
+                       for ( let i = 0; i < 6; i ++ ) {
 
-               } else if ( material.isMeshNormalMaterial ) {
+                               mipmaps = cubeImage[ i ].mipmaps;
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsNormal( uniforms, material );
+                               for ( let j = 0; j < mipmaps.length; j ++ ) {
 
-               } else if ( material.isLineBasicMaterial ) {
+                                       const mipmap = mipmaps[ j ];
 
-                       refreshUniformsLine( uniforms, material );
+                                       if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {
 
-                       if ( material.isLineDashedMaterial ) {
+                                               if ( glFormat !== null ) {
 
-                               refreshUniformsDash( uniforms, material );
+                                                       state.compressedTexImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
 
-                       }
+                                               } else {
 
-               } else if ( material.isPointsMaterial ) {
+                                                       console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' );
 
-                       refreshUniformsPoints( uniforms, material, pixelRatio, height );
+                                               }
 
-               } else if ( material.isSpriteMaterial ) {
+                                       } else {
 
-                       refreshUniformsSprites( uniforms, material );
+                                               state.texImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
 
-               } else if ( material.isShadowMaterial ) {
+                                       }
 
-                       uniforms.color.value.copy( material.color );
-                       uniforms.opacity.value = material.opacity;
+                               }
 
-               } else if ( material.isShaderMaterial ) {
+                       }
 
-                       material.uniformsNeedUpdate = false; // #15581
+                       textureProperties.__maxMipLevel = mipmaps.length - 1;
 
-               }
+               } else {
 
-       }
+                       mipmaps = texture.mipmaps;
 
-       function refreshUniformsCommon( uniforms, material ) {
+                       for ( let i = 0; i < 6; i ++ ) {
 
-               uniforms.opacity.value = material.opacity;
+                               if ( isDataTexture ) {
 
-               if ( material.color ) {
+                                       state.texImage2D( 34069 + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data );
 
-                       uniforms.diffuse.value.copy( material.color );
+                                       for ( let j = 0; j < mipmaps.length; j ++ ) {
 
-               }
+                                               const mipmap = mipmaps[ j ];
+                                               const mipmapImage = mipmap.image[ i ].image;
 
-               if ( material.emissive ) {
+                                               state.texImage2D( 34069 + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data );
 
-                       uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity );
+                                       }
 
-               }
+                               } else {
 
-               if ( material.map ) {
+                                       state.texImage2D( 34069 + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] );
 
-                       uniforms.map.value = material.map;
+                                       for ( let j = 0; j < mipmaps.length; j ++ ) {
 
-               }
+                                               const mipmap = mipmaps[ j ];
 
-               if ( material.alphaMap ) {
+                                               state.texImage2D( 34069 + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] );
 
-                       uniforms.alphaMap.value = material.alphaMap;
+                                       }
+
+                               }
+
+                       }
+
+                       textureProperties.__maxMipLevel = mipmaps.length;
 
                }
 
-               if ( material.specularMap ) {
+               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
 
-                       uniforms.specularMap.value = material.specularMap;
+                       // We assume images for cube map have the same size.
+                       generateMipmap( 34067, texture, image.width, image.height );
 
                }
 
-               const envMap = properties.get( material ).envMap;
+               textureProperties.__version = texture.version;
 
-               if ( envMap ) {
+               if ( texture.onUpdate ) texture.onUpdate( texture );
 
-                       uniforms.envMap.value = envMap;
+       }
 
-                       uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap._needsFlipEnvMap ) ? - 1 : 1;
+       // Render targets
 
-                       uniforms.reflectivity.value = material.reflectivity;
-                       uniforms.refractionRatio.value = material.refractionRatio;
+       // Setup storage for target texture and bind it to correct framebuffer
+       function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget ) {
 
-                       const maxMipLevel = properties.get( envMap ).__maxMipLevel;
+               const glFormat = utils.convert( texture.format );
+               const glType = utils.convert( texture.type );
+               const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
 
-                       if ( maxMipLevel !== undefined ) {
+               if ( textureTarget === 32879 || textureTarget === 35866 ) {
 
-                               uniforms.maxMipLevel.value = maxMipLevel;
+                       state.texImage3D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, renderTarget.depth, 0, glFormat, glType, null );
 
-                       }
+               } else {
+
+                       state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
 
                }
 
-               if ( material.lightMap ) {
+               state.bindFramebuffer( 36160, framebuffer );
+               _gl.framebufferTexture2D( 36160, attachment, textureTarget, properties.get( texture ).__webglTexture, 0 );
+               state.bindFramebuffer( 36160, null );
 
-                       uniforms.lightMap.value = material.lightMap;
-                       uniforms.lightMapIntensity.value = material.lightMapIntensity;
+       }
 
-               }
+       // Setup storage for internal depth/stencil buffers and bind to correct framebuffer
+       function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) {
 
-               if ( material.aoMap ) {
+               _gl.bindRenderbuffer( 36161, renderbuffer );
 
-                       uniforms.aoMap.value = material.aoMap;
-                       uniforms.aoMapIntensity.value = material.aoMapIntensity;
+               if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
 
-               }
+                       let glInternalFormat = 33189;
 
-               // 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
+                       if ( isMultisample ) {
 
-               let uvScaleMap;
+                               const depthTexture = renderTarget.depthTexture;
 
-               if ( material.map ) {
+                               if ( depthTexture && depthTexture.isDepthTexture ) {
 
-                       uvScaleMap = material.map;
+                                       if ( depthTexture.type === FloatType ) {
 
-               } else if ( material.specularMap ) {
+                                               glInternalFormat = 36012;
 
-                       uvScaleMap = material.specularMap;
+                                       } else if ( depthTexture.type === UnsignedIntType ) {
 
-               } else if ( material.displacementMap ) {
+                                               glInternalFormat = 33190;
 
-                       uvScaleMap = material.displacementMap;
+                                       }
 
-               } else if ( material.normalMap ) {
+                               }
 
-                       uvScaleMap = material.normalMap;
+                               const samples = getRenderTargetSamples( renderTarget );
 
-               } else if ( material.bumpMap ) {
+                               _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
 
-                       uvScaleMap = material.bumpMap;
+                       } else {
 
-               } else if ( material.roughnessMap ) {
+                               _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
 
-                       uvScaleMap = material.roughnessMap;
+                       }
 
-               } else if ( material.metalnessMap ) {
+                       _gl.framebufferRenderbuffer( 36160, 36096, 36161, renderbuffer );
 
-                       uvScaleMap = material.metalnessMap;
+               } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
 
-               } else if ( material.alphaMap ) {
+                       if ( isMultisample ) {
 
-                       uvScaleMap = material.alphaMap;
+                               const samples = getRenderTargetSamples( renderTarget );
 
-               } else if ( material.emissiveMap ) {
+                               _gl.renderbufferStorageMultisample( 36161, samples, 35056, renderTarget.width, renderTarget.height );
 
-                       uvScaleMap = material.emissiveMap;
+                       } else {
 
-               } else if ( material.clearcoatMap ) {
+                               _gl.renderbufferStorage( 36161, 34041, renderTarget.width, renderTarget.height );
 
-                       uvScaleMap = material.clearcoatMap;
+                       }
 
-               } else if ( material.clearcoatNormalMap ) {
 
-                       uvScaleMap = material.clearcoatNormalMap;
+                       _gl.framebufferRenderbuffer( 36160, 33306, 36161, renderbuffer );
 
-               } else if ( material.clearcoatRoughnessMap ) {
+               } else {
 
-                       uvScaleMap = material.clearcoatRoughnessMap;
+                       // Use the first texture for MRT so far
+                       const texture = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture[ 0 ] : renderTarget.texture;
 
-               }
+                       const glFormat = utils.convert( texture.format );
+                       const glType = utils.convert( texture.type );
+                       const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
 
-               if ( uvScaleMap !== undefined ) {
+                       if ( isMultisample ) {
 
-                       // backwards compatibility
-                       if ( uvScaleMap.isWebGLRenderTarget ) {
+                               const samples = getRenderTargetSamples( renderTarget );
 
-                               uvScaleMap = uvScaleMap.texture;
+                               _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+                       } else {
+
+                               _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
 
                        }
 
-                       if ( uvScaleMap.matrixAutoUpdate === true ) {
+               }
 
-                               uvScaleMap.updateMatrix();
+               _gl.bindRenderbuffer( 36161, null );
 
-                       }
+       }
 
-                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+       // 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' );
 
-               // uv repeat and offset setting priorities for uv2
-               // 1. ao map
-               // 2. light map
+               state.bindFramebuffer( 36160, framebuffer );
 
-               let uv2ScaleMap;
+               if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) {
 
-               if ( material.aoMap ) {
+                       throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' );
 
-                       uv2ScaleMap = material.aoMap;
+               }
 
-               } else if ( material.lightMap ) {
+               // 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 ) {
 
-                       uv2ScaleMap = material.lightMap;
+                       renderTarget.depthTexture.image.width = renderTarget.width;
+                       renderTarget.depthTexture.image.height = renderTarget.height;
+                       renderTarget.depthTexture.needsUpdate = true;
 
                }
 
-               if ( uv2ScaleMap !== undefined ) {
+               setTexture2D( renderTarget.depthTexture, 0 );
 
-                       // backwards compatibility
-                       if ( uv2ScaleMap.isWebGLRenderTarget ) {
+               const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture;
 
-                               uv2ScaleMap = uv2ScaleMap.texture;
+               if ( renderTarget.depthTexture.format === DepthFormat ) {
 
-                       }
+                       _gl.framebufferTexture2D( 36160, 36096, 3553, webglDepthTexture, 0 );
 
-                       if ( uv2ScaleMap.matrixAutoUpdate === true ) {
+               } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) {
 
-                               uv2ScaleMap.updateMatrix();
+                       _gl.framebufferTexture2D( 36160, 33306, 3553, webglDepthTexture, 0 );
 
-                       }
+               } else {
 
-                       uniforms.uv2Transform.value.copy( uv2ScaleMap.matrix );
+                       throw new Error( 'Unknown depthTexture format' );
 
                }
 
        }
 
-       function refreshUniformsLine( uniforms, material ) {
+       // Setup GL resources for a non-texture depth buffer
+       function setupDepthRenderbuffer( renderTarget ) {
 
-               uniforms.diffuse.value.copy( material.color );
-               uniforms.opacity.value = material.opacity;
+               const renderTargetProperties = properties.get( renderTarget );
 
-       }
+               const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
 
-       function refreshUniformsDash( uniforms, material ) {
+               if ( renderTarget.depthTexture ) {
 
-               uniforms.dashSize.value = material.dashSize;
-               uniforms.totalSize.value = material.dashSize + material.gapSize;
-               uniforms.scale.value = material.scale;
+                       if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' );
 
-       }
+                       setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget );
 
-       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 ) {
+               } else {
 
-                       uniforms.map.value = material.map;
+                       if ( isCube ) {
 
-               }
+                               renderTargetProperties.__webglDepthbuffer = [];
 
-               if ( material.alphaMap ) {
+                               for ( let i = 0; i < 6; i ++ ) {
 
-                       uniforms.alphaMap.value = material.alphaMap;
+                                       state.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer[ i ] );
+                                       renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer();
+                                       setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false );
 
-               }
+                               }
 
-               // uv repeat and offset setting priorities
-               // 1. color map
-               // 2. alpha map
+                       } else {
 
-               let uvScaleMap;
+                               state.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer );
+                               renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer();
+                               setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false );
 
-               if ( material.map ) {
+                       }
 
-                       uvScaleMap = material.map;
+               }
 
-               } else if ( material.alphaMap ) {
+               state.bindFramebuffer( 36160, null );
 
-                       uvScaleMap = material.alphaMap;
+       }
 
-               }
+       // Set up GL resources for the render target
+       function setupRenderTarget( renderTarget ) {
 
-               if ( uvScaleMap !== undefined ) {
+               const texture = renderTarget.texture;
 
-                       if ( uvScaleMap.matrixAutoUpdate === true ) {
+               const renderTargetProperties = properties.get( renderTarget );
+               const textureProperties = properties.get( texture );
 
-                               uvScaleMap.updateMatrix();
+               renderTarget.addEventListener( 'dispose', onRenderTargetDispose );
 
-                       }
+               if ( renderTarget.isWebGLMultipleRenderTargets !== true ) {
 
-                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+                       textureProperties.__webglTexture = _gl.createTexture();
+                       textureProperties.__version = texture.version;
+                       info.memory.textures ++;
 
                }
 
-       }
+               const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
+               const isMultipleRenderTargets = ( renderTarget.isWebGLMultipleRenderTargets === true );
+               const isMultisample = ( renderTarget.isWebGLMultisampleRenderTarget === true );
+               const isRenderTarget3D = texture.isDataTexture3D || texture.isDataTexture2DArray;
+               const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2;
 
-       function refreshUniformsSprites( uniforms, material ) {
+               // Handles WebGL2 RGBFormat fallback - #18858
 
-               uniforms.diffuse.value.copy( material.color );
-               uniforms.opacity.value = material.opacity;
-               uniforms.rotation.value = material.rotation;
+               if ( isWebGL2 && texture.format === RGBFormat && ( texture.type === FloatType || texture.type === HalfFloatType ) ) {
 
-               if ( material.map ) {
+                       texture.format = RGBAFormat;
 
-                       uniforms.map.value = material.map;
+                       console.warn( 'THREE.WebGLRenderer: Rendering to textures with RGB format is not supported. Using RGBA format instead.' );
 
                }
 
-               if ( material.alphaMap ) {
-
-                       uniforms.alphaMap.value = material.alphaMap;
+               // Setup framebuffer
 
-               }
+               if ( isCube ) {
 
-               // uv repeat and offset setting priorities
-               // 1. color map
-               // 2. alpha map
+                       renderTargetProperties.__webglFramebuffer = [];
 
-               let uvScaleMap;
+                       for ( let i = 0; i < 6; i ++ ) {
 
-               if ( material.map ) {
+                               renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer();
 
-                       uvScaleMap = material.map;
+                       }
 
-               } else if ( material.alphaMap ) {
+               } else {
 
-                       uvScaleMap = material.alphaMap;
+                       renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();
 
-               }
+                       if ( isMultipleRenderTargets ) {
 
-               if ( uvScaleMap !== undefined ) {
+                               if ( capabilities.drawBuffers ) {
 
-                       if ( uvScaleMap.matrixAutoUpdate === true ) {
+                                       const textures = renderTarget.texture;
 
-                               uvScaleMap.updateMatrix();
+                                       for ( let i = 0, il = textures.length; i < il; i ++ ) {
 
-                       }
+                                               const attachmentProperties = properties.get( textures[ i ] );
 
-                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+                                               if ( attachmentProperties.__webglTexture === undefined ) {
 
-               }
+                                                       attachmentProperties.__webglTexture = _gl.createTexture();
 
-       }
+                                                       info.memory.textures ++;
 
-       function refreshUniformsLambert( uniforms, material ) {
+                                               }
 
-               if ( material.emissiveMap ) {
+                                       }
 
-                       uniforms.emissiveMap.value = material.emissiveMap;
+                               } else {
 
-               }
+                                       console.warn( 'THREE.WebGLRenderer: WebGLMultipleRenderTargets can only be used with WebGL2 or WEBGL_draw_buffers extension.' );
 
-       }
+                               }
 
-       function refreshUniformsPhong( uniforms, material ) {
+                       } else if ( isMultisample ) {
 
-               uniforms.specular.value.copy( material.specular );
-               uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )
+                               if ( isWebGL2 ) {
 
-               if ( material.emissiveMap ) {
+                                       renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer();
+                                       renderTargetProperties.__webglColorRenderbuffer = _gl.createRenderbuffer();
 
-                       uniforms.emissiveMap.value = material.emissiveMap;
+                                       _gl.bindRenderbuffer( 36161, renderTargetProperties.__webglColorRenderbuffer );
 
-               }
+                                       const glFormat = utils.convert( texture.format );
+                                       const glType = utils.convert( texture.type );
+                                       const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
+                                       const samples = getRenderTargetSamples( renderTarget );
+                                       _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
 
-               if ( material.bumpMap ) {
+                                       state.bindFramebuffer( 36160, renderTargetProperties.__webglMultisampledFramebuffer );
+                                       _gl.framebufferRenderbuffer( 36160, 36064, 36161, renderTargetProperties.__webglColorRenderbuffer );
+                                       _gl.bindRenderbuffer( 36161, null );
 
-                       uniforms.bumpMap.value = material.bumpMap;
-                       uniforms.bumpScale.value = material.bumpScale;
-                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+                                       if ( renderTarget.depthBuffer ) {
 
-               }
+                                               renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer();
+                                               setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true );
 
-               if ( material.normalMap ) {
+                                       }
 
-                       uniforms.normalMap.value = material.normalMap;
-                       uniforms.normalScale.value.copy( material.normalScale );
-                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+                                       state.bindFramebuffer( 36160, null );
 
-               }
 
-               if ( material.displacementMap ) {
+                               } else {
 
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
+                                       console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
 
-               }
+                               }
 
-       }
+                       }
 
-       function refreshUniformsToon( uniforms, material ) {
+               }
 
-               if ( material.gradientMap ) {
+               // Setup color buffer
 
-                       uniforms.gradientMap.value = material.gradientMap;
+               if ( isCube ) {
 
-               }
+                       state.bindTexture( 34067, textureProperties.__webglTexture );
+                       setTextureParameters( 34067, texture, supportsMips );
 
-               if ( material.emissiveMap ) {
+                       for ( let i = 0; i < 6; i ++ ) {
 
-                       uniforms.emissiveMap.value = material.emissiveMap;
+                               setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, 36064, 34069 + i );
 
-               }
+                       }
 
-               if ( material.bumpMap ) {
+                       if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
 
-                       uniforms.bumpMap.value = material.bumpMap;
-                       uniforms.bumpScale.value = material.bumpScale;
-                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+                               generateMipmap( 34067, texture, renderTarget.width, renderTarget.height );
 
-               }
+                       }
 
-               if ( material.normalMap ) {
+                       state.unbindTexture();
 
-                       uniforms.normalMap.value = material.normalMap;
-                       uniforms.normalScale.value.copy( material.normalScale );
-                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+               } else if ( isMultipleRenderTargets ) {
 
-               }
+                       const textures = renderTarget.texture;
 
-               if ( material.displacementMap ) {
+                       for ( let i = 0, il = textures.length; i < il; i ++ ) {
 
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
+                               const attachment = textures[ i ];
+                               const attachmentProperties = properties.get( attachment );
 
-               }
+                               state.bindTexture( 3553, attachmentProperties.__webglTexture );
+                               setTextureParameters( 3553, attachment, supportsMips );
+                               setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, 36064 + i, 3553 );
 
-       }
+                               if ( textureNeedsGenerateMipmaps( attachment, supportsMips ) ) {
 
-       function refreshUniformsStandard( uniforms, material ) {
+                                       generateMipmap( 3553, attachment, renderTarget.width, renderTarget.height );
 
-               uniforms.roughness.value = material.roughness;
-               uniforms.metalness.value = material.metalness;
+                               }
 
-               if ( material.roughnessMap ) {
+                       }
 
-                       uniforms.roughnessMap.value = material.roughnessMap;
+                       state.unbindTexture();
 
-               }
+               } else {
 
-               if ( material.metalnessMap ) {
+                       let glTextureType = 3553;
 
-                       uniforms.metalnessMap.value = material.metalnessMap;
+                       if ( isRenderTarget3D ) {
 
-               }
+                               // Render targets containing layers, i.e: Texture 3D and 2d arrays
 
-               if ( material.emissiveMap ) {
+                               if ( isWebGL2 ) {
 
-                       uniforms.emissiveMap.value = material.emissiveMap;
+                                       const isTexture3D = texture.isDataTexture3D;
+                                       glTextureType = isTexture3D ? 32879 : 35866;
 
-               }
+                               } else {
 
-               if ( material.bumpMap ) {
+                                       console.warn( 'THREE.DataTexture3D and THREE.DataTexture2DArray only supported with WebGL2.' );
 
-                       uniforms.bumpMap.value = material.bumpMap;
-                       uniforms.bumpScale.value = material.bumpScale;
-                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+                               }
 
-               }
+                       }
 
-               if ( material.normalMap ) {
+                       state.bindTexture( glTextureType, textureProperties.__webglTexture );
+                       setTextureParameters( glTextureType, texture, supportsMips );
+                       setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, 36064, glTextureType );
 
-                       uniforms.normalMap.value = material.normalMap;
-                       uniforms.normalScale.value.copy( material.normalScale );
-                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+                       if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
 
-               }
+                               generateMipmap( glTextureType, texture, renderTarget.width, renderTarget.height, renderTarget.depth );
 
-               if ( material.displacementMap ) {
+                       }
 
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
+                       state.unbindTexture();
 
                }
 
-               const envMap = properties.get( material ).envMap;
+               // Setup depth and stencil buffers
 
-               if ( envMap ) {
+               if ( renderTarget.depthBuffer ) {
 
-                       //uniforms.envMap.value = material.envMap; // part of uniforms common
-                       uniforms.envMapIntensity.value = material.envMapIntensity;
+                       setupDepthRenderbuffer( renderTarget );
 
                }
 
        }
 
-       function refreshUniformsPhysical( uniforms, material ) {
+       function updateRenderTargetMipmap( renderTarget ) {
 
-               refreshUniformsStandard( uniforms, material );
+               const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2;
 
-               uniforms.reflectivity.value = material.reflectivity; // also part of uniforms common
+               const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ];
 
-               uniforms.clearcoat.value = material.clearcoat;
-               uniforms.clearcoatRoughness.value = material.clearcoatRoughness;
-               if ( material.sheen ) uniforms.sheen.value.copy( material.sheen );
+               for ( let i = 0, il = textures.length; i < il; i ++ ) {
 
-               if ( material.clearcoatMap ) {
+                       const texture = textures[ i ];
 
-                       uniforms.clearcoatMap.value = material.clearcoatMap;
+                       if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
 
-               }
+                               const target = renderTarget.isWebGLCubeRenderTarget ? 34067 : 3553;
+                               const webglTexture = properties.get( texture ).__webglTexture;
 
-               if ( material.clearcoatRoughnessMap ) {
+                               state.bindTexture( target, webglTexture );
+                               generateMipmap( target, texture, renderTarget.width, renderTarget.height );
+                               state.unbindTexture();
 
-                       uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap;
+                       }
 
                }
 
-               if ( material.clearcoatNormalMap ) {
-
-                       uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale );
-                       uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap;
+       }
 
-                       if ( material.side === BackSide ) {
+       function updateMultisampleRenderTarget( renderTarget ) {
 
-                               uniforms.clearcoatNormalScale.value.negate();
+               if ( renderTarget.isWebGLMultisampleRenderTarget ) {
 
-                       }
+                       if ( isWebGL2 ) {
 
-               }
+                               const width = renderTarget.width;
+                               const height = renderTarget.height;
+                               let mask = 16384;
 
-               uniforms.transmission.value = material.transmission;
+                               if ( renderTarget.depthBuffer ) mask |= 256;
+                               if ( renderTarget.stencilBuffer ) mask |= 1024;
 
-               if ( material.transmissionMap ) {
+                               const renderTargetProperties = properties.get( renderTarget );
 
-                       uniforms.transmissionMap.value = material.transmissionMap;
+                               state.bindFramebuffer( 36008, renderTargetProperties.__webglMultisampledFramebuffer );
+                               state.bindFramebuffer( 36009, renderTargetProperties.__webglFramebuffer );
 
-               }
+                               _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, 9728 );
 
-       }
+                               state.bindFramebuffer( 36008, null );
+                               state.bindFramebuffer( 36009, renderTargetProperties.__webglMultisampledFramebuffer );
 
-       function refreshUniformsMatcap( uniforms, material ) {
+                       } else {
 
-               if ( material.matcap ) {
+                               console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
 
-                       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;
+       function getRenderTargetSamples( renderTarget ) {
 
-               }
+               return ( isWebGL2 && renderTarget.isWebGLMultisampleRenderTarget ) ?
+                       Math.min( maxSamples, renderTarget.samples ) : 0;
 
-               if ( material.normalMap ) {
+       }
 
-                       uniforms.normalMap.value = material.normalMap;
-                       uniforms.normalScale.value.copy( material.normalScale );
-                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+       function updateVideoTexture( texture ) {
 
-               }
+               const frame = info.render.frame;
 
-               if ( material.displacementMap ) {
+               // Check the last frame we updated the VideoTexture
 
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
+               if ( _videoTextures.get( texture ) !== frame ) {
+
+                       _videoTextures.set( texture, frame );
+                       texture.update();
 
                }
 
        }
 
-       function refreshUniformsDepth( uniforms, material ) {
+       // backwards compatibility
 
-               if ( material.displacementMap ) {
+       let warnedTexture2D = false;
+       let warnedTextureCube = false;
 
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
+       function safeSetTexture2D( texture, slot ) {
 
-               }
+               if ( texture && texture.isWebGLRenderTarget ) {
 
-       }
+                       if ( warnedTexture2D === false ) {
 
-       function refreshUniformsDistance( uniforms, material ) {
+                               console.warn( 'THREE.WebGLTextures.safeSetTexture2D: don\'t use render targets as textures. Use their .texture property instead.' );
+                               warnedTexture2D = true;
 
-               if ( material.displacementMap ) {
+                       }
 
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
+                       texture = texture.texture;
 
                }
 
-               uniforms.referencePosition.value.copy( material.referencePosition );
-               uniforms.nearDistance.value = material.nearDistance;
-               uniforms.farDistance.value = material.farDistance;
+               setTexture2D( texture, slot );
 
        }
 
-       function refreshUniformsNormal( uniforms, material ) {
+       function safeSetTextureCube( texture, slot ) {
 
-               if ( material.bumpMap ) {
+               if ( texture && texture.isWebGLCubeRenderTarget ) {
 
-                       uniforms.bumpMap.value = material.bumpMap;
-                       uniforms.bumpScale.value = material.bumpScale;
-                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+                       if ( warnedTextureCube === false ) {
 
-               }
+                               console.warn( 'THREE.WebGLTextures.safeSetTextureCube: don\'t use cube render targets as textures. Use their .texture property instead.' );
+                               warnedTextureCube = true;
 
-               if ( material.normalMap ) {
+                       }
 
-                       uniforms.normalMap.value = material.normalMap;
-                       uniforms.normalScale.value.copy( material.normalScale );
-                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+                       texture = texture.texture;
 
                }
 
-               if ( material.displacementMap ) {
-
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
 
-               }
+               setTextureCube( texture, slot );
 
        }
 
-       return {
-               refreshFogUniforms: refreshFogUniforms,
-               refreshMaterialUniforms: refreshMaterialUniforms
-       };
+       //
 
-}
+       this.allocateTextureUnit = allocateTextureUnit;
+       this.resetTextureUnits = resetTextureUnits;
 
-function createCanvasElement() {
+       this.setTexture2D = setTexture2D;
+       this.setTexture2DArray = setTexture2DArray;
+       this.setTexture3D = setTexture3D;
+       this.setTextureCube = setTextureCube;
+       this.setupRenderTarget = setupRenderTarget;
+       this.updateRenderTargetMipmap = updateRenderTargetMipmap;
+       this.updateMultisampleRenderTarget = updateMultisampleRenderTarget;
 
-       const canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
-       canvas.style.display = 'block';
-       return canvas;
+       this.safeSetTexture2D = safeSetTexture2D;
+       this.safeSetTextureCube = safeSetTextureCube;
 
 }
 
-function WebGLRenderer( parameters ) {
+function WebGLUtils( gl, extensions, capabilities ) {
 
-       parameters = parameters || {};
+       const isWebGL2 = capabilities.isWebGL2;
 
-       const _canvas = parameters.canvas !== undefined ? parameters.canvas : createCanvasElement(),
-               _context = parameters.context !== undefined ? parameters.context : null,
+       function convert( p ) {
 
-               _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 extension;
 
-       let currentRenderList = null;
-       let currentRenderState = null;
+               if ( p === UnsignedByteType ) return 5121;
+               if ( p === UnsignedShort4444Type ) return 32819;
+               if ( p === UnsignedShort5551Type ) return 32820;
+               if ( p === UnsignedShort565Type ) return 33635;
 
-       // 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.
+               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;
 
-       const renderStateStack = [];
+               if ( p === HalfFloatType ) {
 
-       // public properties
+                       if ( isWebGL2 ) return 5131;
 
-       this.domElement = _canvas;
+                       extension = extensions.get( 'OES_texture_half_float' );
 
-       // Debug configuration container
-       this.debug = {
+                       if ( extension !== null ) {
 
-               /**
-                * Enables error checking and reporting when shader programs are being compiled
-                * @type {boolean}
-                */
-               checkShaderErrors: true
-       };
+                               return extension.HALF_FLOAT_OES;
 
-       // clearing
+                       } else {
 
-       this.autoClear = true;
-       this.autoClearColor = true;
-       this.autoClearDepth = true;
-       this.autoClearStencil = true;
+                               return null;
 
-       // scene graph
+                       }
 
-       this.sortObjects = true;
+               }
 
-       // user-defined clipping
+               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;
 
-       this.clippingPlanes = [];
-       this.localClippingEnabled = false;
+               // WebGL2 formats.
 
-       // physically based shading
+               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;
 
-       this.gammaFactor = 2.0; // for backwards compatibility
-       this.outputEncoding = LinearEncoding;
+               if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format ||
+                       p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) {
 
-       // physical lights
+                       extension = extensions.get( 'WEBGL_compressed_texture_s3tc' );
 
-       this.physicallyCorrectLights = false;
+                       if ( extension !== null ) {
 
-       // tone mapping
+                               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;
 
-       this.toneMapping = NoToneMapping;
-       this.toneMappingExposure = 1.0;
+                       } else {
 
-       // morphs
+                               return null;
 
-       this.maxMorphTargets = 8;
-       this.maxMorphNormals = 4;
+                       }
 
-       // internal properties
+               }
 
-       const _this = this;
+               if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format ||
+                       p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {
 
-       let _isContextLost = false;
+                       extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' );
 
-       // internal state cache
+                       if ( extension !== null ) {
 
-       let _framebuffer = 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;
 
-       let _currentActiveCubeFace = 0;
-       let _currentActiveMipmapLevel = 0;
-       let _currentRenderTarget = null;
-       let _currentFramebuffer = null;
-       let _currentMaterialId = - 1;
+                       } else {
 
-       let _currentCamera = null;
+                               return null;
 
-       const _currentViewport = new Vector4();
-       const _currentScissor = new Vector4();
-       let _currentScissorTest = null;
+                       }
 
-       //
+               }
 
-       let _width = _canvas.width;
-       let _height = _canvas.height;
+               if ( p === RGB_ETC1_Format ) {
 
-       let _pixelRatio = 1;
-       let _opaqueSort = null;
-       let _transparentSort = null;
+                       extension = extensions.get( 'WEBGL_compressed_texture_etc1' );
 
-       const _viewport = new Vector4( 0, 0, _width, _height );
-       const _scissor = new Vector4( 0, 0, _width, _height );
-       let _scissorTest = false;
+                       if ( extension !== null ) {
 
-       // frustum
+                               return extension.COMPRESSED_RGB_ETC1_WEBGL;
 
-       const _frustum = new Frustum();
+                       } else {
 
-       // clipping
+                               return null;
 
-       let _clippingEnabled = false;
-       let _localClippingEnabled = false;
+                       }
 
-       // camera matrices cache
+               }
 
-       const _projScreenMatrix = new Matrix4();
+               if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) {
 
-       const _vector3 = new Vector3();
+                       extension = extensions.get( 'WEBGL_compressed_texture_etc' );
 
-       const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true };
+                       if ( extension !== null ) {
 
-       function getTargetPixelRatio() {
+                               if ( p === RGB_ETC2_Format ) return extension.COMPRESSED_RGB8_ETC2;
+                               if ( p === RGBA_ETC2_EAC_Format ) return extension.COMPRESSED_RGBA8_ETC2_EAC;
 
-               return _currentRenderTarget === null ? _pixelRatio : 1;
+                       }
 
-       }
+               }
 
-       // initialize
+               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 ) {
 
-       let _gl = _context;
+                       extension = extensions.get( 'WEBGL_compressed_texture_astc' );
 
-       function getContext( contextNames, contextAttributes ) {
+                       if ( extension !== null ) {
 
-               for ( let i = 0; i < contextNames.length; i ++ ) {
+                               // TODO Complete?
 
-                       const contextName = contextNames[ i ];
-                       const context = _canvas.getContext( contextName, contextAttributes );
-                       if ( context !== null ) return context;
+                               return p;
+
+                       } else {
+
+                               return null;
+
+                       }
 
                }
 
-               return null;
+               if ( p === RGBA_BPTC_Format ) {
 
-       }
+                       extension = extensions.get( 'EXT_texture_compression_bptc' );
 
-       try {
+                       if ( extension !== null ) {
 
-               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 ) {
+                               // TODO Complete?
 
-                       const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ];
+                               return p;
 
-                       if ( _this.isWebGL1Renderer === true ) {
+                       } else {
 
-                               contextNames.shift();
+                               return null;
 
                        }
 
-                       _gl = getContext( contextNames, contextAttributes );
+               }
 
-                       if ( _gl === null ) {
+               if ( p === UnsignedInt248Type ) {
 
-                               if ( getContext( contextNames ) ) {
+                       if ( isWebGL2 ) return 34042;
 
-                                       throw new Error( 'Error creating WebGL context with your selected attributes.' );
+                       extension = extensions.get( 'WEBGL_depth_texture' );
 
-                               } else {
+                       if ( extension !== null ) {
 
-                                       throw new Error( 'Error creating WebGL context.' );
+                               return extension.UNSIGNED_INT_24_8_WEBGL;
 
-                               }
+                       } else {
+
+                               return null;
 
                        }
 
                }
 
-               // Some experimental-webgl implementations do not have getShaderPrecisionFormat
-
-               if ( _gl.getShaderPrecisionFormat === undefined ) {
+       }
 
-                       _gl.getShaderPrecisionFormat = function () {
+       return { convert: convert };
 
-                               return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 };
+}
 
-                       };
+class ArrayCamera extends PerspectiveCamera {
 
-               }
+       constructor( array = [] ) {
 
-       } catch ( error ) {
+               super();
 
-               console.error( 'THREE.WebGLRenderer: ' + error.message );
-               throw error;
+               this.cameras = array;
 
        }
 
-       let extensions, capabilities, state, info;
-       let properties, textures, cubemaps, attributes, geometries, objects;
-       let programCache, materials, renderLists, renderStates, clipping;
+}
 
-       let background, morphtargets, bufferRenderer, indexedBufferRenderer;
+ArrayCamera.prototype.isArrayCamera = true;
 
-       let utils, bindingStates;
+class Group extends Object3D {
 
-       function initGLContext() {
+       constructor() {
 
-               extensions = new WebGLExtensions( _gl );
+               super();
 
-               capabilities = new WebGLCapabilities( _gl, extensions, parameters );
+               this.type = 'Group';
 
-               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() );
+Group.prototype.isGroup = true;
 
-               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 );
+const _moveEvent = { type: 'move' };
 
-               bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities );
-               indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities );
+class WebXRController {
 
-               info.programs = programCache.programs;
+       constructor() {
 
-               _this.capabilities = capabilities;
-               _this.extensions = extensions;
-               _this.properties = properties;
-               _this.renderLists = renderLists;
-               _this.state = state;
-               _this.info = info;
+               this._targetRay = null;
+               this._grip = null;
+               this._hand = null;
 
        }
 
-       initGLContext();
+       getHandSpace() {
 
-       // xr
+               if ( this._hand === null ) {
 
-       const xr = new WebXRManager( _this, _gl );
+                       this._hand = new Group();
+                       this._hand.matrixAutoUpdate = false;
+                       this._hand.visible = false;
 
-       this.xr = xr;
+                       this._hand.joints = {};
+                       this._hand.inputState = { pinching: false };
 
-       // shadow map
+               }
 
-       const shadowMap = new WebGLShadowMap( _this, objects, capabilities.maxTextureSize );
+               return this._hand;
 
-       this.shadowMap = shadowMap;
+       }
 
-       // API
+       getTargetRaySpace() {
 
-       this.getContext = function () {
+               if ( this._targetRay === null ) {
 
-               return _gl;
+                       this._targetRay = new Group();
+                       this._targetRay.matrixAutoUpdate = false;
+                       this._targetRay.visible = false;
+                       this._targetRay.hasLinearVelocity = false;
+                       this._targetRay.linearVelocity = new Vector3();
+                       this._targetRay.hasAngularVelocity = false;
+                       this._targetRay.angularVelocity = new Vector3();
 
-       };
+               }
 
-       this.getContextAttributes = function () {
+               return this._targetRay;
 
-               return _gl.getContextAttributes();
+       }
 
-       };
+       getGripSpace() {
 
-       this.forceContextLoss = function () {
+               if ( this._grip === null ) {
 
-               const extension = extensions.get( 'WEBGL_lose_context' );
-               if ( extension ) extension.loseContext();
+                       this._grip = new Group();
+                       this._grip.matrixAutoUpdate = false;
+                       this._grip.visible = false;
+                       this._grip.hasLinearVelocity = false;
+                       this._grip.linearVelocity = new Vector3();
+                       this._grip.hasAngularVelocity = false;
+                       this._grip.angularVelocity = new Vector3();
 
-       };
+               }
 
-       this.forceContextRestore = function () {
+               return this._grip;
 
-               const extension = extensions.get( 'WEBGL_lose_context' );
-               if ( extension ) extension.restoreContext();
+       }
 
-       };
+       dispatchEvent( event ) {
 
-       this.getPixelRatio = function () {
+               if ( this._targetRay !== null ) {
 
-               return _pixelRatio;
+                       this._targetRay.dispatchEvent( event );
 
-       };
+               }
 
-       this.setPixelRatio = function ( value ) {
+               if ( this._grip !== null ) {
 
-               if ( value === undefined ) return;
+                       this._grip.dispatchEvent( event );
 
-               _pixelRatio = value;
+               }
 
-               this.setSize( _width, _height, false );
+               if ( this._hand !== null ) {
 
-       };
+                       this._hand.dispatchEvent( event );
 
-       this.getSize = function ( target ) {
+               }
 
-               if ( target === undefined ) {
+               return this;
+
+       }
+
+       disconnect( inputSource ) {
 
-                       console.warn( 'WebGLRenderer: .getsize() now requires a Vector2 as an argument' );
+               this.dispatchEvent( { type: 'disconnected', data: inputSource } );
+
+               if ( this._targetRay !== null ) {
 
-                       target = new Vector2();
+                       this._targetRay.visible = false;
 
                }
 
-               return target.set( _width, _height );
+               if ( this._grip !== null ) {
 
-       };
+                       this._grip.visible = false;
 
-       this.setSize = function ( width, height, updateStyle ) {
+               }
 
-               if ( xr.isPresenting ) {
+               if ( this._hand !== null ) {
 
-                       console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' );
-                       return;
+                       this._hand.visible = false;
 
                }
 
-               _width = width;
-               _height = height;
+               return this;
 
-               _canvas.width = Math.floor( width * _pixelRatio );
-               _canvas.height = Math.floor( height * _pixelRatio );
+       }
 
-               if ( updateStyle !== false ) {
+       update( inputSource, frame, referenceSpace ) {
 
-                       _canvas.style.width = width + 'px';
-                       _canvas.style.height = height + 'px';
+               let inputPose = null;
+               let gripPose = null;
+               let handPose = null;
 
-               }
+               const targetRay = this._targetRay;
+               const grip = this._grip;
+               const hand = this._hand;
 
-               this.setViewport( 0, 0, width, height );
+               if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) {
 
-       };
+                       if ( targetRay !== null ) {
 
-       this.getDrawingBufferSize = function ( target ) {
+                               inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace );
 
-               if ( target === undefined ) {
+                               if ( inputPose !== null ) {
 
-                       console.warn( 'WebGLRenderer: .getdrawingBufferSize() now requires a Vector2 as an argument' );
+                                       targetRay.matrix.fromArray( inputPose.transform.matrix );
+                                       targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale );
 
-                       target = new Vector2();
+                                       if ( inputPose.linearVelocity ) {
 
-               }
+                                               targetRay.hasLinearVelocity = true;
+                                               targetRay.linearVelocity.copy( inputPose.linearVelocity );
 
-               return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor();
+                                       } else {
 
-       };
+                                               targetRay.hasLinearVelocity = false;
 
-       this.setDrawingBufferSize = function ( width, height, pixelRatio ) {
+                                       }
 
-               _width = width;
-               _height = height;
+                                       if ( inputPose.angularVelocity ) {
 
-               _pixelRatio = pixelRatio;
+                                               targetRay.hasAngularVelocity = true;
+                                               targetRay.angularVelocity.copy( inputPose.angularVelocity );
 
-               _canvas.width = Math.floor( width * pixelRatio );
-               _canvas.height = Math.floor( height * pixelRatio );
+                                       } else {
 
-               this.setViewport( 0, 0, width, height );
+                                               targetRay.hasAngularVelocity = false;
 
-       };
+                                       }
 
-       this.getCurrentViewport = function ( target ) {
+                                       this.dispatchEvent( _moveEvent );
 
-               if ( target === undefined ) {
+                               }
 
-                       console.warn( 'WebGLRenderer: .getCurrentViewport() now requires a Vector4 as an argument' );
+                       }
 
-                       target = new Vector4();
+                       if ( hand && inputSource.hand ) {
 
-               }
+                               handPose = true;
 
-               return target.copy( _currentViewport );
+                               for ( const inputjoint of inputSource.hand.values() ) {
 
-       };
+                                       // Update the joints groups with the XRJoint poses
+                                       const jointPose = frame.getJointPose( inputjoint, referenceSpace );
 
-       this.getViewport = function ( target ) {
+                                       if ( hand.joints[ inputjoint.jointName ] === undefined ) {
 
-               return target.copy( _viewport );
+                                               // 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 );
 
-       };
+                                       }
 
-       this.setViewport = function ( x, y, width, height ) {
+                                       const joint = hand.joints[ inputjoint.jointName ];
 
-               if ( x.isVector4 ) {
+                                       if ( jointPose !== null ) {
 
-                       _viewport.set( x.x, x.y, x.z, x.w );
+                                               joint.matrix.fromArray( jointPose.transform.matrix );
+                                               joint.matrix.decompose( joint.position, joint.rotation, joint.scale );
+                                               joint.jointRadius = jointPose.radius;
 
-               } else {
+                                       }
 
-                       _viewport.set( x, y, width, height );
+                                       joint.visible = jointPose !== null;
 
-               }
+                               }
 
-               state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() );
+                               // Custom events
 
-       };
+                               // Check pinchz
+                               const indexTip = hand.joints[ 'index-finger-tip' ];
+                               const thumbTip = hand.joints[ 'thumb-tip' ];
+                               const distance = indexTip.position.distanceTo( thumbTip.position );
 
-       this.getScissor = function ( target ) {
+                               const distanceToPinch = 0.02;
+                               const threshold = 0.005;
 
-               return target.copy( _scissor );
+                               if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) {
 
-       };
+                                       hand.inputState.pinching = false;
+                                       this.dispatchEvent( {
+                                               type: 'pinchend',
+                                               handedness: inputSource.handedness,
+                                               target: this
+                                       } );
 
-       this.setScissor = function ( x, y, width, height ) {
+                               } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) {
 
-               if ( x.isVector4 ) {
+                                       hand.inputState.pinching = true;
+                                       this.dispatchEvent( {
+                                               type: 'pinchstart',
+                                               handedness: inputSource.handedness,
+                                               target: this
+                                       } );
 
-                       _scissor.set( x.x, x.y, x.z, x.w );
+                               }
 
-               } else {
+                       } else {
 
-                       _scissor.set( x, y, width, height );
+                               if ( grip !== null && inputSource.gripSpace ) {
 
-               }
+                                       gripPose = frame.getPose( inputSource.gripSpace, referenceSpace );
 
-               state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() );
+                                       if ( gripPose !== null ) {
 
-       };
+                                               grip.matrix.fromArray( gripPose.transform.matrix );
+                                               grip.matrix.decompose( grip.position, grip.rotation, grip.scale );
 
-       this.getScissorTest = function () {
+                                               if ( gripPose.linearVelocity ) {
 
-               return _scissorTest;
+                                                       grip.hasLinearVelocity = true;
+                                                       grip.linearVelocity.copy( gripPose.linearVelocity );
 
-       };
+                                               } else {
 
-       this.setScissorTest = function ( boolean ) {
+                                                       grip.hasLinearVelocity = false;
 
-               state.setScissorTest( _scissorTest = boolean );
+                                               }
 
-       };
+                                               if ( gripPose.angularVelocity ) {
 
-       this.setOpaqueSort = function ( method ) {
+                                                       grip.hasAngularVelocity = true;
+                                                       grip.angularVelocity.copy( gripPose.angularVelocity );
 
-               _opaqueSort = method;
+                                               } else {
 
-       };
+                                                       grip.hasAngularVelocity = false;
 
-       this.setTransparentSort = function ( method ) {
+                                               }
 
-               _transparentSort = method;
+                                       }
 
-       };
+                               }
 
-       // Clearing
+                       }
 
-       this.getClearColor = function ( target ) {
+               }
+
+               if ( targetRay !== null ) {
 
-               if ( target === undefined ) {
+                       targetRay.visible = ( inputPose !== null );
+
+               }
 
-                       console.warn( 'WebGLRenderer: .getClearColor() now requires a Color as an argument' );
+               if ( grip !== null ) {
 
-                       target = new Color();
+                       grip.visible = ( gripPose !== null );
 
                }
 
-               return target.copy( background.getClearColor() );
+               if ( hand !== null ) {
 
-       };
+                       hand.visible = ( handPose !== null );
 
-       this.setClearColor = function () {
+               }
 
-               background.setClearColor.apply( background, arguments );
+               return this;
 
-       };
+       }
 
-       this.getClearAlpha = function () {
+}
 
-               return background.getClearAlpha();
+class WebXRManager extends EventDispatcher {
 
-       };
+       constructor( renderer, gl ) {
 
-       this.setClearAlpha = function () {
+               super();
 
-               background.setClearAlpha.apply( background, arguments );
+               const scope = this;
+               const state = renderer.state;
+
+               let session = null;
+               let framebufferScaleFactor = 1.0;
+
+               let referenceSpace = null;
+               let referenceSpaceType = 'local-floor';
+
+               let pose = null;
+               let glBinding = null;
+               let glFramebuffer = null;
+               let glProjLayer = null;
+               let glBaseLayer = null;
+               let isMultisample = false;
+               let glMultisampledFramebuffer = null;
+               let glColorRenderbuffer = null;
+               let glDepthRenderbuffer = null;
+               let xrFrame = null;
+               let depthStyle = null;
+               let clearStyle = null;
+
+               const controllers = [];
+               const inputSourcesMap = new Map();
 
-       };
+               //
 
-       this.clear = function ( color, depth, stencil ) {
+               const cameraL = new PerspectiveCamera();
+               cameraL.layers.enable( 1 );
+               cameraL.viewport = new Vector4();
 
-               let bits = 0;
+               const cameraR = new PerspectiveCamera();
+               cameraR.layers.enable( 2 );
+               cameraR.viewport = new Vector4();
 
-               if ( color === undefined || color ) bits |= 16384;
-               if ( depth === undefined || depth ) bits |= 256;
-               if ( stencil === undefined || stencil ) bits |= 1024;
+               const cameras = [ cameraL, cameraR ];
 
-               _gl.clear( bits );
+               const cameraVR = new ArrayCamera();
+               cameraVR.layers.enable( 1 );
+               cameraVR.layers.enable( 2 );
 
-       };
+               let _currentDepthNear = null;
+               let _currentDepthFar = null;
 
-       this.clearColor = function () {
+               //
 
-               this.clear( true, false, false );
+               this.cameraAutoUpdate = true;
+               this.enabled = false;
 
-       };
+               this.isPresenting = false;
 
-       this.clearDepth = function () {
+               this.getController = function ( index ) {
 
-               this.clear( false, true, false );
+                       let controller = controllers[ index ];
 
-       };
+                       if ( controller === undefined ) {
 
-       this.clearStencil = function () {
+                               controller = new WebXRController();
+                               controllers[ index ] = controller;
 
-               this.clear( false, false, true );
+                       }
 
-       };
+                       return controller.getTargetRaySpace();
 
-       //
+               };
 
-       this.dispose = function () {
+               this.getControllerGrip = function ( index ) {
 
-               _canvas.removeEventListener( 'webglcontextlost', onContextLost, false );
-               _canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false );
+                       let controller = controllers[ index ];
 
-               renderLists.dispose();
-               renderStates.dispose();
-               properties.dispose();
-               cubemaps.dispose();
-               objects.dispose();
-               bindingStates.dispose();
+                       if ( controller === undefined ) {
 
-               xr.dispose();
+                               controller = new WebXRController();
+                               controllers[ index ] = controller;
 
-               animation.stop();
+                       }
 
-       };
+                       return controller.getGripSpace();
 
-       // Events
+               };
 
-       function onContextLost( event ) {
+               this.getHand = function ( index ) {
 
-               event.preventDefault();
+                       let controller = controllers[ index ];
 
-               console.log( 'THREE.WebGLRenderer: Context Lost.' );
+                       if ( controller === undefined ) {
 
-               _isContextLost = true;
+                               controller = new WebXRController();
+                               controllers[ index ] = controller;
 
-       }
+                       }
 
-       function onContextRestore( /* event */ ) {
+                       return controller.getHandSpace();
 
-               console.log( 'THREE.WebGLRenderer: Context Restored.' );
+               };
 
-               _isContextLost = false;
+               //
 
-               initGLContext();
+               function onSessionEvent( event ) {
 
-       }
+                       const controller = inputSourcesMap.get( event.inputSource );
 
-       function onMaterialDispose( event ) {
+                       if ( controller ) {
 
-               const material = event.target;
+                               controller.dispatchEvent( { type: event.type, data: event.inputSource } );
 
-               material.removeEventListener( 'dispose', onMaterialDispose );
+                       }
 
-               deallocateMaterial( material );
+               }
 
-       }
+               function onSessionEnd() {
 
-       // Buffer deallocation
+                       inputSourcesMap.forEach( function ( controller, inputSource ) {
 
-       function deallocateMaterial( material ) {
+                               controller.disconnect( inputSource );
+
+                       } );
 
-               releaseMaterialProgramReference( material );
+                       inputSourcesMap.clear();
 
-               properties.remove( material );
+                       _currentDepthNear = null;
+                       _currentDepthFar = null;
 
-       }
+                       // restore framebuffer/rendering state
+
+                       state.bindXRFramebuffer( null );
+                       renderer.setRenderTarget( renderer.getRenderTarget() );
 
+                       if ( glFramebuffer ) gl.deleteFramebuffer( glFramebuffer );
+                       if ( glMultisampledFramebuffer ) gl.deleteFramebuffer( glMultisampledFramebuffer );
+                       if ( glColorRenderbuffer ) gl.deleteRenderbuffer( glColorRenderbuffer );
+                       if ( glDepthRenderbuffer ) gl.deleteRenderbuffer( glDepthRenderbuffer );
+                       glFramebuffer = null;
+                       glMultisampledFramebuffer = null;
+                       glColorRenderbuffer = null;
+                       glDepthRenderbuffer = null;
+                       glBaseLayer = null;
+                       glProjLayer = null;
+                       glBinding = null;
+                       session = null;
 
-       function releaseMaterialProgramReference( material ) {
+                       //
 
-               const programInfo = properties.get( material ).program;
+                       animation.stop();
 
-               if ( programInfo !== undefined ) {
+                       scope.isPresenting = false;
 
-                       programCache.releaseProgram( programInfo );
+                       scope.dispatchEvent( { type: 'sessionend' } );
 
                }
 
-       }
+               this.setFramebufferScaleFactor = function ( value ) {
 
-       // Buffer rendering
+                       framebufferScaleFactor = value;
 
-       function renderObjectImmediate( object, program ) {
+                       if ( scope.isPresenting === true ) {
 
-               object.render( function ( object ) {
+                               console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' );
 
-                       _this.renderBufferImmediate( object, program );
+                       }
 
-               } );
+               };
 
-       }
+               this.setReferenceSpaceType = function ( value ) {
 
-       this.renderBufferImmediate = function ( object, program ) {
+                       referenceSpaceType = value;
 
-               bindingStates.initAttributes();
+                       if ( scope.isPresenting === true ) {
 
-               const buffers = properties.get( object );
+                               console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' );
 
-               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 ) {
+               this.getReferenceSpace = function () {
 
-                       _gl.bindBuffer( 34962, buffers.position );
-                       _gl.bufferData( 34962, object.positionArray, 35048 );
+                       return referenceSpace;
 
-                       bindingStates.enableAttribute( programAttributes.position );
-                       _gl.vertexAttribPointer( programAttributes.position, 3, 5126, false, 0, 0 );
+               };
 
-               }
+               this.getBaseLayer = function () {
 
-               if ( object.hasNormals ) {
+                       return glProjLayer !== null ? glProjLayer : glBaseLayer;
 
-                       _gl.bindBuffer( 34962, buffers.normal );
-                       _gl.bufferData( 34962, object.normalArray, 35048 );
+               };
 
-                       bindingStates.enableAttribute( programAttributes.normal );
-                       _gl.vertexAttribPointer( programAttributes.normal, 3, 5126, false, 0, 0 );
+               this.getBinding = function () {
 
-               }
+                       return glBinding;
 
-               if ( object.hasUvs ) {
+               };
 
-                       _gl.bindBuffer( 34962, buffers.uv );
-                       _gl.bufferData( 34962, object.uvArray, 35048 );
+               this.getFrame = function () {
 
-                       bindingStates.enableAttribute( programAttributes.uv );
-                       _gl.vertexAttribPointer( programAttributes.uv, 2, 5126, false, 0, 0 );
+                       return xrFrame;
 
-               }
+               };
 
-               if ( object.hasColors ) {
+               this.getSession = function () {
 
-                       _gl.bindBuffer( 34962, buffers.color );
-                       _gl.bufferData( 34962, object.colorArray, 35048 );
+                       return session;
 
-                       bindingStates.enableAttribute( programAttributes.color );
-                       _gl.vertexAttribPointer( programAttributes.color, 3, 5126, false, 0, 0 );
+               };
 
-               }
+               this.setSession = async function ( value ) {
 
-               bindingStates.disableUnusedAttributes();
+                       session = value;
 
-               _gl.drawArrays( 4, 0, object.count );
+                       if ( session !== null ) {
 
-               object.count = 0;
+                               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();
 
-       this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) {
+                               if ( attributes.xrCompatible !== true ) {
 
-               if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null)
+                                       await gl.makeXRCompatible();
 
-               const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );
+                               }
 
-               const program = setProgram( camera, scene, material, object );
+                               if ( session.renderState.layers === undefined ) {
 
-               state.setMaterial( material, frontFaceCW );
+                                       const layerInit = {
+                                               antialias: attributes.antialias,
+                                               alpha: attributes.alpha,
+                                               depth: attributes.depth,
+                                               stencil: attributes.stencil,
+                                               framebufferScaleFactor: framebufferScaleFactor
+                                       };
 
-               //
+                                       glBaseLayer = new XRWebGLLayer( session, gl, layerInit );
 
-               let index = geometry.index;
-               const position = geometry.attributes.position;
+                                       session.updateRenderState( { baseLayer: glBaseLayer } );
 
-               //
+                               } else if ( gl instanceof WebGLRenderingContext ) {
 
-               if ( index === null ) {
+                                       // Use old style webgl layer because we can't use MSAA
+                                       // WebGL2 support.
 
-                       if ( position === undefined || position.count === 0 ) return;
+                                       const layerInit = {
+                                               antialias: true,
+                                               alpha: attributes.alpha,
+                                               depth: attributes.depth,
+                                               stencil: attributes.stencil,
+                                               framebufferScaleFactor: framebufferScaleFactor
+                                       };
 
-               } else if ( index.count === 0 ) {
+                                       glBaseLayer = new XRWebGLLayer( session, gl, layerInit );
 
-                       return;
+                                       session.updateRenderState( { layers: [ glBaseLayer ] } );
 
-               }
+                               } else {
 
-               //
+                                       isMultisample = attributes.antialias;
+                                       let depthFormat = null;
 
-               let rangeFactor = 1;
 
-               if ( material.wireframe === true ) {
+                                       if ( attributes.depth ) {
 
-                       index = geometries.getWireframeAttribute( geometry );
-                       rangeFactor = 2;
+                                               clearStyle = 256;
 
-               }
+                                               if ( attributes.stencil ) clearStyle |= 1024;
 
-               if ( material.morphTargets || material.morphNormals ) {
+                                               depthStyle = attributes.stencil ? 33306 : 36096;
+                                               depthFormat = attributes.stencil ? 35056 : 33190;
 
-                       morphtargets.update( object, geometry, material, program );
+                                       }
 
-               }
+                                       const projectionlayerInit = {
+                                               colorFormat: attributes.alpha ? 32856 : 32849,
+                                               depthFormat: depthFormat,
+                                               scaleFactor: framebufferScaleFactor
+                                       };
 
-               bindingStates.setup( object, material, program, geometry, index );
+                                       glBinding = new XRWebGLBinding( session, gl );
 
-               let attribute;
-               let renderer = bufferRenderer;
+                                       glProjLayer = glBinding.createProjectionLayer( projectionlayerInit );
 
-               if ( index !== null ) {
+                                       glFramebuffer = gl.createFramebuffer();
 
-                       attribute = attributes.get( index );
+                                       session.updateRenderState( { layers: [ glProjLayer ] } );
 
-                       renderer = indexedBufferRenderer;
-                       renderer.setIndex( attribute );
+                                       if ( isMultisample ) {
 
-               }
+                                               glMultisampledFramebuffer = gl.createFramebuffer();
+                                               glColorRenderbuffer = gl.createRenderbuffer();
+                                               gl.bindRenderbuffer( 36161, glColorRenderbuffer );
+                                               gl.renderbufferStorageMultisample(
+                                                       36161,
+                                                       4,
+                                                       32856,
+                                                       glProjLayer.textureWidth,
+                                                       glProjLayer.textureHeight );
+                                               state.bindFramebuffer( 36160, glMultisampledFramebuffer );
+                                               gl.framebufferRenderbuffer( 36160, 36064, 36161, glColorRenderbuffer );
+                                               gl.bindRenderbuffer( 36161, null );
 
-               //
+                                               if ( depthFormat !== null ) {
 
-               const dataCount = ( index !== null ) ? index.count : position.count;
+                                                       glDepthRenderbuffer = gl.createRenderbuffer();
+                                                       gl.bindRenderbuffer( 36161, glDepthRenderbuffer );
+                                                       gl.renderbufferStorageMultisample( 36161, 4, depthFormat, glProjLayer.textureWidth, glProjLayer.textureHeight );
+                                                       gl.framebufferRenderbuffer( 36160, depthStyle, 36161, glDepthRenderbuffer );
+                                                       gl.bindRenderbuffer( 36161, null );
 
-               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;
+                                               state.bindFramebuffer( 36160, null );
 
-               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;
+                               referenceSpace = await session.requestReferenceSpace( referenceSpaceType );
 
-               //
+                               animation.setContext( session );
+                               animation.start();
 
-               if ( object.isMesh ) {
+                               scope.isPresenting = true;
 
-                       if ( material.wireframe === true ) {
+                               scope.dispatchEvent( { type: 'sessionstart' } );
 
-                               state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() );
-                               renderer.setMode( 1 );
+                       }
 
-                       } else {
+               };
 
-                               renderer.setMode( 4 );
+               function onInputSourcesChange( event ) {
 
-                       }
+                       const inputSources = session.inputSources;
 
-               } else if ( object.isLine ) {
+                       // Assign inputSources to available controllers
 
-                       let lineWidth = material.linewidth;
+                       for ( let i = 0; i < controllers.length; i ++ ) {
 
-                       if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material
+                               inputSourcesMap.set( inputSources[ i ], controllers[ i ] );
 
-                       state.setLineWidth( lineWidth * getTargetPixelRatio() );
+                       }
 
-                       if ( object.isLineSegments ) {
+                       // Notify disconnected
 
-                               renderer.setMode( 1 );
+                       for ( let i = 0; i < event.removed.length; i ++ ) {
 
-                       } else if ( object.isLineLoop ) {
+                               const inputSource = event.removed[ i ];
+                               const controller = inputSourcesMap.get( inputSource );
 
-                               renderer.setMode( 2 );
+                               if ( controller ) {
 
-                       } else {
+                                       controller.dispatchEvent( { type: 'disconnected', data: inputSource } );
+                                       inputSourcesMap.delete( inputSource );
 
-                               renderer.setMode( 3 );
+                               }
 
                        }
 
-               } else if ( object.isPoints ) {
+                       // Notify connected
 
-                       renderer.setMode( 0 );
+                       for ( let i = 0; i < event.added.length; i ++ ) {
 
-               } else if ( object.isSprite ) {
+                               const inputSource = event.added[ i ];
+                               const controller = inputSourcesMap.get( inputSource );
 
-                       renderer.setMode( 4 );
+                               if ( controller ) {
 
-               }
+                                       controller.dispatchEvent( { type: 'connected', data: inputSource } );
 
-               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 );
+               const cameraLPos = new Vector3();
+               const cameraRPos = new Vector3();
 
-               } else {
+               /**
+                * 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 ) {
 
-                       renderer.render( drawStart, drawCount );
+                       cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
+                       cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );
 
-               }
+                       const ipd = cameraLPos.distanceTo( cameraRPos );
 
-       };
+                       const projL = cameraL.projectionMatrix.elements;
+                       const projR = cameraR.projectionMatrix.elements;
 
-       // Compile
+                       // 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 ];
 
-       this.compile = function ( scene, camera ) {
+                       const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
+                       const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
+                       const left = near * leftFov;
+                       const right = near * rightFov;
 
-               currentRenderState = renderStates.get( scene );
-               currentRenderState.init();
+                       // 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;
 
-               scene.traverseVisible( function ( object ) {
+                       // 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();
 
-                       if ( object.isLight && object.layers.test( camera.layers ) ) {
+                       // 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;
 
-                               currentRenderState.pushLight( object );
+                       camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );
 
-                               if ( object.castShadow ) {
+               }
 
-                                       currentRenderState.pushShadow( object );
+               function updateCamera( camera, parent ) {
 
-                               }
+                       if ( parent === null ) {
 
-                       }
+                               camera.matrixWorld.copy( camera.matrix );
 
-               } );
+                       } else {
 
-               currentRenderState.setupLights();
+                               camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix );
 
-               const compiled = new WeakMap();
+                       }
 
-               scene.traverse( function ( object ) {
+                       camera.matrixWorldInverse.copy( camera.matrixWorld ).invert();
 
-                       const material = object.material;
+               }
 
-                       if ( material ) {
+               this.updateCamera = function ( camera ) {
 
-                               if ( Array.isArray( material ) ) {
+                       if ( session === null ) return;
 
-                                       for ( let i = 0; i < material.length; i ++ ) {
+                       cameraVR.near = cameraR.near = cameraL.near = camera.near;
+                       cameraVR.far = cameraR.far = cameraL.far = camera.far;
 
-                                               const material2 = material[ i ];
+                       if ( _currentDepthNear !== cameraVR.near || _currentDepthFar !== cameraVR.far ) {
 
-                                               if ( compiled.has( material2 ) === false ) {
+                               // Note that the new renderState won't apply until the next frame. See #18320
 
-                                                       initMaterial( material2, scene, object );
-                                                       compiled.set( material2 );
+                               session.updateRenderState( {
+                                       depthNear: cameraVR.near,
+                                       depthFar: cameraVR.far
+                               } );
 
-                                               }
+                               _currentDepthNear = cameraVR.near;
+                               _currentDepthFar = cameraVR.far;
 
-                                       }
+                       }
 
-                               } else if ( compiled.has( material ) === false ) {
+                       const parent = camera.parent;
+                       const cameras = cameraVR.cameras;
 
-                                       initMaterial( material, scene, object );
-                                       compiled.set( material );
+                       updateCamera( cameraVR, parent );
 
-                               }
+                       for ( let i = 0; i < cameras.length; i ++ ) {
+
+                               updateCamera( cameras[ i ], parent );
 
                        }
 
-               } );
+                       cameraVR.matrixWorld.decompose( cameraVR.position, cameraVR.quaternion, cameraVR.scale );
 
-       };
+                       // update user camera and its children
 
-       // Animation Loop
+                       camera.position.copy( cameraVR.position );
+                       camera.quaternion.copy( cameraVR.quaternion );
+                       camera.scale.copy( cameraVR.scale );
+                       camera.matrix.copy( cameraVR.matrix );
+                       camera.matrixWorld.copy( cameraVR.matrixWorld );
 
-       let onAnimationFrameCallback = null;
+                       const children = camera.children;
 
-       function onAnimationFrame( time ) {
+                       for ( let i = 0, l = children.length; i < l; i ++ ) {
 
-               if ( xr.isPresenting ) return;
-               if ( onAnimationFrameCallback ) onAnimationFrameCallback( time );
+                               children[ i ].updateMatrixWorld( true );
 
-       }
+                       }
 
-       const animation = new WebGLAnimation();
-       animation.setAnimationLoop( onAnimationFrame );
+                       // update projection matrix for proper view frustum culling
 
-       if ( typeof window !== 'undefined' ) animation.setContext( window );
+                       if ( cameras.length === 2 ) {
 
-       this.setAnimationLoop = function ( callback ) {
+                               setProjectionFromUnion( cameraVR, cameraL, cameraR );
 
-               onAnimationFrameCallback = callback;
-               xr.setAnimationLoop( callback );
+                       } else {
 
-               ( callback === null ) ? animation.stop() : animation.start();
+                               // assume single camera setup (AR)
 
-       };
+                               cameraVR.projectionMatrix.copy( cameraL.projectionMatrix );
 
-       // Rendering
+                       }
 
-       this.render = function ( scene, camera ) {
+               };
 
-               let renderTarget, forceClear;
+               this.getCamera = function () {
 
-               if ( arguments[ 2 ] !== undefined ) {
+                       return cameraVR;
 
-                       console.warn( 'THREE.WebGLRenderer.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.' );
-                       renderTarget = arguments[ 2 ];
+               };
 
-               }
+               this.getFoveation = function () {
 
-               if ( arguments[ 3 ] !== undefined ) {
+                       if ( glProjLayer !== null ) {
 
-                       console.warn( 'THREE.WebGLRenderer.render(): the forceClear argument has been removed. Use .clear() instead.' );
-                       forceClear = arguments[ 3 ];
+                               return glProjLayer.fixedFoveation;
 
-               }
+                       }
 
-               if ( camera !== undefined && camera.isCamera !== true ) {
+                       if ( glBaseLayer !== null ) {
 
-                       console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' );
-                       return;
+                               return glBaseLayer.fixedFoveation;
 
-               }
+                       }
 
-               if ( _isContextLost === true ) return;
+                       return undefined;
 
-               // reset caching for this frame
+               };
 
-               bindingStates.resetDefaultState();
-               _currentMaterialId = - 1;
-               _currentCamera = null;
+               this.setFoveation = function ( foveation ) {
 
-               // update scene graph
+                       // 0 = no foveation = full resolution
+                       // 1 = maximum foveation = the edges render at lower resolution
 
-               if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+                       if ( glProjLayer !== null ) {
 
-               // update camera matrices and frustum
+                               glProjLayer.fixedFoveation = foveation;
 
-               if ( camera.parent === null ) camera.updateMatrixWorld();
+                       }
 
-               if ( xr.enabled === true && xr.isPresenting === true ) {
+                       if ( glBaseLayer !== null && glBaseLayer.fixedFoveation !== undefined ) {
 
-                       camera = xr.getCamera( camera );
+                               glBaseLayer.fixedFoveation = foveation;
 
-               }
+                       }
 
-               //
-               if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, renderTarget || _currentRenderTarget );
+               };
 
-               currentRenderState = renderStates.get( scene, renderStateStack.length );
-               currentRenderState.init();
+               // Animation Loop
 
-               renderStateStack.push( currentRenderState );
+               let onAnimationFrameCallback = null;
 
-               _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
-               _frustum.setFromProjectionMatrix( _projScreenMatrix );
+               function onAnimationFrame( time, frame ) {
 
-               _localClippingEnabled = this.localClippingEnabled;
-               _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled, camera );
+                       pose = frame.getViewerPose( referenceSpace );
+                       xrFrame = frame;
 
-               currentRenderList = renderLists.get( scene, camera );
-               currentRenderList.init();
+                       if ( pose !== null ) {
 
-               projectObject( scene, camera, 0, _this.sortObjects );
+                               const views = pose.views;
 
-               currentRenderList.finish();
+                               if ( glBaseLayer !== null ) {
 
-               if ( _this.sortObjects === true ) {
+                                       state.bindXRFramebuffer( glBaseLayer.framebuffer );
 
-                       currentRenderList.sort( _opaqueSort, _transparentSort );
+                               }
 
-               }
+                               let cameraVRNeedsUpdate = false;
 
-               //
+                               // check if it's necessary to rebuild cameraVR's camera list
 
-               if ( _clippingEnabled === true ) clipping.beginShadows();
+                               if ( views.length !== cameraVR.cameras.length ) {
 
-               const shadowsArray = currentRenderState.state.shadowsArray;
+                                       cameraVR.cameras.length = 0;
 
-               shadowMap.render( shadowsArray, scene, camera );
+                                       cameraVRNeedsUpdate = true;
 
-               currentRenderState.setupLights();
-               currentRenderState.setupLightsView( camera );
+                               }
 
-               if ( _clippingEnabled === true ) clipping.endShadows();
+                               for ( let i = 0; i < views.length; i ++ ) {
 
-               //
+                                       const view = views[ i ];
 
-               if ( this.info.autoReset === true ) this.info.reset();
+                                       let viewport = null;
 
-               if ( renderTarget !== undefined ) {
+                                       if ( glBaseLayer !== null ) {
 
-                       this.setRenderTarget( renderTarget );
+                                               viewport = glBaseLayer.getViewport( view );
 
-               }
+                                       } else {
 
-               //
+                                               const glSubImage = glBinding.getViewSubImage( glProjLayer, view );
 
-               background.render( currentRenderList, scene, camera, forceClear );
+                                               state.bindXRFramebuffer( glFramebuffer );
 
-               // render scene
+                                               if ( glSubImage.depthStencilTexture !== undefined ) {
 
-               const opaqueObjects = currentRenderList.opaque;
-               const transparentObjects = currentRenderList.transparent;
+                                                       gl.framebufferTexture2D( 36160, depthStyle, 3553, glSubImage.depthStencilTexture, 0 );
 
-               if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera );
-               if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera );
+                                               }
 
-               //
+                                               gl.framebufferTexture2D( 36160, 36064, 3553, glSubImage.colorTexture, 0 );
 
-               if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera );
+                                               viewport = glSubImage.viewport;
 
-               //
+                                       }
 
-               if ( _currentRenderTarget !== null ) {
+                                       const camera = cameras[ i ];
 
-                       // Generate mipmap if we're using any kind of mipmap filtering
+                                       camera.matrix.fromArray( view.transform.matrix );
+                                       camera.projectionMatrix.fromArray( view.projectionMatrix );
+                                       camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height );
 
-                       textures.updateRenderTargetMipmap( _currentRenderTarget );
+                                       if ( i === 0 ) {
 
-                       // resolve multisample renderbuffers to a single-sample texture if necessary
+                                               cameraVR.matrix.copy( camera.matrix );
 
-                       textures.updateMultisampleRenderTarget( _currentRenderTarget );
+                                       }
 
-               }
+                                       if ( cameraVRNeedsUpdate === true ) {
 
-               // Ensure depth buffer writing is enabled so it can be cleared on next render
+                                               cameraVR.cameras.push( camera );
 
-               state.buffers.depth.setTest( true );
-               state.buffers.depth.setMask( true );
-               state.buffers.color.setMask( true );
+                                       }
 
-               state.setPolygonOffset( false );
+                               }
 
-               // _gl.finish();
+                               if ( isMultisample ) {
 
-               renderStateStack.pop();
-               if ( renderStateStack.length > 0 ) {
+                                       state.bindXRFramebuffer( glMultisampledFramebuffer );
 
-                       currentRenderState = renderStateStack[ renderStateStack.length - 1 ];
+                                       if ( clearStyle !== null ) gl.clear( clearStyle );
 
-               } else {
+                               }
 
-                       currentRenderState = null;
+                       }
 
-               }
+                       //
 
-               currentRenderList = null;
+                       const inputSources = session.inputSources;
 
-       };
+                       for ( let i = 0; i < controllers.length; i ++ ) {
 
-       function projectObject( object, camera, groupOrder, sortObjects ) {
+                               const controller = controllers[ i ];
+                               const inputSource = inputSources[ i ];
 
-               if ( object.visible === false ) return;
+                               controller.update( inputSource, frame, referenceSpace );
 
-               const visible = object.layers.test( camera.layers );
+                       }
 
-               if ( visible ) {
+                       if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame );
 
-                       if ( object.isGroup ) {
+                       if ( isMultisample ) {
 
-                               groupOrder = object.renderOrder;
+                               const width = glProjLayer.textureWidth;
+                               const height = glProjLayer.textureHeight;
 
-                       } else if ( object.isLOD ) {
+                               state.bindFramebuffer( 36008, glMultisampledFramebuffer );
+                               state.bindFramebuffer( 36009, glFramebuffer );
+                               // Invalidate the depth here to avoid flush of the depth data to main memory.
+                               gl.invalidateFramebuffer( 36008, [ depthStyle ] );
+                               gl.invalidateFramebuffer( 36009, [ depthStyle ] );
+                               gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, 16384, 9728 );
+                               // Invalidate the MSAA buffer because it's not needed anymore.
+                               gl.invalidateFramebuffer( 36008, [ 36064 ] );
+                               state.bindFramebuffer( 36008, null );
+                               state.bindFramebuffer( 36009, null );
 
-                               if ( object.autoUpdate === true ) object.update( camera );
+                               state.bindFramebuffer( 36160, glMultisampledFramebuffer );
 
-                       } else if ( object.isLight ) {
+                       }
 
-                               currentRenderState.pushLight( object );
+                       xrFrame = null;
 
-                               if ( object.castShadow ) {
+               }
 
-                                       currentRenderState.pushShadow( object );
+               const animation = new WebGLAnimation();
 
-                               }
+               animation.setAnimationLoop( onAnimationFrame );
 
-                       } else if ( object.isSprite ) {
+               this.setAnimationLoop = function ( callback ) {
 
-                               if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {
+                       onAnimationFrameCallback = callback;
 
-                                       if ( sortObjects ) {
+               };
 
-                                               _vector3.setFromMatrixPosition( object.matrixWorld )
-                                                       .applyMatrix4( _projScreenMatrix );
+               this.dispose = function () {};
 
-                                       }
+       }
 
-                                       const geometry = objects.update( object );
-                                       const material = object.material;
+}
 
-                                       if ( material.visible ) {
+function WebGLMaterials( properties ) {
 
-                                               currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+       function refreshFogUniforms( uniforms, fog ) {
 
-                                       }
+               uniforms.fogColor.value.copy( fog.color );
 
-                               }
+               if ( fog.isFog ) {
 
-                       } else if ( object.isImmediateRenderObject ) {
+                       uniforms.fogNear.value = fog.near;
+                       uniforms.fogFar.value = fog.far;
 
-                               if ( sortObjects ) {
+               } else if ( fog.isFogExp2 ) {
 
-                                       _vector3.setFromMatrixPosition( object.matrixWorld )
-                                               .applyMatrix4( _projScreenMatrix );
+                       uniforms.fogDensity.value = fog.density;
 
-                               }
+               }
 
-                               currentRenderList.push( object, null, object.material, groupOrder, _vector3.z, null );
+       }
 
-                       } else if ( object.isMesh || object.isLine || object.isPoints ) {
+       function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) {
 
-                               if ( object.isSkinnedMesh ) {
+               if ( material.isMeshBasicMaterial ) {
 
-                                       // update skeleton only once in a frame
+                       refreshUniformsCommon( uniforms, material );
 
-                                       if ( object.skeleton.frame !== info.render.frame ) {
+               } else if ( material.isMeshLambertMaterial ) {
 
-                                               object.skeleton.update();
-                                               object.skeleton.frame = info.render.frame;
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsLambert( uniforms, material );
 
-                                       }
+               } else if ( material.isMeshToonMaterial ) {
 
-                               }
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsToon( uniforms, material );
 
-                               if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {
+               } else if ( material.isMeshPhongMaterial ) {
 
-                                       if ( sortObjects ) {
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsPhong( uniforms, material );
 
-                                               _vector3.setFromMatrixPosition( object.matrixWorld )
-                                                       .applyMatrix4( _projScreenMatrix );
+               } else if ( material.isMeshStandardMaterial ) {
 
-                                       }
+                       refreshUniformsCommon( uniforms, material );
 
-                                       const geometry = objects.update( object );
-                                       const material = object.material;
+                       if ( material.isMeshPhysicalMaterial ) {
 
-                                       if ( Array.isArray( material ) ) {
+                               refreshUniformsPhysical( uniforms, material, transmissionRenderTarget );
 
-                                               const groups = geometry.groups;
+                       } else {
 
-                                               for ( let i = 0, l = groups.length; i < l; i ++ ) {
+                               refreshUniformsStandard( uniforms, material );
 
-                                                       const group = groups[ i ];
-                                                       const groupMaterial = material[ group.materialIndex ];
+                       }
 
-                                                       if ( groupMaterial && groupMaterial.visible ) {
+               } else if ( material.isMeshMatcapMaterial ) {
 
-                                                               currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group );
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsMatcap( uniforms, material );
 
-                                                       }
+               } else if ( material.isMeshDepthMaterial ) {
 
-                                               }
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsDepth( uniforms, material );
 
-                                       } else if ( material.visible ) {
+               } else if ( material.isMeshDistanceMaterial ) {
 
-                                               currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+                       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 ) {
 
-               const children = object.children;
+                       refreshUniformsPoints( uniforms, material, pixelRatio, height );
 
-               for ( let i = 0, l = children.length; i < l; i ++ ) {
+               } else if ( material.isSpriteMaterial ) {
 
-                       projectObject( children[ i ], camera, groupOrder, sortObjects );
+                       refreshUniformsSprites( uniforms, material );
 
-               }
+               } else if ( material.isShadowMaterial ) {
 
-       }
+                       uniforms.color.value.copy( material.color );
+                       uniforms.opacity.value = material.opacity;
 
-       function renderObjects( renderList, scene, camera ) {
+               } else if ( material.isShaderMaterial ) {
 
-               const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null;
+                       material.uniformsNeedUpdate = false; // #15581
 
-               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;
+       function refreshUniformsCommon( uniforms, material ) {
 
-                       if ( camera.isArrayCamera ) {
+               uniforms.opacity.value = material.opacity;
+
+               if ( material.color ) {
+
+                       uniforms.diffuse.value.copy( material.color );
 
-                               const cameras = camera.cameras;
+               }
 
-                               for ( let j = 0, jl = cameras.length; j < jl; j ++ ) {
+               if ( material.emissive ) {
 
-                                       const camera2 = cameras[ j ];
+                       uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity );
 
-                                       if ( object.layers.test( camera2.layers ) ) {
+               }
 
-                                               state.viewport( _currentViewport.copy( camera2.viewport ) );
+               if ( material.map ) {
 
-                                               currentRenderState.setupLightsView( camera2 );
+                       uniforms.map.value = material.map;
 
-                                               renderObject( object, scene, camera2, geometry, material, group );
+               }
 
-                                       }
+               if ( material.alphaMap ) {
 
-                               }
+                       uniforms.alphaMap.value = material.alphaMap;
 
-                       } else {
+               }
 
-                               renderObject( object, scene, camera, geometry, material, group );
+               if ( material.specularMap ) {
 
-                       }
+                       uniforms.specularMap.value = material.specularMap;
 
                }
 
-       }
+               if ( material.alphaTest > 0 ) {
 
-       function renderObject( object, scene, camera, geometry, material, group ) {
+                       uniforms.alphaTest.value = material.alphaTest;
 
-               object.onBeforeRender( _this, scene, camera, geometry, material, group );
+               }
 
-               object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
-               object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
+               const envMap = properties.get( material ).envMap;
+
+               if ( envMap ) {
 
-               if ( object.isImmediateRenderObject ) {
+                       uniforms.envMap.value = envMap;
 
-                       const program = setProgram( camera, scene, material, object );
+                       uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1;
 
-                       state.setMaterial( material );
+                       uniforms.reflectivity.value = material.reflectivity;
+                       uniforms.ior.value = material.ior;
+                       uniforms.refractionRatio.value = material.refractionRatio;
 
-                       bindingStates.reset();
+                       const maxMipLevel = properties.get( envMap ).__maxMipLevel;
 
-                       renderObjectImmediate( object, program );
+                       if ( maxMipLevel !== undefined ) {
 
-               } else {
+                               uniforms.maxMipLevel.value = maxMipLevel;
 
-                       _this.renderBufferDirect( camera, scene, geometry, material, object, group );
+                       }
 
                }
 
-               object.onAfterRender( _this, scene, camera, geometry, material, group );
+               if ( material.lightMap ) {
 
-       }
+                       uniforms.lightMap.value = material.lightMap;
+                       uniforms.lightMapIntensity.value = material.lightMapIntensity;
 
-       function initMaterial( material, scene, object ) {
+               }
 
-               if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
+               if ( material.aoMap ) {
 
-               const materialProperties = properties.get( material );
+                       uniforms.aoMap.value = material.aoMap;
+                       uniforms.aoMapIntensity.value = material.aoMapIntensity;
 
-               const lights = currentRenderState.state.lights;
-               const shadowsArray = currentRenderState.state.shadowsArray;
+               }
 
-               const lightsStateVersion = lights.state.version;
+               // 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
+               // 13. specular intensity map
+               // 14. specular tint map
+               // 15. transmission map
+               // 16. thickness map
 
-               const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object );
-               const programCacheKey = programCache.getProgramCacheKey( parameters );
+               let uvScaleMap;
 
-               let program = materialProperties.program;
-               let programChange = true;
+               if ( material.map ) {
 
-               // always update environment and fog - changing these trigger an initMaterial call, but it's possible that the program doesn't change
+                       uvScaleMap = material.map;
 
-               materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null;
-               materialProperties.fog = scene.fog;
-               materialProperties.envMap = cubemaps.get( material.envMap || materialProperties.environment );
+               } else if ( material.specularMap ) {
 
-               if ( program === undefined ) {
+                       uvScaleMap = material.specularMap;
 
-                       // new material
-                       material.addEventListener( 'dispose', onMaterialDispose );
+               } else if ( material.displacementMap ) {
 
-               } else if ( program.cacheKey !== programCacheKey ) {
+                       uvScaleMap = material.displacementMap;
 
-                       // changed glsl or parameters
-                       releaseMaterialProgramReference( material );
+               } else if ( material.normalMap ) {
 
-               } else if ( materialProperties.lightsStateVersion !== lightsStateVersion ) {
+                       uvScaleMap = material.normalMap;
 
-                       programChange = false;
+               } else if ( material.bumpMap ) {
 
-               } else if ( parameters.shaderID !== undefined ) {
+                       uvScaleMap = material.bumpMap;
 
-                       // same glsl and uniform list
-                       return;
+               } else if ( material.roughnessMap ) {
 
-               } else {
+                       uvScaleMap = material.roughnessMap;
 
-                       // only rebuild uniform list
-                       programChange = false;
+               } else if ( material.metalnessMap ) {
 
-               }
+                       uvScaleMap = material.metalnessMap;
 
-               if ( programChange ) {
+               } else if ( material.alphaMap ) {
 
-                       parameters.uniforms = programCache.getUniforms( material );
+                       uvScaleMap = material.alphaMap;
 
-                       material.onBeforeCompile( parameters, _this );
+               } else if ( material.emissiveMap ) {
 
-                       program = programCache.acquireProgram( parameters, programCacheKey );
+                       uvScaleMap = material.emissiveMap;
 
-                       materialProperties.program = program;
-                       materialProperties.uniforms = parameters.uniforms;
-                       materialProperties.outputEncoding = parameters.outputEncoding;
+               } else if ( material.clearcoatMap ) {
 
-               }
+                       uvScaleMap = material.clearcoatMap;
 
-               const uniforms = materialProperties.uniforms;
+               } else if ( material.clearcoatNormalMap ) {
 
-               if ( ! material.isShaderMaterial &&
-                       ! material.isRawShaderMaterial ||
-                       material.clipping === true ) {
+                       uvScaleMap = material.clearcoatNormalMap;
 
-                       materialProperties.numClippingPlanes = clipping.numPlanes;
-                       materialProperties.numIntersection = clipping.numIntersection;
-                       uniforms.clippingPlanes = clipping.uniform;
+               } else if ( material.clearcoatRoughnessMap ) {
 
-               }
+                       uvScaleMap = material.clearcoatRoughnessMap;
 
-               // store the light setup it was created for
+               } else if ( material.specularIntensityMap ) {
 
-               materialProperties.needsLights = materialNeedsLights( material );
-               materialProperties.lightsStateVersion = lightsStateVersion;
+                       uvScaleMap = material.specularIntensityMap;
 
-               if ( materialProperties.needsLights ) {
+               } else if ( material.specularColorMap ) {
 
-                       // wire up the material to this renderer's lighting state
+                       uvScaleMap = material.specularColorMap;
 
-                       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;
+               } else if ( material.transmissionMap ) {
 
-                       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
+                       uvScaleMap = material.transmissionMap;
 
-               }
+               } else if ( material.thicknessMap ) {
 
-               const progUniforms = materialProperties.program.getUniforms();
-               const uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, uniforms );
+                       uvScaleMap = material.thicknessMap;
 
-               materialProperties.uniformsList = uniformsList;
+               } else if ( material.sheenColorMap ) {
 
-       }
+                       uvScaleMap = material.sheenColorMap;
 
-       function setProgram( camera, scene, material, object ) {
+               } else if ( material.sheenRoughnessMap ) {
 
-               if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
+                       uvScaleMap = material.sheenRoughnessMap;
 
-               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 );
+               if ( uvScaleMap !== undefined ) {
 
-               const materialProperties = properties.get( material );
-               const lights = currentRenderState.state.lights;
+                       // backwards compatibility
+                       if ( uvScaleMap.isWebGLRenderTarget ) {
 
-               if ( _clippingEnabled === true ) {
+                               uvScaleMap = uvScaleMap.texture;
 
-                       if ( _localClippingEnabled === true || camera !== _currentCamera ) {
+                       }
 
-                               const useCache =
-                                       camera === _currentCamera &&
-                                       material.id === _currentMaterialId;
+                       if ( uvScaleMap.matrixAutoUpdate === true ) {
 
-                               // 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 );
+                               uvScaleMap.updateMatrix();
 
                        }
 
+                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+
                }
 
-               if ( material.version === materialProperties.__version ) {
+               // uv repeat and offset setting priorities for uv2
+               // 1. ao map
+               // 2. light map
 
-                       if ( material.fog && materialProperties.fog !== fog ) {
+               let uv2ScaleMap;
 
-                               initMaterial( material, scene, object );
+               if ( material.aoMap ) {
 
-                       } else if ( materialProperties.environment !== environment ) {
+                       uv2ScaleMap = material.aoMap;
 
-                               initMaterial( material, scene, object );
+               } else if ( material.lightMap ) {
 
-                       } else if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) {
+                       uv2ScaleMap = material.lightMap;
 
-                               initMaterial( material, scene, object );
+               }
 
-                       } else if ( materialProperties.numClippingPlanes !== undefined &&
-                               ( materialProperties.numClippingPlanes !== clipping.numPlanes ||
-                               materialProperties.numIntersection !== clipping.numIntersection ) ) {
+               if ( uv2ScaleMap !== undefined ) {
 
-                               initMaterial( material, scene, object );
+                       // backwards compatibility
+                       if ( uv2ScaleMap.isWebGLRenderTarget ) {
 
-                       } else if ( materialProperties.outputEncoding !== encoding ) {
+                               uv2ScaleMap = uv2ScaleMap.texture;
 
-                               initMaterial( material, scene, object );
+                       }
 
-                       } else if ( materialProperties.envMap !== envMap ) {
+                       if ( uv2ScaleMap.matrixAutoUpdate === true ) {
 
-                               initMaterial( material, scene, object );
+                               uv2ScaleMap.updateMatrix();
 
                        }
 
-               } else {
-
-                       initMaterial( material, scene, object );
-                       materialProperties.__version = material.version;
+                       uniforms.uv2Transform.value.copy( uv2ScaleMap.matrix );
 
                }
 
-               let refreshProgram = false;
-               let refreshMaterial = false;
-               let refreshLights = false;
+       }
 
-               const program = materialProperties.program,
-                       p_uniforms = program.getUniforms(),
-                       m_uniforms = materialProperties.uniforms;
+       function refreshUniformsLine( uniforms, material ) {
 
-               if ( state.useProgram( program.program ) ) {
+               uniforms.diffuse.value.copy( material.color );
+               uniforms.opacity.value = material.opacity;
 
-                       refreshProgram = true;
-                       refreshMaterial = true;
-                       refreshLights = true;
+       }
 
-               }
+       function refreshUniformsDash( uniforms, material ) {
 
-               if ( material.id !== _currentMaterialId ) {
+               uniforms.dashSize.value = material.dashSize;
+               uniforms.totalSize.value = material.dashSize + material.gapSize;
+               uniforms.scale.value = material.scale;
 
-                       _currentMaterialId = material.id;
+       }
 
-                       refreshMaterial = true;
+       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 ( refreshProgram || _currentCamera !== camera ) {
+               if ( material.map ) {
 
-                       p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix );
+                       uniforms.map.value = material.map;
 
-                       if ( capabilities.logarithmicDepthBuffer ) {
+               }
 
-                               p_uniforms.setValue( _gl, 'logDepthBufFC',
-                                       2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );
+               if ( material.alphaMap ) {
 
-                       }
+                       uniforms.alphaMap.value = material.alphaMap;
 
-                       if ( _currentCamera !== camera ) {
+               }
 
-                               _currentCamera = camera;
+               if ( material.alphaTest > 0 ) {
 
-                               // 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:
+                       uniforms.alphaTest.value = material.alphaTest;
 
-                               refreshMaterial = true;         // set to true on material change
-                               refreshLights = true;           // remains set until update done
+               }
 
-                       }
+               // uv repeat and offset setting priorities
+               // 1. color map
+               // 2. alpha map
 
-                       // load material specific uniforms
-                       // (shader material also gets them for the sake of genericity)
+               let uvScaleMap;
 
-                       if ( material.isShaderMaterial ||
-                               material.isMeshPhongMaterial ||
-                               material.isMeshToonMaterial ||
-                               material.isMeshStandardMaterial ||
-                               material.envMap ) {
+               if ( material.map ) {
 
-                               const uCamPos = p_uniforms.map.cameraPosition;
+                       uvScaleMap = material.map;
 
-                               if ( uCamPos !== undefined ) {
+               } else if ( material.alphaMap ) {
 
-                                       uCamPos.setValue( _gl,
-                                               _vector3.setFromMatrixPosition( camera.matrixWorld ) );
+                       uvScaleMap = material.alphaMap;
 
-                               }
+               }
 
-                       }
+               if ( uvScaleMap !== undefined ) {
 
-                       if ( material.isMeshPhongMaterial ||
-                               material.isMeshToonMaterial ||
-                               material.isMeshLambertMaterial ||
-                               material.isMeshBasicMaterial ||
-                               material.isMeshStandardMaterial ||
-                               material.isShaderMaterial ) {
+                       if ( uvScaleMap.matrixAutoUpdate === true ) {
 
-                               p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true );
+                               uvScaleMap.updateMatrix();
 
                        }
 
-                       if ( material.isMeshPhongMaterial ||
-                               material.isMeshToonMaterial ||
-                               material.isMeshLambertMaterial ||
-                               material.isMeshBasicMaterial ||
-                               material.isMeshStandardMaterial ||
-                               material.isShaderMaterial ||
-                               material.isShadowMaterial ||
-                               material.skinning ) {
+                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
 
-                               p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );
+               }
 
-                       }
+       }
 
-               }
+       function refreshUniformsSprites( uniforms, material ) {
 
-               // 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
+               uniforms.diffuse.value.copy( material.color );
+               uniforms.opacity.value = material.opacity;
+               uniforms.rotation.value = material.rotation;
 
-               if ( material.skinning ) {
+               if ( material.map ) {
 
-                       p_uniforms.setOptional( _gl, object, 'bindMatrix' );
-                       p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' );
+                       uniforms.map.value = material.map;
 
-                       const skeleton = object.skeleton;
+               }
 
-                       if ( skeleton ) {
+               if ( material.alphaMap ) {
 
-                               const bones = skeleton.bones;
+                       uniforms.alphaMap.value = material.alphaMap;
 
-                               if ( capabilities.floatVertexTextures ) {
+               }
 
-                                       if ( skeleton.boneTexture === null ) {
+               if ( material.alphaTest > 0 ) {
 
-                                               // 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)
+                       uniforms.alphaTest.value = material.alphaTest;
 
+               }
 
-                                               let size = Math.sqrt( bones.length * 4 ); // 4 pixels needed for 1 matrix
-                                               size = MathUtils.ceilPowerOfTwo( size );
-                                               size = Math.max( size, 4 );
+               // uv repeat and offset setting priorities
+               // 1. color map
+               // 2. alpha map
 
-                                               const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel
-                                               boneMatrices.set( skeleton.boneMatrices ); // copy current values
+               let uvScaleMap;
 
-                                               const boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType );
+               if ( material.map ) {
 
-                                               skeleton.boneMatrices = boneMatrices;
-                                               skeleton.boneTexture = boneTexture;
-                                               skeleton.boneTextureSize = size;
+                       uvScaleMap = material.map;
 
-                                       }
+               } else if ( material.alphaMap ) {
 
-                                       p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures );
-                                       p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize );
+                       uvScaleMap = material.alphaMap;
 
-                               } else {
+               }
 
-                                       p_uniforms.setOptional( _gl, skeleton, 'boneMatrices' );
+               if ( uvScaleMap !== undefined ) {
 
-                               }
+                       if ( uvScaleMap.matrixAutoUpdate === true ) {
+
+                               uvScaleMap.updateMatrix();
 
                        }
 
+                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+
                }
 
-               if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) {
+       }
 
-                       materialProperties.receiveShadow = object.receiveShadow;
-                       p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow );
+       function refreshUniformsLambert( uniforms, material ) {
 
-               }
+               if ( material.emissiveMap ) {
 
-               if ( refreshMaterial ) {
+                       uniforms.emissiveMap.value = material.emissiveMap;
 
-                       p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure );
+               }
 
-                       if ( materialProperties.needsLights ) {
+       }
 
-                               // the current material requires lighting info
+       function refreshUniformsPhong( uniforms, material ) {
 
-                               // 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
+               uniforms.specular.value.copy( material.specular );
+               uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )
 
-                               markUniformsLightsNeedsUpdate( m_uniforms, refreshLights );
+               if ( material.emissiveMap ) {
 
-                       }
+                       uniforms.emissiveMap.value = material.emissiveMap;
 
-                       // refresh uniforms common to several materials
+               }
 
-                       if ( fog && material.fog ) {
+               if ( material.bumpMap ) {
 
-                               materials.refreshFogUniforms( m_uniforms, fog );
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
 
-                       }
+               }
 
-                       materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height );
+               if ( material.normalMap ) {
 
-                       WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
 
                }
 
-               if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) {
+               if ( material.displacementMap ) {
 
-                       WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
-                       material.uniformsNeedUpdate = false;
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
                }
 
-               if ( material.isSpriteMaterial ) {
+       }
 
-                       p_uniforms.setValue( _gl, 'center', object.center );
+       function refreshUniformsToon( uniforms, material ) {
+
+               if ( material.gradientMap ) {
+
+                       uniforms.gradientMap.value = material.gradientMap;
 
                }
 
-               // common matrices
+               if ( material.emissiveMap ) {
 
-               p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );
-               p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );
-               p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );
+                       uniforms.emissiveMap.value = material.emissiveMap;
 
-               return program;
+               }
 
-       }
+               if ( material.bumpMap ) {
 
-       // If uniforms are marked as clean, they don't need to be loaded to the GPU.
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
 
-       function markUniformsLightsNeedsUpdate( uniforms, value ) {
+               }
 
-               uniforms.ambientLightColor.needsUpdate = value;
-               uniforms.lightProbe.needsUpdate = value;
+               if ( material.normalMap ) {
 
-               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;
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
 
-       }
+               }
 
-       function materialNeedsLights( material ) {
+               if ( material.displacementMap ) {
 
-               return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial ||
-                       material.isMeshStandardMaterial || material.isShadowMaterial ||
-                       ( material.isShaderMaterial && material.lights === true );
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
+
+               }
 
        }
 
-       //
-       this.setFramebuffer = function ( value ) {
+       function refreshUniformsStandard( uniforms, material ) {
 
-               if ( _framebuffer !== value && _currentRenderTarget === null ) _gl.bindFramebuffer( 36160, value );
+               uniforms.roughness.value = material.roughness;
+               uniforms.metalness.value = material.metalness;
 
-               _framebuffer = value;
+               if ( material.roughnessMap ) {
 
-       };
+                       uniforms.roughnessMap.value = material.roughnessMap;
 
-       this.getActiveCubeFace = function () {
+               }
 
-               return _currentActiveCubeFace;
+               if ( material.metalnessMap ) {
 
-       };
+                       uniforms.metalnessMap.value = material.metalnessMap;
 
-       this.getActiveMipmapLevel = function () {
+               }
 
-               return _currentActiveMipmapLevel;
+               if ( material.emissiveMap ) {
 
-       };
+                       uniforms.emissiveMap.value = material.emissiveMap;
 
-       this.getRenderList = function () {
+               }
 
-               return currentRenderList;
+               if ( material.bumpMap ) {
 
-       };
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
 
-       this.setRenderList = function ( renderList ) {
+               }
 
-               currentRenderList = renderList;
+               if ( material.normalMap ) {
 
-       };
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
 
-       this.getRenderTarget = function () {
+               }
 
-               return _currentRenderTarget;
+               if ( material.displacementMap ) {
 
-       };
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
-       this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) {
+               }
 
-               _currentRenderTarget = renderTarget;
-               _currentActiveCubeFace = activeCubeFace;
-               _currentActiveMipmapLevel = activeMipmapLevel;
+               const envMap = properties.get( material ).envMap;
 
-               if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) {
+               if ( envMap ) {
 
-                       textures.setupRenderTarget( renderTarget );
+                       //uniforms.envMap.value = material.envMap; // part of uniforms common
+                       uniforms.envMapIntensity.value = material.envMapIntensity;
 
                }
 
-               let framebuffer = _framebuffer;
-               let isCube = false;
+       }
 
-               if ( renderTarget ) {
+       function refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ) {
 
-                       const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer;
+               refreshUniformsStandard( uniforms, material );
 
-                       if ( renderTarget.isWebGLCubeRenderTarget ) {
+               uniforms.ior.value = material.ior; // also part of uniforms common
 
-                               framebuffer = __webglFramebuffer[ activeCubeFace ];
-                               isCube = true;
+               if ( material.sheen > 0 ) {
 
-                       } else if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+                       uniforms.sheenColor.value.copy( material.sheenColor ).multiplyScalar( material.sheen );
 
-                               framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer;
+                       uniforms.sheenRoughness.value = material.sheenRoughness;
 
-                       } else {
+                       if ( material.sheenColorMap ) {
 
-                               framebuffer = __webglFramebuffer;
+                               uniforms.sheenColorMap.value = material.sheenColorMap;
 
                        }
 
-                       _currentViewport.copy( renderTarget.viewport );
-                       _currentScissor.copy( renderTarget.scissor );
-                       _currentScissorTest = renderTarget.scissorTest;
+                       if ( material.sheenRoughnessMap ) {
 
-               } else {
+                               uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap;
 
-                       _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor();
-                       _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor();
-                       _currentScissorTest = _scissorTest;
+                       }
 
                }
 
-               if ( _currentFramebuffer !== framebuffer ) {
-
-                       _gl.bindFramebuffer( 36160, framebuffer );
-                       _currentFramebuffer = framebuffer;
+               if ( material.clearcoat > 0 ) {
 
-               }
+                       uniforms.clearcoat.value = material.clearcoat;
+                       uniforms.clearcoatRoughness.value = material.clearcoatRoughness;
 
-               state.viewport( _currentViewport );
-               state.scissor( _currentScissor );
-               state.setScissorTest( _currentScissorTest );
+                       if ( material.clearcoatMap ) {
 
-               if ( isCube ) {
+                               uniforms.clearcoatMap.value = material.clearcoatMap;
 
-                       const textureProperties = properties.get( renderTarget.texture );
-                       _gl.framebufferTexture2D( 36160, 36064, 34069 + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel );
+                       }
 
-               }
+                       if ( material.clearcoatRoughnessMap ) {
 
-       };
+                               uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap;
 
-       this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) {
+                       }
 
-               if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) {
+                       if ( material.clearcoatNormalMap ) {
 
-                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' );
-                       return;
+                               uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale );
+                               uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap;
 
-               }
+                               if ( material.side === BackSide ) {
 
-               let framebuffer = properties.get( renderTarget ).__webglFramebuffer;
+                                       uniforms.clearcoatNormalScale.value.negate();
 
-               if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) {
+                               }
 
-                       framebuffer = framebuffer[ activeCubeFaceIndex ];
+                       }
 
                }
 
-               if ( framebuffer ) {
-
-                       let restore = false;
+               if ( material.transmission > 0 ) {
 
-                       if ( framebuffer !== _currentFramebuffer ) {
+                       uniforms.transmission.value = material.transmission;
+                       uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture;
+                       uniforms.transmissionSamplerSize.value.set( transmissionRenderTarget.width, transmissionRenderTarget.height );
 
-                               _gl.bindFramebuffer( 36160, framebuffer );
+                       if ( material.transmissionMap ) {
 
-                               restore = true;
+                               uniforms.transmissionMap.value = material.transmissionMap;
 
                        }
 
-                       try {
-
-                               const texture = renderTarget.texture;
-                               const textureFormat = texture.format;
-                               const textureType = texture.type;
-
-                               if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( 35739 ) ) {
+                       uniforms.thickness.value = material.thickness;
 
-                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' );
-                                       return;
+                       if ( material.thicknessMap ) {
 
-                               }
+                               uniforms.thicknessMap.value = material.thicknessMap;
 
-                               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 ) {
+                       uniforms.attenuationDistance.value = material.attenuationDistance;
+                       uniforms.attenuationColor.value.copy( material.attenuationColor );
 
-                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' );
-                                       return;
+               }
 
-                               }
+               uniforms.specularIntensity.value = material.specularIntensity;
+               uniforms.specularColor.value.copy( material.specularColor );
 
-                               if ( _gl.checkFramebufferStatus( 36160 ) === 36053 ) {
+               if ( material.specularIntensityMap ) {
 
-                                       // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)
+                       uniforms.specularIntensityMap.value = material.specularIntensityMap;
 
-                                       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 );
+               if ( material.specularColorMap ) {
 
-                                       }
+                       uniforms.specularColorMap.value = material.specularColorMap;
 
-                               } else {
+               }
 
-                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' );
+       }
 
-                               }
+       function refreshUniformsMatcap( uniforms, material ) {
 
-                       } finally {
+               if ( material.matcap ) {
 
-                               if ( restore ) {
+                       uniforms.matcap.value = material.matcap;
 
-                                       _gl.bindFramebuffer( 36160, _currentFramebuffer );
+               }
 
-                               }
+               if ( material.bumpMap ) {
 
-                       }
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
 
                }
 
-       };
+               if ( material.normalMap ) {
 
-       this.copyFramebufferToTexture = function ( position, texture, level = 0 ) {
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
 
-               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 );
+               if ( material.displacementMap ) {
 
-               _gl.copyTexImage2D( 3553, level, glFormat, position.x, position.y, width, height, 0 );
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
-               state.unbindTexture();
+               }
 
-       };
+       }
 
-       this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) {
+       function refreshUniformsDepth( uniforms, material ) {
 
-               const width = srcTexture.image.width;
-               const height = srcTexture.image.height;
-               const glFormat = utils.convert( dstTexture.format );
-               const glType = utils.convert( dstTexture.type );
+               if ( material.displacementMap ) {
 
-               textures.setTexture2D( dstTexture, 0 );
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
-               // 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 );
+       function refreshUniformsDistance( uniforms, material ) {
 
-               } else {
+               if ( material.displacementMap ) {
 
-                       if ( srcTexture.isCompressedTexture ) {
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
-                               _gl.compressedTexSubImage2D( 3553, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data );
+               }
 
-                       } else {
+               uniforms.referencePosition.value.copy( material.referencePosition );
+               uniforms.nearDistance.value = material.nearDistance;
+               uniforms.farDistance.value = material.farDistance;
 
-                               _gl.texSubImage2D( 3553, level, position.x, position.y, glFormat, glType, srcTexture.image );
+       }
 
-                       }
+       function refreshUniformsNormal( uniforms, material ) {
 
-               }
+               if ( material.bumpMap ) {
 
-               // Generate mipmaps only when copying level 0
-               if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( 3553 );
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
 
-               state.unbindTexture();
+               }
 
-       };
+               if ( material.normalMap ) {
 
-       this.initTexture = function ( texture ) {
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
 
-               textures.setTexture2D( texture, 0 );
+               }
 
-               state.unbindTexture();
+               if ( material.displacementMap ) {
 
-       };
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
-       this.resetState = function () {
+               }
 
-               state.reset();
-               bindingStates.reset();
+       }
 
+       return {
+               refreshFogUniforms: refreshFogUniforms,
+               refreshMaterialUniforms: refreshMaterialUniforms
        };
 
-       if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
+}
 
-               __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
+function createCanvasElement() {
 
-       }
+       const canvas = createElementNS( 'canvas' );
+       canvas.style.display = 'block';
+       return canvas;
 
 }
 
-function WebGL1Renderer( parameters ) {
+function WebGLRenderer( parameters = {} ) {
 
-       WebGLRenderer.call( this, 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;
 
-WebGL1Renderer.prototype = Object.assign( Object.create( WebGLRenderer.prototype ), {
+       let currentRenderList = null;
+       let currentRenderState = null;
 
-       constructor: WebGL1Renderer,
+       // render() can be called from within a callback triggered by another render.
+       // We track this so that the nested render call gets its list and state isolated from the parent render call.
 
-       isWebGL1Renderer: true
+       const renderListStack = [];
+       const renderStateStack = [];
 
-} );
+       // public properties
 
-class Scene extends Object3D {
+       this.domElement = _canvas;
 
-       constructor() {
+       // Debug configuration container
+       this.debug = {
 
-               super();
+               /**
+                * Enables error checking and reporting when shader programs are being compiled
+                * @type {boolean}
+                */
+               checkShaderErrors: true
+       };
 
-               Object.defineProperty( this, 'isScene', { value: true } );
+       // clearing
 
-               this.type = 'Scene';
+       this.autoClear = true;
+       this.autoClearColor = true;
+       this.autoClearDepth = true;
+       this.autoClearStencil = true;
 
-               this.background = null;
-               this.environment = null;
-               this.fog = null;
+       // scene graph
 
-               this.overrideMaterial = null;
+       this.sortObjects = true;
 
-               this.autoUpdate = true; // checked by the renderer
+       // user-defined clipping
 
-               if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
+       this.clippingPlanes = [];
+       this.localClippingEnabled = false;
 
-                       __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
+       // physically based shading
 
-               }
+       this.gammaFactor = 2.0; // for backwards compatibility
+       this.outputEncoding = LinearEncoding;
 
-       }
+       // physical lights
 
-       copy( source, recursive ) {
+       this.physicallyCorrectLights = false;
 
-               super.copy( source, recursive );
+       // tone mapping
 
-               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();
+       this.toneMapping = NoToneMapping;
+       this.toneMappingExposure = 1.0;
 
-               if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone();
+       // internal properties
 
-               this.autoUpdate = source.autoUpdate;
-               this.matrixAutoUpdate = source.matrixAutoUpdate;
+       const _this = this;
 
-               return this;
+       let _isContextLost = false;
 
-       }
+       // internal state cache
 
-       toJSON( meta ) {
+       let _currentActiveCubeFace = 0;
+       let _currentActiveMipmapLevel = 0;
+       let _currentRenderTarget = null;
+       let _currentMaterialId = - 1;
 
-               const data = super.toJSON( meta );
+       let _currentCamera = null;
 
-               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();
+       const _currentViewport = new Vector4();
+       const _currentScissor = new Vector4();
+       let _currentScissorTest = null;
 
-               return data;
+       //
 
-       }
+       let _width = _canvas.width;
+       let _height = _canvas.height;
 
-}
+       let _pixelRatio = 1;
+       let _opaqueSort = null;
+       let _transparentSort = null;
 
-function InterleavedBuffer( array, stride ) {
+       const _viewport = new Vector4( 0, 0, _width, _height );
+       const _scissor = new Vector4( 0, 0, _width, _height );
+       let _scissorTest = false;
 
-       this.array = array;
-       this.stride = stride;
-       this.count = array !== undefined ? array.length / stride : 0;
+       //
 
-       this.usage = StaticDrawUsage;
-       this.updateRange = { offset: 0, count: - 1 };
+       const _currentDrawBuffers = [];
 
-       this.version = 0;
+       // frustum
 
-       this.uuid = MathUtils.generateUUID();
+       const _frustum = new Frustum();
 
-}
+       // clipping
 
-Object.defineProperty( InterleavedBuffer.prototype, 'needsUpdate', {
+       let _clippingEnabled = false;
+       let _localClippingEnabled = false;
 
-       set: function ( value ) {
+       // transmission
 
-               if ( value === true ) this.version ++;
+       let _transmissionRenderTarget = null;
 
-       }
+       // camera matrices cache
 
-);
+       const _projScreenMatrix = new Matrix4();
 
-Object.assign( InterleavedBuffer.prototype, {
+       const _vector3 = new Vector3();
 
-       isInterleavedBuffer: true,
+       const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true };
 
-       onUploadCallback: function () {},
+       function getTargetPixelRatio() {
 
-       setUsage: function ( value ) {
-
-               this.usage = value;
+               return _currentRenderTarget === null ? _pixelRatio : 1;
 
-               return this;
+       }
 
-       },
+       // initialize
 
-       copy: function ( source ) {
+       let _gl = _context;
 
-               this.array = new source.array.constructor( source.array );
-               this.count = source.count;
-               this.stride = source.stride;
-               this.usage = source.usage;
+       function getContext( contextNames, contextAttributes ) {
 
-               return this;
+               for ( let i = 0; i < contextNames.length; i ++ ) {
 
-       },
+                       const contextName = contextNames[ i ];
+                       const context = _canvas.getContext( contextName, contextAttributes );
+                       if ( context !== null ) return context;
 
-       copyAt: function ( index1, attribute, index2 ) {
+               }
 
-               index1 *= this.stride;
-               index2 *= attribute.stride;
+               return null;
 
-               for ( let i = 0, l = this.stride; i < l; i ++ ) {
+       }
 
-                       this.array[ index1 + i ] = attribute.array[ index2 + i ];
+       try {
 
-               }
+               const contextAttributes = {
+                       alpha: _alpha,
+                       depth: _depth,
+                       stencil: _stencil,
+                       antialias: _antialias,
+                       premultipliedAlpha: _premultipliedAlpha,
+                       preserveDrawingBuffer: _preserveDrawingBuffer,
+                       powerPreference: _powerPreference,
+                       failIfMajorPerformanceCaveat: _failIfMajorPerformanceCaveat
+               };
 
-               return this;
+               // event listeners must be registered before WebGL context is created, see #12753
 
-       },
+               _canvas.addEventListener( 'webglcontextlost', onContextLost, false );
+               _canvas.addEventListener( 'webglcontextrestored', onContextRestore, false );
 
-       set: function ( value, offset = 0 ) {
+               if ( _gl === null ) {
 
-               this.array.set( value, offset );
+                       const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ];
 
-               return this;
+                       if ( _this.isWebGL1Renderer === true ) {
 
-       },
+                               contextNames.shift();
 
-       clone: function ( data ) {
+                       }
 
-               if ( data.arrayBuffers === undefined ) {
+                       _gl = getContext( contextNames, contextAttributes );
 
-                       data.arrayBuffers = {};
+                       if ( _gl === null ) {
 
-               }
+                               if ( getContext( contextNames ) ) {
 
-               if ( this.array.buffer._uuid === undefined ) {
+                                       throw new Error( 'Error creating WebGL context with your selected attributes.' );
 
-                       this.array.buffer._uuid = MathUtils.generateUUID();
+                               } else {
 
-               }
+                                       throw new Error( 'Error creating WebGL context.' );
 
-               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 ] );
+               // Some experimental-webgl implementations do not have getShaderPrecisionFormat
 
-               const ib = new InterleavedBuffer( array, this.stride );
-               ib.setUsage( this.usage );
+               if ( _gl.getShaderPrecisionFormat === undefined ) {
 
-               return ib;
+                       _gl.getShaderPrecisionFormat = function () {
 
-       },
+                               return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 };
 
-       onUpload: function ( callback ) {
+                       };
 
-               this.onUploadCallback = callback;
+               }
 
-               return this;
+       } catch ( error ) {
 
-       },
+               console.error( 'THREE.WebGLRenderer: ' + error.message );
+               throw error;
 
-       toJSON: function ( data ) {
+       }
 
-               if ( data.arrayBuffers === undefined ) {
+       let extensions, capabilities, state, info;
+       let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects;
+       let programCache, materials, renderLists, renderStates, clipping, shadowMap;
 
-                       data.arrayBuffers = {};
+       let background, morphtargets, bufferRenderer, indexedBufferRenderer;
 
-               }
+       let utils, bindingStates;
 
-               // generate UUID for array buffer if necessary
+       function initGLContext() {
 
-               if ( this.array.buffer._uuid === undefined ) {
+               extensions = new WebGLExtensions( _gl );
 
-                       this.array.buffer._uuid = MathUtils.generateUUID();
+               capabilities = new WebGLCapabilities( _gl, extensions, parameters );
 
-               }
+               extensions.init( capabilities );
 
-               if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) {
+               utils = new WebGLUtils( _gl, extensions, capabilities );
 
-                       data.arrayBuffers[ this.array.buffer._uuid ] = Array.prototype.slice.call( new Uint32Array( this.array.buffer ) );
+               state = new WebGLState( _gl, extensions, capabilities );
 
-               }
+               _currentDrawBuffers[ 0 ] = 1029;
 
-               //
+               info = new WebGLInfo( _gl );
+               properties = new WebGLProperties();
+               textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info );
+               cubemaps = new WebGLCubeMaps( _this );
+               cubeuvmaps = new WebGLCubeUVMaps( _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, capabilities, textures );
+               clipping = new WebGLClipping( properties );
+               programCache = new WebGLPrograms( _this, cubemaps, cubeuvmaps, 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 );
+               shadowMap = new WebGLShadowMap( _this, objects, capabilities );
 
-               return {
-                       uuid: this.uuid,
-                       buffer: this.array.buffer._uuid,
-                       type: this.array.constructor.name,
-                       stride: this.stride
-               };
+               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.shadowMap = shadowMap;
+               _this.state = state;
+               _this.info = info;
 
        }
 
-);
+       initGLContext();
 
-const _vector$6 = new Vector3();
+       // xr
 
-function InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, normalized ) {
+       const xr = new WebXRManager( _this, _gl );
 
-       this.name = '';
+       this.xr = xr;
 
-       this.data = interleavedBuffer;
-       this.itemSize = itemSize;
-       this.offset = offset;
+       // API
 
-       this.normalized = normalized === true;
+       this.getContext = function () {
 
-}
+               return _gl;
 
-Object.defineProperties( InterleavedBufferAttribute.prototype, {
+       };
 
-       count: {
+       this.getContextAttributes = function () {
 
-               get: function () {
+               return _gl.getContextAttributes();
 
-                       return this.data.count;
+       };
 
-               }
+       this.forceContextLoss = function () {
 
-       },
+               const extension = extensions.get( 'WEBGL_lose_context' );
+               if ( extension ) extension.loseContext();
 
-       array: {
+       };
 
-               get: function () {
+       this.forceContextRestore = function () {
 
-                       return this.data.array;
+               const extension = extensions.get( 'WEBGL_lose_context' );
+               if ( extension ) extension.restoreContext();
 
-               }
+       };
 
-       },
+       this.getPixelRatio = function () {
 
-       needsUpdate: {
+               return _pixelRatio;
 
-               set: function ( value ) {
+       };
 
-                       this.data.needsUpdate = value;
+       this.setPixelRatio = function ( value ) {
 
-               }
+               if ( value === undefined ) return;
 
-       }
+               _pixelRatio = value;
 
-} );
+               this.setSize( _width, _height, false );
 
-Object.assign( InterleavedBufferAttribute.prototype, {
+       };
 
-       isInterleavedBufferAttribute: true,
+       this.getSize = function ( target ) {
 
-       applyMatrix4: function ( m ) {
+               return target.set( _width, _height );
 
-               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 );
+       this.setSize = function ( width, height, updateStyle ) {
 
-                       _vector$6.applyMatrix4( m );
+               if ( xr.isPresenting ) {
 
-                       this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z );
+                       console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' );
+                       return;
 
                }
 
-               return this;
+               _width = width;
+               _height = height;
 
-       },
+               _canvas.width = Math.floor( width * _pixelRatio );
+               _canvas.height = Math.floor( height * _pixelRatio );
 
-       setX: function ( index, x ) {
+               if ( updateStyle !== false ) {
 
-               this.data.array[ index * this.data.stride + this.offset ] = x;
+                       _canvas.style.width = width + 'px';
+                       _canvas.style.height = height + 'px';
 
-               return this;
+               }
 
-       },
+               this.setViewport( 0, 0, width, height );
 
-       setY: function ( index, y ) {
+       };
 
-               this.data.array[ index * this.data.stride + this.offset + 1 ] = y;
+       this.getDrawingBufferSize = function ( target ) {
 
-               return this;
+               return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor();
 
-       },
+       };
 
-       setZ: function ( index, z ) {
+       this.setDrawingBufferSize = function ( width, height, pixelRatio ) {
 
-               this.data.array[ index * this.data.stride + this.offset + 2 ] = z;
+               _width = width;
+               _height = height;
 
-               return this;
+               _pixelRatio = pixelRatio;
 
-       },
+               _canvas.width = Math.floor( width * pixelRatio );
+               _canvas.height = Math.floor( height * pixelRatio );
 
-       setW: function ( index, w ) {
+               this.setViewport( 0, 0, width, height );
 
-               this.data.array[ index * this.data.stride + this.offset + 3 ] = w;
+       };
 
-               return this;
+       this.getCurrentViewport = function ( target ) {
 
-       },
+               return target.copy( _currentViewport );
 
-       getX: function ( index ) {
+       };
 
-               return this.data.array[ index * this.data.stride + this.offset ];
+       this.getViewport = function ( target ) {
 
-       },
+               return target.copy( _viewport );
 
-       getY: function ( index ) {
+       };
 
-               return this.data.array[ index * this.data.stride + this.offset + 1 ];
+       this.setViewport = function ( x, y, width, height ) {
 
-       },
+               if ( x.isVector4 ) {
 
-       getZ: function ( index ) {
+                       _viewport.set( x.x, x.y, x.z, x.w );
 
-               return this.data.array[ index * this.data.stride + this.offset + 2 ];
+               } else {
 
-       },
+                       _viewport.set( x, y, width, height );
 
-       getW: function ( index ) {
+               }
 
-               return this.data.array[ index * this.data.stride + this.offset + 3 ];
+               state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() );
 
-       },
+       };
 
-       setXY: function ( index, x, y ) {
+       this.getScissor = function ( target ) {
 
-               index = index * this.data.stride + this.offset;
+               return target.copy( _scissor );
 
-               this.data.array[ index + 0 ] = x;
-               this.data.array[ index + 1 ] = y;
+       };
 
-               return this;
+       this.setScissor = function ( x, y, width, height ) {
 
-       },
+               if ( x.isVector4 ) {
 
-       setXYZ: function ( index, x, y, z ) {
+                       _scissor.set( x.x, x.y, x.z, x.w );
 
-               index = index * this.data.stride + this.offset;
+               } else {
 
-               this.data.array[ index + 0 ] = x;
-               this.data.array[ index + 1 ] = y;
-               this.data.array[ index + 2 ] = z;
+                       _scissor.set( x, y, width, height );
 
-               return this;
+               }
 
-       },
+               state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() );
 
-       setXYZW: function ( index, x, y, z, w ) {
+       };
 
-               index = index * this.data.stride + this.offset;
+       this.getScissorTest = function () {
 
-               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 _scissorTest;
 
-               return this;
+       };
 
-       },
+       this.setScissorTest = function ( boolean ) {
 
-       clone: function ( data ) {
+               state.setScissorTest( _scissorTest = boolean );
 
-               if ( data === undefined ) {
+       };
 
-                       console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interlaved buffer attribute will deinterleave buffer data.' );
+       this.setOpaqueSort = function ( method ) {
 
-                       const array = [];
+               _opaqueSort = method;
 
-                       for ( let i = 0; i < this.count; i ++ ) {
+       };
 
-                               const index = i * this.data.stride + this.offset;
+       this.setTransparentSort = function ( method ) {
 
-                               for ( let j = 0; j < this.itemSize; j ++ ) {
+               _transparentSort = method;
 
-                                       array.push( this.data.array[ index + j ] );
+       };
 
-                               }
+       // Clearing
 
-                       }
+       this.getClearColor = function ( target ) {
 
-                       return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized );
+               return target.copy( background.getClearColor() );
 
-               } else {
+       };
 
-                       if ( data.interleavedBuffers === undefined ) {
+       this.setClearColor = function () {
 
-                               data.interleavedBuffers = {};
+               background.setClearColor.apply( background, arguments );
 
-                       }
+       };
 
-                       if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
+       this.getClearAlpha = function () {
 
-                               data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data );
+               return background.getClearAlpha();
 
-                       }
+       };
 
-                       return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized );
+       this.setClearAlpha = function () {
 
-               }
+               background.setClearAlpha.apply( background, arguments );
 
-       },
+       };
 
-       toJSON: function ( data ) {
+       this.clear = function ( color, depth, stencil ) {
 
-               if ( data === undefined ) {
+               let bits = 0;
 
-                       console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interlaved buffer attribute will deinterleave buffer data.' );
+               if ( color === undefined || color ) bits |= 16384;
+               if ( depth === undefined || depth ) bits |= 256;
+               if ( stencil === undefined || stencil ) bits |= 1024;
 
-                       const array = [];
+               _gl.clear( bits );
 
-                       for ( let i = 0; i < this.count; i ++ ) {
+       };
 
-                               const index = i * this.data.stride + this.offset;
+       this.clearColor = function () {
 
-                               for ( let j = 0; j < this.itemSize; j ++ ) {
+               this.clear( true, false, false );
 
-                                       array.push( this.data.array[ index + j ] );
+       };
 
-                               }
+       this.clearDepth = function () {
 
-                       }
+               this.clear( false, true, false );
 
-                       // 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
-                       };
+       this.clearStencil = function () {
 
-               } else {
+               this.clear( false, false, true );
 
-                       // save as true interlaved attribtue
+       };
 
-                       if ( data.interleavedBuffers === undefined ) {
+       //
 
-                               data.interleavedBuffers = {};
+       this.dispose = function () {
 
-                       }
+               _canvas.removeEventListener( 'webglcontextlost', onContextLost, false );
+               _canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false );
 
-                       if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
+               renderLists.dispose();
+               renderStates.dispose();
+               properties.dispose();
+               cubemaps.dispose();
+               cubeuvmaps.dispose();
+               objects.dispose();
+               bindingStates.dispose();
 
-                               data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data );
+               xr.dispose();
 
-                       }
+               xr.removeEventListener( 'sessionstart', onXRSessionStart );
+               xr.removeEventListener( 'sessionend', onXRSessionEnd );
 
-                       return {
-                               isInterleavedBufferAttribute: true,
-                               itemSize: this.itemSize,
-                               data: this.data.uuid,
-                               offset: this.offset,
-                               normalized: this.normalized
-                       };
+               if ( _transmissionRenderTarget ) {
+
+                       _transmissionRenderTarget.dispose();
+                       _transmissionRenderTarget = null;
 
                }
 
-       }
+               animation.stop();
 
-} );
+       };
 
-/**
- * parameters = {
- *  color: <hex>,
- *  map: new THREE.Texture( <Image> ),
- *  alphaMap: new THREE.Texture( <Image> ),
- *  rotation: <float>,
- *  sizeAttenuation: <bool>
- * }
- */
+       // Events
 
-function SpriteMaterial( parameters ) {
+       function onContextLost( event ) {
 
-       Material.call( this );
+               event.preventDefault();
 
-       this.type = 'SpriteMaterial';
+               console.log( 'THREE.WebGLRenderer: Context Lost.' );
 
-       this.color = new Color( 0xffffff );
+               _isContextLost = true;
 
-       this.map = null;
+       }
 
-       this.alphaMap = null;
+       function onContextRestore( /* event */ ) {
 
-       this.rotation = 0;
+               console.log( 'THREE.WebGLRenderer: Context Restored.' );
 
-       this.sizeAttenuation = true;
+               _isContextLost = false;
 
-       this.transparent = true;
+               const infoAutoReset = info.autoReset;
+               const shadowMapEnabled = shadowMap.enabled;
+               const shadowMapAutoUpdate = shadowMap.autoUpdate;
+               const shadowMapNeedsUpdate = shadowMap.needsUpdate;
+               const shadowMapType = shadowMap.type;
 
-       this.setValues( parameters );
+               initGLContext();
 
-}
+               info.autoReset = infoAutoReset;
+               shadowMap.enabled = shadowMapEnabled;
+               shadowMap.autoUpdate = shadowMapAutoUpdate;
+               shadowMap.needsUpdate = shadowMapNeedsUpdate;
+               shadowMap.type = shadowMapType;
 
-SpriteMaterial.prototype = Object.create( Material.prototype );
-SpriteMaterial.prototype.constructor = SpriteMaterial;
-SpriteMaterial.prototype.isSpriteMaterial = true;
+       }
 
-SpriteMaterial.prototype.copy = function ( source ) {
+       function onMaterialDispose( event ) {
 
-       Material.prototype.copy.call( this, source );
+               const material = event.target;
 
-       this.color.copy( source.color );
+               material.removeEventListener( 'dispose', onMaterialDispose );
 
-       this.map = source.map;
+               deallocateMaterial( material );
 
-       this.alphaMap = source.alphaMap;
+       }
 
-       this.rotation = source.rotation;
+       // Buffer deallocation
 
-       this.sizeAttenuation = source.sizeAttenuation;
+       function deallocateMaterial( material ) {
 
-       return this;
+               releaseMaterialProgramReferences( material );
 
-};
+               properties.remove( material );
 
-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();
+       function releaseMaterialProgramReferences( material ) {
 
-const _vA$1 = new Vector3();
-const _vB$1 = new Vector3();
-const _vC$1 = new Vector3();
+               const programs = properties.get( material ).programs;
 
-const _uvA$1 = new Vector2();
-const _uvB$1 = new Vector2();
-const _uvC$1 = new Vector2();
+               if ( programs !== undefined ) {
 
-function Sprite( material ) {
+                       programs.forEach( function ( program ) {
 
-       Object3D.call( this );
+                               programCache.releaseProgram( program );
 
-       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
-               ] );
+       // Buffer rendering
 
-               const interleavedBuffer = new InterleavedBuffer( float32Array, 5 );
+       this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) {
 
-               _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 ) );
+               if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null)
 
-       }
+               const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );
 
-       this.geometry = _geometry;
-       this.material = ( material !== undefined ) ? material : new SpriteMaterial();
+               const program = setProgram( camera, scene, geometry, material, object );
 
-       this.center = new Vector2( 0.5, 0.5 );
+               state.setMaterial( material, frontFaceCW );
 
-}
+               //
 
-Sprite.prototype = Object.assign( Object.create( Object3D.prototype ), {
+               let index = geometry.index;
+               const position = geometry.attributes.position;
 
-       constructor: Sprite,
+               //
 
-       isSprite: true,
+               if ( index === null ) {
 
-       raycast: function ( raycaster, intersects ) {
+                       if ( position === undefined || position.count === 0 ) return;
 
-               if ( raycaster.camera === null ) {
+               } else if ( index.count === 0 ) {
 
-                       console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' );
+                       return;
 
                }
 
-               _worldScale.setFromMatrixScale( this.matrixWorld );
-
-               _viewWorldMatrix.copy( raycaster.camera.matrixWorld );
-               this.modelViewMatrix.multiplyMatrices( raycaster.camera.matrixWorldInverse, this.matrixWorld );
+               //
 
-               _mvPosition.setFromMatrixPosition( this.modelViewMatrix );
+               let rangeFactor = 1;
 
-               if ( raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false ) {
+               if ( material.wireframe === true ) {
 
-                       _worldScale.multiplyScalar( - _mvPosition.z );
+                       index = geometries.getWireframeAttribute( geometry );
+                       rangeFactor = 2;
 
                }
 
-               const rotation = this.material.rotation;
-               let sin, cos;
+               bindingStates.setup( object, material, program, geometry, index );
 
-               if ( rotation !== 0 ) {
+               let attribute;
+               let renderer = bufferRenderer;
 
-                       cos = Math.cos( rotation );
-                       sin = Math.sin( rotation );
+               if ( index !== null ) {
+
+                       attribute = attributes.get( index );
+
+                       renderer = indexedBufferRenderer;
+                       renderer.setIndex( attribute );
 
                }
 
-               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 );
+               const dataCount = ( index !== null ) ? index.count : position.count;
 
-               _uvA$1.set( 0, 0 );
-               _uvB$1.set( 1, 0 );
-               _uvC$1.set( 1, 1 );
+               const rangeStart = geometry.drawRange.start * rangeFactor;
+               const rangeCount = geometry.drawRange.count * rangeFactor;
 
-               // check first triangle
-               let intersect = raycaster.ray.intersectTriangle( _vA$1, _vB$1, _vC$1, false, _intersectPoint );
+               const groupStart = group !== null ? group.start * rangeFactor : 0;
+               const groupCount = group !== null ? group.count * rangeFactor : Infinity;
 
-               if ( intersect === null ) {
+               const drawStart = Math.max( rangeStart, groupStart );
+               const drawEnd = Math.min( dataCount, rangeStart + rangeCount, groupStart + groupCount ) - 1;
 
-                       // check second triangle
-                       transformVertex( _vB$1.set( - 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
-                       _uvB$1.set( 0, 1 );
+               const drawCount = Math.max( 0, drawEnd - drawStart + 1 );
 
-                       intersect = raycaster.ray.intersectTriangle( _vA$1, _vC$1, _vB$1, false, _intersectPoint );
-                       if ( intersect === null ) {
+               if ( drawCount === 0 ) return;
 
-                               return;
+               //
 
-                       }
+               if ( object.isMesh ) {
 
-               }
+                       if ( material.wireframe === true ) {
 
-               const distance = raycaster.ray.origin.distanceTo( _intersectPoint );
+                               state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() );
+                               renderer.setMode( 1 );
 
-               if ( distance < raycaster.near || distance > raycaster.far ) return;
+                       } else {
 
-               intersects.push( {
+                               renderer.setMode( 4 );
 
-                       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
+                       }
 
-               } );
+               } else if ( object.isLine ) {
 
-       },
+                       let lineWidth = material.linewidth;
 
-       copy: function ( source ) {
+                       if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material
 
-               Object3D.prototype.copy.call( this, source );
+                       state.setLineWidth( lineWidth * getTargetPixelRatio() );
 
-               if ( source.center !== undefined ) this.center.copy( source.center );
+                       if ( object.isLineSegments ) {
 
-               this.material = source.material;
+                               renderer.setMode( 1 );
 
-               return this;
+                       } else if ( object.isLineLoop ) {
 
-       }
+                               renderer.setMode( 2 );
 
-} );
+                       } else {
 
-function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) {
+                               renderer.setMode( 3 );
 
-       // 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 {
+               } else if ( object.isPoints ) {
 
-               _rotatedPosition.copy( _alignedPosition );
+                       renderer.setMode( 0 );
 
-       }
+               } else if ( object.isSprite ) {
 
+                       renderer.setMode( 4 );
 
-       vertexPosition.copy( mvPosition );
-       vertexPosition.x += _rotatedPosition.x;
-       vertexPosition.y += _rotatedPosition.y;
+               }
 
-       // transform to world space
-       vertexPosition.applyMatrix4( _viewWorldMatrix );
+               if ( object.isInstancedMesh ) {
 
-}
+                       renderer.renderInstances( drawStart, drawCount, object.count );
 
-const _v1$4 = new Vector3();
-const _v2$2 = new Vector3();
+               } else if ( geometry.isInstancedBufferGeometry ) {
 
-function LOD() {
+                       const instanceCount = Math.min( geometry.instanceCount, geometry._maxInstanceCount );
 
-       Object3D.call( this );
+                       renderer.renderInstances( drawStart, drawCount, instanceCount );
 
-       this._currentLevel = 0;
+               } else {
 
-       this.type = 'LOD';
+                       renderer.render( drawStart, drawCount );
 
-       Object.defineProperties( this, {
-               levels: {
-                       enumerable: true,
-                       value: []
                }
-       } );
 
-       this.autoUpdate = true;
+       };
 
-}
+       // Compile
 
-LOD.prototype = Object.assign( Object.create( Object3D.prototype ), {
+       this.compile = function ( scene, camera ) {
 
-       constructor: LOD,
+               currentRenderState = renderStates.get( scene );
+               currentRenderState.init();
 
-       isLOD: true,
+               renderStateStack.push( currentRenderState );
 
-       copy: function ( source ) {
+               scene.traverseVisible( function ( object ) {
 
-               Object3D.prototype.copy.call( this, source, false );
+                       if ( object.isLight && object.layers.test( camera.layers ) ) {
 
-               const levels = source.levels;
+                               currentRenderState.pushLight( object );
 
-               for ( let i = 0, l = levels.length; i < l; i ++ ) {
+                               if ( object.castShadow ) {
 
-                       const level = levels[ i ];
+                                       currentRenderState.pushShadow( object );
 
-                       this.addLevel( level.object.clone(), level.distance );
+                               }
 
-               }
+                       }
 
-               this.autoUpdate = source.autoUpdate;
+               } );
 
-               return this;
+               currentRenderState.setupLights( _this.physicallyCorrectLights );
 
-       },
+               scene.traverse( function ( object ) {
 
-       addLevel: function ( object, distance = 0 ) {
+                       const material = object.material;
 
-               distance = Math.abs( distance );
+                       if ( material ) {
 
-               const levels = this.levels;
+                               if ( Array.isArray( material ) ) {
 
-               let l;
+                                       for ( let i = 0; i < material.length; i ++ ) {
 
-               for ( l = 0; l < levels.length; l ++ ) {
+                                               const material2 = material[ i ];
 
-                       if ( distance < levels[ l ].distance ) {
+                                               getProgram( material2, scene, object );
 
-                               break;
+                                       }
 
-                       }
+                               } else {
 
-               }
+                                       getProgram( material, scene, object );
 
-               levels.splice( l, 0, { distance: distance, object: object } );
+                               }
 
-               this.add( object );
+                       }
 
-               return this;
+               } );
 
-       },
+               renderStateStack.pop();
+               currentRenderState = null;
 
-       getCurrentLevel: function () {
+       };
 
-               return this._currentLevel;
+       // Animation Loop
 
-       },
+       let onAnimationFrameCallback = null;
 
-       getObjectForDistance: function ( distance ) {
+       function onAnimationFrame( time ) {
 
-               const levels = this.levels;
+               if ( onAnimationFrameCallback ) onAnimationFrameCallback( time );
 
-               if ( levels.length > 0 ) {
+       }
 
-                       let i, l;
+       function onXRSessionStart() {
 
-                       for ( i = 1, l = levels.length; i < l; i ++ ) {
+               animation.stop();
 
-                               if ( distance < levels[ i ].distance ) {
+       }
 
-                                       break;
+       function onXRSessionEnd() {
 
-                               }
+               animation.start();
 
-                       }
+       }
 
-                       return levels[ i - 1 ].object;
+       const animation = new WebGLAnimation();
+       animation.setAnimationLoop( onAnimationFrame );
 
-               }
+       if ( typeof window !== 'undefined' ) animation.setContext( window );
 
-               return null;
+       this.setAnimationLoop = function ( callback ) {
 
-       },
+               onAnimationFrameCallback = callback;
+               xr.setAnimationLoop( callback );
+
+               ( callback === null ) ? animation.stop() : animation.start();
 
-       raycast: function ( raycaster, intersects ) {
+       };
 
-               const levels = this.levels;
+       xr.addEventListener( 'sessionstart', onXRSessionStart );
+       xr.addEventListener( 'sessionend', onXRSessionEnd );
 
-               if ( levels.length > 0 ) {
+       // Rendering
 
-                       _v1$4.setFromMatrixPosition( this.matrixWorld );
+       this.render = function ( scene, camera ) {
 
-                       const distance = raycaster.ray.origin.distanceTo( _v1$4 );
+               if ( camera !== undefined && camera.isCamera !== true ) {
 
-                       this.getObjectForDistance( distance ).raycast( raycaster, intersects );
+                       console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' );
+                       return;
 
                }
 
-       },
+               if ( _isContextLost === true ) return;
 
-       update: function ( camera ) {
+               // update scene graph
 
-               const levels = this.levels;
+               if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
 
-               if ( levels.length > 1 ) {
+               // update camera matrices and frustum
 
-                       _v1$4.setFromMatrixPosition( camera.matrixWorld );
-                       _v2$2.setFromMatrixPosition( this.matrixWorld );
+               if ( camera.parent === null ) camera.updateMatrixWorld();
 
-                       const distance = _v1$4.distanceTo( _v2$2 ) / camera.zoom;
+               if ( xr.enabled === true && xr.isPresenting === true ) {
 
-                       levels[ 0 ].object.visible = true;
+                       if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera );
 
-                       let i, l;
+                       camera = xr.getCamera(); // use XR camera for rendering
 
-                       for ( i = 1, l = levels.length; i < l; i ++ ) {
+               }
 
-                               if ( distance >= levels[ i ].distance ) {
+               //
+               if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget );
 
-                                       levels[ i - 1 ].object.visible = false;
-                                       levels[ i ].object.visible = true;
+               currentRenderState = renderStates.get( scene, renderStateStack.length );
+               currentRenderState.init();
 
-                               } else {
+               renderStateStack.push( currentRenderState );
 
-                                       break;
+               _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+               _frustum.setFromProjectionMatrix( _projScreenMatrix );
 
-                               }
+               _localClippingEnabled = this.localClippingEnabled;
+               _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled, camera );
 
-                       }
+               currentRenderList = renderLists.get( scene, renderListStack.length );
+               currentRenderList.init();
 
-                       this._currentLevel = i - 1;
+               renderListStack.push( currentRenderList );
+
+               projectObject( scene, camera, 0, _this.sortObjects );
 
-                       for ( ; i < l; i ++ ) {
+               currentRenderList.finish();
 
-                               levels[ i ].object.visible = false;
+               if ( _this.sortObjects === true ) {
 
-                       }
+                       currentRenderList.sort( _opaqueSort, _transparentSort );
 
                }
 
-       },
+               //
 
-       toJSON: function ( meta ) {
+               if ( _clippingEnabled === true ) clipping.beginShadows();
 
-               const data = Object3D.prototype.toJSON.call( this, meta );
+               const shadowsArray = currentRenderState.state.shadowsArray;
 
-               if ( this.autoUpdate === false ) data.object.autoUpdate = false;
+               shadowMap.render( shadowsArray, scene, camera );
 
-               data.object.levels = [];
+               if ( _clippingEnabled === true ) clipping.endShadows();
 
-               const levels = this.levels;
+               //
 
-               for ( let i = 0, l = levels.length; i < l; i ++ ) {
+               if ( this.info.autoReset === true ) this.info.reset();
 
-                       const level = levels[ i ];
+               //
 
-                       data.object.levels.push( {
-                               object: level.object.uuid,
-                               distance: level.distance
-                       } );
+               background.render( currentRenderList, scene );
 
-               }
+               // render scene
 
-               return data;
+               currentRenderState.setupLights( _this.physicallyCorrectLights );
 
-       }
+               if ( camera.isArrayCamera ) {
 
-} );
+                       const cameras = camera.cameras;
 
-const _basePosition = new Vector3();
+                       for ( let i = 0, l = cameras.length; i < l; i ++ ) {
 
-const _skinIndex = new Vector4();
-const _skinWeight = new Vector4();
+                               const camera2 = cameras[ i ];
 
-const _vector$7 = new Vector3();
-const _matrix$1 = new Matrix4();
+                               renderScene( currentRenderList, scene, camera2, camera2.viewport );
 
-function SkinnedMesh( geometry, material ) {
+                       }
 
-       if ( geometry && geometry.isGeometry ) {
+               } else {
 
-               console.error( 'THREE.SkinnedMesh no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+                       renderScene( currentRenderList, scene, camera );
 
-       }
+               }
 
-       Mesh.call( this, geometry, material );
+               //
 
-       this.type = 'SkinnedMesh';
+               if ( _currentRenderTarget !== null ) {
 
-       this.bindMode = 'attached';
-       this.bindMatrix = new Matrix4();
-       this.bindMatrixInverse = new Matrix4();
+                       // resolve multisample renderbuffers to a single-sample texture if necessary
 
-}
+                       textures.updateMultisampleRenderTarget( _currentRenderTarget );
 
-SkinnedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
+                       // Generate mipmap if we're using any kind of mipmap filtering
 
-       constructor: SkinnedMesh,
+                       textures.updateRenderTargetMipmap( _currentRenderTarget );
 
-       isSkinnedMesh: true,
+               }
 
-       copy: function ( source ) {
+               //
 
-               Mesh.prototype.copy.call( this, source );
+               if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera );
 
-               this.bindMode = source.bindMode;
-               this.bindMatrix.copy( source.bindMatrix );
-               this.bindMatrixInverse.copy( source.bindMatrixInverse );
+               // Ensure depth buffer writing is enabled so it can be cleared on next render
 
-               this.skeleton = source.skeleton;
+               state.buffers.depth.setTest( true );
+               state.buffers.depth.setMask( true );
+               state.buffers.color.setMask( true );
 
-               return this;
+               state.setPolygonOffset( false );
 
-       },
+               // _gl.finish();
 
-       bind: function ( skeleton, bindMatrix ) {
+               bindingStates.resetDefaultState();
+               _currentMaterialId = - 1;
+               _currentCamera = null;
 
-               this.skeleton = skeleton;
+               renderStateStack.pop();
 
-               if ( bindMatrix === undefined ) {
+               if ( renderStateStack.length > 0 ) {
 
-                       this.updateMatrixWorld( true );
+                       currentRenderState = renderStateStack[ renderStateStack.length - 1 ];
 
-                       this.skeleton.calculateInverses();
+               } else {
 
-                       bindMatrix = this.matrixWorld;
+                       currentRenderState = null;
 
                }
 
-               this.bindMatrix.copy( bindMatrix );
-               this.bindMatrixInverse.copy( bindMatrix ).invert();
-
-       },
+               renderListStack.pop();
 
-       pose: function () {
+               if ( renderListStack.length > 0 ) {
 
-               this.skeleton.pose();
+                       currentRenderList = renderListStack[ renderListStack.length - 1 ];
 
-       },
+               } else {
 
-       normalizeSkinWeights: function () {
+                       currentRenderList = null;
 
-               const vector = new Vector4();
+               }
 
-               const skinWeight = this.geometry.attributes.skinWeight;
+       };
 
-               for ( let i = 0, l = skinWeight.count; i < l; i ++ ) {
+       function projectObject( object, camera, groupOrder, sortObjects ) {
 
-                       vector.x = skinWeight.getX( i );
-                       vector.y = skinWeight.getY( i );
-                       vector.z = skinWeight.getZ( i );
-                       vector.w = skinWeight.getW( i );
+               if ( object.visible === false ) return;
 
-                       const scale = 1.0 / vector.manhattanLength();
+               const visible = object.layers.test( camera.layers );
 
-                       if ( scale !== Infinity ) {
+               if ( visible ) {
 
-                               vector.multiplyScalar( scale );
+                       if ( object.isGroup ) {
 
-                       } else {
+                               groupOrder = object.renderOrder;
 
-                               vector.set( 1, 0, 0, 0 ); // do something reasonable
+                       } else if ( object.isLOD ) {
 
-                       }
+                               if ( object.autoUpdate === true ) object.update( camera );
 
-                       skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w );
+                       } else if ( object.isLight ) {
 
-               }
+                               currentRenderState.pushLight( object );
 
-       },
+                               if ( object.castShadow ) {
 
-       updateMatrixWorld: function ( force ) {
+                                       currentRenderState.pushShadow( object );
 
-               Mesh.prototype.updateMatrixWorld.call( this, force );
+                               }
 
-               if ( this.bindMode === 'attached' ) {
+                       } else if ( object.isSprite ) {
 
-                       this.bindMatrixInverse.copy( this.matrixWorld ).invert();
+                               if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {
 
-               } else if ( this.bindMode === 'detached' ) {
+                                       if ( sortObjects ) {
 
-                       this.bindMatrixInverse.copy( this.bindMatrix ).invert();
+                                               _vector3.setFromMatrixPosition( object.matrixWorld )
+                                                       .applyMatrix4( _projScreenMatrix );
 
-               } else {
+                                       }
 
-                       console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode );
+                                       const geometry = objects.update( object );
+                                       const material = object.material;
 
-               }
+                                       if ( material.visible ) {
 
-       },
+                                               currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
 
-       boneTransform: function ( index, target ) {
+                                       }
 
-               const skeleton = this.skeleton;
-               const geometry = this.geometry;
+                               }
 
-               _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index );
-               _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index );
+                       } else if ( object.isMesh || object.isLine || object.isPoints ) {
 
-               _basePosition.fromBufferAttribute( geometry.attributes.position, index ).applyMatrix4( this.bindMatrix );
+                               if ( object.isSkinnedMesh ) {
 
-               target.set( 0, 0, 0 );
+                                       // update skeleton only once in a frame
 
-               for ( let i = 0; i < 4; i ++ ) {
+                                       if ( object.skeleton.frame !== info.render.frame ) {
 
-                       const weight = _skinWeight.getComponent( i );
+                                               object.skeleton.update();
+                                               object.skeleton.frame = info.render.frame;
 
-                       if ( weight !== 0 ) {
+                                       }
 
-                               const boneIndex = _skinIndex.getComponent( i );
+                               }
 
-                               _matrix$1.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] );
+                               if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {
 
-                               target.addScaledVector( _vector$7.copy( _basePosition ).applyMatrix4( _matrix$1 ), weight );
+                                       if ( sortObjects ) {
 
-                       }
+                                               _vector3.setFromMatrixPosition( object.matrixWorld )
+                                                       .applyMatrix4( _projScreenMatrix );
 
-               }
+                                       }
 
-               return target.applyMatrix4( this.bindMatrixInverse );
+                                       const geometry = objects.update( object );
+                                       const material = object.material;
 
-       }
+                                       if ( Array.isArray( material ) ) {
 
-} );
+                                               const groups = geometry.groups;
 
-function Bone() {
+                                               for ( let i = 0, l = groups.length; i < l; i ++ ) {
 
-       Object3D.call( this );
+                                                       const group = groups[ i ];
+                                                       const groupMaterial = material[ group.materialIndex ];
 
-       this.type = 'Bone';
+                                                       if ( groupMaterial && groupMaterial.visible ) {
 
-}
+                                                               currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group );
 
-Bone.prototype = Object.assign( Object.create( Object3D.prototype ), {
+                                                       }
 
-       constructor: Bone,
+                                               }
 
-       isBone: true
+                                       } else if ( material.visible ) {
 
-} );
+                                               currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
 
-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;
+               const children = object.children;
 
-       this.frame = - 1;
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
 
-       this.init();
+                       projectObject( children[ i ], camera, groupOrder, sortObjects );
 
-}
+               }
 
-Object.assign( Skeleton.prototype, {
+       }
 
-       init: function () {
+       function renderScene( currentRenderList, scene, camera, viewport ) {
 
-               const bones = this.bones;
-               const boneInverses = this.boneInverses;
+               const opaqueObjects = currentRenderList.opaque;
+               const transmissiveObjects = currentRenderList.transmissive;
+               const transparentObjects = currentRenderList.transparent;
 
-               this.boneMatrices = new Float32Array( bones.length * 16 );
+               currentRenderState.setupLightsView( camera );
 
-               // calculate inverse bone matrices if necessary
+               if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, scene, camera );
 
-               if ( boneInverses.length === 0 ) {
+               if ( viewport ) state.viewport( _currentViewport.copy( viewport ) );
 
-                       this.calculateInverses();
+               if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera );
+               if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera );
+               if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera );
 
-               } else {
+       }
 
-                       // handle special case
+       function renderTransmissionPass( opaqueObjects, scene, camera ) {
 
-                       if ( bones.length !== boneInverses.length ) {
+               if ( _transmissionRenderTarget === null ) {
 
-                               console.warn( 'THREE.Skeleton: Number of inverse bone matrices does not match amount of bones.' );
+                       const needsAntialias = _antialias === true && capabilities.isWebGL2 === true;
+                       const renderTargetType = needsAntialias ? WebGLMultisampleRenderTarget : WebGLRenderTarget;
 
-                               this.boneInverses = [];
+                       _transmissionRenderTarget = new renderTargetType( 1024, 1024, {
+                               generateMipmaps: true,
+                               type: utils.convert( HalfFloatType ) !== null ? HalfFloatType : UnsignedByteType,
+                               minFilter: LinearMipmapLinearFilter,
+                               magFilter: NearestFilter,
+                               wrapS: ClampToEdgeWrapping,
+                               wrapT: ClampToEdgeWrapping
+                       } );
 
-                               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+               }
 
-                                       this.boneInverses.push( new Matrix4() );
+               const currentRenderTarget = _this.getRenderTarget();
+               _this.setRenderTarget( _transmissionRenderTarget );
+               _this.clear();
 
-                               }
+               // Turn off the features which can affect the frag color for opaque objects pass.
+               // Otherwise they are applied twice in opaque objects pass and transmission objects pass.
+               const currentToneMapping = _this.toneMapping;
+               _this.toneMapping = NoToneMapping;
 
-                       }
+               renderObjects( opaqueObjects, scene, camera );
 
-               }
+               _this.toneMapping = currentToneMapping;
 
-       },
+               textures.updateMultisampleRenderTarget( _transmissionRenderTarget );
+               textures.updateRenderTargetMipmap( _transmissionRenderTarget );
 
-       calculateInverses: function () {
+               _this.setRenderTarget( currentRenderTarget );
 
-               this.boneInverses.length = 0;
+       }
 
-               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+       function renderObjects( renderList, scene, camera ) {
 
-                       const inverse = new Matrix4();
+               const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null;
 
-                       if ( this.bones[ i ] ) {
+               for ( let i = 0, l = renderList.length; i < l; i ++ ) {
 
-                               inverse.copy( this.bones[ i ].matrixWorld ).invert();
+                       const renderItem = renderList[ i ];
 
-                       }
+                       const object = renderItem.object;
+                       const geometry = renderItem.geometry;
+                       const material = overrideMaterial === null ? renderItem.material : overrideMaterial;
+                       const group = renderItem.group;
 
-                       this.boneInverses.push( inverse );
+                       if ( object.layers.test( camera.layers ) ) {
 
-               }
+                               renderObject( object, scene, camera, geometry, material, group );
 
-       },
+                       }
 
-       pose: function () {
+               }
 
-               // recover the bind-time world matrices
+       }
 
-               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+       function renderObject( object, scene, camera, geometry, material, group ) {
 
-                       const bone = this.bones[ i ];
+               object.onBeforeRender( _this, scene, camera, geometry, material, group );
 
-                       if ( bone ) {
+               object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+               object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
 
-                               bone.matrixWorld.copy( this.boneInverses[ i ] ).invert();
+               material.onBeforeRender( _this, scene, camera, geometry, object, group );
 
-                       }
+               if ( material.transparent === true && material.side === DoubleSide ) {
 
-               }
+                       material.side = BackSide;
+                       material.needsUpdate = true;
+                       _this.renderBufferDirect( camera, scene, geometry, material, object, group );
 
-               // compute the local matrices, positions, rotations and scales
+                       material.side = FrontSide;
+                       material.needsUpdate = true;
+                       _this.renderBufferDirect( camera, scene, geometry, material, object, group );
 
-               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+                       material.side = DoubleSide;
 
-                       const bone = this.bones[ i ];
+               } else {
 
-                       if ( bone ) {
+                       _this.renderBufferDirect( camera, scene, geometry, material, object, group );
 
-                               if ( bone.parent && bone.parent.isBone ) {
+               }
 
-                                       bone.matrix.copy( bone.parent.matrixWorld ).invert();
-                                       bone.matrix.multiply( bone.matrixWorld );
+               object.onAfterRender( _this, scene, camera, geometry, material, group );
 
-                               } else {
+       }
 
-                                       bone.matrix.copy( bone.matrixWorld );
+       function getProgram( material, scene, object ) {
 
-                               }
+               if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
 
-                               bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+               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 );
 
-       update: function () {
+               let programs = materialProperties.programs;
 
-               const bones = this.bones;
-               const boneInverses = this.boneInverses;
-               const boneMatrices = this.boneMatrices;
-               const boneTexture = this.boneTexture;
+               // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change
 
-               // flatten bone matrices to array
+               materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null;
+               materialProperties.fog = scene.fog;
+               materialProperties.envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || materialProperties.environment );
 
-               for ( let i = 0, il = bones.length; i < il; i ++ ) {
+               if ( programs === undefined ) {
 
-                       // compute the offset between the current and the original transform
+                       // new material
 
-                       const matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix;
+                       material.addEventListener( 'dispose', onMaterialDispose );
 
-                       _offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] );
-                       _offsetMatrix.toArray( boneMatrices, i * 16 );
+                       programs = new Map();
+                       materialProperties.programs = programs;
 
                }
 
-               if ( boneTexture !== null ) {
+               let program = programs.get( programCacheKey );
 
-                       boneTexture.needsUpdate = true;
+               if ( program !== undefined ) {
 
-               }
+                       // early out if program and light state is identical
 
-       },
+                       if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) {
 
-       clone: function () {
+                               updateCommonMaterialProperties( material, parameters );
 
-               return new Skeleton( this.bones, this.boneInverses );
+                               return program;
 
-       },
+                       }
 
-       getBoneByName: function ( name ) {
+               } else {
 
-               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+                       parameters.uniforms = programCache.getUniforms( material );
 
-                       const bone = this.bones[ i ];
+                       material.onBuild( object, parameters, _this );
 
-                       if ( bone.name === name ) {
+                       material.onBeforeCompile( parameters, _this );
 
-                               return bone;
+                       program = programCache.acquireProgram( parameters, programCacheKey );
+                       programs.set( programCacheKey, program );
 
-                       }
+                       materialProperties.uniforms = parameters.uniforms;
 
                }
 
-               return undefined;
+               const uniforms = materialProperties.uniforms;
 
-       },
+               if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) {
 
-       dispose: function ( ) {
+                       uniforms.clippingPlanes = clipping.uniform;
 
-               if ( this.boneTexture !== null ) {
+               }
 
-                       this.boneTexture.dispose();
+               updateCommonMaterialProperties( material, parameters );
 
-                       this.boneTexture = null;
+               // store the light setup it was created for
 
-               }
+               materialProperties.needsLights = materialNeedsLights( material );
+               materialProperties.lightsStateVersion = lightsStateVersion;
 
-       },
+               if ( materialProperties.needsLights ) {
 
-       fromJSON: function ( json, bones ) {
+                       // wire up the material to this renderer's lighting state
 
-               this.uuid = json.uuid;
+                       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;
 
-               for ( let i = 0, l = json.bones.length; i < l; i ++ ) {
+                       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 uuid = json.bones[ i ];
-                       let bone = bones[ uuid ];
+               }
 
-                       if ( bone === undefined ) {
+               const progUniforms = program.getUniforms();
+               const uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, uniforms );
 
-                               console.warn( 'THREE.Skeleton: No bone found with UUID:', uuid );
-                               bone = new Bone();
+               materialProperties.currentProgram = program;
+               materialProperties.uniformsList = uniformsList;
 
-                       }
+               return program;
 
-                       this.bones.push( bone );
-                       this.boneInverses.push( new Matrix4().fromArray( json.boneInverses[ i ] ) );
+       }
 
-               }
+       function updateCommonMaterialProperties( material, parameters ) {
 
-               this.init();
+               const materialProperties = properties.get( material );
 
-               return this;
+               materialProperties.outputEncoding = parameters.outputEncoding;
+               materialProperties.instancing = parameters.instancing;
+               materialProperties.skinning = parameters.skinning;
+               materialProperties.morphTargets = parameters.morphTargets;
+               materialProperties.morphNormals = parameters.morphNormals;
+               materialProperties.morphTargetsCount = parameters.morphTargetsCount;
+               materialProperties.numClippingPlanes = parameters.numClippingPlanes;
+               materialProperties.numIntersection = parameters.numClipIntersection;
+               materialProperties.vertexAlphas = parameters.vertexAlphas;
+               materialProperties.vertexTangents = parameters.vertexTangents;
 
-       },
+       }
 
-       toJSON: function () {
+       function setProgram( camera, scene, geometry, material, object ) {
 
-               const data = {
-                       metadata: {
-                               version: 4.5,
-                               type: 'Skeleton',
-                               generator: 'Skeleton.toJSON'
-                       },
-                       bones: [],
-                       boneInverses: []
-               };
+               if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
 
-               data.uuid = this.uuid;
+               textures.resetTextureUnits();
 
-               const bones = this.bones;
-               const boneInverses = this.boneInverses;
+               const fog = scene.fog;
+               const environment = material.isMeshStandardMaterial ? scene.environment : null;
+               const encoding = ( _currentRenderTarget === null ) ? _this.outputEncoding : _currentRenderTarget.texture.encoding;
+               const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment );
+               const vertexAlphas = material.vertexColors === true && !! geometry.attributes.color && geometry.attributes.color.itemSize === 4;
+               const vertexTangents = !! material.normalMap && !! geometry.attributes.tangent;
+               const morphTargets = !! geometry.morphAttributes.position;
+               const morphNormals = !! geometry.morphAttributes.normal;
+               const morphTargetsCount = !! geometry.morphAttributes.position ? geometry.morphAttributes.position.length : 0;
+
+               const materialProperties = properties.get( material );
+               const lights = currentRenderState.state.lights;
 
-               for ( let i = 0, l = bones.length; i < l; i ++ ) {
+               if ( _clippingEnabled === true ) {
 
-                       const bone = bones[ i ];
-                       data.bones.push( bone.uuid );
+                       if ( _localClippingEnabled === true || camera !== _currentCamera ) {
+
+                               const useCache =
+                                       camera === _currentCamera &&
+                                       material.id === _currentMaterialId;
 
-                       const boneInverse = boneInverses[ i ];
-                       data.boneInverses.push( boneInverse.toArray() );
+                               // 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 );
+
+                       }
 
                }
 
-               return data;
+               //
 
-       }
+               let needsProgramChange = false;
 
-} );
+               if ( material.version === materialProperties.__version ) {
 
-const _instanceLocalMatrix = new Matrix4();
-const _instanceWorldMatrix = new Matrix4();
+                       if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) {
 
-const _instanceIntersects = [];
+                               needsProgramChange = true;
 
-const _mesh = new Mesh();
+                       } else if ( materialProperties.outputEncoding !== encoding ) {
 
-function InstancedMesh( geometry, material, count ) {
+                               needsProgramChange = true;
 
-       Mesh.call( this, geometry, material );
+                       } else if ( object.isInstancedMesh && materialProperties.instancing === false ) {
 
-       this.instanceMatrix = new BufferAttribute( new Float32Array( count * 16 ), 16 );
-       this.instanceColor = null;
+                               needsProgramChange = true;
 
-       this.count = count;
+                       } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) {
 
-       this.frustumCulled = false;
+                               needsProgramChange = true;
 
-}
+                       } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) {
 
-InstancedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
+                               needsProgramChange = true;
 
-       constructor: InstancedMesh,
+                       } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) {
 
-       isInstancedMesh: true,
+                               needsProgramChange = true;
 
-       copy: function ( source ) {
+                       } else if ( materialProperties.envMap !== envMap ) {
 
-               Mesh.prototype.copy.call( this, source );
+                               needsProgramChange = true;
 
-               this.instanceMatrix.copy( source.instanceMatrix );
+                       } else if ( material.fog && materialProperties.fog !== fog ) {
 
-               if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone();
+                               needsProgramChange = true;
 
-               this.count = source.count;
+                       } else if ( materialProperties.numClippingPlanes !== undefined &&
+                               ( materialProperties.numClippingPlanes !== clipping.numPlanes ||
+                               materialProperties.numIntersection !== clipping.numIntersection ) ) {
 
-               return this;
+                               needsProgramChange = true;
 
-       },
+                       } else if ( materialProperties.vertexAlphas !== vertexAlphas ) {
 
-       getColorAt: function ( index, color ) {
+                               needsProgramChange = true;
 
-               color.fromArray( this.instanceColor.array, index * 3 );
+                       } else if ( materialProperties.vertexTangents !== vertexTangents ) {
 
-       },
+                               needsProgramChange = true;
 
-       getMatrixAt: function ( index, matrix ) {
+                       } else if ( materialProperties.morphTargets !== morphTargets ) {
 
-               matrix.fromArray( this.instanceMatrix.array, index * 16 );
+                               needsProgramChange = true;
 
-       },
+                       } else if ( materialProperties.morphNormals !== morphNormals ) {
 
-       raycast: function ( raycaster, intersects ) {
+                               needsProgramChange = true;
 
-               const matrixWorld = this.matrixWorld;
-               const raycastTimes = this.count;
+                       } else if ( capabilities.isWebGL2 === true && materialProperties.morphTargetsCount !== morphTargetsCount ) {
 
-               _mesh.geometry = this.geometry;
-               _mesh.material = this.material;
+                               needsProgramChange = true;
 
-               if ( _mesh.material === undefined ) return;
+                       }
 
-               for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) {
+               } else {
 
-                       // calculate the world matrix for each instance
+                       needsProgramChange = true;
+                       materialProperties.__version = material.version;
 
-                       this.getMatrixAt( instanceId, _instanceLocalMatrix );
+               }
 
-                       _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix );
+               //
 
-                       // the mesh represents this single instance
+               let program = materialProperties.currentProgram;
 
-                       _mesh.matrixWorld = _instanceWorldMatrix;
+               if ( needsProgramChange === true ) {
 
-                       _mesh.raycast( raycaster, _instanceIntersects );
+                       program = getProgram( material, scene, object );
 
-                       // process the result of raycast
+               }
 
-                       for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) {
+               let refreshProgram = false;
+               let refreshMaterial = false;
+               let refreshLights = false;
 
-                               const intersect = _instanceIntersects[ i ];
-                               intersect.instanceId = instanceId;
-                               intersect.object = this;
-                               intersects.push( intersect );
+               const p_uniforms = program.getUniforms(),
+                       m_uniforms = materialProperties.uniforms;
 
-                       }
+               if ( state.useProgram( program.program ) ) {
 
-                       _instanceIntersects.length = 0;
+                       refreshProgram = true;
+                       refreshMaterial = true;
+                       refreshLights = true;
 
                }
 
-       },
-
-       setColorAt: function ( index, color ) {
+               if ( material.id !== _currentMaterialId ) {
 
-               if ( this.instanceColor === null ) {
+                       _currentMaterialId = material.id;
 
-                       this.instanceColor = new BufferAttribute( new Float32Array( this.count * 3 ), 3 );
+                       refreshMaterial = true;
 
                }
 
-               color.toArray( this.instanceColor.array, index * 3 );
+               if ( refreshProgram || _currentCamera !== camera ) {
 
-       },
+                       p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix );
 
-       setMatrixAt: function ( index, matrix ) {
+                       if ( capabilities.logarithmicDepthBuffer ) {
 
-               matrix.toArray( this.instanceMatrix.array, index * 16 );
+                               p_uniforms.setValue( _gl, 'logDepthBufFC',
+                                       2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );
 
-       },
+                       }
 
-       updateMorphTargets: function () {
+                       if ( _currentCamera !== camera ) {
 
-       },
+                               _currentCamera = camera;
 
-       dispose: function () {
+                               // 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:
 
-               this.dispatchEvent( { type: 'dispose' } );
+                               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)
 
-/**
- * parameters = {
- *  color: <hex>,
- *  opacity: <float>,
- *
- *  linewidth: <float>,
- *  linecap: "round",
- *  linejoin: "round"
- * }
- */
+                       if ( material.isShaderMaterial ||
+                               material.isMeshPhongMaterial ||
+                               material.isMeshToonMaterial ||
+                               material.isMeshStandardMaterial ||
+                               material.envMap ) {
 
-function LineBasicMaterial( parameters ) {
+                               const uCamPos = p_uniforms.map.cameraPosition;
 
-       Material.call( this );
+                               if ( uCamPos !== undefined ) {
 
-       this.type = 'LineBasicMaterial';
+                                       uCamPos.setValue( _gl,
+                                               _vector3.setFromMatrixPosition( camera.matrixWorld ) );
 
-       this.color = new Color( 0xffffff );
+                               }
 
-       this.linewidth = 1;
-       this.linecap = 'round';
-       this.linejoin = 'round';
+                       }
 
-       this.morphTargets = false;
+                       if ( material.isMeshPhongMaterial ||
+                               material.isMeshToonMaterial ||
+                               material.isMeshLambertMaterial ||
+                               material.isMeshBasicMaterial ||
+                               material.isMeshStandardMaterial ||
+                               material.isShaderMaterial ) {
 
-       this.setValues( parameters );
+                               p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true );
 
-}
+                       }
 
-LineBasicMaterial.prototype = Object.create( Material.prototype );
-LineBasicMaterial.prototype.constructor = LineBasicMaterial;
+                       if ( material.isMeshPhongMaterial ||
+                               material.isMeshToonMaterial ||
+                               material.isMeshLambertMaterial ||
+                               material.isMeshBasicMaterial ||
+                               material.isMeshStandardMaterial ||
+                               material.isShaderMaterial ||
+                               material.isShadowMaterial ||
+                               object.isSkinnedMesh ) {
 
-LineBasicMaterial.prototype.isLineBasicMaterial = true;
+                               p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );
 
-LineBasicMaterial.prototype.copy = function ( source ) {
+                       }
 
-       Material.prototype.copy.call( this, source );
+               }
 
-       this.color.copy( source.color );
+               // skinning and morph target uniforms must be set even if material didn't change
+               // auto-setting of texture unit for bone and morph texture must go before other textures
+               // otherwise textures used for skinning and morphing can take over texture units reserved for other material textures
 
-       this.linewidth = source.linewidth;
-       this.linecap = source.linecap;
-       this.linejoin = source.linejoin;
+               if ( object.isSkinnedMesh ) {
 
-       this.morphTargets = source.morphTargets;
+                       p_uniforms.setOptional( _gl, object, 'bindMatrix' );
+                       p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' );
 
-       return this;
+                       const skeleton = object.skeleton;
 
-};
+                       if ( skeleton ) {
 
-const _start = new Vector3();
-const _end = new Vector3();
-const _inverseMatrix$1 = new Matrix4();
-const _ray$1 = new Ray();
-const _sphere$2 = new Sphere();
+                               if ( capabilities.floatVertexTextures ) {
 
-function Line( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) {
+                                       if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture();
 
-       Object3D.call( this );
+                                       p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures );
+                                       p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize );
 
-       this.type = 'Line';
+                               } else {
 
-       this.geometry = geometry;
-       this.material = material;
+                                       p_uniforms.setOptional( _gl, skeleton, 'boneMatrices' );
 
-       this.updateMorphTargets();
+                               }
 
-}
+                       }
 
-Line.prototype = Object.assign( Object.create( Object3D.prototype ), {
+               }
 
-       constructor: Line,
+               if ( !! geometry && ( geometry.morphAttributes.position !== undefined || geometry.morphAttributes.normal !== undefined ) ) {
 
-       isLine: true,
+                       morphtargets.update( object, geometry, material, program );
 
-       copy: function ( source ) {
+               }
 
-               Object3D.prototype.copy.call( this, source );
 
-               this.material = source.material;
-               this.geometry = source.geometry;
+               if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) {
 
-               return this;
+                       materialProperties.receiveShadow = object.receiveShadow;
+                       p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow );
 
-       },
+               }
 
-       computeLineDistances: function () {
+               if ( refreshMaterial ) {
 
-               const geometry = this.geometry;
+                       p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure );
 
-               if ( geometry.isBufferGeometry ) {
+                       if ( materialProperties.needsLights ) {
 
-                       // we assume non-indexed geometry
+                               // the current material requires lighting info
 
-                       if ( geometry.index === null ) {
+                               // 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
 
-                               const positionAttribute = geometry.attributes.position;
-                               const lineDistances = [ 0 ];
+                               markUniformsLightsNeedsUpdate( m_uniforms, refreshLights );
 
-                               for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) {
+                       }
 
-                                       _start.fromBufferAttribute( positionAttribute, i - 1 );
-                                       _end.fromBufferAttribute( positionAttribute, i );
+                       // refresh uniforms common to several materials
 
-                                       lineDistances[ i ] = lineDistances[ i - 1 ];
-                                       lineDistances[ i ] += _start.distanceTo( _end );
+                       if ( fog && material.fog ) {
 
-                               }
+                               materials.refreshFogUniforms( m_uniforms, fog );
 
-                               geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
+                       }
 
-                       } else {
+                       materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, _transmissionRenderTarget );
 
-                               console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
+                       WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
 
-                       }
+               }
 
-               } else if ( geometry.isGeometry ) {
+               if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) {
 
-                       console.error( 'THREE.Line.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+                       WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
+                       material.uniformsNeedUpdate = false;
 
                }
 
-               return this;
+               if ( material.isSpriteMaterial ) {
 
-       },
+                       p_uniforms.setValue( _gl, 'center', object.center );
 
-       raycast: function ( raycaster, intersects ) {
+               }
 
-               const geometry = this.geometry;
-               const matrixWorld = this.matrixWorld;
-               const threshold = raycaster.params.Line.threshold;
+               // common matrices
 
-               // Checking boundingSphere distance to ray
+               p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );
+               p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );
+               p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );
 
-               if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+               return program;
 
-               _sphere$2.copy( geometry.boundingSphere );
-               _sphere$2.applyMatrix4( matrixWorld );
-               _sphere$2.radius += threshold;
+       }
 
-               if ( raycaster.ray.intersectsSphere( _sphere$2 ) === false ) return;
+       // If uniforms are marked as clean, they don't need to be loaded to the GPU.
 
-               //
+       function markUniformsLightsNeedsUpdate( uniforms, value ) {
 
-               _inverseMatrix$1.copy( matrixWorld ).invert();
-               _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 );
+               uniforms.ambientLightColor.needsUpdate = value;
+               uniforms.lightProbe.needsUpdate = value;
 
-               const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
-               const localThresholdSq = localThreshold * localThreshold;
+               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;
 
-               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 ) {
+       function materialNeedsLights( material ) {
 
-                       const index = geometry.index;
-                       const attributes = geometry.attributes;
-                       const positionAttribute = attributes.position;
+               return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial ||
+                       material.isMeshStandardMaterial || material.isShadowMaterial ||
+                       ( material.isShaderMaterial && material.lights === true );
 
-                       if ( index !== null ) {
+       }
 
-                               const indices = index.array;
+       this.getActiveCubeFace = function () {
 
-                               for ( let i = 0, l = indices.length - 1; i < l; i += step ) {
+               return _currentActiveCubeFace;
 
-                                       const a = indices[ i ];
-                                       const b = indices[ i + 1 ];
+       };
 
-                                       vStart.fromBufferAttribute( positionAttribute, a );
-                                       vEnd.fromBufferAttribute( positionAttribute, b );
+       this.getActiveMipmapLevel = function () {
 
-                                       const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
+               return _currentActiveMipmapLevel;
 
-                                       if ( distSq > localThresholdSq ) continue;
+       };
 
-                                       interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
+       this.getRenderTarget = function () {
 
-                                       const distance = raycaster.ray.origin.distanceTo( interRay );
+               return _currentRenderTarget;
 
-                                       if ( distance < raycaster.near || distance > raycaster.far ) continue;
+       };
 
-                                       intersects.push( {
+       this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) {
 
-                                               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
+               _currentRenderTarget = renderTarget;
+               _currentActiveCubeFace = activeCubeFace;
+               _currentActiveMipmapLevel = activeMipmapLevel;
 
-                                       } );
+               if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) {
 
-                               }
+                       textures.setupRenderTarget( renderTarget );
 
-                       } else {
+               }
 
-                               for ( let i = 0, l = positionAttribute.count - 1; i < l; i += step ) {
+               let framebuffer = null;
+               let isCube = false;
+               let isRenderTarget3D = false;
 
-                                       vStart.fromBufferAttribute( positionAttribute, i );
-                                       vEnd.fromBufferAttribute( positionAttribute, i + 1 );
+               if ( renderTarget ) {
 
-                                       const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
+                       const texture = renderTarget.texture;
 
-                                       if ( distSq > localThresholdSq ) continue;
+                       if ( texture.isDataTexture3D || texture.isDataTexture2DArray ) {
 
-                                       interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
+                               isRenderTarget3D = true;
 
-                                       const distance = raycaster.ray.origin.distanceTo( interRay );
+                       }
 
-                                       if ( distance < raycaster.near || distance > raycaster.far ) continue;
+                       const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer;
 
-                                       intersects.push( {
+                       if ( renderTarget.isWebGLCubeRenderTarget ) {
 
-                                               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
+                               framebuffer = __webglFramebuffer[ activeCubeFace ];
+                               isCube = true;
 
-                                       } );
+                       } else if ( renderTarget.isWebGLMultisampleRenderTarget ) {
 
-                               }
+                               framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer;
+
+                       } else {
+
+                               framebuffer = __webglFramebuffer;
 
                        }
 
-               } else if ( geometry.isGeometry ) {
+                       _currentViewport.copy( renderTarget.viewport );
+                       _currentScissor.copy( renderTarget.scissor );
+                       _currentScissorTest = renderTarget.scissorTest;
 
-                       console.error( 'THREE.Line.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+               } else {
+
+                       _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor();
+                       _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor();
+                       _currentScissorTest = _scissorTest;
 
                }
 
-       },
+               const framebufferBound = state.bindFramebuffer( 36160, framebuffer );
 
-       updateMorphTargets: function () {
+               if ( framebufferBound && capabilities.drawBuffers ) {
 
-               const geometry = this.geometry;
+                       let needsUpdate = false;
 
-               if ( geometry.isBufferGeometry ) {
+                       if ( renderTarget ) {
 
-                       const morphAttributes = geometry.morphAttributes;
-                       const keys = Object.keys( morphAttributes );
+                               if ( renderTarget.isWebGLMultipleRenderTargets ) {
 
-                       if ( keys.length > 0 ) {
+                                       const textures = renderTarget.texture;
 
-                               const morphAttribute = morphAttributes[ keys[ 0 ] ];
+                                       if ( _currentDrawBuffers.length !== textures.length || _currentDrawBuffers[ 0 ] !== 36064 ) {
 
-                               if ( morphAttribute !== undefined ) {
+                                               for ( let i = 0, il = textures.length; i < il; i ++ ) {
 
-                                       this.morphTargetInfluences = [];
-                                       this.morphTargetDictionary = {};
+                                                       _currentDrawBuffers[ i ] = 36064 + i;
 
-                                       for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
+                                               }
 
-                                               const name = morphAttribute[ m ].name || String( m );
+                                               _currentDrawBuffers.length = textures.length;
 
-                                               this.morphTargetInfluences.push( 0 );
-                                               this.morphTargetDictionary[ name ] = m;
+                                               needsUpdate = true;
 
                                        }
 
-                               }
-
-                       }
+                               } else {
 
-               } else {
+                                       if ( _currentDrawBuffers.length !== 1 || _currentDrawBuffers[ 0 ] !== 36064 ) {
 
-                       const morphTargets = geometry.morphTargets;
+                                               _currentDrawBuffers[ 0 ] = 36064;
+                                               _currentDrawBuffers.length = 1;
 
-                       if ( morphTargets !== undefined && morphTargets.length > 0 ) {
+                                               needsUpdate = true;
 
-                               console.error( 'THREE.Line.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
+                                       }
 
-                       }
+                               }
 
-               }
+                       } else {
 
-       }
+                               if ( _currentDrawBuffers.length !== 1 || _currentDrawBuffers[ 0 ] !== 1029 ) {
 
-} );
+                                       _currentDrawBuffers[ 0 ] = 1029;
+                                       _currentDrawBuffers.length = 1;
 
-const _start$1 = new Vector3();
-const _end$1 = new Vector3();
+                                       needsUpdate = true;
 
-function LineSegments( geometry, material ) {
+                               }
 
-       Line.call( this, geometry, material );
+                       }
 
-       this.type = 'LineSegments';
+                       if ( needsUpdate ) {
 
-}
+                               if ( capabilities.isWebGL2 ) {
 
-LineSegments.prototype = Object.assign( Object.create( Line.prototype ), {
+                                       _gl.drawBuffers( _currentDrawBuffers );
 
-       constructor: LineSegments,
+                               } else {
 
-       isLineSegments: true,
+                                       extensions.get( 'WEBGL_draw_buffers' ).drawBuffersWEBGL( _currentDrawBuffers );
 
-       computeLineDistances: function () {
+                               }
 
-               const geometry = this.geometry;
+                       }
 
-               if ( geometry.isBufferGeometry ) {
+               }
 
-                       // we assume non-indexed geometry
+               state.viewport( _currentViewport );
+               state.scissor( _currentScissor );
+               state.setScissorTest( _currentScissorTest );
 
-                       if ( geometry.index === null ) {
+               if ( isCube ) {
 
-                               const positionAttribute = geometry.attributes.position;
-                               const lineDistances = [];
+                       const textureProperties = properties.get( renderTarget.texture );
+                       _gl.framebufferTexture2D( 36160, 36064, 34069 + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel );
 
-                               for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) {
+               } else if ( isRenderTarget3D ) {
 
-                                       _start$1.fromBufferAttribute( positionAttribute, i );
-                                       _end$1.fromBufferAttribute( positionAttribute, i + 1 );
+                       const textureProperties = properties.get( renderTarget.texture );
+                       const layer = activeCubeFace || 0;
+                       _gl.framebufferTextureLayer( 36160, 36064, textureProperties.__webglTexture, activeMipmapLevel || 0, layer );
 
-                                       lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ];
-                                       lineDistances[ i + 1 ] = lineDistances[ i ] + _start$1.distanceTo( _end$1 );
+               }
 
-                               }
+               _currentMaterialId = - 1; // reset current material to ensure correct uniform bindings
 
-                               geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
+       };
 
-                       } else {
+       this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) {
 
-                               console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
+               if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) {
 
-                       }
+                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' );
+                       return;
 
-               } else if ( geometry.isGeometry ) {
+               }
 
-                       console.error( 'THREE.LineSegments.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+               let framebuffer = properties.get( renderTarget ).__webglFramebuffer;
 
-               }
+               if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) {
 
-               return this;
+                       framebuffer = framebuffer[ activeCubeFaceIndex ];
 
-       }
+               }
 
-} );
+               if ( framebuffer ) {
 
-function LineLoop( geometry, material ) {
+                       state.bindFramebuffer( 36160, framebuffer );
 
-       Line.call( this, geometry, material );
+                       try {
 
-       this.type = 'LineLoop';
+                               const texture = renderTarget.texture;
+                               const textureFormat = texture.format;
+                               const textureType = texture.type;
 
-}
+                               if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( 35739 ) ) {
 
-LineLoop.prototype = Object.assign( Object.create( Line.prototype ), {
+                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' );
+                                       return;
 
-       constructor: LineLoop,
+                               }
 
-       isLineLoop: true,
+                               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 ) && // 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 ) {
 
-/**
- * parameters = {
- *  color: <hex>,
- *  opacity: <float>,
- *  map: new THREE.Texture( <Image> ),
- *  alphaMap: new THREE.Texture( <Image> ),
- *
- *  size: <float>,
- *  sizeAttenuation: <bool>
- *
- *  morphTargets: <bool>
- * }
- */
+                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' );
+                                       return;
 
-function PointsMaterial( parameters ) {
+                               }
 
-       Material.call( this );
+                               if ( _gl.checkFramebufferStatus( 36160 ) === 36053 ) {
 
-       this.type = 'PointsMaterial';
+                                       // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)
 
-       this.color = new Color( 0xffffff );
+                                       if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {
 
-       this.map = null;
+                                               _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer );
 
-       this.alphaMap = null;
+                                       }
 
-       this.size = 1;
-       this.sizeAttenuation = true;
+                               } else {
 
-       this.morphTargets = false;
+                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' );
 
-       this.setValues( parameters );
+                               }
 
-}
+                       } finally {
 
-PointsMaterial.prototype = Object.create( Material.prototype );
-PointsMaterial.prototype.constructor = PointsMaterial;
+                               // restore framebuffer of current render target if necessary
 
-PointsMaterial.prototype.isPointsMaterial = true;
+                               const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null;
+                               state.bindFramebuffer( 36160, framebuffer );
 
-PointsMaterial.prototype.copy = function ( source ) {
+                       }
 
-       Material.prototype.copy.call( this, source );
+               }
 
-       this.color.copy( source.color );
+       };
 
-       this.map = source.map;
+       this.copyFramebufferToTexture = function ( position, texture, level = 0 ) {
 
-       this.alphaMap = source.alphaMap;
+               const levelScale = Math.pow( 2, - level );
+               const width = Math.floor( texture.image.width * levelScale );
+               const height = Math.floor( texture.image.height * levelScale );
 
-       this.size = source.size;
-       this.sizeAttenuation = source.sizeAttenuation;
+               let glFormat = utils.convert( texture.format );
 
-       this.morphTargets = source.morphTargets;
+               if ( capabilities.isWebGL2 ) {
 
-       return this;
+                       // Workaround for https://bugs.chromium.org/p/chromium/issues/detail?id=1120100
+                       // Not needed in Chrome 93+
 
-};
+                       if ( glFormat === 6407 ) glFormat = 32849;
+                       if ( glFormat === 6408 ) glFormat = 32856;
 
-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() ) {
+               textures.setTexture2D( texture, 0 );
 
-       Object3D.call( this );
+               _gl.copyTexImage2D( 3553, level, glFormat, position.x, position.y, width, height, 0 );
 
-       this.type = 'Points';
+               state.unbindTexture();
 
-       this.geometry = geometry;
-       this.material = material;
+       };
 
-       this.updateMorphTargets();
+       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 );
 
-Points.prototype = Object.assign( Object.create( Object3D.prototype ), {
+               textures.setTexture2D( dstTexture, 0 );
 
-       constructor: Points,
+               // 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 );
 
-       isPoints: true,
+               if ( srcTexture.isDataTexture ) {
 
-       copy: function ( source ) {
+                       _gl.texSubImage2D( 3553, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data );
 
-               Object3D.prototype.copy.call( this, source );
+               } else {
 
-               this.material = source.material;
-               this.geometry = source.geometry;
+                       if ( srcTexture.isCompressedTexture ) {
 
-               return this;
+                               _gl.compressedTexSubImage2D( 3553, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data );
 
-       },
+                       } else {
 
-       raycast: function ( raycaster, intersects ) {
+                               _gl.texSubImage2D( 3553, level, position.x, position.y, glFormat, glType, srcTexture.image );
 
-               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();
+               // Generate mipmaps only when copying level 0
+               if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( 3553 );
 
-               _sphere$3.copy( geometry.boundingSphere );
-               _sphere$3.applyMatrix4( matrixWorld );
-               _sphere$3.radius += threshold;
+               state.unbindTexture();
 
-               if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return;
+       };
 
-               //
+       this.copyTextureToTexture3D = function ( sourceBox, position, srcTexture, dstTexture, level = 0 ) {
 
-               _inverseMatrix$2.copy( matrixWorld ).invert();
-               _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 );
+               if ( _this.isWebGL1Renderer ) {
 
-               const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
-               const localThresholdSq = localThreshold * localThreshold;
+                       console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.' );
+                       return;
 
-               if ( geometry.isBufferGeometry ) {
+               }
 
-                       const index = geometry.index;
-                       const attributes = geometry.attributes;
-                       const positionAttribute = attributes.position;
+               const width = sourceBox.max.x - sourceBox.min.x + 1;
+               const height = sourceBox.max.y - sourceBox.min.y + 1;
+               const depth = sourceBox.max.z - sourceBox.min.z + 1;
+               const glFormat = utils.convert( dstTexture.format );
+               const glType = utils.convert( dstTexture.type );
+               let glTarget;
 
-                       if ( index !== null ) {
+               if ( dstTexture.isDataTexture3D ) {
 
-                               const indices = index.array;
+                       textures.setTexture3D( dstTexture, 0 );
+                       glTarget = 32879;
 
-                               for ( let i = 0, il = indices.length; i < il; i ++ ) {
+               } else if ( dstTexture.isDataTexture2DArray ) {
 
-                                       const a = indices[ i ];
+                       textures.setTexture2DArray( dstTexture, 0 );
+                       glTarget = 35866;
 
-                                       _position$1.fromBufferAttribute( positionAttribute, a );
+               } else {
 
-                                       testPoint( _position$1, a, localThresholdSq, matrixWorld, raycaster, intersects, this );
+                       console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.' );
+                       return;
 
-                               }
+               }
 
-                       } else {
+               _gl.pixelStorei( 37440, dstTexture.flipY );
+               _gl.pixelStorei( 37441, dstTexture.premultiplyAlpha );
+               _gl.pixelStorei( 3317, dstTexture.unpackAlignment );
 
-                               for ( let i = 0, l = positionAttribute.count; i < l; i ++ ) {
+               const unpackRowLen = _gl.getParameter( 3314 );
+               const unpackImageHeight = _gl.getParameter( 32878 );
+               const unpackSkipPixels = _gl.getParameter( 3316 );
+               const unpackSkipRows = _gl.getParameter( 3315 );
+               const unpackSkipImages = _gl.getParameter( 32877 );
 
-                                       _position$1.fromBufferAttribute( positionAttribute, i );
+               const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ 0 ] : srcTexture.image;
 
-                                       testPoint( _position$1, i, localThresholdSq, matrixWorld, raycaster, intersects, this );
+               _gl.pixelStorei( 3314, image.width );
+               _gl.pixelStorei( 32878, image.height );
+               _gl.pixelStorei( 3316, sourceBox.min.x );
+               _gl.pixelStorei( 3315, sourceBox.min.y );
+               _gl.pixelStorei( 32877, sourceBox.min.z );
 
-                               }
+               if ( srcTexture.isDataTexture || srcTexture.isDataTexture3D ) {
 
-                       }
+                       _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image.data );
 
                } else {
 
-                       console.error( 'THREE.Points.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+                       if ( srcTexture.isCompressedTexture ) {
 
-               }
+                               console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: untested support for compressed srcTexture.' );
+                               _gl.compressedTexSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, image.data );
 
-       },
+                       } else {
 
-       updateMorphTargets: function () {
+                               _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image );
 
-               const geometry = this.geometry;
+                       }
 
-               if ( geometry.isBufferGeometry ) {
+               }
 
-                       const morphAttributes = geometry.morphAttributes;
-                       const keys = Object.keys( morphAttributes );
+               _gl.pixelStorei( 3314, unpackRowLen );
+               _gl.pixelStorei( 32878, unpackImageHeight );
+               _gl.pixelStorei( 3316, unpackSkipPixels );
+               _gl.pixelStorei( 3315, unpackSkipRows );
+               _gl.pixelStorei( 32877, unpackSkipImages );
 
-                       if ( keys.length > 0 ) {
+               // Generate mipmaps only when copying level 0
+               if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( glTarget );
 
-                               const morphAttribute = morphAttributes[ keys[ 0 ] ];
+               state.unbindTexture();
 
-                               if ( morphAttribute !== undefined ) {
+       };
 
-                                       this.morphTargetInfluences = [];
-                                       this.morphTargetDictionary = {};
+       this.initTexture = function ( texture ) {
 
-                                       for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
+               textures.setTexture2D( texture, 0 );
 
-                                               const name = morphAttribute[ m ].name || String( m );
+               state.unbindTexture();
 
-                                               this.morphTargetInfluences.push( 0 );
-                                               this.morphTargetDictionary[ name ] = m;
+       };
 
-                                       }
+       this.resetState = function () {
 
-                               }
+               _currentActiveCubeFace = 0;
+               _currentActiveMipmapLevel = 0;
+               _currentRenderTarget = null;
 
-                       }
+               state.reset();
+               bindingStates.reset();
 
-               } else {
+       };
 
-                       const morphTargets = geometry.morphTargets;
+       if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
 
-                       if ( morphTargets !== undefined && morphTargets.length > 0 ) {
+               __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
 
-                               console.error( 'THREE.Points.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
+       }
 
-                       }
+}
 
-               }
+WebGLRenderer.prototype.isWebGLRenderer = true;
 
-       }
+class WebGL1Renderer extends WebGLRenderer {}
 
-} );
+WebGL1Renderer.prototype.isWebGL1Renderer = true;
 
-function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) {
+class Scene extends Object3D {
 
-       const rayPointDistanceSq = _ray$2.distanceSqToPoint( point );
+       constructor() {
 
-       if ( rayPointDistanceSq < localThresholdSq ) {
+               super();
 
-               const intersectPoint = new Vector3();
+               this.type = 'Scene';
 
-               _ray$2.closestPointToPoint( point, intersectPoint );
-               intersectPoint.applyMatrix4( matrixWorld );
+               this.background = null;
+               this.environment = null;
+               this.fog = null;
 
-               const distance = raycaster.ray.origin.distanceTo( intersectPoint );
+               this.overrideMaterial = null;
 
-               if ( distance < raycaster.near || distance > raycaster.far ) return;
+               this.autoUpdate = true; // checked by the renderer
 
-               intersects.push( {
+               if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
 
-                       distance: distance,
-                       distanceToRay: Math.sqrt( rayPointDistanceSq ),
-                       point: intersectPoint,
-                       index: index,
-                       face: null,
-                       object: object
+                       __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
 
-               } );
+               }
 
        }
 
-}
-
-function VideoTexture( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
+       copy( source, recursive ) {
 
-       Texture.call( this, video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+               super.copy( source, recursive );
 
-       this.format = format !== undefined ? format : RGBFormat;
+               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();
 
-       this.minFilter = minFilter !== undefined ? minFilter : LinearFilter;
-       this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;
+               if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone();
 
-       this.generateMipmaps = false;
+               this.autoUpdate = source.autoUpdate;
+               this.matrixAutoUpdate = source.matrixAutoUpdate;
 
-       const scope = this;
+               return this;
 
-       function updateVideo() {
+       }
 
-               scope.needsUpdate = true;
-               video.requestVideoFrameCallback( updateVideo );
+       toJSON( meta ) {
 
-       }
+               const data = super.toJSON( meta );
 
-       if ( 'requestVideoFrameCallback' in video ) {
+               if ( this.fog !== null ) data.object.fog = this.fog.toJSON();
 
-               video.requestVideoFrameCallback( updateVideo );
+               return data;
 
        }
 
 }
 
-VideoTexture.prototype = Object.assign( Object.create( Texture.prototype ), {
+Scene.prototype.isScene = true;
 
-       constructor: VideoTexture,
+class InterleavedBuffer {
 
-       clone: function () {
+       constructor( array, stride ) {
 
-               return new this.constructor( this.image ).copy( this );
+               this.array = array;
+               this.stride = stride;
+               this.count = array !== undefined ? array.length / stride : 0;
 
-       },
+               this.usage = StaticDrawUsage;
+               this.updateRange = { offset: 0, count: - 1 };
 
-       isVideoTexture: true,
+               this.version = 0;
 
-       update: function () {
+               this.uuid = generateUUID();
 
-               const video = this.image;
-               const hasVideoFrameCallback = 'requestVideoFrameCallback' in video;
+       }
 
-               if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) {
+       onUploadCallback() {}
 
-                       this.needsUpdate = true;
+       set needsUpdate( value ) {
 
-               }
+               if ( value === true ) this.version ++;
 
        }
 
-} );
+       setUsage( value ) {
 
-function CompressedTexture( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {
+               this.usage = value;
 
-       Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
+               return this;
 
-       this.image = { width: width, height: height };
-       this.mipmaps = mipmaps;
+       }
 
-       // no flipping for cube textures
-       // (also flipping doesn't work for compressed textures )
+       copy( source ) {
 
-       this.flipY = false;
+               this.array = new source.array.constructor( source.array );
+               this.count = source.count;
+               this.stride = source.stride;
+               this.usage = source.usage;
 
-       // can't generate mipmaps for compressed textures
-       // mips must be embedded in DDS files
+               return this;
 
-       this.generateMipmaps = false;
+       }
 
-}
+       copyAt( index1, attribute, index2 ) {
 
-CompressedTexture.prototype = Object.create( Texture.prototype );
-CompressedTexture.prototype.constructor = CompressedTexture;
+               index1 *= this.stride;
+               index2 *= attribute.stride;
 
-CompressedTexture.prototype.isCompressedTexture = true;
+               for ( let i = 0, l = this.stride; i < l; i ++ ) {
 
-function CanvasTexture( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
+                       this.array[ index1 + i ] = attribute.array[ index2 + i ];
 
-       Texture.call( this, canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+               }
 
-       this.needsUpdate = true;
+               return this;
 
-}
+       }
 
-CanvasTexture.prototype = Object.create( Texture.prototype );
-CanvasTexture.prototype.constructor = CanvasTexture;
-CanvasTexture.prototype.isCanvasTexture = true;
+       set( value, offset = 0 ) {
 
-function DepthTexture( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) {
+               this.array.set( value, offset );
 
-       format = format !== undefined ? format : DepthFormat;
+               return this;
 
-       if ( format !== DepthFormat && format !== DepthStencilFormat ) {
+       }
 
-               throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' );
+       clone( data ) {
 
-       }
+               if ( data.arrayBuffers === undefined ) {
 
-       if ( type === undefined && format === DepthFormat ) type = UnsignedShortType;
-       if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type;
+                       data.arrayBuffers = {};
 
-       Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+               }
 
-       this.image = { width: width, height: height };
+               if ( this.array.buffer._uuid === undefined ) {
 
-       this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
-       this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;
+                       this.array.buffer._uuid = generateUUID();
 
-       this.flipY = false;
-       this.generateMipmaps = false;
+               }
 
-}
+               if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) {
 
-DepthTexture.prototype = Object.create( Texture.prototype );
-DepthTexture.prototype.constructor = DepthTexture;
-DepthTexture.prototype.isDepthTexture = true;
+                       data.arrayBuffers[ this.array.buffer._uuid ] = this.array.slice( 0 ).buffer;
 
-class CircleGeometry extends BufferGeometry {
+               }
 
-       constructor( radius = 1, segments = 8, thetaStart = 0, thetaLength = Math.PI * 2 ) {
+               const array = new this.array.constructor( data.arrayBuffers[ this.array.buffer._uuid ] );
 
-               super();
+               const ib = new this.constructor( array, this.stride );
+               ib.setUsage( this.usage );
 
-               this.type = 'CircleGeometry';
+               return ib;
 
-               this.parameters = {
-                       radius: radius,
-                       segments: segments,
-                       thetaStart: thetaStart,
-                       thetaLength: thetaLength
-               };
+       }
 
-               segments = Math.max( 3, segments );
+       onUpload( callback ) {
 
-               // buffers
+               this.onUploadCallback = callback;
 
-               const indices = [];
-               const vertices = [];
-               const normals = [];
-               const uvs = [];
+               return this;
 
-               // helper variables
+       }
 
-               const vertex = new Vector3();
-               const uv = new Vector2();
+       toJSON( data ) {
 
-               // center point
+               if ( data.arrayBuffers === undefined ) {
 
-               vertices.push( 0, 0, 0 );
-               normals.push( 0, 0, 1 );
-               uvs.push( 0.5, 0.5 );
+                       data.arrayBuffers = {};
 
-               for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) {
+               }
 
-                       const segment = thetaStart + s / segments * thetaLength;
+               // generate UUID for array buffer if necessary
 
-                       // vertex
+               if ( this.array.buffer._uuid === undefined ) {
 
-                       vertex.x = radius * Math.cos( segment );
-                       vertex.y = radius * Math.sin( segment );
+                       this.array.buffer._uuid = generateUUID();
 
-                       vertices.push( vertex.x, vertex.y, vertex.z );
+               }
 
-                       // normal
+               if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) {
 
-                       normals.push( 0, 0, 1 );
+                       data.arrayBuffers[ this.array.buffer._uuid ] = Array.prototype.slice.call( new Uint32Array( this.array.buffer ) );
 
-                       // uvs
+               }
 
-                       uv.x = ( vertices[ i ] / radius + 1 ) / 2;
-                       uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2;
+               //
 
-                       uvs.push( uv.x, uv.y );
+               return {
+                       uuid: this.uuid,
+                       buffer: this.array.buffer._uuid,
+                       type: this.array.constructor.name,
+                       stride: this.stride
+               };
 
-               }
+       }
 
-               // indices
+}
 
-               for ( let i = 1; i <= segments; i ++ ) {
+InterleavedBuffer.prototype.isInterleavedBuffer = true;
 
-                       indices.push( i, i + 1, 0 );
+const _vector$6 = /*@__PURE__*/ new Vector3();
 
-               }
+class InterleavedBufferAttribute {
 
-               // build geometry
+       constructor( interleavedBuffer, itemSize, offset, normalized = false ) {
 
-               this.setIndex( indices );
-               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
-               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
-               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+               this.name = '';
 
-       }
+               this.data = interleavedBuffer;
+               this.itemSize = itemSize;
+               this.offset = offset;
 
-}
+               this.normalized = normalized === true;
 
-new Vector3();
-new Vector3();
-new Vector3();
-new Triangle();
+       }
 
-/**
- * Port from https://github.com/mapbox/earcut (v2.2.2)
- */
+       get count() {
 
-const Earcut = {
+               return this.data.count;
 
-       triangulate: function ( data, holeIndices, dim ) {
+       }
 
-               dim = dim || 2;
+       get array() {
 
-               const hasHoles = holeIndices && holeIndices.length;
-               const outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length;
-               let outerNode = linkedList$1( data, 0, outerLen, dim, true );
-               const triangles = [];
+               return this.data.array;
 
-               if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles;
+       }
 
-               let minX, minY, maxX, maxY, x, y, invSize;
+       set needsUpdate( value ) {
 
-               if ( hasHoles ) outerNode = eliminateHoles$1( data, holeIndices, outerNode, dim );
+               this.data.needsUpdate = value;
 
-               // 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 ];
+       applyMatrix4( m ) {
 
-                       for ( let i = dim; i < outerLen; i += dim ) {
+               for ( let i = 0, l = this.data.count; i < l; i ++ ) {
 
-                               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;
+                       _vector$6.x = this.getX( i );
+                       _vector$6.y = this.getY( i );
+                       _vector$6.z = this.getZ( i );
 
-                       }
+                       _vector$6.applyMatrix4( m );
 
-                       // 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;
+                       this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z );
 
                }
 
-               earcutLinked$1( outerNode, triangles, dim, minX, minY, invSize );
-
-               return triangles;
+               return this;
 
        }
 
-};
+       applyNormalMatrix( m ) {
 
-// create a circular doubly linked list from polygon points in the specified winding order
-function linkedList$1( data, start, end, dim, clockwise ) {
+               for ( let i = 0, l = this.count; i < l; i ++ ) {
 
-       let i, last;
+                       _vector$6.x = this.getX( i );
+                       _vector$6.y = this.getY( i );
+                       _vector$6.z = this.getZ( i );
 
-       if ( clockwise === ( signedArea$2( data, start, end, dim ) > 0 ) ) {
+                       _vector$6.applyNormalMatrix( m );
 
-               for ( i = start; i < end; i += dim ) last = insertNode$2( i, data[ i ], data[ i + 1 ], last );
+                       this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z );
 
-       } else {
+               }
 
-               for ( i = end - dim; i >= start; i -= dim ) last = insertNode$2( i, data[ i ], data[ i + 1 ], last );
+               return this;
 
        }
 
-       if ( last && equals$2( last, last.next ) ) {
+       transformDirection( m ) {
 
-               removeNode$2( last );
-               last = last.next;
+               for ( let i = 0, l = this.count; i < l; i ++ ) {
 
-       }
+                       _vector$6.x = this.getX( i );
+                       _vector$6.y = this.getY( i );
+                       _vector$6.z = this.getZ( i );
 
-       return last;
+                       _vector$6.transformDirection( m );
 
-}
+                       this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z );
 
-// eliminate colinear or duplicate points
-function filterPoints$1( start, end ) {
+               }
 
-       if ( ! start ) return start;
-       if ( ! end ) end = start;
+               return this;
 
-       let p = start,
-               again;
-       do {
+       }
 
-               again = false;
+       setX( index, x ) {
 
-               if ( ! p.steiner && ( equals$2( p, p.next ) || area$1( p.prev, p, p.next ) === 0 ) ) {
+               this.data.array[ index * this.data.stride + this.offset ] = x;
 
-                       removeNode$2( p );
-                       p = end = p.prev;
-                       if ( p === p.next ) break;
-                       again = true;
+               return this;
 
-               } else {
+       }
 
-                       p = p.next;
+       setY( index, y ) {
 
-               }
+               this.data.array[ index * this.data.stride + this.offset + 1 ] = y;
 
-       } while ( again || p !== end );
+               return this;
 
-       return end;
+       }
 
-}
+       setZ( index, z ) {
 
-// main ear slicing loop which triangulates a polygon (given as a linked list)
-function earcutLinked$1( ear, triangles, dim, minX, minY, invSize, pass ) {
+               this.data.array[ index * this.data.stride + this.offset + 2 ] = z;
 
-       if ( ! ear ) return;
+               return this;
 
-       // interlink polygon nodes in z-order
-       if ( ! pass && invSize ) indexCurve$1( ear, minX, minY, invSize );
+       }
 
-       let stop = ear,
-               prev, next;
+       setW( index, w ) {
 
-       // iterate through ears, slicing them one by one
-       while ( ear.prev !== ear.next ) {
+               this.data.array[ index * this.data.stride + this.offset + 3 ] = w;
 
-               prev = ear.prev;
-               next = ear.next;
+               return this;
 
-               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 );
+       getX( index ) {
 
-                       removeNode$2( ear );
+               return this.data.array[ index * this.data.stride + this.offset ];
 
-                       // skipping the next vertex leads to less sliver triangles
-                       ear = next.next;
-                       stop = next.next;
+       }
 
-                       continue;
+       getY( index ) {
 
-               }
+               return this.data.array[ index * this.data.stride + this.offset + 1 ];
 
-               ear = next;
+       }
 
-               // if we looped through the whole remaining polygon and can't find any more ears
-               if ( ear === stop ) {
+       getZ( index ) {
 
-                       // try filtering points and slicing again
-                       if ( ! pass ) {
+               return this.data.array[ index * this.data.stride + this.offset + 2 ];
 
-                               earcutLinked$1( filterPoints$1( ear ), triangles, dim, minX, minY, invSize, 1 );
+       }
 
-                               // if this didn't work, try curing all small self-intersections locally
+       getW( index ) {
 
-                       } else if ( pass === 1 ) {
+               return this.data.array[ index * this.data.stride + this.offset + 3 ];
 
-                               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
+       setXY( index, x, y ) {
 
-                       } else if ( pass === 2 ) {
+               index = index * this.data.stride + this.offset;
 
-                               splitEarcut$1( ear, triangles, dim, minX, minY, invSize );
+               this.data.array[ index + 0 ] = x;
+               this.data.array[ index + 1 ] = y;
 
-                       }
+               return this;
 
-                       break;
+       }
 
-               }
+       setXYZ( 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;
 
-// check whether a polygon node forms a valid ear with adjacent nodes
-function isEar$1( ear ) {
+               return this;
 
-       const a = ear.prev,
-               b = ear,
-               c = ear.next;
+       }
 
-       if ( area$1( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
+       setXYZW( index, x, y, z, w ) {
 
-       // now make sure we don't have other points inside the potential ear
-       let p = ear.next.next;
+               index = index * this.data.stride + this.offset;
 
-       while ( p !== ear.prev ) {
+               this.data.array[ index + 0 ] = x;
+               this.data.array[ index + 1 ] = y;
+               this.data.array[ index + 2 ] = z;
+               this.data.array[ index + 3 ] = w;
 
-               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 this;
 
        }
 
-       return true;
+       clone( data ) {
 
-}
+               if ( data === undefined ) {
 
-function isEarHashed$1( ear, minX, minY, invSize ) {
+                       console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interlaved buffer attribute will deinterleave buffer data.' );
 
-       const a = ear.prev,
-               b = ear,
-               c = ear.next;
+                       const array = [];
 
-       if ( area$1( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
+                       for ( let i = 0; i < this.count; i ++ ) {
 
-       // 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 );
+                               const index = i * this.data.stride + this.offset;
 
-       // 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 );
+                               for ( let j = 0; j < this.itemSize; j ++ ) {
 
-       let p = ear.prevZ,
-               n = ear.nextZ;
+                                       array.push( this.data.array[ index + j ] );
 
-       // 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;
+                       return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized );
 
-       }
+               } else {
 
-       // look for remaining points in decreasing z-order
-       while ( p && p.z >= minZ ) {
+                       if ( data.interleavedBuffers === undefined ) {
 
-               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;
+                               data.interleavedBuffers = {};
 
-       }
+                       }
 
-       // look for remaining points in increasing z-order
-       while ( n && n.z <= maxZ ) {
+                       if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
 
-               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;
+                               data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data );
 
-       }
+                       }
 
-       return true;
+                       return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized );
 
-}
+               }
 
-// go through all polygon nodes and cure small local self-intersections
-function cureLocalIntersections$1( start, triangles, dim ) {
+       }
 
-       let p = start;
-       do {
+       toJSON( data ) {
 
-               const a = p.prev,
-                       b = p.next.next;
+               if ( data === undefined ) {
 
-               if ( ! equals$2( a, b ) && intersects$2( a, p, p.next, b ) && locallyInside$1( a, b ) && locallyInside$1( b, a ) ) {
+                       console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interlaved buffer attribute will deinterleave buffer data.' );
 
-                       triangles.push( a.i / dim );
-                       triangles.push( p.i / dim );
-                       triangles.push( b.i / dim );
+                       const array = [];
 
-                       // remove two nodes involved
-                       removeNode$2( p );
-                       removeNode$2( p.next );
+                       for ( let i = 0; i < this.count; i ++ ) {
 
-                       p = start = b;
+                               const index = i * this.data.stride + this.offset;
 
-               }
+                               for ( let j = 0; j < this.itemSize; j ++ ) {
 
-               p = p.next;
+                                       array.push( this.data.array[ index + j ] );
 
-       } while ( p !== start );
+                               }
 
-       return filterPoints$1( p );
+                       }
 
-}
+                       // deinterleave data and save it as an ordinary buffer attribute for now
 
-// try splitting polygon into two and triangulate them independently
-function splitEarcut$1( start, triangles, dim, minX, minY, invSize ) {
+                       return {
+                               itemSize: this.itemSize,
+                               type: this.array.constructor.name,
+                               array: array,
+                               normalized: this.normalized
+                       };
 
-       // look for a valid diagonal that divides the polygon into two
-       let a = start;
-       do {
+               } else {
 
-               let b = a.next.next;
-               while ( b !== a.prev ) {
+                       // save as true interlaved attribtue
 
-                       if ( a.i !== b.i && isValidDiagonal$1( a, b ) ) {
+                       if ( data.interleavedBuffers === undefined ) {
 
-                               // split the polygon in two by the diagonal
-                               let c = splitPolygon$1( a, b );
+                               data.interleavedBuffers = {};
 
-                               // 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;
+                       if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
+
+                               data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data );
 
                        }
 
-                       b = b.next;
+                       return {
+                               isInterleavedBufferAttribute: true,
+                               itemSize: this.itemSize,
+                               data: this.data.uuid,
+                               offset: this.offset,
+                               normalized: this.normalized
+                       };
 
                }
 
-               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 ) {
+InterleavedBufferAttribute.prototype.isInterleavedBufferAttribute = true;
 
-       const queue = [];
-       let i, len, start, end, list;
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  map: new THREE.Texture( <Image> ),
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *  rotation: <float>,
+ *  sizeAttenuation: <bool>
+ * }
+ */
 
-       for ( i = 0, len = holeIndices.length; i < len; i ++ ) {
+class SpriteMaterial extends Material {
 
-               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 ) );
+       constructor( parameters ) {
 
-       }
+               super();
 
-       queue.sort( compareX$1 );
+               this.type = 'SpriteMaterial';
 
-       // process holes from left to right
-       for ( i = 0; i < queue.length; i ++ ) {
+               this.color = new Color( 0xffffff );
 
-               eliminateHole$1( queue[ i ], outerNode );
-               outerNode = filterPoints$1( outerNode, outerNode.next );
+               this.map = null;
 
-       }
+               this.alphaMap = null;
 
-       return outerNode;
+               this.rotation = 0;
 
-}
+               this.sizeAttenuation = true;
 
-function compareX$1( a, b ) {
+               this.transparent = true;
 
-       return a.x - b.x;
+               this.setValues( parameters );
 
-}
+       }
 
-// find a bridge between vertices that connects hole with an outer ring and and link it
-function eliminateHole$1( hole, outerNode ) {
+       copy( source ) {
 
-       outerNode = findHoleBridge$1( hole, outerNode );
-       if ( outerNode ) {
+               super.copy( source );
 
-               const b = splitPolygon$1( outerNode, hole );
+               this.color.copy( source.color );
 
-               // filter collinear points around the cuts
-               filterPoints$1( outerNode, outerNode.next );
-               filterPoints$1( b, b.next );
+               this.map = source.map;
+
+               this.alphaMap = source.alphaMap;
+
+               this.rotation = source.rotation;
+
+               this.sizeAttenuation = source.sizeAttenuation;
+
+               return this;
 
        }
 
 }
 
-// David Eberly's algorithm for finding a bridge between hole and outer polygon
-function findHoleBridge$1( hole, outerNode ) {
+SpriteMaterial.prototype.isSpriteMaterial = true;
 
-       let p = outerNode;
-       const hx = hole.x;
-       const hy = hole.y;
-       let qx = - Infinity, m;
+let _geometry;
 
-       // 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 {
+const _intersectPoint = /*@__PURE__*/ new Vector3();
+const _worldScale = /*@__PURE__*/ new Vector3();
+const _mvPosition = /*@__PURE__*/ new Vector3();
 
-               if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) {
+const _alignedPosition = /*@__PURE__*/ new Vector2();
+const _rotatedPosition = /*@__PURE__*/ new Vector2();
+const _viewWorldMatrix = /*@__PURE__*/ new Matrix4();
 
-                       const x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );
-                       if ( x <= hx && x > qx ) {
+const _vA = /*@__PURE__*/ new Vector3();
+const _vB = /*@__PURE__*/ new Vector3();
+const _vC = /*@__PURE__*/ new Vector3();
 
-                               qx = x;
-                               if ( x === hx ) {
+const _uvA = /*@__PURE__*/ new Vector2();
+const _uvB = /*@__PURE__*/ new Vector2();
+const _uvC = /*@__PURE__*/ new Vector2();
 
-                                       if ( hy === p.y ) return p;
-                                       if ( hy === p.next.y ) return p.next;
+class Sprite extends Object3D {
 
-                               }
+       constructor( material ) {
 
-                               m = p.x < p.next.x ? p : p.next;
+               super();
 
-                       }
+               this.type = 'Sprite';
 
-               }
+               if ( _geometry === undefined ) {
 
-               p = p.next;
+                       _geometry = new BufferGeometry();
 
-       } while ( p !== outerNode );
+                       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
+                       ] );
 
-       if ( ! m ) return null;
+                       const interleavedBuffer = new InterleavedBuffer( float32Array, 5 );
 
-       if ( hx === qx ) return m; // hole touches outer segment; pick leftmost endpoint
+                       _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 ) );
 
-       // 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;
+               this.geometry = _geometry;
+               this.material = ( material !== undefined ) ? material : new SpriteMaterial();
 
-       p = m;
+               this.center = new Vector2( 0.5, 0.5 );
 
-       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 ) ) {
+       raycast( raycaster, intersects ) {
 
-                       tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential
+               if ( raycaster.camera === null ) {
 
-                       if ( locallyInside$1( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector$1( m, p ) ) ) ) ) ) {
+                       console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' );
 
-                               m = p;
-                               tanMin = tan;
+               }
 
-                       }
+               _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 );
 
                }
 
-               p = p.next;
+               const rotation = this.material.rotation;
+               let sin, cos;
 
-       } while ( p !== stop );
+               if ( rotation !== 0 ) {
 
-       return m;
+                       cos = Math.cos( rotation );
+                       sin = Math.sin( rotation );
 
-}
+               }
 
-// whether sector in vertex m contains sector in vertex p in the same coordinates
-function sectorContainsSector$1( m, p ) {
+               const center = this.center;
 
-       return area$1( m.prev, m, p.prev ) < 0 && area$1( p.next, m, m.next ) < 0;
+               transformVertex( _vA.set( - 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+               transformVertex( _vB.set( 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+               transformVertex( _vC.set( 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
 
-}
+               _uvA.set( 0, 0 );
+               _uvB.set( 1, 0 );
+               _uvC.set( 1, 1 );
 
-// interlink polygon nodes in z-order
-function indexCurve$1( start, minX, minY, invSize ) {
+               // check first triangle
+               let intersect = raycaster.ray.intersectTriangle( _vA, _vB, _vC, false, _intersectPoint );
 
-       let p = start;
-       do {
+               if ( intersect === null ) {
 
-               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;
+                       // check second triangle
+                       transformVertex( _vB.set( - 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+                       _uvB.set( 0, 1 );
 
-       } while ( p !== start );
+                       intersect = raycaster.ray.intersectTriangle( _vA, _vC, _vB, false, _intersectPoint );
+                       if ( intersect === null ) {
 
-       p.prevZ.nextZ = null;
-       p.prevZ = null;
+                               return;
 
-       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 ) {
+               const distance = raycaster.ray.origin.distanceTo( _intersectPoint );
 
-       let i, p, q, e, tail, numMerges, pSize, qSize,
-               inSize = 1;
+               if ( distance < raycaster.near || distance > raycaster.far ) return;
 
-       do {
+               intersects.push( {
 
-               p = list;
-               list = null;
-               tail = null;
-               numMerges = 0;
+                       distance: distance,
+                       point: _intersectPoint.clone(),
+                       uv: Triangle.getUV( _intersectPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() ),
+                       face: null,
+                       object: this
 
-               while ( p ) {
+               } );
 
-                       numMerges ++;
-                       q = p;
-                       pSize = 0;
-                       for ( i = 0; i < inSize; i ++ ) {
+       }
 
-                               pSize ++;
-                               q = q.nextZ;
-                               if ( ! q ) break;
+       copy( source ) {
 
-                       }
+               super.copy( source );
 
-                       qSize = inSize;
+               if ( source.center !== undefined ) this.center.copy( source.center );
 
-                       while ( pSize > 0 || ( qSize > 0 && q ) ) {
+               this.material = source.material;
 
-                               if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) {
+               return this;
 
-                                       e = p;
-                                       p = p.nextZ;
-                                       pSize --;
+       }
 
-                               } else {
+}
 
-                                       e = q;
-                                       q = q.nextZ;
-                                       qSize --;
+Sprite.prototype.isSprite = true;
 
-                               }
+function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) {
 
-                               if ( tail ) tail.nextZ = e;
-                               else list = e;
+       // compute position in camera space
+       _alignedPosition.subVectors( vertexPosition, center ).addScalar( 0.5 ).multiply( scale );
 
-                               e.prevZ = tail;
-                               tail = e;
+       // 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 );
 
-                       p = q;
+       } else {
 
-               }
+               _rotatedPosition.copy( _alignedPosition );
 
-               tail.nextZ = null;
-               inSize *= 2;
+       }
 
-       } while ( numMerges > 1 );
 
-       return list;
+       vertexPosition.copy( mvPosition );
+       vertexPosition.x += _rotatedPosition.x;
+       vertexPosition.y += _rotatedPosition.y;
+
+       // transform to world space
+       vertexPosition.applyMatrix4( _viewWorldMatrix );
 
 }
 
-// z-order of a point given coords and inverse of the longer side of data bbox
-function zOrder$1( x, y, minX, minY, invSize ) {
+const _basePosition = /*@__PURE__*/ new Vector3();
 
-       // coords are transformed into non-negative 15-bit integer range
-       x = 32767 * ( x - minX ) * invSize;
-       y = 32767 * ( y - minY ) * invSize;
+const _skinIndex = /*@__PURE__*/ new Vector4();
+const _skinWeight = /*@__PURE__*/ new Vector4();
 
-       x = ( x | ( x << 8 ) ) & 0x00FF00FF;
-       x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
-       x = ( x | ( x << 2 ) ) & 0x33333333;
-       x = ( x | ( x << 1 ) ) & 0x55555555;
+const _vector$5 = /*@__PURE__*/ new Vector3();
+const _matrix = /*@__PURE__*/ new Matrix4();
 
-       y = ( y | ( y << 8 ) ) & 0x00FF00FF;
-       y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
-       y = ( y | ( y << 2 ) ) & 0x33333333;
-       y = ( y | ( y << 1 ) ) & 0x55555555;
+class SkinnedMesh extends Mesh {
 
-       return x | ( y << 1 );
+       constructor( geometry, material ) {
 
-}
+               super( geometry, material );
 
-// find the leftmost node of a polygon ring
-function getLeftmost$1( start ) {
+               this.type = 'SkinnedMesh';
 
-       let p = start,
-               leftmost = start;
-       do {
+               this.bindMode = 'attached';
+               this.bindMatrix = new Matrix4();
+               this.bindMatrixInverse = new Matrix4();
 
-               if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p;
-               p = p.next;
+       }
 
-       } while ( p !== start );
+       copy( source ) {
 
-       return leftmost;
+               super.copy( source );
 
-}
+               this.bindMode = source.bindMode;
+               this.bindMatrix.copy( source.bindMatrix );
+               this.bindMatrixInverse.copy( source.bindMatrixInverse );
 
-// check if a point lies within a convex triangle
-function pointInTriangle$1( ax, ay, bx, by, cx, cy, px, py ) {
+               this.skeleton = source.skeleton;
 
-       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;
+               return this;
 
-}
+       }
 
-// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
-function isValidDiagonal$1( a, b ) {
+       bind( skeleton, bindMatrix ) {
 
-       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
+               this.skeleton = skeleton;
 
-}
+               if ( bindMatrix === undefined ) {
 
-// signed area of a triangle
-function area$1( p, q, r ) {
+                       this.updateMatrixWorld( true );
 
-       return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );
+                       this.skeleton.calculateInverses();
 
-}
+                       bindMatrix = this.matrixWorld;
 
-// check if two points are equal
-function equals$2( p1, p2 ) {
+               }
 
-       return p1.x === p2.x && p1.y === p2.y;
+               this.bindMatrix.copy( bindMatrix );
+               this.bindMatrixInverse.copy( bindMatrix ).invert();
 
-}
+       }
 
-// check if two segments intersect
-function intersects$2( p1, q1, p2, q2 ) {
+       pose() {
 
-       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 ) );
+               this.skeleton.pose();
 
-       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
+       normalizeSkinWeights() {
 
-       return false;
+               const vector = new Vector4();
 
-}
+               const skinWeight = this.geometry.attributes.skinWeight;
 
-// for collinear points p, q, r, check if point q lies on segment pr
-function onSegment$1( p, q, r ) {
+               for ( let i = 0, l = skinWeight.count; i < l; i ++ ) {
 
-       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 );
+                       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();
 
-function sign$2( num ) {
+                       if ( scale !== Infinity ) {
 
-       return num > 0 ? 1 : num < 0 ? - 1 : 0;
+                               vector.multiplyScalar( scale );
 
-}
+                       } else {
 
-// check if a polygon diagonal intersects any polygon segments
-function intersectsPolygon$1( a, b ) {
+                               vector.set( 1, 0, 0, 0 ); // do something reasonable
 
-       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;
+                       skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w );
 
-       } while ( p !== a );
+               }
 
-       return false;
+       }
 
-}
+       updateMatrixWorld( force ) {
 
-// check if a polygon diagonal is locally inside the polygon
-function locallyInside$1( a, b ) {
+               super.updateMatrixWorld( force );
 
-       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;
+               if ( this.bindMode === 'attached' ) {
 
-}
+                       this.bindMatrixInverse.copy( this.matrixWorld ).invert();
 
-// check if the middle point of a polygon diagonal is inside the polygon
-function middleInside$1( a, b ) {
+               } else if ( this.bindMode === 'detached' ) {
 
-       let p = a,
-               inside = false;
-       const px = ( a.x + b.x ) / 2,
-               py = ( a.y + b.y ) / 2;
-       do {
+                       this.bindMatrixInverse.copy( this.bindMatrix ).invert();
 
-               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;
+               } else {
 
-       } while ( p !== a );
+                       console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode );
 
-       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 ) {
+       boneTransform( index, target ) {
 
-       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;
+               const skeleton = this.skeleton;
+               const geometry = this.geometry;
 
-       a.next = b;
-       b.prev = a;
+               _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index );
+               _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index );
 
-       a2.next = an;
-       an.prev = a2;
+               _basePosition.copy( target ).applyMatrix4( this.bindMatrix );
 
-       b2.next = a2;
-       a2.prev = b2;
+               target.set( 0, 0, 0 );
 
-       bp.next = b2;
-       b2.prev = bp;
+               for ( let i = 0; i < 4; i ++ ) {
 
-       return b2;
+                       const weight = _skinWeight.getComponent( i );
 
-}
+                       if ( weight !== 0 ) {
 
-// create a node and optionally link it with previous one (in a circular doubly linked list)
-function insertNode$2( i, x, y, last ) {
+                               const boneIndex = _skinIndex.getComponent( i );
 
-       const p = new Node$1( i, x, y );
+                               _matrix.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] );
 
-       if ( ! last ) {
+                               target.addScaledVector( _vector$5.copy( _basePosition ).applyMatrix4( _matrix ), weight );
 
-               p.prev = p;
-               p.next = p;
+                       }
 
-       } else {
+               }
 
-               p.next = last.next;
-               p.prev = last;
-               last.next.prev = p;
-               last.next = p;
+               return target.applyMatrix4( this.bindMatrixInverse );
 
        }
 
-       return p;
-
 }
 
-function removeNode$2( p ) {
+SkinnedMesh.prototype.isSkinnedMesh = true;
 
-       p.next.prev = p.prev;
-       p.prev.next = p.next;
+class Bone extends Object3D {
 
-       if ( p.prevZ ) p.prevZ.nextZ = p.nextZ;
-       if ( p.nextZ ) p.nextZ.prevZ = p.prevZ;
+       constructor() {
 
-}
+               super();
 
-function Node$1( i, x, y ) {
+               this.type = 'Bone';
 
-       // 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;
+Bone.prototype.isBone = true;
 
-       // z-order curve value
-       this.z = null;
+class DataTexture extends Texture {
 
-       // previous and next nodes in z-order
-       this.prevZ = null;
-       this.nextZ = null;
+       constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, encoding ) {
 
-       // indicates whether this is a steiner point
-       this.steiner = false;
+               super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
 
-}
+               this.image = { data: data, width: width, height: height };
 
-function signedArea$2( data, start, end, dim ) {
+               this.magFilter = magFilter;
+               this.minFilter = minFilter;
 
-       let sum = 0;
-       for ( let i = start, j = end - dim; i < end; i += dim ) {
+               this.generateMipmaps = false;
+               this.flipY = false;
+               this.unpackAlignment = 1;
 
-               sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );
-               j = i;
+               this.needsUpdate = true;
 
        }
 
-       return sum;
-
 }
 
-const ShapeUtils = {
+DataTexture.prototype.isDataTexture = true;
 
-       // calculate area of the contour polygon
+class InstancedBufferAttribute extends BufferAttribute {
 
-       area: function ( contour ) {
+       constructor( array, itemSize, normalized, meshPerAttribute = 1 ) {
 
-               const n = contour.length;
-               let a = 0.0;
+               if ( typeof normalized === 'number' ) {
 
-               for ( let p = n - 1, q = 0; q < n; p = q ++ ) {
+                       meshPerAttribute = normalized;
 
-                       a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
+                       normalized = false;
+
+                       console.error( 'THREE.InstancedBufferAttribute: The constructor now expects normalized as the third argument.' );
 
                }
 
-               return a * 0.5;
+               super( array, itemSize, normalized );
 
-       },
+               this.meshPerAttribute = meshPerAttribute;
 
-       isClockWise: function ( pts ) {
+       }
 
-               return ShapeUtils.area( pts ) < 0;
+       copy( source ) {
 
-       },
+               super.copy( source );
 
-       triangulateShape: function ( contour, holes ) {
+               this.meshPerAttribute = source.meshPerAttribute;
 
-               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 ] ]
+               return this;
 
-               removeDupEndPts( contour );
-               addContour( vertices, contour );
+       }
 
-               //
+       toJSON() {
 
-               let holeIndex = contour.length;
+               const data = super.toJSON();
 
-               holes.forEach( removeDupEndPts );
+               data.meshPerAttribute = this.meshPerAttribute;
 
-               for ( let i = 0; i < holes.length; i ++ ) {
+               data.isInstancedBufferAttribute = true;
 
-                       holeIndices.push( holeIndex );
-                       holeIndex += holes[ i ].length;
-                       addContour( vertices, holes[ i ] );
+               return data;
 
-               }
+       }
 
-               //
+}
 
-               const triangles = Earcut.triangulate( vertices, holeIndices );
+InstancedBufferAttribute.prototype.isInstancedBufferAttribute = true;
 
-               //
+const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4();
+const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4();
 
-               for ( let i = 0; i < triangles.length; i += 3 ) {
+const _instanceIntersects = [];
 
-                       faces.push( triangles.slice( i, i + 3 ) );
+const _mesh = /*@__PURE__*/ new Mesh();
 
-               }
+class InstancedMesh extends Mesh {
 
-               return faces;
+       constructor( geometry, material, count ) {
 
-       }
+               super( geometry, material );
 
-};
+               this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 );
+               this.instanceColor = null;
 
-function removeDupEndPts( points ) {
+               this.count = count;
 
-       const l = points.length;
+               this.frustumCulled = false;
 
-       if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {
+       }
 
-               points.pop();
+       copy( source ) {
 
-       }
+               super.copy( source );
 
-}
+               this.instanceMatrix.copy( source.instanceMatrix );
 
-function addContour( vertices, contour ) {
+               if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone();
 
-       for ( let i = 0; i < contour.length; i ++ ) {
+               this.count = source.count;
 
-               vertices.push( contour[ i ].x );
-               vertices.push( contour[ i ].y );
+               return this;
 
        }
 
-}
+       getColorAt( index, color ) {
 
-/**
- * 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
- *
- * }
- */
+               color.fromArray( this.instanceColor.array, index * 3 );
 
-class ExtrudeGeometry extends BufferGeometry {
+       }
 
-       constructor( shapes, options ) {
+       getMatrixAt( index, matrix ) {
 
-               super();
+               matrix.fromArray( this.instanceMatrix.array, index * 16 );
 
-               this.type = 'ExtrudeGeometry';
+       }
 
-               this.parameters = {
-                       shapes: shapes,
-                       options: options
-               };
+       raycast( raycaster, intersects ) {
 
-               shapes = Array.isArray( shapes ) ? shapes : [ shapes ];
+               const matrixWorld = this.matrixWorld;
+               const raycastTimes = this.count;
 
-               const scope = this;
+               _mesh.geometry = this.geometry;
+               _mesh.material = this.material;
 
-               const verticesArray = [];
-               const uvArray = [];
+               if ( _mesh.material === undefined ) return;
 
-               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+               for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) {
 
-                       const shape = shapes[ i ];
-                       addShape( shape );
+                       // calculate the world matrix for each instance
 
-               }
+                       this.getMatrixAt( instanceId, _instanceLocalMatrix );
 
-               // build geometry
+                       _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix );
 
-               this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) );
-               this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) );
+                       // the mesh represents this single instance
 
-               this.computeVertexNormals();
+                       _mesh.matrixWorld = _instanceWorldMatrix;
 
-               // functions
+                       _mesh.raycast( raycaster, _instanceIntersects );
 
-               function addShape( shape ) {
+                       // process the result of raycast
 
-                       const placeholder = [];
+                       for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) {
 
-                       // options
+                               const intersect = _instanceIntersects[ i ];
+                               intersect.instanceId = instanceId;
+                               intersect.object = this;
+                               intersects.push( intersect );
 
-                       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;
+                       _instanceIntersects.length = 0;
 
-                       const extrudePath = options.extrudePath;
+               }
 
-                       const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator;
+       }
 
-                       // deprecated options
+       setColorAt( index, color ) {
 
-                       if ( options.amount !== undefined ) {
+               if ( this.instanceColor === null ) {
 
-                               console.warn( 'THREE.ExtrudeBufferGeometry: amount has been renamed to depth.' );
-                               depth = options.amount;
+                       this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ), 3 );
 
-                       }
+               }
 
-                       //
+               color.toArray( this.instanceColor.array, index * 3 );
 
-                       let extrudePts, extrudeByPath = false;
-                       let splineTube, binormal, normal, position2;
+       }
 
-                       if ( extrudePath ) {
+       setMatrixAt( index, matrix ) {
 
-                               extrudePts = extrudePath.getSpacedPoints( steps );
+               matrix.toArray( this.instanceMatrix.array, index * 16 );
 
-                               extrudeByPath = true;
-                               bevelEnabled = false; // bevels not supported for path extrusion
+       }
 
-                               // SETUP TNB variables
+       updateMorphTargets() {
 
-                               // TODO1 - have a .isClosed in spline?
+       }
 
-                               splineTube = extrudePath.computeFrenetFrames( steps, false );
+       dispose() {
 
-                               // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);
+               this.dispatchEvent( { type: 'dispose' } );
 
-                               binormal = new Vector3();
-                               normal = new Vector3();
-                               position2 = new Vector3();
+       }
 
-                       }
+}
 
-                       // Safeguards if bevels are not enabled
+InstancedMesh.prototype.isInstancedMesh = true;
 
-                       if ( ! bevelEnabled ) {
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *
+ *  linewidth: <float>,
+ *  linecap: "round",
+ *  linejoin: "round"
+ * }
+ */
 
-                               bevelSegments = 0;
-                               bevelThickness = 0;
-                               bevelSize = 0;
-                               bevelOffset = 0;
+class LineBasicMaterial extends Material {
 
-                       }
+       constructor( parameters ) {
 
-                       // Variables initialization
+               super();
 
-                       const shapePoints = shape.extractPoints( curveSegments );
+               this.type = 'LineBasicMaterial';
 
-                       let vertices = shapePoints.shape;
-                       const holes = shapePoints.holes;
+               this.color = new Color( 0xffffff );
 
-                       const reverse = ! ShapeUtils.isClockWise( vertices );
+               this.linewidth = 1;
+               this.linecap = 'round';
+               this.linejoin = 'round';
 
-                       if ( reverse ) {
+               this.setValues( parameters );
 
-                               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 ++ ) {
+       copy( source ) {
 
-                                       const ahole = holes[ h ];
+               super.copy( source );
 
-                                       if ( ShapeUtils.isClockWise( ahole ) ) {
+               this.color.copy( source.color );
 
-                                               holes[ h ] = ahole.reverse();
+               this.linewidth = source.linewidth;
+               this.linecap = source.linecap;
+               this.linejoin = source.linejoin;
 
-                                       }
+               return this;
 
-                               }
+       }
 
-                       }
+}
 
+LineBasicMaterial.prototype.isLineBasicMaterial = true;
 
-                       const faces = ShapeUtils.triangulateShape( vertices, holes );
+const _start$1 = /*@__PURE__*/ new Vector3();
+const _end$1 = /*@__PURE__*/ new Vector3();
+const _inverseMatrix$1 = /*@__PURE__*/ new Matrix4();
+const _ray$1 = /*@__PURE__*/ new Ray();
+const _sphere$1 = /*@__PURE__*/ new Sphere();
 
-                       /* Vertices */
+class Line extends Object3D {
 
-                       const contour = vertices; // vertices has all points but contour has only points of circumference
+       constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) {
 
-                       for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+               super();
 
-                               const ahole = holes[ h ];
+               this.type = 'Line';
 
-                               vertices = vertices.concat( ahole );
+               this.geometry = geometry;
+               this.material = material;
 
-                       }
+               this.updateMorphTargets();
 
+       }
 
-                       function scalePt2( pt, vec, size ) {
+       copy( source ) {
 
-                               if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' );
+               super.copy( source );
 
-                               return vec.clone().multiplyScalar( size ).add( pt );
+               this.material = source.material;
+               this.geometry = source.geometry;
 
-                       }
+               return this;
 
-                       const vlen = vertices.length, flen = faces.length;
+       }
 
+       computeLineDistances() {
 
-                       // Find directions for point movement
+               const geometry = this.geometry;
 
+               if ( geometry.isBufferGeometry ) {
 
-                       function getBevelVec( inPt, inPrev, inNext ) {
+                       // we assume non-indexed geometry
 
-                               // 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.
+                       if ( geometry.index === null ) {
 
-                               let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt
+                               const positionAttribute = geometry.attributes.position;
+                               const lineDistances = [ 0 ];
 
-                               // good reading for geometry algorithms (here: line-line intersection)
-                               // http://geomalgorithms.com/a05-_intersect-1.html
+                               for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) {
 
-                               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;
+                                       _start$1.fromBufferAttribute( positionAttribute, i - 1 );
+                                       _end$1.fromBufferAttribute( positionAttribute, i );
 
-                               const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y );
+                                       lineDistances[ i ] = lineDistances[ i - 1 ];
+                                       lineDistances[ i ] += _start$1.distanceTo( _end$1 );
 
-                               // check for collinear edges
-                               const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x );
+                               }
 
-                               if ( Math.abs( collinear0 ) > Number.EPSILON ) {
+                               geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
 
-                                       // not collinear
+                       } else {
 
-                                       // length of vectors for normalizing
+                               console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
 
-                                       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
+               } else if ( geometry.isGeometry ) {
 
-                                       const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len );
-                                       const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len );
+                       console.error( 'THREE.Line.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
-                                       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
+               return this;
 
-                                       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
+       raycast( raycaster, intersects ) {
 
-                                       v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x );
-                                       v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y );
+               const geometry = this.geometry;
+               const matrixWorld = this.matrixWorld;
+               const threshold = raycaster.params.Line.threshold;
+               const drawRange = geometry.drawRange;
 
-                                       // 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 ) {
+               // Checking boundingSphere distance to ray
 
-                                               return new Vector2( v_trans_x, v_trans_y );
+               if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
 
-                                       } else {
+               _sphere$1.copy( geometry.boundingSphere );
+               _sphere$1.applyMatrix4( matrixWorld );
+               _sphere$1.radius += threshold;
 
-                                               shrink_by = Math.sqrt( v_trans_lensq / 2 );
+               if ( raycaster.ray.intersectsSphere( _sphere$1 ) === false ) return;
 
-                                       }
+               //
 
-                               } else {
+               _inverseMatrix$1.copy( matrixWorld ).invert();
+               _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 );
 
-                                       // handle special case of collinear edges
+               const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
+               const localThresholdSq = localThreshold * localThreshold;
 
-                                       let direction_eq = false; // assumes: opposite
+               const vStart = new Vector3();
+               const vEnd = new Vector3();
+               const interSegment = new Vector3();
+               const interRay = new Vector3();
+               const step = this.isLineSegments ? 2 : 1;
 
-                                       if ( v_prev_x > Number.EPSILON ) {
+               if ( geometry.isBufferGeometry ) {
 
-                                               if ( v_next_x > Number.EPSILON ) {
+                       const index = geometry.index;
+                       const attributes = geometry.attributes;
+                       const positionAttribute = attributes.position;
 
-                                                       direction_eq = true;
+                       if ( index !== null ) {
 
-                                               }
+                               const start = Math.max( 0, drawRange.start );
+                               const end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
 
-                                       } else {
+                               for ( let i = start, l = end - 1; i < l; i += step ) {
 
-                                               if ( v_prev_x < - Number.EPSILON ) {
+                                       const a = index.getX( i );
+                                       const b = index.getX( i + 1 );
 
-                                                       if ( v_next_x < - Number.EPSILON ) {
+                                       vStart.fromBufferAttribute( positionAttribute, a );
+                                       vEnd.fromBufferAttribute( positionAttribute, b );
 
-                                                               direction_eq = true;
+                                       const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
 
-                                                       }
+                                       if ( distSq > localThresholdSq ) continue;
 
-                                               } else {
+                                       interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
 
-                                                       if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) {
+                                       const distance = raycaster.ray.origin.distanceTo( interRay );
 
-                                                               direction_eq = true;
+                                       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
 
-                                       }
+                                       } );
 
-                                       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 {
 
-                                       } else {
+                               const start = Math.max( 0, drawRange.start );
+                               const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) );
 
-                                               // 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 );
+                               for ( let i = start, l = end - 1; i < l; i += step ) {
 
-                                       }
+                                       vStart.fromBufferAttribute( positionAttribute, i );
+                                       vEnd.fromBufferAttribute( positionAttribute, i + 1 );
 
-                               }
+                                       const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
 
-                               return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by );
+                                       if ( distSq > localThresholdSq ) continue;
 
-                       }
+                                       interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
 
+                                       const distance = raycaster.ray.origin.distanceTo( interRay );
 
-                       const contourMovements = [];
+                                       if ( distance < raycaster.near || distance > raycaster.far ) continue;
 
-                       for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+                                       intersects.push( {
 
-                               if ( j === il ) j = 0;
-                               if ( k === il ) k = 0;
+                                               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
 
-                               //  (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();
+               } else if ( geometry.isGeometry ) {
 
-                       for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+                       console.error( 'THREE.Line.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
-                               const ahole = holes[ h ];
+               }
 
-                               oneHoleMovements = [];
+       }
 
-                               for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+       updateMorphTargets() {
 
-                                       if ( j === il ) j = 0;
-                                       if ( k === il ) k = 0;
+               const geometry = this.geometry;
 
-                                       //  (j)---(i)---(k)
-                                       oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
+               if ( geometry.isBufferGeometry ) {
 
-                               }
+                       const morphAttributes = geometry.morphAttributes;
+                       const keys = Object.keys( morphAttributes );
 
-                               holesMovements.push( oneHoleMovements );
-                               verticesMovements = verticesMovements.concat( oneHoleMovements );
+                       if ( keys.length > 0 ) {
 
-                       }
+                               const morphAttribute = morphAttributes[ keys[ 0 ] ];
 
+                               if ( morphAttribute !== undefined ) {
 
-                       // Loop bevelSegments, 1 for the front, 1 for the back
+                                       this.morphTargetInfluences = [];
+                                       this.morphTargetDictionary = {};
 
-                       for ( let b = 0; b < bevelSegments; b ++ ) {
+                                       for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
 
-                               //for ( b = bevelSegments; b > 0; b -- ) {
+                                               const name = morphAttribute[ m ].name || String( m );
 
-                               const t = b / bevelSegments;
-                               const z = bevelThickness * Math.cos( t * Math.PI / 2 );
-                               const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
+                                               this.morphTargetInfluences.push( 0 );
+                                               this.morphTargetDictionary[ name ] = m;
 
-                               // 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 );
+               } else {
 
-                               }
+                       const morphTargets = geometry.morphTargets;
 
-                               // expand holes
+                       if ( morphTargets !== undefined && morphTargets.length > 0 ) {
 
-                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+                               console.error( 'THREE.Line.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
-                                       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 );
+}
 
-                                       }
+Line.prototype.isLine = true;
 
-                               }
+const _start = /*@__PURE__*/ new Vector3();
+const _end = /*@__PURE__*/ new Vector3();
 
-                       }
+class LineSegments extends Line {
 
-                       const bs = bevelSize + bevelOffset;
+       constructor( geometry, material ) {
 
-                       // Back facing vertices
+               super( geometry, material );
 
-                       for ( let i = 0; i < vlen; i ++ ) {
+               this.type = 'LineSegments';
 
-                               const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+       }
 
-                               if ( ! extrudeByPath ) {
+       computeLineDistances() {
 
-                                       v( vert.x, vert.y, 0 );
+               const geometry = this.geometry;
 
-                               } else {
+               if ( geometry.isBufferGeometry ) {
 
-                                       // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );
+                       // we assume non-indexed geometry
 
-                                       normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x );
-                                       binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y );
+                       if ( geometry.index === null ) {
 
-                                       position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal );
+                               const positionAttribute = geometry.attributes.position;
+                               const lineDistances = [];
 
-                                       v( position2.x, position2.y, position2.z );
+                               for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) {
+
+                                       _start.fromBufferAttribute( positionAttribute, i );
+                                       _end.fromBufferAttribute( positionAttribute, i + 1 );
+
+                                       lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ];
+                                       lineDistances[ i + 1 ] = lineDistances[ i ] + _start.distanceTo( _end );
 
                                }
 
+                               geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
+
+                       } else {
+
+                               console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
+
                        }
 
-                       // Add stepped vertices...
-                       // Including front facing vertices
+               } else if ( geometry.isGeometry ) {
 
-                       for ( let s = 1; s <= steps; s ++ ) {
+                       console.error( 'THREE.LineSegments.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
-                               for ( let i = 0; i < vlen; i ++ ) {
+               }
 
-                                       const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+               return this;
 
-                                       if ( ! extrudeByPath ) {
+       }
 
-                                               v( vert.x, vert.y, depth / steps * s );
+}
 
-                                       } else {
+LineSegments.prototype.isLineSegments = true;
 
-                                               // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );
+class LineLoop extends Line {
 
-                                               normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x );
-                                               binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y );
+       constructor( geometry, material ) {
 
-                                               position2.copy( extrudePts[ s ] ).add( normal ).add( binormal );
+               super( geometry, material );
 
-                                               v( position2.x, position2.y, position2.z );
+               this.type = 'LineLoop';
 
-                                       }
+       }
 
-                               }
+}
 
-                       }
+LineLoop.prototype.isLineLoop = true;
 
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *  map: new THREE.Texture( <Image> ),
+ *  alphaMap: new THREE.Texture( <Image> ),
+ *
+ *  size: <float>,
+ *  sizeAttenuation: <bool>
+ *
+ * }
+ */
 
-                       // Add bevel segments planes
+class PointsMaterial extends Material {
 
-                       //for ( b = 1; b <= bevelSegments; b ++ ) {
-                       for ( let b = bevelSegments - 1; b >= 0; b -- ) {
+       constructor( parameters ) {
 
-                               const t = b / bevelSegments;
-                               const z = bevelThickness * Math.cos( t * Math.PI / 2 );
-                               const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
+               super();
 
-                               // contract shape
+               this.type = 'PointsMaterial';
 
-                               for ( let i = 0, il = contour.length; i < il; i ++ ) {
+               this.color = new Color( 0xffffff );
 
-                                       const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
-                                       v( vert.x, vert.y, depth + z );
+               this.map = null;
 
-                               }
+               this.alphaMap = null;
 
-                               // expand holes
+               this.size = 1;
+               this.sizeAttenuation = true;
 
-                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+               this.setValues( parameters );
 
-                                       const ahole = holes[ h ];
-                                       oneHoleMovements = holesMovements[ h ];
+       }
 
-                                       for ( let i = 0, il = ahole.length; i < il; i ++ ) {
+       copy( source ) {
 
-                                               const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
+               super.copy( source );
 
-                                               if ( ! extrudeByPath ) {
+               this.color.copy( source.color );
 
-                                                       v( vert.x, vert.y, depth + z );
+               this.map = source.map;
 
-                                               } else {
+               this.alphaMap = source.alphaMap;
 
-                                                       v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z );
+               this.size = source.size;
+               this.sizeAttenuation = source.sizeAttenuation;
 
-                                               }
+               return this;
 
-                                       }
+       }
 
-                               }
+}
 
-                       }
+PointsMaterial.prototype.isPointsMaterial = true;
 
-                       /* Faces */
+const _inverseMatrix = /*@__PURE__*/ new Matrix4();
+const _ray = /*@__PURE__*/ new Ray();
+const _sphere = /*@__PURE__*/ new Sphere();
+const _position$2 = /*@__PURE__*/ new Vector3();
 
-                       // Top and bottom faces
+class Points extends Object3D {
 
-                       buildLidFaces();
+       constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) {
 
-                       // Sides faces
+               super();
 
-                       buildSideFaces();
+               this.type = 'Points';
 
+               this.geometry = geometry;
+               this.material = material;
 
-                       /////  Internal functions
+               this.updateMorphTargets();
 
-                       function buildLidFaces() {
+       }
 
-                               const start = verticesArray.length / 3;
+       copy( source ) {
 
-                               if ( bevelEnabled ) {
+               super.copy( source );
 
-                                       let layer = 0; // steps + 1
-                                       let offset = vlen * layer;
+               this.material = source.material;
+               this.geometry = source.geometry;
 
-                                       // Bottom faces
+               return this;
 
-                                       for ( let i = 0; i < flen; i ++ ) {
+       }
 
-                                               const face = faces[ i ];
-                                               f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset );
+       raycast( raycaster, intersects ) {
 
-                                       }
+               const geometry = this.geometry;
+               const matrixWorld = this.matrixWorld;
+               const threshold = raycaster.params.Points.threshold;
+               const drawRange = geometry.drawRange;
 
-                                       layer = steps + bevelSegments * 2;
-                                       offset = vlen * layer;
+               // Checking boundingSphere distance to ray
 
-                                       // Top faces
+               if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
 
-                                       for ( let i = 0; i < flen; i ++ ) {
+               _sphere.copy( geometry.boundingSphere );
+               _sphere.applyMatrix4( matrixWorld );
+               _sphere.radius += threshold;
 
-                                               const face = faces[ i ];
-                                               f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset );
+               if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return;
 
-                                       }
+               //
 
-                               } else {
+               _inverseMatrix.copy( matrixWorld ).invert();
+               _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
 
-                                       // Bottom faces
+               const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
+               const localThresholdSq = localThreshold * localThreshold;
 
-                                       for ( let i = 0; i < flen; i ++ ) {
+               if ( geometry.isBufferGeometry ) {
 
-                                               const face = faces[ i ];
-                                               f3( face[ 2 ], face[ 1 ], face[ 0 ] );
+                       const index = geometry.index;
+                       const attributes = geometry.attributes;
+                       const positionAttribute = attributes.position;
 
-                                       }
+                       if ( index !== null ) {
 
-                                       // Top faces
+                               const start = Math.max( 0, drawRange.start );
+                               const end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
 
-                                       for ( let i = 0; i < flen; i ++ ) {
+                               for ( let i = start, il = end; i < il; i ++ ) {
 
-                                               const face = faces[ i ];
-                                               f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps );
+                                       const a = index.getX( i );
 
-                                       }
+                                       _position$2.fromBufferAttribute( positionAttribute, a );
+
+                                       testPoint( _position$2, a, localThresholdSq, matrixWorld, raycaster, intersects, this );
 
                                }
 
-                               scope.addGroup( start, verticesArray.length / 3 - start, 0 );
+                       } else {
 
-                       }
+                               const start = Math.max( 0, drawRange.start );
+                               const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) );
 
-                       // Create faces for the z-sides of the shape
+                               for ( let i = start, l = end; i < l; i ++ ) {
 
-                       function buildSideFaces() {
+                                       _position$2.fromBufferAttribute( positionAttribute, i );
 
-                               const start = verticesArray.length / 3;
-                               let layeroffset = 0;
-                               sidewalls( contour, layeroffset );
-                               layeroffset += contour.length;
+                                       testPoint( _position$2, i, localThresholdSq, matrixWorld, raycaster, intersects, this );
 
-                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+                               }
 
-                                       const ahole = holes[ h ];
-                                       sidewalls( ahole, layeroffset );
+                       }
 
-                                       //, true
-                                       layeroffset += ahole.length;
+               } else {
 
-                               }
+                       console.error( 'THREE.Points.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
+               }
 
-                               scope.addGroup( start, verticesArray.length / 3 - start, 1 );
+       }
 
+       updateMorphTargets() {
 
-                       }
+               const geometry = this.geometry;
 
-                       function sidewalls( contour, layeroffset ) {
+               if ( geometry.isBufferGeometry ) {
 
-                               let i = contour.length;
+                       const morphAttributes = geometry.morphAttributes;
+                       const keys = Object.keys( morphAttributes );
 
-                               while ( -- i >= 0 ) {
+                       if ( keys.length > 0 ) {
 
-                                       const j = i;
-                                       let k = i - 1;
-                                       if ( k < 0 ) k = contour.length - 1;
+                               const morphAttribute = morphAttributes[ keys[ 0 ] ];
 
-                                       //console.log('b', i,j, i-1, k,vertices.length);
+                               if ( morphAttribute !== undefined ) {
 
-                                       for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) {
+                                       this.morphTargetInfluences = [];
+                                       this.morphTargetDictionary = {};
 
-                                               const slen1 = vlen * s;
-                                               const slen2 = vlen * ( s + 1 );
+                                       for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
 
-                                               const a = layeroffset + j + slen1,
-                                                       b = layeroffset + k + slen1,
-                                                       c = layeroffset + k + slen2,
-                                                       d = layeroffset + j + slen2;
+                                               const name = morphAttribute[ m ].name || String( m );
 
-                                               f4( a, b, c, d );
+                                               this.morphTargetInfluences.push( 0 );
+                                               this.morphTargetDictionary[ name ] = m;
 
                                        }
 
@@ -32651,310 +31556,195 @@ class ExtrudeGeometry extends BufferGeometry {
 
                        }
 
-                       function v( x, y, z ) {
+               } else {
 
-                               placeholder.push( x );
-                               placeholder.push( y );
-                               placeholder.push( z );
+                       const morphTargets = geometry.morphTargets;
 
-                       }
-
-
-                       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 );
+                       if ( morphTargets !== undefined && morphTargets.length > 0 ) {
 
-                               addUV( uvs[ 0 ] );
-                               addUV( uvs[ 1 ] );
-                               addUV( uvs[ 2 ] );
+                               console.error( 'THREE.Points.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
                        }
 
-                       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 ] );
+}
 
-                       }
+Points.prototype.isPoints = true;
 
+function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) {
 
-                       function addUV( vector2 ) {
+       const rayPointDistanceSq = _ray.distanceSqToPoint( point );
 
-                               uvArray.push( vector2.x );
-                               uvArray.push( vector2.y );
+       if ( rayPointDistanceSq < localThresholdSq ) {
 
-                       }
+               const intersectPoint = new Vector3();
 
-               }
+               _ray.closestPointToPoint( point, intersectPoint );
+               intersectPoint.applyMatrix4( matrixWorld );
 
-       }
+               const distance = raycaster.ray.origin.distanceTo( intersectPoint );
 
-       toJSON() {
+               if ( distance < raycaster.near || distance > raycaster.far ) return;
 
-               const data = BufferGeometry.prototype.toJSON.call( this );
+               intersects.push( {
 
-               const shapes = this.parameters.shapes;
-               const options = this.parameters.options;
+                       distance: distance,
+                       distanceToRay: Math.sqrt( rayPointDistanceSq ),
+                       point: intersectPoint,
+                       index: index,
+                       face: null,
+                       object: object
 
-               return toJSON( shapes, options, data );
+               } );
 
        }
 
 }
 
-const WorldUVGenerator = {
+class VideoTexture extends Texture {
 
-       generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) {
+       constructor( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
 
-               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 ];
+               super( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
 
-               return [
-                       new Vector2( a_x, a_y ),
-                       new Vector2( b_x, b_y ),
-                       new Vector2( c_x, c_y )
-               ];
+               this.format = format !== undefined ? format : RGBFormat;
 
-       },
+               this.minFilter = minFilter !== undefined ? minFilter : LinearFilter;
+               this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;
 
-       generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) {
+               this.generateMipmaps = false;
 
-               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 ];
+               const scope = this;
 
-               if ( Math.abs( a_y - b_y ) < 0.01 ) {
+               function updateVideo() {
 
-                       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 )
-                       ];
+                       scope.needsUpdate = true;
+                       video.requestVideoFrameCallback( updateVideo );
 
-               } 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 )
-                       ];
+               if ( 'requestVideoFrameCallback' in video ) {
+
+                       video.requestVideoFrameCallback( updateVideo );
 
                }
 
        }
 
-};
+       clone() {
 
-function toJSON( shapes, options, data ) {
+               return new this.constructor( this.image ).copy( this );
 
-       data.shapes = [];
+       }
 
-       if ( Array.isArray( shapes ) ) {
+       update() {
 
-               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+               const video = this.image;
+               const hasVideoFrameCallback = 'requestVideoFrameCallback' in video;
 
-                       const shape = shapes[ i ];
+               if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) {
 
-                       data.shapes.push( shape.uuid );
+                       this.needsUpdate = true;
 
                }
 
-       } 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 );
+VideoTexture.prototype.isVideoTexture = true;
 
-       this.type = 'ParametricGeometry';
-
-       this.parameters = {
-               func: func,
-               slices: slices,
-               stacks: stacks
-       };
+class CompressedTexture extends Texture {
 
-       // buffers
+       constructor( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {
 
-       const indices = [];
-       const vertices = [];
-       const normals = [];
-       const uvs = [];
+               super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
 
-       const EPS = 0.00001;
+               this.image = { width: width, height: height };
+               this.mipmaps = mipmaps;
 
-       const normal = new Vector3();
+               // no flipping for cube textures
+               // (also flipping doesn't work for compressed textures )
 
-       const p0 = new Vector3(), p1 = new Vector3();
-       const pu = new Vector3(), pv = new Vector3();
+               this.flipY = false;
 
-       if ( func.length < 3 ) {
+               // can't generate mipmaps for compressed textures
+               // mips must be embedded in DDS files
 
-               console.error( 'THREE.ParametricGeometry: Function must now modify a Vector3 as third parameter.' );
+               this.generateMipmaps = false;
 
        }
 
-       // 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 );
+CompressedTexture.prototype.isCompressedTexture = true;
 
-                       } else {
+class CanvasTexture extends Texture {
 
-                               func( u + EPS, v, p1 );
-                               pu.subVectors( p1, p0 );
+       constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
 
-                       }
+               super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
 
-                       if ( v - EPS >= 0 ) {
+               this.needsUpdate = true;
 
-                               func( u, v - EPS, p1 );
-                               pv.subVectors( p0, p1 );
+       }
 
-                       } else {
+}
 
-                               func( u, v + EPS, p1 );
-                               pv.subVectors( p1, p0 );
+CanvasTexture.prototype.isCanvasTexture = true;
 
-                       }
+class DepthTexture extends Texture {
 
-                       // cross product of tangent vectors returns surface normal
+       constructor( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) {
 
-                       normal.crossVectors( pu, pv ).normalize();
-                       normals.push( normal.x, normal.y, normal.z );
+               format = format !== undefined ? format : DepthFormat;
 
-                       // uv
+               if ( format !== DepthFormat && format !== DepthStencilFormat ) {
 
-                       uvs.push( u, v );
+                       throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' );
 
                }
 
-       }
-
-       // generate indices
+               if ( type === undefined && format === DepthFormat ) type = UnsignedShortType;
+               if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type;
 
-       for ( let i = 0; i < stacks; i ++ ) {
+               super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
 
-               for ( let j = 0; j < slices; j ++ ) {
+               this.image = { width: width, height: height };
 
-                       const a = i * sliceCount + j;
-                       const b = i * sliceCount + j + 1;
-                       const c = ( i + 1 ) * sliceCount + j + 1;
-                       const d = ( i + 1 ) * sliceCount + j;
+               this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
+               this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;
 
-                       // faces one and two
-
-                       indices.push( a, b, d );
-                       indices.push( b, c, d );
-
-               }
+               this.flipY = false;
+               this.generateMipmaps    = false;
 
        }
 
-       // 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;
+DepthTexture.prototype.isDepthTexture = true;
 
-class ShapeGeometry extends BufferGeometry {
+class CircleGeometry extends BufferGeometry {
 
-       constructor( shapes, curveSegments = 12 ) {
+       constructor( radius = 1, segments = 8, thetaStart = 0, thetaLength = Math.PI * 2 ) {
 
                super();
-               this.type = 'ShapeGeometry';
+
+               this.type = 'CircleGeometry';
 
                this.parameters = {
-                       shapes: shapes,
-                       curveSegments: curveSegments
+                       radius: radius,
+                       segments: segments,
+                       thetaStart: thetaStart,
+                       thetaLength: thetaLength
                };
 
+               segments = Math.max( 3, segments );
+
                // buffers
 
                const indices = [];
@@ -32964,27 +31754,44 @@ class ShapeGeometry extends BufferGeometry {
 
                // helper variables
 
-               let groupStart = 0;
-               let groupCount = 0;
+               const vertex = new Vector3();
+               const uv = new Vector2();
 
-               // allow single and array values for "shapes" parameter
+               // center point
 
-               if ( Array.isArray( shapes ) === false ) {
+               vertices.push( 0, 0, 0 );
+               normals.push( 0, 0, 1 );
+               uvs.push( 0.5, 0.5 );
 
-                       addShape( shapes );
+               for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) {
 
-               } else {
+                       const segment = thetaStart + s / segments * thetaLength;
 
-                       for ( let i = 0; i < shapes.length; i ++ ) {
+                       // vertex
 
-                               addShape( shapes[ i ] );
+                       vertex.x = radius * Math.cos( segment );
+                       vertex.y = radius * Math.sin( segment );
 
-                               this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support
+                       vertices.push( vertex.x, vertex.y, vertex.z );
 
-                               groupStart += groupCount;
-                               groupCount = 0;
+                       // 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 );
 
                }
 
@@ -32995,8331 +31802,8528 @@ class ShapeGeometry extends BufferGeometry {
                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 );
+       static fromJSON( data ) {
 
-                       let shapeVertices = points.shape;
-                       const shapeHoles = points.holes;
+               return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength );
 
-                       // check direction of vertices
+       }
 
-                       if ( ShapeUtils.isClockWise( shapeVertices ) === false ) {
+}
 
-                               shapeVertices = shapeVertices.reverse();
+new Vector3();
+new Vector3();
+new Vector3();
+new Triangle();
 
-                       }
+/**
+ * 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.
+ *
+ **/
 
-                       for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
+class Curve {
 
-                               const shapeHole = shapeHoles[ i ];
+       constructor() {
 
-                               if ( ShapeUtils.isClockWise( shapeHole ) === true ) {
+               this.type = 'Curve';
 
-                                       shapeHoles[ i ] = shapeHole.reverse();
+               this.arcLengthDivisions = 200;
 
-                               }
+       }
 
-                       }
+       // Virtual base class method to overwrite and implement in subclasses
+       //      - t [0 .. 1]
 
-                       const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles );
+       getPoint( /* t, optionalTarget */ ) {
 
-                       // join vertices of inner and outer paths to a single array
+               console.warn( 'THREE.Curve: .getPoint() not implemented.' );
+               return null;
 
-                       for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
+       }
 
-                               const shapeHole = shapeHoles[ i ];
-                               shapeVertices = shapeVertices.concat( shapeHole );
+       // Get point at relative position in curve according to arc length
+       // - u [0 .. 1]
 
-                       }
+       getPointAt( u, optionalTarget ) {
 
-                       // vertices, normals, uvs
+               const t = this.getUtoTmapping( u );
+               return this.getPoint( t, optionalTarget );
 
-                       for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) {
+       }
 
-                               const vertex = shapeVertices[ i ];
+       // Get sequence of points using getPoint( t )
 
-                               vertices.push( vertex.x, vertex.y, 0 );
-                               normals.push( 0, 0, 1 );
-                               uvs.push( vertex.x, vertex.y ); // world uvs
+       getPoints( divisions = 5 ) {
 
-                       }
+               const points = [];
 
-                       // incides
+               for ( let d = 0; d <= divisions; d ++ ) {
 
-                       for ( let i = 0, l = faces.length; i < l; i ++ ) {
+                       points.push( this.getPoint( d / divisions ) );
 
-                               const face = faces[ i ];
+               }
 
-                               const a = face[ 0 ] + indexOffset;
-                               const b = face[ 1 ] + indexOffset;
-                               const c = face[ 2 ] + indexOffset;
+               return points;
 
-                               indices.push( a, b, c );
-                               groupCount += 3;
+       }
 
-                       }
+       // Get sequence of points using getPointAt( u )
 
-               }
+       getSpacedPoints( divisions = 5 ) {
 
-       }
+               const points = [];
 
-       toJSON() {
+               for ( let d = 0; d <= divisions; d ++ ) {
 
-               const data = BufferGeometry.prototype.toJSON.call( this );
+                       points.push( this.getPointAt( d / divisions ) );
 
-               const shapes = this.parameters.shapes;
+               }
 
-               return toJSON$1( shapes, data );
+               return points;
 
        }
 
-}
+       // Get total curve arc length
 
-function toJSON$1( shapes, data ) {
+       getLength() {
 
-       data.shapes = [];
+               const lengths = this.getLengths();
+               return lengths[ lengths.length - 1 ];
 
-       if ( Array.isArray( shapes ) ) {
+       }
 
-               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+       // Get list of cumulative segment lengths
 
-                       const shape = shapes[ i ];
+       getLengths( divisions = this.arcLengthDivisions ) {
 
-                       data.shapes.push( shape.uuid );
+               if ( this.cacheArcLengths &&
+                       ( this.cacheArcLengths.length === divisions + 1 ) &&
+                       ! this.needsUpdate ) {
+
+                       return this.cacheArcLengths;
 
                }
 
-       } else {
+               this.needsUpdate = false;
 
-               data.shapes.push( shapes.uuid );
+               const cache = [];
+               let current, last = this.getPoint( 0 );
+               let sum = 0;
 
-       }
+               cache.push( 0 );
 
-       return data;
+               for ( let p = 1; p <= divisions; p ++ ) {
 
-}
+                       current = this.getPoint( p / divisions );
+                       sum += current.distanceTo( last );
+                       cache.push( sum );
+                       last = current;
 
-class SphereGeometry extends BufferGeometry {
+               }
 
-       constructor( radius = 1, widthSegments = 8, heightSegments = 6, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) {
+               this.cacheArcLengths = cache;
 
-               super();
-               this.type = 'SphereGeometry';
+               return cache; // { sums: cache, sum: sum }; Sum is in the last element.
 
-               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 ) );
+       updateArcLengths() {
 
-               const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI );
+               this.needsUpdate = true;
+               this.getLengths();
 
-               let index = 0;
-               const grid = [];
+       }
 
-               const vertex = new Vector3();
-               const normal = new Vector3();
+       // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant
 
-               // buffers
+       getUtoTmapping( u, distance ) {
 
-               const indices = [];
-               const vertices = [];
-               const normals = [];
-               const uvs = [];
+               const arcLengths = this.getLengths();
 
-               // generate vertices, normals and uvs
+               let i = 0;
+               const il = arcLengths.length;
 
-               for ( let iy = 0; iy <= heightSegments; iy ++ ) {
+               let targetArcLength; // The targeted u distance value to get
 
-                       const verticesRow = [];
+               if ( distance ) {
 
-                       const v = iy / heightSegments;
+                       targetArcLength = distance;
 
-                       // special case for the poles
+               } else {
 
-                       let uOffset = 0;
+                       targetArcLength = u * arcLengths[ il - 1 ];
 
-                       if ( iy == 0 && thetaStart == 0 ) {
+               }
 
-                               uOffset = 0.5 / widthSegments;
+               // binary search for the index with largest value smaller than target u distance
 
-                       } else if ( iy == heightSegments && thetaEnd == Math.PI ) {
+               let low = 0, high = il - 1, comparison;
 
-                               uOffset = - 0.5 / widthSegments;
+               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
 
-                       for ( let ix = 0; ix <= widthSegments; ix ++ ) {
+                       comparison = arcLengths[ i ] - targetArcLength;
 
-                               const u = ix / widthSegments;
+                       if ( comparison < 0 ) {
 
-                               // vertex
+                               low = i + 1;
 
-                               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 );
+                       } else if ( comparison > 0 ) {
 
-                               vertices.push( vertex.x, vertex.y, vertex.z );
+                               high = i - 1;
 
-                               // normal
+                       } else {
 
-                               normal.copy( vertex ).normalize();
-                               normals.push( normal.x, normal.y, normal.z );
+                               high = i;
+                               break;
 
-                               // uv
+                               // DONE
 
-                               uvs.push( u + uOffset, 1 - v );
+                       }
 
-                               verticesRow.push( index ++ );
+               }
 
-                       }
+               i = high;
 
-                       grid.push( verticesRow );
+               if ( arcLengths[ i ] === targetArcLength ) {
 
-               }
+                       return i / ( il - 1 );
 
-               // indices
+               }
 
-               for ( let iy = 0; iy < heightSegments; iy ++ ) {
+               // we could get finer grain at lengths, or use simple interpolation between two points
 
-                       for ( let ix = 0; ix < widthSegments; ix ++ ) {
+               const lengthBefore = arcLengths[ i ];
+               const lengthAfter = arcLengths[ i + 1 ];
 
-                               const a = grid[ iy ][ ix + 1 ];
-                               const b = grid[ iy ][ ix ];
-                               const c = grid[ iy + 1 ][ ix ];
-                               const d = grid[ iy + 1 ][ ix + 1 ];
+               const segmentLength = lengthAfter - lengthBefore;
 
-                               if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d );
-                               if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d );
+               // determine where we are between the 'before' and 'after' points
 
-                       }
+               const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;
 
-               }
+               // add that fractional amount to t
 
-               // build geometry
+               const t = ( i + segmentFraction ) / ( il - 1 );
 
-               this.setIndex( indices );
-               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
-               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
-               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+               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
 
-/**
- * parameters = {
- *  color: <THREE.Color>
- * }
- */
+       getTangent( t, optionalTarget ) {
 
-function ShadowMaterial( parameters ) {
+               const delta = 0.0001;
+               let t1 = t - delta;
+               let t2 = t + delta;
 
-       Material.call( this );
+               // Capping in case of danger
 
-       this.type = 'ShadowMaterial';
+               if ( t1 < 0 ) t1 = 0;
+               if ( t2 > 1 ) t2 = 1;
 
-       this.color = new Color( 0x000000 );
-       this.transparent = true;
+               const pt1 = this.getPoint( t1 );
+               const pt2 = this.getPoint( t2 );
 
-       this.setValues( parameters );
+               const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() );
 
-}
+               tangent.copy( pt2 ).sub( pt1 ).normalize();
 
-ShadowMaterial.prototype = Object.create( Material.prototype );
-ShadowMaterial.prototype.constructor = ShadowMaterial;
+               return tangent;
 
-ShadowMaterial.prototype.isShadowMaterial = true;
+       }
 
-ShadowMaterial.prototype.copy = function ( source ) {
+       getTangentAt( u, optionalTarget ) {
 
-       Material.prototype.copy.call( this, source );
+               const t = this.getUtoTmapping( u );
+               return this.getTangent( t, optionalTarget );
 
-       this.color.copy( source.color );
+       }
 
-       return this;
+       computeFrenetFrames( segments, closed ) {
 
-};
+               // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
 
-function RawShaderMaterial( parameters ) {
+               const normal = new Vector3();
 
-       ShaderMaterial.call( this, parameters );
+               const tangents = [];
+               const normals = [];
+               const binormals = [];
 
-       this.type = 'RawShaderMaterial';
+               const vec = new Vector3();
+               const mat = new Matrix4();
 
-}
+               // compute the tangent vectors for each segment on the curve
 
-RawShaderMaterial.prototype = Object.create( ShaderMaterial.prototype );
-RawShaderMaterial.prototype.constructor = RawShaderMaterial;
+               for ( let i = 0; i <= segments; i ++ ) {
 
-RawShaderMaterial.prototype.isRawShaderMaterial = true;
+                       const u = i / segments;
 
-/**
- * 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>
- * }
- */
+                       tangents[ i ] = this.getTangentAt( u, new Vector3() );
 
-function MeshStandardMaterial( parameters ) {
+               }
 
-       Material.call( this );
+               // select an initial normal vector perpendicular to the first tangent vector,
+               // and in the direction of the minimum tangent xyz component
 
-       this.defines = { 'STANDARD': '' };
+               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 );
 
-       this.type = 'MeshStandardMaterial';
+               if ( tx <= min ) {
 
-       this.color = new Color( 0xffffff ); // diffuse
-       this.roughness = 1.0;
-       this.metalness = 0.0;
+                       min = tx;
+                       normal.set( 1, 0, 0 );
 
-       this.map = null;
+               }
 
-       this.lightMap = null;
-       this.lightMapIntensity = 1.0;
+               if ( ty <= min ) {
 
-       this.aoMap = null;
-       this.aoMapIntensity = 1.0;
+                       min = ty;
+                       normal.set( 0, 1, 0 );
 
-       this.emissive = new Color( 0x000000 );
-       this.emissiveIntensity = 1.0;
-       this.emissiveMap = null;
+               }
 
-       this.bumpMap = null;
-       this.bumpScale = 1;
+               if ( tz <= min ) {
 
-       this.normalMap = null;
-       this.normalMapType = TangentSpaceNormalMap;
-       this.normalScale = new Vector2( 1, 1 );
+                       normal.set( 0, 0, 1 );
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+               }
 
-       this.roughnessMap = null;
+               vec.crossVectors( tangents[ 0 ], normal ).normalize();
 
-       this.metalnessMap = null;
+               normals[ 0 ].crossVectors( tangents[ 0 ], vec );
+               binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );
 
-       this.alphaMap = null;
 
-       this.envMap = null;
-       this.envMapIntensity = 1.0;
+               // compute the slowly-varying normal and binormal vectors for each segment on the curve
 
-       this.refractionRatio = 0.98;
+               for ( let i = 1; i <= segments; i ++ ) {
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
-       this.wireframeLinecap = 'round';
-       this.wireframeLinejoin = 'round';
+                       normals[ i ] = normals[ i - 1 ].clone();
 
-       this.skinning = false;
-       this.morphTargets = false;
-       this.morphNormals = false;
+                       binormals[ i ] = binormals[ i - 1 ].clone();
 
-       this.vertexTangents = false;
+                       vec.crossVectors( tangents[ i - 1 ], tangents[ i ] );
 
-       this.setValues( parameters );
+                       if ( vec.length() > Number.EPSILON ) {
 
-}
+                               vec.normalize();
 
-MeshStandardMaterial.prototype = Object.create( Material.prototype );
-MeshStandardMaterial.prototype.constructor = MeshStandardMaterial;
+                               const theta = Math.acos( clamp$1( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors
 
-MeshStandardMaterial.prototype.isMeshStandardMaterial = true;
+                               normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );
 
-MeshStandardMaterial.prototype.copy = function ( source ) {
+                       }
 
-       Material.prototype.copy.call( this, source );
+                       binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
 
-       this.defines = { 'STANDARD': '' };
+               }
 
-       this.color.copy( source.color );
-       this.roughness = source.roughness;
-       this.metalness = source.metalness;
+               // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
 
-       this.map = source.map;
+               if ( closed === true ) {
 
-       this.lightMap = source.lightMap;
-       this.lightMapIntensity = source.lightMapIntensity;
+                       let theta = Math.acos( clamp$1( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) );
+                       theta /= segments;
 
-       this.aoMap = source.aoMap;
-       this.aoMapIntensity = source.aoMapIntensity;
+                       if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) {
 
-       this.emissive.copy( source.emissive );
-       this.emissiveMap = source.emissiveMap;
-       this.emissiveIntensity = source.emissiveIntensity;
+                               theta = - theta;
 
-       this.bumpMap = source.bumpMap;
-       this.bumpScale = source.bumpScale;
+                       }
 
-       this.normalMap = source.normalMap;
-       this.normalMapType = source.normalMapType;
-       this.normalScale.copy( source.normalScale );
+                       for ( let i = 1; i <= segments; i ++ ) {
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+                               // twist a little...
+                               normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );
+                               binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
 
-       this.roughnessMap = source.roughnessMap;
+                       }
 
-       this.metalnessMap = source.metalnessMap;
+               }
 
-       this.alphaMap = source.alphaMap;
+               return {
+                       tangents: tangents,
+                       normals: normals,
+                       binormals: binormals
+               };
 
-       this.envMap = source.envMap;
-       this.envMapIntensity = source.envMapIntensity;
+       }
 
-       this.refractionRatio = source.refractionRatio;
+       clone() {
 
-       this.wireframe = source.wireframe;
-       this.wireframeLinewidth = source.wireframeLinewidth;
-       this.wireframeLinecap = source.wireframeLinecap;
-       this.wireframeLinejoin = source.wireframeLinejoin;
+               return new this.constructor().copy( this );
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
-       this.morphNormals = source.morphNormals;
+       }
 
-       this.vertexTangents = source.vertexTangents;
+       copy( source ) {
 
-       return this;
+               this.arcLengthDivisions = source.arcLengthDivisions;
 
-};
+               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 ) {
+       toJSON() {
 
-       MeshStandardMaterial.call( this );
+               const data = {
+                       metadata: {
+                               version: 4.5,
+                               type: 'Curve',
+                               generator: 'Curve.toJSON'
+                       }
+               };
 
-       this.defines = {
+               data.arcLengthDivisions = this.arcLengthDivisions;
+               data.type = this.type;
 
-               'STANDARD': '',
-               'PHYSICAL': ''
+               return data;
 
-       };
+       }
 
-       this.type = 'MeshPhysicalMaterial';
+       fromJSON( json ) {
 
-       this.clearcoat = 0.0;
-       this.clearcoatMap = null;
-       this.clearcoatRoughness = 0.0;
-       this.clearcoatRoughnessMap = null;
-       this.clearcoatNormalScale = new Vector2( 1, 1 );
-       this.clearcoatNormalMap = null;
+               this.arcLengthDivisions = json.arcLengthDivisions;
 
-       this.reflectivity = 0.5; // maps to F0 = 0.04
+               return this;
 
-       Object.defineProperty( this, 'ior', {
-               get: function () {
+       }
 
-                       return ( 1 + 0.4 * this.reflectivity ) / ( 1 - 0.4 * this.reflectivity );
+}
 
-               },
-               set: function ( ior ) {
+class EllipseCurve extends Curve {
 
-                       this.reflectivity = MathUtils.clamp( 2.5 * ( ior - 1 ) / ( ior + 1 ), 0, 1 );
+       constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) {
 
-               }
-       } );
+               super();
 
-       this.sheen = null; // null will disable sheen bsdf
+               this.type = 'EllipseCurve';
 
-       this.transmission = 0.0;
-       this.transmissionMap = null;
+               this.aX = aX;
+               this.aY = aY;
 
-       this.setValues( parameters );
+               this.xRadius = xRadius;
+               this.yRadius = yRadius;
 
-}
+               this.aStartAngle = aStartAngle;
+               this.aEndAngle = aEndAngle;
 
-MeshPhysicalMaterial.prototype = Object.create( MeshStandardMaterial.prototype );
-MeshPhysicalMaterial.prototype.constructor = MeshPhysicalMaterial;
+               this.aClockwise = aClockwise;
 
-MeshPhysicalMaterial.prototype.isMeshPhysicalMaterial = true;
+               this.aRotation = aRotation;
 
-MeshPhysicalMaterial.prototype.copy = function ( source ) {
+       }
 
-       MeshStandardMaterial.prototype.copy.call( this, source );
+       getPoint( t, optionalTarget ) {
 
-       this.defines = {
+               const point = optionalTarget || new Vector2();
 
-               'STANDARD': '',
-               'PHYSICAL': ''
+               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;
 
-       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 );
+               if ( deltaAngle < Number.EPSILON ) {
 
-       this.reflectivity = source.reflectivity;
+                       if ( samePoints ) {
 
-       if ( source.sheen ) {
+                               deltaAngle = 0;
 
-               this.sheen = ( this.sheen || new Color() ).copy( source.sheen );
+                       } else {
 
-       } else {
+                               deltaAngle = twoPi;
 
-               this.sheen = null;
+                       }
 
-       }
+               }
 
-       this.transmission = source.transmission;
-       this.transmissionMap = source.transmissionMap;
+               if ( this.aClockwise === true && ! samePoints ) {
 
-       return this;
+                       if ( deltaAngle === twoPi ) {
 
-};
+                               deltaAngle = - twoPi;
 
-/**
- * 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>
- * }
- */
+                       } else {
 
-function MeshPhongMaterial( parameters ) {
+                               deltaAngle = deltaAngle - twoPi;
 
-       Material.call( this );
+                       }
 
-       this.type = 'MeshPhongMaterial';
+               }
 
-       this.color = new Color( 0xffffff ); // diffuse
-       this.specular = new Color( 0x111111 );
-       this.shininess = 30;
+               const angle = this.aStartAngle + t * deltaAngle;
+               let x = this.aX + this.xRadius * Math.cos( angle );
+               let y = this.aY + this.yRadius * Math.sin( angle );
 
-       this.map = null;
+               if ( this.aRotation !== 0 ) {
 
-       this.lightMap = null;
-       this.lightMapIntensity = 1.0;
+                       const cos = Math.cos( this.aRotation );
+                       const sin = Math.sin( this.aRotation );
 
-       this.aoMap = null;
-       this.aoMapIntensity = 1.0;
+                       const tx = x - this.aX;
+                       const ty = y - this.aY;
 
-       this.emissive = new Color( 0x000000 );
-       this.emissiveIntensity = 1.0;
-       this.emissiveMap = null;
+                       // Rotate the point about the center of the ellipse.
+                       x = tx * cos - ty * sin + this.aX;
+                       y = tx * sin + ty * cos + this.aY;
 
-       this.bumpMap = null;
-       this.bumpScale = 1;
+               }
 
-       this.normalMap = null;
-       this.normalMapType = TangentSpaceNormalMap;
-       this.normalScale = new Vector2( 1, 1 );
+               return point.set( x, y );
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+       }
 
-       this.specularMap = null;
+       copy( source ) {
 
-       this.alphaMap = null;
+               super.copy( source );
 
-       this.envMap = null;
-       this.combine = MultiplyOperation;
-       this.reflectivity = 1;
-       this.refractionRatio = 0.98;
+               this.aX = source.aX;
+               this.aY = source.aY;
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
-       this.wireframeLinecap = 'round';
-       this.wireframeLinejoin = 'round';
+               this.xRadius = source.xRadius;
+               this.yRadius = source.yRadius;
 
-       this.skinning = false;
-       this.morphTargets = false;
-       this.morphNormals = false;
+               this.aStartAngle = source.aStartAngle;
+               this.aEndAngle = source.aEndAngle;
 
-       this.setValues( parameters );
+               this.aClockwise = source.aClockwise;
 
-}
+               this.aRotation = source.aRotation;
 
-MeshPhongMaterial.prototype = Object.create( Material.prototype );
-MeshPhongMaterial.prototype.constructor = MeshPhongMaterial;
+               return this;
 
-MeshPhongMaterial.prototype.isMeshPhongMaterial = true;
+       }
 
-MeshPhongMaterial.prototype.copy = function ( source ) {
+       toJSON() {
 
-       Material.prototype.copy.call( this, source );
+               const data = super.toJSON();
 
-       this.color.copy( source.color );
-       this.specular.copy( source.specular );
-       this.shininess = source.shininess;
+               data.aX = this.aX;
+               data.aY = this.aY;
 
-       this.map = source.map;
+               data.xRadius = this.xRadius;
+               data.yRadius = this.yRadius;
 
-       this.lightMap = source.lightMap;
-       this.lightMapIntensity = source.lightMapIntensity;
+               data.aStartAngle = this.aStartAngle;
+               data.aEndAngle = this.aEndAngle;
 
-       this.aoMap = source.aoMap;
-       this.aoMapIntensity = source.aoMapIntensity;
+               data.aClockwise = this.aClockwise;
 
-       this.emissive.copy( source.emissive );
-       this.emissiveMap = source.emissiveMap;
-       this.emissiveIntensity = source.emissiveIntensity;
+               data.aRotation = this.aRotation;
 
-       this.bumpMap = source.bumpMap;
-       this.bumpScale = source.bumpScale;
+               return data;
 
-       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;
+       fromJSON( json ) {
 
-       this.specularMap = source.specularMap;
+               super.fromJSON( json );
 
-       this.alphaMap = source.alphaMap;
+               this.aX = json.aX;
+               this.aY = json.aY;
 
-       this.envMap = source.envMap;
-       this.combine = source.combine;
-       this.reflectivity = source.reflectivity;
-       this.refractionRatio = source.refractionRatio;
+               this.xRadius = json.xRadius;
+               this.yRadius = json.yRadius;
 
-       this.wireframe = source.wireframe;
-       this.wireframeLinewidth = source.wireframeLinewidth;
-       this.wireframeLinecap = source.wireframeLinecap;
-       this.wireframeLinejoin = source.wireframeLinejoin;
+               this.aStartAngle = json.aStartAngle;
+               this.aEndAngle = json.aEndAngle;
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
-       this.morphNormals = source.morphNormals;
+               this.aClockwise = json.aClockwise;
 
-       return this;
+               this.aRotation = json.aRotation;
 
-};
+               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 );
+EllipseCurve.prototype.isEllipseCurve = true;
 
-       this.defines = { 'TOON': '' };
+class ArcCurve extends EllipseCurve {
 
-       this.type = 'MeshToonMaterial';
+       constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
 
-       this.color = new Color( 0xffffff );
+               super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
 
-       this.map = null;
-       this.gradientMap = null;
+               this.type = 'ArcCurve';
 
-       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;
+ArcCurve.prototype.isArcCurve = true;
 
-       this.bumpMap = null;
-       this.bumpScale = 1;
+/**
+ * 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
+ */
 
-       this.normalMap = null;
-       this.normalMapType = TangentSpaceNormalMap;
-       this.normalScale = new Vector2( 1, 1 );
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+/*
+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.alphaMap = null;
+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.
+*/
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
-       this.wireframeLinecap = 'round';
-       this.wireframeLinejoin = 'round';
+function CubicPoly() {
 
-       this.skinning = false;
-       this.morphTargets = false;
-       this.morphNormals = false;
+       let c0 = 0, c1 = 0, c2 = 0, c3 = 0;
 
-       this.setValues( parameters );
+       /*
+        * 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;
 
-MeshToonMaterial.prototype = Object.create( Material.prototype );
-MeshToonMaterial.prototype.constructor = MeshToonMaterial;
+       }
 
-MeshToonMaterial.prototype.isMeshToonMaterial = true;
+       return {
 
-MeshToonMaterial.prototype.copy = function ( source ) {
+               initCatmullRom: function ( x0, x1, x2, x3, tension ) {
 
-       Material.prototype.copy.call( this, source );
+                       init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );
 
-       this.color.copy( source.color );
+               },
 
-       this.map = source.map;
-       this.gradientMap = source.gradientMap;
+               initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) {
 
-       this.lightMap = source.lightMap;
-       this.lightMapIntensity = source.lightMapIntensity;
+                       // 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;
 
-       this.aoMap = source.aoMap;
-       this.aoMapIntensity = source.aoMapIntensity;
+                       // rescale tangents for parametrization in [0,1]
+                       t1 *= dt1;
+                       t2 *= dt1;
 
-       this.emissive.copy( source.emissive );
-       this.emissiveMap = source.emissiveMap;
-       this.emissiveIntensity = source.emissiveIntensity;
+                       init( x1, x2, t1, t2 );
 
-       this.bumpMap = source.bumpMap;
-       this.bumpScale = source.bumpScale;
+               },
 
-       this.normalMap = source.normalMap;
-       this.normalMapType = source.normalMapType;
-       this.normalScale.copy( source.normalScale );
+               calc: function ( t ) {
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+                       const t2 = t * t;
+                       const t3 = t2 * t;
+                       return c0 + c1 * t + c2 * t2 + c3 * t3;
 
-       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;
+//
 
-};
+const tmp = new Vector3();
+const px = new CubicPoly(), py = new CubicPoly(), pz = new CubicPoly();
 
-/**
- * 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>
- * }
- */
+class CatmullRomCurve3 extends Curve {
 
-function MeshNormalMaterial( parameters ) {
+       constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) {
 
-       Material.call( this );
+               super();
 
-       this.type = 'MeshNormalMaterial';
+               this.type = 'CatmullRomCurve3';
 
-       this.bumpMap = null;
-       this.bumpScale = 1;
+               this.points = points;
+               this.closed = closed;
+               this.curveType = curveType;
+               this.tension = tension;
 
-       this.normalMap = null;
-       this.normalMapType = TangentSpaceNormalMap;
-       this.normalScale = new Vector2( 1, 1 );
+       }
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+       getPoint( t, optionalTarget = new Vector3() ) {
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
+               const point = optionalTarget;
 
-       this.fog = false;
+               const points = this.points;
+               const l = points.length;
 
-       this.skinning = false;
-       this.morphTargets = false;
-       this.morphNormals = false;
+               const p = ( l - ( this.closed ? 0 : 1 ) ) * t;
+               let intPoint = Math.floor( p );
+               let weight = p - intPoint;
 
-       this.setValues( parameters );
+               if ( this.closed ) {
 
-}
+                       intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l;
 
-MeshNormalMaterial.prototype = Object.create( Material.prototype );
-MeshNormalMaterial.prototype.constructor = MeshNormalMaterial;
+               } else if ( weight === 0 && intPoint === l - 1 ) {
 
-MeshNormalMaterial.prototype.isMeshNormalMaterial = true;
+                       intPoint = l - 2;
+                       weight = 1;
 
-MeshNormalMaterial.prototype.copy = function ( source ) {
+               }
 
-       Material.prototype.copy.call( this, source );
+               let p0, p3; // 4 points (p1 & p2 defined below)
 
-       this.bumpMap = source.bumpMap;
-       this.bumpScale = source.bumpScale;
+               if ( this.closed || intPoint > 0 ) {
 
-       this.normalMap = source.normalMap;
-       this.normalMapType = source.normalMapType;
-       this.normalScale.copy( source.normalScale );
+                       p0 = points[ ( intPoint - 1 ) % l ];
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+               } else {
 
-       this.wireframe = source.wireframe;
-       this.wireframeLinewidth = source.wireframeLinewidth;
+                       // extrapolate first point
+                       tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
+                       p0 = tmp;
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
-       this.morphNormals = source.morphNormals;
+               }
 
-       return this;
+               const p1 = points[ intPoint % l ];
+               const p2 = points[ ( intPoint + 1 ) % l ];
 
-};
+               if ( this.closed || intPoint + 2 < l ) {
 
-/**
- * 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>
- * }
- */
+                       p3 = points[ ( intPoint + 2 ) % l ];
 
-function MeshLambertMaterial( parameters ) {
+               } else {
+
+                       // extrapolate last point
+                       tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] );
+                       p3 = tmp;
 
-       Material.call( this );
+               }
 
-       this.type = 'MeshLambertMaterial';
+               if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) {
 
-       this.color = new Color( 0xffffff ); // diffuse
+                       // 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 );
 
-       this.map = null;
+                       // safety check for repeated points
+                       if ( dt1 < 1e-4 ) dt1 = 1.0;
+                       if ( dt0 < 1e-4 ) dt0 = dt1;
+                       if ( dt2 < 1e-4 ) dt2 = dt1;
 
-       this.lightMap = null;
-       this.lightMapIntensity = 1.0;
+                       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 );
 
-       this.aoMap = null;
-       this.aoMapIntensity = 1.0;
+               } else if ( this.curveType === 'catmullrom' ) {
 
-       this.emissive = new Color( 0x000000 );
-       this.emissiveIntensity = 1.0;
-       this.emissiveMap = null;
+                       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 );
 
-       this.specularMap = null;
+               }
 
-       this.alphaMap = null;
+               point.set(
+                       px.calc( weight ),
+                       py.calc( weight ),
+                       pz.calc( weight )
+               );
 
-       this.envMap = null;
-       this.combine = MultiplyOperation;
-       this.reflectivity = 1;
-       this.refractionRatio = 0.98;
+               return point;
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
-       this.wireframeLinecap = 'round';
-       this.wireframeLinejoin = 'round';
+       }
 
-       this.skinning = false;
-       this.morphTargets = false;
-       this.morphNormals = false;
+       copy( source ) {
 
-       this.setValues( parameters );
+               super.copy( source );
 
-}
+               this.points = [];
 
-MeshLambertMaterial.prototype = Object.create( Material.prototype );
-MeshLambertMaterial.prototype.constructor = MeshLambertMaterial;
+               for ( let i = 0, l = source.points.length; i < l; i ++ ) {
 
-MeshLambertMaterial.prototype.isMeshLambertMaterial = true;
+                       const point = source.points[ i ];
 
-MeshLambertMaterial.prototype.copy = function ( source ) {
+                       this.points.push( point.clone() );
 
-       Material.prototype.copy.call( this, source );
+               }
 
-       this.color.copy( source.color );
+               this.closed = source.closed;
+               this.curveType = source.curveType;
+               this.tension = source.tension;
 
-       this.map = source.map;
+               return this;
 
-       this.lightMap = source.lightMap;
-       this.lightMapIntensity = source.lightMapIntensity;
+       }
 
-       this.aoMap = source.aoMap;
-       this.aoMapIntensity = source.aoMapIntensity;
+       toJSON() {
 
-       this.emissive.copy( source.emissive );
-       this.emissiveMap = source.emissiveMap;
-       this.emissiveIntensity = source.emissiveIntensity;
+               const data = super.toJSON();
 
-       this.specularMap = source.specularMap;
+               data.points = [];
 
-       this.alphaMap = source.alphaMap;
+               for ( let i = 0, l = this.points.length; i < l; i ++ ) {
 
-       this.envMap = source.envMap;
-       this.combine = source.combine;
-       this.reflectivity = source.reflectivity;
-       this.refractionRatio = source.refractionRatio;
+                       const point = this.points[ i ];
+                       data.points.push( point.toArray() );
 
-       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;
+               data.closed = this.closed;
+               data.curveType = this.curveType;
+               data.tension = this.tension;
 
-       return this;
+               return data;
 
-};
+       }
 
-/**
- * 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>
- * }
- */
+       fromJSON( json ) {
 
-function MeshMatcapMaterial( parameters ) {
+               super.fromJSON( json );
 
-       Material.call( this );
+               this.points = [];
 
-       this.defines = { 'MATCAP': '' };
+               for ( let i = 0, l = json.points.length; i < l; i ++ ) {
 
-       this.type = 'MeshMatcapMaterial';
+                       const point = json.points[ i ];
+                       this.points.push( new Vector3().fromArray( point ) );
 
-       this.color = new Color( 0xffffff ); // diffuse
+               }
 
-       this.matcap = null;
+               this.closed = json.closed;
+               this.curveType = json.curveType;
+               this.tension = json.tension;
 
-       this.map = null;
+               return this;
 
-       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;
+CatmullRomCurve3.prototype.isCatmullRomCurve3 = true;
 
-       this.alphaMap = null;
+/**
+ * Bezier Curves formulas obtained from
+ * http://en.wikipedia.org/wiki/Bézier_curve
+ */
 
-       this.skinning = false;
-       this.morphTargets = false;
-       this.morphNormals = false;
+function CatmullRom( t, p0, p1, p2, p3 ) {
 
-       this.setValues( parameters );
+       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;
 
 }
 
-MeshMatcapMaterial.prototype = Object.create( Material.prototype );
-MeshMatcapMaterial.prototype.constructor = MeshMatcapMaterial;
+//
 
-MeshMatcapMaterial.prototype.isMeshMatcapMaterial = true;
+function QuadraticBezierP0( t, p ) {
 
-MeshMatcapMaterial.prototype.copy = function ( source ) {
+       const k = 1 - t;
+       return k * k * p;
+
+}
 
-       Material.prototype.copy.call( this, source );
+function QuadraticBezierP1( t, p ) {
 
-       this.defines = { 'MATCAP': '' };
+       return 2 * ( 1 - t ) * t * p;
 
-       this.color.copy( source.color );
+}
 
-       this.matcap = source.matcap;
+function QuadraticBezierP2( t, p ) {
 
-       this.map = source.map;
+       return t * t * p;
 
-       this.bumpMap = source.bumpMap;
-       this.bumpScale = source.bumpScale;
+}
 
-       this.normalMap = source.normalMap;
-       this.normalMapType = source.normalMapType;
-       this.normalScale.copy( source.normalScale );
+function QuadraticBezier( t, p0, p1, p2 ) {
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+       return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) +
+               QuadraticBezierP2( t, p2 );
 
-       this.alphaMap = source.alphaMap;
+}
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
-       this.morphNormals = source.morphNormals;
+//
 
-       return this;
+function CubicBezierP0( t, p ) {
 
-};
+       const k = 1 - t;
+       return k * k * k * p;
 
-/**
- * parameters = {
- *  color: <hex>,
- *  opacity: <float>,
- *
- *  linewidth: <float>,
- *
- *  scale: <float>,
- *  dashSize: <float>,
- *  gapSize: <float>
- * }
- */
+}
 
-function LineDashedMaterial( parameters ) {
+function CubicBezierP1( t, p ) {
 
-       LineBasicMaterial.call( this );
+       const k = 1 - t;
+       return 3 * k * k * t * p;
 
-       this.type = 'LineDashedMaterial';
+}
 
-       this.scale = 1;
-       this.dashSize = 3;
-       this.gapSize = 1;
+function CubicBezierP2( t, p ) {
 
-       this.setValues( parameters );
+       return 3 * ( 1 - t ) * t * t * p;
 
 }
 
-LineDashedMaterial.prototype = Object.create( LineBasicMaterial.prototype );
-LineDashedMaterial.prototype.constructor = LineDashedMaterial;
+function CubicBezierP3( t, p ) {
 
-LineDashedMaterial.prototype.isLineDashedMaterial = true;
+       return t * t * t * p;
 
-LineDashedMaterial.prototype.copy = function ( source ) {
+}
 
-       LineBasicMaterial.prototype.copy.call( this, source );
+function CubicBezier( t, p0, p1, p2, p3 ) {
 
-       this.scale = source.scale;
-       this.dashSize = source.dashSize;
-       this.gapSize = source.gapSize;
+       return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) +
+               CubicBezierP3( t, p3 );
 
-       return this;
+}
 
-};
+class CubicBezierCurve extends Curve {
 
-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
-});
+       constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) {
 
-const AnimationUtils = {
+               super();
 
-       // same as Array.prototype.slice, but also works on typed arrays
-       arraySlice: function ( array, from, to ) {
+               this.type = 'CubicBezierCurve';
 
-               if ( AnimationUtils.isTypedArray( array ) ) {
+               this.v0 = v0;
+               this.v1 = v1;
+               this.v2 = v2;
+               this.v3 = v3;
 
-                       // 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 ) );
+       }
 
-               }
+       getPoint( t, optionalTarget = new Vector2() ) {
 
-               return array.slice( from, to );
+               const point = optionalTarget;
 
-       },
+               const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
 
-       // converts an array to a specific type
-       convertArray: function ( array, type, forceClone ) {
+               point.set(
+                       CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),
+                       CubicBezier( t, v0.y, v1.y, v2.y, v3.y )
+               );
 
-               if ( ! array || // let 'undefined' and 'null' pass
-                       ! forceClone && array.constructor === type ) return array;
+               return point;
 
-               if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {
+       }
 
-                       return new type( array ); // create typed array
+       copy( source ) {
 
-               }
+               super.copy( source );
 
-               return Array.prototype.slice.call( array ); // create Array
+               this.v0.copy( source.v0 );
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
+               this.v3.copy( source.v3 );
 
-       },
+               return this;
 
-       isTypedArray: function ( object ) {
+       }
 
-               return ArrayBuffer.isView( object ) &&
-                       ! ( object instanceof DataView );
+       toJSON() {
 
-       },
+               const data = super.toJSON();
 
-       // returns an array by which times and values can be sorted
-       getKeyframeOrder: function ( times ) {
+               data.v0 = this.v0.toArray();
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
+               data.v3 = this.v3.toArray();
 
-               function compareTime( i, j ) {
+               return data;
 
-                       return times[ i ] - times[ j ];
+       }
 
-               }
+       fromJSON( json ) {
 
-               const n = times.length;
-               const result = new Array( n );
-               for ( let i = 0; i !== n; ++ i ) result[ i ] = i;
+               super.fromJSON( json );
 
-               result.sort( compareTime );
+               this.v0.fromArray( json.v0 );
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
+               this.v3.fromArray( json.v3 );
 
-               return result;
+               return this;
 
-       },
+       }
 
-       // 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 );
+CubicBezierCurve.prototype.isCubicBezierCurve = true;
 
-               for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {
+class CubicBezierCurve3 extends Curve {
 
-                       const srcOffset = order[ i ] * stride;
+       constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) {
 
-                       for ( let j = 0; j !== stride; ++ j ) {
+               super();
 
-                               result[ dstOffset ++ ] = values[ srcOffset + j ];
+               this.type = 'CubicBezierCurve3';
 
-                       }
+               this.v0 = v0;
+               this.v1 = v1;
+               this.v2 = v2;
+               this.v3 = v3;
 
-               }
+       }
 
-               return result;
+       getPoint( t, optionalTarget = new Vector3() ) {
 
-       },
+               const point = optionalTarget;
 
-       // function for parsing AOS keyframe formats
-       flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {
+               const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
 
-               let i = 1, key = jsonKeys[ 0 ];
+               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 )
+               );
 
-               while ( key !== undefined && key[ valuePropertyName ] === undefined ) {
+               return point;
 
-                       key = jsonKeys[ i ++ ];
+       }
 
-               }
+       copy( source ) {
 
-               if ( key === undefined ) return; // no data
+               super.copy( source );
 
-               let value = key[ valuePropertyName ];
-               if ( value === undefined ) return; // no data
+               this.v0.copy( source.v0 );
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
+               this.v3.copy( source.v3 );
 
-               if ( Array.isArray( value ) ) {
+               return this;
 
-                       do {
+       }
 
-                               value = key[ valuePropertyName ];
+       toJSON() {
 
-                               if ( value !== undefined ) {
+               const data = super.toJSON();
 
-                                       times.push( key.time );
-                                       values.push.apply( values, value ); // push all elements
+               data.v0 = this.v0.toArray();
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
+               data.v3 = this.v3.toArray();
 
-                               }
+               return data;
 
-                               key = jsonKeys[ i ++ ];
+       }
 
-                       } while ( key !== undefined );
+       fromJSON( json ) {
 
-               } else if ( value.toArray !== undefined ) {
+               super.fromJSON( json );
 
-                       // ...assume THREE.Math-ish
+               this.v0.fromArray( json.v0 );
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
+               this.v3.fromArray( json.v3 );
 
-                       do {
+               return this;
 
-                               value = key[ valuePropertyName ];
+       }
 
-                               if ( value !== undefined ) {
+}
 
-                                       times.push( key.time );
-                                       value.toArray( values, values.length );
+CubicBezierCurve3.prototype.isCubicBezierCurve3 = true;
 
-                               }
+class LineCurve extends Curve {
 
-                               key = jsonKeys[ i ++ ];
+       constructor( v1 = new Vector2(), v2 = new Vector2() ) {
 
-                       } while ( key !== undefined );
+               super();
 
-               } else {
+               this.type = 'LineCurve';
 
-                       // otherwise push as-is
+               this.v1 = v1;
+               this.v2 = v2;
 
-                       do {
+       }
 
-                               value = key[ valuePropertyName ];
+       getPoint( t, optionalTarget = new Vector2() ) {
 
-                               if ( value !== undefined ) {
+               const point = optionalTarget;
 
-                                       times.push( key.time );
-                                       values.push( value );
+               if ( t === 1 ) {
 
-                               }
+                       point.copy( this.v2 );
 
-                               key = jsonKeys[ i ++ ];
+               } else {
 
-                       } while ( key !== undefined );
+                       point.copy( this.v2 ).sub( this.v1 );
+                       point.multiplyScalar( t ).add( this.v1 );
 
                }
 
-       },
-
-       subclip: function ( sourceClip, name, startFrame, endFrame, fps = 30 ) {
-
-               const clip = sourceClip.clone();
+               return point;
 
-               clip.name = name;
+       }
 
-               const tracks = [];
+       // Line curve is linear, so we can overwrite default getPointAt
+       getPointAt( u, optionalTarget ) {
 
-               for ( let i = 0; i < clip.tracks.length; ++ i ) {
+               return this.getPoint( u, optionalTarget );
 
-                       const track = clip.tracks[ i ];
-                       const valueSize = track.getValueSize();
+       }
 
-                       const times = [];
-                       const values = [];
+       getTangent( t, optionalTarget ) {
 
-                       for ( let j = 0; j < track.times.length; ++ j ) {
+               const tangent = optionalTarget || new Vector2();
 
-                               const frame = track.times[ j ] * fps;
+               tangent.copy( this.v2 ).sub( this.v1 ).normalize();
 
-                               if ( frame < startFrame || frame >= endFrame ) continue;
+               return tangent;
 
-                               times.push( track.times[ j ] );
+       }
 
-                               for ( let k = 0; k < valueSize; ++ k ) {
+       copy( source ) {
 
-                                       values.push( track.values[ j * valueSize + k ] );
+               super.copy( source );
 
-                               }
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
 
-                       }
+               return this;
 
-                       if ( times.length === 0 ) continue;
+       }
 
-                       track.times = AnimationUtils.convertArray( times, track.times.constructor );
-                       track.values = AnimationUtils.convertArray( values, track.values.constructor );
+       toJSON() {
 
-                       tracks.push( track );
+               const data = super.toJSON();
 
-               }
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
 
-               clip.tracks = tracks;
+               return data;
 
-               // find minimum .times value across all tracks in the trimmed clip
+       }
 
-               let minStartTime = Infinity;
+       fromJSON( json ) {
 
-               for ( let i = 0; i < clip.tracks.length; ++ i ) {
+               super.fromJSON( json );
 
-                       if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) {
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
 
-                               minStartTime = clip.tracks[ i ].times[ 0 ];
+               return this;
 
-                       }
+       }
 
-               }
+}
 
-               // shift all tracks such that clip begins at t=0
+LineCurve.prototype.isLineCurve = true;
 
-               for ( let i = 0; i < clip.tracks.length; ++ i ) {
+class LineCurve3 extends Curve {
 
-                       clip.tracks[ i ].shift( - 1 * minStartTime );
+       constructor( v1 = new Vector3(), v2 = new Vector3() ) {
 
-               }
+               super();
 
-               clip.resetDuration();
+               this.type = 'LineCurve3';
+               this.isLineCurve3 = true;
 
-               return clip;
+               this.v1 = v1;
+               this.v2 = v2;
 
-       },
+       }
+       getPoint( t, optionalTarget = new Vector3() ) {
 
-       makeClipAdditive: function ( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) {
+               const point = optionalTarget;
 
-               if ( fps <= 0 ) fps = 30;
+               if ( t === 1 ) {
 
-               const numTracks = referenceClip.tracks.length;
-               const referenceTime = referenceFrame / fps;
+                       point.copy( this.v2 );
 
-               // Make each track's values relative to the values at the reference frame
-               for ( let i = 0; i < numTracks; ++ i ) {
+               } else {
 
-                       const referenceTrack = referenceClip.tracks[ i ];
-                       const referenceTrackType = referenceTrack.ValueTypeName;
+                       point.copy( this.v2 ).sub( this.v1 );
+                       point.multiplyScalar( t ).add( this.v1 );
 
-                       // 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 point;
 
-                               return track.name === referenceTrack.name
-                                       && track.ValueTypeName === referenceTrackType;
+       }
+       // Line curve is linear, so we can overwrite default getPointAt
+       getPointAt( u, optionalTarget ) {
 
-                       } );
+               return this.getPoint( u, optionalTarget );
 
-                       if ( targetTrack === undefined ) continue;
+       }
+       copy( source ) {
 
-                       let referenceOffset = 0;
-                       const referenceValueSize = referenceTrack.getValueSize();
+               super.copy( source );
 
-                       if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
 
-                               referenceOffset = referenceValueSize / 3;
+               return this;
 
-                       }
+       }
+       toJSON() {
 
-                       let targetOffset = 0;
-                       const targetValueSize = targetTrack.getValueSize();
+               const data = super.toJSON();
 
-                       if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
 
-                               targetOffset = targetValueSize / 3;
+               return data;
 
-                       }
+       }
+       fromJSON( json ) {
 
-                       const lastIndex = referenceTrack.times.length - 1;
-                       let referenceValue;
+               super.fromJSON( json );
 
-                       // Find the value to subtract out of the track
-                       if ( referenceTime <= referenceTrack.times[ 0 ] ) {
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
 
-                               // 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 );
+               return this;
 
-                       } 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 {
+class QuadraticBezierCurve extends Curve {
 
-                               // 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 );
+       constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) {
 
-                       }
+               super();
 
-                       // Conjugate the quaternion
-                       if ( referenceTrackType === 'quaternion' ) {
+               this.type = 'QuadraticBezierCurve';
 
-                               const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate();
-                               referenceQuat.toArray( referenceValue );
+               this.v0 = v0;
+               this.v1 = v1;
+               this.v2 = v2;
 
-                       }
+       }
 
-                       // Subtract the reference value from all of the track values
+       getPoint( t, optionalTarget = new Vector2() ) {
 
-                       const numTimes = targetTrack.times.length;
-                       for ( let j = 0; j < numTimes; ++ j ) {
+               const point = optionalTarget;
 
-                               const valueStart = j * targetValueSize + targetOffset;
+               const v0 = this.v0, v1 = this.v1, v2 = this.v2;
 
-                               if ( referenceTrackType === 'quaternion' ) {
+               point.set(
+                       QuadraticBezier( t, v0.x, v1.x, v2.x ),
+                       QuadraticBezier( t, v0.y, v1.y, v2.y )
+               );
 
-                                       // Multiply the conjugate for quaternion track types
-                                       Quaternion.multiplyQuaternionsFlat(
-                                               targetTrack.values,
-                                               valueStart,
-                                               referenceValue,
-                                               0,
-                                               targetTrack.values,
-                                               valueStart
-                                       );
+               return point;
 
-                               } else {
+       }
 
-                                       const valueEnd = targetValueSize - targetOffset * 2;
+       copy( source ) {
 
-                                       // Subtract each value for all other numeric track types
-                                       for ( let k = 0; k < valueEnd; ++ k ) {
+               super.copy( source );
 
-                                               targetTrack.values[ valueStart + k ] -= referenceValue[ k ];
+               this.v0.copy( source.v0 );
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
 
-                                       }
+               return this;
 
-                               }
+       }
 
-                       }
+       toJSON() {
 
-               }
+               const data = super.toJSON();
 
-               targetClip.blendMode = AdditiveAnimationBlendMode;
+               data.v0 = this.v0.toArray();
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
 
-               return targetClip;
+               return data;
 
        }
 
-};
+       fromJSON( json ) {
 
-/**
- * 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
- *
- */
+               super.fromJSON( json );
 
-function Interpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+               this.v0.fromArray( json.v0 );
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
 
-       this.parameterPositions = parameterPositions;
-       this._cachedIndex = 0;
+               return this;
 
-       this.resultBuffer = resultBuffer !== undefined ?
-               resultBuffer : new sampleValues.constructor( sampleSize );
-       this.sampleValues = sampleValues;
-       this.valueSize = sampleSize;
+       }
 
 }
 
-Object.assign( Interpolant.prototype, {
-
-       evaluate: function ( t ) {
+QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true;
 
-               const pp = this.parameterPositions;
-               let i1 = this._cachedIndex,
-                       t1 = pp[ i1 ],
-                       t0 = pp[ i1 - 1 ];
+class QuadraticBezierCurve3 extends Curve {
 
-               validate_interval: {
+       constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) {
 
-                       seek: {
+               super();
 
-                               let right;
+               this.type = 'QuadraticBezierCurve3';
 
-                               linear_scan: {
+               this.v0 = v0;
+               this.v1 = v1;
+               this.v2 = v2;
 
-                                       //- 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; ; ) {
+       getPoint( t, optionalTarget = new Vector3() ) {
 
-                                                       if ( t1 === undefined ) {
+               const point = optionalTarget;
 
-                                                               if ( t < t0 ) break forward_scan;
+               const v0 = this.v0, v1 = this.v1, v2 = this.v2;
 
-                                                               // after end
+               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 )
+               );
 
-                                                               i1 = pp.length;
-                                                               this._cachedIndex = i1;
-                                                               return this.afterEnd_( i1 - 1, t, t0 );
+               return point;
 
-                                                       }
+       }
 
-                                                       if ( i1 === giveUpAt ) break; // this loop
+       copy( source ) {
 
-                                                       t0 = t1;
-                                                       t1 = pp[ ++ i1 ];
+               super.copy( source );
 
-                                                       if ( t < t1 ) {
+               this.v0.copy( source.v0 );
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
 
-                                                               // we have arrived at the sought interval
-                                                               break seek;
+               return this;
 
-                                                       }
+       }
 
-                                               }
+       toJSON() {
 
-                                               // prepare binary search on the right side of the index
-                                               right = pp.length;
-                                               break linear_scan;
+               const data = super.toJSON();
 
-                                       }
+               data.v0 = this.v0.toArray();
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
 
-                                       //- slower code:
-                                       //-                                     if ( t < t0 || t0 === undefined ) {
-                                       if ( ! ( t >= t0 ) ) {
+               return data;
 
-                                               // looping?
+       }
 
-                                               const t1global = pp[ 1 ];
+       fromJSON( json ) {
 
-                                               if ( t < t1global ) {
+               super.fromJSON( json );
 
-                                                       i1 = 2; // + 1, using the scan for the details
-                                                       t0 = t1global;
+               this.v0.fromArray( json.v0 );
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
 
-                                               }
+               return this;
 
-                                               // linear reverse scan
+       }
 
-                                               for ( let giveUpAt = i1 - 2; ; ) {
+}
 
-                                                       if ( t0 === undefined ) {
+QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true;
 
-                                                               // before start
+class SplineCurve extends Curve {
 
-                                                               this._cachedIndex = 0;
-                                                               return this.beforeStart_( 0, t, t1 );
+       constructor( points = [] ) {
 
-                                                       }
+               super();
 
-                                                       if ( i1 === giveUpAt ) break; // this loop
+               this.type = 'SplineCurve';
 
-                                                       t1 = t0;
-                                                       t0 = pp[ -- i1 - 1 ];
+               this.points = points;
 
-                                                       if ( t >= t0 ) {
+       }
 
-                                                               // we have arrived at the sought interval
-                                                               break seek;
+       getPoint( t, optionalTarget = new Vector2() ) {
 
-                                                       }
+               const point = optionalTarget;
 
-                                               }
+               const points = this.points;
+               const p = ( points.length - 1 ) * t;
 
-                                               // prepare binary search on the left side of the index
-                                               right = i1;
-                                               i1 = 0;
-                                               break linear_scan;
+               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 ];
 
-                                       // the interval is valid
+               point.set(
+                       CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ),
+                       CatmullRom( weight, p0.y, p1.y, p2.y, p3.y )
+               );
 
-                                       break validate_interval;
+               return point;
 
-                               } // linear scan
+       }
 
-                               // binary search
+       copy( source ) {
 
-                               while ( i1 < right ) {
+               super.copy( source );
 
-                                       const mid = ( i1 + right ) >>> 1;
+               this.points = [];
 
-                                       if ( t < pp[ mid ] ) {
+               for ( let i = 0, l = source.points.length; i < l; i ++ ) {
 
-                                               right = mid;
+                       const point = source.points[ i ];
 
-                                       } else {
+                       this.points.push( point.clone() );
 
-                                               i1 = mid + 1;
+               }
 
-                                       }
+               return this;
 
-                               }
+       }
 
-                               t1 = pp[ i1 ];
-                               t0 = pp[ i1 - 1 ];
+       toJSON() {
 
-                               // check boundary cases, again
+               const data = super.toJSON();
 
-                               if ( t0 === undefined ) {
+               data.points = [];
 
-                                       this._cachedIndex = 0;
-                                       return this.beforeStart_( 0, t, t1 );
+               for ( let i = 0, l = this.points.length; i < l; i ++ ) {
 
-                               }
+                       const point = this.points[ i ];
+                       data.points.push( point.toArray() );
 
-                               if ( t1 === undefined ) {
+               }
 
-                                       i1 = pp.length;
-                                       this._cachedIndex = i1;
-                                       return this.afterEnd_( i1 - 1, t0, t );
+               return data;
 
-                               }
+       }
 
-                       } // seek
+       fromJSON( json ) {
 
-                       this._cachedIndex = i1;
+               super.fromJSON( json );
 
-                       this.intervalChanged_( i1, t0, t1 );
+               this.points = [];
 
-               } // validate_interval
+               for ( let i = 0, l = json.points.length; i < l; i ++ ) {
 
-               return this.interpolate_( i1, t0, t, t1 );
+                       const point = json.points[ i ];
+                       this.points.push( new Vector2().fromArray( point ) );
 
-       },
+               }
 
-       settings: null, // optional, subclass-specific settings structure
-       // Note: The indirection allows central control of many interpolants.
+               return this;
 
-       // --- Protected interface
+       }
 
-       DefaultSettings_: {},
+}
 
-       getSettings_: function () {
+SplineCurve.prototype.isSplineCurve = true;
 
-               return this.settings || this.DefaultSettings_;
+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
+ **************************************************************/
 
-       copySampleValue_: function ( index ) {
+class CurvePath extends Curve {
 
-               // copies a sample value to the result buffer
+       constructor() {
 
-               const result = this.resultBuffer,
-                       values = this.sampleValues,
-                       stride = this.valueSize,
-                       offset = index * stride;
+               super();
 
-               for ( let i = 0; i !== stride; ++ i ) {
+               this.type = 'CurvePath';
 
-                       result[ i ] = values[ offset + i ];
+               this.curves = [];
+               this.autoClose = false; // Automatically closes the path
 
-               }
+       }
 
-               return result;
+       add( curve ) {
 
-       },
+               this.curves.push( curve );
 
-       // Template methods for derived classes:
+       }
 
-       interpolate_: function ( /* i1, t0, t, t1 */ ) {
+       closePath() {
 
-               throw new Error( 'call to abstract method' );
-               // implementations shall return this.resultBuffer
+               // 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 ) ) {
 
-       intervalChanged_: function ( /* i1, t0, t1 */ ) {
+                       this.curves.push( new LineCurve( endPoint, startPoint ) );
 
-               // empty
+               }
 
        }
 
-} );
+       // To get accurate point with reference to
+       // entire path distance at time t,
+       // following has to be done:
 
-// DECLARE ALIAS AFTER assign prototype
-Object.assign( Interpolant.prototype, {
+       // 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')
 
-       //( 0, t, t0 ), returns this.resultBuffer
-       beforeStart_: Interpolant.prototype.copySampleValue_,
+       getPoint( t, optionalTarget ) {
 
-       //( N-1, tN-1, t ), returns this.resultBuffer
-       afterEnd_: Interpolant.prototype.copySampleValue_,
+               const d = t * this.getLength();
+               const curveLengths = this.getCurveLengths();
+               let i = 0;
 
-} );
+               // To think about boundaries points.
 
-/**
- * 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.
- */
+               while ( i < curveLengths.length ) {
 
-function CubicInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+                       if ( curveLengths[ i ] >= d ) {
 
-       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+                               const diff = curveLengths[ i ] - d;
+                               const curve = this.curves[ i ];
 
-       this._weightPrev = - 0;
-       this._offsetPrev = - 0;
-       this._weightNext = - 0;
-       this._offsetNext = - 0;
+                               const segmentLength = curve.getLength();
+                               const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;
 
-}
+                               return curve.getPointAt( u, optionalTarget );
 
-CubicInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+                       }
 
-       constructor: CubicInterpolant,
+                       i ++;
 
-       DefaultSettings_: {
+               }
 
-               endingStart: ZeroCurvatureEnding,
-               endingEnd: ZeroCurvatureEnding
+               return null;
 
-       },
+               // loop where sum != 0, sum > d , sum+1 <d
 
-       intervalChanged_: function ( i1, t0, t1 ) {
+       }
 
-               const pp = this.parameterPositions;
-               let iPrev = i1 - 2,
-                       iNext = i1 + 1,
+       // 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
 
-                       tPrev = pp[ iPrev ],
-                       tNext = pp[ iNext ];
+       getLength() {
 
-               if ( tPrev === undefined ) {
+               const lens = this.getCurveLengths();
+               return lens[ lens.length - 1 ];
 
-                       switch ( this.getSettings_().endingStart ) {
+       }
 
-                               case ZeroSlopeEnding:
+       // cacheLengths must be recalculated.
+       updateArcLengths() {
 
-                                       // f'(t0) = 0
-                                       iPrev = i1;
-                                       tPrev = 2 * t0 - t1;
+               this.needsUpdate = true;
+               this.cacheLengths = null;
+               this.getCurveLengths();
 
-                                       break;
+       }
 
-                               case WrapAroundEnding:
+       // Compute lengths and cache them
+       // We cannot overwrite getLengths() because UtoT mapping uses it.
 
-                                       // use the other end of the curve
-                                       iPrev = pp.length - 2;
-                                       tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ];
+       getCurveLengths() {
 
-                                       break;
+               // We use cache values if curves and cache array are same length
 
-                               default: // ZeroCurvatureEnding
+               if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) {
 
-                                       // f''(t0) = 0 a.k.a. Natural Spline
-                                       iPrev = i1;
-                                       tPrev = t1;
-
-                       }
+                       return this.cacheLengths;
 
                }
 
-               if ( tNext === undefined ) {
+               // Get length of sub-curve
+               // Push sums into cached array
 
-                       switch ( this.getSettings_().endingEnd ) {
+               const lengths = [];
+               let sums = 0;
 
-                               case ZeroSlopeEnding:
+               for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
 
-                                       // f'(tN) = 0
-                                       iNext = i1;
-                                       tNext = 2 * t1 - t0;
+                       sums += this.curves[ i ].getLength();
+                       lengths.push( sums );
 
-                                       break;
+               }
 
-                               case WrapAroundEnding:
+               this.cacheLengths = lengths;
 
-                                       // use the other end of the curve
-                                       iNext = 1;
-                                       tNext = t1 + pp[ 1 ] - pp[ 0 ];
+               return lengths;
 
-                                       break;
+       }
 
-                               default: // ZeroCurvatureEnding
+       getSpacedPoints( divisions = 40 ) {
 
-                                       // f''(tN) = 0, a.k.a. Natural Spline
-                                       iNext = i1 - 1;
-                                       tNext = t0;
+               const points = [];
 
-                       }
+               for ( let i = 0; i <= divisions; i ++ ) {
+
+                       points.push( this.getPoint( i / divisions ) );
 
                }
 
-               const halfDt = ( t1 - t0 ) * 0.5,
-                       stride = this.valueSize;
+               if ( this.autoClose ) {
 
-               this._weightPrev = halfDt / ( t0 - tPrev );
-               this._weightNext = halfDt / ( tNext - t1 );
-               this._offsetPrev = iPrev * stride;
-               this._offsetNext = iNext * stride;
+                       points.push( points[ 0 ] );
 
-       },
+               }
 
-       interpolate_: function ( i1, t0, t, t1 ) {
+               return points;
 
-               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,
+       getPoints( divisions = 12 ) {
 
-                       p = ( t - t0 ) / ( t1 - t0 ),
-                       pp = p * p,
-                       ppp = pp * p;
+               const points = [];
+               let last;
 
-               // evaluate polynomials
+               for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) {
 
-               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;
+                       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;
 
-               // combine data linearly
+                       const pts = curve.getPoints( resolution );
 
-               for ( let i = 0; i !== stride; ++ i ) {
+                       for ( let j = 0; j < pts.length; j ++ ) {
 
-                       result[ i ] =
-                                       sP * values[ oP + i ] +
-                                       s0 * values[ o0 + i ] +
-                                       s1 * values[ o1 + i ] +
-                                       sN * values[ oN + i ];
+                               const point = pts[ j ];
 
-               }
+                               if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates
 
-               return result;
+                               points.push( point );
+                               last = point;
 
-       }
+                       }
 
-} );
+               }
 
-function LinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+               if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) {
 
-       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+                       points.push( points[ 0 ] );
 
-}
+               }
 
-LinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+               return points;
 
-       constructor: LinearInterpolant,
+       }
 
-       interpolate_: function ( i1, t0, t, t1 ) {
+       copy( source ) {
 
-               const result = this.resultBuffer,
-                       values = this.sampleValues,
-                       stride = this.valueSize,
+               super.copy( source );
 
-                       offset1 = i1 * stride,
-                       offset0 = offset1 - stride,
+               this.curves = [];
 
-                       weight1 = ( t - t0 ) / ( t1 - t0 ),
-                       weight0 = 1 - weight1;
+               for ( let i = 0, l = source.curves.length; i < l; i ++ ) {
 
-               for ( let i = 0; i !== stride; ++ i ) {
+                       const curve = source.curves[ i ];
 
-                       result[ i ] =
-                                       values[ offset0 + i ] * weight0 +
-                                       values[ offset1 + i ] * weight1;
+                       this.curves.push( curve.clone() );
 
                }
 
-               return result;
+               this.autoClose = source.autoClose;
+
+               return this;
 
        }
 
-} );
+       toJSON() {
 
-/**
- *
- * Interpolant that evaluates to the sample value at the position preceeding
- * the parameter.
- */
+               const data = super.toJSON();
 
-function DiscreteInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+               data.autoClose = this.autoClose;
+               data.curves = [];
 
-       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+               for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
 
-}
+                       const curve = this.curves[ i ];
+                       data.curves.push( curve.toJSON() );
 
-DiscreteInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+               }
 
-       constructor: DiscreteInterpolant,
+               return data;
 
-       interpolate_: function ( i1 /*, t0, t, t1 */ ) {
+       }
 
-               return this.copySampleValue_( i1 - 1 );
+       fromJSON( json ) {
 
-       }
+               super.fromJSON( json );
 
-} );
+               this.autoClose = json.autoClose;
+               this.curves = [];
 
-function KeyframeTrack( name, times, values, interpolation ) {
+               for ( let i = 0, l = json.curves.length; i < l; i ++ ) {
 
-       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 );
+                       const curve = json.curves[ i ];
+                       this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) );
 
-       this.name = name;
+               }
 
-       this.times = AnimationUtils.convertArray( times, this.TimeBufferType );
-       this.values = AnimationUtils.convertArray( values, this.ValueBufferType );
+               return this;
 
-       this.setInterpolation( interpolation || this.DefaultInterpolation );
+       }
 
 }
 
-// Static methods
+class Path extends CurvePath {
 
-Object.assign( KeyframeTrack, {
-
-       // Serialization (in static context, because of constructor invocation
-       // and automatic invocation of .toJSON):
+       constructor( points ) {
 
-       toJSON: function ( track ) {
+               super();
+               this.type = 'Path';
 
-               const trackType = track.constructor;
+               this.currentPoint = new Vector2();
 
-               let json;
+               if ( points ) {
 
-               // derived classes can define a static toJSON method
-               if ( trackType.toJSON !== undefined ) {
+                       this.setFromPoints( points );
 
-                       json = trackType.toJSON( track );
+               }
 
-               } else {
+       }
 
-                       // by default, we assume the data can be serialized as-is
-                       json = {
+       setFromPoints( points ) {
 
-                               'name': track.name,
-                               'times': AnimationUtils.convertArray( track.times, Array ),
-                               'values': AnimationUtils.convertArray( track.values, Array )
+               this.moveTo( points[ 0 ].x, points[ 0 ].y );
 
-                       };
+               for ( let i = 1, l = points.length; i < l; i ++ ) {
 
-                       const interpolation = track.getInterpolation();
+                       this.lineTo( points[ i ].x, points[ i ].y );
 
-                       if ( interpolation !== track.DefaultInterpolation ) {
+               }
 
-                               json.interpolation = interpolation;
+               return this;
 
-                       }
+       }
 
-               }
+       moveTo( x, y ) {
 
-               json.type = track.ValueTypeName; // mandatory
+               this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying?
 
-               return json;
+               return this;
 
        }
 
-} );
+       lineTo( x, y ) {
 
-Object.assign( KeyframeTrack.prototype, {
+               const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) );
+               this.curves.push( curve );
 
-       constructor: KeyframeTrack,
+               this.currentPoint.set( x, y );
 
-       TimeBufferType: Float32Array,
+               return this;
 
-       ValueBufferType: Float32Array,
+       }
 
-       DefaultInterpolation: InterpolateLinear,
+       quadraticCurveTo( aCPx, aCPy, aX, aY ) {
 
-       InterpolantFactoryMethodDiscrete: function ( result ) {
+               const curve = new QuadraticBezierCurve(
+                       this.currentPoint.clone(),
+                       new Vector2( aCPx, aCPy ),
+                       new Vector2( aX, aY )
+               );
 
-               return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );
+               this.curves.push( curve );
 
-       },
+               this.currentPoint.set( aX, aY );
 
-       InterpolantFactoryMethodLinear: function ( result ) {
+               return this;
 
-               return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );
+       }
 
-       },
+       bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
 
-       InterpolantFactoryMethodSmooth: function ( result ) {
+               const curve = new CubicBezierCurve(
+                       this.currentPoint.clone(),
+                       new Vector2( aCP1x, aCP1y ),
+                       new Vector2( aCP2x, aCP2y ),
+                       new Vector2( aX, aY )
+               );
 
-               return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );
+               this.curves.push( curve );
 
-       },
+               this.currentPoint.set( aX, aY );
 
-       setInterpolation: function ( interpolation ) {
+               return this;
 
-               let factoryMethod;
+       }
 
-               switch ( interpolation ) {
+       splineThru( pts /*Array of Vector*/ ) {
 
-                       case InterpolateDiscrete:
+               const npts = [ this.currentPoint.clone() ].concat( pts );
 
-                               factoryMethod = this.InterpolantFactoryMethodDiscrete;
+               const curve = new SplineCurve( npts );
+               this.curves.push( curve );
 
-                               break;
+               this.currentPoint.copy( pts[ pts.length - 1 ] );
 
-                       case InterpolateLinear:
+               return this;
 
-                               factoryMethod = this.InterpolantFactoryMethodLinear;
+       }
 
-                               break;
+       arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
 
-                       case InterpolateSmooth:
+               const x0 = this.currentPoint.x;
+               const y0 = this.currentPoint.y;
 
-                               factoryMethod = this.InterpolantFactoryMethodSmooth;
+               this.absarc( aX + x0, aY + y0, aRadius,
+                       aStartAngle, aEndAngle, aClockwise );
 
-                               break;
+               return this;
 
-               }
+       }
 
-               if ( factoryMethod === undefined ) {
+       absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
 
-                       const message = 'unsupported interpolation for ' +
-                               this.ValueTypeName + ' keyframe track named ' + this.name;
+               this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
 
-                       if ( this.createInterpolant === undefined ) {
+               return this;
 
-                               // fall back to default, unless the default itself is messed up
-                               if ( interpolation !== this.DefaultInterpolation ) {
+       }
 
-                                       this.setInterpolation( this.DefaultInterpolation );
+       ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
 
-                               } else {
+               const x0 = this.currentPoint.x;
+               const y0 = this.currentPoint.y;
 
-                                       throw new Error( message ); // fatal, in this case
+               this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
 
-                               }
+               return this;
 
-                       }
+       }
 
-                       console.warn( 'THREE.KeyframeTrack:', message );
-                       return this;
+       absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
 
-               }
+               const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
 
-               this.createInterpolant = factoryMethod;
+               if ( this.curves.length > 0 ) {
 
-               return this;
+                       // if a previous curve is present, attempt to join
+                       const firstPoint = curve.getPoint( 0 );
 
-       },
+                       if ( ! firstPoint.equals( this.currentPoint ) ) {
 
-       getInterpolation: function () {
+                               this.lineTo( firstPoint.x, firstPoint.y );
 
-               switch ( this.createInterpolant ) {
+                       }
 
-                       case this.InterpolantFactoryMethodDiscrete:
+               }
 
-                               return InterpolateDiscrete;
+               this.curves.push( curve );
 
-                       case this.InterpolantFactoryMethodLinear:
+               const lastPoint = curve.getPoint( 1 );
+               this.currentPoint.copy( lastPoint );
 
-                               return InterpolateLinear;
+               return this;
 
-                       case this.InterpolantFactoryMethodSmooth:
+       }
 
-                               return InterpolateSmooth;
+       copy( source ) {
 
-               }
+               super.copy( source );
 
-       },
+               this.currentPoint.copy( source.currentPoint );
 
-       getValueSize: function () {
+               return this;
 
-               return this.values.length / this.times.length;
+       }
 
-       },
+       toJSON() {
 
-       // move all keyframes either forwards or backwards in time
-       shift: function ( timeOffset ) {
+               const data = super.toJSON();
 
-               if ( timeOffset !== 0.0 ) {
+               data.currentPoint = this.currentPoint.toArray();
 
-                       const times = this.times;
+               return data;
 
-                       for ( let i = 0, n = times.length; i !== n; ++ i ) {
+       }
 
-                               times[ i ] += timeOffset;
+       fromJSON( json ) {
 
-                       }
+               super.fromJSON( json );
 
-               }
+               this.currentPoint.fromArray( json.currentPoint );
 
                return this;
 
-       },
+       }
 
-       // scale all keyframe times by a factor (useful for frame <-> seconds conversions)
-       scale: function ( timeScale ) {
+}
 
-               if ( timeScale !== 1.0 ) {
+class Shape extends Path {
 
-                       const times = this.times;
+       constructor( points ) {
 
-                       for ( let i = 0, n = times.length; i !== n; ++ i ) {
+               super( points );
 
-                               times[ i ] *= timeScale;
+               this.uuid = generateUUID();
 
-                       }
+               this.type = 'Shape';
 
-               }
+               this.holes = [];
 
-               return this;
+       }
 
-       },
+       getPointsHoles( divisions ) {
 
-       // 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 holesPts = [];
 
-               const times = this.times,
-                       nKeys = times.length;
+               for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
 
-               let from = 0,
-                       to = nKeys - 1;
+                       holesPts[ i ] = this.holes[ i ].getPoints( divisions );
 
-               while ( from !== nKeys && times[ from ] < startTime ) {
+               }
 
-                       ++ from;
+               return holesPts;
 
-               }
+       }
 
-               while ( to !== - 1 && times[ to ] > endTime ) {
+       // get points of shape and holes (keypoints based on segments parameter)
 
-                       -- to;
+       extractPoints( divisions ) {
 
-               }
+               return {
 
-               ++ to; // inclusive -> exclusive bound
+                       shape: this.getPoints( divisions ),
+                       holes: this.getPointsHoles( divisions )
 
-               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;
+       copy( source ) {
 
-                       }
+               super.copy( source );
 
-                       const stride = this.getValueSize();
-                       this.times = AnimationUtils.arraySlice( times, from, to );
-                       this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride );
+               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;
 
-       },
-
-       // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
-       validate: function () {
+       }
 
-               let valid = true;
+       toJSON() {
 
-               const valueSize = this.getValueSize();
-               if ( valueSize - Math.floor( valueSize ) !== 0 ) {
+               const data = super.toJSON();
 
-                       console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
-                       valid = false;
+               data.uuid = this.uuid;
+               data.holes = [];
 
-               }
+               for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
 
-               const times = this.times,
-                       values = this.values,
+                       const hole = this.holes[ i ];
+                       data.holes.push( hole.toJSON() );
 
-                       nKeys = times.length;
+               }
 
-               if ( nKeys === 0 ) {
+               return data;
 
-                       console.error( 'THREE.KeyframeTrack: Track is empty.', this );
-                       valid = false;
+       }
 
-               }
+       fromJSON( json ) {
 
-               let prevTime = null;
+               super.fromJSON( json );
 
-               for ( let i = 0; i !== nKeys; i ++ ) {
+               this.uuid = json.uuid;
+               this.holes = [];
 
-                       const currTime = times[ i ];
+               for ( let i = 0, l = json.holes.length; i < l; i ++ ) {
 
-                       if ( typeof currTime === 'number' && isNaN( currTime ) ) {
+                       const hole = json.holes[ i ];
+                       this.holes.push( new Path().fromJSON( hole ) );
 
-                               console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime );
-                               valid = false;
-                               break;
+               }
 
-                       }
+               return this;
 
-                       if ( prevTime !== null && prevTime > currTime ) {
+       }
 
-                               console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime );
-                               valid = false;
-                               break;
+}
 
-                       }
+/**
+ * Port from https://github.com/mapbox/earcut (v2.2.2)
+ */
 
-                       prevTime = currTime;
+const Earcut = {
 
-               }
+       triangulate: function ( data, holeIndices, dim = 2 ) {
 
-               if ( values !== undefined ) {
+               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 ( AnimationUtils.isTypedArray( values ) ) {
+               if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles;
 
-                               for ( let i = 0, n = values.length; i !== n; ++ i ) {
+               let minX, minY, maxX, maxY, x, y, invSize;
 
-                                       const value = values[ i ];
+               if ( hasHoles ) outerNode = eliminateHoles$1( data, holeIndices, outerNode, dim );
 
-                                       if ( isNaN( value ) ) {
+               // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
+               if ( data.length > 80 * dim ) {
 
-                                               console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
-                                               valid = false;
-                                               break;
+                       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;
+
                }
 
-               return valid;
+               earcutLinked$1( outerNode, triangles, dim, minX, minY, invSize );
 
-       },
+               return triangles;
 
-       // 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,
+// create a circular doubly linked list from polygon points in the specified winding order
+function linkedList$1( data, start, end, dim, clockwise ) {
 
-                       lastIndex = times.length - 1;
+       let i, last;
 
-               let writeIndex = 1;
+       if ( clockwise === ( signedArea$2( data, start, end, dim ) > 0 ) ) {
 
-               for ( let i = 1; i < lastIndex; ++ i ) {
+               for ( i = start; i < end; i += dim ) last = insertNode$2( i, data[ i ], data[ i + 1 ], last );
 
-                       let keep = false;
+       } else {
 
-                       const time = times[ i ];
-                       const timeNext = times[ i + 1 ];
+               for ( i = end - dim; i >= start; i -= dim ) last = insertNode$2( i, data[ i ], data[ i + 1 ], last );
 
-                       // remove adjacent keyframes scheduled at the same time
+       }
 
-                       if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) {
+       if ( last && equals$2( last, last.next ) ) {
 
-                               if ( ! smoothInterpolation ) {
+               removeNode$2( last );
+               last = last.next;
 
-                                       // remove unnecessary keyframes same as their neighbors
+       }
 
-                                       const offset = i * stride,
-                                               offsetP = offset - stride,
-                                               offsetN = offset + stride;
+       return last;
 
-                                       for ( let j = 0; j !== stride; ++ j ) {
+}
 
-                                               const value = values[ offset + j ];
+// eliminate colinear or duplicate points
+function filterPoints$1( start, end ) {
 
-                                               if ( value !== values[ offsetP + j ] ||
-                                                       value !== values[ offsetN + j ] ) {
+       if ( ! start ) return start;
+       if ( ! end ) end = start;
 
-                                                       keep = true;
-                                                       break;
+       let p = start,
+               again;
+       do {
 
-                                               }
+               again = false;
 
-                                       }
+               if ( ! p.steiner && ( equals$2( p, p.next ) || area$1( p.prev, p, p.next ) === 0 ) ) {
 
-                               } else {
+                       removeNode$2( p );
+                       p = end = p.prev;
+                       if ( p === p.next ) break;
+                       again = true;
 
-                                       keep = true;
+               } else {
 
-                               }
+                       p = p.next;
 
-                       }
+               }
 
-                       // in-place compaction
+       } while ( again || p !== end );
 
-                       if ( keep ) {
+       return end;
 
-                               if ( i !== writeIndex ) {
+}
 
-                                       times[ writeIndex ] = times[ i ];
+// main ear slicing loop which triangulates a polygon (given as a linked list)
+function earcutLinked$1( ear, triangles, dim, minX, minY, invSize, pass ) {
 
-                                       const readOffset = i * stride,
-                                               writeOffset = writeIndex * stride;
+       if ( ! ear ) return;
 
-                                       for ( let j = 0; j !== stride; ++ j ) {
+       // interlink polygon nodes in z-order
+       if ( ! pass && invSize ) indexCurve$1( ear, minX, minY, invSize );
 
-                                               values[ writeOffset + j ] = values[ readOffset + j ];
+       let stop = ear,
+               prev, next;
 
-                                       }
+       // iterate through ears, slicing them one by one
+       while ( ear.prev !== ear.next ) {
 
-                               }
+               prev = ear.prev;
+               next = ear.next;
 
-                               ++ writeIndex;
+               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;
 
                }
 
-               // flush last keyframe (compaction looks ahead)
+               ear = next;
 
-               if ( lastIndex > 0 ) {
+               // if we looped through the whole remaining polygon and can't find any more ears
+               if ( ear === stop ) {
 
-                       times[ writeIndex ] = times[ lastIndex ];
+                       // try filtering points and slicing again
+                       if ( ! pass ) {
 
-                       for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
+                               earcutLinked$1( filterPoints$1( ear ), triangles, dim, minX, minY, invSize, 1 );
 
-                               values[ writeOffset + j ] = values[ readOffset + j ];
+                               // if this didn't work, try curing all small self-intersections locally
 
-                       }
+                       } else if ( pass === 1 ) {
 
-                       ++ writeIndex;
+                               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
 
-               if ( writeIndex !== times.length ) {
+                       } else if ( pass === 2 ) {
 
-                       this.times = AnimationUtils.arraySlice( times, 0, writeIndex );
-                       this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride );
+                               splitEarcut$1( ear, triangles, dim, minX, minY, invSize );
 
-               } else {
+                       }
 
-                       this.times = times;
-                       this.values = values;
+                       break;
 
                }
 
-               return this;
+       }
 
-       },
+}
 
-       clone: function () {
+// check whether a polygon node forms a valid ear with adjacent nodes
+function isEar$1( ear ) {
 
-               const times = AnimationUtils.arraySlice( this.times, 0 );
-               const values = AnimationUtils.arraySlice( this.values, 0 );
+       const a = ear.prev,
+               b = ear,
+               c = ear.next;
 
-               const TypedKeyframeTrack = this.constructor;
-               const track = new TypedKeyframeTrack( this.name, times, values );
+       if ( area$1( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
 
-               // Interpolant argument to constructor is not saved, so copy the factory method directly.
-               track.createInterpolant = this.createInterpolant;
+       // now make sure we don't have other points inside the potential ear
+       let p = ear.next.next;
 
-               return track;
+       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;
 
-/**
- * A Track of Boolean keyframe values.
- */
+}
 
-function BooleanKeyframeTrack( name, times, values ) {
+function isEarHashed$1( ear, minX, minY, invSize ) {
 
-       KeyframeTrack.call( this, name, times, values );
+       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 );
 
-BooleanKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+       // 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 );
 
-       constructor: BooleanKeyframeTrack,
+       let p = ear.prevZ,
+               n = ear.nextZ;
 
-       ValueTypeName: 'bool',
-       ValueBufferType: Array,
+       // look for points inside the triangle in both directions
+       while ( p && p.z >= minZ && n && n.z <= maxZ ) {
 
-       DefaultInterpolation: InterpolateDiscrete,
+               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;
 
-       InterpolantFactoryMethodLinear: undefined,
-       InterpolantFactoryMethodSmooth: undefined
+               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;
 
-       // Note: Actually this track could have a optimized / compressed
-       // representation of a single value and a custom interpolant that
-       // computes "firstValue ^ isOdd( index )".
+       }
 
-} );
+       // look for remaining points in decreasing z-order
+       while ( p && p.z >= minZ ) {
 
-/**
- * A Track of keyframe values that represent color.
- */
+               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;
 
-function ColorKeyframeTrack( name, times, values, interpolation ) {
+       }
 
-       KeyframeTrack.call( this, name, times, values, interpolation );
+       // 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;
 
-ColorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+       }
 
-       constructor: ColorKeyframeTrack,
+       return true;
 
-       ValueTypeName: 'color'
+}
 
-       // ValueBufferType is inherited
+// go through all polygon nodes and cure small local self-intersections
+function cureLocalIntersections$1( start, triangles, dim ) {
 
-       // DefaultInterpolation is inherited
+       let p = start;
+       do {
 
-       // Note: Very basic implementation and nothing special yet.
-       // However, this is the place for color space parameterization.
+               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 ) ) {
 
-/**
- * A Track of numeric keyframe values.
- */
+                       triangles.push( a.i / dim );
+                       triangles.push( p.i / dim );
+                       triangles.push( b.i / dim );
 
-function NumberKeyframeTrack( name, times, values, interpolation ) {
+                       // remove two nodes involved
+                       removeNode$2( p );
+                       removeNode$2( p.next );
 
-       KeyframeTrack.call( this, name, times, values, interpolation );
+                       p = start = b;
 
-}
+               }
 
-NumberKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+               p = p.next;
 
-       constructor: NumberKeyframeTrack,
+       } while ( p !== start );
 
-       ValueTypeName: 'number'
+       return filterPoints$1( p );
 
-       // ValueBufferType is inherited
+}
 
-       // DefaultInterpolation is inherited
+// 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 {
 
-/**
- * Spherical linear unit quaternion interpolant.
- */
+               let b = a.next.next;
+               while ( b !== a.prev ) {
 
-function QuaternionLinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+                       if ( a.i !== b.i && isValidDiagonal$1( a, b ) ) {
 
-       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+                               // 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 );
 
-QuaternionLinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+                               // run earcut on each half
+                               earcutLinked$1( a, triangles, dim, minX, minY, invSize );
+                               earcutLinked$1( c, triangles, dim, minX, minY, invSize );
+                               return;
 
-       constructor: QuaternionLinearInterpolant,
+                       }
 
-       interpolate_: function ( i1, t0, t, t1 ) {
+                       b = b.next;
 
-               const result = this.resultBuffer,
-                       values = this.sampleValues,
-                       stride = this.valueSize,
+               }
 
-                       alpha = ( t - t0 ) / ( t1 - t0 );
+               a = a.next;
 
-               let offset = i1 * stride;
+       } while ( a !== start );
 
-               for ( let end = offset + stride; offset !== end; offset += 4 ) {
+}
 
-                       Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha );
+// 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;
 
-               return result;
+       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 );
 
-/**
- * A Track of quaternion keyframe values.
- */
+       // process holes from left to right
+       for ( i = 0; i < queue.length; i ++ ) {
+
+               eliminateHole$1( queue[ i ], outerNode );
+               outerNode = filterPoints$1( outerNode, outerNode.next );
 
-function QuaternionKeyframeTrack( name, times, values, interpolation ) {
+       }
 
-       KeyframeTrack.call( this, name, times, values, interpolation );
+       return outerNode;
 
 }
 
-QuaternionKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+function compareX$1( a, b ) {
 
-       constructor: QuaternionKeyframeTrack,
+       return a.x - b.x;
 
-       ValueTypeName: 'quaternion',
+}
 
-       // ValueBufferType is inherited
+// find a bridge between vertices that connects hole with an outer ring and and link it
+function eliminateHole$1( hole, outerNode ) {
 
-       DefaultInterpolation: InterpolateLinear,
+       outerNode = findHoleBridge$1( hole, outerNode );
+       if ( outerNode ) {
 
-       InterpolantFactoryMethodLinear: function ( result ) {
+               const b = splitPolygon$1( outerNode, hole );
 
-               return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result );
+               // filter collinear points around the cuts
+               filterPoints$1( outerNode, outerNode.next );
+               filterPoints$1( b, b.next );
 
-       },
+       }
 
-       InterpolantFactoryMethodSmooth: undefined // not yet implemented
+}
 
-} );
+// David Eberly's algorithm for finding a bridge between hole and outer polygon
+function findHoleBridge$1( hole, outerNode ) {
 
-/**
- * A Track that interpolates Strings
- */
+       let p = outerNode;
+       const hx = hole.x;
+       const hy = hole.y;
+       let qx = - Infinity, m;
 
-function StringKeyframeTrack( name, times, values, interpolation ) {
+       // 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 {
 
-       KeyframeTrack.call( this, name, times, values, interpolation );
+               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 ) {
 
-StringKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+                               qx = x;
+                               if ( x === hx ) {
 
-       constructor: StringKeyframeTrack,
+                                       if ( hy === p.y ) return p;
+                                       if ( hy === p.next.y ) return p.next;
 
-       ValueTypeName: 'string',
-       ValueBufferType: Array,
+                               }
 
-       DefaultInterpolation: InterpolateDiscrete,
+                               m = p.x < p.next.x ? p : p.next;
 
-       InterpolantFactoryMethodLinear: undefined,
+                       }
 
-       InterpolantFactoryMethodSmooth: undefined
+               }
 
-} );
+               p = p.next;
 
-/**
- * A Track of vectored keyframe values.
- */
+       } while ( p !== outerNode );
 
-function VectorKeyframeTrack( name, times, values, interpolation ) {
+       if ( ! m ) return null;
 
-       KeyframeTrack.call( this, name, times, values, interpolation );
+       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
 
-VectorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+       const stop = m,
+               mx = m.x,
+               my = m.y;
+       let tanMin = Infinity, tan;
 
-       constructor: VectorKeyframeTrack,
+       p = m;
 
-       ValueTypeName: 'vector'
+       do {
 
-       // ValueBufferType is inherited
+               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 ) ) {
 
-       // DefaultInterpolation is inherited
+                       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 ) ) ) ) ) ) {
 
-function AnimationClip( name, duration = - 1, tracks, blendMode = NormalAnimationBlendMode ) {
+                               m = p;
+                               tanMin = tan;
 
-       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 ) {
+               p = p.next;
 
-               this.resetDuration();
+       } while ( p !== stop );
 
-       }
+       return m;
 
 }
 
-function getTrackTypeForValueTypeName( typeName ) {
+// whether sector in vertex m contains sector in vertex p in the same coordinates
+function sectorContainsSector$1( m, p ) {
 
-       switch ( typeName.toLowerCase() ) {
+       return area$1( m.prev, m, p.prev ) < 0 && area$1( p.next, m, m.next ) < 0;
 
-               case 'scalar':
-               case 'double':
-               case 'float':
-               case 'number':
-               case 'integer':
+}
 
-                       return NumberKeyframeTrack;
+// interlink polygon nodes in z-order
+function indexCurve$1( start, minX, minY, invSize ) {
 
-               case 'vector':
-               case 'vector2':
-               case 'vector3':
-               case 'vector4':
+       let p = start;
+       do {
 
-                       return VectorKeyframeTrack;
+               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;
 
-               case 'color':
+       } while ( p !== start );
 
-                       return ColorKeyframeTrack;
+       p.prevZ.nextZ = null;
+       p.prevZ = null;
 
-               case 'quaternion':
+       sortLinked$1( p );
 
-                       return QuaternionKeyframeTrack;
+}
 
-               case 'bool':
-               case 'boolean':
+// Simon Tatham's linked list merge sort algorithm
+// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
+function sortLinked$1( list ) {
 
-                       return BooleanKeyframeTrack;
+       let i, p, q, e, tail, numMerges, pSize, qSize,
+               inSize = 1;
 
-               case 'string':
+       do {
 
-                       return StringKeyframeTrack;
+               p = list;
+               list = null;
+               tail = null;
+               numMerges = 0;
 
-       }
+               while ( p ) {
 
-       throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName );
+                       numMerges ++;
+                       q = p;
+                       pSize = 0;
+                       for ( i = 0; i < inSize; i ++ ) {
 
-}
+                               pSize ++;
+                               q = q.nextZ;
+                               if ( ! q ) break;
 
-function parseKeyframeTrack( json ) {
+                       }
 
-       if ( json.type === undefined ) {
+                       qSize = inSize;
 
-               throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' );
+                       while ( pSize > 0 || ( qSize > 0 && q ) ) {
 
-       }
+                               if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) {
 
-       const trackType = getTrackTypeForValueTypeName( json.type );
+                                       e = p;
+                                       p = p.nextZ;
+                                       pSize --;
 
-       if ( json.times === undefined ) {
+                               } else {
 
-               const times = [], values = [];
+                                       e = q;
+                                       q = q.nextZ;
+                                       qSize --;
 
-               AnimationUtils.flattenJSON( json.keys, times, values, 'value' );
+                               }
 
-               json.times = times;
-               json.values = values;
+                               if ( tail ) tail.nextZ = e;
+                               else list = e;
 
-       }
+                               e.prevZ = tail;
+                               tail = e;
 
-       // derived classes can define a static parse method
-       if ( trackType.parse !== undefined ) {
+                       }
 
-               return trackType.parse( json );
+                       p = q;
 
-       } else {
+               }
 
-               // by default, we assume a constructor compatible with the base
-               return new trackType( json.name, json.times, json.values, json.interpolation );
+               tail.nextZ = null;
+               inSize *= 2;
 
-       }
+       } while ( numMerges > 1 );
+
+       return list;
 
 }
 
-Object.assign( AnimationClip, {
+// z-order of a point given coords and inverse of the longer side of data bbox
+function zOrder$1( x, y, minX, minY, invSize ) {
 
-       parse: function ( json ) {
+       // coords are transformed into non-negative 15-bit integer range
+       x = 32767 * ( x - minX ) * invSize;
+       y = 32767 * ( y - minY ) * invSize;
 
-               const tracks = [],
-                       jsonTracks = json.tracks,
-                       frameTime = 1.0 / ( json.fps || 1.0 );
+       x = ( x | ( x << 8 ) ) & 0x00FF00FF;
+       x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
+       x = ( x | ( x << 2 ) ) & 0x33333333;
+       x = ( x | ( x << 1 ) ) & 0x55555555;
 
-               for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) {
+       y = ( y | ( y << 8 ) ) & 0x00FF00FF;
+       y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
+       y = ( y | ( y << 2 ) ) & 0x33333333;
+       y = ( y | ( y << 1 ) ) & 0x55555555;
 
-                       tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) );
+       return x | ( y << 1 );
 
-               }
+}
 
-               const clip = new AnimationClip( json.name, json.duration, tracks, json.blendMode );
-               clip.uuid = json.uuid;
+// find the leftmost node of a polygon ring
+function getLeftmost$1( start ) {
 
-               return clip;
+       let p = start,
+               leftmost = start;
+       do {
 
-       },
+               if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p;
+               p = p.next;
 
-       toJSON: function ( clip ) {
+       } while ( p !== start );
 
-               const tracks = [],
-                       clipTracks = clip.tracks;
+       return leftmost;
 
-               const json = {
+}
 
-                       'name': clip.name,
-                       'duration': clip.duration,
-                       'tracks': tracks,
-                       'uuid': clip.uuid,
-                       'blendMode': clip.blendMode
+// 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;
 
-               for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) {
+}
 
-                       tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );
+// 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
 
-               return json;
+}
 
-       },
+// signed area of a triangle
+function area$1( p, q, r ) {
 
-       CreateFromMorphTargetSequence: function ( name, morphTargetSequence, fps, noLoop ) {
+       return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );
 
-               const numMorphTargets = morphTargetSequence.length;
-               const tracks = [];
+}
 
-               for ( let i = 0; i < numMorphTargets; i ++ ) {
+// check if two points are equal
+function equals$2( p1, p2 ) {
 
-                       let times = [];
-                       let values = [];
+       return p1.x === p2.x && p1.y === p2.y;
 
-                       times.push(
-                               ( i + numMorphTargets - 1 ) % numMorphTargets,
-                               i,
-                               ( i + 1 ) % numMorphTargets );
+}
 
-                       values.push( 0, 1, 0 );
+// check if two segments intersect
+function intersects$2( p1, q1, p2, q2 ) {
 
-                       const order = AnimationUtils.getKeyframeOrder( times );
-                       times = AnimationUtils.sortedArray( times, 1, order );
-                       values = AnimationUtils.sortedArray( values, 1, order );
+       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 there is a key at the first frame, duplicate it as the
-                       // last frame as well for perfect loop.
-                       if ( ! noLoop && times[ 0 ] === 0 ) {
+       if ( o1 !== o2 && o3 !== o4 ) return true; // general case
 
-                               times.push( numMorphTargets );
-                               values.push( values[ 0 ] );
+       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;
 
-                       tracks.push(
-                               new NumberKeyframeTrack(
-                                       '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',
-                                       times, values
-                               ).scale( 1.0 / fps ) );
+}
 
-               }
+// for collinear points p, q, r, check if point q lies on segment pr
+function onSegment$1( p, q, r ) {
 
-               return new AnimationClip( name, - 1, tracks );
+       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 );
 
-       },
+}
 
-       findByName: function ( objectOrClipArray, name ) {
+function sign$2( num ) {
 
-               let clipArray = objectOrClipArray;
+       return num > 0 ? 1 : num < 0 ? - 1 : 0;
 
-               if ( ! Array.isArray( objectOrClipArray ) ) {
-
-                       const o = objectOrClipArray;
-                       clipArray = o.geometry && o.geometry.animations || o.animations;
-
-               }
+}
 
-               for ( let i = 0; i < clipArray.length; i ++ ) {
+// check if a polygon diagonal intersects any polygon segments
+function intersectsPolygon$1( a, b ) {
 
-                       if ( clipArray[ i ].name === name ) {
+       let p = a;
+       do {
 
-                               return clipArray[ i ];
+               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;
 
-               return null;
+}
 
-       },
+// check if a polygon diagonal is locally inside the polygon
+function locallyInside$1( a, b ) {
 
-       CreateClipsFromMorphTargetSequences: function ( morphTargets, fps, noLoop ) {
+       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;
 
-               const animationToMorphTargets = {};
+}
 
-               // tested with https://regex101.com/ on trick sequences
-               // such flamingo_flyA_003, flamingo_run1_003, crdeath0059
-               const pattern = /^([\w-]*?)([\d]+)$/;
+// check if the middle point of a polygon diagonal is inside the polygon
+function middleInside$1( a, b ) {
 
-               // 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 ++ ) {
+       let p = a,
+               inside = false;
+       const px = ( a.x + b.x ) / 2,
+               py = ( a.y + b.y ) / 2;
+       do {
 
-                       const morphTarget = morphTargets[ i ];
-                       const parts = morphTarget.name.match( pattern );
+               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;
 
-                       if ( parts && parts.length > 1 ) {
+       } while ( p !== a );
 
-                               const name = parts[ 1 ];
+       return inside;
 
-                               let animationMorphTargets = animationToMorphTargets[ name ];
+}
 
-                               if ( ! animationMorphTargets ) {
+// 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 ) {
 
-                                       animationToMorphTargets[ name ] = animationMorphTargets = [];
+       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;
 
-                               animationMorphTargets.push( morphTarget );
+       a2.next = an;
+       an.prev = a2;
 
-                       }
+       b2.next = a2;
+       a2.prev = b2;
 
-               }
+       bp.next = b2;
+       b2.prev = bp;
 
-               const clips = [];
+       return b2;
 
-               for ( const name in animationToMorphTargets ) {
+}
 
-                       clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );
+// 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 );
 
-               return clips;
+       if ( ! last ) {
 
-       },
+               p.prev = p;
+               p.next = p;
 
-       // parse the animation.hierarchy format
-       parseAnimation: function ( animation, bones ) {
+       } else {
 
-               if ( ! animation ) {
+               p.next = last.next;
+               p.prev = last;
+               last.next.prev = p;
+               last.next = p;
 
-                       console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' );
-                       return null;
+       }
 
-               }
+       return p;
 
-               const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) {
+}
 
-                       // only return track if there are actually keys.
-                       if ( animationKeys.length !== 0 ) {
+function removeNode$2( p ) {
 
-                               const times = [];
-                               const values = [];
+       p.next.prev = p.prev;
+       p.prev.next = p.next;
 
-                               AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );
+       if ( p.prevZ ) p.prevZ.nextZ = p.nextZ;
+       if ( p.nextZ ) p.nextZ.prevZ = p.prevZ;
 
-                               // empty keys are filtered out, so check again
-                               if ( times.length !== 0 ) {
+}
 
-                                       destTracks.push( new trackType( trackName, times, values ) );
+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;
 
-               const tracks = [];
+       // z-order curve value
+       this.z = null;
 
-               const clipName = animation.name || 'default';
-               const fps = animation.fps || 30;
-               const blendMode = animation.blendMode;
+       // previous and next nodes in z-order
+       this.prevZ = null;
+       this.nextZ = null;
 
-               // automatic length determination in AnimationClip.
-               let duration = animation.length || - 1;
+       // indicates whether this is a steiner point
+       this.steiner = false;
 
-               const hierarchyTracks = animation.hierarchy || [];
+}
 
-               for ( let h = 0; h < hierarchyTracks.length; h ++ ) {
+function signedArea$2( data, start, end, dim ) {
 
-                       const animationKeys = hierarchyTracks[ h ].keys;
+       let sum = 0;
+       for ( let i = start, j = end - dim; i < end; i += dim ) {
 
-                       // skip empty tracks
-                       if ( ! animationKeys || animationKeys.length === 0 ) continue;
+               sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );
+               j = i;
 
-                       // process morph targets
-                       if ( animationKeys[ 0 ].morphTargets ) {
+       }
 
-                               // figure out all morph targets used in this track
-                               const morphTargetNames = {};
+       return sum;
 
-                               let k;
+}
 
-                               for ( k = 0; k < animationKeys.length; k ++ ) {
+class ShapeUtils {
 
-                                       if ( animationKeys[ k ].morphTargets ) {
+       // calculate area of the contour polygon
 
-                                               for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) {
+       static area( contour ) {
 
-                                                       morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1;
+               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;
 
-                               }
+               }
 
-                               // 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 ) {
+               return a * 0.5;
 
-                                       const times = [];
-                                       const values = [];
+       }
 
-                                       for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) {
+       static isClockWise( pts ) {
 
-                                               const animationKey = animationKeys[ k ];
+               return ShapeUtils.area( pts ) < 0;
 
-                                               times.push( animationKey.time );
-                                               values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );
+       }
 
-                                       }
+       static triangulateShape( contour, holes ) {
 
-                                       tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) );
+               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 );
 
-                               duration = morphTargetNames.length * ( fps || 1.0 );
+               //
 
-                       } else {
+               let holeIndex = contour.length;
 
-                               // ...assume skeletal animation
+               holes.forEach( removeDupEndPts );
 
-                               const boneName = '.bones[' + bones[ h ].name + ']';
+               for ( let i = 0; i < holes.length; i ++ ) {
 
-                               addNonemptyTrack(
-                                       VectorKeyframeTrack, boneName + '.position',
-                                       animationKeys, 'pos', tracks );
+                       holeIndices.push( holeIndex );
+                       holeIndex += holes[ i ].length;
+                       addContour( vertices, holes[ i ] );
 
-                               addNonemptyTrack(
-                                       QuaternionKeyframeTrack, boneName + '.quaternion',
-                                       animationKeys, 'rot', tracks );
+               }
 
-                               addNonemptyTrack(
-                                       VectorKeyframeTrack, boneName + '.scale',
-                                       animationKeys, 'scl', tracks );
+               //
 
-                       }
+               const triangles = Earcut.triangulate( vertices, holeIndices );
 
-               }
+               //
 
-               if ( tracks.length === 0 ) {
+               for ( let i = 0; i < triangles.length; i += 3 ) {
 
-                       return null;
+                       faces.push( triangles.slice( i, i + 3 ) );
 
                }
 
-               const clip = new AnimationClip( clipName, duration, tracks, blendMode );
-
-               return clip;
+               return faces;
 
        }
 
-} );
-
-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 ];
+function removeDupEndPts( points ) {
 
-                       duration = Math.max( duration, track.times[ track.times.length - 1 ] );
+       const l = points.length;
 
-               }
+       if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {
 
-               this.duration = duration;
+               points.pop();
 
-               return this;
+       }
 
-       },
+}
 
-       trim: function () {
+function addContour( vertices, contour ) {
 
-               for ( let i = 0; i < this.tracks.length; i ++ ) {
+       for ( let i = 0; i < contour.length; i ++ ) {
 
-                       this.tracks[ i ].trim( 0, this.duration );
+               vertices.push( contour[ i ].x );
+               vertices.push( contour[ i ].y );
 
-               }
+       }
 
-               return this;
+}
 
-       },
+/**
+ * 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
+ *
+ * }
+ */
 
-       validate: function () {
+class ExtrudeGeometry extends BufferGeometry {
 
-               let valid = true;
+       constructor( shapes = new Shape( [ new Vector2( 0.5, 0.5 ), new Vector2( - 0.5, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), options = {} ) {
 
-               for ( let i = 0; i < this.tracks.length; i ++ ) {
+               super();
 
-                       valid = valid && this.tracks[ i ].validate();
+               this.type = 'ExtrudeGeometry';
 
-               }
+               this.parameters = {
+                       shapes: shapes,
+                       options: options
+               };
 
-               return valid;
+               shapes = Array.isArray( shapes ) ? shapes : [ shapes ];
 
-       },
+               const scope = this;
 
-       optimize: function () {
+               const verticesArray = [];
+               const uvArray = [];
 
-               for ( let i = 0; i < this.tracks.length; i ++ ) {
+               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
 
-                       this.tracks[ i ].optimize();
+                       const shape = shapes[ i ];
+                       addShape( shape );
 
                }
 
-               return this;
+               // build geometry
 
-       },
+               this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) );
 
-       clone: function () {
+               this.computeVertexNormals();
 
-               const tracks = [];
+               // functions
 
-               for ( let i = 0; i < this.tracks.length; i ++ ) {
+               function addShape( shape ) {
 
-                       tracks.push( this.tracks[ i ].clone() );
+                       const placeholder = [];
 
-               }
+                       // options
 
-               return new AnimationClip( this.name, this.duration, tracks, this.blendMode );
+                       const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;
+                       const steps = options.steps !== undefined ? options.steps : 1;
+                       let depth = options.depth !== undefined ? options.depth : 1;
 
-       },
+                       let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true;
+                       let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 0.2;
+                       let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 0.1;
+                       let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0;
+                       let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3;
 
-       toJSON: function () {
+                       const extrudePath = options.extrudePath;
 
-               return AnimationClip.toJSON( this );
+                       const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator;
 
-       }
+                       // deprecated options
 
-} );
+                       if ( options.amount !== undefined ) {
 
-const Cache = {
+                               console.warn( 'THREE.ExtrudeBufferGeometry: amount has been renamed to depth.' );
+                               depth = options.amount;
 
-       enabled: false,
+                       }
 
-       files: {},
+                       //
 
-       add: function ( key, file ) {
+                       let extrudePts, extrudeByPath = false;
+                       let splineTube, binormal, normal, position2;
 
-               if ( this.enabled === false ) return;
+                       if ( extrudePath ) {
 
-               // console.log( 'THREE.Cache', 'Adding key:', key );
+                               extrudePts = extrudePath.getSpacedPoints( steps );
 
-               this.files[ key ] = file;
+                               extrudeByPath = true;
+                               bevelEnabled = false; // bevels not supported for path extrusion
 
-       },
+                               // SETUP TNB variables
 
-       get: function ( key ) {
+                               // TODO1 - have a .isClosed in spline?
 
-               if ( this.enabled === false ) return;
+                               splineTube = extrudePath.computeFrenetFrames( steps, false );
 
-               // console.log( 'THREE.Cache', 'Checking key:', key );
+                               // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);
 
-               return this.files[ key ];
+                               binormal = new Vector3();
+                               normal = new Vector3();
+                               position2 = new Vector3();
 
-       },
+                       }
 
-       remove: function ( key ) {
+                       // Safeguards if bevels are not enabled
 
-               delete this.files[ key ];
+                       if ( ! bevelEnabled ) {
 
-       },
+                               bevelSegments = 0;
+                               bevelThickness = 0;
+                               bevelSize = 0;
+                               bevelOffset = 0;
 
-       clear: function () {
+                       }
 
-               this.files = {};
+                       // Variables initialization
 
-       }
+                       const shapePoints = shape.extractPoints( curveSegments );
 
-};
+                       let vertices = shapePoints.shape;
+                       const holes = shapePoints.holes;
 
-function LoadingManager( onLoad, onProgress, onError ) {
+                       const reverse = ! ShapeUtils.isClockWise( vertices );
 
-       const scope = this;
+                       if ( reverse ) {
 
-       let isLoading = false;
-       let itemsLoaded = 0;
-       let itemsTotal = 0;
-       let urlModifier = undefined;
-       const handlers = [];
+                               vertices = vertices.reverse();
 
-       // Refer to #5689 for the reason why we don't set .onStart
-       // in the constructor
+                               // Maybe we should also check if holes are in the opposite direction, just to be safe ...
 
-       this.onStart = undefined;
-       this.onLoad = onLoad;
-       this.onProgress = onProgress;
-       this.onError = onError;
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
 
-       this.itemStart = function ( url ) {
+                                       const ahole = holes[ h ];
 
-               itemsTotal ++;
+                                       if ( ShapeUtils.isClockWise( ahole ) ) {
 
-               if ( isLoading === false ) {
+                                               holes[ h ] = ahole.reverse();
 
-                       if ( scope.onStart !== undefined ) {
+                                       }
 
-                               scope.onStart( url, itemsLoaded, itemsTotal );
+                               }
 
                        }
 
-               }
 
-               isLoading = true;
+                       const faces = ShapeUtils.triangulateShape( vertices, holes );
 
-       };
+                       /* Vertices */
 
-       this.itemEnd = function ( url ) {
+                       const contour = vertices; // vertices has all points but contour has only points of circumference
 
-               itemsLoaded ++;
+                       for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
 
-               if ( scope.onProgress !== undefined ) {
+                               const ahole = holes[ h ];
 
-                       scope.onProgress( url, itemsLoaded, itemsTotal );
+                               vertices = vertices.concat( ahole );
 
-               }
+                       }
 
-               if ( itemsLoaded === itemsTotal ) {
 
-                       isLoading = false;
+                       function scalePt2( pt, vec, size ) {
 
-                       if ( scope.onLoad !== undefined ) {
+                               if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' );
 
-                               scope.onLoad();
+                               return vec.clone().multiplyScalar( size ).add( pt );
 
                        }
 
-               }
-
-       };
+                       const vlen = vertices.length, flen = faces.length;
 
-       this.itemError = function ( url ) {
 
-               if ( scope.onError !== undefined ) {
+                       // Find directions for point movement
 
-                       scope.onError( url );
 
-               }
+                       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.
 
-       this.resolveURL = function ( url ) {
+                               let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt
 
-               if ( urlModifier ) {
+                               // good reading for geometry algorithms (here: line-line intersection)
+                               // http://geomalgorithms.com/a05-_intersect-1.html
 
-                       return urlModifier( url );
+                               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 );
 
-               return url;
+                               // check for collinear edges
+                               const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x );
 
-       };
+                               if ( Math.abs( collinear0 ) > Number.EPSILON ) {
 
-       this.setURLModifier = function ( transform ) {
+                                       // not collinear
 
-               urlModifier = transform;
+                                       // length of vectors for normalizing
 
-               return this;
+                                       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
 
-       this.addHandler = function ( regex, loader ) {
+                                       const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len );
+                                       const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len );
 
-               handlers.push( regex, loader );
+                                       const ptNextShift_x = ( inNext.x - v_next_y / v_next_len );
+                                       const ptNextShift_y = ( inNext.y + v_next_x / v_next_len );
 
-               return this;
+                                       // 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 );
 
-       this.removeHandler = function ( regex ) {
+                                       // vector from inPt to intersection point
 
-               const index = handlers.indexOf( regex );
+                                       v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x );
+                                       v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y );
 
-               if ( index !== - 1 ) {
+                                       // 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 ) {
 
-                       handlers.splice( index, 2 );
+                                               return new Vector2( v_trans_x, v_trans_y );
 
-               }
+                                       } else {
 
-               return this;
+                                               shrink_by = Math.sqrt( v_trans_lensq / 2 );
 
-       };
+                                       }
 
-       this.getHandler = function ( file ) {
+                               } else {
 
-               for ( let i = 0, l = handlers.length; i < l; i += 2 ) {
+                                       // handle special case of collinear edges
 
-                       const regex = handlers[ i ];
-                       const loader = handlers[ i + 1 ];
+                                       let direction_eq = false; // assumes: opposite
 
-                       if ( regex.global ) regex.lastIndex = 0; // see #17920
+                                       if ( v_prev_x > Number.EPSILON ) {
 
-                       if ( regex.test( file ) ) {
+                                               if ( v_next_x > Number.EPSILON ) {
 
-                               return loader;
+                                                       direction_eq = true;
 
-                       }
+                                               }
 
-               }
+                                       } else {
 
-               return null;
+                                               if ( v_prev_x < - Number.EPSILON ) {
 
-       };
+                                                       if ( v_next_x < - Number.EPSILON ) {
 
-}
+                                                               direction_eq = true;
 
-const DefaultLoadingManager = new LoadingManager();
+                                                       }
 
-function Loader( manager ) {
+                                               } else {
 
-       this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+                                                       if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) {
 
-       this.crossOrigin = 'anonymous';
-       this.withCredentials = false;
-       this.path = '';
-       this.resourcePath = '';
-       this.requestHeader = {};
+                                                               direction_eq = true;
 
-}
+                                                       }
 
-Object.assign( Loader.prototype, {
+                                               }
 
-       load: function ( /* url, onLoad, onProgress, onError */ ) {},
+                                       }
 
-       loadAsync: function ( url, onProgress ) {
+                                       if ( direction_eq ) {
 
-               const scope = this;
+                                               // 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 );
 
-               return new Promise( function ( resolve, reject ) {
+                                       } else {
 
-                       scope.load( url, resolve, onProgress, reject );
+                                               // 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 );
 
-               } );
+                                       }
 
-       },
+                               }
 
-       parse: function ( /* data */ ) {},
+                               return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by );
 
-       setCrossOrigin: function ( crossOrigin ) {
+                       }
 
-               this.crossOrigin = crossOrigin;
-               return this;
 
-       },
+                       const contourMovements = [];
 
-       setWithCredentials: function ( value ) {
+                       for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
 
-               this.withCredentials = value;
-               return this;
+                               if ( j === il ) j = 0;
+                               if ( k === il ) k = 0;
 
-       },
+                               //  (j)---(i)---(k)
+                               // console.log('i,j,k', i, j , k)
 
-       setPath: function ( path ) {
+                               contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] );
 
-               this.path = path;
-               return this;
+                       }
 
-       },
+                       const holesMovements = [];
+                       let oneHoleMovements, verticesMovements = contourMovements.concat();
 
-       setResourcePath: function ( resourcePath ) {
+                       for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
 
-               this.resourcePath = resourcePath;
-               return this;
+                               const ahole = holes[ h ];
 
-       },
+                               oneHoleMovements = [];
 
-       setRequestHeader: function ( requestHeader ) {
+                               for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
 
-               this.requestHeader = requestHeader;
-               return this;
+                                       if ( j === il ) j = 0;
+                                       if ( k === il ) k = 0;
 
-       }
+                                       //  (j)---(i)---(k)
+                                       oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
 
-} );
+                               }
 
-const loading = {};
+                               holesMovements.push( oneHoleMovements );
+                               verticesMovements = verticesMovements.concat( oneHoleMovements );
 
-function FileLoader( manager ) {
+                       }
 
-       Loader.call( this, manager );
 
-}
+                       // Loop bevelSegments, 1 for the front, 1 for the back
 
-FileLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+                       for ( let b = 0; b < bevelSegments; b ++ ) {
 
-       constructor: FileLoader,
+                               //for ( b = bevelSegments; b > 0; b -- ) {
 
-       load: function ( url, onLoad, onProgress, onError ) {
+                               const t = b / bevelSegments;
+                               const z = bevelThickness * Math.cos( t * Math.PI / 2 );
+                               const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
 
-               if ( url === undefined ) url = '';
+                               // contract shape
 
-               if ( this.path !== undefined ) url = this.path + url;
+                               for ( let i = 0, il = contour.length; i < il; i ++ ) {
 
-               url = this.manager.resolveURL( url );
+                                       const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
 
-               const scope = this;
+                                       v( vert.x, vert.y, - z );
 
-               const cached = Cache.get( url );
+                               }
 
-               if ( cached !== undefined ) {
+                               // expand holes
 
-                       scope.manager.itemStart( url );
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
 
-                       setTimeout( function () {
+                                       const ahole = holes[ h ];
+                                       oneHoleMovements = holesMovements[ h ];
 
-                               if ( onLoad ) onLoad( cached );
+                                       for ( let i = 0, il = ahole.length; i < il; i ++ ) {
 
-                               scope.manager.itemEnd( url );
+                                               const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
 
-                       }, 0 );
+                                               v( vert.x, vert.y, - z );
 
-                       return cached;
+                                       }
 
-               }
+                               }
 
-               // Check if request is duplicate
+                       }
 
-               if ( loading[ url ] !== undefined ) {
+                       const bs = bevelSize + bevelOffset;
 
-                       loading[ url ].push( {
+                       // Back facing vertices
 
-                               onLoad: onLoad,
-                               onProgress: onProgress,
-                               onError: onError
+                       for ( let i = 0; i < vlen; i ++ ) {
 
-                       } );
+                               const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
 
-                       return;
+                               if ( ! extrudeByPath ) {
 
-               }
+                                       v( vert.x, vert.y, 0 );
 
-               // Check for data: URI
-               const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
-               const dataUriRegexResult = url.match( dataUriRegex );
-               let request;
+                               } else {
 
-               // Safari can not handle Data URIs through XMLHttpRequest so process manually
-               if ( dataUriRegexResult ) {
+                                       // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );
 
-                       const mimeType = dataUriRegexResult[ 1 ];
-                       const isBase64 = !! dataUriRegexResult[ 2 ];
+                                       normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x );
+                                       binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y );
 
-                       let data = dataUriRegexResult[ 3 ];
-                       data = decodeURIComponent( data );
+                                       position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal );
 
-                       if ( isBase64 ) data = atob( data );
+                                       v( position2.x, position2.y, position2.z );
 
-                       try {
+                               }
 
-                               let response;
-                               const responseType = ( this.responseType || '' ).toLowerCase();
+                       }
 
-                               switch ( responseType ) {
+                       // Add stepped vertices...
+                       // Including front facing vertices
 
-                                       case 'arraybuffer':
-                                       case 'blob':
+                       for ( let s = 1; s <= steps; s ++ ) {
 
-                                               const view = new Uint8Array( data.length );
+                               for ( let i = 0; i < vlen; i ++ ) {
 
-                                               for ( let i = 0; i < data.length; i ++ ) {
+                                       const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
 
-                                                       view[ i ] = data.charCodeAt( i );
+                                       if ( ! extrudeByPath ) {
 
-                                               }
+                                               v( vert.x, vert.y, depth / steps * s );
 
-                                               if ( responseType === 'blob' ) {
+                                       } else {
 
-                                                       response = new Blob( [ view.buffer ], { type: mimeType } );
+                                               // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );
 
-                                               } else {
+                                               normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x );
+                                               binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y );
 
-                                                       response = view.buffer;
+                                               position2.copy( extrudePts[ s ] ).add( normal ).add( binormal );
 
-                                               }
+                                               v( position2.x, position2.y, position2.z );
 
-                                               break;
+                                       }
 
-                                       case 'document':
+                               }
 
-                                               const parser = new DOMParser();
-                                               response = parser.parseFromString( data, mimeType );
+                       }
 
-                                               break;
 
-                                       case 'json':
+                       // Add bevel segments planes
 
-                                               response = JSON.parse( data );
+                       //for ( b = 1; b <= bevelSegments; b ++ ) {
+                       for ( let b = bevelSegments - 1; b >= 0; b -- ) {
 
-                                               break;
+                               const t = b / bevelSegments;
+                               const z = bevelThickness * Math.cos( t * Math.PI / 2 );
+                               const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
 
-                                       default: // 'text' or other
+                               // contract shape
 
-                                               response = data;
+                               for ( let i = 0, il = contour.length; i < il; i ++ ) {
 
-                                               break;
+                                       const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
+                                       v( vert.x, vert.y, depth + z );
 
                                }
 
-                               // Wait for next browser tick like standard XMLHttpRequest event dispatching does
-                               setTimeout( function () {
-
-                                       if ( onLoad ) onLoad( response );
+                               // expand holes
 
-                                       scope.manager.itemEnd( url );
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
 
-                               }, 0 );
+                                       const ahole = holes[ h ];
+                                       oneHoleMovements = holesMovements[ h ];
 
-                       } catch ( error ) {
+                                       for ( let i = 0, il = ahole.length; i < il; i ++ ) {
 
-                               // Wait for next browser tick like standard XMLHttpRequest event dispatching does
-                               setTimeout( function () {
+                                               const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
 
-                                       if ( onError ) onError( error );
+                                               if ( ! extrudeByPath ) {
 
-                                       scope.manager.itemError( url );
-                                       scope.manager.itemEnd( url );
+                                                       v( vert.x, vert.y, depth + z );
 
-                               }, 0 );
+                                               } else {
 
-                       }
+                                                       v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z );
 
-               } else {
+                                               }
 
-                       // Initialise array for duplicate requests
+                                       }
 
-                       loading[ url ] = [];
+                               }
 
-                       loading[ url ].push( {
+                       }
 
-                               onLoad: onLoad,
-                               onProgress: onProgress,
-                               onError: onError
+                       /* Faces */
 
-                       } );
+                       // Top and bottom faces
 
-                       request = new XMLHttpRequest();
+                       buildLidFaces();
 
-                       request.open( 'GET', url, true );
+                       // Sides faces
 
-                       request.addEventListener( 'load', function ( event ) {
+                       buildSideFaces();
 
-                               const response = this.response;
 
-                               const callbacks = loading[ url ];
+                       /////  Internal functions
 
-                               delete loading[ url ];
+                       function buildLidFaces() {
 
-                               if ( this.status === 200 || this.status === 0 ) {
+                               const start = verticesArray.length / 3;
 
-                                       // Some browsers return HTTP Status 0 when using non-http protocol
-                                       // e.g. 'file://' or 'data://'. Handle as success.
+                               if ( bevelEnabled ) {
 
-                                       if ( this.status === 0 ) console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
+                                       let layer = 0; // steps + 1
+                                       let offset = vlen * layer;
 
-                                       // 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 );
+                                       // Bottom faces
 
-                                       for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+                                       for ( let i = 0; i < flen; i ++ ) {
 
-                                               const callback = callbacks[ i ];
-                                               if ( callback.onLoad ) callback.onLoad( response );
+                                               const face = faces[ i ];
+                                               f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset );
 
                                        }
 
-                                       scope.manager.itemEnd( url );
+                                       layer = steps + bevelSegments * 2;
+                                       offset = vlen * layer;
 
-                               } else {
+                                       // Top faces
 
-                                       for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+                                       for ( let i = 0; i < flen; i ++ ) {
 
-                                               const callback = callbacks[ i ];
-                                               if ( callback.onError ) callback.onError( event );
+                                               const face = faces[ i ];
+                                               f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset );
 
                                        }
 
-                                       scope.manager.itemError( url );
-                                       scope.manager.itemEnd( url );
+                               } else {
 
-                               }
+                                       // Bottom faces
+
+                                       for ( let i = 0; i < flen; i ++ ) {
 
-                       }, false );
+                                               const face = faces[ i ];
+                                               f3( face[ 2 ], face[ 1 ], face[ 0 ] );
 
-                       request.addEventListener( 'progress', function ( event ) {
+                                       }
 
-                               const callbacks = loading[ url ];
+                                       // Top faces
 
-                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+                                       for ( let i = 0; i < flen; i ++ ) {
 
-                                       const callback = callbacks[ i ];
-                                       if ( callback.onProgress ) callback.onProgress( event );
+                                               const face = faces[ i ];
+                                               f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps );
+
+                                       }
 
                                }
 
-                       }, false );
+                               scope.addGroup( start, verticesArray.length / 3 - start, 0 );
 
-                       request.addEventListener( 'error', function ( event ) {
+                       }
 
-                               const callbacks = loading[ url ];
+                       // Create faces for the z-sides of the shape
 
-                               delete loading[ url ];
+                       function buildSideFaces() {
 
-                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+                               const start = verticesArray.length / 3;
+                               let layeroffset = 0;
+                               sidewalls( contour, layeroffset );
+                               layeroffset += contour.length;
 
-                                       const callback = callbacks[ i ];
-                                       if ( callback.onError ) callback.onError( event );
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+                                       const ahole = holes[ h ];
+                                       sidewalls( ahole, layeroffset );
+
+                                       //, true
+                                       layeroffset += ahole.length;
 
                                }
 
-                               scope.manager.itemError( url );
-                               scope.manager.itemEnd( url );
 
-                       }, false );
+                               scope.addGroup( start, verticesArray.length / 3 - start, 1 );
 
-                       request.addEventListener( 'abort', function ( event ) {
 
-                               const callbacks = loading[ url ];
+                       }
 
-                               delete loading[ url ];
+                       function sidewalls( contour, layeroffset ) {
 
-                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+                               let i = contour.length;
 
-                                       const callback = callbacks[ i ];
-                                       if ( callback.onError ) callback.onError( event );
+                               while ( -- i >= 0 ) {
 
-                               }
+                                       const j = i;
+                                       let k = i - 1;
+                                       if ( k < 0 ) k = contour.length - 1;
 
-                               scope.manager.itemError( url );
-                               scope.manager.itemEnd( url );
+                                       //console.log('b', i,j, i-1, k,vertices.length);
 
-                       }, false );
+                                       for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) {
+
+                                               const slen1 = vlen * s;
+                                               const slen2 = vlen * ( s + 1 );
 
-                       if ( this.responseType !== undefined ) request.responseType = this.responseType;
-                       if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials;
+                                               const a = layeroffset + j + slen1,
+                                                       b = layeroffset + k + slen1,
+                                                       c = layeroffset + k + slen2,
+                                                       d = layeroffset + j + slen2;
 
-                       if ( request.overrideMimeType ) request.overrideMimeType( this.mimeType !== undefined ? this.mimeType : 'text/plain' );
+                                               f4( a, b, c, d );
 
-                       for ( const header in this.requestHeader ) {
+                                       }
 
-                               request.setRequestHeader( header, this.requestHeader[ header ] );
+                               }
 
                        }
 
-                       request.send( null );
-
-               }
+                       function v( x, y, z ) {
 
-               scope.manager.itemStart( url );
+                               placeholder.push( x );
+                               placeholder.push( y );
+                               placeholder.push( z );
 
-               return request;
+                       }
 
-       },
 
-       setResponseType: function ( value ) {
+                       function f3( a, b, c ) {
 
-               this.responseType = value;
-               return this;
+                               addVertex( a );
+                               addVertex( b );
+                               addVertex( c );
 
-       },
+                               const nextIndex = verticesArray.length / 3;
+                               const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
 
-       setMimeType: function ( value ) {
+                               addUV( uvs[ 0 ] );
+                               addUV( uvs[ 1 ] );
+                               addUV( uvs[ 2 ] );
 
-               this.mimeType = value;
-               return this;
+                       }
 
-       }
+                       function f4( a, b, c, d ) {
 
-} );
+                               addVertex( a );
+                               addVertex( b );
+                               addVertex( d );
 
-function AnimationLoader( manager ) {
+                               addVertex( b );
+                               addVertex( c );
+                               addVertex( d );
 
-       Loader.call( this, manager );
 
-}
+                               const nextIndex = verticesArray.length / 3;
+                               const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
 
-AnimationLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+                               addUV( uvs[ 0 ] );
+                               addUV( uvs[ 1 ] );
+                               addUV( uvs[ 3 ] );
 
-       constructor: AnimationLoader,
+                               addUV( uvs[ 1 ] );
+                               addUV( uvs[ 2 ] );
+                               addUV( uvs[ 3 ] );
 
-       load: function ( url, onLoad, onProgress, onError ) {
+                       }
 
-               const scope = this;
+                       function addVertex( index ) {
 
-               const loader = new FileLoader( scope.manager );
-               loader.setPath( scope.path );
-               loader.setRequestHeader( scope.requestHeader );
-               loader.setWithCredentials( scope.withCredentials );
-               loader.load( url, function ( text ) {
+                               verticesArray.push( placeholder[ index * 3 + 0 ] );
+                               verticesArray.push( placeholder[ index * 3 + 1 ] );
+                               verticesArray.push( placeholder[ index * 3 + 2 ] );
 
-                       try {
+                       }
 
-                               onLoad( scope.parse( JSON.parse( text ) ) );
 
-                       } catch ( e ) {
+                       function addUV( vector2 ) {
 
-                               if ( onError ) {
+                               uvArray.push( vector2.x );
+                               uvArray.push( vector2.y );
 
-                                       onError( e );
+                       }
 
-                               } else {
+               }
 
-                                       console.error( e );
+       }
 
-                               }
+       toJSON() {
 
-                               scope.manager.itemError( url );
+               const data = super.toJSON();
 
-                       }
+               const shapes = this.parameters.shapes;
+               const options = this.parameters.options;
 
-               }, onProgress, onError );
+               return toJSON$1( shapes, options, data );
 
-       },
+       }
 
-       parse: function ( json ) {
+       static fromJSON( data, shapes ) {
 
-               const animations = [];
+               const geometryShapes = [];
 
-               for ( let i = 0; i < json.length; i ++ ) {
+               for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) {
 
-                       const clip = AnimationClip.parse( json[ i ] );
+                       const shape = shapes[ data.shapes[ j ] ];
 
-                       animations.push( clip );
+                       geometryShapes.push( shape );
 
                }
 
-               return animations;
+               const extrudePath = data.options.extrudePath;
 
-       }
+               if ( extrudePath !== undefined ) {
 
-} );
+                       data.options.extrudePath = new Curves[ extrudePath.type ]().fromJSON( extrudePath );
 
-/**
- * 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 ) {
+               return new ExtrudeGeometry( geometryShapes, data.options );
 
-       Loader.call( this, manager );
+       }
 
 }
 
-CompressedTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+const WorldUVGenerator = {
 
-       constructor: CompressedTextureLoader,
+       generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) {
 
-       load: function ( url, onLoad, onProgress, onError ) {
+               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 ];
 
-               const scope = this;
+               return [
+                       new Vector2( a_x, a_y ),
+                       new Vector2( b_x, b_y ),
+                       new Vector2( c_x, c_y )
+               ];
 
-               const images = [];
+       },
 
-               const texture = new CompressedTexture();
+       generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) {
 
-               const loader = new FileLoader( this.manager );
-               loader.setPath( this.path );
-               loader.setResponseType( 'arraybuffer' );
-               loader.setRequestHeader( this.requestHeader );
-               loader.setWithCredentials( scope.withCredentials );
+               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 ];
 
-               let loaded = 0;
+               if ( Math.abs( a_y - b_y ) < Math.abs( a_x - b_x ) ) {
 
-               function loadTexture( i ) {
+                       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 )
+                       ];
 
-                       loader.load( url[ i ], function ( buffer ) {
+               } else {
 
-                               const texDatas = scope.parse( buffer, true );
+                       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 )
+                       ];
 
-                               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;
+function toJSON$1( shapes, options, data ) {
 
-                                       texture.image = images;
-                                       texture.format = texDatas.format;
-                                       texture.needsUpdate = true;
+       data.shapes = [];
 
-                                       if ( onLoad ) onLoad( texture );
+       if ( Array.isArray( shapes ) ) {
 
-                               }
+               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+
+                       const shape = shapes[ i ];
 
-                       }, onProgress, onError );
+                       data.shapes.push( shape.uuid );
 
                }
 
-               if ( Array.isArray( url ) ) {
+       } else {
+
+               data.shapes.push( shapes.uuid );
 
-                       for ( let i = 0, il = url.length; i < il; ++ i ) {
+       }
 
-                               loadTexture( i );
+       if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON();
 
-                       }
+       return data;
 
-               } else {
+}
 
-                       // compressed cubemap texture stored in a single DDS file
+class ShapeGeometry extends BufferGeometry {
 
-                       loader.load( url, function ( buffer ) {
+       constructor( shapes = new Shape( [ new Vector2( 0, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), curveSegments = 12 ) {
 
-                               const texDatas = scope.parse( buffer, true );
+               super();
+               this.type = 'ShapeGeometry';
 
-                               if ( texDatas.isCubemap ) {
+               this.parameters = {
+                       shapes: shapes,
+                       curveSegments: curveSegments
+               };
 
-                                       const faces = texDatas.mipmaps.length / texDatas.mipmapCount;
+               // buffers
 
-                                       for ( let f = 0; f < faces; f ++ ) {
+               const indices = [];
+               const vertices = [];
+               const normals = [];
+               const uvs = [];
 
-                                               images[ f ] = { mipmaps: [] };
+               // helper variables
 
-                                               for ( let i = 0; i < texDatas.mipmapCount; i ++ ) {
+               let groupStart = 0;
+               let groupCount = 0;
 
-                                                       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;
+               // allow single and array values for "shapes" parameter
 
-                                               }
+               if ( Array.isArray( shapes ) === false ) {
 
-                                       }
+                       addShape( shapes );
 
-                                       texture.image = images;
+               } else {
 
-                               } else {
+                       for ( let i = 0; i < shapes.length; i ++ ) {
 
-                                       texture.image.width = texDatas.width;
-                                       texture.image.height = texDatas.height;
-                                       texture.mipmaps = texDatas.mipmaps;
+                               addShape( shapes[ i ] );
 
-                               }
+                               this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support
 
-                               if ( texDatas.mipmapCount === 1 ) {
+                               groupStart += groupCount;
+                               groupCount = 0;
 
-                                       texture.minFilter = LinearFilter;
+                       }
 
-                               }
+               }
 
-                               texture.format = texDatas.format;
-                               texture.needsUpdate = true;
+               // build geometry
 
-                               if ( onLoad ) onLoad( texture );
+               this.setIndex( indices );
+               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
 
-                       }, onProgress, onError );
 
-               }
+               // helper functions
 
-               return texture;
+               function addShape( shape ) {
 
-       }
+                       const indexOffset = vertices.length / 3;
+                       const points = shape.extractPoints( curveSegments );
 
-} );
+                       let shapeVertices = points.shape;
+                       const shapeHoles = points.holes;
 
-function ImageLoader( manager ) {
+                       // check direction of vertices
 
-       Loader.call( this, manager );
+                       if ( ShapeUtils.isClockWise( shapeVertices ) === false ) {
 
-}
+                               shapeVertices = shapeVertices.reverse();
 
-ImageLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+                       }
 
-       constructor: ImageLoader,
+                       for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
 
-       load: function ( url, onLoad, onProgress, onError ) {
+                               const shapeHole = shapeHoles[ i ];
 
-               if ( this.path !== undefined ) url = this.path + url;
+                               if ( ShapeUtils.isClockWise( shapeHole ) === true ) {
 
-               url = this.manager.resolveURL( url );
+                                       shapeHoles[ i ] = shapeHole.reverse();
 
-               const scope = this;
+                               }
 
-               const cached = Cache.get( url );
+                       }
 
-               if ( cached !== undefined ) {
+                       const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles );
 
-                       scope.manager.itemStart( url );
+                       // join vertices of inner and outer paths to a single array
 
-                       setTimeout( function () {
+                       for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
 
-                               if ( onLoad ) onLoad( cached );
+                               const shapeHole = shapeHoles[ i ];
+                               shapeVertices = shapeVertices.concat( shapeHole );
 
-                               scope.manager.itemEnd( url );
+                       }
 
-                       }, 0 );
+                       // vertices, normals, uvs
 
-                       return cached;
+                       for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) {
 
-               }
+                               const vertex = shapeVertices[ i ];
 
-               const image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' );
+                               vertices.push( vertex.x, vertex.y, 0 );
+                               normals.push( 0, 0, 1 );
+                               uvs.push( vertex.x, vertex.y ); // world uvs
 
-               function onImageLoad() {
+                       }
 
-                       image.removeEventListener( 'load', onImageLoad, false );
-                       image.removeEventListener( 'error', onImageError, false );
+                       // incides
 
-                       Cache.add( url, this );
+                       for ( let i = 0, l = faces.length; i < l; i ++ ) {
 
-                       if ( onLoad ) onLoad( this );
+                               const face = faces[ i ];
 
-                       scope.manager.itemEnd( url );
+                               const a = face[ 0 ] + indexOffset;
+                               const b = face[ 1 ] + indexOffset;
+                               const c = face[ 2 ] + indexOffset;
 
-               }
+                               indices.push( a, b, c );
+                               groupCount += 3;
 
-               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 );
+       toJSON() {
 
-               }
+               const data = super.toJSON();
 
-               image.addEventListener( 'load', onImageLoad, false );
-               image.addEventListener( 'error', onImageError, false );
+               const shapes = this.parameters.shapes;
 
-               if ( url.substr( 0, 5 ) !== 'data:' ) {
+               return toJSON( shapes, data );
 
-                       if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;
+       }
 
-               }
+       static fromJSON( data, shapes ) {
 
-               scope.manager.itemStart( url );
+               const geometryShapes = [];
 
-               image.src = url;
+               for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) {
 
-               return image;
+                       const shape = shapes[ data.shapes[ j ] ];
 
-       }
+                       geometryShapes.push( shape );
 
-} );
+               }
 
-function CubeTextureLoader( manager ) {
+               return new ShapeGeometry( geometryShapes, data.curveSegments );
 
-       Loader.call( this, manager );
+       }
 
 }
 
-CubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+function toJSON( shapes, data ) {
 
-       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 ) {
+       data.shapes = [];
 
-                               texture.images[ i ] = image;
+       if ( Array.isArray( shapes ) ) {
 
-                               loaded ++;
+               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
 
-                               if ( loaded === 6 ) {
+                       const shape = shapes[ i ];
 
-                                       texture.needsUpdate = true;
+                       data.shapes.push( shape.uuid );
 
-                                       if ( onLoad ) onLoad( texture );
+               }
 
-                               }
+       } else {
 
-                       }, undefined, onError );
+               data.shapes.push( shapes.uuid );
 
-               }
+       }
 
-               for ( let i = 0; i < urls.length; ++ i ) {
+       return data;
 
-                       loadTexture( i );
+}
 
-               }
+class SphereGeometry extends BufferGeometry {
 
-               return texture;
+       constructor( radius = 1, widthSegments = 32, heightSegments = 16, 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
+               };
 
-/**
- * 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().
- */
+               widthSegments = Math.max( 3, Math.floor( widthSegments ) );
+               heightSegments = Math.max( 2, Math.floor( heightSegments ) );
 
-function DataTextureLoader( manager ) {
+               const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI );
 
-       Loader.call( this, manager );
+               let index = 0;
+               const grid = [];
 
-}
+               const vertex = new Vector3();
+               const normal = new Vector3();
 
-DataTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+               // buffers
 
-       constructor: DataTextureLoader,
+               const indices = [];
+               const vertices = [];
+               const normals = [];
+               const uvs = [];
 
-       load: function ( url, onLoad, onProgress, onError ) {
+               // generate vertices, normals and uvs
 
-               const scope = this;
+               for ( let iy = 0; iy <= heightSegments; iy ++ ) {
 
-               const texture = new DataTexture();
+                       const verticesRow = [];
 
-               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 v = iy / heightSegments;
 
-                       const texData = scope.parse( buffer );
+                       // special case for the poles
 
-                       if ( ! texData ) return;
+                       let uOffset = 0;
 
-                       if ( texData.image !== undefined ) {
+                       if ( iy == 0 && thetaStart == 0 ) {
 
-                               texture.image = texData.image;
+                               uOffset = 0.5 / widthSegments;
 
-                       } else if ( texData.data !== undefined ) {
+                       } else if ( iy == heightSegments && thetaEnd == Math.PI ) {
 
-                               texture.image.width = texData.width;
-                               texture.image.height = texData.height;
-                               texture.image.data = texData.data;
+                               uOffset = - 0.5 / widthSegments;
 
                        }
 
-                       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;
+                       for ( let ix = 0; ix <= widthSegments; ix ++ ) {
 
-                       texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1;
+                               const u = ix / widthSegments;
 
-                       if ( texData.encoding !== undefined ) {
+                               // vertex
 
-                               texture.encoding = texData.encoding;
+                               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 );
 
-                       if ( texData.flipY !== undefined ) {
+                               // normal
 
-                               texture.flipY = texData.flipY;
+                               normal.copy( vertex ).normalize();
+                               normals.push( normal.x, normal.y, normal.z );
 
-                       }
+                               // uv
 
-                       if ( texData.format !== undefined ) {
+                               uvs.push( u + uOffset, 1 - v );
 
-                               texture.format = texData.format;
+                               verticesRow.push( index ++ );
 
                        }
 
-                       if ( texData.type !== undefined ) {
-
-                               texture.type = texData.type;
+                       grid.push( verticesRow );
 
-                       }
+               }
 
-                       if ( texData.mipmaps !== undefined ) {
+               // indices
 
-                               texture.mipmaps = texData.mipmaps;
-                               texture.minFilter = LinearMipmapLinearFilter; // presumably...
+               for ( let iy = 0; iy < heightSegments; iy ++ ) {
 
-                       }
+                       for ( let ix = 0; ix < widthSegments; ix ++ ) {
 
-                       if ( texData.mipmapCount === 1 ) {
+                               const a = grid[ iy ][ ix + 1 ];
+                               const b = grid[ iy ][ ix ];
+                               const c = grid[ iy + 1 ][ ix ];
+                               const d = grid[ iy + 1 ][ ix + 1 ];
 
-                               texture.minFilter = LinearFilter;
+                               if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d );
+                               if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d );
 
                        }
 
-                       texture.needsUpdate = true;
-
-                       if ( onLoad ) onLoad( texture, texData );
-
-               }, onProgress, onError );
+               }
 
+               // build geometry
 
-               return texture;
+               this.setIndex( indices );
+               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
 
        }
 
-} );
+       static fromJSON( data ) {
 
-function TextureLoader( manager ) {
+               return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength );
 
-       Loader.call( this, manager );
+       }
 
 }
 
-TextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
-
-       constructor: TextureLoader,
-
-       load: function ( url, onLoad, onProgress, onError ) {
+/**
+ * parameters = {
+ *  color: <THREE.Color>
+ * }
+ */
 
-               const texture = new Texture();
+class ShadowMaterial extends Material {
 
-               const loader = new ImageLoader( this.manager );
-               loader.setCrossOrigin( this.crossOrigin );
-               loader.setPath( this.path );
+       constructor( parameters ) {
 
-               loader.load( url, function ( image ) {
+               super();
 
-                       texture.image = image;
+               this.type = 'ShadowMaterial';
 
-                       // 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;
+               this.color = new Color( 0x000000 );
+               this.transparent = true;
 
-                       texture.format = isJPEG ? RGBFormat : RGBAFormat;
-                       texture.needsUpdate = true;
+               this.setValues( parameters );
 
-                       if ( onLoad !== undefined ) {
+       }
 
-                               onLoad( texture );
+       copy( source ) {
 
-                       }
+               super.copy( source );
 
-               }, onProgress, onError );
+               this.color.copy( source.color );
 
-               return texture;
+               return this;
 
        }
 
-} );
+}
+
+ShadowMaterial.prototype.isShadowMaterial = true;
 
 /**
- * Extensible curve object.
+ * parameters = {
+ *  color: <hex>,
+ *  roughness: <float>,
+ *  metalness: <float>,
+ *  opacity: <float>,
  *
- * Some common of curve methods:
- * .getPoint( t, optionalTarget ), .getTangent( t, optionalTarget )
- * .getPointAt( u, optionalTarget ), .getTangentAt( u, optionalTarget )
- * .getPoints(), .getSpacedPoints()
- * .getLength()
- * .updateArcLengths()
+ *  map: new THREE.Texture( <Image> ),
  *
- * This following curves inherit from THREE.Curve:
+ *  lightMap: new THREE.Texture( <Image> ),
+ *  lightMapIntensity: <float>
  *
- * -- 2D curves --
- * THREE.ArcCurve
- * THREE.CubicBezierCurve
- * THREE.EllipseCurve
- * THREE.LineCurve
- * THREE.QuadraticBezierCurve
- * THREE.SplineCurve
+ *  aoMap: new THREE.Texture( <Image> ),
+ *  aoMapIntensity: <float>
  *
- * -- 3D curves --
- * THREE.CatmullRomCurve3
- * THREE.CubicBezierCurve3
- * THREE.LineCurve3
- * THREE.QuadraticBezierCurve3
+ *  emissive: <hex>,
+ *  emissiveIntensity: <float>
+ *  emissiveMap: new THREE.Texture( <Image> ),
  *
- * A series of curves can be represented as a THREE.CurvePath.
+ *  bumpMap: new THREE.Texture( <Image> ),
+ *  bumpScale: <float>,
  *
- **/
-
-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]
+ *  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>,
+ *
+ *  flatShading: <bool>
+ * }
+ */
 
-       getPoint: function ( /* t, optionalTarget */ ) {
+class MeshStandardMaterial extends Material {
 
-               console.warn( 'THREE.Curve: .getPoint() not implemented.' );
-               return null;
+       constructor( parameters ) {
 
-       },
+               super();
 
-       // Get point at relative position in curve according to arc length
-       // - u [0 .. 1]
+               this.defines = { 'STANDARD': '' };
 
-       getPointAt: function ( u, optionalTarget ) {
+               this.type = 'MeshStandardMaterial';
 
-               const t = this.getUtoTmapping( u );
-               return this.getPoint( t, optionalTarget );
+               this.color = new Color( 0xffffff ); // diffuse
+               this.roughness = 1.0;
+               this.metalness = 0.0;
 
-       },
+               this.map = null;
 
-       // Get sequence of points using getPoint( t )
+               this.lightMap = null;
+               this.lightMapIntensity = 1.0;
 
-       getPoints: function ( divisions = 5 ) {
+               this.aoMap = null;
+               this.aoMapIntensity = 1.0;
 
-               const points = [];
+               this.emissive = new Color( 0x000000 );
+               this.emissiveIntensity = 1.0;
+               this.emissiveMap = null;
 
-               for ( let d = 0; d <= divisions; d ++ ) {
+               this.bumpMap = null;
+               this.bumpScale = 1;
 
-                       points.push( this.getPoint( d / divisions ) );
+               this.normalMap = null;
+               this.normalMapType = TangentSpaceNormalMap;
+               this.normalScale = new Vector2( 1, 1 );
 
-               }
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-               return points;
+               this.roughnessMap = null;
 
-       },
+               this.metalnessMap = null;
 
-       // Get sequence of points using getPointAt( u )
+               this.alphaMap = null;
 
-       getSpacedPoints: function ( divisions = 5 ) {
+               this.envMap = null;
+               this.envMapIntensity = 1.0;
 
-               const points = [];
+               this.refractionRatio = 0.98;
 
-               for ( let d = 0; d <= divisions; d ++ ) {
+               this.wireframe = false;
+               this.wireframeLinewidth = 1;
+               this.wireframeLinecap = 'round';
+               this.wireframeLinejoin = 'round';
 
-                       points.push( this.getPointAt( d / divisions ) );
+               this.flatShading = false;
 
-               }
+               this.setValues( parameters );
 
-               return points;
+       }
 
-       },
+       copy( source ) {
 
-       // Get total curve arc length
+               super.copy( source );
 
-       getLength: function () {
+               this.defines = { 'STANDARD': '' };
 
-               const lengths = this.getLengths();
-               return lengths[ lengths.length - 1 ];
+               this.color.copy( source.color );
+               this.roughness = source.roughness;
+               this.metalness = source.metalness;
 
-       },
+               this.map = source.map;
 
-       // Get list of cumulative segment lengths
+               this.lightMap = source.lightMap;
+               this.lightMapIntensity = source.lightMapIntensity;
 
-       getLengths: function ( divisions ) {
+               this.aoMap = source.aoMap;
+               this.aoMapIntensity = source.aoMapIntensity;
 
-               if ( divisions === undefined ) divisions = this.arcLengthDivisions;
+               this.emissive.copy( source.emissive );
+               this.emissiveMap = source.emissiveMap;
+               this.emissiveIntensity = source.emissiveIntensity;
 
-               if ( this.cacheArcLengths &&
-                       ( this.cacheArcLengths.length === divisions + 1 ) &&
-                       ! this.needsUpdate ) {
+               this.bumpMap = source.bumpMap;
+               this.bumpScale = source.bumpScale;
 
-                       return this.cacheArcLengths;
+               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.needsUpdate = false;
+               this.roughnessMap = source.roughnessMap;
 
-               const cache = [];
-               let current, last = this.getPoint( 0 );
-               let sum = 0;
+               this.metalnessMap = source.metalnessMap;
 
-               cache.push( 0 );
+               this.alphaMap = source.alphaMap;
 
-               for ( let p = 1; p <= divisions; p ++ ) {
+               this.envMap = source.envMap;
+               this.envMapIntensity = source.envMapIntensity;
 
-                       current = this.getPoint( p / divisions );
-                       sum += current.distanceTo( last );
-                       cache.push( sum );
-                       last = current;
+               this.refractionRatio = source.refractionRatio;
 
-               }
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
+               this.wireframeLinecap = source.wireframeLinecap;
+               this.wireframeLinejoin = source.wireframeLinejoin;
 
-               this.cacheArcLengths = cache;
+               this.flatShading = source.flatShading;
 
-               return cache; // { sums: cache, sum: sum }; Sum is in the last element.
+               return this;
 
-       },
+       }
 
-       updateArcLengths: function () {
+}
 
-               this.needsUpdate = true;
-               this.getLengths();
+MeshStandardMaterial.prototype.isMeshStandardMaterial = true;
 
-       },
+/**
+ * parameters = {
+ *  clearcoat: <float>,
+ *  clearcoatMap: new THREE.Texture( <Image> ),
+ *  clearcoatRoughness: <float>,
+ *  clearcoatRoughnessMap: new THREE.Texture( <Image> ),
+ *  clearcoatNormalScale: <Vector2>,
+ *  clearcoatNormalMap: new THREE.Texture( <Image> ),
+ *
+ *  ior: <float>,
+ *  reflectivity: <float>,
+ *
+ *  sheen: <float>,
+ *  sheenColor: <Color>,
+ *  sheenColorMap: new THREE.Texture( <Image> ),
+ *  sheenRoughness: <float>,
+ *  sheenRoughnessMap: new THREE.Texture( <Image> ),
+ *
+ *  transmission: <float>,
+ *  transmissionMap: new THREE.Texture( <Image> ),
+ *
+ *  thickness: <float>,
+ *  thicknessMap: new THREE.Texture( <Image> ),
+ *  attenuationDistance: <float>,
+ *  attenuationColor: <Color>,
+ *
+ *  specularIntensity: <float>,
+ *  specularIntensityMap: new THREE.Texture( <Image> ),
+ *  specularColor: <Color>,
+ *  specularColorMap: new THREE.Texture( <Image> )
+ * }
+ */
 
-       // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant
+class MeshPhysicalMaterial extends MeshStandardMaterial {
 
-       getUtoTmapping: function ( u, distance ) {
+       constructor( parameters ) {
 
-               const arcLengths = this.getLengths();
+               super();
 
-               let i = 0;
-               const il = arcLengths.length;
+               this.defines = {
 
-               let targetArcLength; // The targeted u distance value to get
+                       'STANDARD': '',
+                       'PHYSICAL': ''
 
-               if ( distance ) {
+               };
 
-                       targetArcLength = distance;
+               this.type = 'MeshPhysicalMaterial';
 
-               } else {
+               this.clearcoatMap = null;
+               this.clearcoatRoughness = 0.0;
+               this.clearcoatRoughnessMap = null;
+               this.clearcoatNormalScale = new Vector2( 1, 1 );
+               this.clearcoatNormalMap = null;
 
-                       targetArcLength = u * arcLengths[ il - 1 ];
+               this.ior = 1.5;
 
-               }
+               Object.defineProperty( this, 'reflectivity', {
+                       get: function () {
 
-               // binary search for the index with largest value smaller than target u distance
+                               return ( clamp$1( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ) );
 
-               let low = 0, high = il - 1, comparison;
+                       },
+                       set: function ( reflectivity ) {
 
-               while ( low <= high ) {
+                               this.ior = ( 1 + 0.4 * reflectivity ) / ( 1 - 0.4 * reflectivity );
 
-                       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;
+               this.sheenColor = new Color( 0x000000 );
+               this.sheenColorMap = null;
+               this.sheenRoughness = 1.0;
+               this.sheenRoughnessMap = null;
 
-                       if ( comparison < 0 ) {
+               this.transmissionMap = null;
 
-                               low = i + 1;
+               this.thickness = 0.01;
+               this.thicknessMap = null;
+               this.attenuationDistance = 0.0;
+               this.attenuationColor = new Color( 1, 1, 1 );
 
-                       } else if ( comparison > 0 ) {
+               this.specularIntensity = 1.0;
+               this.specularIntensityMap = null;
+               this.specularColor = new Color( 1, 1, 1 );
+               this.specularColorMap = null;
 
-                               high = i - 1;
+               this._sheen = 0.0;
+               this._clearcoat = 0;
+               this._transmission = 0;
 
-                       } else {
+               this.setValues( parameters );
 
-                               high = i;
-                               break;
+       }
 
-                               // DONE
+       get sheen() {
 
-                       }
+               return this._sheen;
 
-               }
+       }
 
-               i = high;
+       set sheen( value ) {
 
-               if ( arcLengths[ i ] === targetArcLength ) {
+               if ( this._sheen > 0 !== value > 0 ) {
 
-                       return i / ( il - 1 );
+                       this.version ++;
 
                }
 
-               // 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
+               this._sheen = value;
 
-               const t = ( i + segmentFraction ) / ( il - 1 );
+       }
 
-               return t;
+       get clearcoat() {
 
-       },
+               return this._clearcoat;
 
-       // 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 ) {
+       set clearcoat( value ) {
 
-               const delta = 0.0001;
-               let t1 = t - delta;
-               let t2 = t + delta;
+               if ( this._clearcoat > 0 !== value > 0 ) {
 
-               // Capping in case of danger
+                       this.version ++;
 
-               if ( t1 < 0 ) t1 = 0;
-               if ( t2 > 1 ) t2 = 1;
+               }
 
-               const pt1 = this.getPoint( t1 );
-               const pt2 = this.getPoint( t2 );
+               this._clearcoat = value;
 
-               const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() );
+       }
 
-               tangent.copy( pt2 ).sub( pt1 ).normalize();
+       get transmission() {
 
-               return tangent;
+               return this._transmission;
 
-       },
+       }
 
-       getTangentAt: function ( u, optionalTarget ) {
+       set transmission( value ) {
 
-               const t = this.getUtoTmapping( u );
-               return this.getTangent( t, optionalTarget );
+               if ( this._transmission > 0 !== value > 0 ) {
 
-       },
+                       this.version ++;
 
-       computeFrenetFrames: function ( segments, closed ) {
+               }
 
-               // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
+               this._transmission = value;
 
-               const normal = new Vector3();
+       }
 
-               const tangents = [];
-               const normals = [];
-               const binormals = [];
+       copy( source ) {
 
-               const vec = new Vector3();
-               const mat = new Matrix4();
+               super.copy( source );
 
-               // compute the tangent vectors for each segment on the curve
+               this.defines = {
 
-               for ( let i = 0; i <= segments; i ++ ) {
+                       'STANDARD': '',
+                       'PHYSICAL': ''
 
-                       const u = i / segments;
+               };
 
-                       tangents[ i ] = this.getTangentAt( u, new Vector3() );
-                       tangents[ i ].normalize();
+               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.ior = source.ior;
 
-               // select an initial normal vector perpendicular to the first tangent vector,
-               // and in the direction of the minimum tangent xyz component
+               this.sheen = source.sheen;
+               this.sheenColor.copy( source.sheenColor );
+               this.sheenColorMap = source.sheenColorMap;
+               this.sheenRoughness = source.sheenRoughness;
+               this.sheenRoughnessMap = source.sheenRoughnessMap;
 
-               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 );
+               this.transmission = source.transmission;
+               this.transmissionMap = source.transmissionMap;
 
-               if ( tx <= min ) {
+               this.thickness = source.thickness;
+               this.thicknessMap = source.thicknessMap;
+               this.attenuationDistance = source.attenuationDistance;
+               this.attenuationColor.copy( source.attenuationColor );
 
-                       min = tx;
-                       normal.set( 1, 0, 0 );
+               this.specularIntensity = source.specularIntensity;
+               this.specularIntensityMap = source.specularIntensityMap;
+               this.specularColor.copy( source.specularColor );
+               this.specularColorMap = source.specularColorMap;
 
-               }
+               return this;
 
-               if ( ty <= min ) {
+       }
 
-                       min = ty;
-                       normal.set( 0, 1, 0 );
+}
 
-               }
+MeshPhysicalMaterial.prototype.isMeshPhysicalMaterial = true;
 
-               if ( tz <= min ) {
+/**
+ * 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>,
+ *
+ *  flatShading: <bool>
+ * }
+ */
 
-                       normal.set( 0, 0, 1 );
+class MeshPhongMaterial extends Material {
 
-               }
+       constructor( parameters ) {
 
-               vec.crossVectors( tangents[ 0 ], normal ).normalize();
+               super();
 
-               normals[ 0 ].crossVectors( tangents[ 0 ], vec );
-               binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );
+               this.type = 'MeshPhongMaterial';
 
+               this.color = new Color( 0xffffff ); // diffuse
+               this.specular = new Color( 0x111111 );
+               this.shininess = 30;
 
-               // compute the slowly-varying normal and binormal vectors for each segment on the curve
+               this.map = null;
 
-               for ( let i = 1; i <= segments; i ++ ) {
+               this.lightMap = null;
+               this.lightMapIntensity = 1.0;
 
-                       normals[ i ] = normals[ i - 1 ].clone();
+               this.aoMap = null;
+               this.aoMapIntensity = 1.0;
 
-                       binormals[ i ] = binormals[ i - 1 ].clone();
+               this.emissive = new Color( 0x000000 );
+               this.emissiveIntensity = 1.0;
+               this.emissiveMap = null;
 
-                       vec.crossVectors( tangents[ i - 1 ], tangents[ i ] );
+               this.bumpMap = null;
+               this.bumpScale = 1;
 
-                       if ( vec.length() > Number.EPSILON ) {
+               this.normalMap = null;
+               this.normalMapType = TangentSpaceNormalMap;
+               this.normalScale = new Vector2( 1, 1 );
 
-                               vec.normalize();
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-                               const theta = Math.acos( MathUtils.clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors
+               this.specularMap = null;
 
-                               normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );
+               this.alphaMap = null;
 
-                       }
+               this.envMap = null;
+               this.combine = MultiplyOperation;
+               this.reflectivity = 1;
+               this.refractionRatio = 0.98;
 
-                       binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+               this.wireframe = false;
+               this.wireframeLinewidth = 1;
+               this.wireframeLinecap = 'round';
+               this.wireframeLinejoin = 'round';
 
-               }
+               this.flatShading = false;
 
-               // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
+               this.setValues( parameters );
 
-               if ( closed === true ) {
+       }
 
-                       let theta = Math.acos( MathUtils.clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) );
-                       theta /= segments;
+       copy( source ) {
 
-                       if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) {
+               super.copy( source );
 
-                               theta = - theta;
+               this.color.copy( source.color );
+               this.specular.copy( source.specular );
+               this.shininess = source.shininess;
 
-                       }
+               this.map = source.map;
 
-                       for ( let i = 1; i <= segments; i ++ ) {
+               this.lightMap = source.lightMap;
+               this.lightMapIntensity = source.lightMapIntensity;
 
-                               // twist a little...
-                               normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );
-                               binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+               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;
 
-               return {
-                       tangents: tangents,
-                       normals: normals,
-                       binormals: binormals
-               };
+               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;
 
-       clone: function () {
+               this.specularMap = source.specularMap;
 
-               return new this.constructor().copy( this );
+               this.alphaMap = source.alphaMap;
 
-       },
+               this.envMap = source.envMap;
+               this.combine = source.combine;
+               this.reflectivity = source.reflectivity;
+               this.refractionRatio = source.refractionRatio;
 
-       copy: function ( source ) {
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
+               this.wireframeLinecap = source.wireframeLinecap;
+               this.wireframeLinejoin = source.wireframeLinejoin;
 
-               this.arcLengthDivisions = source.arcLengthDivisions;
+               this.flatShading = source.flatShading;
 
                return this;
 
-       },
+       }
 
-       toJSON: function () {
+}
 
-               const data = {
-                       metadata: {
-                               version: 4.5,
-                               type: 'Curve',
-                               generator: 'Curve.toJSON'
-                       }
-               };
+MeshPhongMaterial.prototype.isMeshPhongMaterial = true;
 
-               data.arcLengthDivisions = this.arcLengthDivisions;
-               data.type = this.type;
-
-               return data;
+/**
+ * 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>,
+ *
+ * }
+ */
 
-       },
+class MeshToonMaterial extends Material {
 
-       fromJSON: function ( json ) {
+       constructor( parameters ) {
 
-               this.arcLengthDivisions = json.arcLengthDivisions;
+               super();
 
-               return this;
+               this.defines = { 'TOON': '' };
 
-       }
+               this.type = 'MeshToonMaterial';
 
-} );
+               this.color = new Color( 0xffffff );
 
-function EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
+               this.map = null;
+               this.gradientMap = null;
 
-       Curve.call( this );
+               this.lightMap = null;
+               this.lightMapIntensity = 1.0;
 
-       this.type = 'EllipseCurve';
+               this.aoMap = null;
+               this.aoMapIntensity = 1.0;
 
-       this.aX = aX || 0;
-       this.aY = aY || 0;
+               this.emissive = new Color( 0x000000 );
+               this.emissiveIntensity = 1.0;
+               this.emissiveMap = null;
 
-       this.xRadius = xRadius || 1;
-       this.yRadius = yRadius || 1;
+               this.bumpMap = null;
+               this.bumpScale = 1;
 
-       this.aStartAngle = aStartAngle || 0;
-       this.aEndAngle = aEndAngle || 2 * Math.PI;
+               this.normalMap = null;
+               this.normalMapType = TangentSpaceNormalMap;
+               this.normalScale = new Vector2( 1, 1 );
 
-       this.aClockwise = aClockwise || false;
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-       this.aRotation = aRotation || 0;
+               this.alphaMap = null;
 
-}
+               this.wireframe = false;
+               this.wireframeLinewidth = 1;
+               this.wireframeLinecap = 'round';
+               this.wireframeLinejoin = 'round';
 
-EllipseCurve.prototype = Object.create( Curve.prototype );
-EllipseCurve.prototype.constructor = EllipseCurve;
+               this.setValues( parameters );
 
-EllipseCurve.prototype.isEllipseCurve = true;
+       }
 
-EllipseCurve.prototype.getPoint = function ( t, optionalTarget ) {
+       copy( source ) {
 
-       const point = optionalTarget || new Vector2();
+               super.copy( source );
 
-       const twoPi = Math.PI * 2;
-       let deltaAngle = this.aEndAngle - this.aStartAngle;
-       const samePoints = Math.abs( deltaAngle ) < Number.EPSILON;
+               this.color.copy( source.color );
 
-       // ensures that deltaAngle is 0 .. 2 PI
-       while ( deltaAngle < 0 ) deltaAngle += twoPi;
-       while ( deltaAngle > twoPi ) deltaAngle -= twoPi;
+               this.map = source.map;
+               this.gradientMap = source.gradientMap;
 
-       if ( deltaAngle < Number.EPSILON ) {
+               this.lightMap = source.lightMap;
+               this.lightMapIntensity = source.lightMapIntensity;
 
-               if ( samePoints ) {
+               this.aoMap = source.aoMap;
+               this.aoMapIntensity = source.aoMapIntensity;
 
-                       deltaAngle = 0;
+               this.emissive.copy( source.emissive );
+               this.emissiveMap = source.emissiveMap;
+               this.emissiveIntensity = source.emissiveIntensity;
 
-               } else {
+               this.bumpMap = source.bumpMap;
+               this.bumpScale = source.bumpScale;
 
-                       deltaAngle = twoPi;
+               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;
 
-       if ( this.aClockwise === true && ! samePoints ) {
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
+               this.wireframeLinecap = source.wireframeLinecap;
+               this.wireframeLinejoin = source.wireframeLinejoin;
 
-               if ( deltaAngle === twoPi ) {
+               return this;
 
-                       deltaAngle = - twoPi;
+       }
 
-               } else {
+}
 
-                       deltaAngle = deltaAngle - twoPi;
+MeshToonMaterial.prototype.isMeshToonMaterial = true;
 
-               }
+/**
+ * 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>
+ *
+ *  flatShading: <bool>
+ * }
+ */
 
-       }
+class MeshNormalMaterial extends Material {
 
-       const angle = this.aStartAngle + t * deltaAngle;
-       let x = this.aX + this.xRadius * Math.cos( angle );
-       let y = this.aY + this.yRadius * Math.sin( angle );
+       constructor( parameters ) {
 
-       if ( this.aRotation !== 0 ) {
+               super();
 
-               const cos = Math.cos( this.aRotation );
-               const sin = Math.sin( this.aRotation );
+               this.type = 'MeshNormalMaterial';
 
-               const tx = x - this.aX;
-               const ty = y - this.aY;
+               this.bumpMap = null;
+               this.bumpScale = 1;
 
-               // Rotate the point about the center of the ellipse.
-               x = tx * cos - ty * sin + this.aX;
-               y = tx * sin + ty * cos + this.aY;
+               this.normalMap = null;
+               this.normalMapType = TangentSpaceNormalMap;
+               this.normalScale = new Vector2( 1, 1 );
 
-       }
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-       return point.set( x, y );
+               this.wireframe = false;
+               this.wireframeLinewidth = 1;
 
-};
+               this.fog = false;
 
-EllipseCurve.prototype.copy = function ( source ) {
+               this.flatShading = false;
 
-       Curve.prototype.copy.call( this, source );
+               this.setValues( parameters );
 
-       this.aX = source.aX;
-       this.aY = source.aY;
+       }
 
-       this.xRadius = source.xRadius;
-       this.yRadius = source.yRadius;
+       copy( source ) {
 
-       this.aStartAngle = source.aStartAngle;
-       this.aEndAngle = source.aEndAngle;
+               super.copy( source );
 
-       this.aClockwise = source.aClockwise;
+               this.bumpMap = source.bumpMap;
+               this.bumpScale = source.bumpScale;
 
-       this.aRotation = source.aRotation;
+               this.normalMap = source.normalMap;
+               this.normalMapType = source.normalMapType;
+               this.normalScale.copy( source.normalScale );
 
-       return this;
+               this.displacementMap = source.displacementMap;
+               this.displacementScale = source.displacementScale;
+               this.displacementBias = source.displacementBias;
 
-};
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
 
+               this.flatShading = source.flatShading;
 
-EllipseCurve.prototype.toJSON = function () {
+               return this;
 
-       const data = Curve.prototype.toJSON.call( this );
+       }
 
-       data.aX = this.aX;
-       data.aY = this.aY;
+}
 
-       data.xRadius = this.xRadius;
-       data.yRadius = this.yRadius;
+MeshNormalMaterial.prototype.isMeshNormalMaterial = true;
 
-       data.aStartAngle = this.aStartAngle;
-       data.aEndAngle = this.aEndAngle;
+/**
+ * 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>,
+ *
+ * }
+ */
 
-       data.aClockwise = this.aClockwise;
+class MeshLambertMaterial extends Material {
 
-       data.aRotation = this.aRotation;
+       constructor( parameters ) {
 
-       return data;
+               super();
 
-};
+               this.type = 'MeshLambertMaterial';
 
-EllipseCurve.prototype.fromJSON = function ( json ) {
+               this.color = new Color( 0xffffff ); // diffuse
 
-       Curve.prototype.fromJSON.call( this, json );
+               this.map = null;
 
-       this.aX = json.aX;
-       this.aY = json.aY;
+               this.lightMap = null;
+               this.lightMapIntensity = 1.0;
 
-       this.xRadius = json.xRadius;
-       this.yRadius = json.yRadius;
+               this.aoMap = null;
+               this.aoMapIntensity = 1.0;
 
-       this.aStartAngle = json.aStartAngle;
-       this.aEndAngle = json.aEndAngle;
+               this.emissive = new Color( 0x000000 );
+               this.emissiveIntensity = 1.0;
+               this.emissiveMap = null;
 
-       this.aClockwise = json.aClockwise;
+               this.specularMap = null;
 
-       this.aRotation = json.aRotation;
+               this.alphaMap = null;
 
-       return this;
+               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';
 
-function ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+               this.setValues( parameters );
 
-       EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
+       }
 
-       this.type = 'ArcCurve';
+       copy( source ) {
 
-}
+               super.copy( source );
 
-ArcCurve.prototype = Object.create( EllipseCurve.prototype );
-ArcCurve.prototype.constructor = ArcCurve;
+               this.color.copy( source.color );
 
-ArcCurve.prototype.isArcCurve = true;
+               this.map = source.map;
 
-/**
- * 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
- */
+               this.lightMap = source.lightMap;
+               this.lightMapIntensity = source.lightMapIntensity;
 
+               this.aoMap = source.aoMap;
+               this.aoMapIntensity = source.aoMapIntensity;
 
-/*
-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.emissive.copy( source.emissive );
+               this.emissiveMap = source.emissiveMap;
+               this.emissiveIntensity = source.emissiveIntensity;
 
-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.
-*/
+               this.specularMap = source.specularMap;
 
-function CubicPoly() {
+               this.alphaMap = source.alphaMap;
 
-       let c0 = 0, c1 = 0, c2 = 0, c3 = 0;
+               this.envMap = source.envMap;
+               this.combine = source.combine;
+               this.reflectivity = source.reflectivity;
+               this.refractionRatio = source.refractionRatio;
 
-       /*
-        * 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 ) {
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
+               this.wireframeLinecap = source.wireframeLinecap;
+               this.wireframeLinejoin = source.wireframeLinejoin;
 
-               c0 = x0;
-               c1 = t0;
-               c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1;
-               c3 = 2 * x0 - 2 * x1 + t0 + t1;
+               return this;
 
        }
 
-       return {
+}
 
-               initCatmullRom: function ( x0, x1, x2, x3, tension ) {
+MeshLambertMaterial.prototype.isMeshLambertMaterial = true;
 
-                       init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );
+/**
+ * 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> ),
+ *
+ *  flatShading: <bool>
+ * }
+ */
 
-               },
+class MeshMatcapMaterial extends Material {
 
-               initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) {
+       constructor( parameters ) {
 
-                       // 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;
+               super();
 
-                       // rescale tangents for parametrization in [0,1]
-                       t1 *= dt1;
-                       t2 *= dt1;
+               this.defines = { 'MATCAP': '' };
 
-                       init( x1, x2, t1, t2 );
+               this.type = 'MeshMatcapMaterial';
 
-               },
+               this.color = new Color( 0xffffff ); // diffuse
 
-               calc: function ( t ) {
+               this.matcap = null;
 
-                       const t2 = t * t;
-                       const t3 = t2 * t;
-                       return c0 + c1 * t + c2 * t2 + c3 * t3;
+               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;
 
-const tmp = new Vector3();
-const px = new CubicPoly(), py = new CubicPoly(), pz = new CubicPoly();
+               this.flatShading = false;
 
-function CatmullRomCurve3( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) {
+               this.setValues( parameters );
 
-       Curve.call( this );
+       }
 
-       this.type = 'CatmullRomCurve3';
 
-       this.points = points;
-       this.closed = closed;
-       this.curveType = curveType;
-       this.tension = tension;
+       copy( source ) {
 
-}
+               super.copy( source );
 
-CatmullRomCurve3.prototype = Object.create( Curve.prototype );
-CatmullRomCurve3.prototype.constructor = CatmullRomCurve3;
+               this.defines = { 'MATCAP': '' };
 
-CatmullRomCurve3.prototype.isCatmullRomCurve3 = true;
+               this.color.copy( source.color );
 
-CatmullRomCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+               this.matcap = source.matcap;
 
-       const point = optionalTarget;
+               this.map = source.map;
 
-       const points = this.points;
-       const l = points.length;
+               this.bumpMap = source.bumpMap;
+               this.bumpScale = source.bumpScale;
 
-       const p = ( l - ( this.closed ? 0 : 1 ) ) * t;
-       let intPoint = Math.floor( p );
-       let weight = p - intPoint;
+               this.normalMap = source.normalMap;
+               this.normalMapType = source.normalMapType;
+               this.normalScale.copy( source.normalScale );
 
-       if ( this.closed ) {
+               this.displacementMap = source.displacementMap;
+               this.displacementScale = source.displacementScale;
+               this.displacementBias = source.displacementBias;
 
-               intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l;
+               this.alphaMap = source.alphaMap;
 
-       } else if ( weight === 0 && intPoint === l - 1 ) {
+               this.flatShading = source.flatShading;
 
-               intPoint = l - 2;
-               weight = 1;
+               return this;
 
        }
 
-       let p0, p3; // 4 points (p1 & p2 defined below)
-
-       if ( this.closed || intPoint > 0 ) {
-
-               p0 = points[ ( intPoint - 1 ) % l ];
+}
 
-       } else {
+MeshMatcapMaterial.prototype.isMeshMatcapMaterial = true;
 
-               // extrapolate first point
-               tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
-               p0 = tmp;
+/**
+ * parameters = {
+ *  color: <hex>,
+ *  opacity: <float>,
+ *
+ *  linewidth: <float>,
+ *
+ *  scale: <float>,
+ *  dashSize: <float>,
+ *  gapSize: <float>
+ * }
+ */
 
-       }
+class LineDashedMaterial extends LineBasicMaterial {
 
-       const p1 = points[ intPoint % l ];
-       const p2 = points[ ( intPoint + 1 ) % l ];
+       constructor( parameters ) {
 
-       if ( this.closed || intPoint + 2 < l ) {
+               super();
 
-               p3 = points[ ( intPoint + 2 ) % l ];
+               this.type = 'LineDashedMaterial';
 
-       } else {
+               this.scale = 1;
+               this.dashSize = 3;
+               this.gapSize = 1;
 
-               // extrapolate last point
-               tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] );
-               p3 = tmp;
+               this.setValues( parameters );
 
        }
 
-       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;
+       copy( source ) {
 
-               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 );
+               super.copy( source );
 
-       } else if ( this.curveType === 'catmullrom' ) {
+               this.scale = source.scale;
+               this.dashSize = source.dashSize;
+               this.gapSize = source.gapSize;
 
-               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 );
+               return this;
 
        }
 
-       point.set(
-               px.calc( weight ),
-               py.calc( weight ),
-               pz.calc( weight )
-       );
-
-       return point;
+}
 
-};
+LineDashedMaterial.prototype.isLineDashedMaterial = true;
 
-CatmullRomCurve3.prototype.copy = function ( source ) {
+const AnimationUtils = {
 
-       Curve.prototype.copy.call( this, source );
+       // same as Array.prototype.slice, but also works on typed arrays
+       arraySlice: function ( array, from, to ) {
 
-       this.points = [];
+               if ( AnimationUtils.isTypedArray( array ) ) {
 
-       for ( let i = 0, l = source.points.length; i < l; i ++ ) {
+                       // 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 ) );
 
-               const point = source.points[ i ];
+               }
 
-               this.points.push( point.clone() );
+               return array.slice( from, to );
 
-       }
+       },
 
-       this.closed = source.closed;
-       this.curveType = source.curveType;
-       this.tension = source.tension;
+       // converts an array to a specific type
+       convertArray: function ( array, type, forceClone ) {
 
-       return this;
+               if ( ! array || // let 'undefined' and 'null' pass
+                       ! forceClone && array.constructor === type ) return array;
 
-};
+               if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {
 
-CatmullRomCurve3.prototype.toJSON = function () {
+                       return new type( array ); // create typed array
 
-       const data = Curve.prototype.toJSON.call( this );
+               }
 
-       data.points = [];
+               return Array.prototype.slice.call( array ); // create Array
 
-       for ( let i = 0, l = this.points.length; i < l; i ++ ) {
+       },
 
-               const point = this.points[ i ];
-               data.points.push( point.toArray() );
+       isTypedArray: function ( object ) {
 
-       }
+               return ArrayBuffer.isView( object ) &&
+                       ! ( object instanceof DataView );
 
-       data.closed = this.closed;
-       data.curveType = this.curveType;
-       data.tension = this.tension;
+       },
 
-       return data;
+       // returns an array by which times and values can be sorted
+       getKeyframeOrder: function ( times ) {
 
-};
+               function compareTime( i, j ) {
 
-CatmullRomCurve3.prototype.fromJSON = function ( json ) {
+                       return times[ i ] - times[ j ];
 
-       Curve.prototype.fromJSON.call( this, json );
+               }
 
-       this.points = [];
+               const n = times.length;
+               const result = new Array( n );
+               for ( let i = 0; i !== n; ++ i ) result[ i ] = i;
 
-       for ( let i = 0, l = json.points.length; i < l; i ++ ) {
+               result.sort( compareTime );
 
-               const point = json.points[ i ];
-               this.points.push( new Vector3().fromArray( point ) );
+               return result;
 
-       }
+       },
 
-       this.closed = json.closed;
-       this.curveType = json.curveType;
-       this.tension = json.tension;
+       // uses the array previously returned by 'getKeyframeOrder' to sort data
+       sortedArray: function ( values, stride, order ) {
 
-       return this;
+               const nValues = values.length;
+               const result = new values.constructor( nValues );
 
-};
+               for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {
 
-/**
- * Bezier Curves formulas obtained from
- * http://en.wikipedia.org/wiki/Bézier_curve
- */
+                       const srcOffset = order[ i ] * stride;
 
-function CatmullRom( t, p0, p1, p2, p3 ) {
+                       for ( let j = 0; j !== stride; ++ j ) {
 
-       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;
+                               result[ dstOffset ++ ] = values[ srcOffset + j ];
 
-}
+                       }
 
-//
+               }
 
-function QuadraticBezierP0( t, p ) {
+               return result;
 
-       const k = 1 - t;
-       return k * k * p;
+       },
 
-}
+       // function for parsing AOS keyframe formats
+       flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {
 
-function QuadraticBezierP1( t, p ) {
+               let i = 1, key = jsonKeys[ 0 ];
 
-       return 2 * ( 1 - t ) * t * p;
+               while ( key !== undefined && key[ valuePropertyName ] === undefined ) {
 
-}
+                       key = jsonKeys[ i ++ ];
 
-function QuadraticBezierP2( t, p ) {
+               }
 
-       return t * t * p;
+               if ( key === undefined ) return; // no data
 
-}
+               let value = key[ valuePropertyName ];
+               if ( value === undefined ) return; // no data
 
-function QuadraticBezier( t, p0, p1, p2 ) {
+               if ( Array.isArray( value ) ) {
 
-       return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) +
-               QuadraticBezierP2( t, p2 );
+                       do {
 
-}
+                               value = key[ valuePropertyName ];
 
-//
+                               if ( value !== undefined ) {
 
-function CubicBezierP0( t, p ) {
+                                       times.push( key.time );
+                                       values.push.apply( values, value ); // push all elements
 
-       const k = 1 - t;
-       return k * k * k * p;
+                               }
 
-}
+                               key = jsonKeys[ i ++ ];
 
-function CubicBezierP1( t, p ) {
+                       } while ( key !== undefined );
 
-       const k = 1 - t;
-       return 3 * k * k * t * p;
+               } else if ( value.toArray !== undefined ) {
 
-}
+                       // ...assume THREE.Math-ish
 
-function CubicBezierP2( t, p ) {
+                       do {
 
-       return 3 * ( 1 - t ) * t * t * p;
+                               value = key[ valuePropertyName ];
 
-}
+                               if ( value !== undefined ) {
 
-function CubicBezierP3( t, p ) {
+                                       times.push( key.time );
+                                       value.toArray( values, values.length );
 
-       return t * t * t * p;
+                               }
 
-}
+                               key = jsonKeys[ i ++ ];
 
-function CubicBezier( t, p0, p1, p2, p3 ) {
+                       } while ( key !== undefined );
 
-       return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) +
-               CubicBezierP3( t, p3 );
+               } else {
 
-}
+                       // otherwise push as-is
 
-function CubicBezierCurve( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) {
+                       do {
 
-       Curve.call( this );
+                               value = key[ valuePropertyName ];
 
-       this.type = 'CubicBezierCurve';
+                               if ( value !== undefined ) {
 
-       this.v0 = v0;
-       this.v1 = v1;
-       this.v2 = v2;
-       this.v3 = v3;
+                                       times.push( key.time );
+                                       values.push( value );
 
-}
+                               }
 
-CubicBezierCurve.prototype = Object.create( Curve.prototype );
-CubicBezierCurve.prototype.constructor = CubicBezierCurve;
+                               key = jsonKeys[ i ++ ];
 
-CubicBezierCurve.prototype.isCubicBezierCurve = true;
+                       } while ( key !== undefined );
 
-CubicBezierCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+               }
 
-       const point = optionalTarget;
+       },
 
-       const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
+       subclip: function ( sourceClip, name, startFrame, endFrame, fps = 30 ) {
 
-       point.set(
-               CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),
-               CubicBezier( t, v0.y, v1.y, v2.y, v3.y )
-       );
+               const clip = sourceClip.clone();
 
-       return point;
+               clip.name = name;
 
-};
+               const tracks = [];
 
-CubicBezierCurve.prototype.copy = function ( source ) {
+               for ( let i = 0; i < clip.tracks.length; ++ i ) {
 
-       Curve.prototype.copy.call( this, source );
+                       const track = clip.tracks[ i ];
+                       const valueSize = track.getValueSize();
 
-       this.v0.copy( source.v0 );
-       this.v1.copy( source.v1 );
-       this.v2.copy( source.v2 );
-       this.v3.copy( source.v3 );
+                       const times = [];
+                       const values = [];
 
-       return this;
+                       for ( let j = 0; j < track.times.length; ++ j ) {
 
-};
+                               const frame = track.times[ j ] * fps;
 
-CubicBezierCurve.prototype.toJSON = function () {
+                               if ( frame < startFrame || frame >= endFrame ) continue;
 
-       const data = Curve.prototype.toJSON.call( this );
+                               times.push( track.times[ j ] );
 
-       data.v0 = this.v0.toArray();
-       data.v1 = this.v1.toArray();
-       data.v2 = this.v2.toArray();
-       data.v3 = this.v3.toArray();
+                               for ( let k = 0; k < valueSize; ++ k ) {
 
-       return data;
+                                       values.push( track.values[ j * valueSize + k ] );
 
-};
+                               }
 
-CubicBezierCurve.prototype.fromJSON = function ( json ) {
+                       }
 
-       Curve.prototype.fromJSON.call( this, json );
+                       if ( times.length === 0 ) continue;
 
-       this.v0.fromArray( json.v0 );
-       this.v1.fromArray( json.v1 );
-       this.v2.fromArray( json.v2 );
-       this.v3.fromArray( json.v3 );
+                       track.times = AnimationUtils.convertArray( times, track.times.constructor );
+                       track.values = AnimationUtils.convertArray( values, track.values.constructor );
 
-       return this;
+                       tracks.push( track );
 
-};
+               }
 
-function CubicBezierCurve3( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) {
+               clip.tracks = tracks;
 
-       Curve.call( this );
+               // find minimum .times value across all tracks in the trimmed clip
 
-       this.type = 'CubicBezierCurve3';
+               let minStartTime = Infinity;
 
-       this.v0 = v0;
-       this.v1 = v1;
-       this.v2 = v2;
-       this.v3 = v3;
+               for ( let i = 0; i < clip.tracks.length; ++ i ) {
 
-}
+                       if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) {
 
-CubicBezierCurve3.prototype = Object.create( Curve.prototype );
-CubicBezierCurve3.prototype.constructor = CubicBezierCurve3;
+                               minStartTime = clip.tracks[ i ].times[ 0 ];
 
-CubicBezierCurve3.prototype.isCubicBezierCurve3 = true;
+                       }
 
-CubicBezierCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+               }
 
-       const point = optionalTarget;
+               // shift all tracks such that clip begins at t=0
 
-       const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
+               for ( let i = 0; i < clip.tracks.length; ++ i ) {
 
-       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 )
-       );
+                       clip.tracks[ i ].shift( - 1 * minStartTime );
 
-       return point;
+               }
 
-};
+               clip.resetDuration();
 
-CubicBezierCurve3.prototype.copy = function ( source ) {
+               return clip;
 
-       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 );
+       makeClipAdditive: function ( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) {
 
-       return this;
+               if ( fps <= 0 ) fps = 30;
 
-};
+               const numTracks = referenceClip.tracks.length;
+               const referenceTime = referenceFrame / fps;
 
-CubicBezierCurve3.prototype.toJSON = function () {
+               // Make each track's values relative to the values at the reference frame
+               for ( let i = 0; i < numTracks; ++ i ) {
 
-       const data = Curve.prototype.toJSON.call( this );
+                       const referenceTrack = referenceClip.tracks[ i ];
+                       const referenceTrackType = referenceTrack.ValueTypeName;
 
-       data.v0 = this.v0.toArray();
-       data.v1 = this.v1.toArray();
-       data.v2 = this.v2.toArray();
-       data.v3 = this.v3.toArray();
+                       // Skip this track if it's non-numeric
+                       if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue;
 
-       return data;
+                       // 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;
 
-CubicBezierCurve3.prototype.fromJSON = function ( json ) {
+                       } );
 
-       Curve.prototype.fromJSON.call( this, json );
+                       if ( targetTrack === undefined ) continue;
 
-       this.v0.fromArray( json.v0 );
-       this.v1.fromArray( json.v1 );
-       this.v2.fromArray( json.v2 );
-       this.v3.fromArray( json.v3 );
+                       let referenceOffset = 0;
+                       const referenceValueSize = referenceTrack.getValueSize();
 
-       return this;
+                       if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
 
-};
+                               referenceOffset = referenceValueSize / 3;
 
-function LineCurve( v1 = new Vector2(), v2 = new Vector2() ) {
+                       }
 
-       Curve.call( this );
+                       let targetOffset = 0;
+                       const targetValueSize = targetTrack.getValueSize();
 
-       this.type = 'LineCurve';
+                       if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
 
-       this.v1 = v1;
-       this.v2 = v2;
+                               targetOffset = targetValueSize / 3;
 
-}
+                       }
 
-LineCurve.prototype = Object.create( Curve.prototype );
-LineCurve.prototype.constructor = LineCurve;
+                       const lastIndex = referenceTrack.times.length - 1;
+                       let referenceValue;
 
-LineCurve.prototype.isLineCurve = true;
+                       // Find the value to subtract out of the track
+                       if ( referenceTime <= referenceTrack.times[ 0 ] ) {
 
-LineCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+                               // 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 );
 
-       const point = optionalTarget;
+                       } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) {
 
-       if ( t === 1 ) {
+                               // 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 );
 
-               point.copy( this.v2 );
+                       } else {
 
-       } 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 );
 
-               point.copy( this.v2 ).sub( this.v1 );
-               point.multiplyScalar( t ).add( this.v1 );
+                       }
 
-       }
+                       // Conjugate the quaternion
+                       if ( referenceTrackType === 'quaternion' ) {
 
-       return point;
+                               const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate();
+                               referenceQuat.toArray( referenceValue );
 
-};
+                       }
 
-// Line curve is linear, so we can overwrite default getPointAt
+                       // Subtract the reference value from all of the track values
 
-LineCurve.prototype.getPointAt = function ( u, optionalTarget ) {
-
-       return this.getPoint( u, optionalTarget );
-
-};
+                       const numTimes = targetTrack.times.length;
+                       for ( let j = 0; j < numTimes; ++ j ) {
 
-LineCurve.prototype.getTangent = function ( t, optionalTarget ) {
+                               const valueStart = j * targetValueSize + targetOffset;
 
-       const tangent = optionalTarget || new Vector2();
+                               if ( referenceTrackType === 'quaternion' ) {
 
-       tangent.copy( this.v2 ).sub( this.v1 ).normalize();
+                                       // Multiply the conjugate for quaternion track types
+                                       Quaternion.multiplyQuaternionsFlat(
+                                               targetTrack.values,
+                                               valueStart,
+                                               referenceValue,
+                                               0,
+                                               targetTrack.values,
+                                               valueStart
+                                       );
 
-       return tangent;
+                               } else {
 
-};
+                                       const valueEnd = targetValueSize - targetOffset * 2;
 
-LineCurve.prototype.copy = function ( source ) {
+                                       // Subtract each value for all other numeric track types
+                                       for ( let k = 0; k < valueEnd; ++ k ) {
 
-       Curve.prototype.copy.call( this, source );
+                                               targetTrack.values[ valueStart + k ] -= referenceValue[ k ];
 
-       this.v1.copy( source.v1 );
-       this.v2.copy( source.v2 );
+                                       }
 
-       return this;
+                               }
 
-};
+                       }
 
-LineCurve.prototype.toJSON = function () {
+               }
 
-       const data = Curve.prototype.toJSON.call( this );
+               targetClip.blendMode = AdditiveAnimationBlendMode;
 
-       data.v1 = this.v1.toArray();
-       data.v2 = this.v2.toArray();
+               return targetClip;
 
-       return data;
+       }
 
 };
 
-LineCurve.prototype.fromJSON = function ( json ) {
+/**
+ * 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
+ *
+ */
 
-       Curve.prototype.fromJSON.call( this, json );
+class Interpolant {
 
-       this.v1.fromArray( json.v1 );
-       this.v2.fromArray( json.v2 );
+       constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
 
-       return this;
+               this.parameterPositions = parameterPositions;
+               this._cachedIndex = 0;
 
-};
+               this.resultBuffer = resultBuffer !== undefined ?
+                       resultBuffer : new sampleValues.constructor( sampleSize );
+               this.sampleValues = sampleValues;
+               this.valueSize = sampleSize;
 
-function LineCurve3( v1 = new Vector3(), v2 = new Vector3() ) {
+               this.settings = null;
+               this.DefaultSettings_ = {};
 
-       Curve.call( this );
+       }
 
-       this.type = 'LineCurve3';
+       evaluate( t ) {
 
-       this.v1 = v1;
-       this.v2 = v2;
+               const pp = this.parameterPositions;
+               let i1 = this._cachedIndex,
+                       t1 = pp[ i1 ],
+                       t0 = pp[ i1 - 1 ];
 
-}
+               validate_interval: {
 
-LineCurve3.prototype = Object.create( Curve.prototype );
-LineCurve3.prototype.constructor = LineCurve3;
+                       seek: {
 
-LineCurve3.prototype.isLineCurve3 = true;
+                               let right;
 
-LineCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+                               linear_scan: {
 
-       const point = optionalTarget;
+                                       //- See http://jsperf.com/comparison-to-undefined/3
+                                       //- slower code:
+                                       //-
+                                       //-                             if ( t >= t1 || t1 === undefined ) {
+                                       forward_scan: if ( ! ( t < t1 ) ) {
 
-       if ( t === 1 ) {
+                                               for ( let giveUpAt = i1 + 2; ; ) {
 
-               point.copy( this.v2 );
+                                                       if ( t1 === undefined ) {
 
-       } else {
+                                                               if ( t < t0 ) break forward_scan;
 
-               point.copy( this.v2 ).sub( this.v1 );
-               point.multiplyScalar( t ).add( this.v1 );
+                                                               // after end
 
-       }
+                                                               i1 = pp.length;
+                                                               this._cachedIndex = i1;
+                                                               return this.afterEnd_( i1 - 1, t, t0 );
 
-       return point;
+                                                       }
 
-};
+                                                       if ( i1 === giveUpAt ) break; // this loop
 
-// Line curve is linear, so we can overwrite default getPointAt
+                                                       t0 = t1;
+                                                       t1 = pp[ ++ i1 ];
 
-LineCurve3.prototype.getPointAt = function ( u, optionalTarget ) {
+                                                       if ( t < t1 ) {
 
-       return this.getPoint( u, optionalTarget );
+                                                               // we have arrived at the sought interval
+                                                               break seek;
 
-};
+                                                       }
 
-LineCurve3.prototype.copy = function ( source ) {
+                                               }
 
-       Curve.prototype.copy.call( this, source );
+                                               // prepare binary search on the right side of the index
+                                               right = pp.length;
+                                               break linear_scan;
 
-       this.v1.copy( source.v1 );
-       this.v2.copy( source.v2 );
+                                       }
 
-       return this;
+                                       //- slower code:
+                                       //-                                     if ( t < t0 || t0 === undefined ) {
+                                       if ( ! ( t >= t0 ) ) {
 
-};
+                                               // looping?
 
-LineCurve3.prototype.toJSON = function () {
+                                               const t1global = pp[ 1 ];
 
-       const data = Curve.prototype.toJSON.call( this );
+                                               if ( t < t1global ) {
 
-       data.v1 = this.v1.toArray();
-       data.v2 = this.v2.toArray();
+                                                       i1 = 2; // + 1, using the scan for the details
+                                                       t0 = t1global;
 
-       return data;
+                                               }
 
-};
+                                               // linear reverse scan
 
-LineCurve3.prototype.fromJSON = function ( json ) {
+                                               for ( let giveUpAt = i1 - 2; ; ) {
 
-       Curve.prototype.fromJSON.call( this, json );
+                                                       if ( t0 === undefined ) {
 
-       this.v1.fromArray( json.v1 );
-       this.v2.fromArray( json.v2 );
+                                                               // before start
 
-       return this;
+                                                               this._cachedIndex = 0;
+                                                               return this.beforeStart_( 0, t, t1 );
 
-};
+                                                       }
 
-function QuadraticBezierCurve( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) {
+                                                       if ( i1 === giveUpAt ) break; // this loop
 
-       Curve.call( this );
+                                                       t1 = t0;
+                                                       t0 = pp[ -- i1 - 1 ];
 
-       this.type = 'QuadraticBezierCurve';
+                                                       if ( t >= t0 ) {
 
-       this.v0 = v0;
-       this.v1 = v1;
-       this.v2 = v2;
+                                                               // we have arrived at the sought interval
+                                                               break seek;
 
-}
+                                                       }
 
-QuadraticBezierCurve.prototype = Object.create( Curve.prototype );
-QuadraticBezierCurve.prototype.constructor = QuadraticBezierCurve;
+                                               }
 
-QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true;
+                                               // prepare binary search on the left side of the index
+                                               right = i1;
+                                               i1 = 0;
+                                               break linear_scan;
 
-QuadraticBezierCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+                                       }
 
-       const point = optionalTarget;
+                                       // the interval is valid
 
-       const v0 = this.v0, v1 = this.v1, v2 = this.v2;
+                                       break validate_interval;
 
-       point.set(
-               QuadraticBezier( t, v0.x, v1.x, v2.x ),
-               QuadraticBezier( t, v0.y, v1.y, v2.y )
-       );
+                               } // linear scan
 
-       return point;
+                               // binary search
 
-};
+                               while ( i1 < right ) {
 
-QuadraticBezierCurve.prototype.copy = function ( source ) {
+                                       const mid = ( i1 + right ) >>> 1;
 
-       Curve.prototype.copy.call( this, source );
+                                       if ( t < pp[ mid ] ) {
 
-       this.v0.copy( source.v0 );
-       this.v1.copy( source.v1 );
-       this.v2.copy( source.v2 );
+                                               right = mid;
 
-       return this;
+                                       } else {
 
-};
+                                               i1 = mid + 1;
 
-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();
+                               t1 = pp[ i1 ];
+                               t0 = pp[ i1 - 1 ];
 
-       return data;
+                               // check boundary cases, again
 
-};
+                               if ( t0 === undefined ) {
 
-QuadraticBezierCurve.prototype.fromJSON = function ( json ) {
+                                       this._cachedIndex = 0;
+                                       return this.beforeStart_( 0, t, t1 );
 
-       Curve.prototype.fromJSON.call( this, json );
+                               }
 
-       this.v0.fromArray( json.v0 );
-       this.v1.fromArray( json.v1 );
-       this.v2.fromArray( json.v2 );
+                               if ( t1 === undefined ) {
 
-       return this;
+                                       i1 = pp.length;
+                                       this._cachedIndex = i1;
+                                       return this.afterEnd_( i1 - 1, t0, t );
 
-};
+                               }
 
-function QuadraticBezierCurve3( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) {
+                       } // seek
 
-       Curve.call( this );
+                       this._cachedIndex = i1;
 
-       this.type = 'QuadraticBezierCurve3';
+                       this.intervalChanged_( i1, t0, t1 );
 
-       this.v0 = v0;
-       this.v1 = v1;
-       this.v2 = v2;
+               } // validate_interval
 
-}
+               return this.interpolate_( i1, t0, t, t1 );
 
-QuadraticBezierCurve3.prototype = Object.create( Curve.prototype );
-QuadraticBezierCurve3.prototype.constructor = QuadraticBezierCurve3;
+       }
 
-QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true;
+       getSettings_() {
 
-QuadraticBezierCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+               return this.settings || this.DefaultSettings_;
 
-       const point = optionalTarget;
+       }
 
-       const v0 = this.v0, v1 = this.v1, v2 = this.v2;
+       copySampleValue_( index ) {
 
-       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 )
-       );
+               // copies a sample value to the result buffer
 
-       return point;
+               const result = this.resultBuffer,
+                       values = this.sampleValues,
+                       stride = this.valueSize,
+                       offset = index * stride;
 
-};
+               for ( let i = 0; i !== stride; ++ i ) {
 
-QuadraticBezierCurve3.prototype.copy = function ( source ) {
+                       result[ i ] = values[ offset + i ];
 
-       Curve.prototype.copy.call( this, source );
+               }
 
-       this.v0.copy( source.v0 );
-       this.v1.copy( source.v1 );
-       this.v2.copy( source.v2 );
+               return result;
 
-       return this;
+       }
 
-};
+       // Template methods for derived classes:
 
-QuadraticBezierCurve3.prototype.toJSON = function () {
+       interpolate_( /* i1, t0, t, t1 */ ) {
 
-       const data = Curve.prototype.toJSON.call( this );
+               throw new Error( 'call to abstract method' );
+               // implementations shall return this.resultBuffer
 
-       data.v0 = this.v0.toArray();
-       data.v1 = this.v1.toArray();
-       data.v2 = this.v2.toArray();
+       }
 
-       return data;
+       intervalChanged_( /* i1, t0, t1 */ ) {
 
-};
+               // empty
 
-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 );
+// ALIAS DEFINITIONS
 
-       return this;
+Interpolant.prototype.beforeStart_ = Interpolant.prototype.copySampleValue_;
+Interpolant.prototype.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 SplineCurve( points = [] ) {
+class CubicInterpolant extends Interpolant {
 
-       Curve.call( this );
+       constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
 
-       this.type = 'SplineCurve';
+               super( parameterPositions, sampleValues, sampleSize, resultBuffer );
 
-       this.points = points;
+               this._weightPrev = - 0;
+               this._offsetPrev = - 0;
+               this._weightNext = - 0;
+               this._offsetNext = - 0;
 
-}
+               this.DefaultSettings_ = {
 
-SplineCurve.prototype = Object.create( Curve.prototype );
-SplineCurve.prototype.constructor = SplineCurve;
+                       endingStart: ZeroCurvatureEnding,
+                       endingEnd: ZeroCurvatureEnding
 
-SplineCurve.prototype.isSplineCurve = true;
+               };
 
-SplineCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+       }
 
-       const point = optionalTarget;
+       intervalChanged_( i1, t0, t1 ) {
 
-       const points = this.points;
-       const p = ( points.length - 1 ) * t;
+               const pp = this.parameterPositions;
+               let iPrev = i1 - 2,
+                       iNext = i1 + 1,
 
-       const intPoint = Math.floor( p );
-       const weight = p - intPoint;
+                       tPrev = pp[ iPrev ],
+                       tNext = pp[ iNext ];
 
-       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 ];
+               if ( tPrev === undefined ) {
 
-       point.set(
-               CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ),
-               CatmullRom( weight, p0.y, p1.y, p2.y, p3.y )
-       );
+                       switch ( this.getSettings_().endingStart ) {
 
-       return point;
+                               case ZeroSlopeEnding:
 
-};
+                                       // f'(t0) = 0
+                                       iPrev = i1;
+                                       tPrev = 2 * t0 - t1;
 
-SplineCurve.prototype.copy = function ( source ) {
+                                       break;
 
-       Curve.prototype.copy.call( this, source );
+                               case WrapAroundEnding:
 
-       this.points = [];
+                                       // use the other end of the curve
+                                       iPrev = pp.length - 2;
+                                       tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ];
 
-       for ( let i = 0, l = source.points.length; i < l; i ++ ) {
+                                       break;
 
-               const point = source.points[ i ];
+                               default: // ZeroCurvatureEnding
 
-               this.points.push( point.clone() );
+                                       // f''(t0) = 0 a.k.a. Natural Spline
+                                       iPrev = i1;
+                                       tPrev = t1;
 
-       }
+                       }
 
-       return this;
+               }
 
-};
+               if ( tNext === undefined ) {
 
-SplineCurve.prototype.toJSON = function () {
+                       switch ( this.getSettings_().endingEnd ) {
 
-       const data = Curve.prototype.toJSON.call( this );
+                               case ZeroSlopeEnding:
 
-       data.points = [];
+                                       // f'(tN) = 0
+                                       iNext = i1;
+                                       tNext = 2 * t1 - t0;
 
-       for ( let i = 0, l = this.points.length; i < l; i ++ ) {
+                                       break;
 
-               const point = this.points[ i ];
-               data.points.push( point.toArray() );
+                               case WrapAroundEnding:
 
-       }
+                                       // use the other end of the curve
+                                       iNext = 1;
+                                       tNext = t1 + pp[ 1 ] - pp[ 0 ];
 
-       return data;
+                                       break;
 
-};
+                               default: // ZeroCurvatureEnding
 
-SplineCurve.prototype.fromJSON = function ( json ) {
+                                       // f''(tN) = 0, a.k.a. Natural Spline
+                                       iNext = i1 - 1;
+                                       tNext = t0;
 
-       Curve.prototype.fromJSON.call( this, json );
+                       }
 
-       this.points = [];
+               }
 
-       for ( let i = 0, l = json.points.length; i < l; i ++ ) {
+               const halfDt = ( t1 - t0 ) * 0.5,
+                       stride = this.valueSize;
 
-               const point = json.points[ i ];
-               this.points.push( new Vector2().fromArray( point ) );
+               this._weightPrev = halfDt / ( t0 - tPrev );
+               this._weightNext = halfDt / ( tNext - t1 );
+               this._offsetPrev = iPrev * stride;
+               this._offsetNext = iNext * stride;
 
        }
 
-       return this;
+       interpolate_( i1, t0, t, t1 ) {
 
-};
+               const result = this.resultBuffer,
+                       values = this.sampleValues,
+                       stride = this.valueSize,
 
-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
-});
+                       o1 = i1 * stride,               o0 = o1 - stride,
+                       oP = this._offsetPrev,  oN = this._offsetNext,
+                       wP = this._weightPrev,  wN = this._weightNext,
 
-/**************************************************************
- *     Curved Path - a curve path is simply a array of connected
- *  curves, but retains the api of a curve
- **************************************************************/
+                       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 ) {
 
-function CurvePath() {
+                       result[ i ] =
+                                       sP * values[ oP + i ] +
+                                       s0 * values[ o0 + i ] +
+                                       s1 * values[ o1 + i ] +
+                                       sN * values[ oN + i ];
 
-       Curve.call( this );
+               }
 
-       this.type = 'CurvePath';
+               return result;
 
-       this.curves = [];
-       this.autoClose = false; // Automatically closes the path
+       }
 
 }
 
-CurvePath.prototype = Object.assign( Object.create( Curve.prototype ), {
+class LinearInterpolant extends Interpolant {
 
-       constructor: CurvePath,
+       constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
 
-       add: function ( curve ) {
+               super( parameterPositions, sampleValues, sampleSize, resultBuffer );
 
-               this.curves.push( curve );
+       }
 
-       },
+       interpolate_( i1, t0, t, t1 ) {
 
-       closePath: function () {
+               const result = this.resultBuffer,
+                       values = this.sampleValues,
+                       stride = this.valueSize,
 
-               // 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 );
+                       offset1 = i1 * stride,
+                       offset0 = offset1 - stride,
 
-               if ( ! startPoint.equals( endPoint ) ) {
+                       weight1 = ( t - t0 ) / ( t1 - t0 ),
+                       weight0 = 1 - weight1;
 
-                       this.curves.push( new LineCurve( endPoint, startPoint ) );
+               for ( let i = 0; i !== stride; ++ i ) {
 
-               }
+                       result[ i ] =
+                                       values[ offset0 + i ] * weight0 +
+                                       values[ offset1 + i ] * weight1;
 
-       },
+               }
 
-       // To get accurate point with reference to
-       // entire path distance at time t,
-       // following has to be done:
+               return result;
 
-       // 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;
+/**
+ *
+ * Interpolant that evaluates to the sample value at the position preceeding
+ * the parameter.
+ */
 
-               // To think about boundaries points.
+class DiscreteInterpolant extends Interpolant {
 
-               while ( i < curveLengths.length ) {
+       constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
 
-                       if ( curveLengths[ i ] >= d ) {
+               super( parameterPositions, sampleValues, sampleSize, resultBuffer );
 
-                               const diff = curveLengths[ i ] - d;
-                               const curve = this.curves[ i ];
+       }
 
-                               const segmentLength = curve.getLength();
-                               const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;
+       interpolate_( i1 /*, t0, t, t1 */ ) {
 
-                               return curve.getPointAt( u );
+               return this.copySampleValue_( i1 - 1 );
 
-                       }
+       }
 
-                       i ++;
+}
 
-               }
+class KeyframeTrack {
 
-               return null;
+       constructor( name, times, values, interpolation ) {
 
-               // loop where sum != 0, sum > d , sum+1 <d
+               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;
 
-       // 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
+               this.times = AnimationUtils.convertArray( times, this.TimeBufferType );
+               this.values = AnimationUtils.convertArray( values, this.ValueBufferType );
 
-       getLength: function () {
+               this.setInterpolation( interpolation || this.DefaultInterpolation );
 
-               const lens = this.getCurveLengths();
-               return lens[ lens.length - 1 ];
+       }
 
-       },
+       // Serialization (in static context, because of constructor invocation
+       // and automatic invocation of .toJSON):
 
-       // cacheLengths must be recalculated.
-       updateArcLengths: function () {
+       static toJSON( track ) {
 
-               this.needsUpdate = true;
-               this.cacheLengths = null;
-               this.getCurveLengths();
+               const trackType = track.constructor;
 
-       },
+               let json;
 
-       // Compute lengths and cache them
-       // We cannot overwrite getLengths() because UtoT mapping uses it.
+               // derived classes can define a static toJSON method
+               if ( trackType.toJSON !== this.toJSON ) {
 
-       getCurveLengths: function () {
+                       json = trackType.toJSON( track );
 
-               // We use cache values if curves and cache array are same length
+               } else {
 
-               if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) {
+                       // by default, we assume the data can be serialized as-is
+                       json = {
 
-                       return this.cacheLengths;
+                               'name': track.name,
+                               'times': AnimationUtils.convertArray( track.times, Array ),
+                               'values': AnimationUtils.convertArray( track.values, Array )
 
-               }
+                       };
 
-               // Get length of sub-curve
-               // Push sums into cached array
+                       const interpolation = track.getInterpolation();
 
-               const lengths = [];
-               let sums = 0;
+                       if ( interpolation !== track.DefaultInterpolation ) {
 
-               for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
+                               json.interpolation = interpolation;
 
-                       sums += this.curves[ i ].getLength();
-                       lengths.push( sums );
+                       }
 
                }
 
-               this.cacheLengths = lengths;
+               json.type = track.ValueTypeName; // mandatory
 
-               return lengths;
+               return json;
 
-       },
+       }
 
-       getSpacedPoints: function ( divisions = 40 ) {
+       InterpolantFactoryMethodDiscrete( result ) {
 
-               const points = [];
+               return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );
 
-               for ( let i = 0; i <= divisions; i ++ ) {
+       }
 
-                       points.push( this.getPoint( i / divisions ) );
+       InterpolantFactoryMethodLinear( result ) {
 
-               }
+               return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );
 
-               if ( this.autoClose ) {
+       }
 
-                       points.push( points[ 0 ] );
+       InterpolantFactoryMethodSmooth( result ) {
 
-               }
+               return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );
 
-               return points;
+       }
 
-       },
+       setInterpolation( interpolation ) {
 
-       getPoints: function ( divisions = 12 ) {
+               let factoryMethod;
 
-               const points = [];
-               let last;
+               switch ( interpolation ) {
 
-               for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) {
+                       case InterpolateDiscrete:
 
-                       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;
+                               factoryMethod = this.InterpolantFactoryMethodDiscrete;
 
-                       const pts = curve.getPoints( resolution );
+                               break;
 
-                       for ( let j = 0; j < pts.length; j ++ ) {
+                       case InterpolateLinear:
 
-                               const point = pts[ j ];
+                               factoryMethod = this.InterpolantFactoryMethodLinear;
 
-                               if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates
+                               break;
 
-                               points.push( point );
-                               last = point;
+                       case InterpolateSmooth:
 
-                       }
+                               factoryMethod = this.InterpolantFactoryMethodSmooth;
 
-               }
+                               break;
 
-               if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) {
+               }
 
-                       points.push( points[ 0 ] );
+               if ( factoryMethod === undefined ) {
 
-               }
+                       const message = 'unsupported interpolation for ' +
+                               this.ValueTypeName + ' keyframe track named ' + this.name;
 
-               return points;
+                       if ( this.createInterpolant === undefined ) {
 
-       },
+                               // fall back to default, unless the default itself is messed up
+                               if ( interpolation !== this.DefaultInterpolation ) {
 
-       copy: function ( source ) {
+                                       this.setInterpolation( this.DefaultInterpolation );
 
-               Curve.prototype.copy.call( this, source );
+                               } else {
 
-               this.curves = [];
+                                       throw new Error( message ); // fatal, in this case
 
-               for ( let i = 0, l = source.curves.length; i < l; i ++ ) {
+                               }
 
-                       const curve = source.curves[ i ];
+                       }
 
-                       this.curves.push( curve.clone() );
+                       console.warn( 'THREE.KeyframeTrack:', message );
+                       return this;
 
                }
 
-               this.autoClose = source.autoClose;
+               this.createInterpolant = factoryMethod;
 
                return this;
 
-       },
+       }
 
-       toJSON: function () {
+       getInterpolation() {
 
-               const data = Curve.prototype.toJSON.call( this );
+               switch ( this.createInterpolant ) {
 
-               data.autoClose = this.autoClose;
-               data.curves = [];
+                       case this.InterpolantFactoryMethodDiscrete:
 
-               for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
+                               return InterpolateDiscrete;
 
-                       const curve = this.curves[ i ];
-                       data.curves.push( curve.toJSON() );
+                       case this.InterpolantFactoryMethodLinear:
 
-               }
+                               return InterpolateLinear;
 
-               return data;
+                       case this.InterpolantFactoryMethodSmooth:
 
-       },
+                               return InterpolateSmooth;
 
-       fromJSON: function ( json ) {
+               }
 
-               Curve.prototype.fromJSON.call( this, json );
+       }
 
-               this.autoClose = json.autoClose;
-               this.curves = [];
+       getValueSize() {
 
-               for ( let i = 0, l = json.curves.length; i < l; i ++ ) {
+               return this.values.length / this.times.length;
 
-                       const curve = json.curves[ i ];
-                       this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) );
+       }
 
-               }
+       // move all keyframes either forwards or backwards in time
+       shift( timeOffset ) {
 
-               return this;
-
-       }
-
-} );
+               if ( timeOffset !== 0.0 ) {
 
-function Path( points ) {
+                       const times = this.times;
 
-       CurvePath.call( this );
+                       for ( let i = 0, n = times.length; i !== n; ++ i ) {
 
-       this.type = 'Path';
+                               times[ i ] += timeOffset;
 
-       this.currentPoint = new Vector2();
+                       }
 
-       if ( points ) {
+               }
 
-               this.setFromPoints( points );
+               return this;
 
        }
 
-}
-
-Path.prototype = Object.assign( Object.create( CurvePath.prototype ), {
+       // scale all keyframe times by a factor (useful for frame <-> seconds conversions)
+       scale( timeScale ) {
 
-       constructor: Path,
+               if ( timeScale !== 1.0 ) {
 
-       setFromPoints: function ( points ) {
+                       const times = this.times;
 
-               this.moveTo( points[ 0 ].x, points[ 0 ].y );
+                       for ( let i = 0, n = times.length; i !== n; ++ i ) {
 
-               for ( let i = 1, l = points.length; i < l; i ++ ) {
+                               times[ i ] *= timeScale;
 
-                       this.lineTo( points[ i ].x, points[ i ].y );
+                       }
 
                }
 
                return this;
 
-       },
+       }
 
-       moveTo: function ( x, y ) {
+       // 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( startTime, endTime ) {
 
-               this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying?
+               const times = this.times,
+                       nKeys = times.length;
 
-               return this;
+               let from = 0,
+                       to = nKeys - 1;
 
-       },
+               while ( from !== nKeys && times[ from ] < startTime ) {
 
-       lineTo: function ( x, y ) {
+                       ++ from;
 
-               const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) );
-               this.curves.push( curve );
+               }
 
-               this.currentPoint.set( x, y );
+               while ( to !== - 1 && times[ to ] > endTime ) {
 
-               return this;
+                       -- to;
 
-       },
+               }
 
-       quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
+               ++ to; // inclusive -> exclusive bound
 
-               const curve = new QuadraticBezierCurve(
-                       this.currentPoint.clone(),
-                       new Vector2( aCPx, aCPy ),
-                       new Vector2( aX, aY )
-               );
+               if ( from !== 0 || to !== nKeys ) {
 
-               this.curves.push( curve );
+                       // empty tracks are forbidden, so keep at least one keyframe
+                       if ( from >= to ) {
 
-               this.currentPoint.set( aX, aY );
+                               to = Math.max( to, 1 );
+                               from = to - 1;
 
-               return this;
+                       }
 
-       },
+                       const stride = this.getValueSize();
+                       this.times = AnimationUtils.arraySlice( times, from, to );
+                       this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride );
 
-       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 )
-               );
+               return this;
 
-               this.curves.push( curve );
+       }
 
-               this.currentPoint.set( aX, aY );
+       // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
+       validate() {
 
-               return this;
+               let valid = true;
 
-       },
+               const valueSize = this.getValueSize();
+               if ( valueSize - Math.floor( valueSize ) !== 0 ) {
 
-       splineThru: function ( pts /*Array of Vector*/ ) {
+                       console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
+                       valid = false;
 
-               const npts = [ this.currentPoint.clone() ].concat( pts );
+               }
 
-               const curve = new SplineCurve( npts );
-               this.curves.push( curve );
+               const times = this.times,
+                       values = this.values,
 
-               this.currentPoint.copy( pts[ pts.length - 1 ] );
+                       nKeys = times.length;
 
-               return this;
+               if ( nKeys === 0 ) {
 
-       },
+                       console.error( 'THREE.KeyframeTrack: Track is empty.', this );
+                       valid = false;
 
-       arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+               }
 
-               const x0 = this.currentPoint.x;
-               const y0 = this.currentPoint.y;
+               let prevTime = null;
 
-               this.absarc( aX + x0, aY + y0, aRadius,
-                       aStartAngle, aEndAngle, aClockwise );
+               for ( let i = 0; i !== nKeys; i ++ ) {
 
-               return this;
+                       const currTime = times[ i ];
 
-       },
+                       if ( typeof currTime === 'number' && isNaN( currTime ) ) {
 
-       absarc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+                               console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime );
+                               valid = false;
+                               break;
 
-               this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
+                       }
 
-               return this;
+                       if ( prevTime !== null && prevTime > currTime ) {
 
-       },
+                               console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime );
+                               valid = false;
+                               break;
 
-       ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
+                       }
 
-               const x0 = this.currentPoint.x;
-               const y0 = this.currentPoint.y;
+                       prevTime = currTime;
 
-               this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
+               }
 
-               return this;
+               if ( values !== undefined ) {
 
-       },
+                       if ( AnimationUtils.isTypedArray( values ) ) {
 
-       absellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
+                               for ( let i = 0, n = values.length; i !== n; ++ i ) {
 
-               const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
+                                       const value = values[ i ];
 
-               if ( this.curves.length > 0 ) {
+                                       if ( isNaN( value ) ) {
 
-                       // if a previous curve is present, attempt to join
-                       const firstPoint = curve.getPoint( 0 );
+                                               console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
+                                               valid = false;
+                                               break;
 
-                       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;
-
-       },
+               return valid;
 
-       copy: function ( source ) {
+       }
 
-               CurvePath.prototype.copy.call( this, source );
+       // 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() {
 
-               this.currentPoint.copy( source.currentPoint );
+               // 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(),
 
-               return this;
+                       smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
 
-       },
+                       lastIndex = times.length - 1;
 
-       toJSON: function () {
+               let writeIndex = 1;
 
-               const data = CurvePath.prototype.toJSON.call( this );
+               for ( let i = 1; i < lastIndex; ++ i ) {
 
-               data.currentPoint = this.currentPoint.toArray();
+                       let keep = false;
 
-               return data;
+                       const time = times[ i ];
+                       const timeNext = times[ i + 1 ];
 
-       },
+                       // remove adjacent keyframes scheduled at the same time
 
-       fromJSON: function ( json ) {
+                       if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) {
 
-               CurvePath.prototype.fromJSON.call( this, json );
+                               if ( ! smoothInterpolation ) {
 
-               this.currentPoint.fromArray( json.currentPoint );
+                                       // remove unnecessary keyframes same as their neighbors
 
-               return this;
+                                       const offset = i * stride,
+                                               offsetP = offset - stride,
+                                               offsetN = offset + stride;
 
-       }
+                                       for ( let j = 0; j !== stride; ++ j ) {
 
-} );
+                                               const value = values[ offset + j ];
 
-function Shape( points ) {
+                                               if ( value !== values[ offsetP + j ] ||
+                                                       value !== values[ offsetN + j ] ) {
 
-       Path.call( this, points );
+                                                       keep = true;
+                                                       break;
 
-       this.uuid = MathUtils.generateUUID();
+                                               }
 
-       this.type = 'Shape';
+                                       }
 
-       this.holes = [];
+                               } else {
 
-}
+                                       keep = true;
 
-Shape.prototype = Object.assign( Object.create( Path.prototype ), {
+                               }
 
-       constructor: Shape,
+                       }
 
-       getPointsHoles: function ( divisions ) {
+                       // in-place compaction
 
-               const holesPts = [];
+                       if ( keep ) {
 
-               for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
+                               if ( i !== writeIndex ) {
 
-                       holesPts[ i ] = this.holes[ i ].getPoints( divisions );
+                                       times[ writeIndex ] = times[ i ];
 
-               }
+                                       const readOffset = i * stride,
+                                               writeOffset = writeIndex * stride;
 
-               return holesPts;
+                                       for ( let j = 0; j !== stride; ++ j ) {
 
-       },
+                                               values[ writeOffset + j ] = values[ readOffset + j ];
 
-       // get points of shape and holes (keypoints based on segments parameter)
+                                       }
 
-       extractPoints: function ( divisions ) {
+                               }
 
-               return {
+                               ++ writeIndex;
 
-                       shape: this.getPoints( divisions ),
-                       holes: this.getPointsHoles( divisions )
+                       }
 
-               };
+               }
 
-       },
+               // flush last keyframe (compaction looks ahead)
 
-       copy: function ( source ) {
+               if ( lastIndex > 0 ) {
 
-               Path.prototype.copy.call( this, source );
+                       times[ writeIndex ] = times[ lastIndex ];
 
-               this.holes = [];
+                       for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
 
-               for ( let i = 0, l = source.holes.length; i < l; i ++ ) {
+                               values[ writeOffset + j ] = values[ readOffset + j ];
 
-                       const hole = source.holes[ i ];
+                       }
 
-                       this.holes.push( hole.clone() );
+                       ++ writeIndex;
 
                }
 
-               return this;
-
-       },
-
-       toJSON: function () {
-
-               const data = Path.prototype.toJSON.call( this );
+               if ( writeIndex !== times.length ) {
 
-               data.uuid = this.uuid;
-               data.holes = [];
+                       this.times = AnimationUtils.arraySlice( times, 0, writeIndex );
+                       this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride );
 
-               for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
+               } else {
 
-                       const hole = this.holes[ i ];
-                       data.holes.push( hole.toJSON() );
+                       this.times = times;
+                       this.values = values;
 
                }
 
-               return data;
-
-       },
-
-       fromJSON: function ( json ) {
+               return this;
 
-               Path.prototype.fromJSON.call( this, json );
+       }
 
-               this.uuid = json.uuid;
-               this.holes = [];
+       clone() {
 
-               for ( let i = 0, l = json.holes.length; i < l; i ++ ) {
+               const times = AnimationUtils.arraySlice( this.times, 0 );
+               const values = AnimationUtils.arraySlice( this.values, 0 );
 
-                       const hole = json.holes[ i ];
-                       this.holes.push( new Path().fromJSON( hole ) );
+               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 this;
+               return track;
 
        }
 
-} );
+}
 
-function Light( color, intensity = 1 ) {
+KeyframeTrack.prototype.TimeBufferType = Float32Array;
+KeyframeTrack.prototype.ValueBufferType = Float32Array;
+KeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear;
 
-       Object3D.call( this );
+/**
+ * A Track of Boolean keyframe values.
+ */
+class BooleanKeyframeTrack extends KeyframeTrack {}
 
-       this.type = 'Light';
+BooleanKeyframeTrack.prototype.ValueTypeName = 'bool';
+BooleanKeyframeTrack.prototype.ValueBufferType = Array;
+BooleanKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete;
+BooleanKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined;
+BooleanKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined;
 
-       this.color = new Color( color );
-       this.intensity = intensity;
+/**
+ * A Track of keyframe values that represent color.
+ */
+class ColorKeyframeTrack extends KeyframeTrack {}
 
-}
+ColorKeyframeTrack.prototype.ValueTypeName = 'color';
 
-Light.prototype = Object.assign( Object.create( Object3D.prototype ), {
+/**
+ * A Track of numeric keyframe values.
+ */
+class NumberKeyframeTrack extends KeyframeTrack {}
 
-       constructor: Light,
+NumberKeyframeTrack.prototype.ValueTypeName = 'number';
 
-       isLight: true,
+/**
+ * Spherical linear unit quaternion interpolant.
+ */
 
-       copy: function ( source ) {
+class QuaternionLinearInterpolant extends Interpolant {
 
-               Object3D.prototype.copy.call( this, source );
+       constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
 
-               this.color.copy( source.color );
-               this.intensity = source.intensity;
+               super( parameterPositions, sampleValues, sampleSize, resultBuffer );
 
-               return this;
+       }
 
-       },
+       interpolate_( i1, t0, t, t1 ) {
 
-       toJSON: function ( meta ) {
+               const result = this.resultBuffer,
+                       values = this.sampleValues,
+                       stride = this.valueSize,
 
-               const data = Object3D.prototype.toJSON.call( this, meta );
+                       alpha = ( t - t0 ) / ( t1 - t0 );
 
-               data.object.color = this.color.getHex();
-               data.object.intensity = this.intensity;
+               let offset = i1 * stride;
 
-               if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex();
+               for ( let end = offset + stride; offset !== end; offset += 4 ) {
 
-               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;
+                       Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha );
 
-               if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON();
+               }
 
-               return data;
+               return result;
 
        }
 
-} );
-
-function HemisphereLight( skyColor, groundColor, intensity ) {
+}
 
-       Light.call( this, skyColor, intensity );
+/**
+ * A Track of quaternion keyframe values.
+ */
+class QuaternionKeyframeTrack extends KeyframeTrack {
 
-       this.type = 'HemisphereLight';
+       InterpolantFactoryMethodLinear( result ) {
 
-       this.position.copy( Object3D.DefaultUp );
-       this.updateMatrix();
+               return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result );
 
-       this.groundColor = new Color( groundColor );
+       }
 
 }
 
-HemisphereLight.prototype = Object.assign( Object.create( Light.prototype ), {
-
-       constructor: HemisphereLight,
-
-       isHemisphereLight: true,
+QuaternionKeyframeTrack.prototype.ValueTypeName = 'quaternion';
+// ValueBufferType is inherited
+QuaternionKeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear;
+QuaternionKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined;
 
-       copy: function ( source ) {
+/**
+ * A Track that interpolates Strings
+ */
+class StringKeyframeTrack extends KeyframeTrack {}
 
-               Light.prototype.copy.call( this, source );
+StringKeyframeTrack.prototype.ValueTypeName = 'string';
+StringKeyframeTrack.prototype.ValueBufferType = Array;
+StringKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete;
+StringKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined;
+StringKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined;
 
-               this.groundColor.copy( source.groundColor );
+/**
+ * A Track of vectored keyframe values.
+ */
+class VectorKeyframeTrack extends KeyframeTrack {}
 
-               return this;
+VectorKeyframeTrack.prototype.ValueTypeName = 'vector';
 
-       }
+class AnimationClip {
 
-} );
+       constructor( name, duration = - 1, tracks, blendMode = NormalAnimationBlendMode ) {
 
-function LightShadow( camera ) {
+               this.name = name;
+               this.tracks = tracks;
+               this.duration = duration;
+               this.blendMode = blendMode;
 
-       this.camera = camera;
+               this.uuid = generateUUID();
 
-       this.bias = 0;
-       this.normalBias = 0;
-       this.radius = 1;
+               // this means it should figure out its duration by scanning the tracks
+               if ( this.duration < 0 ) {
 
-       this.mapSize = new Vector2( 512, 512 );
+                       this.resetDuration();
 
-       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;
+       static parse( json ) {
 
-       this._viewports = [
+               const tracks = [],
+                       jsonTracks = json.tracks,
+                       frameTime = 1.0 / ( json.fps || 1.0 );
 
-               new Vector4( 0, 0, 1, 1 )
+               for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) {
 
-       ];
+                       tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) );
 
-}
+               }
 
-Object.assign( LightShadow.prototype, {
+               const clip = new this( json.name, json.duration, tracks, json.blendMode );
+               clip.uuid = json.uuid;
 
-       _projScreenMatrix: new Matrix4(),
+               return clip;
 
-       _lightPositionWorld: new Vector3(),
+       }
 
-       _lookTarget: new Vector3(),
+       static toJSON( clip ) {
 
-       getViewportCount: function () {
+               const tracks = [],
+                       clipTracks = clip.tracks;
 
-               return this._viewportCount;
+               const json = {
 
-       },
+                       'name': clip.name,
+                       'duration': clip.duration,
+                       'tracks': tracks,
+                       'uuid': clip.uuid,
+                       'blendMode': clip.blendMode
 
-       getFrustum: function () {
+               };
 
-               return this._frustum;
+               for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) {
 
-       },
+                       tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );
 
-       updateMatrices: function ( light ) {
+               }
 
-               const shadowCamera = this.camera,
-                       shadowMatrix = this.matrix,
-                       projScreenMatrix = this._projScreenMatrix,
-                       lookTarget = this._lookTarget,
-                       lightPositionWorld = this._lightPositionWorld;
+               return json;
 
-               lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
-               shadowCamera.position.copy( lightPositionWorld );
+       }
 
-               lookTarget.setFromMatrixPosition( light.target.matrixWorld );
-               shadowCamera.lookAt( lookTarget );
-               shadowCamera.updateMatrixWorld();
+       static CreateFromMorphTargetSequence( name, morphTargetSequence, fps, noLoop ) {
 
-               projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );
-               this._frustum.setFromProjectionMatrix( projScreenMatrix );
+               const numMorphTargets = morphTargetSequence.length;
+               const tracks = [];
 
-               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
-               );
+               for ( let i = 0; i < numMorphTargets; i ++ ) {
 
-               shadowMatrix.multiply( shadowCamera.projectionMatrix );
-               shadowMatrix.multiply( shadowCamera.matrixWorldInverse );
+                       let times = [];
+                       let values = [];
 
-       },
+                       times.push(
+                               ( i + numMorphTargets - 1 ) % numMorphTargets,
+                               i,
+                               ( i + 1 ) % numMorphTargets );
 
-       getViewport: function ( viewportIndex ) {
+                       values.push( 0, 1, 0 );
 
-               return this._viewports[ viewportIndex ];
+                       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 ) {
 
-       getFrameExtents: function () {
+                               times.push( numMorphTargets );
+                               values.push( values[ 0 ] );
 
-               return this._frameExtents;
+                       }
 
-       },
+                       tracks.push(
+                               new NumberKeyframeTrack(
+                                       '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',
+                                       times, values
+                               ).scale( 1.0 / fps ) );
 
-       copy: function ( source ) {
+               }
 
-               this.camera = source.camera.clone();
+               return new this( name, - 1, tracks );
 
-               this.bias = source.bias;
-               this.radius = source.radius;
+       }
 
-               this.mapSize.copy( source.mapSize );
+       static findByName( objectOrClipArray, name ) {
 
-               return this;
+               let clipArray = objectOrClipArray;
 
-       },
+               if ( ! Array.isArray( objectOrClipArray ) ) {
 
-       clone: function () {
+                       const o = objectOrClipArray;
+                       clipArray = o.geometry && o.geometry.animations || o.animations;
 
-               return new this.constructor().copy( this );
+               }
 
-       },
+               for ( let i = 0; i < clipArray.length; i ++ ) {
 
-       toJSON: function () {
+                       if ( clipArray[ i ].name === name ) {
 
-               const object = {};
+                               return clipArray[ i ];
 
-               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;
+               return null;
 
        }
 
-} );
+       static CreateClipsFromMorphTargetSequences( morphTargets, fps, noLoop ) {
 
-function SpotLightShadow() {
+               const animationToMorphTargets = {};
 
-       LightShadow.call( this, new PerspectiveCamera( 50, 1, 0.5, 500 ) );
+               // tested with https://regex101.com/ on trick sequences
+               // such flamingo_flyA_003, flamingo_run1_003, crdeath0059
+               const pattern = /^([\w-]*?)([\d]+)$/;
 
-       this.focus = 1;
+               // 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 );
 
-SpotLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {
+                       if ( parts && parts.length > 1 ) {
 
-       constructor: SpotLightShadow,
+                               const name = parts[ 1 ];
 
-       isSpotLightShadow: true,
+                               let animationMorphTargets = animationToMorphTargets[ name ];
 
-       updateMatrices: function ( light ) {
+                               if ( ! animationMorphTargets ) {
 
-               const camera = this.camera;
+                                       animationToMorphTargets[ name ] = animationMorphTargets = [];
 
-               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 ) {
+                               animationMorphTargets.push( morphTarget );
 
-                       camera.fov = fov;
-                       camera.aspect = aspect;
-                       camera.far = far;
-                       camera.updateProjectionMatrix();
+                       }
 
                }
 
-               LightShadow.prototype.updateMatrices.call( this, light );
+               const clips = [];
 
-       }
+               for ( const name in animationToMorphTargets ) {
 
-} );
+                       clips.push( this.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );
 
-function SpotLight( color, intensity, distance, angle, penumbra, decay ) {
+               }
 
-       Light.call( this, color, intensity );
+               return clips;
 
-       this.type = 'SpotLight';
+       }
 
-       this.position.copy( Object3D.DefaultUp );
-       this.updateMatrix();
+       // parse the animation.hierarchy format
+       static parseAnimation( animation, bones ) {
 
-       this.target = new Object3D();
+               if ( ! animation ) {
 
-       Object.defineProperty( this, 'power', {
-               get: function () {
+                       console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' );
+                       return null;
 
-                       // 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 ) {
+               const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) {
 
-                       // 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;
+                       // only return track if there are actually keys.
+                       if ( animationKeys.length !== 0 ) {
 
-               }
-       } );
+                               const times = [];
+                               const values = [];
 
-       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.
+                               AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );
 
-       this.shadow = new SpotLightShadow();
+                               // empty keys are filtered out, so check again
+                               if ( times.length !== 0 ) {
 
-}
+                                       destTracks.push( new trackType( trackName, times, values ) );
 
-SpotLight.prototype = Object.assign( Object.create( Light.prototype ), {
+                               }
 
-       constructor: SpotLight,
+                       }
 
-       isSpotLight: true,
+               };
 
-       copy: function ( source ) {
+               const tracks = [];
 
-               Light.prototype.copy.call( this, source );
+               const clipName = animation.name || 'default';
+               const fps = animation.fps || 30;
+               const blendMode = animation.blendMode;
 
-               this.distance = source.distance;
-               this.angle = source.angle;
-               this.penumbra = source.penumbra;
-               this.decay = source.decay;
+               // automatic length determination in AnimationClip.
+               let duration = animation.length || - 1;
 
-               this.target = source.target.clone();
+               const hierarchyTracks = animation.hierarchy || [];
 
-               this.shadow = source.shadow.clone();
+               for ( let h = 0; h < hierarchyTracks.length; h ++ ) {
 
-               return this;
+                       const animationKeys = hierarchyTracks[ h ].keys;
 
-       }
+                       // skip empty tracks
+                       if ( ! animationKeys || animationKeys.length === 0 ) continue;
 
-} );
+                       // process morph targets
+                       if ( animationKeys[ 0 ].morphTargets ) {
 
-function PointLightShadow() {
+                               // figure out all morph targets used in this track
+                               const morphTargetNames = {};
 
-       LightShadow.call( this, new PerspectiveCamera( 90, 1, 0.5, 500 ) );
+                               let k;
 
-       this._frameExtents = new Vector2( 4, 2 );
+                               for ( k = 0; k < animationKeys.length; k ++ ) {
 
-       this._viewportCount = 6;
+                                       if ( animationKeys[ k ].morphTargets ) {
 
-       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 )
-       ];
+                                               for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) {
 
-       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 )
-       ];
+                                                       morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1;
 
-       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 ), {
+                               // 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 ) {
 
-       constructor: PointLightShadow,
+                                       const times = [];
+                                       const values = [];
 
-       isPointLightShadow: true,
+                                       for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) {
 
-       updateMatrices: function ( light, viewportIndex = 0 ) {
+                                               const animationKey = animationKeys[ k ];
 
-               const camera = this.camera,
-                       shadowMatrix = this.matrix,
-                       lightPositionWorld = this._lightPositionWorld,
-                       lookTarget = this._lookTarget,
-                       projScreenMatrix = this._projScreenMatrix;
+                                               times.push( animationKey.time );
+                                               values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );
 
-               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();
+                                       tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) );
 
-               shadowMatrix.makeTranslation( - lightPositionWorld.x, - lightPositionWorld.y, - lightPositionWorld.z );
+                               }
 
-               projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
-               this._frustum.setFromProjectionMatrix( projScreenMatrix );
+                               duration = morphTargetNames.length * ( fps || 1.0 );
 
-       }
+                       } else {
 
-} );
+                               // ...assume skeletal animation
+
+                               const boneName = '.bones[' + bones[ h ].name + ']';
 
-function PointLight( color, intensity, distance, decay ) {
+                               addNonemptyTrack(
+                                       VectorKeyframeTrack, boneName + '.position',
+                                       animationKeys, 'pos', tracks );
 
-       Light.call( this, color, intensity );
+                               addNonemptyTrack(
+                                       QuaternionKeyframeTrack, boneName + '.quaternion',
+                                       animationKeys, 'rot', tracks );
 
-       this.type = 'PointLight';
+                               addNonemptyTrack(
+                                       VectorKeyframeTrack, boneName + '.scale',
+                                       animationKeys, 'scl', tracks );
 
-       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 ) {
+               if ( tracks.length === 0 ) {
 
-                       // 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 );
+                       return null;
 
                }
-       } );
 
-       this.distance = ( distance !== undefined ) ? distance : 0;
-       this.decay = ( decay !== undefined ) ? decay : 1;       // for physically correct lights, should be 2.
+               const clip = new this( clipName, duration, tracks, blendMode );
 
-       this.shadow = new PointLightShadow();
+               return clip;
 
-}
+       }
 
-PointLight.prototype = Object.assign( Object.create( Light.prototype ), {
+       resetDuration() {
 
-       constructor: PointLight,
+               const tracks = this.tracks;
+               let duration = 0;
 
-       isPointLight: true,
+               for ( let i = 0, n = tracks.length; i !== n; ++ i ) {
 
-       copy: function ( source ) {
+                       const track = this.tracks[ i ];
 
-               Light.prototype.copy.call( this, source );
+                       duration = Math.max( duration, track.times[ track.times.length - 1 ] );
 
-               this.distance = source.distance;
-               this.decay = source.decay;
+               }
 
-               this.shadow = source.shadow.clone();
+               this.duration = duration;
 
                return this;
 
        }
 
-} );
+       trim() {
 
-function OrthographicCamera( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) {
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
 
-       Camera$1.call( this );
+                       this.tracks[ i ].trim( 0, this.duration );
 
-       this.type = 'OrthographicCamera';
+               }
 
-       this.zoom = 1;
-       this.view = null;
+               return this;
 
-       this.left = left;
-       this.right = right;
-       this.top = top;
-       this.bottom = bottom;
+       }
 
-       this.near = near;
-       this.far = far;
+       validate() {
 
-       this.updateProjectionMatrix();
+               let valid = true;
 
-}
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
 
-OrthographicCamera.prototype = Object.assign( Object.create( Camera$1.prototype ), {
+                       valid = valid && this.tracks[ i ].validate();
 
-       constructor: OrthographicCamera,
+               }
 
-       isOrthographicCamera: true,
+               return valid;
 
-       copy: function ( source, recursive ) {
+       }
 
-               Camera$1.prototype.copy.call( this, source, recursive );
+       optimize() {
 
-               this.left = source.left;
-               this.right = source.right;
-               this.top = source.top;
-               this.bottom = source.bottom;
-               this.near = source.near;
-               this.far = source.far;
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
 
-               this.zoom = source.zoom;
-               this.view = source.view === null ? null : Object.assign( {}, source.view );
+                       this.tracks[ i ].optimize();
+
+               }
 
                return this;
 
-       },
+       }
 
-       setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {
+       clone() {
 
-               if ( this.view === null ) {
+               const tracks = [];
 
-                       this.view = {
-                               enabled: true,
-                               fullWidth: 1,
-                               fullHeight: 1,
-                               offsetX: 0,
-                               offsetY: 0,
-                               width: 1,
-                               height: 1
-                       };
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
+
+                       tracks.push( this.tracks[ i ].clone() );
 
                }
 
-               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;
+               return new this.constructor( this.name, this.duration, tracks, this.blendMode );
 
-               this.updateProjectionMatrix();
+       }
 
-       },
+       toJSON() {
 
-       clearViewOffset: function () {
+               return this.constructor.toJSON( this );
 
-               if ( this.view !== null ) {
+       }
 
-                       this.view.enabled = false;
+}
 
-               }
+function getTrackTypeForValueTypeName( typeName ) {
 
-               this.updateProjectionMatrix();
+       switch ( typeName.toLowerCase() ) {
 
-       },
+               case 'scalar':
+               case 'double':
+               case 'float':
+               case 'number':
+               case 'integer':
 
-       updateProjectionMatrix: function () {
+                       return NumberKeyframeTrack;
 
-               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;
+               case 'vector':
+               case 'vector2':
+               case 'vector3':
+               case 'vector4':
 
-               let left = cx - dx;
-               let right = cx + dx;
-               let top = cy + dy;
-               let bottom = cy - dy;
+                       return VectorKeyframeTrack;
 
-               if ( this.view !== null && this.view.enabled ) {
+               case 'color':
 
-                       const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom;
-                       const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom;
+                       return ColorKeyframeTrack;
 
-                       left += scaleW * this.view.offsetX;
-                       right = left + scaleW * this.view.width;
-                       top -= scaleH * this.view.offsetY;
-                       bottom = top - scaleH * this.view.height;
+               case 'quaternion':
 
-               }
+                       return QuaternionKeyframeTrack;
 
-               this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far );
+               case 'bool':
+               case 'boolean':
 
-               this.projectionMatrixInverse.copy( this.projectionMatrix ).invert();
+                       return BooleanKeyframeTrack;
 
-       },
+               case 'string':
 
-       toJSON: function ( meta ) {
+                       return StringKeyframeTrack;
 
-               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;
+       throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName );
 
-               if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );
+}
 
-               return data;
+function parseKeyframeTrack( json ) {
+
+       if ( json.type === undefined ) {
+
+               throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' );
 
        }
 
-} );
+       const trackType = getTrackTypeForValueTypeName( json.type );
 
-function DirectionalLightShadow() {
+       if ( json.times === undefined ) {
 
-       LightShadow.call( this, new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) );
+               const times = [], values = [];
 
-}
+               AnimationUtils.flattenJSON( json.keys, times, values, 'value' );
+
+               json.times = times;
+               json.values = values;
 
-DirectionalLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {
+       }
 
-       constructor: DirectionalLightShadow,
+       // derived classes can define a static parse method
+       if ( trackType.parse !== undefined ) {
 
-       isDirectionalLightShadow: true,
+               return trackType.parse( json );
 
-       updateMatrices: function ( light ) {
+       } else {
 
-               LightShadow.prototype.updateMatrices.call( this, light );
+               // by default, we assume a constructor compatible with the base
+               return new trackType( json.name, json.times, json.values, json.interpolation );
 
        }
 
-} );
+}
 
-function DirectionalLight( color, intensity ) {
+const Cache = {
 
-       Light.call( this, color, intensity );
+       enabled: false,
 
-       this.type = 'DirectionalLight';
+       files: {},
 
-       this.position.copy( Object3D.DefaultUp );
-       this.updateMatrix();
+       add: function ( key, file ) {
 
-       this.target = new Object3D();
+               if ( this.enabled === false ) return;
 
-       this.shadow = new DirectionalLightShadow();
+               // console.log( 'THREE.Cache', 'Adding key:', key );
 
-}
+               this.files[ key ] = file;
 
-DirectionalLight.prototype = Object.assign( Object.create( Light.prototype ), {
+       },
 
-       constructor: DirectionalLight,
+       get: function ( key ) {
 
-       isDirectionalLight: true,
+               if ( this.enabled === false ) return;
 
-       copy: function ( source ) {
+               // console.log( 'THREE.Cache', 'Checking key:', key );
 
-               Light.prototype.copy.call( this, source );
+               return this.files[ key ];
 
-               this.target = source.target.clone();
+       },
 
-               this.shadow = source.shadow.clone();
+       remove: function ( key ) {
 
-               return this;
+               delete this.files[ key ];
 
-       }
+       },
 
-} );
+       clear: function () {
 
-function AmbientLight( color, intensity ) {
+               this.files = {};
 
-       Light.call( this, color, intensity );
+       }
 
-       this.type = 'AmbientLight';
+};
 
-}
+class LoadingManager {
 
-AmbientLight.prototype = Object.assign( Object.create( Light.prototype ), {
+       constructor( onLoad, onProgress, onError ) {
 
-       constructor: AmbientLight,
+               const scope = this;
 
-       isAmbientLight: true
+               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
 
-function RectAreaLight( color, intensity, width, height ) {
+               this.onStart = undefined;
+               this.onLoad = onLoad;
+               this.onProgress = onProgress;
+               this.onError = onError;
 
-       Light.call( this, color, intensity );
+               this.itemStart = function ( url ) {
 
-       this.type = 'RectAreaLight';
+                       itemsTotal ++;
 
-       this.width = ( width !== undefined ) ? width : 10;
-       this.height = ( height !== undefined ) ? height : 10;
+                       if ( isLoading === false ) {
 
-}
+                               if ( scope.onStart !== undefined ) {
 
-RectAreaLight.prototype = Object.assign( Object.create( Light.prototype ), {
+                                       scope.onStart( url, itemsLoaded, itemsTotal );
 
-       constructor: RectAreaLight,
+                               }
 
-       isRectAreaLight: true,
+                       }
 
-       copy: function ( source ) {
+                       isLoading = true;
 
-               Light.prototype.copy.call( this, source );
+               };
 
-               this.width = source.width;
-               this.height = source.height;
+               this.itemEnd = function ( url ) {
 
-               return this;
+                       itemsLoaded ++;
 
-       },
+                       if ( scope.onProgress !== undefined ) {
 
-       toJSON: function ( meta ) {
+                               scope.onProgress( url, itemsLoaded, itemsTotal );
 
-               const data = Light.prototype.toJSON.call( this, meta );
+                       }
 
-               data.object.width = this.width;
-               data.object.height = this.height;
+                       if ( itemsLoaded === itemsTotal ) {
 
-               return data;
+                               isLoading = false;
 
-       }
+                               if ( scope.onLoad !== undefined ) {
 
-);
+                                       scope.onLoad();
 
-/**
- * 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() {
+               this.itemError = function ( url ) {
 
-               Object.defineProperty( this, 'isSphericalHarmonics3', { value: true } );
+                       if ( scope.onError !== undefined ) {
 
-               this.coefficients = [];
+                               scope.onError( url );
 
-               for ( let i = 0; i < 9; i ++ ) {
+                       }
 
-                       this.coefficients.push( new Vector3() );
+               };
 
-               }
+               this.resolveURL = function ( url ) {
 
-       }
+                       if ( urlModifier ) {
 
-       set( coefficients ) {
+                               return urlModifier( url );
 
-               for ( let i = 0; i < 9; i ++ ) {
+                       }
 
-                       this.coefficients[ i ].copy( coefficients[ i ] );
+                       return url;
 
-               }
+               };
 
-               return this;
+               this.setURLModifier = function ( transform ) {
 
-       }
+                       urlModifier = transform;
 
-       zero() {
+                       return this;
 
-               for ( let i = 0; i < 9; i ++ ) {
+               };
 
-                       this.coefficients[ i ].set( 0, 0, 0 );
+               this.addHandler = function ( regex, loader ) {
 
-               }
+                       handlers.push( regex, loader );
 
-               return this;
+                       return this;
 
-       }
+               };
 
-       // get the radiance in the direction of the normal
-       // target is a Vector3
-       getAt( normal, target ) {
+               this.removeHandler = function ( regex ) {
 
-               // normal is assumed to be unit length
+                       const index = handlers.indexOf( regex );
 
-               const x = normal.x, y = normal.y, z = normal.z;
+                       if ( index !== - 1 ) {
 
-               const coeff = this.coefficients;
+                               handlers.splice( index, 2 );
 
-               // 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 );
+                       return this;
 
-               // 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;
+               this.getHandler = function ( file ) {
 
-       }
+                       for ( let i = 0, l = handlers.length; i < l; i += 2 ) {
 
-       // 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 ) {
+                               const regex = handlers[ i ];
+                               const loader = handlers[ i + 1 ];
 
-               // normal is assumed to be unit length
+                               if ( regex.global ) regex.lastIndex = 0; // see #17920
 
-               const x = normal.x, y = normal.y, z = normal.z;
+                               if ( regex.test( file ) ) {
 
-               const coeff = this.coefficients;
+                                       return loader;
 
-               // 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 null;
 
-               return target;
+               };
 
        }
 
-       add( sh ) {
+}
 
-               for ( let i = 0; i < 9; i ++ ) {
+const DefaultLoadingManager = new LoadingManager();
 
-                       this.coefficients[ i ].add( sh.coefficients[ i ] );
+class Loader {
 
-               }
+       constructor( manager ) {
 
-               return this;
+               this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
 
-       }
+               this.crossOrigin = 'anonymous';
+               this.withCredentials = false;
+               this.path = '';
+               this.resourcePath = '';
+               this.requestHeader = {};
 
-       addScaledSH( sh, s ) {
+       }
 
-               for ( let i = 0; i < 9; i ++ ) {
+       load( /* url, onLoad, onProgress, onError */ ) {}
 
-                       this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s );
+       loadAsync( url, onProgress ) {
 
-               }
+               const scope = this;
 
-               return this;
+               return new Promise( function ( resolve, reject ) {
 
-       }
+                       scope.load( url, resolve, onProgress, reject );
 
-       scale( s ) {
+               } );
 
-               for ( let i = 0; i < 9; i ++ ) {
+       }
 
-                       this.coefficients[ i ].multiplyScalar( s );
+       parse( /* data */ ) {}
 
-               }
+       setCrossOrigin( crossOrigin ) {
 
+               this.crossOrigin = crossOrigin;
                return this;
 
        }
 
-       lerp( sh, alpha ) {
+       setWithCredentials( value ) {
 
-               for ( let i = 0; i < 9; i ++ ) {
+               this.withCredentials = value;
+               return this;
 
-                       this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha );
+       }
 
-               }
+       setPath( path ) {
 
+               this.path = path;
                return this;
 
        }
 
-       equals( sh ) {
-
-               for ( let i = 0; i < 9; i ++ ) {
-
-                       if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) {
+       setResourcePath( resourcePath ) {
 
-                               return false;
+               this.resourcePath = resourcePath;
+               return this;
 
-                       }
+       }
 
-               }
+       setRequestHeader( requestHeader ) {
 
-               return true;
+               this.requestHeader = requestHeader;
+               return this;
 
        }
 
-       copy( sh ) {
+}
 
-               return this.set( sh.coefficients );
+const loading = {};
 
-       }
+class FileLoader extends Loader {
 
-       clone() {
+       constructor( manager ) {
 
-               return new this.constructor().copy( this );
+               super( manager );
 
        }
 
-       fromArray( array, offset = 0 ) {
+       load( url, onLoad, onProgress, onError ) {
 
-               const coefficients = this.coefficients;
+               if ( url === undefined ) url = '';
 
-               for ( let i = 0; i < 9; i ++ ) {
+               if ( this.path !== undefined ) url = this.path + url;
 
-                       coefficients[ i ].fromArray( array, offset + ( i * 3 ) );
+               url = this.manager.resolveURL( url );
 
-               }
+               const cached = Cache.get( url );
 
-               return this;
+               if ( cached !== undefined ) {
 
-       }
+                       this.manager.itemStart( url );
 
-       toArray( array = [], offset = 0 ) {
+                       setTimeout( () => {
 
-               const coefficients = this.coefficients;
+                               if ( onLoad ) onLoad( cached );
 
-               for ( let i = 0; i < 9; i ++ ) {
+                               this.manager.itemEnd( url );
 
-                       coefficients[ i ].toArray( array, offset + ( i * 3 ) );
+                       }, 0 );
+
+                       return cached;
 
                }
 
-               return array;
+               // Check if request is duplicate
 
-       }
+               if ( loading[ url ] !== undefined ) {
 
-       // evaluate the basis functions
-       // shBasis is an Array[ 9 ]
-       static getBasisAt( normal, shBasis ) {
+                       loading[ url ].push( {
 
-               // normal is assumed to be unit length
+                               onLoad: onLoad,
+                               onProgress: onProgress,
+                               onError: onError
 
-               const x = normal.x, y = normal.y, z = normal.z;
+                       } );
 
-               // band 0
-               shBasis[ 0 ] = 0.282095;
+                       return;
 
-               // 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 );
+               // Initialise array for duplicate requests
+               loading[ url ] = [];
 
-       }
+               loading[ url ].push( {
+                       onLoad: onLoad,
+                       onProgress: onProgress,
+                       onError: onError,
+               } );
 
-}
+               // create request
+               const req = new Request( url, {
+                       headers: new Headers( this.requestHeader ),
+                       credentials: this.withCredentials ? 'include' : 'same-origin',
+                       // An abort controller could be added within a future PR
+               } );
 
-function LightProbe( sh, intensity ) {
+               // start the fetch
+               fetch( req )
+                       .then( response => {
 
-       Light.call( this, undefined, intensity );
+                               if ( response.status === 200 || response.status === 0 ) {
 
-       this.type = 'LightProbe';
+                                       // Some browsers return HTTP Status 0 when using non-http protocol
+                                       // e.g. 'file://' or 'data://'. Handle as success.
 
-       this.sh = ( sh !== undefined ) ? sh : new SphericalHarmonics3();
+                                       if ( response.status === 0 ) {
 
-}
+                                               console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
 
-LightProbe.prototype = Object.assign( Object.create( Light.prototype ), {
+                                       }
 
-       constructor: LightProbe,
+                                       const callbacks = loading[ url ];
+                                       const reader = response.body.getReader();
+                                       const contentLength = response.headers.get( 'Content-Length' );
+                                       const total = contentLength ? parseInt( contentLength ) : 0;
+                                       const lengthComputable = total !== 0;
+                                       let loaded = 0;
 
-       isLightProbe: true,
+                                       // periodically read data into the new stream tracking while download progress
+                                       return new ReadableStream( {
+                                               start( controller ) {
 
-       copy: function ( source ) {
+                                                       readData();
 
-               Light.prototype.copy.call( this, source );
+                                                       function readData() {
 
-               this.sh.copy( source.sh );
+                                                               reader.read().then( ( { done, value } ) => {
 
-               return this;
+                                                                       if ( done ) {
 
-       },
+                                                                               controller.close();
 
-       fromJSON: function ( json ) {
+                                                                       } else {
 
-               this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON();
-               this.sh.fromArray( json.sh );
+                                                                               loaded += value.byteLength;
 
-               return this;
+                                                                               const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } );
+                                                                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
 
-       },
+                                                                                       const callback = callbacks[ i ];
+                                                                                       if ( callback.onProgress ) callback.onProgress( event );
 
-       toJSON: function ( meta ) {
+                                                                               }
 
-               const data = Light.prototype.toJSON.call( this, meta );
+                                                                               controller.enqueue( value );
+                                                                               readData();
 
-               data.object.sh = this.sh.toArray();
+                                                                       }
 
-               return data;
+                                                               } );
 
-       }
+                                                       }
 
-} );
+                                               }
 
-function MaterialLoader( manager ) {
+                                       } );
 
-       Loader.call( this, manager );
+                               } else {
 
-       this.textures = {};
+                                       throw Error( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}` );
 
-}
+                               }
 
-MaterialLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+                       } )
+                       .then( stream => {
 
-       constructor: MaterialLoader,
+                               const response = new Response( stream );
 
-       load: function ( url, onLoad, onProgress, onError ) {
+                               switch ( this.responseType ) {
 
-               const scope = this;
+                                       case 'arraybuffer':
 
-               const loader = new FileLoader( scope.manager );
-               loader.setPath( scope.path );
-               loader.setRequestHeader( scope.requestHeader );
-               loader.setWithCredentials( scope.withCredentials );
-               loader.load( url, function ( text ) {
+                                               return response.arrayBuffer();
 
-                       try {
+                                       case 'blob':
 
-                               onLoad( scope.parse( JSON.parse( text ) ) );
+                                               return response.blob();
 
-                       } catch ( e ) {
+                                       case 'document':
 
-                               if ( onError ) {
+                                               return response.text()
+                                                       .then( text => {
 
-                                       onError( e );
+                                                               const parser = new DOMParser();
+                                                               return parser.parseFromString( text, this.mimeType );
 
-                               } else {
+                                                       } );
 
-                                       console.error( e );
+                                       case 'json':
 
-                               }
+                                               return response.json();
 
-                               scope.manager.itemError( url );
+                                       default:
 
-                       }
+                                               return response.text();
 
-               }, onProgress, onError );
+                               }
 
-       },
+                       } )
+                       .then( data => {
 
-       parse: function ( json ) {
+                               // Add to cache only on HTTP success, so that we do not cache
+                               // error response bodies as proper responses to requests.
+                               Cache.add( url, data );
 
-               const textures = this.textures;
+                               const callbacks = loading[ url ];
+                               delete loading[ url ];
 
-               function getTexture( name ) {
+                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
 
-                       if ( textures[ name ] === undefined ) {
+                                       const callback = callbacks[ i ];
+                                       if ( callback.onLoad ) callback.onLoad( data );
 
-                               console.warn( 'THREE.MaterialLoader: Undefined texture', name );
+                               }
 
-                       }
+                               this.manager.itemEnd( url );
 
-                       return textures[ name ];
+                       } )
+                       .catch( err => {
 
-               }
+                               // Abort errors and other errors are handled the same
 
-               const material = new Materials[ json.type ]();
+                               const callbacks = loading[ url ];
+                               delete loading[ url ];
 
-               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;
+                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
 
-               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;
+                                       const callback = callbacks[ i ];
+                                       if ( callback.onError ) callback.onError( err );
 
-               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;
+                               this.manager.itemError( url );
+                               this.manager.itemEnd( url );
 
-               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;
+               this.manager.itemStart( url );
 
-               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;
+       setResponseType( value ) {
 
-               if ( json.visible !== undefined ) material.visible = json.visible;
+               this.responseType = value;
+               return this;
 
-               if ( json.toneMapped !== undefined ) material.toneMapped = json.toneMapped;
+       }
 
-               if ( json.userData !== undefined ) material.userData = json.userData;
+       setMimeType( value ) {
 
-               if ( json.vertexColors !== undefined ) {
+               this.mimeType = value;
+               return this;
 
-                       if ( typeof json.vertexColors === 'number' ) {
+       }
 
-                               material.vertexColors = ( json.vertexColors > 0 ) ? true : false;
+}
 
-                       } else {
+class ImageLoader extends Loader {
 
-                               material.vertexColors = json.vertexColors;
+       constructor( manager ) {
 
-                       }
+               super( manager );
 
-               }
+       }
 
-               // Shader Material
+       load( url, onLoad, onProgress, onError ) {
 
-               if ( json.uniforms !== undefined ) {
+               if ( this.path !== undefined ) url = this.path + url;
 
-                       for ( const name in json.uniforms ) {
+               url = this.manager.resolveURL( url );
 
-                               const uniform = json.uniforms[ name ];
+               const scope = this;
 
-                               material.uniforms[ name ] = {};
+               const cached = Cache.get( url );
 
-                               switch ( uniform.type ) {
+               if ( cached !== undefined ) {
 
-                                       case 't':
-                                               material.uniforms[ name ].value = getTexture( uniform.value );
-                                               break;
+                       scope.manager.itemStart( url );
 
-                                       case 'c':
-                                               material.uniforms[ name ].value = new Color().setHex( uniform.value );
-                                               break;
+                       setTimeout( function () {
 
-                                       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;
+                               if ( onLoad ) onLoad( cached );
 
-                                       default:
-                                               material.uniforms[ name ].value = uniform.value;
+                               scope.manager.itemEnd( url );
 
-                               }
+                       }, 0 );
 
-                       }
+                       return cached;
 
                }
 
-               if ( json.defines !== undefined ) material.defines = json.defines;
-               if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader;
-               if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader;
+               const image = createElementNS( 'img' );
 
-               if ( json.extensions !== undefined ) {
-
-                       for ( const key in json.extensions ) {
-
-                               material.extensions[ key ] = json.extensions[ key ];
-
-                       }
+               function onImageLoad() {
 
-               }
+                       removeEventListeners();
 
-               // Deprecated
+                       Cache.add( url, this );
 
-               if ( json.shading !== undefined ) material.flatShading = json.shading === 1; // THREE.FlatShading
+                       if ( onLoad ) onLoad( this );
 
-               // for PointsMaterial
+                       scope.manager.itemEnd( url );
 
-               if ( json.size !== undefined ) material.size = json.size;
-               if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation;
+               }
 
-               // maps
+               function onImageError( event ) {
 
-               if ( json.map !== undefined ) material.map = getTexture( json.map );
-               if ( json.matcap !== undefined ) material.matcap = getTexture( json.matcap );
+                       removeEventListeners();
 
-               if ( json.alphaMap !== undefined ) material.alphaMap = getTexture( json.alphaMap );
+                       if ( onError ) onError( event );
 
-               if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap );
-               if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale;
+                       scope.manager.itemError( url );
+                       scope.manager.itemEnd( url );
 
-               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;
+               function removeEventListeners() {
 
-                       if ( Array.isArray( normalScale ) === false ) {
+                       image.removeEventListener( 'load', onImageLoad, false );
+                       image.removeEventListener( 'error', onImageError, false );
 
-                               // Blender exporter used to export a scalar. See #7459
+               }
 
-                               normalScale = [ normalScale, normalScale ];
+               image.addEventListener( 'load', onImageLoad, false );
+               image.addEventListener( 'error', onImageError, false );
 
-                       }
+               if ( url.substr( 0, 5 ) !== 'data:' ) {
 
-                       material.normalScale = new Vector2().fromArray( normalScale );
+                       if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;
 
                }
 
-               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 );
+               scope.manager.itemStart( url );
 
-               if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap );
-               if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity;
+               image.src = url;
 
-               if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap );
+               return image;
 
-               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;
+class CubeTextureLoader extends Loader {
 
-               if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap );
-               if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity;
+       constructor( manager ) {
 
-               if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap );
+               super( manager );
 
-               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 );
+       load( urls, onLoad, onProgress, onError ) {
 
-               return material;
+               const texture = new CubeTexture();
 
-       },
+               const loader = new ImageLoader( this.manager );
+               loader.setCrossOrigin( this.crossOrigin );
+               loader.setPath( this.path );
 
-       setTextures: function ( value ) {
+               let loaded = 0;
 
-               this.textures = value;
-               return this;
+               function loadTexture( i ) {
 
-       }
+                       loader.load( urls[ i ], function ( image ) {
 
-} );
+                               texture.images[ i ] = image;
 
-const LoaderUtils = {
+                               loaded ++;
 
-       decodeText: function ( array ) {
+                               if ( loaded === 6 ) {
 
-               if ( typeof TextDecoder !== 'undefined' ) {
+                                       texture.needsUpdate = true;
 
-                       return new TextDecoder().decode( array );
+                                       if ( onLoad ) onLoad( texture );
 
-               }
+                               }
 
-               // Avoid the String.fromCharCode.apply(null, array) shortcut, which
-               // throws a "maximum call stack size exceeded" error for large arrays.
+                       }, undefined, onError );
 
-               let s = '';
+               }
 
-               for ( let i = 0, il = array.length; i < il; i ++ ) {
+               for ( let i = 0; i < urls.length; ++ i ) {
 
-                       // Implicitly assumes little-endian.
-                       s += String.fromCharCode( array[ i ] );
+                       loadTexture( i );
 
                }
 
-               try {
-
-                       // merges multi-byte utf-8 characters.
+               return texture;
 
-                       return decodeURIComponent( escape( s ) );
+       }
 
-               } catch ( e ) { // see #16358
+}
 
-                       return s;
+class TextureLoader extends Loader {
 
-               }
+       constructor( manager ) {
 
-       },
+               super( manager );
 
-       extractUrlBase: function ( url ) {
+       }
 
-               const index = url.lastIndexOf( '/' );
+       load( url, onLoad, onProgress, onError ) {
 
-               if ( index === - 1 ) return './';
+               const texture = new Texture();
 
-               return url.substr( 0, index + 1 );
+               const loader = new ImageLoader( this.manager );
+               loader.setCrossOrigin( this.crossOrigin );
+               loader.setPath( this.path );
 
-       }
+               loader.load( url, function ( image ) {
 
-};
+                       texture.image = image;
+                       texture.needsUpdate = true;
 
-function InstancedBufferGeometry() {
+                       if ( onLoad !== undefined ) {
 
-       BufferGeometry.call( this );
+                               onLoad( texture );
 
-       this.type = 'InstancedBufferGeometry';
-       this.instanceCount = Infinity;
+                       }
 
-}
+               }, onProgress, onError );
 
-InstancedBufferGeometry.prototype = Object.assign( Object.create( BufferGeometry.prototype ), {
+               return texture;
 
-       constructor: InstancedBufferGeometry,
+       }
 
-       isInstancedBufferGeometry: true,
+}
 
-       copy: function ( source ) {
+class Light extends Object3D {
 
-               BufferGeometry.prototype.copy.call( this, source );
+       constructor( color, intensity = 1 ) {
 
-               this.instanceCount = source.instanceCount;
+               super();
 
-               return this;
+               this.type = 'Light';
 
-       },
+               this.color = new Color( color );
+               this.intensity = intensity;
 
-       clone: function () {
+       }
 
-               return new this.constructor().copy( this );
+       dispose() {
 
-       },
+               // Empty here in base class; some subclasses override.
 
-       toJSON: function () {
+       }
 
-               const data = BufferGeometry.prototype.toJSON.call( this );
+       copy( source ) {
 
-               data.instanceCount = this.instanceCount;
+               super.copy( source );
 
-               data.isInstancedBufferGeometry = true;
+               this.color.copy( source.color );
+               this.intensity = source.intensity;
 
-               return data;
+               return this;
 
        }
 
-} );
-
-function InstancedBufferAttribute( array, itemSize, normalized, meshPerAttribute ) {
+       toJSON( meta ) {
 
-       if ( typeof ( normalized ) === 'number' ) {
+               const data = super.toJSON( meta );
 
-               meshPerAttribute = normalized;
+               data.object.color = this.color.getHex();
+               data.object.intensity = this.intensity;
 
-               normalized = false;
+               if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex();
 
-               console.error( 'THREE.InstancedBufferAttribute: The constructor now expects normalized as the third argument.' );
+               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();
 
-       BufferAttribute.call( this, array, itemSize, normalized );
+               return data;
 
-       this.meshPerAttribute = meshPerAttribute || 1;
+       }
 
 }
 
-InstancedBufferAttribute.prototype = Object.assign( Object.create( BufferAttribute.prototype ), {
-
-       constructor: InstancedBufferAttribute,
+Light.prototype.isLight = true;
 
-       isInstancedBufferAttribute: true,
+class HemisphereLight extends Light {
 
-       copy: function ( source ) {
+       constructor( skyColor, groundColor, intensity ) {
 
-               BufferAttribute.prototype.copy.call( this, source );
+               super( skyColor, intensity );
 
-               this.meshPerAttribute = source.meshPerAttribute;
+               this.type = 'HemisphereLight';
 
-               return this;
+               this.position.copy( Object3D.DefaultUp );
+               this.updateMatrix();
 
-       },
+               this.groundColor = new Color( groundColor );
 
-       toJSON: function ()     {
+       }
 
-               const data = BufferAttribute.prototype.toJSON.call( this );
+       copy( source ) {
 
-               data.meshPerAttribute = this.meshPerAttribute;
+               Light.prototype.copy.call( this, source );
 
-               data.isInstancedBufferAttribute = true;
+               this.groundColor.copy( source.groundColor );
 
-               return data;
+               return this;
 
        }
 
-} );
-
-function BufferGeometryLoader( manager ) {
-
-       Loader.call( this, manager );
-
 }
 
-BufferGeometryLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+HemisphereLight.prototype.isHemisphereLight = true;
 
-       constructor: BufferGeometryLoader,
+const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4();
+const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3();
+const _lookTarget$1 = /*@__PURE__*/ new Vector3();
 
-       load: function ( url, onLoad, onProgress, onError ) {
+class LightShadow {
 
-               const scope = this;
+       constructor( camera ) {
 
-               const loader = new FileLoader( scope.manager );
-               loader.setPath( scope.path );
-               loader.setRequestHeader( scope.requestHeader );
-               loader.setWithCredentials( scope.withCredentials );
-               loader.load( url, function ( text ) {
+               this.camera = camera;
 
-                       try {
+               this.bias = 0;
+               this.normalBias = 0;
+               this.radius = 1;
+               this.blurSamples = 8;
 
-                               onLoad( scope.parse( JSON.parse( text ) ) );
+               this.mapSize = new Vector2( 512, 512 );
 
-                       } catch ( e ) {
+               this.map = null;
+               this.mapPass = null;
+               this.matrix = new Matrix4();
 
-                               if ( onError ) {
+               this.autoUpdate = true;
+               this.needsUpdate = false;
 
-                                       onError( e );
+               this._frustum = new Frustum();
+               this._frameExtents = new Vector2( 1, 1 );
 
-                               } else {
+               this._viewportCount = 1;
 
-                                       console.error( e );
+               this._viewports = [
 
-                               }
+                       new Vector4( 0, 0, 1, 1 )
 
-                               scope.manager.itemError( url );
+               ];
 
-                       }
+       }
 
-               }, onProgress, onError );
+       getViewportCount() {
 
-       },
+               return this._viewportCount;
 
-       parse: function ( json ) {
+       }
 
-               const interleavedBufferMap = {};
-               const arrayBufferMap = {};
+       getFrustum() {
 
-               function getInterleavedBuffer( json, uuid ) {
+               return this._frustum;
 
-                       if ( interleavedBufferMap[ uuid ] !== undefined ) return interleavedBufferMap[ uuid ];
+       }
 
-                       const interleavedBuffers = json.interleavedBuffers;
-                       const interleavedBuffer = interleavedBuffers[ uuid ];
+       updateMatrices( light ) {
 
-                       const buffer = getArrayBuffer( json, interleavedBuffer.buffer );
+               const shadowCamera = this.camera;
+               const shadowMatrix = this.matrix;
 
-                       const array = getTypedArray( interleavedBuffer.type, buffer );
-                       const ib = new InterleavedBuffer( array, interleavedBuffer.stride );
-                       ib.uuid = interleavedBuffer.uuid;
+               _lightPositionWorld$1.setFromMatrixPosition( light.matrixWorld );
+               shadowCamera.position.copy( _lightPositionWorld$1 );
 
-                       interleavedBufferMap[ uuid ] = ib;
+               _lookTarget$1.setFromMatrixPosition( light.target.matrixWorld );
+               shadowCamera.lookAt( _lookTarget$1 );
+               shadowCamera.updateMatrixWorld();
 
-                       return ib;
+               _projScreenMatrix$1.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );
+               this._frustum.setFromProjectionMatrix( _projScreenMatrix$1 );
 
-               }
+               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
+               );
 
-               function getArrayBuffer( json, uuid ) {
+               shadowMatrix.multiply( shadowCamera.projectionMatrix );
+               shadowMatrix.multiply( shadowCamera.matrixWorldInverse );
 
-                       if ( arrayBufferMap[ uuid ] !== undefined ) return arrayBufferMap[ uuid ];
+       }
 
-                       const arrayBuffers = json.arrayBuffers;
-                       const arrayBuffer = arrayBuffers[ uuid ];
+       getViewport( viewportIndex ) {
 
-                       const ab = new Uint32Array( arrayBuffer ).buffer;
+               return this._viewports[ viewportIndex ];
 
-                       arrayBufferMap[ uuid ] = ab;
+       }
 
-                       return ab;
+       getFrameExtents() {
 
-               }
+               return this._frameExtents;
 
-               const geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry();
+       }
 
-               const index = json.data.index;
+       dispose() {
 
-               if ( index !== undefined ) {
+               if ( this.map ) {
 
-                       const typedArray = getTypedArray( index.type, index.array );
-                       geometry.setIndex( new BufferAttribute( typedArray, 1 ) );
+                       this.map.dispose();
 
                }
 
-               const attributes = json.data.attributes;
+               if ( this.mapPass ) {
 
-               for ( const key in attributes ) {
+                       this.mapPass.dispose();
 
-                       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 );
+       copy( source ) {
 
-                       } else {
+               this.camera = source.camera.clone();
 
-                               const typedArray = getTypedArray( attribute.type, attribute.array );
-                               const bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute;
-                               bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized );
+               this.bias = source.bias;
+               this.radius = source.radius;
 
-                       }
+               this.mapSize.copy( source.mapSize );
 
-                       if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name;
-                       geometry.setAttribute( key, bufferAttribute );
+               return this;
 
-               }
+       }
 
-               const morphAttributes = json.data.morphAttributes;
+       clone() {
 
-               if ( morphAttributes ) {
+               return new this.constructor().copy( this );
 
-                       for ( const key in morphAttributes ) {
+       }
 
-                               const attributeArray = morphAttributes[ key ];
+       toJSON() {
 
-                               const array = [];
+               const object = {};
 
-                               for ( let i = 0, il = attributeArray.length; i < il; i ++ ) {
+               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();
 
-                                       const attribute = attributeArray[ i ];
-                                       let bufferAttribute;
+               object.camera = this.camera.toJSON( false ).object;
+               delete object.camera.matrix;
 
-                                       if ( attribute.isInterleavedBufferAttribute ) {
+               return object;
 
-                                               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 );
+class SpotLightShadow extends LightShadow {
 
-                                       }
+       constructor() {
 
-                                       if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name;
-                                       array.push( bufferAttribute );
+               super( new PerspectiveCamera( 50, 1, 0.5, 500 ) );
 
-                               }
+               this.focus = 1;
 
-                               geometry.morphAttributes[ key ] = array;
+       }
 
-                       }
+       updateMatrices( light ) {
 
-               }
+               const camera = this.camera;
 
-               const morphTargetsRelative = json.data.morphTargetsRelative;
+               const fov = RAD2DEG$1 * 2 * light.angle * this.focus;
+               const aspect = this.mapSize.width / this.mapSize.height;
+               const far = light.distance || camera.far;
 
-               if ( morphTargetsRelative ) {
+               if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) {
 
-                       geometry.morphTargetsRelative = true;
+                       camera.fov = fov;
+                       camera.aspect = aspect;
+                       camera.far = far;
+                       camera.updateProjectionMatrix();
 
                }
 
-               const groups = json.data.groups || json.data.drawcalls || json.data.offsets;
+               super.updateMatrices( light );
 
-               if ( groups !== undefined ) {
+       }
 
-                       for ( let i = 0, n = groups.length; i !== n; ++ i ) {
+       copy( source ) {
 
-                               const group = groups[ i ];
+               super.copy( source );
 
-                               geometry.addGroup( group.start, group.count, group.materialIndex );
+               this.focus = source.focus;
 
-                       }
+               return this;
 
-               }
+       }
 
-               const boundingSphere = json.data.boundingSphere;
+}
 
-               if ( boundingSphere !== undefined ) {
+SpotLightShadow.prototype.isSpotLightShadow = true;
 
-                       const center = new Vector3();
+class SpotLight extends Light {
 
-                       if ( boundingSphere.center !== undefined ) {
+       constructor( color, intensity, distance = 0, angle = Math.PI / 3, penumbra = 0, decay = 1 ) {
 
-                               center.fromArray( boundingSphere.center );
+               super( color, intensity );
 
-                       }
+               this.type = 'SpotLight';
 
-                       geometry.boundingSphere = new Sphere( center, boundingSphere.radius );
+               this.position.copy( Object3D.DefaultUp );
+               this.updateMatrix();
 
-               }
+               this.target = new Object3D();
 
-               if ( json.name ) geometry.name = json.name;
-               if ( json.userData ) geometry.userData = json.userData;
+               this.distance = distance;
+               this.angle = angle;
+               this.penumbra = penumbra;
+               this.decay = decay; // for physically correct lights, should be 2.
 
-               return geometry;
+               this.shadow = new SpotLightShadow();
 
        }
 
-} );
+       get power() {
 
-function ImageBitmapLoader( manager ) {
+               // compute the light's luminous power (in lumens) from its intensity (in candela)
+               // by convention for a spotlight, luminous power (lm) = Ï€ * luminous intensity (cd)
+               return this.intensity * Math.PI;
+
+       }
 
-       if ( typeof createImageBitmap === 'undefined' ) {
+       set power( power ) {
 
-               console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' );
+               // set the light's intensity (in candela) from the desired luminous power (in lumens)
+               this.intensity = power / Math.PI;
 
        }
 
-       if ( typeof fetch === 'undefined' ) {
+       dispose() {
 
-               console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' );
+               this.shadow.dispose();
 
        }
 
-       Loader.call( this, manager );
+       copy( source ) {
 
-       this.options = { premultiplyAlpha: 'none' };
+               super.copy( source );
 
-}
+               this.distance = source.distance;
+               this.angle = source.angle;
+               this.penumbra = source.penumbra;
+               this.decay = source.decay;
+
+               this.target = source.target.clone();
 
-ImageBitmapLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+               this.shadow = source.shadow.clone();
 
-       constructor: ImageBitmapLoader,
+               return this;
 
-       isImageBitmapLoader: true,
+       }
 
-       setOptions: function setOptions( options ) {
+}
 
-               this.options = options;
+SpotLight.prototype.isSpotLight = true;
 
-               return this;
+const _projScreenMatrix = /*@__PURE__*/ new Matrix4();
+const _lightPositionWorld = /*@__PURE__*/ new Vector3();
+const _lookTarget = /*@__PURE__*/ new Vector3();
 
-       },
+class PointLightShadow extends LightShadow {
 
-       load: function ( url, onLoad, onProgress, onError ) {
+       constructor() {
 
-               if ( url === undefined ) url = '';
+               super( new PerspectiveCamera( 90, 1, 0.5, 500 ) );
 
-               if ( this.path !== undefined ) url = this.path + url;
+               this._frameExtents = new Vector2( 4, 2 );
 
-               url = this.manager.resolveURL( url );
+               this._viewportCount = 6;
 
-               const scope = this;
+               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 )
+               ];
 
-               const cached = Cache.get( url );
+               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 )
+               ];
 
-               if ( cached !== undefined ) {
+               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 )
+               ];
 
-                       scope.manager.itemStart( url );
+       }
 
-                       setTimeout( function () {
+       updateMatrices( light, viewportIndex = 0 ) {
 
-                               if ( onLoad ) onLoad( cached );
+               const camera = this.camera;
+               const shadowMatrix = this.matrix;
 
-                               scope.manager.itemEnd( url );
+               const far = light.distance || camera.far;
 
-                       }, 0 );
+               if ( far !== camera.far ) {
 
-                       return cached;
+                       camera.far = far;
+                       camera.updateProjectionMatrix();
 
                }
 
-               const fetchOptions = {};
-               fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include';
-
-               fetch( url, fetchOptions ).then( function ( res ) {
+               _lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
+               camera.position.copy( _lightPositionWorld );
 
-                       return res.blob();
+               _lookTarget.copy( camera.position );
+               _lookTarget.add( this._cubeDirections[ viewportIndex ] );
+               camera.up.copy( this._cubeUps[ viewportIndex ] );
+               camera.lookAt( _lookTarget );
+               camera.updateMatrixWorld();
 
-               } ).then( function ( blob ) {
+               shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z );
 
-                       return createImageBitmap( blob, scope.options );
+               _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+               this._frustum.setFromProjectionMatrix( _projScreenMatrix );
 
-               } ).then( function ( imageBitmap ) {
+       }
 
-                       Cache.add( url, imageBitmap );
+}
 
-                       if ( onLoad ) onLoad( imageBitmap );
+PointLightShadow.prototype.isPointLightShadow = true;
 
-                       scope.manager.itemEnd( url );
+class PointLight extends Light {
 
-               } ).catch( function ( e ) {
+       constructor( color, intensity, distance = 0, decay = 1 ) {
 
-                       if ( onError ) onError( e );
+               super( color, intensity );
 
-                       scope.manager.itemError( url );
-                       scope.manager.itemEnd( url );
+               this.type = 'PointLight';
 
-               } );
+               this.distance = distance;
+               this.decay = decay; // for physically correct lights, should be 2.
 
-               scope.manager.itemStart( url );
+               this.shadow = new PointLightShadow();
 
        }
 
-} );
+       get power() {
 
-function ShapePath() {
+               // compute the light's luminous power (in lumens) from its intensity (in candela)
+               // for an isotropic light source, luminous power (lm) = 4 Ï€ luminous intensity (cd)
+               return this.intensity * 4 * Math.PI;
 
-       this.type = 'ShapePath';
+       }
 
-       this.color = new Color();
+       set power( power ) {
 
-       this.subPaths = [];
-       this.currentPath = null;
+               // set the light's intensity (in candela) from the desired luminous power (in lumens)
+               this.intensity = power / ( 4 * Math.PI );
 
-}
+       }
 
-Object.assign( ShapePath.prototype, {
+       dispose() {
 
-       moveTo: function ( x, y ) {
+               this.shadow.dispose();
 
-               this.currentPath = new Path();
-               this.subPaths.push( this.currentPath );
-               this.currentPath.moveTo( x, y );
+       }
 
-               return this;
+       copy( source ) {
 
-       },
+               super.copy( source );
 
-       lineTo: function ( x, y ) {
+               this.distance = source.distance;
+               this.decay = source.decay;
 
-               this.currentPath.lineTo( x, y );
+               this.shadow = source.shadow.clone();
 
                return this;
 
-       },
-
-       quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
-
-               this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY );
+       }
 
-               return this;
+}
 
-       },
+PointLight.prototype.isPointLight = true;
 
-       bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
+class DirectionalLightShadow extends LightShadow {
 
-               this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY );
+       constructor() {
 
-               return this;
+               super( new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) );
 
-       },
+       }
 
-       splineThru: function ( pts ) {
+}
 
-               this.currentPath.splineThru( pts );
+DirectionalLightShadow.prototype.isDirectionalLightShadow = true;
 
-               return this;
+class DirectionalLight extends Light {
 
-       },
+       constructor( color, intensity ) {
 
-       toShapes: function ( isCCW, noHoles ) {
+               super( color, intensity );
 
-               function toShapesNoHoles( inSubpaths ) {
+               this.type = 'DirectionalLight';
 
-                       const shapes = [];
+               this.position.copy( Object3D.DefaultUp );
+               this.updateMatrix();
 
-                       for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) {
+               this.target = new Object3D();
 
-                               const tmpPath = inSubpaths[ i ];
+               this.shadow = new DirectionalLightShadow();
 
-                               const tmpShape = new Shape();
-                               tmpShape.curves = tmpPath.curves;
+       }
 
-                               shapes.push( tmpShape );
+       dispose() {
 
-                       }
+               this.shadow.dispose();
 
-                       return shapes;
+       }
 
-               }
+       copy( source ) {
 
-               function isPointInsidePolygon( inPt, inPolygon ) {
+               super.copy( source );
 
-                       const polyLen = inPolygon.length;
+               this.target = source.target.clone();
+               this.shadow = source.shadow.clone();
 
-                       // 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 ++ ) {
+               return this;
 
-                               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 ) {
+DirectionalLight.prototype.isDirectionalLight = true;
 
-                                       // not parallel
-                                       if ( edgeDy < 0 ) {
+class AmbientLight extends Light {
 
-                                               edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx;
-                                               edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;
+       constructor( color, intensity ) {
 
-                                       }
+               super( color, intensity );
 
-                                       if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) )            continue;
+               this.type = 'AmbientLight';
 
-                                       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 {
+AmbientLight.prototype.isAmbientLight = true;
 
-                                               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
+class RectAreaLight extends Light {
 
-                                       }
+       constructor( color, intensity, width = 10, height = 10 ) {
 
-                               } else {
+               super( color, intensity );
 
-                                       // 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;
+               this.type = 'RectAreaLight';
 
-                               }
+               this.width = width;
+               this.height = height;
 
-                       }
+       }
 
-                       return  inside;
+       get power() {
 
-               }
+               // compute the light's luminous power (in lumens) from its intensity (in nits)
+               return this.intensity * this.width * this.height * Math.PI;
 
-               const isClockWise = ShapeUtils.isClockWise;
+       }
 
-               const subPaths = this.subPaths;
-               if ( subPaths.length === 0 ) return [];
+       set power( power ) {
 
-               if ( noHoles === true ) return  toShapesNoHoles( subPaths );
+               // set the light's intensity (in nits) from the desired luminous power (in lumens)
+               this.intensity = power / ( this.width * this.height * Math.PI );
 
+       }
 
-               let solid, tmpPath, tmpShape;
-               const shapes = [];
+       copy( source ) {
 
-               if ( subPaths.length === 1 ) {
+               super.copy( source );
 
-                       tmpPath = subPaths[ 0 ];
-                       tmpShape = new Shape();
-                       tmpShape.curves = tmpPath.curves;
-                       shapes.push( tmpShape );
-                       return shapes;
+               this.width = source.width;
+               this.height = source.height;
 
-               }
+               return this;
 
-               let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() );
-               holesFirst = isCCW ? ! holesFirst : holesFirst;
+       }
 
-               // console.log("Holes first", holesFirst);
+       toJSON( meta ) {
 
-               const betterShapeHoles = [];
-               const newShapes = [];
-               let newShapeHoles = [];
-               let mainIdx = 0;
-               let tmpPoints;
+               const data = super.toJSON( meta );
 
-               newShapes[ mainIdx ] = undefined;
-               newShapeHoles[ mainIdx ] = [];
+               data.object.width = this.width;
+               data.object.height = this.height;
 
-               for ( let i = 0, l = subPaths.length; i < l; i ++ ) {
+               return data;
 
-                       tmpPath = subPaths[ i ];
-                       tmpPoints = tmpPath.getPoints();
-                       solid = isClockWise( tmpPoints );
-                       solid = isCCW ? ! solid : solid;
+       }
 
-                       if ( solid ) {
+}
 
-                               if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) )     mainIdx ++;
+RectAreaLight.prototype.isRectAreaLight = true;
 
-                               newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints };
-                               newShapes[ mainIdx ].s.curves = tmpPath.curves;
+/**
+ * Primary reference:
+ *   https://graphics.stanford.edu/papers/envmap/envmap.pdf
+ *
+ * Secondary reference:
+ *   https://www.ppsloan.org/publications/StupidSH36.pdf
+ */
 
-                               if ( holesFirst )       mainIdx ++;
-                               newShapeHoles[ mainIdx ] = [];
+// 3-band SH defined by 9 coefficients
 
-                               //console.log('cw', i);
+class SphericalHarmonics3 {
 
-                       } else {
+       constructor() {
 
-                               newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } );
+               this.coefficients = [];
 
-                               //console.log('ccw', i);
+               for ( let i = 0; i < 9; i ++ ) {
 
-                       }
+                       this.coefficients.push( new Vector3() );
 
                }
 
-               // only Holes? -> probably all Shapes with wrong orientation
-               if ( ! newShapes[ 0 ] ) return  toShapesNoHoles( subPaths );
-
+       }
 
-               if ( newShapes.length > 1 ) {
+       set( coefficients ) {
 
-                       let ambiguous = false;
-                       const toChange = [];
+               for ( let i = 0; i < 9; i ++ ) {
 
-                       for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
+                       this.coefficients[ i ].copy( coefficients[ i ] );
 
-                               betterShapeHoles[ sIdx ] = [];
+               }
 
-                       }
+               return this;
 
-                       for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
+       }
 
-                               const sho = newShapeHoles[ sIdx ];
+       zero() {
 
-                               for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) {
+               for ( let i = 0; i < 9; i ++ ) {
 
-                                       const ho = sho[ hIdx ];
-                                       let hole_unassigned = true;
+                       this.coefficients[ i ].set( 0, 0, 0 );
 
-                                       for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) {
+               }
 
-                                               if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) {
+               return this;
 
-                                                       if ( sIdx !== s2Idx )   toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } );
-                                                       if ( hole_unassigned ) {
+       }
 
-                                                               hole_unassigned = false;
-                                                               betterShapeHoles[ s2Idx ].push( ho );
+       // get the radiance in the direction of the normal
+       // target is a Vector3
+       getAt( normal, target ) {
 
-                                                       } else {
+               // normal is assumed to be unit length
 
-                                                               ambiguous = true;
+               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 );
 
-                                       if ( hole_unassigned ) {
+               // 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 ) );
 
-                                               betterShapeHoles[ sIdx ].push( ho );
+               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 ) {
 
-                       }
-                       // console.log("ambiguous: ", ambiguous);
+               // normal is assumed to be unit length
 
-                       if ( toChange.length > 0 ) {
+               const x = normal.x, y = normal.y, z = normal.z;
 
-                               // console.log("to change: ", toChange);
-                               if ( ! ambiguous )      newShapeHoles = betterShapeHoles;
+               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 );
 
-               let tmpHoles;
+               // 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
 
-               for ( let i = 0, il = newShapes.length; i < il; i ++ ) {
+               return target;
 
-                       tmpShape = newShapes[ i ].s;
-                       shapes.push( tmpShape );
-                       tmpHoles = newShapeHoles[ i ];
+       }
 
-                       for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) {
+       add( sh ) {
 
-                               tmpShape.holes.push( tmpHoles[ j ].h );
+               for ( let i = 0; i < 9; i ++ ) {
 
-                       }
+                       this.coefficients[ i ].add( sh.coefficients[ i ] );
 
                }
 
-               //console.log("shape", shapes);
-
-               return shapes;
+               return this;
 
        }
 
-} );
-
-class Font {
+       addScaledSH( sh, s ) {
 
-       constructor( data ) {
+               for ( let i = 0; i < 9; i ++ ) {
 
-               Object.defineProperty( this, 'isFont', { value: true } );
+                       this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s );
 
-               this.type = 'Font';
+               }
 
-               this.data = data;
+               return this;
 
        }
 
-       generateShapes( text, size = 100 ) {
-
-               const shapes = [];
-               const paths = createPaths( text, size, this.data );
+       scale( s ) {
 
-               for ( let p = 0, pl = paths.length; p < pl; p ++ ) {
+               for ( let i = 0; i < 9; i ++ ) {
 
-                       Array.prototype.push.apply( shapes, paths[ p ].toShapes() );
+                       this.coefficients[ i ].multiplyScalar( s );
 
                }
 
-               return shapes;
+               return this;
 
        }
 
-}
+       lerp( sh, alpha ) {
 
-function createPaths( text, size, data ) {
+               for ( let i = 0; i < 9; i ++ ) {
 
-       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;
+                       this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha );
 
-       const paths = [];
+               }
 
-       let offsetX = 0, offsetY = 0;
+               return this;
 
-       for ( let i = 0; i < chars.length; i ++ ) {
+       }
 
-               const char = chars[ i ];
+       equals( sh ) {
 
-               if ( char === '\n' ) {
+               for ( let i = 0; i < 9; i ++ ) {
 
-                       offsetX = 0;
-                       offsetY -= line_height;
+                       if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) {
 
-               } else {
+                               return false;
 
-                       const ret = createPath( char, scale, offsetX, offsetY, data );
-                       offsetX += ret.offsetX;
-                       paths.push( ret.path );
+                       }
 
                }
 
-       }
-
-       return paths;
+               return true;
 
-}
+       }
 
-function createPath( char, scale, offsetX, offsetY, data ) {
+       copy( sh ) {
 
-       const glyph = data.glyphs[ char ] || data.glyphs[ '?' ];
+               return this.set( sh.coefficients );
 
-       if ( ! glyph ) {
+       }
 
-               console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' );
+       clone() {
 
-               return;
+               return new this.constructor().copy( this );
 
        }
 
-       const path = new ShapePath();
+       fromArray( array, offset = 0 ) {
 
-       let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;
+               const coefficients = this.coefficients;
 
-       if ( glyph.o ) {
+               for ( let i = 0; i < 9; i ++ ) {
 
-               const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );
+                       coefficients[ i ].fromArray( array, offset + ( i * 3 ) );
 
-               for ( let i = 0, l = outline.length; i < l; ) {
+               }
 
-                       const action = outline[ i ++ ];
+               return this;
 
-                       switch ( action ) {
+       }
 
-                               case 'm': // moveTo
+       toArray( array = [], offset = 0 ) {
 
-                                       x = outline[ i ++ ] * scale + offsetX;
-                                       y = outline[ i ++ ] * scale + offsetY;
+               const coefficients = this.coefficients;
 
-                                       path.moveTo( x, y );
+               for ( let i = 0; i < 9; i ++ ) {
 
-                                       break;
+                       coefficients[ i ].toArray( array, offset + ( i * 3 ) );
 
-                               case 'l': // lineTo
+               }
 
-                                       x = outline[ i ++ ] * scale + offsetX;
-                                       y = outline[ i ++ ] * scale + offsetY;
+               return array;
 
-                                       path.lineTo( x, y );
+       }
 
-                                       break;
+       // evaluate the basis functions
+       // shBasis is an Array[ 9 ]
+       static getBasisAt( normal, shBasis ) {
 
-                               case 'q': // quadraticCurveTo
+               // normal is assumed to be unit length
 
-                                       cpx = outline[ i ++ ] * scale + offsetX;
-                                       cpy = outline[ i ++ ] * scale + offsetY;
-                                       cpx1 = outline[ i ++ ] * scale + offsetX;
-                                       cpy1 = outline[ i ++ ] * scale + offsetY;
+               const x = normal.x, y = normal.y, z = normal.z;
 
-                                       path.quadraticCurveTo( cpx1, cpy1, cpx, cpy );
+               // band 0
+               shBasis[ 0 ] = 0.282095;
 
-                                       break;
+               // band 1
+               shBasis[ 1 ] = 0.488603 * y;
+               shBasis[ 2 ] = 0.488603 * z;
+               shBasis[ 3 ] = 0.488603 * x;
 
-                               case 'b': // bezierCurveTo
+               // 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 );
 
-                                       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;
+SphericalHarmonics3.prototype.isSphericalHarmonics3 = true;
 
-                       }
+class LightProbe extends Light {
+
+       constructor( sh = new SphericalHarmonics3(), intensity = 1 ) {
+
+               super( undefined, intensity );
+
+               this.sh = sh;
+
+       }
+
+       copy( source ) {
+
+               super.copy( source );
+
+               this.sh.copy( source.sh );
+
+               return this;
+
+       }
+
+       fromJSON( json ) {
+
+               this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON();
+               this.sh.fromArray( json.sh );
+
+               return this;
+
+       }
+
+       toJSON( meta ) {
+
+               const data = super.toJSON( meta );
+
+               data.object.sh = this.sh.toArray();
+
+               return data;
+
+       }
+
+}
+
+LightProbe.prototype.isLightProbe = true;
+
+class LoaderUtils {
+
+       static decodeText( 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;
+
+               }
+
+       }
+
+       static extractUrlBase( url ) {
+
+               const index = url.lastIndexOf( '/' );
+
+               if ( index === - 1 ) return './';
+
+               return url.substr( 0, index + 1 );
+
        }
 
-       return { offsetX: glyph.ha * scale, path: path };
+       static resolveURL( url, path ) {
+
+               // Invalid URL
+               if ( typeof url !== 'string' || url === '' ) return '';
+
+               // Host Relative URL
+               if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) {
+
+                       path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' );
+
+               }
+
+               // Absolute URL http://,https://,//
+               if ( /^(https?:)?\/\//i.test( url ) ) return url;
+
+               // Data URI
+               if ( /^data:.*,.*$/i.test( url ) ) return url;
+
+               // Blob URL
+               if ( /^blob:.*$/i.test( url ) ) return url;
+
+               // Relative URL
+               return path + url;
+
+       }
 
 }
 
-function FontLoader( manager ) {
+class InstancedBufferGeometry extends BufferGeometry {
+
+       constructor() {
+
+               super();
+
+               this.type = 'InstancedBufferGeometry';
+               this.instanceCount = Infinity;
+
+       }
+
+       copy( source ) {
+
+               super.copy( source );
+
+               this.instanceCount = source.instanceCount;
+
+               return this;
+
+       }
+
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
+       toJSON() {
+
+               const data = super.toJSON( this );
+
+               data.instanceCount = this.instanceCount;
 
-       Loader.call( this, manager );
+               data.isInstancedBufferGeometry = true;
+
+               return data;
+
+       }
 
 }
 
-FontLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+InstancedBufferGeometry.prototype.isInstancedBufferGeometry = true;
+
+class ImageBitmapLoader extends Loader {
+
+       constructor( manager ) {
+
+               super( manager );
 
-       constructor: FontLoader,
+               if ( typeof createImageBitmap === 'undefined' ) {
 
-       load: function ( url, onLoad, onProgress, onError ) {
+                       console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' );
+
+               }
+
+               if ( typeof fetch === 'undefined' ) {
+
+                       console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' );
+
+               }
+
+               this.options = { premultiplyAlpha: 'none' };
+
+       }
+
+       setOptions( options ) {
+
+               this.options = options;
+
+               return this;
+
+       }
+
+       load( 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 loader = new FileLoader( this.manager );
-               loader.setPath( this.path );
-               loader.setRequestHeader( this.requestHeader );
-               loader.setWithCredentials( scope.withCredentials );
-               loader.load( url, function ( text ) {
+               const cached = Cache.get( url );
 
-                       let json;
+               if ( cached !== undefined ) {
 
-                       try {
+                       scope.manager.itemStart( url );
 
-                               json = JSON.parse( text );
+                       setTimeout( function () {
 
-                       } catch ( e ) {
+                               if ( onLoad ) onLoad( cached );
 
-                               console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' );
-                               json = JSON.parse( text.substring( 65, text.length - 2 ) );
+                               scope.manager.itemEnd( url );
 
-                       }
+                       }, 0 );
 
-                       const font = scope.parse( json );
+                       return cached;
 
-                       if ( onLoad ) onLoad( font );
+               }
 
-               }, onProgress, onError );
+               const fetchOptions = {};
+               fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include';
+               fetchOptions.headers = this.requestHeader;
 
-       },
+               fetch( url, fetchOptions ).then( function ( res ) {
+
+                       return res.blob();
+
+               } ).then( function ( blob ) {
+
+                       return createImageBitmap( blob, Object.assign( scope.options, { colorSpaceConversion: 'none' } ) );
 
-       parse: function ( json ) {
+               } ).then( function ( imageBitmap ) {
+
+                       Cache.add( url, imageBitmap );
+
+                       if ( onLoad ) onLoad( imageBitmap );
+
+                       scope.manager.itemEnd( url );
 
-               return new Font( json );
+               } ).catch( function ( e ) {
+
+                       if ( onError ) onError( e );
+
+                       scope.manager.itemError( url );
+                       scope.manager.itemEnd( url );
+
+               } );
+
+               scope.manager.itemStart( url );
 
        }
 
-} );
+}
+
+ImageBitmapLoader.prototype.isImageBitmapLoader = true;
 
 let _context;
 
@@ -41345,25 +40349,23 @@ const AudioContext = {
 
 };
 
-function AudioLoader( manager ) {
+class AudioLoader extends Loader {
 
-       Loader.call( this, manager );
+       constructor( manager ) {
 
-}
-
-AudioLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+               super( manager );
 
-       constructor: AudioLoader,
+       }
 
-       load: function ( url, onLoad, onProgress, onError ) {
+       load( url, onLoad, onProgress, onError ) {
 
                const scope = this;
 
-               const loader = new FileLoader( scope.manager );
+               const loader = new FileLoader( this.manager );
                loader.setResponseType( 'arraybuffer' );
-               loader.setPath( scope.path );
-               loader.setRequestHeader( scope.requestHeader );
-               loader.setWithCredentials( scope.withCredentials );
+               loader.setPath( this.path );
+               loader.setRequestHeader( this.requestHeader );
+               loader.setWithCredentials( this.withCredentials );
                loader.load( url, function ( buffer ) {
 
                        try {
@@ -41399,183 +40401,122 @@ AudioLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
 
        }
 
-} );
-
-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,
+class HemisphereLightProbe extends LightProbe {
 
-       copy: function ( source ) { // modifying colors not currently supported
+       constructor( skyColor, groundColor, intensity = 1 ) {
 
-               LightProbe.prototype.copy.call( this, source );
-
-               return this;
-
-       },
+               super( undefined, intensity );
 
-       toJSON: function ( meta ) {
+               const color1 = new Color().set( skyColor );
+               const color2 = new Color().set( groundColor );
 
-               const data = LightProbe.prototype.toJSON.call( this, meta );
+               const sky = new Vector3( color1.r, color1.g, color1.b );
+               const ground = new Vector3( color2.r, color2.g, color2.b );
 
-               // data.sh = this.sh.toArray(); // todo
+               // 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 );
 
-               return data;
+               this.sh.coefficients[ 0 ].copy( sky ).add( ground ).multiplyScalar( c0 );
+               this.sh.coefficients[ 1 ].copy( sky ).sub( ground ).multiplyScalar( c1 );
 
        }
 
-} );
-
-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,
+HemisphereLightProbe.prototype.isHemisphereLightProbe = true;
 
-       isAmbientLightProbe: true,
+class AmbientLightProbe extends LightProbe {
 
-       copy: function ( source ) { // modifying color not currently supported
+       constructor( color, intensity = 1 ) {
 
-               LightProbe.prototype.copy.call( this, source );
+               super( undefined, intensity );
 
-               return this;
-
-       },
-
-       toJSON: function ( meta ) {
+               const color1 = new Color().set( color );
 
-               const data = LightProbe.prototype.toJSON.call( this, meta );
-
-               // data.sh = this.sh.toArray(); // todo
-
-               return data;
+               // 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 ) );
 
        }
 
-} );
-
-const _eyeRight = new Matrix4();
-const _eyeLeft = new Matrix4();
+}
 
-function StereoCamera() {
+AmbientLightProbe.prototype.isAmbientLightProbe = true;
 
-       this.type = 'StereoCamera';
+class Clock {
 
-       this.aspect = 1;
+       constructor( autoStart = true ) {
 
-       this.eyeSep = 0.064;
+               this.autoStart = autoStart;
 
-       this.cameraL = new PerspectiveCamera();
-       this.cameraL.layers.enable( 1 );
-       this.cameraL.matrixAutoUpdate = false;
+               this.startTime = 0;
+               this.oldTime = 0;
+               this.elapsedTime = 0;
 
-       this.cameraR = new PerspectiveCamera();
-       this.cameraR.layers.enable( 2 );
-       this.cameraR.matrixAutoUpdate = false;
+               this.running = false;
 
-       this._cache = {
-               focus: null,
-               fov: null,
-               aspect: null,
-               near: null,
-               far: null,
-               zoom: null,
-               eyeSep: null
-       };
+       }
 
-}
+       start() {
 
-Object.assign( StereoCamera.prototype, {
+               this.startTime = now();
 
-       update: function ( camera ) {
+               this.oldTime = this.startTime;
+               this.elapsedTime = 0;
+               this.running = true;
 
-               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;
+       stop() {
 
-               if ( needsUpdate ) {
+               this.getElapsedTime();
+               this.running = false;
+               this.autoStart = false;
 
-                       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/
+       getElapsedTime() {
 
-                       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;
+               this.getDelta();
+               return this.elapsedTime;
 
-                       // translate xOffset
+       }
 
-                       _eyeLeft.elements[ 12 ] = - eyeSepHalf;
-                       _eyeRight.elements[ 12 ] = eyeSepHalf;
+       getDelta() {
 
-                       // for left eye
+               let diff = 0;
 
-                       xmin = - ymax * cache.aspect + eyeSepOnProjection;
-                       xmax = ymax * cache.aspect + eyeSepOnProjection;
+               if ( this.autoStart && ! this.running ) {
 
-                       projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
-                       projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );
+                       this.start();
+                       return 0;
 
-                       this.cameraL.projectionMatrix.copy( projectionMatrix );
+               }
 
-                       // for right eye
+               if ( this.running ) {
 
-                       xmin = - ymax * cache.aspect - eyeSepOnProjection;
-                       xmax = ymax * cache.aspect - eyeSepOnProjection;
+                       const newTime = now();
 
-                       projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
-                       projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );
+                       diff = ( newTime - this.oldTime ) / 1000;
+                       this.oldTime = newTime;
 
-                       this.cameraR.projectionMatrix.copy( projectionMatrix );
+                       this.elapsedTime += diff;
 
                }
 
-               this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft );
-               this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight );
+               return diff;
 
        }
 
-} );
+}
+
+function now() {
+
+       return ( typeof performance === 'undefined' ? Date : performance ).now(); // see #10732
+
+}
 
 class Audio extends Object3D {
 
@@ -41965,82 +40906,82 @@ class Audio extends Object3D {
 
 }
 
-function PropertyMixer( binding, typeName, valueSize ) {
+class PropertyMixer {
 
-       this.binding = binding;
-       this.valueSize = valueSize;
+       constructor( binding, typeName, valueSize ) {
 
-       let mixFunction,
-               mixFunctionAdditive,
-               setIdentity;
+               this.binding = binding;
+               this.valueSize = valueSize;
 
-       // 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
+               let mixFunction,
+                       mixFunctionAdditive,
+                       setIdentity;
 
-       switch ( typeName ) {
+               // 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
 
-               case 'quaternion':
-                       mixFunction = this._slerp;
-                       mixFunctionAdditive = this._slerpAdditive;
-                       setIdentity = this._setAdditiveIdentityQuaternion;
+               switch ( typeName ) {
 
-                       this.buffer = new Float64Array( valueSize * 6 );
-                       this._workIndex = 5;
-                       break;
+                       case 'quaternion':
+                               mixFunction = this._slerp;
+                               mixFunctionAdditive = this._slerpAdditive;
+                               setIdentity = this._setAdditiveIdentityQuaternion;
 
-               case 'string':
-               case 'bool':
-                       mixFunction = this._select;
+                               this.buffer = new Float64Array( valueSize * 6 );
+                               this._workIndex = 5;
+                               break;
 
-                       // Use the regular mix function and for additive on these types,
-                       // additive is not relevant for non-numeric types
-                       mixFunctionAdditive = this._select;
+                       case 'string':
+                       case 'bool':
+                               mixFunction = this._select;
 
-                       setIdentity = this._setAdditiveIdentityOther;
+                               // Use the regular mix function and for additive on these types,
+                               // additive is not relevant for non-numeric types
+                               mixFunctionAdditive = this._select;
 
-                       this.buffer = new Array( valueSize * 5 );
-                       break;
+                               setIdentity = this._setAdditiveIdentityOther;
 
-               default:
-                       mixFunction = this._lerp;
-                       mixFunctionAdditive = this._lerpAdditive;
-                       setIdentity = this._setAdditiveIdentityNumeric;
+                               this.buffer = new Array( valueSize * 5 );
+                               break;
 
-                       this.buffer = new Float64Array( valueSize * 5 );
+                       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._mixBufferRegion = mixFunction;
+               this._mixBufferRegionAdditive = mixFunctionAdditive;
+               this._setIdentity = setIdentity;
+               this._origIndex = 3;
+               this._addIndex = 4;
 
-       this.useCount = 0;
-       this.referenceCount = 0;
+               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 ) {
+       accumulate( 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
@@ -42075,10 +41016,10 @@ Object.assign( PropertyMixer.prototype, {
 
                this.cumulativeWeight = currentWeight;
 
-       },
+       }
 
        // accumulate data in the 'incoming' region into 'add'
-       accumulateAdditive: function ( weight ) {
+       accumulateAdditive( weight ) {
 
                const buffer = this.buffer,
                        stride = this.valueSize,
@@ -42097,10 +41038,10 @@ Object.assign( PropertyMixer.prototype, {
                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 ) {
+       apply( accuIndex ) {
 
                const stride = this.valueSize,
                        buffer = this.buffer,
@@ -42146,10 +41087,10 @@ Object.assign( PropertyMixer.prototype, {
 
                }
 
-       },
+       }
 
        // remember the state of the bound property and copy it to both accus
-       saveOriginalState: function () {
+       saveOriginalState() {
 
                const binding = this.binding;
 
@@ -42173,17 +41114,17 @@ Object.assign( PropertyMixer.prototype, {
                this.cumulativeWeight = 0;
                this.cumulativeWeightAdditive = 0;
 
-       },
+       }
 
        // apply the state previously taken via 'saveOriginalState' to the binding
-       restoreOriginalState: function () {
+       restoreOriginalState() {
 
                const originalValueOffset = this.valueSize * 3;
                this.binding.setValue( this.buffer, originalValueOffset );
 
-       },
+       }
 
-       _setAdditiveIdentityNumeric: function () {
+       _setAdditiveIdentityNumeric() {
 
                const startIndex = this._addIndex * this.valueSize;
                const endIndex = startIndex + this.valueSize;
@@ -42194,16 +41135,16 @@ Object.assign( PropertyMixer.prototype, {
 
                }
 
-       },
+       }
 
-       _setAdditiveIdentityQuaternion: function () {
+       _setAdditiveIdentityQuaternion() {
 
                this._setAdditiveIdentityNumeric();
                this.buffer[ this._addIndex * this.valueSize + 3 ] = 1;
 
-       },
+       }
 
-       _setAdditiveIdentityOther: function () {
+       _setAdditiveIdentityOther() {
 
                const startIndex = this._origIndex * this.valueSize;
                const targetIndex = this._addIndex * this.valueSize;
@@ -42214,12 +41155,12 @@ Object.assign( PropertyMixer.prototype, {
 
                }
 
-       },
+       }
 
 
        // mix functions
 
-       _select: function ( buffer, dstOffset, srcOffset, t, stride ) {
+       _select( buffer, dstOffset, srcOffset, t, stride ) {
 
                if ( t >= 0.5 ) {
 
@@ -42231,15 +41172,15 @@ Object.assign( PropertyMixer.prototype, {
 
                }
 
-       },
+       }
 
-       _slerp: function ( buffer, dstOffset, srcOffset, t ) {
+       _slerp( buffer, dstOffset, srcOffset, t ) {
 
                Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t );
 
-       },
+       }
 
-       _slerpAdditive: function ( buffer, dstOffset, srcOffset, t, stride ) {
+       _slerpAdditive( buffer, dstOffset, srcOffset, t, stride ) {
 
                const workOffset = this._workIndex * stride;
 
@@ -42249,9 +41190,9 @@ Object.assign( PropertyMixer.prototype, {
                // Slerp to the intermediate result
                Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, workOffset, t );
 
-       },
+       }
 
-       _lerp: function ( buffer, dstOffset, srcOffset, t, stride ) {
+       _lerp( buffer, dstOffset, srcOffset, t, stride ) {
 
                const s = 1 - t;
 
@@ -42263,9 +41204,9 @@ Object.assign( PropertyMixer.prototype, {
 
                }
 
-       },
+       }
 
-       _lerpAdditive: function ( buffer, dstOffset, srcOffset, t, stride ) {
+       _lerpAdditive( buffer, dstOffset, srcOffset, t, stride ) {
 
                for ( let i = 0; i !== stride; ++ i ) {
 
@@ -42277,7 +41218,7 @@ Object.assign( PropertyMixer.prototype, {
 
        }
 
-} );
+}
 
 // Characters [].:/ are reserved for track binding syntax.
 const _RESERVED_CHARS_RE = '\\[\\]\\.:\\/';
@@ -42315,18 +41256,18 @@ const _trackRe = new RegExp( ''
 
 const _supportedObjectNames = [ 'material', 'materials', 'bones' ];
 
-function Composite( targetGroup, path, optionalParsedPath ) {
+class Composite {
 
-       const parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path );
+       constructor( targetGroup, path, optionalParsedPath ) {
 
-       this._targetGroup = targetGroup;
-       this._bindings = targetGroup.subscribe_( path, parsedPath );
+               const parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path );
 
-}
+               this._targetGroup = targetGroup;
+               this._bindings = targetGroup.subscribe_( path, parsedPath );
 
-Object.assign( Composite.prototype, {
+       }
 
-       getValue: function ( array, offset ) {
+       getValue( array, offset ) {
 
                this.bind(); // bind all binding
 
@@ -42336,9 +41277,9 @@ Object.assign( Composite.prototype, {
                // and only call .getValue on the first
                if ( binding !== undefined ) binding.getValue( array, offset );
 
-       },
+       }
 
-       setValue: function ( array, offset ) {
+       setValue( array, offset ) {
 
                const bindings = this._bindings;
 
@@ -42348,9 +41289,9 @@ Object.assign( Composite.prototype, {
 
                }
 
-       },
+       }
 
-       bind: function () {
+       bind() {
 
                const bindings = this._bindings;
 
@@ -42360,9 +41301,9 @@ Object.assign( Composite.prototype, {
 
                }
 
-       },
+       }
 
-       unbind: function () {
+       unbind() {
 
                const bindings = this._bindings;
 
@@ -42374,25 +41315,32 @@ Object.assign( Composite.prototype, {
 
        }
 
-} );
+}
 
+// 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.
+class PropertyBinding {
 
-function PropertyBinding( rootNode, path, parsedPath ) {
+       constructor( rootNode, path, parsedPath ) {
 
-       this.path = path;
-       this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );
+               this.path = path;
+               this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );
 
-       this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode;
+               this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode;
 
-       this.rootNode = rootNode;
+               this.rootNode = rootNode;
 
-}
+               // initial state of these methods that calls 'bind'
+               this.getValue = this._getValue_unbound;
+               this.setValue = this._setValue_unbound;
 
-Object.assign( PropertyBinding, {
+       }
 
-       Composite: Composite,
 
-       create: function ( root, path, parsedPath ) {
+       static create( root, path, parsedPath ) {
 
                if ( ! ( root && root.isAnimationObjectGroup ) ) {
 
@@ -42404,7 +41352,7 @@ Object.assign( PropertyBinding, {
 
                }
 
-       },
+       }
 
        /**
         * Replaces spaces with underscores and removes unsupported characters from
@@ -42413,13 +41361,13 @@ Object.assign( PropertyBinding, {
         * @param {string} name Node name to be sanitized.
         * @return {string}
         */
-       sanitizeNodeName: function ( name ) {
+       static sanitizeNodeName( name ) {
 
                return name.replace( /\s/g, '_' ).replace( _reservedRe, '' );
 
-       },
+       }
 
-       parseTrackName: function ( trackName ) {
+       static parseTrackName( trackName ) {
 
                const matches = _trackRe.exec( trackName );
 
@@ -42465,9 +41413,9 @@ Object.assign( PropertyBinding, {
 
                return results;
 
-       },
+       }
 
-       findNode: function ( root, nodeName ) {
+       static findNode( root, nodeName ) {
 
                if ( ! nodeName || nodeName === '' || nodeName === '.' || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) {
 
@@ -42527,204 +41475,166 @@ Object.assign( PropertyBinding, {
 
        }
 
-} );
-
-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 ) {
+       _getValue_unavailable() {}
+       _setValue_unavailable() {}
 
-                       const source = this.resolvedProperty;
+       // Getters
 
-                       for ( let i = 0, n = source.length; i !== n; ++ i ) {
+       _getValue_direct( buffer, offset ) {
 
-                               buffer[ offset ++ ] = source[ i ];
+               buffer[ offset ] = this.targetObject[ this.propertyName ];
 
-                       }
-
-               },
+       }
 
-               function getValue_arrayElement( buffer, offset ) {
+       _getValue_array( buffer, offset ) {
 
-                       buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
+               const source = this.resolvedProperty;
 
-               },
+               for ( let i = 0, n = source.length; i !== n; ++ i ) {
 
-               function getValue_toArray( buffer, offset ) {
-
-                       this.resolvedProperty.toArray( buffer, offset );
+                       buffer[ offset ++ ] = source[ i ];
 
                }
 
-       ],
+       }
 
-       SetterByBindingTypeAndVersioning: [
+       _getValue_arrayElement( buffer, offset ) {
 
-               [
-                       // Direct
+               buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
 
-                       function setValue_direct( buffer, offset ) {
+       }
 
-                               this.targetObject[ this.propertyName ] = buffer[ offset ];
+       _getValue_toArray( buffer, offset ) {
 
-                       },
+               this.resolvedProperty.toArray( buffer, offset );
 
-                       function setValue_direct_setNeedsUpdate( buffer, offset ) {
+       }
 
-                               this.targetObject[ this.propertyName ] = buffer[ offset ];
-                               this.targetObject.needsUpdate = true;
+       // Direct
 
-                       },
+       _setValue_direct( buffer, offset ) {
 
-                       function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {
+               this.targetObject[ this.propertyName ] = buffer[ offset ];
 
-                               this.targetObject[ this.propertyName ] = buffer[ offset ];
-                               this.targetObject.matrixWorldNeedsUpdate = true;
+       }
 
-                       }
+       _setValue_direct_setNeedsUpdate( buffer, offset ) {
 
-               ], [
+               this.targetObject[ this.propertyName ] = buffer[ offset ];
+               this.targetObject.needsUpdate = true;
 
-                       // EntireArray
+       }
 
-                       function setValue_array( buffer, offset ) {
+       _setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {
 
-                               const dest = this.resolvedProperty;
+               this.targetObject[ this.propertyName ] = buffer[ offset ];
+               this.targetObject.matrixWorldNeedsUpdate = true;
 
-                               for ( let i = 0, n = dest.length; i !== n; ++ i ) {
+       }
 
-                                       dest[ i ] = buffer[ offset ++ ];
+       // EntireArray
 
-                               }
+       _setValue_array( buffer, offset ) {
 
-                       },
+               const dest = this.resolvedProperty;
 
-                       function setValue_array_setNeedsUpdate( buffer, offset ) {
+               for ( let i = 0, n = dest.length; i !== n; ++ i ) {
 
-                               const dest = this.resolvedProperty;
+                       dest[ i ] = buffer[ offset ++ ];
 
-                               for ( let i = 0, n = dest.length; i !== n; ++ i ) {
+               }
 
-                                       dest[ i ] = buffer[ offset ++ ];
+       }
 
-                               }
+       _setValue_array_setNeedsUpdate( buffer, offset ) {
 
-                               this.targetObject.needsUpdate = true;
+               const dest = this.resolvedProperty;
 
-                       },
+               for ( let i = 0, n = dest.length; i !== n; ++ i ) {
 
-                       function setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) {
+                       dest[ i ] = buffer[ offset ++ ];
 
-                               const dest = this.resolvedProperty;
+               }
 
-                               for ( let i = 0, n = dest.length; i !== n; ++ i ) {
+               this.targetObject.needsUpdate = true;
 
-                                       dest[ i ] = buffer[ offset ++ ];
+       }
 
-                               }
+       _setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) {
 
-                               this.targetObject.matrixWorldNeedsUpdate = true;
+               const dest = this.resolvedProperty;
 
-                       }
+               for ( let i = 0, n = dest.length; i !== n; ++ i ) {
 
-               ], [
+                       dest[ i ] = buffer[ offset ++ ];
 
-                       // ArrayElement
+               }
 
-                       function setValue_arrayElement( buffer, offset ) {
+               this.targetObject.matrixWorldNeedsUpdate = true;
 
-                               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
+       }
 
-                       },
+       // ArrayElement
 
-                       function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
+       _setValue_arrayElement( buffer, offset ) {
 
-                               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
-                               this.targetObject.needsUpdate = true;
+               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
 
-                       },
+       }
 
-                       function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
+       _setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
 
-                               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
-                               this.targetObject.matrixWorldNeedsUpdate = true;
+               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
+               this.targetObject.needsUpdate = true;
 
-                       }
+       }
 
-               ], [
+       _setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
 
-                       // HasToFromArray
+               this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
+               this.targetObject.matrixWorldNeedsUpdate = true;
 
-                       function setValue_fromArray( buffer, offset ) {
+       }
 
-                               this.resolvedProperty.fromArray( buffer, offset );
+       // HasToFromArray
 
-                       },
+       _setValue_fromArray( buffer, offset ) {
 
-                       function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
+               this.resolvedProperty.fromArray( buffer, offset );
 
-                               this.resolvedProperty.fromArray( buffer, offset );
-                               this.targetObject.needsUpdate = true;
+       }
 
-                       },
+       _setValue_fromArray_setNeedsUpdate( buffer, offset ) {
 
-                       function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {
+               this.resolvedProperty.fromArray( buffer, offset );
+               this.targetObject.needsUpdate = true;
 
-                               this.resolvedProperty.fromArray( buffer, offset );
-                               this.targetObject.matrixWorldNeedsUpdate = true;
+       }
 
-                       }
+       _setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {
 
-               ]
+               this.resolvedProperty.fromArray( buffer, offset );
+               this.targetObject.matrixWorldNeedsUpdate = true;
 
-       ],
+       }
 
-       getValue: function getValue_unbound( targetArray, offset ) {
+       _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 ) {
+       _setValue_unbound( sourceArray, offset ) {
 
                this.bind();
                this.setValue( sourceArray, offset );
 
-       },
+       }
 
        // create getter / setter pair for a property in the scene graph
-       bind: function () {
+       bind() {
 
                let targetObject = this.node;
                const parsedPath = this.parsedPath;
@@ -42938,9 +41848,9 @@ Object.assign( PropertyBinding.prototype, { // prototype, continued
                this.getValue = this.GetterByBindingType[ bindingType ];
                this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
 
-       },
+       }
 
-       unbind: function () {
+       unbind() {
 
                this.node = null;
 
@@ -42951,399 +41861,65 @@ Object.assign( PropertyBinding.prototype, { // prototype, continued
 
        }
 
-} );
-
-// 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
+PropertyBinding.Composite = Composite;
 
-                               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 );
+PropertyBinding.prototype.BindingType = {
+       Direct: 0,
+       EntireArray: 1,
+       ArrayElement: 2,
+       HasFromToArray: 3
+};
 
-               }
+PropertyBinding.prototype.Versioning = {
+       None: 0,
+       NeedsUpdate: 1,
+       MatrixWorldNeedsUpdate: 2
+};
 
-               return bindingsForPath;
+PropertyBinding.prototype.GetterByBindingType = [
 
-       },
+       PropertyBinding.prototype._getValue_direct,
+       PropertyBinding.prototype._getValue_array,
+       PropertyBinding.prototype._getValue_arrayElement,
+       PropertyBinding.prototype._getValue_toArray,
 
-       unsubscribe_: function ( path ) {
+];
 
-               // tells the group to forget about a property path and no longer
-               // update the array previously obtained with 'subscribe_'
+PropertyBinding.prototype.SetterByBindingTypeAndVersioning = [
 
-               const indicesByPath = this._bindingsIndicesByPath,
-                       index = indicesByPath[ path ];
+       [
+               // Direct
+               PropertyBinding.prototype._setValue_direct,
+               PropertyBinding.prototype._setValue_direct_setNeedsUpdate,
+               PropertyBinding.prototype._setValue_direct_setMatrixWorldNeedsUpdate,
 
-               if ( index !== undefined ) {
+       ], [
 
-                       const paths = this._paths,
-                               parsedPaths = this._parsedPaths,
-                               bindings = this._bindings,
-                               lastBindingsIndex = bindings.length - 1,
-                               lastBindings = bindings[ lastBindingsIndex ],
-                               lastBindingsPath = path[ lastBindingsIndex ];
+               // EntireArray
 
-                       indicesByPath[ lastBindingsPath ] = index;
+               PropertyBinding.prototype._setValue_array,
+               PropertyBinding.prototype._setValue_array_setNeedsUpdate,
+               PropertyBinding.prototype._setValue_array_setMatrixWorldNeedsUpdate,
 
-                       bindings[ index ] = lastBindings;
-                       bindings.pop();
+       ], [
 
-                       parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ];
-                       parsedPaths.pop();
+               // ArrayElement
+               PropertyBinding.prototype._setValue_arrayElement,
+               PropertyBinding.prototype._setValue_arrayElement_setNeedsUpdate,
+               PropertyBinding.prototype._setValue_arrayElement_setMatrixWorldNeedsUpdate,
 
-                       paths[ index ] = paths[ lastBindingsIndex ];
-                       paths.pop();
+       ], [
 
-               }
+               // HasToFromArray
+               PropertyBinding.prototype._setValue_fromArray,
+               PropertyBinding.prototype._setValue_fromArray_setNeedsUpdate,
+               PropertyBinding.prototype._setValue_fromArray_setMatrixWorldNeedsUpdate,
 
-       }
+       ]
 
-} );
+];
 
 class AnimationAction {
 
@@ -44039,23 +42615,21 @@ class AnimationAction {
 
 }
 
-function AnimationMixer( root ) {
+class AnimationMixer extends EventDispatcher {
 
-       this._root = root;
-       this._initMemoryManager();
-       this._accuIndex = 0;
+       constructor( root ) {
 
-       this.time = 0;
-
-       this.timeScale = 1.0;
-
-}
+               super();
 
-AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+               this._root = root;
+               this._initMemoryManager();
+               this._accuIndex = 0;
+               this.time = 0;
+               this.timeScale = 1.0;
 
-       constructor: AnimationMixer,
+       }
 
-       _bindAction: function ( action, prototypeAction ) {
+       _bindAction( action, prototypeAction ) {
 
                const root = action._localRoot || this._root,
                        tracks = action._clip.tracks,
@@ -44122,9 +42696,9 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
-       },
+       }
 
-       _activateAction: function ( action ) {
+       _activateAction( action ) {
 
                if ( ! this._isActiveAction( action ) ) {
 
@@ -44164,9 +42738,9 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
-       },
+       }
 
-       _deactivateAction: function ( action ) {
+       _deactivateAction( action ) {
 
                if ( this._isActiveAction( action ) ) {
 
@@ -44190,11 +42764,11 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
-       },
+       }
 
        // Memory manager
 
-       _initMemoryManager: function () {
+       _initMemoryManager() {
 
                this._actions = []; // 'nActiveActions' followed by inactive ones
                this._nActiveActions = 0;
@@ -44259,18 +42833,18 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                };
 
-       },
+       }
 
        // Memory management for AnimationAction objects
 
-       _isActiveAction: function ( action ) {
+       _isActiveAction( action ) {
 
                const index = action._cacheIndex;
                return index !== null && index < this._nActiveActions;
 
-       },
+       }
 
-       _addInactiveAction: function ( action, clipUuid, rootUuid ) {
+       _addInactiveAction( action, clipUuid, rootUuid ) {
 
                const actions = this._actions,
                        actionsByClip = this._actionsByClip;
@@ -44304,9 +42878,9 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                actionsForClip.actionByRoot[ rootUuid ] = action;
 
-       },
+       }
 
-       _removeInactiveAction: function ( action ) {
+       _removeInactiveAction( action ) {
 
                const actions = this._actions,
                        lastInactiveAction = actions[ actions.length - 1 ],
@@ -44349,9 +42923,9 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                this._removeInactiveBindingsForAction( action );
 
-       },
+       }
 
-       _removeInactiveBindingsForAction: function ( action ) {
+       _removeInactiveBindingsForAction( action ) {
 
                const bindings = action._propertyBindings;
 
@@ -44367,9 +42941,9 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
-       },
+       }
 
-       _lendAction: function ( action ) {
+       _lendAction( action ) {
 
                // [ active actions |  inactive actions  ]
                // [  active actions >| inactive actions ]
@@ -44390,9 +42964,9 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
                firstInactiveAction._cacheIndex = prevIndex;
                actions[ prevIndex ] = firstInactiveAction;
 
-       },
+       }
 
-       _takeBackAction: function ( action ) {
+       _takeBackAction( action ) {
 
                // [  active actions  | inactive actions ]
                // [ active actions |< inactive actions  ]
@@ -44413,11 +42987,11 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
                lastActiveAction._cacheIndex = prevIndex;
                actions[ prevIndex ] = lastActiveAction;
 
-       },
+       }
 
        // Memory management for PropertyMixer objects
 
-       _addInactiveBinding: function ( binding, rootUuid, trackName ) {
+       _addInactiveBinding( binding, rootUuid, trackName ) {
 
                const bindingsByRoot = this._bindingsByRootAndName,
                        bindings = this._bindings;
@@ -44436,9 +43010,9 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
                binding._cacheIndex = bindings.length;
                bindings.push( binding );
 
-       },
+       }
 
-       _removeInactiveBinding: function ( binding ) {
+       _removeInactiveBinding( binding ) {
 
                const bindings = this._bindings,
                        propBinding = binding.binding,
@@ -44462,9 +43036,9 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
-       },
+       }
 
-       _lendBinding: function ( binding ) {
+       _lendBinding( binding ) {
 
                const bindings = this._bindings,
                        prevIndex = binding._cacheIndex,
@@ -44479,9 +43053,9 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
                firstInactiveBinding._cacheIndex = prevIndex;
                bindings[ prevIndex ] = firstInactiveBinding;
 
-       },
+       }
 
-       _takeBackBinding: function ( binding ) {
+       _takeBackBinding( binding ) {
 
                const bindings = this._bindings,
                        prevIndex = binding._cacheIndex,
@@ -44496,12 +43070,12 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
                lastActiveBinding._cacheIndex = prevIndex;
                bindings[ prevIndex ] = lastActiveBinding;
 
-       },
+       }
 
 
        // Memory management of Interpolants for weight and time scale
 
-       _lendControlInterpolant: function () {
+       _lendControlInterpolant() {
 
                const interpolants = this._controlInterpolants,
                        lastActiveIndex = this._nActiveControlInterpolants ++;
@@ -44521,9 +43095,9 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return interpolant;
 
-       },
+       }
 
-       _takeBackControlInterpolant: function ( interpolant ) {
+       _takeBackControlInterpolant( interpolant ) {
 
                const interpolants = this._controlInterpolants,
                        prevIndex = interpolant.__cacheIndex,
@@ -44538,14 +43112,12 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
                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 ) {
+       clipAction( clip, optionalRoot, blendMode ) {
 
                const root = optionalRoot || this._root,
                        rootUuid = root.uuid;
@@ -44604,10 +43176,10 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return newAction;
 
-       },
+       }
 
        // get an existing action
-       existingAction: function ( clip, optionalRoot ) {
+       existingAction( clip, optionalRoot ) {
 
                const root = optionalRoot || this._root,
                        rootUuid = root.uuid,
@@ -44627,10 +43199,10 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return null;
 
-       },
+       }
 
        // deactivates all previously scheduled actions
-       stopAllAction: function () {
+       stopAllAction() {
 
                const actions = this._actions,
                        nActions = this._nActiveActions;
@@ -44643,10 +43215,10 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return this;
 
-       },
+       }
 
        // advance the time and update apply the animation
-       update: function ( deltaTime ) {
+       update( deltaTime ) {
 
                deltaTime *= this.timeScale;
 
@@ -44681,10 +43253,10 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return this;
 
-       },
+       }
 
        // Allows you to seek to a specific time in an animation.
-       setTime: function ( timeInSeconds ) {
+       setTime( timeInSeconds ) {
 
                this.time = 0; // Zero out time attribute for AnimationMixer object;
                for ( let i = 0; i < this._actions.length; i ++ ) {
@@ -44695,17 +43267,17 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                return this.update( timeInSeconds ); // Update used to set exact time. Returns "this" AnimationMixer object.
 
-       },
+       }
 
        // return this mixer's root target object
-       getRoot: function () {
+       getRoot() {
 
                return this._root;
 
-       },
+       }
 
        // free all resources specific to a particular clip
-       uncacheClip: function ( clip ) {
+       uncacheClip( clip ) {
 
                const actions = this._actions,
                        clipUuid = clip.uuid,
@@ -44744,10 +43316,10 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
-       },
+       }
 
        // free all resources specific to a particular root target object
-       uncacheRoot: function ( root ) {
+       uncacheRoot( root ) {
 
                const rootUuid = root.uuid,
                        actionsByClip = this._actionsByClip;
@@ -44781,10 +43353,10 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
                }
 
-       },
+       }
 
        // remove a targeted clip from the cache
-       uncacheAction: function ( clip, optionalRoot ) {
+       uncacheAction( clip, optionalRoot ) {
 
                const action = this.existingAction( clip, optionalRoot );
 
@@ -44797,68 +43369,43 @@ AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototy
 
        }
 
-} );
-
-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 ) {
+AnimationMixer.prototype._controlInterpolantsResultBuffer = new Float32Array( 1 );
 
-       InterleavedBuffer.call( this, array, stride );
+class InstancedInterleavedBuffer extends InterleavedBuffer {
 
-       this.meshPerAttribute = meshPerAttribute || 1;
+       constructor( array, stride, meshPerAttribute = 1 ) {
 
-}
-
-InstancedInterleavedBuffer.prototype = Object.assign( Object.create( InterleavedBuffer.prototype ), {
+               super( array, stride );
 
-       constructor: InstancedInterleavedBuffer,
+               this.meshPerAttribute = meshPerAttribute;
 
-       isInstancedInterleavedBuffer: true,
+       }
 
-       copy: function ( source ) {
+       copy( source ) {
 
-               InterleavedBuffer.prototype.copy.call( this, source );
+               super.copy( source );
 
                this.meshPerAttribute = source.meshPerAttribute;
 
                return this;
 
-       },
+       }
 
-       clone: function ( data ) {
+       clone( data ) {
 
-               const ib = InterleavedBuffer.prototype.clone.call( this, data );
+               const ib = super.clone( data );
 
                ib.meshPerAttribute = this.meshPerAttribute;
 
                return ib;
 
-       },
+       }
 
-       toJSON: function ( data ) {
+       toJSON( data ) {
 
-               const json = InterleavedBuffer.prototype.toJSON.call( this, data );
+               const json = super.toJSON( data );
 
                json.isInstancedInterleavedBuffer = true;
                json.meshPerAttribute = this.meshPerAttribute;
@@ -44867,97 +43414,85 @@ InstancedInterleavedBuffer.prototype = Object.assign( Object.create( Interleaved
 
        }
 
-} );
-
-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', {
+InstancedInterleavedBuffer.prototype.isInstancedInterleavedBuffer = true;
 
-       set: function ( value ) {
+class Raycaster {
 
-               if ( value === true ) this.version ++;
+       constructor( origin, direction, near = 0, far = Infinity ) {
 
-       }
+               this.ray = new Ray( origin, direction );
+               // direction is assumed to be normalized (for accurate distance calculations)
 
-} );
+               this.near = near;
+               this.far = far;
+               this.camera = null;
+               this.layers = new Layers();
+
+               this.params = {
+                       Mesh: {},
+                       Line: { threshold: 1 },
+                       LOD: {},
+                       Points: { threshold: 1 },
+                       Sprite: {}
+               };
 
-Object.assign( GLBufferAttribute.prototype, {
+       }
 
-       isGLBufferAttribute: true,
+       set( origin, direction ) {
 
-       setBuffer: function ( buffer ) {
+               // direction is assumed to be normalized (for accurate distance calculations)
 
-               this.buffer = buffer;
+               this.ray.set( origin, direction );
 
-               return this;
+       }
 
-       },
+       setFromCamera( coords, camera ) {
 
-       setType: function ( type, elementSize ) {
+               if ( camera && camera.isPerspectiveCamera ) {
 
-               this.type = type;
-               this.elementSize = elementSize;
+                       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;
 
-               return this;
+               } 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;
 
-       setItemSize: function ( itemSize ) {
+               } else {
 
-               this.itemSize = itemSize;
+                       console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type );
 
-               return this;
+               }
 
-       },
+       }
 
-       setCount: function ( count ) {
+       intersectObject( object, recursive = true, intersects = [] ) {
 
-               this.count = count;
+               intersectObject( object, this, intersects, recursive );
 
-               return this;
+               intersects.sort( ascSort );
 
-       },
+               return intersects;
 
-} );
+       }
 
-function Raycaster( origin, direction, near, far ) {
+       intersectObjects( objects, recursive = true, intersects = [] ) {
 
-       this.ray = new Ray( origin, direction );
-       // direction is assumed to be normalized (for accurate distance calculations)
+               for ( let i = 0, l = objects.length; i < l; i ++ ) {
 
-       this.near = near || 0;
-       this.far = far || Infinity;
-       this.camera = null;
-       this.layers = new Layers();
+                       intersectObject( objects[ i ], this, intersects, recursive );
 
-       this.params = {
-               Mesh: {},
-               Line: { threshold: 1 },
-               LOD: {},
-               Points: { threshold: 1 },
-               Sprite: {}
-       };
+               }
 
-       Object.defineProperties( this.params, {
-               PointCloud: {
-                       get: function () {
+               intersects.sort( ascSort );
 
-                               console.warn( 'THREE.Raycaster: params.PointCloud has been renamed to params.Points.' );
-                               return this.Points;
+               return intersects;
 
-                       }
-               }
-       } );
+       }
 
 }
 
@@ -44989,1049 +43524,767 @@ function intersectObject( object, raycaster, intersects, recursive ) {
 
 }
 
-Object.assign( Raycaster.prototype, {
-
-       set: function ( origin, direction ) {
-
-               // direction is assumed to be normalized (for accurate distance calculations)
+const _vector$2 = /*@__PURE__*/ new Vector3();
+const _boneMatrix = /*@__PURE__*/ new Matrix4();
+const _matrixWorldInv = /*@__PURE__*/ new Matrix4();
 
-               this.ray.set( origin, direction );
 
-       },
+class SkeletonHelper extends LineSegments {
 
-       setFromCamera: function ( coords, camera ) {
+       constructor( object ) {
 
-               if ( camera && camera.isPerspectiveCamera ) {
+               const bones = getBoneList( object );
 
-                       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;
+               const geometry = new BufferGeometry();
 
-               } else if ( camera && camera.isOrthographicCamera ) {
+               const vertices = [];
+               const colors = [];
 
-                       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;
+               const color1 = new Color( 0, 0, 1 );
+               const color2 = new Color( 0, 1, 0 );
 
-               } else {
+               for ( let i = 0; i < bones.length; i ++ ) {
 
-                       console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type );
+                       const bone = bones[ i ];
 
-               }
+                       if ( bone.parent && bone.parent.isBone ) {
 
-       },
+                               vertices.push( 0, 0, 0 );
+                               vertices.push( 0, 0, 0 );
+                               colors.push( color1.r, color1.g, color1.b );
+                               colors.push( color2.r, color2.g, color2.b );
 
-       intersectObject: function ( object, recursive, optionalTarget ) {
+                       }
 
-               const intersects = optionalTarget || [];
+               }
 
-               intersectObject( object, this, intersects, recursive );
+               geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
 
-               intersects.sort( ascSort );
+               const material = new LineBasicMaterial( { vertexColors: true, depthTest: false, depthWrite: false, toneMapped: false, transparent: true } );
 
-               return intersects;
+               super( geometry, material );
 
-       },
+               this.type = 'SkeletonHelper';
+               this.isSkeletonHelper = true;
 
-       intersectObjects: function ( objects, recursive, optionalTarget ) {
+               this.root = object;
+               this.bones = bones;
 
-               const intersects = optionalTarget || [];
+               this.matrix = object.matrixWorld;
+               this.matrixAutoUpdate = false;
 
-               if ( Array.isArray( objects ) === false ) {
+       }
 
-                       console.warn( 'THREE.Raycaster.intersectObjects: objects is not an Array.' );
-                       return intersects;
+       updateMatrixWorld( force ) {
 
-               }
+               const bones = this.bones;
 
-               for ( let i = 0, l = objects.length; i < l; i ++ ) {
+               const geometry = this.geometry;
+               const position = geometry.getAttribute( 'position' );
 
-                       intersectObject( objects[ i ], this, intersects, recursive );
+               _matrixWorldInv.copy( this.root.matrixWorld ).invert();
 
-               }
+               for ( let i = 0, j = 0; i < bones.length; i ++ ) {
 
-               intersects.sort( ascSort );
+                       const bone = bones[ i ];
 
-               return intersects;
+                       if ( bone.parent && bone.parent.isBone ) {
 
-       }
+                               _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.matrixWorld );
+                               _vector$2.setFromMatrixPosition( _boneMatrix );
+                               position.setXYZ( j, _vector$2.x, _vector$2.y, _vector$2.z );
 
-} );
+                               _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.parent.matrixWorld );
+                               _vector$2.setFromMatrixPosition( _boneMatrix );
+                               position.setXYZ( j + 1, _vector$2.x, _vector$2.y, _vector$2.z );
 
-const _vector$8 = /*@__PURE__*/ new Vector2();
+                               j += 2;
 
-class Box2 {
+                       }
 
-       constructor( min, max ) {
+               }
 
-               Object.defineProperty( this, 'isBox2', { value: true } );
+               geometry.getAttribute( 'position' ).needsUpdate = true;
 
-               this.min = ( min !== undefined ) ? min : new Vector2( + Infinity, + Infinity );
-               this.max = ( max !== undefined ) ? max : new Vector2( - Infinity, - Infinity );
+               super.updateMatrixWorld( force );
 
        }
 
-       set( min, max ) {
-
-               this.min.copy( min );
-               this.max.copy( max );
+}
 
-               return this;
 
-       }
+function getBoneList( object ) {
 
-       setFromPoints( points ) {
+       const boneList = [];
 
-               this.makeEmpty();
+       if ( object && object.isBone ) {
 
-               for ( let i = 0, il = points.length; i < il; i ++ ) {
+               boneList.push( object );
 
-                       this.expandByPoint( points[ i ] );
+       }
 
-               }
+       for ( let i = 0; i < object.children.length; i ++ ) {
 
-               return this;
+               boneList.push.apply( boneList, getBoneList( object.children[ i ] ) );
 
        }
 
-       setFromCenterAndSize( center, size ) {
+       return boneList;
 
-               const halfSize = _vector$8.copy( size ).multiplyScalar( 0.5 );
-               this.min.copy( center ).sub( halfSize );
-               this.max.copy( center ).add( halfSize );
+}
 
-               return this;
+class GridHelper extends LineSegments {
 
-       }
+       constructor( size = 10, divisions = 10, color1 = 0x444444, color2 = 0x888888 ) {
 
-       clone() {
+               color1 = new Color( color1 );
+               color2 = new Color( color2 );
 
-               return new this.constructor().copy( this );
+               const center = divisions / 2;
+               const step = size / divisions;
+               const halfSize = size / 2;
 
-       }
+               const vertices = [], colors = [];
 
-       copy( box ) {
+               for ( let i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) {
 
-               this.min.copy( box.min );
-               this.max.copy( box.max );
+                       vertices.push( - halfSize, 0, k, halfSize, 0, k );
+                       vertices.push( k, 0, - halfSize, k, 0, halfSize );
 
-               return this;
+                       const color = i === center ? color1 : color2;
 
-       }
+                       color.toArray( colors, j ); j += 3;
+                       color.toArray( colors, j ); j += 3;
+                       color.toArray( colors, j ); j += 3;
+                       color.toArray( colors, j ); j += 3;
 
-       makeEmpty() {
+               }
 
-               this.min.x = this.min.y = + Infinity;
-               this.max.x = this.max.y = - Infinity;
+               const geometry = new BufferGeometry();
+               geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
 
-               return this;
+               const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } );
 
-       }
+               super( geometry, material );
 
-       isEmpty() {
+               this.type = 'GridHelper';
 
-               // 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 );
+}
 
-       }
+const _floatView = new Float32Array( 1 );
+new Int32Array( _floatView.buffer );
 
-       getCenter( target ) {
+//
 
-               if ( target === undefined ) {
+Curve.create = function ( construct, getPoint ) {
 
-                       console.warn( 'THREE.Box2: .getCenter() target is now required' );
-                       target = new Vector2();
+       console.log( 'THREE.Curve.create() has been deprecated' );
 
-               }
+       construct.prototype = Object.create( Curve.prototype );
+       construct.prototype.constructor = construct;
+       construct.prototype.getPoint = getPoint;
 
-               return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+       return construct;
 
-       }
+};
 
-       getSize( target ) {
+//
 
-               if ( target === undefined ) {
+Path.prototype.fromPoints = function ( points ) {
 
-                       console.warn( 'THREE.Box2: .getSize() target is now required' );
-                       target = new Vector2();
+       console.warn( 'THREE.Path: .fromPoints() has been renamed to .setFromPoints().' );
+       return this.setFromPoints( points );
 
-               }
+};
 
-               return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min );
+GridHelper.prototype.setColors = function () {
 
-       }
+       console.error( 'THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.' );
 
-       expandByPoint( point ) {
+};
 
-               this.min.min( point );
-               this.max.max( point );
+SkeletonHelper.prototype.update = function () {
 
-               return this;
+       console.error( 'THREE.SkeletonHelper: update() no longer needs to be called.' );
 
-       }
+};
 
-       expandByVector( vector ) {
+//
 
-               this.min.sub( vector );
-               this.max.add( vector );
+Loader.prototype.extractUrlBase = function ( url ) {
 
-               return this;
+       console.warn( 'THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead.' );
+       return LoaderUtils.extractUrlBase( url );
 
-       }
+};
 
-       expandByScalar( scalar ) {
+Loader.Handlers = {
 
-               this.min.addScalar( - scalar );
-               this.max.addScalar( scalar );
+       add: function ( /* regex, loader */ ) {
 
-               return this;
+               console.error( 'THREE.Loader: Handlers.add() has been removed. Use LoadingManager.addHandler() instead.' );
 
-       }
+       },
 
-       containsPoint( point ) {
+       get: function ( /* file */ ) {
 
-               return point.x < this.min.x || point.x > this.max.x ||
-                       point.y < this.min.y || point.y > this.max.y ? false : true;
+               console.error( 'THREE.Loader: Handlers.get() has been removed. Use LoadingManager.getHandler() instead.' );
 
        }
 
-       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 ) {
+Box3.prototype.center = function ( optionalTarget ) {
 
-               // This can potentially have a divide by zero if the box
-               // has a size dimension of 0.
+       console.warn( 'THREE.Box3: .center() has been renamed to .getCenter().' );
+       return this.getCenter( optionalTarget );
 
-               if ( target === undefined ) {
+};
 
-                       console.warn( 'THREE.Box2: .getParameter() target is now required' );
-                       target = new Vector2();
+Box3.prototype.empty = function () {
 
-               }
+       console.warn( 'THREE.Box3: .empty() has been renamed to .isEmpty().' );
+       return this.isEmpty();
 
-               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 )
-               );
+};
 
-       }
+Box3.prototype.isIntersectionBox = function ( box ) {
 
-       intersectsBox( box ) {
+       console.warn( 'THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().' );
+       return this.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;
+Box3.prototype.isIntersectionSphere = function ( sphere ) {
 
-       }
+       console.warn( 'THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
+       return this.intersectsSphere( sphere );
 
-       clampPoint( point, target ) {
+};
 
-               if ( target === undefined ) {
+Box3.prototype.size = function ( optionalTarget ) {
 
-                       console.warn( 'THREE.Box2: .clampPoint() target is now required' );
-                       target = new Vector2();
+       console.warn( 'THREE.Box3: .size() has been renamed to .getSize().' );
+       return this.getSize( optionalTarget );
 
-               }
+};
 
-               return target.copy( point ).clamp( this.min, this.max );
+//
 
-       }
+Sphere.prototype.empty = function () {
 
-       distanceToPoint( point ) {
+       console.warn( 'THREE.Sphere: .empty() has been renamed to .isEmpty().' );
+       return this.isEmpty();
 
-               const clampedPoint = _vector$8.copy( point ).clamp( this.min, this.max );
-               return clampedPoint.sub( point ).length();
+};
 
-       }
+//
 
-       intersect( box ) {
+Frustum.prototype.setFromMatrix = function ( m ) {
 
-               this.min.max( box.min );
-               this.max.min( box.max );
+       console.warn( 'THREE.Frustum: .setFromMatrix() has been renamed to .setFromProjectionMatrix().' );
+       return this.setFromProjectionMatrix( m );
 
-               return this;
+};
 
-       }
+//
 
-       union( box ) {
+Matrix3.prototype.flattenToArrayOffset = function ( array, offset ) {
 
-               this.min.min( box.min );
-               this.max.max( box.max );
+       console.warn( 'THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
+       return this.toArray( array, offset );
 
-               return this;
+};
 
-       }
+Matrix3.prototype.multiplyVector3 = function ( vector ) {
 
-       translate( offset ) {
+       console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' );
+       return vector.applyMatrix3( this );
 
-               this.min.add( offset );
-               this.max.add( offset );
+};
 
-               return this;
+Matrix3.prototype.multiplyVector3Array = function ( /* a */ ) {
 
-       }
+       console.error( 'THREE.Matrix3: .multiplyVector3Array() has been removed.' );
 
-       equals( box ) {
+};
 
-               return box.min.equals( this.min ) && box.max.equals( this.max );
+Matrix3.prototype.applyToBufferAttribute = function ( attribute ) {
 
-       }
+       console.warn( 'THREE.Matrix3: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix3( matrix ) instead.' );
+       return attribute.applyMatrix3( this );
 
-}
+};
 
-function ImmediateRenderObject( material ) {
+Matrix3.prototype.applyToVector3Array = function ( /* array, offset, length */ ) {
 
-       Object3D.call( this );
+       console.error( 'THREE.Matrix3: .applyToVector3Array() has been removed.' );
 
-       this.material = material;
-       this.render = function ( /* renderCallback */ ) {};
+};
 
-       this.hasPositions = false;
-       this.hasNormals = false;
-       this.hasColors = false;
-       this.hasUvs = false;
+Matrix3.prototype.getInverse = function ( matrix ) {
 
-       this.positionArray = null;
-       this.normalArray = null;
-       this.colorArray = null;
-       this.uvArray = null;
+       console.warn( 'THREE.Matrix3: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
+       return this.copy( matrix ).invert();
 
-       this.count = 0;
+};
 
-}
+//
 
-ImmediateRenderObject.prototype = Object.create( Object3D.prototype );
-ImmediateRenderObject.prototype.constructor = ImmediateRenderObject;
+Matrix4.prototype.extractPosition = function ( m ) {
 
-ImmediateRenderObject.prototype.isImmediateRenderObject = true;
+       console.warn( 'THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().' );
+       return this.copyPosition( m );
 
-const backgroundMaterial = new MeshBasicMaterial( {
-       side: BackSide,
-       depthWrite: false,
-       depthTest: false,
-} );
-new Mesh( new BoxGeometry(), backgroundMaterial );
+};
 
-//
+Matrix4.prototype.flattenToArrayOffset = function ( array, offset ) {
 
-Curve.create = function ( construct, getPoint ) {
+       console.warn( 'THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
+       return this.toArray( array, offset );
 
-       console.log( 'THREE.Curve.create() has been deprecated' );
+};
 
-       construct.prototype = Object.create( Curve.prototype );
-       construct.prototype.constructor = construct;
-       construct.prototype.getPoint = getPoint;
+Matrix4.prototype.getPosition = function () {
 
-       return construct;
+       console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' );
+       return new Vector3().setFromMatrixColumn( this, 3 );
 
 };
 
-//
+Matrix4.prototype.setRotationFromQuaternion = function ( q ) {
 
-Object.assign( Path.prototype, {
+       console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' );
+       return this.makeRotationFromQuaternion( q );
 
-       fromPoints: function ( points ) {
+};
 
-               console.warn( 'THREE.Path: .fromPoints() has been renamed to .setFromPoints().' );
-               return this.setFromPoints( points );
+Matrix4.prototype.multiplyToArray = function () {
 
-       }
+       console.warn( 'THREE.Matrix4: .multiplyToArray() has been removed.' );
 
-} );
+};
 
-//
+Matrix4.prototype.multiplyVector3 = function ( vector ) {
 
-function Spline( points ) {
+       console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+       return vector.applyMatrix4( this );
 
-       console.warn( 'THREE.Spline has been removed. Use THREE.CatmullRomCurve3 instead.' );
+};
 
-       CatmullRomCurve3.call( this, points );
-       this.type = 'catmullrom';
+Matrix4.prototype.multiplyVector4 = function ( vector ) {
 
-}
+       console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+       return vector.applyMatrix4( this );
 
-Spline.prototype = Object.create( CatmullRomCurve3.prototype );
+};
 
-Object.assign( Spline.prototype, {
+Matrix4.prototype.multiplyVector3Array = function ( /* a */ ) {
 
-       initFromArray: function ( /* a */ ) {
+       console.error( 'THREE.Matrix4: .multiplyVector3Array() has been removed.' );
 
-               console.error( 'THREE.Spline: .initFromArray() has been removed.' );
+};
 
-       },
-       getControlPointsArray: function ( /* optionalTarget */ ) {
+Matrix4.prototype.rotateAxis = function ( v ) {
 
-               console.error( 'THREE.Spline: .getControlPointsArray() has been removed.' );
+       console.warn( 'THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' );
+       v.transformDirection( this );
 
-       },
-       reparametrizeByArcLength: function ( /* samplingCoef */ ) {
+};
 
-               console.error( 'THREE.Spline: .reparametrizeByArcLength() has been removed.' );
+Matrix4.prototype.crossVector = function ( vector ) {
 
-       }
+       console.warn( 'THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+       return vector.applyMatrix4( this );
 
-} );
+};
 
-//
+Matrix4.prototype.translate = function () {
 
-Object.assign( Loader.prototype, {
+       console.error( 'THREE.Matrix4: .translate() has been removed.' );
 
-       extractUrlBase: function ( url ) {
+};
 
-               console.warn( 'THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead.' );
-               return LoaderUtils.extractUrlBase( url );
+Matrix4.prototype.rotateX = function () {
 
-       }
+       console.error( 'THREE.Matrix4: .rotateX() has been removed.' );
 
-} );
+};
 
-Loader.Handlers = {
+Matrix4.prototype.rotateY = function () {
 
-       add: function ( /* regex, loader */ ) {
+       console.error( 'THREE.Matrix4: .rotateY() has been removed.' );
 
-               console.error( 'THREE.Loader: Handlers.add() has been removed. Use LoadingManager.addHandler() instead.' );
+};
 
-       },
+Matrix4.prototype.rotateZ = function () {
 
-       get: function ( /* file */ ) {
+       console.error( 'THREE.Matrix4: .rotateZ() has been removed.' );
 
-               console.error( 'THREE.Loader: Handlers.get() has been removed. Use LoadingManager.getHandler() instead.' );
+};
 
-       }
+Matrix4.prototype.rotateByAxis = function () {
 
-};
+       console.error( 'THREE.Matrix4: .rotateByAxis() has been removed.' );
 
-//
+};
 
-Object.assign( Box2.prototype, {
+Matrix4.prototype.applyToBufferAttribute = function ( attribute ) {
 
-       center: function ( optionalTarget ) {
+       console.warn( 'THREE.Matrix4: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix4( matrix ) instead.' );
+       return attribute.applyMatrix4( this );
 
-               console.warn( 'THREE.Box2: .center() has been renamed to .getCenter().' );
-               return this.getCenter( optionalTarget );
+};
 
-       },
-       empty: function () {
+Matrix4.prototype.applyToVector3Array = function ( /* array, offset, length */ ) {
 
-               console.warn( 'THREE.Box2: .empty() has been renamed to .isEmpty().' );
-               return this.isEmpty();
+       console.error( 'THREE.Matrix4: .applyToVector3Array() has been removed.' );
 
-       },
-       isIntersectionBox: function ( box ) {
+};
 
-               console.warn( 'THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox().' );
-               return this.intersectsBox( box );
+Matrix4.prototype.makeFrustum = function ( left, right, bottom, top, near, far ) {
 
-       },
-       size: function ( optionalTarget ) {
+       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 );
 
-               console.warn( 'THREE.Box2: .size() has been renamed to .getSize().' );
-               return this.getSize( optionalTarget );
+};
 
-       }
-} );
+Matrix4.prototype.getInverse = function ( matrix ) {
 
-Object.assign( Box3.prototype, {
+       console.warn( 'THREE.Matrix4: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
+       return this.copy( matrix ).invert();
 
-       center: function ( optionalTarget ) {
+};
 
-               console.warn( 'THREE.Box3: .center() has been renamed to .getCenter().' );
-               return this.getCenter( optionalTarget );
+//
 
-       },
-       empty: function () {
+Plane.prototype.isIntersectionLine = function ( line ) {
 
-               console.warn( 'THREE.Box3: .empty() has been renamed to .isEmpty().' );
-               return this.isEmpty();
+       console.warn( 'THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().' );
+       return this.intersectsLine( line );
 
-       },
-       isIntersectionBox: function ( box ) {
+};
 
-               console.warn( 'THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().' );
-               return this.intersectsBox( box );
+//
 
-       },
-       isIntersectionSphere: function ( sphere ) {
+Quaternion.prototype.multiplyVector3 = function ( vector ) {
 
-               console.warn( 'THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
-               return this.intersectsSphere( sphere );
+       console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
+       return vector.applyQuaternion( this );
 
-       },
-       size: function ( optionalTarget ) {
+};
 
-               console.warn( 'THREE.Box3: .size() has been renamed to .getSize().' );
-               return this.getSize( optionalTarget );
+Quaternion.prototype.inverse = function ( ) {
 
-       }
-);
+       console.warn( 'THREE.Quaternion: .inverse() has been renamed to invert().' );
+       return this.invert();
 
-Object.assign( Sphere.prototype, {
+};
 
-       empty: function () {
+//
 
-               console.warn( 'THREE.Sphere: .empty() has been renamed to .isEmpty().' );
-               return this.isEmpty();
+Ray.prototype.isIntersectionBox = function ( box ) {
 
-       },
+       console.warn( 'THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().' );
+       return this.intersectsBox( box );
 
-} );
+};
 
-Frustum.prototype.setFromMatrix = function ( m ) {
+Ray.prototype.isIntersectionPlane = function ( plane ) {
 
-       console.warn( 'THREE.Frustum: .setFromMatrix() has been renamed to .setFromProjectionMatrix().' );
-       return this.setFromProjectionMatrix( m );
+       console.warn( 'THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().' );
+       return this.intersectsPlane( plane );
 
 };
 
-Object.assign( MathUtils, {
+Ray.prototype.isIntersectionSphere = function ( sphere ) {
 
-       random16: function () {
+       console.warn( 'THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
+       return this.intersectsSphere( sphere );
 
-               console.warn( 'THREE.Math: .random16() has been deprecated. Use Math.random() instead.' );
-               return Math.random();
+};
 
-       },
+//
 
-       nearestPowerOfTwo: function ( value ) {
+Triangle.prototype.area = function () {
 
-               console.warn( 'THREE.Math: .nearestPowerOfTwo() has been renamed to .floorPowerOfTwo().' );
-               return MathUtils.floorPowerOfTwo( value );
+       console.warn( 'THREE.Triangle: .area() has been renamed to .getArea().' );
+       return this.getArea();
 
-       },
+};
 
-       nextPowerOfTwo: function ( value ) {
+Triangle.prototype.barycoordFromPoint = function ( point, target ) {
 
-               console.warn( 'THREE.Math: .nextPowerOfTwo() has been renamed to .ceilPowerOfTwo().' );
-               return MathUtils.ceilPowerOfTwo( value );
+       console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
+       return this.getBarycoord( point, target );
 
-       }
+};
 
-} );
+Triangle.prototype.midpoint = function ( target ) {
 
-Object.assign( Matrix3.prototype, {
+       console.warn( 'THREE.Triangle: .midpoint() has been renamed to .getMidpoint().' );
+       return this.getMidpoint( target );
 
-       flattenToArrayOffset: function ( array, offset ) {
+};
 
-               console.warn( 'THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
-               return this.toArray( array, offset );
+Triangle.prototypenormal = function ( target ) {
 
-       },
-       multiplyVector3: function ( vector ) {
+       console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
+       return this.getNormal( target );
 
-               console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' );
-               return vector.applyMatrix3( this );
+};
 
-       },
-       multiplyVector3Array: function ( /* a */ ) {
+Triangle.prototype.plane = function ( target ) {
 
-               console.error( 'THREE.Matrix3: .multiplyVector3Array() has been removed.' );
+       console.warn( 'THREE.Triangle: .plane() has been renamed to .getPlane().' );
+       return this.getPlane( target );
 
-       },
-       applyToBufferAttribute: function ( attribute ) {
+};
 
-               console.warn( 'THREE.Matrix3: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix3( matrix ) instead.' );
-               return attribute.applyMatrix3( this );
+Triangle.barycoordFromPoint = function ( point, a, b, c, target ) {
 
-       },
-       applyToVector3Array: function ( /* array, offset, length */ ) {
+       console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
+       return Triangle.getBarycoord( point, a, b, c, target );
 
-               console.error( 'THREE.Matrix3: .applyToVector3Array() has been removed.' );
+};
 
-       },
-       getInverse: function ( matrix ) {
+Triangle.normal = function ( a, b, c, target ) {
 
-               console.warn( 'THREE.Matrix3: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
-               return this.copy( matrix ).invert();
+       console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
+       return Triangle.getNormal( a, b, c, target );
 
-       }
+};
 
-} );
+//
 
-Object.assign( Matrix4.prototype, {
+Shape.prototype.extractAllPoints = function ( divisions ) {
 
-       extractPosition: function ( m ) {
+       console.warn( 'THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.' );
+       return this.extractPoints( divisions );
 
-               console.warn( 'THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().' );
-               return this.copyPosition( m );
+};
 
-       },
-       flattenToArrayOffset: function ( array, offset ) {
+Shape.prototype.extrude = function ( options ) {
 
-               console.warn( 'THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
-               return this.toArray( array, offset );
+       console.warn( 'THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.' );
+       return new ExtrudeGeometry( this, options );
 
-       },
-       getPosition: function () {
+};
 
-               console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' );
-               return new Vector3().setFromMatrixColumn( this, 3 );
+Shape.prototype.makeGeometry = function ( options ) {
 
-       },
-       setRotationFromQuaternion: function ( q ) {
+       console.warn( 'THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.' );
+       return new ShapeGeometry( this, options );
 
-               console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' );
-               return this.makeRotationFromQuaternion( q );
+};
 
-       },
-       multiplyToArray: function () {
+//
 
-               console.warn( 'THREE.Matrix4: .multiplyToArray() has been removed.' );
+Vector2.prototype.fromAttribute = function ( attribute, index, offset ) {
 
-       },
-       multiplyVector3: function ( vector ) {
+       console.warn( 'THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute().' );
+       return this.fromBufferAttribute( attribute, index, offset );
 
-               console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
-               return vector.applyMatrix4( this );
+};
 
-       },
-       multiplyVector4: function ( vector ) {
+Vector2.prototype.distanceToManhattan = function ( v ) {
 
-               console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
-               return vector.applyMatrix4( this );
+       console.warn( 'THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );
+       return this.manhattanDistanceTo( v );
 
-       },
-       multiplyVector3Array: function ( /* a */ ) {
+};
 
-               console.error( 'THREE.Matrix4: .multiplyVector3Array() has been removed.' );
+Vector2.prototype.lengthManhattan = function () {
 
-       },
-       rotateAxis: function ( v ) {
+       console.warn( 'THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength().' );
+       return this.manhattanLength();
 
-               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 );
+Vector3.prototype.setEulerFromRotationMatrix = function () {
 
-       },
-       translate: function () {
+       console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' );
 
-               console.error( 'THREE.Matrix4: .translate() has been removed.' );
+};
 
-       },
-       rotateX: function () {
+Vector3.prototype.setEulerFromQuaternion = function () {
 
-               console.error( 'THREE.Matrix4: .rotateX() has been removed.' );
+       console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' );
 
-       },
-       rotateY: function () {
+};
 
-               console.error( 'THREE.Matrix4: .rotateY() has been removed.' );
+Vector3.prototype.getPositionFromMatrix = function ( m ) {
 
-       },
-       rotateZ: function () {
+       console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' );
+       return this.setFromMatrixPosition( m );
 
-               console.error( 'THREE.Matrix4: .rotateZ() has been removed.' );
+};
 
-       },
-       rotateByAxis: function () {
+Vector3.prototype.getScaleFromMatrix = function ( m ) {
 
-               console.error( 'THREE.Matrix4: .rotateByAxis() has been removed.' );
+       console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' );
+       return this.setFromMatrixScale( m );
 
-       },
-       applyToBufferAttribute: function ( attribute ) {
+};
 
-               console.warn( 'THREE.Matrix4: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix4( matrix ) instead.' );
-               return attribute.applyMatrix4( this );
+Vector3.prototype.getColumnFromMatrix = function ( index, matrix ) {
 
-       },
-       applyToVector3Array: function ( /* array, offset, length */ ) {
+       console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' );
+       return this.setFromMatrixColumn( matrix, index );
 
-               console.error( 'THREE.Matrix4: .applyToVector3Array() has been removed.' );
+};
 
-       },
-       makeFrustum: function ( left, right, bottom, top, near, far ) {
+Vector3.prototype.applyProjection = function ( m ) {
 
-               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 );
+       console.warn( 'THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead.' );
+       return this.applyMatrix4( m );
 
-       },
-       getInverse: function ( matrix ) {
+};
 
-               console.warn( 'THREE.Matrix4: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
-               return this.copy( matrix ).invert();
+Vector3.prototype.fromAttribute = function ( attribute, index, offset ) {
 
-       }
+       console.warn( 'THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute().' );
+       return this.fromBufferAttribute( attribute, index, offset );
 
-} );
+};
 
-Plane.prototype.isIntersectionLine = function ( line ) {
+Vector3.prototype.distanceToManhattan = function ( v ) {
 
-       console.warn( 'THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().' );
-       return this.intersectsLine( line );
+       console.warn( 'THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );
+       return this.manhattanDistanceTo( v );
 
 };
 
-Object.assign( Quaternion.prototype, {
+Vector3.prototype.lengthManhattan = function () {
 
-       multiplyVector3: function ( vector ) {
+       console.warn( 'THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength().' );
+       return this.manhattanLength();
 
-               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();
+//
 
-       }
+Vector4.prototype.fromAttribute = function ( attribute, index, offset ) {
 
-} );
+       console.warn( 'THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute().' );
+       return this.fromBufferAttribute( attribute, index, offset );
 
-Object.assign( Ray.prototype, {
+};
 
-       isIntersectionBox: function ( box ) {
+Vector4.prototype.lengthManhattan = function () {
 
-               console.warn( 'THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().' );
-               return this.intersectsBox( box );
+       console.warn( 'THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength().' );
+       return this.manhattanLength();
 
-       },
-       isIntersectionPlane: function ( plane ) {
+};
 
-               console.warn( 'THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().' );
-               return this.intersectsPlane( plane );
+//
 
-       },
-       isIntersectionSphere: function ( sphere ) {
+Object3D.prototype.getChildByName = function ( name ) {
 
-               console.warn( 'THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
-               return this.intersectsSphere( sphere );
+       console.warn( 'THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().' );
+       return this.getObjectByName( name );
 
-       }
+};
 
-} );
+Object3D.prototype.renderDepth = function () {
 
-Object.assign( Triangle.prototype, {
+       console.warn( 'THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.' );
 
-       area: function () {
+};
 
-               console.warn( 'THREE.Triangle: .area() has been renamed to .getArea().' );
-               return this.getArea();
+Object3D.prototype.translate = function ( distance, axis ) {
 
-       },
-       barycoordFromPoint: function ( point, target ) {
+       console.warn( 'THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.' );
+       return this.translateOnAxis( axis, distance );
 
-               console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
-               return this.getBarycoord( point, target );
+};
 
-       },
-       midpoint: function ( target ) {
+Object3D.prototype.getWorldRotation = function () {
 
-               console.warn( 'THREE.Triangle: .midpoint() has been renamed to .getMidpoint().' );
-               return this.getMidpoint( target );
+       console.error( 'THREE.Object3D: .getWorldRotation() has been removed. Use THREE.Object3D.getWorldQuaternion( target ) instead.' );
 
-       },
-       normal: function ( target ) {
+};
 
-               console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
-               return this.getNormal( target );
+Object3D.prototype.applyMatrix = function ( matrix ) {
 
-       },
-       plane: function ( target ) {
+       console.warn( 'THREE.Object3D: .applyMatrix() has been renamed to .applyMatrix4().' );
+       return this.applyMatrix4( matrix );
 
-               console.warn( 'THREE.Triangle: .plane() has been renamed to .getPlane().' );
-               return this.getPlane( target );
+};
 
-       }
+Object.defineProperties( Object3D.prototype, {
 
-} );
+       eulerOrder: {
+               get: function () {
 
-Object.assign( Triangle, {
+                       console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' );
+                       return this.rotation.order;
 
-       barycoordFromPoint: function ( point, a, b, c, target ) {
+               },
+               set: function ( value ) {
 
-               console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
-               return Triangle.getBarycoord( point, a, b, c, target );
+                       console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' );
+                       this.rotation.order = value;
 
+               }
        },
-       normal: function ( a, b, c, target ) {
+       useQuaternion: {
+               get: function () {
+
+                       console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' );
 
-               console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
-               return Triangle.getNormal( a, b, c, target );
+               },
+               set: function () {
+
+                       console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' );
 
+               }
        }
 
 } );
 
-Object.assign( Shape.prototype, {
+Mesh.prototype.setDrawMode = function () {
 
-       extractAllPoints: function ( divisions ) {
+       console.error( 'THREE.Mesh: .setDrawMode() has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.' );
 
-               console.warn( 'THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.' );
-               return this.extractPoints( divisions );
+};
 
-       },
-       extrude: function ( options ) {
+Object.defineProperties( Mesh.prototype, {
 
-               console.warn( 'THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.' );
-               return new ExtrudeGeometry( this, options );
+       drawMode: {
+               get: function () {
 
-       },
-       makeGeometry: function ( options ) {
+                       console.error( 'THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode.' );
+                       return TrianglesDrawMode;
+
+               },
+               set: function () {
 
-               console.warn( 'THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.' );
-               return new ShapeGeometry( this, options );
+                       console.error( 'THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.' );
 
+               }
        }
 
 } );
 
-Object.assign( Vector2.prototype, {
+SkinnedMesh.prototype.initBones = function () {
 
-       fromAttribute: function ( attribute, index, offset ) {
+       console.error( 'THREE.SkinnedMesh: initBones() has been removed.' );
 
-               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 ) {
+PerspectiveCamera.prototype.setLens = function ( focalLength, filmGauge ) {
 
        console.warn( 'THREE.PerspectiveCamera.setLens is deprecated. ' +
                        'Use .setFocalLength and .filmGauge for a photographic setup.' );
@@ -46176,96 +44429,100 @@ Object.defineProperties( BufferAttribute.prototype, {
 
 } );
 
-Object.assign( BufferAttribute.prototype, {
-       setDynamic: function ( value ) {
+BufferAttribute.prototype.setDynamic = function ( value ) {
 
-               console.warn( 'THREE.BufferAttribute: .setDynamic() has been deprecated. Use .setUsage() instead.' );
-               this.setUsage( value === true ? DynamicDrawUsage : StaticDrawUsage );
-               return this;
+       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.' );
+BufferAttribute.prototype.copyIndicesArray = function ( /* indices */ ) {
 
-       },
-       setArray: function ( /* array */ ) {
+       console.error( 'THREE.BufferAttribute: .copyIndicesArray() has been removed.' );
 
-               console.error( 'THREE.BufferAttribute: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers' );
+},
 
-       }
-} );
+BufferAttribute.prototype.setArray = function ( /* array */ ) {
 
-Object.assign( BufferGeometry.prototype, {
+       console.error( 'THREE.BufferAttribute: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers' );
 
-       addIndex: function ( index ) {
+};
 
-               console.warn( 'THREE.BufferGeometry: .addIndex() has been renamed to .setIndex().' );
-               this.setIndex( index );
+//
 
-       },
-       addAttribute: function ( name, attribute ) {
+BufferGeometry.prototype.addIndex = function ( index ) {
 
-               console.warn( 'THREE.BufferGeometry: .addAttribute() has been renamed to .setAttribute().' );
+       console.warn( 'THREE.BufferGeometry: .addIndex() has been renamed to .setIndex().' );
+       this.setIndex( index );
 
-               if ( ! ( attribute && attribute.isBufferAttribute ) && ! ( attribute && attribute.isInterleavedBufferAttribute ) ) {
+};
 
-                       console.warn( 'THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).' );
+BufferGeometry.prototype.addAttribute = function ( name, attribute ) {
 
-                       return this.setAttribute( name, new BufferAttribute( arguments[ 1 ], arguments[ 2 ] ) );
+       console.warn( 'THREE.BufferGeometry: .addAttribute() has been renamed to .setAttribute().' );
 
-               }
+       if ( ! ( attribute && attribute.isBufferAttribute ) && ! ( attribute && attribute.isInterleavedBufferAttribute ) ) {
 
-               if ( name === 'index' ) {
+               console.warn( 'THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).' );
 
-                       console.warn( 'THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute.' );
-                       this.setIndex( attribute );
+               return this.setAttribute( name, new BufferAttribute( arguments[ 1 ], arguments[ 2 ] ) );
 
-                       return this;
+       }
 
-               }
+       if ( name === 'index' ) {
 
-               return this.setAttribute( name, attribute );
+               console.warn( 'THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute.' );
+               this.setIndex( attribute );
 
-       },
-       addDrawCall: function ( start, count, indexOffset ) {
+               return this;
 
-               if ( indexOffset !== undefined ) {
+       }
 
-                       console.warn( 'THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.' );
+       return this.setAttribute( name, attribute );
 
-               }
+};
 
-               console.warn( 'THREE.BufferGeometry: .addDrawCall() is now .addGroup().' );
-               this.addGroup( start, count );
+BufferGeometry.prototype.addDrawCall = function ( start, count, indexOffset ) {
 
-       },
-       clearDrawCalls: function () {
+       if ( indexOffset !== undefined ) {
 
-               console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' );
-               this.clearGroups();
+               console.warn( 'THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.' );
 
-       },
-       computeOffsets: function () {
+       }
 
-               console.warn( 'THREE.BufferGeometry: .computeOffsets() has been removed.' );
+       console.warn( 'THREE.BufferGeometry: .addDrawCall() is now .addGroup().' );
+       this.addGroup( start, count );
 
-       },
-       removeAttribute: function ( name ) {
+};
 
-               console.warn( 'THREE.BufferGeometry: .removeAttribute() has been renamed to .deleteAttribute().' );
+BufferGeometry.prototype.clearDrawCalls = function () {
 
-               return this.deleteAttribute( name );
+       console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' );
+       this.clearGroups();
 
-       },
-       applyMatrix: function ( matrix ) {
+};
 
-               console.warn( 'THREE.BufferGeometry: .applyMatrix() has been renamed to .applyMatrix4().' );
-               return this.applyMatrix4( matrix );
+BufferGeometry.prototype.computeOffsets = function () {
 
-       }
+       console.warn( 'THREE.BufferGeometry: .computeOffsets() has been removed.' );
 
-} );
+};
+
+BufferGeometry.prototype.removeAttribute = function ( name ) {
+
+       console.warn( 'THREE.BufferGeometry: .removeAttribute() has been renamed to .deleteAttribute().' );
+
+       return this.deleteAttribute( name );
+
+};
+
+BufferGeometry.prototype.applyMatrix = function ( matrix ) {
+
+       console.warn( 'THREE.BufferGeometry: .applyMatrix() has been renamed to .applyMatrix4().' );
+       return this.applyMatrix4( matrix );
+
+};
 
 Object.defineProperties( BufferGeometry.prototype, {
 
@@ -46288,135 +44545,47 @@ Object.defineProperties( BufferGeometry.prototype, {
 
 } );
 
-Object.defineProperties( InstancedBufferGeometry.prototype, {
-
-       maxInstancedCount: {
-               get: function () {
-
-                       console.warn( 'THREE.InstancedBufferGeometry: .maxInstancedCount has been renamed to .instanceCount.' );
-                       return this.instanceCount;
-
-               },
-               set: function ( value ) {
+InterleavedBuffer.prototype.setDynamic = 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;
 
-               console.warn( 'THREE.InterleavedBuffer: .setDynamic() has been deprecated. Use .setUsage() instead.' );
-               this.setUsage( value === true ? DynamicDrawUsage : StaticDrawUsage );
-               return this;
+};
 
-       },
-       setArray: function ( /* array */ ) {
+InterleavedBuffer.prototype.setArray = function ( /* array */ ) {
 
-               console.error( 'THREE.InterleavedBuffer: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers' );
+       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.' );
-
-       },
+ExtrudeGeometry.prototype.getArrays = function () {
 
-       addShape: function () {
+       console.error( 'THREE.ExtrudeGeometry: .getArrays() has been removed.' );
 
-               console.error( 'THREE.ExtrudeGeometry: .addShape() has been removed.' );
-
-       }
-
-} );
+};
 
-//
+ExtrudeGeometry.prototype.addShapeList = function () {
 
-Object.assign( Scene.prototype, {
+       console.error( 'THREE.ExtrudeGeometry: .addShapeList() has been removed.' );
 
-       dispose: function () {
+};
 
-               console.error( 'THREE.Scene: .dispose() has been removed.' );
+ExtrudeGeometry.prototype.addShape = function () {
 
-       }
+       console.error( 'THREE.ExtrudeGeometry: .addShape() has been removed.' );
 
-} );
+};
 
 //
 
-Object.defineProperties( Uniform.prototype, {
+Scene.prototype.dispose = function () {
 
-       dynamic: {
-               set: function () {
+       console.error( 'THREE.Scene: .dispose() has been removed.' );
 
-                       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;
-
-               }
-       }
-
-} );
+};
 
 //
 
@@ -46484,44 +44653,20 @@ Object.defineProperties( Material.prototype, {
                        this.stencilFuncMask = value;
 
                }
-       }
-
-} );
-
-Object.defineProperties( MeshPhongMaterial.prototype, {
+       },
 
-       metal: {
+       vertexTangents: {
                get: function () {
 
-                       console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead.' );
-                       return false;
+                       console.warn( 'THREE.' + this.type + ': .vertexTangents has been removed.' );
 
                },
                set: function () {
 
-                       console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead' );
+                       console.warn( 'THREE.' + this.type + ': .vertexTangents has been removed.' );
 
                }
-       }
-
-} );
-
-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;
-
-               }
-       }
+       },
 
 } );
 
@@ -46546,152 +44691,172 @@ Object.defineProperties( ShaderMaterial.prototype, {
 
 //
 
-Object.assign( WebGLRenderer.prototype, {
+WebGLRenderer.prototype.clearTarget = function ( renderTarget, color, depth, stencil ) {
 
-       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 );
 
-               console.warn( 'THREE.WebGLRenderer: .clearTarget() has been deprecated. Use .setRenderTarget() and .clear() instead.' );
-               this.setRenderTarget( renderTarget );
-               this.clear( color, depth, stencil );
+};
 
-       },
-       animate: function ( callback ) {
+WebGLRenderer.prototype.animate = function ( callback ) {
 
-               console.warn( 'THREE.WebGLRenderer: .animate() is now .setAnimationLoop().' );
-               this.setAnimationLoop( 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();
+WebGLRenderer.prototype.getCurrentRenderTarget = function () {
 
-       },
-       getMaxAnisotropy: function () {
+       console.warn( 'THREE.WebGLRenderer: .getCurrentRenderTarget() is now .getRenderTarget().' );
+       return this.getRenderTarget();
 
-               console.warn( 'THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy().' );
-               return this.capabilities.getMaxAnisotropy();
+};
 
-       },
-       getPrecision: function () {
+WebGLRenderer.prototype.getMaxAnisotropy = function () {
 
-               console.warn( 'THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision.' );
-               return this.capabilities.precision;
+       console.warn( 'THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy().' );
+       return this.capabilities.getMaxAnisotropy();
 
-       },
-       resetGLState: function () {
+};
 
-               console.warn( 'THREE.WebGLRenderer: .resetGLState() is now .state.reset().' );
-               return this.state.reset();
+WebGLRenderer.prototype.getPrecision = function () {
 
-       },
-       supportsFloatTextures: function () {
+       console.warn( 'THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision.' );
+       return this.capabilities.precision;
 
-               console.warn( 'THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( \'OES_texture_float\' ).' );
-               return this.extensions.get( 'OES_texture_float' );
+};
 
-       },
-       supportsHalfFloatTextures: function () {
+WebGLRenderer.prototype.resetGLState = function () {
 
-               console.warn( 'THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( \'OES_texture_half_float\' ).' );
-               return this.extensions.get( 'OES_texture_half_float' );
+       console.warn( 'THREE.WebGLRenderer: .resetGLState() is now .state.reset().' );
+       return this.state.reset();
 
-       },
-       supportsStandardDerivatives: function () {
+};
 
-               console.warn( 'THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( \'OES_standard_derivatives\' ).' );
-               return this.extensions.get( 'OES_standard_derivatives' );
+WebGLRenderer.prototype.supportsFloatTextures = function () {
 
-       },
-       supportsCompressedTextureS3TC: function () {
+       console.warn( 'THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( \'OES_texture_float\' ).' );
+       return this.extensions.get( 'OES_texture_float' );
 
-               console.warn( 'THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( \'WEBGL_compressed_texture_s3tc\' ).' );
-               return this.extensions.get( 'WEBGL_compressed_texture_s3tc' );
+};
 
-       },
-       supportsCompressedTexturePVRTC: function () {
+WebGLRenderer.prototype.supportsHalfFloatTextures = function () {
 
-               console.warn( 'THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( \'WEBGL_compressed_texture_pvrtc\' ).' );
-               return this.extensions.get( 'WEBGL_compressed_texture_pvrtc' );
+       console.warn( 'THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( \'OES_texture_half_float\' ).' );
+       return this.extensions.get( 'OES_texture_half_float' );
 
-       },
-       supportsBlendMinMax: function () {
+};
 
-               console.warn( 'THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( \'EXT_blend_minmax\' ).' );
-               return this.extensions.get( 'EXT_blend_minmax' );
+WebGLRenderer.prototype.supportsStandardDerivatives = function () {
 
-       },
-       supportsVertexTextures: function () {
+       console.warn( 'THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( \'OES_standard_derivatives\' ).' );
+       return this.extensions.get( 'OES_standard_derivatives' );
 
-               console.warn( 'THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures.' );
-               return this.capabilities.vertexTextures;
+};
 
-       },
-       supportsInstancedArrays: function () {
+WebGLRenderer.prototype.supportsCompressedTextureS3TC = function () {
 
-               console.warn( 'THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( \'ANGLE_instanced_arrays\' ).' );
-               return this.extensions.get( 'ANGLE_instanced_arrays' );
+       console.warn( 'THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( \'WEBGL_compressed_texture_s3tc\' ).' );
+       return this.extensions.get( 'WEBGL_compressed_texture_s3tc' );
 
-       },
-       enableScissorTest: function ( boolean ) {
+};
 
-               console.warn( 'THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest().' );
-               this.setScissorTest( boolean );
+WebGLRenderer.prototype.supportsCompressedTexturePVRTC = function () {
 
-       },
-       initMaterial: function () {
+       console.warn( 'THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( \'WEBGL_compressed_texture_pvrtc\' ).' );
+       return this.extensions.get( 'WEBGL_compressed_texture_pvrtc' );
 
-               console.warn( 'THREE.WebGLRenderer: .initMaterial() has been removed.' );
+};
 
-       },
-       addPrePlugin: function () {
+WebGLRenderer.prototype.supportsBlendMinMax = function () {
 
-               console.warn( 'THREE.WebGLRenderer: .addPrePlugin() has been removed.' );
+       console.warn( 'THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( \'EXT_blend_minmax\' ).' );
+       return this.extensions.get( 'EXT_blend_minmax' );
 
-       },
-       addPostPlugin: function () {
+};
 
-               console.warn( 'THREE.WebGLRenderer: .addPostPlugin() has been removed.' );
+WebGLRenderer.prototype.supportsVertexTextures = function () {
 
-       },
-       updateShadowMap: function () {
+       console.warn( 'THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures.' );
+       return this.capabilities.vertexTextures;
 
-               console.warn( 'THREE.WebGLRenderer: .updateShadowMap() has been removed.' );
+};
 
-       },
-       setFaceCulling: function () {
+WebGLRenderer.prototype.supportsInstancedArrays = function () {
 
-               console.warn( 'THREE.WebGLRenderer: .setFaceCulling() has been removed.' );
+       console.warn( 'THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( \'ANGLE_instanced_arrays\' ).' );
+       return this.extensions.get( 'ANGLE_instanced_arrays' );
 
-       },
-       allocTextureUnit: function () {
+};
 
-               console.warn( 'THREE.WebGLRenderer: .allocTextureUnit() has been removed.' );
+WebGLRenderer.prototype.enableScissorTest = function ( boolean ) {
 
-       },
-       setTexture: function () {
+       console.warn( 'THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest().' );
+       this.setScissorTest( boolean );
 
-               console.warn( 'THREE.WebGLRenderer: .setTexture() has been removed.' );
+};
 
-       },
-       setTexture2D: function () {
+WebGLRenderer.prototype.initMaterial = function () {
 
-               console.warn( 'THREE.WebGLRenderer: .setTexture2D() has been removed.' );
+       console.warn( 'THREE.WebGLRenderer: .initMaterial() has been removed.' );
 
-       },
-       setTextureCube: function () {
+};
 
-               console.warn( 'THREE.WebGLRenderer: .setTextureCube() has been removed.' );
+WebGLRenderer.prototype.addPrePlugin = function () {
 
-       },
-       getActiveMipMapLevel: function () {
+       console.warn( 'THREE.WebGLRenderer: .addPrePlugin() has been removed.' );
 
-               console.warn( 'THREE.WebGLRenderer: .getActiveMipMapLevel() is now .getActiveMipmapLevel().' );
-               return this.getActiveMipmapLevel();
+};
 
-       }
+WebGLRenderer.prototype.addPostPlugin = function () {
 
-} );
+       console.warn( 'THREE.WebGLRenderer: .addPostPlugin() has been removed.' );
+
+};
+
+WebGLRenderer.prototype.updateShadowMap = function () {
+
+       console.warn( 'THREE.WebGLRenderer: .updateShadowMap() has been removed.' );
+
+};
+
+WebGLRenderer.prototype.setFaceCulling = function () {
+
+       console.warn( 'THREE.WebGLRenderer: .setFaceCulling() has been removed.' );
+
+};
+
+WebGLRenderer.prototype.allocTextureUnit = function () {
+
+       console.warn( 'THREE.WebGLRenderer: .allocTextureUnit() has been removed.' );
+
+};
+
+WebGLRenderer.prototype.setTexture = function () {
+
+       console.warn( 'THREE.WebGLRenderer: .setTexture() has been removed.' );
+
+};
+
+WebGLRenderer.prototype.setTexture2D = function () {
+
+       console.warn( 'THREE.WebGLRenderer: .setTexture2D() has been removed.' );
+
+};
+
+WebGLRenderer.prototype.setTextureCube = function () {
+
+       console.warn( 'THREE.WebGLRenderer: .setTextureCube() has been removed.' );
+
+};
+
+WebGLRenderer.prototype.getActiveMipMapLevel = function () {
+
+       console.warn( 'THREE.WebGLRenderer: .getActiveMipMapLevel() is now .getActiveMipmapLevel().' );
+       return this.getActiveMipmapLevel();
+
+};
 
 Object.defineProperties( WebGLRenderer.prototype, {
 
@@ -46986,32 +45151,19 @@ Object.defineProperties( WebGLRenderTarget.prototype, {
 
 //
 
-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;
+Audio.prototype.load = function ( file ) {
 
-               }
-       },
-       startTime: {
-               set: function () {
+       console.warn( 'THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.' );
+       const scope = this;
+       const audioLoader = new AudioLoader();
+       audioLoader.load( file, function ( buffer ) {
 
-                       console.warn( 'THREE.Audio: .startTime is now .play( delay ).' );
+               scope.setBuffer( buffer );
 
-               }
-       }
+       } );
+       return this;
 
-} );
+};
 
 //
 
@@ -51414,4531 +49566,2976 @@ class GeoRBush extends RBush {
     }
 }
 
-/*
- * 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;
+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 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 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;
+function commonjsRequire (path) {
+       throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
+}
+
+var nativeIsArray = Array.isArray;
+var toString$2 = Object.prototype.toString;
 
-    this.cy = 3.0 * p1y;
-    this.by = 3.0 * (p2y - p1y) - this.cy;
-    this.ay = 1.0 - this.cy - this.by;
+var xIsArray = nativeIsArray || isArray$3;
 
-    this.p1x = p1x;
-    this.p1y = p2y;
-    this.p2x = p2x;
-    this.p2y = p2y;
+function isArray$3(obj) {
+    return toString$2.call(obj) === "[object Array]"
 }
 
-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;
-};
+var version$5 = "2";
 
-UnitBezier.prototype.sampleCurveY = function(t) {
-    return ((this.ay * t + this.by) * t + this.cy) * t;
-};
+var version$4 = version$5;
 
-UnitBezier.prototype.sampleCurveDerivativeX = function(t) {
-    return (3.0 * this.ax * t + 2.0 * this.bx) * t + this.cx;
-};
+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$4;
+VirtualPatch.prototype.type = "VirtualPatch";
 
-UnitBezier.prototype.solveCurveX = function(x, epsilon) {
-    if (typeof epsilon === 'undefined') epsilon = 1e-6;
+var version$3 = version$5;
 
-    var t0, t1, t2, x2, i;
+var isVnode = isVirtualNode;
 
-    // First try a few iterations of Newton's method -- normally very fast.
-    for (t2 = x, i = 0; i < 8; i++) {
+function isVirtualNode(x) {
+    return x && x.type === "VirtualNode" && x.version === version$3
+}
 
-        x2 = this.sampleCurveX(t2) - x;
-        if (Math.abs(x2) < epsilon) return t2;
+var version$2 = version$5;
 
-        var d2 = this.sampleCurveDerivativeX(t2);
-        if (Math.abs(d2) < 1e-6) break;
+var isVtext = isVirtualText;
 
-        t2 = t2 - x2 / d2;
-    }
+function isVirtualText(x) {
+    return x && x.type === "VirtualText" && x.version === version$2
+}
 
-    // Fall back to the bisection method for reliability.
-    t0 = 0.0;
-    t1 = 1.0;
-    t2 = x;
+var isWidget_1 = isWidget$7;
 
-    if (t2 < t0) return t0;
-    if (t2 > t1) return t1;
+function isWidget$7(w) {
+    return w && w.type === "Widget"
+}
 
-    while (t0 < t1) {
+var isThunk_1 = isThunk$3;
 
-        x2 = this.sampleCurveX(t2);
-        if (Math.abs(x2 - x) < epsilon) return t2;
+function isThunk$3(t) {
+    return t && t.type === "Thunk"
+}
 
-        if (x > x2) {
-            t0 = t2;
-        } else {
-            t1 = t2;
-        }
+var isVNode$4 = isVnode;
+var isVText$3 = isVtext;
+var isWidget$6 = isWidget_1;
+var isThunk$2 = isThunk_1;
 
-        t2 = (t1 - t0) * 0.5 + t0;
+var handleThunk_1 = handleThunk$2;
+
+function handleThunk$2(a, b) {
+    var renderedA = a;
+    var renderedB = b;
+
+    if (isThunk$2(b)) {
+        renderedB = renderThunk(b, a);
     }
 
-    // Failure.
-    return t2;
-};
+    if (isThunk$2(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$4(renderedThunk) ||
+            isVText$3(renderedThunk) ||
+            isWidget$6(renderedThunk))) {
+        throw new Error("thunk did not return a valid node");
+    }
 
-UnitBezier.prototype.solve = function(x, epsilon) {
-    return this.sampleCurveY(this.solveCurveX(x, epsilon));
+    return renderedThunk
+}
+
+var isObject$2 = function isObject(x) {
+       return typeof x === 'object' && x !== null;
 };
 
-/**
- * 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 = {}));
+var isVhook = isHook$3;
 
-/**
- * @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);
+function isHook$3(hook) {
+    return hook &&
+      (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") ||
+       typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook"))
+}
+
+var isObject$1 = isObject$2;
+var isHook$2 = isVhook;
+
+var diffProps_1 = diffProps$1;
+
+function diffProps$1(a, b) {
+    var diff;
+
+    for (var aKey in a) {
+        if (!(aKey in b)) {
+            diff = diff || {};
+            diff[aKey] = undefined;
         }
-        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;
+
+        var aValue = a[aKey];
+        var bValue = b[aKey];
+
+        if (aValue === bValue) {
+            continue
+        } else if (isObject$1(aValue) && isObject$1(bValue)) {
+            if (getPrototype$1(bValue) !== getPrototype$1(aValue)) {
+                diff = diff || {};
+                diff[aKey] = bValue;
+            } else if (isHook$2(bValue)) {
+                 diff = diff || {};
+                 diff[aKey] = bValue;
+            } else {
+                var objectDiff = diffProps$1(aValue, bValue);
+                if (objectDiff) {
+                    diff = diff || {};
+                    diff[aKey] = objectDiff;
+                }
+            }
+        } else {
+            diff = diff || {};
+            diff[aKey] = bValue;
         }
     }
-    /**
-     * Get position.
-     * @returns {THREE.Vector3} The position vector.
-     */
-    get position() {
-        return this._position;
+
+    for (var bKey in b) {
+        if (!(bKey in a)) {
+            diff = diff || {};
+            diff[bKey] = b[bKey];
+        }
     }
-    /**
-     * Get lookat.
-     * @returns {THREE.Vector3} The lookat vector.
-     */
-    get lookat() {
-        return this._lookat;
+
+    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 isArray$2 = xIsArray;
+
+var VPatch$1 = vpatch;
+var isVNode$3 = isVnode;
+var isVText$2 = isVtext;
+var isWidget$5 = isWidget_1;
+var isThunk$1 = isThunk_1;
+var handleThunk$1 = handleThunk_1;
+
+var diffProps = diffProps_1;
+
+var diff_1$1 = diff$2;
+
+function diff$2(a, b) {
+    var patch = { a: a };
+    walk(a, b, patch, 0);
+    return patch
+}
+
+function walk(a, b, patch, index) {
+    if (a === b) {
+        return
     }
-    /**
-     * Get up.
-     * @returns {THREE.Vector3} The up vector.
-     */
-    get up() {
-        return this._up;
+
+    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$5(a)) {
+            clearState(a, patch, index);
+            apply = patch[index];
+        }
+
+        apply = appendPatch(apply, new VPatch$1(VPatch$1.REMOVE, a, b));
+    } else if (isVNode$3(b)) {
+        if (isVNode$3(a)) {
+            if (a.tagName === b.tagName &&
+                a.namespace === b.namespace &&
+                a.key === b.key) {
+                var propsPatch = diffProps(a.properties, b.properties);
+                if (propsPatch) {
+                    apply = appendPatch(apply,
+                        new VPatch$1(VPatch$1.PROPS, a, propsPatch));
+                }
+                apply = diffChildren(a, b, patch, apply, index);
+            } else {
+                apply = appendPatch(apply, new VPatch$1(VPatch$1.VNODE, a, b));
+                applyClear = true;
+            }
+        } else {
+            apply = appendPatch(apply, new VPatch$1(VPatch$1.VNODE, a, b));
+            applyClear = true;
+        }
+    } else if (isVText$2(b)) {
+        if (!isVText$2(a)) {
+            apply = appendPatch(apply, new VPatch$1(VPatch$1.VTEXT, a, b));
+            applyClear = true;
+        } else if (a.text !== b.text) {
+            apply = appendPatch(apply, new VPatch$1(VPatch$1.VTEXT, a, b));
+        }
+    } else if (isWidget$5(b)) {
+        if (!isWidget$5(a)) {
+            applyClear = true;
+        }
+
+        apply = appendPatch(apply, new VPatch$1(VPatch$1.WIDGET, a, b));
     }
-    /**
-     * Get focal.
-     * @returns {number} The focal length.
-     */
-    get focal() {
-        return this._focal;
+
+    if (apply) {
+        patch[index] = apply;
     }
-    /**
-     * Set focal.
-     */
-    set focal(value) {
-        this._focal = value;
+
+    if (applyClear) {
+        clearState(a, patch, index);
     }
-    /**
-     * 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;
+}
+
+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$1(VPatch$1.INSERT, null, rightNode));
+            }
+        } else {
+            walk(leftNode, rightNode, patch, index);
+        }
+
+        if (isVNode$3(leftNode) && leftNode.count) {
+            index += leftNode.count;
+        }
     }
-    /**
-     * 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;
+
+    if (orderedSet.moves) {
+        // Reorder nodes last
+        apply = appendPatch(apply, new VPatch$1(
+            VPatch$1.ORDER,
+            a,
+            orderedSet.moves
+        ));
     }
-    /**
-     * 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;
+
+    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$5(vNode)) {
+        if (typeof vNode.destroy === "function") {
+            patch[index] = appendPatch(
+                patch[index],
+                new VPatch$1(VPatch$1.REMOVE, vNode, null)
+            );
+        }
+    } else if (isVNode$3(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$3(child) && child.count) {
+                index += child.count;
+            }
+        }
+    } else if (isThunk$1(vNode)) {
+        thunks(vNode, null, patch, index);
     }
-    /**
-     * 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);
+}
+
+// Create a sub-patch for thunks
+function thunks(a, b, patch, index) {
+    var nodes = handleThunk$1(a, b);
+    var thunkPatch = diff$2(nodes.a, nodes.b);
+    if (hasPatches(thunkPatch)) {
+        patch[index] = new VPatch$1(VPatch$1.THUNK, null, thunkPatch);
     }
-    /**
-     * 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;
+}
+
+function hasPatches(patch) {
+    for (var index in patch) {
+        if (index !== "a") {
+            return true
         }
-        return 0.5 / Math.tan(Math.PI / 2);
     }
+
+    return false
 }
 
-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);
+// Execute hooks when two nodes are identical
+function unhook(vNode, patch, index) {
+    if (isVNode$3(vNode)) {
+        if (vNode.hooks) {
+            patch[index] = appendPatch(
+                patch[index],
+                new VPatch$1(
+                    VPatch$1.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$3(child) && child.count) {
+                    index += child.count;
+                }
+            }
+        }
+    } else if (isThunk$1(vNode)) {
+        thunks(vNode, null, patch, index);
     }
-    get ck1() {
-        return this._ck1;
+}
+
+function undefinedKeys(obj) {
+    var result = {};
+
+    for (var key in obj) {
+        result[key] = undefined;
     }
-    get ck2() {
-        return this._ck2;
+
+    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
+        }
     }
-    get cameraType() {
-        return this._cameraType;
+
+    // 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
+        }
     }
-    /**
-     * Get basic aspect.
-     * @returns {number} The orientation adjusted aspect ratio.
-     */
-    get basicAspect() {
-        return this._basicAspect;
+
+    // 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);
+            }
+        }
     }
-    /**
-     * 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;
+
+    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);
+        }
     }
-    get basicRt() {
-        return this._basicWorldToCamera;
+
+    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++;
+        }
     }
-    /**
-     * 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;
+
+    // remove all the remaining nodes from simulate
+    while(simulateIndex < simulate.length) {
+        simulateItem = simulate[simulateIndex];
+        removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key));
     }
-    /**
-     * Get focal.
-     * @returns {number} The image focal length.
-     */
-    get focal() {
-        return this._focal;
+
+    // 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
+        }
     }
-    /**
-     * 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;
+
+    return {
+        children: newChildren,
+        moves: {
+            removes: removes,
+            inserts: inserts
+        }
     }
-    /**
-     * Get orientation.
-     * @returns {number} The image orientation.
-     */
-    get orientation() {
-        return this._orientation;
+}
+
+function remove(arr, index, key) {
+    arr.splice(index, 1);
+
+    return {
+        from: index,
+        key: key
     }
-    /**
-     * Get rt.
-     * @returns {THREE.Matrix4} The extrinsic camera matrix.
-     */
-    get rt() {
-        return this._worldToCamera;
+}
+
+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);
+        }
     }
-    /**
-     * Get srt.
-     * @returns {THREE.Matrix4} The scaled extrinsic camera matrix.
-     */
-    get srt() {
-        return this._scaledWorldToCamera;
+
+    return {
+        keys: keys,     // A hash of key name to index
+        free: free      // An array of unkeyed item indices
     }
-    /**
-     * Get srtInverse.
-     * @returns {THREE.Matrix4} The scaled extrinsic camera matrix.
-     */
-    get srtInverse() {
-        return this._scaledWorldToCameraInverse;
+}
+
+function appendPatch(apply, patch) {
+    if (apply) {
+        if (isArray$2(apply)) {
+            apply.push(patch);
+        } else {
+            apply = [apply, patch];
+        }
+
+        return apply
+    } else {
+        return patch
     }
-    /**
-     * Get scale.
-     * @returns {number} The image atomic reconstruction scale.
-     */
-    get scale() {
-        return this._scale;
+}
+
+var diff$1 = diff_1$1;
+
+var diff_1 = diff$1;
+
+var slice = Array.prototype.slice;
+
+var domWalk$2 = iterativelyWalk;
+
+function iterativelyWalk(nodes, cb) {
+    if (!('length' in nodes)) {
+        nodes = [nodes];
     }
-    /**
-     * 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;
+    
+    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);
+        }
     }
-    /**
-     * Get radial peak.
-     * @returns {number} Value indicating the radius where the radial
-     * undistortion function peaks.
-     */
-    get radialPeak() {
-        return this._radialPeak;
+}
+
+var domComment = Comment$1;
+
+function Comment$1(data, owner) {
+    if (!(this instanceof Comment$1)) {
+        return new Comment$1(data, owner)
     }
-    /**
-     * 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;
+
+    this.data = data;
+    this.nodeValue = data;
+    this.length = data.length;
+    this.ownerDocument = owner || null;
+}
+
+Comment$1.prototype.nodeType = 8;
+Comment$1.prototype.nodeName = "#comment";
+
+Comment$1.prototype.toString = function _Comment_toString() {
+    return "[object Comment]"
+};
+
+var domText = DOMText$1;
+
+function DOMText$1(value, owner) {
+    if (!(this instanceof DOMText$1)) {
+        return new DOMText$1(value)
     }
-    /**
-     * 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]);
-        }
+
+    this.data = value || "";
+    this.length = this.data.length;
+    this.ownerDocument = owner || null;
+}
+
+DOMText$1.prototype.type = "DOMTextNode";
+DOMText$1.prototype.nodeType = 3;
+DOMText$1.prototype.nodeName = "#text";
+
+DOMText$1.prototype.toString = function _Text_toString() {
+    return this.data
+};
+
+DOMText$1.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$2;
+
+function dispatchEvent$2(ev) {
+    var elem = this;
+    var type = ev.type;
+
+    if (!ev.target) {
+        ev.target = elem;
     }
-    /**
-     * 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;
+
+    if (!elem.listeners) {
+        elem.listeners = {};
     }
-    /**
-     * 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);
+
+    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);
+            }
+        })
     }
-    /**
-     * 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]);
+
+    if (elem.parentNode) {
+        elem.parentNode.dispatchEvent(ev);
     }
-    /**
-     * 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,
-        ];
+}
+
+var addEventListener_1 = addEventListener$2;
+
+function addEventListener$2(type, listener) {
+    var elem = this;
+
+    if (!elem.listeners) {
+        elem.listeners = {};
     }
-    /**
-     * 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];
-        }
+
+    if (!elem.listeners[type]) {
+        elem.listeners[type] = [];
     }
-    /** 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;
+
+    if (elem.listeners[type].indexOf(listener) === -1) {
+        elem.listeners[type].push(listener);
     }
-    /**
-     * 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,
-                ];
-            }
-        }
+}
+
+var removeEventListener_1 = removeEventListener$2;
+
+function removeEventListener$2(type, listener) {
+    var elem = this;
+
+    if (!elem.listeners) {
+        return
     }
-    /**
-     * 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];
+
+    if (!elem.listeners[type]) {
+        return
     }
-    /**
-     * 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];
+
+    var list = elem.listeners[type];
+    var index = list.indexOf(listener);
+    if (index !== -1) {
+        list.splice(index, 1);
     }
-    /**
-     * 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;
+}
+
+var serialize = serializeNode$1;
+
+var voidElements = ["area","base","br","col","embed","hr","img","input","keygen","link","menuitem","meta","param","source","track","wbr"];
+
+function serializeNode$1(node) {
+    switch (node.nodeType) {
+        case 3:
+            return escapeText(node.data)
+        case 8:
+            return "<!--" + node.data + "-->"
+        default:
+            return serializeElement(node)
     }
-    _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));
+}
+
+function serializeElement(elem) {
+    var strings = [];
+
+    var tagname = elem.tagName;
+
+    if (elem.namespaceURI === "http://www.w3.org/1999/xhtml") {
+        tagname = tagname.toLowerCase();
     }
-    /**
-     * 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();
+
+    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$1));
+        } else if (elem.textContent || elem.innerText) {
+            strings.push(escapeText(elem.textContent || elem.innerText));
+        } else if (elem.innerHTML) {
+            strings.push(elem.innerHTML);
         }
-        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;
+
+        strings.push("</" + tagname + ">");
     }
-    _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);
+
+    return strings.join("")
+}
+
+function isProperty(elem, key) {
+    var type = typeof elem[key];
+
+    if (key === "style" && Object.keys(elem.style).length > 0) {
+      return true
     }
-    _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;
+
+    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] });
     }
-    /**
-     * 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);
+
+    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(" ") : ""
 }
 
-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));
+function properties(elem) {
+    var props = [];
+    for (var key in elem) {
+        if (isProperty(elem, key)) {
+            props.push({ name: key, value: elem[key] });
         }
-        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;
+
+    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 });
+      }
     }
-    get zoom() {
-        return this._zoom;
+
+    if (elem.className) {
+        props.push({ name: "class", value: elem.className });
     }
-    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;
+
+    return props.length ? stringify(props) : ""
+}
+
+function escapeText(s) {
+    var str = '';
+
+    if (typeof(s) === 'string') { 
+        str = s; 
+    } else if (s) {
+        str = s.toString();
     }
-    get previousTransform() {
-        return this._trajectoryTransforms.length > 1 && this.currentIndex > 0 ?
-            this._trajectoryTransforms[this.currentIndex - 1] : null;
+
+    return str
+        .replace(/&/g, "&amp;")
+        .replace(/</g, "&lt;")
+        .replace(/>/g, "&gt;")
+}
+
+function escapeAttributeValue(str) {
+    return escapeText(str).replace(/"/g, "&quot;")
+}
+
+var domWalk$1 = domWalk$2;
+var dispatchEvent$1 = dispatchEvent_1;
+var addEventListener$1 = addEventListener_1;
+var removeEventListener$1 = removeEventListener_1;
+var serializeNode = serialize;
+
+var htmlns = "http://www.w3.org/1999/xhtml";
+
+var domElement = DOMElement$2;
+
+function DOMElement$2(tagName, owner, namespace) {
+    if (!(this instanceof DOMElement$2)) {
+        return new DOMElement$2(tagName)
     }
-    get motionless() {
-        return this._motionless;
+
+    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';
     }
-    get transitionMode() {
-        return this._transitionMode;
+}
+
+DOMElement$2.prototype.type = "DOMElement";
+DOMElement$2.prototype.nodeType = 1;
+
+DOMElement$2.prototype.appendChild = function _Element_appendChild(child) {
+    if (child.parentNode) {
+        child.parentNode.removeChild(child);
     }
-    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");
+
+    this.childNodes.push(child);
+    child.parentNode = this;
+
+    return child
+};
+
+DOMElement$2.prototype.replaceChild =
+    function _Element_replaceChild(elem, needle) {
+        // TODO: Throw NotFoundError if needle.parentNode !== this
+
+        if (elem.parentNode) {
+            elem.parentNode.removeChild(elem);
         }
-        if (this._currentIndex < 0) {
-            this.set(images);
+
+        var index = this.childNodes.indexOf(needle);
+
+        needle.parentNode = null;
+        this.childNodes[index] = elem;
+        elem.parentNode = this;
+
+        return needle
+    };
+
+DOMElement$2.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$2.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);
         }
-        else {
-            this._trajectory = this._trajectory.concat(images);
-            this._appendToTrajectories(images);
+
+        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);
         }
-    }
-    prepend(images) {
-        if (images.length < 1) {
-            throw Error("Trajectory can not be empty");
+
+        elem.parentNode = this;
+        return elem
+    };
+
+DOMElement$2.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);
         }
-        this._trajectory = images.slice().concat(this._trajectory);
-        this._currentIndex += images.length;
-        this._setCurrentImage();
-        let referenceReset = this._setReference(this._currentImage);
-        if (referenceReset) {
-            this._setTrajectories();
+        if (this.tagName === 'INPUT' && name === 'type') {
+          this.type = value;
         }
         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");
+          var attributes = this._attributes[namespace] || (this._attributes[namespace] = {});
+          attributes[localName] = {value: value, prefix: prefix};
         }
-        for (let i = 0; i < n; i++) {
-            this._trajectory.shift();
-            this._trajectoryTransforms.shift();
-            this._trajectoryCameras.shift();
-            this._currentIndex--;
+    };
+
+DOMElement$2.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;
         }
-        this._setCurrentImage();
-    }
-    clearPrior() {
-        if (this._currentIndex > 0) {
-            this.remove(this._currentIndex - 1);
+        if (typeof value !== "string") {
+            return null
         }
-    }
-    clear() {
-        this.cut();
-        if (this._currentIndex > 0) {
-            this.remove(this._currentIndex - 1);
+        return value
+    };
+
+DOMElement$2.prototype.removeAttributeNS =
+    function _Element_removeAttributeNS(namespace, name) {
+        var attributes = this._attributes[namespace];
+        if (attributes) {
+            delete attributes[name];
         }
-    }
-    cut() {
-        while (this._trajectory.length - 1 > this._currentIndex) {
-            this._trajectory.pop();
-            this._trajectoryTransforms.pop();
-            this._trajectoryCameras.pop();
+    };
+
+DOMElement$2.prototype.hasAttributeNS =
+    function _Element_hasAttributeNS(namespace, name) {
+        var attributes = this._attributes[namespace];
+        return !!attributes && name in attributes;
+    };
+
+DOMElement$2.prototype.setAttribute = function _Element_setAttribute(name, value) {
+    return this.setAttributeNS(null, name, value)
+};
+
+DOMElement$2.prototype.getAttribute = function _Element_getAttribute(name) {
+    return this.getAttributeNS(null, name)
+};
+
+DOMElement$2.prototype.removeAttribute = function _Element_removeAttribute(name) {
+    return this.removeAttributeNS(null, name)
+};
+
+DOMElement$2.prototype.hasAttribute = function _Element_hasAttribute(name) {
+    return this.hasAttributeNS(null, name)
+};
+
+DOMElement$2.prototype.removeEventListener = removeEventListener$1;
+DOMElement$2.prototype.addEventListener = addEventListener$1;
+DOMElement$2.prototype.dispatchEvent = dispatchEvent$1;
+
+// Un-implemented
+DOMElement$2.prototype.focus = function _Element_focus() {
+    return void 0
+};
+
+DOMElement$2.prototype.toString = function _Element_toString() {
+    return serializeNode(this)
+};
+
+DOMElement$2.prototype.getElementsByClassName = function _Element_getElementsByClassName(classNames) {
+    var classes = classNames.split(" ");
+    var elems = [];
+
+    domWalk$1(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);
+            }
         }
-    }
-    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();
+    });
+
+    return elems
+};
+
+DOMElement$2.prototype.getElementsByTagName = function _Element_getElementsByTagName(tagName) {
+    tagName = tagName.toLowerCase();
+    var elems = [];
+
+    domWalk$1(this.childNodes, function (node) {
+        if (node.nodeType === 1 && (tagName === '*' || node.tagName.toLowerCase() === tagName)) {
+            elems.push(node);
         }
-        this._setCurrentCamera();
-    }
-    _setCurrentCamera() {
-        this._currentCamera = this._trajectoryCameras[this._currentIndex].clone();
-        this._previousCamera = this._currentIndex > 0 ?
-            this._trajectoryCameras[this._currentIndex - 1].clone() :
-            this._currentCamera.clone();
+    });
+
+    return elems
+};
+
+DOMElement$2.prototype.contains = function _Element_contains(element) {
+    return domWalk$1(this, function (node) {
+        return element === node
+    }) || false
+};
+
+var DOMElement$1 = domElement;
+
+var domFragment = DocumentFragment$1;
+
+function DocumentFragment$1(owner) {
+    if (!(this instanceof DocumentFragment$1)) {
+        return new DocumentFragment$1()
     }
-    _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()));
+
+    this.childNodes = [];
+    this.parentNode = null;
+    this.ownerDocument = owner || null;
+}
+
+DocumentFragment$1.prototype.type = "DocumentFragment";
+DocumentFragment$1.prototype.nodeType = 11;
+DocumentFragment$1.prototype.nodeName = "#document-fragment";
+
+DocumentFragment$1.prototype.appendChild  = DOMElement$1.prototype.appendChild;
+DocumentFragment$1.prototype.replaceChild = DOMElement$1.prototype.replaceChild;
+DocumentFragment$1.prototype.removeChild  = DOMElement$1.prototype.removeChild;
+
+DocumentFragment$1.prototype.toString =
+    function _DocumentFragment_toString() {
+        return this.childNodes.map(function (node) {
+            return String(node)
+        }).join("")
+    };
+
+var event = Event$1;
+
+function Event$1(family) {}
+
+Event$1.prototype.initEvent = function _Event_initEvent(type, bubbles, cancelable) {
+    this.type = type;
+    this.bubbles = bubbles;
+    this.cancelable = cancelable;
+};
+
+Event$1.prototype.preventDefault = function _Event_preventDefault() {
+    
+};
+
+var domWalk = domWalk$2;
+
+var Comment = domComment;
+var DOMText = domText;
+var DOMElement = domElement;
+var DocumentFragment = domFragment;
+var Event = event;
+var dispatchEvent = dispatchEvent_1;
+var addEventListener = addEventListener_1;
+var removeEventListener = removeEventListener_1;
+
+var document$3 = Document$1;
+
+function Document$1() {
+    if (!(this instanceof Document$1)) {
+        return new Document$1();
     }
-    _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.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$1.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 DocumentFragment(this)
+};
+
+proto.createEvent = function createEvent(family) {
+    return new Event(family)
+};
+
+proto.createComment = function createComment(data) {
+    return new Comment(data, this)
+};
+
+proto.getElementById = function getElementById(id) {
+    id = String(id);
+
+    var result = domWalk(this.childNodes, function (node) {
+        if (String(node.id) === id) {
+            return node
         }
-        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;
+    });
+
+    return result || null
+};
+
+proto.getElementsByClassName = DOMElement.prototype.getElementsByClassName;
+proto.getElementsByTagName = DOMElement.prototype.getElementsByTagName;
+proto.contains = DOMElement.prototype.contains;
+
+proto.removeEventListener = removeEventListener;
+proto.addEventListener = addEventListener;
+proto.dispatchEvent = dispatchEvent;
+
+var Document = document$3;
+
+var minDocument = new Document();
+
+var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal :
+    typeof window !== 'undefined' ? window : {};
+var minDoc = minDocument;
+
+var doccy;
+
+if (typeof document !== 'undefined') {
+    doccy = document;
+} else {
+    doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
+
+    if (!doccy) {
+        doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
     }
-    _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;
+}
+
+var document_1 = doccy;
+
+var isObject = isObject$2;
+var isHook$1 = isVhook;
+
+var applyProperties_1 = applyProperties$2;
+
+function applyProperties$2(node, props, previous) {
+    for (var propName in props) {
+        var propValue = props[propName];
+
+        if (propValue === undefined) {
+            removeProperty(node, propName, propValue, previous);
+        } else if (isHook$1(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;
+            }
         }
     }
-    _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");
+}
+
+function removeProperty(node, propName, propValue, previous) {
+    if (previous) {
+        var previousValue = previous[propName];
+
+        if (!isHook$1(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;
             }
-            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));
+        } else if (previousValue.unhook) {
+            previousValue.unhook(node, propName, propValue);
         }
     }
-    _prependToTrajectories(images) {
-        for (let image of images.reverse()) {
-            if (!image.assetsCached) {
-                throw new ArgumentMapillaryError("Assets must be cached when added to trajectory");
+}
+
+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);
             }
-            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));
         }
+
+        return
     }
-    _imageToTranslation(image, reference) {
-        return computeTranslation({ alt: image.computedAltitude, lat: image.lngLat.lat, lng: image.lngLat.lng }, image.rotation, reference);
+
+    if(previousValue && isObject(previousValue) &&
+        getPrototype(previousValue) !== getPrototype(propValue)) {
+        node[propName] = propValue;
+        return
     }
-    _sameConnectedComponent() {
-        let current = this._currentImage;
-        let previous = this._previousImage;
-        return !!current && !!previous &&
-            current.mergeId === previous.mergeId;
+
+    if (!isObject(node[propName])) {
+        node[propName] = {};
     }
-    _withinOriginalDistance() {
-        let current = this._currentImage;
-        let previous = this._previousImage;
-        if (!current || !previous) {
-            return true;
+
+    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 document$2 = document_1;
+
+var applyProperties$1 = applyProperties_1;
+
+var isVNode$2 = isVnode;
+var isVText$1 = isVtext;
+var isWidget$4 = isWidget_1;
+var handleThunk = handleThunk_1;
+
+var createElement_1$1 = createElement$1;
+
+function createElement$1(vnode, opts) {
+    var doc = opts ? opts.document || document$2 : document$2;
+    var warn = opts ? opts.warn : null;
+
+    vnode = handleThunk(vnode).a;
+
+    if (isWidget$4(vnode)) {
+        return vnode.init()
+    } else if (isVText$1(vnode)) {
+        return doc.createTextNode(vnode.text)
+    } else if (!isVNode$2(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$1(children[i], opts);
+        if (childNode) {
+            node.appendChild(childNode);
         }
-        // 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;
     }
+
+    return node
 }
 
-class EulerRotationDelta {
-    constructor(phi, theta) {
-        this._phi = phi;
-        this._theta = theta;
+// 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$1;
+
+function domIndex$1(rootNode, tree, indices, nodes) {
+    if (!indices || indices.length === 0) {
+        return {}
+    } else {
+        indices.sort(ascending);
+        return recurse(rootNode, tree, indices, nodes, 0)
     }
-    get phi() {
-        return this._phi;
+}
+
+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;
+            }
+        }
     }
-    set phi(value) {
-        this._phi = value;
+
+    return nodes
+}
+
+// Binary search for an index in the interval [left, right]
+function indexInRange(indices, left, right) {
+    if (indices.length === 0) {
+        return false
     }
-    get theta() {
-        return this._theta;
+
+    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
+        }
     }
-    set theta(value) {
-        this._theta = value;
+
+    return false;
+}
+
+function ascending(a, b) {
+    return a > b ? 1 : -1
+}
+
+var isWidget$3 = isWidget_1;
+
+var updateWidget_1 = updateWidget$1;
+
+function updateWidget$1(a, b) {
+    if (isWidget$3(a) && isWidget$3(b)) {
+        if ("name" in a && "name" in b) {
+            return a.id === b.id
+        } else {
+            return a.init === b.init
+        }
     }
-    get isZero() {
-        return this._phi === 0 && this._theta === 0;
+
+    return false
+}
+
+var applyProperties = applyProperties_1;
+
+var isWidget$2 = isWidget_1;
+var VPatch = vpatch;
+
+var updateWidget = updateWidget_1;
+
+var patchOp$1 = applyPatch$1;
+
+function applyPatch$1(vpatch, domNode, renderOptions) {
+    var type = vpatch.type;
+    var vNode = vpatch.vNode;
+    var patch = vpatch.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(domNode, patch, vNode.properties);
+            return domNode
+        case VPatch.THUNK:
+            return replaceRoot(domNode,
+                renderOptions.patch(domNode, patch, renderOptions))
+        default:
+            return domNode
     }
-    copy(delta) {
-        this._phi = delta.phi;
-        this._theta = delta.theta;
+}
+
+function removeNode$1(domNode, vNode) {
+    var parentNode = domNode.parentNode;
+
+    if (parentNode) {
+        parentNode.removeChild(domNode);
     }
-    lerp(other, alpha) {
-        this._phi = (1 - alpha) * this._phi + alpha * other.phi;
-        this._theta = (1 - alpha) * this._theta + alpha * other.theta;
+
+    destroyWidget(domNode, vNode);
+
+    return null
+}
+
+function insertNode$1(parentNode, vNode, renderOptions) {
+    var newNode = renderOptions.render(vNode, renderOptions);
+
+    if (parentNode) {
+        parentNode.appendChild(newNode);
     }
-    multiply(value) {
-        this._phi *= value;
-        this._theta *= value;
+
+    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);
+        }
     }
-    threshold(value) {
-        this._phi = Math.abs(this._phi) > value ? this._phi : 0;
-        this._theta = Math.abs(this._theta) > value ? this._theta : 0;
+
+    return newNode
+}
+
+function widgetPatch(domNode, leftVNode, widget, renderOptions) {
+    var updating = updateWidget(leftVNode, widget);
+    var newNode;
+
+    if (updating) {
+        newNode = widget.update(leftVNode, domNode) || domNode;
+    } else {
+        newNode = renderOptions.render(widget, renderOptions);
     }
-    lengthSquared() {
-        return this._phi * this._phi + this._theta * this._theta;
+
+    var parentNode = domNode.parentNode;
+
+    if (parentNode && newNode !== domNode) {
+        parentNode.replaceChild(newNode, domNode);
     }
-    reset() {
-        this._phi = 0;
-        this._theta = 0;
+
+    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
 }
 
-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;
+function destroyWidget(domNode, w) {
+    if (typeof w.destroy === "function" && isWidget$2(w)) {
+        w.destroy(domNode);
     }
-    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);
+}
+
+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);
     }
-    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));
+
+    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]);
     }
-    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);
+}
+
+function replaceRoot(oldRoot, newRoot) {
+    if (oldRoot && newRoot && oldRoot !== newRoot && oldRoot.parentNode) {
+        oldRoot.parentNode.replaceChild(newRoot, oldRoot);
     }
-    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();
-        }
+
+    return newRoot;
+}
+
+var document$1 = document_1;
+var isArray$1 = xIsArray;
+
+var render = createElement_1$1;
+var domIndex = domIndex_1;
+var patchOp = patchOp$1;
+var patch_1$1 = patch$2;
+
+function patch$2(rootNode, patches, renderOptions) {
+    renderOptions = renderOptions || {};
+    renderOptions.patch = renderOptions.patch && renderOptions.patch !== patch$2
+        ? renderOptions.patch
+        : patchRecursive;
+    renderOptions.render = renderOptions.render || render;
+
+    return renderOptions.patch(rootNode, patches, renderOptions)
+}
+
+function patchRecursive(rootNode, patches, renderOptions) {
+    var indices = patchIndices(patches);
+
+    if (indices.length === 0) {
+        return rootNode
     }
-    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();
-        }
+
+    var index = domIndex(rootNode, patches.a, indices);
+    var ownerDocument = rootNode.ownerDocument;
+
+    if (!renderOptions.document && ownerDocument !== document$1) {
+        renderOptions.document = ownerDocument;
     }
-    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);
+
+    for (var i = 0; i < indices.length; i++) {
+        var nodeIndex = indices[i];
+        rootNode = applyPatch(rootNode,
+            index[nodeIndex],
+            patches[nodeIndex],
+            renderOptions);
     }
-    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);
+
+    return rootNode
+}
+
+function applyPatch(rootNode, domNode, patchList, renderOptions) {
+    if (!domNode) {
+        return rootNode
     }
-    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;
+
+    var newNode;
+
+    if (isArray$1(patchList)) {
+        for (var i = 0; i < patchList.length; i++) {
+            newNode = patchOp(patchList[i], domNode, renderOptions);
+
+            if (domNode === rootNode) {
+                rootNode = newNode;
             }
         }
-        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);
+    } else {
+        newNode = patchOp(patchList, domNode, renderOptions);
+
+        if (domNode === rootNode) {
+            rootNode = newNode;
         }
-        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);
+
+    return rootNode
+}
+
+function patchIndices(patches) {
+    var indices = [];
+
+    for (var key in patches) {
+        if (key !== "a") {
+            indices.push(Number(key));
         }
     }
-    _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);
+
+    return indices
+}
+
+var patch$1 = patch_1$1;
+
+var patch_1 = patch$1;
+
+var version$1 = version$5;
+var isVNode$1 = isVnode;
+var isWidget$1 = isWidget_1;
+var isThunk = isThunk_1;
+var isVHook = isVhook;
+
+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;
             }
-            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;
+
+    for (var i = 0; i < count; i++) {
+        var child = children[i];
+        if (isVNode$1(child)) {
+            descendants += child.count || 0;
+
+            if (!hasWidgets && child.hasWidgets) {
+                hasWidgets = true;
             }
-            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 (!hasThunks && child.hasThunks) {
+                hasThunks = true;
             }
-            if (Math.abs(reqY) > 0) {
-                this._basicRotation[1] = (1 - this._unboundedRotationAlpha) * this._basicRotation[1] + this._unboundedRotationAlpha * reqY;
+
+            if (!descendantHooks && (child.hooks || child.descendantHooks)) {
+                descendantHooks = true;
             }
-            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));
+        } else if (!hasWidgets && isWidget$1(child)) {
+            if (typeof child.destroy === "function") {
+                hasWidgets = true;
             }
-            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;
+        } else if (!hasThunks && isThunk(child)) {
+            hasThunks = true;
         }
-        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;
     }
+
+    this.count = count + descendants;
+    this.hasWidgets = hasWidgets;
+    this.hasThunks = hasThunks;
+    this.hooks = hooks;
+    this.descendantHooks = descendantHooks;
 }
 
-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;
+VirtualNode.prototype.version = version$1;
+VirtualNode.prototype.type = "VirtualNode";
+
+var version = version$5;
+
+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);
     }
-    append(images) {
-        let emptyTrajectory = this._trajectory.length === 0;
-        if (emptyTrajectory) {
-            this._resetTransition();
-        }
-        super.append(images);
-        if (emptyTrajectory) {
-            this._setDesiredCenter();
-            this._setDesiredZoom();
-        }
+    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);
     }
-    prepend(images) {
-        let emptyTrajectory = this._trajectory.length === 0;
-        if (emptyTrajectory) {
-            this._resetTransition();
+    /* 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;
+              }
+            }
+          });
         }
-        super.prepend(images);
-        if (emptyTrajectory) {
-            this._setDesiredCenter();
-            this._setDesiredZoom();
+        if (match.length > 1 && match.index < str.length) {
+          Array.prototype.push.apply(output, match.slice(1));
         }
-    }
-    set(images) {
-        super.set(images);
-        this._desiredLookat = null;
-        this._resetTransition();
-        this._clearRotation();
-        this._setDesiredCenter();
-        this._setDesiredZoom();
-        if (this._trajectory.length < 3) {
-            this._useBezier = true;
+        lastLength = match[0].length;
+        lastLastIndex = lastIndex;
+        if (output.length >= limit) {
+          break;
         }
+      }
+      if (separator.lastIndex === match.index) {
+        separator.lastIndex++; // Avoid an infinite loop
+      }
     }
-    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);
+    if (lastLastIndex === str.length) {
+      if (lastLength || !separator.test("")) {
+        output.push("");
+      }
+    } else {
+      output.push(str.slice(lastLastIndex));
     }
-    _getAlpha() {
-        return this._motionless ? Math.ceil(this._alpha) : this._alpha;
+    return output.length > limit ? output.slice(0, limit) : output;
+  };
+
+  return self;
+})();
+
+var split = browserSplit;
+
+var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/;
+var notClassId = /^\.|#/;
+
+var parseTag_1 = parseTag$1;
+
+function parseTag$1(tag, props) {
+    if (!tag) {
+        return 'DIV';
     }
-    _setCurrentCamera() {
-        super._setCurrentCamera();
-        this._adjustCameras();
+
+    var noId = !(props.hasOwnProperty('id'));
+
+    var tagParts = split(tag, classIdSplit);
+    var tagName = null;
+
+    if (notClassId.test(tagParts[1])) {
+        tagName = 'DIV';
     }
-    _adjustCameras() {
-        if (this._previousImage == null) {
-            return;
+
+    var classes, part, type, i;
+
+    for (i = 0; i < tagParts.length; i++) {
+        part = tagParts[i];
+
+        if (!part) {
+            continue;
         }
-        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));
+
+        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);
         }
     }
-    _resetTransition() {
-        this._alpha = 0;
-        this._baseAlpha = 0;
-        this._motionless = this._motionlessTransition();
+
+    if (classes) {
+        if (props.className) {
+            classes.push(props.className);
+        }
+
+        props.className = classes.join(' ');
     }
+
+    return props.namespace ? tagName : tagName.toUpperCase();
 }
 
-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}`);
-        }
+var softSetHook$1 = SoftSetHook;
+
+function SoftSetHook(value) {
+    if (!(this instanceof SoftSetHook)) {
+        return new SoftSetHook(value);
     }
+
+    this.value = value;
 }
-ComponentService.registeredComponents = {};
 
-var nativeIsArray = Array.isArray;
-var toString$2 = Object.prototype.toString;
+SoftSetHook.prototype.hook = function (node, propertyName) {
+    if (node[propertyName] !== this.value) {
+        node[propertyName] = this.value;
+    }
+};
 
-var xIsArray = nativeIsArray || isArray;
+/*global window, global*/
 
-function isArray(obj) {
-    return toString$2.call(obj) === "[object Array]"
-}
+var root = typeof window !== 'undefined' ?
+    window : typeof commonjsGlobal !== 'undefined' ?
+    commonjsGlobal : {};
 
-var version = "2";
+var individual = Individual$1;
 
-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;
+function Individual$1(key, value) {
+    if (key in root) {
+        return root[key];
+    }
 
-var vpatch = VirtualPatch;
+    root[key] = value;
 
-function VirtualPatch(type, vNode, patch) {
-    this.type = Number(type);
-    this.vNode = vNode;
-    this.patch = patch;
+    return value;
 }
 
-VirtualPatch.prototype.version = version;
-VirtualPatch.prototype.type = "VirtualPatch";
-
-var isVnode = isVirtualNode;
+var Individual = individual;
 
-function isVirtualNode(x) {
-    return x && x.type === "VirtualNode" && x.version === version
-}
+var oneVersion = OneVersion;
 
-var isVtext = isVirtualText;
+function OneVersion(moduleName, version, defaultValue) {
+    var key = '__INDIVIDUAL_ONE_VERSION_' + moduleName;
+    var enforceKey = key + '_ENFORCE_SINGLETON';
 
-function isVirtualText(x) {
-    return x && x.type === "VirtualText" && x.version === version
-}
+    var versionValue = Individual(enforceKey, version);
 
-var isWidget_1 = isWidget;
+    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);
+    }
 
-function isWidget(w) {
-    return w && w.type === "Widget"
+    return Individual(key, defaultValue);
 }
 
-var isThunk_1 = isThunk;
+var OneVersionConstraint = oneVersion;
 
-function isThunk(t) {
-    return t && t.type === "Thunk"
-}
+var MY_VERSION = '7';
+OneVersionConstraint('ev-store', MY_VERSION);
 
-var handleThunk_1 = handleThunk;
+var hashKey = '__EV_STORE_KEY@' + MY_VERSION;
 
-function handleThunk(a, b) {
-    var renderedA = a;
-    var renderedB = b;
+var evStore = EvStore$1;
 
-    if (isThunk_1(b)) {
-        renderedB = renderThunk(b, a);
-    }
+function EvStore$1(elem) {
+    var hash = elem[hashKey];
 
-    if (isThunk_1(a)) {
-        renderedA = renderThunk(a, null);
+    if (!hash) {
+        hash = elem[hashKey] = {};
     }
 
-    return {
-        a: renderedA,
-        b: renderedB
-    }
+    return hash;
 }
 
-function renderThunk(thunk, previous) {
-    var renderedThunk = thunk.vnode;
+var EvStore = evStore;
 
-    if (!renderedThunk) {
-        renderedThunk = thunk.vnode = thunk.render(previous);
-    }
+var evHook$1 = EvHook;
 
-    if (!(isVnode(renderedThunk) ||
-            isVtext(renderedThunk) ||
-            isWidget_1(renderedThunk))) {
-        throw new Error("thunk did not return a valid node");
+function EvHook(value) {
+    if (!(this instanceof EvHook)) {
+        return new EvHook(value);
     }
 
-    return renderedThunk
+    this.value = value;
 }
 
-var isObject = function isObject(x) {
-       return typeof x === 'object' && x !== null;
+EvHook.prototype.hook = function (node, propertyName) {
+    var es = EvStore(node);
+    var propName = propertyName.substr(3);
+
+    es[propName] = this.value;
 };
 
-var isVhook = isHook;
+EvHook.prototype.unhook = function(node, propertyName) {
+    var es = EvStore(node);
+    var propName = propertyName.substr(3);
 
-function isHook(hook) {
-    return hook &&
-      (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") ||
-       typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook"))
-}
+    es[propName] = undefined;
+};
 
-var diffProps_1 = diffProps;
+var isArray = xIsArray;
 
-function diffProps(a, b) {
-    var diff;
+var VNode$1 = vnode;
+var VText$1 = vtext;
+var isVNode = isVnode;
+var isVText = isVtext;
+var isWidget = isWidget_1;
+var isHook = isVhook;
+var isVThunk = isThunk_1;
 
-    for (var aKey in a) {
-        if (!(aKey in b)) {
-            diff = diff || {};
-            diff[aKey] = undefined;
-        }
+var parseTag = parseTag_1;
+var softSetHook = softSetHook$1;
+var evHook = evHook$1;
 
-        var aValue = a[aKey];
-        var bValue = b[aKey];
+var virtualHyperscript = h$2;
 
-        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;
-        }
-    }
+function h$2(tagName, properties, children) {
+    var childNodes = [];
+    var tag, props, key, namespace;
 
-    for (var bKey in b) {
-        if (!(bKey in a)) {
-            diff = diff || {};
-            diff[bKey] = b[bKey];
-        }
+    if (!children && isChildren(properties)) {
+        children = properties;
+        props = {};
     }
 
-    return diff
-}
+    props = props || properties || {};
+    tag = parseTag(tagName, props);
 
-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
-  }
-}
+    // support keys
+    if (props.hasOwnProperty('key')) {
+        key = props.key;
+        props.key = undefined;
+    }
 
-var diff_1$1 = diff;
+    // support namespace
+    if (props.hasOwnProperty('namespace')) {
+        namespace = props.namespace;
+        props.namespace = undefined;
+    }
 
-function diff(a, b) {
-    var patch = { a: a };
-    walk(a, b, patch, 0);
-    return patch
-}
+    // fix cursor bug
+    if (tag === 'INPUT' &&
+        !namespace &&
+        props.hasOwnProperty('value') &&
+        props.value !== undefined &&
+        !isHook(props.value)
+    ) {
+        props.value = softSetHook(props.value);
+    }
 
-function walk(a, b, patch, index) {
-    if (a === b) {
-        return
+    transformProperties(props);
+
+    if (children !== undefined && children !== null) {
+        addChild(children, childNodes, tag, props);
     }
 
-    var apply = patch[index];
-    var applyClear = false;
 
-    if (isThunk_1(a) || isThunk_1(b)) {
-        thunks(a, b, patch, index);
-    } else if (b == null) {
+    return new VNode$1(tag, props, childNodes, key, namespace);
+}
 
-        // 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];
+function addChild(c, childNodes, tag, props) {
+    if (typeof c === 'string') {
+        childNodes.push(new VText$1(c));
+    } else if (typeof c === 'number') {
+        childNodes.push(new VText$1(String(c)));
+    } else if (isChild(c)) {
+        childNodes.push(c);
+    } else if (isArray(c)) {
+        for (var i = 0; i < c.length; i++) {
+            addChild(c[i], childNodes, tag, props);
         }
-
-        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 if (c === null || c === undefined) {
+        return;
+    } else {
+        throw UnexpectedVirtualElement({
+            foreignObject: c,
+            parentVnode: {
+                tagName: tag,
+                properties: props
             }
-        } 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;
+function transformProperties(props) {
+    for (var propName in props) {
+        if (props.hasOwnProperty(propName)) {
+            var value = props[propName];
 
-        if (!leftNode) {
-            if (rightNode) {
-                // Excess nodes in b need to be added
-                apply = appendPatch(apply,
-                    new vpatch(vpatch.INSERT, null, rightNode));
+            if (isHook(value)) {
+                continue;
             }
-        } else {
-            walk(leftNode, rightNode, patch, index);
-        }
 
-        if (isVnode(leftNode) && leftNode.count) {
-            index += leftNode.count;
+            if (propName.substr(0, 3) === 'ev-') {
+                // add ev-foo support
+                props[propName] = evHook(value);
+            }
         }
     }
+}
 
-    if (orderedSet.moves) {
-        // Reorder nodes last
-        apply = appendPatch(apply, new vpatch(
-            vpatch.ORDER,
-            a,
-            orderedSet.moves
-        ));
-    }
-
-    return apply
+function isChild(x) {
+    return isVNode(x) || isVText(x) || isWidget(x) || isVThunk(x);
 }
 
-function clearState(vNode, patch, index) {
-    // TODO: Make this a single walk, not two
-    unhook(vNode, patch, index);
-    destroyWidgets(vNode, patch, index);
+function isChildren(x) {
+    return typeof x === 'string' || isArray(x) || isChild(x);
 }
 
-// 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;
+function UnexpectedVirtualElement(data) {
+    var err = new Error();
 
-            destroyWidgets(child, patch, index);
+    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;
 
-            if (isVnode(child) && child.count) {
-                index += child.count;
-            }
-        }
-    } else if (isThunk_1(vNode)) {
-        thunks(vNode, null, patch, index);
-    }
+    return err;
 }
 
-// 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 errorString(obj) {
+    try {
+        return JSON.stringify(obj, null, '    ');
+    } catch (e) {
+        return String(obj);
     }
 }
 
-function hasPatches(patch) {
-    for (var index in patch) {
-        if (index !== "a") {
-            return true
-        }
-    }
+var h$1 = virtualHyperscript;
 
-    return false
-}
+var h_1 = h$1;
 
-// 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)
-                )
-            );
-        }
+var createElement = createElement_1$1;
 
-        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;
+var createElement_1 = createElement;
 
-                unhook(child, patch, index);
+var diff = diff_1;
+var patch = patch_1;
+var h = h_1;
+var create = createElement_1;
+var VNode = vnode;
+var VText = vtext;
 
-                if (isVnode(child) && child.count) {
-                    index += child.count;
-                }
+var virtualDom = {
+    diff: diff,
+    patch: patch,
+    h: h,
+    create: create,
+    VNode: VNode,
+    VText: VText
+};
+
+class EventEmitter {
+    constructor() { this._events = {}; }
+    /**
+     * @ignore
+     */
+    fire(type, event) {
+        if (!this._listens(type)) {
+            return;
+        }
+        for (const handler of this._events[type]) {
+            handler(event);
+        }
+    }
+    /**
+     * 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];
             }
         }
-    } else if (isThunk_1(vNode)) {
-        thunks(vNode, null, patch, index);
+    }
+    /**
+     * 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);
+    }
+    _listens(eventType) {
+        return eventType in this._events;
     }
 }
 
-function undefinedKeys(obj) {
-    var result = {};
-
-    for (var key in obj) {
-        result[key] = undefined;
+class SubscriptionHolder {
+    constructor() {
+        this._subscriptions = [];
+    }
+    push(subscription) {
+        this._subscriptions.push(subscription);
+    }
+    unsubscribe() {
+        for (const sub of this._subscriptions) {
+            sub.unsubscribe();
+        }
+        this._subscriptions = [];
     }
-
-    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
-        }
+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(() => { });
     }
-
-    // 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
-        }
+    /**
+     * Get activated.
+     *
+     * @returns {boolean} Value indicating if the component is
+     * currently active.
+     */
+    get activated() {
+        return this._activated;
     }
-
-    // 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);
-            }
-        }
+    /** @ignore */
+    get activated$() {
+        return this._activated$;
     }
-
-    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);
-        }
+    /**
+     * Get default configuration.
+     *
+     * @returns {TConfiguration} Default configuration for component.
+     */
+    get defaultConfiguration() {
+        return this._getDefaultConfiguration();
     }
-
-    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));
-            }
+    /** @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;
         }
-        else {
-            simulateIndex++;
-            k++;
+        if (conf !== undefined) {
+            this._configurationSubject$.next(conf);
         }
+        this._activated = true;
+        this._activate();
+        this._activated$.next(true);
     }
-
-    // remove all the remaining nodes from simulate
-    while(simulateIndex < simulate.length) {
-        simulateItem = simulate[simulateIndex];
-        removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key));
+    /**
+     * Configure the component.
+     *
+     * @param configuration Component configuration.
+     */
+    configure(configuration) {
+        this._configurationSubject$.next(configuration);
     }
-
-    // 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
+    /** @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);
     }
-
-    return {
-        children: newChildren,
-        moves: {
-            removes: removes,
-            inserts: inserts
-        }
+    /** @inheritdoc */
+    fire(type, event) {
+        super.fire(type, event);
     }
-}
-
-function remove(arr, index, key) {
-    arr.splice(index, 1);
-
-    return {
-        from: index,
-        key: key
+    /** @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; }
 }
 
-function keyIndex(children) {
-    var keys = {};
-    var free = [];
-    var length = children.length;
-
-    for (var i = 0; i < length; i++) {
-        var child = children[i];
+var CoverState;
+(function (CoverState) {
+    CoverState[CoverState["Hidden"] = 0] = "Hidden";
+    CoverState[CoverState["Loading"] = 1] = "Loading";
+    CoverState[CoverState["Visible"] = 2] = "Visible";
+})(CoverState || (CoverState = {}));
 
-        if (child.key) {
-            keys[child.key] = i;
-        } else {
-            free.push(i);
-        }
+class CoverComponent extends Component {
+    constructor(name, container, navigator) {
+        super(name, container, navigator);
     }
-
-    return {
-        keys: keys,     // A hash of key name to index
-        free: free      // An array of unkeyed item indices
+    _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$));
     }
-}
-
-function appendPatch(apply, patch) {
-    if (apply) {
-        if (xIsArray(apply)) {
-            apply.push(patch);
-        } else {
-            apply = [apply, patch];
-        }
-
-        return apply
-    } else {
-        return patch
+    _deactivate() {
+        this._subscriptions.unsubscribe();
     }
-}
-
-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];
+    _getDefaultConfiguration() {
+        return { state: CoverState.Visible };
     }
-    
-    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);
+    _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);
     }
-}
-
-var domComment = Comment;
-
-function Comment(data, owner) {
-    if (!(this instanceof Comment)) {
-        return new Comment(data, owner)
+    _getImageSrc$(id) {
+        return Observable.create((subscriber) => {
+            this._navigator.api.getImages$([id])
+                .subscribe((items) => {
+                for (const item of items) {
+                    const imageId = typeof id === "number" ?
+                        id.toString() : id;
+                    if (item.node_id !== imageId) {
+                        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);
+            });
+        });
     }
-
-    this.data = data;
-    this.nodeValue = data;
-    this.length = data.length;
-    this.ownerDocument = owner || null;
 }
+CoverComponent.componentName = "cover";
 
-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)
+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$));
     }
-
-    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;
+    _deactivate() {
+        this._subscriptions.unsubscribe();
     }
-
-    if (!elem.listeners) {
-        elem.listeners = {};
+    _getDefaultConfiguration() {
+        return {};
     }
-
-    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);
-            }
-        })
+    makeImageUrl(exploreUrl, id) {
+        return `${exploreUrl}/app/?pKey=${id}&focus=photo`;
     }
-
-    if (elem.parentNode) {
-        elem.parentNode.dispatchEvent(ev);
+    _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]);
     }
-}
-
-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);
+    _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);
     }
-}
-
-var removeEventListener_1 = removeEventListener;
-
-function removeEventListener(type, listener) {
-    var elem = this;
-
-    if (!elem.listeners) {
-        return
+    _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];
     }
-
-    if (!elem.listeners[type]) {
-        return
+    _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];
     }
-
-    var list = elem.listeners[type];
-    var index = list.indexOf(listener);
-    if (index !== -1) {
-        list.splice(index, 1);
+    _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";
 
-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)
+/**
+ * @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;
     }
-}
-
-function serializeElement(elem) {
-    var strings = [];
-
-    var tagname = elem.tagName;
-
-    if (elem.namespaceURI === "http://www.w3.org/1999/xhtml") {
-        tagname = tagname.toLowerCase();
+    /**
+     * 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;
     }
-
-    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);
+    /**
+     * 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;
         }
-
-        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
+        const canvas = this.viewportToCanvas(viewport[0], viewport[1], container);
+        return canvas;
     }
-
-    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] });
+    /**
+     * 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;
     }
-
-    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] });
+    /**
+     * 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;
     }
-
-    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 });
+    /**
+     * 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];
     }
-
-    return props.length ? stringify(props) : ""
-}
-
-function escapeText(s) {
-    var str = '';
-
-    if (typeof(s) === 'string') { 
-        str = s; 
-    } else if (s) {
-        str = s.toString();
+    /**
+     * 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];
     }
-
-    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)
+    /**
+     * 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;
     }
-
-    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';
+    /**
+     * 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];
     }
-}
-
-DOMElement.prototype.type = "DOMElement";
-DOMElement.prototype.nodeType = 1;
-
-DOMElement.prototype.appendChild = function _Element_appendChild(child) {
-    if (child.parentNode) {
-        child.parentNode.removeChild(child);
+    /**
+     * 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];
     }
-
-    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);
+    /**
+     * 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];
         }
-
-        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);
+        if (topRightBasic[0] > 1 && bottomRightBasic[0] > 1) {
+            rightBasicDistance = topRightBasic[0] < bottomRightBasic[0] ?
+                topRightBasic[0] - 1 :
+                bottomRightBasic[0] - 1;
         }
-
-        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);
+        if (bottomRightBasic[1] > 1 && bottomLeftBasic[1] > 1) {
+            bottomBasicDistance = bottomRightBasic[1] < bottomLeftBasic[1] ?
+                bottomRightBasic[1] - 1 :
+                bottomLeftBasic[1] - 1;
         }
-
-        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 (bottomLeftBasic[0] < 0 && topLeftBasic[0] < 0) {
+            leftBasicDistance = bottomLeftBasic[0] > topLeftBasic[0] ?
+                -bottomLeftBasic[0] :
+                -topLeftBasic[0];
         }
-        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;
+        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] ?
@@ -56145,7 +52742,6 @@ class BearingComponent extends Component {
         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;
@@ -56196,7 +52792,7 @@ class BearingComponent extends Component {
         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 alpha = MathUtils.smootherstep(state.alpha, 0, 1);
             const curr = state.curr;
             const prev = state.prev;
             return [
@@ -56206,7 +52802,7 @@ class BearingComponent extends Component {
         }));
         subs.push(imageFov$.pipe(map((nbf) => {
             return (state) => {
-                const a = this._unitBezier.solve(state.alpha);
+                const a = MathUtils.smootherstep(state.alpha, 0, 1);
                 const c = state.curr;
                 const p = state.prev;
                 const prev = [
@@ -61819,123 +58415,709 @@ class SequenceComponent extends Component {
                 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();
+                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 = {}));
+
+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 {
-                this._navigator.playService.stop();
+                return [
+                    bearing[0] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+                    bearing[1] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+                ];
             }
-        }));
-        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;
-                    }
+        }
+        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;
                 }
-                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();
+                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,
+                ];
+            }
+        }
     }
-    _getDefaultConfiguration() {
-        return {
-            direction: NavigationDirection.Next,
-            maxWidth: 108,
-            minWidth: 70,
-            playing: false,
-            visible: true,
-        };
+    /**
+     * 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];
     }
-}
-/** @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.
+     * Convert SfM coordinates to basic coordinates.
      *
-     * @description The slider component moves the
-     * camera between the image origins.
+     * @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.
      *
-     * In this mode it is not possible to zoom or pan.
+     * @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 ].
      *
-     * The slider component falls back to stationary
-     * mode when it determines that the pair of images
-     * does not have a strong enough relation.
+     * @param {Array<number>} rotation - Rotation vector in angle axis representation.
+     * @param {Array<number>} translation - Translation vector.
+     * @returns {THREE.Matrix4} Extrisic camera matrix.
      */
-    SliderConfigurationMode[SliderConfigurationMode["Motion"] = 0] = "Motion";
+    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;
+    }
     /**
-     * Stationary transitions.
+     * Calculates the scaled extrinsic camera matrix scale * [ R | t ].
      *
-     * @description The camera is stationary.
+     * @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.
      *
-     * In this mode it is possible to zoom and pan.
+     * @returns {THREE.Matrix4} Normalized coordinates to texture map
+     * coordinates transformation matrix.
      */
-    SliderConfigurationMode[SliderConfigurationMode["Stationary"] = 1] = "Stationary";
-})(SliderConfigurationMode || (SliderConfigurationMode = {}));
+    _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 SliderGLRenderer {
     constructor() {
@@ -63115,13 +60297,10 @@ class ClusterPoints extends Points {
     constructor(parameters) {
         super();
         this._originalSize = parameters.originalSize;
-        const cluster = parameters.cluster;
-        const scale = parameters.scale;
-        const translation = parameters.translation;
+        const { cluster, color, scale, translation } = parameters;
         this._makeAttributes(cluster);
         this.material.size = scale * this._originalSize;
-        this.material.vertexColors = true;
-        this.material.needsUpdate = true;
+        this.setColor(color);
         this.matrixAutoUpdate = false;
         this.position.fromArray(translation);
         this.updateMatrix();
@@ -63131,6 +60310,11 @@ class ClusterPoints extends Points {
         this.geometry.dispose();
         this.material.dispose();
     }
+    setColor(color) {
+        this.material.vertexColors = color == null;
+        this.material.color = new Color(color);
+        this.material.needsUpdate = true;
+    }
     resize(scale) {
         this.material.size = scale * this._originalSize;
         this.material.needsUpdate = true;
@@ -63583,12 +60767,11 @@ class PerspectiveCameraFrame extends CameraFrameBase {
         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);
+            const corner = transform.unprojectBasic(vertex2d, size);
             corner[0] -= originX;
             corner[1] -= originY;
             corner[2] -= originZ;
@@ -63601,11 +60784,10 @@ class PerspectiveCameraFrame extends CameraFrameBase {
         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);
+            const position = transform.unprojectBasic(vertex2d, size);
             position[0] -= originX;
             position[1] -= originY;
             position[2] -= originZ;
@@ -63635,6 +60817,12 @@ class PerspectiveCameraFrame extends CameraFrameBase {
     }
 }
 
+function resetEnu(reference, prevEnu, prevReference) {
+    const [prevX, prevY, prevZ] = prevEnu;
+    const [lng, lat, alt] = enuToGeodetic(prevX, prevY, prevZ, prevReference.lng, prevReference.lat, prevReference.alt);
+    return geodeticToEnu(lng, lat, alt, reference.lng, reference.lat, reference.alt);
+}
+
 class SpatialCell {
     constructor(id, _scene, _intersection) {
         this.id = id;
@@ -63760,6 +60948,24 @@ class SpatialCell {
     hasImage(key) {
         return this.keys.indexOf(key) !== -1;
     }
+    resetReference(reference, prevReference) {
+        const frames = this._cameraFrames;
+        for (const frameId in frames) {
+            if (!frames.hasOwnProperty(frameId)) {
+                continue;
+            }
+            const frame = frames[frameId];
+            frame.position.fromArray(resetEnu(reference, frame.position.toArray(), prevReference));
+        }
+        const lines = this._positionLines;
+        for (const lineId in lines) {
+            if (!lines.hasOwnProperty(lineId)) {
+                continue;
+            }
+            const line = lines[lineId];
+            line.position.fromArray(resetEnu(reference, line.position.toArray(), prevReference));
+        }
+    }
     visualize(props) {
         var _a, _b;
         const id = props.id;
@@ -63851,6 +61057,23 @@ function isOverviewState(state) {
     return state === State.Custom || state === State.Earth;
 }
 
+var PointVisualizationMode;
+(function (PointVisualizationMode) {
+    /**
+     * Points are hidden.
+     */
+    PointVisualizationMode[PointVisualizationMode["Hidden"] = 0] = "Hidden";
+    /**
+     * Visualize points with original colors.
+     */
+    PointVisualizationMode[PointVisualizationMode["Original"] = 1] = "Original";
+    /**
+     * Paint all points belonging to a specific
+     * cluster with the same random color.
+     */
+    PointVisualizationMode[PointVisualizationMode["Cluster"] = 2] = "Cluster";
+})(PointVisualizationMode || (PointVisualizationMode = {}));
+
 const NO_CLUSTER_ID = "NO_CLUSTER_ID";
 const NO_MERGE_ID = "NO_MERGE_ID";
 const NO_SEQUENCE_ID = "NO_SEQUENCE_ID";
@@ -63875,7 +61098,10 @@ class SpatialScene {
                 CameraVisualizationMode.Homogeneous;
         this._cameraSize = configuration.cameraSize;
         this._pointSize = configuration.pointSize;
-        this._pointsVisible = configuration.pointsVisible;
+        this._pointVisualizationMode =
+            !!configuration.pointVisualizationMode ?
+                configuration.pointVisualizationMode :
+                PointVisualizationMode.Original;
         this._positionMode = configuration.originalPositionMode;
         this._cellsVisible = configuration.cellsVisible;
         this._hoveredId = null;
@@ -63898,14 +61124,18 @@ class SpatialScene {
                 cellIds: [],
             };
             const visible = this._getClusterVisible(clusterId);
-            this._clusters[clusterId].points.visible = visible;
-            this._clusters[clusterId].points.add(new ClusterPoints({
+            const cluster = this._clusters[clusterId];
+            const color = this._pointVisualizationMode === PointVisualizationMode.Cluster ? this._assets.getColor(clusterId) : null;
+            const points = new ClusterPoints({
                 cluster: reconstruction,
+                color,
                 originalSize: this._originalPointSize,
                 scale: this._pointSize,
                 translation,
-            }));
-            this._scene.add(this._clusters[clusterId].points);
+            });
+            cluster.points.visible = visible;
+            cluster.points.add(points);
+            this._scene.add(cluster.points);
         }
         if (this._clusters[clusterId].cellIds.indexOf(cellId) === -1) {
             this._clusters[clusterId].cellIds.push(cellId);
@@ -63989,6 +61219,36 @@ class SpatialScene {
         return cellId in this._images &&
             this._images[cellId].hasImage(imageId);
     }
+    render(camera, renderer) {
+        renderer.render(this._scene, camera);
+        this._needsRender = false;
+    }
+    resetReference(reference, prevReference) {
+        const clusters = this._clusters;
+        for (const clusterId in clusters) {
+            if (!clusters.hasOwnProperty(clusterId)) {
+                continue;
+            }
+            const cluster = clusters[clusterId];
+            cluster.points.position.fromArray(resetEnu(reference, cluster.points.position.toArray(), prevReference));
+        }
+        const cells = this._cells;
+        for (const cellId in cells) {
+            if (!cells.hasOwnProperty(cellId)) {
+                continue;
+            }
+            const cell = cells[cellId];
+            cell.position.fromArray(resetEnu(reference, cell.position.toArray(), prevReference));
+        }
+        const images = this._images;
+        for (const cellId in images) {
+            if (!images.hasOwnProperty(cellId)) {
+                continue;
+            }
+            const spatialCell = images[cellId];
+            spatialCell.resetReference(reference, prevReference);
+        }
+    }
     setCameraSize(cameraSize) {
         if (Math.abs(cameraSize - this._cameraSize) < 1e-3) {
             return;
@@ -64017,7 +61277,7 @@ class SpatialScene {
                 clusterVisibles[clusterId] || (clusterVisibles[clusterId] = imageCV[clusterId]);
             }
         }
-        const pointsVisible = this._pointsVisible;
+        const pointsVisible = this._pointVisualizationMode !== PointVisualizationMode.Hidden;
         for (const clusterId in clusterVisibles) {
             if (!clusterVisibles.hasOwnProperty(clusterId)) {
                 continue;
@@ -64068,17 +61328,23 @@ class SpatialScene {
         this._pointSize = pointSize;
         this._needsRender = true;
     }
-    setPointVisibility(visible) {
-        if (visible === this._pointsVisible) {
+    setPointVisualizationMode(mode) {
+        if (mode === this._pointVisualizationMode) {
             return;
         }
+        this._pointVisualizationMode = mode;
         for (const clusterId in this._clusters) {
             if (!this._clusters.hasOwnProperty(clusterId)) {
                 continue;
             }
-            this._clusters[clusterId].points.visible = visible;
+            const cluster = this._clusters[clusterId];
+            cluster.points.visible = this._getClusterVisible(clusterId);
+            for (const points of cluster.points.children) {
+                const color = mode === PointVisualizationMode.Cluster ?
+                    this._assets.getColor(clusterId) : null;
+                points.setColor(color);
+            }
         }
-        this._pointsVisible = visible;
         this._needsRender = true;
     }
     setPositionMode(mode) {
@@ -64136,10 +61402,6 @@ class SpatialScene {
         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) {
@@ -64168,7 +61430,7 @@ class SpatialScene {
         this._needsRender = true;
     }
     _getClusterVisible(clusterId) {
-        if (!this._pointsVisible) {
+        if (this._pointVisualizationMode === PointVisualizationMode.Hidden) {
             return false;
         }
         let visible = false;
@@ -64557,8 +61819,9 @@ class SpatialComponent extends Component {
         this._navigator.cacheService.configure({ cellDepth: 3 });
         const subs = this._subscriptions;
         subs.push(this._navigator.stateService.reference$
-            .subscribe(() => {
-            this._scene.uncache();
+            .pipe(pairwise())
+            .subscribe(([prevReference, reference]) => {
+            this._scene.resetReference(reference, prevReference);
         }));
         subs.push(this._navigator.graphService.filter$
             .subscribe(imageFilter => { this._scene.setFilter(imageFilter); }));
@@ -64653,15 +61916,19 @@ class SpatialComponent extends Component {
             this._scene.addCluster(reconstruction, this._computeTranslation(reconstruction, reference), cellId);
         }));
         subs.push(this._configuration$.pipe(map((c) => {
+            var _a;
             c.cameraSize = this._spatial.clamp(c.cameraSize, 0.01, 1);
             c.pointSize = this._spatial.clamp(c.pointSize, 0.01, 1);
+            const pointVisualizationMode = c.pointsVisible ?
+                (_a = c.pointVisualizationMode) !== null && _a !== void 0 ? _a : PointVisualizationMode.Original :
+                PointVisualizationMode.Hidden;
             return {
                 cameraSize: c.cameraSize,
                 cameraVisualizationMode: c.cameraVisualizationMode,
                 cellsVisible: c.cellsVisible,
                 originalPositionMode: c.originalPositionMode,
                 pointSize: c.pointSize,
-                pointsVisible: c.pointsVisible,
+                pointVisualizationMode,
             };
         }), distinctUntilChanged((c1, c2) => {
             return c1.cameraSize === c2.cameraSize &&
@@ -64669,15 +61936,16 @@ class SpatialComponent extends Component {
                 c1.cellsVisible === c2.cellsVisible &&
                 c1.originalPositionMode === c2.originalPositionMode &&
                 c1.pointSize === c2.pointSize &&
-                c1.pointsVisible === c2.pointsVisible;
+                c1.pointVisualizationMode === c2.pointVisualizationMode;
         }))
             .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);
+            this._scene.setCellVisibility(c.cellsVisible);
+            this._scene.setPointSize(c.pointSize);
+            const pvm = c.pointVisualizationMode;
+            this._scene.setPointVisualizationMode(pvm);
             const opm = c.originalPositionMode;
             this._scene.setPositionMode(opm);
         }));
@@ -64806,6 +62074,7 @@ class SpatialComponent extends Component {
             originalPositionMode: OriginalPositionMode.Hidden,
             pointSize: 0.1,
             pointsVisible: true,
+            pointVisualizationMode: PointVisualizationMode.Original,
             cellsVisible: false,
         };
     }
@@ -65162,8 +62431,10 @@ class CreateTag {
     }
 }
 
-var earcut_1 = earcut;
-var _default$2 = earcut;
+var earcut$2 = {exports: {}};
+
+earcut$2.exports = earcut;
+earcut$2.exports.default = earcut;
 
 function earcut(data, holeIndices, dim) {
 
@@ -65439,7 +62710,7 @@ function eliminateHoles(data, holeIndices, outerNode, dim) {
 
     // process holes from left to right
     for (i = 0; i < queue.length; i++) {
-        eliminateHole(queue[i], outerNode);
+        outerNode = eliminateHole(queue[i], outerNode);
         outerNode = filterPoints(outerNode, outerNode.next);
     }
 
@@ -65452,14 +62723,19 @@ function compareX(a, b) {
 
 // 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);
+    var bridge = findHoleBridge(hole, outerNode);
+    if (!bridge) {
+        return outerNode;
     }
+
+    var bridgeReverse = splitPolygon(bridge, hole);
+
+    // filter collinear points around the cuts
+    var filteredBridge = filterPoints(bridge, bridge.next);
+    filterPoints(bridgeReverse, bridgeReverse.next);
+
+    // Check if input node was removed by the filtering
+    return outerNode === bridge ? filteredBridge : outerNode;
 }
 
 // David Eberly's algorithm for finding a bridge between hole and outer polygon
@@ -65839,7 +63115,10 @@ earcut.flatten = function (data) {
     }
     return result;
 };
-earcut_1.default = _default$2;
+
+var earcut$1 = earcut$2.exports;
+
+var polylabel$2 = {exports: {}};
 
 class TinyQueue$1 {
     constructor(data = [], compare = defaultCompare$1) {
@@ -65927,12 +63206,12 @@ var tinyqueue$1 = /*#__PURE__*/Object.freeze({
 
 var require$$0 = /*@__PURE__*/getAugmentedNamespace(tinyqueue$1);
 
-var Queue = require$$0;
+var Queue$1 = require$$0;
 
-if (Queue.default) Queue = Queue.default; // temporary webpack fix
+if (Queue$1.default) Queue$1 = Queue$1.default; // temporary webpack fix
 
-var polylabel_1 = polylabel;
-var _default$1 = polylabel;
+polylabel$2.exports = polylabel;
+polylabel$2.exports.default = polylabel;
 
 function polylabel(polygon, precision, debug) {
     precision = precision || 1.0;
@@ -65959,7 +63238,7 @@ function polylabel(polygon, precision, debug) {
     }
 
     // a priority queue of cells in order of their "potential" (max distance to polygon)
-    var cellQueue = new Queue(undefined, compareMax);
+    var cellQueue = new Queue$1(undefined, compareMax);
 
     // cover polygon with initial cells
     for (var x = minX; x < maxX; x += cellSize) {
@@ -66089,7 +63368,8 @@ function getSegDistSq(px, py, a, b) {
 
     return dx * dx + dy * dy;
 }
-polylabel_1.default = _default$1;
+
+var polylabel$1 = polylabel$2.exports;
 
 function DEFAULT_COMPARE (a, b) { return a > b ? 1 : a < b ? -1 : 0; }
 
@@ -67886,8 +65166,10 @@ function connectEdges(sortedEvents) {
   return contours;
 }
 
-var tinyqueue = TinyQueue;
-var _default = TinyQueue;
+var tinyqueue = {exports: {}};
+
+tinyqueue.exports = TinyQueue;
+tinyqueue.exports.default = TinyQueue;
 
 function TinyQueue(data, compare) {
     if (!(this instanceof TinyQueue)) return new TinyQueue(data, compare);
@@ -67972,7 +65254,8 @@ TinyQueue.prototype = {
         data[pos] = item;
     }
 };
-tinyqueue.default = _default;
+
+var Queue = tinyqueue.exports;
 
 const max = Math.max;
 const min = Math.min;
@@ -68019,7 +65302,7 @@ function processPolygon(contourOrHole, isSubject, depth, Q, bbox, isExteriorRing
 
 
 function fillQueue(subject, clipping, sbbox, cbbox, operation) {
-  const eventQueue = new tinyqueue(null, compareEvents);
+  const eventQueue = new Queue(null, compareEvents);
   let polygonSet, isExteriorRing, i, ii, j, jj; //, k, kk;
 
   for (i = 0, ii = subject.length; i < ii; i++) {
@@ -68160,7 +65443,7 @@ class VertexGeometry extends Geometry {
      * @ignore
      */
     _getPoleOfInaccessibility2d(points2d) {
-        let pole2d = polylabel_1([points2d], 3e-2);
+        let pole2d = polylabel$1([points2d], 3e-2);
         return pole2d;
     }
     _project(points2d, transform) {
@@ -68208,8 +65491,8 @@ class VertexGeometry extends Geometry {
         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 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]];
@@ -72363,7 +69646,7 @@ class NavigationFallbackComponent extends Component {
 }
 NavigationFallbackComponent.componentName = "navigationfallback";
 
-/*! pako 2.0.3 https://github.com/nodeca/pako @license (MIT AND Zlib) */
+/*! pako 2.0.4 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
 //
@@ -72392,19 +69675,19 @@ NavigationFallbackComponent.componentName = "navigationfallback";
 //const Z_FILTERED          = 1;
 //const Z_HUFFMAN_ONLY      = 2;
 //const Z_RLE               = 3;
-const Z_FIXED               = 4;
+const Z_FIXED$1               = 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;
+const Z_UNKNOWN$1             = 2;
 
 /*============================================================================*/
 
 
-function zero(buf) { let len = buf.length; while (--len >= 0) { buf[len] = 0; } }
+function zero$1(buf) { let len = buf.length; while (--len >= 0) { buf[len] = 0; } }
 
 // From zutil.h
 
@@ -72413,8 +69696,8 @@ const STATIC_TREES = 1;
 const DYN_TREES    = 2;
 /* The three kinds of block type */
 
-const MIN_MATCH    = 3;
-const MAX_MATCH    = 258;
+const MIN_MATCH$1    = 3;
+const MAX_MATCH$1    = 258;
 /* The minimum and maximum match lengths */
 
 // From deflate.h
@@ -72422,25 +69705,25 @@ const MAX_MATCH    = 258;
  * Internal compression state.
  */
 
-const LENGTH_CODES  = 29;
+const LENGTH_CODES$1  = 29;
 /* number of length codes, not counting the special END_BLOCK code */
 
-const LITERALS      = 256;
+const LITERALS$1      = 256;
 /* number of literal bytes 0..255 */
 
-const L_CODES       = LITERALS + 1 + LENGTH_CODES;
+const L_CODES$1       = LITERALS$1 + 1 + LENGTH_CODES$1;
 /* number of Literal or Length codes, including the END_BLOCK code */
 
-const D_CODES       = 30;
+const D_CODES$1       = 30;
 /* number of distance codes */
 
-const BL_CODES      = 19;
+const BL_CODES$1      = 19;
 /* number of codes used to transfer the bit lengths */
 
-const HEAP_SIZE     = 2 * L_CODES + 1;
+const HEAP_SIZE$1     = 2 * L_CODES$1 + 1;
 /* maximum heap size */
 
-const MAX_BITS      = 15;
+const MAX_BITS$1      = 15;
 /* All codes must not exceed MAX_BITS bits */
 
 const Buf_size      = 16;
@@ -72493,37 +69776,37 @@ const bl_order =
 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);
+const static_ltree  = new Array((L_CODES$1 + 2) * 2);
+zero$1(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);
+const static_dtree  = new Array(D_CODES$1 * 2);
+zero$1(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);
+zero$1(_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);
+const _length_code  = new Array(MAX_MATCH$1 - MIN_MATCH$1 + 1);
+zero$1(_length_code);
 /* length code for each normalized match length (0 == MIN_MATCH) */
 
-const base_length   = new Array(LENGTH_CODES);
-zero(base_length);
+const base_length   = new Array(LENGTH_CODES$1);
+zero$1(base_length);
 /* First normalized length for each code (0 = MIN_MATCH) */
 
-const base_dist     = new Array(D_CODES);
-zero(base_dist);
+const base_dist     = new Array(D_CODES$1);
+zero$1(base_dist);
 /* First normalized distance for each code (0 = distance of 1) */
 
 
@@ -72658,7 +69941,7 @@ const gen_bitlen = (s, desc) =>
   let f;              /* frequency */
   let overflow = 0;   /* number of elements with bit length too large */
 
-  for (bits = 0; bits <= MAX_BITS; bits++) {
+  for (bits = 0; bits <= MAX_BITS$1; bits++) {
     s.bl_count[bits] = 0;
   }
 
@@ -72667,7 +69950,7 @@ const gen_bitlen = (s, desc) =>
    */
   tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */
 
-  for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
+  for (h = s.heap_max + 1; h < HEAP_SIZE$1; h++) {
     n = s.heap[h];
     bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1;
     if (bits > max_length) {
@@ -72742,7 +70025,7 @@ const gen_codes = (tree, max_code, bl_count) =>
 //    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 */
+  const next_code = new Array(MAX_BITS$1 + 1); /* next code value for each bit length */
   let code = 0;              /* running code value */
   let bits;                  /* bit index */
   let n;                     /* code index */
@@ -72750,7 +70033,7 @@ const gen_codes = (tree, max_code, bl_count) =>
   /* The distribution counts are first used to generate the code values
    * without bit reversal.
    */
-  for (bits = 1; bits <= MAX_BITS; bits++) {
+  for (bits = 1; bits <= MAX_BITS$1; bits++) {
     next_code[bits] = code = (code + bl_count[bits - 1]) << 1;
   }
   /* Check that the bit counts in bl_count are consistent. The last code
@@ -72782,7 +70065,7 @@ const tr_static_init = () => {
   let length;   /* length value */
   let code;     /* code value */
   let dist;     /* distance index */
-  const bl_count = new Array(MAX_BITS + 1);
+  const bl_count = new Array(MAX_BITS$1 + 1);
   /* number of codes at each bit length for an optimal tree */
 
   // do check in _tr_init()
@@ -72799,7 +70082,7 @@ const tr_static_init = () => {
 
   /* Initialize the mapping length (0..255) -> length code (0..28) */
   length = 0;
-  for (code = 0; code < LENGTH_CODES - 1; code++) {
+  for (code = 0; code < LENGTH_CODES$1 - 1; code++) {
     base_length[code] = length;
     for (n = 0; n < (1 << extra_lbits[code]); n++) {
       _length_code[length++] = code;
@@ -72822,7 +70105,7 @@ const tr_static_init = () => {
   }
   //Assert (dist == 256, "tr_static_init: dist != 256");
   dist >>= 7; /* from now on, all distances are divided by 128 */
-  for (; code < D_CODES; code++) {
+  for (; code < D_CODES$1; code++) {
     base_dist[code] = dist << 7;
     for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {
       _dist_code[256 + dist++] = code;
@@ -72831,7 +70114,7 @@ const tr_static_init = () => {
   //Assert (dist == 256, "tr_static_init: 256+dist != 512");
 
   /* Construct the codes of the static literal tree */
-  for (bits = 0; bits <= MAX_BITS; bits++) {
+  for (bits = 0; bits <= MAX_BITS$1; bits++) {
     bl_count[bits] = 0;
   }
 
@@ -72860,18 +70143,18 @@ const tr_static_init = () => {
    * tree construction to get a canonical Huffman tree (longest code
    * all ones)
    */
-  gen_codes(static_ltree, L_CODES + 1, bl_count);
+  gen_codes(static_ltree, L_CODES$1 + 1, bl_count);
 
   /* The static distance tree is trivial: */
-  for (n = 0; n < D_CODES; n++) {
+  for (n = 0; n < D_CODES$1; 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_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS$1 + 1, L_CODES$1, MAX_BITS$1);
+  static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0,          D_CODES$1, MAX_BITS$1);
+  static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0,         BL_CODES$1, MAX_BL_BITS);
 
   //static_init_done = true;
 };
@@ -72885,9 +70168,9 @@ 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; }
+  for (n = 0; n < L_CODES$1;  n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; }
+  for (n = 0; n < D_CODES$1;  n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; }
+  for (n = 0; n < BL_CODES$1; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; }
 
   s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1;
   s.opt_len = s.static_len = 0;
@@ -73007,7 +70290,7 @@ const compress_block = (s, ltree, dtree) =>
       } else {
         /* Here, lc is the match length - MIN_MATCH */
         code = _length_code[lc];
-        send_code(s, code + LITERALS + 1, ltree); /* send the length code */
+        send_code(s, code + LITERALS$1 + 1, ltree); /* send the length code */
         extra = extra_lbits[code];
         if (extra !== 0) {
           lc -= base_length[code];
@@ -73061,7 +70344,7 @@ const build_tree = (s, desc) =>
    * heap[0] is not used.
    */
   s.heap_len = 0;
-  s.heap_max = HEAP_SIZE;
+  s.heap_max = HEAP_SIZE$1;
 
   for (n = 0; n < elems; n++) {
     if (tree[n * 2]/*.Freq*/ !== 0) {
@@ -73295,7 +70578,7 @@ const build_bl_tree = (s) => {
    * 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--) {
+  for (max_blindex = BL_CODES$1 - 1; max_blindex >= 3; max_blindex--) {
     if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) {
       break;
     }
@@ -73374,7 +70657,7 @@ const detect_data_type = (s) => {
       s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) {
     return Z_TEXT;
   }
-  for (n = 32; n < LITERALS; n++) {
+  for (n = 32; n < LITERALS$1; n++) {
     if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) {
       return Z_TEXT;
     }
@@ -73392,7 +70675,7 @@ let static_init_done = false;
 /* ===========================================================================
  * Initialize the tree data structures for a new zlib stream.
  */
-const _tr_init = (s) =>
+const _tr_init$1 = (s) =>
 {
 
   if (!static_init_done) {
@@ -73415,7 +70698,7 @@ const _tr_init = (s) =>
 /* ===========================================================================
  * Send a stored block
  */
-const _tr_stored_block = (s, buf, stored_len, last) =>
+const _tr_stored_block$1 = (s, buf, stored_len, last) =>
 //DeflateState *s;
 //charf *buf;       /* input block */
 //ulg stored_len;   /* length of input block */
@@ -73430,7 +70713,7 @@ const _tr_stored_block = (s, buf, stored_len, last) =>
  * 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) => {
+const _tr_align$1 = (s) => {
   send_bits(s, STATIC_TREES << 1, 3);
   send_code(s, END_BLOCK, static_ltree);
   bi_flush(s);
@@ -73441,7 +70724,7 @@ const _tr_align = (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) =>
+const _tr_flush_block$1 = (s, buf, stored_len, last) =>
 //DeflateState *s;
 //charf *buf;       /* input block, or NULL if too old */
 //ulg stored_len;   /* length of input block */
@@ -73454,7 +70737,7 @@ const _tr_flush_block = (s, buf, stored_len, last) =>
   if (s.level > 0) {
 
     /* Check if the file is binary or text */
-    if (s.strm.data_type === Z_UNKNOWN) {
+    if (s.strm.data_type === Z_UNKNOWN$1) {
       s.strm.data_type = detect_data_type(s);
     }
 
@@ -73499,9 +70782,9 @@ const _tr_flush_block = (s, buf, stored_len, last) =>
      * 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);
+    _tr_stored_block$1(s, buf, stored_len, last);
 
-  } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) {
+  } else if (s.strategy === Z_FIXED$1 || static_lenb === opt_lenb) {
 
     send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);
     compress_block(s, static_ltree, static_dtree);
@@ -73528,7 +70811,7 @@ const _tr_flush_block = (s, buf, stored_len, 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) =>
+const _tr_tally$1 = (s, dist, lc) =>
 //    deflate_state *s;
 //    unsigned dist;  /* distance of matched string */
 //    unsigned lc;    /* match length-MIN_MATCH or unmatched char (if dist==0) */
@@ -73552,7 +70835,7 @@ const _tr_tally = (s, dist, lc) =>
     //       (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_ltree[(_length_code[lc] + LITERALS$1 + 1) * 2]/*.Freq*/++;
     s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++;
   }
 
@@ -73586,11 +70869,11 @@ const _tr_tally = (s, dist, lc) =>
    */
 };
 
-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 _tr_init_1  = _tr_init$1;
+var _tr_stored_block_1 = _tr_stored_block$1;
+var _tr_flush_block_1  = _tr_flush_block$1;
+var _tr_tally_1 = _tr_tally$1;
+var _tr_align_1 = _tr_align$1;
 
 var trees = {
        _tr_init: _tr_init_1,
@@ -73758,7 +71041,7 @@ var messages = {
 //   misrepresented as being the original software.
 // 3. This notice may not be removed or altered from any source distribution.
 
-var constants = {
+var constants$2 = {
 
   /* Allowed flush values; see deflate() and inflate() below for details */
   Z_NO_FLUSH:         0,
@@ -73825,7 +71108,7 @@ var constants = {
 //   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;
+const { _tr_init, _tr_stored_block, _tr_flush_block, _tr_tally, _tr_align } = trees;
 
 
 
@@ -73834,42 +71117,42 @@ const { _tr_init: _tr_init$1, _tr_stored_block: _tr_stored_block$1, _tr_flush_bl
 /* ===========================================================================*/
 
 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;
+  Z_NO_FLUSH: Z_NO_FLUSH$2, Z_PARTIAL_FLUSH, Z_FULL_FLUSH: Z_FULL_FLUSH$1, Z_FINISH: Z_FINISH$3, Z_BLOCK: Z_BLOCK$1,
+  Z_OK: Z_OK$3, Z_STREAM_END: Z_STREAM_END$3, Z_STREAM_ERROR: Z_STREAM_ERROR$2, Z_DATA_ERROR: Z_DATA_ERROR$2, Z_BUF_ERROR: Z_BUF_ERROR$1,
+  Z_DEFAULT_COMPRESSION: Z_DEFAULT_COMPRESSION$1,
+  Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE, Z_FIXED, Z_DEFAULT_STRATEGY: Z_DEFAULT_STRATEGY$1,
+  Z_UNKNOWN,
+  Z_DEFLATED: Z_DEFLATED$2
+} = constants$2;
 
 /*============================================================================*/
 
 
 const MAX_MEM_LEVEL = 9;
 /* Maximum value for memLevel in deflateInit2 */
-const MAX_WBITS = 15;
+const MAX_WBITS$1 = 15;
 /* 32K LZ77 window */
 const DEF_MEM_LEVEL = 8;
 
 
-const LENGTH_CODES$1  = 29;
+const LENGTH_CODES  = 29;
 /* number of length codes, not counting the special END_BLOCK code */
-const LITERALS$1      = 256;
+const LITERALS      = 256;
 /* number of literal bytes 0..255 */
-const L_CODES$1       = LITERALS$1 + 1 + LENGTH_CODES$1;
+const L_CODES       = LITERALS + 1 + LENGTH_CODES;
 /* number of Literal or Length codes, including the END_BLOCK code */
-const D_CODES$1       = 30;
+const D_CODES       = 30;
 /* number of distance codes */
-const BL_CODES$1      = 19;
+const BL_CODES      = 19;
 /* number of codes used to transfer the bit lengths */
-const HEAP_SIZE$1     = 2 * L_CODES$1 + 1;
+const HEAP_SIZE     = 2 * L_CODES + 1;
 /* maximum heap size */
-const MAX_BITS$1  = 15;
+const MAX_BITS  = 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 MIN_MATCH = 3;
+const MAX_MATCH = 258;
+const MIN_LOOKAHEAD = (MAX_MATCH + MIN_MATCH + 1);
 
 const PRESET_DICT = 0x20;
 
@@ -73897,7 +71180,7 @@ const rank = (f) => {
   return ((f) << 1) - ((f) > 4 ? 9 : 0);
 };
 
-const zero$1 = (buf) => {
+const zero = (buf) => {
   let len = buf.length; while (--len >= 0) { buf[len] = 0; }
 };
 
@@ -73938,7 +71221,7 @@ const flush_pending = (strm) => {
 
 
 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);
+  _tr_flush_block(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last);
   s.block_start = s.strstart;
   flush_pending(s.strm);
 };
@@ -74025,7 +71308,7 @@ const longest_match = (s, cur_match) => {
    * we prevent matches with the string of window index 0.
    */
 
-  const strend = s.strstart + MAX_MATCH$1;
+  const strend = s.strstart + MAX_MATCH;
   let scan_end1  = _win[scan + best_len - 1];
   let scan_end   = _win[scan + best_len];
 
@@ -74088,8 +71371,8 @@ const longest_match = (s, cur_match) => {
 
     // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
 
-    len = MAX_MATCH$1 - (strend - scan);
-    scan = strend - MAX_MATCH$1;
+    len = MAX_MATCH - (strend - scan);
+    scan = strend - MAX_MATCH;
 
     if (len > best_len) {
       s.match_start = cur_match;
@@ -74203,7 +71486,7 @@ const fill_window = (s) => {
     s.lookahead += n;
 
     /* Initialize the hash value now that we have some input: */
-    if (s.lookahead + s.insert >= MIN_MATCH$1) {
+    if (s.lookahead + s.insert >= MIN_MATCH) {
       str = s.strstart - s.insert;
       s.ins_h = s.window[str];
 
@@ -74214,13 +71497,13 @@ const fill_window = (s) => {
 //#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.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 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) {
+        if (s.lookahead + s.insert < MIN_MATCH) {
           break;
         }
       }
@@ -74302,7 +71585,7 @@ const deflate_stored = (s, flush) => {
 //      }
 
       fill_window(s);
-      if (s.lookahead === 0 && flush === Z_NO_FLUSH) {
+      if (s.lookahead === 0 && flush === Z_NO_FLUSH$2) {
         return BS_NEED_MORE;
       }
 
@@ -74348,7 +71631,7 @@ const deflate_stored = (s, flush) => {
 
   s.insert = 0;
 
-  if (flush === Z_FINISH) {
+  if (flush === Z_FINISH$3) {
     /*** FLUSH_BLOCK(s, 1); ***/
     flush_block_only(s, true);
     if (s.strm.avail_out === 0) {
@@ -74390,7 +71673,7 @@ const deflate_fast = (s, flush) => {
      */
     if (s.lookahead < MIN_LOOKAHEAD) {
       fill_window(s);
-      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) {
         return BS_NEED_MORE;
       }
       if (s.lookahead === 0) {
@@ -74402,9 +71685,9 @@ const deflate_fast = (s, flush) => {
      * dictionary, and set hash_head to the head of the hash chain:
      */
     hash_head = 0/*NIL*/;
-    if (s.lookahead >= MIN_MATCH$1) {
+    if (s.lookahead >= MIN_MATCH) {
       /*** INSERT_STRING(s, s.strstart, hash_head); ***/
-      s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH$1 - 1]);
+      s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]);
       hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
       s.head[s.ins_h] = s.strstart;
       /***/
@@ -74421,24 +71704,24 @@ const deflate_fast = (s, flush) => {
       s.match_length = longest_match(s, hash_head);
       /* longest_match() sets match_start */
     }
-    if (s.match_length >= MIN_MATCH$1) {
+    if (s.match_length >= MIN_MATCH) {
       // 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);
+      bflush = _tr_tally(s, s.strstart - s.match_start, s.match_length - MIN_MATCH);
 
       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) {
+      if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH) {
         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]);
+          s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]);
           hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
           s.head[s.ins_h] = s.strstart;
           /***/
@@ -74466,7 +71749,7 @@ const deflate_fast = (s, flush) => {
       /* 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]);
+      bflush = _tr_tally(s, 0, s.window[s.strstart]);
 
       s.lookahead--;
       s.strstart++;
@@ -74480,8 +71763,8 @@ const deflate_fast = (s, flush) => {
       /***/
     }
   }
-  s.insert = ((s.strstart < (MIN_MATCH$1 - 1)) ? s.strstart : MIN_MATCH$1 - 1);
-  if (flush === Z_FINISH) {
+  s.insert = ((s.strstart < (MIN_MATCH - 1)) ? s.strstart : MIN_MATCH - 1);
+  if (flush === Z_FINISH$3) {
     /*** FLUSH_BLOCK(s, 1); ***/
     flush_block_only(s, true);
     if (s.strm.avail_out === 0) {
@@ -74522,7 +71805,7 @@ const deflate_slow = (s, flush) => {
      */
     if (s.lookahead < MIN_LOOKAHEAD) {
       fill_window(s);
-      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+      if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH$2) {
         return BS_NEED_MORE;
       }
       if (s.lookahead === 0) { break; } /* flush the current block */
@@ -74532,9 +71815,9 @@ const deflate_slow = (s, flush) => {
      * dictionary, and set hash_head to the head of the hash chain:
      */
     hash_head = 0/*NIL*/;
-    if (s.lookahead >= MIN_MATCH$1) {
+    if (s.lookahead >= MIN_MATCH) {
       /*** INSERT_STRING(s, s.strstart, hash_head); ***/
-      s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH$1 - 1]);
+      s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 1]);
       hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
       s.head[s.ins_h] = s.strstart;
       /***/
@@ -74544,7 +71827,7 @@ const deflate_slow = (s, flush) => {
      */
     s.prev_length = s.match_length;
     s.prev_match = s.match_start;
-    s.match_length = MIN_MATCH$1 - 1;
+    s.match_length = MIN_MATCH - 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)*/) {
@@ -74556,26 +71839,26 @@ const deflate_slow = (s, flush) => {
       /* 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*/))) {
+         (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH && 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;
+        s.match_length = MIN_MATCH - 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;
+    if (s.prev_length >= MIN_MATCH && s.match_length <= s.prev_length) {
+      max_insert = s.strstart + s.lookahead - MIN_MATCH;
       /* 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);
+      bflush = _tr_tally(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH);
       /* 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
@@ -74586,14 +71869,14 @@ const deflate_slow = (s, flush) => {
       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]);
+          s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH - 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.match_length = MIN_MATCH - 1;
       s.strstart++;
 
       if (bflush) {
@@ -74612,7 +71895,7 @@ const deflate_slow = (s, flush) => {
        */
       //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]);
+      bflush = _tr_tally(s, 0, s.window[s.strstart - 1]);
 
       if (bflush) {
         /*** FLUSH_BLOCK_ONLY(s, 0) ***/
@@ -74637,12 +71920,12 @@ const deflate_slow = (s, 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]);
+    bflush = _tr_tally(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) {
+  s.insert = s.strstart < MIN_MATCH - 1 ? s.strstart : MIN_MATCH - 1;
+  if (flush === Z_FINISH$3) {
     /*** FLUSH_BLOCK(s, 1); ***/
     flush_block_only(s, true);
     if (s.strm.avail_out === 0) {
@@ -74682,9 +71965,9 @@ const deflate_rle = (s, flush) => {
      * 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) {
+    if (s.lookahead <= MAX_MATCH) {
       fill_window(s);
-      if (s.lookahead <= MAX_MATCH$1 && flush === Z_NO_FLUSH) {
+      if (s.lookahead <= MAX_MATCH && flush === Z_NO_FLUSH$2) {
         return BS_NEED_MORE;
       }
       if (s.lookahead === 0) { break; } /* flush the current block */
@@ -74692,11 +71975,11 @@ const deflate_rle = (s, flush) => {
 
     /* See how many times the previous byte repeats */
     s.match_length = 0;
-    if (s.lookahead >= MIN_MATCH$1 && s.strstart > 0) {
+    if (s.lookahead >= MIN_MATCH && 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;
+        strend = s.strstart + MAX_MATCH;
         do {
           /*jshint noempty:false*/
         } while (prev === _win[++scan] && prev === _win[++scan] &&
@@ -74704,7 +71987,7 @@ const deflate_rle = (s, flush) => {
                  prev === _win[++scan] && prev === _win[++scan] &&
                  prev === _win[++scan] && prev === _win[++scan] &&
                  scan < strend);
-        s.match_length = MAX_MATCH$1 - (strend - scan);
+        s.match_length = MAX_MATCH - (strend - scan);
         if (s.match_length > s.lookahead) {
           s.match_length = s.lookahead;
         }
@@ -74713,11 +71996,11 @@ const deflate_rle = (s, flush) => {
     }
 
     /* Emit match if have run of MIN_MATCH or longer, else emit literal */
-    if (s.match_length >= MIN_MATCH$1) {
+    if (s.match_length >= MIN_MATCH) {
       //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);
+      bflush = _tr_tally(s, 1, s.match_length - MIN_MATCH);
 
       s.lookahead -= s.match_length;
       s.strstart += s.match_length;
@@ -74726,7 +72009,7 @@ const deflate_rle = (s, flush) => {
       /* 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]);
+      bflush = _tr_tally(s, 0, s.window[s.strstart]);
 
       s.lookahead--;
       s.strstart++;
@@ -74741,7 +72024,7 @@ const deflate_rle = (s, flush) => {
     }
   }
   s.insert = 0;
-  if (flush === Z_FINISH) {
+  if (flush === Z_FINISH$3) {
     /*** FLUSH_BLOCK(s, 1); ***/
     flush_block_only(s, true);
     if (s.strm.avail_out === 0) {
@@ -74774,7 +72057,7 @@ const deflate_huff = (s, flush) => {
     if (s.lookahead === 0) {
       fill_window(s);
       if (s.lookahead === 0) {
-        if (flush === Z_NO_FLUSH) {
+        if (flush === Z_NO_FLUSH$2) {
           return BS_NEED_MORE;
         }
         break;      /* flush the current block */
@@ -74785,7 +72068,7 @@ const deflate_huff = (s, flush) => {
     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]);
+    bflush = _tr_tally(s, 0, s.window[s.strstart]);
     s.lookahead--;
     s.strstart++;
     if (bflush) {
@@ -74798,7 +72081,7 @@ const deflate_huff = (s, flush) => {
     }
   }
   s.insert = 0;
-  if (flush === Z_FINISH) {
+  if (flush === Z_FINISH$3) {
     /*** FLUSH_BLOCK(s, 1); ***/
     flush_block_only(s, true);
     if (s.strm.avail_out === 0) {
@@ -74856,7 +72139,7 @@ const lm_init = (s) => {
   s.window_size = 2 * s.w_size;
 
   /*** CLEAR_HASH(s); ***/
-  zero$1(s.head); // Fill with NIL (= 0);
+  zero(s.head); // Fill with NIL (= 0);
 
   /* Set the default configuration parameters:
    */
@@ -74869,7 +72152,7 @@ const lm_init = (s) => {
   s.block_start = 0;
   s.lookahead = 0;
   s.insert = 0;
-  s.match_length = s.prev_length = MIN_MATCH$1 - 1;
+  s.match_length = s.prev_length = MIN_MATCH - 1;
   s.match_available = 0;
   s.ins_h = 0;
 };
@@ -74885,7 +72168,7 @@ function DeflateState() {
   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.method = Z_DEFLATED$2; /* 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) */
@@ -74978,24 +72261,24 @@ function DeflateState() {
 
   // 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.dyn_ltree  = new Uint16Array(HEAP_SIZE * 2);
+  this.dyn_dtree  = new Uint16Array((2 * D_CODES + 1) * 2);
+  this.bl_tree    = new Uint16Array((2 * BL_CODES + 1) * 2);
+  zero(this.dyn_ltree);
+  zero(this.dyn_dtree);
+  zero(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);
+  this.bl_count = new Uint16Array(MAX_BITS + 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 = new Uint16Array(2 * L_CODES + 1);  /* heap used to build the Huffman trees */
+  zero(this.heap);
 
   this.heap_len = 0;               /* number of elements in the heap */
   this.heap_max = 0;               /* element of largest frequency */
@@ -75003,8 +72286,8 @@ function DeflateState() {
    * 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);
+  this.depth = new Uint16Array(2 * L_CODES + 1); //uch depth[2*L_CODES+1];
+  zero(this.depth);
   /* Depth of each subtree used as tie breaker for trees of equal frequency
    */
 
@@ -75067,11 +72350,11 @@ function DeflateState() {
 const deflateResetKeep = (strm) => {
 
   if (!strm || !strm.state) {
-    return err(strm, Z_STREAM_ERROR);
+    return err(strm, Z_STREAM_ERROR$2);
   }
 
   strm.total_in = strm.total_out = 0;
-  strm.data_type = Z_UNKNOWN$1;
+  strm.data_type = Z_UNKNOWN;
 
   const s = strm.state;
   s.pending = 0;
@@ -75086,16 +72369,16 @@ const deflateResetKeep = (strm) => {
     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;
+  s.last_flush = Z_NO_FLUSH$2;
+  _tr_init(s);
+  return Z_OK$3;
 };
 
 
 const deflateReset = (strm) => {
 
   const ret = deflateResetKeep(strm);
-  if (ret === Z_OK) {
+  if (ret === Z_OK$3) {
     lm_init(strm.state);
   }
   return ret;
@@ -75104,21 +72387,21 @@ const deflateReset = (strm) => {
 
 const deflateSetHeader = (strm, head) => {
 
-  if (!strm || !strm.state) { return Z_STREAM_ERROR; }
-  if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; }
+  if (!strm || !strm.state) { return Z_STREAM_ERROR$2; }
+  if (strm.state.wrap !== 2) { return Z_STREAM_ERROR$2; }
   strm.state.gzhead = head;
-  return Z_OK;
+  return Z_OK$3;
 };
 
 
 const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => {
 
   if (!strm) { // === Z_NULL
-    return Z_STREAM_ERROR;
+    return Z_STREAM_ERROR$2;
   }
   let wrap = 1;
 
-  if (level === Z_DEFAULT_COMPRESSION) {
+  if (level === Z_DEFAULT_COMPRESSION$1) {
     level = 6;
   }
 
@@ -75133,10 +72416,10 @@ const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => {
   }
 
 
-  if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED ||
+  if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED$2 ||
     windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
-    strategy < 0 || strategy > Z_FIXED$1) {
-    return err(strm, Z_STREAM_ERROR);
+    strategy < 0 || strategy > Z_FIXED) {
+    return err(strm, Z_STREAM_ERROR$2);
   }
 
 
@@ -75159,7 +72442,7 @@ const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => {
   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.hash_shift = ~~((s.hash_bits + MIN_MATCH - 1) / MIN_MATCH);
 
   s.window = new Uint8Array(s.w_size * 2);
   s.head = new Uint16Array(s.hash_size);
@@ -75192,25 +72475,25 @@ const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => {
 
 const deflateInit = (strm, level) => {
 
-  return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+  return deflateInit2(strm, level, Z_DEFLATED$2, MAX_WBITS$1, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY$1);
 };
 
 
-const deflate = (strm, flush) => {
+const deflate$2 = (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;
+    flush > Z_BLOCK$1 || flush < 0) {
+    return strm ? err(strm, Z_STREAM_ERROR$2) : Z_STREAM_ERROR$2;
   }
 
   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.status === FINISH_STATE && flush !== Z_FINISH$3)) {
+    return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR$1 : Z_STREAM_ERROR$2);
   }
 
   s.strm = strm; /* just in case */
@@ -75265,7 +72548,7 @@ const deflate = (strm, flush) => {
     }
     else // DEFLATE header
     {
-      let header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8;
+      let header = (Z_DEFLATED$2 + ((s.w_bits - 8) << 4)) << 8;
       let level_flags = -1;
 
       if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {
@@ -75428,7 +72711,7 @@ const deflate = (strm, flush) => {
        * return OK instead of BUF_ERROR at next call of deflate:
        */
       s.last_flush = -1;
-      return Z_OK;
+      return Z_OK$3;
     }
 
     /* Make sure there is something to do and avoid duplicate consecutive
@@ -75436,19 +72719,19 @@ const deflate = (strm, flush) => {
      * 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);
+    flush !== Z_FINISH$3) {
+    return err(strm, Z_BUF_ERROR$1);
   }
 
   /* 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);
+    return err(strm, Z_BUF_ERROR$1);
   }
 
   /* 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)) {
+    (flush !== Z_NO_FLUSH$2 && 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));
@@ -75461,7 +72744,7 @@ const deflate = (strm, flush) => {
         s.last_flush = -1;
         /* avoid BUF_ERROR next call, see above */
       }
-      return Z_OK;
+      return Z_OK$3;
       /* 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
@@ -75472,17 +72755,17 @@ const deflate = (strm, flush) => {
     }
     if (bstate === BS_BLOCK_DONE) {
       if (flush === Z_PARTIAL_FLUSH) {
-        _tr_align$1(s);
+        _tr_align(s);
       }
-      else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */
+      else if (flush !== Z_BLOCK$1) { /* FULL_FLUSH or SYNC_FLUSH */
 
-        _tr_stored_block$1(s, 0, 0, false);
+        _tr_stored_block(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) {
+        if (flush === Z_FULL_FLUSH$1) {
           /*** CLEAR_HASH(s); ***/             /* forget history */
-          zero$1(s.head); // Fill with NIL (= 0);
+          zero(s.head); // Fill with NIL (= 0);
 
           if (s.lookahead === 0) {
             s.strstart = 0;
@@ -75494,15 +72777,15 @@ const deflate = (strm, flush) => {
       flush_pending(strm);
       if (strm.avail_out === 0) {
         s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */
-        return Z_OK;
+        return Z_OK$3;
       }
     }
   }
   //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; }
+  if (flush !== Z_FINISH$3) { return Z_OK$3; }
+  if (s.wrap <= 0) { return Z_STREAM_END$3; }
 
   /* Write the trailer */
   if (s.wrap === 2) {
@@ -75527,14 +72810,14 @@ const deflate = (strm, flush) => {
    */
   if (s.wrap > 0) { s.wrap = -s.wrap; }
   /* write the trailer only once! */
-  return s.pending !== 0 ? Z_OK : Z_STREAM_END;
+  return s.pending !== 0 ? Z_OK$3 : Z_STREAM_END$3;
 };
 
 
 const deflateEnd = (strm) => {
 
   if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
-    return Z_STREAM_ERROR;
+    return Z_STREAM_ERROR$2;
   }
 
   const status = strm.state.status;
@@ -75546,12 +72829,12 @@ const deflateEnd = (strm) => {
     status !== BUSY_STATE &&
     status !== FINISH_STATE
   ) {
-    return err(strm, Z_STREAM_ERROR);
+    return err(strm, Z_STREAM_ERROR$2);
   }
 
   strm.state = null;
 
-  return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;
+  return status === BUSY_STATE ? err(strm, Z_DATA_ERROR$2) : Z_OK$3;
 };
 
 
@@ -75564,14 +72847,14 @@ const deflateSetDictionary = (strm, dictionary) => {
   let dictLength = dictionary.length;
 
   if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
-    return Z_STREAM_ERROR;
+    return Z_STREAM_ERROR$2;
   }
 
   const s = strm.state;
   const wrap = s.wrap;
 
   if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) {
-    return Z_STREAM_ERROR;
+    return Z_STREAM_ERROR$2;
   }
 
   /* when using zlib wrappers, compute Adler-32 for provided dictionary */
@@ -75586,7 +72869,7 @@ const deflateSetDictionary = (strm, dictionary) => {
   if (dictLength >= s.w_size) {
     if (wrap === 0) {            /* already empty otherwise */
       /*** CLEAR_HASH(s); ***/
-      zero$1(s.head); // Fill with NIL (= 0);
+      zero(s.head); // Fill with NIL (= 0);
       s.strstart = 0;
       s.block_start = 0;
       s.insert = 0;
@@ -75606,12 +72889,12 @@ const deflateSetDictionary = (strm, dictionary) => {
   strm.next_in = 0;
   strm.input = dictionary;
   fill_window(s);
-  while (s.lookahead >= MIN_MATCH$1) {
+  while (s.lookahead >= MIN_MATCH) {
     let str = s.strstart;
-    let n = s.lookahead - (MIN_MATCH$1 - 1);
+    let n = s.lookahead - (MIN_MATCH - 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.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH - 1]);
 
       s.prev[str & s.w_mask] = s.head[s.ins_h];
 
@@ -75619,20 +72902,20 @@ const deflateSetDictionary = (strm, dictionary) => {
       str++;
     } while (--n);
     s.strstart = str;
-    s.lookahead = MIN_MATCH$1 - 1;
+    s.lookahead = MIN_MATCH - 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_length = s.prev_length = MIN_MATCH - 1;
   s.match_available = 0;
   strm.next_in = next;
   strm.input = input;
   strm.avail_in = avail;
   s.wrap = wrap;
-  return Z_OK;
+  return Z_OK$3;
 };
 
 
@@ -75641,7 +72924,7 @@ var deflateInit2_1 = deflateInit2;
 var deflateReset_1 = deflateReset;
 var deflateResetKeep_1 = deflateResetKeep;
 var deflateSetHeader_1 = deflateSetHeader;
-var deflate_2 = deflate;
+var deflate_2$1 = deflate$2;
 var deflateEnd_1 = deflateEnd;
 var deflateSetDictionary_1 = deflateSetDictionary;
 var deflateInfo = 'pako deflate (from Nodeca project)';
@@ -75655,13 +72938,13 @@ module.exports.deflatePrime = deflatePrime;
 module.exports.deflateTune = deflateTune;
 */
 
-var deflate_1 = {
+var deflate_1$2 = {
        deflateInit: deflateInit_1,
        deflateInit2: deflateInit2_1,
        deflateReset: deflateReset_1,
        deflateResetKeep: deflateResetKeep_1,
        deflateSetHeader: deflateSetHeader_1,
-       deflate: deflate_2,
+       deflate: deflate_2$1,
        deflateEnd: deflateEnd_1,
        deflateSetDictionary: deflateSetDictionary_1,
        deflateInfo: deflateInfo
@@ -75743,6 +73026,10 @@ _utf8len[254] = _utf8len[254] = 1; // Invalid sequence start
 
 // convert string to array (typed, when possible)
 var string2buf = (str) => {
+  if (typeof TextEncoder === 'function' && TextEncoder.prototype.encode) {
+    return new TextEncoder().encode(str);
+  }
+
   let buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;
 
   // count binary size
@@ -75816,9 +73103,14 @@ const buf2binstring = (buf, len) => {
 
 // convert array to string
 var buf2string = (buf, max) => {
-  let i, out;
   const len = max || buf.length;
 
+  if (typeof TextDecoder === 'function' && TextDecoder.prototype.decode) {
+    return new TextDecoder().decode(buf.subarray(0, max));
+  }
+
+  let i, out;
+
   // Reserve max possible length (2 words per char)
   // NB: by unknown reasons, Array is significantly faster for
   //     String.fromCharCode.apply than Uint16Array.
@@ -75935,18 +73227,18 @@ function ZStream() {
 
 var zstream = ZStream;
 
-const toString = Object.prototype.toString;
+const toString$1 = 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_NO_FLUSH: Z_NO_FLUSH$1, Z_SYNC_FLUSH, Z_FULL_FLUSH, Z_FINISH: Z_FINISH$2,
+  Z_OK: Z_OK$2, Z_STREAM_END: Z_STREAM_END$2,
+  Z_DEFAULT_COMPRESSION,
+  Z_DEFAULT_STRATEGY,
   Z_DEFLATED: Z_DEFLATED$1
-} = constants;
+} = constants$2;
 
 /* ===========================================================================*/
 
@@ -76036,14 +73328,14 @@ const {
  * console.log(deflate.result);
  * ```
  **/
-function Deflate(options) {
+function Deflate$1(options) {
   this.options = common.assign({
-    level: Z_DEFAULT_COMPRESSION$1,
+    level: Z_DEFAULT_COMPRESSION,
     method: Z_DEFLATED$1,
     chunkSize: 16384,
     windowBits: 15,
     memLevel: 8,
-    strategy: Z_DEFAULT_STRATEGY$1
+    strategy: Z_DEFAULT_STRATEGY
   }, options || {});
 
   let opt = this.options;
@@ -76064,7 +73356,7 @@ function Deflate(options) {
   this.strm = new zstream();
   this.strm.avail_out = 0;
 
-  let status = deflate_1.deflateInit2(
+  let status = deflate_1$2.deflateInit2(
     this.strm,
     opt.level,
     opt.method,
@@ -76073,12 +73365,12 @@ function Deflate(options) {
     opt.strategy
   );
 
-  if (status !== Z_OK$1) {
+  if (status !== Z_OK$2) {
     throw new Error(messages[status]);
   }
 
   if (opt.header) {
-    deflate_1.deflateSetHeader(this.strm, opt.header);
+    deflate_1$2.deflateSetHeader(this.strm, opt.header);
   }
 
   if (opt.dictionary) {
@@ -76087,15 +73379,15 @@ function Deflate(options) {
     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]') {
+    } else if (toString$1.call(opt.dictionary) === '[object ArrayBuffer]') {
       dict = new Uint8Array(opt.dictionary);
     } else {
       dict = opt.dictionary;
     }
 
-    status = deflate_1.deflateSetDictionary(this.strm, dict);
+    status = deflate_1$2.deflateSetDictionary(this.strm, dict);
 
-    if (status !== Z_OK$1) {
+    if (status !== Z_OK$2) {
       throw new Error(messages[status]);
     }
 
@@ -76125,7 +73417,7 @@ function Deflate(options) {
  * push(chunk, true);  // push last chunk
  * ```
  **/
-Deflate.prototype.push = function (data, flush_mode) {
+Deflate$1.prototype.push = function (data, flush_mode) {
   const strm = this.strm;
   const chunkSize = this.options.chunkSize;
   let status, _flush_mode;
@@ -76133,13 +73425,13 @@ Deflate.prototype.push = function (data, 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;
+  else _flush_mode = flush_mode === true ? Z_FINISH$2 : 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]') {
+  } else if (toString$1.call(data) === '[object ArrayBuffer]') {
     strm.input = new Uint8Array(data);
   } else {
     strm.input = data;
@@ -76156,23 +73448,23 @@ Deflate.prototype.push = function (data, flush_mode) {
     }
 
     // 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) {
+    if ((_flush_mode === Z_SYNC_FLUSH || _flush_mode === Z_FULL_FLUSH) && 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);
+    status = deflate_1$2.deflate(strm, _flush_mode);
 
     // Ended => flush and finish
-    if (status === Z_STREAM_END$1) {
+    if (status === Z_STREAM_END$2) {
       if (strm.next_out > 0) {
         this.onData(strm.output.subarray(0, strm.next_out));
       }
-      status = deflate_1.deflateEnd(this.strm);
+      status = deflate_1$2.deflateEnd(this.strm);
       this.onEnd(status);
       this.ended = true;
-      return status === Z_OK$1;
+      return status === Z_OK$2;
     }
 
     // Flush if out buffer full
@@ -76202,7 +73494,7 @@ Deflate.prototype.push = function (data, flush_mode) {
  * 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) {
+Deflate$1.prototype.onData = function (chunk) {
   this.chunks.push(chunk);
 };
 
@@ -76216,9 +73508,9 @@ Deflate.prototype.onData = function (chunk) {
  * complete (Z_FINISH). By default - join collected chunks,
  * free memory and fill `results` / `err` properties.
  **/
-Deflate.prototype.onEnd = function (status) {
+Deflate$1.prototype.onEnd = function (status) {
   // On success - join
-  if (status === Z_OK$1) {
+  if (status === Z_OK$2) {
     this.result = common.flattenChunks(this.chunks);
   }
   this.chunks = [];
@@ -76246,8 +73538,8 @@ Deflate.prototype.onEnd = function (status) {
 // 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 */
+const BAD$1 = 30;       /* got a data error -- remain here until reset */
+const TYPE$1 = 12;      /* i: waiting for type bits, including last-flag bit */
 
 /*
    Decode literal, length, and distance codes and write out the resulting
@@ -76409,7 +73701,7 @@ var inffast = function inflate_fast(strm, start) {
 //#ifdef INFLATE_STRICT
             if (dist > dmax) {
               strm.msg = 'invalid distance too far back';
-              state.mode = BAD;
+              state.mode = BAD$1;
               break top;
             }
 //#endif
@@ -76422,7 +73714,7 @@ var inffast = function inflate_fast(strm, start) {
               if (op > whave) {
                 if (state.sane) {
                   strm.msg = 'invalid distance too far back';
-                  state.mode = BAD;
+                  state.mode = BAD$1;
                   break top;
                 }
 
@@ -76527,7 +73819,7 @@ var inffast = function inflate_fast(strm, start) {
           }
           else {
             strm.msg = 'invalid distance code';
-            state.mode = BAD;
+            state.mode = BAD$1;
             break top;
           }
 
@@ -76540,12 +73832,12 @@ var inffast = function inflate_fast(strm, start) {
       }
       else if (op & 32) {                     /* end-of-block */
         //Tracevv((stderr, "inflate:         end of block\n"));
-        state.mode = TYPE;
+        state.mode = TYPE$1;
         break top;
       }
       else {
         strm.msg = 'invalid literal/length code';
-        state.mode = BAD;
+        state.mode = BAD$1;
         break top;
       }
 
@@ -76589,13 +73881,13 @@ var inffast = function inflate_fast(strm, start) {
 // 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_LENS$1 = 852;
+const ENOUGH_DISTS$1 = 592;
 //const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);
 
-const CODES = 0;
-const LENS = 1;
-const DISTS = 2;
+const CODES$1 = 0;
+const LENS$1 = 1;
+const DISTS$1 = 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,
@@ -76727,7 +74019,7 @@ const inflate_table = (type, lens, lens_index, codes, table, table_index, work,
       return -1;
     }        /* over-subscribed */
   }
-  if (left > 0 && (type === CODES || max !== 1)) {
+  if (left > 0 && (type === CODES$1 || max !== 1)) {
     return -1;                      /* incomplete set */
   }
 
@@ -76778,11 +74070,11 @@ const inflate_table = (type, lens, lens_index, codes, table, table_index, work,
   /* set up for code type */
   // poor man optimization - use if-else instead of switch,
   // to avoid deopts in old v8
-  if (type === CODES) {
+  if (type === CODES$1) {
     base = extra = work;    /* dummy value--not used */
     end = 19;
 
-  } else if (type === LENS) {
+  } else if (type === LENS$1) {
     base = lbase;
     base_index -= 257;
     extra = lext;
@@ -76807,8 +74099,8 @@ const inflate_table = (type, lens, lens_index, codes, table, table_index, work,
   mask = used - 1;            /* mask for comparing low */
 
   /* check available table space */
-  if ((type === LENS && used > ENOUGH_LENS) ||
-    (type === DISTS && used > ENOUGH_DISTS)) {
+  if ((type === LENS$1 && used > ENOUGH_LENS$1) ||
+    (type === DISTS$1 && used > ENOUGH_DISTS$1)) {
     return 1;
   }
 
@@ -76879,8 +74171,8 @@ const inflate_table = (type, lens, lens_index, codes, table, table_index, work,
 
       /* check for enough space */
       used += 1 << curr;
-      if ((type === LENS && used > ENOUGH_LENS) ||
-        (type === DISTS && used > ENOUGH_DISTS)) {
+      if ((type === LENS$1 && used > ENOUGH_LENS$1) ||
+        (type === DISTS$1 && used > ENOUGH_DISTS$1)) {
         return 1;
       }
 
@@ -76936,18 +74228,18 @@ var inftrees = inflate_table;
 
 
 
-const CODES$1 = 0;
-const LENS$1 = 1;
-const DISTS$1 = 2;
+const CODES = 0;
+const LENS = 1;
+const DISTS = 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;
+  Z_FINISH: Z_FINISH$1, Z_BLOCK, Z_TREES,
+  Z_OK: Z_OK$1, Z_STREAM_END: Z_STREAM_END$1, Z_NEED_DICT: Z_NEED_DICT$1, Z_STREAM_ERROR: Z_STREAM_ERROR$1, Z_DATA_ERROR: Z_DATA_ERROR$1, Z_MEM_ERROR: Z_MEM_ERROR$1, Z_BUF_ERROR,
+  Z_DEFLATED
+} = constants$2;
 
 
 /* STATES ====================================================================*/
@@ -76965,7 +74257,7 @@ 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        TYPE = 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 */
@@ -76983,7 +74275,7 @@ 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    BAD = 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() */
 
@@ -76991,13 +74283,13 @@ const    SYNC = 32;      /* looking for synchronization bytes to restart inflate
 
 
 
-const ENOUGH_LENS$1 = 852;
-const ENOUGH_DISTS$1 = 592;
+const ENOUGH_LENS = 852;
+const ENOUGH_DISTS = 592;
 //const ENOUGH =  (ENOUGH_LENS+ENOUGH_DISTS);
 
-const MAX_WBITS$1 = 15;
+const MAX_WBITS = 15;
 /* 32K LZ77 window */
-const DEF_WBITS = MAX_WBITS$1;
+const DEF_WBITS = MAX_WBITS;
 
 
 const zswap32 = (q) => {
@@ -77085,13 +74377,13 @@ const inflateResetKeep = (strm) => {
   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.lencode = state.lendyn = new Int32Array(ENOUGH_LENS);
+  state.distcode = state.distdyn = new Int32Array(ENOUGH_DISTS);
 
   state.sane = 1;
   state.back = -1;
   //Tracev((stderr, "inflate: reset\n"));
-  return Z_OK$2;
+  return Z_OK$1;
 };
 
 
@@ -77153,7 +74445,7 @@ const inflateInit2 = (strm, windowBits) => {
   strm.state = state;
   state.window = null/*Z_NULL*/;
   const ret = inflateReset2(strm, windowBits);
-  if (ret !== Z_OK$2) {
+  if (ret !== Z_OK$1) {
     strm.state = null/*Z_NULL*/;
   }
   return ret;
@@ -77195,13 +74487,13 @@ const fixedtables = (state) => {
     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 });
+    inftrees(LENS,  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 });
+    inftrees(DISTS, state.lens, 0, 32,   distfix, 0, state.work, { bits: 5 });
 
     /* do this just once */
     virgin = false;
@@ -77272,7 +74564,7 @@ const updatewindow = (strm, src, end, copy) => {
 };
 
 
-const inflate = (strm, flush) => {
+const inflate$2 = (strm, flush) => {
 
   let state;
   let input, output;          // input/output buffers
@@ -77306,7 +74598,7 @@ const inflate = (strm, flush) => {
   }
 
   state = strm.state;
-  if (state.mode === TYPE$1) { state.mode = TYPEDO; }    /* skip check */
+  if (state.mode === TYPE) { state.mode = TYPEDO; }    /* skip check */
 
 
   //--- LOAD() ---
@@ -77322,7 +74614,7 @@ const inflate = (strm, flush) => {
 
   _in = have;
   _out = left;
-  ret = Z_OK$2;
+  ret = Z_OK$1;
 
   inf_leave: // goto emulation
   for (;;) {
@@ -77362,12 +74654,12 @@ const inflate = (strm, flush) => {
         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;
+          state.mode = BAD;
           break;
         }
-        if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED$2) {
+        if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED) {
           strm.msg = 'unknown compression method';
-          state.mode = BAD$1;
+          state.mode = BAD;
           break;
         }
         //--- DROPBITS(4) ---//
@@ -77380,7 +74672,7 @@ const inflate = (strm, flush) => {
         }
         else if (len > state.wbits) {
           strm.msg = 'invalid window size';
-          state.mode = BAD$1;
+          state.mode = BAD;
           break;
         }
 
@@ -77391,7 +74683,7 @@ const inflate = (strm, flush) => {
 
         //Tracev((stderr, "inflate:   zlib header ok\n"));
         strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
-        state.mode = hold & 0x200 ? DICTID : TYPE$1;
+        state.mode = hold & 0x200 ? DICTID : TYPE;
         //=== INITBITS();
         hold = 0;
         bits = 0;
@@ -77407,14 +74699,14 @@ const inflate = (strm, flush) => {
         }
         //===//
         state.flags = hold;
-        if ((state.flags & 0xff) !== Z_DEFLATED$2) {
+        if ((state.flags & 0xff) !== Z_DEFLATED) {
           strm.msg = 'unknown compression method';
-          state.mode = BAD$1;
+          state.mode = BAD;
           break;
         }
         if (state.flags & 0xe000) {
           strm.msg = 'unknown header flags set';
-          state.mode = BAD$1;
+          state.mode = BAD;
           break;
         }
         if (state.head) {
@@ -77617,7 +74909,7 @@ const inflate = (strm, flush) => {
           //===//
           if (hold !== (state.check & 0xffff)) {
             strm.msg = 'header crc mismatch';
-            state.mode = BAD$1;
+            state.mode = BAD;
             break;
           }
           //=== INITBITS();
@@ -77630,7 +74922,7 @@ const inflate = (strm, flush) => {
           state.head.done = true;
         }
         strm.adler = state.check = 0;
-        state.mode = TYPE$1;
+        state.mode = TYPE;
         break;
       case DICTID:
         //=== NEEDBITS(32); */
@@ -77658,13 +74950,13 @@ const inflate = (strm, flush) => {
           state.hold = hold;
           state.bits = bits;
           //---
-          return Z_NEED_DICT;
+          return Z_NEED_DICT$1;
         }
         strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
-        state.mode = TYPE$1;
+        state.mode = TYPE;
         /* falls through */
-      case TYPE$1:
-        if (flush === Z_BLOCK$1 || flush === Z_TREES) { break inf_leave; }
+      case TYPE:
+        if (flush === Z_BLOCK || flush === Z_TREES) { break inf_leave; }
         /* falls through */
       case TYPEDO:
         if (state.last) {
@@ -77715,7 +75007,7 @@ const inflate = (strm, flush) => {
             break;
           case 3:
             strm.msg = 'invalid block type';
-            state.mode = BAD$1;
+            state.mode = BAD;
         }
         //--- DROPBITS(2) ---//
         hold >>>= 2;
@@ -77737,7 +75029,7 @@ const inflate = (strm, flush) => {
         //===//
         if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {
           strm.msg = 'invalid stored block lengths';
-          state.mode = BAD$1;
+          state.mode = BAD;
           break;
         }
         state.length = hold & 0xffff;
@@ -77770,7 +75062,7 @@ const inflate = (strm, flush) => {
           break;
         }
         //Tracev((stderr, "inflate:       stored end\n"));
-        state.mode = TYPE$1;
+        state.mode = TYPE;
         break;
       case TABLE:
         //=== NEEDBITS(14); */
@@ -77799,7 +75091,7 @@ const inflate = (strm, flush) => {
 //#ifndef PKZIP_BUG_WORKAROUND
         if (state.nlen > 286 || state.ndist > 30) {
           strm.msg = 'too many length or distance symbols';
-          state.mode = BAD$1;
+          state.mode = BAD;
           break;
         }
 //#endif
@@ -77834,12 +75126,12 @@ const inflate = (strm, flush) => {
         state.lenbits = 7;
 
         opts = { bits: state.lenbits };
-        ret = inftrees(CODES$1, state.lens, 0, 19, state.lencode, 0, state.work, opts);
+        ret = inftrees(CODES, 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;
+          state.mode = BAD;
           break;
         }
         //Tracev((stderr, "inflate:       code lengths ok\n"));
@@ -77886,7 +75178,7 @@ const inflate = (strm, flush) => {
               //---//
               if (state.have === 0) {
                 strm.msg = 'invalid bit length repeat';
-                state.mode = BAD$1;
+                state.mode = BAD;
                 break;
               }
               len = state.lens[state.have - 1];
@@ -77940,7 +75232,7 @@ const inflate = (strm, flush) => {
             }
             if (state.have + copy > state.nlen + state.ndist) {
               strm.msg = 'invalid bit length repeat';
-              state.mode = BAD$1;
+              state.mode = BAD;
               break;
             }
             while (copy--) {
@@ -77950,12 +75242,12 @@ const inflate = (strm, flush) => {
         }
 
         /* handle error breaks in while */
-        if (state.mode === BAD$1) { break; }
+        if (state.mode === BAD) { 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;
+          state.mode = BAD;
           break;
         }
 
@@ -77965,7 +75257,7 @@ const inflate = (strm, flush) => {
         state.lenbits = 9;
 
         opts = { bits: state.lenbits };
-        ret = inftrees(LENS$1, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);
+        ret = inftrees(LENS, 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;
@@ -77973,7 +75265,7 @@ const inflate = (strm, flush) => {
 
         if (ret) {
           strm.msg = 'invalid literal/lengths set';
-          state.mode = BAD$1;
+          state.mode = BAD;
           break;
         }
 
@@ -77982,7 +75274,7 @@ const inflate = (strm, flush) => {
         // 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);
+        ret = inftrees(DISTS, 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;
@@ -77990,7 +75282,7 @@ const inflate = (strm, flush) => {
 
         if (ret) {
           strm.msg = 'invalid distances set';
-          state.mode = BAD$1;
+          state.mode = BAD;
           break;
         }
         //Tracev((stderr, 'inflate:       codes ok\n'));
@@ -78022,7 +75314,7 @@ const inflate = (strm, flush) => {
           bits = state.bits;
           //---
 
-          if (state.mode === TYPE$1) {
+          if (state.mode === TYPE) {
             state.back = -1;
           }
           break;
@@ -78083,12 +75375,12 @@ const inflate = (strm, flush) => {
         if (here_op & 32) {
           //Tracevv((stderr, "inflate:         end of block\n"));
           state.back = -1;
-          state.mode = TYPE$1;
+          state.mode = TYPE;
           break;
         }
         if (here_op & 64) {
           strm.msg = 'invalid literal/length code';
-          state.mode = BAD$1;
+          state.mode = BAD;
           break;
         }
         state.extra = here_op & 15;
@@ -78163,7 +75455,7 @@ const inflate = (strm, flush) => {
         state.back += here_bits;
         if (here_op & 64) {
           strm.msg = 'invalid distance code';
-          state.mode = BAD$1;
+          state.mode = BAD;
           break;
         }
         state.offset = here_val;
@@ -78191,7 +75483,7 @@ const inflate = (strm, flush) => {
 //#ifdef INFLATE_STRICT
         if (state.offset > state.dmax) {
           strm.msg = 'invalid distance too far back';
-          state.mode = BAD$1;
+          state.mode = BAD;
           break;
         }
 //#endif
@@ -78206,7 +75498,7 @@ const inflate = (strm, flush) => {
           if (copy > state.whave) {
             if (state.sane) {
               strm.msg = 'invalid distance too far back';
-              state.mode = BAD$1;
+              state.mode = BAD;
               break;
             }
 // (!) This block is disabled in zlib defaults,
@@ -78278,7 +75570,7 @@ const inflate = (strm, flush) => {
           // 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;
+            state.mode = BAD;
             break;
           }
           //=== INITBITS();
@@ -78301,7 +75593,7 @@ const inflate = (strm, flush) => {
           //===//
           if (hold !== (state.total & 0xffffffff)) {
             strm.msg = 'incorrect length check';
-            state.mode = BAD$1;
+            state.mode = BAD;
             break;
           }
           //=== INITBITS();
@@ -78313,13 +75605,13 @@ const inflate = (strm, flush) => {
         state.mode = DONE;
         /* falls through */
       case DONE:
-        ret = Z_STREAM_END$2;
+        ret = Z_STREAM_END$1;
         break inf_leave;
-      case BAD$1:
+      case BAD:
         ret = Z_DATA_ERROR$1;
         break inf_leave;
       case MEM:
-        return Z_MEM_ERROR;
+        return Z_MEM_ERROR$1;
       case SYNC:
         /* falls through */
       default:
@@ -78345,8 +75637,8 @@ const inflate = (strm, flush) => {
   state.bits = bits;
   //---
 
-  if (state.wsize || (_out !== strm.avail_out && state.mode < BAD$1 &&
-                      (state.mode < CHECK || flush !== Z_FINISH$2))) {
+  if (state.wsize || (_out !== strm.avail_out && state.mode < BAD &&
+                      (state.mode < CHECK || flush !== Z_FINISH$1))) {
     if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) ;
   }
   _in -= strm.avail_in;
@@ -78359,10 +75651,10 @@ const inflate = (strm, flush) => {
       (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 === TYPE ? 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;
+  if (((_in === 0 && _out === 0) || flush === Z_FINISH$1) && ret === Z_OK$1) {
+    ret = Z_BUF_ERROR;
   }
   return ret;
 };
@@ -78379,7 +75671,7 @@ const inflateEnd = (strm) => {
     state.window = null;
   }
   strm.state = null;
-  return Z_OK$2;
+  return Z_OK$1;
 };
 
 
@@ -78393,7 +75685,7 @@ const inflateGetHeader = (strm, head) => {
   /* save header structure */
   state.head = head;
   head.done = false;
-  return Z_OK$2;
+  return Z_OK$1;
 };
 
 
@@ -78426,11 +75718,11 @@ const inflateSetDictionary = (strm, dictionary) => {
   ret = updatewindow(strm, dictionary, dictLength, dictLength);
   if (ret) {
     state.mode = MEM;
-    return Z_MEM_ERROR;
+    return Z_MEM_ERROR$1;
   }
   state.havedict = 1;
   // Tracev((stderr, "inflate:   dictionary set\n"));
-  return Z_OK$2;
+  return Z_OK$1;
 };
 
 
@@ -78439,7 +75731,7 @@ var inflateReset2_1 = inflateReset2;
 var inflateResetKeep_1 = inflateResetKeep;
 var inflateInit_1 = inflateInit;
 var inflateInit2_1 = inflateInit2;
-var inflate_2 = inflate;
+var inflate_2$1 = inflate$2;
 var inflateEnd_1 = inflateEnd;
 var inflateGetHeader_1 = inflateGetHeader;
 var inflateSetDictionary_1 = inflateSetDictionary;
@@ -78455,13 +75747,13 @@ module.exports.inflateSyncPoint = inflateSyncPoint;
 module.exports.inflateUndermine = inflateUndermine;
 */
 
-var inflate_1 = {
+var inflate_1$2 = {
        inflateReset: inflateReset_1,
        inflateReset2: inflateReset2_1,
        inflateResetKeep: inflateResetKeep_1,
        inflateInit: inflateInit_1,
        inflateInit2: inflateInit2_1,
-       inflate: inflate_2,
+       inflate: inflate_2$1,
        inflateEnd: inflateEnd_1,
        inflateGetHeader: inflateGetHeader_1,
        inflateSetDictionary: inflateSetDictionary_1,
@@ -78525,15 +75817,15 @@ function GZheader() {
 
 var gzheader = GZheader;
 
-const toString$1 = Object.prototype.toString;
+const toString = 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;
+  Z_NO_FLUSH, Z_FINISH,
+  Z_OK, Z_STREAM_END, Z_NEED_DICT, Z_STREAM_ERROR, Z_DATA_ERROR, Z_MEM_ERROR
+} = constants$2;
 
 /* ===========================================================================*/
 
@@ -78615,7 +75907,7 @@ const {
  * console.log(inflate.result);
  * ```
  **/
-function Inflate(options) {
+function Inflate$1(options) {
   this.options = common.assign({
     chunkSize: 1024 * 64,
     windowBits: 15,
@@ -78655,30 +75947,30 @@ function Inflate(options) {
   this.strm   = new zstream();
   this.strm.avail_out = 0;
 
-  let status  = inflate_1.inflateInit2(
+  let status  = inflate_1$2.inflateInit2(
     this.strm,
     opt.windowBits
   );
 
-  if (status !== Z_OK$3) {
+  if (status !== Z_OK) {
     throw new Error(messages[status]);
   }
 
   this.header = new gzheader();
 
-  inflate_1.inflateGetHeader(this.strm, this.header);
+  inflate_1$2.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]') {
+    } else if (toString.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) {
+      status = inflate_1$2.inflateSetDictionary(this.strm, opt.dictionary);
+      if (status !== Z_OK) {
         throw new Error(messages[status]);
       }
     }
@@ -78710,7 +76002,7 @@ function Inflate(options) {
  * push(chunk, true);  // push last chunk
  * ```
  **/
-Inflate.prototype.push = function (data, flush_mode) {
+Inflate$1.prototype.push = function (data, flush_mode) {
   const strm = this.strm;
   const chunkSize = this.options.chunkSize;
   const dictionary = this.options.dictionary;
@@ -78719,10 +76011,10 @@ Inflate.prototype.push = function (data, flush_mode) {
   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;
+  else _flush_mode = flush_mode === true ? Z_FINISH : Z_NO_FLUSH;
 
   // Convert data if needed
-  if (toString$1.call(data) === '[object ArrayBuffer]') {
+  if (toString.call(data) === '[object ArrayBuffer]') {
     strm.input = new Uint8Array(data);
   } else {
     strm.input = data;
@@ -78738,34 +76030,34 @@ Inflate.prototype.push = function (data, flush_mode) {
       strm.avail_out = chunkSize;
     }
 
-    status = inflate_1.inflate(strm, _flush_mode);
+    status = inflate_1$2.inflate(strm, _flush_mode);
 
-    if (status === Z_NEED_DICT$1 && dictionary) {
-      status = inflate_1.inflateSetDictionary(strm, dictionary);
+    if (status === Z_NEED_DICT && dictionary) {
+      status = inflate_1$2.inflateSetDictionary(strm, dictionary);
 
-      if (status === Z_OK$3) {
-        status = inflate_1.inflate(strm, _flush_mode);
-      } else if (status === Z_DATA_ERROR$2) {
+      if (status === Z_OK) {
+        status = inflate_1$2.inflate(strm, _flush_mode);
+      } else if (status === Z_DATA_ERROR) {
         // Replace code with more verbose
-        status = Z_NEED_DICT$1;
+        status = Z_NEED_DICT;
       }
     }
 
     // Skip snyc markers if more data follows and not raw mode
     while (strm.avail_in > 0 &&
-           status === Z_STREAM_END$3 &&
+           status === Z_STREAM_END &&
            strm.state.wrap > 0 &&
            data[strm.next_in] !== 0)
     {
-      inflate_1.inflateReset(strm);
-      status = inflate_1.inflate(strm, _flush_mode);
+      inflate_1$2.inflateReset(strm);
+      status = inflate_1$2.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:
+      case Z_STREAM_ERROR:
+      case Z_DATA_ERROR:
+      case Z_NEED_DICT:
+      case Z_MEM_ERROR:
         this.onEnd(status);
         this.ended = true;
         return false;
@@ -78776,7 +76068,7 @@ Inflate.prototype.push = function (data, flush_mode) {
     last_avail_out = strm.avail_out;
 
     if (strm.next_out) {
-      if (strm.avail_out === 0 || status === Z_STREAM_END$3) {
+      if (strm.avail_out === 0 || status === Z_STREAM_END) {
 
         if (this.options.to === 'string') {
 
@@ -78799,11 +76091,11 @@ Inflate.prototype.push = function (data, flush_mode) {
     }
 
     // Must repeat iteration if out buffer is full
-    if (status === Z_OK$3 && last_avail_out === 0) continue;
+    if (status === Z_OK && last_avail_out === 0) continue;
 
     // Finalize if end of stream reached.
-    if (status === Z_STREAM_END$3) {
-      status = inflate_1.inflateEnd(this.strm);
+    if (status === Z_STREAM_END) {
+      status = inflate_1$2.inflateEnd(this.strm);
       this.onEnd(status);
       this.ended = true;
       return true;
@@ -78824,7 +76116,7 @@ Inflate.prototype.push = function (data, flush_mode) {
  * 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) {
+Inflate$1.prototype.onData = function (chunk) {
   this.chunks.push(chunk);
 };
 
@@ -78838,9 +76130,9 @@ Inflate.prototype.onData = function (chunk) {
  * complete (Z_FINISH). By default - join collected chunks,
  * free memory and fill `results` / `err` properties.
  **/
-Inflate.prototype.onEnd = function (status) {
+Inflate$1.prototype.onEnd = function (status) {
   // On success - join
-  if (status === Z_OK$3) {
+  if (status === Z_OK) {
     if (this.options.to === 'string') {
       this.result = this.chunks.join('');
     } else {
@@ -78887,13 +76179,13 @@ Inflate.prototype.onEnd = function (status) {
  *
  * try {
  *   output = pako.inflate(input);
- * } catch (err)
+ * } catch (err) {
  *   console.log(err);
  * }
  * ```
  **/
 function inflate$1(input, options) {
-  const inflator = new Inflate(options);
+  const inflator = new Inflate$1(options);
 
   inflator.push(input);
 
@@ -78912,7 +76204,7 @@ function inflate$1(input, options) {
  * The same as [[inflate]], but creates raw data, without wrapper
  * (header and adler32 crc).
  **/
-function inflateRaw(input, options) {
+function inflateRaw$1(input, options) {
   options = options || {};
   options.raw = true;
   return inflate$1(input, options);
@@ -78929,25 +76221,28 @@ function inflateRaw(input, options) {
  **/
 
 
-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$1;
+var inflate_2 = inflate$1;
+var inflateRaw_1$1 = inflateRaw$1;
+var ungzip$1 = inflate$1;
+var constants = constants$2;
 
 var inflate_1$1 = {
-       Inflate: Inflate_1,
-       inflate: inflate_2$1,
-       inflateRaw: inflateRaw_1,
-       ungzip: ungzip,
-       constants: constants$2
+       Inflate: Inflate_1$1,
+       inflate: inflate_2,
+       inflateRaw: inflateRaw_1$1,
+       ungzip: ungzip$1,
+       constants: constants
 };
 
-const { Inflate: Inflate$1, inflate: inflate$2, inflateRaw: inflateRaw$1, ungzip: ungzip$1 } = inflate_1$1;
-var inflate_1$2 = inflate$2;
+const { Inflate, inflate, inflateRaw, ungzip } = inflate_1$1;
+var inflate_1 = inflate;
+
+var ieee754$1 = {};
 
 /*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
-var read = function (buffer, offset, isLE, mLen, nBytes) {
+
+ieee754$1.read = function (buffer, offset, isLE, mLen, nBytes) {
   var e, m;
   var eLen = (nBytes * 8) - mLen - 1;
   var eMax = (1 << eLen) - 1;
@@ -78980,7 +76275,7 @@ var read = function (buffer, offset, isLE, mLen, nBytes) {
   return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
 };
 
-var write = function (buffer, value, offset, isLE, mLen, nBytes) {
+ieee754$1.write = function (buffer, value, offset, isLE, mLen, nBytes) {
   var e, m, c;
   var eLen = (nBytes * 8) - mLen - 1;
   var eMax = (1 << eLen) - 1;
@@ -79032,14 +76327,9 @@ var write = function (buffer, value, offset, isLE, mLen, nBytes) {
   buffer[offset + i - d] |= s * 128;
 };
 
-var ieee754 = {
-       read: read,
-       write: write
-};
-
 var pbf = Pbf;
 
-
+var ieee754 = ieee754$1;
 
 function Pbf(buf) {
     this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0);
@@ -79689,7 +76979,7 @@ function writeUtf8(buf, str, pos) {
  * @returns {Object} Parsed object.
  */
 function decompress(buffer) {
-    const inflated = inflate_1$2(buffer, { to: "string" });
+    const inflated = inflate_1(buffer, { to: "string" });
     return JSON.parse(inflated);
 }
 /**
@@ -79770,131 +77060,6 @@ function readMeshPbfField(tag, mesh, pbf) {
     }
 }
 
-/**
- * @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
  *
@@ -79917,20 +77082,17 @@ class DataProviderBase extends EventEmitter {
     /**
      * Create a new data provider base instance.
      *
-     * @param {GeometryProviderBase} geometry - Geometry
+     * @param {IGeometryProvider} 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.
+     * @returns {IGeometryProvider} Geometry provider instance.
      */
     get geometry() {
         return this._geometry;
@@ -80061,6 +77223,135 @@ class DataProviderBase extends EventEmitter {
     }
 }
 
+/**
+ * @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];
+    }
+}
+
+var s2geometry = {exports: {}};
+
+var long = {exports: {}};
+
 /*
  Copyright 2013 Daniel Wirtz <dcode@dcode.io>
  Copyright 2009 The Closure Library Authors. All Rights Reserved.
@@ -80078,7 +77369,7 @@ class DataProviderBase extends EventEmitter {
  limitations under the License.
  */
 
-var long = createCommonjsModule(function (module) {
+(function (module) {
 /**
  * @license long.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
  * Released under the Apache License, Version 2.0
@@ -81268,9 +78559,9 @@ var long = createCommonjsModule(function (module) {
 
     return Long;
 });
-});
+}(long));
 
-var s2geometry = createCommonjsModule(function (module) {
+(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
@@ -81716,7 +79007,7 @@ 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 Long = exports.dcodeIO && exports.dcodeIO.Long || long.exports;
   var faceB;
   var posB;
   var bin;
@@ -81764,7 +79055,7 @@ S2.idToKey = S2.S2Cell.idToKey
 = S2.fromId = S2.fromCellId
 = S2.S2Cell.toHilbertQuadkey  = S2.toHilbertQuadkey
 = function (idS) {
-  var Long = exports.dcodeIO && exports.dcodeIO.Long || long;
+  var Long = exports.dcodeIO && exports.dcodeIO.Long || long.exports;
   var bin = Long.fromString(idS, true, 10).toString(2);
 
   while (bin.length < (S2.FACE_BITS + S2.POS_BITS)) {
@@ -81824,7 +79115,7 @@ S2.S2Cell.latLngToKey = S2.latLngToKey
 };
 
 S2.stepKey = function (key, num) {
-  var Long = exports.dcodeIO && exports.dcodeIO.Long || long;
+  var Long = exports.dcodeIO && exports.dcodeIO.Long || long.exports;
   var parts = key.split('/');
 
   var faceS = parts[0];
@@ -81863,7 +79154,7 @@ S2.S2Cell.nextKey = S2.nextKey = function (key) {
 };
 
 })(module.exports );
-});
+}(s2geometry));
 
 /**
  * @class S2GeometryProvider
@@ -81894,7 +79185,7 @@ class S2GeometryProvider extends GeometryProviderBase {
     }
     /** @inheritdoc */
     getAdjacent(cellId) {
-        const k = s2geometry.S2.idToKey(cellId);
+        const k = s2geometry.exports.S2.idToKey(cellId);
         const position = k.split('/')[1];
         const level = position.length;
         const [a0, a1, a2, a3] = this._getNeighbors(k, level);
@@ -81920,12 +79211,12 @@ class S2GeometryProvider extends GeometryProviderBase {
                 adjacent.push(other);
             }
         }
-        return adjacent.map((a) => s2geometry.S2.keyToId(a));
+        return adjacent.map((a) => s2geometry.exports.S2.keyToId(a));
     }
     /** @inheritdoc */
     getVertices(cellId) {
-        const key = s2geometry.S2.idToKey(cellId);
-        const cell = s2geometry.S2.S2Cell.FromHilbertQuadKey(key);
+        const key = s2geometry.exports.S2.idToKey(cellId);
+        const cell = s2geometry.exports.S2.S2Cell.FromHilbertQuadKey(key);
         return cell
             .getCornerLatLngs()
             .map((c) => {
@@ -81937,13 +79228,13 @@ class S2GeometryProvider extends GeometryProviderBase {
         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);
+        const latlng = s2geometry.exports.S2.keyToLatLng(s2key);
+        const neighbors = s2geometry.exports.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);
+        const s2key = s2geometry.exports.S2.latLngToKey(lngLat.lat, lngLat.lng, level);
+        return s2geometry.exports.S2.keyToId(s2key);
     }
 }
 
@@ -82199,3667 +79490,4611 @@ class GraphDataProvider extends DataProviderBase {
             return result;
         });
     }
-    setAccessToken(accessToken) {
-        this._accessToken = accessToken;
+    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$;
     }
-    _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}`,
-            });
+    /**
+     * @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;
         }
-        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);
-        });
+    /**
+     * 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);
     }
-    _makeErrorMessage(graphError) {
-        const error = graphError.error;
-        const message = error ?
-            `${error.code} (${error.type}, ${error.fbtrace_id}): ${error.message}` :
-            "Failed to fetch data";
-        return message;
+    /**
+     * 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);
     }
-}
-
-/**
- * @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;
+    /**
+     * 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);
     }
     /**
-     * Get id.
-     * @returns {string} The id of the marker.
+     * 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]);
+     * ```
      */
-    get id() {
-        return this._id;
+    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);
     }
     /**
-     * Get geometry.
+     * Sets the popup's content to a string of text.
      *
-     * @ignore
+     * @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]);
+     * ```
      */
-    get geometry() {
-        return this._geometry;
+    setText(text) {
+        this.setDOMContent(this._dom.document.createTextNode(text));
     }
     /**
-     * Get lngLat.
-     * @returns {LngLat} The geographic coordinates of the marker.
+     * @description Internal method for attaching the popup to
+     * its parent container so that it is rendered in the DOM tree.
+     * @ignore
      */
-    get lngLat() {
-        return this._lngLat;
+    setParentContainer(parentContainer) {
+        this._parentContainer = parentContainer;
     }
-    /** @ignore */
-    createGeometry(position) {
-        if (!!this._geometry) {
+    /**
+     * @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;
         }
-        this._createGeometry(position);
-        // update matrix world if raycasting occurs before first render
-        this._geometry.updateMatrixWorld(true);
-    }
-    /** @ignore */
-    disposeGeometry() {
-        if (!this._geometry) {
+        if (!this._point && !this._rect) {
             return;
         }
-        this._disposeGeometry();
-        this._geometry = undefined;
+        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();
     }
-    /** @ignore */
-    getInteractiveObjects() {
-        if (!this._geometry) {
-            return [];
-        }
-        return this._getInteractiveObjects();
+    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;
     }
-    /** @ignore */
-    lerpAltitude(alt, alpha) {
-        if (!this._geometry) {
-            return;
-        }
-        this._geometry.position.z =
-            (1 - alpha) * this._geometry.position.z + alpha * alt;
+    const context = canvas.getContext("webgl", attributes) ||
+        canvas
+            .getContext("experimental-webgl", attributes);
+    if (!context) {
+        return false;
     }
-    /** @ignore */
-    updatePosition(position, lngLat) {
-        if (!!lngLat) {
-            this._lngLat.lat = lngLat.lat;
-            this._lngLat.lng = lngLat.lng;
-        }
-        if (!this._geometry) {
-            return;
+    const requiredExtensions = ["OES_standard_derivatives"];
+    const supportedExtensions = context.getSupportedExtensions();
+    for (const requiredExtension of requiredExtensions) {
+        if (supportedExtensions.indexOf(requiredExtension) === -1) {
+            return false;
         }
-        this._geometry.position.fromArray(position);
-        this._geometry.updateMatrixWorld(true);
     }
+    return 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
+ * Test whether the current browser supports the full
+ * functionality of MapillaryJS.
  *
- * @example
- * ```js
- * var defaultMarker = new CircleMarker(
- *     "id-1",
- *     { lat: 0, lng: 0, });
+ * @description The full functionality includes WebGL rendering.
  *
- * var configuredMarker = new CircleMarker(
- *     "id-2",
- *     { lat: 0, lng: 0, },
- *     {
- *         color: "#0ff",
- *         opacity: 0.3,
- *         radius: 0.7,
- *     });
+ * @return {boolean}
  *
- * markerComponent.add([defaultMarker, configuredMarker]);
- * ```
+ * @example `var supported = isSupported();`
  */
-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 [];
-    }
+function isSupported() {
+    return isFallbackSupported() &&
+        isWebGLSupportedCached();
 }
-
 /**
- * @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
+ * Test whether the current browser supports the fallback
+ * functionality of MapillaryJS.
  *
- * @example
- * ```js
- * var defaultMarker = new SimpleMarker(
- *     "id-1",
- *     { lat: 0, lng: 0, });
+ * @description The fallback functionality does not include WebGL
+ * rendering, only 2D canvas rendering.
  *
- * 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,
- *     });
+ * @return {boolean}
  *
- * markerComponent.add([defaultMarker, interactiveMarker]);
- * ```
+ * @example `var fallbackSupported = isFallbackSupported();`
  */
-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;
-    }
+function isFallbackSupported() {
+    return isBrowser() &&
+        isArraySupported() &&
+        isBlobSupported() &&
+        isFunctionSupported() &&
+        isJSONSupported() &&
+        isMapSupported() &&
+        isObjectSupported() &&
+        isPromiseSupported() &&
+        isSetSupported();
 }
 
 /**
- * @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]);
+ * Enumeration for camera controls.
  *
- * popupComponent.add([defaultPopup, cleanPopup]);
- * ```
+ * @description Specifies different modes for how the
+ * camera is controlled through pointer, keyboard or
+ * other modes of input.
  *
- * @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
+ * @enum {number}
+ * @readonly
  */
-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();
-    }
+var CameraControls;
+(function (CameraControls) {
     /**
-     * @description Internal observable used by the component to
-     * render the popup when its position or content has changed.
-     * @ignore
+     * Control the camera with custom logic by
+     * attaching a custom camera controls
+     * instance to the {@link Viewer}.
      */
-    get changed$() {
-        return this._notifyChanged$;
-    }
+    CameraControls[CameraControls["Custom"] = 0] = "Custom";
     /**
-     * @description Internal method used by the component to
-     * remove all references to the popup.
-     * @ignore
+     * Control the camera from a birds perspective
+     * to get an overview.
      */
-    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;
-        }
-    }
+    CameraControls[CameraControls["Earth"] = 1] = "Earth";
     /**
-     * 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]);
-     * ```
+     * Control the camera in a first person view
+     * from the street level perspective.
      */
-    setBasicPoint(basicPoint) {
-        this._point = basicPoint.slice();
-        this._rect = null;
-        this._notifyChanged$.next(this);
-    }
+    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) {
     /**
-     * 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]);
+     * Displays all content within the viewer.
      *
-     * popupComponent.add([popup]);
-     * ```
+     * @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.
      */
-    setBasicRect(basicRect) {
-        this._rect = basicRect.slice();
-        this._point = null;
-        this._notifyChanged$.next(this);
-    }
+    RenderMode[RenderMode["Letterbox"] = 0] = "Letterbox";
     /**
-     * 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.
+     * Fills the viewer by cropping content.
      *
-     * @example
-     * ```js
-     * var div = document.createElement('div');
-     * div.innerHTML = 'hello image';
+     * @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 = {}));
+
+/**
+ * Enumeration for transition mode
+ * @enum {number}
+ * @readonly
+ * @description Modes for specifying how transitions
+ * between images are performed.
+ */
+var TransitionMode;
+(function (TransitionMode) {
+    /**
+     * Default transitions.
      *
-     * var popup = new Popup();
-     * popup.setDOMContent(div);
-     * popup.setBasicPoint([0.3, 0.3]);
+     * @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.
      *
-     * popupComponent.add([popup]);
-     * ```
+     * @description All transitions are performed
+     * without motion or blending.
      */
-    setDOMContent(htmlNode) {
-        if (this._content && this._content.parentNode) {
-            this._content.parentNode.removeChild(this._content);
+    TransitionMode[TransitionMode["Instantaneous"] = 1] = "Instantaneous";
+})(TransitionMode || (TransitionMode = {}));
+
+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();
         }
-        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);
+        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);
     }
-    /**
-     * 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);
+    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();
         }
-        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));
+    _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");
     }
-    /**
-     * @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;
+    _initilizeCoverComponent() {
+        let options = this._options;
+        this._coverComponent.configure({ id: this._key });
+        if (options.cover === undefined || options.cover) {
+            this.activateCover();
+        }
+        else {
+            this.deactivateCover();
+        }
     }
-    /**
-     * @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) {
+    _setNavigable(navigable) {
+        if (this._navigable === navigable) {
             return;
         }
-        if (!this._point && !this._rect) {
+        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 (!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);
+        if (typeof option === "boolean") {
+            if (option) {
+                this._componentService.activate(name);
             }
-            this._container.appendChild(this._content);
-            this._parentContainer.appendChild(this._container);
-            if (this._options.opacity != null) {
-                this._container.style.opacity = this._options.opacity.toString();
+            else {
+                this._componentService.deactivate(name);
             }
+            return;
         }
-        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);
+        this._componentService.configure(name, option);
+        this._componentService.activate(name);
+    }
+    _uTrue(option, name) {
+        if (option === undefined) {
+            this._componentService.activate(name);
+            return;
         }
-        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;
-                }
+        if (typeof option === "boolean") {
+            if (option) {
+                this._componentService.activate(name);
             }
-            [pointPixel, position] = this._rectToPixel(this._rect, position, appliedPosition, renderCamera, size, transform);
-            if (!float) {
-                float = position;
+            else {
+                this._componentService.deactivate(name);
             }
-        }
-        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)`;
+        this._componentService.configure(name, option);
+        this._componentService.activate(name);
     }
-    _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],
+}
+
+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 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) {
+        }));
+        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;
                 }
-                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];
+                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;
                 }
-                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;
+                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);
                 }
             }
-            if (largestVisibleArea[0] > 0) {
-                return [largestVisibleArea[1], largestVisibleArea[2]];
+            const renderer = co.renderer.renderer;
+            renderer.resetState();
+            renderer.setClearColor(clearColor, 1.0);
+            renderer.clear();
+            for (const renderBackground of backgroundRenders) {
+                renderBackground(perspectiveCamera, renderer);
             }
-        }
-        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],
+            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;
             };
-        }
-        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],
+        }))
+            .subscribe(this._renderCameraOperation$));
+        this._renderFrameSubscribe();
+        const renderHash$ = this._render$.pipe(map((hash) => {
+            return (hashes) => {
+                hashes[hash.name] = hash.renderer;
+                return hashes;
             };
-        }
-    }
-    _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;
+        }));
+        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$));
     }
-    _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];
-        }
+    get render$() {
+        return this._render$;
     }
-}
-
-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();
+    get opaqueRender$() {
+        return this._opaqueRender$;
     }
-    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;
+    get webGLRenderer$() {
+        return this._webGLRenderer$;
     }
-    const context = canvas.getContext("webgl", attributes) ||
-        canvas
-            .getContext("experimental-webgl", attributes);
-    if (!context) {
-        return false;
+    clear(name) {
+        this._clear$.next(name);
     }
-    const requiredExtensions = ["OES_standard_derivatives"];
-    const supportedExtensions = context.getSupportedExtensions();
-    for (const requiredExtension of requiredExtensions) {
-        if (supportedExtensions.indexOf(requiredExtension) === -1) {
-            return false;
+    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$);
     }
-    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.
+ * @class Camera
  *
- * @enum {number}
- * @readonly
+ * @classdesc Holds information about a camera.
  */
-var CameraControls;
-(function (CameraControls) {
+class Camera {
     /**
-     * Control the camera with custom logic by
-     * attaching a custom camera controls
-     * instance to the {@link Viewer}.
+     * Create a new camera instance.
+     * @param {Transform} [transform] - Optional transform instance.
      */
-    CameraControls[CameraControls["Custom"] = 0] = "Custom";
+    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;
+        }
+    }
     /**
-     * Control the camera from a birds perspective
-     * to get an overview.
+     * Get position.
+     * @returns {THREE.Vector3} The position vector.
      */
-    CameraControls[CameraControls["Earth"] = 1] = "Earth";
+    get position() {
+        return this._position;
+    }
     /**
-     * Control the camera in a first person view
-     * from the street level perspective.
+     * Get lookat.
+     * @returns {THREE.Vector3} The lookat vector.
      */
-    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) {
+    get lookat() {
+        return this._lookat;
+    }
     /**
-     * Displays all content within the viewer.
+     * 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.
      *
-     * @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.
+     * @param {Camera} a - First camera.
+     * @param {Camera} b - Second camera.
+     * @param {number} alpha - Interpolation value on the interval [0, 1].
      */
-    RenderMode[RenderMode["Letterbox"] = 0] = "Letterbox";
+    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;
+    }
     /**
-     * Fills the viewer by cropping content.
+     * Copy the properties of another camera to this camera.
      *
-     * @description Cropping is done either
-     * in horizontal or vertical direction
-     * depending on the aspect ratio relation
-     * between the image and the viewer.
+     * @param {Camera} other - Another camera.
      */
-    RenderMode[RenderMode["Fill"] = 1] = "Fill";
-})(RenderMode || (RenderMode = {}));
-
-var RenderPass;
-(function (RenderPass) {
+    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);
+    }
     /**
-     * Occurs after the background render pass.
+     * 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.
      */
-    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();
-            });
+    _getFocal(transform) {
+        if (!isSpherical(transform.cameraType)) {
+            return transform.focal;
         }
+        return 0.5 / Math.tan(Math.PI / 2);
     }
-    get navigable() {
-        return this._navigable;
+}
+
+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._stateTransitionAlpha = -1;
+        this._stateTransitionFov = -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.1, 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(name) {
-        return this._componentService.get(name);
+    get alpha() {
+        return this._alpha;
     }
-    activate(name) {
-        this._componentService.activate(name);
+    get camera() {
+        return this._camera;
     }
-    activateCover() {
-        this._coverComponent.configure({ state: CoverState.Visible });
+    get changed() {
+        return this._frameId === this._changedForFrame;
     }
-    deactivate(name) {
-        this._componentService.deactivate(name);
+    get frameId() {
+        return this._frameId;
     }
-    deactivateCover() {
-        this._coverComponent.configure({ state: CoverState.Loading });
+    get perspective() {
+        return this._perspective;
     }
-    remove() {
-        this._componentService.remove();
-        if (this._configurationSubscription != null) {
-            this._configurationSubscription.unsubscribe();
-        }
+    get renderMode() {
+        return this._renderMode;
     }
-    _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");
+    get rotation() {
+        return this._rotation;
     }
-    _initilizeCoverComponent() {
-        let options = this._options;
-        this._coverComponent.configure({ id: this._key });
-        if (options.cover === undefined || options.cover) {
-            this.activateCover();
-        }
-        else {
-            this.deactivateCover();
-        }
+    get zoom() {
+        return this._zoom;
     }
-    _setNavigable(navigable) {
-        if (this._navigable === navigable) {
-            return;
-        }
-        this._navigable = navigable;
-        this._observer.navigable$.next(navigable);
+    get size() {
+        return this._size;
     }
-    _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);
-                }
-            });
+    getTilt() {
+        return 90 - this._spatial.radToDeg(this._rotation.theta);
     }
-    _uFalse(option, name) {
-        if (option === undefined) {
-            this._componentService.deactivate(name);
-            return;
+    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);
+            }
+            if (this._state === State.Earth) {
+                const y = this._fovToY(this._perspective.fov, this._zoom);
+                this._stateTransitionFov = this._yToFov(y, 0);
+            }
+            this._changed = true;
         }
-        if (typeof option === "boolean") {
-            if (option) {
-                this._componentService.activate(name);
+        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._changed = true;
+        }
+        if (this._changed) {
+            this._currentFov = this._computeCurrentFov(zoom);
+            this._previousFov = this._computePreviousFov(zoom);
+        }
+        const alpha = state.alpha;
+        const sta = state.stateTransitionAlpha;
+        if (this._changed ||
+            alpha !== this._alpha ||
+            sta !== this._stateTransitionAlpha) {
+            this._alpha = alpha;
+            this._stateTransitionAlpha = sta;
+            switch (this._state) {
+                case State.Earth: {
+                    const startFov = this._stateTransitionFov;
+                    const endFov = this._focalToFov(state.camera.focal);
+                    const fov = MathUtils.lerp(startFov, endFov, sta);
+                    const y = this._fovToY(fov, 0);
+                    this._perspective.fov = this._yToFov(y, zoom);
+                    break;
+                }
+                case State.Custom:
+                    break;
+                default:
+                    this._perspective.fov =
+                        this._interpolateFov(this._currentFov, this._previousFov, this._alpha);
+                    this._changed = true;
+                    break;
             }
-            else {
-                this._componentService.deactivate(name);
+            this._zoom = zoom;
+            if (this._state !== State.Custom) {
+                this._perspective.updateProjectionMatrix();
             }
-            return;
         }
-        this._componentService.configure(name, option);
-        this._componentService.activate(name);
+        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);
     }
-    _uTrue(option, name) {
-        if (option === undefined) {
-            this._componentService.activate(name);
+    setProjectionMatrix(matrix) {
+        this._perspective.fov = this._focalToFov(matrix[5] / 2);
+        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;
         }
-        if (typeof option === "boolean") {
-            if (option) {
-                this._componentService.activate(name);
-            }
-            else {
-                this._componentService.deactivate(name);
-            }
+        this._perspective.fov = this._computeFov();
+        this._perspective.updateProjectionMatrix();
+        this._changed = true;
+    }
+    setSize(size) {
+        this._size = size;
+        if (this._state === State.Custom) {
             return;
         }
-        this._componentService.configure(name, option);
-        this._componentService.activate(name);
+        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;
+    }
+    _focalToFov(focal) {
+        return 2 * Math.atan2(1, 2 * focal) * 180 / Math.PI;
+    }
+    _fovToY(fov, zoom) {
+        return Math.pow(2, zoom) * Math.tan(Math.PI * fov / 360);
+    }
+    _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 DOMRenderer {
-    constructor(element, renderService, currentFrame$) {
-        this._adaptiveOperation$ = new Subject();
-        this._render$ = new Subject();
-        this._renderAdaptive$ = new Subject();
+class RenderService {
+    constructor(element, currentFrame$, renderMode, renderCamera) {
         this._subscriptions = new SubscriptionHolder();
-        this._renderService = renderService;
+        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;
-        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;
-                }
-            }
+        subs.push(this._resize$.pipe(map(() => {
             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;
+                height: this._element.offsetHeight,
+                width: this._element.offsetWidth,
             };
         }))
-            .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._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._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._renderCameraOperation$));
+        subs.push(this._renderMode$.pipe(skip(1), map((rm) => {
+            return (rc) => {
+                rc.setRenderMode(rm);
+                return rc;
             };
         }))
-            .subscribe(this._adaptiveOperation$));
-        subs.push(this._renderService.renderMode$.pipe(map((renderMode) => {
-            return (adaptive) => {
-                adaptive.renderMode = renderMode;
-                return adaptive;
+            .subscribe(this._renderCameraOperation$));
+        subs.push(this._projectionMatrix$.pipe(map((projectionMatrix) => {
+            return (rc) => {
+                rc.setProjectionMatrix(projectionMatrix);
+                return rc;
             };
         }))
-            .subscribe(this._adaptiveOperation$));
+            .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 element$() {
-        return this._element$;
+    get bearing$() {
+        return this._bearing$;
     }
-    get render$() {
-        return this._render$;
+    get element() {
+        return this._element;
     }
-    get renderAdaptive$() {
-        return this._renderAdaptive$;
+    get projectionMatrix$() {
+        return this._projectionMatrix$;
     }
-    clear(name) {
-        this._renderAdaptive$.next({ name: name, vNode: null });
-        this._render$.next({ name: name, vNode: null });
+    get renderCamera$() {
+        return this._renderCamera$;
     }
-    remove() {
+    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 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();
+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();
-        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);
+        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];
             }
-            renderer.clearDepth();
-            for (const renderOpaque of opaqueRenders) {
-                renderOpaque(perspectiveCamera, renderer);
+            else {
+                claims[claim.name] = claim.deferPixels;
             }
-            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;
+            return claims;
+        }, {}), map((claims) => {
+            let deferPixelMax = -1;
+            for (const key in claims) {
+                if (!claims.hasOwnProperty(key)) {
+                    continue;
                 }
-                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;
+                const deferPixels = claims[key];
+                if (deferPixels > deferPixelMax) {
+                    deferPixelMax = deferPixels;
                 }
-                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();
+            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();
         }));
-        subs.push(renderCollectionEmpty$.pipe(map(() => {
-            return (eraser) => {
-                eraser.needsRender = true;
-                return eraser;
-            };
-        }))
-            .subscribe(this._eraserOperation$));
+        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 render$() {
-        return this._render$;
+    get mouseDrag$() {
+        return this._mouseDrag$;
     }
-    get opaqueRender$() {
-        return this._opaqueRender$;
+    get mouseDragEnd$() {
+        return this._mouseDragEnd$;
     }
-    get webGLRenderer$() {
-        return this._webGLRenderer$;
+    get mouseRightDragStart$() {
+        return this._mouseRightDragStart$;
     }
-    clear(name) {
-        this._clear$.next(name);
+    get mouseRightDrag$() {
+        return this._mouseRightDrag$;
     }
-    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();
+    get mouseRightDragEnd$() {
+        return this._mouseRightDragEnd$;
     }
-    triggerRerender() {
-        this._renderService.renderCameraFrame$
-            .pipe(skip(1), first())
-            .subscribe(() => {
-            this._triggerOperation$.next((trigger) => {
-                trigger.needsRender = true;
-                return trigger;
-            });
-        });
+    get proximateClick$() {
+        return this._proximateClick$;
     }
-    _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$);
+    get staticClick$() {
+        return this._staticClick$;
     }
-}
-
-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 windowBlur$() {
+        return this._windowBlur$;
     }
-    get alpha() {
-        return this._alpha;
+    dispose() {
+        this._subscriptions.unsubscribe();
     }
-    get camera() {
-        return this._camera;
+    claimMouse(name, zindex) {
+        this._claimMouse$.next({ name: name, zindex: zindex });
     }
-    get changed() {
-        return this._frameId === this._changedForFrame;
+    unclaimMouse(name) {
+        this._claimMouse$.next({ name: name, zindex: null });
     }
-    get frameId() {
-        return this._frameId;
+    deferPixels(name, deferPixels) {
+        this._deferPixelClaims$.next({ name: name, deferPixels: deferPixels });
     }
-    get perspective() {
-        return this._perspective;
+    undeferPixels(name) {
+        this._deferPixelClaims$.next({ name: name, deferPixels: null });
     }
-    get renderMode() {
-        return this._renderMode;
+    claimWheel(name, zindex) {
+        this._claimWheel$.next({ name: name, zindex: zindex });
     }
-    get rotation() {
-        return this._rotation;
+    unclaimWheel(name) {
+        this._claimWheel$.next({ name: name, zindex: null });
     }
-    get zoom() {
-        return this._zoom;
+    filtered$(name, observable$) {
+        return this._filtered(name, observable$, this._mouseOwner$);
     }
-    get size() {
-        return this._size;
+    filteredWheel$(name, observable$) {
+        return this._filtered(name, observable$, this._wheelOwner$);
     }
-    getTilt() {
-        return 90 - this._spatial.radToDeg(this._rotation.theta);
+    _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;
+        }));
     }
-    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;
+    _createMouseDrag$(mouseDragStartInitiate$, stop$) {
+        return mouseDragStartInitiate$.pipe(map(([, mouseMove]) => {
+            return mouseMove;
+        }), switchMap((mouseMove) => {
+            return concat(of(mouseMove), this._documentMouseMove$).pipe(takeUntil(stop$));
+        }));
     }
-    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);
+    _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];
             }
-            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;
+            else {
+                claims[claim.name] = claim.zindex;
             }
-            if (this._state !== State.Custom) {
-                this._perspective.updateProjectionMatrix();
+            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;
+                }
             }
-        }
-        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);
+            return owner;
+        }), startWith(null));
     }
-    setProjectionMatrix(matrix) {
-        this._perspective.projectionMatrix.fromArray(matrix);
-        this._perspective.projectionMatrixInverse
-            .copy(this._perspective.projectionMatrix)
-            .invert();
-        this._changed = true;
+    _filtered(name, observable$, owner$) {
+        return observable$.pipe(withLatestFrom(owner$), filter(([, owner]) => {
+            return owner === name;
+        }), map(([item]) => {
+            return item;
+        }));
     }
-    setRenderMode(renderMode) {
-        this._renderMode = renderMode;
-        if (this._state === State.Custom) {
-            return;
+    _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;
         }
-        this._perspective.fov = this._computeFov();
-        this._perspective.updateProjectionMatrix();
-        this._changed = true;
+        return event.button;
     }
-    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;
+    _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;
     }
-    _computeAspect(elementWidth, elementHeight) {
-        return elementWidth === 0 ? 0 : elementWidth / elementHeight;
+    _isMousePen(event) {
+        const type = event.pointerType;
+        return type === "mouse" || type === "pen";
     }
-    _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);
+}
+
+class SpriteAtlas {
+    set json(value) {
+        this._json = value;
     }
-    _computeFov() {
-        this._currentFov = this._computeCurrentFov(this._zoom);
-        this._previousFov = this._computePreviousFov(this._zoom);
-        return this._interpolateFov(this._currentFov, this._previousFov, this._alpha);
+    set image(value) {
+        this._image = value;
+        this._texture = new Texture(this._image);
+        this._texture.minFilter = NearestFilter;
     }
-    _computePreviousFov(zoom) {
-        if (this._perspective.aspect === 0) {
-            return 0;
+    get loaded() {
+        return !!(this._image && this._json);
+    }
+    getGLSprite(name) {
+        if (!this.loaded) {
+            throw new Error("Sprites cannot be retrieved before the atlas is loaded.");
         }
-        if (!this._currentImageId) {
-            return this._initialFov;
+        let definition = this._json[name];
+        if (!definition) {
+            console.warn("Sprite with key" + name + "does not exist in sprite definition.");
+            return new Object3D();
         }
-        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 };
+        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);
     }
-    _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;
+    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, []);
     }
-    _yToFov(y, zoom) {
-        return 2 * Math.atan(y / Math.pow(2, zoom)) * 180 / Math.PI;
+}
+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();
     }
-    _interpolateFov(v1, v2, alpha) {
-        return alpha * v1 + (1 - alpha) * v2;
+    get spriteAtlas$() {
+        return this._spriteAtlas$;
     }
-    _setFrameId(frameId) {
-        this._frameId = frameId;
-        if (this._changed) {
-            this._changed = false;
-            this._changedForFrame = frameId;
-        }
+    dispose() {
+        this._atlasSubscription.unsubscribe();
     }
 }
 
-class RenderService {
-    constructor(element, currentFrame$, renderMode, renderCamera) {
+class TouchService {
+    constructor(canvasContainer, domContainer) {
         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;
+        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._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(() => { }));
+            .subscribe(this._pinchOperation$);
+        subs.push(pinchSubscription);
+        this._pinchChange$ = this._pinchStart$.pipe(switchMap(() => {
+            return this._pinch$.pipe(skip(1), takeUntil(this._pinchEnd$));
+        }));
     }
-    get bearing$() {
-        return this._bearing$;
+    get active$() {
+        return this._active$;
     }
-    get element() {
-        return this._element;
+    get activate$() {
+        return this._activeSubject$;
     }
-    get projectionMatrix$() {
-        return this._projectionMatrix$;
+    get doubleTap$() {
+        return this._doubleTap$;
     }
-    get renderCamera$() {
-        return this._renderCamera$;
+    get touchStart$() {
+        return this._touchStart$;
     }
-    get renderCameraFrame$() {
-        return this._renderCameraFrame$;
+    get touchMove$() {
+        return this._touchMove$;
     }
-    get renderMode$() {
-        return this._renderMode$;
+    get touchEnd$() {
+        return this._touchEnd$;
     }
-    get resize$() {
-        return this._resize$;
+    get touchCancel$() {
+        return this._touchCancel$;
     }
-    get size$() {
-        return this._size$;
+    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 KeyboardService {
-    constructor(canvasContainer) {
-        this._keyDown$ = fromEvent(canvasContainer, "keydown");
-        this._keyUp$ = fromEvent(canvasContainer, "keyup");
+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 keyDown$() {
-        return this._keyDown$;
+    get exploreUrl$() {
+        return this._exploreUrl$;
     }
-    get keyUp$() {
-        return this._keyUp$;
+    get imageTiling$() {
+        return this._imageTiling$;
     }
 }
 
-// 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;
+class Container {
+    constructor(options, stateService, dom) {
+        var _a;
+        this._onWindowResize = () => {
+            if (this._trackResize) {
+                this.renderService.resize$.next();
             }
-            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;
-                }
+        };
+        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.`);
             }
-            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(() => { }));
+        }
+        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 active$() {
-        return this._active$;
+    get canvas() {
+        return !!this._canvas.parentNode ?
+            this._canvas : null;
     }
-    get activate$() {
-        return this._activeSubject$;
+    get canvasContainer() {
+        return this._canvasContainer;
     }
-    get documentMouseMove$() {
-        return this._documentMouseMove$;
+    get container() {
+        return this._container;
+    }
+    get domContainer() {
+        return this._domContainer;
     }
-    get documentMouseUp$() {
-        return this._documentMouseUp$;
+    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");
     }
-    get domMouseDragStart$() {
-        return this._domMouseDragStart$;
+    _removeNode(node) {
+        if (node.parentNode) {
+            node.parentNode.removeChild(node);
+        }
     }
-    get domMouseDrag$() {
-        return this._domMouseDrag$;
+}
+
+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 domMouseDragEnd$() {
-        return this._domMouseDragEnd$;
+    get started() {
+        return this._started;
     }
-    get domMouseDown$() {
-        return this._domMouseDown$;
+    configure(configuration) {
+        if (!configuration) {
+            this._cellDepth = 1;
+            return;
+        }
+        this._cellDepth = Math.max(1, Math.min(3, configuration.cellDepth));
     }
-    get domMouseMove$() {
-        return this._domMouseMove$;
+    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;
     }
-    get mouseOwner$() {
-        return this._mouseOwner$;
+    stop() {
+        if (!this._started) {
+            return;
+        }
+        this._subscriptions.unsubscribe();
+        this._started = false;
     }
-    get mouseDown$() {
-        return this._mouseDown$;
+    _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();
+        }));
     }
-    get mouseEnter$() {
-        return this._mouseEnter$;
+}
+
+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 mouseMove$() {
-        return this._mouseMove$;
+    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());
     }
-    get mouseLeave$() {
-        return this._mouseLeave$;
+    taskLoading$(task) {
+        return this._loaders$.pipe(map((loaders) => {
+            return !!loaders[task];
+        }), debounceTime(100), distinctUntilChanged());
     }
-    get mouseOut$() {
-        return this._mouseOut$;
+    startLoading(task) {
+        this._loadersSubject$.next({ loading: true, task: task });
     }
-    get mouseOver$() {
-        return this._mouseOver$;
+    stopLoading(task) {
+        this._loadersSubject$.next({ loading: false, task: task });
     }
-    get mouseUp$() {
-        return this._mouseUp$;
+}
+
+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 click$() {
-        return this._click$;
+    get panImages$() {
+        return this._panImages$;
     }
-    get dblClick$() {
-        return this._dblClick$;
+    dispose() {
+        this.stop();
+        if (this._panImagesSubscription != null) {
+            this._panImagesSubscription.unsubscribe();
+        }
+        this._subscriptions.unsubscribe();
     }
-    get contextMenu$() {
-        return this._consistentContextMenu$;
+    enable() {
+        if (this._mode !== PanMode.Disabled) {
+            return;
+        }
+        this._mode = PanMode.Enabled;
+        this.start();
     }
-    get mouseWheel$() {
-        return this._mouseWheel$;
+    disable() {
+        if (this._mode === PanMode.Disabled) {
+            return;
+        }
+        this.stop();
+        this._mode = PanMode.Disabled;
     }
-    get mouseDragStart$() {
-        return this._mouseDragStart$;
+    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;
     }
-    get mouseDrag$() {
-        return this._mouseDrag$;
+    stop() {
+        if (this._mode !== PanMode.Started) {
+            return;
+        }
+        this._panImagesSubscription.unsubscribe();
+        this._panImagesSubject$.next([]);
+        this._mode = PanMode.Enabled;
     }
-    get mouseDragEnd$() {
-        return this._mouseDragEnd$;
+    _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);
     }
-    get mouseRightDragStart$() {
-        return this._mouseRightDragStart$;
+    _timeDifference(image, reference) {
+        const milliSecond = (1000 * 60 * 60 * 24 * 30);
+        return Math.abs(image.capturedAt - reference.capturedAt) / milliSecond;
     }
-    get mouseRightDrag$() {
-        return this._mouseRightDrag$;
+    _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);
     }
-    get mouseRightDragEnd$() {
-        return this._mouseRightDragEnd$;
+    _computeProjectedPoints(transform) {
+        const vertices = [[1, 0]];
+        const directions = [[0, 0.5]];
+        const pointsPerLine = 20;
+        return computeProjectedPoints(transform, vertices, directions, pointsPerLine, this._viewportCoords);
     }
-    get proximateClick$() {
-        return this._proximateClick$;
+    _computeHorizontalFov(projectedPoints) {
+        const fovs = projectedPoints
+            .map((projectedPoint) => {
+            return this._coordToFov(projectedPoint[0]);
+        });
+        const fov = Math.min(...fovs);
+        return fov;
     }
-    get staticClick$() {
-        return this._staticClick$;
+    _coordToFov(x) {
+        return 2 * Math.atan(x) * 180 / Math.PI;
     }
-    get windowBlur$() {
-        return this._windowBlur$;
+}
+
+/**
+ * @class API
+ *
+ * @classdesc Provides methods for access to the API.
+ */
+class APIWrapper {
+    constructor(_data) {
+        this._data = _data;
     }
-    dispose() {
-        this._subscriptions.unsubscribe();
+    get data() {
+        return this._data;
     }
-    claimMouse(name, zindex) {
-        this._claimMouse$.next({ name: name, zindex: zindex });
+    getCoreImages$(cellId) {
+        return this._wrap$(this._data.getCoreImages(cellId));
     }
-    unclaimMouse(name) {
-        this._claimMouse$.next({ name: name, zindex: null });
+    getImages$(imageIds) {
+        return this._wrap$(this._data.getImages(imageIds));
     }
-    deferPixels(name, deferPixels) {
-        this._deferPixelClaims$.next({ name: name, deferPixels: deferPixels });
+    getImageTiles$(tiles) {
+        return this._wrap$(this._data.getImageTiles(tiles));
     }
-    undeferPixels(name) {
-        this._deferPixelClaims$.next({ name: name, deferPixels: null });
+    getSequence$(sequenceId) {
+        return this._wrap$(this._data.getSequence(sequenceId));
     }
-    claimWheel(name, zindex) {
-        this._claimWheel$.next({ name: name, zindex: zindex });
+    getSpatialImages$(imageIds) {
+        return this._wrap$(this._data.getSpatialImages(imageIds));
     }
-    unclaimWheel(name) {
-        this._claimWheel$.next({ name: name, zindex: null });
+    setAccessToken(accessToken) {
+        this._data.setAccessToken(accessToken);
     }
-    filtered$(name, observable$) {
-        return this._filtered(name, observable$, this._mouseOwner$);
+    _wrap$(promise) {
+        return Observable.create((subscriber) => {
+            promise.then((value) => {
+                subscriber.next(value);
+                subscriber.complete();
+            }, (error) => {
+                subscriber.error(error);
+            });
+        });
     }
-    filteredWheel$(name, observable$) {
-        return this._filtered(name, observable$, this._wheelOwner$);
+}
+
+/**
+ * @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);
     }
-    _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;
-        }));
+    /**
+     * Get dataAdded$.
+     *
+     * @returns {Observable<string>} Observable emitting
+     * a cell id every time data has been added to a cell.
+     */
+    get dataAdded$() {
+        return this._dataAdded$;
     }
-    _createMouseDrag$(mouseDragStartInitiate$, stop$) {
-        return mouseDragStartInitiate$.pipe(map(([, mouseMove]) => {
-            return mouseMove;
-        }), switchMap((mouseMove) => {
-            return concat(of(mouseMove), this._documentMouseMove$).pipe(takeUntil(stop$));
+    /**
+     * 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$;
         }));
     }
-    _createMouseDragEnd$(mouseDragStart$, stop$) {
-        return mouseDragStart$.pipe(switchMap(() => {
-            return stop$.pipe(first());
-        }));
+    /**
+     * 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$;
     }
-    _createMouseDragStart$(mouseDragStartInitiate$) {
-        return mouseDragStartInitiate$.pipe(map(([mouseDown]) => {
-            return mouseDown;
+    /**
+     * 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);
         }));
     }
-    _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));
+    /**
+     * 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);
         }));
     }
-    _createOwner$(claim$) {
-        return claim$.pipe(scan((claims, claim) => {
-            if (claim.zindex == null) {
-                delete claims[claim.name];
+    /**
+     * 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);
             }
-            else {
-                claims[claim.name] = claim.zindex;
+            if (graph.isCachingFill(id) || !graph.getNode(id).complete) {
+                return graph.cacheFill$(id);
             }
-            return claims;
-        }, {}), map((claims) => {
-            let owner = null;
-            let zIndexMax = -1;
-            for (const name in claims) {
-                if (!claims.hasOwnProperty(name)) {
-                    continue;
+            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();
                 }
-                if (claims[name] > zIndexMax) {
-                    zIndexMax = claims[name];
-                    owner = name;
+                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 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;
+        return image$.pipe(first((image) => {
+            return image.assetsCached;
+        }));
     }
-    _isMousePen(event) {
-        const type = event.pointerType;
-        return type === "mouse" || type === "pen";
+    /**
+     * 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);
+        }));
     }
-}
-
-class SpriteAtlas {
-    set json(value) {
-        this._json = value;
+    /**
+     * 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);
+        }));
     }
-    set image(value) {
-        this._image = value;
-        this._texture = new Texture(this._image);
-        this._texture.minFilter = NearestFilter;
+    /**
+     * Dispose the graph service and its children.
+     */
+    dispose() {
+        this._graph$
+            .pipe(first())
+            .subscribe((graph) => { graph.unsubscribe(); });
+        this._subscriptions.unsubscribe();
     }
-    get loaded() {
-        return !!(this._image && this._json);
+    /**
+     * 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;
+        }));
     }
-    getGLSprite(name) {
-        if (!this.loaded) {
-            throw new Error("Sprites cannot be retrieved before the atlas is loaded.");
+    /**
+     * 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;
         }
-        let definition = this._json[name];
-        if (!definition) {
-            console.warn("Sprite with key" + name + "does not exist in sprite definition.");
-            return new Object3D();
+        if (mode === GraphMode.Sequence) {
+            this._resetSubscriptions(this._spatialSubscriptions);
         }
-        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);
+        this._graphMode = mode;
+        this._graphModeSubject$.next(this._graphMode);
     }
-    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, []);
+    /**
+     * 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;
+        }));
     }
-}
-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();
+    /**
+     * 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;
+        }));
     }
-    get spriteAtlas$() {
-        return this._spriteAtlas$;
+    _abortSubjects(subjects) {
+        for (const subject of subjects.slice()) {
+            this._removeFromArray(subject, subjects);
+            subject.error(new Error("Cache image request was aborted."));
+        }
     }
-    dispose() {
-        this._atlasSubscription.unsubscribe();
+    _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 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$));
-        }));
+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 active$() {
-        return this._active$;
+    get cancelAnimationFrame() {
+        return this._cancelAnimationFrame;
     }
-    get activate$() {
-        return this._activeSubject$;
+    get requestAnimationFrame() {
+        return this._requestAnimationFrame;
     }
-    get doubleTap$() {
-        return this._doubleTap$;
+}
+
+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._stateTransitionAlpha = 0;
+        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 touchStart$() {
-        return this._touchStart$;
+    get reference() {
+        return this._reference;
     }
-    get touchMove$() {
-        return this._touchMove$;
+    get alpha() {
+        return this._getAlpha();
     }
-    get touchEnd$() {
-        return this._touchEnd$;
+    get stateTransitionAlpha() {
+        return this._getStateTransitionAlpha();
     }
-    get touchCancel$() {
-        return this._touchCancel$;
+    get camera() {
+        return this._camera;
     }
-    get singleTouchDragStart$() {
-        return this._singleTouchDragStart$;
+    get zoom() {
+        return this._zoom;
     }
-    get singleTouchDrag$() {
-        return this._singleTouchDrag$;
+    get trajectory() {
+        return this._trajectory;
     }
-    get singleTouchDragEnd$() {
-        return this._singleTouchDragEnd$;
+    get currentIndex() {
+        return this._currentIndex;
     }
-    get pinch$() {
-        return this._pinchChange$;
+    get currentImage() {
+        return this._currentImage;
     }
-    get pinchStart$() {
-        return this._pinchStart$;
+    get previousImage() {
+        return this._previousImage;
     }
-    get pinchEnd$() {
-        return this._pinchEnd$;
+    get currentCamera() {
+        return this._currentCamera;
     }
-    dispose() {
-        this._subscriptions.unsubscribe();
+    get currentTransform() {
+        return this._trajectoryTransforms.length > 0 ?
+            this._trajectoryTransforms[this.currentIndex] : null;
     }
-}
-
-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 previousTransform() {
+        return this._trajectoryTransforms.length > 1 && this.currentIndex > 0 ?
+            this._trajectoryTransforms[this.currentIndex - 1] : null;
     }
-    get exploreUrl$() {
-        return this._exploreUrl$;
+    get motionless() {
+        return this._motionless;
     }
-    get imageTiling$() {
-        return this._imageTiling$;
+    get transitionMode() {
+        return this._transitionMode;
     }
-}
-
-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.`);
-            }
+    move(delta) { }
+    moveTo(position) { }
+    rotate(delta) { }
+    rotateUnbounded(delta) { }
+    rotateWithoutInertia(delta) { }
+    rotateBasic(basicRotation) { }
+    rotateBasicUnbounded(basicRotation) { }
+    rotateBasicWithoutInertia(basicRotation) { }
+    rotateToBasic(basic) { }
+    setSpeed(speed) { }
+    zoomIn(delta, reference) { }
+    update(delta) { }
+    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");
         }
-        else if (options.container instanceof HTMLElement) {
-            this._container = options.container;
+        if (this._currentIndex < 0) {
+            this.set(images);
         }
         else {
-            throw new Error(`Invalid type: "container" must be ` +
-                `a String or HTMLElement.`);
+            this._trajectory = this._trajectory.concat(images);
+            this._appendToTrajectories(images);
         }
-        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);
+    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();
     }
-}
-
-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;
+    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();
     }
-    get started() {
-        return this._started;
+    clearPrior() {
+        if (this._currentIndex > 0) {
+            this.remove(this._currentIndex - 1);
+        }
     }
-    configure(configuration) {
-        if (!configuration) {
-            this._cellDepth = 1;
-            return;
+    clear() {
+        this.cut();
+        if (this._currentIndex > 0) {
+            this.remove(this._currentIndex - 1);
         }
-        this._cellDepth = Math.max(1, Math.min(3, configuration.cellDepth));
     }
-    start() {
-        if (this._started) {
-            return;
+    cut() {
+        while (this._trajectory.length - 1 > this._currentIndex) {
+            this._trajectory.pop();
+            this._trajectoryTransforms.pop();
+            this._trajectoryCameras.pop();
         }
-        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;
+    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; }
+    _getStateTransitionAlpha() { return 1; }
+    _setCurrent() {
+        this._setCurrentImage();
+        let referenceReset = this._setReference(this._currentImage);
+        if (referenceReset) {
+            this._setTrajectories();
         }
-        this._subscriptions.unsubscribe();
-        this._started = false;
+        this._setCurrentCamera();
     }
-    _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();
-        }));
+    _setCurrentCamera() {
+        this._currentCamera = this._trajectoryCameras[this._currentIndex].clone();
+        this._previousCamera = this._currentIndex > 0 ?
+            this._trajectoryCameras[this._currentIndex - 1].clone() :
+            this._currentCamera.clone();
     }
-}
-
-class LoadingService {
-    constructor() {
-        this._loadersSubject$ = new Subject();
-        this._loaders$ = this._loadersSubject$.pipe(scan((loaders, loader) => {
-            if (loader.task !== undefined) {
-                loaders[loader.task] = loader.loading;
+    _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");
             }
-            return loaders;
-        }, {}), startWith({}), publishReplay(1), refCount());
+            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));
+        }
     }
-    get loading$() {
-        return this._loaders$.pipe(map((loaders) => {
-            for (const key in loaders) {
-                if (!loaders.hasOwnProperty(key)) {
-                    continue;
-                }
-                if (loaders[key]) {
-                    return true;
-                }
+    _prependToTrajectories(images) {
+        for (let image of images.reverse()) {
+            if (!image.assetsCached) {
+                throw new ArgumentMapillaryError("Assets must be cached when added to trajectory");
             }
-            return false;
-        }), debounceTime(100), distinctUntilChanged());
+            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));
+        }
     }
-    taskLoading$(task) {
-        return this._loaders$.pipe(map((loaders) => {
-            return !!loaders[task];
-        }), debounceTime(100), distinctUntilChanged());
+    _imageToTranslation(image, reference) {
+        return computeTranslation({ alt: image.computedAltitude, lat: image.lngLat.lat, lng: image.lngLat.lng }, image.rotation, reference);
     }
-    startLoading(task) {
-        this._loadersSubject$.next({ loading: true, task: task });
+    _sameConnectedComponent() {
+        let current = this._currentImage;
+        let previous = this._previousImage;
+        return !!current && !!previous &&
+            current.mergeId === previous.mergeId;
     }
-    stopLoading(task) {
-        this._loadersSubject$.next({ loading: false, task: task });
+    _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;
     }
 }
 
-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());
+class CustomState extends StateBase {
+    constructor(state) {
+        super(state);
     }
-    get panImages$() {
-        return this._panImages$;
+    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;
     }
-    dispose() {
-        this.stop();
-        if (this._panImagesSubscription != null) {
-            this._panImagesSubscription.unsubscribe();
+}
+
+class EarthState extends StateBase {
+    constructor(state) {
+        super(state);
+        this._transition = 0;
+        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;
         }
-        this._subscriptions.unsubscribe();
+        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()))));
+        }
+        const eye1 = this._camera.position.clone();
+        const lookat1 = eye1.clone().add(forward.clone().normalize().multiplyScalar(10));
+        const up1 = this._camera.up.clone();
+        const eye0 = lookat1.clone();
+        const lookat0 = eye0.clone().add(forward.clone().normalize().multiplyScalar(10));
+        const up0 = up1.clone();
+        const eye2 = eye.clone();
+        const lookat2 = lookat.clone();
+        const up2 = new Vector3(0, 0, 1);
+        const eye3 = eye.clone().add(lookat2.clone().sub(eye2).normalize().multiplyScalar(-10));
+        const lookat3 = lookat2.clone();
+        const up3 = up2.clone();
+        this._curveE = new CatmullRomCurve3([eye0, eye1, eye2, eye3]);
+        this._curveL = new CatmullRomCurve3([lookat0, lookat1, lookat2, lookat3]);
+        this._curveU = new CatmullRomCurve3([up0, up1, up2, up3]);
+        this._zoom0 = this._zoom;
+        this._zoom1 = 0;
+        this._camera.focal = 0.5 / Math.tan(Math.PI / 4);
+    }
+    get _isTransitioning() {
+        return this._transition < 1;
     }
-    enable() {
-        if (this._mode !== PanMode.Disabled) {
+    dolly(delta) {
+        if (this._isTransitioning) {
             return;
         }
-        this._mode = PanMode.Enabled;
-        this.start();
+        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);
     }
-    disable() {
-        if (this._mode === PanMode.Disabled) {
+    orbit(rotation) {
+        if (this._isTransitioning) {
             return;
         }
-        this.stop();
-        this._mode = PanMode.Disabled;
+        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));
     }
-    start() {
-        if (this._mode !== PanMode.Enabled) {
+    truck(direction) {
+        if (this._isTransitioning) {
             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;
+        const camera = this._camera;
+        camera.position
+            .add(new Vector3().fromArray(direction));
+        camera.lookat
+            .add(new Vector3().fromArray(direction));
     }
-    stop() {
-        if (this._mode !== PanMode.Started) {
+    update(delta) {
+        if (!this._isTransitioning) {
             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;
+        this._transition = Math.min(this._transition + 2 * delta / 3, 1);
+        const sta = MathUtils.smootherstep(this._transition, 0, 1);
+        const t = (sta + 1) / 3;
+        const eye = this._curveE.getPoint(t);
+        const lookat = this._curveL.getPoint(t);
+        const up = this._curveU.getPoint(t);
+        this._camera.position.copy(eye);
+        this._camera.lookat.copy(lookat);
+        this._camera.up.copy(up);
+        this._zoom = MathUtils.lerp(this._zoom0, this._zoom1, sta);
+        this._stateTransitionAlpha = sta;
     }
-    _coordToFov(x) {
-        return 2 * Math.atan(x) * 180 / Math.PI;
+    _getStateTransitionAlpha() {
+        return this._stateTransitionAlpha;
     }
 }
 
-/**
- * @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));
+class EulerRotationDelta {
+    constructor(phi, theta) {
+        this._phi = phi;
+        this._theta = theta;
     }
-    setAccessToken(accessToken) {
-        this._data.setAccessToken(accessToken);
+    get phi() {
+        return this._phi;
     }
-    _wrap$(promise) {
-        return Observable.create((subscriber) => {
-            promise.then((value) => {
-                subscriber.next(value);
-                subscriber.complete();
-            }, (error) => {
-                subscriber.error(error);
-            });
-        });
+    set phi(value) {
+        this._phi = value;
     }
-}
-
-/**
- * @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 theta() {
+        return this._theta;
     }
-    /**
-     * Get dataAdded$.
-     *
-     * @returns {Observable<string>} Observable emitting
-     * a cell id every time data has been added to a cell.
-     */
-    get dataAdded$() {
-        return this._dataAdded$;
+    set theta(value) {
+        this._theta = value;
     }
-    /**
-     * 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 isZero() {
+        return this._phi === 0 && this._theta === 0;
     }
-    /**
-     * 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$;
+    copy(delta) {
+        this._phi = delta.phi;
+        this._theta = delta.theta;
     }
-    /**
-     * 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);
-        }));
+    lerp(other, alpha) {
+        this._phi = (1 - alpha) * this._phi + alpha * other.phi;
+        this._theta = (1 - alpha) * this._theta + alpha * other.theta;
     }
-    /**
-     * 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);
-        }));
+    multiply(value) {
+        this._phi *= value;
+        this._theta *= value;
     }
-    /**
-     * 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);
+    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;
         }
-        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 (rotationDelta.phi === 0 && rotationDelta.theta === 0) {
+            return;
         }
-        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);
-            }
+        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);
         }
-        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);
-        }));
+    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));
     }
-    /**
-     * Dispose the graph service and its children.
-     */
-    dispose() {
-        this._graph$
-            .pipe(first())
-            .subscribe((graph) => { graph.unsubscribe(); });
-        this._subscriptions.unsubscribe();
+    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);
     }
-    /**
-     * 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;
-        }));
+    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();
+        }
     }
-    /**
-     * 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) {
+    rotateBasicUnbounded(basicRotation) {
+        if (this._currentImage == null) {
             return;
         }
-        if (mode === GraphMode.Sequence) {
-            this._resetSubscriptions(this._spatialSubscriptions);
+        if (this._requestedBasicRotationUnbounded != null) {
+            this._requestedBasicRotationUnbounded[0] += basicRotation[0];
+            this._requestedBasicRotationUnbounded[1] += basicRotation[1];
+        }
+        else {
+            this._requestedBasicRotationUnbounded = basicRotation.slice();
         }
-        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;
-        }));
+    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);
     }
-    /**
-     * 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;
-        }));
+    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);
     }
-    _abortSubjects(subjects) {
-        for (const subject of subjects.slice()) {
-            this._removeFromArray(subject, subjects);
-            subject.error(new Error("Cache image request was aborted."));
+    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));
     }
-    _removeFromArray(object, objects) {
-        const index = objects.indexOf(object);
-        if (index !== -1) {
-            objects.splice(index, 1);
+    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);
     }
-    _resetSubscriptions(subscriptions) {
-        for (const subscription of subscriptions.slice()) {
-            this._removeFromArray(subscription, subscriptions);
-            if (!subscription.closed) {
-                subscription.unsubscribe();
-            }
+    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));
     }
-}
-
-class FrameGenerator {
-    constructor(root) {
-        if (root.requestAnimationFrame) {
-            this._cancelAnimationFrame = root.cancelAnimationFrame.bind(root);
-            this._requestAnimationFrame = root.requestAnimationFrame.bind(root);
+    _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 if (root.mozRequestAnimationFrame) {
-            this._cancelAnimationFrame = root.mozCancelAnimationFrame.bind(root);
-            this._requestAnimationFrame = root.mozRequestAnimationFrame.bind(root);
+        else {
+            currentBasic[0] = this._spatial.clamp(currentBasic[0] + basicRotation[0], 0, 1);
+            currentBasic[1] = this._spatial.clamp(currentBasic[1] + basicRotation[1], 0, 1);
         }
-        else if (root.webkitRequestAnimationFrame) {
-            this._cancelAnimationFrame = root.webkitCancelAnimationFrame.bind(root);
-            this._requestAnimationFrame = root.webkitRequestAnimationFrame.bind(root);
+        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 if (root.msRequestAnimationFrame) {
-            this._cancelAnimationFrame = root.msCancelAnimationFrame.bind(root);
-            this._requestAnimationFrame = root.msRequestAnimationFrame.bind(root);
+        else {
+            previousBasic[0] = this._spatial.clamp(previousBasic[0] + basicRotation[0], 0, 1);
+            previousBasic[1] = this._spatial.clamp(currentBasic[1] + basicRotation[1], 0, 1);
         }
-        else if (root.oRequestAnimationFrame) {
-            this._cancelAnimationFrame = root.oCancelAnimationFrame.bind(root);
-            this._requestAnimationFrame = root.oRequestAnimationFrame.bind(root);
+        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._cancelAnimationFrame = root.clearTimeout.bind(root);
-            this._requestAnimationFrame = (cb) => { return root.setTimeout(cb, 1000 / 60); };
+            this._zoom += sign * Math.max(Math.abs(5 * animationSpeed * diff), 2e-3);
         }
     }
-    get cancelAnimationFrame() {
-        return this._cancelAnimationFrame;
-    }
-    get requestAnimationFrame() {
-        return this._requestAnimationFrame;
-    }
-}
-
-class CustomState extends StateBase {
-    constructor(state) {
-        super(state);
+    _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);
+        }
     }
-    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;
+    _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);
     }
-}
-
-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;
+    _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;
         }
-        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()))));
+        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];
         }
-        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);
+    _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];
+        }
     }
-    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));
+    _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;
     }
-    truck(direction) {
-        const camera = this._camera;
-        camera.position
-            .add(new Vector3().fromArray(direction));
-        camera.lookat
-            .add(new Vector3().fromArray(direction));
+    _setDesiredZoom() {
+        this._desiredZoom =
+            isSpherical(this._currentImage.cameraType) ||
+                this._previousImage == null ?
+                this._zoom : 0;
     }
-    update() { }
 }
 
 class InteractiveWaitingState extends InteractiveStateBase {
@@ -85882,7 +84117,7 @@ class InteractiveWaitingState extends InteractiveStateBase {
     moveTo(position) {
         this._alpha = Math.max(0, Math.min(1, position));
     }
-    update(fps) {
+    update(delta) {
         this._updateRotation();
         if (!this._rotationDelta.isZero) {
             this._applyRotation(this._rotationDelta, this._previousCamera);
@@ -85892,7 +84127,7 @@ class InteractiveWaitingState extends InteractiveStateBase {
         if (this._basicRotation[0] !== 0 || this._basicRotation[1] !== 0) {
             this._applyRotationBasic(this._basicRotation);
         }
-        let animationSpeed = this._animationSpeed * (60 / fps);
+        let animationSpeed = this._animationSpeed * delta / 1e-1 * 6;
         this._updateZoom(animationSpeed);
         this._updateLookat(animationSpeed);
         this._camera.lerpCameras(this._previousCamera, this._currentCamera, this.alpha);
@@ -85919,6 +84154,109 @@ class InteractiveWaitingState extends InteractiveStateBase {
     }
 }
 
+class TraversingState extends InteractiveStateBase {
+    constructor(state) {
+        super(state);
+        this._adjustCameras();
+        this._motionless = this._motionlessTransition();
+        this._baseAlpha = this._alpha;
+        this._speedCoefficient = 1;
+        this._smoothing = false;
+    }
+    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._smoothing = true;
+        }
+    }
+    setSpeed(speed) {
+        this._speedCoefficient = this._spatial.clamp(speed, 0, 10);
+    }
+    update(delta) {
+        if (this._alpha === 1 && this._currentIndex + this._alpha < this._trajectory.length) {
+            this._currentIndex += 1;
+            this._smoothing = 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 * delta / 1e-1 * 6;
+        this._baseAlpha = Math.min(1, this._baseAlpha + this._speedCoefficient * animationSpeed);
+        if (this._smoothing) {
+            this._alpha = MathUtils.smootherstep(this._baseAlpha, 0, 1);
+        }
+        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 WaitingState extends StateBase {
     constructor(state) {
         super(state);
@@ -85940,7 +84278,7 @@ class WaitingState extends StateBase {
     moveTo(position) {
         this._alpha = Math.max(0, Math.min(1, position));
     }
-    update(fps) {
+    update() {
         this._camera.lerpCameras(this._previousCamera, this._currentCamera, this.alpha);
     }
     _getAlpha() {
@@ -86046,6 +84384,9 @@ class StateContext {
     get alpha() {
         return this._state.alpha;
     }
+    get stateTransitionAlpha() {
+        return this._state.stateTransitionAlpha;
+    }
     get camera() {
         return this._state.camera;
     }
@@ -86106,8 +84447,8 @@ class StateContext {
     setZoom(zoom) {
         this._state.setZoom(zoom);
     }
-    update(fps) {
-        this._state.update(fps);
+    update(delta) {
+        this._state.update(delta);
     }
     append(images) {
         this._state.append(images);
@@ -86192,11 +84533,11 @@ class StateContext {
 class StateService {
     constructor(initialState, transitionMode) {
         this._appendImage$ = new Subject();
+        this._clock = new Clock();
         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;
         });
@@ -86206,21 +84547,14 @@ class StateService {
         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];
+        this._currentState$ = this._frame$.pipe(withLatestFrom(this._context$, (frameId, context) => {
+            return [frameId, context];
         }), filter((fc) => {
-            return fc[2].currentImage != null;
+            return fc[1].currentImage != null;
         }), tap((fc) => {
-            fc[2].update(fc[1]);
+            fc[1].update(this._clock.getDelta());
         }), map((fc) => {
-            return { fps: fc[1], id: fc[0], state: fc[2] };
+            return { fps: 60, id: fc[0], state: fc[1] };
         }), share());
         this._lastState$ = this._currentState$.pipe(publishReplay(1), refCount());
         let imageChanged$ = this._currentState$.pipe(distinctUntilChanged(undefined, (f) => {
@@ -86481,6 +84815,7 @@ class StateService {
         this._invokeContextOperation((context) => { context.setZoom(zoom); });
     }
     start() {
+        this._clock.start();
         if (this._frameId == null) {
             this._start$.next(null);
             this._frameId = this._frameGenerator.requestAnimationFrame(this._frame.bind(this));
@@ -86488,6 +84823,7 @@ class StateService {
         }
     }
     stop() {
+        this._clock.stop();
         if (this._frameId != null) {
             this._frameGenerator.cancelAnimationFrame(this._frameId);
             this._frameId = null;
@@ -86526,12 +84862,7 @@ class Navigator {
             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.");
-            }
+            this._api = new APIWrapper(options.dataProvider);
         }
         else {
             this._api = new APIWrapper(new GraphDataProvider({
@@ -86877,7 +85208,7 @@ class Observer {
             .subscribe((image) => {
             const type = "image";
             const event = {
-                image: image,
+                image,
                 target: this._viewer,
                 type,
             };
@@ -86907,6 +85238,16 @@ class Observer {
             };
             this._viewer.fire(type, event);
         }));
+        subs.push(this._navigator.stateService.reference$
+            .subscribe((reference) => {
+            const type = "reference";
+            const event = {
+                reference,
+                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())
@@ -87083,6 +85424,7 @@ class CustomCameraControls {
         if (this._controls) {
             throw new MapillaryError('Custom camera controls already attached');
         }
+        this._controls = controls;
         const attach$ = new Subject();
         const active$ = attach$
             .pipe(switchMap(() => {
@@ -87154,25 +85496,33 @@ class CustomCameraControls {
             attach$.next();
             attach$.complete();
         }));
-        this._controls = controls;
-    }
-    dispose(viewer) {
-        this.detach(viewer);
     }
     detach(viewer) {
-        if (!this._controls) {
-            return;
-        }
+        const controls = this._controls;
+        this._controls = null;
         this._subscriptions.unsubscribe();
-        this._navigator.stateService.state$
-            .subscribe(state => {
-            if (state === State.Custom) {
-                this._controls.onDeactivate(viewer);
-            }
-            this._controls.onDetach(viewer);
-            this._controls = null;
+        return new Promise(resolve => {
+            this._navigator.stateService.state$
+                .pipe(take(1))
+                .subscribe(state => {
+                if (!controls) {
+                    resolve(null);
+                    return;
+                }
+                if (state === State.Custom) {
+                    controls.onDeactivate(viewer);
+                }
+                controls.onDetach(viewer);
+                resolve(controls);
+            });
         });
     }
+    dispose(viewer) {
+        this.detach(viewer);
+    }
+    has(controls) {
+        return !!this._controls && controls === this._controls;
+    }
     _updateProjectionMatrix(projectionMatrix) {
         this._navigator.stateService.state$
             .pipe(first())
@@ -87206,7 +85556,7 @@ class CustomCameraControls {
  * @class Viewer
  *
  * @classdesc The Viewer object represents the navigable image viewer.
- * Create a Viewer by specifying a container, client ID, image id and
+ * Create a Viewer by specifying a container, client ID, image ID and
  * other options. The viewer exposes methods and events for programmatic
  * interaction.
  *
@@ -87217,38 +85567,43 @@ class Viewer extends EventEmitter {
     /**
      * Create a new viewer instance.
      *
-     * @description It is possible to initialize the viewer with or
-     * without a id.
+     * @description The `Viewer` object represents the street imagery
+     * viewer on your web page. It exposes methods and properties that
+     * you can use to programatically change the view, and fires
+     * events as users interact with it.
+     *
+     * 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.
+     * the start you should initialize it with a ID.
      *
-     * When you do not know the first image id at implementation
+     * 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.
+     * 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
+     * When initializing with an 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
+     * 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
+     * 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
+     * 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.
+     * specifying Viewer's and the components' initial setup.
      *
      * @example
      * ```js
@@ -87273,6 +85628,19 @@ class Viewer extends EventEmitter {
         this._customCameraControls =
             new CustomCameraControls(this._container, this._navigator);
     }
+    /**
+     * Returns the data provider used by the viewer to fetch
+     * all contracts, ents, and buffers.
+     *
+     * @description The viewer's data provider can be set
+     * upon initialization through the {@link ViewerOptions.dataProvider}
+     * property.
+     *
+     * @returns {IDataProvider} The viewer's data provider.
+     */
+    get dataProvider() {
+        return this._navigator.api.data;
+    }
     /**
      * Return a boolean indicating if the viewer is in a navigable state.
      *
@@ -87280,9 +85648,9 @@ class Viewer extends EventEmitter {
      * 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.
+     * 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.
+     * supplied a ID it will be navigable.
      *
      * @returns {boolean} Boolean indicating whether the viewer is navigable.
      */
@@ -87403,7 +85771,7 @@ class Viewer extends EventEmitter {
      * control instance.
      */
     detachCustomCameraControls() {
-        this._customCameraControls.detach(this);
+        return this._customCameraControls.detach(this);
     }
     fire(type, event) {
         super.fire(type, event);
@@ -87442,7 +85810,7 @@ class Viewer extends EventEmitter {
      *
      * @description The camera control mode determines
      * how the camera is controlled when the viewer
-     * recieves pointer and keyboard input.
+     * receives pointer and keyboard input.
      *
      * @returns {CameraControls} controls - Camera control mode.
      *
@@ -87629,6 +85997,25 @@ class Viewer extends EventEmitter {
             });
         });
     }
+    /**
+     * Get the viewer's current reference position.
+     *
+     * @description The reference position specifies the origin in
+     * the viewer's topocentric coordinate system.
+     *
+     * @returns {Promise<LngLatAlt>} Promise to the reference position.
+     *
+     * @example
+     * ```js
+     * viewer.getReference().then(reference => { console.log(reference); });
+     * ```
+     */
+    getReference() {
+        return new Promise((resolve, reject) => {
+            this._navigator.stateService.reference$.pipe(first())
+                .subscribe((reference) => { resolve(reference); }, (error) => { reject(error); });
+        });
+    }
     /**
      * Get the image's current zoom level.
      *
@@ -87650,11 +86037,22 @@ class Viewer extends EventEmitter {
             });
         });
     }
+    /**
+     * Check if a controls instance is the camera controls that are
+     * currently attached to the viewer.
+     *
+     * @param {ICustomCameraControls} controls - Camera controls instance.
+     * @returns {boolean} Value indicating whether the controls instance
+     * is currently attached.
+     */
+    hasCustomCameraControls(controls) {
+        return this._customCameraControls.has(controls);
+    }
     /**
      * Check if a custom renderer has been added to the viewer's
      * rendering pipeline.
      *
-     * @param {string} id - Unique id of the custom renderer.
+     * @param {string} id - Unique ID of the custom renderer.
      * @returns {boolean} Value indicating whether the customer
      * renderer has been added.
      */
@@ -87693,14 +86091,14 @@ class Viewer extends EventEmitter {
         });
     }
     /**
-     * Navigate to a given image id.
+     * 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.
+     * move request is made before the move to ID call has completed.
      *
      * @example
      * ```js
@@ -87835,7 +86233,7 @@ class Viewer extends EventEmitter {
     /**
      * Remove a custom renderer from the viewer's rendering pipeline.
      *
-     * @param id - Unique id of the custom renderer.
+     * @param id - Unique ID of the custom renderer.
      */
     removeCustomRenderer(rendererId) {
         this._customRenderer.remove(rendererId, this);
@@ -87867,7 +86265,7 @@ class Viewer extends EventEmitter {
      *
      * @description The camera control mode determines
      * how the camera is controlled when the viewer
-     * recieves pointer and keyboard input.
+     * receives pointer and keyboard input.
      *
      * Changing the camera control mode is not possible
      * when the slider component is active and attempts
@@ -87980,7 +86378,8 @@ class Viewer extends EventEmitter {
      * 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.
+     * @param {FilterExpression} [filter] - The filter expression.
+     * Applied filter is cleared if omitted.
      * @returns {Promise<void>} Promise that resolves after filter is applied.
      *
      * @example
@@ -88164,13 +86563,12 @@ class Viewer extends EventEmitter {
  *
  * 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
+ * fully this should be removed. GeoRBush is registered
+ * here only to avoid loading it during
  * unit tests.
  */
 Graph.register(GeoRBush);
 MarkerSet.register(GeoRBush);
-TraversingState.register(unitbezier);
 ComponentService.registerCover(CoverComponent);
 ComponentService.register(AttributionComponent);
 ComponentService.register(BearingComponent);
@@ -88189,5 +86587,5 @@ 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 };
+export { Alignment, ArgumentMapillaryError, BearingComponent, CacheComponent, CameraControls, CameraVisualizationMode, CancelMapillaryError, CircleMarker, Component, ComponentSize, DataProviderBase, DirectionComponent, DragPanHandler, EventEmitter, ExtremePointTag, Geometry, GeometryProviderBase, GeometryTagError, GraphDataProvider, GraphMapillaryError, Image$1 as Image, KeyPlayHandler, KeySequenceNavigationHandler, KeySpatialNavigationHandler, KeyZoomHandler, KeyboardComponent, MapillaryError, Marker, MarkerComponent, NavigationDirection, OriginalPositionMode, OutlineTag, PointGeometry, PointVisualizationMode, 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, ecefToEnu, ecefToGeodetic, enuToEcef, enuToGeodetic, fetchArrayBuffer, geodeticToEcef, geodeticToEnu, isFallbackSupported, isSupported, readMeshPbf };
 //# sourceMappingURL=mapillary.module.js.map