]> git.openstreetmap.org Git - rails.git/blobdiff - vendor/assets/iD/iD/mapillary-js/mapillary.unminified.js
Update to iD v2.22.0
[rails.git] / vendor / assets / iD / iD / mapillary-js / mapillary.unminified.js
index 25c528dff1da94587661c8525da0df1206e0576e..93f58894cdd987c06e0abc91d1abeb5feff28646 100644 (file)
@@ -2,7 +2,7 @@
     typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
     typeof define === 'function' && define.amd ? define(['exports'], factory) :
     (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.mapillary = {}));
-}(this, (function (exports) { 'use strict';
+})(this, (function (exports) { 'use strict';
 
     /*! *****************************************************************************
     Copyright (c) Microsoft Corporation.
     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;
         };
     }
 
-    /** 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;
             }
             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) {
                     }
                     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;
             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) {
             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) {
         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 () {
         };
         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;
             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;
             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;
             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;
             }
             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) {
                 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;
         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;
     }
         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();
         });
     }
 
-    /** 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();
                     }
                         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
         }
     }
 
-    // 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;
     const RGBAFormat = 1023;
     const LuminanceFormat = 1024;
     const LuminanceAlphaFormat = 1025;
+    const RGBEFormat = RGBAFormat;
     const DepthFormat = 1026;
     const DepthStencilFormat = 1027;
     const RedFormat = 1028;
      * 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 = {};
 
 
                }
 
-       },
+       }
 
-       hasEventListener: function ( type, listener ) {
+       hasEventListener( type, listener ) {
 
                if ( this._listeners === undefined ) return false;
 
 
                return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;
 
-       },
+       }
 
-       removeEventListener: function ( type, listener ) {
+       removeEventListener( type, listener ) {
 
                if ( this._listeners === undefined ) return;
 
 
                }
 
-       },
+       }
 
-       dispatchEvent: function ( event ) {
+       dispatchEvent( event ) {
 
                if ( this._listeners === undefined ) return;
 
 
                        }
 
+                       event.target = null;
+
                }
 
        }
 
-    } );
+    }
+
+    let _seed = 1234567;
+
+    const DEG2RAD$1 = Math.PI / 180;
+    const RAD2DEG$1 = 180 / Math.PI;
+
+    //
 
     const _lut = [];
 
 
     }
 
-    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;
 
 
        }
 
+       *[ 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,
 
        }
 
-       clone() {
-
-               return new this.constructor().fromArray( this.elements );
-
-       }
-
        copy( m ) {
 
                const te = this.elements;
 
        getNormalMatrix( matrix4 ) {
 
-               return this.setFromMatrix4( matrix4 ).copy( this ).invert().transpose();
+               return this.setFromMatrix4( matrix4 ).invert().transpose();
 
        }
 
 
        }
 
+       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 ) ) {
 
 
                } 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;
 
                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 {
 
        }
 
-    };
+    }
 
     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;
 
                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' );
 
 
                        if ( image.uuid === undefined ) {
 
-                               image.uuid = MathUtils.generateUUID(); // UGH
+                               image.uuid = generateUUID(); // UGH
 
                        }
 
 
                }
 
+               if ( JSON.stringify( this.userData ) !== '{}' ) output.userData = this.userData;
+
                if ( ! isRootObject ) {
 
                        meta.textures[ this.uuid ] = output;
 
                return output;
 
-       },
+       }
 
-       dispose: function () {
+       dispose() {
 
                this.dispatchEvent( { type: 'dispose' } );
 
-       },
+       }
 
-       transformUv: function ( uv ) {
+       transformUv( uv ) {
 
                if ( this.mapping !== UVMapping ) return uv;
 
 
        }
 
-    } );
-
-    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 ) {
 
 
        constructor( x = 0, y = 0, z = 0, w = 1 ) {
 
-               Object.defineProperty( this, 'isVector4', { value: true } );
-
                this.x = x;
                this.y = y;
                this.z = z;
 
        }
 
+       *[ 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
     */
     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;
 
        }
 
-       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();
 
 
                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;
 
     }
 
+    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;
 
        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 );
 
        }
 
                        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;
 
                // 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;
 
 
        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 ) ) );
 
        }
 
 
        }
 
+       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 );
 
     }
 
+    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;
 
                }
 
-               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 ) );
 
        }
 
 
        projectOnPlane( planeNormal ) {
 
-               _vector.copy( this ).projectOnVector( planeNormal );
+               _vector$c.copy( this ).projectOnVector( planeNormal );
 
-               return this.sub( _vector );
+               return this.sub( _vector$c );
 
        }
 
                // 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 ) ) );
 
        }
 
 
                // clamp, to handle numerical problems
 
-               return Math.acos( MathUtils.clamp( theta, - 1, 1 ) );
+               return Math.acos( clamp$1( theta, - 1, 1 ) );
 
        }
 
 
        }
 
+       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;
 
        }
 
 
        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 );
 
        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 );
 
        }
 
                        }
 
-                       _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 );
 
                }
 
                // 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 ),
        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 );
 
        }
 
                _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
                        _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;
 
 
                // 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;
 
                _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();
 
 
        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;
 
 
     }
 
-    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(),
        /*@__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
 
     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;
 
        }
 
 
                } else {
 
-                       _box$1.setFromPoints( points ).getCenter( center );
+                       _box$2.setFromPoints( points ).getCenter( center );
 
                }
 
 
        }
 
-       clone() {
-
-               return new this.constructor().copy( this );
-
-       }
-
        copy( sphere ) {
 
                this.center.copy( sphere.center );
 
                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 ) ) {
 
        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
 
        }
 
+       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;
 
        }
 
 
        }
 
-       clone() {
-
-               return new this.constructor().copy( this );
-
-       }
-
        copy( ray ) {
 
                this.origin.copy( ray.origin );
 
        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 );
 
        }
 
        recast( t ) {
 
-               this.origin.copy( this.at( t, _vector$2 ) );
+               this.origin.copy( this.at( t, _vector$a ) );
 
                return this;
 
 
        closestPointToPoint( point, target ) {
 
-               if ( target === undefined ) {
-
-                       console.warn( 'THREE.Ray: .closestPointToPoint() target is now required' );
-                       target = new Vector3();
-
-               }
-
                target.subVectors( point, this.origin );
 
                const directionDistance = target.dot( this.direction );
 
        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
 
 
                }
 
-               _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 );
 
        }
 
 
        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;
 
        intersectsBox( box ) {
 
-               return this.intersectBox( box, _vector$2 ) !== null;
+               return this.intersectBox( box, _vector$a ) !== null;
 
        }
 
 
                _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 ) {
                }
 
                // 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 ) {
 
        }
 
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
     }
 
     class Matrix4 {
 
        constructor() {
 
-               Object.defineProperty( this, 'isMatrix4', { value: true } );
-
                this.elements = [
 
                        1, 0, 0, 0,
                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;
 
        }
 
-       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
 
                );
 
                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();
                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;
 
     }
 
-    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;
 
        }
 
-       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();
 
 
        }
 
-       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)
 
                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 ) {
 
 
                        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 ) {
 
 
                        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 ) {
 
 
                        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 ) {
 
 
                        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 ) {
 
 
                        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 ) {
 
 
                this._order = order;
 
-               if ( update !== false ) this._onChangeCallback();
+               if ( update === true ) this._onChangeCallback();
 
                return this;
 
 
        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 );
 
        }
 
 
                // 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 );
 
        }
 
 
     }
 
+    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() {
 
     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();
 
 
                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
 
                return this;
 
-       },
+       }
 
-       rotateOnWorldAxis: function ( axis, angle ) {
+       rotateOnWorldAxis( axis, angle ) {
 
                // rotate object on axis in world space
                // axis is assumed to be normalized
 
                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)
 
 
                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 );
 
                }
 
 
                }
 
-       },
+       }
 
-       add: function ( object ) {
+       add( object ) {
 
                if ( arguments.length > 1 ) {
 
 
                return this;
 
-       },
+       }
 
-       remove: function ( object ) {
+       remove( object ) {
 
                if ( arguments.length > 1 ) {
 
 
                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 ++ ) {
 
                return this;
 
 
-       },
+       }
 
-       attach: function ( object ) {
+       attach( object ) {
 
                // adds object as a child of this, while maintaining the object's world transform
 
 
                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;
 
 
                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 );
 
 
                return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize();
 
-       },
+       }
 
-       raycast: function () {},
+       raycast() {}
 
-       traverse: function ( callback ) {
+       traverse( callback ) {
 
                callback( this );
 
 
                }
 
-       },
+       }
 
-       traverseVisible: function ( callback ) {
+       traverseVisible( callback ) {
 
                if ( this.visible === false ) return;
 
 
                }
 
-       },
+       }
 
-       traverseAncestors: function ( callback ) {
+       traverseAncestors( callback ) {
 
                const parent = this.parent;
 
 
                }
 
-       },
+       }
 
-       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();
 
 
                }
 
-       },
+       }
 
-       updateWorldMatrix: function ( updateParents, updateChildren ) {
+       updateWorldMatrix( updateParents, updateChildren ) {
 
                const parent = this.parent;
 
 
                }
 
-       },
+       }
 
-       toJSON: function ( meta ) {
+       toJSON( meta ) {
 
                // meta is a string when called from JSON.stringify
                const isRootObject = ( meta === undefined || typeof meta === 'string' );
                        object.type = 'InstancedMesh';
                        object.count = this.count;
                        object.instanceMatrix = this.instanceMatrix.toJSON();
+                       if ( this.instanceColor !== null ) object.instanceColor = this.instanceColor.toJSON();
 
                }
 
 
                }
 
-               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 );
 
 
                }
 
-       },
+       }
 
-       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;
 
 
        }
 
-    } );
-
-    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();
 
     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 );
 
                _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 ) {
 
 
        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;
 
 
        }
 
+       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 );
 
        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 );
 
        }
 
        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 );
 
        }
 
        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;
 
 
     }
 
+    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,
 
        constructor( r, g, b ) {
 
-               Object.defineProperty( this, 'isColor', { value: true } );
-
                if ( g === undefined && b === undefined ) {
 
                        // r is THREE.Color, hex or string
        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 ) {
 
        setColorName( style ) {
 
                // color keywords
-               const hex = _colorKeywords[ style ];
+               const hex = _colorKeywords[ style.toLowerCase() ];
 
                if ( hex !== undefined ) {
 
 
                // 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 );
                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 );
 
     }
 
     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;
 
 
     }
 
-    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;
 
                return this;
 
-       },
+       }
 
-       copyVector2sArray: function ( vectors ) {
+       copyVector2sArray( vectors ) {
 
                const array = this.array;
                let offset = 0;
 
                return this;
 
-       },
+       }
 
-       copyVector3sArray: function ( vectors ) {
+       copyVector3sArray( vectors ) {
 
                const array = this.array;
                let offset = 0;
 
                return this;
 
-       },
+       }
 
-       copyVector4sArray: function ( vectors ) {
+       copyVector4sArray( vectors ) {
 
                const array = this.array;
                let offset = 0;
 
                return this;
 
-       },
+       }
 
-       applyMatrix3: function ( m ) {
+       applyMatrix3( m ) {
 
                if ( this.itemSize === 2 ) {
 
 
                        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 );
 
                        }
 
 
                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;
 
 
                return this;
 
-       },
+       }
 
-       setXYZ: function ( index, x, y, z ) {
+       setXYZ( index, x, y, z ) {
 
                index *= this.itemSize;
 
 
                return this;
 
-       },
+       }
 
-       setXYZW: function ( index, x, y, z, w ) {
+       setXYZW( index, x, y, z, w ) {
 
                index *= this.itemSize;
 
 
                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 ) ) {
 
 
                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( {
 
 
                } );
 
-       },
+       }
 
-       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;
 
 
                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 );
 
 
                return this;
 
-       },
+       }
 
-       center: function () {
+       center() {
 
                this.computeBoundingBox();
 
 
                return this;
 
-       },
+       }
 
-       setFromPoints: function ( points ) {
+       setFromPoints( points ) {
 
                const position = [];
 
 
                return this;
 
-       },
+       }
 
-       computeBoundingBox: function () {
+       computeBoundingBox() {
 
                if ( this.boundingBox === null ) {
 
                                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 );
 
                                        }
 
 
                }
 
-       },
+       }
 
-       computeBoundingSphere: function () {
+       computeBoundingSphere() {
 
                if ( this.boundingSphere === null ) {
 
 
                        const center = this.boundingSphere.center;
 
-                       _box$2.setFromBufferAttribute( position );
+                       _box$1.setFromBufferAttribute( position );
 
                        // process morph attributes if present
 
 
                                        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 );
 
                                        }
 
 
                        }
 
-                       _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
 
                        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 ) );
 
                        }
 
 
                                        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 ) );
 
                                        }
 
 
                }
 
-       },
-
-       computeFaceNormals: function () {
-
-               // backwards compatibility
-
-       },
+       }
 
-       computeTangents: function () {
+       computeTangents() {
 
                const index = this.index;
                const attributes = this.attributes;
 
                }
 
-       },
+       }
 
-       computeVertexNormals: function () {
+       computeVertexNormals() {
 
                const index = this.index;
                const positionAttribute = this.getAttribute( 'position' );
 
                }
 
-       },
+       }
 
-       merge: function ( geometry, offset ) {
+       merge( geometry, offset ) {
 
                if ( ! ( geometry && geometry.isBufferGeometry ) ) {
 
 
                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 ) {
 
 
                        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 ++ ) {
 
 
                return geometry2;
 
-       },
+       }
 
-       toJSON: function () {
+       toJSON() {
 
                const data = {
                        metadata: {
 
                }
 
+               // for simplicity the code assumes attributes are not shared across geometries, see #15811
+
                data.data = { attributes: {} };
 
                const index = this.index;
 
                        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 );
 
                }
 
 
                                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 ) );
 
                        }
 
 
                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
 
 
                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 ) {
 
 
                return this;
 
-       },
+       }
 
-       updateMorphTargets: function () {
+       updateMorphTargets() {
 
                const geometry = this.geometry;
 
 
                }
 
-       },
+       }
 
-       raycast: function ( raycaster, intersects ) {
+       raycast( raycaster, intersects ) {
 
                const geometry = this.geometry;
                const material = this.material;
 
                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;
 
                }
 
                                                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 ) {
 
                                                        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 ) {
 
                                                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 ) {
 
                                                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 ) {
 
                                                        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 ) {
 
                                                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 ) {
 
 
        }
 
-    } );
+    }
+
+    Mesh.prototype.isMesh = true;
 
     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 );
 
                        } 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;
 
 
        }
 
+       static fromJSON( data ) {
+
+               return new BoxGeometry( data.width, data.height, data.depth, data.widthSegments, data.heightSegments, data.depthSegments );
+
+       }
+
     }
 
     /**
                        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();
 
      *  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 );
 
 
                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 );
 
 
                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;
 
                return this;
 
-       },
+       }
 
        /**
         * Sets the FOV by focal length in respect to the current .filmGauge.
         *
         * 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
         *
         *   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;
 
 
                this.updateProjectionMatrix();
 
-       },
+       }
 
-       clearViewOffset: function () {
+       clearViewOffset() {
 
                if ( this.view !== null ) {
 
 
                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;
 
                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;
 
        }
 
-    } );
+    }
+
+    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();
 
 
                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 {
 
 
                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;
 
 
     }
 
-    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 ];
 
        }
 
 
        }
 
-       clone() {
-
-               return new this.constructor().copy( this );
-
-       }
-
        copy( frustum ) {
 
                const planes = this.planes;
 
                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 );
 
        }
 
 
                        // 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;
 
 
        }
 
+       clone() {
+
+               return new this.constructor().copy( this );
+
+       }
+
     }
 
     function WebGLAnimation() {
 
                        type = 5121;
 
+               } else if ( array instanceof Uint8ClampedArray ) {
+
+                       type = 5121;
+
                }
 
                return {
 
        }
 
+       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";
 
 
     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";
 
 
     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";
 
 
     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";
 
 
     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";
 
 
     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";
 
 
     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";
 
 
     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,
        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,
        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,
        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
     };
 
     /**
 
        common: {
 
-               diffuse: { value: new Color( 0xeeeeee ) },
+               diffuse: { value: new Color( 0xffffff ) },
                opacity: { value: 1.0 },
 
                map: { value: null },
                uv2Transform: { value: new Matrix3() },
 
                alphaMap: { value: null },
+               alphaTest: { value: 0 }
 
        },
 
 
                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 }
 
 
        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() }
 
        }
                        }
                ] ),
 
-               vertexShader: ShaderChunk.normal_vert,
-               fragmentShader: ShaderChunk.normal_frag
+               vertexShader: ShaderChunk.meshnormal_vert,
+               fragmentShader: ShaderChunk.meshnormal_frag
 
        },
 
                        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 },
                }
        ] ),
 
        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 ) {
 
                }
 
-               if ( background && ( background.isCubeTexture || background.isWebGLCubeRenderTarget || background.mapping === CubeUVReflectionMapping ) ) {
+               if ( background && ( background.isCubeTexture || background.mapping === CubeUVReflectionMapping ) ) {
 
                        if ( boxMesh === undefined ) {
 
 
                        }
 
-                       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 ||
 
                        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 ) {
 
 
                                                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 ) {
 
                                                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 );
 
                                                }
 
 
                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 );
 
 
        }
 
+       const drawBuffers = isWebGL2 || extensions.has( 'WEBGL_draw_buffers' );
+
        const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true;
 
        const maxTextures = gl.getParameter( 34930 );
        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;
 
                isWebGL2: isWebGL2,
 
+               drawBuffers: drawBuffers,
+
                getMaxAnisotropy: getMaxAnisotropy,
                getMaxPrecision: getMaxPrecision,
 
 
        function get( texture ) {
 
-               if ( texture && texture.isTexture ) {
+               if ( texture && texture.isTexture && texture.isRenderTargetTexture === false ) {
 
                        const mapping = texture.mapping;
 
 
                                        if ( image && image.height > 0 ) {
 
-                                               const currentRenderList = renderer.getRenderList();
                                                const currentRenderTarget = renderer.getRenderTarget();
 
                                                const renderTarget = new WebGLCubeRenderTarget( image.height / 2 );
                                                cubemaps.set( texture, renderTarget );
 
                                                renderer.setRenderTarget( currentRenderTarget );
-                                               renderer.setRenderList( currentRenderList );
 
                                                texture.addEventListener( 'dispose', onTextureDispose );
 
 
     }
 
-    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 ++ ) {
+                       weights[ i ] = weights[ i ] / sum;
+
+               }
+
+               blurUniforms[ 'envMap' ].value = targetIn.texture;
+               blurUniforms[ 'samples' ].value = samples;
+               blurUniforms[ 'weights' ].value = weights;
+               blurUniforms[ 'latitudinal' ].value = direction === 'latitudinal';
 
-                       const influence = influences[ i ];
+               if ( poleAxis ) {
 
-                       influence[ 0 ] = i;
-                       influence[ 1 ] = objectInfluences[ i ];
+                       blurUniforms[ 'poleAxis' ].value = poleAxis;
 
                }
 
-               influences.sort( absNumericalSort );
+               blurUniforms[ 'dTheta' ].value = radiansPerPixel;
+               blurUniforms[ 'mipInt' ].value = LOD_MAX - lodIn;
 
-               for ( let i = 0; i < 8; i ++ ) {
+               this._setEncoding( blurUniforms[ 'inputEncoding' ], targetIn.texture );
+               this._setEncoding( blurUniforms[ 'outputEncoding' ], targetIn.texture );
 
-                       if ( i < length && influences[ i ][ 1 ] ) {
+               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 );
 
-                               workInfluences[ i ][ 0 ] = influences[ i ][ 0 ];
-                               workInfluences[ i ][ 1 ] = influences[ i ][ 1 ];
+               _setViewport( targetOut, x, y, 3 * outputSize, 2 * outputSize );
+               renderer.setRenderTarget( targetOut );
+               renderer.render( blurMesh, _flatCamera );
 
-                       } else {
+       }
 
-                               workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER;
-                               workInfluences[ i ][ 1 ] = 0;
+    }
 
-                       }
+    function _isLDR( texture ) {
 
-               }
+       if ( texture === undefined || texture.type !== UnsignedByteType ) return false;
 
-               workInfluences.sort( numericalSort );
+       return texture.encoding === LinearEncoding || texture.encoding === sRGBEncoding || texture.encoding === GammaEncoding;
 
-               const morphTargets = material.morphTargets && geometry.morphAttributes.position;
-               const morphNormals = material.morphNormals && geometry.morphAttributes.normal;
+    }
 
-               let morphInfluencesSum = 0;
+    function _createPlanes() {
 
-               for ( let i = 0; i < 8; i ++ ) {
+       const _lodPlanes = [];
+       const _sizeLods = [];
+       const _sigmas = [];
 
-                       const influence = workInfluences[ i ];
-                       const index = influence[ 0 ];
-                       const value = influence[ 1 ];
+       let lod = LOD_MAX;
 
-                       if ( index !== Number.MAX_SAFE_INTEGER && value ) {
+       for ( let i = 0; i < TOTAL_LODS; i ++ ) {
 
-                               if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) {
+               const sizeLod = Math.pow( 2, lod );
+               _sizeLods.push( sizeLod );
+               let sigma = 1.0 / sizeLod;
 
-                                       geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] );
+               if ( i > LOD_MAX - LOD_MIN ) {
 
-                               }
+                       sigma = EXTRA_LOD_SIGMA[ i - LOD_MAX + LOD_MIN - 1 ];
 
-                               if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) {
+               } else if ( i == 0 ) {
 
-                                       geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] );
+                       sigma = 0;
 
-                               }
+               }
 
-                               morphInfluences[ i ] = value;
-                               morphInfluencesSum += value;
+               _sigmas.push( sigma );
 
-                       } else {
+               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 ];
 
-                               if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) {
+               const cubeFaces = 6;
+               const vertices = 6;
+               const positionSize = 3;
+               const uvSize = 2;
+               const faceIndexSize = 1;
 
-                                       geometry.deleteAttribute( 'morphTarget' + i );
+               const position = new Float32Array( positionSize * vertices * cubeFaces );
+               const uv = new Float32Array( uvSize * vertices * cubeFaces );
+               const faceIndex = new Float32Array( faceIndexSize * vertices * cubeFaces );
 
-                               }
+               for ( let face = 0; face < cubeFaces; face ++ ) {
 
-                               if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) {
+                       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 );
 
-                                       geometry.deleteAttribute( 'morphNormal' + i );
+               }
 
-                               }
+               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 );
 
-                               morphInfluences[ i ] = 0;
+               if ( lod > LOD_MIN ) {
 
-                       }
+                       lod --;
 
                }
 
-               // 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;
+       }
+
+       return { _lodPlanes, _sizeLods, _sigmas };
 
-               program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
-               program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences );
+    }
 
-       }
+    function _createRenderTarget( params ) {
 
-       return {
+       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;
 
-               update: update
+    }
 
-       };
+    function _setViewport( target, x, y, width, height ) {
+
+       target.viewport.set( x, y, width, height );
+       target.scissor.set( x, y, width, height );
 
     }
 
-    function WebGLObjects( gl, geometries, attributes, info ) {
+    function _getBlurShader( maxSamples ) {
 
-       let updateMap = new WeakMap();
+       const weights = new Float32Array( maxSamples );
+       const poleAxis = new Vector3( 0, 1, 0 );
+       const shaderMaterial = new RawShaderMaterial( {
 
-       function update( object ) {
+               name: 'SphericalGaussianBlur',
 
-               const frame = info.render.frame;
+               defines: { 'n': maxSamples },
 
-               const geometry = object.geometry;
-               const buffergeometry = geometries.get( object, geometry );
+               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 ] }
+               },
 
-               // Update once per frame
+               vertexShader: _getCommonVertexShader(),
 
-               if ( updateMap.get( buffergeometry ) !== frame ) {
+               fragmentShader: /* glsl */`
 
-                       geometries.update( buffergeometry );
+                       precision mediump float;
+                       precision mediump int;
 
-                       updateMap.set( buffergeometry, frame );
+                       varying vec3 vOutputDirection;
 
-               }
+                       uniform sampler2D envMap;
+                       uniform int samples;
+                       uniform float weights[ n ];
+                       uniform bool latitudinal;
+                       uniform float dTheta;
+                       uniform float mipInt;
+                       uniform vec3 poleAxis;
 
-               if ( object.isInstancedMesh ) {
+                       ${ _getEncodings() }
 
-                       if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) {
+                       #define ENVMAP_TYPE_CUBE_UV
+                       #include <cube_uv_reflection_fragment>
 
-                               object.addEventListener( 'dispose', onInstancedMeshDispose );
+                       vec3 getSample( float theta, vec3 axis ) {
 
-                       }
+                               float cosTheta = cos( theta );
+                               // Rodrigues' axis-angle rotation
+                               vec3 sampleDirection = vOutputDirection * cosTheta
+                                       + cross( axis, vOutputDirection ) * sin( theta )
+                                       + axis * dot( axis, vOutputDirection ) * ( 1.0 - cosTheta );
 
-                       attributes.update( object.instanceMatrix, 34962 );
+                               return bilinearCubeUV( envMap, sampleDirection, mipInt );
 
-                       if ( object.instanceColor !== null ) {
+                       }
 
-                               attributes.update( object.instanceColor, 34962 );
+                       void main() {
 
-                       }
+                               vec3 axis = latitudinal ? poleAxis : cross( poleAxis, vOutputDirection );
 
-               }
+                               if ( all( equal( axis, vec3( 0.0 ) ) ) ) {
 
-               return buffergeometry;
+                                       axis = vec3( vOutputDirection.z, 0.0, - vOutputDirection.x );
 
-       }
+                               }
 
-       function dispose() {
+                               axis = normalize( axis );
 
-               updateMap = new WeakMap();
+                               gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );
+                               gl_FragColor.rgb += weights[ 0 ] * getSample( 0.0, axis );
 
-       }
+                               for ( int i = 1; i < n; i++ ) {
 
-       function onInstancedMeshDispose( event ) {
+                                       if ( i >= samples ) {
 
-               const instancedMesh = event.target;
+                                               break;
 
-               instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose );
+                                       }
 
-               attributes.remove( instancedMesh.instanceMatrix );
+                                       float theta = dTheta * float( i );
+                                       gl_FragColor.rgb += weights[ i ] * getSample( -1.0 * theta, axis );
+                                       gl_FragColor.rgb += weights[ i ] * getSample( theta, axis );
 
-               if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor );
+                               }
 
-       }
+                               gl_FragColor = linearToOutputTexel( gl_FragColor );
 
-       return {
+                       }
+               `,
 
-               update: update,
-               dispose: dispose
+               blending: NoBlending,
+               depthTest: false,
+               depthWrite: false
 
-       };
+       } );
+
+       return shaderMaterial;
 
     }
 
-    function DataTexture2DArray( data = null, width = 1, height = 1, depth = 1 ) {
+    function _getEquirectShader() {
 
-       Texture.call( this, null );
+       const texelSize = new Vector2( 1, 1 );
+       const shaderMaterial = new RawShaderMaterial( {
 
-       this.image = { data, width, height, depth };
+               name: 'EquirectangularToCubeUV',
 
-       this.magFilter = NearestFilter;
-       this.minFilter = NearestFilter;
+               uniforms: {
+                       'envMap': { value: null },
+                       'texelSize': { value: texelSize },
+                       'inputEncoding': { value: ENCODINGS[ LinearEncoding ] },
+                       'outputEncoding': { value: ENCODINGS[ LinearEncoding ] }
+               },
 
-       this.wrapR = ClampToEdgeWrapping;
+               vertexShader: _getCommonVertexShader(),
 
-       this.generateMipmaps = false;
-       this.flipY = false;
+               fragmentShader: /* glsl */`
 
-       this.needsUpdate = true;
+                       precision mediump float;
+                       precision mediump int;
 
-    }
+                       varying vec3 vOutputDirection;
 
-    DataTexture2DArray.prototype = Object.create( Texture.prototype );
-    DataTexture2DArray.prototype.constructor = DataTexture2DArray;
-    DataTexture2DArray.prototype.isDataTexture2DArray = true;
+                       uniform sampler2D envMap;
+                       uniform vec2 texelSize;
 
-    function DataTexture3D( data = null, width = 1, height = 1, depth = 1 ) {
+                       ${ _getEncodings() }
 
-       // 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
+                       #include <common>
 
-       Texture.call( this, null );
+                       void main() {
 
-       this.image = { data, width, height, depth };
+                               gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 );
 
-       this.magFilter = NearestFilter;
-       this.minFilter = NearestFilter;
+                               vec3 outputDirection = normalize( vOutputDirection );
+                               vec2 uv = equirectUv( outputDirection );
 
-       this.wrapR = ClampToEdgeWrapping;
+                               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;
 
-       this.generateMipmaps = false;
-       this.flipY = false;
+                               vec3 tm = mix( tl, tr, f.x );
+                               vec3 bm = mix( bl, br, f.x );
+                               gl_FragColor.rgb = mix( tm, bm, f.y );
 
-       this.needsUpdate = true;
+                               gl_FragColor = linearToOutputTexel( gl_FragColor );
 
+                       }
+               `,
 
-    }
+               blending: NoBlending,
+               depthTest: false,
+               depthWrite: false
 
-    DataTexture3D.prototype = Object.create( Texture.prototype );
-    DataTexture3D.prototype.constructor = DataTexture3D;
-    DataTexture3D.prototype.isDataTexture3D = true;
+       } );
 
-    /**
-     * Uniforms of a program.
-     * Those form a tree structure with a special top-level container for the root,
-     * which you get by calling 'new WebGLUniforms( gl, program )'.
-     *
-     *
-     * Properties of inner nodes including the top-level container:
-     *
-     * .seq - array of nested uniforms
-     * .map - nested uniforms by name
-     *
-     *
-     * Methods of all nodes except the top-level container:
-     *
-     * .setValue( gl, value, [textures] )
-     *
-     *                 uploads a uniform value(s)
-     *         the 'textures' parameter is needed for sampler uniforms
-     *
-     *
-     * Static methods of the top-level container (textures factorizations):
-     *
-     * .upload( gl, seq, values, textures )
-     *
-     *                 sets uniforms in 'seq' to 'values[id].value'
-     *
-     * .seqWithValue( seq, values ) : filteredSeq
-     *
-     *                 filters 'seq' entries with corresponding entry in values
-     *
-     *
-     * Methods of the top-level container (textures factorizations):
-     *
-     * .setValue( gl, name, value, textures )
-     *
-     *                 sets uniform with  name 'name' to 'value'
-     *
-     * .setOptional( gl, obj, prop )
-     *
-     *                 like .set for an optional property of the object
-     *
-     */
+       return shaderMaterial;
 
-    const emptyTexture = new Texture();
-    const emptyTexture2dArray = new DataTexture2DArray();
-    const emptyTexture3d = new DataTexture3D();
-    const emptyCubeTexture = new CubeTexture();
+    }
 
-    // --- Utilities ---
+    function _getCubemapShader() {
 
-    // Array Caches (provide typed arrays for temporary by size)
+       const shaderMaterial = new RawShaderMaterial( {
 
-    const arrayCacheF32 = [];
-    const arrayCacheI32 = [];
+               name: 'CubemapToCubeUV',
 
-    // Float32Array caches used for uploading Matrix uniforms
+               uniforms: {
+                       'envMap': { value: null },
+                       'inputEncoding': { value: ENCODINGS[ LinearEncoding ] },
+                       'outputEncoding': { value: ENCODINGS[ LinearEncoding ] }
+               },
 
-    const mat4array = new Float32Array( 16 );
-    const mat3array = new Float32Array( 9 );
-    const mat2array = new Float32Array( 4 );
+               vertexShader: _getCommonVertexShader(),
 
-    // Flattening for arrays of vectors and matrices
+               fragmentShader: /* glsl */`
 
-    function flatten( array, nBlocks, blockSize ) {
+                       precision mediump float;
+                       precision mediump int;
 
-       const firstElem = array[ 0 ];
+                       varying vec3 vOutputDirection;
 
-       if ( firstElem <= 0 || firstElem > 0 ) return array;
-       // unoptimized: ! isNaN( firstElem )
-       // see http://jacksondunstan.com/articles/983
+                       uniform samplerCube envMap;
 
-       const n = nBlocks * blockSize;
-       let r = arrayCacheF32[ n ];
+                       ${ _getEncodings() }
 
-       if ( r === undefined ) {
+                       void main() {
 
-               r = new Float32Array( n );
-               arrayCacheF32[ n ] = r;
+                               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 );
 
-       }
+                       }
+               `,
 
-       if ( nBlocks !== 0 ) {
+               blending: NoBlending,
+               depthTest: false,
+               depthWrite: false
 
-               firstElem.toArray( r, 0 );
+       } );
 
-               for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) {
+       return shaderMaterial;
 
-                       offset += blockSize;
-                       array[ i ].toArray( r, offset );
+    }
 
-               }
+    function _getCommonVertexShader() {
 
-       }
+       return /* glsl */`
 
-       return r;
+               precision mediump float;
+               precision mediump int;
 
-    }
+               attribute vec3 position;
+               attribute vec2 uv;
+               attribute float faceIndex;
 
-    function arraysEqual( a, b ) {
+               varying vec3 vOutputDirection;
 
-       if ( a.length !== b.length ) return false;
+               // RH coordinate system; PMREM face-indexing convention
+               vec3 getDirection( vec2 uv, float face ) {
 
-       for ( let i = 0, l = a.length; i < l; i ++ ) {
+                       uv = 2.0 * uv - 1.0;
 
-               if ( a[ i ] !== b[ i ] ) return false;
+                       vec3 direction = vec3( uv, 1.0 );
 
-       }
+                       if ( face == 0.0 ) {
 
-       return true;
+                               direction = direction.zyx; // ( 1, v, u ) pos x
 
-    }
+                       } else if ( face == 1.0 ) {
 
-    function copyArray( a, b ) {
+                               direction = direction.xzy;
+                               direction.xz *= -1.0; // ( -u, 1, -v ) pos y
 
-       for ( let i = 0, l = b.length; i < l; i ++ ) {
+                       } else if ( face == 2.0 ) {
 
-               a[ i ] = b[ i ];
+                               direction.x *= -1.0; // ( -u, v, 1 ) pos z
 
-       }
+                       } else if ( face == 3.0 ) {
 
-    }
+                               direction = direction.zyx;
+                               direction.xz *= -1.0; // ( -1, v, -u ) neg x
 
-    // Texture unit allocation
+                       } else if ( face == 4.0 ) {
 
-    function allocTexUnits( textures, n ) {
+                               direction = direction.xzy;
+                               direction.xy *= -1.0; // ( -u, -1, v ) neg y
 
-       let r = arrayCacheI32[ n ];
+                       } else if ( face == 5.0 ) {
 
-       if ( r === undefined ) {
+                               direction.z *= -1.0; // ( u, v, -1 ) neg z
 
-               r = new Int32Array( n );
-               arrayCacheI32[ n ] = r;
+                       }
 
-       }
+                       return direction;
 
-       for ( let i = 0; i !== n; ++ i ) {
+               }
 
-               r[ i ] = textures.allocateTextureUnit();
+               void main() {
 
-       }
+                       vOutputDirection = getDirection( uv, faceIndex );
+                       gl_Position = vec4( position, 1.0 );
 
-       return r;
+               }
+       `;
 
     }
 
-    // --- Setters ---
-
-    // Note: Defining these methods externally, because they come in a bunch
-    // and this way their names minify.
+    function _getEncodings() {
 
-    // Single scalar
+       return /* glsl */`
 
-    function setValueV1f( gl, v ) {
+               uniform int inputEncoding;
+               uniform int outputEncoding;
 
-       const cache = this.cache;
+               #include <encodings_pars_fragment>
 
-       if ( cache[ 0 ] === v ) return;
+               vec4 inputTexelToLinear( vec4 value ) {
 
-       gl.uniform1f( this.addr, v );
+                       if ( inputEncoding == 0 ) {
 
-       cache[ 0 ] = v;
+                               return value;
 
-    }
+                       } else if ( inputEncoding == 1 ) {
 
-    // Single float vector (from flat array or THREE.VectorN)
+                               return sRGBToLinear( value );
 
-    function setValueV2f( gl, v ) {
+                       } else if ( inputEncoding == 2 ) {
 
-       const cache = this.cache;
+                               return RGBEToLinear( value );
 
-       if ( v.x !== undefined ) {
+                       } else if ( inputEncoding == 3 ) {
 
-               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) {
+                               return RGBMToLinear( value, 7.0 );
 
-                       gl.uniform2f( this.addr, v.x, v.y );
+                       } else if ( inputEncoding == 4 ) {
 
-                       cache[ 0 ] = v.x;
-                       cache[ 1 ] = v.y;
+                               return RGBMToLinear( value, 16.0 );
 
-               }
+                       } else if ( inputEncoding == 5 ) {
 
-       } else {
+                               return RGBDToLinear( value, 256.0 );
 
-               if ( arraysEqual( cache, v ) ) return;
+                       } else {
 
-               gl.uniform2fv( this.addr, v );
+                               return GammaToLinear( value, 2.2 );
 
-               copyArray( cache, v );
+                       }
 
-       }
+               }
 
-    }
+               vec4 linearToOutputTexel( vec4 value ) {
 
-    function setValueV3f( gl, v ) {
+                       if ( outputEncoding == 0 ) {
 
-       const cache = this.cache;
+                               return value;
 
-       if ( v.x !== undefined ) {
+                       } else if ( outputEncoding == 1 ) {
 
-               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) {
+                               return LinearTosRGB( value );
 
-                       gl.uniform3f( this.addr, v.x, v.y, v.z );
+                       } else if ( outputEncoding == 2 ) {
 
-                       cache[ 0 ] = v.x;
-                       cache[ 1 ] = v.y;
-                       cache[ 2 ] = v.z;
+                               return LinearToRGBE( value );
 
-               }
+                       } else if ( outputEncoding == 3 ) {
 
-       } else if ( v.r !== undefined ) {
+                               return LinearToRGBM( value, 7.0 );
 
-               if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) {
+                       } else if ( outputEncoding == 4 ) {
 
-                       gl.uniform3f( this.addr, v.r, v.g, v.b );
+                               return LinearToRGBM( value, 16.0 );
 
-                       cache[ 0 ] = v.r;
-                       cache[ 1 ] = v.g;
-                       cache[ 2 ] = v.b;
+                       } else if ( outputEncoding == 5 ) {
 
-               }
+                               return LinearToRGBD( value, 256.0 );
 
-       } else {
+                       } else {
 
-               if ( arraysEqual( cache, v ) ) return;
+                               return LinearToGamma( value, 2.2 );
 
-               gl.uniform3fv( this.addr, v );
+                       }
 
-               copyArray( cache, v );
+               }
 
-       }
+               vec4 envMapTexelToLinear( vec4 color ) {
 
-    }
+                       return inputTexelToLinear( color );
 
-    function setValueV4f( gl, v ) {
+               }
+       `;
 
-       const cache = this.cache;
+    }
 
-       if ( v.x !== undefined ) {
+    function WebGLCubeUVMaps( renderer ) {
 
-               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) {
+       let cubeUVmaps = new WeakMap();
 
-                       gl.uniform4f( this.addr, v.x, v.y, v.z, v.w );
+       let pmremGenerator = null;
 
-                       cache[ 0 ] = v.x;
-                       cache[ 1 ] = v.y;
-                       cache[ 2 ] = v.z;
-                       cache[ 3 ] = v.w;
+       function get( texture ) {
 
-               }
+               if ( texture && texture.isTexture && texture.isRenderTargetTexture === false ) {
 
-       } else {
+                       const mapping = texture.mapping;
 
-               if ( arraysEqual( cache, v ) ) return;
+                       const isEquirectMap = ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping );
+                       const isCubeMap = ( mapping === CubeReflectionMapping || mapping === CubeRefractionMapping );
 
-               gl.uniform4fv( this.addr, v );
+                       if ( isEquirectMap || isCubeMap ) {
 
-               copyArray( cache, v );
+                               // equirect/cube map to cubeUV conversion
 
-       }
+                               if ( cubeUVmaps.has( texture ) ) {
 
-    }
+                                       return cubeUVmaps.get( texture ).texture;
 
-    // Single matrix (from flat array or MatrixN)
+                               } else {
 
-    function setValueM2( 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.uniformMatrix2fv( 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;
 
-               mat2array.set( elements );
+                                       } else {
 
-               gl.uniformMatrix2fv( this.addr, false, mat2array );
+                                               // image not yet ready. try the conversion next frame
 
-               copyArray( cache, elements );
+                                               return null;
 
-       }
+                                       }
 
-    }
+                               }
 
-    function setValueM3( gl, v ) {
+                       }
 
-       const cache = this.cache;
-       const elements = v.elements;
+               }
 
-       if ( elements === undefined ) {
+               return texture;
 
-               if ( arraysEqual( cache, v ) ) return;
+       }
 
-               gl.uniformMatrix3fv( 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 ++;
 
-               mat3array.set( elements );
+               }
 
-               gl.uniformMatrix3fv( this.addr, false, mat3array );
+               return count === length;
 
-               copyArray( cache, elements );
 
        }
 
-    }
-
-    function setValueM4( gl, v ) {
+       function onTextureDispose( event ) {
 
-       const cache = this.cache;
-       const elements = v.elements;
+               const texture = event.target;
 
-       if ( elements === undefined ) {
+               texture.removeEventListener( 'dispose', onTextureDispose );
 
-               if ( arraysEqual( cache, v ) ) return;
+               const cubemapUV = cubeUVmaps.get( texture );
 
-               gl.uniformMatrix4fv( this.addr, false, v );
+               if ( cubemapUV !== undefined ) {
 
-               copyArray( cache, v );
+                       cubeUVmaps.delete( texture );
+                       cubemapUV.dispose();
 
-       } else {
+               }
 
-               if ( arraysEqual( cache, elements ) ) return;
+       }
 
-               mat4array.set( elements );
+       function dispose() {
 
-               gl.uniformMatrix4fv( this.addr, false, mat4array );
+               cubeUVmaps = new WeakMap();
 
-               copyArray( cache, elements );
+               if ( pmremGenerator !== null ) {
 
-       }
+                       pmremGenerator.dispose();
+                       pmremGenerator = null;
 
-    }
+               }
 
-    // Single texture (2D / Cube)
+       }
 
-    function setValueT1( gl, v, textures ) {
+       return {
+               get: get,
+               dispose: dispose
+       };
 
-       const cache = this.cache;
-       const unit = textures.allocateTextureUnit();
+    }
 
-       if ( cache[ 0 ] !== unit ) {
+    function WebGLExtensions( gl ) {
 
-               gl.uniform1i( this.addr, unit );
-               cache[ 0 ] = unit;
+       const extensions = {};
 
-       }
+       function getExtension( name ) {
 
-       textures.safeSetTexture2D( v || emptyTexture, unit );
+               if ( extensions[ name ] !== undefined ) {
 
-    }
+                       return extensions[ name ];
 
-    function setValueT2DArray1( gl, v, textures ) {
+               }
 
-       const cache = this.cache;
-       const unit = textures.allocateTextureUnit();
+               let extension;
 
-       if ( cache[ 0 ] !== unit ) {
+               switch ( name ) {
 
-               gl.uniform1i( this.addr, unit );
-               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;
 
-       }
+                       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;
 
-       textures.setTexture2DArray( v || emptyTexture2dArray, unit );
+                       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;
 
-    function setValueT3D1( gl, v, textures ) {
+                       default:
+                               extension = gl.getExtension( name );
 
-       const cache = this.cache;
-       const unit = textures.allocateTextureUnit();
+               }
 
-       if ( cache[ 0 ] !== unit ) {
+               extensions[ name ] = extension;
 
-               gl.uniform1i( this.addr, unit );
-               cache[ 0 ] = unit;
+               return extension;
 
        }
 
-       textures.setTexture3D( v || emptyTexture3d, unit );
-
-    }
+       return {
 
-    function setValueT6( gl, v, textures ) {
+               has: function ( name ) {
 
-       const cache = this.cache;
-       const unit = textures.allocateTextureUnit();
+                       return getExtension( name ) !== null;
 
-       if ( cache[ 0 ] !== unit ) {
+               },
 
-               gl.uniform1i( this.addr, unit );
-               cache[ 0 ] = unit;
+               init: function ( capabilities ) {
 
-       }
+                       if ( capabilities.isWebGL2 ) {
 
-       textures.safeSetTextureCube( v || emptyCubeTexture, unit );
+                               getExtension( 'EXT_color_buffer_float' );
 
-    }
+                       } else {
 
-    // Integer / Boolean vectors or arrays thereof (always flat arrays)
+                               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 setValueV1i( gl, v ) {
+                       }
 
-       const cache = this.cache;
+                       getExtension( 'OES_texture_float_linear' );
+                       getExtension( 'EXT_color_buffer_half_float' );
 
-       if ( cache[ 0 ] === v ) return;
+               },
 
-       gl.uniform1i( this.addr, v );
+               get: function ( name ) {
 
-       cache[ 0 ] = v;
+                       const extension = getExtension( name );
 
-    }
+                       if ( extension === null ) {
 
-    function setValueV2i( gl, v ) {
+                               console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' );
 
-       const cache = this.cache;
+                       }
 
-       if ( arraysEqual( cache, v ) ) return;
+                       return extension;
 
-       gl.uniform2iv( this.addr, v );
+               }
 
-       copyArray( cache, v );
+       };
 
     }
 
-    function setValueV3i( gl, v ) {
-
-       const cache = this.cache;
+    function WebGLGeometries( gl, attributes, info, bindingStates ) {
 
-       if ( arraysEqual( cache, v ) ) return;
+       const geometries = {};
+       const wireframeAttributes = new WeakMap();
 
-       gl.uniform3iv( this.addr, v );
+       function onGeometryDispose( event ) {
 
-       copyArray( cache, v );
+               const geometry = event.target;
 
-    }
+               if ( geometry.index !== null ) {
 
-    function setValueV4i( gl, v ) {
+                       attributes.remove( geometry.index );
 
-       const cache = this.cache;
+               }
 
-       if ( arraysEqual( cache, v ) ) return;
+               for ( const name in geometry.attributes ) {
 
-       gl.uniform4iv( this.addr, v );
+                       attributes.remove( geometry.attributes[ name ] );
 
-       copyArray( cache, v );
+               }
 
-    }
+               geometry.removeEventListener( 'dispose', onGeometryDispose );
 
-    // uint
+               delete geometries[ geometry.id ];
 
-    function setValueV1ui( gl, v ) {
+               const attribute = wireframeAttributes.get( geometry );
 
-       const cache = this.cache;
+               if ( attribute ) {
 
-       if ( cache[ 0 ] === v ) return;
+                       attributes.remove( attribute );
+                       wireframeAttributes.delete( geometry );
 
-       gl.uniform1ui( this.addr, v );
+               }
 
-       cache[ 0 ] = v;
+               bindingStates.releaseStatesOfGeometry( geometry );
 
-    }
+               if ( geometry.isInstancedBufferGeometry === true ) {
 
-    // Helper to pick the right setter for the singular case
+                       delete geometry._maxInstanceCount;
 
-    function getSingularSetter( type ) {
+               }
 
-       switch ( type ) {
+               //
 
-               case 0x1406: return setValueV1f; // FLOAT
-               case 0x8b50: return setValueV2f; // _VEC2
-               case 0x8b51: return setValueV3f; // _VEC3
-               case 0x8b52: return setValueV4f; // _VEC4
+               info.memory.geometries --;
 
-               case 0x8b5a: return setValueM2; // _MAT2
-               case 0x8b5b: return setValueM3; // _MAT3
-               case 0x8b5c: return setValueM4; // _MAT4
+       }
 
-               case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL
-               case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2
-               case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3
-               case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4
+       function get( object, geometry ) {
 
-               case 0x1405: return setValueV1ui; // UINT
+               if ( geometries[ geometry.id ] === true ) return 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 setValueT1;
+               geometry.addEventListener( 'dispose', onGeometryDispose );
 
-               case 0x8b5f: // SAMPLER_3D
-               case 0x8dcb: // INT_SAMPLER_3D
-               case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D
-                       return setValueT3D1;
+               geometries[ geometry.id ] = true;
 
-               case 0x8b60: // SAMPLER_CUBE
-               case 0x8dcc: // INT_SAMPLER_CUBE
-               case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE
-               case 0x8dc5: // SAMPLER_CUBE_SHADOW
-                       return setValueT6;
+               info.memory.geometries ++;
 
-               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;
+               return geometry;
 
        }
 
-    }
+       function update( geometry ) {
 
-    // Array of scalars
-    function setValueV1fArray( gl, v ) {
+               const geometryAttributes = geometry.attributes;
 
-       gl.uniform1fv( this.addr, v );
+               // Updating index buffer in VAO now. See WebGLBindingStates.
 
-    }
+               for ( const name in geometryAttributes ) {
 
-    // Integer / Boolean vectors or arrays thereof (always flat arrays)
-    function setValueV1iArray( gl, v ) {
+                       attributes.update( geometryAttributes[ name ], 34962 );
 
-       gl.uniform1iv( this.addr, v );
+               }
 
-    }
+               // morph targets
 
-    function setValueV2iArray( gl, v ) {
+               const morphAttributes = geometry.morphAttributes;
 
-       gl.uniform2iv( this.addr, v );
+               for ( const name in morphAttributes ) {
 
-    }
+                       const array = morphAttributes[ name ];
 
-    function setValueV3iArray( gl, v ) {
+                       for ( let i = 0, l = array.length; i < l; i ++ ) {
 
-       gl.uniform3iv( this.addr, v );
+                               attributes.update( array[ i ], 34962 );
 
-    }
+                       }
 
-    function setValueV4iArray( gl, v ) {
+               }
 
-       gl.uniform4iv( this.addr, v );
+       }
 
-    }
+       function updateWireframeAttribute( geometry ) {
 
+               const indices = [];
 
-    // Array of vectors (flat or from THREE classes)
+               const geometryIndex = geometry.index;
+               const geometryPosition = geometry.attributes.position;
+               let version = 0;
 
-    function setValueV2fArray( gl, v ) {
+               if ( geometryIndex !== null ) {
 
-       const data = flatten( v, this.size, 2 );
+                       const array = geometryIndex.array;
+                       version = geometryIndex.version;
 
-       gl.uniform2fv( this.addr, data );
+                       for ( let i = 0, l = array.length; i < l; i += 3 ) {
 
-    }
+                               const a = array[ i + 0 ];
+                               const b = array[ i + 1 ];
+                               const c = array[ i + 2 ];
 
-    function setValueV3fArray( gl, v ) {
+                               indices.push( a, b, b, c, c, a );
 
-       const data = flatten( v, this.size, 3 );
+                       }
 
-       gl.uniform3fv( this.addr, data );
+               } else {
 
-    }
+                       const array = geometryPosition.array;
+                       version = geometryPosition.version;
 
-    function setValueV4fArray( gl, v ) {
+                       for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {
 
-       const data = flatten( v, this.size, 4 );
+                               const a = i + 0;
+                               const b = i + 1;
+                               const c = i + 2;
 
-       gl.uniform4fv( this.addr, data );
+                               indices.push( a, b, b, c, c, a );
 
-    }
+                       }
 
-    // Array of matrices (flat or from THREE clases)
+               }
 
-    function setValueM2Array( gl, v ) {
+               const attribute = new ( arrayMax( indices ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );
+               attribute.version = version;
 
-       const data = flatten( v, this.size, 4 );
+               // Updating index buffer in VAO now. See WebGLBindingStates
 
-       gl.uniformMatrix2fv( this.addr, false, data );
+               //
 
-    }
+               const previousAttribute = wireframeAttributes.get( geometry );
 
-    function setValueM3Array( gl, v ) {
+               if ( previousAttribute ) attributes.remove( previousAttribute );
 
-       const data = flatten( v, this.size, 9 );
+               //
 
-       gl.uniformMatrix3fv( this.addr, false, data );
+               wireframeAttributes.set( geometry, attribute );
 
-    }
-
-    function setValueM4Array( gl, v ) {
+       }
 
-       const data = flatten( v, this.size, 16 );
+       function getWireframeAttribute( geometry ) {
 
-       gl.uniformMatrix4fv( this.addr, false, data );
+               const currentAttribute = wireframeAttributes.get( geometry );
 
-    }
+               if ( currentAttribute ) {
 
-    // Array of textures (2D / Cube)
+                       const geometryIndex = geometry.index;
 
-    function setValueT1Array( gl, v, textures ) {
+                       if ( geometryIndex !== null ) {
 
-       const n = v.length;
+                               // if the attribute is obsolete, create a new one
 
-       const units = allocTexUnits( textures, n );
+                               if ( currentAttribute.version < geometryIndex.version ) {
 
-       gl.uniform1iv( this.addr, units );
+                                       updateWireframeAttribute( geometry );
 
-       for ( let i = 0; i !== n; ++ i ) {
+                               }
 
-               textures.safeSetTexture2D( v[ i ] || emptyTexture, units[ i ] );
+                       }
 
-       }
+               } else {
 
-    }
+                       updateWireframeAttribute( geometry );
 
-    function setValueT6Array( gl, v, textures ) {
+               }
 
-       const n = v.length;
+               return wireframeAttributes.get( geometry );
 
-       const units = allocTexUnits( textures, n );
+       }
 
-       gl.uniform1iv( this.addr, units );
+       return {
 
-       for ( let i = 0; i !== n; ++ i ) {
+               get: get,
+               update: update,
 
-               textures.safeSetTextureCube( v[ i ] || emptyCubeTexture, units[ i ] );
+               getWireframeAttribute: getWireframeAttribute
 
-       }
+       };
 
     }
 
-    // Helper to pick the right setter for a pure (bottom-level) array
+    function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) {
 
-    function getPureArraySetter( type ) {
+       const isWebGL2 = capabilities.isWebGL2;
 
-       switch ( type ) {
+       let mode;
 
-               case 0x1406: return setValueV1fArray; // FLOAT
-               case 0x8b50: return setValueV2fArray; // _VEC2
-               case 0x8b51: return setValueV3fArray; // _VEC3
-               case 0x8b52: return setValueV4fArray; // _VEC4
+       function setMode( value ) {
 
-               case 0x8b5a: return setValueM2Array; // _MAT2
-               case 0x8b5b: return setValueM3Array; // _MAT3
-               case 0x8b5c: return setValueM4Array; // _MAT4
+               mode = value;
 
-               case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL
-               case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2
-               case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3
-               case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4
+       }
 
-               case 0x8b5e: // SAMPLER_2D
-               case 0x8d66: // SAMPLER_EXTERNAL_OES
-               case 0x8dca: // INT_SAMPLER_2D
-               case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D
-               case 0x8b62: // SAMPLER_2D_SHADOW
-                       return setValueT1Array;
+       let type, bytesPerElement;
 
-               case 0x8b60: // SAMPLER_CUBE
-               case 0x8dcc: // INT_SAMPLER_CUBE
-               case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE
-               case 0x8dc5: // SAMPLER_CUBE_SHADOW
-                       return setValueT6Array;
+       function setIndex( value ) {
+
+               type = value.type;
+               bytesPerElement = value.bytesPerElement;
 
        }
 
-    }
+       function render( start, count ) {
 
-    // --- Uniform Classes ---
+               gl.drawElements( mode, count, type, start * bytesPerElement );
 
-    function SingleUniform( id, activeInfo, addr ) {
+               info.update( count, mode, 1 );
 
-       this.id = id;
-       this.addr = addr;
-       this.cache = [];
-       this.setValue = getSingularSetter( activeInfo.type );
+       }
 
-       // this.path = activeInfo.name; // DEBUG
+       function renderInstances( start, count, primcount ) {
 
-    }
+               if ( primcount === 0 ) return;
 
-    function PureArrayUniform( id, activeInfo, addr ) {
+               let extension, methodName;
 
-       this.id = id;
-       this.addr = addr;
-       this.cache = [];
-       this.size = activeInfo.size;
-       this.setValue = getPureArraySetter( activeInfo.type );
+               if ( isWebGL2 ) {
 
-       // this.path = activeInfo.name; // DEBUG
+                       extension = gl;
+                       methodName = 'drawElementsInstanced';
 
-    }
+               } else {
 
-    PureArrayUniform.prototype.updateCache = function ( data ) {
+                       extension = extensions.get( 'ANGLE_instanced_arrays' );
+                       methodName = 'drawElementsInstancedANGLE';
 
-       const cache = this.cache;
+                       if ( extension === null ) {
 
-       if ( data instanceof Float32Array && cache.length !== data.length ) {
+                               console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );
+                               return;
 
-               this.cache = new Float32Array( data.length );
+                       }
 
-       }
+               }
 
-       copyArray( cache, data );
+               extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount );
 
-    };
+               info.update( count, mode, primcount );
 
-    function StructuredUniform( id ) {
+       }
 
-       this.id = id;
+       //
 
-       this.seq = [];
-       this.map = {};
+       this.setMode = setMode;
+       this.setIndex = setIndex;
+       this.render = render;
+       this.renderInstances = renderInstances;
 
     }
 
-    StructuredUniform.prototype.setValue = function ( gl, value, textures ) {
-
-       const seq = this.seq;
+    function WebGLInfo( gl ) {
 
-       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+       const memory = {
+               geometries: 0,
+               textures: 0
+       };
 
-               const u = seq[ i ];
-               u.setValue( gl, value[ u.id ], textures );
+       const render = {
+               frame: 0,
+               calls: 0,
+               triangles: 0,
+               points: 0,
+               lines: 0
+       };
 
-       }
+       function update( count, mode, instanceCount ) {
 
-    };
+               render.calls ++;
 
-    // --- Top-level ---
+               switch ( mode ) {
 
-    // Parser - builds up the property tree from the path strings
+                       case 4:
+                               render.triangles += instanceCount * ( count / 3 );
+                               break;
 
-    const RePathPart = /(\w+)(\])?(\[|\.)?/g;
+                       case 1:
+                               render.lines += instanceCount * ( count / 2 );
+                               break;
 
-    // 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.
+                       case 3:
+                               render.lines += instanceCount * ( count - 1 );
+                               break;
 
-    function addUniform( container, uniformObject ) {
+                       case 2:
+                               render.lines += instanceCount * count;
+                               break;
 
-       container.seq.push( uniformObject );
-       container.map[ uniformObject.id ] = uniformObject;
+                       case 0:
+                               render.points += instanceCount * count;
+                               break;
 
-    }
+                       default:
+                               console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode );
+                               break;
 
-    function parseUniform( activeInfo, addr, container ) {
+               }
 
-       const path = activeInfo.name,
-               pathLength = path.length;
+       }
 
-       // reset RegExp object, because of the early exit of a previous run
-       RePathPart.lastIndex = 0;
+       function reset() {
 
-       while ( true ) {
+               render.frame ++;
+               render.calls = 0;
+               render.triangles = 0;
+               render.points = 0;
+               render.lines = 0;
 
-               const match = RePathPart.exec( path ),
-                       matchEnd = RePathPart.lastIndex;
+       }
 
-               let id = match[ 1 ];
-               const idIsIndex = match[ 2 ] === ']',
-                       subscript = match[ 3 ];
+       return {
+               memory: memory,
+               render: render,
+               programs: null,
+               autoReset: true,
+               reset: reset,
+               update: update
+       };
 
-               if ( idIsIndex ) id = id | 0; // convert to integer
+    }
 
-               if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) {
+    class DataTexture2DArray extends Texture {
 
-                       // bare name or "pure" bottom-level array "[0]" suffix
+       constructor( data = null, width = 1, height = 1, depth = 1 ) {
 
-                       addUniform( container, subscript === undefined ?
-                               new SingleUniform( id, activeInfo, addr ) :
-                               new PureArrayUniform( id, activeInfo, addr ) );
+               super( null );
 
-                       break;
+               this.image = { data, width, height, depth };
 
-               } else {
+               this.magFilter = NearestFilter;
+               this.minFilter = NearestFilter;
 
-                       // step into inner node / create it in case it doesn't exist
+               this.wrapR = ClampToEdgeWrapping;
 
-                       const map = container.map;
-                       let next = map[ id ];
+               this.generateMipmaps = false;
+               this.flipY = false;
+               this.unpackAlignment = 1;
 
-                       if ( next === undefined ) {
+               this.needsUpdate = true;
 
-                               next = new StructuredUniform( id );
-                               addUniform( container, next );
+       }
 
-                       }
+    }
 
-                       container = next;
+    DataTexture2DArray.prototype.isDataTexture2DArray = true;
 
-               }
+    function numericalSort( a, b ) {
 
-       }
+       return a[ 0 ] - b[ 0 ];
 
     }
 
-    // Root Container
-
-    function WebGLUniforms( gl, program ) {
+    function absNumericalSort( a, b ) {
 
-       this.seq = [];
-       this.map = {};
+       return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] );
 
-       const n = gl.getProgramParameter( program, 35718 );
+    }
 
-       for ( let i = 0; i < n; ++ i ) {
+    function denormalize( morph, attribute ) {
 
-               const info = gl.getActiveUniform( program, i ),
-                       addr = gl.getUniformLocation( program, info.name );
+       let denominator = 1;
+       const array = attribute.isInterleavedBufferAttribute ? attribute.data.array : attribute.array;
 
-               parseUniform( info, addr, this );
+       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 );
 
-       }
+       morph.divideScalar( denominator );
 
     }
 
-    WebGLUniforms.prototype.setValue = function ( gl, name, value, textures ) {
+    function WebGLMorphtargets( gl, capabilities, textures ) {
 
-       const u = this.map[ name ];
+       const influencesList = {};
+       const morphInfluences = new Float32Array( 8 );
+       const morphTextures = new WeakMap();
+       const morph = new Vector3();
 
-       if ( u !== undefined ) u.setValue( gl, value, textures );
+       const workInfluences = [];
 
-    };
+       for ( let i = 0; i < 8; i ++ ) {
 
-    WebGLUniforms.prototype.setOptional = function ( gl, object, name ) {
+               workInfluences[ i ] = [ i, 0 ];
 
-       const v = object[ name ];
+       }
 
-       if ( v !== undefined ) this.setValue( gl, name, v );
+       function update( object, geometry, material, program ) {
 
-    };
+               const objectInfluences = object.morphTargetInfluences;
 
+               if ( capabilities.isWebGL2 === true ) {
 
-    // Static interface
+                       // 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.
 
-    WebGLUniforms.upload = function ( gl, seq, values, textures ) {
+                       const numberOfMorphTargets = geometry.morphAttributes.position.length;
 
-       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+                       let entry = morphTextures.get( geometry );
 
-               const u = seq[ i ],
-                       v = values[ u.id ];
+                       if ( entry === undefined || entry.count !== numberOfMorphTargets ) {
 
-               if ( v.needsUpdate !== false ) {
+                               if ( entry !== undefined ) entry.texture.dispose();
 
-                       // note: always updating when .needsUpdate is undefined
-                       u.setValue( gl, v.value, textures );
+                               const hasMorphNormals = geometry.morphAttributes.normal !== undefined;
 
-               }
+                               const morphTargets = geometry.morphAttributes.position;
+                               const morphNormals = geometry.morphAttributes.normal || [];
 
-       }
+                               const numberOfVertices = geometry.attributes.position.count;
+                               const numberOfVertexData = ( hasMorphNormals === true ) ? 2 : 1; // (v,n) vs. (v)
 
-    };
+                               let width = numberOfVertices * numberOfVertexData;
+                               let height = 1;
 
-    WebGLUniforms.seqWithValue = function ( seq, values ) {
+                               if ( width > capabilities.maxTextureSize ) {
 
-       const r = [];
+                                       height = Math.ceil( width / capabilities.maxTextureSize );
+                                       width = capabilities.maxTextureSize;
 
-       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+                               }
 
-               const u = seq[ i ];
-               if ( u.id in values ) r.push( u );
+                               const buffer = new Float32Array( width * height * 4 * numberOfMorphTargets );
 
-       }
+                               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;
 
-       return r;
+                               // fill buffer
 
-    };
+                               const vertexDataStride = numberOfVertexData * 4;
 
-    function WebGLShader( gl, type, string ) {
+                               for ( let i = 0; i < numberOfMorphTargets; i ++ ) {
 
-       const shader = gl.createShader( type );
+                                       const morphTarget = morphTargets[ i ];
+                                       const morphNormal = morphNormals[ i ];
 
-       gl.shaderSource( shader, string );
-       gl.compileShader( shader );
+                                       const offset = width * height * 4 * i;
 
-       return shader;
+                                       for ( let j = 0; j < morphTarget.count; j ++ ) {
 
-    }
+                                               morph.fromBufferAttribute( morphTarget, j );
 
-    let programIdCount = 0;
+                                               if ( morphTarget.normalized === true ) denormalize( morph, morphTarget );
 
-    function addLineNumbers( string ) {
+                                               const stride = j * vertexDataStride;
 
-       const lines = string.split( '\n' );
+                                               buffer[ offset + stride + 0 ] = morph.x;
+                                               buffer[ offset + stride + 1 ] = morph.y;
+                                               buffer[ offset + stride + 2 ] = morph.z;
+                                               buffer[ offset + stride + 3 ] = 0;
 
-       for ( let i = 0; i < lines.length; i ++ ) {
+                                               if ( hasMorphNormals === true ) {
 
-               lines[ i ] = ( i + 1 ) + ': ' + lines[ i ];
+                                                       morph.fromBufferAttribute( morphNormal, j );
 
-       }
+                                                       if ( morphNormal.normalized === true ) denormalize( morph, morphNormal );
 
-       return lines.join( '\n' );
+                                                       buffer[ offset + stride + 4 ] = morph.x;
+                                                       buffer[ offset + stride + 5 ] = morph.y;
+                                                       buffer[ offset + stride + 6 ] = morph.z;
+                                                       buffer[ offset + stride + 7 ] = 0;
 
-    }
+                                               }
 
-    function getEncodingComponents( encoding ) {
+                                       }
 
-       switch ( encoding ) {
+                               }
 
-               case LinearEncoding:
-                       return [ 'Linear', '( value )' ];
-               case sRGBEncoding:
-                       return [ 'sRGB', '( value )' ];
-               case RGBEEncoding:
-                       return [ 'RGBE', '( value )' ];
-               case RGBM7Encoding:
-                       return [ 'RGBM', '( value, 7.0 )' ];
-               case RGBM16Encoding:
-                       return [ 'RGBM', '( value, 16.0 )' ];
-               case RGBDEncoding:
-                       return [ 'RGBD', '( value, 256.0 )' ];
-               case GammaEncoding:
-                       return [ 'Gamma', '( value, float( GAMMA_FACTOR ) )' ];
-               case LogLuvEncoding:
-                       return [ 'LogLuv', '( value )' ];
-               default:
-                       console.warn( 'THREE.WebGLProgram: Unsupported encoding:', encoding );
-                       return [ 'Linear', '( value )' ];
+                               entry = {
+                                       count: numberOfMorphTargets,
+                                       texture: texture,
+                                       size: new Vector2( width, height )
+                               };
 
-       }
+                               morphTextures.set( geometry, entry );
 
-    }
+                       }
 
-    function getShaderErrors( gl, shader, type ) {
+                       //
 
-       const status = gl.getShaderParameter( shader, 35713 );
-       const log = gl.getShaderInfoLog( shader ).trim();
+                       let morphInfluencesSum = 0;
 
-       if ( status && log === '' ) return '';
+                       for ( let i = 0; i < objectInfluences.length; i ++ ) {
 
-       // --enable-privileged-webgl-extension
-       // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) );
+                               morphInfluencesSum += objectInfluences[ i ];
 
-       const source = gl.getShaderSource( shader );
+                       }
 
-       return 'THREE.WebGLShader: gl.getShaderInfoLog() ' + type + '\n' + log + addLineNumbers( source );
+                       const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;
 
-    }
+                       program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
+                       program.getUniforms().setValue( gl, 'morphTargetInfluences', objectInfluences );
 
-    function getTexelDecodingFunction( functionName, encoding ) {
+                       program.getUniforms().setValue( gl, 'morphTargetsTexture', entry.texture, textures );
+                       program.getUniforms().setValue( gl, 'morphTargetsTextureSize', entry.size );
 
-       const components = getEncodingComponents( encoding );
-       return 'vec4 ' + functionName + '( vec4 value ) { return ' + components[ 0 ] + 'ToLinear' + components[ 1 ] + '; }';
 
-    }
+               } else {
 
-    function getTexelEncodingFunction( functionName, encoding ) {
+                       // 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 components = getEncodingComponents( encoding );
-       return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }';
+                       const length = objectInfluences === undefined ? 0 : objectInfluences.length;
 
-    }
+                       let influences = influencesList[ geometry.id ];
 
-    function getToneMappingFunction( functionName, toneMapping ) {
+                       if ( influences === undefined || influences.length !== length ) {
 
-       let toneMappingName;
+                               // initialise list
 
-       switch ( toneMapping ) {
+                               influences = [];
 
-               case LinearToneMapping:
-                       toneMappingName = 'Linear';
-                       break;
+                               for ( let i = 0; i < length; i ++ ) {
 
-               case ReinhardToneMapping:
-                       toneMappingName = 'Reinhard';
-                       break;
+                                       influences[ i ] = [ i, 0 ];
 
-               case CineonToneMapping:
-                       toneMappingName = 'OptimizedCineon';
-                       break;
+                               }
 
-               case ACESFilmicToneMapping:
-                       toneMappingName = 'ACESFilmic';
-                       break;
+                               influencesList[ geometry.id ] = influences;
 
-               case CustomToneMapping:
-                       toneMappingName = 'Custom';
-                       break;
+                       }
 
-               default:
-                       console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping );
-                       toneMappingName = 'Linear';
+                       // Collect influences
 
-       }
+                       for ( let i = 0; i < length; i ++ ) {
 
-       return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';
+                               const influence = influences[ i ];
 
-    }
+                               influence[ 0 ] = i;
+                               influence[ 1 ] = objectInfluences[ i ];
 
-    function generateExtensions( parameters ) {
+                       }
 
-       const chunks = [
-               ( parameters.extensionDerivatives || parameters.envMapCubeUV || parameters.bumpMap || parameters.tangentSpaceNormalMap || parameters.clearcoatNormalMap || parameters.flatShading || parameters.shaderID === 'physical' ) ? '#extension GL_OES_standard_derivatives : enable' : '',
-               ( parameters.extensionFragDepth || parameters.logarithmicDepthBuffer ) && parameters.rendererExtensionFragDepth ? '#extension GL_EXT_frag_depth : enable' : '',
-               ( parameters.extensionDrawBuffers && parameters.rendererExtensionDrawBuffers ) ? '#extension GL_EXT_draw_buffers : require' : '',
-               ( parameters.extensionShaderTextureLOD || parameters.envMap ) && parameters.rendererExtensionShaderTextureLod ? '#extension GL_EXT_shader_texture_lod : enable' : ''
-       ];
+                       influences.sort( absNumericalSort );
 
-       return chunks.filter( filterEmptyLine ).join( '\n' );
+                       for ( let i = 0; i < 8; i ++ ) {
 
-    }
+                               if ( i < length && influences[ i ][ 1 ] ) {
 
-    function generateDefines( defines ) {
+                                       workInfluences[ i ][ 0 ] = influences[ i ][ 0 ];
+                                       workInfluences[ i ][ 1 ] = influences[ i ][ 1 ];
 
-       const chunks = [];
+                               } else {
 
-       for ( const name in defines ) {
+                                       workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER;
+                                       workInfluences[ i ][ 1 ] = 0;
 
-               const value = defines[ name ];
+                               }
 
-               if ( value === false ) continue;
+                       }
 
-               chunks.push( '#define ' + name + ' ' + value );
+                       workInfluences.sort( numericalSort );
 
-       }
+                       const morphTargets = geometry.morphAttributes.position;
+                       const morphNormals = geometry.morphAttributes.normal;
 
-       return chunks.join( '\n' );
+                       let morphInfluencesSum = 0;
 
-    }
+                       for ( let i = 0; i < 8; i ++ ) {
 
-    function fetchAttributeLocations( gl, program ) {
+                               const influence = workInfluences[ i ];
+                               const index = influence[ 0 ];
+                               const value = influence[ 1 ];
 
-       const attributes = {};
+                               if ( index !== Number.MAX_SAFE_INTEGER && value ) {
 
-       const n = gl.getProgramParameter( program, 35721 );
+                                       if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) {
 
-       for ( let i = 0; i < n; i ++ ) {
+                                               geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] );
 
-               const info = gl.getActiveAttrib( program, i );
-               const name = info.name;
+                                       }
 
-               // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i );
+                                       if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) {
 
-               attributes[ name ] = gl.getAttribLocation( program, name );
+                                               geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] );
 
-       }
+                                       }
 
-       return attributes;
+                                       morphInfluences[ i ] = value;
+                                       morphInfluencesSum += value;
 
-    }
+                               } else {
 
-    function filterEmptyLine( string ) {
+                                       if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) {
 
-       return string !== '';
+                                               geometry.deleteAttribute( 'morphTarget' + i );
 
-    }
+                                       }
 
-    function replaceLightNums( string, parameters ) {
+                                       if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) {
 
-       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 );
+                                               geometry.deleteAttribute( 'morphNormal' + i );
 
-    }
+                                       }
 
-    function replaceClippingPlaneNums( string, parameters ) {
+                                       morphInfluences[ i ] = 0;
 
-       return string
-               .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes )
-               .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) );
+                               }
 
-    }
+                       }
 
-    // Resolve Includes
+                       // 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 includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm;
+                       program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
+                       program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences );
 
-    function resolveIncludes( string ) {
+               }
 
-       return string.replace( includePattern, includeReplacer );
+       }
 
-    }
+       return {
 
-    function includeReplacer( match, include ) {
+               update: update
 
-       const string = ShaderChunk[ include ];
+       };
 
-       if ( string === undefined ) {
+    }
 
-               throw new Error( 'Can not resolve #include <' + include + '>' );
+    function WebGLObjects( gl, geometries, attributes, info ) {
 
-       }
+       let updateMap = new WeakMap();
 
-       return resolveIncludes( string );
+       function update( object ) {
 
-    }
+               const frame = info.render.frame;
 
-    // Unroll Loops
+               const geometry = object.geometry;
+               const buffergeometry = geometries.get( object, geometry );
 
-    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;
+               // Update once per frame
 
-    function unrollLoops( string ) {
+               if ( updateMap.get( buffergeometry ) !== frame ) {
 
-       return string
-               .replace( unrollLoopPattern, loopReplacer )
-               .replace( deprecatedUnrollLoopPattern, deprecatedLoopReplacer );
+                       geometries.update( buffergeometry );
 
-    }
+                       updateMap.set( buffergeometry, frame );
 
-    function deprecatedLoopReplacer( match, start, end, snippet ) {
+               }
 
-       console.warn( 'WebGLProgram: #pragma unroll_loop shader syntax is deprecated. Please use #pragma unroll_loop_start syntax instead.' );
-       return loopReplacer( match, start, end, snippet );
+               if ( object.isInstancedMesh ) {
 
-    }
+                       if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) {
 
-    function loopReplacer( match, start, end, snippet ) {
+                               object.addEventListener( 'dispose', onInstancedMeshDispose );
 
-       let string = '';
+                       }
 
-       for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) {
+                       attributes.update( object.instanceMatrix, 34962 );
 
-               string += snippet
-                       .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' )
-                       .replace( /UNROLLED_LOOP_INDEX/g, i );
+                       if ( object.instanceColor !== null ) {
 
-       }
+                               attributes.update( object.instanceColor, 34962 );
 
-       return string;
+                       }
 
-    }
+               }
 
-    //
+               return buffergeometry;
 
-    function generatePrecision( parameters ) {
+       }
 
-       let precisionstring = 'precision ' + parameters.precision + ' float;\nprecision ' + parameters.precision + ' int;';
+       function dispose() {
 
-       if ( parameters.precision === 'highp' ) {
+               updateMap = new WeakMap();
 
-               precisionstring += '\n#define HIGH_PRECISION';
+       }
 
-       } else if ( parameters.precision === 'mediump' ) {
+       function onInstancedMeshDispose( event ) {
 
-               precisionstring += '\n#define MEDIUM_PRECISION';
+               const instancedMesh = event.target;
 
-       } else if ( parameters.precision === 'lowp' ) {
+               instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose );
 
-               precisionstring += '\n#define LOW_PRECISION';
+               attributes.remove( instancedMesh.instanceMatrix );
 
-       }
+               if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor );
 
-       return precisionstring;
+       }
+
+       return {
+
+               update: update,
+               dispose: dispose
+
+       };
 
     }
 
-    function generateShadowMapTypeDefine( parameters ) {
+    class DataTexture3D extends Texture {
 
-       let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC';
+       constructor( data = null, width = 1, height = 1, depth = 1 ) {
 
-       if ( parameters.shadowMapType === PCFShadowMap ) {
+               // 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
 
-               shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF';
+               super( null );
 
-       } else if ( parameters.shadowMapType === PCFSoftShadowMap ) {
+               this.image = { data, width, height, depth };
 
-               shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT';
+               this.magFilter = NearestFilter;
+               this.minFilter = NearestFilter;
 
-       } else if ( parameters.shadowMapType === VSMShadowMap ) {
+               this.wrapR = ClampToEdgeWrapping;
 
-               shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM';
+               this.generateMipmaps = false;
+               this.flipY = false;
+               this.unpackAlignment = 1;
 
-       }
+               this.needsUpdate = true;
 
-       return shadowMapTypeDefine;
+       }
 
     }
 
-    function generateEnvMapTypeDefine( parameters ) {
+    DataTexture3D.prototype.isDataTexture3D = true;
 
-       let envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
+    /**
+     * 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 CubeReflectionMapping:
-                       case CubeRefractionMapping:
-                               envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
-                               break;
+    // Array Caches (provide typed arrays for temporary by size)
 
-                       case CubeUVReflectionMapping:
-                       case CubeUVRefractionMapping:
-                               envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV';
-                               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 envMapTypeDefine;
+    // Flattening for arrays of vectors and matrices
 
-    }
+    function flatten( array, nBlocks, blockSize ) {
 
-    function generateEnvMapModeDefine( parameters ) {
+       const firstElem = array[ 0 ];
 
-       let envMapModeDefine = 'ENVMAP_MODE_REFLECTION';
+       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.envMapMode ) {
+       if ( r === undefined ) {
 
-                       case CubeRefractionMapping:
-                       case CubeUVRefractionMapping:
+               r = new Float32Array( n );
+               arrayCacheF32[ n ] = r;
 
-                               envMapModeDefine = 'ENVMAP_MODE_REFRACTION';
-                               break;
+       }
 
-               }
+       if ( nBlocks !== 0 ) {
 
-       }
+               firstElem.toArray( r, 0 );
 
-       return envMapModeDefine;
+               for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) {
 
-    }
+                       offset += blockSize;
+                       array[ i ].toArray( r, offset );
 
-    function generateEnvMapBlendingDefine( parameters ) {
+               }
 
-       let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE';
+       }
 
-       if ( parameters.envMap ) {
+       return r;
 
-               switch ( parameters.combine ) {
+    }
 
-                       case MultiplyOperation:
-                               envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY';
-                               break;
+    function arraysEqual( a, b ) {
 
-                       case MixOperation:
-                               envMapBlendingDefine = 'ENVMAP_BLENDING_MIX';
-                               break;
+       if ( a.length !== b.length ) return false;
 
-                       case AddOperation:
-                               envMapBlendingDefine = 'ENVMAP_BLENDING_ADD';
-                               break;
+       for ( let i = 0, l = a.length; i < l; i ++ ) {
 
-               }
+               if ( a[ i ] !== b[ i ] ) return false;
 
        }
 
-       return envMapBlendingDefine;
+       return true;
 
     }
 
-    function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
+    function copyArray( a, b ) {
 
-       const gl = renderer.getContext();
+       for ( let i = 0, l = b.length; i < l; i ++ ) {
 
-       const defines = parameters.defines;
+               a[ i ] = b[ i ];
 
-       let vertexShader = parameters.vertexShader;
-       let fragmentShader = parameters.fragmentShader;
+       }
 
-       const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters );
-       const envMapTypeDefine = generateEnvMapTypeDefine( parameters );
-       const envMapModeDefine = generateEnvMapModeDefine( parameters );
-       const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters );
+    }
 
+    // Texture unit allocation
 
-       const gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0;
+    function allocTexUnits( textures, n ) {
 
-       const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters );
+       let r = arrayCacheI32[ n ];
 
-       const customDefines = generateDefines( defines );
+       if ( r === undefined ) {
 
-       const program = gl.createProgram();
+               r = new Int32Array( n );
+               arrayCacheI32[ n ] = r;
 
-       let prefixVertex, prefixFragment;
-       let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : '';
+       }
 
-       if ( parameters.isRawShaderMaterial ) {
+       for ( let i = 0; i !== n; ++ i ) {
 
-               prefixVertex = [
+               r[ i ] = textures.allocateTextureUnit();
 
-                       customDefines
+       }
 
-               ].filter( filterEmptyLine ).join( '\n' );
+       return r;
 
-               if ( prefixVertex.length > 0 ) {
+    }
 
-                       prefixVertex += '\n';
+    // --- Setters ---
 
-               }
+    // Note: Defining these methods externally, because they come in a bunch
+    // and this way their names minify.
 
-               prefixFragment = [
+    // Single scalar
 
-                       customExtensions,
-                       customDefines
+    function setValueV1f( gl, v ) {
 
-               ].filter( filterEmptyLine ).join( '\n' );
+       const cache = this.cache;
 
-               if ( prefixFragment.length > 0 ) {
+       if ( cache[ 0 ] === v ) return;
 
-                       prefixFragment += '\n';
+       gl.uniform1f( this.addr, v );
 
-               }
+       cache[ 0 ] = v;
 
-       } else {
+    }
 
-               prefixVertex = [
+    // Single float vector (from flat array or THREE.VectorN)
 
-                       generatePrecision( parameters ),
+    function setValueV2f( gl, v ) {
 
-                       '#define SHADER_NAME ' + parameters.shaderName,
+       const cache = this.cache;
 
-                       customDefines,
+       if ( v.x !== undefined ) {
 
-                       parameters.instancing ? '#define USE_INSTANCING' : '',
-                       parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '',
+               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) {
 
-                       parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '',
+                       gl.uniform2f( this.addr, v.x, v.y );
 
-                       '#define GAMMA_FACTOR ' + gammaFactorDefine,
+                       cache[ 0 ] = v.x;
+                       cache[ 1 ] = v.y;
 
-                       '#define MAX_BONES ' + parameters.maxBones,
-                       ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
-                       ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',
+               }
 
-                       parameters.map ? '#define USE_MAP' : '',
-                       parameters.envMap ? '#define USE_ENVMAP' : '',
-                       parameters.envMap ? '#define ' + envMapModeDefine : '',
-                       parameters.lightMap ? '#define USE_LIGHTMAP' : '',
-                       parameters.aoMap ? '#define USE_AOMAP' : '',
-                       parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',
-                       parameters.bumpMap ? '#define USE_BUMPMAP' : '',
-                       parameters.normalMap ? '#define USE_NORMALMAP' : '',
-                       ( parameters.normalMap && parameters.objectSpaceNormalMap ) ? '#define OBJECTSPACE_NORMALMAP' : '',
-                       ( parameters.normalMap && parameters.tangentSpaceNormalMap ) ? '#define TANGENTSPACE_NORMALMAP' : '',
+       } else {
 
-                       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' : '',
+               if ( arraysEqual( cache, v ) ) return;
 
-                       parameters.vertexTangents ? '#define USE_TANGENT' : '',
-                       parameters.vertexColors ? '#define USE_COLOR' : '',
-                       parameters.vertexUvs ? '#define USE_UV' : '',
-                       parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',
+               gl.uniform2fv( this.addr, v );
 
-                       parameters.flatShading ? '#define FLAT_SHADED' : '',
+               copyArray( cache, v );
 
-                       parameters.skinning ? '#define USE_SKINNING' : '',
-                       parameters.useVertexTexture ? '#define BONE_TEXTURE' : '',
+       }
 
-                       parameters.morphTargets ? '#define USE_MORPHTARGETS' : '',
-                       parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '',
-                       parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
-                       parameters.flipSided ? '#define FLIP_SIDED' : '',
+    }
 
-                       parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
-                       parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
+    function setValueV3f( gl, v ) {
 
-                       parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '',
+       const cache = this.cache;
 
-                       parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
-                       ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
+       if ( v.x !== undefined ) {
 
-                       'uniform mat4 modelMatrix;',
-                       'uniform mat4 modelViewMatrix;',
-                       'uniform mat4 projectionMatrix;',
-                       'uniform mat4 viewMatrix;',
-                       'uniform mat3 normalMatrix;',
-                       'uniform vec3 cameraPosition;',
-                       'uniform bool isOrthographic;',
+               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) {
 
-                       '#ifdef USE_INSTANCING',
+                       gl.uniform3f( this.addr, v.x, v.y, v.z );
 
-                       '       attribute mat4 instanceMatrix;',
+                       cache[ 0 ] = v.x;
+                       cache[ 1 ] = v.y;
+                       cache[ 2 ] = v.z;
 
-                       '#endif',
+               }
 
-                       '#ifdef USE_INSTANCING_COLOR',
+       } else if ( v.r !== undefined ) {
 
-                       '       attribute vec3 instanceColor;',
+               if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) {
 
-                       '#endif',
+                       gl.uniform3f( this.addr, v.r, v.g, v.b );
 
-                       'attribute vec3 position;',
-                       'attribute vec3 normal;',
-                       'attribute vec2 uv;',
+                       cache[ 0 ] = v.r;
+                       cache[ 1 ] = v.g;
+                       cache[ 2 ] = v.b;
 
-                       '#ifdef USE_TANGENT',
+               }
 
-                       '       attribute vec4 tangent;',
+       } else {
 
-                       '#endif',
+               if ( arraysEqual( cache, v ) ) return;
 
-                       '#ifdef USE_COLOR',
+               gl.uniform3fv( this.addr, v );
 
-                       '       attribute vec3 color;',
+               copyArray( cache, v );
 
-                       '#endif',
+       }
 
-                       '#ifdef USE_MORPHTARGETS',
+    }
 
-                       '       attribute vec3 morphTarget0;',
-                       '       attribute vec3 morphTarget1;',
-                       '       attribute vec3 morphTarget2;',
-                       '       attribute vec3 morphTarget3;',
+    function setValueV4f( gl, v ) {
 
-                       '       #ifdef USE_MORPHNORMALS',
+       const cache = this.cache;
 
-                       '               attribute vec3 morphNormal0;',
-                       '               attribute vec3 morphNormal1;',
-                       '               attribute vec3 morphNormal2;',
-                       '               attribute vec3 morphNormal3;',
+       if ( v.x !== undefined ) {
 
-                       '       #else',
+               if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) {
 
-                       '               attribute vec3 morphTarget4;',
-                       '               attribute vec3 morphTarget5;',
-                       '               attribute vec3 morphTarget6;',
-                       '               attribute vec3 morphTarget7;',
+                       gl.uniform4f( this.addr, v.x, v.y, v.z, v.w );
 
-                       '       #endif',
+                       cache[ 0 ] = v.x;
+                       cache[ 1 ] = v.y;
+                       cache[ 2 ] = v.z;
+                       cache[ 3 ] = v.w;
 
-                       '#endif',
+               }
 
-                       '#ifdef USE_SKINNING',
+       } else {
 
-                       '       attribute vec4 skinIndex;',
-                       '       attribute vec4 skinWeight;',
+               if ( arraysEqual( cache, v ) ) return;
 
-                       '#endif',
+               gl.uniform4fv( this.addr, v );
 
-                       '\n'
+               copyArray( cache, v );
 
-               ].filter( filterEmptyLine ).join( '\n' );
+       }
 
-               prefixFragment = [
+    }
 
-                       customExtensions,
+    // Single matrix (from flat array or THREE.MatrixN)
 
-                       generatePrecision( parameters ),
+    function setValueM2( gl, v ) {
 
-                       '#define SHADER_NAME ' + parameters.shaderName,
+       const cache = this.cache;
+       const elements = v.elements;
 
-                       customDefines,
+       if ( elements === undefined ) {
 
-                       parameters.alphaTest ? '#define ALPHATEST ' + parameters.alphaTest + ( parameters.alphaTest % 1 ? '' : '.0' ) : '', // add '.0' if integer
+               if ( arraysEqual( cache, v ) ) return;
 
-                       '#define GAMMA_FACTOR ' + gammaFactorDefine,
+               gl.uniformMatrix2fv( this.addr, false, v );
 
-                       ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
-                       ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',
+               copyArray( cache, v );
 
-                       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' : '',
+       } else {
 
-                       parameters.sheen ? '#define USE_SHEEN' : '',
-                       parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '',
+               if ( arraysEqual( cache, elements ) ) return;
 
-                       parameters.vertexTangents ? '#define USE_TANGENT' : '',
-                       parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '',
-                       parameters.vertexUvs ? '#define USE_UV' : '',
-                       parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',
+               mat2array.set( elements );
 
-                       parameters.gradientMap ? '#define USE_GRADIENTMAP' : '',
+               gl.uniformMatrix2fv( this.addr, false, mat2array );
 
-                       parameters.flatShading ? '#define FLAT_SHADED' : '',
+               copyArray( cache, elements );
 
-                       parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
-                       parameters.flipSided ? '#define FLIP_SIDED' : '',
+       }
 
-                       parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
-                       parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
+    }
 
-                       parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '',
+    function setValueM3( gl, v ) {
 
-                       parameters.physicallyCorrectLights ? '#define PHYSICALLY_CORRECT_LIGHTS' : '',
+       const cache = this.cache;
+       const elements = v.elements;
 
-                       parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
-                       ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
+       if ( elements === undefined ) {
 
-                       ( ( parameters.extensionShaderTextureLOD || parameters.envMap ) && parameters.rendererExtensionShaderTextureLod ) ? '#define TEXTURE_LOD_EXT' : '',
+               if ( arraysEqual( cache, v ) ) return;
 
-                       'uniform mat4 viewMatrix;',
-                       'uniform vec3 cameraPosition;',
-                       'uniform bool isOrthographic;',
+               gl.uniformMatrix3fv( this.addr, false, v );
 
-                       ( 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 ) : '',
+               copyArray( cache, v );
 
-                       parameters.dithering ? '#define DITHERING' : '',
+       } else {
 
-                       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 ),
+               if ( arraysEqual( cache, elements ) ) return;
 
-                       parameters.depthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '',
+               mat3array.set( elements );
 
-                       '\n'
+               gl.uniformMatrix3fv( this.addr, false, mat3array );
 
-               ].filter( filterEmptyLine ).join( '\n' );
+               copyArray( cache, elements );
 
        }
 
-       vertexShader = resolveIncludes( vertexShader );
-       vertexShader = replaceLightNums( vertexShader, parameters );
-       vertexShader = replaceClippingPlaneNums( vertexShader, parameters );
+    }
 
-       fragmentShader = resolveIncludes( fragmentShader );
-       fragmentShader = replaceLightNums( fragmentShader, parameters );
-       fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters );
+    function setValueM4( gl, v ) {
 
-       vertexShader = unrollLoops( vertexShader );
-       fragmentShader = unrollLoops( fragmentShader );
+       const cache = this.cache;
+       const elements = v.elements;
 
-       if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) {
+       if ( elements === undefined ) {
 
-               // GLSL 3.0 conversion for built-in materials and ShaderMaterial
+               if ( arraysEqual( cache, v ) ) return;
 
-               versionString = '#version 300 es\n';
+               gl.uniformMatrix4fv( this.addr, false, v );
 
-               prefixVertex = [
-                       '#define attribute in',
-                       '#define varying out',
-                       '#define texture2D texture'
-               ].join( '\n' ) + '\n' + prefixVertex;
+               copyArray( cache, v );
 
-               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;
+       } else {
 
-       }
+               if ( arraysEqual( cache, elements ) ) return;
 
-       const vertexGlsl = versionString + prefixVertex + vertexShader;
-       const fragmentGlsl = versionString + prefixFragment + fragmentShader;
+               mat4array.set( elements );
 
-       // console.log( '*VERTEX*', vertexGlsl );
-       // console.log( '*FRAGMENT*', fragmentGlsl );
+               gl.uniformMatrix4fv( this.addr, false, mat4array );
 
-       const glVertexShader = WebGLShader( gl, 35633, vertexGlsl );
-       const glFragmentShader = WebGLShader( gl, 35632, fragmentGlsl );
+               copyArray( cache, elements );
 
-       gl.attachShader( program, glVertexShader );
-       gl.attachShader( program, glFragmentShader );
+       }
 
-       // Force a particular attribute to index 0.
+    }
 
-       if ( parameters.index0AttributeName !== undefined ) {
+    // Single integer / boolean
 
-               gl.bindAttribLocation( program, 0, parameters.index0AttributeName );
+    function setValueV1i( gl, v ) {
 
-       } else if ( parameters.morphTargets === true ) {
+       const cache = this.cache;
 
-               // programs with morphTargets displace position out of attribute 0
-               gl.bindAttribLocation( program, 0, 'position' );
+       if ( cache[ 0 ] === v ) return;
 
-       }
+       gl.uniform1i( this.addr, v );
 
-       gl.linkProgram( program );
+       cache[ 0 ] = v;
 
-       // 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 vector (from flat array)
 
-               let runnable = true;
-               let haveDiagnostics = true;
+    function setValueV2i( gl, v ) {
 
-               if ( gl.getProgramParameter( program, 35714 ) === false ) {
+       const cache = this.cache;
 
-                       runnable = false;
+       if ( arraysEqual( cache, v ) ) return;
 
-                       const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' );
-                       const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' );
+       gl.uniform2iv( this.addr, v );
 
-                       console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), '35715', gl.getProgramParameter( program, 35715 ), 'gl.getProgramInfoLog', programLog, vertexErrors, fragmentErrors );
+       copyArray( cache, v );
 
-               } else if ( programLog !== '' ) {
+    }
 
-                       console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog );
+    function setValueV3i( gl, v ) {
 
-               } else if ( vertexLog === '' || fragmentLog === '' ) {
+       const cache = this.cache;
 
-                       haveDiagnostics = false;
+       if ( arraysEqual( cache, v ) ) return;
 
-               }
+       gl.uniform3iv( this.addr, v );
 
-               if ( haveDiagnostics ) {
+       copyArray( cache, v );
 
-                       this.diagnostics = {
+    }
 
-                               runnable: runnable,
+    function setValueV4i( gl, v ) {
 
-                               programLog: programLog,
+       const cache = this.cache;
 
-                               vertexShader: {
+       if ( arraysEqual( cache, v ) ) return;
 
-                                       log: vertexLog,
-                                       prefix: prefixVertex
+       gl.uniform4iv( this.addr, v );
 
-                               },
+       copyArray( cache, v );
 
-                               fragmentShader: {
+    }
 
-                                       log: fragmentLog,
-                                       prefix: prefixFragment
+    // Single unsigned integer
 
-                               }
+    function setValueV1ui( gl, v ) {
 
-                       };
+       const cache = this.cache;
 
-               }
+       if ( cache[ 0 ] === v ) return;
 
-       }
+       gl.uniform1ui( this.addr, v );
 
-       // Clean up
+       cache[ 0 ] = v;
 
-       // Crashes in iOS9 and iOS10. #18402
-       // gl.detachShader( program, glVertexShader );
-       // gl.detachShader( program, glFragmentShader );
+    }
 
-       gl.deleteShader( glVertexShader );
-       gl.deleteShader( glFragmentShader );
+    // Single unsigned integer vector (from flat array)
 
-       // set up caching for uniform locations
+    function setValueV2ui( gl, v ) {
 
-       let cachedUniforms;
+       const cache = this.cache;
 
-       this.getUniforms = function () {
+       if ( arraysEqual( cache, v ) ) return;
 
-               if ( cachedUniforms === undefined ) {
+       gl.uniform2uiv( this.addr, v );
 
-                       cachedUniforms = new WebGLUniforms( gl, program );
+       copyArray( cache, v );
 
-               }
+    }
 
-               return cachedUniforms;
+    function setValueV3ui( gl, v ) {
 
-       };
+       const cache = this.cache;
 
-       // set up caching for attribute locations
+       if ( arraysEqual( cache, v ) ) return;
 
-       let cachedAttributes;
+       gl.uniform3uiv( this.addr, v );
 
-       this.getAttributes = function () {
+       copyArray( cache, v );
 
-               if ( cachedAttributes === undefined ) {
+    }
 
-                       cachedAttributes = fetchAttributeLocations( gl, program );
+    function setValueV4ui( gl, v ) {
 
-               }
+       const cache = this.cache;
 
-               return cachedAttributes;
+       if ( arraysEqual( cache, v ) ) return;
 
-       };
+       gl.uniform4uiv( this.addr, v );
 
-       // free resource
+       copyArray( cache, v );
 
-       this.destroy = function () {
+    }
 
-               bindingStates.releaseStatesOfProgram( this );
 
-               gl.deleteProgram( program );
-               this.program = undefined;
+    // Single texture (2D / Cube)
 
-       };
+    function setValueT1( gl, v, textures ) {
 
-       //
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
 
-       this.name = parameters.shaderName;
-       this.id = programIdCount ++;
-       this.cacheKey = cacheKey;
-       this.usedTimes = 1;
-       this.program = program;
-       this.vertexShader = glVertexShader;
-       this.fragmentShader = glFragmentShader;
+       if ( cache[ 0 ] !== unit ) {
 
-       return this;
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
 
-    }
+       }
 
-    function WebGLPrograms( renderer, cubemaps, extensions, capabilities, bindingStates, clipping ) {
+       textures.safeSetTexture2D( v || emptyTexture, unit );
 
-       const programs = [];
+    }
 
-       const isWebGL2 = capabilities.isWebGL2;
-       const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer;
-       const floatVertexTextures = capabilities.floatVertexTextures;
-       const maxVertexUniforms = capabilities.maxVertexUniforms;
-       const vertexTextures = capabilities.vertexTextures;
+    function setValueT3D1( gl, v, textures ) {
 
-       let precision = capabilities.precision;
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
 
-       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'
-       };
+       if ( cache[ 0 ] !== unit ) {
 
-       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'
-       ];
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
 
-       function getMaxBones( object ) {
+       }
 
-               const skeleton = object.skeleton;
-               const bones = skeleton.bones;
+       textures.setTexture3D( v || emptyTexture3d, unit );
 
-               if ( floatVertexTextures ) {
+    }
 
-                       return 1024;
+    function setValueT6( gl, v, textures ) {
 
-               } else {
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
 
-                       // 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)
+       if ( cache[ 0 ] !== unit ) {
 
-                       const nVertexUniforms = maxVertexUniforms;
-                       const nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 );
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
 
-                       const maxBones = Math.min( nVertexMatrices, bones.length );
+       }
 
-                       if ( maxBones < bones.length ) {
+       textures.safeSetTextureCube( v || emptyCubeTexture, unit );
 
-                               console.warn( 'THREE.WebGLRenderer: Skeleton has ' + bones.length + ' bones. This GPU supports ' + maxBones + '.' );
-                               return 0;
+    }
 
-                       }
+    function setValueT2DArray1( gl, v, textures ) {
 
-                       return maxBones;
+       const cache = this.cache;
+       const unit = textures.allocateTextureUnit();
 
-               }
+       if ( cache[ 0 ] !== unit ) {
+
+               gl.uniform1i( this.addr, unit );
+               cache[ 0 ] = unit;
 
        }
 
-       function getTextureEncodingFromMap( map ) {
+       textures.setTexture2DArray( v || emptyTexture2dArray, unit );
 
-               let encoding;
+    }
 
-               if ( map && map.isTexture ) {
+    // Helper to pick the right setter for the singular case
 
-                       encoding = map.encoding;
+    function getSingularSetter( type ) {
 
-               } else if ( map && map.isWebGLRenderTarget ) {
+       switch ( type ) {
 
-                       console.warn( 'THREE.WebGLPrograms.getTextureEncodingFromMap: don\'t use render targets as textures. Use their .texture property instead.' );
-                       encoding = map.texture.encoding;
+               case 0x1406: return setValueV1f; // FLOAT
+               case 0x8b50: return setValueV2f; // _VEC2
+               case 0x8b51: return setValueV3f; // _VEC3
+               case 0x8b52: return setValueV4f; // _VEC4
 
-               } else {
+               case 0x8b5a: return setValueM2; // _MAT2
+               case 0x8b5b: return setValueM3; // _MAT3
+               case 0x8b5c: return setValueM4; // _MAT4
 
-                       encoding = LinearEncoding;
+               case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL
+               case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2
+               case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3
+               case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4
 
-               }
+               case 0x1405: return setValueV1ui; // UINT
+               case 0x8dc6: return setValueV2ui; // _VEC2
+               case 0x8dc7: return setValueV3ui; // _VEC3
+               case 0x8dc8: return setValueV4ui; // _VEC4
 
-               return encoding;
+               case 0x8b5e: // SAMPLER_2D
+               case 0x8d66: // SAMPLER_EXTERNAL_OES
+               case 0x8dca: // INT_SAMPLER_2D
+               case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D
+               case 0x8b62: // SAMPLER_2D_SHADOW
+                       return setValueT1;
+
+               case 0x8b5f: // SAMPLER_3D
+               case 0x8dcb: // INT_SAMPLER_3D
+               case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D
+                       return setValueT3D1;
+
+               case 0x8b60: // SAMPLER_CUBE
+               case 0x8dcc: // INT_SAMPLER_CUBE
+               case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE
+               case 0x8dc5: // SAMPLER_CUBE_SHADOW
+                       return setValueT6;
+
+               case 0x8dc1: // SAMPLER_2D_ARRAY
+               case 0x8dcf: // INT_SAMPLER_2D_ARRAY
+               case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY
+               case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW
+                       return setValueT2DArray1;
 
        }
 
-       function getParameters( material, lights, shadows, scene, object ) {
+    }
 
-               const fog = scene.fog;
-               const environment = material.isMeshStandardMaterial ? scene.environment : null;
 
-               const envMap = cubemaps.get( material.envMap || environment );
+    // Array of scalars
 
-               const shaderID = shaderIDs[ material.type ];
+    function setValueV1fArray( gl, v ) {
 
-               // heuristics to create shader parameters according to lights in the scene
-               // (not to blow over maxLights budget)
+       gl.uniform1fv( this.addr, v );
 
-               const maxBones = object.isSkinnedMesh ? getMaxBones( object ) : 0;
+    }
 
-               if ( material.precision !== null ) {
+    // Array of vectors (from flat array or array of THREE.VectorN)
 
-                       precision = capabilities.getMaxPrecision( material.precision );
+    function setValueV2fArray( gl, v ) {
 
-                       if ( precision !== material.precision ) {
+       const data = flatten( v, this.size, 2 );
 
-                               console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' );
+       gl.uniform2fv( this.addr, data );
 
-                       }
+    }
 
-               }
+    function setValueV3fArray( gl, v ) {
 
-               let vertexShader, fragmentShader;
+       const data = flatten( v, this.size, 3 );
 
-               if ( shaderID ) {
+       gl.uniform3fv( this.addr, data );
 
-                       const shader = ShaderLib[ shaderID ];
+    }
 
-                       vertexShader = shader.vertexShader;
-                       fragmentShader = shader.fragmentShader;
+    function setValueV4fArray( gl, v ) {
 
-               } else {
+       const data = flatten( v, this.size, 4 );
 
-                       vertexShader = material.vertexShader;
-                       fragmentShader = material.fragmentShader;
+       gl.uniform4fv( this.addr, data );
 
-               }
+    }
 
-               const currentRenderTarget = renderer.getRenderTarget();
+    // Array of matrices (from flat array or array of THREE.MatrixN)
 
-               const parameters = {
+    function setValueM2Array( gl, v ) {
 
-                       isWebGL2: isWebGL2,
+       const data = flatten( v, this.size, 4 );
 
-                       shaderID: shaderID,
-                       shaderName: material.type,
+       gl.uniformMatrix2fv( this.addr, false, data );
 
-                       vertexShader: vertexShader,
-                       fragmentShader: fragmentShader,
-                       defines: material.defines,
+    }
 
-                       isRawShaderMaterial: material.isRawShaderMaterial === true,
-                       glslVersion: material.glslVersion,
+    function setValueM3Array( gl, v ) {
 
-                       precision: precision,
+       const data = flatten( v, this.size, 9 );
 
-                       instancing: object.isInstancedMesh === true,
-                       instancingColor: object.isInstancedMesh === true && object.instanceColor !== null,
+       gl.uniformMatrix3fv( this.addr, false, data );
 
-                       supportsVertexTextures: vertexTextures,
-                       outputEncoding: ( currentRenderTarget !== null ) ? getTextureEncodingFromMap( currentRenderTarget.texture ) : renderer.outputEncoding,
-                       map: !! material.map,
-                       mapEncoding: getTextureEncodingFromMap( material.map ),
-                       matcap: !! material.matcap,
-                       matcapEncoding: getTextureEncodingFromMap( material.matcap ),
-                       envMap: !! envMap,
-                       envMapMode: envMap && envMap.mapping,
-                       envMapEncoding: getTextureEncodingFromMap( envMap ),
-                       envMapCubeUV: ( !! envMap ) && ( ( envMap.mapping === CubeUVReflectionMapping ) || ( envMap.mapping === CubeUVRefractionMapping ) ),
-                       lightMap: !! material.lightMap,
-                       lightMapEncoding: getTextureEncodingFromMap( material.lightMap ),
-                       aoMap: !! material.aoMap,
-                       emissiveMap: !! material.emissiveMap,
-                       emissiveMapEncoding: getTextureEncodingFromMap( material.emissiveMap ),
-                       bumpMap: !! material.bumpMap,
-                       normalMap: !! material.normalMap,
-                       objectSpaceNormalMap: material.normalMapType === ObjectSpaceNormalMap,
-                       tangentSpaceNormalMap: material.normalMapType === TangentSpaceNormalMap,
-                       clearcoatMap: !! material.clearcoatMap,
-                       clearcoatRoughnessMap: !! material.clearcoatRoughnessMap,
-                       clearcoatNormalMap: !! material.clearcoatNormalMap,
-                       displacementMap: !! material.displacementMap,
-                       roughnessMap: !! material.roughnessMap,
-                       metalnessMap: !! material.metalnessMap,
-                       specularMap: !! material.specularMap,
-                       alphaMap: !! material.alphaMap,
+    }
 
-                       gradientMap: !! material.gradientMap,
+    function setValueM4Array( gl, v ) {
 
-                       sheen: !! material.sheen,
+       const data = flatten( v, this.size, 16 );
 
-                       transmissionMap: !! material.transmissionMap,
+       gl.uniformMatrix4fv( this.addr, false, data );
 
-                       combine: material.combine,
+    }
 
-                       vertexTangents: ( material.normalMap && material.vertexTangents ),
-                       vertexColors: material.vertexColors,
-                       vertexUvs: !! material.map || !! material.bumpMap || !! material.normalMap || !! material.specularMap || !! material.alphaMap || !! material.emissiveMap || !! material.roughnessMap || !! material.metalnessMap || !! material.clearcoatMap || !! material.clearcoatRoughnessMap || !! material.clearcoatNormalMap || !! material.displacementMap || !! material.transmissionMap,
-                       uvsVertexOnly: ! ( !! material.map || !! material.bumpMap || !! material.normalMap || !! material.specularMap || !! material.alphaMap || !! material.emissiveMap || !! material.roughnessMap || !! material.metalnessMap || !! material.clearcoatNormalMap || !! material.transmissionMap ) && !! material.displacementMap,
+    // Array of integer / boolean
 
-                       fog: !! fog,
-                       useFog: material.fog,
-                       fogExp2: ( fog && fog.isFogExp2 ),
+    function setValueV1iArray( gl, v ) {
 
-                       flatShading: material.flatShading,
+       gl.uniform1iv( this.addr, v );
 
-                       sizeAttenuation: material.sizeAttenuation,
-                       logarithmicDepthBuffer: logarithmicDepthBuffer,
+    }
 
-                       skinning: material.skinning && maxBones > 0,
-                       maxBones: maxBones,
-                       useVertexTexture: floatVertexTextures,
+    // Array of integer / boolean vectors (from flat array)
 
-                       morphTargets: material.morphTargets,
-                       morphNormals: material.morphNormals,
-                       maxMorphTargets: renderer.maxMorphTargets,
-                       maxMorphNormals: renderer.maxMorphNormals,
+    function setValueV2iArray( gl, v ) {
 
-                       numDirLights: lights.directional.length,
-                       numPointLights: lights.point.length,
-                       numSpotLights: lights.spot.length,
-                       numRectAreaLights: lights.rectArea.length,
-                       numHemiLights: lights.hemi.length,
+       gl.uniform2iv( this.addr, v );
 
-                       numDirLightShadows: lights.directionalShadowMap.length,
-                       numPointLightShadows: lights.pointShadowMap.length,
-                       numSpotLightShadows: lights.spotShadowMap.length,
+    }
 
-                       numClippingPlanes: clipping.numPlanes,
-                       numClipIntersection: clipping.numIntersection,
+    function setValueV3iArray( gl, v ) {
 
-                       dithering: material.dithering,
+       gl.uniform3iv( this.addr, v );
 
-                       shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0,
-                       shadowMapType: renderer.shadowMap.type,
+    }
 
-                       toneMapping: material.toneMapped ? renderer.toneMapping : NoToneMapping,
-                       physicallyCorrectLights: renderer.physicallyCorrectLights,
+    function setValueV4iArray( gl, v ) {
 
-                       premultipliedAlpha: material.premultipliedAlpha,
+       gl.uniform4iv( this.addr, v );
 
-                       alphaTest: material.alphaTest,
-                       doubleSided: material.side === DoubleSide,
-                       flipSided: material.side === BackSide,
+    }
 
-                       depthPacking: ( material.depthPacking !== undefined ) ? material.depthPacking : false,
+    // Array of unsigned integer
 
-                       index0AttributeName: material.index0AttributeName,
+    function setValueV1uiArray( gl, v ) {
 
-                       extensionDerivatives: material.extensions && material.extensions.derivatives,
-                       extensionFragDepth: material.extensions && material.extensions.fragDepth,
-                       extensionDrawBuffers: material.extensions && material.extensions.drawBuffers,
-                       extensionShaderTextureLOD: material.extensions && material.extensions.shaderTextureLOD,
+       gl.uniform1uiv( this.addr, v );
 
-                       rendererExtensionFragDepth: isWebGL2 || extensions.has( 'EXT_frag_depth' ),
-                       rendererExtensionDrawBuffers: isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ),
-                       rendererExtensionShaderTextureLod: isWebGL2 || extensions.has( 'EXT_shader_texture_lod' ),
+    }
 
-                       customProgramCacheKey: material.customProgramCacheKey()
+    // Array of unsigned integer vectors (from flat array)
 
-               };
+    function setValueV2uiArray( gl, v ) {
 
-               return parameters;
+       gl.uniform2uiv( this.addr, v );
 
-       }
+    }
 
-       function getProgramCacheKey( parameters ) {
+    function setValueV3uiArray( gl, v ) {
 
-               const array = [];
+       gl.uniform3uiv( this.addr, v );
 
-               if ( parameters.shaderID ) {
+    }
 
-                       array.push( parameters.shaderID );
+    function setValueV4uiArray( gl, v ) {
 
-               } else {
+       gl.uniform4uiv( this.addr, v );
 
-                       array.push( parameters.fragmentShader );
-                       array.push( parameters.vertexShader );
+    }
 
-               }
 
-               if ( parameters.defines !== undefined ) {
+    // Array of textures (2D / Cube)
 
-                       for ( const name in parameters.defines ) {
+    function setValueT1Array( gl, v, textures ) {
 
-                               array.push( name );
-                               array.push( parameters.defines[ name ] );
+       const n = v.length;
 
-                       }
+       const units = allocTexUnits( textures, n );
 
-               }
+       gl.uniform1iv( this.addr, units );
 
-               if ( parameters.isRawShaderMaterial === false ) {
+       for ( let i = 0; i !== n; ++ i ) {
 
-                       for ( let i = 0; i < parameterNames.length; i ++ ) {
+               textures.safeSetTexture2D( v[ i ] || emptyTexture, units[ i ] );
 
-                               array.push( parameters[ parameterNames[ i ] ] );
+       }
 
-                       }
+    }
 
-                       array.push( renderer.outputEncoding );
-                       array.push( renderer.gammaFactor );
+    function setValueT6Array( gl, v, textures ) {
 
-               }
+       const n = v.length;
 
-               array.push( parameters.customProgramCacheKey );
+       const units = allocTexUnits( textures, n );
 
-               return array.join();
+       gl.uniform1iv( this.addr, units );
+
+       for ( let i = 0; i !== n; ++ i ) {
+
+               textures.safeSetTextureCube( v[ i ] || emptyCubeTexture, units[ i ] );
 
        }
 
-       function getUniforms( material ) {
+    }
 
-               const shaderID = shaderIDs[ material.type ];
-               let uniforms;
+    // Helper to pick the right setter for a pure (bottom-level) array
 
-               if ( shaderID ) {
+    function getPureArraySetter( type ) {
 
-                       const shader = ShaderLib[ shaderID ];
-                       uniforms = UniformsUtils.clone( shader.uniforms );
+       switch ( type ) {
 
-               } else {
+               case 0x1406: return setValueV1fArray; // FLOAT
+               case 0x8b50: return setValueV2fArray; // _VEC2
+               case 0x8b51: return setValueV3fArray; // _VEC3
+               case 0x8b52: return setValueV4fArray; // _VEC4
 
-                       uniforms = material.uniforms;
+               case 0x8b5a: return setValueM2Array; // _MAT2
+               case 0x8b5b: return setValueM3Array; // _MAT3
+               case 0x8b5c: return setValueM4Array; // _MAT4
 
-               }
+               case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL
+               case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2
+               case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3
+               case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4
 
-               return uniforms;
+               case 0x1405: return setValueV1uiArray; // UINT
+               case 0x8dc6: return setValueV2uiArray; // _VEC2
+               case 0x8dc7: return setValueV3uiArray; // _VEC3
+               case 0x8dc8: return setValueV4uiArray; // _VEC4
+
+               case 0x8b5e: // SAMPLER_2D
+               case 0x8d66: // SAMPLER_EXTERNAL_OES
+               case 0x8dca: // INT_SAMPLER_2D
+               case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D
+               case 0x8b62: // SAMPLER_2D_SHADOW
+                       return setValueT1Array;
+
+               case 0x8b60: // SAMPLER_CUBE
+               case 0x8dcc: // INT_SAMPLER_CUBE
+               case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE
+               case 0x8dc5: // SAMPLER_CUBE_SHADOW
+                       return setValueT6Array;
 
        }
 
-       function acquireProgram( parameters, cacheKey ) {
+    }
 
-               let program;
+    // --- Uniform Classes ---
 
-               // Check if code has been already compiled
-               for ( let p = 0, pl = programs.length; p < pl; p ++ ) {
+    function SingleUniform( id, activeInfo, addr ) {
 
-                       const preexistingProgram = programs[ p ];
+       this.id = id;
+       this.addr = addr;
+       this.cache = [];
+       this.setValue = getSingularSetter( activeInfo.type );
 
-                       if ( preexistingProgram.cacheKey === cacheKey ) {
+       // this.path = activeInfo.name; // DEBUG
 
-                               program = preexistingProgram;
-                               ++ program.usedTimes;
+    }
 
-                               break;
+    function PureArrayUniform( id, activeInfo, addr ) {
 
-                       }
+       this.id = id;
+       this.addr = addr;
+       this.cache = [];
+       this.size = activeInfo.size;
+       this.setValue = getPureArraySetter( activeInfo.type );
 
-               }
+       // this.path = activeInfo.name; // DEBUG
 
-               if ( program === undefined ) {
+    }
 
-                       program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates );
-                       programs.push( program );
+    PureArrayUniform.prototype.updateCache = function ( data ) {
 
-               }
+       const cache = this.cache;
 
-               return program;
+       if ( data instanceof Float32Array && cache.length !== data.length ) {
+
+               this.cache = new Float32Array( data.length );
 
        }
 
-       function releaseProgram( program ) {
+       copyArray( cache, data );
 
-               if ( -- program.usedTimes === 0 ) {
+    };
 
-                       // Remove from unordered set
-                       const i = programs.indexOf( program );
-                       programs[ i ] = programs[ programs.length - 1 ];
-                       programs.pop();
+    function StructuredUniform( id ) {
 
-                       // Free WebGL resources
-                       program.destroy();
+       this.id = id;
 
-               }
+       this.seq = [];
+       this.map = {};
 
-       }
+    }
 
-       return {
-               getParameters: getParameters,
-               getProgramCacheKey: getProgramCacheKey,
-               getUniforms: getUniforms,
-               acquireProgram: acquireProgram,
-               releaseProgram: releaseProgram,
-               // Exposed for resource monitoring & error feedback via renderer.info:
-               programs: programs
-       };
+    StructuredUniform.prototype.setValue = function ( gl, value, textures ) {
 
-    }
+       const seq = this.seq;
 
-    function WebGLProperties() {
+       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
 
-       let properties = new WeakMap();
+               const u = seq[ i ];
+               u.setValue( gl, value[ u.id ], textures );
 
-       function get( object ) {
+       }
 
-               let map = properties.get( object );
+    };
 
-               if ( map === undefined ) {
+    // --- Top-level ---
 
-                       map = {};
-                       properties.set( object, map );
+    // Parser - builds up the property tree from the path strings
 
-               }
+    const RePathPart = /(\w+)(\])?(\[|\.)?/g;
 
-               return map;
+    // 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 ) {
 
-       function remove( object ) {
+       container.seq.push( uniformObject );
+       container.map[ uniformObject.id ] = uniformObject;
 
-               properties.delete( object );
+    }
 
-       }
+    function parseUniform( activeInfo, addr, container ) {
 
-       function update( object, key, value ) {
+       const path = activeInfo.name,
+               pathLength = path.length;
 
-               properties.get( object )[ key ] = value;
+       // reset RegExp object, because of the early exit of a previous run
+       RePathPart.lastIndex = 0;
 
-       }
+       while ( true ) {
 
-       function dispose() {
+               const match = RePathPart.exec( path ),
+                       matchEnd = RePathPart.lastIndex;
 
-               properties = new WeakMap();
+               let id = match[ 1 ];
+               const idIsIndex = match[ 2 ] === ']',
+                       subscript = match[ 3 ];
 
-       }
+               if ( idIsIndex ) id = id | 0; // convert to integer
 
-       return {
-               get: get,
-               remove: remove,
-               update: update,
-               dispose: dispose
-       };
+               if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) {
 
-    }
+                       // bare name or "pure" bottom-level array "[0]" suffix
 
-    function painterSortStable( a, b ) {
+                       addUniform( container, subscript === undefined ?
+                               new SingleUniform( id, activeInfo, addr ) :
+                               new PureArrayUniform( id, activeInfo, addr ) );
 
-       if ( a.groupOrder !== b.groupOrder ) {
+                       break;
 
-               return a.groupOrder - b.groupOrder;
+               } else {
 
-       } else if ( a.renderOrder !== b.renderOrder ) {
+                       // step into inner node / create it in case it doesn't exist
 
-               return a.renderOrder - b.renderOrder;
+                       const map = container.map;
+                       let next = map[ id ];
 
-       } else if ( a.program !== b.program ) {
+                       if ( next === undefined ) {
 
-               return a.program.id - b.program.id;
+                               next = new StructuredUniform( id );
+                               addUniform( container, next );
 
-       } else if ( a.material.id !== b.material.id ) {
+                       }
 
-               return a.material.id - b.material.id;
+                       container = next;
 
-       } else if ( a.z !== b.z ) {
+               }
 
-               return a.z - b.z;
+       }
 
-       } else {
+    }
 
-               return a.id - b.id;
+    // Root Container
+
+    function WebGLUniforms( gl, program ) {
+
+       this.seq = [];
+       this.map = {};
+
+       const n = gl.getProgramParameter( program, 35718 );
+
+       for ( let i = 0; i < n; ++ i ) {
+
+               const info = gl.getActiveUniform( program, i ),
+                       addr = gl.getUniformLocation( program, info.name );
+
+               parseUniform( info, addr, this );
 
        }
 
     }
 
-    function reversePainterSortStable( a, b ) {
+    WebGLUniforms.prototype.setValue = function ( gl, name, value, textures ) {
 
-       if ( a.groupOrder !== b.groupOrder ) {
+       const u = this.map[ name ];
 
-               return a.groupOrder - b.groupOrder;
+       if ( u !== undefined ) u.setValue( gl, value, textures );
 
-       } else if ( a.renderOrder !== b.renderOrder ) {
+    };
 
-               return a.renderOrder - b.renderOrder;
+    WebGLUniforms.prototype.setOptional = function ( gl, object, name ) {
 
-       } else if ( a.z !== b.z ) {
+       const v = object[ name ];
 
-               return b.z - a.z;
+       if ( v !== undefined ) this.setValue( gl, name, v );
 
-       } else {
+    };
 
-               return a.id - b.id;
 
-       }
+    // Static interface
 
-    }
+    WebGLUniforms.upload = function ( gl, seq, values, textures ) {
 
+       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
 
-    function WebGLRenderList( properties ) {
+               const u = seq[ i ],
+                       v = values[ u.id ];
 
-       const renderItems = [];
-       let renderItemsIndex = 0;
+               if ( v.needsUpdate !== false ) {
 
-       const opaque = [];
-       const transparent = [];
+                       // note: always updating when .needsUpdate is undefined
+                       u.setValue( gl, v.value, textures );
 
-       const defaultProgram = { id: - 1 };
+               }
 
-       function init() {
+       }
 
-               renderItemsIndex = 0;
+    };
 
-               opaque.length = 0;
-               transparent.length = 0;
+    WebGLUniforms.seqWithValue = function ( seq, values ) {
+
+       const r = [];
+
+       for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+
+               const u = seq[ i ];
+               if ( u.id in values ) r.push( u );
 
        }
 
-       function getNextRenderItem( object, geometry, material, groupOrder, z, group ) {
+       return r;
 
-               let renderItem = renderItems[ renderItemsIndex ];
-               const materialProperties = properties.get( material );
+    };
 
-               if ( renderItem === undefined ) {
+    function WebGLShader( gl, type, string ) {
 
-                       renderItem = {
-                               id: object.id,
-                               object: object,
-                               geometry: geometry,
-                               material: material,
-                               program: materialProperties.program || defaultProgram,
-                               groupOrder: groupOrder,
-                               renderOrder: object.renderOrder,
-                               z: z,
-                               group: group
-                       };
+       const shader = gl.createShader( type );
 
-                       renderItems[ renderItemsIndex ] = renderItem;
+       gl.shaderSource( shader, string );
+       gl.compileShader( shader );
 
-               } else {
+       return shader;
 
-                       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;
+    }
 
-               }
+    let programIdCount = 0;
 
-               renderItemsIndex ++;
+    function addLineNumbers( string ) {
 
-               return renderItem;
+       const lines = string.split( '\n' );
+
+       for ( let i = 0; i < lines.length; i ++ ) {
+
+               lines[ i ] = ( i + 1 ) + ': ' + lines[ i ];
 
        }
 
-       function push( object, geometry, material, groupOrder, z, group ) {
+       return lines.join( '\n' );
 
-               const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
+    }
+
+    function getEncodingComponents( encoding ) {
+
+       switch ( encoding ) {
 
-               ( material.transparent === true ? transparent : opaque ).push( renderItem );
+               case LinearEncoding:
+                       return [ 'Linear', '( value )' ];
+               case sRGBEncoding:
+                       return [ 'sRGB', '( value )' ];
+               case RGBEEncoding:
+                       return [ 'RGBE', '( value )' ];
+               case RGBM7Encoding:
+                       return [ 'RGBM', '( value, 7.0 )' ];
+               case RGBM16Encoding:
+                       return [ 'RGBM', '( value, 16.0 )' ];
+               case RGBDEncoding:
+                       return [ 'RGBD', '( value, 256.0 )' ];
+               case GammaEncoding:
+                       return [ 'Gamma', '( value, float( GAMMA_FACTOR ) )' ];
+               case LogLuvEncoding:
+                       return [ 'LogLuv', '( value )' ];
+               default:
+                       console.warn( 'THREE.WebGLProgram: Unsupported encoding:', encoding );
+                       return [ 'Linear', '( value )' ];
 
        }
 
-       function unshift( object, geometry, material, groupOrder, z, group ) {
+    }
 
-               const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
+    function getShaderErrors( gl, shader, type ) {
 
-               ( material.transparent === true ? transparent : opaque ).unshift( renderItem );
+       const status = gl.getShaderParameter( shader, 35713 );
+       const errors = gl.getShaderInfoLog( shader ).trim();
 
-       }
+       if ( status && errors === '' ) return '';
 
-       function sort( customOpaqueSort, customTransparentSort ) {
+       // --enable-privileged-webgl-extension
+       // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) );
 
-               if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable );
-               if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable );
+       return type.toUpperCase() + '\n\n' + errors + '\n\n' + addLineNumbers( gl.getShaderSource( shader ) );
 
-       }
+    }
 
-       function finish() {
+    function getTexelDecodingFunction( functionName, encoding ) {
 
-               // Clear references from inactive renderItems in the list
+       const components = getEncodingComponents( encoding );
+       return 'vec4 ' + functionName + '( vec4 value ) { return ' + components[ 0 ] + 'ToLinear' + components[ 1 ] + '; }';
 
-               for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) {
+    }
 
-                       const renderItem = renderItems[ i ];
+    function getTexelEncodingFunction( functionName, encoding ) {
 
-                       if ( renderItem.id === null ) break;
+       const components = getEncodingComponents( encoding );
+       return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }';
 
-                       renderItem.id = null;
-                       renderItem.object = null;
-                       renderItem.geometry = null;
-                       renderItem.material = null;
-                       renderItem.program = null;
-                       renderItem.group = null;
+    }
 
-               }
+    function getToneMappingFunction( functionName, toneMapping ) {
 
-       }
+       let toneMappingName;
 
-       return {
+       switch ( toneMapping ) {
 
-               opaque: opaque,
-               transparent: transparent,
+               case LinearToneMapping:
+                       toneMappingName = 'Linear';
+                       break;
 
-               init: init,
-               push: push,
-               unshift: unshift,
-               finish: finish,
+               case ReinhardToneMapping:
+                       toneMappingName = 'Reinhard';
+                       break;
 
-               sort: sort
-       };
+               case CineonToneMapping:
+                       toneMappingName = 'OptimizedCineon';
+                       break;
 
-    }
+               case ACESFilmicToneMapping:
+                       toneMappingName = 'ACESFilmic';
+                       break;
 
-    function WebGLRenderLists( properties ) {
+               case CustomToneMapping:
+                       toneMappingName = 'Custom';
+                       break;
 
-       let lists = new WeakMap();
+               default:
+                       console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping );
+                       toneMappingName = 'Linear';
 
-       function get( scene, camera ) {
+       }
 
-               const cameras = lists.get( scene );
-               let list;
+       return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';
 
-               if ( cameras === undefined ) {
+    }
 
-                       list = new WebGLRenderList( properties );
-                       lists.set( scene, new WeakMap() );
-                       lists.get( scene ).set( camera, list );
+    function generateExtensions( parameters ) {
 
-               } else {
+       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' : ''
+       ];
 
-                       list = cameras.get( camera );
-                       if ( list === undefined ) {
+       return chunks.filter( filterEmptyLine ).join( '\n' );
 
-                               list = new WebGLRenderList( properties );
-                               cameras.set( camera, list );
+    }
 
-                       }
+    function generateDefines( defines ) {
 
-               }
+       const chunks = [];
 
-               return list;
+       for ( const name in defines ) {
 
-       }
+               const value = defines[ name ];
 
-       function dispose() {
+               if ( value === false ) continue;
 
-               lists = new WeakMap();
+               chunks.push( '#define ' + name + ' ' + value );
 
        }
 
-       return {
-               get: get,
-               dispose: dispose
-       };
+       return chunks.join( '\n' );
 
     }
 
-    function UniformsCache() {
+    function fetchAttributeLocations( gl, program ) {
 
-       const lights = {};
+       const attributes = {};
 
-       return {
+       const n = gl.getProgramParameter( program, 35721 );
 
-               get: function ( light ) {
+       for ( let i = 0; i < n; i ++ ) {
 
-                       if ( lights[ light.id ] !== undefined ) {
+               const info = gl.getActiveAttrib( program, i );
+               const name = info.name;
 
-                               return lights[ light.id ];
+               let locationSize = 1;
+               if ( info.type === 35674 ) locationSize = 2;
+               if ( info.type === 35675 ) locationSize = 3;
+               if ( info.type === 35676 ) locationSize = 4;
 
-                       }
+               // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i );
 
-                       let uniforms;
+               attributes[ name ] = {
+                       type: info.type,
+                       location: gl.getAttribLocation( program, name ),
+                       locationSize: locationSize
+               };
 
-                       switch ( light.type ) {
+       }
 
-                               case 'DirectionalLight':
-                                       uniforms = {
-                                               direction: new Vector3(),
-                                               color: new Color()
-                                       };
-                                       break;
+       return attributes;
 
-                               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 filterEmptyLine( string ) {
 
-                               case 'HemisphereLight':
-                                       uniforms = {
-                                               direction: new Vector3(),
-                                               skyColor: new Color(),
-                                               groundColor: new Color()
-                                       };
-                                       break;
+       return string !== '';
 
-                               case 'RectAreaLight':
-                                       uniforms = {
-                                               color: new Color(),
-                                               position: new Vector3(),
-                                               halfWidth: new Vector3(),
-                                               halfHeight: new Vector3()
-                                       };
-                                       break;
+    }
 
-                       }
+    function replaceLightNums( string, parameters ) {
 
-                       lights[ light.id ] = uniforms;
+       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 );
 
-                       return uniforms;
+    }
 
-               }
+    function replaceClippingPlaneNums( string, parameters ) {
 
-       };
+       return string
+               .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes )
+               .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) );
 
     }
 
-    function ShadowUniformsCache() {
+    // Resolve Includes
 
-       const lights = {};
+    const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm;
 
-       return {
+    function resolveIncludes( string ) {
 
-               get: function ( light ) {
+       return string.replace( includePattern, includeReplacer );
 
-                       if ( lights[ light.id ] !== undefined ) {
+    }
 
-                               return lights[ light.id ];
+    function includeReplacer( match, include ) {
 
-                       }
+       const string = ShaderChunk[ include ];
 
-                       let uniforms;
+       if ( string === undefined ) {
 
-                       switch ( light.type ) {
+               throw new Error( 'Can not resolve #include <' + include + '>' );
 
-                               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 resolveIncludes( string );
 
-                               case 'PointLight':
-                                       uniforms = {
-                                               shadowBias: 0,
-                                               shadowNormalBias: 0,
-                                               shadowRadius: 1,
-                                               shadowMapSize: new Vector2(),
-                                               shadowCameraNear: 1,
-                                               shadowCameraFar: 1000
-                                       };
-                                       break;
+    }
 
-                               // TODO (abelnation): set RectAreaLight shadow uniforms
+    // Unroll Loops
 
-                       }
+    const deprecatedUnrollLoopPattern = /#pragma unroll_loop[\s]+?for \( int i \= (\d+)\; i < (\d+)\; i \+\+ \) \{([\s\S]+?)(?=\})\}/g;
+    const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;
 
-                       lights[ light.id ] = uniforms;
+    function unrollLoops( string ) {
 
-                       return uniforms;
+       return string
+               .replace( unrollLoopPattern, loopReplacer )
+               .replace( deprecatedUnrollLoopPattern, deprecatedLoopReplacer );
 
-               }
+    }
 
-       };
+    function deprecatedLoopReplacer( match, start, end, snippet ) {
+
+       console.warn( 'WebGLProgram: #pragma unroll_loop shader syntax is deprecated. Please use #pragma unroll_loop_start syntax instead.' );
+       return loopReplacer( match, start, end, snippet );
 
     }
 
+    function loopReplacer( match, start, end, snippet ) {
 
+       let string = '';
 
-    let nextVersion = 0;
+       for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) {
 
-    function shadowCastingLightsFirst( lightA, lightB ) {
+               string += snippet
+                       .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' )
+                       .replace( /UNROLLED_LOOP_INDEX/g, i );
 
-       return ( lightB.castShadow ? 1 : 0 ) - ( lightA.castShadow ? 1 : 0 );
+       }
+
+       return string;
 
     }
 
-    function WebGLLights( extensions, capabilities ) {
+    //
 
-       const cache = new UniformsCache();
+    function generatePrecision( parameters ) {
 
-       const shadowCache = ShadowUniformsCache();
+       let precisionstring = 'precision ' + parameters.precision + ' float;\nprecision ' + parameters.precision + ' int;';
 
-       const state = {
+       if ( parameters.precision === 'highp' ) {
 
-               version: 0,
+               precisionstring += '\n#define HIGH_PRECISION';
 
-               hash: {
-                       directionalLength: - 1,
-                       pointLength: - 1,
-                       spotLength: - 1,
-                       rectAreaLength: - 1,
-                       hemiLength: - 1,
+       } else if ( parameters.precision === 'mediump' ) {
 
-                       numDirectionalShadows: - 1,
-                       numPointShadows: - 1,
-                       numSpotShadows: - 1
-               },
+               precisionstring += '\n#define MEDIUM_PRECISION';
 
-               ambient: [ 0, 0, 0 ],
-               probe: [],
-               directional: [],
-               directionalShadow: [],
-               directionalShadowMap: [],
-               directionalShadowMatrix: [],
-               spot: [],
-               spotShadow: [],
-               spotShadowMap: [],
-               spotShadowMatrix: [],
-               rectArea: [],
-               rectAreaLTC1: null,
-               rectAreaLTC2: null,
-               point: [],
-               pointShadow: [],
-               pointShadowMap: [],
-               pointShadowMatrix: [],
-               hemi: []
+       } else if ( parameters.precision === 'lowp' ) {
 
-       };
+               precisionstring += '\n#define LOW_PRECISION';
 
-       for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() );
+       }
 
-       const vector3 = new Vector3();
-       const matrix4 = new Matrix4();
-       const matrix42 = new Matrix4();
+       return precisionstring;
 
-       function setup( lights ) {
+    }
 
-               let r = 0, g = 0, b = 0;
+    function generateShadowMapTypeDefine( parameters ) {
 
-               for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 );
+       let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC';
 
-               let directionalLength = 0;
-               let pointLength = 0;
-               let spotLength = 0;
-               let rectAreaLength = 0;
-               let hemiLength = 0;
+       if ( parameters.shadowMapType === PCFShadowMap ) {
 
-               let numDirectionalShadows = 0;
-               let numPointShadows = 0;
-               let numSpotShadows = 0;
+               shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF';
 
-               lights.sort( shadowCastingLightsFirst );
+       } else if ( parameters.shadowMapType === PCFSoftShadowMap ) {
 
-               for ( let i = 0, l = lights.length; i < l; i ++ ) {
+               shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT';
 
-                       const light = lights[ i ];
+       } else if ( parameters.shadowMapType === VSMShadowMap ) {
 
-                       const color = light.color;
-                       const intensity = light.intensity;
-                       const distance = light.distance;
+               shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM';
 
-                       const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null;
+       }
 
-                       if ( light.isAmbientLight ) {
+       return shadowMapTypeDefine;
 
-                               r += color.r * intensity;
-                               g += color.g * intensity;
-                               b += color.b * intensity;
+    }
 
-                       } else if ( light.isLightProbe ) {
+    function generateEnvMapTypeDefine( parameters ) {
 
-                               for ( let j = 0; j < 9; j ++ ) {
+       let envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
 
-                                       state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity );
+       if ( parameters.envMap ) {
 
-                               }
+               switch ( parameters.envMapMode ) {
 
-                       } else if ( light.isDirectionalLight ) {
+                       case CubeReflectionMapping:
+                       case CubeRefractionMapping:
+                               envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
+                               break;
 
-                               const uniforms = cache.get( light );
+                       case CubeUVReflectionMapping:
+                       case CubeUVRefractionMapping:
+                               envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV';
+                               break;
 
-                               uniforms.color.copy( light.color ).multiplyScalar( light.intensity );
+               }
 
-                               if ( light.castShadow ) {
+       }
 
-                                       const shadow = light.shadow;
+       return envMapTypeDefine;
 
-                                       const shadowUniforms = shadowCache.get( light );
+    }
 
-                                       shadowUniforms.shadowBias = shadow.bias;
-                                       shadowUniforms.shadowNormalBias = shadow.normalBias;
-                                       shadowUniforms.shadowRadius = shadow.radius;
-                                       shadowUniforms.shadowMapSize = shadow.mapSize;
+    function generateEnvMapModeDefine( parameters ) {
 
-                                       state.directionalShadow[ directionalLength ] = shadowUniforms;
-                                       state.directionalShadowMap[ directionalLength ] = shadowMap;
-                                       state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix;
+       let envMapModeDefine = 'ENVMAP_MODE_REFLECTION';
 
-                                       numDirectionalShadows ++;
+       if ( parameters.envMap ) {
 
-                               }
+               switch ( parameters.envMapMode ) {
 
-                               state.directional[ directionalLength ] = uniforms;
+                       case CubeRefractionMapping:
+                       case CubeUVRefractionMapping:
 
-                               directionalLength ++;
+                               envMapModeDefine = 'ENVMAP_MODE_REFRACTION';
+                               break;
 
-                       } else if ( light.isSpotLight ) {
+               }
 
-                               const uniforms = cache.get( light );
+       }
 
-                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
+       return envMapModeDefine;
 
-                               uniforms.color.copy( color ).multiplyScalar( intensity );
-                               uniforms.distance = distance;
+    }
 
-                               uniforms.coneCos = Math.cos( light.angle );
-                               uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) );
-                               uniforms.decay = light.decay;
+    function generateEnvMapBlendingDefine( parameters ) {
 
-                               if ( light.castShadow ) {
+       let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE';
 
-                                       const shadow = light.shadow;
+       if ( parameters.envMap ) {
 
-                                       const shadowUniforms = shadowCache.get( light );
+               switch ( parameters.combine ) {
 
-                                       shadowUniforms.shadowBias = shadow.bias;
-                                       shadowUniforms.shadowNormalBias = shadow.normalBias;
-                                       shadowUniforms.shadowRadius = shadow.radius;
-                                       shadowUniforms.shadowMapSize = shadow.mapSize;
+                       case MultiplyOperation:
+                               envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY';
+                               break;
 
-                                       state.spotShadow[ spotLength ] = shadowUniforms;
-                                       state.spotShadowMap[ spotLength ] = shadowMap;
-                                       state.spotShadowMatrix[ spotLength ] = light.shadow.matrix;
+                       case MixOperation:
+                               envMapBlendingDefine = 'ENVMAP_BLENDING_MIX';
+                               break;
 
-                                       numSpotShadows ++;
+                       case AddOperation:
+                               envMapBlendingDefine = 'ENVMAP_BLENDING_ADD';
+                               break;
 
-                               }
+               }
 
-                               state.spot[ spotLength ] = uniforms;
+       }
 
-                               spotLength ++;
+       return envMapBlendingDefine;
 
-                       } else if ( light.isRectAreaLight ) {
+    }
 
-                               const uniforms = cache.get( light );
+    function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
 
-                               // (a) intensity is the total visible light emitted
-                               //uniforms.color.copy( color ).multiplyScalar( intensity / ( light.width * light.height * Math.PI ) );
+       // TODO Send this event to Three.js DevTools
+       // console.log( 'WebGLProgram', cacheKey );
 
-                               // (b) intensity is the brightness of the light
-                               uniforms.color.copy( color ).multiplyScalar( intensity );
+       const gl = renderer.getContext();
 
-                               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
-                               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
+       const defines = parameters.defines;
 
-                               state.rectArea[ rectAreaLength ] = uniforms;
+       let vertexShader = parameters.vertexShader;
+       let fragmentShader = parameters.fragmentShader;
 
-                               rectAreaLength ++;
+       const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters );
+       const envMapTypeDefine = generateEnvMapTypeDefine( parameters );
+       const envMapModeDefine = generateEnvMapModeDefine( parameters );
+       const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters );
 
-                       } else if ( light.isPointLight ) {
 
-                               const uniforms = cache.get( light );
+       const gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0;
 
-                               uniforms.color.copy( light.color ).multiplyScalar( light.intensity );
-                               uniforms.distance = light.distance;
-                               uniforms.decay = light.decay;
+       const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters );
 
-                               if ( light.castShadow ) {
+       const customDefines = generateDefines( defines );
 
-                                       const shadow = light.shadow;
+       const program = gl.createProgram();
 
-                                       const shadowUniforms = shadowCache.get( light );
+       let prefixVertex, prefixFragment;
+       let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : '';
 
-                                       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;
+       if ( parameters.isRawShaderMaterial ) {
 
-                                       state.pointShadow[ pointLength ] = shadowUniforms;
-                                       state.pointShadowMap[ pointLength ] = shadowMap;
-                                       state.pointShadowMatrix[ pointLength ] = light.shadow.matrix;
+               prefixVertex = [
 
-                                       numPointShadows ++;
+                       customDefines
 
-                               }
+               ].filter( filterEmptyLine ).join( '\n' );
 
-                               state.point[ pointLength ] = uniforms;
+               if ( prefixVertex.length > 0 ) {
 
-                               pointLength ++;
+                       prefixVertex += '\n';
 
-                       } else if ( light.isHemisphereLight ) {
+               }
 
-                               const uniforms = cache.get( light );
+               prefixFragment = [
 
-                               uniforms.skyColor.copy( light.color ).multiplyScalar( intensity );
-                               uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity );
+                       customExtensions,
+                       customDefines
 
-                               state.hemi[ hemiLength ] = uniforms;
+               ].filter( filterEmptyLine ).join( '\n' );
 
-                               hemiLength ++;
+               if ( prefixFragment.length > 0 ) {
 
-                       }
+                       prefixFragment += '\n';
 
                }
 
-               if ( rectAreaLength > 0 ) {
-
-                       if ( capabilities.isWebGL2 ) {
+       } else {
 
-                               // WebGL 2
+               prefixVertex = [
 
-                               state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
-                               state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
+                       generatePrecision( parameters ),
 
-                       } else {
+                       '#define SHADER_NAME ' + parameters.shaderName,
 
-                               // WebGL 1
+                       customDefines,
 
-                               if ( extensions.has( 'OES_texture_float_linear' ) === true ) {
+                       parameters.instancing ? '#define USE_INSTANCING' : '',
+                       parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '',
 
-                                       state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
-                                       state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
+                       parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '',
 
-                               } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) {
+                       '#define GAMMA_FACTOR ' + gammaFactorDefine,
 
-                                       state.rectAreaLTC1 = UniformsLib.LTC_HALF_1;
-                                       state.rectAreaLTC2 = UniformsLib.LTC_HALF_2;
+                       '#define MAX_BONES ' + parameters.maxBones,
+                       ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
+                       ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',
 
-                               } else {
+                       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' : '',
 
-                                       console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' );
+                       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.specularIntensityMap ? '#define USE_SPECULARINTENSITYMAP' : '',
+                       parameters.specularColorMap ? '#define USE_SPECULARCOLORMAP' : '',
 
-               }
+                       parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',
+                       parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',
+                       parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
 
-               state.ambient[ 0 ] = r;
-               state.ambient[ 1 ] = g;
-               state.ambient[ 2 ] = b;
+                       parameters.transmission ? '#define USE_TRANSMISSION' : '',
+                       parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '',
+                       parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '',
 
-               const hash = state.hash;
+                       parameters.sheenColorMap ? '#define USE_SHEENCOLORMAP' : '',
+                       parameters.sheenRoughnessMap ? '#define USE_SHEENROUGHNESSMAP' : '',
 
-               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 ) {
+                       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' : '',
 
-                       state.directional.length = directionalLength;
-                       state.spot.length = spotLength;
-                       state.rectArea.length = rectAreaLength;
-                       state.point.length = pointLength;
-                       state.hemi.length = hemiLength;
+                       parameters.flatShading ? '#define FLAT_SHADED' : '',
 
-                       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;
+                       parameters.skinning ? '#define USE_SKINNING' : '',
+                       parameters.useVertexTexture ? '#define BONE_TEXTURE' : '',
 
-                       hash.directionalLength = directionalLength;
-                       hash.pointLength = pointLength;
-                       hash.spotLength = spotLength;
-                       hash.rectAreaLength = rectAreaLength;
-                       hash.hemiLength = hemiLength;
+                       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' : '',
 
-                       hash.numDirectionalShadows = numDirectionalShadows;
-                       hash.numPointShadows = numPointShadows;
-                       hash.numSpotShadows = numSpotShadows;
+                       parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
+                       parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
 
-                       state.version = nextVersion ++;
+                       parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '',
 
-               }
+                       parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
+                       ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
 
-       }
+                       'uniform mat4 modelMatrix;',
+                       'uniform mat4 modelViewMatrix;',
+                       'uniform mat4 projectionMatrix;',
+                       'uniform mat4 viewMatrix;',
+                       'uniform mat3 normalMatrix;',
+                       'uniform vec3 cameraPosition;',
+                       'uniform bool isOrthographic;',
 
-       function setupView( lights, camera ) {
+                       '#ifdef USE_INSTANCING',
 
-               let directionalLength = 0;
-               let pointLength = 0;
-               let spotLength = 0;
-               let rectAreaLength = 0;
-               let hemiLength = 0;
+                       '       attribute mat4 instanceMatrix;',
 
-               const viewMatrix = camera.matrixWorldInverse;
+                       '#endif',
 
-               for ( let i = 0, l = lights.length; i < l; i ++ ) {
+                       '#ifdef USE_INSTANCING_COLOR',
 
-                       const light = lights[ i ];
+                       '       attribute vec3 instanceColor;',
 
-                       if ( light.isDirectionalLight ) {
+                       '#endif',
 
-                               const uniforms = state.directional[ directionalLength ];
+                       'attribute vec3 position;',
+                       'attribute vec3 normal;',
+                       'attribute vec2 uv;',
 
-                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
-                               vector3.setFromMatrixPosition( light.target.matrixWorld );
-                               uniforms.direction.sub( vector3 );
-                               uniforms.direction.transformDirection( viewMatrix );
+                       '#ifdef USE_TANGENT',
 
-                               directionalLength ++;
+                       '       attribute vec4 tangent;',
 
-                       } else if ( light.isSpotLight ) {
+                       '#endif',
 
-                               const uniforms = state.spot[ spotLength ];
+                       '#if defined( USE_COLOR_ALPHA )',
 
-                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
-                               uniforms.position.applyMatrix4( viewMatrix );
+                       '       attribute vec4 color;',
 
-                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
-                               vector3.setFromMatrixPosition( light.target.matrixWorld );
-                               uniforms.direction.sub( vector3 );
-                               uniforms.direction.transformDirection( viewMatrix );
+                       '#elif defined( USE_COLOR )',
 
-                               spotLength ++;
+                       '       attribute vec3 color;',
 
-                       } else if ( light.isRectAreaLight ) {
+                       '#endif',
 
-                               const uniforms = state.rectArea[ rectAreaLength ];
+                       '#if ( defined( USE_MORPHTARGETS ) && ! defined( MORPHTARGETS_TEXTURE ) )',
 
-                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
-                               uniforms.position.applyMatrix4( viewMatrix );
+                       '       attribute vec3 morphTarget0;',
+                       '       attribute vec3 morphTarget1;',
+                       '       attribute vec3 morphTarget2;',
+                       '       attribute vec3 morphTarget3;',
 
-                               // extract local rotation of light to derive width/height half vectors
-                               matrix42.identity();
-                               matrix4.copy( light.matrixWorld );
-                               matrix4.premultiply( viewMatrix );
-                               matrix42.extractRotation( matrix4 );
+                       '       #ifdef USE_MORPHNORMALS',
 
-                               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
-                               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
+                       '               attribute vec3 morphNormal0;',
+                       '               attribute vec3 morphNormal1;',
+                       '               attribute vec3 morphNormal2;',
+                       '               attribute vec3 morphNormal3;',
 
-                               uniforms.halfWidth.applyMatrix4( matrix42 );
-                               uniforms.halfHeight.applyMatrix4( matrix42 );
+                       '       #else',
 
-                               rectAreaLength ++;
+                       '               attribute vec3 morphTarget4;',
+                       '               attribute vec3 morphTarget5;',
+                       '               attribute vec3 morphTarget6;',
+                       '               attribute vec3 morphTarget7;',
 
-                       } else if ( light.isPointLight ) {
+                       '       #endif',
 
-                               const uniforms = state.point[ pointLength ];
+                       '#endif',
 
-                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
-                               uniforms.position.applyMatrix4( viewMatrix );
+                       '#ifdef USE_SKINNING',
 
-                               pointLength ++;
+                       '       attribute vec4 skinIndex;',
+                       '       attribute vec4 skinWeight;',
 
-                       } else if ( light.isHemisphereLight ) {
+                       '#endif',
 
-                               const uniforms = state.hemi[ hemiLength ];
+                       '\n'
 
-                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
-                               uniforms.direction.transformDirection( viewMatrix );
-                               uniforms.direction.normalize();
+               ].filter( filterEmptyLine ).join( '\n' );
 
-                               hemiLength ++;
+               prefixFragment = [
 
-                       }
+                       customExtensions,
 
-               }
+                       generatePrecision( parameters ),
 
-       }
+                       '#define SHADER_NAME ' + parameters.shaderName,
 
-       return {
-               setup: setup,
-               setupView: setupView,
-               state: state
-       };
+                       customDefines,
 
-    }
+                       '#define GAMMA_FACTOR ' + gammaFactorDefine,
 
-    function WebGLRenderState( extensions, capabilities ) {
+                       ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
+                       ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',
 
-       const lights = new WebGLLights( extensions, capabilities );
+                       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' : '',
 
-       const lightsArray = [];
-       const shadowsArray = [];
+                       parameters.clearcoat ? '#define USE_CLEARCOAT' : '',
+                       parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '',
+                       parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '',
+                       parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '',
 
-       function init() {
+                       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' : '',
 
-               lightsArray.length = 0;
-               shadowsArray.length = 0;
+                       parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
+                       parameters.alphaTest ? '#define USE_ALPHATEST' : '',
 
-       }
+                       parameters.sheen ? '#define USE_SHEEN' : '',
+                       parameters.sheenColorMap ? '#define USE_SHEENCOLORMAP' : '',
+                       parameters.sheenRoughnessMap ? '#define USE_SHEENROUGHNESSMAP' : '',
 
-       function pushLight( light ) {
+                       parameters.transmission ? '#define USE_TRANSMISSION' : '',
+                       parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '',
+                       parameters.thicknessMap ? '#define USE_THICKNESSMAP' : '',
 
-               lightsArray.push( light );
+                       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' : '',
 
-       function pushShadow( shadowLight ) {
+                       parameters.flatShading ? '#define FLAT_SHADED' : '',
 
-               shadowsArray.push( shadowLight );
+                       parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
+                       parameters.flipSided ? '#define FLIP_SIDED' : '',
 
-       }
+                       parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
+                       parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
 
-       function setupLights() {
+                       parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '',
 
-               lights.setup( lightsArray );
+                       parameters.physicallyCorrectLights ? '#define PHYSICALLY_CORRECT_LIGHTS' : '',
 
-       }
+                       parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
+                       ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
 
-       function setupLightsView( camera ) {
+                       ( ( parameters.extensionShaderTextureLOD || parameters.envMap ) && parameters.rendererExtensionShaderTextureLod ) ? '#define TEXTURE_LOD_EXT' : '',
 
-               lights.setupView( lightsArray, camera );
+                       'uniform mat4 viewMatrix;',
+                       'uniform vec3 cameraPosition;',
+                       'uniform bool isOrthographic;',
 
-       }
+                       ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '',
+                       ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below
+                       ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '',
 
-       const state = {
-               lightsArray: lightsArray,
-               shadowsArray: shadowsArray,
+                       parameters.dithering ? '#define DITHERING' : '',
+                       parameters.format === RGBFormat ? '#define OPAQUE' : '',
 
-               lights: lights
-       };
+                       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 ),
 
-       return {
-               init: init,
-               state: state,
-               setupLights: setupLights,
-               setupLightsView: setupLightsView,
+                       parameters.depthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '',
 
-               pushLight: pushLight,
-               pushShadow: pushShadow
-       };
+                       '\n'
 
-    }
+               ].filter( filterEmptyLine ).join( '\n' );
 
-    function WebGLRenderStates( extensions, capabilities ) {
+       }
 
-       let renderStates = new WeakMap();
-
-       function get( scene, renderCallDepth = 0 ) {
-
-               let renderState;
+       vertexShader = resolveIncludes( vertexShader );
+       vertexShader = replaceLightNums( vertexShader, parameters );
+       vertexShader = replaceClippingPlaneNums( vertexShader, parameters );
 
-               if ( renderStates.has( scene ) === false ) {
+       fragmentShader = resolveIncludes( fragmentShader );
+       fragmentShader = replaceLightNums( fragmentShader, parameters );
+       fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters );
 
-                       renderState = new WebGLRenderState( extensions, capabilities );
-                       renderStates.set( scene, [] );
-                       renderStates.get( scene ).push( renderState );
+       vertexShader = unrollLoops( vertexShader );
+       fragmentShader = unrollLoops( fragmentShader );
 
-               } else {
+       if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) {
 
-                       if ( renderCallDepth >= renderStates.get( scene ).length ) {
+               // GLSL 3.0 conversion for built-in materials and ShaderMaterial
 
-                               renderState = new WebGLRenderState( extensions, capabilities );
-                               renderStates.get( scene ).push( renderState );
+               versionString = '#version 300 es\n';
 
-                       } else {
+               prefixVertex = [
+                       'precision mediump sampler2DArray;',
+                       '#define attribute in',
+                       '#define varying out',
+                       '#define texture2D texture'
+               ].join( '\n' ) + '\n' + prefixVertex;
 
-                               renderState = renderStates.get( scene )[ renderCallDepth ];
+               prefixFragment = [
+                       '#define varying in',
+                       ( parameters.glslVersion === GLSL3 ) ? '' : 'out highp vec4 pc_fragColor;',
+                       ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor',
+                       '#define gl_FragDepthEXT gl_FragDepth',
+                       '#define texture2D texture',
+                       '#define textureCube texture',
+                       '#define texture2DProj textureProj',
+                       '#define texture2DLodEXT textureLod',
+                       '#define texture2DProjLodEXT textureProjLod',
+                       '#define textureCubeLodEXT textureLod',
+                       '#define texture2DGradEXT textureGrad',
+                       '#define texture2DProjGradEXT textureProjGrad',
+                       '#define textureCubeGradEXT textureGrad'
+               ].join( '\n' ) + '\n' + prefixFragment;
 
-                       }
+       }
 
-               }
+       const vertexGlsl = versionString + prefixVertex + vertexShader;
+       const fragmentGlsl = versionString + prefixFragment + fragmentShader;
 
-               return renderState;
+       // console.log( '*VERTEX*', vertexGlsl );
+       // console.log( '*FRAGMENT*', fragmentGlsl );
 
-       }
+       const glVertexShader = WebGLShader( gl, 35633, vertexGlsl );
+       const glFragmentShader = WebGLShader( gl, 35632, fragmentGlsl );
 
-       function dispose() {
+       gl.attachShader( program, glVertexShader );
+       gl.attachShader( program, glFragmentShader );
 
-               renderStates = new WeakMap();
+       // Force a particular attribute to index 0.
 
-       }
+       if ( parameters.index0AttributeName !== undefined ) {
 
-       return {
-               get: get,
-               dispose: dispose
-       };
+               gl.bindAttribLocation( program, 0, parameters.index0AttributeName );
 
-    }
+       } else if ( parameters.morphTargets === true ) {
 
-    /**
-     * 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>
-     * }
-     */
+               // programs with morphTargets displace position out of attribute 0
+               gl.bindAttribLocation( program, 0, 'position' );
 
-    function MeshDepthMaterial( parameters ) {
+       }
 
-       Material.call( this );
+       gl.linkProgram( program );
 
-       this.type = 'MeshDepthMaterial';
+       // check for link errors
+       if ( renderer.debug.checkShaderErrors ) {
 
-       this.depthPacking = BasicDepthPacking;
+               const programLog = gl.getProgramInfoLog( program ).trim();
+               const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim();
+               const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim();
 
-       this.skinning = false;
-       this.morphTargets = false;
+               let runnable = true;
+               let haveDiagnostics = true;
 
-       this.map = null;
+               if ( gl.getProgramParameter( program, 35714 ) === false ) {
 
-       this.alphaMap = null;
+                       runnable = false;
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+                       const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' );
+                       const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' );
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
+                       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.fog = false;
+               } else if ( programLog !== '' ) {
 
-       this.setValues( parameters );
+                       console.warn( 'THREE.WebGLProgram: Program Info Log:', programLog );
 
-    }
+               } else if ( vertexLog === '' || fragmentLog === '' ) {
 
-    MeshDepthMaterial.prototype = Object.create( Material.prototype );
-    MeshDepthMaterial.prototype.constructor = MeshDepthMaterial;
+                       haveDiagnostics = false;
 
-    MeshDepthMaterial.prototype.isMeshDepthMaterial = true;
+               }
 
-    MeshDepthMaterial.prototype.copy = function ( source ) {
+               if ( haveDiagnostics ) {
 
-       Material.prototype.copy.call( this, source );
+                       this.diagnostics = {
 
-       this.depthPacking = source.depthPacking;
+                               runnable: runnable,
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
+                               programLog: programLog,
 
-       this.map = source.map;
+                               vertexShader: {
 
-       this.alphaMap = source.alphaMap;
+                                       log: vertexLog,
+                                       prefix: prefixVertex
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+                               },
 
-       this.wireframe = source.wireframe;
-       this.wireframeLinewidth = source.wireframeLinewidth;
+                               fragmentShader: {
 
-       return this;
+                                       log: fragmentLog,
+                                       prefix: prefixFragment
 
-    };
+                               }
 
-    /**
-     * parameters = {
-     *
-     *  referencePosition: <float>,
-     *  nearDistance: <float>,
-     *  farDistance: <float>,
-     *
-     *  skinning: <bool>,
-     *  morphTargets: <bool>,
-     *
-     *  map: new THREE.Texture( <Image> ),
-     *
-     *  alphaMap: new THREE.Texture( <Image> ),
-     *
-     *  displacementMap: new THREE.Texture( <Image> ),
-     *  displacementScale: <float>,
-     *  displacementBias: <float>
-     *
-     * }
-     */
+                       };
 
-    function MeshDistanceMaterial( parameters ) {
+               }
 
-       Material.call( this );
+       }
 
-       this.type = 'MeshDistanceMaterial';
+       // Clean up
 
-       this.referencePosition = new Vector3();
-       this.nearDistance = 1;
-       this.farDistance = 1000;
+       // Crashes in iOS9 and iOS10. #18402
+       // gl.detachShader( program, glVertexShader );
+       // gl.detachShader( program, glFragmentShader );
 
-       this.skinning = false;
-       this.morphTargets = false;
+       gl.deleteShader( glVertexShader );
+       gl.deleteShader( glFragmentShader );
 
-       this.map = null;
+       // set up caching for uniform locations
 
-       this.alphaMap = null;
+       let cachedUniforms;
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+       this.getUniforms = function () {
 
-       this.fog = false;
+               if ( cachedUniforms === undefined ) {
 
-       this.setValues( parameters );
+                       cachedUniforms = new WebGLUniforms( gl, program );
 
-    }
+               }
 
-    MeshDistanceMaterial.prototype = Object.create( Material.prototype );
-    MeshDistanceMaterial.prototype.constructor = MeshDistanceMaterial;
+               return cachedUniforms;
 
-    MeshDistanceMaterial.prototype.isMeshDistanceMaterial = true;
+       };
 
-    MeshDistanceMaterial.prototype.copy = function ( source ) {
+       // set up caching for attribute locations
 
-       Material.prototype.copy.call( this, source );
+       let cachedAttributes;
 
-       this.referencePosition.copy( source.referencePosition );
-       this.nearDistance = source.nearDistance;
-       this.farDistance = source.farDistance;
+       this.getAttributes = function () {
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
+               if ( cachedAttributes === undefined ) {
 
-       this.map = source.map;
+                       cachedAttributes = fetchAttributeLocations( gl, program );
 
-       this.alphaMap = source.alphaMap;
+               }
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+               return cachedAttributes;
 
-       return this;
+       };
 
-    };
+       // free resource
 
-    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}";
+       this.destroy = function () {
 
-    var vsm_vert = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}";
+               bindingStates.releaseStatesOfProgram( this );
 
-    function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
+               gl.deleteProgram( program );
+               this.program = undefined;
 
-       let _frustum = new Frustum();
+       };
 
-       const _shadowMapSize = new Vector2(),
-               _viewportSize = new Vector2(),
+       //
 
-               _viewport = new Vector4(),
+       this.name = parameters.shaderName;
+       this.id = programIdCount ++;
+       this.cacheKey = cacheKey;
+       this.usedTimes = 1;
+       this.program = program;
+       this.vertexShader = glVertexShader;
+       this.fragmentShader = glFragmentShader;
 
-               _depthMaterials = [],
-               _distanceMaterials = [],
+       return this;
 
-               _materialCache = {};
+    }
 
-       const shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide };
+    function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities, bindingStates, clipping ) {
 
-       const shadowMaterialVertical = new ShaderMaterial( {
+       const programs = [];
 
-               defines: {
-                       SAMPLE_RATE: 2.0 / 8.0,
-                       HALF_SAMPLE_RATE: 1.0 / 8.0
-               },
+       const isWebGL2 = capabilities.isWebGL2;
+       const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer;
+       const floatVertexTextures = capabilities.floatVertexTextures;
+       const maxVertexUniforms = capabilities.maxVertexUniforms;
+       const vertexTextures = capabilities.vertexTextures;
 
-               uniforms: {
-                       shadow_pass: { value: null },
-                       resolution: { value: new Vector2() },
-                       radius: { value: 4.0 }
-               },
+       let precision = capabilities.precision;
 
-               vertexShader: vsm_vert,
+       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'
+       };
 
-               fragmentShader: vsm_frag
+       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 ) {
 
-       const shadowMaterialHorizontal = shadowMaterialVertical.clone();
-       shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1;
+               const skeleton = object.skeleton;
+               const bones = skeleton.bones;
 
-       const fullScreenTri = new BufferGeometry();
-       fullScreenTri.setAttribute(
-               'position',
-               new BufferAttribute(
-                       new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ),
-                       3
-               )
-       );
+               if ( floatVertexTextures ) {
 
-       const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical );
+                       return 1024;
 
-       const scope = this;
+               } else {
 
-       this.enabled = false;
+                       // 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)
 
-       this.autoUpdate = true;
-       this.needsUpdate = false;
+                       const nVertexUniforms = maxVertexUniforms;
+                       const nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 );
 
-       this.type = PCFShadowMap;
+                       const maxBones = Math.min( nVertexMatrices, bones.length );
 
-       this.render = function ( lights, scene, camera ) {
+                       if ( maxBones < bones.length ) {
 
-               if ( scope.enabled === false ) return;
-               if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;
+                               console.warn( 'THREE.WebGLRenderer: Skeleton has ' + bones.length + ' bones. This GPU supports ' + maxBones + '.' );
+                               return 0;
 
-               if ( lights.length === 0 ) return;
+                       }
 
-               const currentRenderTarget = _renderer.getRenderTarget();
-               const activeCubeFace = _renderer.getActiveCubeFace();
-               const activeMipmapLevel = _renderer.getActiveMipmapLevel();
+                       return maxBones;
 
-               const _state = _renderer.state;
+               }
 
-               // Set GL state for depth map.
-               _state.setBlending( NoBlending );
-               _state.buffers.color.setClear( 1, 1, 1, 1 );
-               _state.buffers.depth.setTest( true );
-               _state.setScissorTest( false );
+       }
 
-               // render depth map
+       function getTextureEncodingFromMap( map ) {
 
-               for ( let i = 0, il = lights.length; i < il; i ++ ) {
+               let encoding;
 
-                       const light = lights[ i ];
-                       const shadow = light.shadow;
+               if ( map && map.isTexture ) {
 
-                       if ( shadow === undefined ) {
+                       encoding = map.encoding;
 
-                               console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' );
-                               continue;
+               } else if ( map && map.isWebGLRenderTarget ) {
 
-                       }
+                       console.warn( 'THREE.WebGLPrograms.getTextureEncodingFromMap: don\'t use render targets as textures. Use their .texture property instead.' );
+                       encoding = map.texture.encoding;
 
-                       if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue;
+               } else {
 
-                       _shadowMapSize.copy( shadow.mapSize );
+                       encoding = LinearEncoding;
 
-                       const shadowFrameExtents = shadow.getFrameExtents();
+               }
 
-                       _shadowMapSize.multiply( shadowFrameExtents );
+               if ( isWebGL2 && map && map.isTexture && map.format === RGBAFormat && map.type === UnsignedByteType && map.encoding === sRGBEncoding ) {
 
-                       _viewportSize.copy( shadow.mapSize );
+                       encoding = LinearEncoding; // disable inline decode for sRGB textures in WebGL 2
 
-                       if ( _shadowMapSize.x > maxTextureSize || _shadowMapSize.y > maxTextureSize ) {
+               }
 
-                               if ( _shadowMapSize.x > maxTextureSize ) {
+               return encoding;
 
-                                       _viewportSize.x = Math.floor( maxTextureSize / shadowFrameExtents.x );
-                                       _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x;
-                                       shadow.mapSize.x = _viewportSize.x;
+       }
 
-                               }
+       function getParameters( material, lights, shadows, scene, object ) {
 
-                               if ( _shadowMapSize.y > maxTextureSize ) {
+               const fog = scene.fog;
+               const environment = material.isMeshStandardMaterial ? scene.environment : null;
 
-                                       _viewportSize.y = Math.floor( maxTextureSize / shadowFrameExtents.y );
-                                       _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y;
-                                       shadow.mapSize.y = _viewportSize.y;
+               const envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || environment );
 
-                               }
+               const shaderID = shaderIDs[ material.type ];
 
-                       }
+               // heuristics to create shader parameters according to lights in the scene
+               // (not to blow over maxLights budget)
 
-                       if ( shadow.map === null && ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
+               const maxBones = object.isSkinnedMesh ? getMaxBones( object ) : 0;
 
-                               const pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat };
+               if ( material.precision !== null ) {
 
-                               shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
-                               shadow.map.texture.name = light.name + '.shadowMap';
+                       precision = capabilities.getMaxPrecision( material.precision );
 
-                               shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+                       if ( precision !== material.precision ) {
 
-                               shadow.camera.updateProjectionMatrix();
+                               console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' );
 
                        }
 
-                       if ( shadow.map === null ) {
-
-                               const pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat };
+               }
 
-                               shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
-                               shadow.map.texture.name = light.name + '.shadowMap';
+               let vertexShader, fragmentShader;
 
-                               shadow.camera.updateProjectionMatrix();
+               if ( shaderID ) {
 
-                       }
+                       const shader = ShaderLib[ shaderID ];
 
-                       _renderer.setRenderTarget( shadow.map );
-                       _renderer.clear();
+                       vertexShader = shader.vertexShader;
+                       fragmentShader = shader.fragmentShader;
 
-                       const viewportCount = shadow.getViewportCount();
+               } else {
 
-                       for ( let vp = 0; vp < viewportCount; vp ++ ) {
+                       vertexShader = material.vertexShader;
+                       fragmentShader = material.fragmentShader;
 
-                               const viewport = shadow.getViewport( vp );
+               }
 
-                               _viewport.set(
-                                       _viewportSize.x * viewport.x,
-                                       _viewportSize.y * viewport.y,
-                                       _viewportSize.x * viewport.z,
-                                       _viewportSize.y * viewport.w
-                               );
+               const currentRenderTarget = renderer.getRenderTarget();
 
-                               _state.viewport( _viewport );
+               const useAlphaTest = material.alphaTest > 0;
+               const useClearcoat = material.clearcoat > 0;
 
-                               shadow.updateMatrices( light, vp );
+               const parameters = {
 
-                               _frustum = shadow.getFrustum();
+                       isWebGL2: isWebGL2,
 
-                               renderObject( scene, camera, shadow.camera, light, this.type );
+                       shaderID: shaderID,
+                       shaderName: material.type,
 
-                       }
+                       vertexShader: vertexShader,
+                       fragmentShader: fragmentShader,
+                       defines: material.defines,
 
-                       // do blur pass for VSM
+                       isRawShaderMaterial: material.isRawShaderMaterial === true,
+                       glslVersion: material.glslVersion,
 
-                       if ( ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
+                       precision: precision,
 
-                               VSMPass( shadow, camera );
+                       instancing: object.isInstancedMesh === true,
+                       instancingColor: object.isInstancedMesh === true && object.instanceColor !== null,
 
-                       }
+                       supportsVertexTextures: vertexTextures,
+                       outputEncoding: ( currentRenderTarget !== null ) ? getTextureEncodingFromMap( currentRenderTarget.texture ) : renderer.outputEncoding,
+                       map: !! material.map,
+                       mapEncoding: getTextureEncodingFromMap( material.map ),
+                       matcap: !! material.matcap,
+                       matcapEncoding: getTextureEncodingFromMap( material.matcap ),
+                       envMap: !! envMap,
+                       envMapMode: envMap && envMap.mapping,
+                       envMapEncoding: getTextureEncodingFromMap( envMap ),
+                       envMapCubeUV: ( !! envMap ) && ( ( envMap.mapping === CubeUVReflectionMapping ) || ( envMap.mapping === CubeUVRefractionMapping ) ),
+                       lightMap: !! material.lightMap,
+                       lightMapEncoding: getTextureEncodingFromMap( material.lightMap ),
+                       aoMap: !! material.aoMap,
+                       emissiveMap: !! material.emissiveMap,
+                       emissiveMapEncoding: getTextureEncodingFromMap( material.emissiveMap ),
+                       bumpMap: !! material.bumpMap,
+                       normalMap: !! material.normalMap,
+                       objectSpaceNormalMap: material.normalMapType === ObjectSpaceNormalMap,
+                       tangentSpaceNormalMap: material.normalMapType === TangentSpaceNormalMap,
 
-                       shadow.needsUpdate = false;
+                       clearcoat: useClearcoat,
+                       clearcoatMap: useClearcoat && !! material.clearcoatMap,
+                       clearcoatRoughnessMap: useClearcoat && !! material.clearcoatRoughnessMap,
+                       clearcoatNormalMap: useClearcoat && !! material.clearcoatNormalMap,
 
-               }
+                       displacementMap: !! material.displacementMap,
+                       roughnessMap: !! material.roughnessMap,
+                       metalnessMap: !! material.metalnessMap,
+                       specularMap: !! material.specularMap,
+                       specularIntensityMap: !! material.specularIntensityMap,
+                       specularColorMap: !! material.specularColorMap,
+                       specularColorMapEncoding: getTextureEncodingFromMap( material.specularColorMap ),
 
-               scope.needsUpdate = false;
+                       alphaMap: !! material.alphaMap,
+                       alphaTest: useAlphaTest,
 
-               _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel );
+                       gradientMap: !! material.gradientMap,
 
-       };
+                       sheen: material.sheen > 0,
+                       sheenColorMap: !! material.sheenColorMap,
+                       sheenColorMapEncoding: getTextureEncodingFromMap( material.sheenColorMap ),
+                       sheenRoughnessMap: !! material.sheenRoughnessMap,
 
-       function VSMPass( shadow, camera ) {
+                       transmission: material.transmission > 0,
+                       transmissionMap: !! material.transmissionMap,
+                       thicknessMap: !! material.thicknessMap,
 
-               const geometry = _objects.update( fullScreenMesh );
+                       combine: material.combine,
 
-               // vertical pass
+                       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,
 
-               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 );
+                       fog: !! fog,
+                       useFog: material.fog,
+                       fogExp2: ( fog && fog.isFogExp2 ),
 
-               // horizontal pass
+                       flatShading: !! material.flatShading,
 
-               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 );
+                       sizeAttenuation: material.sizeAttenuation,
+                       logarithmicDepthBuffer: logarithmicDepthBuffer,
 
-       }
+                       skinning: object.isSkinnedMesh === true && maxBones > 0,
+                       maxBones: maxBones,
+                       useVertexTexture: floatVertexTextures,
 
-       function getDepthMaterialVariant( useMorphing, useSkinning, useInstancing ) {
+                       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,
 
-               const index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;
+                       numDirLights: lights.directional.length,
+                       numPointLights: lights.point.length,
+                       numSpotLights: lights.spot.length,
+                       numRectAreaLights: lights.rectArea.length,
+                       numHemiLights: lights.hemi.length,
 
-               let material = _depthMaterials[ index ];
+                       numDirLightShadows: lights.directionalShadowMap.length,
+                       numPointLightShadows: lights.pointShadowMap.length,
+                       numSpotLightShadows: lights.spotShadowMap.length,
 
-               if ( material === undefined ) {
+                       numClippingPlanes: clipping.numPlanes,
+                       numClipIntersection: clipping.numIntersection,
 
-                       material = new MeshDepthMaterial( {
+                       format: material.format,
+                       dithering: material.dithering,
 
-                               depthPacking: RGBADepthPacking,
+                       shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0,
+                       shadowMapType: renderer.shadowMap.type,
 
-                               morphTargets: useMorphing,
-                               skinning: useSkinning
+                       toneMapping: material.toneMapped ? renderer.toneMapping : NoToneMapping,
+                       physicallyCorrectLights: renderer.physicallyCorrectLights,
 
-                       } );
+                       premultipliedAlpha: material.premultipliedAlpha,
 
-                       _depthMaterials[ index ] = material;
+                       doubleSided: material.side === DoubleSide,
+                       flipSided: material.side === BackSide,
 
-               }
+                       depthPacking: ( material.depthPacking !== undefined ) ? material.depthPacking : false,
 
-               return material;
+                       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 getDistanceMaterialVariant( useMorphing, useSkinning, useInstancing ) {
+                       rendererExtensionFragDepth: isWebGL2 || extensions.has( 'EXT_frag_depth' ),
+                       rendererExtensionDrawBuffers: isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ),
+                       rendererExtensionShaderTextureLod: isWebGL2 || extensions.has( 'EXT_shader_texture_lod' ),
 
-               const index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;
+                       customProgramCacheKey: material.customProgramCacheKey()
 
-               let material = _distanceMaterials[ index ];
+               };
 
-               if ( material === undefined ) {
+               return parameters;
 
-                       material = new MeshDistanceMaterial( {
+       }
 
-                               morphTargets: useMorphing,
-                               skinning: useSkinning
+       function getProgramCacheKey( parameters ) {
 
-                       } );
+               const array = [];
 
-                       _distanceMaterials[ index ] = material;
+               if ( parameters.shaderID ) {
 
-               }
+                       array.push( parameters.shaderID );
 
-               return material;
+               } else {
 
-       }
+                       array.push( hashString( parameters.fragmentShader ) );
+                       array.push( hashString( parameters.vertexShader ) );
 
-       function getDepthMaterial( object, geometry, material, light, shadowCameraNear, shadowCameraFar, type ) {
+               }
 
-               let result = null;
+               if ( parameters.defines !== undefined ) {
 
-               let getMaterialVariant = getDepthMaterialVariant;
-               let customMaterial = object.customDepthMaterial;
+                       for ( const name in parameters.defines ) {
 
-               if ( light.isPointLight === true ) {
+                               array.push( name );
+                               array.push( parameters.defines[ name ] );
 
-                       getMaterialVariant = getDistanceMaterialVariant;
-                       customMaterial = object.customDistanceMaterial;
+                       }
 
                }
 
-               if ( customMaterial === undefined ) {
-
-                       let useMorphing = false;
+               if ( parameters.isRawShaderMaterial === false ) {
 
-                       if ( material.morphTargets === true ) {
+                       for ( let i = 0; i < parameterNames.length; i ++ ) {
 
-                               useMorphing = geometry.morphAttributes && geometry.morphAttributes.position && geometry.morphAttributes.position.length > 0;
+                               array.push( parameters[ parameterNames[ i ] ] );
 
                        }
 
-                       let useSkinning = false;
-
-                       if ( object.isSkinnedMesh === true ) {
+                       array.push( renderer.outputEncoding );
+                       array.push( renderer.gammaFactor );
 
-                               if ( material.skinning === true ) {
+               }
 
-                                       useSkinning = true;
+               array.push( parameters.customProgramCacheKey );
 
-                               } else {
+               return array.join();
 
-                                       console.warn( 'THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:', object );
+       }
 
-                               }
+       function getUniforms( material ) {
 
-                       }
+               const shaderID = shaderIDs[ material.type ];
+               let uniforms;
 
-                       const useInstancing = object.isInstancedMesh === true;
+               if ( shaderID ) {
 
-                       result = getMaterialVariant( useMorphing, useSkinning, useInstancing );
+                       const shader = ShaderLib[ shaderID ];
+                       uniforms = UniformsUtils.clone( shader.uniforms );
 
                } else {
 
-                       result = customMaterial;
+                       uniforms = material.uniforms;
 
                }
 
-               if ( _renderer.localClippingEnabled &&
-                               material.clipShadows === true &&
-                               material.clippingPlanes.length !== 0 ) {
+               return uniforms;
 
-                       // in this case we need a unique material instance reflecting the
-                       // appropriate state
-
-                       const keyA = result.uuid, keyB = material.uuid;
+       }
 
-                       let materialsForVariant = _materialCache[ keyA ];
+       function acquireProgram( parameters, cacheKey ) {
 
-                       if ( materialsForVariant === undefined ) {
+               let program;
 
-                               materialsForVariant = {};
-                               _materialCache[ keyA ] = materialsForVariant;
+               // Check if code has been already compiled
+               for ( let p = 0, pl = programs.length; p < pl; p ++ ) {
 
-                       }
+                       const preexistingProgram = programs[ p ];
 
-                       let cachedMaterial = materialsForVariant[ keyB ];
+                       if ( preexistingProgram.cacheKey === cacheKey ) {
 
-                       if ( cachedMaterial === undefined ) {
+                               program = preexistingProgram;
+                               ++ program.usedTimes;
 
-                               cachedMaterial = result.clone();
-                               materialsForVariant[ keyB ] = cachedMaterial;
+                               break;
 
                        }
 
-                       result = cachedMaterial;
-
                }
 
-               result.visible = material.visible;
-               result.wireframe = material.wireframe;
-
-               if ( type === VSMShadowMap ) {
+               if ( program === undefined ) {
 
-                       result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side;
+                       program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates );
+                       programs.push( program );
 
-               } else {
+               }
 
-                       result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ];
+               return program;
 
-               }
+       }
 
-               result.clipShadows = material.clipShadows;
-               result.clippingPlanes = material.clippingPlanes;
-               result.clipIntersection = material.clipIntersection;
+       function releaseProgram( program ) {
 
-               result.wireframeLinewidth = material.wireframeLinewidth;
-               result.linewidth = material.linewidth;
+               if ( -- program.usedTimes === 0 ) {
 
-               if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) {
+                       // Remove from unordered set
+                       const i = programs.indexOf( program );
+                       programs[ i ] = programs[ programs.length - 1 ];
+                       programs.pop();
 
-                       result.referencePosition.setFromMatrixPosition( light.matrixWorld );
-                       result.nearDistance = shadowCameraNear;
-                       result.farDistance = shadowCameraFar;
+                       // Free WebGL resources
+                       program.destroy();
 
                }
 
-               return result;
-
        }
 
-       function renderObject( object, camera, shadowCamera, light, type ) {
+       return {
+               getParameters: getParameters,
+               getProgramCacheKey: getProgramCacheKey,
+               getUniforms: getUniforms,
+               acquireProgram: acquireProgram,
+               releaseProgram: releaseProgram,
+               // Exposed for resource monitoring & error feedback via renderer.info:
+               programs: programs
+       };
 
-               if ( object.visible === false ) return;
+    }
 
-               const visible = object.layers.test( camera.layers );
+    function WebGLProperties() {
 
-               if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) {
+       let properties = new WeakMap();
 
-                       if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) {
+       function get( object ) {
 
-                               object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
+               let map = properties.get( object );
 
-                               const geometry = _objects.update( object );
-                               const material = object.material;
+               if ( map === undefined ) {
 
-                               if ( Array.isArray( material ) ) {
+                       map = {};
+                       properties.set( object, map );
 
-                                       const groups = geometry.groups;
+               }
 
-                                       for ( let k = 0, kl = groups.length; k < kl; k ++ ) {
+               return map;
 
-                                               const group = groups[ k ];
-                                               const groupMaterial = material[ group.materialIndex ];
+       }
 
-                                               if ( groupMaterial && groupMaterial.visible ) {
+       function remove( object ) {
 
-                                                       const depthMaterial = getDepthMaterial( object, geometry, groupMaterial, light, shadowCamera.near, shadowCamera.far, type );
+               properties.delete( object );
 
-                                                       _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group );
+       }
 
-                                               }
+       function update( object, key, value ) {
 
-                                       }
+               properties.get( object )[ key ] = value;
 
-                               } else if ( material.visible ) {
+       }
 
-                                       const depthMaterial = getDepthMaterial( object, geometry, material, light, shadowCamera.near, shadowCamera.far, type );
+       function dispose() {
 
-                                       _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null );
+               properties = new WeakMap();
 
-                               }
+       }
 
-                       }
+       return {
+               get: get,
+               remove: remove,
+               update: update,
+               dispose: dispose
+       };
 
-               }
+    }
 
-               const children = object.children;
+    function painterSortStable( a, b ) {
 
-               for ( let i = 0, l = children.length; i < l; i ++ ) {
+       if ( a.groupOrder !== b.groupOrder ) {
 
-                       renderObject( children[ i ], camera, shadowCamera, light, type );
+               return a.groupOrder - b.groupOrder;
 
-               }
+       } else if ( a.renderOrder !== b.renderOrder ) {
 
-       }
+               return a.renderOrder - b.renderOrder;
 
-    }
+       } else if ( a.program !== b.program ) {
 
-    function WebGLState( gl, extensions, capabilities ) {
+               return a.program.id - b.program.id;
 
-       const isWebGL2 = capabilities.isWebGL2;
+       } else if ( a.material.id !== b.material.id ) {
 
-       function ColorBuffer() {
+               return a.material.id - b.material.id;
 
-               let locked = false;
+       } else if ( a.z !== b.z ) {
 
-               const color = new Vector4();
-               let currentColorMask = null;
-               const currentColorClear = new Vector4( 0, 0, 0, 0 );
+               return a.z - b.z;
 
-               return {
+       } else {
 
-                       setMask: function ( colorMask ) {
+               return a.id - b.id;
 
-                               if ( currentColorMask !== colorMask && ! locked ) {
+       }
 
-                                       gl.colorMask( colorMask, colorMask, colorMask, colorMask );
-                                       currentColorMask = colorMask;
+    }
 
-                               }
+    function reversePainterSortStable( a, b ) {
 
-                       },
+       if ( a.groupOrder !== b.groupOrder ) {
 
-                       setLocked: function ( lock ) {
+               return a.groupOrder - b.groupOrder;
 
-                               locked = lock;
+       } else if ( a.renderOrder !== b.renderOrder ) {
 
-                       },
+               return a.renderOrder - b.renderOrder;
 
-                       setClear: function ( r, g, b, a, premultipliedAlpha ) {
+       } else if ( a.z !== b.z ) {
 
-                               if ( premultipliedAlpha === true ) {
+               return b.z - a.z;
 
-                                       r *= a; g *= a; b *= a;
+       } else {
 
-                               }
+               return a.id - b.id;
 
-                               color.set( r, g, b, a );
+       }
 
-                               if ( currentColorClear.equals( color ) === false ) {
+    }
 
-                                       gl.clearColor( r, g, b, a );
-                                       currentColorClear.copy( color );
 
-                               }
+    function WebGLRenderList( properties ) {
 
-                       },
+       const renderItems = [];
+       let renderItemsIndex = 0;
 
-                       reset: function () {
+       const opaque = [];
+       const transmissive = [];
+       const transparent = [];
 
-                               locked = false;
+       const defaultProgram = { id: - 1 };
 
-                               currentColorMask = null;
-                               currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state
+       function init() {
 
-                       }
+               renderItemsIndex = 0;
 
-               };
+               opaque.length = 0;
+               transmissive.length = 0;
+               transparent.length = 0;
 
        }
 
-       function DepthBuffer() {
+       function getNextRenderItem( object, geometry, material, groupOrder, z, group ) {
 
-               let locked = false;
+               let renderItem = renderItems[ renderItemsIndex ];
+               const materialProperties = properties.get( material );
 
-               let currentDepthMask = null;
-               let currentDepthFunc = null;
-               let currentDepthClear = null;
+               if ( renderItem === undefined ) {
 
-               return {
+                       renderItem = {
+                               id: object.id,
+                               object: object,
+                               geometry: geometry,
+                               material: material,
+                               program: materialProperties.program || defaultProgram,
+                               groupOrder: groupOrder,
+                               renderOrder: object.renderOrder,
+                               z: z,
+                               group: group
+                       };
 
-                       setTest: function ( depthTest ) {
+                       renderItems[ renderItemsIndex ] = renderItem;
 
-                               if ( depthTest ) {
+               } else {
 
-                                       enable( 2929 );
+                       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;
 
-                               } else {
+               }
 
-                                       disable( 2929 );
+               renderItemsIndex ++;
 
-                               }
+               return renderItem;
 
-                       },
+       }
 
-                       setMask: function ( depthMask ) {
+       function push( object, geometry, material, groupOrder, z, group ) {
 
-                               if ( currentDepthMask !== depthMask && ! locked ) {
+               const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
 
-                                       gl.depthMask( depthMask );
-                                       currentDepthMask = depthMask;
+               if ( material.transmission > 0.0 ) {
 
-                               }
+                       transmissive.push( renderItem );
 
-                       },
+               } else if ( material.transparent === true ) {
 
-                       setFunc: function ( depthFunc ) {
+                       transparent.push( renderItem );
 
-                               if ( currentDepthFunc !== depthFunc ) {
+               } else {
 
-                                       if ( depthFunc ) {
+                       opaque.push( renderItem );
 
-                                               switch ( depthFunc ) {
+               }
 
-                                                       case NeverDepth:
+       }
 
-                                                               gl.depthFunc( 512 );
-                                                               break;
+       function unshift( object, geometry, material, groupOrder, z, group ) {
 
-                                                       case AlwaysDepth:
+               const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
 
-                                                               gl.depthFunc( 519 );
-                                                               break;
+               if ( material.transmission > 0.0 ) {
 
-                                                       case LessDepth:
+                       transmissive.unshift( renderItem );
 
-                                                               gl.depthFunc( 513 );
-                                                               break;
+               } else if ( material.transparent === true ) {
 
-                                                       case LessEqualDepth:
+                       transparent.unshift( renderItem );
 
-                                                               gl.depthFunc( 515 );
-                                                               break;
+               } else {
 
-                                                       case EqualDepth:
+                       opaque.unshift( renderItem );
 
-                                                               gl.depthFunc( 514 );
-                                                               break;
+               }
 
-                                                       case GreaterEqualDepth:
+       }
 
-                                                               gl.depthFunc( 518 );
-                                                               break;
+       function sort( customOpaqueSort, customTransparentSort ) {
 
-                                                       case GreaterDepth:
+               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.depthFunc( 516 );
-                                                               break;
+       }
 
-                                                       case NotEqualDepth:
+       function finish() {
 
-                                                               gl.depthFunc( 517 );
-                                                               break;
+               // Clear references from inactive renderItems in the list
 
-                                                       default:
+               for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) {
 
-                                                               gl.depthFunc( 515 );
+                       const renderItem = renderItems[ i ];
 
-                                               }
+                       if ( renderItem.id === null ) break;
 
-                                       } else {
+                       renderItem.id = null;
+                       renderItem.object = null;
+                       renderItem.geometry = null;
+                       renderItem.material = null;
+                       renderItem.program = null;
+                       renderItem.group = null;
 
-                                               gl.depthFunc( 515 );
+               }
 
-                                       }
+       }
 
-                                       currentDepthFunc = depthFunc;
+       return {
 
-                               }
+               opaque: opaque,
+               transmissive: transmissive,
+               transparent: transparent,
 
-                       },
+               init: init,
+               push: push,
+               unshift: unshift,
+               finish: finish,
 
-                       setLocked: function ( lock ) {
+               sort: sort
+       };
 
-                               locked = lock;
+    }
 
-                       },
+    function WebGLRenderLists( properties ) {
 
-                       setClear: function ( depth ) {
+       let lists = new WeakMap();
 
-                               if ( currentDepthClear !== depth ) {
+       function get( scene, renderCallDepth ) {
 
-                                       gl.clearDepth( depth );
-                                       currentDepthClear = depth;
+               let list;
 
-                               }
+               if ( lists.has( scene ) === false ) {
 
-                       },
+                       list = new WebGLRenderList( properties );
+                       lists.set( scene, [ list ] );
 
-                       reset: function () {
+               } else {
 
-                               locked = false;
+                       if ( renderCallDepth >= lists.get( scene ).length ) {
 
-                               currentDepthMask = null;
-                               currentDepthFunc = null;
-                               currentDepthClear = null;
+                               list = new WebGLRenderList( properties );
+                               lists.get( scene ).push( list );
 
-                       }
+                       } else {
 
-               };
+                               list = lists.get( scene )[ renderCallDepth ];
 
-       }
+                       }
 
-       function StencilBuffer() {
+               }
 
-               let locked = false;
+               return list;
 
-               let currentStencilMask = null;
-               let currentStencilFunc = null;
-               let currentStencilRef = null;
-               let currentStencilFuncMask = null;
-               let currentStencilFail = null;
-               let currentStencilZFail = null;
-               let currentStencilZPass = null;
-               let currentStencilClear = null;
+       }
 
-               return {
+       function dispose() {
 
-                       setTest: function ( stencilTest ) {
+               lists = new WeakMap();
 
-                               if ( ! locked ) {
+       }
 
-                                       if ( stencilTest ) {
+       return {
+               get: get,
+               dispose: dispose
+       };
 
-                                               enable( 2960 );
+    }
 
-                                       } else {
+    function UniformsCache() {
 
-                                               disable( 2960 );
+       const lights = {};
 
-                                       }
+       return {
 
-                               }
+               get: function ( light ) {
 
-                       },
+                       if ( lights[ light.id ] !== undefined ) {
 
-                       setMask: function ( stencilMask ) {
+                               return lights[ light.id ];
 
-                               if ( currentStencilMask !== stencilMask && ! locked ) {
+                       }
 
-                                       gl.stencilMask( stencilMask );
-                                       currentStencilMask = stencilMask;
+                       let uniforms;
 
-                               }
+                       switch ( light.type ) {
 
-                       },
+                               case 'DirectionalLight':
+                                       uniforms = {
+                                               direction: new Vector3(),
+                                               color: new Color()
+                                       };
+                                       break;
 
-                       setFunc: function ( stencilFunc, stencilRef, stencilMask ) {
+                               case 'SpotLight':
+                                       uniforms = {
+                                               position: new Vector3(),
+                                               direction: new Vector3(),
+                                               color: new Color(),
+                                               distance: 0,
+                                               coneCos: 0,
+                                               penumbraCos: 0,
+                                               decay: 0
+                                       };
+                                       break;
 
-                               if ( currentStencilFunc !== stencilFunc ||
-                                    currentStencilRef !== stencilRef ||
-                                    currentStencilFuncMask !== stencilMask ) {
+                               case 'PointLight':
+                                       uniforms = {
+                                               position: new Vector3(),
+                                               color: new Color(),
+                                               distance: 0,
+                                               decay: 0
+                                       };
+                                       break;
 
-                                       gl.stencilFunc( stencilFunc, stencilRef, stencilMask );
+                               case 'HemisphereLight':
+                                       uniforms = {
+                                               direction: new Vector3(),
+                                               skyColor: new Color(),
+                                               groundColor: new Color()
+                                       };
+                                       break;
 
-                                       currentStencilFunc = stencilFunc;
-                                       currentStencilRef = stencilRef;
-                                       currentStencilFuncMask = stencilMask;
+                               case 'RectAreaLight':
+                                       uniforms = {
+                                               color: new Color(),
+                                               position: new Vector3(),
+                                               halfWidth: new Vector3(),
+                                               halfHeight: new Vector3()
+                                       };
+                                       break;
 
-                               }
+                       }
 
-                       },
+                       lights[ light.id ] = uniforms;
 
-                       setOp: function ( stencilFail, stencilZFail, stencilZPass ) {
+                       return uniforms;
 
-                               if ( currentStencilFail !== stencilFail ||
-                                    currentStencilZFail !== stencilZFail ||
-                                    currentStencilZPass !== stencilZPass ) {
+               }
 
-                                       gl.stencilOp( stencilFail, stencilZFail, stencilZPass );
+       };
 
-                                       currentStencilFail = stencilFail;
-                                       currentStencilZFail = stencilZFail;
-                                       currentStencilZPass = stencilZPass;
+    }
 
-                               }
+    function ShadowUniformsCache() {
 
-                       },
+       const lights = {};
 
-                       setLocked: function ( lock ) {
+       return {
 
-                               locked = lock;
+               get: function ( light ) {
 
-                       },
+                       if ( lights[ light.id ] !== undefined ) {
 
-                       setClear: function ( stencil ) {
+                               return lights[ light.id ];
 
-                               if ( currentStencilClear !== stencil ) {
+                       }
 
-                                       gl.clearStencil( stencil );
-                                       currentStencilClear = stencil;
+                       let uniforms;
 
-                               }
+                       switch ( light.type ) {
 
-                       },
+                               case 'DirectionalLight':
+                                       uniforms = {
+                                               shadowBias: 0,
+                                               shadowNormalBias: 0,
+                                               shadowRadius: 1,
+                                               shadowMapSize: new Vector2()
+                                       };
+                                       break;
 
-                       reset: function () {
+                               case 'SpotLight':
+                                       uniforms = {
+                                               shadowBias: 0,
+                                               shadowNormalBias: 0,
+                                               shadowRadius: 1,
+                                               shadowMapSize: new Vector2()
+                                       };
+                                       break;
 
-                               locked = false;
+                               case 'PointLight':
+                                       uniforms = {
+                                               shadowBias: 0,
+                                               shadowNormalBias: 0,
+                                               shadowRadius: 1,
+                                               shadowMapSize: new Vector2(),
+                                               shadowCameraNear: 1,
+                                               shadowCameraFar: 1000
+                                       };
+                                       break;
 
-                               currentStencilMask = null;
-                               currentStencilFunc = null;
-                               currentStencilRef = null;
-                               currentStencilFuncMask = null;
-                               currentStencilFail = null;
-                               currentStencilZFail = null;
-                               currentStencilZPass = null;
-                               currentStencilClear = null;
+                               // TODO (abelnation): set RectAreaLight shadow uniforms
 
                        }
 
-               };
+                       lights[ light.id ] = uniforms;
 
-       }
+                       return uniforms;
 
-       //
+               }
 
-       const colorBuffer = new ColorBuffer();
-       const depthBuffer = new DepthBuffer();
-       const stencilBuffer = new StencilBuffer();
+       };
 
-       let enabledCapabilities = {};
+    }
 
-       let currentProgram = null;
 
-       let currentBlendingEnabled = null;
-       let currentBlending = null;
-       let currentBlendEquation = null;
-       let currentBlendSrc = null;
-       let currentBlendDst = null;
-       let currentBlendEquationAlpha = null;
-       let currentBlendSrcAlpha = null;
-       let currentBlendDstAlpha = null;
-       let currentPremultipledAlpha = false;
 
-       let currentFlipSided = null;
-       let currentCullFace = null;
+    let nextVersion = 0;
 
-       let currentLineWidth = null;
+    function shadowCastingLightsFirst( lightA, lightB ) {
 
-       let currentPolygonOffsetFactor = null;
-       let currentPolygonOffsetUnits = null;
+       return ( lightB.castShadow ? 1 : 0 ) - ( lightA.castShadow ? 1 : 0 );
 
-       const maxTextures = gl.getParameter( 35661 );
+    }
 
-       let lineWidthAvailable = false;
-       let version = 0;
-       const glVersion = gl.getParameter( 7938 );
+    function WebGLLights( extensions, capabilities ) {
 
-       if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) {
+       const cache = new UniformsCache();
 
-               version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] );
-               lineWidthAvailable = ( version >= 1.0 );
+       const shadowCache = ShadowUniformsCache();
 
-       } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) {
+       const state = {
 
-               version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] );
-               lineWidthAvailable = ( version >= 2.0 );
+               version: 0,
 
-       }
+               hash: {
+                       directionalLength: - 1,
+                       pointLength: - 1,
+                       spotLength: - 1,
+                       rectAreaLength: - 1,
+                       hemiLength: - 1,
 
-       let currentTextureSlot = null;
-       let currentBoundTextures = {};
+                       numDirectionalShadows: - 1,
+                       numPointShadows: - 1,
+                       numSpotShadows: - 1
+               },
 
-       const currentScissor = new Vector4();
-       const currentViewport = new Vector4();
+               ambient: [ 0, 0, 0 ],
+               probe: [],
+               directional: [],
+               directionalShadow: [],
+               directionalShadowMap: [],
+               directionalShadowMatrix: [],
+               spot: [],
+               spotShadow: [],
+               spotShadowMap: [],
+               spotShadowMatrix: [],
+               rectArea: [],
+               rectAreaLTC1: null,
+               rectAreaLTC2: null,
+               point: [],
+               pointShadow: [],
+               pointShadowMap: [],
+               pointShadowMatrix: [],
+               hemi: []
 
-       function createTexture( type, target, count ) {
+       };
 
-               const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4.
-               const texture = gl.createTexture();
+       for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() );
 
-               gl.bindTexture( type, texture );
-               gl.texParameteri( type, 10241, 9728 );
-               gl.texParameteri( type, 10240, 9728 );
+       const vector3 = new Vector3();
+       const matrix4 = new Matrix4();
+       const matrix42 = new Matrix4();
 
-               for ( let i = 0; i < count; i ++ ) {
+       function setup( lights, physicallyCorrectLights ) {
 
-                       gl.texImage2D( target + i, 0, 6408, 1, 1, 0, 6408, 5121, data );
+               let r = 0, g = 0, b = 0;
 
-               }
+               for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 );
 
-               return texture;
+               let directionalLength = 0;
+               let pointLength = 0;
+               let spotLength = 0;
+               let rectAreaLength = 0;
+               let hemiLength = 0;
 
-       }
+               let numDirectionalShadows = 0;
+               let numPointShadows = 0;
+               let numSpotShadows = 0;
 
-       const emptyTextures = {};
-       emptyTextures[ 3553 ] = createTexture( 3553, 3553, 1 );
-       emptyTextures[ 34067 ] = createTexture( 34067, 34069, 6 );
+               lights.sort( shadowCastingLightsFirst );
 
-       // init
+               // artist-friendly light intensity scaling factor
+               const scaleFactor = ( physicallyCorrectLights !== true ) ? Math.PI : 1;
 
-       colorBuffer.setClear( 0, 0, 0, 1 );
-       depthBuffer.setClear( 1 );
-       stencilBuffer.setClear( 0 );
+               for ( let i = 0, l = lights.length; i < l; i ++ ) {
 
-       enable( 2929 );
-       depthBuffer.setFunc( LessEqualDepth );
+                       const light = lights[ i ];
 
-       setFlipSided( false );
-       setCullFace( CullFaceBack );
-       enable( 2884 );
+                       const color = light.color;
+                       const intensity = light.intensity;
+                       const distance = light.distance;
 
-       setBlending( NoBlending );
+                       const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null;
 
-       //
+                       if ( light.isAmbientLight ) {
 
-       function enable( id ) {
+                               r += color.r * intensity * scaleFactor;
+                               g += color.g * intensity * scaleFactor;
+                               b += color.b * intensity * scaleFactor;
 
-               if ( enabledCapabilities[ id ] !== true ) {
+                       } else if ( light.isLightProbe ) {
 
-                       gl.enable( id );
-                       enabledCapabilities[ id ] = true;
+                               for ( let j = 0; j < 9; j ++ ) {
 
-               }
+                                       state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity );
 
-       }
+                               }
 
-       function disable( id ) {
+                       } else if ( light.isDirectionalLight ) {
 
-               if ( enabledCapabilities[ id ] !== false ) {
+                               const uniforms = cache.get( light );
 
-                       gl.disable( id );
-                       enabledCapabilities[ id ] = false;
+                               uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor );
 
-               }
+                               if ( light.castShadow ) {
 
-       }
+                                       const shadow = light.shadow;
 
-       function useProgram( program ) {
+                                       const shadowUniforms = shadowCache.get( light );
 
-               if ( currentProgram !== program ) {
+                                       shadowUniforms.shadowBias = shadow.bias;
+                                       shadowUniforms.shadowNormalBias = shadow.normalBias;
+                                       shadowUniforms.shadowRadius = shadow.radius;
+                                       shadowUniforms.shadowMapSize = shadow.mapSize;
 
-                       gl.useProgram( program );
+                                       state.directionalShadow[ directionalLength ] = shadowUniforms;
+                                       state.directionalShadowMap[ directionalLength ] = shadowMap;
+                                       state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix;
 
-                       currentProgram = program;
+                                       numDirectionalShadows ++;
 
-                       return true;
+                               }
 
-               }
+                               state.directional[ directionalLength ] = uniforms;
 
-               return false;
+                               directionalLength ++;
 
-       }
+                       } else if ( light.isSpotLight ) {
 
-       const equationToGL = {
-               [ AddEquation ]: 32774,
-               [ SubtractEquation ]: 32778,
-               [ ReverseSubtractEquation ]: 32779
-       };
+                               const uniforms = cache.get( light );
 
-       if ( isWebGL2 ) {
+                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
 
-               equationToGL[ MinEquation ] = 32775;
-               equationToGL[ MaxEquation ] = 32776;
+                               uniforms.color.copy( color ).multiplyScalar( intensity * scaleFactor );
+                               uniforms.distance = distance;
 
-       } else {
+                               uniforms.coneCos = Math.cos( light.angle );
+                               uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) );
+                               uniforms.decay = light.decay;
 
-               const extension = extensions.get( 'EXT_blend_minmax' );
+                               if ( light.castShadow ) {
 
-               if ( extension !== null ) {
+                                       const shadow = light.shadow;
 
-                       equationToGL[ MinEquation ] = extension.MIN_EXT;
-                       equationToGL[ MaxEquation ] = extension.MAX_EXT;
+                                       const shadowUniforms = shadowCache.get( light );
 
-               }
+                                       shadowUniforms.shadowBias = shadow.bias;
+                                       shadowUniforms.shadowNormalBias = shadow.normalBias;
+                                       shadowUniforms.shadowRadius = shadow.radius;
+                                       shadowUniforms.shadowMapSize = shadow.mapSize;
 
-       }
+                                       state.spotShadow[ spotLength ] = shadowUniforms;
+                                       state.spotShadowMap[ spotLength ] = shadowMap;
+                                       state.spotShadowMatrix[ spotLength ] = light.shadow.matrix;
 
-       const factorToGL = {
-               [ ZeroFactor ]: 0,
-               [ OneFactor ]: 1,
-               [ SrcColorFactor ]: 768,
-               [ SrcAlphaFactor ]: 770,
-               [ SrcAlphaSaturateFactor ]: 776,
-               [ DstColorFactor ]: 774,
-               [ DstAlphaFactor ]: 772,
-               [ OneMinusSrcColorFactor ]: 769,
-               [ OneMinusSrcAlphaFactor ]: 771,
-               [ OneMinusDstColorFactor ]: 775,
-               [ OneMinusDstAlphaFactor ]: 773
-       };
+                                       numSpotShadows ++;
 
-       function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {
+                               }
 
-               if ( blending === NoBlending ) {
+                               state.spot[ spotLength ] = uniforms;
 
-                       if ( currentBlendingEnabled ) {
+                               spotLength ++;
 
-                               disable( 3042 );
-                               currentBlendingEnabled = false;
+                       } else if ( light.isRectAreaLight ) {
 
-                       }
+                               const uniforms = cache.get( light );
 
-                       return;
+                               // (a) intensity is the total visible light emitted
+                               //uniforms.color.copy( color ).multiplyScalar( intensity / ( light.width * light.height * Math.PI ) );
 
-               }
+                               // (b) intensity is the brightness of the light
+                               uniforms.color.copy( color ).multiplyScalar( intensity );
 
-               if ( ! currentBlendingEnabled ) {
+                               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
+                               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
 
-                       enable( 3042 );
-                       currentBlendingEnabled = true;
+                               state.rectArea[ rectAreaLength ] = uniforms;
 
-               }
+                               rectAreaLength ++;
 
-               if ( blending !== CustomBlending ) {
+                       } else if ( light.isPointLight ) {
 
-                       if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) {
+                               const uniforms = cache.get( light );
 
-                               if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) {
+                               uniforms.color.copy( light.color ).multiplyScalar( light.intensity * scaleFactor );
+                               uniforms.distance = light.distance;
+                               uniforms.decay = light.decay;
 
-                                       gl.blendEquation( 32774 );
+                               if ( light.castShadow ) {
 
-                                       currentBlendEquation = AddEquation;
-                                       currentBlendEquationAlpha = AddEquation;
+                                       const shadow = light.shadow;
 
-                               }
+                                       const shadowUniforms = shadowCache.get( light );
 
-                               if ( premultipliedAlpha ) {
+                                       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;
 
-                                       switch ( blending ) {
+                                       state.pointShadow[ pointLength ] = shadowUniforms;
+                                       state.pointShadowMap[ pointLength ] = shadowMap;
+                                       state.pointShadowMatrix[ pointLength ] = light.shadow.matrix;
 
-                                               case NormalBlending:
-                                                       gl.blendFuncSeparate( 1, 771, 1, 771 );
-                                                       break;
+                                       numPointShadows ++;
 
-                                               case AdditiveBlending:
-                                                       gl.blendFunc( 1, 1 );
-                                                       break;
+                               }
 
-                                               case SubtractiveBlending:
-                                                       gl.blendFuncSeparate( 0, 0, 769, 771 );
-                                                       break;
+                               state.point[ pointLength ] = uniforms;
 
-                                               case MultiplyBlending:
-                                                       gl.blendFuncSeparate( 0, 768, 0, 770 );
-                                                       break;
+                               pointLength ++;
 
-                                               default:
-                                                       console.error( 'THREE.WebGLState: Invalid blending: ', blending );
-                                                       break;
+                       } else if ( light.isHemisphereLight ) {
 
-                                       }
+                               const uniforms = cache.get( light );
 
-                               } else {
+                               uniforms.skyColor.copy( light.color ).multiplyScalar( intensity * scaleFactor );
+                               uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity * scaleFactor );
 
-                                       switch ( blending ) {
+                               state.hemi[ hemiLength ] = uniforms;
 
-                                               case NormalBlending:
-                                                       gl.blendFuncSeparate( 770, 771, 1, 771 );
-                                                       break;
+                               hemiLength ++;
 
-                                               case AdditiveBlending:
-                                                       gl.blendFunc( 770, 1 );
-                                                       break;
+                       }
 
-                                               case SubtractiveBlending:
-                                                       gl.blendFunc( 0, 769 );
-                                                       break;
+               }
 
-                                               case MultiplyBlending:
-                                                       gl.blendFunc( 0, 768 );
-                                                       break;
+               if ( rectAreaLength > 0 ) {
 
-                                               default:
-                                                       console.error( 'THREE.WebGLState: Invalid blending: ', blending );
-                                                       break;
+                       if ( capabilities.isWebGL2 ) {
 
-                                       }
+                               // WebGL 2
 
-                               }
+                               state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
+                               state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
 
-                               currentBlendSrc = null;
-                               currentBlendDst = null;
-                               currentBlendSrcAlpha = null;
-                               currentBlendDstAlpha = null;
+                       } else {
 
-                               currentBlending = blending;
-                               currentPremultipledAlpha = premultipliedAlpha;
+                               // WebGL 1
 
-                       }
+                               if ( extensions.has( 'OES_texture_float_linear' ) === true ) {
 
-                       return;
+                                       state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
+                                       state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
 
-               }
+                               } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) {
 
-               // custom blending
+                                       state.rectAreaLTC1 = UniformsLib.LTC_HALF_1;
+                                       state.rectAreaLTC2 = UniformsLib.LTC_HALF_2;
 
-               blendEquationAlpha = blendEquationAlpha || blendEquation;
-               blendSrcAlpha = blendSrcAlpha || blendSrc;
-               blendDstAlpha = blendDstAlpha || blendDst;
+                               } else {
 
-               if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) {
+                                       console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' );
 
-                       gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] );
+                               }
 
-                       currentBlendEquation = blendEquation;
-                       currentBlendEquationAlpha = blendEquationAlpha;
+                       }
 
                }
 
-               if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) {
+               state.ambient[ 0 ] = r;
+               state.ambient[ 1 ] = g;
+               state.ambient[ 2 ] = b;
 
-                       gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] );
+               const hash = state.hash;
 
-                       currentBlendSrc = blendSrc;
-                       currentBlendDst = blendDst;
-                       currentBlendSrcAlpha = blendSrcAlpha;
-                       currentBlendDstAlpha = blendDstAlpha;
+               if ( hash.directionalLength !== directionalLength ||
+                       hash.pointLength !== pointLength ||
+                       hash.spotLength !== spotLength ||
+                       hash.rectAreaLength !== rectAreaLength ||
+                       hash.hemiLength !== hemiLength ||
+                       hash.numDirectionalShadows !== numDirectionalShadows ||
+                       hash.numPointShadows !== numPointShadows ||
+                       hash.numSpotShadows !== numSpotShadows ) {
 
-               }
+                       state.directional.length = directionalLength;
+                       state.spot.length = spotLength;
+                       state.rectArea.length = rectAreaLength;
+                       state.point.length = pointLength;
+                       state.hemi.length = hemiLength;
 
-               currentBlending = blending;
-               currentPremultipledAlpha = null;
+                       state.directionalShadow.length = numDirectionalShadows;
+                       state.directionalShadowMap.length = numDirectionalShadows;
+                       state.pointShadow.length = numPointShadows;
+                       state.pointShadowMap.length = numPointShadows;
+                       state.spotShadow.length = numSpotShadows;
+                       state.spotShadowMap.length = numSpotShadows;
+                       state.directionalShadowMatrix.length = numDirectionalShadows;
+                       state.pointShadowMatrix.length = numPointShadows;
+                       state.spotShadowMatrix.length = numSpotShadows;
 
-       }
+                       hash.directionalLength = directionalLength;
+                       hash.pointLength = pointLength;
+                       hash.spotLength = spotLength;
+                       hash.rectAreaLength = rectAreaLength;
+                       hash.hemiLength = hemiLength;
 
-       function setMaterial( material, frontFaceCW ) {
+                       hash.numDirectionalShadows = numDirectionalShadows;
+                       hash.numPointShadows = numPointShadows;
+                       hash.numSpotShadows = numSpotShadows;
 
-               material.side === DoubleSide
-                       ? disable( 2884 )
-                       : enable( 2884 );
+                       state.version = nextVersion ++;
 
-               let flipSided = ( material.side === BackSide );
-               if ( frontFaceCW ) flipSided = ! flipSided;
+               }
 
-               setFlipSided( flipSided );
+       }
 
-               ( material.blending === NormalBlending && material.transparent === false )
-                       ? setBlending( NoBlending )
-                       : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha );
+       function setupView( lights, camera ) {
 
-               depthBuffer.setFunc( material.depthFunc );
-               depthBuffer.setTest( material.depthTest );
-               depthBuffer.setMask( material.depthWrite );
-               colorBuffer.setMask( material.colorWrite );
+               let directionalLength = 0;
+               let pointLength = 0;
+               let spotLength = 0;
+               let rectAreaLength = 0;
+               let hemiLength = 0;
 
-               const stencilWrite = material.stencilWrite;
-               stencilBuffer.setTest( stencilWrite );
-               if ( stencilWrite ) {
+               const viewMatrix = camera.matrixWorldInverse;
 
-                       stencilBuffer.setMask( material.stencilWriteMask );
-                       stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask );
-                       stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass );
+               for ( let i = 0, l = lights.length; i < l; i ++ ) {
 
-               }
+                       const light = lights[ i ];
 
-               setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+                       if ( light.isDirectionalLight ) {
 
-       }
+                               const uniforms = state.directional[ directionalLength ];
 
-       //
+                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+                               vector3.setFromMatrixPosition( light.target.matrixWorld );
+                               uniforms.direction.sub( vector3 );
+                               uniforms.direction.transformDirection( viewMatrix );
 
-       function setFlipSided( flipSided ) {
+                               directionalLength ++;
 
-               if ( currentFlipSided !== flipSided ) {
+                       } else if ( light.isSpotLight ) {
 
-                       if ( flipSided ) {
+                               const uniforms = state.spot[ spotLength ];
 
-                               gl.frontFace( 2304 );
+                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
+                               uniforms.position.applyMatrix4( viewMatrix );
 
-                       } else {
+                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+                               vector3.setFromMatrixPosition( light.target.matrixWorld );
+                               uniforms.direction.sub( vector3 );
+                               uniforms.direction.transformDirection( viewMatrix );
 
-                               gl.frontFace( 2305 );
+                               spotLength ++;
 
-                       }
+                       } else if ( light.isRectAreaLight ) {
 
-                       currentFlipSided = flipSided;
+                               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 setCullFace( cullFace ) {
+                               uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
+                               uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
 
-               if ( cullFace !== CullFaceNone ) {
+                               uniforms.halfWidth.applyMatrix4( matrix42 );
+                               uniforms.halfHeight.applyMatrix4( matrix42 );
 
-                       enable( 2884 );
+                               rectAreaLength ++;
 
-                       if ( cullFace !== currentCullFace ) {
+                       } else if ( light.isPointLight ) {
 
-                               if ( cullFace === CullFaceBack ) {
+                               const uniforms = state.point[ pointLength ];
 
-                                       gl.cullFace( 1029 );
+                               uniforms.position.setFromMatrixPosition( light.matrixWorld );
+                               uniforms.position.applyMatrix4( viewMatrix );
 
-                               } else if ( cullFace === CullFaceFront ) {
+                               pointLength ++;
 
-                                       gl.cullFace( 1028 );
+                       } else if ( light.isHemisphereLight ) {
 
-                               } else {
+                               const uniforms = state.hemi[ hemiLength ];
 
-                                       gl.cullFace( 1032 );
+                               uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+                               uniforms.direction.transformDirection( viewMatrix );
+                               uniforms.direction.normalize();
 
-                               }
+                               hemiLength ++;
 
                        }
 
-               } else {
-
-                       disable( 2884 );
-
                }
 
-               currentCullFace = cullFace;
-
        }
 
-       function setLineWidth( width ) {
+       return {
+               setup: setup,
+               setupView: setupView,
+               state: state
+       };
 
-               if ( width !== currentLineWidth ) {
+    }
 
-                       if ( lineWidthAvailable ) gl.lineWidth( width );
+    function WebGLRenderState( extensions, capabilities ) {
 
-                       currentLineWidth = width;
+       const lights = new WebGLLights( extensions, capabilities );
 
-               }
+       const lightsArray = [];
+       const shadowsArray = [];
 
-       }
+       function init() {
 
-       function setPolygonOffset( polygonOffset, factor, units ) {
+               lightsArray.length = 0;
+               shadowsArray.length = 0;
 
-               if ( polygonOffset ) {
+       }
 
-                       enable( 32823 );
+       function pushLight( light ) {
 
-                       if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) {
+               lightsArray.push( light );
 
-                               gl.polygonOffset( factor, units );
+       }
 
-                               currentPolygonOffsetFactor = factor;
-                               currentPolygonOffsetUnits = units;
+       function pushShadow( shadowLight ) {
 
-                       }
+               shadowsArray.push( shadowLight );
 
-               } else {
+       }
 
-                       disable( 32823 );
+       function setupLights( physicallyCorrectLights ) {
 
-               }
+               lights.setup( lightsArray, physicallyCorrectLights );
 
        }
 
-       function setScissorTest( scissorTest ) {
-
-               if ( scissorTest ) {
+       function setupLightsView( camera ) {
 
-                       enable( 3089 );
+               lights.setupView( lightsArray, camera );
 
-               } else {
+       }
 
-                       disable( 3089 );
+       const state = {
+               lightsArray: lightsArray,
+               shadowsArray: shadowsArray,
 
-               }
+               lights: lights
+       };
 
-       }
+       return {
+               init: init,
+               state: state,
+               setupLights: setupLights,
+               setupLightsView: setupLightsView,
 
-       // texture
+               pushLight: pushLight,
+               pushShadow: pushShadow
+       };
 
-       function activeTexture( webglSlot ) {
+    }
 
-               if ( webglSlot === undefined ) webglSlot = 33984 + maxTextures - 1;
+    function WebGLRenderStates( extensions, capabilities ) {
 
-               if ( currentTextureSlot !== webglSlot ) {
+       let renderStates = new WeakMap();
 
-                       gl.activeTexture( webglSlot );
-                       currentTextureSlot = webglSlot;
+       function get( scene, renderCallDepth = 0 ) {
 
-               }
+               let renderState;
 
-       }
+               if ( renderStates.has( scene ) === false ) {
 
-       function bindTexture( webglType, webglTexture ) {
+                       renderState = new WebGLRenderState( extensions, capabilities );
+                       renderStates.set( scene, [ renderState ] );
 
-               if ( currentTextureSlot === null ) {
+               } else {
 
-                       activeTexture();
+                       if ( renderCallDepth >= renderStates.get( scene ).length ) {
 
-               }
+                               renderState = new WebGLRenderState( extensions, capabilities );
+                               renderStates.get( scene ).push( renderState );
 
-               let boundTexture = currentBoundTextures[ currentTextureSlot ];
+                       } else {
 
-               if ( boundTexture === undefined ) {
+                               renderState = renderStates.get( scene )[ renderCallDepth ];
 
-                       boundTexture = { type: undefined, texture: undefined };
-                       currentBoundTextures[ currentTextureSlot ] = boundTexture;
+                       }
 
                }
 
-               if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {
+               return renderState;
 
-                       gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );
+       }
 
-                       boundTexture.type = webglType;
-                       boundTexture.texture = webglTexture;
+       function dispose() {
 
-               }
+               renderStates = new WeakMap();
 
        }
 
-       function unbindTexture() {
-
-               const boundTexture = currentBoundTextures[ currentTextureSlot ];
+       return {
+               get: get,
+               dispose: dispose
+       };
 
-               if ( boundTexture !== undefined && boundTexture.type !== undefined ) {
+    }
 
-                       gl.bindTexture( boundTexture.type, null );
+    /**
+     * 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>
+     * }
+     */
 
-                       boundTexture.type = undefined;
-                       boundTexture.texture = undefined;
+    class MeshDepthMaterial extends Material {
 
-               }
+       constructor( parameters ) {
 
-       }
+               super();
 
-       function compressedTexImage2D() {
+               this.type = 'MeshDepthMaterial';
 
-               try {
+               this.depthPacking = BasicDepthPacking;
 
-                       gl.compressedTexImage2D.apply( gl, arguments );
+               this.map = null;
 
-               } catch ( error ) {
+               this.alphaMap = null;
 
-                       console.error( 'THREE.WebGLState:', error );
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-               }
+               this.wireframe = false;
+               this.wireframeLinewidth = 1;
 
-       }
+               this.fog = false;
 
-       function texImage2D() {
+               this.setValues( parameters );
 
-               try {
+       }
 
-                       gl.texImage2D.apply( gl, arguments );
+       copy( source ) {
 
-               } catch ( error ) {
+               super.copy( source );
 
-                       console.error( 'THREE.WebGLState:', error );
+               this.depthPacking = source.depthPacking;
 
-               }
+               this.map = source.map;
 
-       }
+               this.alphaMap = source.alphaMap;
 
-       function texImage3D() {
+               this.displacementMap = source.displacementMap;
+               this.displacementScale = source.displacementScale;
+               this.displacementBias = source.displacementBias;
 
-               try {
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
 
-                       gl.texImage3D.apply( gl, arguments );
+               return this;
 
-               } catch ( error ) {
+       }
 
-                       console.error( 'THREE.WebGLState:', error );
+    }
 
-               }
+    MeshDepthMaterial.prototype.isMeshDepthMaterial = true;
 
-       }
+    /**
+     * 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>
+     *
+     * }
+     */
 
-       //
+    class MeshDistanceMaterial extends Material {
 
-       function scissor( scissor ) {
+       constructor( parameters ) {
 
-               if ( currentScissor.equals( scissor ) === false ) {
+               super();
 
-                       gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w );
-                       currentScissor.copy( scissor );
+               this.type = 'MeshDistanceMaterial';
 
-               }
+               this.referencePosition = new Vector3();
+               this.nearDistance = 1;
+               this.farDistance = 1000;
 
-       }
+               this.map = null;
 
-       function viewport( viewport ) {
+               this.alphaMap = null;
 
-               if ( currentViewport.equals( viewport ) === false ) {
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-                       gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w );
-                       currentViewport.copy( viewport );
+               this.fog = false;
 
-               }
+               this.setValues( parameters );
 
        }
 
-       //
+       copy( source ) {
 
-       function reset() {
+               super.copy( source );
 
-               enabledCapabilities = {};
+               this.referencePosition.copy( source.referencePosition );
+               this.nearDistance = source.nearDistance;
+               this.farDistance = source.farDistance;
 
-               currentTextureSlot = null;
-               currentBoundTextures = {};
+               this.map = source.map;
 
-               currentProgram = null;
+               this.alphaMap = source.alphaMap;
 
-               currentBlendingEnabled = null;
-               currentBlending = null;
-               currentBlendEquation = null;
-               currentBlendSrc = null;
-               currentBlendDst = null;
-               currentBlendEquationAlpha = null;
-               currentBlendSrcAlpha = null;
-               currentBlendDstAlpha = null;
-               currentPremultipledAlpha = false;
+               this.displacementMap = source.displacementMap;
+               this.displacementScale = source.displacementScale;
+               this.displacementBias = source.displacementBias;
 
-               currentFlipSided = null;
-               currentCullFace = null;
+               return this;
 
-               currentLineWidth = null;
+       }
 
-               currentPolygonOffsetFactor = null;
-               currentPolygonOffsetUnits = null;
+    }
 
-               colorBuffer.reset();
-               depthBuffer.reset();
-               stencilBuffer.reset();
+    MeshDistanceMaterial.prototype.isMeshDistanceMaterial = true;
 
-       }
+    const vertex = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}";
 
-       return {
+    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}";
 
-               buffers: {
-                       color: colorBuffer,
-                       depth: depthBuffer,
-                       stencil: stencilBuffer
-               },
+    function WebGLShadowMap( _renderer, _objects, _capabilities ) {
 
-               enable: enable,
-               disable: disable,
+       let _frustum = new Frustum();
 
-               useProgram: useProgram,
+       const _shadowMapSize = new Vector2(),
+               _viewportSize = new Vector2(),
 
-               setBlending: setBlending,
-               setMaterial: setMaterial,
+               _viewport = new Vector4(),
 
-               setFlipSided: setFlipSided,
-               setCullFace: setCullFace,
+               _depthMaterial = new MeshDepthMaterial( { depthPacking: RGBADepthPacking } ),
+               _distanceMaterial = new MeshDistanceMaterial(),
 
-               setLineWidth: setLineWidth,
-               setPolygonOffset: setPolygonOffset,
+               _materialCache = {},
 
-               setScissorTest: setScissorTest,
+               _maxTextureSize = _capabilities.maxTextureSize;
 
-               activeTexture: activeTexture,
-               bindTexture: bindTexture,
-               unbindTexture: unbindTexture,
-               compressedTexImage2D: compressedTexImage2D,
-               texImage2D: texImage2D,
-               texImage3D: texImage3D,
+       const shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide };
 
-               scissor: scissor,
-               viewport: viewport,
+       const shadowMaterialVertical = new ShaderMaterial( {
+               defines: {
+                       VSM_SAMPLES: 8
+               },
+               uniforms: {
+                       shadow_pass: { value: null },
+                       resolution: { value: new Vector2() },
+                       radius: { value: 4.0 }
+               },
 
-               reset: reset
+               vertexShader: vertex,
+               fragmentShader: fragment
 
-       };
+       } );
 
-    }
+       const shadowMaterialHorizontal = shadowMaterialVertical.clone();
+       shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1;
 
-    function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) {
+       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 isWebGL2 = capabilities.isWebGL2;
-       const maxTextures = capabilities.maxTextures;
-       const maxCubemapSize = capabilities.maxCubemapSize;
-       const maxTextureSize = capabilities.maxTextureSize;
-       const maxSamples = capabilities.maxSamples;
+       const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical );
 
-       const _videoTextures = new WeakMap();
-       let _canvas;
+       const scope = this;
 
-       // 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).
+       this.enabled = false;
 
-       let useOffscreenCanvas = false;
+       this.autoUpdate = true;
+       this.needsUpdate = false;
 
-       try {
+       this.type = PCFShadowMap;
 
-               useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined'
-                       && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null;
+       this.render = function ( lights, scene, camera ) {
 
-       } catch ( err ) {
+               if ( scope.enabled === false ) return;
+               if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;
 
-               // Ignore any errors
+               if ( lights.length === 0 ) return;
 
-       }
+               const currentRenderTarget = _renderer.getRenderTarget();
+               const activeCubeFace = _renderer.getActiveCubeFace();
+               const activeMipmapLevel = _renderer.getActiveMipmapLevel();
 
-       function createCanvas( width, height ) {
+               const _state = _renderer.state;
 
-               // Use OffscreenCanvas when available. Specially needed in web workers
+               // 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 );
 
-               return useOffscreenCanvas ?
-                       new OffscreenCanvas( width, height ) :
-                       document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+               // render depth map
 
-       }
+               for ( let i = 0, il = lights.length; i < il; i ++ ) {
 
-       function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) {
+                       const light = lights[ i ];
+                       const shadow = light.shadow;
 
-               let scale = 1;
+                       if ( shadow === undefined ) {
 
-               // handle case if texture exceeds max size
+                               console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' );
+                               continue;
 
-               if ( image.width > maxSize || image.height > maxSize ) {
+                       }
 
-                       scale = maxSize / Math.max( image.width, image.height );
+                       if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue;
 
-               }
+                       _shadowMapSize.copy( shadow.mapSize );
 
-               // only perform resize if necessary
+                       const shadowFrameExtents = shadow.getFrameExtents();
 
-               if ( scale < 1 || needsPowerOfTwo === true ) {
+                       _shadowMapSize.multiply( shadowFrameExtents );
 
-                       // only perform resize for certain image types
+                       _viewportSize.copy( shadow.mapSize );
 
-                       if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
-                               ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
-                               ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {
+                       if ( _shadowMapSize.x > _maxTextureSize || _shadowMapSize.y > _maxTextureSize ) {
 
-                               const floor = needsPowerOfTwo ? MathUtils.floorPowerOfTwo : Math.floor;
+                               if ( _shadowMapSize.x > _maxTextureSize ) {
 
-                               const width = floor( scale * image.width );
-                               const height = floor( scale * image.height );
+                                       _viewportSize.x = Math.floor( _maxTextureSize / shadowFrameExtents.x );
+                                       _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x;
+                                       shadow.mapSize.x = _viewportSize.x;
 
-                               if ( _canvas === undefined ) _canvas = createCanvas( width, height );
+                               }
 
-                               // cube textures can't reuse the same canvas
+                               if ( _shadowMapSize.y > _maxTextureSize ) {
 
-                               const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas;
+                                       _viewportSize.y = Math.floor( _maxTextureSize / shadowFrameExtents.y );
+                                       _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y;
+                                       shadow.mapSize.y = _viewportSize.y;
 
-                               canvas.width = width;
-                               canvas.height = height;
+                               }
 
-                               const context = canvas.getContext( '2d' );
-                               context.drawImage( image, 0, 0, width, height );
+                       }
 
-                               console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').' );
+                       if ( shadow.map === null && ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
 
-                               return canvas;
+                               const pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat };
 
-                       } else {
+                               shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+                               shadow.map.texture.name = light.name + '.shadowMap';
 
-                               if ( 'data' in image ) {
+                               shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
 
-                                       console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );
+                               shadow.camera.updateProjectionMatrix();
 
-                               }
+                       }
 
-                               return image;
+                       if ( shadow.map === null ) {
 
-                       }
+                               const pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat };
 
-               }
+                               shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+                               shadow.map.texture.name = light.name + '.shadowMap';
 
-               return image;
+                               shadow.camera.updateProjectionMatrix();
 
-       }
+                       }
 
-       function isPowerOfTwo( image ) {
+                       _renderer.setRenderTarget( shadow.map );
+                       _renderer.clear();
 
-               return MathUtils.isPowerOfTwo( image.width ) && MathUtils.isPowerOfTwo( image.height );
+                       const viewportCount = shadow.getViewportCount();
 
-       }
+                       for ( let vp = 0; vp < viewportCount; vp ++ ) {
 
-       function textureNeedsPowerOfTwo( texture ) {
+                               const viewport = shadow.getViewport( vp );
 
-               if ( isWebGL2 ) return false;
+                               _viewport.set(
+                                       _viewportSize.x * viewport.x,
+                                       _viewportSize.y * viewport.y,
+                                       _viewportSize.x * viewport.z,
+                                       _viewportSize.y * viewport.w
+                               );
 
-               return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) ||
-                       ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter );
+                               _state.viewport( _viewport );
 
-       }
+                               shadow.updateMatrices( light, vp );
 
-       function textureNeedsGenerateMipmaps( texture, supportsMips ) {
+                               _frustum = shadow.getFrustum();
 
-               return texture.generateMipmaps && supportsMips &&
-                       texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter;
+                               renderObject( scene, camera, shadow.camera, light, this.type );
 
-       }
+                       }
 
-       function generateMipmap( target, texture, width, height ) {
+                       // do blur pass for VSM
 
-               _gl.generateMipmap( target );
+                       if ( ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
 
-               const textureProperties = properties.get( texture );
+                               VSMPass( shadow, camera );
 
-               // 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;
+                       }
 
-       }
+                       shadow.needsUpdate = false;
 
-       function getInternalFormat( internalFormatName, glFormat, glType ) {
+               }
 
-               if ( isWebGL2 === false ) return glFormat;
+               scope.needsUpdate = false;
 
-               if ( internalFormatName !== null ) {
+               _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel );
 
-                       if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ];
+       };
 
-                       console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' );
+       function VSMPass( shadow, camera ) {
 
-               }
+               const geometry = _objects.update( fullScreenMesh );
 
-               let internalFormat = glFormat;
+               if ( shadowMaterialVertical.defines.VSM_SAMPLES !== shadow.blurSamples ) {
 
-               if ( glFormat === 6403 ) {
+                       shadowMaterialVertical.defines.VSM_SAMPLES = shadow.blurSamples;
+                       shadowMaterialHorizontal.defines.VSM_SAMPLES = shadow.blurSamples;
 
-                       if ( glType === 5126 ) internalFormat = 33326;
-                       if ( glType === 5131 ) internalFormat = 33325;
-                       if ( glType === 5121 ) internalFormat = 33321;
+                       shadowMaterialVertical.needsUpdate = true;
+                       shadowMaterialHorizontal.needsUpdate = true;
 
                }
 
-               if ( glFormat === 6407 ) {
+               // vertical pass
 
-                       if ( glType === 5126 ) internalFormat = 34837;
-                       if ( glType === 5131 ) internalFormat = 34843;
-                       if ( glType === 5121 ) internalFormat = 32849;
+               shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture;
+               shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize;
+               shadowMaterialVertical.uniforms.radius.value = shadow.radius;
+               _renderer.setRenderTarget( shadow.mapPass );
+               _renderer.clear();
+               _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null );
 
-               }
+               // horizontal pass
 
-               if ( glFormat === 6408 ) {
+               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 );
 
-                       if ( glType === 5126 ) internalFormat = 34836;
-                       if ( glType === 5131 ) internalFormat = 34842;
-                       if ( glType === 5121 ) internalFormat = 32856;
+       }
 
-               }
+       function getDepthMaterial( object, geometry, material, light, shadowCameraNear, shadowCameraFar, type ) {
 
-               if ( internalFormat === 33325 || internalFormat === 33326 ||
-                       internalFormat === 34842 || internalFormat === 34836 ) {
+               let result = null;
 
-                       extensions.get( 'EXT_color_buffer_float' );
+               const customMaterial = ( light.isPointLight === true ) ? object.customDistanceMaterial : object.customDepthMaterial;
 
-               }
+               if ( customMaterial !== undefined ) {
 
-               return internalFormat;
+                       result = customMaterial;
 
-       }
+               } else {
 
-       // Fallback filters for non-power-of-2 textures
+                       result = ( light.isPointLight === true ) ? _distanceMaterial : _depthMaterial;
 
-       function filterFallback( f ) {
+               }
 
-               if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) {
+               if ( ( _renderer.localClippingEnabled && material.clipShadows === true && material.clippingPlanes.length !== 0 ) ||
+                       ( material.displacementMap && material.displacementScale !== 0 ) ||
+                       ( material.alphaMap && material.alphaTest > 0 ) ) {
 
-                       return 9728;
+                       // in this case we need a unique material instance reflecting the
+                       // appropriate state
 
-               }
+                       const keyA = result.uuid, keyB = material.uuid;
 
-               return 9729;
+                       let materialsForVariant = _materialCache[ keyA ];
 
-       }
+                       if ( materialsForVariant === undefined ) {
 
-       //
+                               materialsForVariant = {};
+                               _materialCache[ keyA ] = materialsForVariant;
 
-       function onTextureDispose( event ) {
+                       }
 
-               const texture = event.target;
+                       let cachedMaterial = materialsForVariant[ keyB ];
 
-               texture.removeEventListener( 'dispose', onTextureDispose );
+                       if ( cachedMaterial === undefined ) {
 
-               deallocateTexture( texture );
+                               cachedMaterial = result.clone();
+                               materialsForVariant[ keyB ] = cachedMaterial;
 
-               if ( texture.isVideoTexture ) {
+                       }
 
-                       _videoTextures.delete( texture );
+                       result = cachedMaterial;
 
                }
 
-               info.memory.textures --;
+               result.visible = material.visible;
+               result.wireframe = material.wireframe;
 
-       }
+               if ( type === VSMShadowMap ) {
 
-       function onRenderTargetDispose( event ) {
+                       result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side;
 
-               const renderTarget = event.target;
+               } else {
 
-               renderTarget.removeEventListener( 'dispose', onRenderTargetDispose );
+                       result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ];
 
-               deallocateRenderTarget( renderTarget );
+               }
 
-               info.memory.textures --;
+               result.alphaMap = material.alphaMap;
+               result.alphaTest = material.alphaTest;
 
-       }
+               result.clipShadows = material.clipShadows;
+               result.clippingPlanes = material.clippingPlanes;
+               result.clipIntersection = material.clipIntersection;
 
-       //
+               result.displacementMap = material.displacementMap;
+               result.displacementScale = material.displacementScale;
+               result.displacementBias = material.displacementBias;
 
-       function deallocateTexture( texture ) {
+               result.wireframeLinewidth = material.wireframeLinewidth;
+               result.linewidth = material.linewidth;
 
-               const textureProperties = properties.get( texture );
+               if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) {
 
-               if ( textureProperties.__webglInit === undefined ) return;
+                       result.referencePosition.setFromMatrixPosition( light.matrixWorld );
+                       result.nearDistance = shadowCameraNear;
+                       result.farDistance = shadowCameraFar;
 
-               _gl.deleteTexture( textureProperties.__webglTexture );
+               }
 
-               properties.remove( texture );
+               return result;
 
        }
 
-       function deallocateRenderTarget( renderTarget ) {
+       function renderObject( object, camera, shadowCamera, light, type ) {
 
-               const renderTargetProperties = properties.get( renderTarget );
-               const textureProperties = properties.get( renderTarget.texture );
+               if ( object.visible === false ) return;
 
-               if ( ! renderTarget ) return;
+               const visible = object.layers.test( camera.layers );
 
-               if ( textureProperties.__webglTexture !== undefined ) {
+               if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) {
 
-                       _gl.deleteTexture( textureProperties.__webglTexture );
+                       if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) {
 
-               }
+                               object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
 
-               if ( renderTarget.depthTexture ) {
+                               const geometry = _objects.update( object );
+                               const material = object.material;
 
-                       renderTarget.depthTexture.dispose();
+                               if ( Array.isArray( material ) ) {
 
-               }
+                                       const groups = geometry.groups;
 
-               if ( renderTarget.isWebGLCubeRenderTarget ) {
+                                       for ( let k = 0, kl = groups.length; k < kl; k ++ ) {
 
-                       for ( let i = 0; i < 6; i ++ ) {
-
-                               _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] );
-                               if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] );
-
-                       }
+                                               const group = groups[ k ];
+                                               const groupMaterial = material[ group.materialIndex ];
 
-               } else {
+                                               if ( groupMaterial && groupMaterial.visible ) {
 
-                       _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 );
+                                                       const depthMaterial = getDepthMaterial( object, geometry, groupMaterial, light, shadowCamera.near, shadowCamera.far, type );
 
-               }
+                                                       _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group );
 
-               properties.remove( renderTarget.texture );
-               properties.remove( renderTarget );
+                                               }
 
-       }
+                                       }
 
-       //
+                               } else if ( material.visible ) {
 
-       let textureUnits = 0;
+                                       const depthMaterial = getDepthMaterial( object, geometry, material, light, shadowCamera.near, shadowCamera.far, type );
 
-       function resetTextureUnits() {
+                                       _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null );
 
-               textureUnits = 0;
+                               }
 
-       }
+                       }
 
-       function allocateTextureUnit() {
+               }
 
-               const textureUnit = textureUnits;
+               const children = object.children;
 
-               if ( textureUnit >= maxTextures ) {
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
 
-                       console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + maxTextures );
+                       renderObject( children[ i ], camera, shadowCamera, light, type );
 
                }
 
-               textureUnits += 1;
-
-               return textureUnit;
-
        }
 
-       //
+    }
 
-       function setTexture2D( texture, slot ) {
+    function WebGLState( gl, extensions, capabilities ) {
 
-               const textureProperties = properties.get( texture );
+       const isWebGL2 = capabilities.isWebGL2;
 
-               if ( texture.isVideoTexture ) updateVideoTexture( texture );
+       function ColorBuffer() {
 
-               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+               let locked = false;
 
-                       const image = texture.image;
+               const color = new Vector4();
+               let currentColorMask = null;
+               const currentColorClear = new Vector4( 0, 0, 0, 0 );
 
-                       if ( image === undefined ) {
+               return {
 
-                               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined' );
+                       setMask: function ( colorMask ) {
 
-                       } else if ( image.complete === false ) {
+                               if ( currentColorMask !== colorMask && ! locked ) {
 
-                               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' );
+                                       gl.colorMask( colorMask, colorMask, colorMask, colorMask );
+                                       currentColorMask = colorMask;
 
-                       } else {
+                               }
 
-                               uploadTexture( textureProperties, texture, slot );
-                               return;
+                       },
 
-                       }
+                       setLocked: function ( lock ) {
 
-               }
+                               locked = lock;
 
-               state.activeTexture( 33984 + slot );
-               state.bindTexture( 3553, textureProperties.__webglTexture );
+                       },
 
-       }
+                       setClear: function ( r, g, b, a, premultipliedAlpha ) {
 
-       function setTexture2DArray( texture, slot ) {
+                               if ( premultipliedAlpha === true ) {
 
-               const textureProperties = properties.get( texture );
+                                       r *= a; g *= a; b *= a;
 
-               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+                               }
 
-                       uploadTexture( textureProperties, texture, slot );
-                       return;
+                               color.set( r, g, b, a );
 
-               }
+                               if ( currentColorClear.equals( color ) === false ) {
 
-               state.activeTexture( 33984 + slot );
-               state.bindTexture( 35866, textureProperties.__webglTexture );
+                                       gl.clearColor( r, g, b, a );
+                                       currentColorClear.copy( color );
 
-       }
+                               }
 
-       function setTexture3D( texture, slot ) {
+                       },
 
-               const textureProperties = properties.get( texture );
+                       reset: function () {
 
-               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+                               locked = false;
 
-                       uploadTexture( textureProperties, texture, slot );
-                       return;
+                               currentColorMask = null;
+                               currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state
 
-               }
+                       }
 
-               state.activeTexture( 33984 + slot );
-               state.bindTexture( 32879, textureProperties.__webglTexture );
+               };
 
        }
 
-       function setTextureCube( texture, slot ) {
+       function DepthBuffer() {
 
-               const textureProperties = properties.get( texture );
+               let locked = false;
 
-               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+               let currentDepthMask = null;
+               let currentDepthFunc = null;
+               let currentDepthClear = null;
 
-                       uploadCubeTexture( textureProperties, texture, slot );
-                       return;
+               return {
 
-               }
+                       setTest: function ( depthTest ) {
 
-               state.activeTexture( 33984 + slot );
-               state.bindTexture( 34067, textureProperties.__webglTexture );
+                               if ( depthTest ) {
 
-       }
+                                       enable( 2929 );
 
-       const wrappingToGL = {
-               [ RepeatWrapping ]: 10497,
-               [ ClampToEdgeWrapping ]: 33071,
-               [ MirroredRepeatWrapping ]: 33648
-       };
+                               } else {
 
-       const filterToGL = {
-               [ NearestFilter ]: 9728,
-               [ NearestMipmapNearestFilter ]: 9984,
-               [ NearestMipmapLinearFilter ]: 9986,
+                                       disable( 2929 );
 
-               [ LinearFilter ]: 9729,
-               [ LinearMipmapNearestFilter ]: 9985,
-               [ LinearMipmapLinearFilter ]: 9987
-       };
+                               }
 
-       function setTextureParameters( textureType, texture, supportsMips ) {
+                       },
 
-               if ( supportsMips ) {
+                       setMask: function ( depthMask ) {
 
-                       _gl.texParameteri( textureType, 10242, wrappingToGL[ texture.wrapS ] );
-                       _gl.texParameteri( textureType, 10243, wrappingToGL[ texture.wrapT ] );
+                               if ( currentDepthMask !== depthMask && ! locked ) {
 
-                       if ( textureType === 32879 || textureType === 35866 ) {
+                                       gl.depthMask( depthMask );
+                                       currentDepthMask = depthMask;
 
-                               _gl.texParameteri( textureType, 32882, wrappingToGL[ texture.wrapR ] );
+                               }
 
-                       }
+                       },
 
-                       _gl.texParameteri( textureType, 10240, filterToGL[ texture.magFilter ] );
-                       _gl.texParameteri( textureType, 10241, filterToGL[ texture.minFilter ] );
+                       setFunc: function ( depthFunc ) {
 
-               } else {
+                               if ( currentDepthFunc !== depthFunc ) {
 
-                       _gl.texParameteri( textureType, 10242, 33071 );
-                       _gl.texParameteri( textureType, 10243, 33071 );
+                                       if ( depthFunc ) {
 
-                       if ( textureType === 32879 || textureType === 35866 ) {
+                                               switch ( depthFunc ) {
 
-                               _gl.texParameteri( textureType, 32882, 33071 );
+                                                       case NeverDepth:
 
-                       }
+                                                               gl.depthFunc( 512 );
+                                                               break;
 
-                       if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) {
+                                                       case AlwaysDepth:
 
-                               console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.' );
+                                                               gl.depthFunc( 519 );
+                                                               break;
 
-                       }
+                                                       case LessDepth:
 
-                       _gl.texParameteri( textureType, 10240, filterFallback( texture.magFilter ) );
-                       _gl.texParameteri( textureType, 10241, filterFallback( texture.minFilter ) );
+                                                               gl.depthFunc( 513 );
+                                                               break;
 
-                       if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) {
+                                                       case LessEqualDepth:
 
-                               console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.' );
+                                                               gl.depthFunc( 515 );
+                                                               break;
 
-                       }
+                                                       case EqualDepth:
 
-               }
+                                                               gl.depthFunc( 514 );
+                                                               break;
 
-               const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
+                                                       case GreaterEqualDepth:
 
-               if ( extension ) {
+                                                               gl.depthFunc( 518 );
+                                                               break;
 
-                       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;
+                                                       case GreaterDepth:
 
-                       if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) {
+                                                               gl.depthFunc( 516 );
+                                                               break;
 
-                               _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) );
-                               properties.get( texture ).__currentAnisotropy = texture.anisotropy;
+                                                       case NotEqualDepth:
 
-                       }
+                                                               gl.depthFunc( 517 );
+                                                               break;
 
-               }
+                                                       default:
 
-       }
+                                                               gl.depthFunc( 515 );
 
-       function initTexture( textureProperties, texture ) {
+                                               }
 
-               if ( textureProperties.__webglInit === undefined ) {
+                                       } else {
 
-                       textureProperties.__webglInit = true;
+                                               gl.depthFunc( 515 );
 
-                       texture.addEventListener( 'dispose', onTextureDispose );
+                                       }
 
-                       textureProperties.__webglTexture = _gl.createTexture();
+                                       currentDepthFunc = depthFunc;
 
-                       info.memory.textures ++;
+                               }
 
-               }
+                       },
 
-       }
+                       setLocked: function ( lock ) {
 
-       function uploadTexture( textureProperties, texture, slot ) {
+                               locked = lock;
 
-               let textureType = 3553;
+                       },
 
-               if ( texture.isDataTexture2DArray ) textureType = 35866;
-               if ( texture.isDataTexture3D ) textureType = 32879;
+                       setClear: function ( depth ) {
 
-               initTexture( textureProperties, texture );
+                               if ( currentDepthClear !== depth ) {
 
-               state.activeTexture( 33984 + slot );
-               state.bindTexture( textureType, textureProperties.__webglTexture );
+                                       gl.clearDepth( depth );
+                                       currentDepthClear = depth;
 
-               _gl.pixelStorei( 37440, texture.flipY );
-               _gl.pixelStorei( 37441, texture.premultiplyAlpha );
-               _gl.pixelStorei( 3317, texture.unpackAlignment );
+                               }
 
-               const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( texture.image ) === false;
-               const image = resizeImage( texture.image, needsPowerOfTwo, false, maxTextureSize );
+                       },
 
-               const supportsMips = isPowerOfTwo( image ) || isWebGL2,
-                       glFormat = utils.convert( texture.format );
+                       reset: function () {
 
-               let glType = utils.convert( texture.type ),
-                       glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType );
+                               locked = false;
 
-               setTextureParameters( textureType, texture, supportsMips );
+                               currentDepthMask = null;
+                               currentDepthFunc = null;
+                               currentDepthClear = null;
 
-               let mipmap;
-               const mipmaps = texture.mipmaps;
+                       }
 
-               if ( texture.isDepthTexture ) {
+               };
 
-                       // populate depth texture with dummy data
+       }
 
-                       glInternalFormat = 6402;
+       function StencilBuffer() {
 
-                       if ( isWebGL2 ) {
+               let locked = false;
 
-                               if ( texture.type === FloatType ) {
+               let currentStencilMask = null;
+               let currentStencilFunc = null;
+               let currentStencilRef = null;
+               let currentStencilFuncMask = null;
+               let currentStencilFail = null;
+               let currentStencilZFail = null;
+               let currentStencilZPass = null;
+               let currentStencilClear = null;
 
-                                       glInternalFormat = 36012;
+               return {
 
-                               } else if ( texture.type === UnsignedIntType ) {
+                       setTest: function ( stencilTest ) {
 
-                                       glInternalFormat = 33190;
+                               if ( ! locked ) {
 
-                               } else if ( texture.type === UnsignedInt248Type ) {
+                                       if ( stencilTest ) {
 
-                                       glInternalFormat = 35056;
+                                               enable( 2960 );
 
-                               } else {
+                                       } else {
 
-                                       glInternalFormat = 33189; // WebGL2 requires sized internalformat for glTexImage2D
+                                               disable( 2960 );
+
+                                       }
 
                                }
 
-                       } else {
+                       },
 
-                               if ( texture.type === FloatType ) {
+                       setMask: function ( stencilMask ) {
 
-                                       console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' );
+                               if ( currentStencilMask !== stencilMask && ! locked ) {
 
-                               }
+                                       gl.stencilMask( stencilMask );
+                                       currentStencilMask = stencilMask;
 
-                       }
+                               }
 
-                       // validation checks for WebGL 1
+                       },
 
-                       if ( texture.format === DepthFormat && glInternalFormat === 6402 ) {
+                       setFunc: function ( stencilFunc, stencilRef, stencilMask ) {
 
-                               // 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 ) {
+                               if ( currentStencilFunc !== stencilFunc ||
+                                    currentStencilRef !== stencilRef ||
+                                    currentStencilFuncMask !== stencilMask ) {
 
-                                       console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' );
+                                       gl.stencilFunc( stencilFunc, stencilRef, stencilMask );
 
-                                       texture.type = UnsignedShortType;
-                                       glType = utils.convert( texture.type );
+                                       currentStencilFunc = stencilFunc;
+                                       currentStencilRef = stencilRef;
+                                       currentStencilFuncMask = stencilMask;
 
                                }
 
-                       }
-
-                       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;
+                       setOp: function ( stencilFail, stencilZFail, stencilZPass ) {
 
-                               // 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 ) {
+                               if ( currentStencilFail !== stencilFail ||
+                                    currentStencilZFail !== stencilZFail ||
+                                    currentStencilZPass !== stencilZPass ) {
 
-                                       console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' );
+                                       gl.stencilOp( stencilFail, stencilZFail, stencilZPass );
 
-                                       texture.type = UnsignedInt248Type;
-                                       glType = utils.convert( texture.type );
+                                       currentStencilFail = stencilFail;
+                                       currentStencilZFail = stencilZFail;
+                                       currentStencilZPass = stencilZPass;
 
                                }
 
-                       }
-
-                       //
+                       },
 
-                       state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null );
+                       setLocked: function ( lock ) {
 
-               } else if ( texture.isDataTexture ) {
+                               locked = lock;
 
-                       // 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 ) {
+                       setClear: function ( stencil ) {
 
-                               for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+                               if ( currentStencilClear !== stencil ) {
 
-                                       mipmap = mipmaps[ i ];
-                                       state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+                                       gl.clearStencil( stencil );
+                                       currentStencilClear = stencil;
 
                                }
 
-                               texture.generateMipmaps = false;
-                               textureProperties.__maxMipLevel = mipmaps.length - 1;
+                       },
 
-                       } else {
+                       reset: function () {
 
-                               state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data );
-                               textureProperties.__maxMipLevel = 0;
+                               locked = false;
+
+                               currentStencilMask = null;
+                               currentStencilFunc = null;
+                               currentStencilRef = null;
+                               currentStencilFuncMask = null;
+                               currentStencilFail = null;
+                               currentStencilZFail = null;
+                               currentStencilZPass = null;
+                               currentStencilClear = null;
 
                        }
 
-               } else if ( texture.isCompressedTexture ) {
+               };
 
-                       for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+       }
 
-                               mipmap = mipmaps[ i ];
+       //
 
-                               if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {
+       const colorBuffer = new ColorBuffer();
+       const depthBuffer = new DepthBuffer();
+       const stencilBuffer = new StencilBuffer();
 
-                                       if ( glFormat !== null ) {
+       let enabledCapabilities = {};
 
-                                               state.compressedTexImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+       let xrFramebuffer = null;
+       let currentBoundFramebuffers = {};
 
-                                       } else {
+       let currentProgram = null;
 
-                                               console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' );
+       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;
 
-                                       }
+       let currentFlipSided = null;
+       let currentCullFace = null;
 
-                               } else {
+       let currentLineWidth = null;
 
-                                       state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+       let currentPolygonOffsetFactor = null;
+       let currentPolygonOffsetUnits = null;
 
-                               }
+       const maxTextures = gl.getParameter( 35661 );
 
-                       }
+       let lineWidthAvailable = false;
+       let version = 0;
+       const glVersion = gl.getParameter( 7938 );
 
-                       textureProperties.__maxMipLevel = mipmaps.length - 1;
+       if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) {
 
-               } else if ( texture.isDataTexture2DArray ) {
+               version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] );
+               lineWidthAvailable = ( version >= 1.0 );
 
-                       state.texImage3D( 35866, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
-                       textureProperties.__maxMipLevel = 0;
+       } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) {
 
-               } else if ( texture.isDataTexture3D ) {
+               version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] );
+               lineWidthAvailable = ( version >= 2.0 );
 
-                       state.texImage3D( 32879, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
-                       textureProperties.__maxMipLevel = 0;
+       }
 
-               } else {
+       let currentTextureSlot = null;
+       let currentBoundTextures = {};
 
-                       // regular Texture (image, video, canvas)
+       const scissorParam = gl.getParameter( 3088 );
+       const viewportParam = gl.getParameter( 2978 );
 
-                       // 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
+       const currentScissor = new Vector4().fromArray( scissorParam );
+       const currentViewport = new Vector4().fromArray( viewportParam );
 
-                       if ( mipmaps.length > 0 && supportsMips ) {
+       function createTexture( type, target, count ) {
 
-                               for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+               const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4.
+               const texture = gl.createTexture();
 
-                                       mipmap = mipmaps[ i ];
-                                       state.texImage2D( 3553, i, glInternalFormat, glFormat, glType, mipmap );
+               gl.bindTexture( type, texture );
+               gl.texParameteri( type, 10241, 9728 );
+               gl.texParameteri( type, 10240, 9728 );
 
-                               }
+               for ( let i = 0; i < count; i ++ ) {
 
-                               texture.generateMipmaps = false;
-                               textureProperties.__maxMipLevel = mipmaps.length - 1;
+                       gl.texImage2D( target + i, 0, 6408, 1, 1, 0, 6408, 5121, data );
 
-                       } else {
+               }
 
-                               state.texImage2D( 3553, 0, glInternalFormat, glFormat, glType, image );
-                               textureProperties.__maxMipLevel = 0;
+               return texture;
 
-                       }
+       }
 
-               }
+       const emptyTextures = {};
+       emptyTextures[ 3553 ] = createTexture( 3553, 3553, 1 );
+       emptyTextures[ 34067 ] = createTexture( 34067, 34069, 6 );
 
-               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+       // init
 
-                       generateMipmap( textureType, texture, image.width, image.height );
+       colorBuffer.setClear( 0, 0, 0, 1 );
+       depthBuffer.setClear( 1 );
+       stencilBuffer.setClear( 0 );
 
-               }
+       enable( 2929 );
+       depthBuffer.setFunc( LessEqualDepth );
 
-               textureProperties.__version = texture.version;
+       setFlipSided( false );
+       setCullFace( CullFaceBack );
+       enable( 2884 );
 
-               if ( texture.onUpdate ) texture.onUpdate( texture );
+       setBlending( NoBlending );
 
-       }
+       //
 
-       function uploadCubeTexture( textureProperties, texture, slot ) {
+       function enable( id ) {
 
-               if ( texture.image.length !== 6 ) return;
+               if ( enabledCapabilities[ id ] !== true ) {
 
-               initTexture( textureProperties, texture );
+                       gl.enable( id );
+                       enabledCapabilities[ id ] = true;
 
-               state.activeTexture( 33984 + slot );
-               state.bindTexture( 34067, textureProperties.__webglTexture );
+               }
 
-               _gl.pixelStorei( 37440, texture.flipY );
-               _gl.pixelStorei( 37441, texture.premultiplyAlpha );
-               _gl.pixelStorei( 3317, texture.unpackAlignment );
+       }
 
-               const isCompressed = ( texture && ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ) );
-               const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture );
+       function disable( id ) {
 
-               const cubeImage = [];
+               if ( enabledCapabilities[ id ] !== false ) {
 
-               for ( let i = 0; i < 6; i ++ ) {
+                       gl.disable( id );
+                       enabledCapabilities[ id ] = false;
 
-                       if ( ! isCompressed && ! isDataTexture ) {
+               }
 
-                               cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, maxCubemapSize );
+       }
 
-                       } else {
+       function bindXRFramebuffer( framebuffer ) {
 
-                               cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ];
+               if ( framebuffer !== xrFramebuffer ) {
 
-                       }
+                       gl.bindFramebuffer( 36160, framebuffer );
+
+                       xrFramebuffer = framebuffer;
 
                }
 
-               const image = cubeImage[ 0 ],
-                       supportsMips = isPowerOfTwo( image ) || isWebGL2,
-                       glFormat = utils.convert( texture.format ),
-                       glType = utils.convert( texture.type ),
-                       glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType );
-
-               setTextureParameters( 34067, texture, supportsMips );
-
-               let mipmaps;
-
-               if ( isCompressed ) {
-
-                       for ( let i = 0; i < 6; i ++ ) {
+       }
 
-                               mipmaps = cubeImage[ i ].mipmaps;
+       function bindFramebuffer( target, framebuffer ) {
 
-                               for ( let j = 0; j < mipmaps.length; j ++ ) {
+               if ( framebuffer === null && xrFramebuffer !== null ) framebuffer = xrFramebuffer; // use active XR framebuffer if available
 
-                                       const mipmap = mipmaps[ j ];
+               if ( currentBoundFramebuffers[ target ] !== framebuffer ) {
 
-                                       if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {
+                       gl.bindFramebuffer( target, framebuffer );
 
-                                               if ( glFormat !== null ) {
+                       currentBoundFramebuffers[ target ] = framebuffer;
 
-                                                       state.compressedTexImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+                       if ( isWebGL2 ) {
 
-                                               } else {
+                               // 36009 is equivalent to 36160
 
-                                                       console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' );
+                               if ( target === 36009 ) {
 
-                                               }
+                                       currentBoundFramebuffers[ 36160 ] = framebuffer;
 
-                                       } else {
+                               }
 
-                                               state.texImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+                               if ( target === 36160 ) {
 
-                                       }
+                                       currentBoundFramebuffers[ 36009 ] = framebuffer;
 
                                }
 
                        }
 
-                       textureProperties.__maxMipLevel = mipmaps.length - 1;
+                       return true;
 
-               } else {
+               }
 
-                       mipmaps = texture.mipmaps;
+               return false;
 
-                       for ( let i = 0; i < 6; i ++ ) {
+       }
 
-                               if ( isDataTexture ) {
+       function useProgram( program ) {
 
-                                       state.texImage2D( 34069 + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data );
+               if ( currentProgram !== program ) {
 
-                                       for ( let j = 0; j < mipmaps.length; j ++ ) {
+                       gl.useProgram( program );
 
-                                               const mipmap = mipmaps[ j ];
-                                               const mipmapImage = mipmap.image[ i ].image;
+                       currentProgram = program;
 
-                                               state.texImage2D( 34069 + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data );
+                       return true;
 
-                                       }
+               }
 
-                               } else {
+               return false;
 
-                                       state.texImage2D( 34069 + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] );
+       }
 
-                                       for ( let j = 0; j < mipmaps.length; j ++ ) {
+       const equationToGL = {
+               [ AddEquation ]: 32774,
+               [ SubtractEquation ]: 32778,
+               [ ReverseSubtractEquation ]: 32779
+       };
 
-                                               const mipmap = mipmaps[ j ];
+       if ( isWebGL2 ) {
 
-                                               state.texImage2D( 34069 + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] );
+               equationToGL[ MinEquation ] = 32775;
+               equationToGL[ MaxEquation ] = 32776;
 
-                                       }
+       } else {
 
-                               }
+               const extension = extensions.get( 'EXT_blend_minmax' );
 
-                       }
+               if ( extension !== null ) {
 
-                       textureProperties.__maxMipLevel = mipmaps.length;
+                       equationToGL[ MinEquation ] = extension.MIN_EXT;
+                       equationToGL[ MaxEquation ] = extension.MAX_EXT;
 
                }
 
-               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+       }
 
-                       // We assume images for cube map have the same size.
-                       generateMipmap( 34067, texture, image.width, image.height );
+       const factorToGL = {
+               [ ZeroFactor ]: 0,
+               [ OneFactor ]: 1,
+               [ SrcColorFactor ]: 768,
+               [ SrcAlphaFactor ]: 770,
+               [ SrcAlphaSaturateFactor ]: 776,
+               [ DstColorFactor ]: 774,
+               [ DstAlphaFactor ]: 772,
+               [ OneMinusSrcColorFactor ]: 769,
+               [ OneMinusSrcAlphaFactor ]: 771,
+               [ OneMinusDstColorFactor ]: 775,
+               [ OneMinusDstAlphaFactor ]: 773
+       };
 
-               }
+       function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {
 
-               textureProperties.__version = texture.version;
+               if ( blending === NoBlending ) {
 
-               if ( texture.onUpdate ) texture.onUpdate( texture );
+                       if ( currentBlendingEnabled === true ) {
 
-       }
+                               disable( 3042 );
+                               currentBlendingEnabled = false;
 
-       // Render targets
+                       }
 
-       // Setup storage for target texture and bind it to correct framebuffer
-       function setupFrameBufferTexture( framebuffer, renderTarget, attachment, textureTarget ) {
+                       return;
 
-               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 );
+               }
 
-       }
+               if ( currentBlendingEnabled === false ) {
 
-       // Setup storage for internal depth/stencil buffers and bind to correct framebuffer
-       function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) {
+                       enable( 3042 );
+                       currentBlendingEnabled = true;
 
-               _gl.bindRenderbuffer( 36161, renderbuffer );
+               }
 
-               if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
+               if ( blending !== CustomBlending ) {
 
-                       let glInternalFormat = 33189;
+                       if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) {
 
-                       if ( isMultisample ) {
+                               if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) {
 
-                               const depthTexture = renderTarget.depthTexture;
+                                       gl.blendEquation( 32774 );
 
-                               if ( depthTexture && depthTexture.isDepthTexture ) {
+                                       currentBlendEquation = AddEquation;
+                                       currentBlendEquationAlpha = AddEquation;
 
-                                       if ( depthTexture.type === FloatType ) {
+                               }
 
-                                               glInternalFormat = 36012;
+                               if ( premultipliedAlpha ) {
 
-                                       } else if ( depthTexture.type === UnsignedIntType ) {
+                                       switch ( blending ) {
 
-                                               glInternalFormat = 33190;
+                                               case NormalBlending:
+                                                       gl.blendFuncSeparate( 1, 771, 1, 771 );
+                                                       break;
 
-                                       }
+                                               case AdditiveBlending:
+                                                       gl.blendFunc( 1, 1 );
+                                                       break;
 
-                               }
+                                               case SubtractiveBlending:
+                                                       gl.blendFuncSeparate( 0, 0, 769, 771 );
+                                                       break;
 
-                               const samples = getRenderTargetSamples( renderTarget );
+                                               case MultiplyBlending:
+                                                       gl.blendFuncSeparate( 0, 768, 0, 770 );
+                                                       break;
 
-                               _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+                                               default:
+                                                       console.error( 'THREE.WebGLState: Invalid blending: ', blending );
+                                                       break;
 
-                       } else {
+                                       }
 
-                               _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
+                               } else {
 
-                       }
+                                       switch ( blending ) {
 
-                       _gl.framebufferRenderbuffer( 36160, 36096, 36161, renderbuffer );
+                                               case NormalBlending:
+                                                       gl.blendFuncSeparate( 770, 771, 1, 771 );
+                                                       break;
 
-               } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+                                               case AdditiveBlending:
+                                                       gl.blendFunc( 770, 1 );
+                                                       break;
 
-                       if ( isMultisample ) {
+                                               case SubtractiveBlending:
+                                                       gl.blendFunc( 0, 769 );
+                                                       break;
 
-                               const samples = getRenderTargetSamples( renderTarget );
+                                               case MultiplyBlending:
+                                                       gl.blendFunc( 0, 768 );
+                                                       break;
 
-                               _gl.renderbufferStorageMultisample( 36161, samples, 35056, renderTarget.width, renderTarget.height );
+                                               default:
+                                                       console.error( 'THREE.WebGLState: Invalid blending: ', blending );
+                                                       break;
 
-                       } else {
+                                       }
 
-                               _gl.renderbufferStorage( 36161, 34041, renderTarget.width, renderTarget.height );
+                               }
+
+                               currentBlendSrc = null;
+                               currentBlendDst = null;
+                               currentBlendSrcAlpha = null;
+                               currentBlendDstAlpha = null;
+
+                               currentBlending = blending;
+                               currentPremultipledAlpha = premultipliedAlpha;
 
                        }
 
+                       return;
 
-                       _gl.framebufferRenderbuffer( 36160, 33306, 36161, renderbuffer );
+               }
 
-               } else {
+               // custom blending
 
-                       const glFormat = utils.convert( renderTarget.texture.format );
-                       const glType = utils.convert( renderTarget.texture.type );
-                       const glInternalFormat = getInternalFormat( renderTarget.texture.internalFormat, glFormat, glType );
+               blendEquationAlpha = blendEquationAlpha || blendEquation;
+               blendSrcAlpha = blendSrcAlpha || blendSrc;
+               blendDstAlpha = blendDstAlpha || blendDst;
 
-                       if ( isMultisample ) {
+               if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) {
 
-                               const samples = getRenderTargetSamples( renderTarget );
+                       gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] );
 
-                               _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+                       currentBlendEquation = blendEquation;
+                       currentBlendEquationAlpha = blendEquationAlpha;
 
-                       } else {
+               }
 
-                               _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
+               if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) {
 
-                       }
+                       gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] );
+
+                       currentBlendSrc = blendSrc;
+                       currentBlendDst = blendDst;
+                       currentBlendSrcAlpha = blendSrcAlpha;
+                       currentBlendDstAlpha = blendDstAlpha;
 
                }
 
-               _gl.bindRenderbuffer( 36161, null );
+               currentBlending = blending;
+               currentPremultipledAlpha = null;
 
        }
 
-       // Setup resources for a Depth Texture for a FBO (needs an extension)
-       function setupDepthTexture( framebuffer, renderTarget ) {
+       function setMaterial( material, frontFaceCW ) {
 
-               const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget );
-               if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' );
+               material.side === DoubleSide
+                       ? disable( 2884 )
+                       : enable( 2884 );
 
-               _gl.bindFramebuffer( 36160, framebuffer );
+               let flipSided = ( material.side === BackSide );
+               if ( frontFaceCW ) flipSided = ! flipSided;
 
-               if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) {
+               setFlipSided( flipSided );
 
-                       throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' );
+               ( material.blending === NormalBlending && material.transparent === false )
+                       ? setBlending( NoBlending )
+                       : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha );
 
-               }
+               depthBuffer.setFunc( material.depthFunc );
+               depthBuffer.setTest( material.depthTest );
+               depthBuffer.setMask( material.depthWrite );
+               colorBuffer.setMask( material.colorWrite );
 
-               // 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 ) {
+               const stencilWrite = material.stencilWrite;
+               stencilBuffer.setTest( stencilWrite );
+               if ( stencilWrite ) {
 
-                       renderTarget.depthTexture.image.width = renderTarget.width;
-                       renderTarget.depthTexture.image.height = renderTarget.height;
-                       renderTarget.depthTexture.needsUpdate = true;
+                       stencilBuffer.setMask( material.stencilWriteMask );
+                       stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask );
+                       stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass );
 
                }
 
-               setTexture2D( renderTarget.depthTexture, 0 );
+               setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
 
-               const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture;
+               material.alphaToCoverage === true
+                       ? enable( 32926 )
+                       : disable( 32926 );
 
-               if ( renderTarget.depthTexture.format === DepthFormat ) {
+       }
 
-                       _gl.framebufferTexture2D( 36160, 36096, 3553, webglDepthTexture, 0 );
+       //
 
-               } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) {
+       function setFlipSided( flipSided ) {
 
-                       _gl.framebufferTexture2D( 36160, 33306, 3553, webglDepthTexture, 0 );
+               if ( currentFlipSided !== flipSided ) {
 
-               } else {
+                       if ( flipSided ) {
 
-                       throw new Error( 'Unknown depthTexture format' );
+                               gl.frontFace( 2304 );
+
+                       } else {
+
+                               gl.frontFace( 2305 );
+
+                       }
+
+                       currentFlipSided = flipSided;
 
                }
 
        }
 
-       // Setup GL resources for a non-texture depth buffer
-       function setupDepthRenderbuffer( renderTarget ) {
-
-               const renderTargetProperties = properties.get( renderTarget );
+       function setCullFace( cullFace ) {
 
-               const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
+               if ( cullFace !== CullFaceNone ) {
 
-               if ( renderTarget.depthTexture ) {
+                       enable( 2884 );
 
-                       if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' );
+                       if ( cullFace !== currentCullFace ) {
 
-                       setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget );
+                               if ( cullFace === CullFaceBack ) {
 
-               } else {
+                                       gl.cullFace( 1029 );
 
-                       if ( isCube ) {
+                               } else if ( cullFace === CullFaceFront ) {
 
-                               renderTargetProperties.__webglDepthbuffer = [];
+                                       gl.cullFace( 1028 );
 
-                               for ( let i = 0; i < 6; i ++ ) {
+                               } else {
 
-                                       _gl.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer[ i ] );
-                                       renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer();
-                                       setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false );
+                                       gl.cullFace( 1032 );
 
                                }
 
-                       } else {
+                       }
 
-                               _gl.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer );
-                               renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer();
-                               setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false );
+               } else {
 
-                       }
+                       disable( 2884 );
 
                }
 
-               _gl.bindFramebuffer( 36160, null );
+               currentCullFace = cullFace;
 
        }
 
-       // Set up GL resources for the render target
-       function setupRenderTarget( renderTarget ) {
-
-               const renderTargetProperties = properties.get( renderTarget );
-               const textureProperties = properties.get( renderTarget.texture );
-
-               renderTarget.addEventListener( 'dispose', onRenderTargetDispose );
-
-               textureProperties.__webglTexture = _gl.createTexture();
-
-               info.memory.textures ++;
+       function setLineWidth( width ) {
 
-               const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
-               const isMultisample = ( renderTarget.isWebGLMultisampleRenderTarget === true );
-               const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2;
+               if ( width !== currentLineWidth ) {
 
-               // Handles WebGL2 RGBFormat fallback - #18858
+                       if ( lineWidthAvailable ) gl.lineWidth( width );
 
-               if ( isWebGL2 && renderTarget.texture.format === RGBFormat && ( renderTarget.texture.type === FloatType || renderTarget.texture.type === HalfFloatType ) ) {
+                       currentLineWidth = width;
 
-                       renderTarget.texture.format = RGBAFormat;
+               }
 
-                       console.warn( 'THREE.WebGLRenderer: Rendering to textures with RGB format is not supported. Using RGBA format instead.' );
+       }
 
-               }
+       function setPolygonOffset( polygonOffset, factor, units ) {
 
-               // Setup framebuffer
+               if ( polygonOffset ) {
 
-               if ( isCube ) {
+                       enable( 32823 );
 
-                       renderTargetProperties.__webglFramebuffer = [];
+                       if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) {
 
-                       for ( let i = 0; i < 6; i ++ ) {
+                               gl.polygonOffset( factor, units );
 
-                               renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer();
+                               currentPolygonOffsetFactor = factor;
+                               currentPolygonOffsetUnits = units;
 
                        }
 
                } else {
 
-                       renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();
-
-                       if ( isMultisample ) {
+                       disable( 32823 );
 
-                               if ( isWebGL2 ) {
+               }
 
-                                       renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer();
-                                       renderTargetProperties.__webglColorRenderbuffer = _gl.createRenderbuffer();
+       }
 
-                                       _gl.bindRenderbuffer( 36161, renderTargetProperties.__webglColorRenderbuffer );
+       function setScissorTest( scissorTest ) {
 
-                                       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 );
+               if ( scissorTest ) {
 
-                                       _gl.bindFramebuffer( 36160, renderTargetProperties.__webglMultisampledFramebuffer );
-                                       _gl.framebufferRenderbuffer( 36160, 36064, 36161, renderTargetProperties.__webglColorRenderbuffer );
-                                       _gl.bindRenderbuffer( 36161, null );
+                       enable( 3089 );
 
-                                       if ( renderTarget.depthBuffer ) {
+               } else {
 
-                                               renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer();
-                                               setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true );
+                       disable( 3089 );
 
-                                       }
+               }
 
-                                       _gl.bindFramebuffer( 36160, null );
+       }
 
+       // texture
 
-                               } else {
+       function activeTexture( webglSlot ) {
 
-                                       console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+               if ( webglSlot === undefined ) webglSlot = 33984 + maxTextures - 1;
 
-                               }
+               if ( currentTextureSlot !== webglSlot ) {
 
-                       }
+                       gl.activeTexture( webglSlot );
+                       currentTextureSlot = webglSlot;
 
                }
 
-               // Setup color buffer
-
-               if ( isCube ) {
+       }
 
-                       state.bindTexture( 34067, textureProperties.__webglTexture );
-                       setTextureParameters( 34067, renderTarget.texture, supportsMips );
+       function bindTexture( webglType, webglTexture ) {
 
-                       for ( let i = 0; i < 6; i ++ ) {
+               if ( currentTextureSlot === null ) {
 
-                               setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, 36064, 34069 + i );
+                       activeTexture();
 
-                       }
+               }
 
-                       if ( textureNeedsGenerateMipmaps( renderTarget.texture, supportsMips ) ) {
+               let boundTexture = currentBoundTextures[ currentTextureSlot ];
 
-                               generateMipmap( 34067, renderTarget.texture, renderTarget.width, renderTarget.height );
+               if ( boundTexture === undefined ) {
 
-                       }
+                       boundTexture = { type: undefined, texture: undefined };
+                       currentBoundTextures[ currentTextureSlot ] = boundTexture;
 
-                       state.bindTexture( 34067, null );
+               }
 
-               } else {
+               if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {
 
-                       state.bindTexture( 3553, textureProperties.__webglTexture );
-                       setTextureParameters( 3553, renderTarget.texture, supportsMips );
-                       setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, 36064, 3553 );
+                       gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );
 
-                       if ( textureNeedsGenerateMipmaps( renderTarget.texture, supportsMips ) ) {
+                       boundTexture.type = webglType;
+                       boundTexture.texture = webglTexture;
 
-                               generateMipmap( 3553, renderTarget.texture, renderTarget.width, renderTarget.height );
+               }
 
-                       }
+       }
 
-                       state.bindTexture( 3553, null );
+       function unbindTexture() {
 
-               }
+               const boundTexture = currentBoundTextures[ currentTextureSlot ];
 
-               // Setup depth and stencil buffers
+               if ( boundTexture !== undefined && boundTexture.type !== undefined ) {
 
-               if ( renderTarget.depthBuffer ) {
+                       gl.bindTexture( boundTexture.type, null );
 
-                       setupDepthRenderbuffer( renderTarget );
+                       boundTexture.type = undefined;
+                       boundTexture.texture = undefined;
 
                }
 
        }
 
-       function updateRenderTargetMipmap( renderTarget ) {
+       function compressedTexImage2D() {
 
-               const texture = renderTarget.texture;
-               const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2;
+               try {
 
-               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+                       gl.compressedTexImage2D.apply( gl, arguments );
 
-                       const target = renderTarget.isWebGLCubeRenderTarget ? 34067 : 3553;
-                       const webglTexture = properties.get( texture ).__webglTexture;
+               } catch ( error ) {
 
-                       state.bindTexture( target, webglTexture );
-                       generateMipmap( target, texture, renderTarget.width, renderTarget.height );
-                       state.bindTexture( target, null );
+                       console.error( 'THREE.WebGLState:', error );
 
                }
 
        }
 
-       function updateMultisampleRenderTarget( renderTarget ) {
+       function texImage2D() {
 
-               if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+               try {
 
-                       if ( isWebGL2 ) {
+                       gl.texImage2D.apply( gl, arguments );
 
-                               const renderTargetProperties = properties.get( renderTarget );
+               } catch ( error ) {
 
-                               _gl.bindFramebuffer( 36008, renderTargetProperties.__webglMultisampledFramebuffer );
-                               _gl.bindFramebuffer( 36009, renderTargetProperties.__webglFramebuffer );
+                       console.error( 'THREE.WebGLState:', error );
 
-                               const width = renderTarget.width;
-                               const height = renderTarget.height;
-                               let mask = 16384;
+               }
 
-                               if ( renderTarget.depthBuffer ) mask |= 256;
-                               if ( renderTarget.stencilBuffer ) mask |= 1024;
+       }
 
-                               _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, 9728 );
+       function texImage3D() {
 
-                               _gl.bindFramebuffer( 36160, renderTargetProperties.__webglMultisampledFramebuffer ); // see #18905
+               try {
 
-                       } else {
+                       gl.texImage3D.apply( gl, arguments );
 
-                               console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+               } catch ( error ) {
 
-                       }
+                       console.error( 'THREE.WebGLState:', error );
 
                }
 
        }
 
-       function getRenderTargetSamples( renderTarget ) {
+       //
 
-               return ( isWebGL2 && renderTarget.isWebGLMultisampleRenderTarget ) ?
-                       Math.min( maxSamples, renderTarget.samples ) : 0;
+       function scissor( scissor ) {
 
-       }
+               if ( currentScissor.equals( scissor ) === false ) {
 
-       function updateVideoTexture( texture ) {
+                       gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w );
+                       currentScissor.copy( scissor );
 
-               const frame = info.render.frame;
+               }
 
-               // Check the last frame we updated the VideoTexture
+       }
 
-               if ( _videoTextures.get( texture ) !== frame ) {
+       function viewport( viewport ) {
 
-                       _videoTextures.set( texture, frame );
-                       texture.update();
+               if ( currentViewport.equals( viewport ) === false ) {
+
+                       gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w );
+                       currentViewport.copy( viewport );
 
                }
 
        }
 
-       // backwards compatibility
+       //
 
-       let warnedTexture2D = false;
-       let warnedTextureCube = false;
+       function reset() {
 
-       function safeSetTexture2D( texture, slot ) {
+               // reset state
 
-               if ( texture && texture.isWebGLRenderTarget ) {
+               gl.disable( 3042 );
+               gl.disable( 2884 );
+               gl.disable( 2929 );
+               gl.disable( 32823 );
+               gl.disable( 3089 );
+               gl.disable( 2960 );
+               gl.disable( 32926 );
 
-                       if ( warnedTexture2D === false ) {
+               gl.blendEquation( 32774 );
+               gl.blendFunc( 1, 0 );
+               gl.blendFuncSeparate( 1, 0, 1, 0 );
 
-                               console.warn( 'THREE.WebGLTextures.safeSetTexture2D: don\'t use render targets as textures. Use their .texture property instead.' );
-                               warnedTexture2D = true;
+               gl.colorMask( true, true, true, true );
+               gl.clearColor( 0, 0, 0, 0 );
 
-                       }
+               gl.depthMask( true );
+               gl.depthFunc( 513 );
+               gl.clearDepth( 1 );
 
-                       texture = texture.texture;
+               gl.stencilMask( 0xffffffff );
+               gl.stencilFunc( 519, 0, 0xffffffff );
+               gl.stencilOp( 7680, 7680, 7680 );
+               gl.clearStencil( 0 );
 
-               }
+               gl.cullFace( 1029 );
+               gl.frontFace( 2305 );
 
-               setTexture2D( texture, slot );
+               gl.polygonOffset( 0, 0 );
 
-       }
+               gl.activeTexture( 33984 );
 
-       function safeSetTextureCube( texture, slot ) {
+               gl.bindFramebuffer( 36160, null );
 
-               if ( texture && texture.isWebGLCubeRenderTarget ) {
+               if ( isWebGL2 === true ) {
 
-                       if ( warnedTextureCube === false ) {
+                       gl.bindFramebuffer( 36009, null );
+                       gl.bindFramebuffer( 36008, null );
 
-                               console.warn( 'THREE.WebGLTextures.safeSetTextureCube: don\'t use cube render targets as textures. Use their .texture property instead.' );
-                               warnedTextureCube = true;
+               }
 
-                       }
+               gl.useProgram( null );
 
-                       texture = texture.texture;
+               gl.lineWidth( 1 );
 
-               }
+               gl.scissor( 0, 0, gl.canvas.width, gl.canvas.height );
+               gl.viewport( 0, 0, gl.canvas.width, gl.canvas.height );
 
+               // reset internals
 
-               setTextureCube( texture, slot );
+               enabledCapabilities = {};
 
-       }
+               currentTextureSlot = null;
+               currentBoundTextures = {};
 
-       //
+               xrFramebuffer = null;
+               currentBoundFramebuffers = {};
 
-       this.allocateTextureUnit = allocateTextureUnit;
-       this.resetTextureUnits = resetTextureUnits;
+               currentProgram = null;
 
-       this.setTexture2D = setTexture2D;
-       this.setTexture2DArray = setTexture2DArray;
-       this.setTexture3D = setTexture3D;
-       this.setTextureCube = setTextureCube;
-       this.setupRenderTarget = setupRenderTarget;
-       this.updateRenderTargetMipmap = updateRenderTargetMipmap;
-       this.updateMultisampleRenderTarget = updateMultisampleRenderTarget;
+               currentBlendingEnabled = false;
+               currentBlending = null;
+               currentBlendEquation = null;
+               currentBlendSrc = null;
+               currentBlendDst = null;
+               currentBlendEquationAlpha = null;
+               currentBlendSrcAlpha = null;
+               currentBlendDstAlpha = null;
+               currentPremultipledAlpha = false;
 
-       this.safeSetTexture2D = safeSetTexture2D;
-       this.safeSetTextureCube = safeSetTextureCube;
+               currentFlipSided = null;
+               currentCullFace = null;
 
-    }
+               currentLineWidth = null;
 
-    function WebGLUtils( gl, extensions, capabilities ) {
+               currentPolygonOffsetFactor = null;
+               currentPolygonOffsetUnits = null;
 
-       const isWebGL2 = capabilities.isWebGL2;
+               currentScissor.set( 0, 0, gl.canvas.width, gl.canvas.height );
+               currentViewport.set( 0, 0, gl.canvas.width, gl.canvas.height );
 
-       function convert( p ) {
+               colorBuffer.reset();
+               depthBuffer.reset();
+               stencilBuffer.reset();
 
-               let extension;
+       }
 
-               if ( p === UnsignedByteType ) return 5121;
-               if ( p === UnsignedShort4444Type ) return 32819;
-               if ( p === UnsignedShort5551Type ) return 32820;
-               if ( p === UnsignedShort565Type ) return 33635;
+       return {
 
-               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;
+               buffers: {
+                       color: colorBuffer,
+                       depth: depthBuffer,
+                       stencil: stencilBuffer
+               },
 
-               if ( p === HalfFloatType ) {
+               enable: enable,
+               disable: disable,
 
-                       if ( isWebGL2 ) return 5131;
+               bindFramebuffer: bindFramebuffer,
+               bindXRFramebuffer: bindXRFramebuffer,
 
-                       extension = extensions.get( 'OES_texture_half_float' );
+               useProgram: useProgram,
 
-                       if ( extension !== null ) {
+               setBlending: setBlending,
+               setMaterial: setMaterial,
 
-                               return extension.HALF_FLOAT_OES;
+               setFlipSided: setFlipSided,
+               setCullFace: setCullFace,
 
-                       } else {
+               setLineWidth: setLineWidth,
+               setPolygonOffset: setPolygonOffset,
 
-                               return null;
+               setScissorTest: setScissorTest,
 
-                       }
+               activeTexture: activeTexture,
+               bindTexture: bindTexture,
+               unbindTexture: unbindTexture,
+               compressedTexImage2D: compressedTexImage2D,
+               texImage2D: texImage2D,
+               texImage3D: texImage3D,
 
-               }
+               scissor: scissor,
+               viewport: viewport,
 
-               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;
+               reset: reset
 
-               // 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 ) {
+    function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) {
 
-                       extension = extensions.get( 'WEBGL_compressed_texture_s3tc' );
+       const isWebGL2 = capabilities.isWebGL2;
+       const maxTextures = capabilities.maxTextures;
+       const maxCubemapSize = capabilities.maxCubemapSize;
+       const maxTextureSize = capabilities.maxTextureSize;
+       const maxSamples = capabilities.maxSamples;
 
-                       if ( extension !== null ) {
+       const _videoTextures = new WeakMap();
+       let _canvas;
 
-                               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;
+       // 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).
 
-                       } else {
+       let useOffscreenCanvas = false;
 
-                               return null;
+       try {
 
-                       }
+               useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined'
+                       && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null;
 
-               }
+       } catch ( err ) {
 
-               if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format ||
-                       p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {
+               // Ignore any errors
 
-                       extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' );
+       }
 
-                       if ( extension !== null ) {
+       function createCanvas( width, height ) {
 
-                               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;
+               // Use OffscreenCanvas when available. Specially needed in web workers
 
-                       } else {
+               return useOffscreenCanvas ?
+                       new OffscreenCanvas( width, height ) : createElementNS( 'canvas' );
 
-                               return null;
+       }
 
-                       }
+       function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) {
 
-               }
+               let scale = 1;
 
-               if ( p === RGB_ETC1_Format ) {
+               // handle case if texture exceeds max size
 
-                       extension = extensions.get( 'WEBGL_compressed_texture_etc1' );
+               if ( image.width > maxSize || image.height > maxSize ) {
 
-                       if ( extension !== null ) {
+                       scale = maxSize / Math.max( image.width, image.height );
 
-                               return extension.COMPRESSED_RGB_ETC1_WEBGL;
+               }
 
-                       } else {
+               // only perform resize if necessary
 
-                               return null;
+               if ( scale < 1 || needsPowerOfTwo === true ) {
 
-                       }
+                       // only perform resize for certain image types
 
-               }
+                       if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
+                               ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
+                               ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {
 
-               if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) {
+                               const floor = needsPowerOfTwo ? floorPowerOfTwo : Math.floor;
 
-                       extension = extensions.get( 'WEBGL_compressed_texture_etc' );
+                               const width = floor( scale * image.width );
+                               const height = floor( scale * image.height );
 
-                       if ( extension !== null ) {
+                               if ( _canvas === undefined ) _canvas = createCanvas( width, height );
 
-                               if ( p === RGB_ETC2_Format ) return extension.COMPRESSED_RGB8_ETC2;
-                               if ( p === RGBA_ETC2_EAC_Format ) return extension.COMPRESSED_RGBA8_ETC2_EAC;
+                               // cube textures can't reuse the same canvas
 
-                       }
+                               const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas;
 
-               }
+                               canvas.width = width;
+                               canvas.height = height;
 
-               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 ) {
+                               const context = canvas.getContext( '2d' );
+                               context.drawImage( image, 0, 0, width, height );
 
-                       extension = extensions.get( 'WEBGL_compressed_texture_astc' );
+                               console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').' );
 
-                       if ( extension !== null ) {
+                               return canvas;
 
-                               // TODO Complete?
+                       } else {
 
-                               return p;
+                               if ( 'data' in image ) {
 
-                       } else {
+                                       console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );
 
-                               return null;
+                               }
+
+                               return image;
 
                        }
 
                }
 
-               if ( p === RGBA_BPTC_Format ) {
-
-                       extension = extensions.get( 'EXT_texture_compression_bptc' );
-
-                       if ( extension !== null ) {
+               return image;
 
-                               // TODO Complete?
+       }
 
-                               return p;
+       function isPowerOfTwo$1( image ) {
 
-                       } else {
+               return isPowerOfTwo( image.width ) && isPowerOfTwo( image.height );
 
-                               return null;
+       }
 
-                       }
+       function textureNeedsPowerOfTwo( texture ) {
 
-               }
+               if ( isWebGL2 ) return false;
 
-               if ( p === UnsignedInt248Type ) {
+               return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) ||
+                       ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter );
 
-                       if ( isWebGL2 ) return 34042;
+       }
 
-                       extension = extensions.get( 'WEBGL_depth_texture' );
+       function textureNeedsGenerateMipmaps( texture, supportsMips ) {
 
-                       if ( extension !== null ) {
+               return texture.generateMipmaps && supportsMips &&
+                       texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter;
 
-                               return extension.UNSIGNED_INT_24_8_WEBGL;
+       }
 
-                       } else {
+       function generateMipmap( target, texture, width, height, depth = 1 ) {
 
-                               return null;
+               _gl.generateMipmap( target );
 
-                       }
+               const textureProperties = properties.get( texture );
 
-               }
+               textureProperties.__maxMipLevel = Math.log2( Math.max( width, height, depth ) );
 
        }
 
-       return { convert: convert };
-
-    }
-
-    function ArrayCamera( array = [] ) {
+       function getInternalFormat( internalFormatName, glFormat, glType, encoding ) {
 
-       PerspectiveCamera.call( this );
+               if ( isWebGL2 === false ) return glFormat;
 
-       this.cameras = array;
+               if ( internalFormatName !== null ) {
 
-    }
+                       if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ];
 
-    ArrayCamera.prototype = Object.assign( Object.create( PerspectiveCamera.prototype ), {
+                       console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' );
 
-       constructor: ArrayCamera,
+               }
 
-       isArrayCamera: true
+               let internalFormat = glFormat;
 
-    } );
+               if ( glFormat === 6403 ) {
 
-    function Group() {
+                       if ( glType === 5126 ) internalFormat = 33326;
+                       if ( glType === 5131 ) internalFormat = 33325;
+                       if ( glType === 5121 ) internalFormat = 33321;
 
-       Object3D.call( this );
+               }
 
-       this.type = 'Group';
+               if ( glFormat === 6407 ) {
 
-    }
+                       if ( glType === 5126 ) internalFormat = 34837;
+                       if ( glType === 5131 ) internalFormat = 34843;
+                       if ( glType === 5121 ) internalFormat = 32849;
 
-    Group.prototype = Object.assign( Object.create( Object3D.prototype ), {
+               }
 
-       constructor: Group,
+               if ( glFormat === 6408 ) {
 
-       isGroup: true
+                       if ( glType === 5126 ) internalFormat = 34836;
+                       if ( glType === 5131 ) internalFormat = 34842;
+                       if ( glType === 5121 ) internalFormat = ( encoding === sRGBEncoding ) ? 35907 : 32856;
 
-    } );
+               }
 
-    function WebXRController() {
+               if ( internalFormat === 33325 || internalFormat === 33326 ||
+                       internalFormat === 34842 || internalFormat === 34836 ) {
 
-       this._targetRay = null;
-       this._grip = null;
-       this._hand = null;
+                       extensions.get( 'EXT_color_buffer_float' );
 
-    }
+               }
 
-    Object.assign( WebXRController.prototype, {
+               return internalFormat;
 
-       constructor: WebXRController,
+       }
 
-       getHandSpace: function () {
+       // Fallback filters for non-power-of-2 textures
 
-               if ( this._hand === null ) {
+       function filterFallback( f ) {
 
-                       this._hand = new Group();
-                       this._hand.matrixAutoUpdate = false;
-                       this._hand.visible = false;
+               if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) {
 
-                       this._hand.joints = {};
-                       this._hand.inputState = { pinching: false };
+                       return 9728;
 
                }
 
-               return this._hand;
-
-       },
-
-       getTargetRaySpace: function () {
+               return 9729;
 
-               if ( this._targetRay === null ) {
+       }
 
-                       this._targetRay = new Group();
-                       this._targetRay.matrixAutoUpdate = false;
-                       this._targetRay.visible = false;
+       //
 
-               }
+       function onTextureDispose( event ) {
 
-               return this._targetRay;
+               const texture = event.target;
 
-       },
+               texture.removeEventListener( 'dispose', onTextureDispose );
 
-       getGripSpace: function () {
+               deallocateTexture( texture );
 
-               if ( this._grip === null ) {
+               if ( texture.isVideoTexture ) {
 
-                       this._grip = new Group();
-                       this._grip.matrixAutoUpdate = false;
-                       this._grip.visible = false;
+                       _videoTextures.delete( texture );
 
                }
 
-               return this._grip;
+               info.memory.textures --;
 
-       },
+       }
 
-       dispatchEvent: function ( event ) {
+       function onRenderTargetDispose( event ) {
 
-               if ( this._targetRay !== null ) {
+               const renderTarget = event.target;
 
-                       this._targetRay.dispatchEvent( event );
+               renderTarget.removeEventListener( 'dispose', onRenderTargetDispose );
 
-               }
+               deallocateRenderTarget( renderTarget );
 
-               if ( this._grip !== null ) {
+       }
 
-                       this._grip.dispatchEvent( event );
+       //
 
-               }
+       function deallocateTexture( texture ) {
 
-               if ( this._hand !== null ) {
+               const textureProperties = properties.get( texture );
 
-                       this._hand.dispatchEvent( event );
+               if ( textureProperties.__webglInit === undefined ) return;
 
-               }
+               _gl.deleteTexture( textureProperties.__webglTexture );
 
-               return this;
+               properties.remove( texture );
 
-       },
+       }
 
-       disconnect: function ( inputSource ) {
+       function deallocateRenderTarget( renderTarget ) {
 
-               this.dispatchEvent( { type: 'disconnected', data: inputSource } );
+               const texture = renderTarget.texture;
 
-               if ( this._targetRay !== null ) {
+               const renderTargetProperties = properties.get( renderTarget );
+               const textureProperties = properties.get( texture );
 
-                       this._targetRay.visible = false;
+               if ( ! renderTarget ) return;
 
-               }
+               if ( textureProperties.__webglTexture !== undefined ) {
 
-               if ( this._grip !== null ) {
+                       _gl.deleteTexture( textureProperties.__webglTexture );
 
-                       this._grip.visible = false;
+                       info.memory.textures --;
 
                }
 
-               if ( this._hand !== null ) {
+               if ( renderTarget.depthTexture ) {
 
-                       this._hand.visible = false;
+                       renderTarget.depthTexture.dispose();
 
                }
 
-               return this;
-
-       },
-
-       update: function ( inputSource, frame, referenceSpace ) {
-
-               let inputPose = null;
-               let gripPose = null;
-               let handPose = null;
-
-               const targetRay = this._targetRay;
-               const grip = this._grip;
-               const hand = this._hand;
-
-               if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) {
+               if ( renderTarget.isWebGLCubeRenderTarget ) {
 
-                       if ( hand && inputSource.hand ) {
+                       for ( let i = 0; i < 6; i ++ ) {
 
-                               handPose = true;
+                               _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] );
+                               if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] );
 
-                               for ( const inputjoint of inputSource.hand.values() ) {
+                       }
 
-                                       // Update the joints groups with the XRJoint poses
-                                       const jointPose = frame.getJointPose( inputjoint, referenceSpace );
+               } else {
 
-                                       if ( hand.joints[ inputjoint.jointName ] === undefined ) {
+                       _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 );
 
-                                               // 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 );
+               }
 
-                                       }
+               if ( renderTarget.isWebGLMultipleRenderTargets ) {
 
-                                       const joint = hand.joints[ inputjoint.jointName ];
+                       for ( let i = 0, il = texture.length; i < il; i ++ ) {
 
-                                       if ( jointPose !== null ) {
+                               const attachmentProperties = properties.get( texture[ i ] );
 
-                                               joint.matrix.fromArray( jointPose.transform.matrix );
-                                               joint.matrix.decompose( joint.position, joint.rotation, joint.scale );
-                                               joint.jointRadius = jointPose.radius;
+                               if ( attachmentProperties.__webglTexture ) {
 
-                                       }
+                                       _gl.deleteTexture( attachmentProperties.__webglTexture );
 
-                                       joint.visible = jointPose !== null;
+                                       info.memory.textures --;
 
                                }
 
-                               // Custom events
+                               properties.remove( texture[ i ] );
 
-                               // Check pinchz
-                               const indexTip = hand.joints[ 'index-finger-tip' ];
-                               const thumbTip = hand.joints[ 'thumb-tip' ];
-                               const distance = indexTip.position.distanceTo( thumbTip.position );
+                       }
 
-                               const distanceToPinch = 0.02;
-                               const threshold = 0.005;
+               }
 
-                               if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) {
+               properties.remove( texture );
+               properties.remove( renderTarget );
 
-                                       hand.inputState.pinching = false;
-                                       this.dispatchEvent( {
-                                               type: 'pinchend',
-                                               handedness: inputSource.handedness,
-                                               target: this
-                                       } );
+       }
 
-                               } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) {
+       //
 
-                                       hand.inputState.pinching = true;
-                                       this.dispatchEvent( {
-                                               type: 'pinchstart',
-                                               handedness: inputSource.handedness,
-                                               target: this
-                                       } );
+       let textureUnits = 0;
 
-                               }
+       function resetTextureUnits() {
 
-                       } else {
+               textureUnits = 0;
 
-                               if ( targetRay !== null ) {
+       }
 
-                                       inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace );
+       function allocateTextureUnit() {
 
-                                       if ( inputPose !== null ) {
+               const textureUnit = textureUnits;
 
-                                               targetRay.matrix.fromArray( inputPose.transform.matrix );
-                                               targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale );
+               if ( textureUnit >= maxTextures ) {
 
-                                       }
+                       console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + maxTextures );
 
-                               }
+               }
 
-                               if ( grip !== null && inputSource.gripSpace ) {
+               textureUnits += 1;
 
-                                       gripPose = frame.getPose( inputSource.gripSpace, referenceSpace );
+               return textureUnit;
 
-                                       if ( gripPose !== null ) {
+       }
 
-                                               grip.matrix.fromArray( gripPose.transform.matrix );
-                                               grip.matrix.decompose( grip.position, grip.rotation, grip.scale );
+       //
 
-                                       }
+       function setTexture2D( texture, slot ) {
 
-                               }
+               const textureProperties = properties.get( texture );
 
-                       }
+               if ( texture.isVideoTexture ) updateVideoTexture( texture );
 
-               }
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
 
-               if ( targetRay !== null ) {
+                       const image = texture.image;
 
-                       targetRay.visible = ( inputPose !== null );
+                       if ( image === undefined ) {
 
-               }
+                               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined' );
 
-               if ( grip !== null ) {
+                       } else if ( image.complete === false ) {
 
-                       grip.visible = ( gripPose !== null );
+                               console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' );
 
-               }
+                       } else {
 
-               if ( hand !== null ) {
+                               uploadTexture( textureProperties, texture, slot );
+                               return;
 
-                       hand.visible = ( handPose !== null );
+                       }
 
                }
 
-               return this;
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 3553, textureProperties.__webglTexture );
 
        }
 
-    } );
+       function setTexture2DArray( texture, slot ) {
 
-    function WebXRManager( renderer, gl ) {
+               const textureProperties = properties.get( texture );
 
-       const scope = this;
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
 
-       let session = null;
+                       uploadTexture( textureProperties, texture, slot );
+                       return;
 
-       let framebufferScaleFactor = 1.0;
+               }
 
-       let referenceSpace = null;
-       let referenceSpaceType = 'local-floor';
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 35866, textureProperties.__webglTexture );
 
-       let pose = null;
+       }
 
-       const controllers = [];
-       const inputSourcesMap = new Map();
+       function setTexture3D( texture, slot ) {
 
-       //
+               const textureProperties = properties.get( texture );
 
-       const cameraL = new PerspectiveCamera();
-       cameraL.layers.enable( 1 );
-       cameraL.viewport = new Vector4();
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
 
-       const cameraR = new PerspectiveCamera();
-       cameraR.layers.enable( 2 );
-       cameraR.viewport = new Vector4();
+                       uploadTexture( textureProperties, texture, slot );
+                       return;
 
-       const cameras = [ cameraL, cameraR ];
+               }
 
-       const cameraVR = new ArrayCamera();
-       cameraVR.layers.enable( 1 );
-       cameraVR.layers.enable( 2 );
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 32879, textureProperties.__webglTexture );
 
-       let _currentDepthNear = null;
-       let _currentDepthFar = null;
+       }
 
-       //
+       function setTextureCube( texture, slot ) {
 
-       this.enabled = false;
+               const textureProperties = properties.get( texture );
 
-       this.isPresenting = false;
+               if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
 
-       this.getController = function ( index ) {
+                       uploadCubeTexture( textureProperties, texture, slot );
+                       return;
 
-               let controller = controllers[ index ];
+               }
 
-               if ( controller === undefined ) {
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 34067, textureProperties.__webglTexture );
 
-                       controller = new WebXRController();
-                       controllers[ index ] = controller;
+       }
 
-               }
+       const wrappingToGL = {
+               [ RepeatWrapping ]: 10497,
+               [ ClampToEdgeWrapping ]: 33071,
+               [ MirroredRepeatWrapping ]: 33648
+       };
 
-               return controller.getTargetRaySpace();
+       const filterToGL = {
+               [ NearestFilter ]: 9728,
+               [ NearestMipmapNearestFilter ]: 9984,
+               [ NearestMipmapLinearFilter ]: 9986,
 
+               [ LinearFilter ]: 9729,
+               [ LinearMipmapNearestFilter ]: 9985,
+               [ LinearMipmapLinearFilter ]: 9987
        };
 
-       this.getControllerGrip = function ( index ) {
+       function setTextureParameters( textureType, texture, supportsMips ) {
 
-               let controller = controllers[ index ];
+               if ( supportsMips ) {
 
-               if ( controller === undefined ) {
+                       _gl.texParameteri( textureType, 10242, wrappingToGL[ texture.wrapS ] );
+                       _gl.texParameteri( textureType, 10243, wrappingToGL[ texture.wrapT ] );
 
-                       controller = new WebXRController();
-                       controllers[ index ] = controller;
+                       if ( textureType === 32879 || textureType === 35866 ) {
 
-               }
+                               _gl.texParameteri( textureType, 32882, wrappingToGL[ texture.wrapR ] );
 
-               return controller.getGripSpace();
+                       }
 
-       };
+                       _gl.texParameteri( textureType, 10240, filterToGL[ texture.magFilter ] );
+                       _gl.texParameteri( textureType, 10241, filterToGL[ texture.minFilter ] );
 
-       this.getHand = function ( index ) {
+               } else {
 
-               let controller = controllers[ index ];
+                       _gl.texParameteri( textureType, 10242, 33071 );
+                       _gl.texParameteri( textureType, 10243, 33071 );
 
-               if ( controller === undefined ) {
+                       if ( textureType === 32879 || textureType === 35866 ) {
 
-                       controller = new WebXRController();
-                       controllers[ index ] = controller;
+                               _gl.texParameteri( textureType, 32882, 33071 );
 
-               }
+                       }
 
-               return controller.getHandSpace();
+                       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.' );
 
-       //
+                       }
 
-       function onSessionEvent( event ) {
+                       _gl.texParameteri( textureType, 10240, filterFallback( texture.magFilter ) );
+                       _gl.texParameteri( textureType, 10241, filterFallback( texture.minFilter ) );
 
-               const controller = inputSourcesMap.get( event.inputSource );
+                       if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) {
 
-               if ( controller ) {
+                               console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.' );
 
-                       controller.dispatchEvent( { type: event.type, data: event.inputSource } );
+                       }
 
                }
 
-       }
+               if ( extensions.has( 'EXT_texture_filter_anisotropic' ) === true ) {
 
-       function onSessionEnd() {
+                       const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
 
-               inputSourcesMap.forEach( function ( controller, 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
 
-                       controller.disconnect( inputSource );
+                       if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) {
 
-               } );
+                               _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) );
+                               properties.get( texture ).__currentAnisotropy = texture.anisotropy;
 
-               inputSourcesMap.clear();
+                       }
 
-               _currentDepthNear = null;
-               _currentDepthFar = null;
+               }
 
-               //
+       }
 
-               renderer.setFramebuffer( null );
-               renderer.setRenderTarget( renderer.getRenderTarget() ); // Hack #15830
-               animation.stop();
+       function initTexture( textureProperties, texture ) {
 
-               scope.isPresenting = false;
+               if ( textureProperties.__webglInit === undefined ) {
 
-               scope.dispatchEvent( { type: 'sessionend' } );
+                       textureProperties.__webglInit = true;
 
-       }
+                       texture.addEventListener( 'dispose', onTextureDispose );
 
-       this.setFramebufferScaleFactor = function ( value ) {
+                       textureProperties.__webglTexture = _gl.createTexture();
 
-               framebufferScaleFactor = value;
+                       info.memory.textures ++;
 
-               if ( scope.isPresenting === true ) {
+               }
 
-                       console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' );
+       }
 
-               }
+       function uploadTexture( textureProperties, texture, slot ) {
 
-       };
+               let textureType = 3553;
 
-       this.setReferenceSpaceType = function ( value ) {
+               if ( texture.isDataTexture2DArray ) textureType = 35866;
+               if ( texture.isDataTexture3D ) textureType = 32879;
 
-               referenceSpaceType = value;
+               initTexture( textureProperties, texture );
 
-               if ( scope.isPresenting === true ) {
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( textureType, textureProperties.__webglTexture );
 
-                       console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' );
+               _gl.pixelStorei( 37440, texture.flipY );
+               _gl.pixelStorei( 37441, texture.premultiplyAlpha );
+               _gl.pixelStorei( 3317, texture.unpackAlignment );
+               _gl.pixelStorei( 37443, 0 );
 
-               }
+               const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo$1( texture.image ) === false;
+               const image = resizeImage( texture.image, needsPowerOfTwo, false, maxTextureSize );
 
-       };
+               const supportsMips = isPowerOfTwo$1( image ) || isWebGL2,
+                       glFormat = utils.convert( texture.format );
 
-       this.getReferenceSpace = function () {
+               let glType = utils.convert( texture.type ),
+                       glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
 
-               return referenceSpace;
+               setTextureParameters( textureType, texture, supportsMips );
 
-       };
+               let mipmap;
+               const mipmaps = texture.mipmaps;
 
-       this.getSession = function () {
+               if ( texture.isDepthTexture ) {
 
-               return session;
+                       // populate depth texture with dummy data
 
-       };
+                       glInternalFormat = 6402;
 
-       this.setSession = async function ( value ) {
+                       if ( isWebGL2 ) {
 
-               session = value;
+                               if ( texture.type === FloatType ) {
 
-               if ( session !== null ) {
+                                       glInternalFormat = 36012;
 
-                       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 );
+                               } else if ( texture.type === UnsignedIntType ) {
 
-                       const attributes = gl.getContextAttributes();
+                                       glInternalFormat = 33190;
 
-                       if ( attributes.xrCompatible !== true ) {
+                               } else if ( texture.type === UnsignedInt248Type ) {
 
-                               await gl.makeXRCompatible();
+                                       glInternalFormat = 35056;
 
-                       }
+                               } else {
 
-                       const layerInit = {
-                               antialias: attributes.antialias,
-                               alpha: attributes.alpha,
-                               depth: attributes.depth,
-                               stencil: attributes.stencil,
-                               framebufferScaleFactor: framebufferScaleFactor
-                       };
+                                       glInternalFormat = 33189; // WebGL2 requires sized internalformat for glTexImage2D
 
-                       // eslint-disable-next-line no-undef
-                       const baseLayer = new XRWebGLLayer( session, gl, layerInit );
+                               }
 
-                       session.updateRenderState( { baseLayer: baseLayer } );
+                       } else {
 
-                       referenceSpace = await session.requestReferenceSpace( referenceSpaceType );
+                               if ( texture.type === FloatType ) {
 
-                       animation.setContext( session );
-                       animation.start();
+                                       console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' );
 
-                       scope.isPresenting = true;
+                               }
 
-                       scope.dispatchEvent( { type: 'sessionstart' } );
+                       }
 
-               }
+                       // validation checks for WebGL 1
 
-       };
+                       if ( texture.format === DepthFormat && glInternalFormat === 6402 ) {
 
-       function onInputSourcesChange( event ) {
+                               // 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 ) {
 
-               const inputSources = session.inputSources;
+                                       console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' );
 
-               // Assign inputSources to available controllers
+                                       texture.type = UnsignedShortType;
+                                       glType = utils.convert( texture.type );
 
-               for ( let i = 0; i < controllers.length; i ++ ) {
+                               }
 
-                       inputSourcesMap.set( inputSources[ i ], controllers[ i ] );
+                       }
 
-               }
+                       if ( texture.format === DepthStencilFormat && glInternalFormat === 6402 ) {
 
-               // Notify disconnected
+                               // Depth stencil textures need the DEPTH_STENCIL internal format
+                               // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
+                               glInternalFormat = 34041;
 
-               for ( let i = 0; i < event.removed.length; i ++ ) {
+                               // 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 ) {
 
-                       const inputSource = event.removed[ i ];
-                       const controller = inputSourcesMap.get( inputSource );
+                                       console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' );
 
-                       if ( controller ) {
+                                       texture.type = UnsignedInt248Type;
+                                       glType = utils.convert( texture.type );
 
-                               controller.dispatchEvent( { type: 'disconnected', data: inputSource } );
-                               inputSourcesMap.delete( inputSource );
+                               }
 
                        }
 
-               }
+                       //
 
-               // Notify connected
+                       state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null );
 
-               for ( let i = 0; i < event.added.length; i ++ ) {
+               } else if ( texture.isDataTexture ) {
 
-                       const inputSource = event.added[ i ];
-                       const controller = inputSourcesMap.get( inputSource );
+                       // 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 ( controller ) {
+                       if ( mipmaps.length > 0 && supportsMips ) {
 
-                               controller.dispatchEvent( { type: 'connected', data: inputSource } );
+                               for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
 
-                       }
+                                       mipmap = mipmaps[ i ];
+                                       state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
 
-               }
+                               }
 
-       }
+                               texture.generateMipmaps = false;
+                               textureProperties.__maxMipLevel = mipmaps.length - 1;
 
-       //
+                       } else {
 
-       const cameraLPos = new Vector3();
-       const cameraRPos = new Vector3();
+                               state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data );
+                               textureProperties.__maxMipLevel = 0;
 
-       /**
-        * Assumes 2 cameras that are parallel and share an X-axis, and that
-        * the cameras' projection and world matrices have already been set.
-        * And that near and far planes are identical for both cameras.
-        * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
-        */
-       function setProjectionFromUnion( camera, cameraL, cameraR ) {
+                       }
 
-               cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
-               cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );
+               } else if ( texture.isCompressedTexture ) {
 
-               const ipd = cameraLPos.distanceTo( cameraRPos );
+                       for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
 
-               const projL = cameraL.projectionMatrix.elements;
-               const projR = cameraR.projectionMatrix.elements;
+                               mipmap = mipmaps[ i ];
 
-               // 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 ];
+                               if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {
 
-               const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
-               const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
-               const left = near * leftFov;
-               const right = near * rightFov;
+                                       if ( glFormat !== null ) {
 
-               // 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;
+                                               state.compressedTexImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
 
-               // 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();
+                                       } else {
 
-               // 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;
+                                               console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' );
 
-               camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );
+                                       }
 
-       }
+                               } else {
 
-       function updateCamera( camera, parent ) {
+                                       state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
 
-               if ( parent === null ) {
+                               }
 
-                       camera.matrixWorld.copy( camera.matrix );
+                       }
 
-               } else {
+                       textureProperties.__maxMipLevel = mipmaps.length - 1;
 
-                       camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix );
+               } else if ( texture.isDataTexture2DArray ) {
 
-               }
+                       state.texImage3D( 35866, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
+                       textureProperties.__maxMipLevel = 0;
 
-               camera.matrixWorldInverse.copy( camera.matrixWorld ).invert();
+               } else if ( texture.isDataTexture3D ) {
 
-       }
+                       state.texImage3D( 32879, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
+                       textureProperties.__maxMipLevel = 0;
 
-       this.getCamera = function ( camera ) {
+               } else {
 
-               cameraVR.near = cameraR.near = cameraL.near = camera.near;
-               cameraVR.far = cameraR.far = cameraL.far = camera.far;
+                       // regular Texture (image, video, canvas)
 
-               if ( _currentDepthNear !== cameraVR.near || _currentDepthFar !== cameraVR.far ) {
+                       // 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
 
-                       // Note that the new renderState won't apply until the next frame. See #18320
+                       if ( mipmaps.length > 0 && supportsMips ) {
 
-                       session.updateRenderState( {
-                               depthNear: cameraVR.near,
-                               depthFar: cameraVR.far
-                       } );
+                               for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
 
-                       _currentDepthNear = cameraVR.near;
-                       _currentDepthFar = cameraVR.far;
+                                       mipmap = mipmaps[ i ];
+                                       state.texImage2D( 3553, i, glInternalFormat, glFormat, glType, mipmap );
 
-               }
+                               }
 
-               const parent = camera.parent;
-               const cameras = cameraVR.cameras;
+                               texture.generateMipmaps = false;
+                               textureProperties.__maxMipLevel = mipmaps.length - 1;
 
-               updateCamera( cameraVR, parent );
+                       } else {
 
-               for ( let i = 0; i < cameras.length; i ++ ) {
+                               state.texImage2D( 3553, 0, glInternalFormat, glFormat, glType, image );
+                               textureProperties.__maxMipLevel = 0;
 
-                       updateCamera( cameras[ i ], parent );
+                       }
 
                }
 
-               // update camera and its children
+               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
 
-               camera.matrixWorld.copy( cameraVR.matrixWorld );
-               camera.matrix.copy( cameraVR.matrix );
-               camera.matrix.decompose( camera.position, camera.quaternion, camera.scale );
+                       generateMipmap( textureType, texture, image.width, image.height );
 
-               const children = camera.children;
+               }
 
-               for ( let i = 0, l = children.length; i < l; i ++ ) {
+               textureProperties.__version = texture.version;
 
-                       children[ i ].updateMatrixWorld( true );
+               if ( texture.onUpdate ) texture.onUpdate( texture );
 
-               }
+       }
 
-               // update projection matrix for proper view frustum culling
+       function uploadCubeTexture( textureProperties, texture, slot ) {
 
-               if ( cameras.length === 2 ) {
+               if ( texture.image.length !== 6 ) return;
 
-                       setProjectionFromUnion( cameraVR, cameraL, cameraR );
+               initTexture( textureProperties, texture );
 
-               } else {
+               state.activeTexture( 33984 + slot );
+               state.bindTexture( 34067, textureProperties.__webglTexture );
 
-                       // assume single camera setup (AR)
+               _gl.pixelStorei( 37440, texture.flipY );
+               _gl.pixelStorei( 37441, texture.premultiplyAlpha );
+               _gl.pixelStorei( 3317, texture.unpackAlignment );
+               _gl.pixelStorei( 37443, 0 );
 
-                       cameraVR.projectionMatrix.copy( cameraL.projectionMatrix );
+               const isCompressed = ( texture && ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ) );
+               const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture );
 
-               }
+               const cubeImage = [];
 
-               return cameraVR;
+               for ( let i = 0; i < 6; i ++ ) {
 
-       };
+                       if ( ! isCompressed && ! isDataTexture ) {
 
-       // Animation Loop
+                               cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, maxCubemapSize );
 
-       let onAnimationFrameCallback = null;
+                       } else {
 
-       function onAnimationFrame( time, frame ) {
+                               cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ];
 
-               pose = frame.getViewerPose( referenceSpace );
+                       }
+
+               }
+
+               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 );
 
-               if ( pose !== null ) {
+               setTextureParameters( 34067, texture, supportsMips );
 
-                       const views = pose.views;
-                       const baseLayer = session.renderState.baseLayer;
+               let mipmaps;
 
-                       renderer.setFramebuffer( baseLayer.framebuffer );
+               if ( isCompressed ) {
 
-                       let cameraVRNeedsUpdate = false;
+                       for ( let i = 0; i < 6; i ++ ) {
 
-                       // check if it's necessary to rebuild cameraVR's camera list
+                               mipmaps = cubeImage[ i ].mipmaps;
 
-                       if ( views.length !== cameraVR.cameras.length ) {
+                               for ( let j = 0; j < mipmaps.length; j ++ ) {
 
-                               cameraVR.cameras.length = 0;
-                               cameraVRNeedsUpdate = true;
+                                       const mipmap = mipmaps[ j ];
 
-                       }
+                                       if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {
 
-                       for ( let i = 0; i < views.length; i ++ ) {
+                                               if ( glFormat !== null ) {
 
-                               const view = views[ i ];
-                               const viewport = baseLayer.getViewport( view );
+                                                       state.compressedTexImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
 
-                               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 );
+                                               } else {
 
-                               if ( i === 0 ) {
+                                                       console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' );
 
-                                       cameraVR.matrix.copy( camera.matrix );
+                                               }
 
-                               }
+                                       } else {
 
-                               if ( cameraVRNeedsUpdate === true ) {
+                                               state.texImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
 
-                                       cameraVR.cameras.push( camera );
+                                       }
 
                                }
 
                        }
 
-               }
-
-               //
+                       textureProperties.__maxMipLevel = mipmaps.length - 1;
 
-               const inputSources = session.inputSources;
+               } else {
 
-               for ( let i = 0; i < controllers.length; i ++ ) {
+                       mipmaps = texture.mipmaps;
 
-                       const controller = controllers[ i ];
-                       const inputSource = inputSources[ i ];
+                       for ( let i = 0; i < 6; i ++ ) {
 
-                       controller.update( inputSource, frame, referenceSpace );
+                               if ( isDataTexture ) {
 
-               }
+                                       state.texImage2D( 34069 + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data );
 
-               if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame );
+                                       for ( let j = 0; j < mipmaps.length; j ++ ) {
 
-       }
+                                               const mipmap = mipmaps[ j ];
+                                               const mipmapImage = mipmap.image[ i ].image;
 
-       const animation = new WebGLAnimation();
-       animation.setAnimationLoop( onAnimationFrame );
+                                               state.texImage2D( 34069 + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data );
 
-       this.setAnimationLoop = function ( callback ) {
+                                       }
 
-               onAnimationFrameCallback = callback;
+                               } else {
 
-       };
+                                       state.texImage2D( 34069 + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] );
 
-       this.dispose = function () {};
+                                       for ( let j = 0; j < mipmaps.length; j ++ ) {
 
-    }
+                                               const mipmap = mipmaps[ j ];
 
-    Object.assign( WebXRManager.prototype, EventDispatcher.prototype );
+                                               state.texImage2D( 34069 + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] );
 
-    function WebGLMaterials( properties ) {
+                                       }
 
-       function refreshFogUniforms( uniforms, fog ) {
+                               }
 
-               uniforms.fogColor.value.copy( fog.color );
+                       }
 
-               if ( fog.isFog ) {
+                       textureProperties.__maxMipLevel = mipmaps.length;
 
-                       uniforms.fogNear.value = fog.near;
-                       uniforms.fogFar.value = fog.far;
+               }
 
-               } else if ( fog.isFogExp2 ) {
+               if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
 
-                       uniforms.fogDensity.value = fog.density;
+                       // We assume images for cube map have the same size.
+                       generateMipmap( 34067, texture, image.width, image.height );
 
                }
 
-       }
+               textureProperties.__version = texture.version;
 
-       function refreshMaterialUniforms( uniforms, material, pixelRatio, height ) {
+               if ( texture.onUpdate ) texture.onUpdate( texture );
 
-               if ( material.isMeshBasicMaterial ) {
+       }
 
-                       refreshUniformsCommon( uniforms, material );
+       // Render targets
 
-               } else if ( material.isMeshLambertMaterial ) {
+       // Setup storage for target texture and bind it to correct framebuffer
+       function setupFrameBufferTexture( framebuffer, renderTarget, texture, attachment, textureTarget ) {
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsLambert( uniforms, material );
+               const glFormat = utils.convert( texture.format );
+               const glType = utils.convert( texture.type );
+               const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
 
-               } else if ( material.isMeshToonMaterial ) {
+               if ( textureTarget === 32879 || textureTarget === 35866 ) {
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsToon( uniforms, material );
+                       state.texImage3D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, renderTarget.depth, 0, glFormat, glType, null );
 
-               } else if ( material.isMeshPhongMaterial ) {
+               } else {
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsPhong( uniforms, material );
+                       state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
 
-               } else if ( material.isMeshStandardMaterial ) {
+               }
 
-                       refreshUniformsCommon( uniforms, material );
+               state.bindFramebuffer( 36160, framebuffer );
+               _gl.framebufferTexture2D( 36160, attachment, textureTarget, properties.get( texture ).__webglTexture, 0 );
+               state.bindFramebuffer( 36160, null );
 
-                       if ( material.isMeshPhysicalMaterial ) {
+       }
 
-                               refreshUniformsPhysical( uniforms, material );
+       // Setup storage for internal depth/stencil buffers and bind to correct framebuffer
+       function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) {
 
-                       } else {
+               _gl.bindRenderbuffer( 36161, renderbuffer );
 
-                               refreshUniformsStandard( uniforms, material );
+               if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
 
-                       }
+                       let glInternalFormat = 33189;
 
-               } else if ( material.isMeshMatcapMaterial ) {
+                       if ( isMultisample ) {
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsMatcap( uniforms, material );
+                               const depthTexture = renderTarget.depthTexture;
 
-               } else if ( material.isMeshDepthMaterial ) {
+                               if ( depthTexture && depthTexture.isDepthTexture ) {
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsDepth( uniforms, material );
+                                       if ( depthTexture.type === FloatType ) {
 
-               } else if ( material.isMeshDistanceMaterial ) {
+                                               glInternalFormat = 36012;
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsDistance( uniforms, material );
+                                       } else if ( depthTexture.type === UnsignedIntType ) {
 
-               } else if ( material.isMeshNormalMaterial ) {
+                                               glInternalFormat = 33190;
 
-                       refreshUniformsCommon( uniforms, material );
-                       refreshUniformsNormal( uniforms, material );
+                                       }
 
-               } else if ( material.isLineBasicMaterial ) {
+                               }
 
-                       refreshUniformsLine( uniforms, material );
+                               const samples = getRenderTargetSamples( renderTarget );
 
-                       if ( material.isLineDashedMaterial ) {
+                               _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
 
-                               refreshUniformsDash( uniforms, material );
+                       } else {
+
+                               _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
 
                        }
 
-               } else if ( material.isPointsMaterial ) {
+                       _gl.framebufferRenderbuffer( 36160, 36096, 36161, renderbuffer );
 
-                       refreshUniformsPoints( uniforms, material, pixelRatio, height );
+               } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
 
-               } else if ( material.isSpriteMaterial ) {
+                       if ( isMultisample ) {
 
-                       refreshUniformsSprites( uniforms, material );
+                               const samples = getRenderTargetSamples( renderTarget );
 
-               } else if ( material.isShadowMaterial ) {
+                               _gl.renderbufferStorageMultisample( 36161, samples, 35056, renderTarget.width, renderTarget.height );
 
-                       uniforms.color.value.copy( material.color );
-                       uniforms.opacity.value = material.opacity;
+                       } else {
 
-               } else if ( material.isShaderMaterial ) {
+                               _gl.renderbufferStorage( 36161, 34041, renderTarget.width, renderTarget.height );
 
-                       material.uniformsNeedUpdate = false; // #15581
+                       }
 
-               }
 
-       }
+                       _gl.framebufferRenderbuffer( 36160, 33306, 36161, renderbuffer );
 
-       function refreshUniformsCommon( uniforms, material ) {
+               } else {
 
-               uniforms.opacity.value = material.opacity;
+                       // Use the first texture for MRT so far
+                       const texture = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture[ 0 ] : renderTarget.texture;
 
-               if ( material.color ) {
+                       const glFormat = utils.convert( texture.format );
+                       const glType = utils.convert( texture.type );
+                       const glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType, texture.encoding );
 
-                       uniforms.diffuse.value.copy( material.color );
+                       if ( isMultisample ) {
 
-               }
+                               const samples = getRenderTargetSamples( renderTarget );
 
-               if ( material.emissive ) {
+                               _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
 
-                       uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity );
+                       } else {
+
+                               _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
+
+                       }
 
                }
 
-               if ( material.map ) {
+               _gl.bindRenderbuffer( 36161, null );
 
-                       uniforms.map.value = material.map;
+       }
 
-               }
+       // Setup resources for a Depth Texture for a FBO (needs an extension)
+       function setupDepthTexture( framebuffer, renderTarget ) {
 
-               if ( material.alphaMap ) {
+               const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget );
+               if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' );
 
-                       uniforms.alphaMap.value = material.alphaMap;
+               state.bindFramebuffer( 36160, framebuffer );
+
+               if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) {
+
+                       throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' );
 
                }
 
-               if ( material.specularMap ) {
+               // 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 ) {
 
-                       uniforms.specularMap.value = material.specularMap;
+                       renderTarget.depthTexture.image.width = renderTarget.width;
+                       renderTarget.depthTexture.image.height = renderTarget.height;
+                       renderTarget.depthTexture.needsUpdate = true;
 
                }
 
-               const envMap = properties.get( material ).envMap;
-
-               if ( envMap ) {
+               setTexture2D( renderTarget.depthTexture, 0 );
 
-                       uniforms.envMap.value = envMap;
+               const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture;
 
-                       uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap._needsFlipEnvMap ) ? - 1 : 1;
+               if ( renderTarget.depthTexture.format === DepthFormat ) {
 
-                       uniforms.reflectivity.value = material.reflectivity;
-                       uniforms.refractionRatio.value = material.refractionRatio;
+                       _gl.framebufferTexture2D( 36160, 36096, 3553, webglDepthTexture, 0 );
 
-                       const maxMipLevel = properties.get( envMap ).__maxMipLevel;
+               } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) {
 
-                       if ( maxMipLevel !== undefined ) {
+                       _gl.framebufferTexture2D( 36160, 33306, 3553, webglDepthTexture, 0 );
 
-                               uniforms.maxMipLevel.value = maxMipLevel;
+               } else {
 
-                       }
+                       throw new Error( 'Unknown depthTexture format' );
 
                }
 
-               if ( material.lightMap ) {
-
-                       uniforms.lightMap.value = material.lightMap;
-                       uniforms.lightMapIntensity.value = material.lightMapIntensity;
+       }
 
-               }
+       // Setup GL resources for a non-texture depth buffer
+       function setupDepthRenderbuffer( renderTarget ) {
 
-               if ( material.aoMap ) {
+               const renderTargetProperties = properties.get( renderTarget );
 
-                       uniforms.aoMap.value = material.aoMap;
-                       uniforms.aoMapIntensity.value = material.aoMapIntensity;
+               const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
 
-               }
+               if ( renderTarget.depthTexture ) {
 
-               // 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 ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' );
 
-               let uvScaleMap;
+                       setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget );
 
-               if ( material.map ) {
+               } else {
 
-                       uvScaleMap = material.map;
+                       if ( isCube ) {
 
-               } else if ( material.specularMap ) {
+                               renderTargetProperties.__webglDepthbuffer = [];
 
-                       uvScaleMap = material.specularMap;
+                               for ( let i = 0; i < 6; i ++ ) {
 
-               } else if ( material.displacementMap ) {
+                                       state.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer[ i ] );
+                                       renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer();
+                                       setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false );
 
-                       uvScaleMap = material.displacementMap;
+                               }
 
-               } else if ( material.normalMap ) {
+                       } else {
 
-                       uvScaleMap = material.normalMap;
+                               state.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer );
+                               renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer();
+                               setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false );
 
-               } else if ( material.bumpMap ) {
+                       }
 
-                       uvScaleMap = material.bumpMap;
+               }
 
-               } else if ( material.roughnessMap ) {
+               state.bindFramebuffer( 36160, null );
 
-                       uvScaleMap = material.roughnessMap;
+       }
 
-               } else if ( material.metalnessMap ) {
+       // Set up GL resources for the render target
+       function setupRenderTarget( renderTarget ) {
 
-                       uvScaleMap = material.metalnessMap;
+               const texture = renderTarget.texture;
 
-               } else if ( material.alphaMap ) {
+               const renderTargetProperties = properties.get( renderTarget );
+               const textureProperties = properties.get( texture );
 
-                       uvScaleMap = material.alphaMap;
+               renderTarget.addEventListener( 'dispose', onRenderTargetDispose );
 
-               } else if ( material.emissiveMap ) {
+               if ( renderTarget.isWebGLMultipleRenderTargets !== true ) {
 
-                       uvScaleMap = material.emissiveMap;
+                       textureProperties.__webglTexture = _gl.createTexture();
+                       textureProperties.__version = texture.version;
+                       info.memory.textures ++;
 
-               } else if ( material.clearcoatMap ) {
+               }
 
-                       uvScaleMap = material.clearcoatMap;
+               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;
 
-               } else if ( material.clearcoatNormalMap ) {
+               // Handles WebGL2 RGBFormat fallback - #18858
 
-                       uvScaleMap = material.clearcoatNormalMap;
+               if ( isWebGL2 && texture.format === RGBFormat && ( texture.type === FloatType || texture.type === HalfFloatType ) ) {
 
-               } else if ( material.clearcoatRoughnessMap ) {
+                       texture.format = RGBAFormat;
 
-                       uvScaleMap = material.clearcoatRoughnessMap;
+                       console.warn( 'THREE.WebGLRenderer: Rendering to textures with RGB format is not supported. Using RGBA format instead.' );
 
                }
 
-               if ( uvScaleMap !== undefined ) {
-
-                       // backwards compatibility
-                       if ( uvScaleMap.isWebGLRenderTarget ) {
+               // Setup framebuffer
 
-                               uvScaleMap = uvScaleMap.texture;
+               if ( isCube ) {
 
-                       }
+                       renderTargetProperties.__webglFramebuffer = [];
 
-                       if ( uvScaleMap.matrixAutoUpdate === true ) {
+                       for ( let i = 0; i < 6; i ++ ) {
 
-                               uvScaleMap.updateMatrix();
+                               renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer();
 
                        }
 
-                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+               } else {
 
-               }
+                       renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();
 
-               // uv repeat and offset setting priorities for uv2
-               // 1. ao map
-               // 2. light map
+                       if ( isMultipleRenderTargets ) {
 
-               let uv2ScaleMap;
+                               if ( capabilities.drawBuffers ) {
 
-               if ( material.aoMap ) {
+                                       const textures = renderTarget.texture;
 
-                       uv2ScaleMap = material.aoMap;
+                                       for ( let i = 0, il = textures.length; i < il; i ++ ) {
 
-               } else if ( material.lightMap ) {
+                                               const attachmentProperties = properties.get( textures[ i ] );
 
-                       uv2ScaleMap = material.lightMap;
+                                               if ( attachmentProperties.__webglTexture === undefined ) {
 
-               }
+                                                       attachmentProperties.__webglTexture = _gl.createTexture();
 
-               if ( uv2ScaleMap !== undefined ) {
+                                                       info.memory.textures ++;
 
-                       // backwards compatibility
-                       if ( uv2ScaleMap.isWebGLRenderTarget ) {
+                                               }
 
-                               uv2ScaleMap = uv2ScaleMap.texture;
-
-                       }
-
-                       if ( uv2ScaleMap.matrixAutoUpdate === true ) {
+                                       }
 
-                               uv2ScaleMap.updateMatrix();
+                               } else {
 
-                       }
+                                       console.warn( 'THREE.WebGLRenderer: WebGLMultipleRenderTargets can only be used with WebGL2 or WEBGL_draw_buffers extension.' );
 
-                       uniforms.uv2Transform.value.copy( uv2ScaleMap.matrix );
+                               }
 
-               }
+                       } else if ( isMultisample ) {
 
-       }
+                               if ( isWebGL2 ) {
 
-       function refreshUniformsLine( uniforms, material ) {
+                                       renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer();
+                                       renderTargetProperties.__webglColorRenderbuffer = _gl.createRenderbuffer();
 
-               uniforms.diffuse.value.copy( material.color );
-               uniforms.opacity.value = material.opacity;
+                                       _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 );
 
-       function refreshUniformsDash( uniforms, material ) {
+                                       state.bindFramebuffer( 36160, renderTargetProperties.__webglMultisampledFramebuffer );
+                                       _gl.framebufferRenderbuffer( 36160, 36064, 36161, renderTargetProperties.__webglColorRenderbuffer );
+                                       _gl.bindRenderbuffer( 36161, null );
 
-               uniforms.dashSize.value = material.dashSize;
-               uniforms.totalSize.value = material.dashSize + material.gapSize;
-               uniforms.scale.value = material.scale;
+                                       if ( renderTarget.depthBuffer ) {
 
-       }
+                                               renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer();
+                                               setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, 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;
+                                       state.bindFramebuffer( 36160, null );
 
-               if ( material.map ) {
 
-                       uniforms.map.value = material.map;
+                               } else {
 
-               }
+                                       console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
 
-               if ( material.alphaMap ) {
+                               }
 
-                       uniforms.alphaMap.value = material.alphaMap;
+                       }
 
                }
 
-               // uv repeat and offset setting priorities
-               // 1. color map
-               // 2. alpha map
+               // Setup color buffer
 
-               let uvScaleMap;
+               if ( isCube ) {
 
-               if ( material.map ) {
+                       state.bindTexture( 34067, textureProperties.__webglTexture );
+                       setTextureParameters( 34067, texture, supportsMips );
 
-                       uvScaleMap = material.map;
+                       for ( let i = 0; i < 6; i ++ ) {
 
-               } else if ( material.alphaMap ) {
+                               setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, texture, 36064, 34069 + i );
 
-                       uvScaleMap = material.alphaMap;
+                       }
 
-               }
+                       if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
 
-               if ( uvScaleMap !== undefined ) {
+                               generateMipmap( 34067, texture, renderTarget.width, renderTarget.height );
 
-                       if ( uvScaleMap.matrixAutoUpdate === true ) {
+                       }
 
-                               uvScaleMap.updateMatrix();
+                       state.unbindTexture();
 
-                       }
+               } else if ( isMultipleRenderTargets ) {
 
-                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+                       const textures = renderTarget.texture;
 
-               }
+                       for ( let i = 0, il = textures.length; i < il; i ++ ) {
 
-       }
+                               const attachment = textures[ i ];
+                               const attachmentProperties = properties.get( attachment );
 
-       function refreshUniformsSprites( uniforms, material ) {
+                               state.bindTexture( 3553, attachmentProperties.__webglTexture );
+                               setTextureParameters( 3553, attachment, supportsMips );
+                               setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, attachment, 36064 + i, 3553 );
 
-               uniforms.diffuse.value.copy( material.color );
-               uniforms.opacity.value = material.opacity;
-               uniforms.rotation.value = material.rotation;
+                               if ( textureNeedsGenerateMipmaps( attachment, supportsMips ) ) {
 
-               if ( material.map ) {
+                                       generateMipmap( 3553, attachment, renderTarget.width, renderTarget.height );
 
-                       uniforms.map.value = material.map;
+                               }
 
-               }
+                       }
 
-               if ( material.alphaMap ) {
+                       state.unbindTexture();
 
-                       uniforms.alphaMap.value = material.alphaMap;
+               } else {
 
-               }
+                       let glTextureType = 3553;
 
-               // uv repeat and offset setting priorities
-               // 1. color map
-               // 2. alpha map
+                       if ( isRenderTarget3D ) {
 
-               let uvScaleMap;
+                               // Render targets containing layers, i.e: Texture 3D and 2d arrays
 
-               if ( material.map ) {
+                               if ( isWebGL2 ) {
 
-                       uvScaleMap = material.map;
+                                       const isTexture3D = texture.isDataTexture3D;
+                                       glTextureType = isTexture3D ? 32879 : 35866;
 
-               } else if ( material.alphaMap ) {
+                               } else {
 
-                       uvScaleMap = material.alphaMap;
+                                       console.warn( 'THREE.DataTexture3D and THREE.DataTexture2DArray only supported with WebGL2.' );
 
-               }
+                               }
 
-               if ( uvScaleMap !== undefined ) {
+                       }
 
-                       if ( uvScaleMap.matrixAutoUpdate === true ) {
+                       state.bindTexture( glTextureType, textureProperties.__webglTexture );
+                       setTextureParameters( glTextureType, texture, supportsMips );
+                       setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, texture, 36064, glTextureType );
 
-                               uvScaleMap.updateMatrix();
+                       if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+
+                               generateMipmap( glTextureType, texture, renderTarget.width, renderTarget.height, renderTarget.depth );
 
                        }
 
-                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+                       state.unbindTexture();
 
                }
 
-       }
-
-       function refreshUniformsLambert( uniforms, material ) {
+               // Setup depth and stencil buffers
 
-               if ( material.emissiveMap ) {
+               if ( renderTarget.depthBuffer ) {
 
-                       uniforms.emissiveMap.value = material.emissiveMap;
+                       setupDepthRenderbuffer( renderTarget );
 
                }
 
        }
 
-       function refreshUniformsPhong( uniforms, material ) {
+       function updateRenderTargetMipmap( renderTarget ) {
 
-               uniforms.specular.value.copy( material.specular );
-               uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )
+               const supportsMips = isPowerOfTwo$1( renderTarget ) || isWebGL2;
 
-               if ( material.emissiveMap ) {
+               const textures = renderTarget.isWebGLMultipleRenderTargets === true ? renderTarget.texture : [ renderTarget.texture ];
 
-                       uniforms.emissiveMap.value = material.emissiveMap;
+               for ( let i = 0, il = textures.length; i < il; i ++ ) {
 
-               }
+                       const texture = textures[ 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;
+                               const target = renderTarget.isWebGLCubeRenderTarget ? 34067 : 3553;
+                               const webglTexture = properties.get( texture ).__webglTexture;
+
+                               state.bindTexture( target, webglTexture );
+                               generateMipmap( target, texture, renderTarget.width, renderTarget.height );
+                               state.unbindTexture();
+
+                       }
 
                }
 
-               if ( material.normalMap ) {
+       }
 
-                       uniforms.normalMap.value = material.normalMap;
-                       uniforms.normalScale.value.copy( material.normalScale );
-                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+       function updateMultisampleRenderTarget( renderTarget ) {
 
-               }
+               if ( renderTarget.isWebGLMultisampleRenderTarget ) {
 
-               if ( material.displacementMap ) {
+                       if ( isWebGL2 ) {
 
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
+                               const width = renderTarget.width;
+                               const height = renderTarget.height;
+                               let mask = 16384;
 
-               }
+                               if ( renderTarget.depthBuffer ) mask |= 256;
+                               if ( renderTarget.stencilBuffer ) mask |= 1024;
 
-       }
+                               const renderTargetProperties = properties.get( renderTarget );
 
-       function refreshUniformsToon( uniforms, material ) {
+                               state.bindFramebuffer( 36008, renderTargetProperties.__webglMultisampledFramebuffer );
+                               state.bindFramebuffer( 36009, renderTargetProperties.__webglFramebuffer );
 
-               if ( material.gradientMap ) {
+                               _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, 9728 );
 
-                       uniforms.gradientMap.value = material.gradientMap;
+                               state.bindFramebuffer( 36008, null );
+                               state.bindFramebuffer( 36009, renderTargetProperties.__webglMultisampledFramebuffer );
 
-               }
+                       } else {
 
-               if ( material.emissiveMap ) {
+                               console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
 
-                       uniforms.emissiveMap.value = material.emissiveMap;
+                       }
 
                }
 
-               if ( material.bumpMap ) {
+       }
 
-                       uniforms.bumpMap.value = material.bumpMap;
-                       uniforms.bumpScale.value = material.bumpScale;
-                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+       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 refreshUniformsStandard( uniforms, material ) {
+       // backwards compatibility
 
-               uniforms.roughness.value = material.roughness;
-               uniforms.metalness.value = material.metalness;
+       let warnedTexture2D = false;
+       let warnedTextureCube = false;
 
-               if ( material.roughnessMap ) {
+       function safeSetTexture2D( texture, slot ) {
 
-                       uniforms.roughnessMap.value = material.roughnessMap;
+               if ( texture && texture.isWebGLRenderTarget ) {
 
-               }
+                       if ( warnedTexture2D === false ) {
 
-               if ( material.metalnessMap ) {
+                               console.warn( 'THREE.WebGLTextures.safeSetTexture2D: don\'t use render targets as textures. Use their .texture property instead.' );
+                               warnedTexture2D = true;
 
-                       uniforms.metalnessMap.value = material.metalnessMap;
+                       }
+
+                       texture = texture.texture;
 
                }
 
-               if ( material.emissiveMap ) {
+               setTexture2D( texture, slot );
 
-                       uniforms.emissiveMap.value = material.emissiveMap;
+       }
 
-               }
+       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 );
 
-               const envMap = properties.get( material ).envMap;
+       }
 
-               if ( envMap ) {
+       //
 
-                       //uniforms.envMap.value = material.envMap; // part of uniforms common
-                       uniforms.envMapIntensity.value = material.envMapIntensity;
+       this.allocateTextureUnit = allocateTextureUnit;
+       this.resetTextureUnits = resetTextureUnits;
 
-               }
+       this.setTexture2D = setTexture2D;
+       this.setTexture2DArray = setTexture2DArray;
+       this.setTexture3D = setTexture3D;
+       this.setTextureCube = setTextureCube;
+       this.setupRenderTarget = setupRenderTarget;
+       this.updateRenderTargetMipmap = updateRenderTargetMipmap;
+       this.updateMultisampleRenderTarget = updateMultisampleRenderTarget;
 
-       }
+       this.safeSetTexture2D = safeSetTexture2D;
+       this.safeSetTextureCube = safeSetTextureCube;
 
-       function refreshUniformsPhysical( uniforms, material ) {
+    }
 
-               refreshUniformsStandard( uniforms, material );
+    function WebGLUtils( gl, extensions, capabilities ) {
 
-               uniforms.reflectivity.value = material.reflectivity; // also part of uniforms common
+       const isWebGL2 = capabilities.isWebGL2;
 
-               uniforms.clearcoat.value = material.clearcoat;
-               uniforms.clearcoatRoughness.value = material.clearcoatRoughness;
-               if ( material.sheen ) uniforms.sheen.value.copy( material.sheen );
+       function convert( p ) {
 
-               if ( material.clearcoatMap ) {
+               let extension;
 
-                       uniforms.clearcoatMap.value = material.clearcoatMap;
+               if ( p === UnsignedByteType ) return 5121;
+               if ( p === UnsignedShort4444Type ) return 32819;
+               if ( p === UnsignedShort5551Type ) return 32820;
+               if ( p === UnsignedShort565Type ) return 33635;
 
-               }
+               if ( p === ByteType ) return 5120;
+               if ( p === ShortType ) return 5122;
+               if ( p === UnsignedShortType ) return 5123;
+               if ( p === IntType ) return 5124;
+               if ( p === UnsignedIntType ) return 5125;
+               if ( p === FloatType ) return 5126;
 
-               if ( material.clearcoatRoughnessMap ) {
+               if ( p === HalfFloatType ) {
 
-                       uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap;
+                       if ( isWebGL2 ) return 5131;
 
-               }
+                       extension = extensions.get( 'OES_texture_half_float' );
 
-               if ( material.clearcoatNormalMap ) {
+                       if ( extension !== null ) {
 
-                       uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale );
-                       uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap;
+                               return extension.HALF_FLOAT_OES;
 
-                       if ( material.side === BackSide ) {
+                       } else {
 
-                               uniforms.clearcoatNormalScale.value.negate();
+                               return null;
 
                        }
 
                }
 
-               uniforms.transmission.value = material.transmission;
-
-               if ( material.transmissionMap ) {
+               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;
 
-                       uniforms.transmissionMap.value = material.transmissionMap;
+               // 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 ) {
 
-       function refreshUniformsMatcap( uniforms, material ) {
+                       extension = extensions.get( 'WEBGL_compressed_texture_s3tc' );
 
-               if ( material.matcap ) {
+                       if ( extension !== null ) {
 
-                       uniforms.matcap.value = material.matcap;
+                               if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT;
+                               if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
+                               if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
+                               if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
 
-               }
+                       } else {
 
-               if ( material.bumpMap ) {
+                               return null;
 
-                       uniforms.bumpMap.value = material.bumpMap;
-                       uniforms.bumpScale.value = material.bumpScale;
-                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+                       }
 
                }
 
-               if ( material.normalMap ) {
+               if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format ||
+                       p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {
 
-                       uniforms.normalMap.value = material.normalMap;
-                       uniforms.normalScale.value.copy( material.normalScale );
-                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+                       extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' );
 
-               }
+                       if ( extension !== null ) {
 
-               if ( material.displacementMap ) {
+                               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;
 
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
+                       } else {
 
-               }
+                               return null;
 
-       }
+                       }
 
-       function refreshUniformsDepth( uniforms, material ) {
+               }
 
-               if ( material.displacementMap ) {
+               if ( p === RGB_ETC1_Format ) {
 
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
+                       extension = extensions.get( 'WEBGL_compressed_texture_etc1' );
 
-               }
+                       if ( extension !== null ) {
 
-       }
+                               return extension.COMPRESSED_RGB_ETC1_WEBGL;
 
-       function refreshUniformsDistance( uniforms, material ) {
+                       } else {
 
-               if ( material.displacementMap ) {
+                               return null;
 
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
+                       }
 
                }
 
-               uniforms.referencePosition.value.copy( material.referencePosition );
-               uniforms.nearDistance.value = material.nearDistance;
-               uniforms.farDistance.value = material.farDistance;
+               if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) {
 
-       }
+                       extension = extensions.get( 'WEBGL_compressed_texture_etc' );
 
-       function refreshUniformsNormal( uniforms, material ) {
+                       if ( extension !== null ) {
 
-               if ( material.bumpMap ) {
+                               if ( p === RGB_ETC2_Format ) return extension.COMPRESSED_RGB8_ETC2;
+                               if ( p === RGBA_ETC2_EAC_Format ) return extension.COMPRESSED_RGBA8_ETC2_EAC;
 
-                       uniforms.bumpMap.value = material.bumpMap;
-                       uniforms.bumpScale.value = material.bumpScale;
-                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+                       }
 
                }
 
-               if ( material.normalMap ) {
+               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 ) {
 
-                       uniforms.normalMap.value = material.normalMap;
-                       uniforms.normalScale.value.copy( material.normalScale );
-                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+                       extension = extensions.get( 'WEBGL_compressed_texture_astc' );
 
-               }
+                       if ( extension !== null ) {
 
-               if ( material.displacementMap ) {
+                               // TODO Complete?
 
-                       uniforms.displacementMap.value = material.displacementMap;
-                       uniforms.displacementScale.value = material.displacementScale;
-                       uniforms.displacementBias.value = material.displacementBias;
+                               return p;
 
-               }
+                       } else {
 
-       }
+                               return null;
 
-       return {
-               refreshFogUniforms: refreshFogUniforms,
-               refreshMaterialUniforms: refreshMaterialUniforms
-       };
+                       }
 
-    }
+               }
 
-    function createCanvasElement() {
+               if ( p === RGBA_BPTC_Format ) {
 
-       const canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
-       canvas.style.display = 'block';
-       return canvas;
+                       extension = extensions.get( 'EXT_texture_compression_bptc' );
 
-    }
+                       if ( extension !== null ) {
 
-    function WebGLRenderer( parameters ) {
+                               // TODO Complete?
 
-       parameters = parameters || {};
+                               return p;
 
-       const _canvas = parameters.canvas !== undefined ? parameters.canvas : createCanvasElement(),
-               _context = parameters.context !== undefined ? parameters.context : null,
+                       } else {
 
-               _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;
+                               return null;
 
-       let currentRenderList = null;
-       let currentRenderState = null;
+                       }
 
-       // render() can be called from within a callback triggered by another render.
-       // We track this so that the nested render call gets its state isolated from the parent render call.
+               }
 
-       const renderStateStack = [];
+               if ( p === UnsignedInt248Type ) {
 
-       // public properties
+                       if ( isWebGL2 ) return 34042;
 
-       this.domElement = _canvas;
+                       extension = extensions.get( 'WEBGL_depth_texture' );
 
-       // 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.UNSIGNED_INT_24_8_WEBGL;
 
-       // clearing
+                       } else {
 
-       this.autoClear = true;
-       this.autoClearColor = true;
-       this.autoClearDepth = true;
-       this.autoClearStencil = true;
+                               return null;
 
-       // scene graph
+                       }
 
-       this.sortObjects = true;
+               }
 
-       // user-defined clipping
+       }
 
-       this.clippingPlanes = [];
-       this.localClippingEnabled = false;
+       return { convert: convert };
 
-       // physically based shading
+    }
 
-       this.gammaFactor = 2.0; // for backwards compatibility
-       this.outputEncoding = LinearEncoding;
+    class ArrayCamera extends PerspectiveCamera {
 
-       // physical lights
+       constructor( array = [] ) {
 
-       this.physicallyCorrectLights = false;
+               super();
 
-       // tone mapping
+               this.cameras = array;
 
-       this.toneMapping = NoToneMapping;
-       this.toneMappingExposure = 1.0;
+       }
 
-       // morphs
+    }
 
-       this.maxMorphTargets = 8;
-       this.maxMorphNormals = 4;
+    ArrayCamera.prototype.isArrayCamera = true;
 
-       // internal properties
+    class Group extends Object3D {
 
-       const _this = this;
+       constructor() {
 
-       let _isContextLost = false;
+               super();
 
-       // internal state cache
+               this.type = 'Group';
 
-       let _framebuffer = null;
+       }
 
-       let _currentActiveCubeFace = 0;
-       let _currentActiveMipmapLevel = 0;
-       let _currentRenderTarget = null;
-       let _currentFramebuffer = null;
-       let _currentMaterialId = - 1;
+    }
 
-       let _currentCamera = null;
+    Group.prototype.isGroup = true;
 
-       const _currentViewport = new Vector4();
-       const _currentScissor = new Vector4();
-       let _currentScissorTest = null;
+    const _moveEvent = { type: 'move' };
 
-       //
+    class WebXRController {
 
-       let _width = _canvas.width;
-       let _height = _canvas.height;
+       constructor() {
 
-       let _pixelRatio = 1;
-       let _opaqueSort = null;
-       let _transparentSort = null;
+               this._targetRay = null;
+               this._grip = null;
+               this._hand = null;
 
-       const _viewport = new Vector4( 0, 0, _width, _height );
-       const _scissor = new Vector4( 0, 0, _width, _height );
-       let _scissorTest = false;
+       }
 
-       // frustum
+       getHandSpace() {
 
-       const _frustum = new Frustum();
+               if ( this._hand === null ) {
 
-       // clipping
+                       this._hand = new Group();
+                       this._hand.matrixAutoUpdate = false;
+                       this._hand.visible = false;
 
-       let _clippingEnabled = false;
-       let _localClippingEnabled = false;
+                       this._hand.joints = {};
+                       this._hand.inputState = { pinching: false };
 
-       // camera matrices cache
+               }
 
-       const _projScreenMatrix = new Matrix4();
+               return this._hand;
 
-       const _vector3 = new Vector3();
+       }
 
-       const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true };
+       getTargetRaySpace() {
 
-       function getTargetPixelRatio() {
+               if ( this._targetRay === null ) {
 
-               return _currentRenderTarget === null ? _pixelRatio : 1;
+                       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();
 
-       }
+               }
 
-       // initialize
+               return this._targetRay;
 
-       let _gl = _context;
+       }
 
-       function getContext( contextNames, contextAttributes ) {
+       getGripSpace() {
 
-               for ( let i = 0; i < contextNames.length; i ++ ) {
+               if ( this._grip === null ) {
 
-                       const contextName = contextNames[ i ];
-                       const context = _canvas.getContext( contextName, contextAttributes );
-                       if ( context !== null ) return context;
+                       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();
 
                }
 
-               return null;
+               return this._grip;
 
        }
 
-       try {
-
-               const contextAttributes = {
-                       alpha: _alpha,
-                       depth: _depth,
-                       stencil: _stencil,
-                       antialias: _antialias,
-                       premultipliedAlpha: _premultipliedAlpha,
-                       preserveDrawingBuffer: _preserveDrawingBuffer,
-                       powerPreference: _powerPreference,
-                       failIfMajorPerformanceCaveat: _failIfMajorPerformanceCaveat
-               };
+       dispatchEvent( event ) {
 
-               // event listeners must be registered before WebGL context is created, see #12753
+               if ( this._targetRay !== null ) {
 
-               _canvas.addEventListener( 'webglcontextlost', onContextLost, false );
-               _canvas.addEventListener( 'webglcontextrestored', onContextRestore, false );
+                       this._targetRay.dispatchEvent( event );
 
-               if ( _gl === null ) {
+               }
 
-                       const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ];
+               if ( this._grip !== null ) {
 
-                       if ( _this.isWebGL1Renderer === true ) {
+                       this._grip.dispatchEvent( event );
 
-                               contextNames.shift();
+               }
 
-                       }
+               if ( this._hand !== null ) {
 
-                       _gl = getContext( contextNames, contextAttributes );
+                       this._hand.dispatchEvent( event );
 
-                       if ( _gl === null ) {
+               }
 
-                               if ( getContext( contextNames ) ) {
+               return this;
 
-                                       throw new Error( 'Error creating WebGL context with your selected attributes.' );
+       }
 
-                               } else {
+       disconnect( inputSource ) {
 
-                                       throw new Error( 'Error creating WebGL context.' );
+               this.dispatchEvent( { type: 'disconnected', data: inputSource } );
 
-                               }
+               if ( this._targetRay !== null ) {
 
-                       }
+                       this._targetRay.visible = false;
 
                }
 
-               // Some experimental-webgl implementations do not have getShaderPrecisionFormat
+               if ( this._grip !== null ) {
 
-               if ( _gl.getShaderPrecisionFormat === undefined ) {
+                       this._grip.visible = false;
 
-                       _gl.getShaderPrecisionFormat = function () {
+               }
 
-                               return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 };
+               if ( this._hand !== null ) {
 
-                       };
+                       this._hand.visible = false;
 
                }
 
-       } catch ( error ) {
-
-               console.error( 'THREE.WebGLRenderer: ' + error.message );
-               throw error;
+               return this;
 
        }
 
-       let extensions, capabilities, state, info;
-       let properties, textures, cubemaps, attributes, geometries, objects;
-       let programCache, materials, renderLists, renderStates, clipping;
+       update( inputSource, frame, referenceSpace ) {
 
-       let background, morphtargets, bufferRenderer, indexedBufferRenderer;
+               let inputPose = null;
+               let gripPose = null;
+               let handPose = null;
 
-       let utils, bindingStates;
+               const targetRay = this._targetRay;
+               const grip = this._grip;
+               const hand = this._hand;
 
-       function initGLContext() {
+               if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) {
 
-               extensions = new WebGLExtensions( _gl );
+                       if ( targetRay !== null ) {
 
-               capabilities = new WebGLCapabilities( _gl, extensions, parameters );
+                               inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace );
 
-               extensions.init( capabilities );
+                               if ( inputPose !== null ) {
 
-               utils = new WebGLUtils( _gl, extensions, capabilities );
+                                       targetRay.matrix.fromArray( inputPose.transform.matrix );
+                                       targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale );
 
-               state = new WebGLState( _gl, extensions, capabilities );
-               state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() );
-               state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() );
+                                       if ( inputPose.linearVelocity ) {
 
-               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 );
+                                               targetRay.hasLinearVelocity = true;
+                                               targetRay.linearVelocity.copy( inputPose.linearVelocity );
 
-               bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities );
-               indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities );
+                                       } else {
 
-               info.programs = programCache.programs;
+                                               targetRay.hasLinearVelocity = false;
 
-               _this.capabilities = capabilities;
-               _this.extensions = extensions;
-               _this.properties = properties;
-               _this.renderLists = renderLists;
-               _this.state = state;
-               _this.info = info;
+                                       }
 
-       }
+                                       if ( inputPose.angularVelocity ) {
 
-       initGLContext();
+                                               targetRay.hasAngularVelocity = true;
+                                               targetRay.angularVelocity.copy( inputPose.angularVelocity );
 
-       // xr
+                                       } else {
 
-       const xr = new WebXRManager( _this, _gl );
+                                               targetRay.hasAngularVelocity = false;
 
-       this.xr = xr;
+                                       }
 
-       // shadow map
+                                       this.dispatchEvent( _moveEvent );
 
-       const shadowMap = new WebGLShadowMap( _this, objects, capabilities.maxTextureSize );
+                               }
 
-       this.shadowMap = shadowMap;
+                       }
 
-       // API
+                       if ( hand && inputSource.hand ) {
 
-       this.getContext = function () {
+                               handPose = true;
 
-               return _gl;
+                               for ( const inputjoint of inputSource.hand.values() ) {
 
-       };
+                                       // Update the joints groups with the XRJoint poses
+                                       const jointPose = frame.getJointPose( inputjoint, referenceSpace );
 
-       this.getContextAttributes = function () {
+                                       if ( hand.joints[ inputjoint.jointName ] === undefined ) {
 
-               return _gl.getContextAttributes();
+                                               // 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.forceContextLoss = function () {
+                                       const joint = hand.joints[ inputjoint.jointName ];
 
-               const extension = extensions.get( 'WEBGL_lose_context' );
-               if ( extension ) extension.loseContext();
+                                       if ( jointPose !== null ) {
 
-       };
+                                               joint.matrix.fromArray( jointPose.transform.matrix );
+                                               joint.matrix.decompose( joint.position, joint.rotation, joint.scale );
+                                               joint.jointRadius = jointPose.radius;
 
-       this.forceContextRestore = function () {
+                                       }
 
-               const extension = extensions.get( 'WEBGL_lose_context' );
-               if ( extension ) extension.restoreContext();
+                                       joint.visible = jointPose !== null;
 
-       };
+                               }
 
-       this.getPixelRatio = function () {
+                               // Custom events
 
-               return _pixelRatio;
+                               // Check pinchz
+                               const indexTip = hand.joints[ 'index-finger-tip' ];
+                               const thumbTip = hand.joints[ 'thumb-tip' ];
+                               const distance = indexTip.position.distanceTo( thumbTip.position );
 
-       };
+                               const distanceToPinch = 0.02;
+                               const threshold = 0.005;
 
-       this.setPixelRatio = function ( value ) {
+                               if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) {
 
-               if ( value === undefined ) return;
+                                       hand.inputState.pinching = false;
+                                       this.dispatchEvent( {
+                                               type: 'pinchend',
+                                               handedness: inputSource.handedness,
+                                               target: this
+                                       } );
 
-               _pixelRatio = value;
+                               } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) {
 
-               this.setSize( _width, _height, false );
+                                       hand.inputState.pinching = true;
+                                       this.dispatchEvent( {
+                                               type: 'pinchstart',
+                                               handedness: inputSource.handedness,
+                                               target: this
+                                       } );
 
-       };
+                               }
 
-       this.getSize = function ( target ) {
+                       } else {
 
-               if ( target === undefined ) {
+                               if ( grip !== null && inputSource.gripSpace ) {
 
-                       console.warn( 'WebGLRenderer: .getsize() now requires a Vector2 as an argument' );
+                                       gripPose = frame.getPose( inputSource.gripSpace, referenceSpace );
 
-                       target = new Vector2();
+                                       if ( gripPose !== null ) {
 
-               }
+                                               grip.matrix.fromArray( gripPose.transform.matrix );
+                                               grip.matrix.decompose( grip.position, grip.rotation, grip.scale );
 
-               return target.set( _width, _height );
+                                               if ( gripPose.linearVelocity ) {
 
-       };
+                                                       grip.hasLinearVelocity = true;
+                                                       grip.linearVelocity.copy( gripPose.linearVelocity );
 
-       this.setSize = function ( width, height, updateStyle ) {
+                                               } else {
 
-               if ( xr.isPresenting ) {
+                                                       grip.hasLinearVelocity = false;
 
-                       console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' );
-                       return;
+                                               }
 
-               }
+                                               if ( gripPose.angularVelocity ) {
 
-               _width = width;
-               _height = height;
+                                                       grip.hasAngularVelocity = true;
+                                                       grip.angularVelocity.copy( gripPose.angularVelocity );
 
-               _canvas.width = Math.floor( width * _pixelRatio );
-               _canvas.height = Math.floor( height * _pixelRatio );
+                                               } else {
 
-               if ( updateStyle !== false ) {
+                                                       grip.hasAngularVelocity = false;
 
-                       _canvas.style.width = width + 'px';
-                       _canvas.style.height = height + 'px';
+                                               }
 
-               }
+                                       }
 
-               this.setViewport( 0, 0, width, height );
+                               }
 
-       };
+                       }
 
-       this.getDrawingBufferSize = function ( target ) {
+               }
 
-               if ( target === undefined ) {
+               if ( targetRay !== null ) {
+
+                       targetRay.visible = ( inputPose !== null );
+
+               }
 
-                       console.warn( 'WebGLRenderer: .getdrawingBufferSize() now requires a Vector2 as an argument' );
+               if ( grip !== null ) {
 
-                       target = new Vector2();
+                       grip.visible = ( gripPose !== null );
 
                }
 
-               return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor();
+               if ( hand !== null ) {
 
-       };
+                       hand.visible = ( handPose !== null );
 
-       this.setDrawingBufferSize = function ( width, height, pixelRatio ) {
+               }
 
-               _width = width;
-               _height = height;
+               return this;
 
-               _pixelRatio = pixelRatio;
+       }
 
-               _canvas.width = Math.floor( width * pixelRatio );
-               _canvas.height = Math.floor( height * pixelRatio );
+    }
 
-               this.setViewport( 0, 0, width, height );
+    class WebXRManager extends EventDispatcher {
 
-       };
+       constructor( renderer, gl ) {
 
-       this.getCurrentViewport = function ( target ) {
+               super();
 
-               if ( target === undefined ) {
+               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();
 
-                       console.warn( 'WebGLRenderer: .getCurrentViewport() now requires a Vector4 as an argument' );
+               //
 
-                       target = new Vector4();
+               const cameraL = new PerspectiveCamera();
+               cameraL.layers.enable( 1 );
+               cameraL.viewport = new Vector4();
 
-               }
+               const cameraR = new PerspectiveCamera();
+               cameraR.layers.enable( 2 );
+               cameraR.viewport = new Vector4();
 
-               return target.copy( _currentViewport );
+               const cameras = [ cameraL, cameraR ];
 
-       };
+               const cameraVR = new ArrayCamera();
+               cameraVR.layers.enable( 1 );
+               cameraVR.layers.enable( 2 );
 
-       this.getViewport = function ( target ) {
+               let _currentDepthNear = null;
+               let _currentDepthFar = null;
 
-               return target.copy( _viewport );
+               //
 
-       };
+               this.cameraAutoUpdate = true;
+               this.enabled = false;
 
-       this.setViewport = function ( x, y, width, height ) {
+               this.isPresenting = false;
 
-               if ( x.isVector4 ) {
+               this.getController = function ( index ) {
 
-                       _viewport.set( x.x, x.y, x.z, x.w );
+                       let controller = controllers[ index ];
 
-               } else {
+                       if ( controller === undefined ) {
 
-                       _viewport.set( x, y, width, height );
+                               controller = new WebXRController();
+                               controllers[ index ] = controller;
 
-               }
+                       }
 
-               state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() );
+                       return controller.getTargetRaySpace();
 
-       };
+               };
 
-       this.getScissor = function ( target ) {
+               this.getControllerGrip = function ( index ) {
 
-               return target.copy( _scissor );
+                       let controller = controllers[ index ];
 
-       };
+                       if ( controller === undefined ) {
 
-       this.setScissor = function ( x, y, width, height ) {
+                               controller = new WebXRController();
+                               controllers[ index ] = controller;
 
-               if ( x.isVector4 ) {
+                       }
 
-                       _scissor.set( x.x, x.y, x.z, x.w );
+                       return controller.getGripSpace();
 
-               } else {
+               };
 
-                       _scissor.set( x, y, width, height );
+               this.getHand = function ( index ) {
 
-               }
+                       let controller = controllers[ index ];
 
-               state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() );
+                       if ( controller === undefined ) {
 
-       };
+                               controller = new WebXRController();
+                               controllers[ index ] = controller;
 
-       this.getScissorTest = function () {
+                       }
 
-               return _scissorTest;
+                       return controller.getHandSpace();
 
-       };
+               };
 
-       this.setScissorTest = function ( boolean ) {
+               //
 
-               state.setScissorTest( _scissorTest = boolean );
+               function onSessionEvent( event ) {
 
-       };
+                       const controller = inputSourcesMap.get( event.inputSource );
 
-       this.setOpaqueSort = function ( method ) {
+                       if ( controller ) {
 
-               _opaqueSort = method;
+                               controller.dispatchEvent( { type: event.type, data: event.inputSource } );
 
-       };
+                       }
 
-       this.setTransparentSort = function ( method ) {
+               }
 
-               _transparentSort = method;
+               function onSessionEnd() {
 
-       };
+                       inputSourcesMap.forEach( function ( controller, inputSource ) {
 
-       // Clearing
+                               controller.disconnect( inputSource );
 
-       this.getClearColor = function ( target ) {
+                       } );
 
-               if ( target === undefined ) {
+                       inputSourcesMap.clear();
 
-                       console.warn( 'WebGLRenderer: .getClearColor() now requires a Color as an argument' );
+                       _currentDepthNear = null;
+                       _currentDepthFar = null;
 
-                       target = new Color();
+                       // restore framebuffer/rendering state
 
-               }
+                       state.bindXRFramebuffer( null );
+                       renderer.setRenderTarget( renderer.getRenderTarget() );
 
-               return target.copy( background.getClearColor() );
+                       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;
 
-       };
+                       //
 
-       this.setClearColor = function () {
+                       animation.stop();
 
-               background.setClearColor.apply( background, arguments );
+                       scope.isPresenting = false;
 
-       };
+                       scope.dispatchEvent( { type: 'sessionend' } );
 
-       this.getClearAlpha = function () {
+               }
 
-               return background.getClearAlpha();
+               this.setFramebufferScaleFactor = function ( value ) {
 
-       };
+                       framebufferScaleFactor = value;
 
-       this.setClearAlpha = function () {
+                       if ( scope.isPresenting === true ) {
 
-               background.setClearAlpha.apply( background, arguments );
+                               console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' );
 
-       };
+                       }
 
-       this.clear = function ( color, depth, stencil ) {
+               };
 
-               let bits = 0;
+               this.setReferenceSpaceType = function ( value ) {
 
-               if ( color === undefined || color ) bits |= 16384;
-               if ( depth === undefined || depth ) bits |= 256;
-               if ( stencil === undefined || stencil ) bits |= 1024;
+                       referenceSpaceType = value;
 
-               _gl.clear( bits );
+                       if ( scope.isPresenting === true ) {
 
-       };
+                               console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' );
 
-       this.clearColor = function () {
+                       }
 
-               this.clear( true, false, false );
+               };
 
-       };
+               this.getReferenceSpace = function () {
 
-       this.clearDepth = function () {
+                       return referenceSpace;
 
-               this.clear( false, true, false );
+               };
 
-       };
+               this.getBaseLayer = function () {
 
-       this.clearStencil = function () {
+                       return glProjLayer !== null ? glProjLayer : glBaseLayer;
 
-               this.clear( false, false, true );
+               };
 
-       };
+               this.getBinding = function () {
 
-       //
+                       return glBinding;
 
-       this.dispose = function () {
+               };
 
-               _canvas.removeEventListener( 'webglcontextlost', onContextLost, false );
-               _canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false );
+               this.getFrame = function () {
 
-               renderLists.dispose();
-               renderStates.dispose();
-               properties.dispose();
-               cubemaps.dispose();
-               objects.dispose();
-               bindingStates.dispose();
+                       return xrFrame;
 
-               xr.dispose();
+               };
 
-               animation.stop();
+               this.getSession = function () {
 
-       };
+                       return session;
 
-       // Events
+               };
 
-       function onContextLost( event ) {
+               this.setSession = async function ( value ) {
 
-               event.preventDefault();
+                       session = value;
 
-               console.log( 'THREE.WebGLRenderer: Context Lost.' );
+                       if ( session !== null ) {
 
-               _isContextLost = true;
+                               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();
 
-       function onContextRestore( /* event */ ) {
+                               if ( attributes.xrCompatible !== true ) {
 
-               console.log( 'THREE.WebGLRenderer: Context Restored.' );
+                                       await gl.makeXRCompatible();
 
-               _isContextLost = false;
+                               }
 
-               initGLContext();
+                               if ( session.renderState.layers === undefined ) {
 
-       }
+                                       const layerInit = {
+                                               antialias: attributes.antialias,
+                                               alpha: attributes.alpha,
+                                               depth: attributes.depth,
+                                               stencil: attributes.stencil,
+                                               framebufferScaleFactor: framebufferScaleFactor
+                                       };
 
-       function onMaterialDispose( event ) {
+                                       glBaseLayer = new XRWebGLLayer( session, gl, layerInit );
 
-               const material = event.target;
+                                       session.updateRenderState( { baseLayer: glBaseLayer } );
 
-               material.removeEventListener( 'dispose', onMaterialDispose );
+                               } else if ( gl instanceof WebGLRenderingContext ) {
 
-               deallocateMaterial( material );
+                                       // Use old style webgl layer because we can't use MSAA
+                                       // WebGL2 support.
 
-       }
+                                       const layerInit = {
+                                               antialias: true,
+                                               alpha: attributes.alpha,
+                                               depth: attributes.depth,
+                                               stencil: attributes.stencil,
+                                               framebufferScaleFactor: framebufferScaleFactor
+                                       };
 
-       // Buffer deallocation
+                                       glBaseLayer = new XRWebGLLayer( session, gl, layerInit );
 
-       function deallocateMaterial( material ) {
+                                       session.updateRenderState( { layers: [ glBaseLayer ] } );
+
+                               } else {
 
-               releaseMaterialProgramReference( material );
+                                       isMultisample = attributes.antialias;
+                                       let depthFormat = null;
 
-               properties.remove( material );
 
-       }
+                                       if ( attributes.depth ) {
 
+                                               clearStyle = 256;
 
-       function releaseMaterialProgramReference( material ) {
+                                               if ( attributes.stencil ) clearStyle |= 1024;
 
-               const programInfo = properties.get( material ).program;
+                                               depthStyle = attributes.stencil ? 33306 : 36096;
+                                               depthFormat = attributes.stencil ? 35056 : 33190;
 
-               if ( programInfo !== undefined ) {
+                                       }
 
-                       programCache.releaseProgram( programInfo );
+                                       const projectionlayerInit = {
+                                               colorFormat: attributes.alpha ? 32856 : 32849,
+                                               depthFormat: depthFormat,
+                                               scaleFactor: framebufferScaleFactor
+                                       };
 
-               }
+                                       glBinding = new XRWebGLBinding( session, gl );
 
-       }
+                                       glProjLayer = glBinding.createProjectionLayer( projectionlayerInit );
 
-       // Buffer rendering
+                                       glFramebuffer = gl.createFramebuffer();
 
-       function renderObjectImmediate( object, program ) {
+                                       session.updateRenderState( { layers: [ glProjLayer ] } );
 
-               object.render( function ( object ) {
+                                       if ( isMultisample ) {
 
-                       _this.renderBufferImmediate( object, program );
+                                               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 ) {
 
-       }
+                                                       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 );
 
-       this.renderBufferImmediate = function ( object, program ) {
+                                               }
 
-               bindingStates.initAttributes();
+                                               state.bindFramebuffer( 36160, null );
 
-               const buffers = properties.get( object );
+                                       }
 
-               if ( object.hasPositions && ! buffers.position ) buffers.position = _gl.createBuffer();
-               if ( object.hasNormals && ! buffers.normal ) buffers.normal = _gl.createBuffer();
-               if ( object.hasUvs && ! buffers.uv ) buffers.uv = _gl.createBuffer();
-               if ( object.hasColors && ! buffers.color ) buffers.color = _gl.createBuffer();
+                               }
 
-               const programAttributes = program.getAttributes();
+                               referenceSpace = await session.requestReferenceSpace( referenceSpaceType );
 
-               if ( object.hasPositions ) {
+                               animation.setContext( session );
+                               animation.start();
 
-                       _gl.bindBuffer( 34962, buffers.position );
-                       _gl.bufferData( 34962, object.positionArray, 35048 );
+                               scope.isPresenting = true;
 
-                       bindingStates.enableAttribute( programAttributes.position );
-                       _gl.vertexAttribPointer( programAttributes.position, 3, 5126, false, 0, 0 );
+                               scope.dispatchEvent( { type: 'sessionstart' } );
 
-               }
+                       }
 
-               if ( object.hasNormals ) {
+               };
 
-                       _gl.bindBuffer( 34962, buffers.normal );
-                       _gl.bufferData( 34962, object.normalArray, 35048 );
+               function onInputSourcesChange( event ) {
 
-                       bindingStates.enableAttribute( programAttributes.normal );
-                       _gl.vertexAttribPointer( programAttributes.normal, 3, 5126, false, 0, 0 );
+                       const inputSources = session.inputSources;
 
-               }
+                       // Assign inputSources to available controllers
 
-               if ( object.hasUvs ) {
+                       for ( let i = 0; i < controllers.length; i ++ ) {
 
-                       _gl.bindBuffer( 34962, buffers.uv );
-                       _gl.bufferData( 34962, object.uvArray, 35048 );
+                               inputSourcesMap.set( inputSources[ i ], controllers[ i ] );
 
-                       bindingStates.enableAttribute( programAttributes.uv );
-                       _gl.vertexAttribPointer( programAttributes.uv, 2, 5126, false, 0, 0 );
+                       }
 
-               }
+                       // Notify disconnected
 
-               if ( object.hasColors ) {
+                       for ( let i = 0; i < event.removed.length; i ++ ) {
 
-                       _gl.bindBuffer( 34962, buffers.color );
-                       _gl.bufferData( 34962, object.colorArray, 35048 );
+                               const inputSource = event.removed[ i ];
+                               const controller = inputSourcesMap.get( inputSource );
 
-                       bindingStates.enableAttribute( programAttributes.color );
-                       _gl.vertexAttribPointer( programAttributes.color, 3, 5126, false, 0, 0 );
+                               if ( controller ) {
 
-               }
+                                       controller.dispatchEvent( { type: 'disconnected', data: inputSource } );
+                                       inputSourcesMap.delete( inputSource );
 
-               bindingStates.disableUnusedAttributes();
+                               }
 
-               _gl.drawArrays( 4, 0, object.count );
+                       }
 
-               object.count = 0;
+                       // Notify connected
 
-       };
+                       for ( let i = 0; i < event.added.length; i ++ ) {
 
-       this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) {
+                               const inputSource = event.added[ i ];
+                               const controller = inputSourcesMap.get( inputSource );
 
-               if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null)
+                               if ( controller ) {
 
-               const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );
+                                       controller.dispatchEvent( { type: 'connected', data: inputSource } );
+
+                               }
 
-               const program = setProgram( camera, scene, material, object );
+                       }
 
-               state.setMaterial( material, frontFaceCW );
+               }
 
                //
 
-               let index = geometry.index;
-               const position = geometry.attributes.position;
+               const cameraLPos = new Vector3();
+               const cameraRPos = new Vector3();
 
-               //
+               /**
+                * Assumes 2 cameras that are parallel and share an X-axis, and that
+                * the cameras' projection and world matrices have already been set.
+                * And that near and far planes are identical for both cameras.
+                * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
+                */
+               function setProjectionFromUnion( camera, cameraL, cameraR ) {
 
-               if ( index === null ) {
+                       cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
+                       cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );
 
-                       if ( position === undefined || position.count === 0 ) return;
+                       const ipd = cameraLPos.distanceTo( cameraRPos );
 
-               } else if ( index.count === 0 ) {
+                       const projL = cameraL.projectionMatrix.elements;
+                       const projR = cameraR.projectionMatrix.elements;
 
-                       return;
+                       // VR systems will have identical far and near planes, and
+                       // most likely identical top and bottom frustum extents.
+                       // Use the left camera for these values.
+                       const near = projL[ 14 ] / ( projL[ 10 ] - 1 );
+                       const far = projL[ 14 ] / ( projL[ 10 ] + 1 );
+                       const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ];
+                       const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ];
 
-               }
+                       const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
+                       const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
+                       const left = near * leftFov;
+                       const right = near * rightFov;
 
-               //
+                       // Calculate the new camera's position offset from the
+                       // left camera. xOffset should be roughly half `ipd`.
+                       const zOffset = ipd / ( - leftFov + rightFov );
+                       const xOffset = zOffset * - leftFov;
 
-               let rangeFactor = 1;
+                       // 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 ( material.wireframe === true ) {
+                       // 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;
 
-                       index = geometries.getWireframeAttribute( geometry );
-                       rangeFactor = 2;
+                       camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );
 
                }
 
-               if ( material.morphTargets || material.morphNormals ) {
-
-                       morphtargets.update( object, geometry, material, program );
+               function updateCamera( camera, parent ) {
 
-               }
+                       if ( parent === null ) {
 
-               bindingStates.setup( object, material, program, geometry, index );
+                               camera.matrixWorld.copy( camera.matrix );
 
-               let attribute;
-               let renderer = bufferRenderer;
+                       } else {
 
-               if ( index !== null ) {
+                               camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix );
 
-                       attribute = attributes.get( index );
+                       }
 
-                       renderer = indexedBufferRenderer;
-                       renderer.setIndex( attribute );
+                       camera.matrixWorldInverse.copy( camera.matrixWorld ).invert();
 
                }
 
-               //
-
-               const dataCount = ( index !== null ) ? index.count : position.count;
+               this.updateCamera = function ( camera ) {
 
-               const rangeStart = geometry.drawRange.start * rangeFactor;
-               const rangeCount = geometry.drawRange.count * rangeFactor;
+                       if ( session === null ) return;
 
-               const groupStart = group !== null ? group.start * rangeFactor : 0;
-               const groupCount = group !== null ? group.count * rangeFactor : Infinity;
+                       cameraVR.near = cameraR.near = cameraL.near = camera.near;
+                       cameraVR.far = cameraR.far = cameraL.far = camera.far;
 
-               const drawStart = Math.max( rangeStart, groupStart );
-               const drawEnd = Math.min( dataCount, rangeStart + rangeCount, groupStart + groupCount ) - 1;
+                       if ( _currentDepthNear !== cameraVR.near || _currentDepthFar !== cameraVR.far ) {
 
-               const drawCount = Math.max( 0, drawEnd - drawStart + 1 );
+                               // Note that the new renderState won't apply until the next frame. See #18320
 
-               if ( drawCount === 0 ) return;
+                               session.updateRenderState( {
+                                       depthNear: cameraVR.near,
+                                       depthFar: cameraVR.far
+                               } );
 
-               //
+                               _currentDepthNear = cameraVR.near;
+                               _currentDepthFar = cameraVR.far;
 
-               if ( object.isMesh ) {
+                       }
 
-                       if ( material.wireframe === true ) {
+                       const parent = camera.parent;
+                       const cameras = cameraVR.cameras;
 
-                               state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() );
-                               renderer.setMode( 1 );
+                       updateCamera( cameraVR, parent );
 
-                       } else {
+                       for ( let i = 0; i < cameras.length; i ++ ) {
 
-                               renderer.setMode( 4 );
+                               updateCamera( cameras[ i ], parent );
 
                        }
 
-               } else if ( object.isLine ) {
+                       cameraVR.matrixWorld.decompose( cameraVR.position, cameraVR.quaternion, cameraVR.scale );
 
-                       let lineWidth = material.linewidth;
+                       // update user camera and its children
 
-                       if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material
+                       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 );
 
-                       state.setLineWidth( lineWidth * getTargetPixelRatio() );
+                       const children = camera.children;
 
-                       if ( object.isLineSegments ) {
+                       for ( let i = 0, l = children.length; i < l; i ++ ) {
 
-                               renderer.setMode( 1 );
+                               children[ i ].updateMatrixWorld( true );
 
-                       } else if ( object.isLineLoop ) {
+                       }
 
-                               renderer.setMode( 2 );
+                       // update projection matrix for proper view frustum culling
 
-                       } else {
+                       if ( cameras.length === 2 ) {
 
-                               renderer.setMode( 3 );
+                               setProjectionFromUnion( cameraVR, cameraL, cameraR );
 
-                       }
+                       } else {
 
-               } else if ( object.isPoints ) {
+                               // assume single camera setup (AR)
 
-                       renderer.setMode( 0 );
+                               cameraVR.projectionMatrix.copy( cameraL.projectionMatrix );
 
-               } else if ( object.isSprite ) {
+                       }
 
-                       renderer.setMode( 4 );
+               };
 
-               }
+               this.getCamera = function () {
 
-               if ( object.isInstancedMesh ) {
+                       return cameraVR;
 
-                       renderer.renderInstances( drawStart, drawCount, object.count );
+               };
 
-               } else if ( geometry.isInstancedBufferGeometry ) {
+               this.getFoveation = function () {
 
-                       const instanceCount = Math.min( geometry.instanceCount, geometry._maxInstanceCount );
+                       if ( glProjLayer !== null ) {
 
-                       renderer.renderInstances( drawStart, drawCount, instanceCount );
+                               return glProjLayer.fixedFoveation;
 
-               } else {
+                       }
 
-                       renderer.render( drawStart, drawCount );
+                       if ( glBaseLayer !== null ) {
 
-               }
+                               return glBaseLayer.fixedFoveation;
 
-       };
+                       }
 
-       // Compile
+                       return undefined;
 
-       this.compile = function ( scene, camera ) {
+               };
 
-               currentRenderState = renderStates.get( scene );
-               currentRenderState.init();
+               this.setFoveation = function ( foveation ) {
 
-               scene.traverseVisible( function ( object ) {
+                       // 0 = no foveation = full resolution
+                       // 1 = maximum foveation = the edges render at lower resolution
 
-                       if ( object.isLight && object.layers.test( camera.layers ) ) {
+                       if ( glProjLayer !== null ) {
 
-                               currentRenderState.pushLight( object );
+                               glProjLayer.fixedFoveation = foveation;
 
-                               if ( object.castShadow ) {
+                       }
 
-                                       currentRenderState.pushShadow( object );
+                       if ( glBaseLayer !== null && glBaseLayer.fixedFoveation !== undefined ) {
 
-                               }
+                               glBaseLayer.fixedFoveation = foveation;
 
                        }
 
-               } );
+               };
 
-               currentRenderState.setupLights();
+               // Animation Loop
 
-               const compiled = new WeakMap();
+               let onAnimationFrameCallback = null;
 
-               scene.traverse( function ( object ) {
+               function onAnimationFrame( time, frame ) {
 
-                       const material = object.material;
+                       pose = frame.getViewerPose( referenceSpace );
+                       xrFrame = frame;
 
-                       if ( material ) {
+                       if ( pose !== null ) {
 
-                               if ( Array.isArray( material ) ) {
+                               const views = pose.views;
 
-                                       for ( let i = 0; i < material.length; i ++ ) {
+                               if ( glBaseLayer !== null ) {
 
-                                               const material2 = material[ i ];
+                                       state.bindXRFramebuffer( glBaseLayer.framebuffer );
 
-                                               if ( compiled.has( material2 ) === false ) {
+                               }
 
-                                                       initMaterial( material2, scene, object );
-                                                       compiled.set( material2 );
+                               let cameraVRNeedsUpdate = false;
 
-                                               }
+                               // check if it's necessary to rebuild cameraVR's camera list
 
-                                       }
+                               if ( views.length !== cameraVR.cameras.length ) {
 
-                               } else if ( compiled.has( material ) === false ) {
+                                       cameraVR.cameras.length = 0;
 
-                                       initMaterial( material, scene, object );
-                                       compiled.set( material );
+                                       cameraVRNeedsUpdate = true;
 
                                }
 
-                       }
+                               for ( let i = 0; i < views.length; i ++ ) {
 
-               } );
+                                       const view = views[ i ];
 
-       };
+                                       let viewport = null;
 
-       // Animation Loop
+                                       if ( glBaseLayer !== null ) {
 
-       let onAnimationFrameCallback = null;
+                                               viewport = glBaseLayer.getViewport( view );
 
-       function onAnimationFrame( time ) {
+                                       } else {
 
-               if ( xr.isPresenting ) return;
-               if ( onAnimationFrameCallback ) onAnimationFrameCallback( time );
+                                               const glSubImage = glBinding.getViewSubImage( glProjLayer, view );
 
-       }
+                                               state.bindXRFramebuffer( glFramebuffer );
 
-       const animation = new WebGLAnimation();
-       animation.setAnimationLoop( onAnimationFrame );
+                                               if ( glSubImage.depthStencilTexture !== undefined ) {
 
-       if ( typeof window !== 'undefined' ) animation.setContext( window );
+                                                       gl.framebufferTexture2D( 36160, depthStyle, 3553, glSubImage.depthStencilTexture, 0 );
 
-       this.setAnimationLoop = function ( callback ) {
+                                               }
 
-               onAnimationFrameCallback = callback;
-               xr.setAnimationLoop( callback );
+                                               gl.framebufferTexture2D( 36160, 36064, 3553, glSubImage.colorTexture, 0 );
 
-               ( callback === null ) ? animation.stop() : animation.start();
+                                               viewport = glSubImage.viewport;
 
-       };
+                                       }
 
-       // Rendering
+                                       const camera = cameras[ i ];
 
-       this.render = function ( scene, camera ) {
+                                       camera.matrix.fromArray( view.transform.matrix );
+                                       camera.projectionMatrix.fromArray( view.projectionMatrix );
+                                       camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height );
 
-               let renderTarget, forceClear;
+                                       if ( i === 0 ) {
 
-               if ( arguments[ 2 ] !== undefined ) {
+                                               cameraVR.matrix.copy( camera.matrix );
 
-                       console.warn( 'THREE.WebGLRenderer.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.' );
-                       renderTarget = arguments[ 2 ];
+                                       }
 
-               }
+                                       if ( cameraVRNeedsUpdate === true ) {
 
-               if ( arguments[ 3 ] !== undefined ) {
+                                               cameraVR.cameras.push( camera );
 
-                       console.warn( 'THREE.WebGLRenderer.render(): the forceClear argument has been removed. Use .clear() instead.' );
-                       forceClear = arguments[ 3 ];
+                                       }
 
-               }
+                               }
 
-               if ( camera !== undefined && camera.isCamera !== true ) {
+                               if ( isMultisample ) {
 
-                       console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' );
-                       return;
+                                       state.bindXRFramebuffer( glMultisampledFramebuffer );
 
-               }
+                                       if ( clearStyle !== null ) gl.clear( clearStyle );
 
-               if ( _isContextLost === true ) return;
+                               }
 
-               // reset caching for this frame
+                       }
 
-               bindingStates.resetDefaultState();
-               _currentMaterialId = - 1;
-               _currentCamera = null;
+                       //
 
-               // update scene graph
+                       const inputSources = session.inputSources;
 
-               if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+                       for ( let i = 0; i < controllers.length; i ++ ) {
 
-               // update camera matrices and frustum
+                               const controller = controllers[ i ];
+                               const inputSource = inputSources[ i ];
 
-               if ( camera.parent === null ) camera.updateMatrixWorld();
+                               controller.update( inputSource, frame, referenceSpace );
 
-               if ( xr.enabled === true && xr.isPresenting === true ) {
+                       }
 
-                       camera = xr.getCamera( camera );
+                       if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame );
 
-               }
+                       if ( isMultisample ) {
 
-               //
-               if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, renderTarget || _currentRenderTarget );
+                               const width = glProjLayer.textureWidth;
+                               const height = glProjLayer.textureHeight;
 
-               currentRenderState = renderStates.get( scene, renderStateStack.length );
-               currentRenderState.init();
+                               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 );
 
-               renderStateStack.push( currentRenderState );
+                               state.bindFramebuffer( 36160, glMultisampledFramebuffer );
 
-               _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
-               _frustum.setFromProjectionMatrix( _projScreenMatrix );
+                       }
 
-               _localClippingEnabled = this.localClippingEnabled;
-               _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled, camera );
+                       xrFrame = null;
 
-               currentRenderList = renderLists.get( scene, camera );
-               currentRenderList.init();
+               }
 
-               projectObject( scene, camera, 0, _this.sortObjects );
+               const animation = new WebGLAnimation();
 
-               currentRenderList.finish();
+               animation.setAnimationLoop( onAnimationFrame );
 
-               if ( _this.sortObjects === true ) {
+               this.setAnimationLoop = function ( callback ) {
 
-                       currentRenderList.sort( _opaqueSort, _transparentSort );
+                       onAnimationFrameCallback = callback;
 
-               }
+               };
 
-               //
+               this.dispose = function () {};
 
-               if ( _clippingEnabled === true ) clipping.beginShadows();
+       }
 
-               const shadowsArray = currentRenderState.state.shadowsArray;
+    }
 
-               shadowMap.render( shadowsArray, scene, camera );
+    function WebGLMaterials( properties ) {
 
-               currentRenderState.setupLights();
-               currentRenderState.setupLightsView( camera );
+       function refreshFogUniforms( uniforms, fog ) {
 
-               if ( _clippingEnabled === true ) clipping.endShadows();
+               uniforms.fogColor.value.copy( fog.color );
 
-               //
+               if ( fog.isFog ) {
 
-               if ( this.info.autoReset === true ) this.info.reset();
+                       uniforms.fogNear.value = fog.near;
+                       uniforms.fogFar.value = fog.far;
 
-               if ( renderTarget !== undefined ) {
+               } else if ( fog.isFogExp2 ) {
 
-                       this.setRenderTarget( renderTarget );
+                       uniforms.fogDensity.value = fog.density;
 
                }
 
-               //
+       }
 
-               background.render( currentRenderList, scene, camera, forceClear );
+       function refreshMaterialUniforms( uniforms, material, pixelRatio, height, transmissionRenderTarget ) {
 
-               // render scene
+               if ( material.isMeshBasicMaterial ) {
 
-               const opaqueObjects = currentRenderList.opaque;
-               const transparentObjects = currentRenderList.transparent;
+                       refreshUniformsCommon( uniforms, material );
 
-               if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera );
-               if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera );
+               } else if ( material.isMeshLambertMaterial ) {
 
-               //
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsLambert( uniforms, material );
 
-               if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera );
+               } else if ( material.isMeshToonMaterial ) {
 
-               //
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsToon( uniforms, material );
 
-               if ( _currentRenderTarget !== null ) {
+               } else if ( material.isMeshPhongMaterial ) {
 
-                       // Generate mipmap if we're using any kind of mipmap filtering
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsPhong( uniforms, material );
 
-                       textures.updateRenderTargetMipmap( _currentRenderTarget );
+               } else if ( material.isMeshStandardMaterial ) {
 
-                       // resolve multisample renderbuffers to a single-sample texture if necessary
+                       refreshUniformsCommon( uniforms, material );
 
-                       textures.updateMultisampleRenderTarget( _currentRenderTarget );
+                       if ( material.isMeshPhysicalMaterial ) {
 
-               }
+                               refreshUniformsPhysical( uniforms, material, transmissionRenderTarget );
 
-               // Ensure depth buffer writing is enabled so it can be cleared on next render
+                       } else {
 
-               state.buffers.depth.setTest( true );
-               state.buffers.depth.setMask( true );
-               state.buffers.color.setMask( true );
+                               refreshUniformsStandard( uniforms, material );
 
-               state.setPolygonOffset( false );
+                       }
 
-               // _gl.finish();
+               } else if ( material.isMeshMatcapMaterial ) {
 
-               renderStateStack.pop();
-               if ( renderStateStack.length > 0 ) {
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsMatcap( uniforms, material );
 
-                       currentRenderState = renderStateStack[ renderStateStack.length - 1 ];
+               } else if ( material.isMeshDepthMaterial ) {
 
-               } else {
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsDepth( uniforms, material );
 
-                       currentRenderState = null;
+               } else if ( material.isMeshDistanceMaterial ) {
 
-               }
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsDistance( uniforms, material );
 
-               currentRenderList = null;
+               } else if ( material.isMeshNormalMaterial ) {
 
-       };
+                       refreshUniformsCommon( uniforms, material );
+                       refreshUniformsNormal( uniforms, material );
 
-       function projectObject( object, camera, groupOrder, sortObjects ) {
+               } else if ( material.isLineBasicMaterial ) {
 
-               if ( object.visible === false ) return;
+                       refreshUniformsLine( uniforms, material );
 
-               const visible = object.layers.test( camera.layers );
+                       if ( material.isLineDashedMaterial ) {
 
-               if ( visible ) {
+                               refreshUniformsDash( uniforms, material );
 
-                       if ( object.isGroup ) {
+                       }
 
-                               groupOrder = object.renderOrder;
+               } else if ( material.isPointsMaterial ) {
 
-                       } else if ( object.isLOD ) {
+                       refreshUniformsPoints( uniforms, material, pixelRatio, height );
 
-                               if ( object.autoUpdate === true ) object.update( camera );
+               } else if ( material.isSpriteMaterial ) {
 
-                       } else if ( object.isLight ) {
+                       refreshUniformsSprites( uniforms, material );
 
-                               currentRenderState.pushLight( object );
+               } else if ( material.isShadowMaterial ) {
 
-                               if ( object.castShadow ) {
+                       uniforms.color.value.copy( material.color );
+                       uniforms.opacity.value = material.opacity;
 
-                                       currentRenderState.pushShadow( object );
+               } else if ( material.isShaderMaterial ) {
 
-                               }
+                       material.uniformsNeedUpdate = false; // #15581
 
-                       } else if ( object.isSprite ) {
+               }
 
-                               if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {
+       }
 
-                                       if ( sortObjects ) {
+       function refreshUniformsCommon( uniforms, material ) {
 
-                                               _vector3.setFromMatrixPosition( object.matrixWorld )
-                                                       .applyMatrix4( _projScreenMatrix );
+               uniforms.opacity.value = material.opacity;
 
-                                       }
+               if ( material.color ) {
 
-                                       const geometry = objects.update( object );
-                                       const material = object.material;
+                       uniforms.diffuse.value.copy( material.color );
 
-                                       if ( material.visible ) {
+               }
 
-                                               currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+               if ( material.emissive ) {
 
-                                       }
+                       uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity );
 
-                               }
+               }
 
-                       } else if ( object.isImmediateRenderObject ) {
+               if ( material.map ) {
 
-                               if ( sortObjects ) {
+                       uniforms.map.value = material.map;
 
-                                       _vector3.setFromMatrixPosition( object.matrixWorld )
-                                               .applyMatrix4( _projScreenMatrix );
+               }
 
-                               }
+               if ( material.alphaMap ) {
 
-                               currentRenderList.push( object, null, object.material, groupOrder, _vector3.z, null );
+                       uniforms.alphaMap.value = material.alphaMap;
 
-                       } else if ( object.isMesh || object.isLine || object.isPoints ) {
+               }
 
-                               if ( object.isSkinnedMesh ) {
+               if ( material.specularMap ) {
 
-                                       // update skeleton only once in a frame
+                       uniforms.specularMap.value = material.specularMap;
 
-                                       if ( object.skeleton.frame !== info.render.frame ) {
+               }
 
-                                               object.skeleton.update();
-                                               object.skeleton.frame = info.render.frame;
+               if ( material.alphaTest > 0 ) {
 
-                                       }
+                       uniforms.alphaTest.value = material.alphaTest;
 
-                               }
+               }
 
-                               if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {
+               const envMap = properties.get( material ).envMap;
 
-                                       if ( sortObjects ) {
+               if ( envMap ) {
 
-                                               _vector3.setFromMatrixPosition( object.matrixWorld )
-                                                       .applyMatrix4( _projScreenMatrix );
+                       uniforms.envMap.value = envMap;
 
-                                       }
+                       uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap.isRenderTargetTexture === false ) ? - 1 : 1;
 
-                                       const geometry = objects.update( object );
-                                       const material = object.material;
+                       uniforms.reflectivity.value = material.reflectivity;
+                       uniforms.ior.value = material.ior;
+                       uniforms.refractionRatio.value = material.refractionRatio;
 
-                                       if ( Array.isArray( material ) ) {
+                       const maxMipLevel = properties.get( envMap ).__maxMipLevel;
 
-                                               const groups = geometry.groups;
+                       if ( maxMipLevel !== undefined ) {
 
-                                               for ( let i = 0, l = groups.length; i < l; i ++ ) {
+                               uniforms.maxMipLevel.value = maxMipLevel;
 
-                                                       const group = groups[ i ];
-                                                       const groupMaterial = material[ group.materialIndex ];
+                       }
 
-                                                       if ( groupMaterial && groupMaterial.visible ) {
+               }
 
-                                                               currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group );
+               if ( material.lightMap ) {
 
-                                                       }
+                       uniforms.lightMap.value = material.lightMap;
+                       uniforms.lightMapIntensity.value = material.lightMapIntensity;
 
-                                               }
+               }
 
-                                       } else if ( material.visible ) {
+               if ( material.aoMap ) {
 
-                                               currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+                       uniforms.aoMap.value = material.aoMap;
+                       uniforms.aoMapIntensity.value = material.aoMapIntensity;
 
-                                       }
+               }
 
-                               }
+               // uv repeat and offset setting priorities
+               // 1. color map
+               // 2. specular map
+               // 3. displacementMap map
+               // 4. normal map
+               // 5. bump map
+               // 6. roughnessMap map
+               // 7. metalnessMap map
+               // 8. alphaMap map
+               // 9. emissiveMap map
+               // 10. clearcoat map
+               // 11. clearcoat normal map
+               // 12. clearcoat roughnessMap map
+               // 13. specular intensity map
+               // 14. specular tint map
+               // 15. transmission map
+               // 16. thickness map
 
-                       }
+               let uvScaleMap;
 
-               }
+               if ( material.map ) {
 
-               const children = object.children;
+                       uvScaleMap = material.map;
 
-               for ( let i = 0, l = children.length; i < l; i ++ ) {
+               } else if ( material.specularMap ) {
 
-                       projectObject( children[ i ], camera, groupOrder, sortObjects );
+                       uvScaleMap = material.specularMap;
 
-               }
+               } else if ( material.displacementMap ) {
 
-       }
+                       uvScaleMap = material.displacementMap;
 
-       function renderObjects( renderList, scene, camera ) {
+               } else if ( material.normalMap ) {
 
-               const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null;
+                       uvScaleMap = material.normalMap;
 
-               for ( let i = 0, l = renderList.length; i < l; i ++ ) {
+               } else if ( material.bumpMap ) {
 
-                       const renderItem = renderList[ i ];
+                       uvScaleMap = material.bumpMap;
 
-                       const object = renderItem.object;
-                       const geometry = renderItem.geometry;
-                       const material = overrideMaterial === null ? renderItem.material : overrideMaterial;
-                       const group = renderItem.group;
+               } else if ( material.roughnessMap ) {
 
-                       if ( camera.isArrayCamera ) {
+                       uvScaleMap = material.roughnessMap;
 
-                               const cameras = camera.cameras;
+               } else if ( material.metalnessMap ) {
 
-                               for ( let j = 0, jl = cameras.length; j < jl; j ++ ) {
+                       uvScaleMap = material.metalnessMap;
 
-                                       const camera2 = cameras[ j ];
+               } else if ( material.alphaMap ) {
 
-                                       if ( object.layers.test( camera2.layers ) ) {
+                       uvScaleMap = material.alphaMap;
 
-                                               state.viewport( _currentViewport.copy( camera2.viewport ) );
+               } else if ( material.emissiveMap ) {
 
-                                               currentRenderState.setupLightsView( camera2 );
+                       uvScaleMap = material.emissiveMap;
 
-                                               renderObject( object, scene, camera2, geometry, material, group );
+               } else if ( material.clearcoatMap ) {
 
-                                       }
+                       uvScaleMap = material.clearcoatMap;
 
-                               }
+               } else if ( material.clearcoatNormalMap ) {
 
-                       } else {
+                       uvScaleMap = material.clearcoatNormalMap;
 
-                               renderObject( object, scene, camera, geometry, material, group );
+               } else if ( material.clearcoatRoughnessMap ) {
 
-                       }
+                       uvScaleMap = material.clearcoatRoughnessMap;
 
-               }
+               } else if ( material.specularIntensityMap ) {
 
-       }
+                       uvScaleMap = material.specularIntensityMap;
 
-       function renderObject( object, scene, camera, geometry, material, group ) {
+               } else if ( material.specularColorMap ) {
 
-               object.onBeforeRender( _this, scene, camera, geometry, material, group );
+                       uvScaleMap = material.specularColorMap;
 
-               object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
-               object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
+               } else if ( material.transmissionMap ) {
 
-               if ( object.isImmediateRenderObject ) {
+                       uvScaleMap = material.transmissionMap;
 
-                       const program = setProgram( camera, scene, material, object );
+               } else if ( material.thicknessMap ) {
 
-                       state.setMaterial( material );
+                       uvScaleMap = material.thicknessMap;
 
-                       bindingStates.reset();
+               } else if ( material.sheenColorMap ) {
 
-                       renderObjectImmediate( object, program );
+                       uvScaleMap = material.sheenColorMap;
 
-               } else {
+               } else if ( material.sheenRoughnessMap ) {
 
-                       _this.renderBufferDirect( camera, scene, geometry, material, object, group );
+                       uvScaleMap = material.sheenRoughnessMap;
 
                }
 
-               object.onAfterRender( _this, scene, camera, geometry, material, group );
+               if ( uvScaleMap !== undefined ) {
 
-       }
+                       // backwards compatibility
+                       if ( uvScaleMap.isWebGLRenderTarget ) {
 
-       function initMaterial( material, scene, object ) {
+                               uvScaleMap = uvScaleMap.texture;
 
-               if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
+                       }
 
-               const materialProperties = properties.get( material );
+                       if ( uvScaleMap.matrixAutoUpdate === true ) {
 
-               const lights = currentRenderState.state.lights;
-               const shadowsArray = currentRenderState.state.shadowsArray;
+                               uvScaleMap.updateMatrix();
 
-               const lightsStateVersion = lights.state.version;
+                       }
 
-               const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object );
-               const programCacheKey = programCache.getProgramCacheKey( parameters );
+                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
 
-               let program = materialProperties.program;
-               let programChange = true;
+               }
 
-               // always update environment and fog - changing these trigger an initMaterial call, but it's possible that the program doesn't change
+               // uv repeat and offset setting priorities for uv2
+               // 1. ao map
+               // 2. light map
 
-               materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null;
-               materialProperties.fog = scene.fog;
-               materialProperties.envMap = cubemaps.get( material.envMap || materialProperties.environment );
+               let uv2ScaleMap;
 
-               if ( program === undefined ) {
+               if ( material.aoMap ) {
 
-                       // new material
-                       material.addEventListener( 'dispose', onMaterialDispose );
+                       uv2ScaleMap = material.aoMap;
 
-               } else if ( program.cacheKey !== programCacheKey ) {
+               } else if ( material.lightMap ) {
 
-                       // changed glsl or parameters
-                       releaseMaterialProgramReference( material );
+                       uv2ScaleMap = material.lightMap;
 
-               } else if ( materialProperties.lightsStateVersion !== lightsStateVersion ) {
+               }
 
-                       programChange = false;
+               if ( uv2ScaleMap !== undefined ) {
 
-               } else if ( parameters.shaderID !== undefined ) {
+                       // backwards compatibility
+                       if ( uv2ScaleMap.isWebGLRenderTarget ) {
 
-                       // same glsl and uniform list
-                       return;
+                               uv2ScaleMap = uv2ScaleMap.texture;
 
-               } else {
+                       }
 
-                       // only rebuild uniform list
-                       programChange = false;
+                       if ( uv2ScaleMap.matrixAutoUpdate === true ) {
 
-               }
+                               uv2ScaleMap.updateMatrix();
 
-               if ( programChange ) {
+                       }
 
-                       parameters.uniforms = programCache.getUniforms( material );
+                       uniforms.uv2Transform.value.copy( uv2ScaleMap.matrix );
 
-                       material.onBeforeCompile( parameters, _this );
+               }
 
-                       program = programCache.acquireProgram( parameters, programCacheKey );
+       }
 
-                       materialProperties.program = program;
-                       materialProperties.uniforms = parameters.uniforms;
-                       materialProperties.outputEncoding = parameters.outputEncoding;
+       function refreshUniformsLine( uniforms, material ) {
 
-               }
+               uniforms.diffuse.value.copy( material.color );
+               uniforms.opacity.value = material.opacity;
 
-               const uniforms = materialProperties.uniforms;
+       }
 
-               if ( ! material.isShaderMaterial &&
-                       ! material.isRawShaderMaterial ||
-                       material.clipping === true ) {
+       function refreshUniformsDash( uniforms, material ) {
 
-                       materialProperties.numClippingPlanes = clipping.numPlanes;
-                       materialProperties.numIntersection = clipping.numIntersection;
-                       uniforms.clippingPlanes = clipping.uniform;
+               uniforms.dashSize.value = material.dashSize;
+               uniforms.totalSize.value = material.dashSize + material.gapSize;
+               uniforms.scale.value = material.scale;
 
-               }
+       }
 
-               // store the light setup it was created for
+       function refreshUniformsPoints( uniforms, material, pixelRatio, height ) {
 
-               materialProperties.needsLights = materialNeedsLights( material );
-               materialProperties.lightsStateVersion = lightsStateVersion;
+               uniforms.diffuse.value.copy( material.color );
+               uniforms.opacity.value = material.opacity;
+               uniforms.size.value = material.size * pixelRatio;
+               uniforms.scale.value = height * 0.5;
 
-               if ( materialProperties.needsLights ) {
+               if ( material.map ) {
 
-                       // wire up the material to this renderer's lighting state
+                       uniforms.map.value = material.map;
 
-                       uniforms.ambientLightColor.value = lights.state.ambient;
-                       uniforms.lightProbe.value = lights.state.probe;
-                       uniforms.directionalLights.value = lights.state.directional;
-                       uniforms.directionalLightShadows.value = lights.state.directionalShadow;
-                       uniforms.spotLights.value = lights.state.spot;
-                       uniforms.spotLightShadows.value = lights.state.spotShadow;
-                       uniforms.rectAreaLights.value = lights.state.rectArea;
-                       uniforms.ltc_1.value = lights.state.rectAreaLTC1;
-                       uniforms.ltc_2.value = lights.state.rectAreaLTC2;
-                       uniforms.pointLights.value = lights.state.point;
-                       uniforms.pointLightShadows.value = lights.state.pointShadow;
-                       uniforms.hemisphereLights.value = lights.state.hemi;
+               }
 
-                       uniforms.directionalShadowMap.value = lights.state.directionalShadowMap;
-                       uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix;
-                       uniforms.spotShadowMap.value = lights.state.spotShadowMap;
-                       uniforms.spotShadowMatrix.value = lights.state.spotShadowMatrix;
-                       uniforms.pointShadowMap.value = lights.state.pointShadowMap;
-                       uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix;
-                       // TODO (abelnation): add area lights shadow info to uniforms
+               if ( material.alphaMap ) {
+
+                       uniforms.alphaMap.value = material.alphaMap;
 
                }
 
-               const progUniforms = materialProperties.program.getUniforms();
-               const uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, uniforms );
+               if ( material.alphaTest > 0 ) {
 
-               materialProperties.uniformsList = uniformsList;
+                       uniforms.alphaTest.value = material.alphaTest;
 
-       }
+               }
 
-       function setProgram( camera, scene, material, object ) {
+               // uv repeat and offset setting priorities
+               // 1. color map
+               // 2. alpha map
 
-               if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
+               let uvScaleMap;
 
-               textures.resetTextureUnits();
+               if ( material.map ) {
 
-               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 );
+                       uvScaleMap = material.map;
 
-               const materialProperties = properties.get( material );
-               const lights = currentRenderState.state.lights;
+               } else if ( material.alphaMap ) {
 
-               if ( _clippingEnabled === true ) {
+                       uvScaleMap = material.alphaMap;
 
-                       if ( _localClippingEnabled === true || camera !== _currentCamera ) {
+               }
 
-                               const useCache =
-                                       camera === _currentCamera &&
-                                       material.id === _currentMaterialId;
+               if ( uvScaleMap !== undefined ) {
 
-                               // we might want to call this function with some ClippingGroup
-                               // object instead of the material, once it becomes feasible
-                               // (#8465, #8379)
-                               clipping.setState( material, camera, useCache );
+                       if ( uvScaleMap.matrixAutoUpdate === true ) {
+
+                               uvScaleMap.updateMatrix();
 
                        }
 
+                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+
                }
 
-               if ( material.version === materialProperties.__version ) {
+       }
 
-                       if ( material.fog && materialProperties.fog !== fog ) {
+       function refreshUniformsSprites( uniforms, material ) {
 
-                               initMaterial( material, scene, object );
+               uniforms.diffuse.value.copy( material.color );
+               uniforms.opacity.value = material.opacity;
+               uniforms.rotation.value = material.rotation;
 
-                       } else if ( materialProperties.environment !== environment ) {
+               if ( material.map ) {
 
-                               initMaterial( material, scene, object );
+                       uniforms.map.value = material.map;
 
-                       } else if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) {
+               }
 
-                               initMaterial( material, scene, object );
+               if ( material.alphaMap ) {
 
-                       } else if ( materialProperties.numClippingPlanes !== undefined &&
-                               ( materialProperties.numClippingPlanes !== clipping.numPlanes ||
-                               materialProperties.numIntersection !== clipping.numIntersection ) ) {
+                       uniforms.alphaMap.value = material.alphaMap;
 
-                               initMaterial( material, scene, object );
+               }
 
-                       } else if ( materialProperties.outputEncoding !== encoding ) {
+               if ( material.alphaTest > 0 ) {
 
-                               initMaterial( material, scene, object );
+                       uniforms.alphaTest.value = material.alphaTest;
 
-                       } else if ( materialProperties.envMap !== envMap ) {
+               }
+
+               // uv repeat and offset setting priorities
+               // 1. color map
+               // 2. alpha map
 
-                               initMaterial( material, scene, object );
+               let uvScaleMap;
 
-                       }
+               if ( material.map ) {
 
-               } else {
+                       uvScaleMap = material.map;
 
-                       initMaterial( material, scene, object );
-                       materialProperties.__version = material.version;
+               } else if ( material.alphaMap ) {
+
+                       uvScaleMap = material.alphaMap;
 
                }
 
-               let refreshProgram = false;
-               let refreshMaterial = false;
-               let refreshLights = false;
+               if ( uvScaleMap !== undefined ) {
 
-               const program = materialProperties.program,
-                       p_uniforms = program.getUniforms(),
-                       m_uniforms = materialProperties.uniforms;
+                       if ( uvScaleMap.matrixAutoUpdate === true ) {
 
-               if ( state.useProgram( program.program ) ) {
+                               uvScaleMap.updateMatrix();
 
-                       refreshProgram = true;
-                       refreshMaterial = true;
-                       refreshLights = true;
+                       }
+
+                       uniforms.uvTransform.value.copy( uvScaleMap.matrix );
 
                }
 
-               if ( material.id !== _currentMaterialId ) {
+       }
 
-                       _currentMaterialId = material.id;
+       function refreshUniformsLambert( uniforms, material ) {
 
-                       refreshMaterial = true;
+               if ( material.emissiveMap ) {
+
+                       uniforms.emissiveMap.value = material.emissiveMap;
 
                }
 
-               if ( refreshProgram || _currentCamera !== camera ) {
+       }
 
-                       p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix );
+       function refreshUniformsPhong( uniforms, material ) {
 
-                       if ( capabilities.logarithmicDepthBuffer ) {
+               uniforms.specular.value.copy( material.specular );
+               uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )
 
-                               p_uniforms.setValue( _gl, 'logDepthBufFC',
-                                       2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );
-
-                       }
+               if ( material.emissiveMap ) {
 
-                       if ( _currentCamera !== camera ) {
+                       uniforms.emissiveMap.value = material.emissiveMap;
 
-                               _currentCamera = camera;
+               }
 
-                               // lighting uniforms depend on the camera so enforce an update
-                               // now, in case this material supports lights - or later, when
-                               // the next material that does gets activated:
+               if ( material.bumpMap ) {
 
-                               refreshMaterial = true;         // set to true on material change
-                               refreshLights = true;           // remains set until update done
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
 
-                       }
+               }
 
-                       // load material specific uniforms
-                       // (shader material also gets them for the sake of genericity)
+               if ( material.normalMap ) {
 
-                       if ( material.isShaderMaterial ||
-                               material.isMeshPhongMaterial ||
-                               material.isMeshToonMaterial ||
-                               material.isMeshStandardMaterial ||
-                               material.envMap ) {
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
 
-                               const uCamPos = p_uniforms.map.cameraPosition;
+               }
 
-                               if ( uCamPos !== undefined ) {
+               if ( material.displacementMap ) {
 
-                                       uCamPos.setValue( _gl,
-                                               _vector3.setFromMatrixPosition( camera.matrixWorld ) );
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
-                               }
+               }
 
-                       }
+       }
 
-                       if ( material.isMeshPhongMaterial ||
-                               material.isMeshToonMaterial ||
-                               material.isMeshLambertMaterial ||
-                               material.isMeshBasicMaterial ||
-                               material.isMeshStandardMaterial ||
-                               material.isShaderMaterial ) {
+       function refreshUniformsToon( uniforms, material ) {
 
-                               p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true );
+               if ( material.gradientMap ) {
 
-                       }
+                       uniforms.gradientMap.value = material.gradientMap;
 
-                       if ( material.isMeshPhongMaterial ||
-                               material.isMeshToonMaterial ||
-                               material.isMeshLambertMaterial ||
-                               material.isMeshBasicMaterial ||
-                               material.isMeshStandardMaterial ||
-                               material.isShaderMaterial ||
-                               material.isShadowMaterial ||
-                               material.skinning ) {
+               }
 
-                               p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );
+               if ( material.emissiveMap ) {
 
-                       }
+                       uniforms.emissiveMap.value = material.emissiveMap;
 
                }
 
-               // skinning uniforms must be set even if material didn't change
-               // auto-setting of texture unit for bone texture must go before other textures
-               // otherwise textures used for skinning can take over texture units reserved for other material textures
+               if ( material.bumpMap ) {
 
-               if ( material.skinning ) {
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
 
-                       p_uniforms.setOptional( _gl, object, 'bindMatrix' );
-                       p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' );
+               }
 
-                       const skeleton = object.skeleton;
+               if ( material.normalMap ) {
 
-                       if ( skeleton ) {
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
 
-                               const bones = skeleton.bones;
+               }
 
-                               if ( capabilities.floatVertexTextures ) {
+               if ( material.displacementMap ) {
 
-                                       if ( skeleton.boneTexture === null ) {
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
-                                               // layout (1 matrix = 4 pixels)
-                                               //      RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
-                                               //  with  8x8  pixel texture max   16 bones * 4 pixels =  (8 * 8)
-                                               //       16x16 pixel texture max   64 bones * 4 pixels = (16 * 16)
-                                               //       32x32 pixel texture max  256 bones * 4 pixels = (32 * 32)
-                                               //       64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64)
+               }
 
+       }
 
-                                               let size = Math.sqrt( bones.length * 4 ); // 4 pixels needed for 1 matrix
-                                               size = MathUtils.ceilPowerOfTwo( size );
-                                               size = Math.max( size, 4 );
+       function refreshUniformsStandard( uniforms, material ) {
 
-                                               const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel
-                                               boneMatrices.set( skeleton.boneMatrices ); // copy current values
+               uniforms.roughness.value = material.roughness;
+               uniforms.metalness.value = material.metalness;
 
-                                               const boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType );
+               if ( material.roughnessMap ) {
 
-                                               skeleton.boneMatrices = boneMatrices;
-                                               skeleton.boneTexture = boneTexture;
-                                               skeleton.boneTextureSize = size;
+                       uniforms.roughnessMap.value = material.roughnessMap;
 
-                                       }
+               }
 
-                                       p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures );
-                                       p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize );
+               if ( material.metalnessMap ) {
 
-                               } else {
+                       uniforms.metalnessMap.value = material.metalnessMap;
 
-                                       p_uniforms.setOptional( _gl, skeleton, 'boneMatrices' );
+               }
 
-                               }
+               if ( material.emissiveMap ) {
 
-                       }
+                       uniforms.emissiveMap.value = material.emissiveMap;
 
                }
 
-               if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) {
+               if ( material.bumpMap ) {
 
-                       materialProperties.receiveShadow = object.receiveShadow;
-                       p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow );
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
 
                }
 
-               if ( refreshMaterial ) {
-
-                       p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure );
-
-                       if ( materialProperties.needsLights ) {
+               if ( material.normalMap ) {
 
-                               // the current material requires lighting info
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
 
-                               // note: all lighting uniforms are always set correctly
-                               // they simply reference the renderer's state for their
-                               // values
-                               //
-                               // use the current material's .needsUpdate flags to set
-                               // the GL state when required
+               }
 
-                               markUniformsLightsNeedsUpdate( m_uniforms, refreshLights );
+               if ( material.displacementMap ) {
 
-                       }
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
-                       // refresh uniforms common to several materials
+               }
 
-                       if ( fog && material.fog ) {
+               const envMap = properties.get( material ).envMap;
 
-                               materials.refreshFogUniforms( m_uniforms, fog );
+               if ( envMap ) {
 
-                       }
+                       //uniforms.envMap.value = material.envMap; // part of uniforms common
+                       uniforms.envMapIntensity.value = material.envMapIntensity;
 
-                       materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height );
+               }
 
-                       WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
+       }
 
-               }
+       function refreshUniformsPhysical( uniforms, material, transmissionRenderTarget ) {
 
-               if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) {
+               refreshUniformsStandard( uniforms, material );
 
-                       WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
-                       material.uniformsNeedUpdate = false;
+               uniforms.ior.value = material.ior; // also part of uniforms common
 
-               }
+               if ( material.sheen > 0 ) {
 
-               if ( material.isSpriteMaterial ) {
+                       uniforms.sheenColor.value.copy( material.sheenColor ).multiplyScalar( material.sheen );
 
-                       p_uniforms.setValue( _gl, 'center', object.center );
+                       uniforms.sheenRoughness.value = material.sheenRoughness;
 
-               }
+                       if ( material.sheenColorMap ) {
 
-               // common matrices
+                               uniforms.sheenColorMap.value = material.sheenColorMap;
 
-               p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );
-               p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );
-               p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );
+                       }
 
-               return program;
+                       if ( material.sheenRoughnessMap ) {
 
-       }
+                               uniforms.sheenRoughnessMap.value = material.sheenRoughnessMap;
 
-       // If uniforms are marked as clean, they don't need to be loaded to the GPU.
+                       }
 
-       function markUniformsLightsNeedsUpdate( uniforms, value ) {
+               }
 
-               uniforms.ambientLightColor.needsUpdate = value;
-               uniforms.lightProbe.needsUpdate = value;
+               if ( material.clearcoat > 0 ) {
 
-               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.clearcoat.value = material.clearcoat;
+                       uniforms.clearcoatRoughness.value = material.clearcoatRoughness;
 
-       }
+                       if ( material.clearcoatMap ) {
 
-       function materialNeedsLights( material ) {
+                               uniforms.clearcoatMap.value = material.clearcoatMap;
 
-               return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial ||
-                       material.isMeshStandardMaterial || material.isShadowMaterial ||
-                       ( material.isShaderMaterial && material.lights === true );
+                       }
 
-       }
+                       if ( material.clearcoatRoughnessMap ) {
 
-       //
-       this.setFramebuffer = function ( value ) {
+                               uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap;
 
-               if ( _framebuffer !== value && _currentRenderTarget === null ) _gl.bindFramebuffer( 36160, value );
+                       }
 
-               _framebuffer = value;
+                       if ( material.clearcoatNormalMap ) {
 
-       };
+                               uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale );
+                               uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap;
 
-       this.getActiveCubeFace = function () {
+                               if ( material.side === BackSide ) {
 
-               return _currentActiveCubeFace;
+                                       uniforms.clearcoatNormalScale.value.negate();
 
-       };
+                               }
 
-       this.getActiveMipmapLevel = function () {
+                       }
 
-               return _currentActiveMipmapLevel;
+               }
 
-       };
+               if ( material.transmission > 0 ) {
 
-       this.getRenderList = function () {
+                       uniforms.transmission.value = material.transmission;
+                       uniforms.transmissionSamplerMap.value = transmissionRenderTarget.texture;
+                       uniforms.transmissionSamplerSize.value.set( transmissionRenderTarget.width, transmissionRenderTarget.height );
 
-               return currentRenderList;
+                       if ( material.transmissionMap ) {
 
-       };
+                               uniforms.transmissionMap.value = material.transmissionMap;
 
-       this.setRenderList = function ( renderList ) {
+                       }
 
-               currentRenderList = renderList;
+                       uniforms.thickness.value = material.thickness;
 
-       };
+                       if ( material.thicknessMap ) {
 
-       this.getRenderTarget = function () {
+                               uniforms.thicknessMap.value = material.thicknessMap;
 
-               return _currentRenderTarget;
+                       }
 
-       };
+                       uniforms.attenuationDistance.value = material.attenuationDistance;
+                       uniforms.attenuationColor.value.copy( material.attenuationColor );
 
-       this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) {
+               }
 
-               _currentRenderTarget = renderTarget;
-               _currentActiveCubeFace = activeCubeFace;
-               _currentActiveMipmapLevel = activeMipmapLevel;
+               uniforms.specularIntensity.value = material.specularIntensity;
+               uniforms.specularColor.value.copy( material.specularColor );
 
-               if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) {
+               if ( material.specularIntensityMap ) {
 
-                       textures.setupRenderTarget( renderTarget );
+                       uniforms.specularIntensityMap.value = material.specularIntensityMap;
 
                }
 
-               let framebuffer = _framebuffer;
-               let isCube = false;
+               if ( material.specularColorMap ) {
 
-               if ( renderTarget ) {
+                       uniforms.specularColorMap.value = material.specularColorMap;
 
-                       const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer;
+               }
 
-                       if ( renderTarget.isWebGLCubeRenderTarget ) {
+       }
 
-                               framebuffer = __webglFramebuffer[ activeCubeFace ];
-                               isCube = true;
+       function refreshUniformsMatcap( uniforms, material ) {
 
-                       } else if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+               if ( material.matcap ) {
 
-                               framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer;
+                       uniforms.matcap.value = material.matcap;
 
-                       } else {
+               }
 
-                               framebuffer = __webglFramebuffer;
+               if ( material.bumpMap ) {
 
-                       }
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
 
-                       _currentViewport.copy( renderTarget.viewport );
-                       _currentScissor.copy( renderTarget.scissor );
-                       _currentScissorTest = renderTarget.scissorTest;
+               }
 
-               } else {
+               if ( material.normalMap ) {
 
-                       _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor();
-                       _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor();
-                       _currentScissorTest = _scissorTest;
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
 
                }
 
-               if ( _currentFramebuffer !== framebuffer ) {
+               if ( material.displacementMap ) {
 
-                       _gl.bindFramebuffer( 36160, framebuffer );
-                       _currentFramebuffer = framebuffer;
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
                }
 
-               state.viewport( _currentViewport );
-               state.scissor( _currentScissor );
-               state.setScissorTest( _currentScissorTest );
+       }
 
-               if ( isCube ) {
+       function refreshUniformsDepth( uniforms, material ) {
 
-                       const textureProperties = properties.get( renderTarget.texture );
-                       _gl.framebufferTexture2D( 36160, 36064, 34069 + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel );
+               if ( material.displacementMap ) {
+
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
                }
 
-       };
+       }
 
-       this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) {
+       function refreshUniformsDistance( uniforms, material ) {
 
-               if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) {
+               if ( material.displacementMap ) {
 
-                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' );
-                       return;
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
                }
 
-               let framebuffer = properties.get( renderTarget ).__webglFramebuffer;
+               uniforms.referencePosition.value.copy( material.referencePosition );
+               uniforms.nearDistance.value = material.nearDistance;
+               uniforms.farDistance.value = material.farDistance;
 
-               if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) {
+       }
 
-                       framebuffer = framebuffer[ activeCubeFaceIndex ];
+       function refreshUniformsNormal( uniforms, material ) {
 
-               }
+               if ( material.bumpMap ) {
 
-               if ( framebuffer ) {
+                       uniforms.bumpMap.value = material.bumpMap;
+                       uniforms.bumpScale.value = material.bumpScale;
+                       if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
 
-                       let restore = false;
+               }
 
-                       if ( framebuffer !== _currentFramebuffer ) {
+               if ( material.normalMap ) {
 
-                               _gl.bindFramebuffer( 36160, framebuffer );
+                       uniforms.normalMap.value = material.normalMap;
+                       uniforms.normalScale.value.copy( material.normalScale );
+                       if ( material.side === BackSide ) uniforms.normalScale.value.negate();
 
-                               restore = true;
+               }
 
-                       }
+               if ( material.displacementMap ) {
 
-                       try {
+                       uniforms.displacementMap.value = material.displacementMap;
+                       uniforms.displacementScale.value = material.displacementScale;
+                       uniforms.displacementBias.value = material.displacementBias;
 
-                               const texture = renderTarget.texture;
-                               const textureFormat = texture.format;
-                               const textureType = texture.type;
+               }
 
-                               if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( 35739 ) ) {
+       }
 
-                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' );
-                                       return;
+       return {
+               refreshFogUniforms: refreshFogUniforms,
+               refreshMaterialUniforms: refreshMaterialUniforms
+       };
 
-                               }
+    }
 
-                               const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || ( capabilities.isWebGL2 && extensions.has( 'EXT_color_buffer_float' ) ) );
+    function createCanvasElement() {
 
-                               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 ) {
+       const canvas = createElementNS( 'canvas' );
+       canvas.style.display = 'block';
+       return canvas;
 
-                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' );
-                                       return;
+    }
 
-                               }
+    function WebGLRenderer( parameters = {} ) {
 
-                               if ( _gl.checkFramebufferStatus( 36160 ) === 36053 ) {
+       const _canvas = parameters.canvas !== undefined ? parameters.canvas : createCanvasElement(),
+               _context = parameters.context !== undefined ? parameters.context : null,
 
-                                       // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)
+               _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;
 
-                                       if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {
+       let currentRenderList = null;
+       let currentRenderState = null;
 
-                                               _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer );
+       // 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.
 
-                                       }
+       const renderListStack = [];
+       const renderStateStack = [];
 
-                               } else {
+       // public properties
 
-                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' );
+       this.domElement = _canvas;
 
-                               }
+       // Debug configuration container
+       this.debug = {
 
-                       } finally {
+               /**
+                * Enables error checking and reporting when shader programs are being compiled
+                * @type {boolean}
+                */
+               checkShaderErrors: true
+       };
 
-                               if ( restore ) {
+       // clearing
 
-                                       _gl.bindFramebuffer( 36160, _currentFramebuffer );
+       this.autoClear = true;
+       this.autoClearColor = true;
+       this.autoClearDepth = true;
+       this.autoClearStencil = true;
 
-                               }
+       // scene graph
 
-                       }
+       this.sortObjects = true;
 
-               }
+       // user-defined clipping
 
-       };
+       this.clippingPlanes = [];
+       this.localClippingEnabled = false;
 
-       this.copyFramebufferToTexture = function ( position, texture, level = 0 ) {
+       // physically based shading
 
-               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 );
+       this.gammaFactor = 2.0; // for backwards compatibility
+       this.outputEncoding = LinearEncoding;
 
-               textures.setTexture2D( texture, 0 );
+       // physical lights
 
-               _gl.copyTexImage2D( 3553, level, glFormat, position.x, position.y, width, height, 0 );
+       this.physicallyCorrectLights = false;
 
-               state.unbindTexture();
+       // tone mapping
 
-       };
+       this.toneMapping = NoToneMapping;
+       this.toneMappingExposure = 1.0;
 
-       this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) {
+       // internal properties
 
-               const width = srcTexture.image.width;
-               const height = srcTexture.image.height;
-               const glFormat = utils.convert( dstTexture.format );
-               const glType = utils.convert( dstTexture.type );
+       const _this = this;
 
-               textures.setTexture2D( dstTexture, 0 );
+       let _isContextLost = false;
 
-               // 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 );
+       // internal state cache
 
-               if ( srcTexture.isDataTexture ) {
+       let _currentActiveCubeFace = 0;
+       let _currentActiveMipmapLevel = 0;
+       let _currentRenderTarget = null;
+       let _currentMaterialId = - 1;
 
-                       _gl.texSubImage2D( 3553, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data );
+       let _currentCamera = null;
 
-               } else {
+       const _currentViewport = new Vector4();
+       const _currentScissor = new Vector4();
+       let _currentScissorTest = null;
 
-                       if ( srcTexture.isCompressedTexture ) {
+       //
 
-                               _gl.compressedTexSubImage2D( 3553, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data );
+       let _width = _canvas.width;
+       let _height = _canvas.height;
 
-                       } else {
+       let _pixelRatio = 1;
+       let _opaqueSort = null;
+       let _transparentSort = null;
 
-                               _gl.texSubImage2D( 3553, level, position.x, position.y, glFormat, glType, srcTexture.image );
+       const _viewport = new Vector4( 0, 0, _width, _height );
+       const _scissor = new Vector4( 0, 0, _width, _height );
+       let _scissorTest = false;
 
-                       }
+       //
 
-               }
+       const _currentDrawBuffers = [];
 
-               // Generate mipmaps only when copying level 0
-               if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( 3553 );
+       // frustum
 
-               state.unbindTexture();
+       const _frustum = new Frustum();
 
-       };
+       // clipping
 
-       this.initTexture = function ( texture ) {
+       let _clippingEnabled = false;
+       let _localClippingEnabled = false;
 
-               textures.setTexture2D( texture, 0 );
+       // transmission
 
-               state.unbindTexture();
+       let _transmissionRenderTarget = null;
 
-       };
+       // camera matrices cache
 
-       this.resetState = function () {
+       const _projScreenMatrix = new Matrix4();
 
-               state.reset();
-               bindingStates.reset();
+       const _vector3 = new Vector3();
 
-       };
+       const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true };
 
-       if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
+       function getTargetPixelRatio() {
 
-               __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
+               return _currentRenderTarget === null ? _pixelRatio : 1;
 
        }
 
-    }
+       // initialize
 
-    function WebGL1Renderer( parameters ) {
+       let _gl = _context;
 
-       WebGLRenderer.call( this, parameters );
+       function getContext( contextNames, contextAttributes ) {
 
-    }
+               for ( let i = 0; i < contextNames.length; i ++ ) {
 
-    WebGL1Renderer.prototype = Object.assign( Object.create( WebGLRenderer.prototype ), {
+                       const contextName = contextNames[ i ];
+                       const context = _canvas.getContext( contextName, contextAttributes );
+                       if ( context !== null ) return context;
 
-       constructor: WebGL1Renderer,
+               }
 
-       isWebGL1Renderer: true
+               return null;
 
-    } );
+       }
 
-    class Scene extends Object3D {
+       try {
 
-       constructor() {
+               const contextAttributes = {
+                       alpha: _alpha,
+                       depth: _depth,
+                       stencil: _stencil,
+                       antialias: _antialias,
+                       premultipliedAlpha: _premultipliedAlpha,
+                       preserveDrawingBuffer: _preserveDrawingBuffer,
+                       powerPreference: _powerPreference,
+                       failIfMajorPerformanceCaveat: _failIfMajorPerformanceCaveat
+               };
 
-               super();
+               // event listeners must be registered before WebGL context is created, see #12753
 
-               Object.defineProperty( this, 'isScene', { value: true } );
+               _canvas.addEventListener( 'webglcontextlost', onContextLost, false );
+               _canvas.addEventListener( 'webglcontextrestored', onContextRestore, false );
 
-               this.type = 'Scene';
+               if ( _gl === null ) {
 
-               this.background = null;
-               this.environment = null;
-               this.fog = null;
+                       const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ];
 
-               this.overrideMaterial = null;
+                       if ( _this.isWebGL1Renderer === true ) {
 
-               this.autoUpdate = true; // checked by the renderer
+                               contextNames.shift();
 
-               if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
+                       }
 
-                       __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
+                       _gl = getContext( contextNames, contextAttributes );
 
-               }
+                       if ( _gl === null ) {
 
-       }
+                               if ( getContext( contextNames ) ) {
 
-       copy( source, recursive ) {
+                                       throw new Error( 'Error creating WebGL context with your selected attributes.' );
 
-               super.copy( source, recursive );
+                               } else {
 
-               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();
+                                       throw new Error( 'Error creating WebGL context.' );
 
-               if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone();
+                               }
 
-               this.autoUpdate = source.autoUpdate;
-               this.matrixAutoUpdate = source.matrixAutoUpdate;
-
-               return this;
+                       }
 
-       }
+               }
 
-       toJSON( meta ) {
+               // Some experimental-webgl implementations do not have getShaderPrecisionFormat
 
-               const data = super.toJSON( meta );
+               if ( _gl.getShaderPrecisionFormat === undefined ) {
 
-               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();
+                       _gl.getShaderPrecisionFormat = function () {
 
-               return data;
+                               return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 };
 
-       }
+                       };
 
-    }
+               }
 
-    function InterleavedBuffer( array, stride ) {
+       } catch ( error ) {
 
-       this.array = array;
-       this.stride = stride;
-       this.count = array !== undefined ? array.length / stride : 0;
+               console.error( 'THREE.WebGLRenderer: ' + error.message );
+               throw error;
 
-       this.usage = StaticDrawUsage;
-       this.updateRange = { offset: 0, count: - 1 };
+       }
 
-       this.version = 0;
+       let extensions, capabilities, state, info;
+       let properties, textures, cubemaps, cubeuvmaps, attributes, geometries, objects;
+       let programCache, materials, renderLists, renderStates, clipping, shadowMap;
 
-       this.uuid = MathUtils.generateUUID();
+       let background, morphtargets, bufferRenderer, indexedBufferRenderer;
 
-    }
+       let utils, bindingStates;
 
-    Object.defineProperty( InterleavedBuffer.prototype, 'needsUpdate', {
+       function initGLContext() {
 
-       set: function ( value ) {
+               extensions = new WebGLExtensions( _gl );
 
-               if ( value === true ) this.version ++;
+               capabilities = new WebGLCapabilities( _gl, extensions, parameters );
 
-       }
+               extensions.init( capabilities );
 
-    } );
+               utils = new WebGLUtils( _gl, extensions, capabilities );
 
-    Object.assign( InterleavedBuffer.prototype, {
+               state = new WebGLState( _gl, extensions, capabilities );
 
-       isInterleavedBuffer: true,
+               _currentDrawBuffers[ 0 ] = 1029;
 
-       onUploadCallback: function () {},
+               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 );
 
-       setUsage: function ( value ) {
+               bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities );
+               indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities );
 
-               this.usage = value;
+               info.programs = programCache.programs;
 
-               return this;
+               _this.capabilities = capabilities;
+               _this.extensions = extensions;
+               _this.properties = properties;
+               _this.renderLists = renderLists;
+               _this.shadowMap = shadowMap;
+               _this.state = state;
+               _this.info = info;
 
-       },
+       }
 
-       copy: function ( source ) {
+       initGLContext();
 
-               this.array = new source.array.constructor( source.array );
-               this.count = source.count;
-               this.stride = source.stride;
-               this.usage = source.usage;
+       // xr
 
-               return this;
+       const xr = new WebXRManager( _this, _gl );
 
-       },
+       this.xr = xr;
 
-       copyAt: function ( index1, attribute, index2 ) {
+       // API
 
-               index1 *= this.stride;
-               index2 *= attribute.stride;
+       this.getContext = function () {
 
-               for ( let i = 0, l = this.stride; i < l; i ++ ) {
+               return _gl;
 
-                       this.array[ index1 + i ] = attribute.array[ index2 + i ];
+       };
 
-               }
+       this.getContextAttributes = function () {
 
-               return this;
+               return _gl.getContextAttributes();
 
-       },
+       };
 
-       set: function ( value, offset = 0 ) {
+       this.forceContextLoss = function () {
 
-               this.array.set( value, offset );
+               const extension = extensions.get( 'WEBGL_lose_context' );
+               if ( extension ) extension.loseContext();
 
-               return this;
+       };
 
-       },
+       this.forceContextRestore = function () {
 
-       clone: function ( data ) {
+               const extension = extensions.get( 'WEBGL_lose_context' );
+               if ( extension ) extension.restoreContext();
 
-               if ( data.arrayBuffers === undefined ) {
+       };
 
-                       data.arrayBuffers = {};
+       this.getPixelRatio = function () {
 
-               }
+               return _pixelRatio;
 
-               if ( this.array.buffer._uuid === undefined ) {
+       };
 
-                       this.array.buffer._uuid = MathUtils.generateUUID();
+       this.setPixelRatio = function ( value ) {
 
-               }
+               if ( value === undefined ) return;
 
-               if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) {
+               _pixelRatio = value;
 
-                       data.arrayBuffers[ this.array.buffer._uuid ] = this.array.slice( 0 ).buffer;
+               this.setSize( _width, _height, false );
 
-               }
+       };
 
-               const array = new this.array.constructor( data.arrayBuffers[ this.array.buffer._uuid ] );
+       this.getSize = function ( target ) {
 
-               const ib = new InterleavedBuffer( array, this.stride );
-               ib.setUsage( this.usage );
+               return target.set( _width, _height );
 
-               return ib;
+       };
 
-       },
+       this.setSize = function ( width, height, updateStyle ) {
 
-       onUpload: function ( callback ) {
+               if ( xr.isPresenting ) {
 
-               this.onUploadCallback = callback;
+                       console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' );
+                       return;
 
-               return this;
+               }
 
-       },
+               _width = width;
+               _height = height;
 
-       toJSON: function ( data ) {
+               _canvas.width = Math.floor( width * _pixelRatio );
+               _canvas.height = Math.floor( height * _pixelRatio );
 
-               if ( data.arrayBuffers === undefined ) {
+               if ( updateStyle !== false ) {
 
-                       data.arrayBuffers = {};
+                       _canvas.style.width = width + 'px';
+                       _canvas.style.height = height + 'px';
 
                }
 
-               // generate UUID for array buffer if necessary
+               this.setViewport( 0, 0, width, height );
 
-               if ( this.array.buffer._uuid === undefined ) {
+       };
 
-                       this.array.buffer._uuid = MathUtils.generateUUID();
+       this.getDrawingBufferSize = function ( target ) {
 
-               }
+               return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor();
 
-               if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) {
+       };
 
-                       data.arrayBuffers[ this.array.buffer._uuid ] = Array.prototype.slice.call( new Uint32Array( this.array.buffer ) );
+       this.setDrawingBufferSize = function ( width, height, pixelRatio ) {
 
-               }
+               _width = width;
+               _height = height;
 
-               //
+               _pixelRatio = pixelRatio;
 
-               return {
-                       uuid: this.uuid,
-                       buffer: this.array.buffer._uuid,
-                       type: this.array.constructor.name,
-                       stride: this.stride
-               };
+               _canvas.width = Math.floor( width * pixelRatio );
+               _canvas.height = Math.floor( height * pixelRatio );
 
-       }
+               this.setViewport( 0, 0, width, height );
 
-    } );
+       };
 
-    const _vector$6 = new Vector3();
+       this.getCurrentViewport = function ( target ) {
+
+               return target.copy( _currentViewport );
 
-    function InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, normalized ) {
+       };
 
-       this.name = '';
+       this.getViewport = function ( target ) {
 
-       this.data = interleavedBuffer;
-       this.itemSize = itemSize;
-       this.offset = offset;
+               return target.copy( _viewport );
 
-       this.normalized = normalized === true;
+       };
 
-    }
+       this.setViewport = function ( x, y, width, height ) {
 
-    Object.defineProperties( InterleavedBufferAttribute.prototype, {
+               if ( x.isVector4 ) {
 
-       count: {
+                       _viewport.set( x.x, x.y, x.z, x.w );
 
-               get: function () {
+               } else {
 
-                       return this.data.count;
+                       _viewport.set( x, y, width, height );
 
                }
 
-       },
+               state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() );
 
-       array: {
+       };
 
-               get: function () {
+       this.getScissor = function ( target ) {
+
+               return target.copy( _scissor );
 
-                       return this.data.array;
+       };
 
-               }
+       this.setScissor = function ( x, y, width, height ) {
 
-       },
+               if ( x.isVector4 ) {
 
-       needsUpdate: {
+                       _scissor.set( x.x, x.y, x.z, x.w );
 
-               set: function ( value ) {
+               } else {
 
-                       this.data.needsUpdate = value;
+                       _scissor.set( x, y, width, height );
 
                }
 
-       }
+               state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() );
 
-    } );
+       };
 
-    Object.assign( InterleavedBufferAttribute.prototype, {
+       this.getScissorTest = function () {
 
-       isInterleavedBufferAttribute: true,
+               return _scissorTest;
 
-       applyMatrix4: function ( m ) {
+       };
 
-               for ( let i = 0, l = this.data.count; i < l; i ++ ) {
+       this.setScissorTest = function ( boolean ) {
 
-                       _vector$6.x = this.getX( i );
-                       _vector$6.y = this.getY( i );
-                       _vector$6.z = this.getZ( i );
+               state.setScissorTest( _scissorTest = boolean );
 
-                       _vector$6.applyMatrix4( m );
+       };
 
-                       this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z );
+       this.setOpaqueSort = function ( method ) {
 
-               }
+               _opaqueSort = method;
 
-               return this;
+       };
 
-       },
+       this.setTransparentSort = function ( method ) {
 
-       setX: function ( index, x ) {
+               _transparentSort = method;
 
-               this.data.array[ index * this.data.stride + this.offset ] = x;
+       };
 
-               return this;
+       // Clearing
 
-       },
+       this.getClearColor = function ( target ) {
 
-       setY: function ( index, y ) {
+               return target.copy( background.getClearColor() );
 
-               this.data.array[ index * this.data.stride + this.offset + 1 ] = y;
+       };
 
-               return this;
+       this.setClearColor = function () {
 
-       },
+               background.setClearColor.apply( background, arguments );
 
-       setZ: function ( index, z ) {
+       };
 
-               this.data.array[ index * this.data.stride + this.offset + 2 ] = z;
+       this.getClearAlpha = function () {
 
-               return this;
+               return background.getClearAlpha();
 
-       },
+       };
 
-       setW: function ( index, w ) {
+       this.setClearAlpha = function () {
 
-               this.data.array[ index * this.data.stride + this.offset + 3 ] = w;
+               background.setClearAlpha.apply( background, arguments );
 
-               return this;
+       };
 
-       },
+       this.clear = function ( color, depth, stencil ) {
 
-       getX: function ( index ) {
+               let bits = 0;
 
-               return this.data.array[ index * this.data.stride + this.offset ];
+               if ( color === undefined || color ) bits |= 16384;
+               if ( depth === undefined || depth ) bits |= 256;
+               if ( stencil === undefined || stencil ) bits |= 1024;
 
-       },
+               _gl.clear( bits );
 
-       getY: function ( index ) {
+       };
 
-               return this.data.array[ index * this.data.stride + this.offset + 1 ];
+       this.clearColor = function () {
 
-       },
+               this.clear( true, false, false );
 
-       getZ: function ( index ) {
+       };
 
-               return this.data.array[ index * this.data.stride + this.offset + 2 ];
+       this.clearDepth = function () {
 
-       },
+               this.clear( false, true, false );
 
-       getW: function ( index ) {
+       };
 
-               return this.data.array[ index * this.data.stride + this.offset + 3 ];
+       this.clearStencil = function () {
 
-       },
+               this.clear( false, false, true );
 
-       setXY: function ( index, x, y ) {
+       };
 
-               index = index * this.data.stride + this.offset;
+       //
 
-               this.data.array[ index + 0 ] = x;
-               this.data.array[ index + 1 ] = y;
+       this.dispose = function () {
 
-               return this;
+               _canvas.removeEventListener( 'webglcontextlost', onContextLost, false );
+               _canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false );
 
-       },
+               renderLists.dispose();
+               renderStates.dispose();
+               properties.dispose();
+               cubemaps.dispose();
+               cubeuvmaps.dispose();
+               objects.dispose();
+               bindingStates.dispose();
 
-       setXYZ: function ( index, x, y, z ) {
+               xr.dispose();
 
-               index = index * this.data.stride + this.offset;
+               xr.removeEventListener( 'sessionstart', onXRSessionStart );
+               xr.removeEventListener( 'sessionend', onXRSessionEnd );
 
-               this.data.array[ index + 0 ] = x;
-               this.data.array[ index + 1 ] = y;
-               this.data.array[ index + 2 ] = z;
+               if ( _transmissionRenderTarget ) {
 
-               return this;
+                       _transmissionRenderTarget.dispose();
+                       _transmissionRenderTarget = null;
 
-       },
+               }
 
-       setXYZW: function ( index, x, y, z, w ) {
+               animation.stop();
 
-               index = index * this.data.stride + this.offset;
+       };
 
-               this.data.array[ index + 0 ] = x;
-               this.data.array[ index + 1 ] = y;
-               this.data.array[ index + 2 ] = z;
-               this.data.array[ index + 3 ] = w;
+       // Events
 
-               return this;
+       function onContextLost( event ) {
 
-       },
+               event.preventDefault();
 
-       clone: function ( data ) {
+               console.log( 'THREE.WebGLRenderer: Context Lost.' );
 
-               if ( data === undefined ) {
+               _isContextLost = true;
 
-                       console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interlaved buffer attribute will deinterleave buffer data.' );
+       }
 
-                       const array = [];
+       function onContextRestore( /* event */ ) {
 
-                       for ( let i = 0; i < this.count; i ++ ) {
+               console.log( 'THREE.WebGLRenderer: Context Restored.' );
 
-                               const index = i * this.data.stride + this.offset;
+               _isContextLost = false;
 
-                               for ( let j = 0; j < this.itemSize; j ++ ) {
+               const infoAutoReset = info.autoReset;
+               const shadowMapEnabled = shadowMap.enabled;
+               const shadowMapAutoUpdate = shadowMap.autoUpdate;
+               const shadowMapNeedsUpdate = shadowMap.needsUpdate;
+               const shadowMapType = shadowMap.type;
 
-                                       array.push( this.data.array[ index + j ] );
+               initGLContext();
 
-                               }
+               info.autoReset = infoAutoReset;
+               shadowMap.enabled = shadowMapEnabled;
+               shadowMap.autoUpdate = shadowMapAutoUpdate;
+               shadowMap.needsUpdate = shadowMapNeedsUpdate;
+               shadowMap.type = shadowMapType;
 
-                       }
+       }
 
-                       return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized );
+       function onMaterialDispose( event ) {
 
-               } else {
+               const material = event.target;
 
-                       if ( data.interleavedBuffers === undefined ) {
+               material.removeEventListener( 'dispose', onMaterialDispose );
 
-                               data.interleavedBuffers = {};
+               deallocateMaterial( material );
 
-                       }
+       }
 
-                       if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
+       // Buffer deallocation
 
-                               data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data );
+       function deallocateMaterial( material ) {
 
-                       }
+               releaseMaterialProgramReferences( material );
 
-                       return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized );
+               properties.remove( material );
 
-               }
+       }
 
-       },
 
-       toJSON: function ( data ) {
+       function releaseMaterialProgramReferences( material ) {
 
-               if ( data === undefined ) {
+               const programs = properties.get( material ).programs;
 
-                       console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interlaved buffer attribute will deinterleave buffer data.' );
+               if ( programs !== undefined ) {
 
-                       const array = [];
+                       programs.forEach( function ( program ) {
 
-                       for ( let i = 0; i < this.count; i ++ ) {
+                               programCache.releaseProgram( program );
 
-                               const index = i * this.data.stride + this.offset;
+                       } );
 
-                               for ( let j = 0; j < this.itemSize; j ++ ) {
+               }
 
-                                       array.push( this.data.array[ index + j ] );
+       }
 
-                               }
+       // Buffer rendering
 
-                       }
+       this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) {
 
-                       // deinterleave data and save it as an ordinary buffer attribute for now
+               if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null)
 
-                       return {
-                               itemSize: this.itemSize,
-                               type: this.array.constructor.name,
-                               array: array,
-                               normalized: this.normalized
-                       };
+               const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );
 
-               } else {
+               const program = setProgram( camera, scene, geometry, material, object );
 
-                       // save as true interlaved attribtue
+               state.setMaterial( material, frontFaceCW );
 
-                       if ( data.interleavedBuffers === undefined ) {
+               //
 
-                               data.interleavedBuffers = {};
+               let index = geometry.index;
+               const position = geometry.attributes.position;
 
-                       }
+               //
 
-                       if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
+               if ( index === null ) {
 
-                               data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data );
+                       if ( position === undefined || position.count === 0 ) return;
 
-                       }
+               } else if ( index.count === 0 ) {
 
-                       return {
-                               isInterleavedBufferAttribute: true,
-                               itemSize: this.itemSize,
-                               data: this.data.uuid,
-                               offset: this.offset,
-                               normalized: this.normalized
-                       };
+                       return;
 
                }
 
-       }
+               //
 
-    } );
+               let rangeFactor = 1;
 
-    /**
-     * parameters = {
-     *  color: <hex>,
-     *  map: new THREE.Texture( <Image> ),
-     *  alphaMap: new THREE.Texture( <Image> ),
-     *  rotation: <float>,
-     *  sizeAttenuation: <bool>
-     * }
-     */
+               if ( material.wireframe === true ) {
 
-    function SpriteMaterial( parameters ) {
+                       index = geometries.getWireframeAttribute( geometry );
+                       rangeFactor = 2;
 
-       Material.call( this );
+               }
 
-       this.type = 'SpriteMaterial';
+               bindingStates.setup( object, material, program, geometry, index );
 
-       this.color = new Color( 0xffffff );
+               let attribute;
+               let renderer = bufferRenderer;
 
-       this.map = null;
+               if ( index !== null ) {
 
-       this.alphaMap = null;
+                       attribute = attributes.get( index );
 
-       this.rotation = 0;
+                       renderer = indexedBufferRenderer;
+                       renderer.setIndex( attribute );
 
-       this.sizeAttenuation = true;
+               }
 
-       this.transparent = true;
+               //
 
-       this.setValues( parameters );
+               const dataCount = ( index !== null ) ? index.count : position.count;
 
-    }
+               const rangeStart = geometry.drawRange.start * rangeFactor;
+               const rangeCount = geometry.drawRange.count * rangeFactor;
 
-    SpriteMaterial.prototype = Object.create( Material.prototype );
-    SpriteMaterial.prototype.constructor = SpriteMaterial;
-    SpriteMaterial.prototype.isSpriteMaterial = true;
+               const groupStart = group !== null ? group.start * rangeFactor : 0;
+               const groupCount = group !== null ? group.count * rangeFactor : Infinity;
 
-    SpriteMaterial.prototype.copy = function ( source ) {
+               const drawStart = Math.max( rangeStart, groupStart );
+               const drawEnd = Math.min( dataCount, rangeStart + rangeCount, groupStart + groupCount ) - 1;
 
-       Material.prototype.copy.call( this, source );
+               const drawCount = Math.max( 0, drawEnd - drawStart + 1 );
 
-       this.color.copy( source.color );
+               if ( drawCount === 0 ) return;
 
-       this.map = source.map;
+               //
 
-       this.alphaMap = source.alphaMap;
+               if ( object.isMesh ) {
 
-       this.rotation = source.rotation;
+                       if ( material.wireframe === true ) {
 
-       this.sizeAttenuation = source.sizeAttenuation;
+                               state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() );
+                               renderer.setMode( 1 );
 
-       return this;
+                       } else {
 
-    };
+                               renderer.setMode( 4 );
 
-    let _geometry;
+                       }
 
-    const _intersectPoint = new Vector3();
-    const _worldScale = new Vector3();
-    const _mvPosition = new Vector3();
+               } else if ( object.isLine ) {
 
-    const _alignedPosition = new Vector2();
-    const _rotatedPosition = new Vector2();
-    const _viewWorldMatrix = new Matrix4();
+                       let lineWidth = material.linewidth;
 
-    const _vA$1 = new Vector3();
-    const _vB$1 = new Vector3();
-    const _vC$1 = new Vector3();
+                       if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material
 
-    const _uvA$1 = new Vector2();
-    const _uvB$1 = new Vector2();
-    const _uvC$1 = new Vector2();
+                       state.setLineWidth( lineWidth * getTargetPixelRatio() );
 
-    function Sprite( material ) {
+                       if ( object.isLineSegments ) {
 
-       Object3D.call( this );
+                               renderer.setMode( 1 );
 
-       this.type = 'Sprite';
+                       } else if ( object.isLineLoop ) {
 
-       if ( _geometry === undefined ) {
+                               renderer.setMode( 2 );
 
-               _geometry = new BufferGeometry();
+                       } else {
 
-               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
-               ] );
+                               renderer.setMode( 3 );
 
-               const interleavedBuffer = new InterleavedBuffer( float32Array, 5 );
+                       }
 
-               _geometry.setIndex( [ 0, 1, 2,  0, 2, 3 ] );
-               _geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) );
-               _geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) );
+               } else if ( object.isPoints ) {
 
-       }
+                       renderer.setMode( 0 );
 
-       this.geometry = _geometry;
-       this.material = ( material !== undefined ) ? material : new SpriteMaterial();
+               } else if ( object.isSprite ) {
 
-       this.center = new Vector2( 0.5, 0.5 );
+                       renderer.setMode( 4 );
 
-    }
+               }
+
+               if ( object.isInstancedMesh ) {
 
-    Sprite.prototype = Object.assign( Object.create( Object3D.prototype ), {
+                       renderer.renderInstances( drawStart, drawCount, object.count );
 
-       constructor: Sprite,
+               } else if ( geometry.isInstancedBufferGeometry ) {
 
-       isSprite: true,
+                       const instanceCount = Math.min( geometry.instanceCount, geometry._maxInstanceCount );
 
-       raycast: function ( raycaster, intersects ) {
+                       renderer.renderInstances( drawStart, drawCount, instanceCount );
 
-               if ( raycaster.camera === null ) {
+               } else {
 
-                       console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' );
+                       renderer.render( drawStart, drawCount );
 
                }
 
-               _worldScale.setFromMatrixScale( this.matrixWorld );
+       };
 
-               _viewWorldMatrix.copy( raycaster.camera.matrixWorld );
-               this.modelViewMatrix.multiplyMatrices( raycaster.camera.matrixWorldInverse, this.matrixWorld );
+       // Compile
 
-               _mvPosition.setFromMatrixPosition( this.modelViewMatrix );
+       this.compile = function ( scene, camera ) {
 
-               if ( raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false ) {
+               currentRenderState = renderStates.get( scene );
+               currentRenderState.init();
 
-                       _worldScale.multiplyScalar( - _mvPosition.z );
+               renderStateStack.push( currentRenderState );
 
-               }
+               scene.traverseVisible( function ( object ) {
 
-               const rotation = this.material.rotation;
-               let sin, cos;
+                       if ( object.isLight && object.layers.test( camera.layers ) ) {
 
-               if ( rotation !== 0 ) {
+                               currentRenderState.pushLight( object );
 
-                       cos = Math.cos( rotation );
-                       sin = Math.sin( rotation );
+                               if ( object.castShadow ) {
 
-               }
+                                       currentRenderState.pushShadow( object );
 
-               const center = this.center;
+                               }
 
-               transformVertex( _vA$1.set( - 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
-               transformVertex( _vB$1.set( 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
-               transformVertex( _vC$1.set( 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+                       }
 
-               _uvA$1.set( 0, 0 );
-               _uvB$1.set( 1, 0 );
-               _uvC$1.set( 1, 1 );
+               } );
 
-               // check first triangle
-               let intersect = raycaster.ray.intersectTriangle( _vA$1, _vB$1, _vC$1, false, _intersectPoint );
+               currentRenderState.setupLights( _this.physicallyCorrectLights );
 
-               if ( intersect === null ) {
+               scene.traverse( function ( object ) {
 
-                       // check second triangle
-                       transformVertex( _vB$1.set( - 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
-                       _uvB$1.set( 0, 1 );
+                       const material = object.material;
 
-                       intersect = raycaster.ray.intersectTriangle( _vA$1, _vC$1, _vB$1, false, _intersectPoint );
-                       if ( intersect === null ) {
+                       if ( material ) {
 
-                               return;
+                               if ( Array.isArray( material ) ) {
 
-                       }
+                                       for ( let i = 0; i < material.length; i ++ ) {
 
-               }
+                                               const material2 = material[ i ];
 
-               const distance = raycaster.ray.origin.distanceTo( _intersectPoint );
+                                               getProgram( material2, scene, object );
 
-               if ( distance < raycaster.near || distance > raycaster.far ) return;
+                                       }
 
-               intersects.push( {
+                               } else {
 
-                       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
+                                       getProgram( material, scene, object );
 
-               } );
+                               }
 
-       },
+                       }
 
-       copy: function ( source ) {
+               } );
 
-               Object3D.prototype.copy.call( this, source );
+               renderStateStack.pop();
+               currentRenderState = null;
 
-               if ( source.center !== undefined ) this.center.copy( source.center );
+       };
 
-               this.material = source.material;
+       // Animation Loop
 
-               return this;
+       let onAnimationFrameCallback = null;
 
-       }
+       function onAnimationFrame( time ) {
 
-    } );
+               if ( onAnimationFrameCallback ) onAnimationFrameCallback( time );
 
-    function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) {
+       }
 
-       // compute position in camera space
-       _alignedPosition.subVectors( vertexPosition, center ).addScalar( 0.5 ).multiply( scale );
+       function onXRSessionStart() {
 
-       // to check if rotation is not zero
-       if ( sin !== undefined ) {
+               animation.stop();
 
-               _rotatedPosition.x = ( cos * _alignedPosition.x ) - ( sin * _alignedPosition.y );
-               _rotatedPosition.y = ( sin * _alignedPosition.x ) + ( cos * _alignedPosition.y );
+       }
 
-       } else {
+       function onXRSessionEnd() {
 
-               _rotatedPosition.copy( _alignedPosition );
+               animation.start();
 
        }
 
+       const animation = new WebGLAnimation();
+       animation.setAnimationLoop( onAnimationFrame );
 
-       vertexPosition.copy( mvPosition );
-       vertexPosition.x += _rotatedPosition.x;
-       vertexPosition.y += _rotatedPosition.y;
-
-       // transform to world space
-       vertexPosition.applyMatrix4( _viewWorldMatrix );
+       if ( typeof window !== 'undefined' ) animation.setContext( window );
 
-    }
+       this.setAnimationLoop = function ( callback ) {
 
-    const _v1$4 = new Vector3();
-    const _v2$2 = new Vector3();
+               onAnimationFrameCallback = callback;
+               xr.setAnimationLoop( callback );
 
-    function LOD() {
+               ( callback === null ) ? animation.stop() : animation.start();
 
-       Object3D.call( this );
+       };
 
-       this._currentLevel = 0;
+       xr.addEventListener( 'sessionstart', onXRSessionStart );
+       xr.addEventListener( 'sessionend', onXRSessionEnd );
 
-       this.type = 'LOD';
+       // Rendering
 
-       Object.defineProperties( this, {
-               levels: {
-                       enumerable: true,
-                       value: []
-               }
-       } );
+       this.render = function ( scene, camera ) {
 
-       this.autoUpdate = true;
+               if ( camera !== undefined && camera.isCamera !== true ) {
 
-    }
+                       console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' );
+                       return;
 
-    LOD.prototype = Object.assign( Object.create( Object3D.prototype ), {
+               }
 
-       constructor: LOD,
+               if ( _isContextLost === true ) return;
 
-       isLOD: true,
+               // update scene graph
 
-       copy: function ( source ) {
+               if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
 
-               Object3D.prototype.copy.call( this, source, false );
+               // update camera matrices and frustum
 
-               const levels = source.levels;
+               if ( camera.parent === null ) camera.updateMatrixWorld();
 
-               for ( let i = 0, l = levels.length; i < l; i ++ ) {
+               if ( xr.enabled === true && xr.isPresenting === true ) {
 
-                       const level = levels[ i ];
+                       if ( xr.cameraAutoUpdate === true ) xr.updateCamera( camera );
 
-                       this.addLevel( level.object.clone(), level.distance );
+                       camera = xr.getCamera(); // use XR camera for rendering
 
                }
 
-               this.autoUpdate = source.autoUpdate;
+               //
+               if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, _currentRenderTarget );
 
-               return this;
+               currentRenderState = renderStates.get( scene, renderStateStack.length );
+               currentRenderState.init();
 
-       },
+               renderStateStack.push( currentRenderState );
 
-       addLevel: function ( object, distance = 0 ) {
+               _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+               _frustum.setFromProjectionMatrix( _projScreenMatrix );
 
-               distance = Math.abs( distance );
+               _localClippingEnabled = this.localClippingEnabled;
+               _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled, camera );
 
-               const levels = this.levels;
+               currentRenderList = renderLists.get( scene, renderListStack.length );
+               currentRenderList.init();
 
-               let l;
+               renderListStack.push( currentRenderList );
 
-               for ( l = 0; l < levels.length; l ++ ) {
+               projectObject( scene, camera, 0, _this.sortObjects );
 
-                       if ( distance < levels[ l ].distance ) {
+               currentRenderList.finish();
 
-                               break;
+               if ( _this.sortObjects === true ) {
 
-                       }
+                       currentRenderList.sort( _opaqueSort, _transparentSort );
 
                }
 
-               levels.splice( l, 0, { distance: distance, object: object } );
+               //
+
+               if ( _clippingEnabled === true ) clipping.beginShadows();
 
-               this.add( object );
+               const shadowsArray = currentRenderState.state.shadowsArray;
 
-               return this;
+               shadowMap.render( shadowsArray, scene, camera );
 
-       },
+               if ( _clippingEnabled === true ) clipping.endShadows();
 
-       getCurrentLevel: function () {
+               //
 
-               return this._currentLevel;
+               if ( this.info.autoReset === true ) this.info.reset();
 
-       },
+               //
 
-       getObjectForDistance: function ( distance ) {
+               background.render( currentRenderList, scene );
 
-               const levels = this.levels;
+               // render scene
 
-               if ( levels.length > 0 ) {
+               currentRenderState.setupLights( _this.physicallyCorrectLights );
 
-                       let i, l;
+               if ( camera.isArrayCamera ) {
 
-                       for ( i = 1, l = levels.length; i < l; i ++ ) {
+                       const cameras = camera.cameras;
 
-                               if ( distance < levels[ i ].distance ) {
+                       for ( let i = 0, l = cameras.length; i < l; i ++ ) {
 
-                                       break;
+                               const camera2 = cameras[ i ];
 
-                               }
+                               renderScene( currentRenderList, scene, camera2, camera2.viewport );
 
                        }
 
-                       return levels[ i - 1 ].object;
-
-               }
+               } else {
 
-               return null;
+                       renderScene( currentRenderList, scene, camera );
 
-       },
+               }
 
-       raycast: function ( raycaster, intersects ) {
+               //
 
-               const levels = this.levels;
+               if ( _currentRenderTarget !== null ) {
 
-               if ( levels.length > 0 ) {
+                       // resolve multisample renderbuffers to a single-sample texture if necessary
 
-                       _v1$4.setFromMatrixPosition( this.matrixWorld );
+                       textures.updateMultisampleRenderTarget( _currentRenderTarget );
 
-                       const distance = raycaster.ray.origin.distanceTo( _v1$4 );
+                       // Generate mipmap if we're using any kind of mipmap filtering
 
-                       this.getObjectForDistance( distance ).raycast( raycaster, intersects );
+                       textures.updateRenderTargetMipmap( _currentRenderTarget );
 
                }
 
-       },
-
-       update: function ( camera ) {
+               //
 
-               const levels = this.levels;
+               if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera );
 
-               if ( levels.length > 1 ) {
+               // Ensure depth buffer writing is enabled so it can be cleared on next render
 
-                       _v1$4.setFromMatrixPosition( camera.matrixWorld );
-                       _v2$2.setFromMatrixPosition( this.matrixWorld );
+               state.buffers.depth.setTest( true );
+               state.buffers.depth.setMask( true );
+               state.buffers.color.setMask( true );
 
-                       const distance = _v1$4.distanceTo( _v2$2 ) / camera.zoom;
+               state.setPolygonOffset( false );
 
-                       levels[ 0 ].object.visible = true;
+               // _gl.finish();
 
-                       let i, l;
+               bindingStates.resetDefaultState();
+               _currentMaterialId = - 1;
+               _currentCamera = null;
 
-                       for ( i = 1, l = levels.length; i < l; i ++ ) {
+               renderStateStack.pop();
 
-                               if ( distance >= levels[ i ].distance ) {
+               if ( renderStateStack.length > 0 ) {
 
-                                       levels[ i - 1 ].object.visible = false;
-                                       levels[ i ].object.visible = true;
+                       currentRenderState = renderStateStack[ renderStateStack.length - 1 ];
 
-                               } else {
+               } else {
 
-                                       break;
+                       currentRenderState = null;
 
-                               }
+               }
 
-                       }
+               renderListStack.pop();
 
-                       this._currentLevel = i - 1;
+               if ( renderListStack.length > 0 ) {
 
-                       for ( ; i < l; i ++ ) {
+                       currentRenderList = renderListStack[ renderListStack.length - 1 ];
 
-                               levels[ i ].object.visible = false;
+               } else {
 
-                       }
+                       currentRenderList = null;
 
                }
 
-       },
-
-       toJSON: function ( meta ) {
+       };
 
-               const data = Object3D.prototype.toJSON.call( this, meta );
+       function projectObject( object, camera, groupOrder, sortObjects ) {
 
-               if ( this.autoUpdate === false ) data.object.autoUpdate = false;
+               if ( object.visible === false ) return;
 
-               data.object.levels = [];
+               const visible = object.layers.test( camera.layers );
 
-               const levels = this.levels;
+               if ( visible ) {
 
-               for ( let i = 0, l = levels.length; i < l; i ++ ) {
+                       if ( object.isGroup ) {
 
-                       const level = levels[ i ];
+                               groupOrder = object.renderOrder;
 
-                       data.object.levels.push( {
-                               object: level.object.uuid,
-                               distance: level.distance
-                       } );
+                       } else if ( object.isLOD ) {
 
-               }
+                               if ( object.autoUpdate === true ) object.update( camera );
 
-               return data;
+                       } else if ( object.isLight ) {
 
-       }
+                               currentRenderState.pushLight( object );
 
-    } );
+                               if ( object.castShadow ) {
 
-    const _basePosition = new Vector3();
+                                       currentRenderState.pushShadow( object );
 
-    const _skinIndex = new Vector4();
-    const _skinWeight = new Vector4();
+                               }
 
-    const _vector$7 = new Vector3();
-    const _matrix$1 = new Matrix4();
+                       } else if ( object.isSprite ) {
 
-    function SkinnedMesh( geometry, material ) {
+                               if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {
 
-       if ( geometry && geometry.isGeometry ) {
+                                       if ( sortObjects ) {
 
-               console.error( 'THREE.SkinnedMesh no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+                                               _vector3.setFromMatrixPosition( object.matrixWorld )
+                                                       .applyMatrix4( _projScreenMatrix );
 
-       }
+                                       }
 
-       Mesh.call( this, geometry, material );
+                                       const geometry = objects.update( object );
+                                       const material = object.material;
 
-       this.type = 'SkinnedMesh';
+                                       if ( material.visible ) {
 
-       this.bindMode = 'attached';
-       this.bindMatrix = new Matrix4();
-       this.bindMatrixInverse = new Matrix4();
+                                               currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
 
-    }
+                                       }
 
-    SkinnedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
+                               }
 
-       constructor: SkinnedMesh,
+                       } else if ( object.isMesh || object.isLine || object.isPoints ) {
 
-       isSkinnedMesh: true,
+                               if ( object.isSkinnedMesh ) {
 
-       copy: function ( source ) {
+                                       // update skeleton only once in a frame
 
-               Mesh.prototype.copy.call( this, source );
+                                       if ( object.skeleton.frame !== info.render.frame ) {
 
-               this.bindMode = source.bindMode;
-               this.bindMatrix.copy( source.bindMatrix );
-               this.bindMatrixInverse.copy( source.bindMatrixInverse );
+                                               object.skeleton.update();
+                                               object.skeleton.frame = info.render.frame;
 
-               this.skeleton = source.skeleton;
+                                       }
 
-               return this;
+                               }
 
-       },
+                               if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {
 
-       bind: function ( skeleton, bindMatrix ) {
+                                       if ( sortObjects ) {
 
-               this.skeleton = skeleton;
+                                               _vector3.setFromMatrixPosition( object.matrixWorld )
+                                                       .applyMatrix4( _projScreenMatrix );
 
-               if ( bindMatrix === undefined ) {
+                                       }
 
-                       this.updateMatrixWorld( true );
+                                       const geometry = objects.update( object );
+                                       const material = object.material;
 
-                       this.skeleton.calculateInverses();
+                                       if ( Array.isArray( material ) ) {
 
-                       bindMatrix = this.matrixWorld;
+                                               const groups = geometry.groups;
 
-               }
+                                               for ( let i = 0, l = groups.length; i < l; i ++ ) {
 
-               this.bindMatrix.copy( bindMatrix );
-               this.bindMatrixInverse.copy( bindMatrix ).invert();
+                                                       const group = groups[ i ];
+                                                       const groupMaterial = material[ group.materialIndex ];
 
-       },
+                                                       if ( groupMaterial && groupMaterial.visible ) {
 
-       pose: function () {
+                                                               currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group );
 
-               this.skeleton.pose();
+                                                       }
 
-       },
+                                               }
 
-       normalizeSkinWeights: function () {
+                                       } else if ( material.visible ) {
 
-               const vector = new Vector4();
+                                               currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
 
-               const skinWeight = this.geometry.attributes.skinWeight;
+                                       }
 
-               for ( let i = 0, l = skinWeight.count; i < l; i ++ ) {
+                               }
 
-                       vector.x = skinWeight.getX( i );
-                       vector.y = skinWeight.getY( i );
-                       vector.z = skinWeight.getZ( i );
-                       vector.w = skinWeight.getW( i );
+                       }
 
-                       const scale = 1.0 / vector.manhattanLength();
+               }
 
-                       if ( scale !== Infinity ) {
+               const children = object.children;
 
-                               vector.multiplyScalar( scale );
+               for ( let i = 0, l = children.length; i < l; i ++ ) {
 
-                       } else {
+                       projectObject( children[ i ], camera, groupOrder, sortObjects );
 
-                               vector.set( 1, 0, 0, 0 ); // do something reasonable
+               }
 
-                       }
+       }
 
-                       skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w );
+       function renderScene( currentRenderList, scene, camera, viewport ) {
 
-               }
+               const opaqueObjects = currentRenderList.opaque;
+               const transmissiveObjects = currentRenderList.transmissive;
+               const transparentObjects = currentRenderList.transparent;
 
-       },
+               currentRenderState.setupLightsView( camera );
 
-       updateMatrixWorld: function ( force ) {
+               if ( transmissiveObjects.length > 0 ) renderTransmissionPass( opaqueObjects, scene, camera );
 
-               Mesh.prototype.updateMatrixWorld.call( this, force );
+               if ( viewport ) state.viewport( _currentViewport.copy( viewport ) );
 
-               if ( this.bindMode === 'attached' ) {
+               if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera );
+               if ( transmissiveObjects.length > 0 ) renderObjects( transmissiveObjects, scene, camera );
+               if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera );
 
-                       this.bindMatrixInverse.copy( this.matrixWorld ).invert();
+       }
 
-               } else if ( this.bindMode === 'detached' ) {
+       function renderTransmissionPass( opaqueObjects, scene, camera ) {
 
-                       this.bindMatrixInverse.copy( this.bindMatrix ).invert();
+               if ( _transmissionRenderTarget === null ) {
 
-               } else {
+                       const needsAntialias = _antialias === true && capabilities.isWebGL2 === true;
+                       const renderTargetType = needsAntialias ? WebGLMultisampleRenderTarget : WebGLRenderTarget;
 
-                       console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode );
+                       _transmissionRenderTarget = new renderTargetType( 1024, 1024, {
+                               generateMipmaps: true,
+                               type: utils.convert( HalfFloatType ) !== null ? HalfFloatType : UnsignedByteType,
+                               minFilter: LinearMipmapLinearFilter,
+                               magFilter: NearestFilter,
+                               wrapS: ClampToEdgeWrapping,
+                               wrapT: ClampToEdgeWrapping
+                       } );
 
                }
 
-       },
+               const currentRenderTarget = _this.getRenderTarget();
+               _this.setRenderTarget( _transmissionRenderTarget );
+               _this.clear();
 
-       boneTransform: function ( index, target ) {
+               // 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;
 
-               const skeleton = this.skeleton;
-               const geometry = this.geometry;
+               renderObjects( opaqueObjects, scene, camera );
 
-               _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index );
-               _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index );
+               _this.toneMapping = currentToneMapping;
 
-               _basePosition.fromBufferAttribute( geometry.attributes.position, index ).applyMatrix4( this.bindMatrix );
+               textures.updateMultisampleRenderTarget( _transmissionRenderTarget );
+               textures.updateRenderTargetMipmap( _transmissionRenderTarget );
 
-               target.set( 0, 0, 0 );
+               _this.setRenderTarget( currentRenderTarget );
 
-               for ( let i = 0; i < 4; i ++ ) {
+       }
 
-                       const weight = _skinWeight.getComponent( i );
+       function renderObjects( renderList, scene, camera ) {
 
-                       if ( weight !== 0 ) {
+               const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null;
 
-                               const boneIndex = _skinIndex.getComponent( i );
+               for ( let i = 0, l = renderList.length; i < l; i ++ ) {
+
+                       const renderItem = renderList[ i ];
 
-                               _matrix$1.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] );
+                       const object = renderItem.object;
+                       const geometry = renderItem.geometry;
+                       const material = overrideMaterial === null ? renderItem.material : overrideMaterial;
+                       const group = renderItem.group;
 
-                               target.addScaledVector( _vector$7.copy( _basePosition ).applyMatrix4( _matrix$1 ), weight );
+                       if ( object.layers.test( camera.layers ) ) {
+
+                               renderObject( object, scene, camera, geometry, material, group );
 
                        }
 
                }
 
-               return target.applyMatrix4( this.bindMatrixInverse );
-
        }
 
-    } );
+       function renderObject( object, scene, camera, geometry, material, group ) {
+
+               object.onBeforeRender( _this, scene, camera, geometry, material, group );
 
-    function Bone() {
+               object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+               object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
 
-       Object3D.call( this );
+               material.onBeforeRender( _this, scene, camera, geometry, object, group );
 
-       this.type = 'Bone';
+               if ( material.transparent === true && material.side === DoubleSide ) {
 
-    }
+                       material.side = BackSide;
+                       material.needsUpdate = true;
+                       _this.renderBufferDirect( camera, scene, geometry, material, object, group );
 
-    Bone.prototype = Object.assign( Object.create( Object3D.prototype ), {
+                       material.side = FrontSide;
+                       material.needsUpdate = true;
+                       _this.renderBufferDirect( camera, scene, geometry, material, object, group );
 
-       constructor: Bone,
+                       material.side = DoubleSide;
 
-       isBone: true
+               } else {
 
-    } );
+                       _this.renderBufferDirect( camera, scene, geometry, material, object, group );
 
-    const _offsetMatrix = new Matrix4();
-    const _identityMatrix = new Matrix4();
+               }
 
-    function Skeleton( bones = [], boneInverses = [] ) {
+               object.onAfterRender( _this, scene, camera, geometry, material, group );
 
-       this.uuid = MathUtils.generateUUID();
+       }
 
-       this.bones = bones.slice( 0 );
-       this.boneInverses = boneInverses;
-       this.boneMatrices = null;
+       function getProgram( material, scene, object ) {
 
-       this.boneTexture = null;
-       this.boneTextureSize = 0;
+               if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
 
-       this.frame = - 1;
+               const materialProperties = properties.get( material );
 
-       this.init();
+               const lights = currentRenderState.state.lights;
+               const shadowsArray = currentRenderState.state.shadowsArray;
 
-    }
+               const lightsStateVersion = lights.state.version;
 
-    Object.assign( Skeleton.prototype, {
+               const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object );
+               const programCacheKey = programCache.getProgramCacheKey( parameters );
 
-       init: function () {
+               let programs = materialProperties.programs;
 
-               const bones = this.bones;
-               const boneInverses = this.boneInverses;
+               // always update environment and fog - changing these trigger an getProgram call, but it's possible that the program doesn't change
 
-               this.boneMatrices = new Float32Array( bones.length * 16 );
+               materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null;
+               materialProperties.fog = scene.fog;
+               materialProperties.envMap = ( material.isMeshStandardMaterial ? cubeuvmaps : cubemaps ).get( material.envMap || materialProperties.environment );
 
-               // calculate inverse bone matrices if necessary
+               if ( programs === undefined ) {
 
-               if ( boneInverses.length === 0 ) {
+                       // new material
 
-                       this.calculateInverses();
+                       material.addEventListener( 'dispose', onMaterialDispose );
 
-               } else {
+                       programs = new Map();
+                       materialProperties.programs = programs;
 
-                       // handle special case
+               }
 
-                       if ( bones.length !== boneInverses.length ) {
+               let program = programs.get( programCacheKey );
 
-                               console.warn( 'THREE.Skeleton: Number of inverse bone matrices does not match amount of bones.' );
+               if ( program !== undefined ) {
 
-                               this.boneInverses = [];
+                       // early out if program and light state is identical
 
-                               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+                       if ( materialProperties.currentProgram === program && materialProperties.lightsStateVersion === lightsStateVersion ) {
 
-                                       this.boneInverses.push( new Matrix4() );
+                               updateCommonMaterialProperties( material, parameters );
 
-                               }
+                               return program;
 
                        }
 
-               }
+               } else {
 
-       },
+                       parameters.uniforms = programCache.getUniforms( material );
 
-       calculateInverses: function () {
+                       material.onBuild( object, parameters, _this );
 
-               this.boneInverses.length = 0;
+                       material.onBeforeCompile( parameters, _this );
 
-               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+                       program = programCache.acquireProgram( parameters, programCacheKey );
+                       programs.set( programCacheKey, program );
 
-                       const inverse = new Matrix4();
+                       materialProperties.uniforms = parameters.uniforms;
 
-                       if ( this.bones[ i ] ) {
+               }
 
-                               inverse.copy( this.bones[ i ].matrixWorld ).invert();
+               const uniforms = materialProperties.uniforms;
 
-                       }
+               if ( ( ! material.isShaderMaterial && ! material.isRawShaderMaterial ) || material.clipping === true ) {
 
-                       this.boneInverses.push( inverse );
+                       uniforms.clippingPlanes = clipping.uniform;
 
                }
 
-       },
-
-       pose: function () {
+               updateCommonMaterialProperties( material, parameters );
 
-               // recover the bind-time world matrices
+               // store the light setup it was created for
 
-               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+               materialProperties.needsLights = materialNeedsLights( material );
+               materialProperties.lightsStateVersion = lightsStateVersion;
 
-                       const bone = this.bones[ i ];
+               if ( materialProperties.needsLights ) {
 
-                       if ( bone ) {
+                       // wire up the material to this renderer's lighting state
 
-                               bone.matrixWorld.copy( this.boneInverses[ i ] ).invert();
+                       uniforms.ambientLightColor.value = lights.state.ambient;
+                       uniforms.lightProbe.value = lights.state.probe;
+                       uniforms.directionalLights.value = lights.state.directional;
+                       uniforms.directionalLightShadows.value = lights.state.directionalShadow;
+                       uniforms.spotLights.value = lights.state.spot;
+                       uniforms.spotLightShadows.value = lights.state.spotShadow;
+                       uniforms.rectAreaLights.value = lights.state.rectArea;
+                       uniforms.ltc_1.value = lights.state.rectAreaLTC1;
+                       uniforms.ltc_2.value = lights.state.rectAreaLTC2;
+                       uniforms.pointLights.value = lights.state.point;
+                       uniforms.pointLightShadows.value = lights.state.pointShadow;
+                       uniforms.hemisphereLights.value = lights.state.hemi;
 
-                       }
+                       uniforms.directionalShadowMap.value = lights.state.directionalShadowMap;
+                       uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix;
+                       uniforms.spotShadowMap.value = lights.state.spotShadowMap;
+                       uniforms.spotShadowMatrix.value = lights.state.spotShadowMatrix;
+                       uniforms.pointShadowMap.value = lights.state.pointShadowMap;
+                       uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix;
+                       // TODO (abelnation): add area lights shadow info to uniforms
 
                }
 
-               // compute the local matrices, positions, rotations and scales
+               const progUniforms = program.getUniforms();
+               const uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, uniforms );
 
-               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+               materialProperties.currentProgram = program;
+               materialProperties.uniformsList = uniformsList;
 
-                       const bone = this.bones[ i ];
+               return program;
 
-                       if ( bone ) {
+       }
 
-                               if ( bone.parent && bone.parent.isBone ) {
+       function updateCommonMaterialProperties( material, parameters ) {
 
-                                       bone.matrix.copy( bone.parent.matrixWorld ).invert();
-                                       bone.matrix.multiply( bone.matrixWorld );
+               const materialProperties = properties.get( material );
 
-                               } else {
+               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;
 
-                                       bone.matrix.copy( bone.matrixWorld );
+       }
 
-                               }
+       function setProgram( camera, scene, geometry, material, object ) {
 
-                               bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+               if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
 
-                       }
+               textures.resetTextureUnits();
 
-               }
+               const fog = scene.fog;
+               const environment = material.isMeshStandardMaterial ? scene.environment : null;
+               const encoding = ( _currentRenderTarget === null ) ? _this.outputEncoding : _currentRenderTarget.texture.encoding;
+               const envMap = ( 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;
 
-       update: function () {
+               if ( _clippingEnabled === true ) {
 
-               const bones = this.bones;
-               const boneInverses = this.boneInverses;
-               const boneMatrices = this.boneMatrices;
-               const boneTexture = this.boneTexture;
+                       if ( _localClippingEnabled === true || camera !== _currentCamera ) {
 
-               // flatten bone matrices to array
+                               const useCache =
+                                       camera === _currentCamera &&
+                                       material.id === _currentMaterialId;
 
-               for ( let i = 0, il = bones.length; i < il; i ++ ) {
+                               // 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 );
 
-                       // compute the offset between the current and the original transform
+                       }
 
-                       const matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix;
+               }
 
-                       _offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] );
-                       _offsetMatrix.toArray( boneMatrices, i * 16 );
+               //
 
-               }
+               let needsProgramChange = false;
 
-               if ( boneTexture !== null ) {
+               if ( material.version === materialProperties.__version ) {
 
-                       boneTexture.needsUpdate = true;
+                       if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) {
 
-               }
+                               needsProgramChange = true;
 
-       },
+                       } else if ( materialProperties.outputEncoding !== encoding ) {
 
-       clone: function () {
+                               needsProgramChange = true;
 
-               return new Skeleton( this.bones, this.boneInverses );
+                       } else if ( object.isInstancedMesh && materialProperties.instancing === false ) {
 
-       },
+                               needsProgramChange = true;
 
-       getBoneByName: function ( name ) {
+                       } else if ( ! object.isInstancedMesh && materialProperties.instancing === true ) {
 
-               for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+                               needsProgramChange = true;
 
-                       const bone = this.bones[ i ];
+                       } else if ( object.isSkinnedMesh && materialProperties.skinning === false ) {
 
-                       if ( bone.name === name ) {
+                               needsProgramChange = true;
 
-                               return bone;
+                       } else if ( ! object.isSkinnedMesh && materialProperties.skinning === true ) {
 
-                       }
+                               needsProgramChange = true;
 
-               }
+                       } else if ( materialProperties.envMap !== envMap ) {
 
-               return undefined;
+                               needsProgramChange = true;
 
-       },
+                       } else if ( material.fog && materialProperties.fog !== fog ) {
 
-       dispose: function ( ) {
+                               needsProgramChange = true;
 
-               if ( this.boneTexture !== null ) {
+                       } else if ( materialProperties.numClippingPlanes !== undefined &&
+                               ( materialProperties.numClippingPlanes !== clipping.numPlanes ||
+                               materialProperties.numIntersection !== clipping.numIntersection ) ) {
 
-                       this.boneTexture.dispose();
+                               needsProgramChange = true;
 
-                       this.boneTexture = null;
+                       } else if ( materialProperties.vertexAlphas !== vertexAlphas ) {
 
-               }
+                               needsProgramChange = true;
 
-       },
+                       } else if ( materialProperties.vertexTangents !== vertexTangents ) {
 
-       fromJSON: function ( json, bones ) {
+                               needsProgramChange = true;
 
-               this.uuid = json.uuid;
+                       } else if ( materialProperties.morphTargets !== morphTargets ) {
 
-               for ( let i = 0, l = json.bones.length; i < l; i ++ ) {
+                               needsProgramChange = true;
 
-                       const uuid = json.bones[ i ];
-                       let bone = bones[ uuid ];
+                       } else if ( materialProperties.morphNormals !== morphNormals ) {
 
-                       if ( bone === undefined ) {
+                               needsProgramChange = true;
 
-                               console.warn( 'THREE.Skeleton: No bone found with UUID:', uuid );
-                               bone = new Bone();
+                       } else if ( capabilities.isWebGL2 === true && materialProperties.morphTargetsCount !== morphTargetsCount ) {
+
+                               needsProgramChange = true;
 
                        }
 
-                       this.bones.push( bone );
-                       this.boneInverses.push( new Matrix4().fromArray( json.boneInverses[ i ] ) );
+               } else {
+
+                       needsProgramChange = true;
+                       materialProperties.__version = material.version;
 
                }
 
-               this.init();
-
-               return this;
+               //
 
-       },
+               let program = materialProperties.currentProgram;
 
-       toJSON: function () {
+               if ( needsProgramChange === true ) {
 
-               const data = {
-                       metadata: {
-                               version: 4.5,
-                               type: 'Skeleton',
-                               generator: 'Skeleton.toJSON'
-                       },
-                       bones: [],
-                       boneInverses: []
-               };
+                       program = getProgram( material, scene, object );
 
-               data.uuid = this.uuid;
+               }
 
-               const bones = this.bones;
-               const boneInverses = this.boneInverses;
+               let refreshProgram = false;
+               let refreshMaterial = false;
+               let refreshLights = false;
 
-               for ( let i = 0, l = bones.length; i < l; i ++ ) {
+               const p_uniforms = program.getUniforms(),
+                       m_uniforms = materialProperties.uniforms;
 
-                       const bone = bones[ i ];
-                       data.bones.push( bone.uuid );
+               if ( state.useProgram( program.program ) ) {
 
-                       const boneInverse = boneInverses[ i ];
-                       data.boneInverses.push( boneInverse.toArray() );
+                       refreshProgram = true;
+                       refreshMaterial = true;
+                       refreshLights = true;
 
                }
 
-               return data;
-
-       }
+               if ( material.id !== _currentMaterialId ) {
 
-    } );
+                       _currentMaterialId = material.id;
 
-    const _instanceLocalMatrix = new Matrix4();
-    const _instanceWorldMatrix = new Matrix4();
+                       refreshMaterial = true;
 
-    const _instanceIntersects = [];
+               }
 
-    const _mesh = new Mesh();
+               if ( refreshProgram || _currentCamera !== camera ) {
 
-    function InstancedMesh( geometry, material, count ) {
+                       p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix );
 
-       Mesh.call( this, geometry, material );
+                       if ( capabilities.logarithmicDepthBuffer ) {
 
-       this.instanceMatrix = new BufferAttribute( new Float32Array( count * 16 ), 16 );
-       this.instanceColor = null;
+                               p_uniforms.setValue( _gl, 'logDepthBufFC',
+                                       2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );
 
-       this.count = count;
+                       }
 
-       this.frustumCulled = false;
+                       if ( _currentCamera !== camera ) {
 
-    }
+                               _currentCamera = camera;
 
-    InstancedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
+                               // 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:
 
-       constructor: InstancedMesh,
+                               refreshMaterial = true;         // set to true on material change
+                               refreshLights = true;           // remains set until update done
 
-       isInstancedMesh: true,
+                       }
 
-       copy: function ( source ) {
+                       // load material specific uniforms
+                       // (shader material also gets them for the sake of genericity)
 
-               Mesh.prototype.copy.call( this, source );
+                       if ( material.isShaderMaterial ||
+                               material.isMeshPhongMaterial ||
+                               material.isMeshToonMaterial ||
+                               material.isMeshStandardMaterial ||
+                               material.envMap ) {
 
-               this.instanceMatrix.copy( source.instanceMatrix );
+                               const uCamPos = p_uniforms.map.cameraPosition;
 
-               if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone();
+                               if ( uCamPos !== undefined ) {
 
-               this.count = source.count;
+                                       uCamPos.setValue( _gl,
+                                               _vector3.setFromMatrixPosition( camera.matrixWorld ) );
 
-               return this;
+                               }
 
-       },
+                       }
 
-       getColorAt: function ( index, color ) {
+                       if ( material.isMeshPhongMaterial ||
+                               material.isMeshToonMaterial ||
+                               material.isMeshLambertMaterial ||
+                               material.isMeshBasicMaterial ||
+                               material.isMeshStandardMaterial ||
+                               material.isShaderMaterial ) {
 
-               color.fromArray( this.instanceColor.array, index * 3 );
+                               p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true );
 
-       },
+                       }
 
-       getMatrixAt: function ( index, matrix ) {
+                       if ( material.isMeshPhongMaterial ||
+                               material.isMeshToonMaterial ||
+                               material.isMeshLambertMaterial ||
+                               material.isMeshBasicMaterial ||
+                               material.isMeshStandardMaterial ||
+                               material.isShaderMaterial ||
+                               material.isShadowMaterial ||
+                               object.isSkinnedMesh ) {
 
-               matrix.fromArray( this.instanceMatrix.array, index * 16 );
+                               p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );
 
-       },
+                       }
 
-       raycast: function ( raycaster, intersects ) {
+               }
 
-               const matrixWorld = this.matrixWorld;
-               const raycastTimes = this.count;
+               // 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
 
-               _mesh.geometry = this.geometry;
-               _mesh.material = this.material;
+               if ( object.isSkinnedMesh ) {
 
-               if ( _mesh.material === undefined ) return;
+                       p_uniforms.setOptional( _gl, object, 'bindMatrix' );
+                       p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' );
 
-               for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) {
+                       const skeleton = object.skeleton;
 
-                       // calculate the world matrix for each instance
+                       if ( skeleton ) {
 
-                       this.getMatrixAt( instanceId, _instanceLocalMatrix );
+                               if ( capabilities.floatVertexTextures ) {
 
-                       _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix );
+                                       if ( skeleton.boneTexture === null ) skeleton.computeBoneTexture();
 
-                       // the mesh represents this single instance
+                                       p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures );
+                                       p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize );
 
-                       _mesh.matrixWorld = _instanceWorldMatrix;
+                               } else {
 
-                       _mesh.raycast( raycaster, _instanceIntersects );
+                                       p_uniforms.setOptional( _gl, skeleton, 'boneMatrices' );
 
-                       // process the result of raycast
+                               }
 
-                       for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) {
+                       }
 
-                               const intersect = _instanceIntersects[ i ];
-                               intersect.instanceId = instanceId;
-                               intersect.object = this;
-                               intersects.push( intersect );
+               }
 
-                       }
+               if ( !! geometry && ( geometry.morphAttributes.position !== undefined || geometry.morphAttributes.normal !== undefined ) ) {
 
-                       _instanceIntersects.length = 0;
+                       morphtargets.update( object, geometry, material, program );
 
                }
 
-       },
-
-       setColorAt: function ( index, color ) {
 
-               if ( this.instanceColor === null ) {
+               if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) {
 
-                       this.instanceColor = new BufferAttribute( new Float32Array( this.count * 3 ), 3 );
+                       materialProperties.receiveShadow = object.receiveShadow;
+                       p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow );
 
                }
 
-               color.toArray( this.instanceColor.array, index * 3 );
+               if ( refreshMaterial ) {
 
-       },
+                       p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure );
 
-       setMatrixAt: function ( index, matrix ) {
+                       if ( materialProperties.needsLights ) {
 
-               matrix.toArray( this.instanceMatrix.array, index * 16 );
+                               // the current material requires lighting info
 
-       },
+                               // note: all lighting uniforms are always set correctly
+                               // they simply reference the renderer's state for their
+                               // values
+                               //
+                               // use the current material's .needsUpdate flags to set
+                               // the GL state when required
 
-       updateMorphTargets: function () {
+                               markUniformsLightsNeedsUpdate( m_uniforms, refreshLights );
 
-       },
+                       }
 
-       dispose: function () {
+                       // refresh uniforms common to several materials
 
-               this.dispatchEvent( { type: 'dispose' } );
+                       if ( fog && material.fog ) {
 
-       }
+                               materials.refreshFogUniforms( m_uniforms, fog );
 
-    } );
+                       }
 
-    /**
-     * parameters = {
-     *  color: <hex>,
-     *  opacity: <float>,
-     *
-     *  linewidth: <float>,
-     *  linecap: "round",
-     *  linejoin: "round"
-     * }
-     */
+                       materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height, _transmissionRenderTarget );
 
-    function LineBasicMaterial( parameters ) {
+                       WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
 
-       Material.call( this );
+               }
 
-       this.type = 'LineBasicMaterial';
+               if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) {
 
-       this.color = new Color( 0xffffff );
+                       WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
+                       material.uniformsNeedUpdate = false;
 
-       this.linewidth = 1;
-       this.linecap = 'round';
-       this.linejoin = 'round';
+               }
 
-       this.morphTargets = false;
+               if ( material.isSpriteMaterial ) {
 
-       this.setValues( parameters );
+                       p_uniforms.setValue( _gl, 'center', object.center );
 
-    }
+               }
 
-    LineBasicMaterial.prototype = Object.create( Material.prototype );
-    LineBasicMaterial.prototype.constructor = LineBasicMaterial;
+               // common matrices
 
-    LineBasicMaterial.prototype.isLineBasicMaterial = true;
+               p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );
+               p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );
+               p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );
 
-    LineBasicMaterial.prototype.copy = function ( source ) {
+               return program;
 
-       Material.prototype.copy.call( this, source );
+       }
 
-       this.color.copy( source.color );
+       // If uniforms are marked as clean, they don't need to be loaded to the GPU.
 
-       this.linewidth = source.linewidth;
-       this.linecap = source.linecap;
-       this.linejoin = source.linejoin;
+       function markUniformsLightsNeedsUpdate( uniforms, value ) {
 
-       this.morphTargets = source.morphTargets;
+               uniforms.ambientLightColor.needsUpdate = value;
+               uniforms.lightProbe.needsUpdate = value;
 
-       return this;
+               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 _start = new Vector3();
-    const _end = new Vector3();
-    const _inverseMatrix$1 = new Matrix4();
-    const _ray$1 = new Ray();
-    const _sphere$2 = new Sphere();
+       function materialNeedsLights( material ) {
 
-    function Line( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) {
+               return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial ||
+                       material.isMeshStandardMaterial || material.isShadowMaterial ||
+                       ( material.isShaderMaterial && material.lights === true );
 
-       Object3D.call( this );
+       }
 
-       this.type = 'Line';
+       this.getActiveCubeFace = function () {
 
-       this.geometry = geometry;
-       this.material = material;
+               return _currentActiveCubeFace;
 
-       this.updateMorphTargets();
+       };
 
-    }
+       this.getActiveMipmapLevel = function () {
 
-    Line.prototype = Object.assign( Object.create( Object3D.prototype ), {
+               return _currentActiveMipmapLevel;
 
-       constructor: Line,
+       };
 
-       isLine: true,
+       this.getRenderTarget = function () {
 
-       copy: function ( source ) {
+               return _currentRenderTarget;
 
-               Object3D.prototype.copy.call( this, source );
+       };
 
-               this.material = source.material;
-               this.geometry = source.geometry;
+       this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) {
 
-               return this;
+               _currentRenderTarget = renderTarget;
+               _currentActiveCubeFace = activeCubeFace;
+               _currentActiveMipmapLevel = activeMipmapLevel;
 
-       },
+               if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) {
 
-       computeLineDistances: function () {
+                       textures.setupRenderTarget( renderTarget );
 
-               const geometry = this.geometry;
+               }
 
-               if ( geometry.isBufferGeometry ) {
+               let framebuffer = null;
+               let isCube = false;
+               let isRenderTarget3D = false;
 
-                       // we assume non-indexed geometry
+               if ( renderTarget ) {
 
-                       if ( geometry.index === null ) {
+                       const texture = renderTarget.texture;
 
-                               const positionAttribute = geometry.attributes.position;
-                               const lineDistances = [ 0 ];
+                       if ( texture.isDataTexture3D || texture.isDataTexture2DArray ) {
 
-                               for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) {
+                               isRenderTarget3D = true;
 
-                                       _start.fromBufferAttribute( positionAttribute, i - 1 );
-                                       _end.fromBufferAttribute( positionAttribute, i );
+                       }
 
-                                       lineDistances[ i ] = lineDistances[ i - 1 ];
-                                       lineDistances[ i ] += _start.distanceTo( _end );
+                       const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer;
 
-                               }
+                       if ( renderTarget.isWebGLCubeRenderTarget ) {
 
-                               geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
+                               framebuffer = __webglFramebuffer[ activeCubeFace ];
+                               isCube = true;
+
+                       } else if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+
+                               framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer;
 
                        } else {
 
-                               console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
+                               framebuffer = __webglFramebuffer;
 
                        }
 
-               } else if ( geometry.isGeometry ) {
+                       _currentViewport.copy( renderTarget.viewport );
+                       _currentScissor.copy( renderTarget.scissor );
+                       _currentScissorTest = renderTarget.scissorTest;
 
-                       console.error( 'THREE.Line.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+               } else {
+
+                       _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor();
+                       _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor();
+                       _currentScissorTest = _scissorTest;
 
                }
 
-               return this;
+               const framebufferBound = state.bindFramebuffer( 36160, framebuffer );
 
-       },
+               if ( framebufferBound && capabilities.drawBuffers ) {
 
-       raycast: function ( raycaster, intersects ) {
+                       let needsUpdate = false;
 
-               const geometry = this.geometry;
-               const matrixWorld = this.matrixWorld;
-               const threshold = raycaster.params.Line.threshold;
+                       if ( renderTarget ) {
 
-               // Checking boundingSphere distance to ray
+                               if ( renderTarget.isWebGLMultipleRenderTargets ) {
 
-               if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+                                       const textures = renderTarget.texture;
 
-               _sphere$2.copy( geometry.boundingSphere );
-               _sphere$2.applyMatrix4( matrixWorld );
-               _sphere$2.radius += threshold;
+                                       if ( _currentDrawBuffers.length !== textures.length || _currentDrawBuffers[ 0 ] !== 36064 ) {
 
-               if ( raycaster.ray.intersectsSphere( _sphere$2 ) === false ) return;
+                                               for ( let i = 0, il = textures.length; i < il; i ++ ) {
 
-               //
+                                                       _currentDrawBuffers[ i ] = 36064 + i;
 
-               _inverseMatrix$1.copy( matrixWorld ).invert();
-               _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 );
+                                               }
 
-               const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
-               const localThresholdSq = localThreshold * localThreshold;
+                                               _currentDrawBuffers.length = textures.length;
 
-               const vStart = new Vector3();
-               const vEnd = new Vector3();
-               const interSegment = new Vector3();
-               const interRay = new Vector3();
-               const step = this.isLineSegments ? 2 : 1;
+                                               needsUpdate = true;
 
-               if ( geometry.isBufferGeometry ) {
+                                       }
 
-                       const index = geometry.index;
-                       const attributes = geometry.attributes;
-                       const positionAttribute = attributes.position;
+                               } else {
 
-                       if ( index !== null ) {
+                                       if ( _currentDrawBuffers.length !== 1 || _currentDrawBuffers[ 0 ] !== 36064 ) {
 
-                               const indices = index.array;
+                                               _currentDrawBuffers[ 0 ] = 36064;
+                                               _currentDrawBuffers.length = 1;
 
-                               for ( let i = 0, l = indices.length - 1; i < l; i += step ) {
+                                               needsUpdate = true;
 
-                                       const a = indices[ i ];
-                                       const b = indices[ i + 1 ];
+                                       }
 
-                                       vStart.fromBufferAttribute( positionAttribute, a );
-                                       vEnd.fromBufferAttribute( positionAttribute, b );
+                               }
 
-                                       const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
+                       } else {
 
-                                       if ( distSq > localThresholdSq ) continue;
+                               if ( _currentDrawBuffers.length !== 1 || _currentDrawBuffers[ 0 ] !== 1029 ) {
 
-                                       interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
+                                       _currentDrawBuffers[ 0 ] = 1029;
+                                       _currentDrawBuffers.length = 1;
 
-                                       const distance = raycaster.ray.origin.distanceTo( interRay );
+                                       needsUpdate = 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 ( needsUpdate ) {
 
-                                       } );
+                               if ( capabilities.isWebGL2 ) {
+
+                                       _gl.drawBuffers( _currentDrawBuffers );
+
+                               } else {
+
+                                       extensions.get( 'WEBGL_draw_buffers' ).drawBuffersWEBGL( _currentDrawBuffers );
 
                                }
 
-                       } else {
+                       }
 
-                               for ( let i = 0, l = positionAttribute.count - 1; i < l; i += step ) {
+               }
 
-                                       vStart.fromBufferAttribute( positionAttribute, i );
-                                       vEnd.fromBufferAttribute( positionAttribute, i + 1 );
+               state.viewport( _currentViewport );
+               state.scissor( _currentScissor );
+               state.setScissorTest( _currentScissorTest );
 
-                                       const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
+               if ( isCube ) {
 
-                                       if ( distSq > localThresholdSq ) continue;
+                       const textureProperties = properties.get( renderTarget.texture );
+                       _gl.framebufferTexture2D( 36160, 36064, 34069 + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel );
 
-                                       interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
+               } else if ( isRenderTarget3D ) {
 
-                                       const distance = raycaster.ray.origin.distanceTo( interRay );
+                       const textureProperties = properties.get( renderTarget.texture );
+                       const layer = activeCubeFace || 0;
+                       _gl.framebufferTextureLayer( 36160, 36064, textureProperties.__webglTexture, activeMipmapLevel || 0, layer );
 
-                                       if ( distance < raycaster.near || distance > raycaster.far ) continue;
+               }
 
-                                       intersects.push( {
+               _currentMaterialId = - 1; // reset current material to ensure correct uniform bindings
 
-                                               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
+       };
 
-                                       } );
+       this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) {
 
-                               }
+               if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) {
 
-                       }
+                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' );
+                       return;
 
-               } else if ( geometry.isGeometry ) {
+               }
 
-                       console.error( 'THREE.Line.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+               let framebuffer = properties.get( renderTarget ).__webglFramebuffer;
+
+               if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) {
+
+                       framebuffer = framebuffer[ activeCubeFaceIndex ];
 
                }
 
-       },
+               if ( framebuffer ) {
 
-       updateMorphTargets: function () {
+                       state.bindFramebuffer( 36160, framebuffer );
 
-               const geometry = this.geometry;
+                       try {
 
-               if ( geometry.isBufferGeometry ) {
+                               const texture = renderTarget.texture;
+                               const textureFormat = texture.format;
+                               const textureType = texture.type;
 
-                       const morphAttributes = geometry.morphAttributes;
-                       const keys = Object.keys( morphAttributes );
+                               if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( 35739 ) ) {
 
-                       if ( keys.length > 0 ) {
+                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' );
+                                       return;
 
-                               const morphAttribute = morphAttributes[ keys[ 0 ] ];
+                               }
 
-                               if ( morphAttribute !== undefined ) {
+                               const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || ( capabilities.isWebGL2 && extensions.has( 'EXT_color_buffer_float' ) ) );
 
-                                       this.morphTargetInfluences = [];
-                                       this.morphTargetDictionary = {};
+                               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 ) {
 
-                                       for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
+                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' );
+                                       return;
 
-                                               const name = morphAttribute[ m ].name || String( m );
+                               }
 
-                                               this.morphTargetInfluences.push( 0 );
-                                               this.morphTargetDictionary[ name ] = m;
+                               if ( _gl.checkFramebufferStatus( 36160 ) === 36053 ) {
+
+                                       // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)
+
+                                       if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {
+
+                                               _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer );
 
                                        }
 
-                               }
+                               } else {
 
-                       }
+                                       console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' );
 
-               } else {
+                               }
 
-                       const morphTargets = geometry.morphTargets;
+                       } finally {
 
-                       if ( morphTargets !== undefined && morphTargets.length > 0 ) {
+                               // restore framebuffer of current render target if necessary
 
-                               console.error( 'THREE.Line.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
+                               const framebuffer = ( _currentRenderTarget !== null ) ? properties.get( _currentRenderTarget ).__webglFramebuffer : null;
+                               state.bindFramebuffer( 36160, framebuffer );
 
                        }
 
                }
 
-       }
+       };
 
-    } );
+       this.copyFramebufferToTexture = function ( position, texture, level = 0 ) {
 
-    const _start$1 = new Vector3();
-    const _end$1 = new Vector3();
+               const levelScale = Math.pow( 2, - level );
+               const width = Math.floor( texture.image.width * levelScale );
+               const height = Math.floor( texture.image.height * levelScale );
 
-    function LineSegments( geometry, material ) {
+               let glFormat = utils.convert( texture.format );
 
-       Line.call( this, geometry, material );
+               if ( capabilities.isWebGL2 ) {
 
-       this.type = 'LineSegments';
+                       // 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;
 
-    LineSegments.prototype = Object.assign( Object.create( Line.prototype ), {
+               }
 
-       constructor: LineSegments,
+               textures.setTexture2D( texture, 0 );
 
-       isLineSegments: true,
+               _gl.copyTexImage2D( 3553, level, glFormat, position.x, position.y, width, height, 0 );
 
-       computeLineDistances: function () {
+               state.unbindTexture();
 
-               const geometry = this.geometry;
+       };
 
-               if ( geometry.isBufferGeometry ) {
+       this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) {
 
-                       // we assume non-indexed geometry
+               const width = srcTexture.image.width;
+               const height = srcTexture.image.height;
+               const glFormat = utils.convert( dstTexture.format );
+               const glType = utils.convert( dstTexture.type );
 
-                       if ( geometry.index === null ) {
+               textures.setTexture2D( dstTexture, 0 );
 
-                               const positionAttribute = geometry.attributes.position;
-                               const lineDistances = [];
+               // 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 );
 
-                               for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) {
+               if ( srcTexture.isDataTexture ) {
 
-                                       _start$1.fromBufferAttribute( positionAttribute, i );
-                                       _end$1.fromBufferAttribute( positionAttribute, i + 1 );
+                       _gl.texSubImage2D( 3553, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data );
 
-                                       lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ];
-                                       lineDistances[ i + 1 ] = lineDistances[ i ] + _start$1.distanceTo( _end$1 );
+               } else {
 
-                               }
+                       if ( srcTexture.isCompressedTexture ) {
 
-                               geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
+                               _gl.compressedTexSubImage2D( 3553, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data );
 
                        } else {
 
-                               console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
+                               _gl.texSubImage2D( 3553, level, position.x, position.y, glFormat, glType, srcTexture.image );
 
                        }
 
-               } else if ( geometry.isGeometry ) {
-
-                       console.error( 'THREE.LineSegments.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
-
                }
 
-               return this;
+               // Generate mipmaps only when copying level 0
+               if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( 3553 );
 
-       }
+               state.unbindTexture();
 
-    } );
+       };
 
-    function LineLoop( geometry, material ) {
+       this.copyTextureToTexture3D = function ( sourceBox, position, srcTexture, dstTexture, level = 0 ) {
 
-       Line.call( this, geometry, material );
+               if ( _this.isWebGL1Renderer ) {
 
-       this.type = 'LineLoop';
+                       console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: can only be used with WebGL2.' );
+                       return;
 
-    }
+               }
 
-    LineLoop.prototype = Object.assign( Object.create( Line.prototype ), {
+               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;
 
-       constructor: LineLoop,
+               if ( dstTexture.isDataTexture3D ) {
 
-       isLineLoop: true,
+                       textures.setTexture3D( dstTexture, 0 );
+                       glTarget = 32879;
 
-    } );
+               } else if ( dstTexture.isDataTexture2DArray ) {
 
-    /**
-     * parameters = {
-     *  color: <hex>,
-     *  opacity: <float>,
-     *  map: new THREE.Texture( <Image> ),
-     *  alphaMap: new THREE.Texture( <Image> ),
-     *
-     *  size: <float>,
-     *  sizeAttenuation: <bool>
-     *
-     *  morphTargets: <bool>
-     * }
-     */
+                       textures.setTexture2DArray( dstTexture, 0 );
+                       glTarget = 35866;
 
-    function PointsMaterial( parameters ) {
+               } else {
 
-       Material.call( this );
+                       console.warn( 'THREE.WebGLRenderer.copyTextureToTexture3D: only supports THREE.DataTexture3D and THREE.DataTexture2DArray.' );
+                       return;
 
-       this.type = 'PointsMaterial';
+               }
 
-       this.color = new Color( 0xffffff );
+               _gl.pixelStorei( 37440, dstTexture.flipY );
+               _gl.pixelStorei( 37441, dstTexture.premultiplyAlpha );
+               _gl.pixelStorei( 3317, dstTexture.unpackAlignment );
 
-       this.map = null;
+               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 );
 
-       this.alphaMap = null;
+               const image = srcTexture.isCompressedTexture ? srcTexture.mipmaps[ 0 ] : srcTexture.image;
 
-       this.size = 1;
-       this.sizeAttenuation = true;
+               _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 );
 
-       this.morphTargets = false;
+               if ( srcTexture.isDataTexture || srcTexture.isDataTexture3D ) {
 
-       this.setValues( parameters );
+                       _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image.data );
 
-    }
+               } else {
 
-    PointsMaterial.prototype = Object.create( Material.prototype );
-    PointsMaterial.prototype.constructor = PointsMaterial;
+                       if ( srcTexture.isCompressedTexture ) {
 
-    PointsMaterial.prototype.isPointsMaterial = true;
+                               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 );
 
-    PointsMaterial.prototype.copy = function ( source ) {
+                       } else {
 
-       Material.prototype.copy.call( this, source );
+                               _gl.texSubImage3D( glTarget, level, position.x, position.y, position.z, width, height, depth, glFormat, glType, image );
 
-       this.color.copy( source.color );
+                       }
 
-       this.map = source.map;
+               }
 
-       this.alphaMap = source.alphaMap;
+               _gl.pixelStorei( 3314, unpackRowLen );
+               _gl.pixelStorei( 32878, unpackImageHeight );
+               _gl.pixelStorei( 3316, unpackSkipPixels );
+               _gl.pixelStorei( 3315, unpackSkipRows );
+               _gl.pixelStorei( 32877, unpackSkipImages );
 
-       this.size = source.size;
-       this.sizeAttenuation = source.sizeAttenuation;
+               // Generate mipmaps only when copying level 0
+               if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( glTarget );
 
-       this.morphTargets = source.morphTargets;
+               state.unbindTexture();
 
-       return this;
+       };
 
-    };
+       this.initTexture = function ( texture ) {
 
-    const _inverseMatrix$2 = new Matrix4();
-    const _ray$2 = new Ray();
-    const _sphere$3 = new Sphere();
-    const _position$1 = new Vector3();
+               textures.setTexture2D( texture, 0 );
 
-    function Points( geometry = new BufferGeometry(), material = new PointsMaterial() ) {
+               state.unbindTexture();
 
-       Object3D.call( this );
+       };
 
-       this.type = 'Points';
+       this.resetState = function () {
 
-       this.geometry = geometry;
-       this.material = material;
+               _currentActiveCubeFace = 0;
+               _currentActiveMipmapLevel = 0;
+               _currentRenderTarget = null;
 
-       this.updateMorphTargets();
+               state.reset();
+               bindingStates.reset();
 
-    }
+       };
 
-    Points.prototype = Object.assign( Object.create( Object3D.prototype ), {
+       if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
 
-       constructor: Points,
+               __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
 
-       isPoints: true,
+       }
 
-       copy: function ( source ) {
+    }
 
-               Object3D.prototype.copy.call( this, source );
+    WebGLRenderer.prototype.isWebGLRenderer = true;
 
-               this.material = source.material;
-               this.geometry = source.geometry;
+    class WebGL1Renderer extends WebGLRenderer {}
 
-               return this;
+    WebGL1Renderer.prototype.isWebGL1Renderer = true;
 
-       },
+    class Scene extends Object3D {
 
-       raycast: function ( raycaster, intersects ) {
+       constructor() {
 
-               const geometry = this.geometry;
-               const matrixWorld = this.matrixWorld;
-               const threshold = raycaster.params.Points.threshold;
+               super();
 
-               // Checking boundingSphere distance to ray
+               this.type = 'Scene';
 
-               if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+               this.background = null;
+               this.environment = null;
+               this.fog = null;
 
-               _sphere$3.copy( geometry.boundingSphere );
-               _sphere$3.applyMatrix4( matrixWorld );
-               _sphere$3.radius += threshold;
+               this.overrideMaterial = null;
 
-               if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return;
+               this.autoUpdate = true; // checked by the renderer
 
-               //
+               if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
 
-               _inverseMatrix$2.copy( matrixWorld ).invert();
-               _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 );
+                       __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
 
-               const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
-               const localThresholdSq = localThreshold * localThreshold;
+               }
 
-               if ( geometry.isBufferGeometry ) {
+       }
 
-                       const index = geometry.index;
-                       const attributes = geometry.attributes;
-                       const positionAttribute = attributes.position;
+       copy( source, recursive ) {
 
-                       if ( index !== null ) {
+               super.copy( source, recursive );
 
-                               const indices = index.array;
+               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();
 
-                               for ( let i = 0, il = indices.length; i < il; i ++ ) {
+               if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone();
 
-                                       const a = indices[ i ];
+               this.autoUpdate = source.autoUpdate;
+               this.matrixAutoUpdate = source.matrixAutoUpdate;
 
-                                       _position$1.fromBufferAttribute( positionAttribute, a );
+               return this;
 
-                                       testPoint( _position$1, a, localThresholdSq, matrixWorld, raycaster, intersects, this );
+       }
 
-                               }
+       toJSON( meta ) {
 
-                       } else {
+               const data = super.toJSON( meta );
 
-                               for ( let i = 0, l = positionAttribute.count; i < l; i ++ ) {
+               if ( this.fog !== null ) data.object.fog = this.fog.toJSON();
 
-                                       _position$1.fromBufferAttribute( positionAttribute, i );
+               return data;
 
-                                       testPoint( _position$1, i, localThresholdSq, matrixWorld, raycaster, intersects, this );
+       }
 
-                               }
+    }
 
-                       }
+    Scene.prototype.isScene = true;
 
-               } else {
+    class InterleavedBuffer {
 
-                       console.error( 'THREE.Points.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+       constructor( array, stride ) {
 
-               }
+               this.array = array;
+               this.stride = stride;
+               this.count = array !== undefined ? array.length / stride : 0;
 
-       },
+               this.usage = StaticDrawUsage;
+               this.updateRange = { offset: 0, count: - 1 };
 
-       updateMorphTargets: function () {
+               this.version = 0;
 
-               const geometry = this.geometry;
+               this.uuid = generateUUID();
 
-               if ( geometry.isBufferGeometry ) {
+       }
 
-                       const morphAttributes = geometry.morphAttributes;
-                       const keys = Object.keys( morphAttributes );
+       onUploadCallback() {}
 
-                       if ( keys.length > 0 ) {
+       set needsUpdate( value ) {
 
-                               const morphAttribute = morphAttributes[ keys[ 0 ] ];
+               if ( value === true ) this.version ++;
 
-                               if ( morphAttribute !== undefined ) {
+       }
 
-                                       this.morphTargetInfluences = [];
-                                       this.morphTargetDictionary = {};
+       setUsage( value ) {
 
-                                       for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
+               this.usage = value;
 
-                                               const name = morphAttribute[ m ].name || String( m );
+               return this;
 
-                                               this.morphTargetInfluences.push( 0 );
-                                               this.morphTargetDictionary[ name ] = m;
+       }
 
-                                       }
+       copy( source ) {
 
-                               }
+               this.array = new source.array.constructor( source.array );
+               this.count = source.count;
+               this.stride = source.stride;
+               this.usage = source.usage;
 
-                       }
+               return this;
 
-               } else {
+       }
 
-                       const morphTargets = geometry.morphTargets;
+       copyAt( index1, attribute, index2 ) {
 
-                       if ( morphTargets !== undefined && morphTargets.length > 0 ) {
+               index1 *= this.stride;
+               index2 *= attribute.stride;
 
-                               console.error( 'THREE.Points.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
+               for ( let i = 0, l = this.stride; i < l; i ++ ) {
 
-                       }
+                       this.array[ index1 + i ] = attribute.array[ index2 + i ];
 
                }
 
+               return this;
+
        }
 
-    } );
+       set( value, offset = 0 ) {
 
-    function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) {
+               this.array.set( value, offset );
 
-       const rayPointDistanceSq = _ray$2.distanceSqToPoint( point );
+               return this;
 
-       if ( rayPointDistanceSq < localThresholdSq ) {
+       }
 
-               const intersectPoint = new Vector3();
+       clone( data ) {
 
-               _ray$2.closestPointToPoint( point, intersectPoint );
-               intersectPoint.applyMatrix4( matrixWorld );
+               if ( data.arrayBuffers === undefined ) {
 
-               const distance = raycaster.ray.origin.distanceTo( intersectPoint );
+                       data.arrayBuffers = {};
 
-               if ( distance < raycaster.near || distance > raycaster.far ) return;
+               }
 
-               intersects.push( {
+               if ( this.array.buffer._uuid === undefined ) {
 
-                       distance: distance,
-                       distanceToRay: Math.sqrt( rayPointDistanceSq ),
-                       point: intersectPoint,
-                       index: index,
-                       face: null,
-                       object: object
+                       this.array.buffer._uuid = generateUUID();
 
-               } );
+               }
 
-       }
+               if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) {
 
-    }
+                       data.arrayBuffers[ this.array.buffer._uuid ] = this.array.slice( 0 ).buffer;
 
-    function VideoTexture( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
+               }
 
-       Texture.call( this, video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+               const array = new this.array.constructor( data.arrayBuffers[ this.array.buffer._uuid ] );
 
-       this.format = format !== undefined ? format : RGBFormat;
+               const ib = new this.constructor( array, this.stride );
+               ib.setUsage( this.usage );
 
-       this.minFilter = minFilter !== undefined ? minFilter : LinearFilter;
-       this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;
+               return ib;
 
-       this.generateMipmaps = false;
+       }
 
-       const scope = this;
+       onUpload( callback ) {
 
-       function updateVideo() {
+               this.onUploadCallback = callback;
 
-               scope.needsUpdate = true;
-               video.requestVideoFrameCallback( updateVideo );
+               return this;
 
        }
 
-       if ( 'requestVideoFrameCallback' in video ) {
+       toJSON( data ) {
 
-               video.requestVideoFrameCallback( updateVideo );
+               if ( data.arrayBuffers === undefined ) {
 
-       }
+                       data.arrayBuffers = {};
 
-    }
+               }
 
-    VideoTexture.prototype = Object.assign( Object.create( Texture.prototype ), {
+               // generate UUID for array buffer if necessary
 
-       constructor: VideoTexture,
-
-       clone: function () {
-
-               return new this.constructor( this.image ).copy( this );
+               if ( this.array.buffer._uuid === undefined ) {
 
-       },
+                       this.array.buffer._uuid = generateUUID();
 
-       isVideoTexture: true,
+               }
 
-       update: function () {
+               if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) {
 
-               const video = this.image;
-               const hasVideoFrameCallback = 'requestVideoFrameCallback' in video;
+                       data.arrayBuffers[ this.array.buffer._uuid ] = Array.prototype.slice.call( new Uint32Array( this.array.buffer ) );
 
-               if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) {
+               }
 
-                       this.needsUpdate = true;
+               //
 
-               }
+               return {
+                       uuid: this.uuid,
+                       buffer: this.array.buffer._uuid,
+                       type: this.array.constructor.name,
+                       stride: this.stride
+               };
 
        }
 
-    } );
+    }
 
-    function CompressedTexture( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {
+    InterleavedBuffer.prototype.isInterleavedBuffer = true;
 
-       Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
+    const _vector$6 = /*@__PURE__*/ new Vector3();
 
-       this.image = { width: width, height: height };
-       this.mipmaps = mipmaps;
+    class InterleavedBufferAttribute {
 
-       // no flipping for cube textures
-       // (also flipping doesn't work for compressed textures )
+       constructor( interleavedBuffer, itemSize, offset, normalized = false ) {
 
-       this.flipY = false;
+               this.name = '';
 
-       // can't generate mipmaps for compressed textures
-       // mips must be embedded in DDS files
+               this.data = interleavedBuffer;
+               this.itemSize = itemSize;
+               this.offset = offset;
 
-       this.generateMipmaps = false;
+               this.normalized = normalized === true;
 
-    }
+       }
 
-    CompressedTexture.prototype = Object.create( Texture.prototype );
-    CompressedTexture.prototype.constructor = CompressedTexture;
+       get count() {
 
-    CompressedTexture.prototype.isCompressedTexture = true;
+               return this.data.count;
 
-    function CanvasTexture( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
+       }
 
-       Texture.call( this, canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+       get array() {
 
-       this.needsUpdate = true;
+               return this.data.array;
 
-    }
+       }
 
-    CanvasTexture.prototype = Object.create( Texture.prototype );
-    CanvasTexture.prototype.constructor = CanvasTexture;
-    CanvasTexture.prototype.isCanvasTexture = true;
+       set needsUpdate( value ) {
 
-    function DepthTexture( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) {
+               this.data.needsUpdate = value;
 
-       format = format !== undefined ? format : DepthFormat;
+       }
 
-       if ( format !== DepthFormat && format !== DepthStencilFormat ) {
+       applyMatrix4( m ) {
 
-               throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' );
+               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 );
 
-       if ( type === undefined && format === DepthFormat ) type = UnsignedShortType;
-       if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type;
+                       _vector$6.applyMatrix4( m );
 
-       Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+                       this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z );
 
-       this.image = { width: width, height: height };
+               }
 
-       this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
-       this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;
+               return this;
 
-       this.flipY = false;
-       this.generateMipmaps = false;
+       }
 
-    }
+       applyNormalMatrix( m ) {
 
-    DepthTexture.prototype = Object.create( Texture.prototype );
-    DepthTexture.prototype.constructor = DepthTexture;
-    DepthTexture.prototype.isDepthTexture = true;
+               for ( let i = 0, l = this.count; i < l; i ++ ) {
 
-    class CircleGeometry extends BufferGeometry {
+                       _vector$6.x = this.getX( i );
+                       _vector$6.y = this.getY( i );
+                       _vector$6.z = this.getZ( i );
 
-       constructor( radius = 1, segments = 8, thetaStart = 0, thetaLength = Math.PI * 2 ) {
+                       _vector$6.applyNormalMatrix( m );
 
-               super();
+                       this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z );
 
-               this.type = 'CircleGeometry';
+               }
 
-               this.parameters = {
-                       radius: radius,
-                       segments: segments,
-                       thetaStart: thetaStart,
-                       thetaLength: thetaLength
-               };
+               return this;
 
-               segments = Math.max( 3, segments );
+       }
 
-               // buffers
+       transformDirection( m ) {
 
-               const indices = [];
-               const vertices = [];
-               const normals = [];
-               const uvs = [];
+               for ( let i = 0, l = this.count; i < l; i ++ ) {
 
-               // helper variables
+                       _vector$6.x = this.getX( i );
+                       _vector$6.y = this.getY( i );
+                       _vector$6.z = this.getZ( i );
 
-               const vertex = new Vector3();
-               const uv = new Vector2();
+                       _vector$6.transformDirection( m );
 
-               // center point
+                       this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z );
 
-               vertices.push( 0, 0, 0 );
-               normals.push( 0, 0, 1 );
-               uvs.push( 0.5, 0.5 );
+               }
 
-               for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) {
+               return this;
 
-                       const segment = thetaStart + s / segments * thetaLength;
+       }
 
-                       // vertex
+       setX( index, x ) {
 
-                       vertex.x = radius * Math.cos( segment );
-                       vertex.y = radius * Math.sin( segment );
+               this.data.array[ index * this.data.stride + this.offset ] = x;
 
-                       vertices.push( vertex.x, vertex.y, vertex.z );
+               return this;
 
-                       // normal
+       }
 
-                       normals.push( 0, 0, 1 );
+       setY( index, y ) {
 
-                       // uvs
+               this.data.array[ index * this.data.stride + this.offset + 1 ] = y;
 
-                       uv.x = ( vertices[ i ] / radius + 1 ) / 2;
-                       uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2;
+               return this;
 
-                       uvs.push( uv.x, uv.y );
+       }
 
-               }
+       setZ( index, z ) {
 
-               // indices
+               this.data.array[ index * this.data.stride + this.offset + 2 ] = z;
 
-               for ( let i = 1; i <= segments; i ++ ) {
+               return this;
 
-                       indices.push( i, i + 1, 0 );
+       }
 
-               }
+       setW( index, w ) {
 
-               // build geometry
+               this.data.array[ index * this.data.stride + this.offset + 3 ] = w;
 
-               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 this;
 
        }
 
-    }
-
-    new Vector3();
-    new Vector3();
-    new Vector3();
-    new Triangle();
+       getX( index ) {
 
-    /**
-     * Port from https://github.com/mapbox/earcut (v2.2.2)
-     */
+               return this.data.array[ index * this.data.stride + this.offset ];
 
-    const Earcut = {
+       }
 
-       triangulate: function ( data, holeIndices, dim ) {
+       getY( index ) {
 
-               dim = dim || 2;
+               return this.data.array[ index * this.data.stride + this.offset + 1 ];
 
-               const hasHoles = holeIndices && holeIndices.length;
-               const outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length;
-               let outerNode = linkedList$1( data, 0, outerLen, dim, true );
-               const triangles = [];
+       }
 
-               if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles;
+       getZ( index ) {
 
-               let minX, minY, maxX, maxY, x, y, invSize;
+               return this.data.array[ index * this.data.stride + this.offset + 2 ];
 
-               if ( hasHoles ) outerNode = eliminateHoles$1( data, holeIndices, outerNode, dim );
+       }
 
-               // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
-               if ( data.length > 80 * dim ) {
+       getW( index ) {
 
-                       minX = maxX = data[ 0 ];
-                       minY = maxY = data[ 1 ];
+               return this.data.array[ index * this.data.stride + this.offset + 3 ];
 
-                       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;
+       setXY( index, x, y ) {
 
-                       }
+               index = index * this.data.stride + this.offset;
 
-                       // 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.data.array[ index + 0 ] = x;
+               this.data.array[ index + 1 ] = y;
 
-               }
+               return this;
 
-               earcutLinked$1( outerNode, triangles, dim, minX, minY, invSize );
+       }
 
-               return triangles;
+       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;
 
-    // create a circular doubly linked list from polygon points in the specified winding order
-    function linkedList$1( data, start, end, dim, clockwise ) {
+               return this;
 
-       let i, last;
+       }
 
-       if ( clockwise === ( signedArea$2( data, start, end, dim ) > 0 ) ) {
+       setXYZW( index, x, y, z, w ) {
 
-               for ( i = start; i < end; i += dim ) last = insertNode$2( i, data[ i ], data[ i + 1 ], last );
+               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;
+               this.data.array[ index + 3 ] = w;
 
-               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 ) ) {
+       clone( data ) {
 
-               removeNode$2( last );
-               last = last.next;
+               if ( data === undefined ) {
 
-       }
+                       console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interlaved buffer attribute will deinterleave buffer data.' );
 
-       return last;
+                       const array = [];
 
-    }
+                       for ( let i = 0; i < this.count; i ++ ) {
 
-    // eliminate colinear or duplicate points
-    function filterPoints$1( start, end ) {
+                               const index = i * this.data.stride + this.offset;
 
-       if ( ! start ) return start;
-       if ( ! end ) end = start;
+                               for ( let j = 0; j < this.itemSize; j ++ ) {
 
-       let p = start,
-               again;
-       do {
+                                       array.push( this.data.array[ index + j ] );
 
-               again = false;
+                               }
 
-               if ( ! p.steiner && ( equals$2( p, p.next ) || area$1( p.prev, p, p.next ) === 0 ) ) {
+                       }
 
-                       removeNode$2( p );
-                       p = end = p.prev;
-                       if ( p === p.next ) break;
-                       again = true;
+                       return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized );
 
                } else {
 
-                       p = p.next;
+                       if ( data.interleavedBuffers === undefined ) {
 
-               }
+                               data.interleavedBuffers = {};
 
-       } while ( again || p !== end );
+                       }
 
-       return end;
+                       if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
 
-    }
+                               data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data );
 
-    // main ear slicing loop which triangulates a polygon (given as a linked list)
-    function earcutLinked$1( ear, triangles, dim, minX, minY, invSize, pass ) {
+                       }
 
-       if ( ! ear ) return;
+                       return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized );
 
-       // interlink polygon nodes in z-order
-       if ( ! pass && invSize ) indexCurve$1( ear, minX, minY, invSize );
+               }
 
-       let stop = ear,
-               prev, next;
+       }
 
-       // iterate through ears, slicing them one by one
-       while ( ear.prev !== ear.next ) {
+       toJSON( data ) {
 
-               prev = ear.prev;
-               next = ear.next;
+               if ( data === undefined ) {
 
-               if ( invSize ? isEarHashed$1( ear, minX, minY, invSize ) : isEar$1( ear ) ) {
+                       console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interlaved buffer attribute will deinterleave buffer data.' );
 
-                       // cut off the triangle
-                       triangles.push( prev.i / dim );
-                       triangles.push( ear.i / dim );
-                       triangles.push( next.i / dim );
+                       const array = [];
 
-                       removeNode$2( ear );
+                       for ( let i = 0; i < this.count; i ++ ) {
 
-                       // skipping the next vertex leads to less sliver triangles
-                       ear = next.next;
-                       stop = next.next;
+                               const index = i * this.data.stride + this.offset;
 
-                       continue;
+                               for ( let j = 0; j < this.itemSize; j ++ ) {
 
-               }
+                                       array.push( this.data.array[ index + j ] );
 
-               ear = next;
+                               }
 
-               // if we looped through the whole remaining polygon and can't find any more ears
-               if ( ear === stop ) {
+                       }
 
-                       // try filtering points and slicing again
-                       if ( ! pass ) {
+                       // deinterleave data and save it as an ordinary buffer attribute for now
 
-                               earcutLinked$1( filterPoints$1( ear ), triangles, dim, minX, minY, invSize, 1 );
+                       return {
+                               itemSize: this.itemSize,
+                               type: this.array.constructor.name,
+                               array: array,
+                               normalized: this.normalized
+                       };
 
-                               // if this didn't work, try curing all small self-intersections locally
+               } else {
 
-                       } else if ( pass === 1 ) {
+                       // save as true interlaved attribtue
 
-                               ear = cureLocalIntersections$1( filterPoints$1( ear ), triangles, dim );
-                               earcutLinked$1( ear, triangles, dim, minX, minY, invSize, 2 );
+                       if ( data.interleavedBuffers === undefined ) {
 
-                               // as a last resort, try splitting the remaining polygon into two
+                               data.interleavedBuffers = {};
 
-                       } else if ( pass === 2 ) {
+                       }
 
-                               splitEarcut$1( ear, triangles, dim, minX, minY, invSize );
+                       if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
+
+                               data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data );
 
                        }
 
-                       break;
+                       return {
+                               isInterleavedBufferAttribute: true,
+                               itemSize: this.itemSize,
+                               data: this.data.uuid,
+                               offset: this.offset,
+                               normalized: this.normalized
+                       };
 
                }
 
 
     }
 
-    // check whether a polygon node forms a valid ear with adjacent nodes
-    function isEar$1( ear ) {
-
-       const a = ear.prev,
-               b = ear,
-               c = ear.next;
-
-       if ( area$1( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
+    InterleavedBufferAttribute.prototype.isInterleavedBufferAttribute = true;
 
-       // now make sure we don't have other points inside the potential ear
-       let p = ear.next.next;
+    /**
+     * parameters = {
+     *  color: <hex>,
+     *  map: new THREE.Texture( <Image> ),
+     *  alphaMap: new THREE.Texture( <Image> ),
+     *  rotation: <float>,
+     *  sizeAttenuation: <bool>
+     * }
+     */
 
-       while ( p !== ear.prev ) {
+    class SpriteMaterial extends Material {
 
-               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;
+       constructor( parameters ) {
 
-       }
+               super();
 
-       return true;
+               this.type = 'SpriteMaterial';
 
-    }
+               this.color = new Color( 0xffffff );
 
-    function isEarHashed$1( ear, minX, minY, invSize ) {
+               this.map = null;
 
-       const a = ear.prev,
-               b = ear,
-               c = ear.next;
+               this.alphaMap = null;
 
-       if ( area$1( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
+               this.rotation = 0;
 
-       // 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 );
+               this.sizeAttenuation = true;
 
-       // 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 );
+               this.transparent = true;
 
-       let p = ear.prevZ,
-               n = ear.nextZ;
+               this.setValues( parameters );
 
-       // 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;
+       copy( source ) {
 
-               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;
+               super.copy( source );
 
-       }
+               this.color.copy( source.color );
 
-       // look for remaining points in decreasing z-order
-       while ( p && p.z >= minZ ) {
+               this.map = source.map;
 
-               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;
+               this.alphaMap = source.alphaMap;
 
-       }
+               this.rotation = source.rotation;
 
-       // look for remaining points in increasing z-order
-       while ( n && n.z <= maxZ ) {
+               this.sizeAttenuation = source.sizeAttenuation;
 
-               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 this;
 
        }
 
-       return true;
-
     }
 
-    // go through all polygon nodes and cure small local self-intersections
-    function cureLocalIntersections$1( start, triangles, dim ) {
+    SpriteMaterial.prototype.isSpriteMaterial = true;
 
-       let p = start;
-       do {
+    let _geometry;
 
-               const a = p.prev,
-                       b = p.next.next;
+    const _intersectPoint = /*@__PURE__*/ new Vector3();
+    const _worldScale = /*@__PURE__*/ new Vector3();
+    const _mvPosition = /*@__PURE__*/ new Vector3();
 
-               if ( ! equals$2( a, b ) && intersects$2( a, p, p.next, b ) && locallyInside$1( a, b ) && locallyInside$1( b, a ) ) {
+    const _alignedPosition = /*@__PURE__*/ new Vector2();
+    const _rotatedPosition = /*@__PURE__*/ new Vector2();
+    const _viewWorldMatrix = /*@__PURE__*/ new Matrix4();
 
-                       triangles.push( a.i / dim );
-                       triangles.push( p.i / dim );
-                       triangles.push( b.i / dim );
+    const _vA = /*@__PURE__*/ new Vector3();
+    const _vB = /*@__PURE__*/ new Vector3();
+    const _vC = /*@__PURE__*/ new Vector3();
 
-                       // remove two nodes involved
-                       removeNode$2( p );
-                       removeNode$2( p.next );
+    const _uvA = /*@__PURE__*/ new Vector2();
+    const _uvB = /*@__PURE__*/ new Vector2();
+    const _uvC = /*@__PURE__*/ new Vector2();
 
-                       p = start = b;
+    class Sprite extends Object3D {
 
-               }
+       constructor( material ) {
 
-               p = p.next;
+               super();
 
-       } while ( p !== start );
+               this.type = 'Sprite';
 
-       return filterPoints$1( p );
+               if ( _geometry === undefined ) {
 
-    }
+                       _geometry = new BufferGeometry();
 
-    // try splitting polygon into two and triangulate them independently
-    function splitEarcut$1( start, triangles, dim, minX, minY, invSize ) {
+                       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
+                       ] );
 
-       // look for a valid diagonal that divides the polygon into two
-       let a = start;
-       do {
+                       const interleavedBuffer = new InterleavedBuffer( float32Array, 5 );
 
-               let b = a.next.next;
-               while ( b !== a.prev ) {
+                       _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 ( a.i !== b.i && isValidDiagonal$1( a, b ) ) {
+               }
 
-                               // split the polygon in two by the diagonal
-                               let c = splitPolygon$1( a, b );
+               this.geometry = _geometry;
+               this.material = ( material !== undefined ) ? material : new SpriteMaterial();
 
-                               // filter colinear points around the cuts
-                               a = filterPoints$1( a, a.next );
-                               c = filterPoints$1( c, c.next );
+               this.center = new Vector2( 0.5, 0.5 );
 
-                               // run earcut on each half
-                               earcutLinked$1( a, triangles, dim, minX, minY, invSize );
-                               earcutLinked$1( c, triangles, dim, minX, minY, invSize );
-                               return;
+       }
 
-                       }
+       raycast( raycaster, intersects ) {
 
-                       b = b.next;
+               if ( raycaster.camera === null ) {
+
+                       console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' );
 
                }
 
-               a = a.next;
+               _worldScale.setFromMatrixScale( this.matrixWorld );
 
-       } while ( a !== start );
+               _viewWorldMatrix.copy( raycaster.camera.matrixWorld );
+               this.modelViewMatrix.multiplyMatrices( raycaster.camera.matrixWorldInverse, this.matrixWorld );
 
-    }
+               _mvPosition.setFromMatrixPosition( this.modelViewMatrix );
 
-    // link every hole into the outer loop, producing a single-ring polygon without holes
-    function eliminateHoles$1( data, holeIndices, outerNode, dim ) {
+               if ( raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false ) {
 
-       const queue = [];
-       let i, len, start, end, list;
+                       _worldScale.multiplyScalar( - _mvPosition.z );
 
-       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 ) );
+               const rotation = this.material.rotation;
+               let sin, cos;
 
-       }
+               if ( rotation !== 0 ) {
 
-       queue.sort( compareX$1 );
+                       cos = Math.cos( rotation );
+                       sin = Math.sin( rotation );
 
-       // process holes from left to right
-       for ( i = 0; i < queue.length; i ++ ) {
+               }
 
-               eliminateHole$1( queue[ i ], outerNode );
-               outerNode = filterPoints$1( outerNode, outerNode.next );
+               const center = this.center;
 
-       }
+               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 );
 
-       return outerNode;
+               _uvA.set( 0, 0 );
+               _uvB.set( 1, 0 );
+               _uvC.set( 1, 1 );
 
-    }
+               // check first triangle
+               let intersect = raycaster.ray.intersectTriangle( _vA, _vB, _vC, false, _intersectPoint );
 
-    function compareX$1( a, b ) {
+               if ( intersect === null ) {
 
-       return a.x - b.x;
+                       // check second triangle
+                       transformVertex( _vB.set( - 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+                       _uvB.set( 0, 1 );
 
-    }
+                       intersect = raycaster.ray.intersectTriangle( _vA, _vC, _vB, false, _intersectPoint );
+                       if ( intersect === null ) {
 
-    // find a bridge between vertices that connects hole with an outer ring and and link it
-    function eliminateHole$1( hole, outerNode ) {
+                               return;
 
-       outerNode = findHoleBridge$1( hole, outerNode );
-       if ( outerNode ) {
+                       }
 
-               const b = splitPolygon$1( outerNode, hole );
+               }
 
-               // filter collinear points around the cuts
-               filterPoints$1( outerNode, outerNode.next );
-               filterPoints$1( b, b.next );
+               const distance = raycaster.ray.origin.distanceTo( _intersectPoint );
 
-       }
+               if ( distance < raycaster.near || distance > raycaster.far ) return;
 
-    }
+               intersects.push( {
 
-    // David Eberly's algorithm for finding a bridge between hole and outer polygon
-    function findHoleBridge$1( hole, outerNode ) {
+                       distance: distance,
+                       point: _intersectPoint.clone(),
+                       uv: Triangle.getUV( _intersectPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() ),
+                       face: null,
+                       object: this
 
-       let p = outerNode;
-       const hx = hole.x;
-       const hy = hole.y;
-       let qx = - Infinity, m;
+               } );
 
-       // find a segment intersected by a ray from the hole's leftmost point to the left;
-       // segment's endpoint with lesser x will be potential connection point
-       do {
+       }
 
-               if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) {
+       copy( source ) {
 
-                       const x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );
-                       if ( x <= hx && x > qx ) {
+               super.copy( source );
 
-                               qx = x;
-                               if ( x === hx ) {
+               if ( source.center !== undefined ) this.center.copy( source.center );
 
-                                       if ( hy === p.y ) return p;
-                                       if ( hy === p.next.y ) return p.next;
+               this.material = source.material;
 
-                               }
+               return this;
 
-                               m = p.x < p.next.x ? p : p.next;
+       }
 
-                       }
+    }
 
-               }
+    Sprite.prototype.isSprite = true;
 
-               p = p.next;
+    function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) {
 
-       } while ( p !== outerNode );
+       // compute position in camera space
+       _alignedPosition.subVectors( vertexPosition, center ).addScalar( 0.5 ).multiply( scale );
 
-       if ( ! m ) return null;
+       // to check if rotation is not zero
+       if ( sin !== undefined ) {
 
-       if ( hx === qx ) return m; // hole touches outer segment; pick leftmost endpoint
+               _rotatedPosition.x = ( cos * _alignedPosition.x ) - ( sin * _alignedPosition.y );
+               _rotatedPosition.y = ( sin * _alignedPosition.x ) + ( cos * _alignedPosition.y );
 
-       // 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
+       } else {
 
-       const stop = m,
-               mx = m.x,
-               my = m.y;
-       let tanMin = Infinity, tan;
+               _rotatedPosition.copy( _alignedPosition );
 
-       p = m;
+       }
 
-       do {
 
-               if ( hx >= p.x && p.x >= mx && hx !== p.x &&
-                               pointInTriangle$1( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {
+       vertexPosition.copy( mvPosition );
+       vertexPosition.x += _rotatedPosition.x;
+       vertexPosition.y += _rotatedPosition.y;
 
-                       tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential
+       // transform to world space
+       vertexPosition.applyMatrix4( _viewWorldMatrix );
 
-                       if ( locallyInside$1( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector$1( m, p ) ) ) ) ) ) {
+    }
 
-                               m = p;
-                               tanMin = tan;
+    const _basePosition = /*@__PURE__*/ new Vector3();
 
-                       }
+    const _skinIndex = /*@__PURE__*/ new Vector4();
+    const _skinWeight = /*@__PURE__*/ new Vector4();
 
-               }
+    const _vector$5 = /*@__PURE__*/ new Vector3();
+    const _matrix = /*@__PURE__*/ new Matrix4();
 
-               p = p.next;
+    class SkinnedMesh extends Mesh {
 
-       } while ( p !== stop );
+       constructor( geometry, material ) {
 
-       return m;
+               super( geometry, material );
 
-    }
+               this.type = 'SkinnedMesh';
 
-    // whether sector in vertex m contains sector in vertex p in the same coordinates
-    function sectorContainsSector$1( m, p ) {
+               this.bindMode = 'attached';
+               this.bindMatrix = new Matrix4();
+               this.bindMatrixInverse = new Matrix4();
 
-       return area$1( m.prev, m, p.prev ) < 0 && area$1( p.next, m, m.next ) < 0;
+       }
 
-    }
+       copy( source ) {
 
-    // interlink polygon nodes in z-order
-    function indexCurve$1( start, minX, minY, invSize ) {
+               super.copy( source );
 
-       let p = start;
-       do {
+               this.bindMode = source.bindMode;
+               this.bindMatrix.copy( source.bindMatrix );
+               this.bindMatrixInverse.copy( source.bindMatrixInverse );
 
-               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;
+               this.skeleton = source.skeleton;
 
-       } while ( p !== start );
+               return this;
 
-       p.prevZ.nextZ = null;
-       p.prevZ = null;
+       }
 
-       sortLinked$1( p );
+       bind( skeleton, bindMatrix ) {
 
-    }
+               this.skeleton = skeleton;
 
-    // Simon Tatham's linked list merge sort algorithm
-    // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
-    function sortLinked$1( list ) {
+               if ( bindMatrix === undefined ) {
 
-       let i, p, q, e, tail, numMerges, pSize, qSize,
-               inSize = 1;
+                       this.updateMatrixWorld( true );
 
-       do {
+                       this.skeleton.calculateInverses();
 
-               p = list;
-               list = null;
-               tail = null;
-               numMerges = 0;
+                       bindMatrix = this.matrixWorld;
 
-               while ( p ) {
+               }
 
-                       numMerges ++;
-                       q = p;
-                       pSize = 0;
-                       for ( i = 0; i < inSize; i ++ ) {
+               this.bindMatrix.copy( bindMatrix );
+               this.bindMatrixInverse.copy( bindMatrix ).invert();
 
-                               pSize ++;
-                               q = q.nextZ;
-                               if ( ! q ) break;
+       }
 
-                       }
+       pose() {
 
-                       qSize = inSize;
+               this.skeleton.pose();
 
-                       while ( pSize > 0 || ( qSize > 0 && q ) ) {
+       }
 
-                               if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) {
+       normalizeSkinWeights() {
 
-                                       e = p;
-                                       p = p.nextZ;
-                                       pSize --;
+               const vector = new Vector4();
 
-                               } else {
+               const skinWeight = this.geometry.attributes.skinWeight;
 
-                                       e = q;
-                                       q = q.nextZ;
-                                       qSize --;
+               for ( let i = 0, l = skinWeight.count; i < l; i ++ ) {
 
-                               }
+                       vector.x = skinWeight.getX( i );
+                       vector.y = skinWeight.getY( i );
+                       vector.z = skinWeight.getZ( i );
+                       vector.w = skinWeight.getW( i );
 
-                               if ( tail ) tail.nextZ = e;
-                               else list = e;
+                       const scale = 1.0 / vector.manhattanLength();
 
-                               e.prevZ = tail;
-                               tail = e;
+                       if ( scale !== Infinity ) {
 
-                       }
+                               vector.multiplyScalar( scale );
 
-                       p = q;
+                       } else {
 
-               }
+                               vector.set( 1, 0, 0, 0 ); // do something reasonable
 
-               tail.nextZ = null;
-               inSize *= 2;
+                       }
 
-       } while ( numMerges > 1 );
+                       skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w );
 
-       return list;
+               }
 
-    }
+       }
 
-    // z-order of a point given coords and inverse of the longer side of data bbox
-    function zOrder$1( x, y, minX, minY, invSize ) {
+       updateMatrixWorld( force ) {
 
-       // coords are transformed into non-negative 15-bit integer range
-       x = 32767 * ( x - minX ) * invSize;
-       y = 32767 * ( y - minY ) * invSize;
+               super.updateMatrixWorld( force );
 
-       x = ( x | ( x << 8 ) ) & 0x00FF00FF;
-       x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
-       x = ( x | ( x << 2 ) ) & 0x33333333;
-       x = ( x | ( x << 1 ) ) & 0x55555555;
+               if ( this.bindMode === 'attached' ) {
 
-       y = ( y | ( y << 8 ) ) & 0x00FF00FF;
-       y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
-       y = ( y | ( y << 2 ) ) & 0x33333333;
-       y = ( y | ( y << 1 ) ) & 0x55555555;
+                       this.bindMatrixInverse.copy( this.matrixWorld ).invert();
 
-       return x | ( y << 1 );
+               } else if ( this.bindMode === 'detached' ) {
 
-    }
+                       this.bindMatrixInverse.copy( this.bindMatrix ).invert();
 
-    // find the leftmost node of a polygon ring
-    function getLeftmost$1( start ) {
+               } else {
 
-       let p = start,
-               leftmost = start;
-       do {
+                       console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode );
 
-               if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p;
-               p = p.next;
+               }
 
-       } while ( p !== start );
+       }
 
-       return leftmost;
+       boneTransform( index, target ) {
 
-    }
+               const skeleton = this.skeleton;
+               const geometry = this.geometry;
 
-    // check if a point lies within a convex triangle
-    function pointInTriangle$1( ax, ay, bx, by, cx, cy, px, py ) {
+               _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index );
+               _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index );
 
-       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;
+               _basePosition.copy( target ).applyMatrix4( this.bindMatrix );
 
-    }
+               target.set( 0, 0, 0 );
 
-    // check if a diagonal between two polygon nodes is valid (lies in polygon interior)
-    function isValidDiagonal$1( a, b ) {
+               for ( let i = 0; i < 4; i ++ ) {
 
-       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
+                       const weight = _skinWeight.getComponent( i );
 
-    }
+                       if ( weight !== 0 ) {
 
-    // signed area of a triangle
-    function area$1( p, q, r ) {
+                               const boneIndex = _skinIndex.getComponent( i );
 
-       return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );
+                               _matrix.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] );
 
-    }
+                               target.addScaledVector( _vector$5.copy( _basePosition ).applyMatrix4( _matrix ), weight );
 
-    // check if two points are equal
-    function equals$2( p1, p2 ) {
+                       }
 
-       return p1.x === p2.x && p1.y === p2.y;
+               }
 
-    }
+               return target.applyMatrix4( this.bindMatrixInverse );
 
-    // check if two segments intersect
-    function intersects$2( p1, q1, p2, q2 ) {
+       }
 
-       const o1 = sign$2( area$1( p1, q1, p2 ) );
-       const o2 = sign$2( area$1( p1, q1, q2 ) );
-       const o3 = sign$2( area$1( p2, q2, p1 ) );
-       const o4 = sign$2( area$1( p2, q2, q1 ) );
+    }
 
-       if ( o1 !== o2 && o3 !== o4 ) return true; // general case
+    SkinnedMesh.prototype.isSkinnedMesh = true;
 
-       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
+    class Bone extends Object3D {
 
-       return false;
+       constructor() {
 
-    }
+               super();
 
-    // for collinear points p, q, r, check if point q lies on segment pr
-    function onSegment$1( p, q, r ) {
+               this.type = 'Bone';
 
-       return q.x <= Math.max( p.x, r.x ) && q.x >= Math.min( p.x, r.x ) && q.y <= Math.max( p.y, r.y ) && q.y >= Math.min( p.y, r.y );
+       }
 
     }
 
-    function sign$2( num ) {
-
-       return num > 0 ? 1 : num < 0 ? - 1 : 0;
-
-    }
+    Bone.prototype.isBone = true;
 
-    // check if a polygon diagonal intersects any polygon segments
-    function intersectsPolygon$1( a, b ) {
+    class DataTexture extends Texture {
 
-       let p = a;
-       do {
+       constructor( data = null, width = 1, height = 1, format, type, mapping, wrapS, wrapT, magFilter = NearestFilter, minFilter = NearestFilter, anisotropy, encoding ) {
 
-               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;
+               super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
 
-       } while ( p !== a );
+               this.image = { data: data, width: width, height: height };
 
-       return false;
+               this.magFilter = magFilter;
+               this.minFilter = minFilter;
 
-    }
+               this.generateMipmaps = false;
+               this.flipY = false;
+               this.unpackAlignment = 1;
 
-    // check if a polygon diagonal is locally inside the polygon
-    function locallyInside$1( a, b ) {
+               this.needsUpdate = true;
 
-       return area$1( a.prev, a, a.next ) < 0 ?
-               area$1( a, b, a.next ) >= 0 && area$1( a, a.prev, b ) >= 0 :
-               area$1( a, b, a.prev ) < 0 || area$1( a, a.next, b ) < 0;
+       }
 
     }
 
-    // check if the middle point of a polygon diagonal is inside the polygon
-    function middleInside$1( a, b ) {
+    DataTexture.prototype.isDataTexture = true;
 
-       let p = a,
-               inside = false;
-       const px = ( a.x + b.x ) / 2,
-               py = ( a.y + b.y ) / 2;
-       do {
+    class InstancedBufferAttribute extends BufferAttribute {
 
-               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;
+       constructor( array, itemSize, normalized, meshPerAttribute = 1 ) {
 
-       } while ( p !== a );
+               if ( typeof normalized === 'number' ) {
 
-       return inside;
+                       meshPerAttribute = normalized;
 
-    }
+                       normalized = false;
 
-    // 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 ) {
+                       console.error( 'THREE.InstancedBufferAttribute: The constructor now expects normalized as the third argument.' );
 
-       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;
+               super( array, itemSize, normalized );
 
-       a2.next = an;
-       an.prev = a2;
+               this.meshPerAttribute = meshPerAttribute;
 
-       b2.next = a2;
-       a2.prev = b2;
+       }
 
-       bp.next = b2;
-       b2.prev = bp;
+       copy( source ) {
 
-       return b2;
+               super.copy( source );
 
-    }
+               this.meshPerAttribute = source.meshPerAttribute;
 
-    // create a node and optionally link it with previous one (in a circular doubly linked list)
-    function insertNode$2( i, x, y, last ) {
+               return this;
 
-       const p = new Node$1( i, x, y );
+       }
 
-       if ( ! last ) {
+       toJSON() {
 
-               p.prev = p;
-               p.next = p;
+               const data = super.toJSON();
 
-       } else {
+               data.meshPerAttribute = this.meshPerAttribute;
 
-               p.next = last.next;
-               p.prev = last;
-               last.next.prev = p;
-               last.next = p;
+               data.isInstancedBufferAttribute = true;
 
-       }
+               return data;
 
-       return p;
+       }
 
     }
 
-    function removeNode$2( p ) {
+    InstancedBufferAttribute.prototype.isInstancedBufferAttribute = true;
 
-       p.next.prev = p.prev;
-       p.prev.next = p.next;
+    const _instanceLocalMatrix = /*@__PURE__*/ new Matrix4();
+    const _instanceWorldMatrix = /*@__PURE__*/ new Matrix4();
 
-       if ( p.prevZ ) p.prevZ.nextZ = p.nextZ;
-       if ( p.nextZ ) p.nextZ.prevZ = p.prevZ;
+    const _instanceIntersects = [];
 
-    }
+    const _mesh = /*@__PURE__*/ new Mesh();
 
-    function Node$1( i, x, y ) {
+    class InstancedMesh extends Mesh {
 
-       // vertex index in coordinates array
-       this.i = i;
+       constructor( geometry, material, count ) {
 
-       // vertex coordinates
-       this.x = x;
-       this.y = y;
+               super( geometry, material );
 
-       // previous and next vertex nodes in a polygon ring
-       this.prev = null;
-       this.next = null;
+               this.instanceMatrix = new InstancedBufferAttribute( new Float32Array( count * 16 ), 16 );
+               this.instanceColor = null;
 
-       // z-order curve value
-       this.z = null;
+               this.count = count;
 
-       // previous and next nodes in z-order
-       this.prevZ = null;
-       this.nextZ = null;
+               this.frustumCulled = false;
 
-       // indicates whether this is a steiner point
-       this.steiner = false;
+       }
 
-    }
+       copy( source ) {
 
-    function signedArea$2( data, start, end, dim ) {
+               super.copy( source );
 
-       let sum = 0;
-       for ( let i = start, j = end - dim; i < end; i += dim ) {
+               this.instanceMatrix.copy( source.instanceMatrix );
 
-               sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );
-               j = i;
+               if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone();
+
+               this.count = source.count;
+
+               return this;
 
        }
 
-       return sum;
+       getColorAt( index, color ) {
 
-    }
+               color.fromArray( this.instanceColor.array, index * 3 );
 
-    const ShapeUtils = {
+       }
 
-       // calculate area of the contour polygon
+       getMatrixAt( index, matrix ) {
 
-       area: function ( contour ) {
+               matrix.fromArray( this.instanceMatrix.array, index * 16 );
 
-               const n = contour.length;
-               let a = 0.0;
+       }
 
-               for ( let p = n - 1, q = 0; q < n; p = q ++ ) {
+       raycast( raycaster, intersects ) {
 
-                       a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
+               const matrixWorld = this.matrixWorld;
+               const raycastTimes = this.count;
 
-               }
+               _mesh.geometry = this.geometry;
+               _mesh.material = this.material;
 
-               return a * 0.5;
+               if ( _mesh.material === undefined ) return;
 
-       },
+               for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) {
 
-       isClockWise: function ( pts ) {
+                       // calculate the world matrix for each instance
 
-               return ShapeUtils.area( pts ) < 0;
+                       this.getMatrixAt( instanceId, _instanceLocalMatrix );
 
-       },
+                       _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix );
 
-       triangulateShape: function ( contour, holes ) {
+                       // the mesh represents this single instance
 
-               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 ] ]
+                       _mesh.matrixWorld = _instanceWorldMatrix;
 
-               removeDupEndPts( contour );
-               addContour( vertices, contour );
+                       _mesh.raycast( raycaster, _instanceIntersects );
 
-               //
+                       // process the result of raycast
 
-               let holeIndex = contour.length;
+                       for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) {
 
-               holes.forEach( removeDupEndPts );
+                               const intersect = _instanceIntersects[ i ];
+                               intersect.instanceId = instanceId;
+                               intersect.object = this;
+                               intersects.push( intersect );
 
-               for ( let i = 0; i < holes.length; i ++ ) {
+                       }
 
-                       holeIndices.push( holeIndex );
-                       holeIndex += holes[ i ].length;
-                       addContour( vertices, holes[ i ] );
+                       _instanceIntersects.length = 0;
 
                }
 
-               //
-
-               const triangles = Earcut.triangulate( vertices, holeIndices );
+       }
 
-               //
+       setColorAt( index, color ) {
 
-               for ( let i = 0; i < triangles.length; i += 3 ) {
+               if ( this.instanceColor === null ) {
 
-                       faces.push( triangles.slice( i, i + 3 ) );
+                       this.instanceColor = new InstancedBufferAttribute( new Float32Array( this.instanceMatrix.count * 3 ), 3 );
 
                }
 
-               return faces;
+               color.toArray( this.instanceColor.array, index * 3 );
 
        }
 
-    };
-
-    function removeDupEndPts( points ) {
-
-       const l = points.length;
-
-       if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {
+       setMatrixAt( index, matrix ) {
 
-               points.pop();
+               matrix.toArray( this.instanceMatrix.array, index * 16 );
 
        }
 
-    }
+       updateMorphTargets() {
 
-    function addContour( vertices, contour ) {
+       }
 
-       for ( let i = 0; i < contour.length; i ++ ) {
+       dispose() {
 
-               vertices.push( contour[ i ].x );
-               vertices.push( contour[ i ].y );
+               this.dispatchEvent( { type: 'dispose' } );
 
        }
 
     }
 
+    InstancedMesh.prototype.isInstancedMesh = true;
+
     /**
-     * Creates extruded geometry from a path shape.
-     *
      * parameters = {
+     *  color: <hex>,
+     *  opacity: <float>,
      *
-     *  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
-     *
+     *  linewidth: <float>,
+     *  linecap: "round",
+     *  linejoin: "round"
      * }
      */
 
-    class ExtrudeGeometry extends BufferGeometry {
+    class LineBasicMaterial extends Material {
 
-       constructor( shapes, options ) {
+       constructor( parameters ) {
 
                super();
 
-               this.type = 'ExtrudeGeometry';
+               this.type = 'LineBasicMaterial';
 
-               this.parameters = {
-                       shapes: shapes,
-                       options: options
-               };
+               this.color = new Color( 0xffffff );
 
-               shapes = Array.isArray( shapes ) ? shapes : [ shapes ];
+               this.linewidth = 1;
+               this.linecap = 'round';
+               this.linejoin = 'round';
 
-               const scope = this;
+               this.setValues( parameters );
 
-               const verticesArray = [];
-               const uvArray = [];
+       }
 
-               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
 
-                       const shape = shapes[ i ];
-                       addShape( shape );
+       copy( source ) {
 
-               }
+               super.copy( source );
 
-               // build geometry
+               this.color.copy( source.color );
 
-               this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) );
-               this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) );
+               this.linewidth = source.linewidth;
+               this.linecap = source.linecap;
+               this.linejoin = source.linejoin;
 
-               this.computeVertexNormals();
+               return this;
 
-               // functions
+       }
 
-               function addShape( shape ) {
+    }
 
-                       const placeholder = [];
+    LineBasicMaterial.prototype.isLineBasicMaterial = true;
 
-                       // options
+    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();
 
-                       const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;
-                       const steps = options.steps !== undefined ? options.steps : 1;
-                       let depth = options.depth !== undefined ? options.depth : 100;
+    class Line extends Object3D {
 
-                       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;
+       constructor( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) {
 
-                       const extrudePath = options.extrudePath;
+               super();
 
-                       const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator;
+               this.type = 'Line';
 
-                       // deprecated options
+               this.geometry = geometry;
+               this.material = material;
 
-                       if ( options.amount !== undefined ) {
+               this.updateMorphTargets();
 
-                               console.warn( 'THREE.ExtrudeBufferGeometry: amount has been renamed to depth.' );
-                               depth = options.amount;
+       }
 
-                       }
+       copy( source ) {
 
-                       //
+               super.copy( source );
 
-                       let extrudePts, extrudeByPath = false;
-                       let splineTube, binormal, normal, position2;
+               this.material = source.material;
+               this.geometry = source.geometry;
 
-                       if ( extrudePath ) {
+               return this;
 
-                               extrudePts = extrudePath.getSpacedPoints( steps );
+       }
 
-                               extrudeByPath = true;
-                               bevelEnabled = false; // bevels not supported for path extrusion
+       computeLineDistances() {
 
-                               // SETUP TNB variables
+               const geometry = this.geometry;
 
-                               // TODO1 - have a .isClosed in spline?
+               if ( geometry.isBufferGeometry ) {
 
-                               splineTube = extrudePath.computeFrenetFrames( steps, false );
+                       // we assume non-indexed geometry
 
-                               // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);
+                       if ( geometry.index === null ) {
 
-                               binormal = new Vector3();
-                               normal = new Vector3();
-                               position2 = new Vector3();
+                               const positionAttribute = geometry.attributes.position;
+                               const lineDistances = [ 0 ];
 
-                       }
+                               for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) {
 
-                       // Safeguards if bevels are not enabled
+                                       _start$1.fromBufferAttribute( positionAttribute, i - 1 );
+                                       _end$1.fromBufferAttribute( positionAttribute, i );
 
-                       if ( ! bevelEnabled ) {
+                                       lineDistances[ i ] = lineDistances[ i - 1 ];
+                                       lineDistances[ i ] += _start$1.distanceTo( _end$1 );
 
-                               bevelSegments = 0;
-                               bevelThickness = 0;
-                               bevelSize = 0;
-                               bevelOffset = 0;
+                               }
+
+                               geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
+
+                       } else {
+
+                               console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
 
                        }
 
-                       // Variables initialization
+               } else if ( geometry.isGeometry ) {
 
-                       const shapePoints = shape.extractPoints( curveSegments );
+                       console.error( 'THREE.Line.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
-                       let vertices = shapePoints.shape;
-                       const holes = shapePoints.holes;
+               }
 
-                       const reverse = ! ShapeUtils.isClockWise( vertices );
+               return this;
 
-                       if ( reverse ) {
+       }
 
-                               vertices = vertices.reverse();
+       raycast( raycaster, intersects ) {
 
-                               // Maybe we should also check if holes are in the opposite direction, just to be safe ...
+               const geometry = this.geometry;
+               const matrixWorld = this.matrixWorld;
+               const threshold = raycaster.params.Line.threshold;
+               const drawRange = geometry.drawRange;
 
-                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+               // Checking boundingSphere distance to ray
 
-                                       const ahole = holes[ h ];
+               if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
 
-                                       if ( ShapeUtils.isClockWise( ahole ) ) {
+               _sphere$1.copy( geometry.boundingSphere );
+               _sphere$1.applyMatrix4( matrixWorld );
+               _sphere$1.radius += threshold;
 
-                                               holes[ h ] = ahole.reverse();
+               if ( raycaster.ray.intersectsSphere( _sphere$1 ) === false ) return;
 
-                                       }
+               //
 
-                               }
+               _inverseMatrix$1.copy( matrixWorld ).invert();
+               _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 );
 
-                       }
+               const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
+               const localThresholdSq = localThreshold * localThreshold;
 
+               const vStart = new Vector3();
+               const vEnd = new Vector3();
+               const interSegment = new Vector3();
+               const interRay = new Vector3();
+               const step = this.isLineSegments ? 2 : 1;
 
-                       const faces = ShapeUtils.triangulateShape( vertices, holes );
+               if ( geometry.isBufferGeometry ) {
 
-                       /* Vertices */
+                       const index = geometry.index;
+                       const attributes = geometry.attributes;
+                       const positionAttribute = attributes.position;
 
-                       const contour = vertices; // vertices has all points but contour has only points of circumference
+                       if ( index !== null ) {
 
-                       for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+                               const start = Math.max( 0, drawRange.start );
+                               const end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
 
-                               const ahole = holes[ h ];
+                               for ( let i = start, l = end - 1; i < l; i += step ) {
 
-                               vertices = vertices.concat( ahole );
+                                       const a = index.getX( i );
+                                       const b = index.getX( i + 1 );
 
-                       }
+                                       vStart.fromBufferAttribute( positionAttribute, a );
+                                       vEnd.fromBufferAttribute( positionAttribute, b );
 
+                                       const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
 
-                       function scalePt2( pt, vec, size ) {
+                                       if ( distSq > localThresholdSq ) continue;
 
-                               if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' );
+                                       interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
 
-                               return vec.clone().multiplyScalar( size ).add( pt );
+                                       const distance = raycaster.ray.origin.distanceTo( interRay );
 
-                       }
+                                       if ( distance < raycaster.near || distance > raycaster.far ) continue;
 
-                       const vlen = vertices.length, flen = faces.length;
+                                       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
 
-                       // Find directions for point movement
+                                       } );
 
+                               }
 
-                       function getBevelVec( inPt, inPrev, inNext ) {
+                       } else {
 
-                               // 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.
+                               const start = Math.max( 0, drawRange.start );
+                               const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) );
 
-                               let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt
+                               for ( let i = start, l = end - 1; i < l; i += step ) {
 
-                               // good reading for geometry algorithms (here: line-line intersection)
-                               // http://geomalgorithms.com/a05-_intersect-1.html
+                                       vStart.fromBufferAttribute( positionAttribute, i );
+                                       vEnd.fromBufferAttribute( positionAttribute, i + 1 );
 
-                               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 distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
 
-                               const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y );
+                                       if ( distSq > localThresholdSq ) continue;
 
-                               // check for collinear edges
-                               const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x );
+                                       interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
 
-                               if ( Math.abs( collinear0 ) > Number.EPSILON ) {
+                                       const distance = raycaster.ray.origin.distanceTo( interRay );
 
-                                       // not collinear
+                                       if ( distance < raycaster.near || distance > raycaster.far ) continue;
 
-                                       // length of vectors for normalizing
+                                       intersects.push( {
 
-                                       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 );
+                                               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
 
-                                       // shift adjacent points by unit vectors to the left
+                                       } );
 
-                                       const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len );
-                                       const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len );
+                               }
 
-                                       const ptNextShift_x = ( inNext.x - v_next_y / v_next_len );
-                                       const ptNextShift_y = ( inNext.y + v_next_x / v_next_len );
+                       }
 
-                                       // scaling factor for v_prev to intersection point
+               } else if ( geometry.isGeometry ) {
 
-                                       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 );
+                       console.error( 'THREE.Line.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
-                                       // vector from inPt to intersection point
+               }
 
-                                       v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x );
-                                       v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y );
+       }
 
-                                       // Don't normalize!, otherwise sharp corners become ugly
-                                       //  but prevent crazy spikes
-                                       const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y );
-                                       if ( v_trans_lensq <= 2 ) {
+       updateMorphTargets() {
 
-                                               return new Vector2( v_trans_x, v_trans_y );
+               const geometry = this.geometry;
 
-                                       } else {
+               if ( geometry.isBufferGeometry ) {
 
-                                               shrink_by = Math.sqrt( v_trans_lensq / 2 );
+                       const morphAttributes = geometry.morphAttributes;
+                       const keys = Object.keys( morphAttributes );
 
-                                       }
+                       if ( keys.length > 0 ) {
 
-                               } else {
+                               const morphAttribute = morphAttributes[ keys[ 0 ] ];
 
-                                       // handle special case of collinear edges
+                               if ( morphAttribute !== undefined ) {
 
-                                       let direction_eq = false; // assumes: opposite
+                                       this.morphTargetInfluences = [];
+                                       this.morphTargetDictionary = {};
 
-                                       if ( v_prev_x > Number.EPSILON ) {
+                                       for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
 
-                                               if ( v_next_x > Number.EPSILON ) {
+                                               const name = morphAttribute[ m ].name || String( m );
 
-                                                       direction_eq = true;
+                                               this.morphTargetInfluences.push( 0 );
+                                               this.morphTargetDictionary[ name ] = m;
 
-                                               }
+                                       }
 
-                                       } else {
+                               }
 
-                                               if ( v_prev_x < - Number.EPSILON ) {
+                       }
 
-                                                       if ( v_next_x < - Number.EPSILON ) {
+               } else {
 
-                                                               direction_eq = true;
+                       const morphTargets = geometry.morphTargets;
 
-                                                       }
+                       if ( morphTargets !== undefined && morphTargets.length > 0 ) {
 
-                                               } else {
+                               console.error( 'THREE.Line.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
-                                                       if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) {
+                       }
 
-                                                               direction_eq = true;
+               }
 
-                                                       }
+       }
 
-                                               }
+    }
 
-                                       }
+    Line.prototype.isLine = true;
 
-                                       if ( direction_eq ) {
+    const _start = /*@__PURE__*/ new Vector3();
+    const _end = /*@__PURE__*/ new Vector3();
 
-                                               // 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 );
+    class LineSegments extends Line {
 
-                                       } else {
+       constructor( geometry, material ) {
 
-                                               // 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 );
+               super( geometry, material );
 
-                                       }
+               this.type = 'LineSegments';
 
-                               }
+       }
 
-                               return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by );
+       computeLineDistances() {
 
-                       }
+               const geometry = this.geometry;
 
+               if ( geometry.isBufferGeometry ) {
 
-                       const contourMovements = [];
+                       // we assume non-indexed geometry
 
-                       for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+                       if ( geometry.index === null ) {
 
-                               if ( j === il ) j = 0;
-                               if ( k === il ) k = 0;
+                               const positionAttribute = geometry.attributes.position;
+                               const lineDistances = [];
 
-                               //  (j)---(i)---(k)
-                               // console.log('i,j,k', i, j , k)
+                               for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) {
 
-                               contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] );
+                                       _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 );
 
-                       const holesMovements = [];
-                       let oneHoleMovements, verticesMovements = contourMovements.concat();
+                               }
 
-                       for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+                               geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
 
-                               const ahole = holes[ h ];
+                       } else {
 
-                               oneHoleMovements = [];
+                               console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
 
-                               for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+                       }
 
-                                       if ( j === il ) j = 0;
-                                       if ( k === il ) k = 0;
+               } else if ( geometry.isGeometry ) {
 
-                                       //  (j)---(i)---(k)
-                                       oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
+                       console.error( 'THREE.LineSegments.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
-                               }
+               }
 
-                               holesMovements.push( oneHoleMovements );
-                               verticesMovements = verticesMovements.concat( oneHoleMovements );
+               return this;
 
-                       }
+       }
 
+    }
 
-                       // Loop bevelSegments, 1 for the front, 1 for the back
+    LineSegments.prototype.isLineSegments = true;
 
-                       for ( let b = 0; b < bevelSegments; b ++ ) {
+    class LineLoop extends Line {
 
-                               //for ( b = bevelSegments; b > 0; b -- ) {
+       constructor( geometry, material ) {
 
-                               const t = b / bevelSegments;
-                               const z = bevelThickness * Math.cos( t * Math.PI / 2 );
-                               const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
+               super( geometry, material );
 
-                               // contract shape
+               this.type = 'LineLoop';
 
-                               for ( let i = 0, il = contour.length; i < il; i ++ ) {
+       }
 
-                                       const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
+    }
 
-                                       v( vert.x, vert.y, - z );
+    LineLoop.prototype.isLineLoop = true;
 
-                               }
+    /**
+     * parameters = {
+     *  color: <hex>,
+     *  opacity: <float>,
+     *  map: new THREE.Texture( <Image> ),
+     *  alphaMap: new THREE.Texture( <Image> ),
+     *
+     *  size: <float>,
+     *  sizeAttenuation: <bool>
+     *
+     * }
+     */
 
-                               // expand holes
+    class PointsMaterial extends Material {
 
-                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+       constructor( parameters ) {
 
-                                       const ahole = holes[ h ];
-                                       oneHoleMovements = holesMovements[ h ];
+               super();
 
-                                       for ( let i = 0, il = ahole.length; i < il; i ++ ) {
+               this.type = 'PointsMaterial';
 
-                                               const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
+               this.color = new Color( 0xffffff );
 
-                                               v( vert.x, vert.y, - z );
+               this.map = null;
 
-                                       }
+               this.alphaMap = null;
 
-                               }
+               this.size = 1;
+               this.sizeAttenuation = true;
 
-                       }
+               this.setValues( parameters );
 
-                       const bs = bevelSize + bevelOffset;
+       }
 
-                       // Back facing vertices
+       copy( source ) {
 
-                       for ( let i = 0; i < vlen; i ++ ) {
+               super.copy( source );
 
-                               const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+               this.color.copy( source.color );
 
-                               if ( ! extrudeByPath ) {
+               this.map = source.map;
 
-                                       v( vert.x, vert.y, 0 );
+               this.alphaMap = source.alphaMap;
 
-                               } else {
+               this.size = source.size;
+               this.sizeAttenuation = source.sizeAttenuation;
 
-                                       // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );
+               return this;
 
-                                       normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x );
-                                       binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y );
+       }
 
-                                       position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal );
+    }
 
-                                       v( position2.x, position2.y, position2.z );
+    PointsMaterial.prototype.isPointsMaterial = true;
 
-                               }
+    const _inverseMatrix = /*@__PURE__*/ new Matrix4();
+    const _ray = /*@__PURE__*/ new Ray();
+    const _sphere = /*@__PURE__*/ new Sphere();
+    const _position$2 = /*@__PURE__*/ new Vector3();
 
-                       }
+    class Points extends Object3D {
 
-                       // Add stepped vertices...
-                       // Including front facing vertices
+       constructor( geometry = new BufferGeometry(), material = new PointsMaterial() ) {
 
-                       for ( let s = 1; s <= steps; s ++ ) {
+               super();
 
-                               for ( let i = 0; i < vlen; i ++ ) {
+               this.type = 'Points';
 
-                                       const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+               this.geometry = geometry;
+               this.material = material;
 
-                                       if ( ! extrudeByPath ) {
+               this.updateMorphTargets();
 
-                                               v( vert.x, vert.y, depth / steps * s );
+       }
 
-                                       } else {
+       copy( source ) {
 
-                                               // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );
+               super.copy( source );
 
-                                               normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x );
-                                               binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y );
+               this.material = source.material;
+               this.geometry = source.geometry;
 
-                                               position2.copy( extrudePts[ s ] ).add( normal ).add( binormal );
+               return this;
 
-                                               v( position2.x, position2.y, position2.z );
+       }
 
-                                       }
+       raycast( raycaster, intersects ) {
 
-                               }
+               const geometry = this.geometry;
+               const matrixWorld = this.matrixWorld;
+               const threshold = raycaster.params.Points.threshold;
+               const drawRange = geometry.drawRange;
 
-                       }
+               // Checking boundingSphere distance to ray
 
+               if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
 
-                       // Add bevel segments planes
+               _sphere.copy( geometry.boundingSphere );
+               _sphere.applyMatrix4( matrixWorld );
+               _sphere.radius += threshold;
 
-                       //for ( b = 1; b <= bevelSegments; b ++ ) {
-                       for ( let b = bevelSegments - 1; b >= 0; b -- ) {
+               if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return;
 
-                               const t = b / bevelSegments;
-                               const z = bevelThickness * Math.cos( t * Math.PI / 2 );
-                               const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
+               //
 
-                               // contract shape
+               _inverseMatrix.copy( matrixWorld ).invert();
+               _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
 
-                               for ( let i = 0, il = contour.length; i < il; i ++ ) {
+               const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
+               const localThresholdSq = localThreshold * localThreshold;
 
-                                       const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
-                                       v( vert.x, vert.y, depth + z );
+               if ( geometry.isBufferGeometry ) {
 
-                               }
+                       const index = geometry.index;
+                       const attributes = geometry.attributes;
+                       const positionAttribute = attributes.position;
 
-                               // expand holes
+                       if ( index !== null ) {
 
-                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+                               const start = Math.max( 0, drawRange.start );
+                               const end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
 
-                                       const ahole = holes[ h ];
-                                       oneHoleMovements = holesMovements[ h ];
+                               for ( let i = start, il = end; i < il; i ++ ) {
 
-                                       for ( let i = 0, il = ahole.length; i < il; i ++ ) {
+                                       const a = index.getX( i );
 
-                                               const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
+                                       _position$2.fromBufferAttribute( positionAttribute, a );
 
-                                               if ( ! extrudeByPath ) {
+                                       testPoint( _position$2, a, localThresholdSq, matrixWorld, raycaster, intersects, this );
 
-                                                       v( vert.x, vert.y, depth + z );
+                               }
 
-                                               } else {
+                       } else {
 
-                                                       v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z );
+                               const start = Math.max( 0, drawRange.start );
+                               const end = Math.min( positionAttribute.count, ( drawRange.start + drawRange.count ) );
 
-                                               }
+                               for ( let i = start, l = end; i < l; i ++ ) {
 
-                                       }
+                                       _position$2.fromBufferAttribute( positionAttribute, i );
+
+                                       testPoint( _position$2, i, localThresholdSq, matrixWorld, raycaster, intersects, this );
 
                                }
 
                        }
 
-                       /* Faces */
+               } else {
 
-                       // Top and bottom faces
+                       console.error( 'THREE.Points.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
-                       buildLidFaces();
+               }
 
-                       // Sides faces
+       }
 
-                       buildSideFaces();
+       updateMorphTargets() {
 
+               const geometry = this.geometry;
 
-                       /////  Internal functions
+               if ( geometry.isBufferGeometry ) {
 
-                       function buildLidFaces() {
+                       const morphAttributes = geometry.morphAttributes;
+                       const keys = Object.keys( morphAttributes );
 
-                               const start = verticesArray.length / 3;
+                       if ( keys.length > 0 ) {
 
-                               if ( bevelEnabled ) {
+                               const morphAttribute = morphAttributes[ keys[ 0 ] ];
 
-                                       let layer = 0; // steps + 1
-                                       let offset = vlen * layer;
+                               if ( morphAttribute !== undefined ) {
 
-                                       // Bottom faces
+                                       this.morphTargetInfluences = [];
+                                       this.morphTargetDictionary = {};
 
-                                       for ( let i = 0; i < flen; i ++ ) {
+                                       for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
 
-                                               const face = faces[ i ];
-                                               f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset );
+                                               const name = morphAttribute[ m ].name || String( m );
+
+                                               this.morphTargetInfluences.push( 0 );
+                                               this.morphTargetDictionary[ name ] = m;
 
                                        }
 
-                                       layer = steps + bevelSegments * 2;
-                                       offset = vlen * layer;
+                               }
 
-                                       // Top faces
+                       }
 
-                                       for ( let i = 0; i < flen; i ++ ) {
+               } else {
 
-                                               const face = faces[ i ];
-                                               f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset );
+                       const morphTargets = geometry.morphTargets;
 
-                                       }
+                       if ( morphTargets !== undefined && morphTargets.length > 0 ) {
 
-                               } else {
+                               console.error( 'THREE.Points.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
 
-                                       // Bottom faces
+                       }
 
-                                       for ( let i = 0; i < flen; i ++ ) {
+               }
 
-                                               const face = faces[ i ];
-                                               f3( face[ 2 ], face[ 1 ], face[ 0 ] );
+       }
 
-                                       }
+    }
 
-                                       // Top faces
+    Points.prototype.isPoints = true;
 
-                                       for ( let i = 0; i < flen; i ++ ) {
+    function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) {
 
-                                               const face = faces[ i ];
-                                               f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps );
+       const rayPointDistanceSq = _ray.distanceSqToPoint( point );
 
-                                       }
+       if ( rayPointDistanceSq < localThresholdSq ) {
 
-                               }
+               const intersectPoint = new Vector3();
 
-                               scope.addGroup( start, verticesArray.length / 3 - start, 0 );
+               _ray.closestPointToPoint( point, intersectPoint );
+               intersectPoint.applyMatrix4( matrixWorld );
 
-                       }
+               const distance = raycaster.ray.origin.distanceTo( intersectPoint );
 
-                       // Create faces for the z-sides of the shape
+               if ( distance < raycaster.near || distance > raycaster.far ) return;
 
-                       function buildSideFaces() {
+               intersects.push( {
 
-                               const start = verticesArray.length / 3;
-                               let layeroffset = 0;
-                               sidewalls( contour, layeroffset );
-                               layeroffset += contour.length;
+                       distance: distance,
+                       distanceToRay: Math.sqrt( rayPointDistanceSq ),
+                       point: intersectPoint,
+                       index: index,
+                       face: null,
+                       object: object
 
-                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+               } );
 
-                                       const ahole = holes[ h ];
-                                       sidewalls( ahole, layeroffset );
+       }
 
-                                       //, true
-                                       layeroffset += ahole.length;
+    }
 
-                               }
+    class VideoTexture extends Texture {
 
+       constructor( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
 
-                               scope.addGroup( start, verticesArray.length / 3 - start, 1 );
+               super( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
 
+               this.format = format !== undefined ? format : RGBFormat;
 
-                       }
+               this.minFilter = minFilter !== undefined ? minFilter : LinearFilter;
+               this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;
 
-                       function sidewalls( contour, layeroffset ) {
+               this.generateMipmaps = false;
 
-                               let i = contour.length;
+               const scope = this;
 
-                               while ( -- i >= 0 ) {
+               function updateVideo() {
 
-                                       const j = i;
-                                       let k = i - 1;
-                                       if ( k < 0 ) k = contour.length - 1;
+                       scope.needsUpdate = true;
+                       video.requestVideoFrameCallback( updateVideo );
 
-                                       //console.log('b', i,j, i-1, k,vertices.length);
+               }
 
-                                       for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) {
+               if ( 'requestVideoFrameCallback' in video ) {
 
-                                               const slen1 = vlen * s;
-                                               const slen2 = vlen * ( s + 1 );
+                       video.requestVideoFrameCallback( updateVideo );
 
-                                               const a = layeroffset + j + slen1,
-                                                       b = layeroffset + k + slen1,
-                                                       c = layeroffset + k + slen2,
-                                                       d = layeroffset + j + slen2;
+               }
 
-                                               f4( a, b, c, d );
+       }
 
-                                       }
+       clone() {
 
-                               }
+               return new this.constructor( this.image ).copy( this );
 
-                       }
+       }
 
-                       function v( x, y, z ) {
+       update() {
 
-                               placeholder.push( x );
-                               placeholder.push( y );
-                               placeholder.push( z );
+               const video = this.image;
+               const hasVideoFrameCallback = 'requestVideoFrameCallback' in video;
 
-                       }
+               if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) {
 
+                       this.needsUpdate = true;
 
-                       function f3( a, b, c ) {
+               }
 
-                               addVertex( a );
-                               addVertex( b );
-                               addVertex( c );
+       }
 
-                               const nextIndex = verticesArray.length / 3;
-                               const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
+    }
 
-                               addUV( uvs[ 0 ] );
-                               addUV( uvs[ 1 ] );
-                               addUV( uvs[ 2 ] );
+    VideoTexture.prototype.isVideoTexture = true;
 
-                       }
+    class CompressedTexture extends Texture {
 
-                       function f4( a, b, c, d ) {
-
-                               addVertex( a );
-                               addVertex( b );
-                               addVertex( d );
-
-                               addVertex( b );
-                               addVertex( c );
-                               addVertex( d );
+       constructor( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {
 
+               super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
 
-                               const nextIndex = verticesArray.length / 3;
-                               const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
+               this.image = { width: width, height: height };
+               this.mipmaps = mipmaps;
 
-                               addUV( uvs[ 0 ] );
-                               addUV( uvs[ 1 ] );
-                               addUV( uvs[ 3 ] );
+               // no flipping for cube textures
+               // (also flipping doesn't work for compressed textures )
 
-                               addUV( uvs[ 1 ] );
-                               addUV( uvs[ 2 ] );
-                               addUV( uvs[ 3 ] );
+               this.flipY = false;
 
-                       }
+               // can't generate mipmaps for compressed textures
+               // mips must be embedded in DDS files
 
-                       function addVertex( index ) {
+               this.generateMipmaps = false;
 
-                               verticesArray.push( placeholder[ index * 3 + 0 ] );
-                               verticesArray.push( placeholder[ index * 3 + 1 ] );
-                               verticesArray.push( placeholder[ index * 3 + 2 ] );
+       }
 
-                       }
+    }
 
+    CompressedTexture.prototype.isCompressedTexture = true;
 
-                       function addUV( vector2 ) {
+    class CanvasTexture extends Texture {
 
-                               uvArray.push( vector2.x );
-                               uvArray.push( vector2.y );
+       constructor( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
 
-                       }
+               super( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
 
-               }
+               this.needsUpdate = true;
 
        }
 
-       toJSON() {
+    }
 
-               const data = BufferGeometry.prototype.toJSON.call( this );
+    CanvasTexture.prototype.isCanvasTexture = true;
 
-               const shapes = this.parameters.shapes;
-               const options = this.parameters.options;
+    class DepthTexture extends Texture {
 
-               return toJSON( shapes, options, data );
+       constructor( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) {
 
-       }
+               format = format !== undefined ? format : DepthFormat;
 
-    }
+               if ( format !== DepthFormat && format !== DepthStencilFormat ) {
 
-    const WorldUVGenerator = {
+                       throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' );
 
-       generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) {
+               }
 
-               const a_x = vertices[ indexA * 3 ];
-               const a_y = vertices[ indexA * 3 + 1 ];
-               const b_x = vertices[ indexB * 3 ];
-               const b_y = vertices[ indexB * 3 + 1 ];
-               const c_x = vertices[ indexC * 3 ];
-               const c_y = vertices[ indexC * 3 + 1 ];
+               if ( type === undefined && format === DepthFormat ) type = UnsignedShortType;
+               if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type;
 
-               return [
-                       new Vector2( a_x, a_y ),
-                       new Vector2( b_x, b_y ),
-                       new Vector2( c_x, c_y )
-               ];
+               super( null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
 
-       },
+               this.image = { width: width, height: height };
 
-       generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) {
+               this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
+               this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;
 
-               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 ];
+               this.flipY = false;
+               this.generateMipmaps    = false;
 
-               if ( Math.abs( a_y - b_y ) < 0.01 ) {
+       }
 
-                       return [
-                               new Vector2( a_x, 1 - a_z ),
-                               new Vector2( b_x, 1 - b_z ),
-                               new Vector2( c_x, 1 - c_z ),
-                               new Vector2( d_x, 1 - d_z )
-                       ];
 
-               } else {
+    }
 
-                       return [
-                               new Vector2( a_y, 1 - a_z ),
-                               new Vector2( b_y, 1 - b_z ),
-                               new Vector2( c_y, 1 - c_z ),
-                               new Vector2( d_y, 1 - d_z )
-                       ];
+    DepthTexture.prototype.isDepthTexture = true;
 
-               }
+    class CircleGeometry extends BufferGeometry {
 
-       }
+       constructor( radius = 1, segments = 8, thetaStart = 0, thetaLength = Math.PI * 2 ) {
 
-    };
+               super();
 
-    function toJSON( shapes, options, data ) {
+               this.type = 'CircleGeometry';
 
-       data.shapes = [];
+               this.parameters = {
+                       radius: radius,
+                       segments: segments,
+                       thetaStart: thetaStart,
+                       thetaLength: thetaLength
+               };
 
-       if ( Array.isArray( shapes ) ) {
+               segments = Math.max( 3, segments );
 
-               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+               // buffers
 
-                       const shape = shapes[ i ];
+               const indices = [];
+               const vertices = [];
+               const normals = [];
+               const uvs = [];
 
-                       data.shapes.push( shape.uuid );
+               // helper variables
 
-               }
+               const vertex = new Vector3();
+               const uv = new Vector2();
 
-       } else {
+               // center point
 
-               data.shapes.push( shapes.uuid );
+               vertices.push( 0, 0, 0 );
+               normals.push( 0, 0, 1 );
+               uvs.push( 0.5, 0.5 );
 
-       }
+               for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) {
 
-       if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON();
+                       const segment = thetaStart + s / segments * thetaLength;
 
-       return data;
+                       // vertex
 
-    }
+                       vertex.x = radius * Math.cos( segment );
+                       vertex.y = radius * Math.sin( segment );
 
-    /**
-     * Parametric Surfaces Geometry
-     * based on the brilliant article by @prideout https://prideout.net/blog/old/blog/index.html@p=44.html
-     */
+                       vertices.push( vertex.x, vertex.y, vertex.z );
+
+                       // normal
 
-    function ParametricGeometry( func, slices, stacks ) {
+                       normals.push( 0, 0, 1 );
 
-       BufferGeometry.call( this );
+                       // uvs
 
-       this.type = 'ParametricGeometry';
+                       uv.x = ( vertices[ i ] / radius + 1 ) / 2;
+                       uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2;
 
-       this.parameters = {
-               func: func,
-               slices: slices,
-               stacks: stacks
-       };
+                       uvs.push( uv.x, uv.y );
 
-       // buffers
+               }
 
-       const indices = [];
-       const vertices = [];
-       const normals = [];
-       const uvs = [];
+               // indices
 
-       const EPS = 0.00001;
+               for ( let i = 1; i <= segments; i ++ ) {
 
-       const normal = new Vector3();
+                       indices.push( i, i + 1, 0 );
 
-       const p0 = new Vector3(), p1 = new Vector3();
-       const pu = new Vector3(), pv = new Vector3();
+               }
 
-       if ( func.length < 3 ) {
+               // build geometry
 
-               console.error( 'THREE.ParametricGeometry: Function must now modify a Vector3 as third parameter.' );
+               this.setIndex( indices );
+               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
 
        }
 
-       // generate vertices, normals and uvs
+       static fromJSON( data ) {
 
-       const sliceCount = slices + 1;
+               return new CircleGeometry( data.radius, data.segments, data.thetaStart, data.thetaLength );
 
-       for ( let i = 0; i <= stacks; i ++ ) {
+       }
 
-               const v = i / stacks;
+    }
 
-               for ( let j = 0; j <= slices; j ++ ) {
+    new Vector3();
+    new Vector3();
+    new Vector3();
+    new Triangle();
 
-                       const u = j / slices;
+    /**
+     * 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.
+     *
+     **/
 
-                       // vertex
+    class Curve {
 
-                       func( u, v, p0 );
-                       vertices.push( p0.x, p0.y, p0.z );
+       constructor() {
 
-                       // normal
+               this.type = 'Curve';
 
-                       // approximate tangent vectors via finite differences
+               this.arcLengthDivisions = 200;
 
-                       if ( u - EPS >= 0 ) {
+       }
 
-                               func( u - EPS, v, p1 );
-                               pu.subVectors( p0, p1 );
+       // Virtual base class method to overwrite and implement in subclasses
+       //      - t [0 .. 1]
 
-                       } else {
+       getPoint( /* t, optionalTarget */ ) {
 
-                               func( u + EPS, v, p1 );
-                               pu.subVectors( p1, p0 );
+               console.warn( 'THREE.Curve: .getPoint() not implemented.' );
+               return null;
 
-                       }
+       }
 
-                       if ( v - EPS >= 0 ) {
+       // Get point at relative position in curve according to arc length
+       // - u [0 .. 1]
 
-                               func( u, v - EPS, p1 );
-                               pv.subVectors( p0, p1 );
+       getPointAt( u, optionalTarget ) {
 
-                       } else {
+               const t = this.getUtoTmapping( u );
+               return this.getPoint( t, optionalTarget );
 
-                               func( u, v + EPS, p1 );
-                               pv.subVectors( p1, p0 );
+       }
 
-                       }
+       // Get sequence of points using getPoint( t )
 
-                       // cross product of tangent vectors returns surface normal
+       getPoints( divisions = 5 ) {
 
-                       normal.crossVectors( pu, pv ).normalize();
-                       normals.push( normal.x, normal.y, normal.z );
+               const points = [];
 
-                       // uv
+               for ( let d = 0; d <= divisions; d ++ ) {
 
-                       uvs.push( u, v );
+                       points.push( this.getPoint( d / divisions ) );
 
                }
 
-       }
+               return points;
 
-       // generate indices
+       }
 
-       for ( let i = 0; i < stacks; i ++ ) {
+       // Get sequence of points using getPointAt( u )
 
-               for ( let j = 0; j < slices; j ++ ) {
+       getSpacedPoints( divisions = 5 ) {
 
-                       const a = i * sliceCount + j;
-                       const b = i * sliceCount + j + 1;
-                       const c = ( i + 1 ) * sliceCount + j + 1;
-                       const d = ( i + 1 ) * sliceCount + j;
+               const points = [];
 
-                       // faces one and two
+               for ( let d = 0; d <= divisions; d ++ ) {
 
-                       indices.push( a, b, d );
-                       indices.push( b, c, d );
+                       points.push( this.getPointAt( d / divisions ) );
 
                }
 
+               return points;
+
        }
 
-       // build geometry
+       // Get total curve arc length
 
-       this.setIndex( indices );
-       this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
-       this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
-       this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+       getLength() {
 
-    }
+               const lengths = this.getLengths();
+               return lengths[ lengths.length - 1 ];
 
-    ParametricGeometry.prototype = Object.create( BufferGeometry.prototype );
-    ParametricGeometry.prototype.constructor = ParametricGeometry;
+       }
 
-    class ShapeGeometry extends BufferGeometry {
+       // Get list of cumulative segment lengths
 
-       constructor( shapes, curveSegments = 12 ) {
+       getLengths( divisions = this.arcLengthDivisions ) {
 
-               super();
-               this.type = 'ShapeGeometry';
+               if ( this.cacheArcLengths &&
+                       ( this.cacheArcLengths.length === divisions + 1 ) &&
+                       ! this.needsUpdate ) {
 
-               this.parameters = {
-                       shapes: shapes,
-                       curveSegments: curveSegments
-               };
+                       return this.cacheArcLengths;
 
-               // buffers
+               }
 
-               const indices = [];
-               const vertices = [];
-               const normals = [];
-               const uvs = [];
+               this.needsUpdate = false;
 
-               // helper variables
+               const cache = [];
+               let current, last = this.getPoint( 0 );
+               let sum = 0;
 
-               let groupStart = 0;
-               let groupCount = 0;
+               cache.push( 0 );
 
-               // allow single and array values for "shapes" parameter
+               for ( let p = 1; p <= divisions; p ++ ) {
 
-               if ( Array.isArray( shapes ) === false ) {
+                       current = this.getPoint( p / divisions );
+                       sum += current.distanceTo( last );
+                       cache.push( sum );
+                       last = current;
 
-                       addShape( shapes );
+               }
 
-               } else {
+               this.cacheArcLengths = cache;
 
-                       for ( let i = 0; i < shapes.length; i ++ ) {
+               return cache; // { sums: cache, sum: sum }; Sum is in the last element.
 
-                               addShape( shapes[ i ] );
+       }
 
-                               this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support
+       updateArcLengths() {
 
-                               groupStart += groupCount;
-                               groupCount = 0;
+               this.needsUpdate = true;
+               this.getLengths();
 
-                       }
+       }
 
-               }
+       // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant
 
-               // build geometry
+       getUtoTmapping( u, distance ) {
 
-               this.setIndex( indices );
-               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
-               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
-               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+               const arcLengths = this.getLengths();
 
+               let i = 0;
+               const il = arcLengths.length;
 
-               // helper functions
+               let targetArcLength; // The targeted u distance value to get
 
-               function addShape( shape ) {
+               if ( distance ) {
 
-                       const indexOffset = vertices.length / 3;
-                       const points = shape.extractPoints( curveSegments );
+                       targetArcLength = distance;
 
-                       let shapeVertices = points.shape;
-                       const shapeHoles = points.holes;
+               } else {
 
-                       // check direction of vertices
+                       targetArcLength = u * arcLengths[ il - 1 ];
 
-                       if ( ShapeUtils.isClockWise( shapeVertices ) === false ) {
+               }
 
-                               shapeVertices = shapeVertices.reverse();
+               // binary search for the index with largest value smaller than target u distance
 
-                       }
+               let low = 0, high = il - 1, comparison;
 
-                       for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
+               while ( low <= high ) {
 
-                               const shapeHole = shapeHoles[ i ];
+                       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
 
-                               if ( ShapeUtils.isClockWise( shapeHole ) === true ) {
+                       comparison = arcLengths[ i ] - targetArcLength;
 
-                                       shapeHoles[ i ] = shapeHole.reverse();
+                       if ( comparison < 0 ) {
 
-                               }
+                               low = i + 1;
 
-                       }
+                       } else if ( comparison > 0 ) {
 
-                       const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles );
+                               high = i - 1;
 
-                       // join vertices of inner and outer paths to a single array
+                       } else {
 
-                       for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
+                               high = i;
+                               break;
 
-                               const shapeHole = shapeHoles[ i ];
-                               shapeVertices = shapeVertices.concat( shapeHole );
+                               // DONE
 
                        }
 
-                       // vertices, normals, uvs
-
-                       for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) {
-
-                               const vertex = shapeVertices[ i ];
+               }
 
-                               vertices.push( vertex.x, vertex.y, 0 );
-                               normals.push( 0, 0, 1 );
-                               uvs.push( vertex.x, vertex.y ); // world uvs
+               i = high;
 
-                       }
+               if ( arcLengths[ i ] === targetArcLength ) {
 
-                       // incides
+                       return i / ( il - 1 );
 
-                       for ( let i = 0, l = faces.length; i < l; i ++ ) {
+               }
 
-                               const face = faces[ i ];
+               // we could get finer grain at lengths, or use simple interpolation between two points
 
-                               const a = face[ 0 ] + indexOffset;
-                               const b = face[ 1 ] + indexOffset;
-                               const c = face[ 2 ] + indexOffset;
+               const lengthBefore = arcLengths[ i ];
+               const lengthAfter = arcLengths[ i + 1 ];
 
-                               indices.push( a, b, c );
-                               groupCount += 3;
+               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
 
-       toJSON() {
+               const t = ( i + segmentFraction ) / ( il - 1 );
 
-               const data = BufferGeometry.prototype.toJSON.call( this );
+               return t;
 
-               const shapes = this.parameters.shapes;
+       }
 
-               return toJSON$1( shapes, data );
+       // 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( t, optionalTarget ) {
 
-    }
+               const delta = 0.0001;
+               let t1 = t - delta;
+               let t2 = t + delta;
 
-    function toJSON$1( shapes, data ) {
+               // Capping in case of danger
 
-       data.shapes = [];
+               if ( t1 < 0 ) t1 = 0;
+               if ( t2 > 1 ) t2 = 1;
 
-       if ( Array.isArray( shapes ) ) {
+               const pt1 = this.getPoint( t1 );
+               const pt2 = this.getPoint( t2 );
 
-               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+               const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() );
 
-                       const shape = shapes[ i ];
+               tangent.copy( pt2 ).sub( pt1 ).normalize();
 
-                       data.shapes.push( shape.uuid );
+               return tangent;
 
-               }
+       }
 
-       } else {
+       getTangentAt( u, optionalTarget ) {
 
-               data.shapes.push( shapes.uuid );
+               const t = this.getUtoTmapping( u );
+               return this.getTangent( t, optionalTarget );
 
        }
 
-       return data;
+       computeFrenetFrames( segments, closed ) {
 
-    }
+               // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
 
-    class SphereGeometry extends BufferGeometry {
+               const normal = new Vector3();
 
-       constructor( radius = 1, widthSegments = 8, heightSegments = 6, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) {
+               const tangents = [];
+               const normals = [];
+               const binormals = [];
 
-               super();
-               this.type = 'SphereGeometry';
+               const vec = new Vector3();
+               const mat = new Matrix4();
 
-               this.parameters = {
-                       radius: radius,
-                       widthSegments: widthSegments,
-                       heightSegments: heightSegments,
-                       phiStart: phiStart,
-                       phiLength: phiLength,
-                       thetaStart: thetaStart,
-                       thetaLength: thetaLength
-               };
+               // compute the tangent vectors for each segment on the curve
 
-               widthSegments = Math.max( 3, Math.floor( widthSegments ) );
-               heightSegments = Math.max( 2, Math.floor( heightSegments ) );
+               for ( let i = 0; i <= segments; i ++ ) {
 
-               const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI );
+                       const u = i / segments;
 
-               let index = 0;
-               const grid = [];
+                       tangents[ i ] = this.getTangentAt( u, new Vector3() );
 
-               const vertex = new Vector3();
-               const normal = new Vector3();
+               }
 
-               // buffers
+               // select an initial normal vector perpendicular to the first tangent vector,
+               // and in the direction of the minimum tangent xyz component
 
-               const indices = [];
-               const vertices = [];
-               const normals = [];
-               const uvs = [];
+               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 );
 
-               // generate vertices, normals and uvs
+               if ( tx <= min ) {
 
-               for ( let iy = 0; iy <= heightSegments; iy ++ ) {
+                       min = tx;
+                       normal.set( 1, 0, 0 );
 
-                       const verticesRow = [];
+               }
 
-                       const v = iy / heightSegments;
+               if ( ty <= min ) {
 
-                       // special case for the poles
+                       min = ty;
+                       normal.set( 0, 1, 0 );
 
-                       let uOffset = 0;
+               }
 
-                       if ( iy == 0 && thetaStart == 0 ) {
+               if ( tz <= min ) {
 
-                               uOffset = 0.5 / widthSegments;
+                       normal.set( 0, 0, 1 );
 
-                       } else if ( iy == heightSegments && thetaEnd == Math.PI ) {
+               }
 
-                               uOffset = - 0.5 / widthSegments;
+               vec.crossVectors( tangents[ 0 ], normal ).normalize();
 
-                       }
+               normals[ 0 ].crossVectors( tangents[ 0 ], vec );
+               binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );
 
-                       for ( let ix = 0; ix <= widthSegments; ix ++ ) {
 
-                               const u = ix / widthSegments;
+               // compute the slowly-varying normal and binormal vectors for each segment on the curve
 
-                               // vertex
+               for ( let i = 1; i <= segments; i ++ ) {
 
-                               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 );
+                       normals[ i ] = normals[ i - 1 ].clone();
 
-                               vertices.push( vertex.x, vertex.y, vertex.z );
+                       binormals[ i ] = binormals[ i - 1 ].clone();
 
-                               // normal
+                       vec.crossVectors( tangents[ i - 1 ], tangents[ i ] );
 
-                               normal.copy( vertex ).normalize();
-                               normals.push( normal.x, normal.y, normal.z );
+                       if ( vec.length() > Number.EPSILON ) {
 
-                               // uv
+                               vec.normalize();
 
-                               uvs.push( u + uOffset, 1 - v );
+                               const theta = Math.acos( clamp$1( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors
 
-                               verticesRow.push( index ++ );
+                               normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );
 
                        }
 
-                       grid.push( verticesRow );
+                       binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
 
                }
 
-               // indices
+               // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
 
-               for ( let iy = 0; iy < heightSegments; iy ++ ) {
+               if ( closed === true ) {
 
-                       for ( let ix = 0; ix < widthSegments; ix ++ ) {
+                       let theta = Math.acos( clamp$1( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) );
+                       theta /= segments;
 
-                               const a = grid[ iy ][ ix + 1 ];
-                               const b = grid[ iy ][ ix ];
-                               const c = grid[ iy + 1 ][ ix ];
-                               const d = grid[ iy + 1 ][ ix + 1 ];
+                       if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) {
 
-                               if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d );
-                               if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d );
+                               theta = - theta;
 
                        }
 
-               }
+                       for ( let i = 1; i <= segments; i ++ ) {
 
-               // build geometry
+                               // twist a little...
+                               normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );
+                               binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
 
-               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 {
+                       tangents: tangents,
+                       normals: normals,
+                       binormals: binormals
+               };
 
-    /**
-     * parameters = {
-     *  color: <THREE.Color>
-     * }
-     */
+       }
 
-    function ShadowMaterial( parameters ) {
+       clone() {
 
-       Material.call( this );
+               return new this.constructor().copy( this );
 
-       this.type = 'ShadowMaterial';
+       }
 
-       this.color = new Color( 0x000000 );
-       this.transparent = true;
+       copy( source ) {
 
-       this.setValues( parameters );
+               this.arcLengthDivisions = source.arcLengthDivisions;
 
-    }
+               return this;
 
-    ShadowMaterial.prototype = Object.create( Material.prototype );
-    ShadowMaterial.prototype.constructor = ShadowMaterial;
+       }
 
-    ShadowMaterial.prototype.isShadowMaterial = true;
+       toJSON() {
 
-    ShadowMaterial.prototype.copy = function ( source ) {
+               const data = {
+                       metadata: {
+                               version: 4.5,
+                               type: 'Curve',
+                               generator: 'Curve.toJSON'
+                       }
+               };
 
-       Material.prototype.copy.call( this, source );
+               data.arcLengthDivisions = this.arcLengthDivisions;
+               data.type = this.type;
 
-       this.color.copy( source.color );
+               return data;
 
-       return this;
+       }
 
-    };
+       fromJSON( json ) {
 
-    function RawShaderMaterial( parameters ) {
+               this.arcLengthDivisions = json.arcLengthDivisions;
 
-       ShaderMaterial.call( this, parameters );
+               return this;
 
-       this.type = 'RawShaderMaterial';
+       }
 
     }
 
-    RawShaderMaterial.prototype = Object.create( ShaderMaterial.prototype );
-    RawShaderMaterial.prototype.constructor = RawShaderMaterial;
+    class EllipseCurve extends Curve {
 
-    RawShaderMaterial.prototype.isRawShaderMaterial = true;
+       constructor( aX = 0, aY = 0, xRadius = 1, yRadius = 1, aStartAngle = 0, aEndAngle = Math.PI * 2, aClockwise = false, aRotation = 0 ) {
 
-    /**
-     * 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>
-     * }
-     */
+               super();
 
-    function MeshStandardMaterial( parameters ) {
+               this.type = 'EllipseCurve';
 
-       Material.call( this );
+               this.aX = aX;
+               this.aY = aY;
 
-       this.defines = { 'STANDARD': '' };
+               this.xRadius = xRadius;
+               this.yRadius = yRadius;
 
-       this.type = 'MeshStandardMaterial';
+               this.aStartAngle = aStartAngle;
+               this.aEndAngle = aEndAngle;
 
-       this.color = new Color( 0xffffff ); // diffuse
-       this.roughness = 1.0;
-       this.metalness = 0.0;
+               this.aClockwise = aClockwise;
 
-       this.map = null;
+               this.aRotation = aRotation;
 
-       this.lightMap = null;
-       this.lightMapIntensity = 1.0;
+       }
 
-       this.aoMap = null;
-       this.aoMapIntensity = 1.0;
+       getPoint( t, optionalTarget ) {
 
-       this.emissive = new Color( 0x000000 );
-       this.emissiveIntensity = 1.0;
-       this.emissiveMap = null;
+               const point = optionalTarget || new Vector2();
 
-       this.bumpMap = null;
-       this.bumpScale = 1;
+               const twoPi = Math.PI * 2;
+               let deltaAngle = this.aEndAngle - this.aStartAngle;
+               const samePoints = Math.abs( deltaAngle ) < Number.EPSILON;
 
-       this.normalMap = null;
-       this.normalMapType = TangentSpaceNormalMap;
-       this.normalScale = new Vector2( 1, 1 );
+               // ensures that deltaAngle is 0 .. 2 PI
+               while ( deltaAngle < 0 ) deltaAngle += twoPi;
+               while ( deltaAngle > twoPi ) deltaAngle -= twoPi;
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+               if ( deltaAngle < Number.EPSILON ) {
 
-       this.roughnessMap = null;
+                       if ( samePoints ) {
 
-       this.metalnessMap = null;
+                               deltaAngle = 0;
 
-       this.alphaMap = null;
+                       } else {
 
-       this.envMap = null;
-       this.envMapIntensity = 1.0;
+                               deltaAngle = twoPi;
 
-       this.refractionRatio = 0.98;
+                       }
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
-       this.wireframeLinecap = 'round';
-       this.wireframeLinejoin = 'round';
+               }
 
-       this.skinning = false;
-       this.morphTargets = false;
-       this.morphNormals = false;
+               if ( this.aClockwise === true && ! samePoints ) {
 
-       this.vertexTangents = false;
+                       if ( deltaAngle === twoPi ) {
 
-       this.setValues( parameters );
+                               deltaAngle = - twoPi;
 
-    }
+                       } else {
 
-    MeshStandardMaterial.prototype = Object.create( Material.prototype );
-    MeshStandardMaterial.prototype.constructor = MeshStandardMaterial;
+                               deltaAngle = deltaAngle - twoPi;
 
-    MeshStandardMaterial.prototype.isMeshStandardMaterial = true;
+                       }
 
-    MeshStandardMaterial.prototype.copy = function ( source ) {
+               }
 
-       Material.prototype.copy.call( this, source );
+               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.defines = { 'STANDARD': '' };
+               if ( this.aRotation !== 0 ) {
 
-       this.color.copy( source.color );
-       this.roughness = source.roughness;
-       this.metalness = source.metalness;
+                       const cos = Math.cos( this.aRotation );
+                       const sin = Math.sin( this.aRotation );
 
-       this.map = source.map;
+                       const tx = x - this.aX;
+                       const ty = y - this.aY;
 
-       this.lightMap = source.lightMap;
-       this.lightMapIntensity = source.lightMapIntensity;
+                       // Rotate the point about the center of the ellipse.
+                       x = tx * cos - ty * sin + this.aX;
+                       y = tx * sin + ty * cos + this.aY;
 
-       this.aoMap = source.aoMap;
-       this.aoMapIntensity = source.aoMapIntensity;
+               }
 
-       this.emissive.copy( source.emissive );
-       this.emissiveMap = source.emissiveMap;
-       this.emissiveIntensity = source.emissiveIntensity;
+               return point.set( x, y );
 
-       this.bumpMap = source.bumpMap;
-       this.bumpScale = source.bumpScale;
+       }
 
-       this.normalMap = source.normalMap;
-       this.normalMapType = source.normalMapType;
-       this.normalScale.copy( source.normalScale );
+       copy( source ) {
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+               super.copy( source );
 
-       this.roughnessMap = source.roughnessMap;
+               this.aX = source.aX;
+               this.aY = source.aY;
 
-       this.metalnessMap = source.metalnessMap;
+               this.xRadius = source.xRadius;
+               this.yRadius = source.yRadius;
 
-       this.alphaMap = source.alphaMap;
+               this.aStartAngle = source.aStartAngle;
+               this.aEndAngle = source.aEndAngle;
 
-       this.envMap = source.envMap;
-       this.envMapIntensity = source.envMapIntensity;
+               this.aClockwise = source.aClockwise;
 
-       this.refractionRatio = source.refractionRatio;
+               this.aRotation = source.aRotation;
 
-       this.wireframe = source.wireframe;
-       this.wireframeLinewidth = source.wireframeLinewidth;
-       this.wireframeLinecap = source.wireframeLinecap;
-       this.wireframeLinejoin = source.wireframeLinejoin;
+               return this;
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
-       this.morphNormals = source.morphNormals;
+       }
 
-       this.vertexTangents = source.vertexTangents;
+       toJSON() {
 
-       return this;
+               const data = super.toJSON();
 
-    };
+               data.aX = this.aX;
+               data.aY = this.aY;
 
-    /**
-     * 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> )
-     * }
-     */
+               data.xRadius = this.xRadius;
+               data.yRadius = this.yRadius;
 
-    function MeshPhysicalMaterial( parameters ) {
+               data.aStartAngle = this.aStartAngle;
+               data.aEndAngle = this.aEndAngle;
 
-       MeshStandardMaterial.call( this );
+               data.aClockwise = this.aClockwise;
 
-       this.defines = {
+               data.aRotation = this.aRotation;
 
-               '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;
+               super.fromJSON( json );
 
-       this.reflectivity = 0.5; // maps to F0 = 0.04
+               this.aX = json.aX;
+               this.aY = json.aY;
 
-       Object.defineProperty( this, 'ior', {
-               get: function () {
+               this.xRadius = json.xRadius;
+               this.yRadius = json.yRadius;
 
-                       return ( 1 + 0.4 * this.reflectivity ) / ( 1 - 0.4 * this.reflectivity );
+               this.aStartAngle = json.aStartAngle;
+               this.aEndAngle = json.aEndAngle;
 
-               },
-               set: function ( ior ) {
+               this.aClockwise = json.aClockwise;
 
-                       this.reflectivity = MathUtils.clamp( 2.5 * ( ior - 1 ) / ( ior + 1 ), 0, 1 );
+               this.aRotation = json.aRotation;
 
-               }
-       } );
+               return this;
 
-       this.sheen = null; // null will disable sheen bsdf
+       }
 
-       this.transmission = 0.0;
-       this.transmissionMap = null;
+    }
 
-       this.setValues( parameters );
+    EllipseCurve.prototype.isEllipseCurve = true;
 
-    }
+    class ArcCurve extends EllipseCurve {
 
-    MeshPhysicalMaterial.prototype = Object.create( MeshStandardMaterial.prototype );
-    MeshPhysicalMaterial.prototype.constructor = MeshPhysicalMaterial;
+       constructor( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
 
-    MeshPhysicalMaterial.prototype.isMeshPhysicalMaterial = true;
+               super( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
 
-    MeshPhysicalMaterial.prototype.copy = function ( source ) {
+               this.type = 'ArcCurve';
 
-       MeshStandardMaterial.prototype.copy.call( this, source );
+       }
 
-       this.defines = {
+    }
 
-               'STANDARD': '',
-               'PHYSICAL': ''
+    ArcCurve.prototype.isArcCurve = true;
+
+    /**
+     * Centripetal CatmullRom Curve - which is useful for avoiding
+     * cusps and self-intersections in non-uniform catmull rom curves.
+     * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
+     *
+     * curve.type accepts centripetal(default), chordal and catmullrom
+     * curve.tension is used for catmullrom which defaults to 0.5
+     */
 
-       };
 
-       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 );
+    /*
+    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.reflectivity = source.reflectivity;
+    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.
+    */
 
-       if ( source.sheen ) {
+    function CubicPoly() {
 
-               this.sheen = ( this.sheen || new Color() ).copy( source.sheen );
+       let c0 = 0, c1 = 0, c2 = 0, c3 = 0;
 
-       } else {
+       /*
+        * 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.sheen = null;
+               c0 = x0;
+               c1 = t0;
+               c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1;
+               c3 = 2 * x0 - 2 * x1 + t0 + t1;
 
        }
 
-       this.transmission = source.transmission;
-       this.transmissionMap = source.transmissionMap;
-
-       return this;
+       return {
 
-    };
+               initCatmullRom: function ( x0, x1, x2, x3, tension ) {
 
-    /**
-     * 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>
-     * }
-     */
+                       init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );
 
-    function MeshPhongMaterial( parameters ) {
+               },
 
-       Material.call( this );
+               initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) {
 
-       this.type = 'MeshPhongMaterial';
+                       // 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.color = new Color( 0xffffff ); // diffuse
-       this.specular = new Color( 0x111111 );
-       this.shininess = 30;
+                       // rescale tangents for parametrization in [0,1]
+                       t1 *= dt1;
+                       t2 *= dt1;
 
-       this.map = null;
+                       init( x1, x2, t1, t2 );
 
-       this.lightMap = null;
-       this.lightMapIntensity = 1.0;
+               },
 
-       this.aoMap = null;
-       this.aoMapIntensity = 1.0;
+               calc: function ( t ) {
 
-       this.emissive = new Color( 0x000000 );
-       this.emissiveIntensity = 1.0;
-       this.emissiveMap = null;
+                       const t2 = t * t;
+                       const t3 = t2 * t;
+                       return c0 + c1 * t + c2 * t2 + c3 * t3;
 
-       this.bumpMap = null;
-       this.bumpScale = 1;
+               }
 
-       this.normalMap = null;
-       this.normalMapType = TangentSpaceNormalMap;
-       this.normalScale = new Vector2( 1, 1 );
+       };
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+    }
 
-       this.specularMap = null;
+    //
 
-       this.alphaMap = null;
+    const tmp = new Vector3();
+    const px = new CubicPoly(), py = new CubicPoly(), pz = new CubicPoly();
 
-       this.envMap = null;
-       this.combine = MultiplyOperation;
-       this.reflectivity = 1;
-       this.refractionRatio = 0.98;
+    class CatmullRomCurve3 extends Curve {
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
-       this.wireframeLinecap = 'round';
-       this.wireframeLinejoin = 'round';
+       constructor( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) {
 
-       this.skinning = false;
-       this.morphTargets = false;
-       this.morphNormals = false;
+               super();
 
-       this.setValues( parameters );
+               this.type = 'CatmullRomCurve3';
 
-    }
+               this.points = points;
+               this.closed = closed;
+               this.curveType = curveType;
+               this.tension = tension;
 
-    MeshPhongMaterial.prototype = Object.create( Material.prototype );
-    MeshPhongMaterial.prototype.constructor = MeshPhongMaterial;
+       }
 
-    MeshPhongMaterial.prototype.isMeshPhongMaterial = true;
+       getPoint( t, optionalTarget = new Vector3() ) {
 
-    MeshPhongMaterial.prototype.copy = function ( source ) {
+               const point = optionalTarget;
 
-       Material.prototype.copy.call( this, source );
+               const points = this.points;
+               const l = points.length;
 
-       this.color.copy( source.color );
-       this.specular.copy( source.specular );
-       this.shininess = source.shininess;
+               const p = ( l - ( this.closed ? 0 : 1 ) ) * t;
+               let intPoint = Math.floor( p );
+               let weight = p - intPoint;
 
-       this.map = source.map;
+               if ( this.closed ) {
 
-       this.lightMap = source.lightMap;
-       this.lightMapIntensity = source.lightMapIntensity;
+                       intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l;
 
-       this.aoMap = source.aoMap;
-       this.aoMapIntensity = source.aoMapIntensity;
+               } else if ( weight === 0 && intPoint === l - 1 ) {
 
-       this.emissive.copy( source.emissive );
-       this.emissiveMap = source.emissiveMap;
-       this.emissiveIntensity = source.emissiveIntensity;
+                       intPoint = l - 2;
+                       weight = 1;
 
-       this.bumpMap = source.bumpMap;
-       this.bumpScale = source.bumpScale;
+               }
 
-       this.normalMap = source.normalMap;
-       this.normalMapType = source.normalMapType;
-       this.normalScale.copy( source.normalScale );
+               let p0, p3; // 4 points (p1 & p2 defined below)
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+               if ( this.closed || intPoint > 0 ) {
 
-       this.specularMap = source.specularMap;
+                       p0 = points[ ( intPoint - 1 ) % l ];
 
-       this.alphaMap = source.alphaMap;
+               } else {
 
-       this.envMap = source.envMap;
-       this.combine = source.combine;
-       this.reflectivity = source.reflectivity;
-       this.refractionRatio = source.refractionRatio;
+                       // extrapolate first point
+                       tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
+                       p0 = tmp;
 
-       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;
+               const p1 = points[ intPoint % l ];
+               const p2 = points[ ( intPoint + 1 ) % l ];
 
-       return this;
+               if ( this.closed || intPoint + 2 < l ) {
 
-    };
+                       p3 = points[ ( intPoint + 2 ) % l ];
 
-    /**
-     * 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>
-     * }
-     */
+               } else {
 
-    function MeshToonMaterial( parameters ) {
+                       // extrapolate last point
+                       tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] );
+                       p3 = tmp;
 
-       Material.call( this );
+               }
 
-       this.defines = { 'TOON': '' };
+               if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) {
 
-       this.type = 'MeshToonMaterial';
+                       // 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.color = new Color( 0xffffff );
+                       // 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.map = null;
-       this.gradientMap = null;
+                       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.lightMap = null;
-       this.lightMapIntensity = 1.0;
+               } else if ( this.curveType === 'catmullrom' ) {
 
-       this.aoMap = null;
-       this.aoMapIntensity = 1.0;
+                       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.emissive = new Color( 0x000000 );
-       this.emissiveIntensity = 1.0;
-       this.emissiveMap = null;
+               }
 
-       this.bumpMap = null;
-       this.bumpScale = 1;
+               point.set(
+                       px.calc( weight ),
+                       py.calc( weight ),
+                       pz.calc( weight )
+               );
 
-       this.normalMap = null;
-       this.normalMapType = TangentSpaceNormalMap;
-       this.normalScale = new Vector2( 1, 1 );
+               return point;
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+       }
 
-       this.alphaMap = null;
+       copy( source ) {
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
-       this.wireframeLinecap = 'round';
-       this.wireframeLinejoin = 'round';
+               super.copy( source );
 
-       this.skinning = false;
-       this.morphTargets = false;
-       this.morphNormals = false;
+               this.points = [];
 
-       this.setValues( parameters );
+               for ( let i = 0, l = source.points.length; i < l; i ++ ) {
 
-    }
+                       const point = source.points[ i ];
 
-    MeshToonMaterial.prototype = Object.create( Material.prototype );
-    MeshToonMaterial.prototype.constructor = MeshToonMaterial;
+                       this.points.push( point.clone() );
 
-    MeshToonMaterial.prototype.isMeshToonMaterial = true;
+               }
 
-    MeshToonMaterial.prototype.copy = function ( source ) {
+               this.closed = source.closed;
+               this.curveType = source.curveType;
+               this.tension = source.tension;
 
-       Material.prototype.copy.call( this, source );
+               return this;
 
-       this.color.copy( source.color );
+       }
 
-       this.map = source.map;
-       this.gradientMap = source.gradientMap;
+       toJSON() {
 
-       this.lightMap = source.lightMap;
-       this.lightMapIntensity = source.lightMapIntensity;
+               const data = super.toJSON();
 
-       this.aoMap = source.aoMap;
-       this.aoMapIntensity = source.aoMapIntensity;
+               data.points = [];
 
-       this.emissive.copy( source.emissive );
-       this.emissiveMap = source.emissiveMap;
-       this.emissiveIntensity = source.emissiveIntensity;
+               for ( let i = 0, l = this.points.length; i < l; i ++ ) {
 
-       this.bumpMap = source.bumpMap;
-       this.bumpScale = source.bumpScale;
+                       const point = this.points[ i ];
+                       data.points.push( point.toArray() );
 
-       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;
+               data.closed = this.closed;
+               data.curveType = this.curveType;
+               data.tension = this.tension;
 
-       this.alphaMap = source.alphaMap;
+               return data;
 
-       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;
+       fromJSON( json ) {
 
-       return this;
+               super.fromJSON( json );
 
-    };
+               this.points = [];
 
-    /**
-     * 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>
-     * }
-     */
+               for ( let i = 0, l = json.points.length; i < l; i ++ ) {
 
-    function MeshNormalMaterial( parameters ) {
+                       const point = json.points[ i ];
+                       this.points.push( new Vector3().fromArray( point ) );
 
-       Material.call( this );
+               }
 
-       this.type = 'MeshNormalMaterial';
+               this.closed = json.closed;
+               this.curveType = json.curveType;
+               this.tension = json.tension;
 
-       this.bumpMap = null;
-       this.bumpScale = 1;
+               return this;
 
-       this.normalMap = null;
-       this.normalMapType = TangentSpaceNormalMap;
-       this.normalScale = new Vector2( 1, 1 );
+       }
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+    }
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
+    CatmullRomCurve3.prototype.isCatmullRomCurve3 = true;
 
-       this.fog = false;
+    /**
+     * 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;
 
     }
 
-    MeshNormalMaterial.prototype = Object.create( Material.prototype );
-    MeshNormalMaterial.prototype.constructor = MeshNormalMaterial;
-
-    MeshNormalMaterial.prototype.isMeshNormalMaterial = true;
+    //
 
-    MeshNormalMaterial.prototype.copy = function ( source ) {
+    function QuadraticBezierP0( t, p ) {
 
-       Material.prototype.copy.call( this, source );
+       const k = 1 - t;
+       return k * k * p;
 
-       this.bumpMap = source.bumpMap;
-       this.bumpScale = source.bumpScale;
+    }
 
-       this.normalMap = source.normalMap;
-       this.normalMapType = source.normalMapType;
-       this.normalScale.copy( source.normalScale );
+    function QuadraticBezierP1( t, p ) {
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+       return 2 * ( 1 - t ) * t * p;
 
-       this.wireframe = source.wireframe;
-       this.wireframeLinewidth = source.wireframeLinewidth;
+    }
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
-       this.morphNormals = source.morphNormals;
+    function QuadraticBezierP2( t, p ) {
 
-       return this;
+       return t * t * p;
 
-    };
+    }
 
-    /**
-     * parameters = {
-     *  color: <hex>,
-     *  opacity: <float>,
-     *
-     *  map: new THREE.Texture( <Image> ),
-     *
-     *  lightMap: new THREE.Texture( <Image> ),
-     *  lightMapIntensity: <float>
-     *
-     *  aoMap: new THREE.Texture( <Image> ),
-     *  aoMapIntensity: <float>
-     *
-     *  emissive: <hex>,
-     *  emissiveIntensity: <float>
-     *  emissiveMap: new THREE.Texture( <Image> ),
-     *
-     *  specularMap: new THREE.Texture( <Image> ),
-     *
-     *  alphaMap: new THREE.Texture( <Image> ),
-     *
-     *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
-     *  combine: THREE.Multiply,
-     *  reflectivity: <float>,
-     *  refractionRatio: <float>,
-     *
-     *  wireframe: <boolean>,
-     *  wireframeLinewidth: <float>,
-     *
-     *  skinning: <bool>,
-     *  morphTargets: <bool>,
-     *  morphNormals: <bool>
-     * }
-     */
+    function QuadraticBezier( t, p0, p1, p2 ) {
 
-    function MeshLambertMaterial( parameters ) {
+       return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) +
+               QuadraticBezierP2( t, p2 );
 
-       Material.call( this );
+    }
 
-       this.type = 'MeshLambertMaterial';
+    //
 
-       this.color = new Color( 0xffffff ); // diffuse
+    function CubicBezierP0( t, p ) {
 
-       this.map = null;
+       const k = 1 - t;
+       return k * k * k * p;
 
-       this.lightMap = null;
-       this.lightMapIntensity = 1.0;
+    }
 
-       this.aoMap = null;
-       this.aoMapIntensity = 1.0;
+    function CubicBezierP1( t, p ) {
 
-       this.emissive = new Color( 0x000000 );
-       this.emissiveIntensity = 1.0;
-       this.emissiveMap = null;
+       const k = 1 - t;
+       return 3 * k * k * t * p;
 
-       this.specularMap = null;
+    }
 
-       this.alphaMap = null;
+    function CubicBezierP2( t, p ) {
 
-       this.envMap = null;
-       this.combine = MultiplyOperation;
-       this.reflectivity = 1;
-       this.refractionRatio = 0.98;
+       return 3 * ( 1 - t ) * t * t * p;
 
-       this.wireframe = false;
-       this.wireframeLinewidth = 1;
-       this.wireframeLinecap = 'round';
-       this.wireframeLinejoin = 'round';
+    }
 
-       this.skinning = false;
-       this.morphTargets = false;
-       this.morphNormals = false;
+    function CubicBezierP3( t, p ) {
 
-       this.setValues( parameters );
+       return t * t * t * p;
 
     }
 
-    MeshLambertMaterial.prototype = Object.create( Material.prototype );
-    MeshLambertMaterial.prototype.constructor = MeshLambertMaterial;
+    function CubicBezier( t, p0, p1, p2, p3 ) {
 
-    MeshLambertMaterial.prototype.isMeshLambertMaterial = true;
+       return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) +
+               CubicBezierP3( t, p3 );
 
-    MeshLambertMaterial.prototype.copy = function ( source ) {
+    }
 
-       Material.prototype.copy.call( this, source );
+    class CubicBezierCurve extends Curve {
 
-       this.color.copy( source.color );
+       constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) {
 
-       this.map = source.map;
+               super();
 
-       this.lightMap = source.lightMap;
-       this.lightMapIntensity = source.lightMapIntensity;
+               this.type = 'CubicBezierCurve';
 
-       this.aoMap = source.aoMap;
-       this.aoMapIntensity = source.aoMapIntensity;
+               this.v0 = v0;
+               this.v1 = v1;
+               this.v2 = v2;
+               this.v3 = v3;
 
-       this.emissive.copy( source.emissive );
-       this.emissiveMap = source.emissiveMap;
-       this.emissiveIntensity = source.emissiveIntensity;
+       }
 
-       this.specularMap = source.specularMap;
+       getPoint( t, optionalTarget = new Vector2() ) {
 
-       this.alphaMap = source.alphaMap;
+               const point = optionalTarget;
 
-       this.envMap = source.envMap;
-       this.combine = source.combine;
-       this.reflectivity = source.reflectivity;
-       this.refractionRatio = source.refractionRatio;
+               const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
 
-       this.wireframe = source.wireframe;
-       this.wireframeLinewidth = source.wireframeLinewidth;
-       this.wireframeLinecap = source.wireframeLinecap;
-       this.wireframeLinejoin = source.wireframeLinejoin;
+               point.set(
+                       CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),
+                       CubicBezier( t, v0.y, v1.y, v2.y, v3.y )
+               );
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
-       this.morphNormals = source.morphNormals;
+               return point;
 
-       return this;
+       }
 
-    };
+       copy( source ) {
 
-    /**
-     * 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>
-     * }
-     */
+               super.copy( source );
 
-    function MeshMatcapMaterial( parameters ) {
+               this.v0.copy( source.v0 );
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
+               this.v3.copy( source.v3 );
 
-       Material.call( this );
+               return this;
 
-       this.defines = { 'MATCAP': '' };
+       }
 
-       this.type = 'MeshMatcapMaterial';
+       toJSON() {
 
-       this.color = new Color( 0xffffff ); // diffuse
+               const data = super.toJSON();
 
-       this.matcap = null;
+               data.v0 = this.v0.toArray();
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
+               data.v3 = this.v3.toArray();
 
-       this.map = null;
+               return data;
 
-       this.bumpMap = null;
-       this.bumpScale = 1;
+       }
 
-       this.normalMap = null;
-       this.normalMapType = TangentSpaceNormalMap;
-       this.normalScale = new Vector2( 1, 1 );
+       fromJSON( json ) {
 
-       this.displacementMap = null;
-       this.displacementScale = 1;
-       this.displacementBias = 0;
+               super.fromJSON( json );
 
-       this.alphaMap = null;
+               this.v0.fromArray( json.v0 );
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
+               this.v3.fromArray( json.v3 );
 
-       this.skinning = false;
-       this.morphTargets = false;
-       this.morphNormals = false;
+               return this;
 
-       this.setValues( parameters );
+       }
 
     }
 
-    MeshMatcapMaterial.prototype = Object.create( Material.prototype );
-    MeshMatcapMaterial.prototype.constructor = MeshMatcapMaterial;
+    CubicBezierCurve.prototype.isCubicBezierCurve = true;
 
-    MeshMatcapMaterial.prototype.isMeshMatcapMaterial = true;
+    class CubicBezierCurve3 extends Curve {
 
-    MeshMatcapMaterial.prototype.copy = function ( source ) {
+       constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) {
 
-       Material.prototype.copy.call( this, source );
+               super();
 
-       this.defines = { 'MATCAP': '' };
+               this.type = 'CubicBezierCurve3';
 
-       this.color.copy( source.color );
+               this.v0 = v0;
+               this.v1 = v1;
+               this.v2 = v2;
+               this.v3 = v3;
 
-       this.matcap = source.matcap;
+       }
 
-       this.map = source.map;
+       getPoint( t, optionalTarget = new Vector3() ) {
 
-       this.bumpMap = source.bumpMap;
-       this.bumpScale = source.bumpScale;
+               const point = optionalTarget;
 
-       this.normalMap = source.normalMap;
-       this.normalMapType = source.normalMapType;
-       this.normalScale.copy( source.normalScale );
+               const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
 
-       this.displacementMap = source.displacementMap;
-       this.displacementScale = source.displacementScale;
-       this.displacementBias = source.displacementBias;
+               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 )
+               );
 
-       this.alphaMap = source.alphaMap;
+               return point;
 
-       this.skinning = source.skinning;
-       this.morphTargets = source.morphTargets;
-       this.morphNormals = source.morphNormals;
+       }
 
-       return this;
+       copy( source ) {
 
-    };
+               super.copy( source );
 
-    /**
-     * parameters = {
-     *  color: <hex>,
-     *  opacity: <float>,
-     *
-     *  linewidth: <float>,
-     *
-     *  scale: <float>,
-     *  dashSize: <float>,
-     *  gapSize: <float>
-     * }
-     */
+               this.v0.copy( source.v0 );
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
+               this.v3.copy( source.v3 );
 
-    function LineDashedMaterial( parameters ) {
+               return this;
 
-       LineBasicMaterial.call( this );
+       }
 
-       this.type = 'LineDashedMaterial';
+       toJSON() {
 
-       this.scale = 1;
-       this.dashSize = 3;
-       this.gapSize = 1;
+               const data = super.toJSON();
 
-       this.setValues( parameters );
+               data.v0 = this.v0.toArray();
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
+               data.v3 = this.v3.toArray();
 
-    }
+               return data;
 
-    LineDashedMaterial.prototype = Object.create( LineBasicMaterial.prototype );
-    LineDashedMaterial.prototype.constructor = LineDashedMaterial;
+       }
 
-    LineDashedMaterial.prototype.isLineDashedMaterial = true;
+       fromJSON( json ) {
 
-    LineDashedMaterial.prototype.copy = function ( source ) {
+               super.fromJSON( json );
 
-       LineBasicMaterial.prototype.copy.call( this, source );
+               this.v0.fromArray( json.v0 );
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
+               this.v3.fromArray( json.v3 );
 
-       this.scale = source.scale;
-       this.dashSize = source.dashSize;
-       this.gapSize = source.gapSize;
+               return this;
 
-       return this;
+       }
 
-    };
+    }
 
-    var Materials = /*#__PURE__*/Object.freeze({
-       __proto__: null,
-       ShadowMaterial: ShadowMaterial,
-       SpriteMaterial: SpriteMaterial,
-       RawShaderMaterial: RawShaderMaterial,
-       ShaderMaterial: ShaderMaterial,
-       PointsMaterial: PointsMaterial,
-       MeshPhysicalMaterial: MeshPhysicalMaterial,
-       MeshStandardMaterial: MeshStandardMaterial,
-       MeshPhongMaterial: MeshPhongMaterial,
-       MeshToonMaterial: MeshToonMaterial,
-       MeshNormalMaterial: MeshNormalMaterial,
-       MeshLambertMaterial: MeshLambertMaterial,
-       MeshDepthMaterial: MeshDepthMaterial,
-       MeshDistanceMaterial: MeshDistanceMaterial,
-       MeshBasicMaterial: MeshBasicMaterial,
-       MeshMatcapMaterial: MeshMatcapMaterial,
-       LineDashedMaterial: LineDashedMaterial,
-       LineBasicMaterial: LineBasicMaterial,
-       Material: Material
-    });
+    CubicBezierCurve3.prototype.isCubicBezierCurve3 = true;
 
-    const AnimationUtils = {
+    class LineCurve extends Curve {
 
-       // same as Array.prototype.slice, but also works on typed arrays
-       arraySlice: function ( array, from, to ) {
+       constructor( v1 = new Vector2(), v2 = new Vector2() ) {
 
-               if ( AnimationUtils.isTypedArray( array ) ) {
+               super();
 
-                       // 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 ) );
+               this.type = 'LineCurve';
 
-               }
+               this.v1 = v1;
+               this.v2 = v2;
 
-               return array.slice( from, to );
+       }
 
-       },
+       getPoint( t, optionalTarget = new Vector2() ) {
 
-       // converts an array to a specific type
-       convertArray: function ( array, type, forceClone ) {
+               const point = optionalTarget;
 
-               if ( ! array || // let 'undefined' and 'null' pass
-                       ! forceClone && array.constructor === type ) return array;
+               if ( t === 1 ) {
 
-               if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {
+                       point.copy( this.v2 );
 
-                       return new type( array ); // create typed array
+               } else {
+
+                       point.copy( this.v2 ).sub( this.v1 );
+                       point.multiplyScalar( t ).add( this.v1 );
 
                }
 
-               return Array.prototype.slice.call( array ); // create Array
+               return point;
 
-       },
+       }
 
-       isTypedArray: function ( object ) {
+       // Line curve is linear, so we can overwrite default getPointAt
+       getPointAt( u, optionalTarget ) {
 
-               return ArrayBuffer.isView( object ) &&
-                       ! ( object instanceof DataView );
+               return this.getPoint( u, optionalTarget );
 
-       },
+       }
 
-       // returns an array by which times and values can be sorted
-       getKeyframeOrder: function ( times ) {
+       getTangent( t, optionalTarget ) {
 
-               function compareTime( i, j ) {
+               const tangent = optionalTarget || new Vector2();
 
-                       return times[ i ] - times[ j ];
+               tangent.copy( this.v2 ).sub( this.v1 ).normalize();
 
-               }
+               return tangent;
 
-               const n = times.length;
-               const result = new Array( n );
-               for ( let i = 0; i !== n; ++ i ) result[ i ] = i;
+       }
 
-               result.sort( compareTime );
+       copy( source ) {
 
-               return result;
+               super.copy( source );
 
-       },
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
 
-       // 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 ) {
+       toJSON() {
 
-                       const srcOffset = order[ i ] * stride;
+               const data = super.toJSON();
 
-                       for ( let j = 0; j !== stride; ++ j ) {
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
 
-                               result[ dstOffset ++ ] = values[ srcOffset + j ];
+               return data;
 
-                       }
+       }
 
-               }
+       fromJSON( json ) {
 
-               return result;
+               super.fromJSON( json );
 
-       },
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
 
-       // function for parsing AOS keyframe formats
-       flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {
+               return this;
 
-               let i = 1, key = jsonKeys[ 0 ];
+       }
 
-               while ( key !== undefined && key[ valuePropertyName ] === undefined ) {
+    }
 
-                       key = jsonKeys[ i ++ ];
+    LineCurve.prototype.isLineCurve = true;
 
-               }
+    class LineCurve3 extends Curve {
 
-               if ( key === undefined ) return; // no data
+       constructor( v1 = new Vector3(), v2 = new Vector3() ) {
 
-               let value = key[ valuePropertyName ];
-               if ( value === undefined ) return; // no data
+               super();
 
-               if ( Array.isArray( value ) ) {
+               this.type = 'LineCurve3';
+               this.isLineCurve3 = true;
 
-                       do {
+               this.v1 = v1;
+               this.v2 = v2;
 
-                               value = key[ valuePropertyName ];
+       }
+       getPoint( t, optionalTarget = new Vector3() ) {
 
-                               if ( value !== undefined ) {
+               const point = optionalTarget;
 
-                                       times.push( key.time );
-                                       values.push.apply( values, value ); // push all elements
+               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 );
 
-               } else if ( value.toArray !== undefined ) {
+               }
 
-                       // ...assume THREE.Math-ish
+               return point;
 
-                       do {
+       }
+       // Line curve is linear, so we can overwrite default getPointAt
+       getPointAt( u, optionalTarget ) {
 
-                               value = key[ valuePropertyName ];
+               return this.getPoint( u, optionalTarget );
 
-                               if ( value !== undefined ) {
+       }
+       copy( source ) {
 
-                                       times.push( key.time );
-                                       value.toArray( values, values.length );
+               super.copy( source );
 
-                               }
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
 
-                               key = jsonKeys[ i ++ ];
+               return this;
 
-                       } while ( key !== undefined );
+       }
+       toJSON() {
 
-               } else {
+               const data = super.toJSON();
 
-                       // otherwise push as-is
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
 
-                       do {
+               return data;
 
-                               value = key[ valuePropertyName ];
+       }
+       fromJSON( json ) {
 
-                               if ( value !== undefined ) {
+               super.fromJSON( json );
 
-                                       times.push( key.time );
-                                       values.push( value );
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
 
-                               }
+               return this;
 
-                               key = jsonKeys[ i ++ ];
+       }
 
-                       } while ( key !== undefined );
+    }
 
-               }
+    class QuadraticBezierCurve extends Curve {
 
-       },
+       constructor( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) {
 
-       subclip: function ( sourceClip, name, startFrame, endFrame, fps = 30 ) {
+               super();
 
-               const clip = sourceClip.clone();
+               this.type = 'QuadraticBezierCurve';
 
-               clip.name = name;
+               this.v0 = v0;
+               this.v1 = v1;
+               this.v2 = v2;
 
-               const tracks = [];
+       }
 
-               for ( let i = 0; i < clip.tracks.length; ++ i ) {
+       getPoint( t, optionalTarget = new Vector2() ) {
 
-                       const track = clip.tracks[ i ];
-                       const valueSize = track.getValueSize();
+               const point = optionalTarget;
 
-                       const times = [];
-                       const values = [];
+               const v0 = this.v0, v1 = this.v1, v2 = this.v2;
 
-                       for ( let j = 0; j < track.times.length; ++ j ) {
+               point.set(
+                       QuadraticBezier( t, v0.x, v1.x, v2.x ),
+                       QuadraticBezier( t, v0.y, v1.y, v2.y )
+               );
 
-                               const frame = track.times[ j ] * fps;
+               return point;
 
-                               if ( frame < startFrame || frame >= endFrame ) continue;
+       }
 
-                               times.push( track.times[ j ] );
+       copy( source ) {
 
-                               for ( let k = 0; k < valueSize; ++ k ) {
+               super.copy( source );
 
-                                       values.push( track.values[ j * valueSize + k ] );
+               this.v0.copy( source.v0 );
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
 
-                               }
+               return this;
 
-                       }
+       }
 
-                       if ( times.length === 0 ) continue;
+       toJSON() {
 
-                       track.times = AnimationUtils.convertArray( times, track.times.constructor );
-                       track.values = AnimationUtils.convertArray( values, track.values.constructor );
+               const data = super.toJSON();
 
-                       tracks.push( track );
+               data.v0 = this.v0.toArray();
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
 
-               }
+               return data;
 
-               clip.tracks = tracks;
+       }
 
-               // find minimum .times value across all tracks in the trimmed clip
+       fromJSON( json ) {
 
-               let minStartTime = Infinity;
+               super.fromJSON( json );
 
-               for ( let i = 0; i < clip.tracks.length; ++ i ) {
+               this.v0.fromArray( json.v0 );
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
 
-                       if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) {
+               return this;
 
-                               minStartTime = clip.tracks[ i ].times[ 0 ];
+       }
 
-                       }
+    }
 
-               }
+    QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true;
 
-               // shift all tracks such that clip begins at t=0
+    class QuadraticBezierCurve3 extends Curve {
 
-               for ( let i = 0; i < clip.tracks.length; ++ i ) {
+       constructor( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) {
 
-                       clip.tracks[ i ].shift( - 1 * minStartTime );
+               super();
 
-               }
+               this.type = 'QuadraticBezierCurve3';
 
-               clip.resetDuration();
+               this.v0 = v0;
+               this.v1 = v1;
+               this.v2 = v2;
 
-               return clip;
+       }
 
-       },
+       getPoint( t, optionalTarget = new Vector3() ) {
 
-       makeClipAdditive: function ( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) {
+               const point = optionalTarget;
 
-               if ( fps <= 0 ) fps = 30;
+               const v0 = this.v0, v1 = this.v1, v2 = this.v2;
 
-               const numTracks = referenceClip.tracks.length;
-               const referenceTime = referenceFrame / fps;
+               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 )
+               );
 
-               // Make each track's values relative to the values at the reference frame
-               for ( let i = 0; i < numTracks; ++ i ) {
+               return point;
 
-                       const referenceTrack = referenceClip.tracks[ i ];
-                       const referenceTrackType = referenceTrack.ValueTypeName;
+       }
 
-                       // Skip this track if it's non-numeric
-                       if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue;
+       copy( source ) {
 
-                       // Find the track in the target clip whose name and type matches the reference track
-                       const targetTrack = targetClip.tracks.find( function ( track ) {
+               super.copy( source );
 
-                               return track.name === referenceTrack.name
-                                       && track.ValueTypeName === referenceTrackType;
+               this.v0.copy( source.v0 );
+               this.v1.copy( source.v1 );
+               this.v2.copy( source.v2 );
 
-                       } );
+               return this;
 
-                       if ( targetTrack === undefined ) continue;
+       }
 
-                       let referenceOffset = 0;
-                       const referenceValueSize = referenceTrack.getValueSize();
+       toJSON() {
 
-                       if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
+               const data = super.toJSON();
 
-                               referenceOffset = referenceValueSize / 3;
+               data.v0 = this.v0.toArray();
+               data.v1 = this.v1.toArray();
+               data.v2 = this.v2.toArray();
 
-                       }
+               return data;
 
-                       let targetOffset = 0;
-                       const targetValueSize = targetTrack.getValueSize();
+       }
 
-                       if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
+       fromJSON( json ) {
 
-                               targetOffset = targetValueSize / 3;
+               super.fromJSON( json );
 
-                       }
+               this.v0.fromArray( json.v0 );
+               this.v1.fromArray( json.v1 );
+               this.v2.fromArray( json.v2 );
 
-                       const lastIndex = referenceTrack.times.length - 1;
-                       let referenceValue;
+               return this;
 
-                       // Find the value to subtract out of the track
-                       if ( referenceTime <= referenceTrack.times[ 0 ] ) {
+       }
 
-                               // Reference frame is earlier than the first keyframe, so just use the first keyframe
-                               const startIndex = referenceOffset;
-                               const endIndex = referenceValueSize - referenceOffset;
-                               referenceValue = AnimationUtils.arraySlice( referenceTrack.values, startIndex, endIndex );
+    }
 
-                       } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) {
+    QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true;
 
-                               // 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 );
+    class SplineCurve extends Curve {
 
-                       } else {
+       constructor( points = [] ) {
 
-                               // 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 );
+               super();
 
-                       }
+               this.type = 'SplineCurve';
 
-                       // Conjugate the quaternion
-                       if ( referenceTrackType === 'quaternion' ) {
+               this.points = points;
 
-                               const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate();
-                               referenceQuat.toArray( referenceValue );
+       }
 
-                       }
+       getPoint( t, optionalTarget = new Vector2() ) {
 
-                       // Subtract the reference value from all of the track values
+               const point = optionalTarget;
 
-                       const numTimes = targetTrack.times.length;
-                       for ( let j = 0; j < numTimes; ++ j ) {
+               const points = this.points;
+               const p = ( points.length - 1 ) * t;
 
-                               const valueStart = j * targetValueSize + targetOffset;
+               const intPoint = Math.floor( p );
+               const weight = p - intPoint;
 
-                               if ( referenceTrackType === 'quaternion' ) {
+               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 ];
 
-                                       // Multiply the conjugate for quaternion track types
-                                       Quaternion.multiplyQuaternionsFlat(
-                                               targetTrack.values,
-                                               valueStart,
-                                               referenceValue,
-                                               0,
-                                               targetTrack.values,
-                                               valueStart
-                                       );
+               point.set(
+                       CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ),
+                       CatmullRom( weight, p0.y, p1.y, p2.y, p3.y )
+               );
 
-                               } else {
+               return point;
 
-                                       const valueEnd = targetValueSize - targetOffset * 2;
+       }
 
-                                       // Subtract each value for all other numeric track types
-                                       for ( let k = 0; k < valueEnd; ++ k ) {
+       copy( source ) {
 
-                                               targetTrack.values[ valueStart + k ] -= referenceValue[ k ];
+               super.copy( source );
 
-                                       }
+               this.points = [];
 
-                               }
+               for ( let i = 0, l = source.points.length; i < l; i ++ ) {
 
-                       }
+                       const point = source.points[ i ];
 
-               }
+                       this.points.push( point.clone() );
 
-               targetClip.blendMode = AdditiveAnimationBlendMode;
+               }
 
-               return targetClip;
+               return this;
 
        }
 
-    };
+       toJSON() {
 
-    /**
-     * 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
-     *
-     */
+               const data = super.toJSON();
 
-    function Interpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+               data.points = [];
 
-       this.parameterPositions = parameterPositions;
-       this._cachedIndex = 0;
+               for ( let i = 0, l = this.points.length; i < l; i ++ ) {
 
-       this.resultBuffer = resultBuffer !== undefined ?
-               resultBuffer : new sampleValues.constructor( sampleSize );
-       this.sampleValues = sampleValues;
-       this.valueSize = sampleSize;
+                       const point = this.points[ i ];
+                       data.points.push( point.toArray() );
 
-    }
+               }
 
-    Object.assign( Interpolant.prototype, {
+               return data;
 
-       evaluate: function ( t ) {
+       }
 
-               const pp = this.parameterPositions;
-               let i1 = this._cachedIndex,
-                       t1 = pp[ i1 ],
-                       t0 = pp[ i1 - 1 ];
+       fromJSON( json ) {
 
-               validate_interval: {
+               super.fromJSON( json );
 
-                       seek: {
+               this.points = [];
 
-                               let right;
+               for ( let i = 0, l = json.points.length; i < l; i ++ ) {
 
-                               linear_scan: {
+                       const point = json.points[ i ];
+                       this.points.push( new Vector2().fromArray( point ) );
 
-                                       //- 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; ; ) {
+               return this;
 
-                                                       if ( t1 === undefined ) {
+       }
 
-                                                               if ( t < t0 ) break forward_scan;
+    }
 
-                                                               // after end
+    SplineCurve.prototype.isSplineCurve = true;
 
-                                                               i1 = pp.length;
-                                                               this._cachedIndex = i1;
-                                                               return this.afterEnd_( i1 - 1, t, t0 );
+    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
+     **************************************************************/
 
-                                                       if ( i1 === giveUpAt ) break; // this loop
+    class CurvePath extends Curve {
 
-                                                       t0 = t1;
-                                                       t1 = pp[ ++ i1 ];
+       constructor() {
 
-                                                       if ( t < t1 ) {
+               super();
 
-                                                               // we have arrived at the sought interval
-                                                               break seek;
+               this.type = 'CurvePath';
 
-                                                       }
+               this.curves = [];
+               this.autoClose = false; // Automatically closes the path
 
-                                               }
+       }
 
-                                               // prepare binary search on the right side of the index
-                                               right = pp.length;
-                                               break linear_scan;
+       add( curve ) {
 
-                                       }
+               this.curves.push( curve );
 
-                                       //- slower code:
-                                       //-                                     if ( t < t0 || t0 === undefined ) {
-                                       if ( ! ( t >= t0 ) ) {
+       }
 
-                                               // looping?
+       closePath() {
 
-                                               const t1global = pp[ 1 ];
+               // 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 ( t < t1global ) {
+               if ( ! startPoint.equals( endPoint ) ) {
 
-                                                       i1 = 2; // + 1, using the scan for the details
-                                                       t0 = t1global;
+                       this.curves.push( new LineCurve( endPoint, startPoint ) );
 
-                                               }
+               }
 
-                                               // linear reverse scan
+       }
 
-                                               for ( let giveUpAt = i1 - 2; ; ) {
+       // To get accurate point with reference to
+       // entire path distance at time t,
+       // following has to be done:
 
-                                                       if ( t0 === undefined ) {
+       // 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')
 
-                                                               // before start
+       getPoint( t, optionalTarget ) {
 
-                                                               this._cachedIndex = 0;
-                                                               return this.beforeStart_( 0, t, t1 );
+               const d = t * this.getLength();
+               const curveLengths = this.getCurveLengths();
+               let i = 0;
 
-                                                       }
+               // To think about boundaries points.
 
-                                                       if ( i1 === giveUpAt ) break; // this loop
+               while ( i < curveLengths.length ) {
 
-                                                       t1 = t0;
-                                                       t0 = pp[ -- i1 - 1 ];
+                       if ( curveLengths[ i ] >= d ) {
 
-                                                       if ( t >= t0 ) {
+                               const diff = curveLengths[ i ] - d;
+                               const curve = this.curves[ i ];
 
-                                                               // we have arrived at the sought interval
-                                                               break seek;
+                               const segmentLength = curve.getLength();
+                               const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;
 
-                                                       }
+                               return curve.getPointAt( u, optionalTarget );
 
-                                               }
+                       }
 
-                                               // prepare binary search on the left side of the index
-                                               right = i1;
-                                               i1 = 0;
-                                               break linear_scan;
+                       i ++;
 
-                                       }
+               }
 
-                                       // the interval is valid
+               return null;
 
-                                       break validate_interval;
+               // loop where sum != 0, sum > d , sum+1 <d
 
-                               } // linear scan
+       }
 
-                               // binary search
+       // 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
 
-                               while ( i1 < right ) {
+       getLength() {
 
-                                       const mid = ( i1 + right ) >>> 1;
+               const lens = this.getCurveLengths();
+               return lens[ lens.length - 1 ];
 
-                                       if ( t < pp[ mid ] ) {
+       }
 
-                                               right = mid;
+       // cacheLengths must be recalculated.
+       updateArcLengths() {
 
-                                       } else {
+               this.needsUpdate = true;
+               this.cacheLengths = null;
+               this.getCurveLengths();
 
-                                               i1 = mid + 1;
+       }
 
-                                       }
+       // Compute lengths and cache them
+       // We cannot overwrite getLengths() because UtoT mapping uses it.
 
-                               }
+       getCurveLengths() {
 
-                               t1 = pp[ i1 ];
-                               t0 = pp[ i1 - 1 ];
+               // We use cache values if curves and cache array are same length
 
-                               // check boundary cases, again
+               if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) {
 
-                               if ( t0 === undefined ) {
+                       return this.cacheLengths;
 
-                                       this._cachedIndex = 0;
-                                       return this.beforeStart_( 0, t, t1 );
+               }
 
-                               }
+               // Get length of sub-curve
+               // Push sums into cached array
 
-                               if ( t1 === undefined ) {
+               const lengths = [];
+               let sums = 0;
 
-                                       i1 = pp.length;
-                                       this._cachedIndex = i1;
-                                       return this.afterEnd_( i1 - 1, t0, t );
+               for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
 
-                               }
+                       sums += this.curves[ i ].getLength();
+                       lengths.push( sums );
 
-                       } // seek
+               }
 
-                       this._cachedIndex = i1;
+               this.cacheLengths = lengths;
 
-                       this.intervalChanged_( i1, t0, t1 );
+               return lengths;
 
-               } // validate_interval
+       }
 
-               return this.interpolate_( i1, t0, t, t1 );
+       getSpacedPoints( divisions = 40 ) {
 
-       },
+               const points = [];
 
-       settings: null, // optional, subclass-specific settings structure
-       // Note: The indirection allows central control of many interpolants.
+               for ( let i = 0; i <= divisions; i ++ ) {
 
-       // --- Protected interface
+                       points.push( this.getPoint( i / divisions ) );
 
-       DefaultSettings_: {},
+               }
 
-       getSettings_: function () {
+               if ( this.autoClose ) {
 
-               return this.settings || this.DefaultSettings_;
+                       points.push( points[ 0 ] );
 
-       },
+               }
 
-       copySampleValue_: function ( index ) {
+               return points;
 
-               // copies a sample value to the result buffer
+       }
 
-               const result = this.resultBuffer,
-                       values = this.sampleValues,
-                       stride = this.valueSize,
-                       offset = index * stride;
+       getPoints( divisions = 12 ) {
 
-               for ( let i = 0; i !== stride; ++ i ) {
+               const points = [];
+               let last;
 
-                       result[ i ] = values[ offset + i ];
+               for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) {
 
-               }
+                       const curve = curves[ i ];
+                       const resolution = ( curve && curve.isEllipseCurve ) ? divisions * 2
+                               : ( curve && ( curve.isLineCurve || curve.isLineCurve3 ) ) ? 1
+                                       : ( curve && curve.isSplineCurve ) ? divisions * curve.points.length
+                                               : divisions;
 
-               return result;
+                       const pts = curve.getPoints( resolution );
 
-       },
+                       for ( let j = 0; j < pts.length; j ++ ) {
 
-       // Template methods for derived classes:
+                               const point = pts[ j ];
 
-       interpolate_: function ( /* i1, t0, t, t1 */ ) {
+                               if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates
 
-               throw new Error( 'call to abstract method' );
-               // implementations shall return this.resultBuffer
+                               points.push( point );
+                               last = point;
 
-       },
+                       }
 
-       intervalChanged_: function ( /* i1, t0, t1 */ ) {
+               }
 
-               // empty
+               if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) {
 
-       }
+                       points.push( points[ 0 ] );
 
-    } );
+               }
 
-    // DECLARE ALIAS AFTER assign prototype
-    Object.assign( Interpolant.prototype, {
+               return points;
 
-       //( 0, t, t0 ), returns this.resultBuffer
-       beforeStart_: Interpolant.prototype.copySampleValue_,
+       }
 
-       //( N-1, tN-1, t ), returns this.resultBuffer
-       afterEnd_: Interpolant.prototype.copySampleValue_,
+       copy( source ) {
 
-    } );
+               super.copy( source );
 
-    /**
-     * 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.
-     */
+               this.curves = [];
 
-    function CubicInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+               for ( let i = 0, l = source.curves.length; i < l; i ++ ) {
 
-       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+                       const curve = source.curves[ i ];
 
-       this._weightPrev = - 0;
-       this._offsetPrev = - 0;
-       this._weightNext = - 0;
-       this._offsetNext = - 0;
+                       this.curves.push( curve.clone() );
 
-    }
+               }
 
-    CubicInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+               this.autoClose = source.autoClose;
 
-       constructor: CubicInterpolant,
+               return this;
 
-       DefaultSettings_: {
+       }
 
-               endingStart: ZeroCurvatureEnding,
-               endingEnd: ZeroCurvatureEnding
+       toJSON() {
 
-       },
+               const data = super.toJSON();
 
-       intervalChanged_: function ( i1, t0, t1 ) {
+               data.autoClose = this.autoClose;
+               data.curves = [];
 
-               const pp = this.parameterPositions;
-               let iPrev = i1 - 2,
-                       iNext = i1 + 1,
+               for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
 
-                       tPrev = pp[ iPrev ],
-                       tNext = pp[ iNext ];
+                       const curve = this.curves[ i ];
+                       data.curves.push( curve.toJSON() );
 
-               if ( tPrev === undefined ) {
+               }
 
-                       switch ( this.getSettings_().endingStart ) {
+               return data;
 
-                               case ZeroSlopeEnding:
+       }
 
-                                       // f'(t0) = 0
-                                       iPrev = i1;
-                                       tPrev = 2 * t0 - t1;
+       fromJSON( json ) {
 
-                                       break;
-
-                               case WrapAroundEnding:
-
-                                       // use the other end of the curve
-                                       iPrev = pp.length - 2;
-                                       tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ];
-
-                                       break;
+               super.fromJSON( json );
 
-                               default: // ZeroCurvatureEnding
+               this.autoClose = json.autoClose;
+               this.curves = [];
 
-                                       // f''(t0) = 0 a.k.a. Natural Spline
-                                       iPrev = i1;
-                                       tPrev = t1;
+               for ( let i = 0, l = json.curves.length; i < l; i ++ ) {
 
-                       }
+                       const curve = json.curves[ i ];
+                       this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) );
 
                }
 
-               if ( tNext === undefined ) {
-
-                       switch ( this.getSettings_().endingEnd ) {
-
-                               case ZeroSlopeEnding:
+               return this;
 
-                                       // f'(tN) = 0
-                                       iNext = i1;
-                                       tNext = 2 * t1 - t0;
+       }
 
-                                       break;
+    }
 
-                               case WrapAroundEnding:
+    class Path extends CurvePath {
 
-                                       // use the other end of the curve
-                                       iNext = 1;
-                                       tNext = t1 + pp[ 1 ] - pp[ 0 ];
+       constructor( points ) {
 
-                                       break;
+               super();
+               this.type = 'Path';
 
-                               default: // ZeroCurvatureEnding
+               this.currentPoint = new Vector2();
 
-                                       // f''(tN) = 0, a.k.a. Natural Spline
-                                       iNext = i1 - 1;
-                                       tNext = t0;
+               if ( points ) {
 
-                       }
+                       this.setFromPoints( points );
 
                }
 
-               const halfDt = ( t1 - t0 ) * 0.5,
-                       stride = this.valueSize;
-
-               this._weightPrev = halfDt / ( t0 - tPrev );
-               this._weightNext = halfDt / ( tNext - t1 );
-               this._offsetPrev = iPrev * stride;
-               this._offsetNext = iNext * stride;
-
-       },
-
-       interpolate_: function ( i1, t0, t, t1 ) {
-
-               const result = this.resultBuffer,
-                       values = this.sampleValues,
-                       stride = this.valueSize,
-
-                       o1 = i1 * stride,               o0 = o1 - stride,
-                       oP = this._offsetPrev,  oN = this._offsetNext,
-                       wP = this._weightPrev,  wN = this._weightNext,
-
-                       p = ( t - t0 ) / ( t1 - t0 ),
-                       pp = p * p,
-                       ppp = pp * p;
-
-               // evaluate polynomials
+       }
 
-               const sP = - wP * ppp + 2 * wP * pp - wP * p;
-               const s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1;
-               const s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p;
-               const sN = wN * ppp - wN * pp;
+       setFromPoints( points ) {
 
-               // combine data linearly
+               this.moveTo( points[ 0 ].x, points[ 0 ].y );
 
-               for ( let i = 0; i !== stride; ++ i ) {
+               for ( let i = 1, l = points.length; i < l; i ++ ) {
 
-                       result[ i ] =
-                                       sP * values[ oP + i ] +
-                                       s0 * values[ o0 + i ] +
-                                       s1 * values[ o1 + i ] +
-                                       sN * values[ oN + i ];
+                       this.lineTo( points[ i ].x, points[ i ].y );
 
                }
 
-               return result;
+               return this;
 
        }
 
-    } );
+       moveTo( x, y ) {
 
-    function LinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+               this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying?
 
-       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+               return this;
 
-    }
+       }
 
-    LinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+       lineTo( x, y ) {
 
-       constructor: LinearInterpolant,
+               const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) );
+               this.curves.push( curve );
 
-       interpolate_: function ( i1, t0, t, t1 ) {
+               this.currentPoint.set( x, y );
 
-               const result = this.resultBuffer,
-                       values = this.sampleValues,
-                       stride = this.valueSize,
+               return this;
 
-                       offset1 = i1 * stride,
-                       offset0 = offset1 - stride,
+       }
 
-                       weight1 = ( t - t0 ) / ( t1 - t0 ),
-                       weight0 = 1 - weight1;
+       quadraticCurveTo( aCPx, aCPy, aX, aY ) {
 
-               for ( let i = 0; i !== stride; ++ i ) {
+               const curve = new QuadraticBezierCurve(
+                       this.currentPoint.clone(),
+                       new Vector2( aCPx, aCPy ),
+                       new Vector2( aX, aY )
+               );
 
-                       result[ i ] =
-                                       values[ offset0 + i ] * weight0 +
-                                       values[ offset1 + i ] * weight1;
+               this.curves.push( curve );
 
-               }
+               this.currentPoint.set( aX, aY );
 
-               return result;
+               return this;
 
        }
 
-    } );
+       bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
 
-    /**
-     *
-     * Interpolant that evaluates to the sample value at the position preceeding
-     * the parameter.
-     */
+               const curve = new CubicBezierCurve(
+                       this.currentPoint.clone(),
+                       new Vector2( aCP1x, aCP1y ),
+                       new Vector2( aCP2x, aCP2y ),
+                       new Vector2( aX, aY )
+               );
 
-    function DiscreteInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+               this.curves.push( curve );
 
-       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+               this.currentPoint.set( aX, aY );
 
-    }
+               return this;
 
-    DiscreteInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+       }
 
-       constructor: DiscreteInterpolant,
+       splineThru( pts /*Array of Vector*/ ) {
 
-       interpolate_: function ( i1 /*, t0, t, t1 */ ) {
+               const npts = [ this.currentPoint.clone() ].concat( pts );
 
-               return this.copySampleValue_( i1 - 1 );
+               const curve = new SplineCurve( npts );
+               this.curves.push( curve );
 
-       }
+               this.currentPoint.copy( pts[ pts.length - 1 ] );
 
-    } );
+               return this;
 
-    function KeyframeTrack( name, times, values, interpolation ) {
+       }
 
-       if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' );
-       if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name );
+       arc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
 
-       this.name = name;
+               const x0 = this.currentPoint.x;
+               const y0 = this.currentPoint.y;
 
-       this.times = AnimationUtils.convertArray( times, this.TimeBufferType );
-       this.values = AnimationUtils.convertArray( values, this.ValueBufferType );
+               this.absarc( aX + x0, aY + y0, aRadius,
+                       aStartAngle, aEndAngle, aClockwise );
 
-       this.setInterpolation( interpolation || this.DefaultInterpolation );
+               return this;
 
-    }
+       }
 
-    // Static methods
+       absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
 
-    Object.assign( KeyframeTrack, {
+               this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
 
-       // Serialization (in static context, because of constructor invocation
-       // and automatic invocation of .toJSON):
+               return this;
 
-       toJSON: function ( track ) {
+       }
 
-               const trackType = track.constructor;
+       ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
 
-               let json;
+               const x0 = this.currentPoint.x;
+               const y0 = this.currentPoint.y;
 
-               // derived classes can define a static toJSON method
-               if ( trackType.toJSON !== undefined ) {
+               this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
 
-                       json = trackType.toJSON( track );
+               return this;
 
-               } else {
+       }
 
-                       // by default, we assume the data can be serialized as-is
-                       json = {
+       absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
 
-                               'name': track.name,
-                               'times': AnimationUtils.convertArray( track.times, Array ),
-                               'values': AnimationUtils.convertArray( track.values, Array )
+               const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
 
-                       };
+               if ( this.curves.length > 0 ) {
 
-                       const interpolation = track.getInterpolation();
+                       // if a previous curve is present, attempt to join
+                       const firstPoint = curve.getPoint( 0 );
 
-                       if ( interpolation !== track.DefaultInterpolation ) {
+                       if ( ! firstPoint.equals( this.currentPoint ) ) {
 
-                               json.interpolation = interpolation;
+                               this.lineTo( firstPoint.x, firstPoint.y );
 
                        }
 
                }
 
-               json.type = track.ValueTypeName; // mandatory
+               this.curves.push( curve );
 
-               return json;
+               const lastPoint = curve.getPoint( 1 );
+               this.currentPoint.copy( lastPoint );
+
+               return this;
 
        }
 
-    } );
+       copy( source ) {
 
-    Object.assign( KeyframeTrack.prototype, {
+               super.copy( source );
 
-       constructor: KeyframeTrack,
+               this.currentPoint.copy( source.currentPoint );
 
-       TimeBufferType: Float32Array,
+               return this;
 
-       ValueBufferType: Float32Array,
+       }
 
-       DefaultInterpolation: InterpolateLinear,
+       toJSON() {
 
-       InterpolantFactoryMethodDiscrete: function ( result ) {
+               const data = super.toJSON();
 
-               return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );
+               data.currentPoint = this.currentPoint.toArray();
 
-       },
+               return data;
 
-       InterpolantFactoryMethodLinear: function ( result ) {
+       }
 
-               return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );
+       fromJSON( json ) {
 
-       },
+               super.fromJSON( json );
 
-       InterpolantFactoryMethodSmooth: function ( result ) {
+               this.currentPoint.fromArray( json.currentPoint );
 
-               return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );
+               return this;
 
-       },
+       }
 
-       setInterpolation: function ( interpolation ) {
+    }
 
-               let factoryMethod;
+    class Shape extends Path {
 
-               switch ( interpolation ) {
+       constructor( points ) {
 
-                       case InterpolateDiscrete:
+               super( points );
 
-                               factoryMethod = this.InterpolantFactoryMethodDiscrete;
+               this.uuid = generateUUID();
 
-                               break;
+               this.type = 'Shape';
 
-                       case InterpolateLinear:
+               this.holes = [];
 
-                               factoryMethod = this.InterpolantFactoryMethodLinear;
+       }
 
-                               break;
+       getPointsHoles( divisions ) {
 
-                       case InterpolateSmooth:
+               const holesPts = [];
 
-                               factoryMethod = this.InterpolantFactoryMethodSmooth;
+               for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
 
-                               break;
+                       holesPts[ i ] = this.holes[ i ].getPoints( divisions );
 
                }
 
-               if ( factoryMethod === undefined ) {
+               return holesPts;
 
-                       const message = 'unsupported interpolation for ' +
-                               this.ValueTypeName + ' keyframe track named ' + this.name;
+       }
 
-                       if ( this.createInterpolant === undefined ) {
+       // get points of shape and holes (keypoints based on segments parameter)
 
-                               // fall back to default, unless the default itself is messed up
-                               if ( interpolation !== this.DefaultInterpolation ) {
+       extractPoints( divisions ) {
 
-                                       this.setInterpolation( this.DefaultInterpolation );
+               return {
 
-                               } else {
+                       shape: this.getPoints( divisions ),
+                       holes: this.getPointsHoles( divisions )
 
-                                       throw new Error( message ); // fatal, in this case
+               };
 
-                               }
+       }
 
-                       }
+       copy( source ) {
 
-                       console.warn( 'THREE.KeyframeTrack:', message );
-                       return this;
+               super.copy( source );
 
-               }
+               this.holes = [];
 
-               this.createInterpolant = factoryMethod;
+               for ( let i = 0, l = source.holes.length; i < l; i ++ ) {
 
-               return this;
+                       const hole = source.holes[ i ];
 
-       },
+                       this.holes.push( hole.clone() );
 
-       getInterpolation: function () {
+               }
 
-               switch ( this.createInterpolant ) {
+               return this;
 
-                       case this.InterpolantFactoryMethodDiscrete:
+       }
 
-                               return InterpolateDiscrete;
+       toJSON() {
 
-                       case this.InterpolantFactoryMethodLinear:
+               const data = super.toJSON();
 
-                               return InterpolateLinear;
+               data.uuid = this.uuid;
+               data.holes = [];
 
-                       case this.InterpolantFactoryMethodSmooth:
+               for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
 
-                               return InterpolateSmooth;
+                       const hole = this.holes[ i ];
+                       data.holes.push( hole.toJSON() );
 
                }
 
-       },
-
-       getValueSize: function () {
-
-               return this.values.length / this.times.length;
-
-       },
+               return data;
 
-       // move all keyframes either forwards or backwards in time
-       shift: function ( timeOffset ) {
+       }
 
-               if ( timeOffset !== 0.0 ) {
+       fromJSON( json ) {
 
-                       const times = this.times;
+               super.fromJSON( json );
 
-                       for ( let i = 0, n = times.length; i !== n; ++ i ) {
+               this.uuid = json.uuid;
+               this.holes = [];
 
-                               times[ i ] += timeOffset;
+               for ( let i = 0, l = json.holes.length; i < l; i ++ ) {
 
-                       }
+                       const hole = json.holes[ i ];
+                       this.holes.push( new Path().fromJSON( hole ) );
 
                }
 
                return this;
 
-       },
+       }
 
-       // scale all keyframe times by a factor (useful for frame <-> seconds conversions)
-       scale: function ( timeScale ) {
+    }
 
-               if ( timeScale !== 1.0 ) {
+    /**
+     * Port from https://github.com/mapbox/earcut (v2.2.2)
+     */
 
-                       const times = this.times;
+    const Earcut = {
 
-                       for ( let i = 0, n = times.length; i !== n; ++ i ) {
+       triangulate: function ( data, holeIndices, dim = 2 ) {
 
-                               times[ i ] *= timeScale;
+               const hasHoles = holeIndices && holeIndices.length;
+               const outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length;
+               let outerNode = linkedList$1( data, 0, outerLen, dim, true );
+               const triangles = [];
 
-                       }
+               if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles;
 
-               }
+               let minX, minY, maxX, maxY, x, y, invSize;
 
-               return this;
+               if ( hasHoles ) outerNode = eliminateHoles$1( data, holeIndices, outerNode, dim );
 
-       },
+               // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
+               if ( data.length > 80 * dim ) {
 
-       // 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 ) {
+                       minX = maxX = data[ 0 ];
+                       minY = maxY = data[ 1 ];
 
-               const times = this.times,
-                       nKeys = times.length;
+                       for ( let i = dim; i < outerLen; i += dim ) {
 
-               let from = 0,
-                       to = nKeys - 1;
+                               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;
 
-               while ( from !== nKeys && times[ from ] < startTime ) {
+                       }
 
-                       ++ from;
+                       // 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;
 
                }
 
-               while ( to !== - 1 && times[ to ] > endTime ) {
+               earcutLinked$1( outerNode, triangles, dim, minX, minY, invSize );
 
-                       -- to;
+               return triangles;
 
-               }
+       }
 
-               ++ to; // inclusive -> exclusive bound
+    };
 
-               if ( from !== 0 || to !== nKeys ) {
+    // create a circular doubly linked list from polygon points in the specified winding order
+    function linkedList$1( data, start, end, dim, clockwise ) {
 
-                       // empty tracks are forbidden, so keep at least one keyframe
-                       if ( from >= to ) {
+       let i, last;
 
-                               to = Math.max( to, 1 );
-                               from = to - 1;
+       if ( clockwise === ( signedArea$2( data, start, end, dim ) > 0 ) ) {
 
-                       }
+               for ( i = start; i < end; i += dim ) last = insertNode$2( i, data[ i ], data[ i + 1 ], last );
 
-                       const stride = this.getValueSize();
-                       this.times = AnimationUtils.arraySlice( times, from, to );
-                       this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride );
+       } 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 ) ) {
 
-       // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
-       validate: function () {
+               removeNode$2( last );
+               last = last.next;
 
-               let valid = true;
+       }
 
-               const valueSize = this.getValueSize();
-               if ( valueSize - Math.floor( valueSize ) !== 0 ) {
+       return last;
 
-                       console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
-                       valid = false;
+    }
 
-               }
+    // eliminate colinear or duplicate points
+    function filterPoints$1( start, end ) {
 
-               const times = this.times,
-                       values = this.values,
+       if ( ! start ) return start;
+       if ( ! end ) end = start;
 
-                       nKeys = times.length;
+       let p = start,
+               again;
+       do {
 
-               if ( nKeys === 0 ) {
+               again = false;
 
-                       console.error( 'THREE.KeyframeTrack: Track is empty.', this );
-                       valid = false;
+               if ( ! p.steiner && ( equals$2( p, p.next ) || area$1( p.prev, p, p.next ) === 0 ) ) {
 
-               }
+                       removeNode$2( p );
+                       p = end = p.prev;
+                       if ( p === p.next ) break;
+                       again = true;
 
-               let prevTime = null;
+               } else {
 
-               for ( let i = 0; i !== nKeys; i ++ ) {
+                       p = p.next;
 
-                       const currTime = times[ i ];
+               }
 
-                       if ( typeof currTime === 'number' && isNaN( currTime ) ) {
+       } while ( again || p !== end );
 
-                               console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime );
-                               valid = false;
-                               break;
+       return end;
 
-                       }
+    }
 
-                       if ( prevTime !== null && prevTime > currTime ) {
+    // main ear slicing loop which triangulates a polygon (given as a linked list)
+    function earcutLinked$1( ear, triangles, dim, minX, minY, invSize, pass ) {
 
-                               console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime );
-                               valid = false;
-                               break;
+       if ( ! ear ) return;
 
-                       }
+       // interlink polygon nodes in z-order
+       if ( ! pass && invSize ) indexCurve$1( ear, minX, minY, invSize );
 
-                       prevTime = currTime;
+       let stop = ear,
+               prev, next;
 
-               }
+       // iterate through ears, slicing them one by one
+       while ( ear.prev !== ear.next ) {
 
-               if ( values !== undefined ) {
+               prev = ear.prev;
+               next = ear.next;
 
-                       if ( AnimationUtils.isTypedArray( values ) ) {
+               if ( invSize ? isEarHashed$1( ear, minX, minY, invSize ) : isEar$1( ear ) ) {
 
-                               for ( let i = 0, n = values.length; i !== n; ++ i ) {
+                       // cut off the triangle
+                       triangles.push( prev.i / dim );
+                       triangles.push( ear.i / dim );
+                       triangles.push( next.i / dim );
 
-                                       const value = values[ i ];
+                       removeNode$2( ear );
 
-                                       if ( isNaN( value ) ) {
+                       // skipping the next vertex leads to less sliver triangles
+                       ear = next.next;
+                       stop = next.next;
 
-                                               console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
-                                               valid = false;
-                                               break;
+                       continue;
 
-                                       }
+               }
 
-                               }
+               ear = next;
 
-                       }
+               // if we looped through the whole remaining polygon and can't find any more ears
+               if ( ear === stop ) {
 
-               }
+                       // try filtering points and slicing again
+                       if ( ! pass ) {
 
-               return valid;
+                               earcutLinked$1( filterPoints$1( ear ), triangles, dim, minX, minY, invSize, 1 );
 
-       },
+                               // if this didn't work, try curing all small self-intersections locally
 
-       // 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 () {
+                       } else if ( pass === 1 ) {
 
-               // 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(),
+                               ear = cureLocalIntersections$1( filterPoints$1( ear ), triangles, dim );
+                               earcutLinked$1( ear, triangles, dim, minX, minY, invSize, 2 );
 
-                       smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
+                               // as a last resort, try splitting the remaining polygon into two
 
-                       lastIndex = times.length - 1;
+                       } else if ( pass === 2 ) {
 
-               let writeIndex = 1;
+                               splitEarcut$1( ear, triangles, dim, minX, minY, invSize );
 
-               for ( let i = 1; i < lastIndex; ++ i ) {
+                       }
 
-                       let keep = false;
+                       break;
 
-                       const time = times[ i ];
-                       const timeNext = times[ i + 1 ];
+               }
 
-                       // remove adjacent keyframes scheduled at the same time
+       }
 
-                       if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) {
+    }
 
-                               if ( ! smoothInterpolation ) {
+    // check whether a polygon node forms a valid ear with adjacent nodes
+    function isEar$1( ear ) {
 
-                                       // remove unnecessary keyframes same as their neighbors
+       const a = ear.prev,
+               b = ear,
+               c = ear.next;
 
-                                       const offset = i * stride,
-                                               offsetP = offset - stride,
-                                               offsetN = offset + stride;
+       if ( area$1( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
 
-                                       for ( let j = 0; j !== stride; ++ j ) {
+       // now make sure we don't have other points inside the potential ear
+       let p = ear.next.next;
 
-                                               const value = values[ offset + j ];
+       while ( p !== ear.prev ) {
 
-                                               if ( value !== values[ offsetP + j ] ||
-                                                       value !== values[ offsetN + j ] ) {
+               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;
 
-                                                       keep = true;
-                                                       break;
+       }
 
-                                               }
+       return true;
 
-                                       }
+    }
 
-                               } else {
+    function isEarHashed$1( ear, minX, minY, invSize ) {
 
-                                       keep = true;
+       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 );
 
-                       // in-place compaction
+       // 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 );
 
-                       if ( keep ) {
+       let p = ear.prevZ,
+               n = ear.nextZ;
 
-                               if ( i !== writeIndex ) {
+       // look for points inside the triangle in both directions
+       while ( p && p.z >= minZ && n && n.z <= maxZ ) {
 
-                                       times[ writeIndex ] = times[ i ];
+               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;
 
-                                       const readOffset = i * stride,
-                                               writeOffset = writeIndex * stride;
+               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;
 
-                                       for ( let j = 0; j !== stride; ++ j ) {
+       }
 
-                                               values[ writeOffset + j ] = values[ readOffset + j ];
+       // look for remaining points in decreasing z-order
+       while ( p && p.z >= minZ ) {
 
-                                       }
+               if ( p !== ear.prev && p !== ear.next &&
+                       pointInTriangle$1( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
+                       area$1( p.prev, p, p.next ) >= 0 ) return false;
+               p = p.prevZ;
 
-                               }
+       }
 
-                               ++ writeIndex;
+       // 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;
 
-               }
+       }
 
-               // flush last keyframe (compaction looks ahead)
+       return true;
 
-               if ( lastIndex > 0 ) {
+    }
 
-                       times[ writeIndex ] = times[ lastIndex ];
+    // go through all polygon nodes and cure small local self-intersections
+    function cureLocalIntersections$1( start, triangles, dim ) {
 
-                       for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
+       let p = start;
+       do {
 
-                               values[ writeOffset + j ] = values[ readOffset + j ];
+               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 ) ) {
 
-                       ++ writeIndex;
+                       triangles.push( a.i / dim );
+                       triangles.push( p.i / dim );
+                       triangles.push( b.i / dim );
 
-               }
+                       // remove two nodes involved
+                       removeNode$2( p );
+                       removeNode$2( p.next );
 
-               if ( writeIndex !== times.length ) {
+                       p = start = b;
 
-                       this.times = AnimationUtils.arraySlice( times, 0, writeIndex );
-                       this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride );
+               }
 
-               } else {
+               p = p.next;
 
-                       this.times = times;
-                       this.values = values;
+       } while ( p !== start );
 
-               }
+       return filterPoints$1( p );
 
-               return this;
+    }
 
-       },
+    // 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 {
 
-       clone: function () {
+               let b = a.next.next;
+               while ( b !== a.prev ) {
 
-               const times = AnimationUtils.arraySlice( this.times, 0 );
-               const values = AnimationUtils.arraySlice( this.values, 0 );
+                       if ( a.i !== b.i && isValidDiagonal$1( a, b ) ) {
 
-               const TypedKeyframeTrack = this.constructor;
-               const track = new TypedKeyframeTrack( this.name, times, values );
+                               // split the polygon in two by the diagonal
+                               let c = splitPolygon$1( a, b );
 
-               // Interpolant argument to constructor is not saved, so copy the factory method directly.
-               track.createInterpolant = this.createInterpolant;
+                               // filter colinear points around the cuts
+                               a = filterPoints$1( a, a.next );
+                               c = filterPoints$1( c, c.next );
 
-               return track;
+                               // run earcut on each half
+                               earcutLinked$1( a, triangles, dim, minX, minY, invSize );
+                               earcutLinked$1( c, triangles, dim, minX, minY, invSize );
+                               return;
 
-       }
+                       }
 
-    } );
+                       b = b.next;
 
-    /**
-     * A Track of Boolean keyframe values.
-     */
+               }
 
-    function BooleanKeyframeTrack( name, times, values ) {
+               a = a.next;
 
-       KeyframeTrack.call( this, name, times, values );
+       } while ( a !== start );
 
     }
 
-    BooleanKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+    // link every hole into the outer loop, producing a single-ring polygon without holes
+    function eliminateHoles$1( data, holeIndices, outerNode, dim ) {
 
-       constructor: BooleanKeyframeTrack,
+       const queue = [];
+       let i, len, start, end, list;
 
-       ValueTypeName: 'bool',
-       ValueBufferType: Array,
+       for ( i = 0, len = holeIndices.length; i < len; i ++ ) {
 
-       DefaultInterpolation: InterpolateDiscrete,
+               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 ) );
 
-       InterpolantFactoryMethodLinear: undefined,
-       InterpolantFactoryMethodSmooth: undefined
+       }
 
-       // Note: Actually this track could have a optimized / compressed
-       // representation of a single value and a custom interpolant that
-       // computes "firstValue ^ isOdd( index )".
+       queue.sort( compareX$1 );
 
-    } );
+       // process holes from left to right
+       for ( i = 0; i < queue.length; i ++ ) {
 
-    /**
-     * A Track of keyframe values that represent color.
-     */
+               eliminateHole$1( queue[ i ], outerNode );
+               outerNode = filterPoints$1( outerNode, outerNode.next );
 
-    function ColorKeyframeTrack( name, times, values, interpolation ) {
+       }
 
-       KeyframeTrack.call( this, name, times, values, interpolation );
+       return outerNode;
 
     }
 
-    ColorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
-
-       constructor: ColorKeyframeTrack,
-
-       ValueTypeName: 'color'
+    function compareX$1( a, b ) {
 
-       // ValueBufferType is inherited
+       return a.x - b.x;
 
-       // DefaultInterpolation is inherited
+    }
 
-       // Note: Very basic implementation and nothing special yet.
-       // However, this is the place for color space parameterization.
+    // find a bridge between vertices that connects hole with an outer ring and and link it
+    function eliminateHole$1( hole, outerNode ) {
 
-    } );
+       outerNode = findHoleBridge$1( hole, outerNode );
+       if ( outerNode ) {
 
-    /**
-     * A Track of numeric keyframe values.
-     */
+               const b = splitPolygon$1( outerNode, hole );
 
-    function NumberKeyframeTrack( name, times, values, interpolation ) {
+               // filter collinear points around the cuts
+               filterPoints$1( outerNode, outerNode.next );
+               filterPoints$1( b, b.next );
 
-       KeyframeTrack.call( this, name, times, values, interpolation );
+       }
 
     }
 
-    NumberKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
-
-       constructor: NumberKeyframeTrack,
+    // David Eberly's algorithm for finding a bridge between hole and outer polygon
+    function findHoleBridge$1( hole, outerNode ) {
 
-       ValueTypeName: 'number'
+       let p = outerNode;
+       const hx = hole.x;
+       const hy = hole.y;
+       let qx = - Infinity, m;
 
-       // ValueBufferType is inherited
+       // 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 {
 
-       // DefaultInterpolation is inherited
+               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 ) {
 
-    /**
-     * Spherical linear unit quaternion interpolant.
-     */
+                               qx = x;
+                               if ( x === hx ) {
 
-    function QuaternionLinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+                                       if ( hy === p.y ) return p;
+                                       if ( hy === p.next.y ) return p.next;
 
-       Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+                               }
 
-    }
+                               m = p.x < p.next.x ? p : p.next;
 
-    QuaternionLinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+                       }
 
-       constructor: QuaternionLinearInterpolant,
+               }
 
-       interpolate_: function ( i1, t0, t, t1 ) {
+               p = p.next;
 
-               const result = this.resultBuffer,
-                       values = this.sampleValues,
-                       stride = this.valueSize,
+       } while ( p !== outerNode );
 
-                       alpha = ( t - t0 ) / ( t1 - t0 );
+       if ( ! m ) return null;
 
-               let offset = i1 * stride;
+       if ( hx === qx ) return m; // hole touches outer segment; pick leftmost endpoint
 
-               for ( let end = offset + stride; offset !== end; offset += 4 ) {
+       // 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
 
-                       Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha );
+       const stop = m,
+               mx = m.x,
+               my = m.y;
+       let tanMin = Infinity, tan;
 
-               }
+       p = m;
 
-               return result;
+       do {
 
-       }
+               if ( hx >= p.x && p.x >= mx && hx !== p.x &&
+                               pointInTriangle$1( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {
 
-    } );
+                       tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential
 
-    /**
-     * A Track of quaternion keyframe values.
-     */
+                       if ( locallyInside$1( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector$1( m, p ) ) ) ) ) ) {
 
-    function QuaternionKeyframeTrack( name, times, values, interpolation ) {
+                               m = p;
+                               tanMin = tan;
 
-       KeyframeTrack.call( this, name, times, values, interpolation );
+                       }
 
-    }
+               }
 
-    QuaternionKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+               p = p.next;
 
-       constructor: QuaternionKeyframeTrack,
+       } while ( p !== stop );
 
-       ValueTypeName: 'quaternion',
+       return m;
 
-       // ValueBufferType is inherited
+    }
 
-       DefaultInterpolation: InterpolateLinear,
+    // whether sector in vertex m contains sector in vertex p in the same coordinates
+    function sectorContainsSector$1( m, p ) {
 
-       InterpolantFactoryMethodLinear: function ( result ) {
+       return area$1( m.prev, m, p.prev ) < 0 && area$1( p.next, m, m.next ) < 0;
 
-               return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result );
+    }
 
-       },
+    // interlink polygon nodes in z-order
+    function indexCurve$1( start, minX, minY, invSize ) {
 
-       InterpolantFactoryMethodSmooth: undefined // not yet implemented
+       let p = start;
+       do {
 
-    } );
+               if ( p.z === null ) p.z = zOrder$1( p.x, p.y, minX, minY, invSize );
+               p.prevZ = p.prev;
+               p.nextZ = p.next;
+               p = p.next;
 
-    /**
-     * A Track that interpolates Strings
-     */
+       } while ( p !== start );
 
-    function StringKeyframeTrack( name, times, values, interpolation ) {
+       p.prevZ.nextZ = null;
+       p.prevZ = null;
 
-       KeyframeTrack.call( this, name, times, values, interpolation );
+       sortLinked$1( p );
 
     }
 
-    StringKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+    // Simon Tatham's linked list merge sort algorithm
+    // http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
+    function sortLinked$1( list ) {
 
-       constructor: StringKeyframeTrack,
+       let i, p, q, e, tail, numMerges, pSize, qSize,
+               inSize = 1;
 
-       ValueTypeName: 'string',
-       ValueBufferType: Array,
+       do {
 
-       DefaultInterpolation: InterpolateDiscrete,
+               p = list;
+               list = null;
+               tail = null;
+               numMerges = 0;
 
-       InterpolantFactoryMethodLinear: undefined,
+               while ( p ) {
 
-       InterpolantFactoryMethodSmooth: undefined
+                       numMerges ++;
+                       q = p;
+                       pSize = 0;
+                       for ( i = 0; i < inSize; i ++ ) {
 
-    } );
+                               pSize ++;
+                               q = q.nextZ;
+                               if ( ! q ) break;
 
-    /**
-     * A Track of vectored keyframe values.
-     */
+                       }
 
-    function VectorKeyframeTrack( name, times, values, interpolation ) {
+                       qSize = inSize;
 
-       KeyframeTrack.call( this, name, times, values, interpolation );
+                       while ( pSize > 0 || ( qSize > 0 && q ) ) {
 
-    }
+                               if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) {
 
-    VectorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+                                       e = p;
+                                       p = p.nextZ;
+                                       pSize --;
 
-       constructor: VectorKeyframeTrack,
+                               } else {
 
-       ValueTypeName: 'vector'
+                                       e = q;
+                                       q = q.nextZ;
+                                       qSize --;
 
-       // ValueBufferType is inherited
+                               }
 
-       // DefaultInterpolation is inherited
+                               if ( tail ) tail.nextZ = e;
+                               else list = e;
 
-    } );
+                               e.prevZ = tail;
+                               tail = e;
 
-    function AnimationClip( name, duration = - 1, tracks, blendMode = NormalAnimationBlendMode ) {
+                       }
 
-       this.name = name;
-       this.tracks = tracks;
-       this.duration = duration;
-       this.blendMode = blendMode;
+                       p = q;
 
-       this.uuid = MathUtils.generateUUID();
+               }
 
-       // this means it should figure out its duration by scanning the tracks
-       if ( this.duration < 0 ) {
+               tail.nextZ = null;
+               inSize *= 2;
 
-               this.resetDuration();
+       } while ( numMerges > 1 );
 
-       }
+       return list;
 
     }
 
-    function getTrackTypeForValueTypeName( typeName ) {
-
-       switch ( typeName.toLowerCase() ) {
-
-               case 'scalar':
-               case 'double':
-               case 'float':
-               case 'number':
-               case 'integer':
+    // z-order of a point given coords and inverse of the longer side of data bbox
+    function zOrder$1( x, y, minX, minY, invSize ) {
 
-                       return NumberKeyframeTrack;
+       // coords are transformed into non-negative 15-bit integer range
+       x = 32767 * ( x - minX ) * invSize;
+       y = 32767 * ( y - minY ) * invSize;
 
-               case 'vector':
-               case 'vector2':
-               case 'vector3':
-               case 'vector4':
+       x = ( x | ( x << 8 ) ) & 0x00FF00FF;
+       x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
+       x = ( x | ( x << 2 ) ) & 0x33333333;
+       x = ( x | ( x << 1 ) ) & 0x55555555;
 
-                       return VectorKeyframeTrack;
+       y = ( y | ( y << 8 ) ) & 0x00FF00FF;
+       y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
+       y = ( y | ( y << 2 ) ) & 0x33333333;
+       y = ( y | ( y << 1 ) ) & 0x55555555;
 
-               case 'color':
+       return x | ( y << 1 );
 
-                       return ColorKeyframeTrack;
+    }
 
-               case 'quaternion':
+    // find the leftmost node of a polygon ring
+    function getLeftmost$1( start ) {
 
-                       return QuaternionKeyframeTrack;
+       let p = start,
+               leftmost = start;
+       do {
 
-               case 'bool':
-               case 'boolean':
+               if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p;
+               p = p.next;
 
-                       return BooleanKeyframeTrack;
+       } while ( p !== start );
 
-               case 'string':
+       return leftmost;
 
-                       return StringKeyframeTrack;
+    }
 
-       }
+    // check if a point lies within a convex triangle
+    function pointInTriangle$1( ax, ay, bx, by, cx, cy, px, py ) {
 
-       throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName );
+       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;
 
     }
 
-    function parseKeyframeTrack( json ) {
-
-       if ( json.type === undefined ) {
+    // check if a diagonal between two polygon nodes is valid (lies in polygon interior)
+    function isValidDiagonal$1( a, b ) {
 
-               throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' );
+       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
 
-       }
+    }
 
-       const trackType = getTrackTypeForValueTypeName( json.type );
+    // signed area of a triangle
+    function area$1( p, q, r ) {
 
-       if ( json.times === undefined ) {
+       return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );
 
-               const times = [], values = [];
+    }
 
-               AnimationUtils.flattenJSON( json.keys, times, values, 'value' );
+    // check if two points are equal
+    function equals$2( p1, p2 ) {
 
-               json.times = times;
-               json.values = values;
+       return p1.x === p2.x && p1.y === p2.y;
 
-       }
+    }
 
-       // derived classes can define a static parse method
-       if ( trackType.parse !== undefined ) {
+    // check if two segments intersect
+    function intersects$2( p1, q1, p2, q2 ) {
 
-               return trackType.parse( json );
+       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 ) );
 
-       } else {
+       if ( o1 !== o2 && o3 !== o4 ) return true; // general case
 
-               // by default, we assume a constructor compatible with the base
-               return new trackType( json.name, json.times, json.values, json.interpolation );
+       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;
 
     }
 
-    Object.assign( AnimationClip, {
+    // for collinear points p, q, r, check if point q lies on segment pr
+    function onSegment$1( p, q, r ) {
 
-       parse: function ( json ) {
+       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 );
 
-               const tracks = [],
-                       jsonTracks = json.tracks,
-                       frameTime = 1.0 / ( json.fps || 1.0 );
+    }
 
-               for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) {
+    function sign$2( num ) {
 
-                       tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) );
+       return num > 0 ? 1 : num < 0 ? - 1 : 0;
 
-               }
+    }
 
-               const clip = new AnimationClip( json.name, json.duration, tracks, json.blendMode );
-               clip.uuid = json.uuid;
+    // check if a polygon diagonal intersects any polygon segments
+    function intersectsPolygon$1( a, b ) {
 
-               return clip;
+       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;
 
-       toJSON: function ( clip ) {
+       } while ( p !== a );
 
-               const tracks = [],
-                       clipTracks = clip.tracks;
+       return false;
 
-               const json = {
+    }
 
-                       'name': clip.name,
-                       'duration': clip.duration,
-                       'tracks': tracks,
-                       'uuid': clip.uuid,
-                       'blendMode': clip.blendMode
+    // check if a polygon diagonal is locally inside the polygon
+    function locallyInside$1( a, b ) {
 
-               };
+       return area$1( a.prev, a, a.next ) < 0 ?
+               area$1( a, b, a.next ) >= 0 && area$1( a, a.prev, b ) >= 0 :
+               area$1( a, b, a.prev ) < 0 || area$1( a, a.next, b ) < 0;
 
-               for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) {
+    }
 
-                       tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );
+    // check if the middle point of a polygon diagonal is inside the polygon
+    function middleInside$1( a, b ) {
 
-               }
+       let p = a,
+               inside = false;
+       const px = ( a.x + b.x ) / 2,
+               py = ( a.y + b.y ) / 2;
+       do {
 
-               return json;
+               if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y &&
+                               ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) )
+                       inside = ! inside;
+               p = p.next;
 
-       },
+       } while ( p !== a );
 
-       CreateFromMorphTargetSequence: function ( name, morphTargetSequence, fps, noLoop ) {
+       return inside;
 
-               const numMorphTargets = morphTargetSequence.length;
-               const tracks = [];
+    }
 
-               for ( let i = 0; i < numMorphTargets; i ++ ) {
+    // 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 ) {
 
-                       let times = [];
-                       let values = [];
+       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;
 
-                       times.push(
-                               ( i + numMorphTargets - 1 ) % numMorphTargets,
-                               i,
-                               ( i + 1 ) % numMorphTargets );
+       a.next = b;
+       b.prev = a;
 
-                       values.push( 0, 1, 0 );
+       a2.next = an;
+       an.prev = a2;
 
-                       const order = AnimationUtils.getKeyframeOrder( times );
-                       times = AnimationUtils.sortedArray( times, 1, order );
-                       values = AnimationUtils.sortedArray( values, 1, order );
+       b2.next = a2;
+       a2.prev = b2;
 
-                       // 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 ) {
+       bp.next = b2;
+       b2.prev = bp;
 
-                               times.push( numMorphTargets );
-                               values.push( values[ 0 ] );
+       return b2;
 
-                       }
+    }
 
-                       tracks.push(
-                               new NumberKeyframeTrack(
-                                       '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',
-                                       times, values
-                               ).scale( 1.0 / fps ) );
+    // 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 new AnimationClip( name, - 1, tracks );
+       if ( ! last ) {
 
-       },
+               p.prev = p;
+               p.next = p;
 
-       findByName: function ( objectOrClipArray, name ) {
+       } else {
 
-               let clipArray = objectOrClipArray;
+               p.next = last.next;
+               p.prev = last;
+               last.next.prev = p;
+               last.next = p;
 
-               if ( ! Array.isArray( objectOrClipArray ) ) {
+       }
 
-                       const o = objectOrClipArray;
-                       clipArray = o.geometry && o.geometry.animations || o.animations;
+       return p;
 
-               }
+    }
 
-               for ( let i = 0; i < clipArray.length; i ++ ) {
+    function removeNode$2( p ) {
 
-                       if ( clipArray[ i ].name === name ) {
+       p.next.prev = p.prev;
+       p.prev.next = p.next;
 
-                               return clipArray[ i ];
+       if ( p.prevZ ) p.prevZ.nextZ = p.nextZ;
+       if ( p.nextZ ) p.nextZ.prevZ = p.prevZ;
 
-                       }
+    }
 
-               }
+    function Node$1( i, x, y ) {
 
-               return null;
+       // vertex index in coordinates array
+       this.i = i;
 
-       },
+       // vertex coordinates
+       this.x = x;
+       this.y = y;
 
-       CreateClipsFromMorphTargetSequences: function ( morphTargets, fps, noLoop ) {
+       // previous and next vertex nodes in a polygon ring
+       this.prev = null;
+       this.next = null;
 
-               const animationToMorphTargets = {};
+       // z-order curve value
+       this.z = null;
 
-               // tested with https://regex101.com/ on trick sequences
-               // such flamingo_flyA_003, flamingo_run1_003, crdeath0059
-               const pattern = /^([\w-]*?)([\d]+)$/;
+       // previous and next nodes in z-order
+       this.prevZ = null;
+       this.nextZ = null;
 
-               // 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 ++ ) {
+       // indicates whether this is a steiner point
+       this.steiner = false;
 
-                       const morphTarget = morphTargets[ i ];
-                       const parts = morphTarget.name.match( pattern );
+    }
 
-                       if ( parts && parts.length > 1 ) {
+    function signedArea$2( data, start, end, dim ) {
 
-                               const name = parts[ 1 ];
+       let sum = 0;
+       for ( let i = start, j = end - dim; i < end; i += dim ) {
 
-                               let animationMorphTargets = animationToMorphTargets[ name ];
+               sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );
+               j = i;
 
-                               if ( ! animationMorphTargets ) {
+       }
 
-                                       animationToMorphTargets[ name ] = animationMorphTargets = [];
+       return sum;
 
-                               }
+    }
 
-                               animationMorphTargets.push( morphTarget );
+    class ShapeUtils {
 
-                       }
+       // calculate area of the contour polygon
 
-               }
+       static area( contour ) {
 
-               const clips = [];
+               const n = contour.length;
+               let a = 0.0;
 
-               for ( const name in animationToMorphTargets ) {
+               for ( let p = n - 1, q = 0; q < n; p = q ++ ) {
 
-                       clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );
+                       a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
 
                }
 
-               return clips;
-
-       },
-
-       // parse the animation.hierarchy format
-       parseAnimation: function ( animation, bones ) {
-
-               if ( ! animation ) {
+               return a * 0.5;
 
-                       console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' );
-                       return null;
+       }
 
-               }
+       static isClockWise( pts ) {
 
-               const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) {
+               return ShapeUtils.area( pts ) < 0;
 
-                       // only return track if there are actually keys.
-                       if ( animationKeys.length !== 0 ) {
+       }
 
-                               const times = [];
-                               const values = [];
+       static triangulateShape( contour, holes ) {
 
-                               AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );
+               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 ] ]
 
-                               // empty keys are filtered out, so check again
-                               if ( times.length !== 0 ) {
+               removeDupEndPts( contour );
+               addContour( vertices, contour );
 
-                                       destTracks.push( new trackType( trackName, times, values ) );
+               //
 
-                               }
+               let holeIndex = contour.length;
 
-                       }
+               holes.forEach( removeDupEndPts );
 
-               };
+               for ( let i = 0; i < holes.length; i ++ ) {
 
-               const tracks = [];
+                       holeIndices.push( holeIndex );
+                       holeIndex += holes[ i ].length;
+                       addContour( vertices, holes[ i ] );
 
-               const clipName = animation.name || 'default';
-               const fps = animation.fps || 30;
-               const blendMode = animation.blendMode;
+               }
 
-               // automatic length determination in AnimationClip.
-               let duration = animation.length || - 1;
+               //
 
-               const hierarchyTracks = animation.hierarchy || [];
+               const triangles = Earcut.triangulate( vertices, holeIndices );
 
-               for ( let h = 0; h < hierarchyTracks.length; h ++ ) {
+               //
 
-                       const animationKeys = hierarchyTracks[ h ].keys;
+               for ( let i = 0; i < triangles.length; i += 3 ) {
 
-                       // skip empty tracks
-                       if ( ! animationKeys || animationKeys.length === 0 ) continue;
+                       faces.push( triangles.slice( i, i + 3 ) );
 
-                       // process morph targets
-                       if ( animationKeys[ 0 ].morphTargets ) {
+               }
 
-                               // figure out all morph targets used in this track
-                               const morphTargetNames = {};
+               return faces;
 
-                               let k;
+       }
 
-                               for ( k = 0; k < animationKeys.length; k ++ ) {
+    }
 
-                                       if ( animationKeys[ k ].morphTargets ) {
+    function removeDupEndPts( points ) {
 
-                                               for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) {
+       const l = points.length;
 
-                                                       morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1;
+       if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {
 
-                                               }
+               points.pop();
 
-                                       }
+       }
 
-                               }
+    }
 
-                               // 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 ) {
+    function addContour( vertices, contour ) {
 
-                                       const times = [];
-                                       const values = [];
+       for ( let i = 0; i < contour.length; i ++ ) {
 
-                                       for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) {
+               vertices.push( contour[ i ].x );
+               vertices.push( contour[ i ].y );
 
-                                               const animationKey = animationKeys[ k ];
+       }
 
-                                               times.push( animationKey.time );
-                                               values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );
+    }
 
-                                       }
+    /**
+     * 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
+     *
+     * }
+     */
 
-                                       tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) );
+    class ExtrudeGeometry extends BufferGeometry {
 
-                               }
+       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 = {} ) {
 
-                               duration = morphTargetNames.length * ( fps || 1.0 );
+               super();
 
-                       } else {
+               this.type = 'ExtrudeGeometry';
 
-                               // ...assume skeletal animation
+               this.parameters = {
+                       shapes: shapes,
+                       options: options
+               };
 
-                               const boneName = '.bones[' + bones[ h ].name + ']';
+               shapes = Array.isArray( shapes ) ? shapes : [ shapes ];
 
-                               addNonemptyTrack(
-                                       VectorKeyframeTrack, boneName + '.position',
-                                       animationKeys, 'pos', tracks );
+               const scope = this;
 
-                               addNonemptyTrack(
-                                       QuaternionKeyframeTrack, boneName + '.quaternion',
-                                       animationKeys, 'rot', tracks );
+               const verticesArray = [];
+               const uvArray = [];
 
-                               addNonemptyTrack(
-                                       VectorKeyframeTrack, boneName + '.scale',
-                                       animationKeys, 'scl', tracks );
+               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
 
-                       }
+                       const shape = shapes[ i ];
+                       addShape( shape );
 
                }
 
-               if ( tracks.length === 0 ) {
+               // build geometry
 
-                       return null;
+               this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) );
 
-               }
+               this.computeVertexNormals();
 
-               const clip = new AnimationClip( clipName, duration, tracks, blendMode );
+               // functions
 
-               return clip;
+               function addShape( shape ) {
 
-       }
+                       const placeholder = [];
 
-    } );
+                       // options
 
-    Object.assign( AnimationClip.prototype, {
+                       const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;
+                       const steps = options.steps !== undefined ? options.steps : 1;
+                       let depth = options.depth !== undefined ? options.depth : 1;
 
-       resetDuration: function () {
+                       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;
 
-               const tracks = this.tracks;
-               let duration = 0;
+                       const extrudePath = options.extrudePath;
 
-               for ( let i = 0, n = tracks.length; i !== n; ++ i ) {
+                       const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator;
 
-                       const track = this.tracks[ i ];
+                       // deprecated options
 
-                       duration = Math.max( duration, track.times[ track.times.length - 1 ] );
+                       if ( options.amount !== undefined ) {
 
-               }
+                               console.warn( 'THREE.ExtrudeBufferGeometry: amount has been renamed to depth.' );
+                               depth = options.amount;
 
-               this.duration = duration;
+                       }
 
-               return this;
+                       //
 
-       },
+                       let extrudePts, extrudeByPath = false;
+                       let splineTube, binormal, normal, position2;
 
-       trim: function () {
+                       if ( extrudePath ) {
 
-               for ( let i = 0; i < this.tracks.length; i ++ ) {
+                               extrudePts = extrudePath.getSpacedPoints( steps );
 
-                       this.tracks[ i ].trim( 0, this.duration );
+                               extrudeByPath = true;
+                               bevelEnabled = false; // bevels not supported for path extrusion
 
-               }
+                               // SETUP TNB variables
 
-               return this;
+                               // TODO1 - have a .isClosed in spline?
 
-       },
+                               splineTube = extrudePath.computeFrenetFrames( steps, false );
 
-       validate: function () {
+                               // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);
 
-               let valid = true;
+                               binormal = new Vector3();
+                               normal = new Vector3();
+                               position2 = new Vector3();
 
-               for ( let i = 0; i < this.tracks.length; i ++ ) {
+                       }
 
-                       valid = valid && this.tracks[ i ].validate();
+                       // Safeguards if bevels are not enabled
 
-               }
+                       if ( ! bevelEnabled ) {
 
-               return valid;
+                               bevelSegments = 0;
+                               bevelThickness = 0;
+                               bevelSize = 0;
+                               bevelOffset = 0;
 
-       },
+                       }
 
-       optimize: function () {
+                       // Variables initialization
 
-               for ( let i = 0; i < this.tracks.length; i ++ ) {
+                       const shapePoints = shape.extractPoints( curveSegments );
 
-                       this.tracks[ i ].optimize();
+                       let vertices = shapePoints.shape;
+                       const holes = shapePoints.holes;
 
-               }
+                       const reverse = ! ShapeUtils.isClockWise( vertices );
 
-               return this;
+                       if ( reverse ) {
 
-       },
+                               vertices = vertices.reverse();
 
-       clone: function () {
+                               // Maybe we should also check if holes are in the opposite direction, just to be safe ...
 
-               const tracks = [];
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
 
-               for ( let i = 0; i < this.tracks.length; i ++ ) {
+                                       const ahole = holes[ h ];
 
-                       tracks.push( this.tracks[ i ].clone() );
+                                       if ( ShapeUtils.isClockWise( ahole ) ) {
 
-               }
+                                               holes[ h ] = ahole.reverse();
 
-               return new AnimationClip( this.name, this.duration, tracks, this.blendMode );
+                                       }
 
-       },
+                               }
 
-       toJSON: function () {
+                       }
 
-               return AnimationClip.toJSON( this );
 
-       }
+                       const faces = ShapeUtils.triangulateShape( vertices, holes );
 
-    } );
+                       /* Vertices */
 
-    const Cache = {
+                       const contour = vertices; // vertices has all points but contour has only points of circumference
 
-       enabled: false,
+                       for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
 
-       files: {},
+                               const ahole = holes[ h ];
 
-       add: function ( key, file ) {
+                               vertices = vertices.concat( ahole );
 
-               if ( this.enabled === false ) return;
+                       }
 
-               // console.log( 'THREE.Cache', 'Adding key:', key );
 
-               this.files[ key ] = file;
+                       function scalePt2( pt, vec, size ) {
 
-       },
+                               if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' );
 
-       get: function ( key ) {
+                               return vec.clone().multiplyScalar( size ).add( pt );
 
-               if ( this.enabled === false ) return;
+                       }
 
-               // console.log( 'THREE.Cache', 'Checking key:', key );
+                       const vlen = vertices.length, flen = faces.length;
 
-               return this.files[ key ];
 
-       },
+                       // Find directions for point movement
 
-       remove: function ( key ) {
 
-               delete this.files[ key ];
+                       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.
 
-       clear: function () {
+                               let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt
 
-               this.files = {};
+                               // good reading for geometry algorithms (here: line-line intersection)
+                               // http://geomalgorithms.com/a05-_intersect-1.html
 
-       }
+                               const v_prev_x = inPt.x - inPrev.x,
+                                       v_prev_y = inPt.y - inPrev.y;
+                               const v_next_x = inNext.x - inPt.x,
+                                       v_next_y = inNext.y - inPt.y;
 
-    };
+                               const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y );
+
+                               // check for collinear edges
+                               const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x );
 
-    function LoadingManager( onLoad, onProgress, onError ) {
+                               if ( Math.abs( collinear0 ) > Number.EPSILON ) {
 
-       const scope = this;
+                                       // not collinear
 
-       let isLoading = false;
-       let itemsLoaded = 0;
-       let itemsTotal = 0;
-       let urlModifier = undefined;
-       const handlers = [];
+                                       // length of vectors for normalizing
 
-       // Refer to #5689 for the reason why we don't set .onStart
-       // in the constructor
+                                       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 );
 
-       this.onStart = undefined;
-       this.onLoad = onLoad;
-       this.onProgress = onProgress;
-       this.onError = onError;
+                                       // shift adjacent points by unit vectors to the left
 
-       this.itemStart = function ( url ) {
+                                       const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len );
+                                       const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len );
 
-               itemsTotal ++;
+                                       const ptNextShift_x = ( inNext.x - v_next_y / v_next_len );
+                                       const ptNextShift_y = ( inNext.y + v_next_x / v_next_len );
 
-               if ( isLoading === false ) {
+                                       // scaling factor for v_prev to intersection point
 
-                       if ( scope.onStart !== undefined ) {
+                                       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 );
 
-                               scope.onStart( url, itemsLoaded, itemsTotal );
+                                       // vector from inPt to intersection point
 
-                       }
+                                       v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x );
+                                       v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y );
 
-               }
+                                       // Don't normalize!, otherwise sharp corners become ugly
+                                       //  but prevent crazy spikes
+                                       const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y );
+                                       if ( v_trans_lensq <= 2 ) {
 
-               isLoading = true;
+                                               return new Vector2( v_trans_x, v_trans_y );
 
-       };
+                                       } else {
 
-       this.itemEnd = function ( url ) {
+                                               shrink_by = Math.sqrt( v_trans_lensq / 2 );
 
-               itemsLoaded ++;
+                                       }
 
-               if ( scope.onProgress !== undefined ) {
+                               } else {
 
-                       scope.onProgress( url, itemsLoaded, itemsTotal );
+                                       // handle special case of collinear edges
 
-               }
+                                       let direction_eq = false; // assumes: opposite
 
-               if ( itemsLoaded === itemsTotal ) {
+                                       if ( v_prev_x > Number.EPSILON ) {
 
-                       isLoading = false;
+                                               if ( v_next_x > Number.EPSILON ) {
 
-                       if ( scope.onLoad !== undefined ) {
+                                                       direction_eq = true;
 
-                               scope.onLoad();
+                                               }
 
-                       }
+                                       } else {
 
-               }
+                                               if ( v_prev_x < - Number.EPSILON ) {
 
-       };
+                                                       if ( v_next_x < - Number.EPSILON ) {
 
-       this.itemError = function ( url ) {
+                                                               direction_eq = true;
 
-               if ( scope.onError !== undefined ) {
+                                                       }
 
-                       scope.onError( url );
+                                               } else {
 
-               }
+                                                       if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) {
 
-       };
+                                                               direction_eq = true;
 
-       this.resolveURL = function ( url ) {
+                                                       }
 
-               if ( urlModifier ) {
+                                               }
 
-                       return urlModifier( url );
+                                       }
 
-               }
+                                       if ( direction_eq ) {
 
-               return url;
+                                               // 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 {
 
-       this.setURLModifier = function ( transform ) {
+                                               // 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 );
 
-               urlModifier = transform;
+                                       }
 
-               return this;
+                               }
 
-       };
+                               return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by );
 
-       this.addHandler = function ( regex, loader ) {
+                       }
 
-               handlers.push( regex, loader );
 
-               return this;
+                       const contourMovements = [];
 
-       };
+                       for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
 
-       this.removeHandler = function ( regex ) {
+                               if ( j === il ) j = 0;
+                               if ( k === il ) k = 0;
 
-               const index = handlers.indexOf( regex );
+                               //  (j)---(i)---(k)
+                               // console.log('i,j,k', i, j , k)
 
-               if ( index !== - 1 ) {
+                               contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] );
 
-                       handlers.splice( index, 2 );
+                       }
 
-               }
+                       const holesMovements = [];
+                       let oneHoleMovements, verticesMovements = contourMovements.concat();
 
-               return this;
+                       for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
 
-       };
+                               const ahole = holes[ h ];
 
-       this.getHandler = function ( file ) {
+                               oneHoleMovements = [];
 
-               for ( let i = 0, l = handlers.length; i < l; i += 2 ) {
+                               for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
 
-                       const regex = handlers[ i ];
-                       const loader = handlers[ i + 1 ];
+                                       if ( j === il ) j = 0;
+                                       if ( k === il ) k = 0;
 
-                       if ( regex.global ) regex.lastIndex = 0; // see #17920
+                                       //  (j)---(i)---(k)
+                                       oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
 
-                       if ( regex.test( file ) ) {
+                               }
 
-                               return loader;
+                               holesMovements.push( oneHoleMovements );
+                               verticesMovements = verticesMovements.concat( oneHoleMovements );
 
                        }
 
-               }
 
-               return null;
+                       // Loop bevelSegments, 1 for the front, 1 for the back
 
-       };
+                       for ( let b = 0; b < bevelSegments; b ++ ) {
 
-    }
+                               //for ( b = bevelSegments; b > 0; b -- ) {
 
-    const DefaultLoadingManager = new LoadingManager();
+                               const t = b / bevelSegments;
+                               const z = bevelThickness * Math.cos( t * Math.PI / 2 );
+                               const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
 
-    function Loader( manager ) {
+                               // contract shape
 
-       this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+                               for ( let i = 0, il = contour.length; i < il; i ++ ) {
 
-       this.crossOrigin = 'anonymous';
-       this.withCredentials = false;
-       this.path = '';
-       this.resourcePath = '';
-       this.requestHeader = {};
+                                       const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
 
-    }
+                                       v( vert.x, vert.y, - z );
 
-    Object.assign( Loader.prototype, {
+                               }
 
-       load: function ( /* url, onLoad, onProgress, onError */ ) {},
+                               // expand holes
 
-       loadAsync: function ( url, onProgress ) {
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
 
-               const scope = this;
+                                       const ahole = holes[ h ];
+                                       oneHoleMovements = holesMovements[ h ];
 
-               return new Promise( function ( resolve, reject ) {
+                                       for ( let i = 0, il = ahole.length; i < il; i ++ ) {
 
-                       scope.load( url, resolve, onProgress, reject );
+                                               const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
 
-               } );
+                                               v( vert.x, vert.y, - z );
 
-       },
+                                       }
 
-       parse: function ( /* data */ ) {},
+                               }
 
-       setCrossOrigin: function ( crossOrigin ) {
+                       }
 
-               this.crossOrigin = crossOrigin;
-               return this;
+                       const bs = bevelSize + bevelOffset;
 
-       },
+                       // Back facing vertices
 
-       setWithCredentials: function ( value ) {
+                       for ( let i = 0; i < vlen; i ++ ) {
 
-               this.withCredentials = value;
-               return this;
+                               const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
 
-       },
+                               if ( ! extrudeByPath ) {
 
-       setPath: function ( path ) {
+                                       v( vert.x, vert.y, 0 );
 
-               this.path = path;
-               return this;
-
-       },
+                               } else {
 
-       setResourcePath: function ( resourcePath ) {
+                                       // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );
 
-               this.resourcePath = resourcePath;
-               return this;
+                                       normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x );
+                                       binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y );
 
-       },
+                                       position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal );
 
-       setRequestHeader: function ( requestHeader ) {
+                                       v( position2.x, position2.y, position2.z );
 
-               this.requestHeader = requestHeader;
-               return this;
+                               }
 
-       }
+                       }
 
-    } );
+                       // Add stepped vertices...
+                       // Including front facing vertices
 
-    const loading = {};
+                       for ( let s = 1; s <= steps; s ++ ) {
 
-    function FileLoader( manager ) {
+                               for ( let i = 0; i < vlen; i ++ ) {
 
-       Loader.call( this, manager );
+                                       const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
 
-    }
+                                       if ( ! extrudeByPath ) {
 
-    FileLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+                                               v( vert.x, vert.y, depth / steps * s );
 
-       constructor: FileLoader,
+                                       } else {
 
-       load: function ( url, onLoad, onProgress, onError ) {
+                                               // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );
 
-               if ( url === undefined ) url = '';
+                                               normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x );
+                                               binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y );
 
-               if ( this.path !== undefined ) url = this.path + url;
+                                               position2.copy( extrudePts[ s ] ).add( normal ).add( binormal );
 
-               url = this.manager.resolveURL( url );
+                                               v( position2.x, position2.y, position2.z );
 
-               const scope = this;
+                                       }
 
-               const cached = Cache.get( url );
+                               }
 
-               if ( cached !== undefined ) {
+                       }
 
-                       scope.manager.itemStart( url );
 
-                       setTimeout( function () {
+                       // Add bevel segments planes
 
-                               if ( onLoad ) onLoad( cached );
+                       //for ( b = 1; b <= bevelSegments; b ++ ) {
+                       for ( let b = bevelSegments - 1; b >= 0; b -- ) {
 
-                               scope.manager.itemEnd( url );
+                               const t = b / bevelSegments;
+                               const z = bevelThickness * Math.cos( t * Math.PI / 2 );
+                               const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
 
-                       }, 0 );
+                               // contract shape
 
-                       return cached;
+                               for ( let i = 0, il = contour.length; i < il; i ++ ) {
 
-               }
+                                       const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
+                                       v( vert.x, vert.y, depth + z );
 
-               // Check if request is duplicate
+                               }
 
-               if ( loading[ url ] !== undefined ) {
+                               // expand holes
 
-                       loading[ url ].push( {
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
 
-                               onLoad: onLoad,
-                               onProgress: onProgress,
-                               onError: onError
+                                       const ahole = holes[ h ];
+                                       oneHoleMovements = holesMovements[ h ];
 
-                       } );
+                                       for ( let i = 0, il = ahole.length; i < il; i ++ ) {
 
-                       return;
+                                               const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
 
-               }
+                                               if ( ! extrudeByPath ) {
 
-               // Check for data: URI
-               const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
-               const dataUriRegexResult = url.match( dataUriRegex );
-               let request;
+                                                       v( vert.x, vert.y, depth + z );
 
-               // Safari can not handle Data URIs through XMLHttpRequest so process manually
-               if ( dataUriRegexResult ) {
+                                               } else {
 
-                       const mimeType = dataUriRegexResult[ 1 ];
-                       const isBase64 = !! dataUriRegexResult[ 2 ];
+                                                       v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z );
 
-                       let data = dataUriRegexResult[ 3 ];
-                       data = decodeURIComponent( data );
+                                               }
 
-                       if ( isBase64 ) data = atob( data );
+                                       }
 
-                       try {
+                               }
 
-                               let response;
-                               const responseType = ( this.responseType || '' ).toLowerCase();
+                       }
 
-                               switch ( responseType ) {
+                       /* Faces */
 
-                                       case 'arraybuffer':
-                                       case 'blob':
+                       // Top and bottom faces
 
-                                               const view = new Uint8Array( data.length );
+                       buildLidFaces();
 
-                                               for ( let i = 0; i < data.length; i ++ ) {
+                       // Sides faces
 
-                                                       view[ i ] = data.charCodeAt( i );
+                       buildSideFaces();
 
-                                               }
 
-                                               if ( responseType === 'blob' ) {
+                       /////  Internal functions
 
-                                                       response = new Blob( [ view.buffer ], { type: mimeType } );
+                       function buildLidFaces() {
 
-                                               } else {
+                               const start = verticesArray.length / 3;
 
-                                                       response = view.buffer;
+                               if ( bevelEnabled ) {
 
-                                               }
+                                       let layer = 0; // steps + 1
+                                       let offset = vlen * layer;
 
-                                               break;
+                                       // Bottom faces
 
-                                       case 'document':
+                                       for ( let i = 0; i < flen; i ++ ) {
 
-                                               const parser = new DOMParser();
-                                               response = parser.parseFromString( data, mimeType );
+                                               const face = faces[ i ];
+                                               f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset );
 
-                                               break;
+                                       }
 
-                                       case 'json':
+                                       layer = steps + bevelSegments * 2;
+                                       offset = vlen * layer;
 
-                                               response = JSON.parse( data );
+                                       // Top faces
 
-                                               break;
+                                       for ( let i = 0; i < flen; i ++ ) {
 
-                                       default: // 'text' or other
+                                               const face = faces[ i ];
+                                               f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset );
 
-                                               response = data;
+                                       }
 
-                                               break;
+                               } else {
 
-                               }
+                                       // Bottom faces
 
-                               // Wait for next browser tick like standard XMLHttpRequest event dispatching does
-                               setTimeout( function () {
+                                       for ( let i = 0; i < flen; i ++ ) {
 
-                                       if ( onLoad ) onLoad( response );
+                                               const face = faces[ i ];
+                                               f3( face[ 2 ], face[ 1 ], face[ 0 ] );
 
-                                       scope.manager.itemEnd( url );
+                                       }
 
-                               }, 0 );
+                                       // Top faces
 
-                       } catch ( error ) {
+                                       for ( let i = 0; i < flen; i ++ ) {
 
-                               // Wait for next browser tick like standard XMLHttpRequest event dispatching does
-                               setTimeout( function () {
+                                               const face = faces[ i ];
+                                               f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps );
 
-                                       if ( onError ) onError( error );
+                                       }
 
-                                       scope.manager.itemError( url );
-                                       scope.manager.itemEnd( url );
+                               }
 
-                               }, 0 );
+                               scope.addGroup( start, verticesArray.length / 3 - start, 0 );
 
                        }
 
-               } else {
-
-                       // Initialise array for duplicate requests
-
-                       loading[ url ] = [];
-
-                       loading[ url ].push( {
+                       // Create faces for the z-sides of the shape
 
-                               onLoad: onLoad,
-                               onProgress: onProgress,
-                               onError: onError
+                       function buildSideFaces() {
 
-                       } );
+                               const start = verticesArray.length / 3;
+                               let layeroffset = 0;
+                               sidewalls( contour, layeroffset );
+                               layeroffset += contour.length;
 
-                       request = new XMLHttpRequest();
+                               for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
 
-                       request.open( 'GET', url, true );
+                                       const ahole = holes[ h ];
+                                       sidewalls( ahole, layeroffset );
 
-                       request.addEventListener( 'load', function ( event ) {
+                                       //, true
+                                       layeroffset += ahole.length;
 
-                               const response = this.response;
+                               }
 
-                               const callbacks = loading[ url ];
 
-                               delete loading[ url ];
+                               scope.addGroup( start, verticesArray.length / 3 - start, 1 );
 
-                               if ( this.status === 200 || this.status === 0 ) {
 
-                                       // Some browsers return HTTP Status 0 when using non-http protocol
-                                       // e.g. 'file://' or 'data://'. Handle as success.
+                       }
 
-                                       if ( this.status === 0 ) console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
+                       function sidewalls( contour, layeroffset ) {
 
-                                       // 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 );
+                               let i = contour.length;
 
-                                       for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+                               while ( -- i >= 0 ) {
 
-                                               const callback = callbacks[ i ];
-                                               if ( callback.onLoad ) callback.onLoad( response );
+                                       const j = i;
+                                       let k = i - 1;
+                                       if ( k < 0 ) k = contour.length - 1;
 
-                                       }
+                                       //console.log('b', i,j, i-1, k,vertices.length);
 
-                                       scope.manager.itemEnd( url );
+                                       for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) {
 
-                               } else {
+                                               const slen1 = vlen * s;
+                                               const slen2 = vlen * ( s + 1 );
 
-                                       for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+                                               const a = layeroffset + j + slen1,
+                                                       b = layeroffset + k + slen1,
+                                                       c = layeroffset + k + slen2,
+                                                       d = layeroffset + j + slen2;
 
-                                               const callback = callbacks[ i ];
-                                               if ( callback.onError ) callback.onError( event );
+                                               f4( a, b, c, d );
 
                                        }
 
-                                       scope.manager.itemError( url );
-                                       scope.manager.itemEnd( url );
-
                                }
 
-                       }, false );
+                       }
 
-                       request.addEventListener( 'progress', function ( event ) {
+                       function v( x, y, z ) {
 
-                               const callbacks = loading[ url ];
+                               placeholder.push( x );
+                               placeholder.push( y );
+                               placeholder.push( z );
 
-                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+                       }
 
-                                       const callback = callbacks[ i ];
-                                       if ( callback.onProgress ) callback.onProgress( event );
 
-                               }
+                       function f3( a, b, c ) {
 
-                       }, false );
+                               addVertex( a );
+                               addVertex( b );
+                               addVertex( c );
 
-                       request.addEventListener( 'error', function ( event ) {
+                               const nextIndex = verticesArray.length / 3;
+                               const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
 
-                               const callbacks = loading[ url ];
+                               addUV( uvs[ 0 ] );
+                               addUV( uvs[ 1 ] );
+                               addUV( uvs[ 2 ] );
 
-                               delete loading[ url ];
+                       }
 
-                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+                       function f4( a, b, c, d ) {
 
-                                       const callback = callbacks[ i ];
-                                       if ( callback.onError ) callback.onError( event );
+                               addVertex( a );
+                               addVertex( b );
+                               addVertex( d );
 
-                               }
+                               addVertex( b );
+                               addVertex( c );
+                               addVertex( d );
 
-                               scope.manager.itemError( url );
-                               scope.manager.itemEnd( url );
 
-                       }, false );
+                               const nextIndex = verticesArray.length / 3;
+                               const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
 
-                       request.addEventListener( 'abort', function ( event ) {
+                               addUV( uvs[ 0 ] );
+                               addUV( uvs[ 1 ] );
+                               addUV( uvs[ 3 ] );
 
-                               const callbacks = loading[ url ];
+                               addUV( uvs[ 1 ] );
+                               addUV( uvs[ 2 ] );
+                               addUV( uvs[ 3 ] );
 
-                               delete loading[ url ];
+                       }
 
-                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+                       function addVertex( index ) {
 
-                                       const callback = callbacks[ i ];
-                                       if ( callback.onError ) callback.onError( event );
+                               verticesArray.push( placeholder[ index * 3 + 0 ] );
+                               verticesArray.push( placeholder[ index * 3 + 1 ] );
+                               verticesArray.push( placeholder[ index * 3 + 2 ] );
 
-                               }
+                       }
 
-                               scope.manager.itemError( url );
-                               scope.manager.itemEnd( url );
 
-                       }, false );
+                       function addUV( vector2 ) {
 
-                       if ( this.responseType !== undefined ) request.responseType = this.responseType;
-                       if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials;
+                               uvArray.push( vector2.x );
+                               uvArray.push( vector2.y );
 
-                       if ( request.overrideMimeType ) request.overrideMimeType( this.mimeType !== undefined ? this.mimeType : 'text/plain' );
+                       }
 
-                       for ( const header in this.requestHeader ) {
+               }
 
-                               request.setRequestHeader( header, this.requestHeader[ header ] );
+       }
 
-                       }
+       toJSON() {
 
-                       request.send( null );
+               const data = super.toJSON();
 
-               }
+               const shapes = this.parameters.shapes;
+               const options = this.parameters.options;
 
-               scope.manager.itemStart( url );
+               return toJSON$1( shapes, options, data );
 
-               return request;
+       }
 
-       },
+       static fromJSON( data, shapes ) {
 
-       setResponseType: function ( value ) {
+               const geometryShapes = [];
 
-               this.responseType = value;
-               return this;
+               for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) {
 
-       },
+                       const shape = shapes[ data.shapes[ j ] ];
 
-       setMimeType: function ( value ) {
+                       geometryShapes.push( shape );
 
-               this.mimeType = value;
-               return this;
+               }
 
-       }
+               const extrudePath = data.options.extrudePath;
 
-    } );
+               if ( extrudePath !== undefined ) {
 
-    function AnimationLoader( manager ) {
+                       data.options.extrudePath = new Curves[ extrudePath.type ]().fromJSON( extrudePath );
 
-       Loader.call( this, manager );
+               }
 
-    }
+               return new ExtrudeGeometry( geometryShapes, data.options );
 
-    AnimationLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+       }
 
-       constructor: AnimationLoader,
+    }
 
-       load: function ( url, onLoad, onProgress, onError ) {
+    const WorldUVGenerator = {
 
-               const scope = this;
+       generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) {
 
-               const loader = new FileLoader( scope.manager );
-               loader.setPath( scope.path );
-               loader.setRequestHeader( scope.requestHeader );
-               loader.setWithCredentials( scope.withCredentials );
-               loader.load( url, function ( text ) {
+               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 ];
 
-                       try {
+               return [
+                       new Vector2( a_x, a_y ),
+                       new Vector2( b_x, b_y ),
+                       new Vector2( c_x, c_y )
+               ];
 
-                               onLoad( scope.parse( JSON.parse( text ) ) );
+       },
 
-                       } catch ( e ) {
+       generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) {
 
-                               if ( onError ) {
+               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 ];
 
-                                       onError( e );
+               if ( Math.abs( a_y - b_y ) < Math.abs( a_x - b_x ) ) {
 
-                               } else {
+                       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 )
+                       ];
 
-                                       console.error( e );
+               } 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 )
+                       ];
 
-                               scope.manager.itemError( url );
+               }
 
-                       }
+       }
 
-               }, onProgress, onError );
+    };
 
-       },
+    function toJSON$1( shapes, options, data ) {
 
-       parse: function ( json ) {
+       data.shapes = [];
 
-               const animations = [];
+       if ( Array.isArray( shapes ) ) {
 
-               for ( let i = 0; i < json.length; i ++ ) {
+               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
 
-                       const clip = AnimationClip.parse( json[ i ] );
+                       const shape = shapes[ i ];
 
-                       animations.push( clip );
+                       data.shapes.push( shape.uuid );
 
                }
 
-               return animations;
+       } else {
+
+               data.shapes.push( shapes.uuid );
 
        }
 
-    );
+       if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON();
 
-    /**
-     * Abstract Base class to block based textures loader (dds, pvr, ...)
-     *
-     * Sub classes have to implement the parse() method which will be used in load().
-     */
+       return data;
 
-    function CompressedTextureLoader( manager ) {
+    }
 
-       Loader.call( this, manager );
+    class ShapeGeometry extends BufferGeometry {
 
-    }
+       constructor( shapes = new Shape( [ new Vector2( 0, 0.5 ), new Vector2( - 0.5, - 0.5 ), new Vector2( 0.5, - 0.5 ) ] ), curveSegments = 12 ) {
 
-    CompressedTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+               super();
+               this.type = 'ShapeGeometry';
 
-       constructor: CompressedTextureLoader,
+               this.parameters = {
+                       shapes: shapes,
+                       curveSegments: curveSegments
+               };
 
-       load: function ( url, onLoad, onProgress, onError ) {
+               // buffers
 
-               const scope = this;
+               const indices = [];
+               const vertices = [];
+               const normals = [];
+               const uvs = [];
 
-               const images = [];
+               // helper variables
 
-               const texture = new CompressedTexture();
+               let groupStart = 0;
+               let groupCount = 0;
 
-               const loader = new FileLoader( this.manager );
-               loader.setPath( this.path );
-               loader.setResponseType( 'arraybuffer' );
-               loader.setRequestHeader( this.requestHeader );
-               loader.setWithCredentials( scope.withCredentials );
+               // allow single and array values for "shapes" parameter
 
-               let loaded = 0;
+               if ( Array.isArray( shapes ) === false ) {
 
-               function loadTexture( i ) {
+                       addShape( shapes );
 
-                       loader.load( url[ i ], function ( buffer ) {
+               } else {
 
-                               const texDatas = scope.parse( buffer, true );
+                       for ( let i = 0; i < shapes.length; i ++ ) {
 
-                               images[ i ] = {
-                                       width: texDatas.width,
-                                       height: texDatas.height,
-                                       format: texDatas.format,
-                                       mipmaps: texDatas.mipmaps
-                               };
+                               addShape( shapes[ i ] );
 
-                               loaded += 1;
+                               this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support
 
-                               if ( loaded === 6 ) {
+                               groupStart += groupCount;
+                               groupCount = 0;
 
-                                       if ( texDatas.mipmapCount === 1 ) texture.minFilter = LinearFilter;
+                       }
 
-                                       texture.image = images;
-                                       texture.format = texDatas.format;
-                                       texture.needsUpdate = true;
+               }
 
-                                       if ( onLoad ) onLoad( texture );
+               // 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 ) );
 
-                       }, onProgress, onError );
 
-               }
+               // helper functions
+
+               function addShape( shape ) {
+
+                       const indexOffset = vertices.length / 3;
+                       const points = shape.extractPoints( curveSegments );
 
-               if ( Array.isArray( url ) ) {
+                       let shapeVertices = points.shape;
+                       const shapeHoles = points.holes;
+
+                       // check direction of vertices
 
-                       for ( let i = 0, il = url.length; i < il; ++ i ) {
+                       if ( ShapeUtils.isClockWise( shapeVertices ) === false ) {
 
-                               loadTexture( i );
+                               shapeVertices = shapeVertices.reverse();
 
                        }
 
-               } else {
+                       for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
 
-                       // compressed cubemap texture stored in a single DDS file
+                               const shapeHole = shapeHoles[ i ];
 
-                       loader.load( url, function ( buffer ) {
+                               if ( ShapeUtils.isClockWise( shapeHole ) === true ) {
 
-                               const texDatas = scope.parse( buffer, true );
+                                       shapeHoles[ i ] = shapeHole.reverse();
 
-                               if ( texDatas.isCubemap ) {
+                               }
 
-                                       const faces = texDatas.mipmaps.length / texDatas.mipmapCount;
+                       }
 
-                                       for ( let f = 0; f < faces; f ++ ) {
+                       const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles );
 
-                                               images[ f ] = { mipmaps: [] };
+                       // join vertices of inner and outer paths to a single array
 
-                                               for ( let i = 0; i < texDatas.mipmapCount; i ++ ) {
+                       for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
 
-                                                       images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] );
-                                                       images[ f ].format = texDatas.format;
-                                                       images[ f ].width = texDatas.width;
-                                                       images[ f ].height = texDatas.height;
+                               const shapeHole = shapeHoles[ i ];
+                               shapeVertices = shapeVertices.concat( shapeHole );
 
-                                               }
+                       }
 
-                                       }
+                       // vertices, normals, uvs
 
-                                       texture.image = images;
+                       for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) {
 
-                               } else {
+                               const vertex = shapeVertices[ i ];
 
-                                       texture.image.width = texDatas.width;
-                                       texture.image.height = texDatas.height;
-                                       texture.mipmaps = texDatas.mipmaps;
+                               vertices.push( vertex.x, vertex.y, 0 );
+                               normals.push( 0, 0, 1 );
+                               uvs.push( vertex.x, vertex.y ); // world uvs
 
-                               }
+                       }
 
-                               if ( texDatas.mipmapCount === 1 ) {
+                       // incides
 
-                                       texture.minFilter = LinearFilter;
+                       for ( let i = 0, l = faces.length; i < l; i ++ ) {
 
-                               }
+                               const face = faces[ i ];
 
-                               texture.format = texDatas.format;
-                               texture.needsUpdate = true;
+                               const a = face[ 0 ] + indexOffset;
+                               const b = face[ 1 ] + indexOffset;
+                               const c = face[ 2 ] + indexOffset;
 
-                               if ( onLoad ) onLoad( texture );
+                               indices.push( a, b, c );
+                               groupCount += 3;
 
-                       }, onProgress, onError );
+                       }
 
                }
 
-               return texture;
-
        }
 
-    } );
+       toJSON() {
 
-    function ImageLoader( manager ) {
+               const data = super.toJSON();
 
-       Loader.call( this, manager );
+               const shapes = this.parameters.shapes;
 
-    }
+               return toJSON( shapes, data );
 
-    ImageLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+       }
 
-       constructor: ImageLoader,
+       static fromJSON( data, shapes ) {
 
-       load: function ( url, onLoad, onProgress, onError ) {
+               const geometryShapes = [];
 
-               if ( this.path !== undefined ) url = this.path + url;
+               for ( let j = 0, jl = data.shapes.length; j < jl; j ++ ) {
 
-               url = this.manager.resolveURL( url );
+                       const shape = shapes[ data.shapes[ j ] ];
 
-               const scope = this;
+                       geometryShapes.push( shape );
 
-               const cached = Cache.get( url );
+               }
 
-               if ( cached !== undefined ) {
+               return new ShapeGeometry( geometryShapes, data.curveSegments );
 
-                       scope.manager.itemStart( url );
+       }
 
-                       setTimeout( function () {
+    }
 
-                               if ( onLoad ) onLoad( cached );
+    function toJSON( shapes, data ) {
 
-                               scope.manager.itemEnd( url );
+       data.shapes = [];
 
-                       }, 0 );
+       if ( Array.isArray( shapes ) ) {
 
-                       return cached;
+               for ( let i = 0, l = shapes.length; i < l; i ++ ) {
 
-               }
+                       const shape = shapes[ i ];
 
-               const image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' );
+                       data.shapes.push( shape.uuid );
 
-               function onImageLoad() {
+               }
 
-                       image.removeEventListener( 'load', onImageLoad, false );
-                       image.removeEventListener( 'error', onImageError, false );
+       } else {
 
-                       Cache.add( url, this );
+               data.shapes.push( shapes.uuid );
 
-                       if ( onLoad ) onLoad( this );
+       }
 
-                       scope.manager.itemEnd( url );
+       return data;
 
-               }
+    }
 
-               function onImageError( event ) {
+    class SphereGeometry extends BufferGeometry {
 
-                       image.removeEventListener( 'load', onImageLoad, false );
-                       image.removeEventListener( 'error', onImageError, false );
+       constructor( radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) {
 
-                       if ( onError ) onError( event );
+               super();
+               this.type = 'SphereGeometry';
 
-                       scope.manager.itemError( url );
-                       scope.manager.itemEnd( url );
+               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 ) );
 
-               image.addEventListener( 'load', onImageLoad, false );
-               image.addEventListener( 'error', onImageError, false );
+               const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI );
 
-               if ( url.substr( 0, 5 ) !== 'data:' ) {
+               let index = 0;
+               const grid = [];
 
-                       if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;
+               const vertex = new Vector3();
+               const normal = new Vector3();
 
-               }
+               // buffers
 
-               scope.manager.itemStart( url );
+               const indices = [];
+               const vertices = [];
+               const normals = [];
+               const uvs = [];
 
-               image.src = url;
+               // generate vertices, normals and uvs
 
-               return image;
+               for ( let iy = 0; iy <= heightSegments; iy ++ ) {
 
-       }
+                       const verticesRow = [];
 
-    } );
+                       const v = iy / heightSegments;
 
-    function CubeTextureLoader( manager ) {
+                       // special case for the poles
 
-       Loader.call( this, manager );
+                       let uOffset = 0;
 
-    }
+                       if ( iy == 0 && thetaStart == 0 ) {
 
-    CubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+                               uOffset = 0.5 / widthSegments;
 
-       constructor: CubeTextureLoader,
+                       } else if ( iy == heightSegments && thetaEnd == Math.PI ) {
 
-       load: function ( urls, onLoad, onProgress, onError ) {
+                               uOffset = - 0.5 / widthSegments;
 
-               const texture = new CubeTexture();
+                       }
 
-               const loader = new ImageLoader( this.manager );
-               loader.setCrossOrigin( this.crossOrigin );
-               loader.setPath( this.path );
+                       for ( let ix = 0; ix <= widthSegments; ix ++ ) {
 
-               let loaded = 0;
+                               const u = ix / widthSegments;
 
-               function loadTexture( i ) {
+                               // vertex
 
-                       loader.load( urls[ i ], function ( image ) {
+                               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 );
 
-                               texture.images[ i ] = image;
+                               vertices.push( vertex.x, vertex.y, vertex.z );
 
-                               loaded ++;
+                               // normal
 
-                               if ( loaded === 6 ) {
+                               normal.copy( vertex ).normalize();
+                               normals.push( normal.x, normal.y, normal.z );
 
-                                       texture.needsUpdate = true;
+                               // uv
 
-                                       if ( onLoad ) onLoad( texture );
+                               uvs.push( u + uOffset, 1 - v );
 
-                               }
+                               verticesRow.push( index ++ );
 
-                       }, undefined, onError );
+                       }
+
+                       grid.push( verticesRow );
 
                }
 
-               for ( let i = 0; i < urls.length; ++ i ) {
+               // indices
 
-                       loadTexture( i );
+               for ( let iy = 0; iy < heightSegments; iy ++ ) {
 
-               }
+                       for ( let ix = 0; ix < widthSegments; ix ++ ) {
 
-               return texture;
+                               const a = grid[ iy ][ ix + 1 ];
+                               const b = grid[ iy ][ ix ];
+                               const c = grid[ iy + 1 ][ ix ];
+                               const d = grid[ iy + 1 ][ ix + 1 ];
 
-       }
+                               if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d );
+                               if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d );
 
-    } );
+                       }
 
-    /**
-     * 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().
-     */
+               }
+
+               // build geometry
 
-    function DataTextureLoader( manager ) {
+               this.setIndex( indices );
+               this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+               this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
 
-       Loader.call( this, manager );
+       }
 
-    }
+       static fromJSON( data ) {
 
-    DataTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+               return new SphereGeometry( data.radius, data.widthSegments, data.heightSegments, data.phiStart, data.phiLength, data.thetaStart, data.thetaLength );
 
-       constructor: DataTextureLoader,
+       }
 
-       load: function ( url, onLoad, onProgress, onError ) {
+    }
 
-               const scope = this;
+    /**
+     * parameters = {
+     *  color: <THREE.Color>
+     * }
+     */
 
-               const texture = new DataTexture();
+    class ShadowMaterial extends Material {
 
-               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 ) {
+       constructor( parameters ) {
 
-                       const texData = scope.parse( buffer );
+               super();
 
-                       if ( ! texData ) return;
+               this.type = 'ShadowMaterial';
 
-                       if ( texData.image !== undefined ) {
+               this.color = new Color( 0x000000 );
+               this.transparent = true;
 
-                               texture.image = texData.image;
+               this.setValues( parameters );
 
-                       } else if ( texData.data !== undefined ) {
+       }
 
-                               texture.image.width = texData.width;
-                               texture.image.height = texData.height;
-                               texture.image.data = texData.data;
+       copy( source ) {
 
-                       }
+               super.copy( source );
 
-                       texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping;
-                       texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping;
+               this.color.copy( source.color );
 
-                       texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter;
-                       texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter;
+               return this;
 
-                       texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1;
+       }
 
-                       if ( texData.encoding !== undefined ) {
+    }
 
-                               texture.encoding = texData.encoding;
+    ShadowMaterial.prototype.isShadowMaterial = true;
 
-                       }
+    /**
+     * parameters = {
+     *  color: <hex>,
+     *  roughness: <float>,
+     *  metalness: <float>,
+     *  opacity: <float>,
+     *
+     *  map: new THREE.Texture( <Image> ),
+     *
+     *  lightMap: new THREE.Texture( <Image> ),
+     *  lightMapIntensity: <float>
+     *
+     *  aoMap: new THREE.Texture( <Image> ),
+     *  aoMapIntensity: <float>
+     *
+     *  emissive: <hex>,
+     *  emissiveIntensity: <float>
+     *  emissiveMap: new THREE.Texture( <Image> ),
+     *
+     *  bumpMap: new THREE.Texture( <Image> ),
+     *  bumpScale: <float>,
+     *
+     *  normalMap: new THREE.Texture( <Image> ),
+     *  normalMapType: THREE.TangentSpaceNormalMap,
+     *  normalScale: <Vector2>,
+     *
+     *  displacementMap: new THREE.Texture( <Image> ),
+     *  displacementScale: <float>,
+     *  displacementBias: <float>,
+     *
+     *  roughnessMap: new THREE.Texture( <Image> ),
+     *
+     *  metalnessMap: new THREE.Texture( <Image> ),
+     *
+     *  alphaMap: new THREE.Texture( <Image> ),
+     *
+     *  envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
+     *  envMapIntensity: <float>
+     *
+     *  refractionRatio: <float>,
+     *
+     *  wireframe: <boolean>,
+     *  wireframeLinewidth: <float>,
+     *
+     *  flatShading: <bool>
+     * }
+     */
 
-                       if ( texData.flipY !== undefined ) {
+    class MeshStandardMaterial extends Material {
 
-                               texture.flipY = texData.flipY;
+       constructor( parameters ) {
 
-                       }
+               super();
 
-                       if ( texData.format !== undefined ) {
+               this.defines = { 'STANDARD': '' };
 
-                               texture.format = texData.format;
+               this.type = 'MeshStandardMaterial';
 
-                       }
+               this.color = new Color( 0xffffff ); // diffuse
+               this.roughness = 1.0;
+               this.metalness = 0.0;
 
-                       if ( texData.type !== undefined ) {
+               this.map = null;
 
-                               texture.type = texData.type;
+               this.lightMap = null;
+               this.lightMapIntensity = 1.0;
 
-                       }
+               this.aoMap = null;
+               this.aoMapIntensity = 1.0;
 
-                       if ( texData.mipmaps !== undefined ) {
+               this.emissive = new Color( 0x000000 );
+               this.emissiveIntensity = 1.0;
+               this.emissiveMap = null;
 
-                               texture.mipmaps = texData.mipmaps;
-                               texture.minFilter = LinearMipmapLinearFilter; // presumably...
+               this.bumpMap = null;
+               this.bumpScale = 1;
 
-                       }
+               this.normalMap = null;
+               this.normalMapType = TangentSpaceNormalMap;
+               this.normalScale = new Vector2( 1, 1 );
 
-                       if ( texData.mipmapCount === 1 ) {
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-                               texture.minFilter = LinearFilter;
+               this.roughnessMap = null;
 
-                       }
+               this.metalnessMap = null;
 
-                       texture.needsUpdate = true;
+               this.alphaMap = null;
 
-                       if ( onLoad ) onLoad( texture, texData );
+               this.envMap = null;
+               this.envMapIntensity = 1.0;
 
-               }, onProgress, onError );
+               this.refractionRatio = 0.98;
 
+               this.wireframe = false;
+               this.wireframeLinewidth = 1;
+               this.wireframeLinecap = 'round';
+               this.wireframeLinejoin = 'round';
 
-               return texture;
+               this.flatShading = false;
+
+               this.setValues( parameters );
 
        }
 
-    } );
+       copy( source ) {
 
-    function TextureLoader( manager ) {
+               super.copy( source );
 
-       Loader.call( this, manager );
+               this.defines = { 'STANDARD': '' };
 
-    }
+               this.color.copy( source.color );
+               this.roughness = source.roughness;
+               this.metalness = source.metalness;
 
-    TextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+               this.map = source.map;
 
-       constructor: TextureLoader,
+               this.lightMap = source.lightMap;
+               this.lightMapIntensity = source.lightMapIntensity;
 
-       load: function ( url, onLoad, onProgress, onError ) {
+               this.aoMap = source.aoMap;
+               this.aoMapIntensity = source.aoMapIntensity;
 
-               const texture = new Texture();
+               this.emissive.copy( source.emissive );
+               this.emissiveMap = source.emissiveMap;
+               this.emissiveIntensity = source.emissiveIntensity;
 
-               const loader = new ImageLoader( this.manager );
-               loader.setCrossOrigin( this.crossOrigin );
-               loader.setPath( this.path );
+               this.bumpMap = source.bumpMap;
+               this.bumpScale = source.bumpScale;
 
-               loader.load( url, function ( image ) {
+               this.normalMap = source.normalMap;
+               this.normalMapType = source.normalMapType;
+               this.normalScale.copy( source.normalScale );
 
-                       texture.image = image;
+               this.displacementMap = source.displacementMap;
+               this.displacementScale = source.displacementScale;
+               this.displacementBias = source.displacementBias;
 
-                       // 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.roughnessMap = source.roughnessMap;
 
-                       texture.format = isJPEG ? RGBFormat : RGBAFormat;
-                       texture.needsUpdate = true;
+               this.metalnessMap = source.metalnessMap;
 
-                       if ( onLoad !== undefined ) {
+               this.alphaMap = source.alphaMap;
 
-                               onLoad( texture );
+               this.envMap = source.envMap;
+               this.envMapIntensity = source.envMapIntensity;
 
-                       }
+               this.refractionRatio = source.refractionRatio;
 
-               }, onProgress, onError );
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
+               this.wireframeLinecap = source.wireframeLinecap;
+               this.wireframeLinejoin = source.wireframeLinejoin;
 
-               return texture;
+               this.flatShading = source.flatShading;
+
+               return this;
 
        }
 
-    } );
+    }
+
+    MeshStandardMaterial.prototype.isMeshStandardMaterial = true;
 
     /**
-     * Extensible curve object.
-     *
-     * Some common of curve methods:
-     * .getPoint( t, optionalTarget ), .getTangent( t, optionalTarget )
-     * .getPointAt( u, optionalTarget ), .getTangentAt( u, optionalTarget )
-     * .getPoints(), .getSpacedPoints()
-     * .getLength()
-     * .updateArcLengths()
+     * parameters = {
+     *  clearcoat: <float>,
+     *  clearcoatMap: new THREE.Texture( <Image> ),
+     *  clearcoatRoughness: <float>,
+     *  clearcoatRoughnessMap: new THREE.Texture( <Image> ),
+     *  clearcoatNormalScale: <Vector2>,
+     *  clearcoatNormalMap: new THREE.Texture( <Image> ),
      *
-     * This following curves inherit from THREE.Curve:
+     *  ior: <float>,
+     *  reflectivity: <float>,
      *
-     * -- 2D curves --
-     * THREE.ArcCurve
-     * THREE.CubicBezierCurve
-     * THREE.EllipseCurve
-     * THREE.LineCurve
-     * THREE.QuadraticBezierCurve
-     * THREE.SplineCurve
+     *  sheen: <float>,
+     *  sheenColor: <Color>,
+     *  sheenColorMap: new THREE.Texture( <Image> ),
+     *  sheenRoughness: <float>,
+     *  sheenRoughnessMap: new THREE.Texture( <Image> ),
      *
-     * -- 3D curves --
-     * THREE.CatmullRomCurve3
-     * THREE.CubicBezierCurve3
-     * THREE.LineCurve3
-     * THREE.QuadraticBezierCurve3
+     *  transmission: <float>,
+     *  transmissionMap: new THREE.Texture( <Image> ),
      *
-     * A series of curves can be represented as a THREE.CurvePath.
+     *  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> )
+     * }
+     */
 
-    function Curve() {
+    class MeshPhysicalMaterial extends MeshStandardMaterial {
 
-       this.type = 'Curve';
+       constructor( parameters ) {
 
-       this.arcLengthDivisions = 200;
+               super();
 
-    }
+               this.defines = {
 
-    Object.assign( Curve.prototype, {
+                       'STANDARD': '',
+                       'PHYSICAL': ''
 
-       // Virtual base class method to overwrite and implement in subclasses
-       //      - t [0 .. 1]
+               };
 
-       getPoint: function ( /* t, optionalTarget */ ) {
+               this.type = 'MeshPhysicalMaterial';
 
-               console.warn( 'THREE.Curve: .getPoint() not implemented.' );
-               return null;
+               this.clearcoatMap = null;
+               this.clearcoatRoughness = 0.0;
+               this.clearcoatRoughnessMap = null;
+               this.clearcoatNormalScale = new Vector2( 1, 1 );
+               this.clearcoatNormalMap = null;
 
-       },
+               this.ior = 1.5;
 
-       // Get point at relative position in curve according to arc length
-       // - u [0 .. 1]
+               Object.defineProperty( this, 'reflectivity', {
+                       get: function () {
 
-       getPointAt: function ( u, optionalTarget ) {
+                               return ( clamp$1( 2.5 * ( this.ior - 1 ) / ( this.ior + 1 ), 0, 1 ) );
 
-               const t = this.getUtoTmapping( u );
-               return this.getPoint( t, optionalTarget );
+                       },
+                       set: function ( reflectivity ) {
 
-       },
+                               this.ior = ( 1 + 0.4 * reflectivity ) / ( 1 - 0.4 * reflectivity );
 
-       // Get sequence of points using getPoint( t )
+                       }
+               } );
 
-       getPoints: function ( divisions = 5 ) {
+               this.sheenColor = new Color( 0x000000 );
+               this.sheenColorMap = null;
+               this.sheenRoughness = 1.0;
+               this.sheenRoughnessMap = null;
 
-               const points = [];
+               this.transmissionMap = null;
 
-               for ( let d = 0; d <= divisions; d ++ ) {
+               this.thickness = 0.01;
+               this.thicknessMap = null;
+               this.attenuationDistance = 0.0;
+               this.attenuationColor = new Color( 1, 1, 1 );
 
-                       points.push( this.getPoint( d / divisions ) );
+               this.specularIntensity = 1.0;
+               this.specularIntensityMap = null;
+               this.specularColor = new Color( 1, 1, 1 );
+               this.specularColorMap = null;
 
-               }
+               this._sheen = 0.0;
+               this._clearcoat = 0;
+               this._transmission = 0;
 
-               return points;
+               this.setValues( parameters );
 
-       },
+       }
 
-       // Get sequence of points using getPointAt( u )
+       get sheen() {
 
-       getSpacedPoints: function ( divisions = 5 ) {
+               return this._sheen;
 
-               const points = [];
+       }
 
-               for ( let d = 0; d <= divisions; d ++ ) {
+       set sheen( value ) {
 
-                       points.push( this.getPointAt( d / divisions ) );
+               if ( this._sheen > 0 !== value > 0 ) {
+
+                       this.version ++;
 
                }
 
-               return points;
+               this._sheen = value;
 
-       },
+       }
 
-       // Get total curve arc length
+       get clearcoat() {
 
-       getLength: function () {
+               return this._clearcoat;
 
-               const lengths = this.getLengths();
-               return lengths[ lengths.length - 1 ];
+       }
 
-       },
+       set clearcoat( value ) {
 
-       // Get list of cumulative segment lengths
+               if ( this._clearcoat > 0 !== value > 0 ) {
 
-       getLengths: function ( divisions ) {
+                       this.version ++;
 
-               if ( divisions === undefined ) divisions = this.arcLengthDivisions;
+               }
 
-               if ( this.cacheArcLengths &&
-                       ( this.cacheArcLengths.length === divisions + 1 ) &&
-                       ! this.needsUpdate ) {
+               this._clearcoat = value;
 
-                       return this.cacheArcLengths;
+       }
 
-               }
+       get transmission() {
 
-               this.needsUpdate = false;
+               return this._transmission;
 
-               const cache = [];
-               let current, last = this.getPoint( 0 );
-               let sum = 0;
+       }
 
-               cache.push( 0 );
+       set transmission( value ) {
 
-               for ( let p = 1; p <= divisions; p ++ ) {
+               if ( this._transmission > 0 !== value > 0 ) {
 
-                       current = this.getPoint( p / divisions );
-                       sum += current.distanceTo( last );
-                       cache.push( sum );
-                       last = current;
+                       this.version ++;
 
                }
 
-               this.cacheArcLengths = cache;
+               this._transmission = value;
 
-               return cache; // { sums: cache, sum: sum }; Sum is in the last element.
+       }
 
-       },
+       copy( source ) {
 
-       updateArcLengths: function () {
+               super.copy( source );
 
-               this.needsUpdate = true;
-               this.getLengths();
+               this.defines = {
 
-       },
+                       'STANDARD': '',
+                       'PHYSICAL': ''
 
-       // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant
+               };
 
-       getUtoTmapping: function ( u, distance ) {
+               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 );
 
-               const arcLengths = this.getLengths();
+               this.ior = source.ior;
 
-               let i = 0;
-               const il = arcLengths.length;
+               this.sheen = source.sheen;
+               this.sheenColor.copy( source.sheenColor );
+               this.sheenColorMap = source.sheenColorMap;
+               this.sheenRoughness = source.sheenRoughness;
+               this.sheenRoughnessMap = source.sheenRoughnessMap;
 
-               let targetArcLength; // The targeted u distance value to get
+               this.transmission = source.transmission;
+               this.transmissionMap = source.transmissionMap;
 
-               if ( distance ) {
+               this.thickness = source.thickness;
+               this.thicknessMap = source.thicknessMap;
+               this.attenuationDistance = source.attenuationDistance;
+               this.attenuationColor.copy( source.attenuationColor );
 
-                       targetArcLength = distance;
+               this.specularIntensity = source.specularIntensity;
+               this.specularIntensityMap = source.specularIntensityMap;
+               this.specularColor.copy( source.specularColor );
+               this.specularColorMap = source.specularColorMap;
 
-               } else {
+               return this;
 
-                       targetArcLength = u * arcLengths[ il - 1 ];
+       }
 
-               }
+    }
 
-               // binary search for the index with largest value smaller than target u distance
+    MeshPhysicalMaterial.prototype.isMeshPhysicalMaterial = true;
 
-               let low = 0, high = il - 1, comparison;
+    /**
+     * 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>
+     * }
+     */
 
-               while ( low <= high ) {
+    class MeshPhongMaterial extends Material {
 
-                       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
+       constructor( parameters ) {
 
-                       comparison = arcLengths[ i ] - targetArcLength;
+               super();
 
-                       if ( comparison < 0 ) {
+               this.type = 'MeshPhongMaterial';
 
-                               low = i + 1;
+               this.color = new Color( 0xffffff ); // diffuse
+               this.specular = new Color( 0x111111 );
+               this.shininess = 30;
 
-                       } else if ( comparison > 0 ) {
+               this.map = null;
 
-                               high = i - 1;
+               this.lightMap = null;
+               this.lightMapIntensity = 1.0;
 
-                       } else {
+               this.aoMap = null;
+               this.aoMapIntensity = 1.0;
 
-                               high = i;
-                               break;
+               this.emissive = new Color( 0x000000 );
+               this.emissiveIntensity = 1.0;
+               this.emissiveMap = null;
 
-                               // DONE
+               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;
 
-               i = high;
+               this.specularMap = null;
 
-               if ( arcLengths[ i ] === targetArcLength ) {
+               this.alphaMap = null;
 
-                       return i / ( il - 1 );
+               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';
 
-               // we could get finer grain at lengths, or use simple interpolation between two points
+               this.flatShading = false;
 
-               const lengthBefore = arcLengths[ i ];
-               const lengthAfter = arcLengths[ i + 1 ];
+               this.setValues( parameters );
 
-               const segmentLength = lengthAfter - lengthBefore;
+       }
 
-               // determine where we are between the 'before' and 'after' points
+       copy( source ) {
 
-               const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;
+               super.copy( source );
 
-               // add that fractional amount to t
+               this.color.copy( source.color );
+               this.specular.copy( source.specular );
+               this.shininess = source.shininess;
 
-               const t = ( i + segmentFraction ) / ( il - 1 );
+               this.map = source.map;
 
-               return t;
+               this.lightMap = source.lightMap;
+               this.lightMapIntensity = source.lightMapIntensity;
 
-       },
+               this.aoMap = source.aoMap;
+               this.aoMapIntensity = source.aoMapIntensity;
 
-       // 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
+               this.emissive.copy( source.emissive );
+               this.emissiveMap = source.emissiveMap;
+               this.emissiveIntensity = source.emissiveIntensity;
 
-       getTangent: function ( t, optionalTarget ) {
+               this.bumpMap = source.bumpMap;
+               this.bumpScale = source.bumpScale;
 
-               const delta = 0.0001;
-               let t1 = t - delta;
-               let t2 = t + delta;
+               this.normalMap = source.normalMap;
+               this.normalMapType = source.normalMapType;
+               this.normalScale.copy( source.normalScale );
 
-               // Capping in case of danger
+               this.displacementMap = source.displacementMap;
+               this.displacementScale = source.displacementScale;
+               this.displacementBias = source.displacementBias;
 
-               if ( t1 < 0 ) t1 = 0;
-               if ( t2 > 1 ) t2 = 1;
+               this.specularMap = source.specularMap;
 
-               const pt1 = this.getPoint( t1 );
-               const pt2 = this.getPoint( t2 );
+               this.alphaMap = source.alphaMap;
 
-               const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() );
+               this.envMap = source.envMap;
+               this.combine = source.combine;
+               this.reflectivity = source.reflectivity;
+               this.refractionRatio = source.refractionRatio;
 
-               tangent.copy( pt2 ).sub( pt1 ).normalize();
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
+               this.wireframeLinecap = source.wireframeLinecap;
+               this.wireframeLinejoin = source.wireframeLinejoin;
 
-               return tangent;
+               this.flatShading = source.flatShading;
 
-       },
+               return this;
 
-       getTangentAt: function ( u, optionalTarget ) {
+       }
 
-               const t = this.getUtoTmapping( u );
-               return this.getTangent( t, optionalTarget );
+    }
 
-       },
+    MeshPhongMaterial.prototype.isMeshPhongMaterial = true;
 
-       computeFrenetFrames: function ( segments, closed ) {
+    /**
+     * 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>,
+     *
+     * }
+     */
 
-               // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
+    class MeshToonMaterial extends Material {
 
-               const normal = new Vector3();
+       constructor( parameters ) {
 
-               const tangents = [];
-               const normals = [];
-               const binormals = [];
+               super();
 
-               const vec = new Vector3();
-               const mat = new Matrix4();
+               this.defines = { 'TOON': '' };
 
-               // compute the tangent vectors for each segment on the curve
+               this.type = 'MeshToonMaterial';
 
-               for ( let i = 0; i <= segments; i ++ ) {
+               this.color = new Color( 0xffffff );
 
-                       const u = i / segments;
+               this.map = null;
+               this.gradientMap = null;
 
-                       tangents[ i ] = this.getTangentAt( u, new Vector3() );
-                       tangents[ i ].normalize();
+               this.lightMap = null;
+               this.lightMapIntensity = 1.0;
 
-               }
+               this.aoMap = null;
+               this.aoMapIntensity = 1.0;
 
-               // select an initial normal vector perpendicular to the first tangent vector,
-               // and in the direction of the minimum tangent xyz component
+               this.emissive = new Color( 0x000000 );
+               this.emissiveIntensity = 1.0;
+               this.emissiveMap = null;
 
-               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.bumpMap = null;
+               this.bumpScale = 1;
 
-               if ( tx <= min ) {
+               this.normalMap = null;
+               this.normalMapType = TangentSpaceNormalMap;
+               this.normalScale = new Vector2( 1, 1 );
 
-                       min = tx;
-                       normal.set( 1, 0, 0 );
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-               }
+               this.alphaMap = null;
 
-               if ( ty <= min ) {
+               this.wireframe = false;
+               this.wireframeLinewidth = 1;
+               this.wireframeLinecap = 'round';
+               this.wireframeLinejoin = 'round';
 
-                       min = ty;
-                       normal.set( 0, 1, 0 );
+               this.setValues( parameters );
 
-               }
+       }
 
-               if ( tz <= min ) {
+       copy( source ) {
 
-                       normal.set( 0, 0, 1 );
+               super.copy( source );
 
-               }
+               this.color.copy( source.color );
 
-               vec.crossVectors( tangents[ 0 ], normal ).normalize();
+               this.map = source.map;
+               this.gradientMap = source.gradientMap;
 
-               normals[ 0 ].crossVectors( tangents[ 0 ], vec );
-               binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );
+               this.lightMap = source.lightMap;
+               this.lightMapIntensity = source.lightMapIntensity;
 
+               this.aoMap = source.aoMap;
+               this.aoMapIntensity = source.aoMapIntensity;
 
-               // compute the slowly-varying normal and binormal vectors for each segment on the curve
+               this.emissive.copy( source.emissive );
+               this.emissiveMap = source.emissiveMap;
+               this.emissiveIntensity = source.emissiveIntensity;
 
-               for ( let i = 1; i <= segments; i ++ ) {
+               this.bumpMap = source.bumpMap;
+               this.bumpScale = source.bumpScale;
 
-                       normals[ i ] = normals[ i - 1 ].clone();
+               this.normalMap = source.normalMap;
+               this.normalMapType = source.normalMapType;
+               this.normalScale.copy( source.normalScale );
 
-                       binormals[ i ] = binormals[ i - 1 ].clone();
+               this.displacementMap = source.displacementMap;
+               this.displacementScale = source.displacementScale;
+               this.displacementBias = source.displacementBias;
 
-                       vec.crossVectors( tangents[ i - 1 ], tangents[ i ] );
+               this.alphaMap = source.alphaMap;
 
-                       if ( vec.length() > Number.EPSILON ) {
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
+               this.wireframeLinecap = source.wireframeLinecap;
+               this.wireframeLinejoin = source.wireframeLinejoin;
 
-                               vec.normalize();
+               return this;
 
-                               const theta = Math.acos( MathUtils.clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors
+       }
 
-                               normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );
+    }
 
-                       }
+    MeshToonMaterial.prototype.isMeshToonMaterial = true;
 
-                       binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+    /**
+     * 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 {
 
-               // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
+       constructor( parameters ) {
 
-               if ( closed === true ) {
+               super();
 
-                       let theta = Math.acos( MathUtils.clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) );
-                       theta /= segments;
+               this.type = 'MeshNormalMaterial';
 
-                       if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) {
+               this.bumpMap = null;
+               this.bumpScale = 1;
 
-                               theta = - theta;
+               this.normalMap = null;
+               this.normalMapType = TangentSpaceNormalMap;
+               this.normalScale = new Vector2( 1, 1 );
 
-                       }
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-                       for ( let i = 1; i <= segments; i ++ ) {
+               this.wireframe = false;
+               this.wireframeLinewidth = 1;
 
-                               // twist a little...
-                               normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );
-                               binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+               this.fog = false;
 
-                       }
+               this.flatShading = false;
 
-               }
+               this.setValues( parameters );
 
-               return {
-                       tangents: tangents,
-                       normals: normals,
-                       binormals: binormals
-               };
+       }
 
-       },
-
-       clone: function () {
-
-               return new this.constructor().copy( this );
-
-       },
-
-       copy: function ( source ) {
-
-               this.arcLengthDivisions = source.arcLengthDivisions;
-
-               return this;
-
-       },
-
-       toJSON: function () {
+       copy( source ) {
 
-               const data = {
-                       metadata: {
-                               version: 4.5,
-                               type: 'Curve',
-                               generator: 'Curve.toJSON'
-                       }
-               };
+               super.copy( source );
 
-               data.arcLengthDivisions = this.arcLengthDivisions;
-               data.type = this.type;
+               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: function ( json ) {
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
 
-               this.arcLengthDivisions = json.arcLengthDivisions;
+               this.flatShading = source.flatShading;
 
                return this;
 
        }
 
-    } );
-
-    function EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
-
-       Curve.call( this );
-
-       this.type = 'EllipseCurve';
-
-       this.aX = aX || 0;
-       this.aY = aY || 0;
-
-       this.xRadius = xRadius || 1;
-       this.yRadius = yRadius || 1;
+    }
 
-       this.aStartAngle = aStartAngle || 0;
-       this.aEndAngle = aEndAngle || 2 * Math.PI;
+    MeshNormalMaterial.prototype.isMeshNormalMaterial = true;
 
-       this.aClockwise = aClockwise || false;
+    /**
+     * 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>,
+     *
+     * }
+     */
 
-       this.aRotation = aRotation || 0;
+    class MeshLambertMaterial extends Material {
 
-    }
+       constructor( parameters ) {
 
-    EllipseCurve.prototype = Object.create( Curve.prototype );
-    EllipseCurve.prototype.constructor = EllipseCurve;
+               super();
 
-    EllipseCurve.prototype.isEllipseCurve = true;
+               this.type = 'MeshLambertMaterial';
 
-    EllipseCurve.prototype.getPoint = function ( t, optionalTarget ) {
+               this.color = new Color( 0xffffff ); // diffuse
 
-       const point = optionalTarget || new Vector2();
+               this.map = null;
 
-       const twoPi = Math.PI * 2;
-       let deltaAngle = this.aEndAngle - this.aStartAngle;
-       const samePoints = Math.abs( deltaAngle ) < Number.EPSILON;
+               this.lightMap = null;
+               this.lightMapIntensity = 1.0;
 
-       // ensures that deltaAngle is 0 .. 2 PI
-       while ( deltaAngle < 0 ) deltaAngle += twoPi;
-       while ( deltaAngle > twoPi ) deltaAngle -= twoPi;
+               this.aoMap = null;
+               this.aoMapIntensity = 1.0;
 
-       if ( deltaAngle < Number.EPSILON ) {
+               this.emissive = new Color( 0x000000 );
+               this.emissiveIntensity = 1.0;
+               this.emissiveMap = null;
 
-               if ( samePoints ) {
+               this.specularMap = null;
 
-                       deltaAngle = 0;
+               this.alphaMap = null;
 
-               } else {
+               this.envMap = null;
+               this.combine = MultiplyOperation;
+               this.reflectivity = 1;
+               this.refractionRatio = 0.98;
 
-                       deltaAngle = twoPi;
+               this.wireframe = false;
+               this.wireframeLinewidth = 1;
+               this.wireframeLinecap = 'round';
+               this.wireframeLinejoin = 'round';
 
-               }
+               this.setValues( parameters );
 
        }
 
-       if ( this.aClockwise === true && ! samePoints ) {
+       copy( source ) {
 
-               if ( deltaAngle === twoPi ) {
+               super.copy( source );
 
-                       deltaAngle = - twoPi;
+               this.color.copy( source.color );
 
-               } else {
+               this.map = source.map;
 
-                       deltaAngle = deltaAngle - twoPi;
+               this.lightMap = source.lightMap;
+               this.lightMapIntensity = source.lightMapIntensity;
 
-               }
+               this.aoMap = source.aoMap;
+               this.aoMapIntensity = source.aoMapIntensity;
 
-       }
+               this.emissive.copy( source.emissive );
+               this.emissiveMap = source.emissiveMap;
+               this.emissiveIntensity = source.emissiveIntensity;
 
-       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.specularMap = source.specularMap;
 
-       if ( this.aRotation !== 0 ) {
+               this.alphaMap = source.alphaMap;
 
-               const cos = Math.cos( this.aRotation );
-               const sin = Math.sin( this.aRotation );
+               this.envMap = source.envMap;
+               this.combine = source.combine;
+               this.reflectivity = source.reflectivity;
+               this.refractionRatio = source.refractionRatio;
 
-               const tx = x - this.aX;
-               const ty = y - this.aY;
+               this.wireframe = source.wireframe;
+               this.wireframeLinewidth = source.wireframeLinewidth;
+               this.wireframeLinecap = source.wireframeLinecap;
+               this.wireframeLinejoin = source.wireframeLinejoin;
 
-               // Rotate the point about the center of the ellipse.
-               x = tx * cos - ty * sin + this.aX;
-               y = tx * sin + ty * cos + this.aY;
+               return this;
 
        }
 
-       return point.set( x, y );
-
-    };
+    }
 
-    EllipseCurve.prototype.copy = function ( source ) {
+    MeshLambertMaterial.prototype.isMeshLambertMaterial = true;
 
-       Curve.prototype.copy.call( this, source );
+    /**
+     * 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>
+     * }
+     */
 
-       this.aX = source.aX;
-       this.aY = source.aY;
+    class MeshMatcapMaterial extends Material {
 
-       this.xRadius = source.xRadius;
-       this.yRadius = source.yRadius;
+       constructor( parameters ) {
 
-       this.aStartAngle = source.aStartAngle;
-       this.aEndAngle = source.aEndAngle;
+               super();
 
-       this.aClockwise = source.aClockwise;
+               this.defines = { 'MATCAP': '' };
 
-       this.aRotation = source.aRotation;
+               this.type = 'MeshMatcapMaterial';
 
-       return this;
+               this.color = new Color( 0xffffff ); // diffuse
 
-    };
+               this.matcap = null;
 
+               this.map = null;
 
-    EllipseCurve.prototype.toJSON = function () {
+               this.bumpMap = null;
+               this.bumpScale = 1;
 
-       const data = Curve.prototype.toJSON.call( this );
+               this.normalMap = null;
+               this.normalMapType = TangentSpaceNormalMap;
+               this.normalScale = new Vector2( 1, 1 );
 
-       data.aX = this.aX;
-       data.aY = this.aY;
+               this.displacementMap = null;
+               this.displacementScale = 1;
+               this.displacementBias = 0;
 
-       data.xRadius = this.xRadius;
-       data.yRadius = this.yRadius;
+               this.alphaMap = null;
 
-       data.aStartAngle = this.aStartAngle;
-       data.aEndAngle = this.aEndAngle;
+               this.flatShading = false;
 
-       data.aClockwise = this.aClockwise;
+               this.setValues( parameters );
 
-       data.aRotation = this.aRotation;
+       }
 
-       return data;
 
-    };
+       copy( source ) {
 
-    EllipseCurve.prototype.fromJSON = function ( json ) {
+               super.copy( source );
 
-       Curve.prototype.fromJSON.call( this, json );
+               this.defines = { 'MATCAP': '' };
 
-       this.aX = json.aX;
-       this.aY = json.aY;
+               this.color.copy( source.color );
 
-       this.xRadius = json.xRadius;
-       this.yRadius = json.yRadius;
+               this.matcap = source.matcap;
 
-       this.aStartAngle = json.aStartAngle;
-       this.aEndAngle = json.aEndAngle;
+               this.map = source.map;
 
-       this.aClockwise = json.aClockwise;
+               this.bumpMap = source.bumpMap;
+               this.bumpScale = source.bumpScale;
 
-       this.aRotation = json.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.alphaMap = source.alphaMap;
 
-    function ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+               this.flatShading = source.flatShading;
 
-       EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
+               return this;
 
-       this.type = 'ArcCurve';
+       }
 
     }
 
-    ArcCurve.prototype = Object.create( EllipseCurve.prototype );
-    ArcCurve.prototype.constructor = ArcCurve;
-
-    ArcCurve.prototype.isArcCurve = true;
+    MeshMatcapMaterial.prototype.isMeshMatcapMaterial = true;
 
     /**
-     * Centripetal CatmullRom Curve - which is useful for avoiding
-     * cusps and self-intersections in non-uniform catmull rom curves.
-     * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
+     * parameters = {
+     *  color: <hex>,
+     *  opacity: <float>,
      *
-     * curve.type accepts centripetal(default), chordal and catmullrom
-     * curve.tension is used for catmullrom which defaults to 0.5
+     *  linewidth: <float>,
+     *
+     *  scale: <float>,
+     *  dashSize: <float>,
+     *  gapSize: <float>
+     * }
      */
 
+    class LineDashedMaterial extends LineBasicMaterial {
 
-    /*
-    Based on an optimized c++ solution in
-     - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/
-     - http://ideone.com/NoEbVM
-
-    This CubicPoly class could be used for reusing some variables and calculations,
-    but for three.js curve use, it could be possible inlined and flatten into a single function call
-    which can be placed in CurveUtils.
-    */
+       constructor( parameters ) {
 
-    function CubicPoly() {
+               super();
 
-       let c0 = 0, c1 = 0, c2 = 0, c3 = 0;
+               this.type = 'LineDashedMaterial';
 
-       /*
-        * 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.scale = 1;
+               this.dashSize = 3;
+               this.gapSize = 1;
 
-               c0 = x0;
-               c1 = t0;
-               c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1;
-               c3 = 2 * x0 - 2 * x1 + t0 + t1;
+               this.setValues( parameters );
 
        }
 
-       return {
+       copy( source ) {
 
-               initCatmullRom: function ( x0, x1, x2, x3, tension ) {
+               super.copy( source );
 
-                       init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );
+               this.scale = source.scale;
+               this.dashSize = source.dashSize;
+               this.gapSize = source.gapSize;
 
-               },
+               return this;
 
-               initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) {
+       }
 
-                       // compute tangents when parameterized in [t1,t2]
-                       let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1;
-                       let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2;
+    }
 
-                       // rescale tangents for parametrization in [0,1]
-                       t1 *= dt1;
-                       t2 *= dt1;
+    LineDashedMaterial.prototype.isLineDashedMaterial = true;
 
-                       init( x1, x2, t1, t2 );
+    const AnimationUtils = {
 
-               },
+       // same as Array.prototype.slice, but also works on typed arrays
+       arraySlice: function ( array, from, to ) {
 
-               calc: function ( t ) {
+               if ( AnimationUtils.isTypedArray( array ) ) {
 
-                       const t2 = t * t;
-                       const t3 = t2 * t;
-                       return c0 + c1 * t + c2 * t2 + c3 * t3;
+                       // in ios9 array.subarray(from, undefined) will return empty array
+                       // but array.subarray(from) or array.subarray(from, len) is correct
+                       return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) );
 
                }
 
-       };
+               return array.slice( from, to );
 
-    }
+       },
 
-    //
+       // converts an array to a specific type
+       convertArray: function ( array, type, forceClone ) {
 
-    const tmp = new Vector3();
-    const px = new CubicPoly(), py = new CubicPoly(), pz = new CubicPoly();
+               if ( ! array || // let 'undefined' and 'null' pass
+                       ! forceClone && array.constructor === type ) return array;
 
-    function CatmullRomCurve3( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) {
+               if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {
 
-       Curve.call( this );
+                       return new type( array ); // create typed array
 
-       this.type = 'CatmullRomCurve3';
+               }
 
-       this.points = points;
-       this.closed = closed;
-       this.curveType = curveType;
-       this.tension = tension;
+               return Array.prototype.slice.call( array ); // create Array
 
-    }
+       },
 
-    CatmullRomCurve3.prototype = Object.create( Curve.prototype );
-    CatmullRomCurve3.prototype.constructor = CatmullRomCurve3;
+       isTypedArray: function ( object ) {
 
-    CatmullRomCurve3.prototype.isCatmullRomCurve3 = true;
+               return ArrayBuffer.isView( object ) &&
+                       ! ( object instanceof DataView );
 
-    CatmullRomCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+       },
 
-       const point = optionalTarget;
+       // returns an array by which times and values can be sorted
+       getKeyframeOrder: function ( times ) {
 
-       const points = this.points;
-       const l = points.length;
+               function compareTime( i, j ) {
 
-       const p = ( l - ( this.closed ? 0 : 1 ) ) * t;
-       let intPoint = Math.floor( p );
-       let weight = p - intPoint;
+                       return times[ i ] - times[ j ];
 
-       if ( this.closed ) {
+               }
 
-               intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l;
+               const n = times.length;
+               const result = new Array( n );
+               for ( let i = 0; i !== n; ++ i ) result[ i ] = i;
 
-       } else if ( weight === 0 && intPoint === l - 1 ) {
+               result.sort( compareTime );
 
-               intPoint = l - 2;
-               weight = 1;
+               return result;
 
-       }
+       },
 
-       let p0, p3; // 4 points (p1 & p2 defined below)
+       // uses the array previously returned by 'getKeyframeOrder' to sort data
+       sortedArray: function ( values, stride, order ) {
 
-       if ( this.closed || intPoint > 0 ) {
+               const nValues = values.length;
+               const result = new values.constructor( nValues );
 
-               p0 = points[ ( intPoint - 1 ) % l ];
+               for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {
 
-       } else {
+                       const srcOffset = order[ i ] * stride;
 
-               // extrapolate first point
-               tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
-               p0 = tmp;
+                       for ( let j = 0; j !== stride; ++ j ) {
 
-       }
+                               result[ dstOffset ++ ] = values[ srcOffset + j ];
 
-       const p1 = points[ intPoint % l ];
-       const p2 = points[ ( intPoint + 1 ) % l ];
+                       }
 
-       if ( this.closed || intPoint + 2 < l ) {
+               }
 
-               p3 = points[ ( intPoint + 2 ) % l ];
+               return result;
 
-       } else {
+       },
 
-               // extrapolate last point
-               tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] );
-               p3 = tmp;
+       // function for parsing AOS keyframe formats
+       flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {
 
-       }
+               let i = 1, key = jsonKeys[ 0 ];
 
-       if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) {
+               while ( key !== undefined && key[ valuePropertyName ] === undefined ) {
 
-               // 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 );
+                       key = jsonKeys[ i ++ ];
 
-               // safety check for repeated points
-               if ( dt1 < 1e-4 ) dt1 = 1.0;
-               if ( dt0 < 1e-4 ) dt0 = dt1;
-               if ( dt2 < 1e-4 ) dt2 = dt1;
+               }
 
-               px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 );
-               py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 );
-               pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 );
+               if ( key === undefined ) return; // no data
 
-       } else if ( this.curveType === 'catmullrom' ) {
+               let value = key[ valuePropertyName ];
+               if ( value === undefined ) return; // no data
 
-               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 );
+               if ( Array.isArray( value ) ) {
 
-       }
+                       do {
 
-       point.set(
-               px.calc( weight ),
-               py.calc( weight ),
-               pz.calc( weight )
-       );
+                               value = key[ valuePropertyName ];
 
-       return point;
+                               if ( value !== undefined ) {
 
-    };
+                                       times.push( key.time );
+                                       values.push.apply( values, value ); // push all elements
 
-    CatmullRomCurve3.prototype.copy = function ( source ) {
+                               }
 
-       Curve.prototype.copy.call( this, source );
+                               key = jsonKeys[ i ++ ];
 
-       this.points = [];
+                       } while ( key !== undefined );
 
-       for ( let i = 0, l = source.points.length; i < l; i ++ ) {
+               } else if ( value.toArray !== undefined ) {
 
-               const point = source.points[ i ];
+                       // ...assume THREE.Math-ish
 
-               this.points.push( point.clone() );
+                       do {
 
-       }
+                               value = key[ valuePropertyName ];
 
-       this.closed = source.closed;
-       this.curveType = source.curveType;
-       this.tension = source.tension;
+                               if ( value !== undefined ) {
 
-       return this;
+                                       times.push( key.time );
+                                       value.toArray( values, values.length );
 
-    };
+                               }
 
-    CatmullRomCurve3.prototype.toJSON = function () {
+                               key = jsonKeys[ i ++ ];
 
-       const data = Curve.prototype.toJSON.call( this );
+                       } while ( key !== undefined );
 
-       data.points = [];
+               } else {
 
-       for ( let i = 0, l = this.points.length; i < l; i ++ ) {
+                       // otherwise push as-is
 
-               const point = this.points[ i ];
-               data.points.push( point.toArray() );
+                       do {
 
-       }
+                               value = key[ valuePropertyName ];
 
-       data.closed = this.closed;
-       data.curveType = this.curveType;
-       data.tension = this.tension;
+                               if ( value !== undefined ) {
 
-       return data;
+                                       times.push( key.time );
+                                       values.push( value );
 
-    };
+                               }
 
-    CatmullRomCurve3.prototype.fromJSON = function ( json ) {
+                               key = jsonKeys[ i ++ ];
 
-       Curve.prototype.fromJSON.call( this, json );
+                       } while ( key !== undefined );
 
-       this.points = [];
+               }
 
-       for ( let i = 0, l = json.points.length; i < l; i ++ ) {
+       },
 
-               const point = json.points[ i ];
-               this.points.push( new Vector3().fromArray( point ) );
+       subclip: function ( sourceClip, name, startFrame, endFrame, fps = 30 ) {
 
-       }
+               const clip = sourceClip.clone();
 
-       this.closed = json.closed;
-       this.curveType = json.curveType;
-       this.tension = json.tension;
+               clip.name = name;
 
-       return this;
+               const tracks = [];
 
-    };
+               for ( let i = 0; i < clip.tracks.length; ++ i ) {
 
-    /**
-     * Bezier Curves formulas obtained from
-     * http://en.wikipedia.org/wiki/Bézier_curve
-     */
+                       const track = clip.tracks[ i ];
+                       const valueSize = track.getValueSize();
 
-    function CatmullRom( t, p0, p1, p2, p3 ) {
+                       const times = [];
+                       const values = [];
 
-       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;
+                       for ( let j = 0; j < track.times.length; ++ j ) {
 
-    }
+                               const frame = track.times[ j ] * fps;
 
-    //
+                               if ( frame < startFrame || frame >= endFrame ) continue;
 
-    function QuadraticBezierP0( t, p ) {
+                               times.push( track.times[ j ] );
 
-       const k = 1 - t;
-       return k * k * p;
+                               for ( let k = 0; k < valueSize; ++ k ) {
 
-    }
+                                       values.push( track.values[ j * valueSize + k ] );
 
-    function QuadraticBezierP1( t, p ) {
+                               }
 
-       return 2 * ( 1 - t ) * t * p;
+                       }
 
-    }
+                       if ( times.length === 0 ) continue;
 
-    function QuadraticBezierP2( t, p ) {
+                       track.times = AnimationUtils.convertArray( times, track.times.constructor );
+                       track.values = AnimationUtils.convertArray( values, track.values.constructor );
 
-       return t * t * p;
+                       tracks.push( track );
 
-    }
+               }
 
-    function QuadraticBezier( t, p0, p1, p2 ) {
+               clip.tracks = tracks;
 
-       return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) +
-               QuadraticBezierP2( t, p2 );
+               // find minimum .times value across all tracks in the trimmed clip
 
-    }
+               let minStartTime = Infinity;
 
-    //
+               for ( let i = 0; i < clip.tracks.length; ++ i ) {
 
-    function CubicBezierP0( t, p ) {
+                       if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) {
 
-       const k = 1 - t;
-       return k * k * k * p;
+                               minStartTime = clip.tracks[ i ].times[ 0 ];
 
-    }
+                       }
 
-    function CubicBezierP1( t, p ) {
+               }
 
-       const k = 1 - t;
-       return 3 * k * k * t * p;
+               // shift all tracks such that clip begins at t=0
 
-    }
+               for ( let i = 0; i < clip.tracks.length; ++ i ) {
 
-    function CubicBezierP2( t, p ) {
+                       clip.tracks[ i ].shift( - 1 * minStartTime );
 
-       return 3 * ( 1 - t ) * t * t * p;
+               }
 
-    }
+               clip.resetDuration();
 
-    function CubicBezierP3( t, p ) {
+               return clip;
 
-       return t * t * t * p;
+       },
 
-    }
+       makeClipAdditive: function ( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) {
 
-    function CubicBezier( t, p0, p1, p2, p3 ) {
+               if ( fps <= 0 ) fps = 30;
 
-       return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) +
-               CubicBezierP3( t, p3 );
+               const numTracks = referenceClip.tracks.length;
+               const referenceTime = referenceFrame / fps;
 
-    }
+               // Make each track's values relative to the values at the reference frame
+               for ( let i = 0; i < numTracks; ++ i ) {
+
+                       const referenceTrack = referenceClip.tracks[ i ];
+                       const referenceTrackType = referenceTrack.ValueTypeName;
 
-    function CubicBezierCurve( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) {
+                       // Skip this track if it's non-numeric
+                       if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue;
 
-       Curve.call( this );
+                       // Find the track in the target clip whose name and type matches the reference track
+                       const targetTrack = targetClip.tracks.find( function ( track ) {
 
-       this.type = 'CubicBezierCurve';
+                               return track.name === referenceTrack.name
+                                       && track.ValueTypeName === referenceTrackType;
 
-       this.v0 = v0;
-       this.v1 = v1;
-       this.v2 = v2;
-       this.v3 = v3;
+                       } );
 
-    }
+                       if ( targetTrack === undefined ) continue;
 
-    CubicBezierCurve.prototype = Object.create( Curve.prototype );
-    CubicBezierCurve.prototype.constructor = CubicBezierCurve;
+                       let referenceOffset = 0;
+                       const referenceValueSize = referenceTrack.getValueSize();
 
-    CubicBezierCurve.prototype.isCubicBezierCurve = true;
+                       if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
 
-    CubicBezierCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+                               referenceOffset = referenceValueSize / 3;
 
-       const point = optionalTarget;
+                       }
 
-       const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
+                       let targetOffset = 0;
+                       const targetValueSize = targetTrack.getValueSize();
 
-       point.set(
-               CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),
-               CubicBezier( t, v0.y, v1.y, v2.y, v3.y )
-       );
+                       if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
 
-       return point;
+                               targetOffset = targetValueSize / 3;
 
-    };
+                       }
 
-    CubicBezierCurve.prototype.copy = function ( source ) {
+                       const lastIndex = referenceTrack.times.length - 1;
+                       let referenceValue;
 
-       Curve.prototype.copy.call( this, source );
+                       // Find the value to subtract out of the track
+                       if ( referenceTime <= referenceTrack.times[ 0 ] ) {
 
-       this.v0.copy( source.v0 );
-       this.v1.copy( source.v1 );
-       this.v2.copy( source.v2 );
-       this.v3.copy( source.v3 );
+                               // 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 );
 
-    CubicBezierCurve.prototype.toJSON = function () {
+                       } else {
 
-       const data = Curve.prototype.toJSON.call( this );
+                               // 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 );
 
-       data.v0 = this.v0.toArray();
-       data.v1 = this.v1.toArray();
-       data.v2 = this.v2.toArray();
-       data.v3 = this.v3.toArray();
+                       }
 
-       return data;
+                       // Conjugate the quaternion
+                       if ( referenceTrackType === 'quaternion' ) {
 
-    };
+                               const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate();
+                               referenceQuat.toArray( referenceValue );
 
-    CubicBezierCurve.prototype.fromJSON = function ( json ) {
+                       }
 
-       Curve.prototype.fromJSON.call( this, json );
+                       // Subtract the reference value from all of the track values
 
-       this.v0.fromArray( json.v0 );
-       this.v1.fromArray( json.v1 );
-       this.v2.fromArray( json.v2 );
-       this.v3.fromArray( json.v3 );
+                       const numTimes = targetTrack.times.length;
+                       for ( let j = 0; j < numTimes; ++ j ) {
 
-       return this;
+                               const valueStart = j * targetValueSize + targetOffset;
 
-    };
+                               if ( referenceTrackType === 'quaternion' ) {
 
-    function CubicBezierCurve3( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) {
+                                       // Multiply the conjugate for quaternion track types
+                                       Quaternion.multiplyQuaternionsFlat(
+                                               targetTrack.values,
+                                               valueStart,
+                                               referenceValue,
+                                               0,
+                                               targetTrack.values,
+                                               valueStart
+                                       );
 
-       Curve.call( this );
+                               } else {
 
-       this.type = 'CubicBezierCurve3';
+                                       const valueEnd = targetValueSize - targetOffset * 2;
 
-       this.v0 = v0;
-       this.v1 = v1;
-       this.v2 = v2;
-       this.v3 = v3;
+                                       // Subtract each value for all other numeric track types
+                                       for ( let k = 0; k < valueEnd; ++ k ) {
 
-    }
+                                               targetTrack.values[ valueStart + k ] -= referenceValue[ k ];
 
-    CubicBezierCurve3.prototype = Object.create( Curve.prototype );
-    CubicBezierCurve3.prototype.constructor = CubicBezierCurve3;
+                                       }
 
-    CubicBezierCurve3.prototype.isCubicBezierCurve3 = true;
+                               }
 
-    CubicBezierCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+                       }
 
-       const point = optionalTarget;
+               }
 
-       const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
+               targetClip.blendMode = AdditiveAnimationBlendMode;
 
-       point.set(
-               CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),
-               CubicBezier( t, v0.y, v1.y, v2.y, v3.y ),
-               CubicBezier( t, v0.z, v1.z, v2.z, v3.z )
-       );
+               return targetClip;
 
-       return point;
+       }
 
     };
 
-    CubicBezierCurve3.prototype.copy = function ( source ) {
+    /**
+     * 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.copy.call( this, source );
+    class Interpolant {
 
-       this.v0.copy( source.v0 );
-       this.v1.copy( source.v1 );
-       this.v2.copy( source.v2 );
-       this.v3.copy( source.v3 );
+       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;
 
-    CubicBezierCurve3.prototype.toJSON = function () {
+               this.settings = null;
+               this.DefaultSettings_ = {};
 
-       const data = Curve.prototype.toJSON.call( this );
+       }
 
-       data.v0 = this.v0.toArray();
-       data.v1 = this.v1.toArray();
-       data.v2 = this.v2.toArray();
-       data.v3 = this.v3.toArray();
+       evaluate( t ) {
 
-       return data;
+               const pp = this.parameterPositions;
+               let i1 = this._cachedIndex,
+                       t1 = pp[ i1 ],
+                       t0 = pp[ i1 - 1 ];
 
-    };
+               validate_interval: {
 
-    CubicBezierCurve3.prototype.fromJSON = function ( json ) {
+                       seek: {
 
-       Curve.prototype.fromJSON.call( this, json );
+                               let right;
 
-       this.v0.fromArray( json.v0 );
-       this.v1.fromArray( json.v1 );
-       this.v2.fromArray( json.v2 );
-       this.v3.fromArray( json.v3 );
+                               linear_scan: {
 
-       return this;
+                                       //- 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; ; ) {
 
-    function LineCurve( v1 = new Vector2(), v2 = new Vector2() ) {
+                                                       if ( t1 === undefined ) {
 
-       Curve.call( this );
+                                                               if ( t < t0 ) break forward_scan;
 
-       this.type = 'LineCurve';
-
-       this.v1 = v1;
-       this.v2 = v2;
-
-    }
+                                                               // after end
 
-    LineCurve.prototype = Object.create( Curve.prototype );
-    LineCurve.prototype.constructor = LineCurve;
+                                                               i1 = pp.length;
+                                                               this._cachedIndex = i1;
+                                                               return this.afterEnd_( i1 - 1, t, t0 );
 
-    LineCurve.prototype.isLineCurve = true;
+                                                       }
 
-    LineCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+                                                       if ( i1 === giveUpAt ) break; // this loop
 
-       const point = optionalTarget;
+                                                       t0 = t1;
+                                                       t1 = pp[ ++ i1 ];
 
-       if ( t === 1 ) {
+                                                       if ( t < t1 ) {
 
-               point.copy( this.v2 );
+                                                               // we have arrived at the sought interval
+                                                               break seek;
 
-       } else {
+                                                       }
 
-               point.copy( this.v2 ).sub( this.v1 );
-               point.multiplyScalar( t ).add( this.v1 );
+                                               }
 
-       }
+                                               // prepare binary search on the right side of the index
+                                               right = pp.length;
+                                               break linear_scan;
 
-       return point;
+                                       }
 
-    };
+                                       //- slower code:
+                                       //-                                     if ( t < t0 || t0 === undefined ) {
+                                       if ( ! ( t >= t0 ) ) {
 
-    // Line curve is linear, so we can overwrite default getPointAt
+                                               // looping?
 
-    LineCurve.prototype.getPointAt = function ( u, optionalTarget ) {
+                                               const t1global = pp[ 1 ];
 
-       return this.getPoint( u, optionalTarget );
+                                               if ( t < t1global ) {
 
-    };
+                                                       i1 = 2; // + 1, using the scan for the details
+                                                       t0 = t1global;
 
-    LineCurve.prototype.getTangent = function ( t, optionalTarget ) {
+                                               }
 
-       const tangent = optionalTarget || new Vector2();
+                                               // linear reverse scan
 
-       tangent.copy( this.v2 ).sub( this.v1 ).normalize();
+                                               for ( let giveUpAt = i1 - 2; ; ) {
 
-       return tangent;
+                                                       if ( t0 === undefined ) {
 
-    };
+                                                               // before start
 
-    LineCurve.prototype.copy = function ( source ) {
+                                                               this._cachedIndex = 0;
+                                                               return this.beforeStart_( 0, t, t1 );
 
-       Curve.prototype.copy.call( this, source );
+                                                       }
 
-       this.v1.copy( source.v1 );
-       this.v2.copy( source.v2 );
+                                                       if ( i1 === giveUpAt ) break; // this loop
 
-       return this;
+                                                       t1 = t0;
+                                                       t0 = pp[ -- i1 - 1 ];
 
-    };
+                                                       if ( t >= t0 ) {
 
-    LineCurve.prototype.toJSON = function () {
+                                                               // we have arrived at the sought interval
+                                                               break seek;
 
-       const data = Curve.prototype.toJSON.call( this );
+                                                       }
 
-       data.v1 = this.v1.toArray();
-       data.v2 = this.v2.toArray();
+                                               }
 
-       return data;
+                                               // prepare binary search on the left side of the index
+                                               right = i1;
+                                               i1 = 0;
+                                               break linear_scan;
 
-    };
+                                       }
 
-    LineCurve.prototype.fromJSON = function ( json ) {
+                                       // the interval is valid
 
-       Curve.prototype.fromJSON.call( this, json );
+                                       break validate_interval;
 
-       this.v1.fromArray( json.v1 );
-       this.v2.fromArray( json.v2 );
+                               } // linear scan
 
-       return this;
+                               // binary search
 
-    };
+                               while ( i1 < right ) {
 
-    function LineCurve3( v1 = new Vector3(), v2 = new Vector3() ) {
+                                       const mid = ( i1 + right ) >>> 1;
 
-       Curve.call( this );
+                                       if ( t < pp[ mid ] ) {
 
-       this.type = 'LineCurve3';
+                                               right = mid;
 
-       this.v1 = v1;
-       this.v2 = v2;
+                                       } else {
 
-    }
+                                               i1 = mid + 1;
 
-    LineCurve3.prototype = Object.create( Curve.prototype );
-    LineCurve3.prototype.constructor = LineCurve3;
+                                       }
 
-    LineCurve3.prototype.isLineCurve3 = true;
+                               }
 
-    LineCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+                               t1 = pp[ i1 ];
+                               t0 = pp[ i1 - 1 ];
 
-       const point = optionalTarget;
+                               // check boundary cases, again
 
-       if ( t === 1 ) {
+                               if ( t0 === undefined ) {
 
-               point.copy( this.v2 );
+                                       this._cachedIndex = 0;
+                                       return this.beforeStart_( 0, t, t1 );
 
-       } else {
+                               }
 
-               point.copy( this.v2 ).sub( this.v1 );
-               point.multiplyScalar( t ).add( this.v1 );
+                               if ( t1 === undefined ) {
 
-       }
+                                       i1 = pp.length;
+                                       this._cachedIndex = i1;
+                                       return this.afterEnd_( i1 - 1, t0, t );
 
-       return point;
+                               }
 
-    };
+                       } // seek
 
-    // Line curve is linear, so we can overwrite default getPointAt
+                       this._cachedIndex = i1;
 
-    LineCurve3.prototype.getPointAt = function ( u, optionalTarget ) {
+                       this.intervalChanged_( i1, t0, t1 );
 
-       return this.getPoint( u, optionalTarget );
+               } // validate_interval
 
-    };
+               return this.interpolate_( i1, t0, t, t1 );
 
-    LineCurve3.prototype.copy = function ( source ) {
+       }
 
-       Curve.prototype.copy.call( this, source );
+       getSettings_() {
 
-       this.v1.copy( source.v1 );
-       this.v2.copy( source.v2 );
+               return this.settings || this.DefaultSettings_;
 
-       return this;
+       }
 
-    };
+       copySampleValue_( index ) {
 
-    LineCurve3.prototype.toJSON = function () {
+               // copies a sample value to the result buffer
 
-       const data = Curve.prototype.toJSON.call( this );
+               const result = this.resultBuffer,
+                       values = this.sampleValues,
+                       stride = this.valueSize,
+                       offset = index * stride;
 
-       data.v1 = this.v1.toArray();
-       data.v2 = this.v2.toArray();
+               for ( let i = 0; i !== stride; ++ i ) {
 
-       return data;
+                       result[ i ] = values[ offset + i ];
 
-    };
+               }
 
-    LineCurve3.prototype.fromJSON = function ( json ) {
+               return result;
 
-       Curve.prototype.fromJSON.call( this, json );
+       }
 
-       this.v1.fromArray( json.v1 );
-       this.v2.fromArray( json.v2 );
+       // Template methods for derived classes:
 
-       return this;
+       interpolate_( /* i1, t0, t, t1 */ ) {
 
-    };
+               throw new Error( 'call to abstract method' );
+               // implementations shall return this.resultBuffer
 
-    function QuadraticBezierCurve( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) {
+       }
 
-       Curve.call( this );
+       intervalChanged_( /* i1, t0, t1 */ ) {
 
-       this.type = 'QuadraticBezierCurve';
+               // empty
 
-       this.v0 = v0;
-       this.v1 = v1;
-       this.v2 = v2;
+       }
 
     }
 
-    QuadraticBezierCurve.prototype = Object.create( Curve.prototype );
-    QuadraticBezierCurve.prototype.constructor = QuadraticBezierCurve;
+    // ALIAS DEFINITIONS
 
-    QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true;
-
-    QuadraticBezierCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+    Interpolant.prototype.beforeStart_ = Interpolant.prototype.copySampleValue_;
+    Interpolant.prototype.afterEnd_ = Interpolant.prototype.copySampleValue_;
 
-       const point = optionalTarget;
-
-       const v0 = this.v0, v1 = this.v1, v2 = this.v2;
+    /**
+     * 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.
+     */
 
-       point.set(
-               QuadraticBezier( t, v0.x, v1.x, v2.x ),
-               QuadraticBezier( t, v0.y, v1.y, v2.y )
-       );
+    class CubicInterpolant extends Interpolant {
 
-       return point;
+       constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
 
-    };
+               super( parameterPositions, sampleValues, sampleSize, resultBuffer );
 
-    QuadraticBezierCurve.prototype.copy = function ( source ) {
+               this._weightPrev = - 0;
+               this._offsetPrev = - 0;
+               this._weightNext = - 0;
+               this._offsetNext = - 0;
 
-       Curve.prototype.copy.call( this, source );
+               this.DefaultSettings_ = {
 
-       this.v0.copy( source.v0 );
-       this.v1.copy( source.v1 );
-       this.v2.copy( source.v2 );
+                       endingStart: ZeroCurvatureEnding,
+                       endingEnd: ZeroCurvatureEnding
 
-       return this;
+               };
 
-    };
+       }
 
-    QuadraticBezierCurve.prototype.toJSON = function () {
+       intervalChanged_( i1, t0, t1 ) {
 
-       const data = Curve.prototype.toJSON.call( this );
+               const pp = this.parameterPositions;
+               let iPrev = i1 - 2,
+                       iNext = i1 + 1,
 
-       data.v0 = this.v0.toArray();
-       data.v1 = this.v1.toArray();
-       data.v2 = this.v2.toArray();
+                       tPrev = pp[ iPrev ],
+                       tNext = pp[ iNext ];
 
-       return data;
+               if ( tPrev === undefined ) {
 
-    };
+                       switch ( this.getSettings_().endingStart ) {
 
-    QuadraticBezierCurve.prototype.fromJSON = function ( json ) {
+                               case ZeroSlopeEnding:
 
-       Curve.prototype.fromJSON.call( this, json );
+                                       // f'(t0) = 0
+                                       iPrev = i1;
+                                       tPrev = 2 * t0 - t1;
 
-       this.v0.fromArray( json.v0 );
-       this.v1.fromArray( json.v1 );
-       this.v2.fromArray( json.v2 );
+                                       break;
 
-       return this;
+                               case WrapAroundEnding:
 
-    };
+                                       // use the other end of the curve
+                                       iPrev = pp.length - 2;
+                                       tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ];
 
-    function QuadraticBezierCurve3( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) {
+                                       break;
 
-       Curve.call( this );
+                               default: // ZeroCurvatureEnding
 
-       this.type = 'QuadraticBezierCurve3';
+                                       // f''(t0) = 0 a.k.a. Natural Spline
+                                       iPrev = i1;
+                                       tPrev = t1;
 
-       this.v0 = v0;
-       this.v1 = v1;
-       this.v2 = v2;
+                       }
 
-    }
+               }
 
-    QuadraticBezierCurve3.prototype = Object.create( Curve.prototype );
-    QuadraticBezierCurve3.prototype.constructor = QuadraticBezierCurve3;
+               if ( tNext === undefined ) {
 
-    QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true;
+                       switch ( this.getSettings_().endingEnd ) {
 
-    QuadraticBezierCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+                               case ZeroSlopeEnding:
 
-       const point = optionalTarget;
+                                       // f'(tN) = 0
+                                       iNext = i1;
+                                       tNext = 2 * t1 - t0;
 
-       const v0 = this.v0, v1 = this.v1, v2 = this.v2;
+                                       break;
 
-       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 )
-       );
+                               case WrapAroundEnding:
 
-       return point;
+                                       // use the other end of the curve
+                                       iNext = 1;
+                                       tNext = t1 + pp[ 1 ] - pp[ 0 ];
 
-    };
+                                       break;
 
-    QuadraticBezierCurve3.prototype.copy = function ( source ) {
+                               default: // ZeroCurvatureEnding
 
-       Curve.prototype.copy.call( this, source );
+                                       // f''(tN) = 0, a.k.a. Natural Spline
+                                       iNext = i1 - 1;
+                                       tNext = t0;
 
-       this.v0.copy( source.v0 );
-       this.v1.copy( source.v1 );
-       this.v2.copy( source.v2 );
+                       }
 
-       return this;
+               }
 
-    };
+               const halfDt = ( t1 - t0 ) * 0.5,
+                       stride = this.valueSize;
 
-    QuadraticBezierCurve3.prototype.toJSON = function () {
+               this._weightPrev = halfDt / ( t0 - tPrev );
+               this._weightNext = halfDt / ( tNext - t1 );
+               this._offsetPrev = iPrev * stride;
+               this._offsetNext = iNext * stride;
 
-       const data = Curve.prototype.toJSON.call( this );
+       }
 
-       data.v0 = this.v0.toArray();
-       data.v1 = this.v1.toArray();
-       data.v2 = this.v2.toArray();
+       interpolate_( i1, t0, t, t1 ) {
 
-       return data;
+               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,
 
-    QuadraticBezierCurve3.prototype.fromJSON = function ( json ) {
+                       p = ( t - t0 ) / ( t1 - t0 ),
+                       pp = p * p,
+                       ppp = pp * p;
 
-       Curve.prototype.fromJSON.call( this, json );
+               // evaluate polynomials
 
-       this.v0.fromArray( json.v0 );
-       this.v1.fromArray( json.v1 );
-       this.v2.fromArray( json.v2 );
+               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;
 
-       return this;
+               // combine data linearly
 
-    };
+               for ( let i = 0; i !== stride; ++ i ) {
 
-    function SplineCurve( points = [] ) {
+                       result[ i ] =
+                                       sP * values[ oP + i ] +
+                                       s0 * values[ o0 + i ] +
+                                       s1 * values[ o1 + i ] +
+                                       sN * values[ oN + i ];
 
-       Curve.call( this );
+               }
 
-       this.type = 'SplineCurve';
+               return result;
 
-       this.points = points;
+       }
 
     }
 
-    SplineCurve.prototype = Object.create( Curve.prototype );
-    SplineCurve.prototype.constructor = SplineCurve;
+    class LinearInterpolant extends Interpolant {
 
-    SplineCurve.prototype.isSplineCurve = true;
-
-    SplineCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
-
-       const point = optionalTarget;
-
-       const points = this.points;
-       const p = ( points.length - 1 ) * t;
+       constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
 
-       const intPoint = Math.floor( p );
-       const weight = p - intPoint;
+               super( parameterPositions, sampleValues, sampleSize, resultBuffer );
 
-       const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ];
-       const p1 = points[ intPoint ];
-       const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ];
-       const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ];
-
-       point.set(
-               CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ),
-               CatmullRom( weight, p0.y, p1.y, p2.y, p3.y )
-       );
+       }
 
-       return point;
+       interpolate_( i1, t0, t, t1 ) {
 
-    };
+               const result = this.resultBuffer,
+                       values = this.sampleValues,
+                       stride = this.valueSize,
 
-    SplineCurve.prototype.copy = function ( source ) {
+                       offset1 = i1 * stride,
+                       offset0 = offset1 - stride,
 
-       Curve.prototype.copy.call( this, source );
+                       weight1 = ( t - t0 ) / ( t1 - t0 ),
+                       weight0 = 1 - weight1;
 
-       this.points = [];
+               for ( let i = 0; i !== stride; ++ i ) {
 
-       for ( let i = 0, l = source.points.length; i < l; i ++ ) {
+                       result[ i ] =
+                                       values[ offset0 + i ] * weight0 +
+                                       values[ offset1 + i ] * weight1;
 
-               const point = source.points[ i ];
+               }
 
-               this.points.push( point.clone() );
+               return result;
 
        }
 
-       return this;
-
-    };
-
-    SplineCurve.prototype.toJSON = function () {
+    }
 
-       const data = Curve.prototype.toJSON.call( this );
+    /**
+     *
+     * Interpolant that evaluates to the sample value at the position preceeding
+     * the parameter.
+     */
 
-       data.points = [];
+    class DiscreteInterpolant extends Interpolant {
 
-       for ( let i = 0, l = this.points.length; i < l; i ++ ) {
+       constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
 
-               const point = this.points[ i ];
-               data.points.push( point.toArray() );
+               super( parameterPositions, sampleValues, sampleSize, resultBuffer );
 
        }
 
-       return data;
-
-    };
+       interpolate_( i1 /*, t0, t, t1 */ ) {
 
-    SplineCurve.prototype.fromJSON = function ( json ) {
+               return this.copySampleValue_( i1 - 1 );
 
-       Curve.prototype.fromJSON.call( this, json );
+       }
 
-       this.points = [];
+    }
 
-       for ( let i = 0, l = json.points.length; i < l; i ++ ) {
+    class KeyframeTrack {
 
-               const point = json.points[ i ];
-               this.points.push( new Vector2().fromArray( point ) );
+       constructor( name, times, values, interpolation ) {
 
-       }
+               if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' );
+               if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name );
 
-       return this;
+               this.name = name;
 
-    };
+               this.times = AnimationUtils.convertArray( times, this.TimeBufferType );
+               this.values = AnimationUtils.convertArray( values, this.ValueBufferType );
 
-    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
-    });
+               this.setInterpolation( interpolation || this.DefaultInterpolation );
 
-    /**************************************************************
-     * Curved Path - a curve path is simply a array of connected
-     *  curves, but retains the api of a curve
-     **************************************************************/
+       }
 
-    function CurvePath() {
+       // Serialization (in static context, because of constructor invocation
+       // and automatic invocation of .toJSON):
 
-       Curve.call( this );
+       static toJSON( track ) {
 
-       this.type = 'CurvePath';
+               const trackType = track.constructor;
 
-       this.curves = [];
-       this.autoClose = false; // Automatically closes the path
+               let json;
 
-    }
+               // derived classes can define a static toJSON method
+               if ( trackType.toJSON !== this.toJSON ) {
 
-    CurvePath.prototype = Object.assign( Object.create( Curve.prototype ), {
+                       json = trackType.toJSON( track );
 
-       constructor: CurvePath,
+               } else {
 
-       add: function ( curve ) {
+                       // by default, we assume the data can be serialized as-is
+                       json = {
 
-               this.curves.push( curve );
+                               'name': track.name,
+                               'times': AnimationUtils.convertArray( track.times, Array ),
+                               'values': AnimationUtils.convertArray( track.values, Array )
 
-       },
+                       };
 
-       closePath: function () {
+                       const interpolation = track.getInterpolation();
 
-               // 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 ( interpolation !== track.DefaultInterpolation ) {
 
-               if ( ! startPoint.equals( endPoint ) ) {
+                               json.interpolation = interpolation;
 
-                       this.curves.push( new LineCurve( endPoint, startPoint ) );
+                       }
 
                }
 
-       },
+               json.type = track.ValueTypeName; // mandatory
 
-       // To get accurate point with reference to
-       // entire path distance at time t,
-       // following has to be done:
+               return json;
 
-       // 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 ) {
+       InterpolantFactoryMethodDiscrete( result ) {
 
-               const d = t * this.getLength();
-               const curveLengths = this.getCurveLengths();
-               let i = 0;
+               return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );
 
-               // To think about boundaries points.
+       }
 
-               while ( i < curveLengths.length ) {
+       InterpolantFactoryMethodLinear( result ) {
 
-                       if ( curveLengths[ i ] >= d ) {
+               return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );
 
-                               const diff = curveLengths[ i ] - d;
-                               const curve = this.curves[ i ];
+       }
 
-                               const segmentLength = curve.getLength();
-                               const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;
+       InterpolantFactoryMethodSmooth( result ) {
 
-                               return curve.getPointAt( u );
+               return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );
 
-                       }
+       }
 
-                       i ++;
+       setInterpolation( interpolation ) {
 
-               }
+               let factoryMethod;
 
-               return null;
+               switch ( interpolation ) {
 
-               // loop where sum != 0, sum > d , sum+1 <d
+                       case InterpolateDiscrete:
 
-       },
+                               factoryMethod = this.InterpolantFactoryMethodDiscrete;
 
-       // 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
+                               break;
 
-       getLength: function () {
+                       case InterpolateLinear:
 
-               const lens = this.getCurveLengths();
-               return lens[ lens.length - 1 ];
+                               factoryMethod = this.InterpolantFactoryMethodLinear;
 
-       },
+                               break;
 
-       // cacheLengths must be recalculated.
-       updateArcLengths: function () {
+                       case InterpolateSmooth:
 
-               this.needsUpdate = true;
-               this.cacheLengths = null;
-               this.getCurveLengths();
+                               factoryMethod = this.InterpolantFactoryMethodSmooth;
 
-       },
+                               break;
 
-       // Compute lengths and cache them
-       // We cannot overwrite getLengths() because UtoT mapping uses it.
+               }
 
-       getCurveLengths: function () {
+               if ( factoryMethod === undefined ) {
 
-               // We use cache values if curves and cache array are same length
+                       const message = 'unsupported interpolation for ' +
+                               this.ValueTypeName + ' keyframe track named ' + this.name;
 
-               if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) {
+                       if ( this.createInterpolant === undefined ) {
 
-                       return this.cacheLengths;
+                               // fall back to default, unless the default itself is messed up
+                               if ( interpolation !== this.DefaultInterpolation ) {
 
-               }
+                                       this.setInterpolation( this.DefaultInterpolation );
 
-               // Get length of sub-curve
-               // Push sums into cached array
+                               } else {
 
-               const lengths = [];
-               let sums = 0;
+                                       throw new Error( message ); // fatal, in this case
 
-               for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
+                               }
 
-                       sums += this.curves[ i ].getLength();
-                       lengths.push( sums );
+                       }
 
-               }
+                       console.warn( 'THREE.KeyframeTrack:', message );
+                       return this;
 
-               this.cacheLengths = lengths;
+               }
 
-               return lengths;
+               this.createInterpolant = factoryMethod;
 
-       },
+               return this;
 
-       getSpacedPoints: function ( divisions = 40 ) {
+       }
 
-               const points = [];
+       getInterpolation() {
 
-               for ( let i = 0; i <= divisions; i ++ ) {
+               switch ( this.createInterpolant ) {
 
-                       points.push( this.getPoint( i / divisions ) );
+                       case this.InterpolantFactoryMethodDiscrete:
 
-               }
+                               return InterpolateDiscrete;
 
-               if ( this.autoClose ) {
+                       case this.InterpolantFactoryMethodLinear:
 
-                       points.push( points[ 0 ] );
+                               return InterpolateLinear;
 
-               }
+                       case this.InterpolantFactoryMethodSmooth:
 
-               return points;
+                               return InterpolateSmooth;
 
-       },
+               }
 
-       getPoints: function ( divisions = 12 ) {
+       }
 
-               const points = [];
-               let last;
+       getValueSize() {
 
-               for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) {
+               return this.values.length / this.times.length;
 
-                       const curve = curves[ i ];
-                       const resolution = ( curve && curve.isEllipseCurve ) ? divisions * 2
-                               : ( curve && ( curve.isLineCurve || curve.isLineCurve3 ) ) ? 1
-                                       : ( curve && curve.isSplineCurve ) ? divisions * curve.points.length
-                                               : divisions;
+       }
 
-                       const pts = curve.getPoints( resolution );
+       // move all keyframes either forwards or backwards in time
+       shift( timeOffset ) {
 
-                       for ( let j = 0; j < pts.length; j ++ ) {
+               if ( timeOffset !== 0.0 ) {
 
-                               const point = pts[ j ];
+                       const times = this.times;
 
-                               if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates
+                       for ( let i = 0, n = times.length; i !== n; ++ i ) {
 
-                               points.push( point );
-                               last = point;
+                               times[ i ] += timeOffset;
 
                        }
 
                }
 
-               if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) {
-
-                       points.push( points[ 0 ] );
-
-               }
-
-               return points;
+               return this;
 
-       },
+       }
 
-       copy: function ( source ) {
+       // scale all keyframe times by a factor (useful for frame <-> seconds conversions)
+       scale( timeScale ) {
 
-               Curve.prototype.copy.call( this, source );
+               if ( timeScale !== 1.0 ) {
 
-               this.curves = [];
+                       const times = this.times;
 
-               for ( let i = 0, l = source.curves.length; i < l; i ++ ) {
+                       for ( let i = 0, n = times.length; i !== n; ++ i ) {
 
-                       const curve = source.curves[ i ];
+                               times[ i ] *= timeScale;
 
-                       this.curves.push( curve.clone() );
+                       }
 
                }
 
-               this.autoClose = source.autoClose;
-
                return this;
 
-       },
+       }
 
-       toJSON: function () {
+       // 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 ) {
 
-               const data = Curve.prototype.toJSON.call( this );
+               const times = this.times,
+                       nKeys = times.length;
 
-               data.autoClose = this.autoClose;
-               data.curves = [];
+               let from = 0,
+                       to = nKeys - 1;
 
-               for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
+               while ( from !== nKeys && times[ from ] < startTime ) {
 
-                       const curve = this.curves[ i ];
-                       data.curves.push( curve.toJSON() );
+                       ++ from;
 
                }
 
-               return data;
+               while ( to !== - 1 && times[ to ] > endTime ) {
 
-       },
+                       -- to;
 
-       fromJSON: function ( json ) {
+               }
 
-               Curve.prototype.fromJSON.call( this, json );
+               ++ to; // inclusive -> exclusive bound
 
-               this.autoClose = json.autoClose;
-               this.curves = [];
+               if ( from !== 0 || to !== nKeys ) {
 
-               for ( let i = 0, l = json.curves.length; i < l; i ++ ) {
+                       // empty tracks are forbidden, so keep at least one keyframe
+                       if ( from >= to ) {
 
-                       const curve = json.curves[ i ];
-                       this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) );
+                               to = Math.max( to, 1 );
+                               from = to - 1;
+
+                       }
+
+                       const stride = this.getValueSize();
+                       this.times = AnimationUtils.arraySlice( times, from, to );
+                       this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride );
 
                }
 
 
        }
 
-    } );
+       // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
+       validate() {
 
-    function Path( points ) {
+               let valid = true;
 
-       CurvePath.call( this );
+               const valueSize = this.getValueSize();
+               if ( valueSize - Math.floor( valueSize ) !== 0 ) {
 
-       this.type = 'Path';
+                       console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
+                       valid = false;
 
-       this.currentPoint = new Vector2();
+               }
 
-       if ( points ) {
+               const times = this.times,
+                       values = this.values,
 
-               this.setFromPoints( points );
+                       nKeys = times.length;
 
-       }
+               if ( nKeys === 0 ) {
 
-    }
+                       console.error( 'THREE.KeyframeTrack: Track is empty.', this );
+                       valid = false;
 
-    Path.prototype = Object.assign( Object.create( CurvePath.prototype ), {
+               }
 
-       constructor: Path,
+               let prevTime = null;
 
-       setFromPoints: function ( points ) {
+               for ( let i = 0; i !== nKeys; i ++ ) {
 
-               this.moveTo( points[ 0 ].x, points[ 0 ].y );
+                       const currTime = times[ i ];
 
-               for ( let i = 1, l = points.length; i < l; i ++ ) {
+                       if ( typeof currTime === 'number' && isNaN( currTime ) ) {
 
-                       this.lineTo( points[ i ].x, points[ i ].y );
+                               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;
 
-       moveTo: function ( x, y ) {
+                       }
 
-               this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying?
+                       prevTime = currTime;
 
-               return this;
+               }
 
-       },
+               if ( values !== undefined ) {
 
-       lineTo: function ( x, y ) {
+                       if ( AnimationUtils.isTypedArray( values ) ) {
 
-               const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) );
-               this.curves.push( curve );
+                               for ( let i = 0, n = values.length; i !== n; ++ i ) {
 
-               this.currentPoint.set( x, y );
+                                       const value = values[ i ];
 
-               return this;
+                                       if ( isNaN( value ) ) {
 
-       },
+                                               console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
+                                               valid = false;
+                                               break;
 
-       quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
+                                       }
 
-               const curve = new QuadraticBezierCurve(
-                       this.currentPoint.clone(),
-                       new Vector2( aCPx, aCPy ),
-                       new Vector2( aX, aY )
-               );
+                               }
 
-               this.curves.push( curve );
+                       }
 
-               this.currentPoint.set( aX, aY );
+               }
 
-               return this;
+               return valid;
 
-       },
+       }
 
-       bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
+       // 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() {
 
-               const curve = new CubicBezierCurve(
-                       this.currentPoint.clone(),
-                       new Vector2( aCP1x, aCP1y ),
-                       new Vector2( aCP2x, aCP2y ),
-                       new Vector2( aX, aY )
-               );
+               // 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(),
 
-               this.curves.push( curve );
+                       smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
 
-               this.currentPoint.set( aX, aY );
+                       lastIndex = times.length - 1;
 
-               return this;
+               let writeIndex = 1;
 
-       },
+               for ( let i = 1; i < lastIndex; ++ i ) {
 
-       splineThru: function ( pts /*Array of Vector*/ ) {
+                       let keep = false;
 
-               const npts = [ this.currentPoint.clone() ].concat( pts );
+                       const time = times[ i ];
+                       const timeNext = times[ i + 1 ];
 
-               const curve = new SplineCurve( npts );
-               this.curves.push( curve );
+                       // remove adjacent keyframes scheduled at the same time
 
-               this.currentPoint.copy( pts[ pts.length - 1 ] );
+                       if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) {
 
-               return this;
+                               if ( ! smoothInterpolation ) {
 
-       },
+                                       // remove unnecessary keyframes same as their neighbors
 
-       arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+                                       const offset = i * stride,
+                                               offsetP = offset - stride,
+                                               offsetN = offset + stride;
 
-               const x0 = this.currentPoint.x;
-               const y0 = this.currentPoint.y;
+                                       for ( let j = 0; j !== stride; ++ j ) {
 
-               this.absarc( aX + x0, aY + y0, aRadius,
-                       aStartAngle, aEndAngle, aClockwise );
+                                               const value = values[ offset + j ];
 
-               return this;
+                                               if ( value !== values[ offsetP + j ] ||
+                                                       value !== values[ offsetN + j ] ) {
 
-       },
+                                                       keep = true;
+                                                       break;
 
-       absarc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+                                               }
 
-               this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
+                                       }
 
-               return this;
+                               } else {
 
-       },
+                                       keep = true;
 
-       ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
+                               }
 
-               const x0 = this.currentPoint.x;
-               const y0 = this.currentPoint.y;
+                       }
 
-               this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
+                       // in-place compaction
 
-               return this;
+                       if ( keep ) {
 
-       },
+                               if ( i !== writeIndex ) {
 
-       absellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
+                                       times[ writeIndex ] = times[ i ];
 
-               const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
+                                       const readOffset = i * stride,
+                                               writeOffset = writeIndex * stride;
 
-               if ( this.curves.length > 0 ) {
+                                       for ( let j = 0; j !== stride; ++ j ) {
 
-                       // if a previous curve is present, attempt to join
-                       const firstPoint = curve.getPoint( 0 );
+                                               values[ writeOffset + j ] = values[ readOffset + j ];
 
-                       if ( ! firstPoint.equals( this.currentPoint ) ) {
+                                       }
 
-                               this.lineTo( firstPoint.x, firstPoint.y );
+                               }
+
+                               ++ writeIndex;
 
                        }
 
                }
 
-               this.curves.push( curve );
-
-               const lastPoint = curve.getPoint( 1 );
-               this.currentPoint.copy( lastPoint );
-
-               return this;
-
-       },
-
-       copy: function ( source ) {
+               // flush last keyframe (compaction looks ahead)
 
-               CurvePath.prototype.copy.call( this, source );
+               if ( lastIndex > 0 ) {
 
-               this.currentPoint.copy( source.currentPoint );
+                       times[ writeIndex ] = times[ lastIndex ];
 
-               return this;
+                       for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
 
-       },
+                               values[ writeOffset + j ] = values[ readOffset + j ];
 
-       toJSON: function () {
+                       }
 
-               const data = CurvePath.prototype.toJSON.call( this );
+                       ++ writeIndex;
 
-               data.currentPoint = this.currentPoint.toArray();
+               }
 
-               return data;
+               if ( writeIndex !== times.length ) {
 
-       },
+                       this.times = AnimationUtils.arraySlice( times, 0, writeIndex );
+                       this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride );
 
-       fromJSON: function ( json ) {
+               } else {
 
-               CurvePath.prototype.fromJSON.call( this, json );
+                       this.times = times;
+                       this.values = values;
 
-               this.currentPoint.fromArray( json.currentPoint );
+               }
 
                return this;
 
        }
 
-    } );
+       clone() {
 
-    function Shape( points ) {
+               const times = AnimationUtils.arraySlice( this.times, 0 );
+               const values = AnimationUtils.arraySlice( this.values, 0 );
 
-       Path.call( this, points );
+               const TypedKeyframeTrack = this.constructor;
+               const track = new TypedKeyframeTrack( this.name, times, values );
 
-       this.uuid = MathUtils.generateUUID();
+               // Interpolant argument to constructor is not saved, so copy the factory method directly.
+               track.createInterpolant = this.createInterpolant;
 
-       this.type = 'Shape';
+               return track;
 
-       this.holes = [];
+       }
 
     }
 
-    Shape.prototype = Object.assign( Object.create( Path.prototype ), {
-
-       constructor: Shape,
-
-       getPointsHoles: function ( divisions ) {
-
-               const holesPts = [];
+    KeyframeTrack.prototype.TimeBufferType = Float32Array;
+    KeyframeTrack.prototype.ValueBufferType = Float32Array;
+    KeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear;
 
-               for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
+    /**
+     * A Track of Boolean keyframe values.
+     */
+    class BooleanKeyframeTrack extends KeyframeTrack {}
 
-                       holesPts[ i ] = this.holes[ i ].getPoints( divisions );
+    BooleanKeyframeTrack.prototype.ValueTypeName = 'bool';
+    BooleanKeyframeTrack.prototype.ValueBufferType = Array;
+    BooleanKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete;
+    BooleanKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined;
+    BooleanKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined;
 
-               }
+    /**
+     * A Track of keyframe values that represent color.
+     */
+    class ColorKeyframeTrack extends KeyframeTrack {}
 
-               return holesPts;
+    ColorKeyframeTrack.prototype.ValueTypeName = 'color';
 
-       },
+    /**
+     * A Track of numeric keyframe values.
+     */
+    class NumberKeyframeTrack extends KeyframeTrack {}
 
-       // get points of shape and holes (keypoints based on segments parameter)
+    NumberKeyframeTrack.prototype.ValueTypeName = 'number';
 
-       extractPoints: function ( divisions ) {
+    /**
+     * Spherical linear unit quaternion interpolant.
+     */
 
-               return {
+    class QuaternionLinearInterpolant extends Interpolant {
 
-                       shape: this.getPoints( divisions ),
-                       holes: this.getPointsHoles( divisions )
+       constructor( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
 
-               };
+               super( parameterPositions, sampleValues, sampleSize, resultBuffer );
 
-       },
+       }
 
-       copy: function ( source ) {
+       interpolate_( i1, t0, t, t1 ) {
 
-               Path.prototype.copy.call( this, source );
+               const result = this.resultBuffer,
+                       values = this.sampleValues,
+                       stride = this.valueSize,
 
-               this.holes = [];
+                       alpha = ( t - t0 ) / ( t1 - t0 );
 
-               for ( let i = 0, l = source.holes.length; i < l; i ++ ) {
+               let offset = i1 * stride;
 
-                       const hole = source.holes[ i ];
+               for ( let end = offset + stride; offset !== end; offset += 4 ) {
 
-                       this.holes.push( hole.clone() );
+                       Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha );
 
                }
 
-               return this;
+               return result;
 
-       },
+       }
 
-       toJSON: function () {
+    }
 
-               const data = Path.prototype.toJSON.call( this );
+    /**
+     * A Track of quaternion keyframe values.
+     */
+    class QuaternionKeyframeTrack extends KeyframeTrack {
 
-               data.uuid = this.uuid;
-               data.holes = [];
+       InterpolantFactoryMethodLinear( result ) {
 
-               for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
+               return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result );
 
-                       const hole = this.holes[ i ];
-                       data.holes.push( hole.toJSON() );
+       }
 
-               }
+    }
 
-               return data;
+    QuaternionKeyframeTrack.prototype.ValueTypeName = 'quaternion';
+    // ValueBufferType is inherited
+    QuaternionKeyframeTrack.prototype.DefaultInterpolation = InterpolateLinear;
+    QuaternionKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined;
 
-       },
+    /**
+     * A Track that interpolates Strings
+     */
+    class StringKeyframeTrack extends KeyframeTrack {}
 
-       fromJSON: function ( json ) {
+    StringKeyframeTrack.prototype.ValueTypeName = 'string';
+    StringKeyframeTrack.prototype.ValueBufferType = Array;
+    StringKeyframeTrack.prototype.DefaultInterpolation = InterpolateDiscrete;
+    StringKeyframeTrack.prototype.InterpolantFactoryMethodLinear = undefined;
+    StringKeyframeTrack.prototype.InterpolantFactoryMethodSmooth = undefined;
 
-               Path.prototype.fromJSON.call( this, json );
+    /**
+     * A Track of vectored keyframe values.
+     */
+    class VectorKeyframeTrack extends KeyframeTrack {}
 
-               this.uuid = json.uuid;
-               this.holes = [];
+    VectorKeyframeTrack.prototype.ValueTypeName = 'vector';
 
-               for ( let i = 0, l = json.holes.length; i < l; i ++ ) {
+    class AnimationClip {
 
-                       const hole = json.holes[ i ];
-                       this.holes.push( new Path().fromJSON( hole ) );
+       constructor( name, duration = - 1, tracks, blendMode = NormalAnimationBlendMode ) {
 
-               }
+               this.name = name;
+               this.tracks = tracks;
+               this.duration = duration;
+               this.blendMode = blendMode;
 
-               return this;
+               this.uuid = generateUUID();
 
-       }
+               // this means it should figure out its duration by scanning the tracks
+               if ( this.duration < 0 ) {
 
-    );
+                       this.resetDuration();
 
-    function Light( color, intensity = 1 ) {
+               }
 
-       Object3D.call( this );
+       }
 
-       this.type = 'Light';
 
-       this.color = new Color( color );
-       this.intensity = intensity;
+       static parse( json ) {
 
-    }
+               const tracks = [],
+                       jsonTracks = json.tracks,
+                       frameTime = 1.0 / ( json.fps || 1.0 );
 
-    Light.prototype = Object.assign( Object.create( Object3D.prototype ), {
+               for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) {
 
-       constructor: Light,
+                       tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) );
 
-       isLight: true,
+               }
 
-       copy: function ( source ) {
+               const clip = new this( json.name, json.duration, tracks, json.blendMode );
+               clip.uuid = json.uuid;
 
-               Object3D.prototype.copy.call( this, source );
+               return clip;
 
-               this.color.copy( source.color );
-               this.intensity = source.intensity;
+       }
 
-               return this;
+       static toJSON( clip ) {
 
-       },
+               const tracks = [],
+                       clipTracks = clip.tracks;
 
-       toJSON: function ( meta ) {
+               const json = {
 
-               const data = Object3D.prototype.toJSON.call( this, meta );
+                       'name': clip.name,
+                       'duration': clip.duration,
+                       'tracks': tracks,
+                       'uuid': clip.uuid,
+                       'blendMode': clip.blendMode
 
-               data.object.color = this.color.getHex();
-               data.object.intensity = this.intensity;
+               };
 
-               if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex();
+               for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) {
 
-               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;
+                       tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );
 
-               if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON();
+               }
 
-               return data;
+               return json;
 
        }
 
-    } );
-
-    function HemisphereLight( skyColor, groundColor, intensity ) {
+       static CreateFromMorphTargetSequence( name, morphTargetSequence, fps, noLoop ) {
 
-       Light.call( this, skyColor, intensity );
+               const numMorphTargets = morphTargetSequence.length;
+               const tracks = [];
 
-       this.type = 'HemisphereLight';
+               for ( let i = 0; i < numMorphTargets; i ++ ) {
 
-       this.position.copy( Object3D.DefaultUp );
-       this.updateMatrix();
+                       let times = [];
+                       let values = [];
 
-       this.groundColor = new Color( groundColor );
+                       times.push(
+                               ( i + numMorphTargets - 1 ) % numMorphTargets,
+                               i,
+                               ( i + 1 ) % numMorphTargets );
 
-    }
+                       values.push( 0, 1, 0 );
 
-    HemisphereLight.prototype = Object.assign( Object.create( Light.prototype ), {
+                       const order = AnimationUtils.getKeyframeOrder( times );
+                       times = AnimationUtils.sortedArray( times, 1, order );
+                       values = AnimationUtils.sortedArray( values, 1, order );
 
-       constructor: HemisphereLight,
+                       // 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 ) {
 
-       isHemisphereLight: true,
+                               times.push( numMorphTargets );
+                               values.push( values[ 0 ] );
 
-       copy: function ( source ) {
+                       }
 
-               Light.prototype.copy.call( this, source );
+                       tracks.push(
+                               new NumberKeyframeTrack(
+                                       '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',
+                                       times, values
+                               ).scale( 1.0 / fps ) );
 
-               this.groundColor.copy( source.groundColor );
+               }
 
-               return this;
+               return new this( name, - 1, tracks );
 
        }
 
-    } );
+       static findByName( objectOrClipArray, name ) {
 
-    function LightShadow( camera ) {
+               let clipArray = objectOrClipArray;
 
-       this.camera = camera;
+               if ( ! Array.isArray( objectOrClipArray ) ) {
 
-       this.bias = 0;
-       this.normalBias = 0;
-       this.radius = 1;
+                       const o = objectOrClipArray;
+                       clipArray = o.geometry && o.geometry.animations || o.animations;
 
-       this.mapSize = new Vector2( 512, 512 );
+               }
 
-       this.map = null;
-       this.mapPass = null;
-       this.matrix = new Matrix4();
+               for ( let i = 0; i < clipArray.length; i ++ ) {
 
-       this.autoUpdate = true;
-       this.needsUpdate = false;
+                       if ( clipArray[ i ].name === name ) {
 
-       this._frustum = new Frustum();
-       this._frameExtents = new Vector2( 1, 1 );
+                               return clipArray[ i ];
 
-       this._viewportCount = 1;
+                       }
 
-       this._viewports = [
+               }
 
-               new Vector4( 0, 0, 1, 1 )
+               return null;
 
-       ];
+       }
 
-    }
+       static CreateClipsFromMorphTargetSequences( morphTargets, fps, noLoop ) {
 
-    Object.assign( LightShadow.prototype, {
+               const animationToMorphTargets = {};
 
-       _projScreenMatrix: new Matrix4(),
+               // tested with https://regex101.com/ on trick sequences
+               // such flamingo_flyA_003, flamingo_run1_003, crdeath0059
+               const pattern = /^([\w-]*?)([\d]+)$/;
 
-       _lightPositionWorld: new Vector3(),
+               // 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 ++ ) {
 
-       _lookTarget: new Vector3(),
+                       const morphTarget = morphTargets[ i ];
+                       const parts = morphTarget.name.match( pattern );
 
-       getViewportCount: function () {
+                       if ( parts && parts.length > 1 ) {
 
-               return this._viewportCount;
+                               const name = parts[ 1 ];
 
-       },
+                               let animationMorphTargets = animationToMorphTargets[ name ];
 
-       getFrustum: function () {
+                               if ( ! animationMorphTargets ) {
 
-               return this._frustum;
+                                       animationToMorphTargets[ name ] = animationMorphTargets = [];
 
-       },
+                               }
 
-       updateMatrices: function ( light ) {
+                               animationMorphTargets.push( morphTarget );
 
-               const shadowCamera = this.camera,
-                       shadowMatrix = this.matrix,
-                       projScreenMatrix = this._projScreenMatrix,
-                       lookTarget = this._lookTarget,
-                       lightPositionWorld = this._lightPositionWorld;
+                       }
 
-               lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
-               shadowCamera.position.copy( lightPositionWorld );
+               }
 
-               lookTarget.setFromMatrixPosition( light.target.matrixWorld );
-               shadowCamera.lookAt( lookTarget );
-               shadowCamera.updateMatrixWorld();
+               const clips = [];
 
-               projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );
-               this._frustum.setFromProjectionMatrix( projScreenMatrix );
+               for ( const name in animationToMorphTargets ) {
 
-               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
-               );
+                       clips.push( this.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );
 
-               shadowMatrix.multiply( shadowCamera.projectionMatrix );
-               shadowMatrix.multiply( shadowCamera.matrixWorldInverse );
+               }
 
-       },
+               return clips;
 
-       getViewport: function ( viewportIndex ) {
+       }
 
-               return this._viewports[ viewportIndex ];
+       // parse the animation.hierarchy format
+       static parseAnimation( animation, bones ) {
 
-       },
+               if ( ! animation ) {
 
-       getFrameExtents: function () {
+                       console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' );
+                       return null;
 
-               return this._frameExtents;
+               }
 
-       },
+               const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) {
 
-       copy: function ( source ) {
+                       // only return track if there are actually keys.
+                       if ( animationKeys.length !== 0 ) {
 
-               this.camera = source.camera.clone();
+                               const times = [];
+                               const values = [];
 
-               this.bias = source.bias;
-               this.radius = source.radius;
+                               AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );
 
-               this.mapSize.copy( source.mapSize );
+                               // empty keys are filtered out, so check again
+                               if ( times.length !== 0 ) {
 
-               return this;
+                                       destTracks.push( new trackType( trackName, times, values ) );
 
-       },
+                               }
 
-       clone: function () {
+                       }
 
-               return new this.constructor().copy( this );
+               };
 
-       },
+               const tracks = [];
 
-       toJSON: function () {
+               const clipName = animation.name || 'default';
+               const fps = animation.fps || 30;
+               const blendMode = animation.blendMode;
 
-               const object = {};
+               // automatic length determination in AnimationClip.
+               let duration = animation.length || - 1;
 
-               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 hierarchyTracks = animation.hierarchy || [];
 
-               object.camera = this.camera.toJSON( false ).object;
-               delete object.camera.matrix;
+               for ( let h = 0; h < hierarchyTracks.length; h ++ ) {
 
-               return object;
+                       const animationKeys = hierarchyTracks[ h ].keys;
 
-       }
+                       // skip empty tracks
+                       if ( ! animationKeys || animationKeys.length === 0 ) continue;
 
-    } );
+                       // process morph targets
+                       if ( animationKeys[ 0 ].morphTargets ) {
 
-    function SpotLightShadow() {
+                               // figure out all morph targets used in this track
+                               const morphTargetNames = {};
 
-       LightShadow.call( this, new PerspectiveCamera( 50, 1, 0.5, 500 ) );
+                               let k;
 
-       this.focus = 1;
+                               for ( k = 0; k < animationKeys.length; k ++ ) {
 
-    }
+                                       if ( animationKeys[ k ].morphTargets ) {
 
-    SpotLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {
+                                               for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) {
 
-       constructor: SpotLightShadow,
+                                                       morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1;
 
-       isSpotLightShadow: true,
+                                               }
 
-       updateMatrices: function ( light ) {
+                                       }
 
-               const camera = this.camera;
+                               }
 
-               const fov = MathUtils.RAD2DEG * 2 * light.angle * this.focus;
-               const aspect = this.mapSize.width / this.mapSize.height;
-               const far = light.distance || camera.far;
+                               // 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 ) {
 
-               if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) {
+                                       const times = [];
+                                       const values = [];
 
-                       camera.fov = fov;
-                       camera.aspect = aspect;
-                       camera.far = far;
-                       camera.updateProjectionMatrix();
+                                       for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) {
 
-               }
+                                               const animationKey = animationKeys[ k ];
 
-               LightShadow.prototype.updateMatrices.call( this, light );
+                                               times.push( animationKey.time );
+                                               values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );
 
-       }
+                                       }
 
-    } );
+                                       tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) );
 
-    function SpotLight( color, intensity, distance, angle, penumbra, decay ) {
+                               }
 
-       Light.call( this, color, intensity );
+                               duration = morphTargetNames.length * ( fps || 1.0 );
 
-       this.type = 'SpotLight';
+                       } else {
 
-       this.position.copy( Object3D.DefaultUp );
-       this.updateMatrix();
+                               // ...assume skeletal animation
 
-       this.target = new Object3D();
+                               const boneName = '.bones[' + bones[ h ].name + ']';
 
-       Object.defineProperty( this, 'power', {
-               get: function () {
+                               addNonemptyTrack(
+                                       VectorKeyframeTrack, boneName + '.position',
+                                       animationKeys, 'pos', tracks );
 
-                       // 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;
+                               addNonemptyTrack(
+                                       QuaternionKeyframeTrack, boneName + '.quaternion',
+                                       animationKeys, 'rot', tracks );
 
-               },
-               set: function ( power ) {
+                               addNonemptyTrack(
+                                       VectorKeyframeTrack, boneName + '.scale',
+                                       animationKeys, 'scl', tracks );
 
-                       // intensity = power per solid angle.
-                       // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
-                       this.intensity = power / Math.PI;
+                       }
 
                }
-       } );
 
-       this.distance = ( distance !== undefined ) ? distance : 0;
-       this.angle = ( angle !== undefined ) ? angle : Math.PI / 3;
-       this.penumbra = ( penumbra !== undefined ) ? penumbra : 0;
-       this.decay = ( decay !== undefined ) ? decay : 1;       // for physically correct lights, should be 2.
+               if ( tracks.length === 0 ) {
+
+                       return null;
 
-       this.shadow = new SpotLightShadow();
+               }
 
-    }
+               const clip = new this( clipName, duration, tracks, blendMode );
 
-    SpotLight.prototype = Object.assign( Object.create( Light.prototype ), {
+               return clip;
 
-       constructor: SpotLight,
+       }
 
-       isSpotLight: true,
+       resetDuration() {
 
-       copy: function ( source ) {
+               const tracks = this.tracks;
+               let duration = 0;
 
-               Light.prototype.copy.call( this, source );
+               for ( let i = 0, n = tracks.length; i !== n; ++ i ) {
 
-               this.distance = source.distance;
-               this.angle = source.angle;
-               this.penumbra = source.penumbra;
-               this.decay = source.decay;
+                       const track = this.tracks[ i ];
 
-               this.target = source.target.clone();
+                       duration = Math.max( duration, track.times[ track.times.length - 1 ] );
 
-               this.shadow = source.shadow.clone();
+               }
+
+               this.duration = duration;
 
                return this;
 
        }
 
-    } );
+       trim() {
 
-    function PointLightShadow() {
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
 
-       LightShadow.call( this, new PerspectiveCamera( 90, 1, 0.5, 500 ) );
+                       this.tracks[ i ].trim( 0, this.duration );
 
-       this._frameExtents = new Vector2( 4, 2 );
+               }
 
-       this._viewportCount = 6;
+               return 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 )
-       ];
+       }
 
-       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 )
-       ];
+       validate() {
 
-       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 )
-       ];
+               let valid = true;
 
-    }
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
 
-    PointLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {
+                       valid = valid && this.tracks[ i ].validate();
 
-       constructor: PointLightShadow,
+               }
 
-       isPointLightShadow: true,
+               return valid;
 
-       updateMatrices: function ( light, viewportIndex = 0 ) {
+       }
 
-               const camera = this.camera,
-                       shadowMatrix = this.matrix,
-                       lightPositionWorld = this._lightPositionWorld,
-                       lookTarget = this._lookTarget,
-                       projScreenMatrix = this._projScreenMatrix;
+       optimize() {
 
-               lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
-               camera.position.copy( lightPositionWorld );
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
 
-               lookTarget.copy( camera.position );
-               lookTarget.add( this._cubeDirections[ viewportIndex ] );
-               camera.up.copy( this._cubeUps[ viewportIndex ] );
-               camera.lookAt( lookTarget );
-               camera.updateMatrixWorld();
+                       this.tracks[ i ].optimize();
 
-               shadowMatrix.makeTranslation( - lightPositionWorld.x, - lightPositionWorld.y, - lightPositionWorld.z );
+               }
 
-               projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
-               this._frustum.setFromProjectionMatrix( projScreenMatrix );
+               return this;
 
        }
 
-    } );
-
-    function PointLight( color, intensity, distance, decay ) {
+       clone() {
 
-       Light.call( this, color, intensity );
+               const tracks = [];
 
-       this.type = 'PointLight';
+               for ( let i = 0; i < this.tracks.length; i ++ ) {
 
-       Object.defineProperty( this, 'power', {
-               get: function () {
+                       tracks.push( this.tracks[ i ].clone() );
 
-                       // 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 ) {
+               return new this.constructor( this.name, this.duration, tracks, this.blendMode );
 
-                       // 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 );
+       }
 
-               }
-       } );
+       toJSON() {
 
-       this.distance = ( distance !== undefined ) ? distance : 0;
-       this.decay = ( decay !== undefined ) ? decay : 1;       // for physically correct lights, should be 2.
+               return this.constructor.toJSON( this );
 
-       this.shadow = new PointLightShadow();
+       }
 
     }
 
-    PointLight.prototype = Object.assign( Object.create( Light.prototype ), {
-
-       constructor: PointLight,
+    function getTrackTypeForValueTypeName( typeName ) {
 
-       isPointLight: true,
+       switch ( typeName.toLowerCase() ) {
 
-       copy: function ( source ) {
+               case 'scalar':
+               case 'double':
+               case 'float':
+               case 'number':
+               case 'integer':
 
-               Light.prototype.copy.call( this, source );
+                       return NumberKeyframeTrack;
 
-               this.distance = source.distance;
-               this.decay = source.decay;
+               case 'vector':
+               case 'vector2':
+               case 'vector3':
+               case 'vector4':
 
-               this.shadow = source.shadow.clone();
+                       return VectorKeyframeTrack;
 
-               return this;
+               case 'color':
 
-       }
+                       return ColorKeyframeTrack;
 
-    } );
+               case 'quaternion':
 
-    function OrthographicCamera( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) {
+                       return QuaternionKeyframeTrack;
 
-       Camera$1.call( this );
+               case 'bool':
+               case 'boolean':
 
-       this.type = 'OrthographicCamera';
+                       return BooleanKeyframeTrack;
 
-       this.zoom = 1;
-       this.view = null;
+               case 'string':
 
-       this.left = left;
-       this.right = right;
-       this.top = top;
-       this.bottom = bottom;
+                       return StringKeyframeTrack;
 
-       this.near = near;
-       this.far = far;
+       }
 
-       this.updateProjectionMatrix();
+       throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName );
 
     }
 
-    OrthographicCamera.prototype = Object.assign( Object.create( Camera$1.prototype ), {
+    function parseKeyframeTrack( json ) {
 
-       constructor: OrthographicCamera,
+       if ( json.type === undefined ) {
 
-       isOrthographicCamera: true,
+               throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' );
 
-       copy: function ( source, recursive ) {
+       }
 
-               Camera$1.prototype.copy.call( this, source, recursive );
+       const trackType = getTrackTypeForValueTypeName( json.type );
 
-               this.left = source.left;
-               this.right = source.right;
-               this.top = source.top;
-               this.bottom = source.bottom;
-               this.near = source.near;
-               this.far = source.far;
+       if ( json.times === undefined ) {
 
-               this.zoom = source.zoom;
-               this.view = source.view === null ? null : Object.assign( {}, source.view );
+               const times = [], values = [];
 
-               return this;
+               AnimationUtils.flattenJSON( json.keys, times, values, 'value' );
 
-       },
+               json.times = times;
+               json.values = values;
 
-       setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {
+       }
 
-               if ( this.view === null ) {
+       // derived classes can define a static parse method
+       if ( trackType.parse !== undefined ) {
 
-                       this.view = {
-                               enabled: true,
-                               fullWidth: 1,
-                               fullHeight: 1,
-                               offsetX: 0,
-                               offsetY: 0,
-                               width: 1,
-                               height: 1
-                       };
+               return trackType.parse( json );
 
-               }
+       } else {
 
-               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;
+               // by default, we assume a constructor compatible with the base
+               return new trackType( json.name, json.times, json.values, json.interpolation );
 
-               this.updateProjectionMatrix();
+       }
 
-       },
+    }
 
-       clearViewOffset: function () {
+    const Cache = {
 
-               if ( this.view !== null ) {
+       enabled: false,
 
-                       this.view.enabled = false;
+       files: {},
 
-               }
+       add: function ( key, file ) {
 
-               this.updateProjectionMatrix();
+               if ( this.enabled === false ) return;
 
-       },
+               // console.log( 'THREE.Cache', 'Adding key:', key );
 
-       updateProjectionMatrix: function () {
+               this.files[ key ] = file;
 
-               const dx = ( this.right - this.left ) / ( 2 * this.zoom );
-               const dy = ( this.top - this.bottom ) / ( 2 * this.zoom );
-               const cx = ( this.right + this.left ) / 2;
-               const cy = ( this.top + this.bottom ) / 2;
+       },
 
-               let left = cx - dx;
-               let right = cx + dx;
-               let top = cy + dy;
-               let bottom = cy - dy;
+       get: function ( key ) {
 
-               if ( this.view !== null && this.view.enabled ) {
+               if ( this.enabled === false ) return;
 
-                       const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom;
-                       const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom;
+               // console.log( 'THREE.Cache', 'Checking key:', key );
 
-                       left += scaleW * this.view.offsetX;
-                       right = left + scaleW * this.view.width;
-                       top -= scaleH * this.view.offsetY;
-                       bottom = top - scaleH * this.view.height;
+               return this.files[ key ];
 
-               }
+       },
 
-               this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far );
+       remove: function ( key ) {
 
-               this.projectionMatrixInverse.copy( this.projectionMatrix ).invert();
+               delete this.files[ key ];
 
        },
 
-       toJSON: function ( meta ) {
+       clear: function () {
 
-               const data = Object3D.prototype.toJSON.call( this, meta );
+               this.files = {};
 
-               data.object.zoom = this.zoom;
-               data.object.left = this.left;
-               data.object.right = this.right;
-               data.object.top = this.top;
-               data.object.bottom = this.bottom;
-               data.object.near = this.near;
-               data.object.far = this.far;
+       }
 
-               if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );
+    };
 
-               return data;
+    class LoadingManager {
 
-       }
+       constructor( onLoad, onProgress, onError ) {
 
-    } );
+               const scope = this;
 
-    function DirectionalLightShadow() {
+               let isLoading = false;
+               let itemsLoaded = 0;
+               let itemsTotal = 0;
+               let urlModifier = undefined;
+               const handlers = [];
 
-       LightShadow.call( this, new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) );
+               // Refer to #5689 for the reason why we don't set .onStart
+               // in the constructor
 
-    }
+               this.onStart = undefined;
+               this.onLoad = onLoad;
+               this.onProgress = onProgress;
+               this.onError = onError;
 
-    DirectionalLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {
+               this.itemStart = function ( url ) {
 
-       constructor: DirectionalLightShadow,
+                       itemsTotal ++;
 
-       isDirectionalLightShadow: true,
+                       if ( isLoading === false ) {
 
-       updateMatrices: function ( light ) {
+                               if ( scope.onStart !== undefined ) {
 
-               LightShadow.prototype.updateMatrices.call( this, light );
+                                       scope.onStart( url, itemsLoaded, itemsTotal );
 
-       }
+                               }
 
-    } );
+                       }
 
-    function DirectionalLight( color, intensity ) {
+                       isLoading = true;
 
-       Light.call( this, color, intensity );
+               };
 
-       this.type = 'DirectionalLight';
+               this.itemEnd = function ( url ) {
 
-       this.position.copy( Object3D.DefaultUp );
-       this.updateMatrix();
+                       itemsLoaded ++;
 
-       this.target = new Object3D();
+                       if ( scope.onProgress !== undefined ) {
 
-       this.shadow = new DirectionalLightShadow();
+                               scope.onProgress( url, itemsLoaded, itemsTotal );
 
-    }
+                       }
 
-    DirectionalLight.prototype = Object.assign( Object.create( Light.prototype ), {
+                       if ( itemsLoaded === itemsTotal ) {
 
-       constructor: DirectionalLight,
+                               isLoading = false;
 
-       isDirectionalLight: true,
+                               if ( scope.onLoad !== undefined ) {
 
-       copy: function ( source ) {
+                                       scope.onLoad();
 
-               Light.prototype.copy.call( this, source );
+                               }
 
-               this.target = source.target.clone();
+                       }
 
-               this.shadow = source.shadow.clone();
+               };
 
-               return this;
+               this.itemError = function ( url ) {
 
-       }
+                       if ( scope.onError !== undefined ) {
 
-    } );
+                               scope.onError( url );
 
-    function AmbientLight( color, intensity ) {
+                       }
 
-       Light.call( this, color, intensity );
+               };
 
-       this.type = 'AmbientLight';
+               this.resolveURL = function ( url ) {
 
-    }
+                       if ( urlModifier ) {
 
-    AmbientLight.prototype = Object.assign( Object.create( Light.prototype ), {
+                               return urlModifier( url );
 
-       constructor: AmbientLight,
+                       }
 
-       isAmbientLight: true
+                       return url;
 
-    } );
+               };
 
-    function RectAreaLight( color, intensity, width, height ) {
+               this.setURLModifier = function ( transform ) {
 
-       Light.call( this, color, intensity );
+                       urlModifier = transform;
 
-       this.type = 'RectAreaLight';
+                       return this;
 
-       this.width = ( width !== undefined ) ? width : 10;
-       this.height = ( height !== undefined ) ? height : 10;
+               };
 
-    }
+               this.addHandler = function ( regex, loader ) {
 
-    RectAreaLight.prototype = Object.assign( Object.create( Light.prototype ), {
+                       handlers.push( regex, loader );
 
-       constructor: RectAreaLight,
+                       return this;
 
-       isRectAreaLight: true,
+               };
 
-       copy: function ( source ) {
+               this.removeHandler = function ( regex ) {
 
-               Light.prototype.copy.call( this, source );
+                       const index = handlers.indexOf( regex );
 
-               this.width = source.width;
-               this.height = source.height;
+                       if ( index !== - 1 ) {
 
-               return this;
+                               handlers.splice( index, 2 );
 
-       },
+                       }
 
-       toJSON: function ( meta ) {
+                       return this;
 
-               const data = Light.prototype.toJSON.call( this, meta );
+               };
 
-               data.object.width = this.width;
-               data.object.height = this.height;
+               this.getHandler = function ( file ) {
 
-               return data;
+                       for ( let i = 0, l = handlers.length; i < l; i += 2 ) {
 
-       }
+                               const regex = handlers[ i ];
+                               const loader = handlers[ i + 1 ];
 
-    } );
+                               if ( regex.global ) regex.lastIndex = 0; // see #17920
 
-    /**
-     * Primary reference:
-     *   https://graphics.stanford.edu/papers/envmap/envmap.pdf
-     *
-     * Secondary reference:
-     *   https://www.ppsloan.org/publications/StupidSH36.pdf
-     */
+                               if ( regex.test( file ) ) {
 
-    // 3-band SH defined by 9 coefficients
+                                       return loader;
 
-    class SphericalHarmonics3 {
+                               }
 
-       constructor() {
+                       }
 
-               Object.defineProperty( this, 'isSphericalHarmonics3', { value: true } );
+                       return null;
 
-               this.coefficients = [];
+               };
 
-               for ( let i = 0; i < 9; i ++ ) {
+       }
 
-                       this.coefficients.push( new Vector3() );
+    }
 
-               }
+    const DefaultLoadingManager = new LoadingManager();
+
+    class Loader {
+
+       constructor( manager ) {
+
+               this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+               this.crossOrigin = 'anonymous';
+               this.withCredentials = false;
+               this.path = '';
+               this.resourcePath = '';
+               this.requestHeader = {};
 
        }
 
-       set( coefficients ) {
+       load( /* url, onLoad, onProgress, onError */ ) {}
 
-               for ( let i = 0; i < 9; i ++ ) {
+       loadAsync( url, onProgress ) {
 
-                       this.coefficients[ i ].copy( coefficients[ i ] );
+               const scope = this;
 
-               }
+               return new Promise( function ( resolve, reject ) {
 
-               return this;
+                       scope.load( url, resolve, onProgress, reject );
+
+               } );
 
        }
 
-       zero() {
+       parse( /* data */ ) {}
 
-               for ( let i = 0; i < 9; i ++ ) {
+       setCrossOrigin( crossOrigin ) {
 
-                       this.coefficients[ i ].set( 0, 0, 0 );
+               this.crossOrigin = crossOrigin;
+               return this;
 
-               }
+       }
 
+       setWithCredentials( value ) {
+
+               this.withCredentials = value;
                return this;
 
        }
 
-       // get the radiance in the direction of the normal
-       // target is a Vector3
-       getAt( normal, target ) {
+       setPath( path ) {
 
-               // normal is assumed to be unit length
+               this.path = path;
+               return this;
 
-               const x = normal.x, y = normal.y, z = normal.z;
+       }
 
-               const coeff = this.coefficients;
+       setResourcePath( resourcePath ) {
 
-               // band 0
-               target.copy( coeff[ 0 ] ).multiplyScalar( 0.282095 );
+               this.resourcePath = resourcePath;
+               return this;
 
-               // band 1
-               target.addScaledVector( coeff[ 1 ], 0.488603 * y );
-               target.addScaledVector( coeff[ 2 ], 0.488603 * z );
-               target.addScaledVector( coeff[ 3 ], 0.488603 * x );
+       }
 
-               // band 2
-               target.addScaledVector( coeff[ 4 ], 1.092548 * ( x * y ) );
-               target.addScaledVector( coeff[ 5 ], 1.092548 * ( y * z ) );
-               target.addScaledVector( coeff[ 6 ], 0.315392 * ( 3.0 * z * z - 1.0 ) );
-               target.addScaledVector( coeff[ 7 ], 1.092548 * ( x * z ) );
-               target.addScaledVector( coeff[ 8 ], 0.546274 * ( x * x - y * y ) );
+       setRequestHeader( requestHeader ) {
 
-               return target;
+               this.requestHeader = requestHeader;
+               return this;
 
        }
 
-       // get the irradiance (radiance convolved with cosine lobe) in the direction of the normal
-       // target is a Vector3
-       // https://graphics.stanford.edu/papers/envmap/envmap.pdf
-       getIrradianceAt( normal, target ) {
+    }
 
-               // normal is assumed to be unit length
+    const loading = {};
 
-               const x = normal.x, y = normal.y, z = normal.z;
+    class FileLoader extends Loader {
 
-               const coeff = this.coefficients;
+       constructor( manager ) {
 
-               // band 0
-               target.copy( coeff[ 0 ] ).multiplyScalar( 0.886227 ); // Ï€ * 0.282095
+               super( manager );
 
-               // 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
+       load( url, onLoad, onProgress, onError ) {
 
-               return target;
+               if ( url === undefined ) url = '';
 
-       }
+               if ( this.path !== undefined ) url = this.path + url;
 
-       add( sh ) {
+               url = this.manager.resolveURL( url );
 
-               for ( let i = 0; i < 9; i ++ ) {
+               const cached = Cache.get( url );
 
-                       this.coefficients[ i ].add( sh.coefficients[ i ] );
+               if ( cached !== undefined ) {
 
-               }
+                       this.manager.itemStart( url );
 
-               return this;
+                       setTimeout( () => {
 
-       }
+                               if ( onLoad ) onLoad( cached );
 
-       addScaledSH( sh, s ) {
+                               this.manager.itemEnd( url );
 
-               for ( let i = 0; i < 9; i ++ ) {
+                       }, 0 );
 
-                       this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s );
+                       return cached;
 
                }
 
-               return this;
+               // Check if request is duplicate
 
-       }
+               if ( loading[ url ] !== undefined ) {
 
-       scale( s ) {
+                       loading[ url ].push( {
 
-               for ( let i = 0; i < 9; i ++ ) {
+                               onLoad: onLoad,
+                               onProgress: onProgress,
+                               onError: onError
 
-                       this.coefficients[ i ].multiplyScalar( s );
+                       } );
+
+                       return;
 
                }
 
-               return this;
+               // Initialise array for duplicate requests
+               loading[ url ] = [];
 
-       }
+               loading[ url ].push( {
+                       onLoad: onLoad,
+                       onProgress: onProgress,
+                       onError: onError,
+               } );
 
-       lerp( sh, alpha ) {
+               // 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
+               } );
 
-               for ( let i = 0; i < 9; i ++ ) {
+               // start the fetch
+               fetch( req )
+                       .then( response => {
 
-                       this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha );
+                               if ( response.status === 200 || response.status === 0 ) {
 
-               }
+                                       // Some browsers return HTTP Status 0 when using non-http protocol
+                                       // e.g. 'file://' or 'data://'. Handle as success.
 
-               return this;
+                                       if ( response.status === 0 ) {
 
-       }
+                                               console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
 
-       equals( sh ) {
+                                       }
 
-               for ( let i = 0; i < 9; i ++ ) {
+                                       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;
 
-                       if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) {
+                                       // periodically read data into the new stream tracking while download progress
+                                       return new ReadableStream( {
+                                               start( controller ) {
 
-                               return false;
+                                                       readData();
 
-                       }
+                                                       function readData() {
 
-               }
+                                                               reader.read().then( ( { done, value } ) => {
 
-               return true;
+                                                                       if ( done ) {
 
-       }
+                                                                               controller.close();
 
-       copy( sh ) {
+                                                                       } else {
 
-               return this.set( sh.coefficients );
+                                                                               loaded += value.byteLength;
 
-       }
+                                                                               const event = new ProgressEvent( 'progress', { lengthComputable, loaded, total } );
+                                                                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
 
-       clone() {
+                                                                                       const callback = callbacks[ i ];
+                                                                                       if ( callback.onProgress ) callback.onProgress( event );
 
-               return new this.constructor().copy( this );
+                                                                               }
 
-       }
+                                                                               controller.enqueue( value );
+                                                                               readData();
 
-       fromArray( array, offset = 0 ) {
+                                                                       }
 
-               const coefficients = this.coefficients;
+                                                               } );
 
-               for ( let i = 0; i < 9; i ++ ) {
+                                                       }
 
-                       coefficients[ i ].fromArray( array, offset + ( i * 3 ) );
+                                               }
 
-               }
+                                       } );
 
-               return this;
+                               } else {
 
-       }
+                                       throw Error( `fetch for "${response.url}" responded with ${response.status}: ${response.statusText}` );
 
-       toArray( array = [], offset = 0 ) {
+                               }
 
-               const coefficients = this.coefficients;
+                       } )
+                       .then( stream => {
 
-               for ( let i = 0; i < 9; i ++ ) {
+                               const response = new Response( stream );
 
-                       coefficients[ i ].toArray( array, offset + ( i * 3 ) );
+                               switch ( this.responseType ) {
 
-               }
+                                       case 'arraybuffer':
 
-               return array;
+                                               return response.arrayBuffer();
 
-       }
+                                       case 'blob':
 
-       // evaluate the basis functions
-       // shBasis is an Array[ 9 ]
-       static getBasisAt( normal, shBasis ) {
+                                               return response.blob();
 
-               // normal is assumed to be unit length
+                                       case 'document':
 
-               const x = normal.x, y = normal.y, z = normal.z;
+                                               return response.text()
+                                                       .then( text => {
 
-               // band 0
-               shBasis[ 0 ] = 0.282095;
+                                                               const parser = new DOMParser();
+                                                               return parser.parseFromString( text, this.mimeType );
 
-               // 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 );
+                                       case 'json':
 
-       }
+                                               return response.json();
 
-    }
+                                       default:
 
-    function LightProbe( sh, intensity ) {
+                                               return response.text();
 
-       Light.call( this, undefined, intensity );
+                               }
 
-       this.type = 'LightProbe';
+                       } )
+                       .then( data => {
 
-       this.sh = ( sh !== undefined ) ? sh : new SphericalHarmonics3();
+                               // 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 callbacks = loading[ url ];
+                               delete loading[ url ];
 
-    LightProbe.prototype = Object.assign( Object.create( Light.prototype ), {
+                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
 
-       constructor: LightProbe,
+                                       const callback = callbacks[ i ];
+                                       if ( callback.onLoad ) callback.onLoad( data );
 
-       isLightProbe: true,
+                               }
 
-       copy: function ( source ) {
+                               this.manager.itemEnd( url );
 
-               Light.prototype.copy.call( this, source );
+                       } )
+                       .catch( err => {
 
-               this.sh.copy( source.sh );
+                               // Abort errors and other errors are handled the same
 
-               return this;
+                               const callbacks = loading[ url ];
+                               delete loading[ url ];
 
-       },
+                               for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
 
-       fromJSON: function ( json ) {
+                                       const callback = callbacks[ i ];
+                                       if ( callback.onError ) callback.onError( err );
 
-               this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON();
-               this.sh.fromArray( json.sh );
+                               }
 
-               return this;
+                               this.manager.itemError( url );
+                               this.manager.itemEnd( url );
 
-       },
+                       } );
 
-       toJSON: function ( meta ) {
+               this.manager.itemStart( url );
 
-               const data = Light.prototype.toJSON.call( this, meta );
+       }
 
-               data.object.sh = this.sh.toArray();
+       setResponseType( value ) {
 
-               return data;
+               this.responseType = value;
+               return this;
 
        }
 
-    } );
-
-    function MaterialLoader( manager ) {
+       setMimeType( value ) {
 
-       Loader.call( this, manager );
+               this.mimeType = value;
+               return this;
 
-       this.textures = {};
+       }
 
     }
 
-    MaterialLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
-
-       constructor: MaterialLoader,
+    class ImageLoader extends Loader {
 
-       load: function ( url, onLoad, onProgress, onError ) {
+       constructor( manager ) {
 
-               const scope = this;
+               super( manager );
 
-               const loader = new FileLoader( scope.manager );
-               loader.setPath( scope.path );
-               loader.setRequestHeader( scope.requestHeader );
-               loader.setWithCredentials( scope.withCredentials );
-               loader.load( url, function ( text ) {
+       }
 
-                       try {
+       load( url, onLoad, onProgress, onError ) {
 
-                               onLoad( scope.parse( JSON.parse( text ) ) );
+               if ( this.path !== undefined ) url = this.path + url;
 
-                       } catch ( e ) {
+               url = this.manager.resolveURL( url );
 
-                               if ( onError ) {
+               const scope = this;
 
-                                       onError( e );
+               const cached = Cache.get( url );
 
-                               } else {
+               if ( cached !== undefined ) {
 
-                                       console.error( e );
+                       scope.manager.itemStart( url );
 
-                               }
+                       setTimeout( function () {
 
-                               scope.manager.itemError( url );
+                               if ( onLoad ) onLoad( cached );
 
-                       }
+                               scope.manager.itemEnd( url );
 
-               }, onProgress, onError );
+                       }, 0 );
 
-       },
+                       return cached;
 
-       parse: function ( json ) {
+               }
 
-               const textures = this.textures;
+               const image = createElementNS( 'img' );
 
-               function getTexture( name ) {
+               function onImageLoad() {
 
-                       if ( textures[ name ] === undefined ) {
+                       removeEventListeners();
 
-                               console.warn( 'THREE.MaterialLoader: Undefined texture', name );
+                       Cache.add( url, this );
 
-                       }
+                       if ( onLoad ) onLoad( this );
 
-                       return textures[ name ];
+                       scope.manager.itemEnd( url );
 
                }
 
-               const material = new Materials[ json.type ]();
-
-               if ( json.uuid !== undefined ) material.uuid = json.uuid;
-               if ( json.name !== undefined ) material.name = json.name;
-               if ( json.color !== undefined && material.color !== undefined ) material.color.setHex( json.color );
-               if ( json.roughness !== undefined ) material.roughness = json.roughness;
-               if ( json.metalness !== undefined ) material.metalness = json.metalness;
-               if ( json.sheen !== undefined ) material.sheen = new Color().setHex( json.sheen );
-               if ( json.emissive !== undefined && material.emissive !== undefined ) material.emissive.setHex( json.emissive );
-               if ( json.specular !== undefined && material.specular !== undefined ) material.specular.setHex( json.specular );
-               if ( json.shininess !== undefined ) material.shininess = json.shininess;
-               if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat;
-               if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness;
-               if ( json.fog !== undefined ) material.fog = json.fog;
-               if ( json.flatShading !== undefined ) material.flatShading = json.flatShading;
-               if ( json.blending !== undefined ) material.blending = json.blending;
-               if ( json.combine !== undefined ) material.combine = json.combine;
-               if ( json.side !== undefined ) material.side = json.side;
-               if ( json.opacity !== undefined ) material.opacity = json.opacity;
-               if ( json.transparent !== undefined ) material.transparent = json.transparent;
-               if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest;
-               if ( json.depthTest !== undefined ) material.depthTest = json.depthTest;
-               if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite;
-               if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite;
+               function onImageError( event ) {
 
-               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;
+                       removeEventListeners();
 
-               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 ( onError ) onError( event );
 
-               if ( json.rotation !== undefined ) material.rotation = json.rotation;
+                       scope.manager.itemError( url );
+                       scope.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;
+               function removeEventListeners() {
 
-               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;
+                       image.removeEventListener( 'load', onImageLoad, false );
+                       image.removeEventListener( 'error', onImageError, false );
 
-               if ( json.vertexTangents !== undefined ) material.vertexTangents = json.vertexTangents;
+               }
 
-               if ( json.visible !== undefined ) material.visible = json.visible;
+               image.addEventListener( 'load', onImageLoad, false );
+               image.addEventListener( 'error', onImageError, false );
 
-               if ( json.toneMapped !== undefined ) material.toneMapped = json.toneMapped;
+               if ( url.substr( 0, 5 ) !== 'data:' ) {
 
-               if ( json.userData !== undefined ) material.userData = json.userData;
+                       if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;
 
-               if ( json.vertexColors !== undefined ) {
+               }
 
-                       if ( typeof json.vertexColors === 'number' ) {
+               scope.manager.itemStart( url );
 
-                               material.vertexColors = ( json.vertexColors > 0 ) ? true : false;
+               image.src = url;
 
-                       } else {
+               return image;
 
-                               material.vertexColors = json.vertexColors;
+       }
 
-                       }
+    }
 
-               }
+    class CubeTextureLoader extends Loader {
 
-               // Shader Material
+       constructor( manager ) {
 
-               if ( json.uniforms !== undefined ) {
+               super( manager );
 
-                       for ( const name in json.uniforms ) {
+       }
 
-                               const uniform = json.uniforms[ name ];
+       load( urls, onLoad, onProgress, onError ) {
 
-                               material.uniforms[ name ] = {};
+               const texture = new CubeTexture();
 
-                               switch ( uniform.type ) {
+               const loader = new ImageLoader( this.manager );
+               loader.setCrossOrigin( this.crossOrigin );
+               loader.setPath( this.path );
 
-                                       case 't':
-                                               material.uniforms[ name ].value = getTexture( uniform.value );
-                                               break;
+               let loaded = 0;
 
-                                       case 'c':
-                                               material.uniforms[ name ].value = new Color().setHex( uniform.value );
-                                               break;
+               function loadTexture( i ) {
 
-                                       case 'v2':
-                                               material.uniforms[ name ].value = new Vector2().fromArray( uniform.value );
-                                               break;
+                       loader.load( urls[ i ], function ( image ) {
 
-                                       case 'v3':
-                                               material.uniforms[ name ].value = new Vector3().fromArray( uniform.value );
-                                               break;
+                               texture.images[ i ] = image;
 
-                                       case 'v4':
-                                               material.uniforms[ name ].value = new Vector4().fromArray( uniform.value );
-                                               break;
+                               loaded ++;
 
-                                       case 'm3':
-                                               material.uniforms[ name ].value = new Matrix3().fromArray( uniform.value );
-                                               break;
+                               if ( loaded === 6 ) {
 
-                                       case 'm4':
-                                               material.uniforms[ name ].value = new Matrix4().fromArray( uniform.value );
-                                               break;
+                                       texture.needsUpdate = true;
 
-                                       default:
-                                               material.uniforms[ name ].value = uniform.value;
+                                       if ( onLoad ) onLoad( texture );
 
                                }
 
-                       }
+                       }, undefined, onError );
 
                }
 
-               if ( json.defines !== undefined ) material.defines = json.defines;
-               if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader;
-               if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader;
+               for ( let i = 0; i < urls.length; ++ i ) {
 
-               if ( json.extensions !== undefined ) {
+                       loadTexture( i );
 
-                       for ( const key in json.extensions ) {
+               }
 
-                               material.extensions[ key ] = json.extensions[ key ];
+               return texture;
 
-                       }
+       }
 
-               }
+    }
 
-               // Deprecated
+    class TextureLoader extends Loader {
 
-               if ( json.shading !== undefined ) material.flatShading = json.shading === 1; // THREE.FlatShading
+       constructor( manager ) {
 
-               // for PointsMaterial
+               super( manager );
 
-               if ( json.size !== undefined ) material.size = json.size;
-               if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation;
+       }
 
-               // maps
+       load( url, onLoad, onProgress, onError ) {
 
-               if ( json.map !== undefined ) material.map = getTexture( json.map );
-               if ( json.matcap !== undefined ) material.matcap = getTexture( json.matcap );
-
-               if ( json.alphaMap !== undefined ) material.alphaMap = getTexture( json.alphaMap );
-
-               if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap );
-               if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale;
+               const texture = new Texture();
 
-               if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap );
-               if ( json.normalMapType !== undefined ) material.normalMapType = json.normalMapType;
-               if ( json.normalScale !== undefined ) {
+               const loader = new ImageLoader( this.manager );
+               loader.setCrossOrigin( this.crossOrigin );
+               loader.setPath( this.path );
 
-                       let normalScale = json.normalScale;
+               loader.load( url, function ( image ) {
 
-                       if ( Array.isArray( normalScale ) === false ) {
+                       texture.image = image;
+                       texture.needsUpdate = true;
 
-                               // Blender exporter used to export a scalar. See #7459
+                       if ( onLoad !== undefined ) {
 
-                               normalScale = [ normalScale, normalScale ];
+                               onLoad( texture );
 
                        }
 
-                       material.normalScale = new Vector2().fromArray( normalScale );
+               }, onProgress, onError );
 
-               }
+               return texture;
 
-               if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap );
-               if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale;
-               if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias;
+       }
 
-               if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap );
-               if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap );
+    }
 
-               if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap );
-               if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity;
+    class Light extends Object3D {
 
-               if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap );
+       constructor( color, intensity = 1 ) {
 
-               if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap );
-               if ( json.envMapIntensity !== undefined ) material.envMapIntensity = json.envMapIntensity;
+               super();
 
-               if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity;
-               if ( json.refractionRatio !== undefined ) material.refractionRatio = json.refractionRatio;
+               this.type = 'Light';
 
-               if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap );
-               if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity;
+               this.color = new Color( color );
+               this.intensity = intensity;
 
-               if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap );
-               if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity;
+       }
 
-               if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap );
+       dispose() {
 
-               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 );
+               // Empty here in base class; some subclasses override.
 
-               if ( json.transmission !== undefined ) material.transmission = json.transmission;
-               if ( json.transmissionMap !== undefined ) material.transmissionMap = getTexture( json.transmissionMap );
+       }
 
-               return material;
+       copy( source ) {
 
-       },
+               super.copy( source );
 
-       setTextures: function ( value ) {
+               this.color.copy( source.color );
+               this.intensity = source.intensity;
 
-               this.textures = value;
                return this;
 
        }
 
-    } );
-
-    const LoaderUtils = {
+       toJSON( meta ) {
 
-       decodeText: function ( array ) {
+               const data = super.toJSON( meta );
 
-               if ( typeof TextDecoder !== 'undefined' ) {
+               data.object.color = this.color.getHex();
+               data.object.intensity = this.intensity;
 
-                       return new TextDecoder().decode( array );
+               if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex();
 
-               }
+               if ( this.distance !== undefined ) data.object.distance = this.distance;
+               if ( this.angle !== undefined ) data.object.angle = this.angle;
+               if ( this.decay !== undefined ) data.object.decay = this.decay;
+               if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra;
 
-               // Avoid the String.fromCharCode.apply(null, array) shortcut, which
-               // throws a "maximum call stack size exceeded" error for large arrays.
+               if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON();
 
-               let s = '';
+               return data;
 
-               for ( let i = 0, il = array.length; i < il; i ++ ) {
+       }
 
-                       // Implicitly assumes little-endian.
-                       s += String.fromCharCode( array[ i ] );
+    }
 
-               }
+    Light.prototype.isLight = true;
 
-               try {
+    class HemisphereLight extends Light {
 
-                       // merges multi-byte utf-8 characters.
+       constructor( skyColor, groundColor, intensity ) {
 
-                       return decodeURIComponent( escape( s ) );
+               super( skyColor, intensity );
 
-               } catch ( e ) { // see #16358
+               this.type = 'HemisphereLight';
 
-                       return s;
+               this.position.copy( Object3D.DefaultUp );
+               this.updateMatrix();
 
-               }
+               this.groundColor = new Color( groundColor );
 
-       },
+       }
 
-       extractUrlBase: function ( url ) {
+       copy( source ) {
 
-               const index = url.lastIndexOf( '/' );
+               Light.prototype.copy.call( this, source );
 
-               if ( index === - 1 ) return './';
+               this.groundColor.copy( source.groundColor );
 
-               return url.substr( 0, index + 1 );
+               return this;
 
        }
 
-    };
-
-    function InstancedBufferGeometry() {
-
-       BufferGeometry.call( this );
-
-       this.type = 'InstancedBufferGeometry';
-       this.instanceCount = Infinity;
-
     }
 
-    InstancedBufferGeometry.prototype = Object.assign( Object.create( BufferGeometry.prototype ), {
-
-       constructor: InstancedBufferGeometry,
+    HemisphereLight.prototype.isHemisphereLight = true;
 
-       isInstancedBufferGeometry: true,
+    const _projScreenMatrix$1 = /*@__PURE__*/ new Matrix4();
+    const _lightPositionWorld$1 = /*@__PURE__*/ new Vector3();
+    const _lookTarget$1 = /*@__PURE__*/ new Vector3();
 
-       copy: function ( source ) {
+    class LightShadow {
 
-               BufferGeometry.prototype.copy.call( this, source );
-
-               this.instanceCount = source.instanceCount;
+       constructor( camera ) {
 
-               return this;
+               this.camera = camera;
 
-       },
+               this.bias = 0;
+               this.normalBias = 0;
+               this.radius = 1;
+               this.blurSamples = 8;
 
-       clone: function () {
+               this.mapSize = new Vector2( 512, 512 );
 
-               return new this.constructor().copy( this );
+               this.map = null;
+               this.mapPass = null;
+               this.matrix = new Matrix4();
 
-       },
+               this.autoUpdate = true;
+               this.needsUpdate = false;
 
-       toJSON: function () {
+               this._frustum = new Frustum();
+               this._frameExtents = new Vector2( 1, 1 );
 
-               const data = BufferGeometry.prototype.toJSON.call( this );
+               this._viewportCount = 1;
 
-               data.instanceCount = this.instanceCount;
+               this._viewports = [
 
-               data.isInstancedBufferGeometry = true;
+                       new Vector4( 0, 0, 1, 1 )
 
-               return data;
+               ];
 
        }
 
-    } );
-
-    function InstancedBufferAttribute( array, itemSize, normalized, meshPerAttribute ) {
+       getViewportCount() {
 
-       if ( typeof ( normalized ) === 'number' ) {
+               return this._viewportCount;
 
-               meshPerAttribute = normalized;
+       }
 
-               normalized = false;
+       getFrustum() {
 
-               console.error( 'THREE.InstancedBufferAttribute: The constructor now expects normalized as the third argument.' );
+               return this._frustum;
 
        }
 
-       BufferAttribute.call( this, array, itemSize, normalized );
+       updateMatrices( light ) {
 
-       this.meshPerAttribute = meshPerAttribute || 1;
+               const shadowCamera = this.camera;
+               const shadowMatrix = this.matrix;
 
-    }
+               _lightPositionWorld$1.setFromMatrixPosition( light.matrixWorld );
+               shadowCamera.position.copy( _lightPositionWorld$1 );
 
-    InstancedBufferAttribute.prototype = Object.assign( Object.create( BufferAttribute.prototype ), {
+               _lookTarget$1.setFromMatrixPosition( light.target.matrixWorld );
+               shadowCamera.lookAt( _lookTarget$1 );
+               shadowCamera.updateMatrixWorld();
 
-       constructor: InstancedBufferAttribute,
+               _projScreenMatrix$1.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );
+               this._frustum.setFromProjectionMatrix( _projScreenMatrix$1 );
 
-       isInstancedBufferAttribute: true,
+               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
+               );
 
-       copy: function ( source ) {
+               shadowMatrix.multiply( shadowCamera.projectionMatrix );
+               shadowMatrix.multiply( shadowCamera.matrixWorldInverse );
 
-               BufferAttribute.prototype.copy.call( this, source );
+       }
 
-               this.meshPerAttribute = source.meshPerAttribute;
+       getViewport( viewportIndex ) {
 
-               return this;
+               return this._viewports[ viewportIndex ];
 
-       },
+       }
 
-       toJSON: function ()     {
+       getFrameExtents() {
 
-               const data = BufferAttribute.prototype.toJSON.call( this );
+               return this._frameExtents;
 
-               data.meshPerAttribute = this.meshPerAttribute;
+       }
 
-               data.isInstancedBufferAttribute = true;
+       dispose() {
 
-               return data;
+               if ( this.map ) {
 
-       }
+                       this.map.dispose();
 
-    } );
+               }
 
-    function BufferGeometryLoader( manager ) {
+               if ( this.mapPass ) {
 
-       Loader.call( this, manager );
+                       this.mapPass.dispose();
 
-    }
+               }
 
-    BufferGeometryLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+       }
 
-       constructor: BufferGeometryLoader,
+       copy( source ) {
 
-       load: function ( url, onLoad, onProgress, onError ) {
+               this.camera = source.camera.clone();
 
-               const scope = this;
+               this.bias = source.bias;
+               this.radius = source.radius;
 
-               const loader = new FileLoader( scope.manager );
-               loader.setPath( scope.path );
-               loader.setRequestHeader( scope.requestHeader );
-               loader.setWithCredentials( scope.withCredentials );
-               loader.load( url, function ( text ) {
+               this.mapSize.copy( source.mapSize );
 
-                       try {
+               return this;
 
-                               onLoad( scope.parse( JSON.parse( text ) ) );
+       }
 
-                       } catch ( e ) {
+       clone() {
 
-                               if ( onError ) {
+               return new this.constructor().copy( this );
 
-                                       onError( e );
+       }
 
-                               } else {
+       toJSON() {
 
-                                       console.error( e );
+               const object = {};
 
-                               }
+               if ( this.bias !== 0 ) object.bias = this.bias;
+               if ( this.normalBias !== 0 ) object.normalBias = this.normalBias;
+               if ( this.radius !== 1 ) object.radius = this.radius;
+               if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray();
 
-                               scope.manager.itemError( url );
+               object.camera = this.camera.toJSON( false ).object;
+               delete object.camera.matrix;
 
-                       }
+               return object;
 
-               }, onProgress, onError );
+       }
 
-       },
+    }
+
+    class SpotLightShadow extends LightShadow {
 
-       parse: function ( json ) {
+       constructor() {
 
-               const interleavedBufferMap = {};
-               const arrayBufferMap = {};
+               super( new PerspectiveCamera( 50, 1, 0.5, 500 ) );
 
-               function getInterleavedBuffer( json, uuid ) {
+               this.focus = 1;
 
-                       if ( interleavedBufferMap[ uuid ] !== undefined ) return interleavedBufferMap[ uuid ];
+       }
 
-                       const interleavedBuffers = json.interleavedBuffers;
-                       const interleavedBuffer = interleavedBuffers[ uuid ];
+       updateMatrices( light ) {
 
-                       const buffer = getArrayBuffer( json, interleavedBuffer.buffer );
+               const camera = this.camera;
 
-                       const array = getTypedArray( interleavedBuffer.type, buffer );
-                       const ib = new InterleavedBuffer( array, interleavedBuffer.stride );
-                       ib.uuid = interleavedBuffer.uuid;
+               const fov = RAD2DEG$1 * 2 * light.angle * this.focus;
+               const aspect = this.mapSize.width / this.mapSize.height;
+               const far = light.distance || camera.far;
 
-                       interleavedBufferMap[ uuid ] = ib;
+               if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) {
 
-                       return ib;
+                       camera.fov = fov;
+                       camera.aspect = aspect;
+                       camera.far = far;
+                       camera.updateProjectionMatrix();
 
                }
 
-               function getArrayBuffer( json, uuid ) {
+               super.updateMatrices( light );
 
-                       if ( arrayBufferMap[ uuid ] !== undefined ) return arrayBufferMap[ uuid ];
+       }
 
-                       const arrayBuffers = json.arrayBuffers;
-                       const arrayBuffer = arrayBuffers[ uuid ];
+       copy( source ) {
 
-                       const ab = new Uint32Array( arrayBuffer ).buffer;
+               super.copy( source );
 
-                       arrayBufferMap[ uuid ] = ab;
+               this.focus = source.focus;
 
-                       return ab;
+               return this;
 
-               }
+       }
 
-               const geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry();
+    }
 
-               const index = json.data.index;
+    SpotLightShadow.prototype.isSpotLightShadow = true;
 
-               if ( index !== undefined ) {
+    class SpotLight extends Light {
 
-                       const typedArray = getTypedArray( index.type, index.array );
-                       geometry.setIndex( new BufferAttribute( typedArray, 1 ) );
+       constructor( color, intensity, distance = 0, angle = Math.PI / 3, penumbra = 0, decay = 1 ) {
 
-               }
+               super( color, intensity );
 
-               const attributes = json.data.attributes;
+               this.type = 'SpotLight';
 
-               for ( const key in attributes ) {
+               this.position.copy( Object3D.DefaultUp );
+               this.updateMatrix();
 
-                       const attribute = attributes[ key ];
-                       let bufferAttribute;
+               this.target = new Object3D();
 
-                       if ( attribute.isInterleavedBufferAttribute ) {
+               this.distance = distance;
+               this.angle = angle;
+               this.penumbra = penumbra;
+               this.decay = decay; // for physically correct lights, should be 2.
 
-                               const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data );
-                               bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized );
+               this.shadow = new SpotLightShadow();
 
-                       } else {
+       }
 
-                               const typedArray = getTypedArray( attribute.type, attribute.array );
-                               const bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute;
-                               bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized );
+       get power() {
 
-                       }
+               // 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 ( attribute.name !== undefined ) bufferAttribute.name = attribute.name;
-                       geometry.setAttribute( key, bufferAttribute );
+       }
 
-               }
+       set power( power ) {
 
-               const morphAttributes = json.data.morphAttributes;
+               // set the light's intensity (in candela) from the desired luminous power (in lumens)
+               this.intensity = power / Math.PI;
 
-               if ( morphAttributes ) {
+       }
 
-                       for ( const key in morphAttributes ) {
+       dispose() {
 
-                               const attributeArray = morphAttributes[ key ];
+               this.shadow.dispose();
 
-                               const array = [];
+       }
 
-                               for ( let i = 0, il = attributeArray.length; i < il; i ++ ) {
+       copy( source ) {
 
-                                       const attribute = attributeArray[ i ];
-                                       let bufferAttribute;
+               super.copy( source );
 
-                                       if ( attribute.isInterleavedBufferAttribute ) {
+               this.distance = source.distance;
+               this.angle = source.angle;
+               this.penumbra = source.penumbra;
+               this.decay = source.decay;
 
-                                               const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data );
-                                               bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized );
+               this.target = source.target.clone();
 
-                                       } else {
+               this.shadow = source.shadow.clone();
 
-                                               const typedArray = getTypedArray( attribute.type, attribute.array );
-                                               bufferAttribute = new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized );
+               return this;
 
-                                       }
+       }
 
-                                       if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name;
-                                       array.push( bufferAttribute );
+    }
 
-                               }
+    SpotLight.prototype.isSpotLight = true;
 
-                               geometry.morphAttributes[ key ] = array;
+    const _projScreenMatrix = /*@__PURE__*/ new Matrix4();
+    const _lightPositionWorld = /*@__PURE__*/ new Vector3();
+    const _lookTarget = /*@__PURE__*/ new Vector3();
 
-                       }
+    class PointLightShadow extends LightShadow {
 
-               }
+       constructor() {
 
-               const morphTargetsRelative = json.data.morphTargetsRelative;
+               super( new PerspectiveCamera( 90, 1, 0.5, 500 ) );
 
-               if ( morphTargetsRelative ) {
+               this._frameExtents = new Vector2( 4, 2 );
 
-                       geometry.morphTargetsRelative = true;
+               this._viewportCount = 6;
 
-               }
+               this._viewports = [
+                       // These viewports map a cube-map onto a 2D texture with the
+                       // following orientation:
+                       //
+                       //  xzXZ
+                       //   y Y
+                       //
+                       // X - Positive x direction
+                       // x - Negative x direction
+                       // Y - Positive y direction
+                       // y - Negative y direction
+                       // Z - Positive z direction
+                       // z - Negative z direction
+
+                       // positive X
+                       new Vector4( 2, 1, 1, 1 ),
+                       // negative X
+                       new Vector4( 0, 1, 1, 1 ),
+                       // positive Z
+                       new Vector4( 3, 1, 1, 1 ),
+                       // negative Z
+                       new Vector4( 1, 1, 1, 1 ),
+                       // positive Y
+                       new Vector4( 3, 0, 1, 1 ),
+                       // negative Y
+                       new Vector4( 1, 0, 1, 1 )
+               ];
 
-               const groups = json.data.groups || json.data.drawcalls || json.data.offsets;
+               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 ( groups !== 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 )
+               ];
 
-                       for ( let i = 0, n = groups.length; i !== n; ++ i ) {
+       }
 
-                               const group = groups[ i ];
+       updateMatrices( light, viewportIndex = 0 ) {
 
-                               geometry.addGroup( group.start, group.count, group.materialIndex );
+               const camera = this.camera;
+               const shadowMatrix = this.matrix;
 
-                       }
+               const far = light.distance || camera.far;
 
-               }
+               if ( far !== camera.far ) {
 
-               const boundingSphere = json.data.boundingSphere;
+                       camera.far = far;
+                       camera.updateProjectionMatrix();
 
-               if ( boundingSphere !== undefined ) {
+               }
 
-                       const center = new Vector3();
+               _lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
+               camera.position.copy( _lightPositionWorld );
 
-                       if ( boundingSphere.center !== undefined ) {
+               _lookTarget.copy( camera.position );
+               _lookTarget.add( this._cubeDirections[ viewportIndex ] );
+               camera.up.copy( this._cubeUps[ viewportIndex ] );
+               camera.lookAt( _lookTarget );
+               camera.updateMatrixWorld();
 
-                               center.fromArray( boundingSphere.center );
+               shadowMatrix.makeTranslation( - _lightPositionWorld.x, - _lightPositionWorld.y, - _lightPositionWorld.z );
 
-                       }
+               _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+               this._frustum.setFromProjectionMatrix( _projScreenMatrix );
 
-                       geometry.boundingSphere = new Sphere( center, boundingSphere.radius );
+       }
 
-               }
+    }
 
-               if ( json.name ) geometry.name = json.name;
-               if ( json.userData ) geometry.userData = json.userData;
+    PointLightShadow.prototype.isPointLightShadow = true;
 
-               return geometry;
+    class PointLight extends Light {
 
-       }
+       constructor( color, intensity, distance = 0, decay = 1 ) {
 
-    } );
+               super( color, intensity );
 
-    function ImageBitmapLoader( manager ) {
+               this.type = 'PointLight';
 
-       if ( typeof createImageBitmap === 'undefined' ) {
+               this.distance = distance;
+               this.decay = decay; // for physically correct lights, should be 2.
 
-               console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' );
+               this.shadow = new PointLightShadow();
 
        }
 
-       if ( typeof fetch === 'undefined' ) {
+       get power() {
 
-               console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' );
+               // 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;
 
        }
 
-       Loader.call( this, manager );
+       set power( power ) {
 
-       this.options = { premultiplyAlpha: 'none' };
+               // set the light's intensity (in candela) from the desired luminous power (in lumens)
+               this.intensity = power / ( 4 * Math.PI );
 
-    }
-
-    ImageBitmapLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+       }
 
-       constructor: ImageBitmapLoader,
+       dispose() {
 
-       isImageBitmapLoader: true,
+               this.shadow.dispose();
 
-       setOptions: function setOptions( options ) {
+       }
 
-               this.options = options;
+       copy( source ) {
 
-               return this;
+               super.copy( source );
 
-       },
+               this.distance = source.distance;
+               this.decay = source.decay;
 
-       load: function ( url, onLoad, onProgress, onError ) {
+               this.shadow = source.shadow.clone();
 
-               if ( url === undefined ) url = '';
+               return this;
 
-               if ( this.path !== undefined ) url = this.path + url;
+       }
 
-               url = this.manager.resolveURL( url );
+    }
 
-               const scope = this;
+    PointLight.prototype.isPointLight = true;
 
-               const cached = Cache.get( url );
+    class DirectionalLightShadow extends LightShadow {
 
-               if ( cached !== undefined ) {
+       constructor() {
 
-                       scope.manager.itemStart( url );
+               super( new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) );
 
-                       setTimeout( function () {
+       }
 
-                               if ( onLoad ) onLoad( cached );
+    }
 
-                               scope.manager.itemEnd( url );
+    DirectionalLightShadow.prototype.isDirectionalLightShadow = true;
 
-                       }, 0 );
+    class DirectionalLight extends Light {
 
-                       return cached;
+       constructor( color, intensity ) {
 
-               }
+               super( color, intensity );
 
-               const fetchOptions = {};
-               fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include';
+               this.type = 'DirectionalLight';
 
-               fetch( url, fetchOptions ).then( function ( res ) {
+               this.position.copy( Object3D.DefaultUp );
+               this.updateMatrix();
 
-                       return res.blob();
+               this.target = new Object3D();
 
-               } ).then( function ( blob ) {
+               this.shadow = new DirectionalLightShadow();
 
-                       return createImageBitmap( blob, scope.options );
+       }
 
-               } ).then( function ( imageBitmap ) {
+       dispose() {
 
-                       Cache.add( url, imageBitmap );
+               this.shadow.dispose();
 
-                       if ( onLoad ) onLoad( imageBitmap );
+       }
 
-                       scope.manager.itemEnd( url );
+       copy( source ) {
 
-               } ).catch( function ( e ) {
+               super.copy( source );
 
-                       if ( onError ) onError( e );
+               this.target = source.target.clone();
+               this.shadow = source.shadow.clone();
 
-                       scope.manager.itemError( url );
-                       scope.manager.itemEnd( url );
+               return this;
 
-               } );
+       }
 
-               scope.manager.itemStart( url );
+    }
 
-       }
+    DirectionalLight.prototype.isDirectionalLight = true;
 
-    } );
+    class AmbientLight extends Light {
 
-    function ShapePath() {
+       constructor( color, intensity ) {
 
-       this.type = 'ShapePath';
+               super( color, intensity );
 
-       this.color = new Color();
+               this.type = 'AmbientLight';
 
-       this.subPaths = [];
-       this.currentPath = null;
+       }
 
     }
 
-    Object.assign( ShapePath.prototype, {
+    AmbientLight.prototype.isAmbientLight = true;
 
-       moveTo: function ( x, y ) {
+    class RectAreaLight extends Light {
 
-               this.currentPath = new Path();
-               this.subPaths.push( this.currentPath );
-               this.currentPath.moveTo( x, y );
+       constructor( color, intensity, width = 10, height = 10 ) {
 
-               return this;
+               super( color, intensity );
 
-       },
+               this.type = 'RectAreaLight';
 
-       lineTo: function ( x, y ) {
+               this.width = width;
+               this.height = height;
 
-               this.currentPath.lineTo( x, y );
+       }
 
-               return this;
+       get power() {
 
-       },
+               // compute the light's luminous power (in lumens) from its intensity (in nits)
+               return this.intensity * this.width * this.height * Math.PI;
 
-       quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
+       }
 
-               this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY );
+       set power( power ) {
 
-               return this;
+               // set the light's intensity (in nits) from the desired luminous power (in lumens)
+               this.intensity = power / ( this.width * this.height * Math.PI );
 
-       },
+       }
+
+       copy( source ) {
 
-       bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
+               super.copy( source );
 
-               this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY );
+               this.width = source.width;
+               this.height = source.height;
 
                return this;
 
-       },
+       }
 
-       splineThru: function ( pts ) {
+       toJSON( meta ) {
 
-               this.currentPath.splineThru( pts );
+               const data = super.toJSON( meta );
 
-               return this;
+               data.object.width = this.width;
+               data.object.height = this.height;
 
-       },
+               return data;
+
+       }
 
-       toShapes: function ( isCCW, noHoles ) {
+    }
 
-               function toShapesNoHoles( inSubpaths ) {
+    RectAreaLight.prototype.isRectAreaLight = true;
 
-                       const shapes = [];
+    /**
+     * Primary reference:
+     *   https://graphics.stanford.edu/papers/envmap/envmap.pdf
+     *
+     * Secondary reference:
+     *   https://www.ppsloan.org/publications/StupidSH36.pdf
+     */
 
-                       for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) {
+    // 3-band SH defined by 9 coefficients
 
-                               const tmpPath = inSubpaths[ i ];
+    class SphericalHarmonics3 {
 
-                               const tmpShape = new Shape();
-                               tmpShape.curves = tmpPath.curves;
+       constructor() {
 
-                               shapes.push( tmpShape );
+               this.coefficients = [];
 
-                       }
+               for ( let i = 0; i < 9; i ++ ) {
 
-                       return shapes;
+                       this.coefficients.push( new Vector3() );
 
                }
 
-               function isPointInsidePolygon( inPt, inPolygon ) {
+       }
 
-                       const polyLen = inPolygon.length;
+       set( coefficients ) {
 
-                       // 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 ++ ) {
+               for ( let i = 0; i < 9; i ++ ) {
 
-                               let edgeLowPt = inPolygon[ p ];
-                               let edgeHighPt = inPolygon[ q ];
+                       this.coefficients[ i ].copy( coefficients[ i ] );
 
-                               let edgeDx = edgeHighPt.x - edgeLowPt.x;
-                               let edgeDy = edgeHighPt.y - edgeLowPt.y;
+               }
 
-                               if ( Math.abs( edgeDy ) > Number.EPSILON ) {
+               return this;
 
-                                       // not parallel
-                                       if ( edgeDy < 0 ) {
+       }
 
-                                               edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx;
-                                               edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;
+       zero() {
 
-                                       }
+               for ( let i = 0; i < 9; i ++ ) {
 
-                                       if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) )            continue;
+                       this.coefficients[ i ].set( 0, 0, 0 );
 
-                                       if ( inPt.y === edgeLowPt.y ) {
+               }
 
-                                               if ( inPt.x === edgeLowPt.x )           return  true;           // inPt is on contour ?
-                                               // continue;                            // no intersection or edgeLowPt => doesn't count !!!
+               return this;
 
-                                       } else {
+       }
 
-                                               const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y );
-                                               if ( perpEdge === 0 )                           return  true;           // inPt is on contour ?
-                                               if ( perpEdge < 0 )                             continue;
-                                               inside = ! inside;              // true intersection left of inPt
+       // get the radiance in the direction of the normal
+       // target is a Vector3
+       getAt( normal, target ) {
 
-                                       }
+               // normal is assumed to be unit length
 
-                               } else {
+               const x = normal.x, y = normal.y, z = normal.z;
 
-                                       // 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;
+               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 );
 
-                       return  inside;
+               // 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;
 
-               const isClockWise = ShapeUtils.isClockWise;
+       }
 
-               const subPaths = this.subPaths;
-               if ( subPaths.length === 0 ) return [];
+       // 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 ) {
 
-               if ( noHoles === true ) return  toShapesNoHoles( subPaths );
+               // normal is assumed to be unit length
 
+               const x = normal.x, y = normal.y, z = normal.z;
 
-               let solid, tmpPath, tmpShape;
-               const shapes = [];
+               const coeff = this.coefficients;
 
-               if ( subPaths.length === 1 ) {
+               // band 0
+               target.copy( coeff[ 0 ] ).multiplyScalar( 0.886227 ); // Ï€ * 0.282095
 
-                       tmpPath = subPaths[ 0 ];
-                       tmpShape = new Shape();
-                       tmpShape.curves = tmpPath.curves;
-                       shapes.push( tmpShape );
-                       return shapes;
+               // 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
 
-               let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() );
-               holesFirst = isCCW ? ! holesFirst : holesFirst;
+               return target;
 
-               // console.log("Holes first", holesFirst);
+       }
 
-               const betterShapeHoles = [];
-               const newShapes = [];
-               let newShapeHoles = [];
-               let mainIdx = 0;
-               let tmpPoints;
+       add( sh ) {
 
-               newShapes[ mainIdx ] = undefined;
-               newShapeHoles[ mainIdx ] = [];
+               for ( let i = 0; i < 9; i ++ ) {
 
-               for ( let i = 0, l = subPaths.length; i < l; i ++ ) {
+                       this.coefficients[ i ].add( sh.coefficients[ i ] );
 
-                       tmpPath = subPaths[ i ];
-                       tmpPoints = tmpPath.getPoints();
-                       solid = isClockWise( tmpPoints );
-                       solid = isCCW ? ! solid : solid;
+               }
 
-                       if ( solid ) {
+               return this;
 
-                               if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) )     mainIdx ++;
+       }
 
-                               newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints };
-                               newShapes[ mainIdx ].s.curves = tmpPath.curves;
+       addScaledSH( sh, s ) {
 
-                               if ( holesFirst )       mainIdx ++;
-                               newShapeHoles[ mainIdx ] = [];
+               for ( let i = 0; i < 9; i ++ ) {
 
-                               //console.log('cw', i);
+                       this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s );
 
-                       } else {
+               }
 
-                               newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } );
+               return this;
 
-                               //console.log('ccw', i);
+       }
 
-                       }
+       scale( s ) {
 
-               }
+               for ( let i = 0; i < 9; i ++ ) {
 
-               // only Holes? -> probably all Shapes with wrong orientation
-               if ( ! newShapes[ 0 ] ) return  toShapesNoHoles( subPaths );
+                       this.coefficients[ i ].multiplyScalar( s );
 
+               }
 
-               if ( newShapes.length > 1 ) {
+               return this;
 
-                       let ambiguous = false;
-                       const toChange = [];
+       }
 
-                       for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
+       lerp( sh, alpha ) {
 
-                               betterShapeHoles[ sIdx ] = [];
+               for ( let i = 0; i < 9; i ++ ) {
 
-                       }
+                       this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha );
 
-                       for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
+               }
 
-                               const sho = newShapeHoles[ sIdx ];
+               return this;
 
-                               for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) {
+       }
 
-                                       const ho = sho[ hIdx ];
-                                       let hole_unassigned = true;
+       equals( sh ) {
 
-                                       for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) {
+               for ( let i = 0; i < 9; i ++ ) {
 
-                                               if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) {
+                       if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) {
 
-                                                       if ( sIdx !== s2Idx )   toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } );
-                                                       if ( hole_unassigned ) {
+                               return false;
 
-                                                               hole_unassigned = false;
-                                                               betterShapeHoles[ s2Idx ].push( ho );
+                       }
 
-                                                       } else {
+               }
 
-                                                               ambiguous = true;
+               return true;
 
-                                                       }
+       }
 
-                                               }
+       copy( sh ) {
 
-                                       }
+               return this.set( sh.coefficients );
 
-                                       if ( hole_unassigned ) {
+       }
 
-                                               betterShapeHoles[ sIdx ].push( ho );
+       clone() {
 
-                                       }
+               return new this.constructor().copy( this );
 
-                               }
+       }
 
-                       }
-                       // console.log("ambiguous: ", ambiguous);
+       fromArray( array, offset = 0 ) {
 
-                       if ( toChange.length > 0 ) {
+               const coefficients = this.coefficients;
 
-                               // console.log("to change: ", toChange);
-                               if ( ! ambiguous )      newShapeHoles = betterShapeHoles;
+               for ( let i = 0; i < 9; i ++ ) {
 
-                       }
+                       coefficients[ i ].fromArray( array, offset + ( i * 3 ) );
 
                }
 
-               let tmpHoles;
+               return this;
 
-               for ( let i = 0, il = newShapes.length; i < il; i ++ ) {
+       }
 
-                       tmpShape = newShapes[ i ].s;
-                       shapes.push( tmpShape );
-                       tmpHoles = newShapeHoles[ i ];
+       toArray( array = [], offset = 0 ) {
 
-                       for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) {
+               const coefficients = this.coefficients;
 
-                               tmpShape.holes.push( tmpHoles[ j ].h );
+               for ( let i = 0; i < 9; i ++ ) {
 
-                       }
+                       coefficients[ i ].toArray( array, offset + ( i * 3 ) );
 
                }
 
-               //console.log("shape", shapes);
-
-               return shapes;
+               return array;
 
        }
 
-    } );
+       // evaluate the basis functions
+       // shBasis is an Array[ 9 ]
+       static getBasisAt( normal, shBasis ) {
 
-    class Font {
+               // normal is assumed to be unit length
 
-       constructor( data ) {
+               const x = normal.x, y = normal.y, z = normal.z;
 
-               Object.defineProperty( this, 'isFont', { value: true } );
+               // band 0
+               shBasis[ 0 ] = 0.282095;
 
-               this.type = 'Font';
+               // band 1
+               shBasis[ 1 ] = 0.488603 * y;
+               shBasis[ 2 ] = 0.488603 * z;
+               shBasis[ 3 ] = 0.488603 * x;
 
-               this.data = data;
+               // 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 );
 
        }
 
-       generateShapes( text, size = 100 ) {
+    }
 
-               const shapes = [];
-               const paths = createPaths( text, size, this.data );
+    SphericalHarmonics3.prototype.isSphericalHarmonics3 = true;
 
-               for ( let p = 0, pl = paths.length; p < pl; p ++ ) {
+    class LightProbe extends Light {
 
-                       Array.prototype.push.apply( shapes, paths[ p ].toShapes() );
+       constructor( sh = new SphericalHarmonics3(), intensity = 1 ) {
 
-               }
+               super( undefined, intensity );
 
-               return shapes;
+               this.sh = sh;
 
        }
 
-    }
+       copy( source ) {
+
+               super.copy( source );
 
-    function createPaths( text, size, data ) {
+               this.sh.copy( source.sh );
 
-       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;
+               return this;
 
-       const paths = [];
+       }
 
-       let offsetX = 0, offsetY = 0;
+       fromJSON( json ) {
 
-       for ( let i = 0; i < chars.length; i ++ ) {
+               this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON();
+               this.sh.fromArray( json.sh );
 
-               const char = chars[ i ];
+               return this;
 
-               if ( char === '\n' ) {
+       }
 
-                       offsetX = 0;
-                       offsetY -= line_height;
+       toJSON( meta ) {
 
-               } else {
+               const data = super.toJSON( meta );
 
-                       const ret = createPath( char, scale, offsetX, offsetY, data );
-                       offsetX += ret.offsetX;
-                       paths.push( ret.path );
+               data.object.sh = this.sh.toArray();
 
-               }
+               return data;
 
        }
 
-       return paths;
-
     }
 
-    function createPath( char, scale, offsetX, offsetY, data ) {
+    LightProbe.prototype.isLightProbe = true;
 
-       const glyph = data.glyphs[ char ] || data.glyphs[ '?' ];
+    class LoaderUtils {
 
-       if ( ! glyph ) {
+       static decodeText( array ) {
 
-               console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' );
+               if ( typeof TextDecoder !== 'undefined' ) {
 
-               return;
+                       return new TextDecoder().decode( array );
 
-       }
+               }
 
-       const path = new ShapePath();
+               // Avoid the String.fromCharCode.apply(null, array) shortcut, which
+               // throws a "maximum call stack size exceeded" error for large arrays.
+
+               let s = '';
 
-       let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;
+               for ( let i = 0, il = array.length; i < il; i ++ ) {
 
-       if ( glyph.o ) {
+                       // Implicitly assumes little-endian.
+                       s += String.fromCharCode( array[ i ] );
 
-               const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );
+               }
 
-               for ( let i = 0, l = outline.length; i < l; ) {
+               try {
 
-                       const action = outline[ i ++ ];
+                       // merges multi-byte utf-8 characters.
 
-                       switch ( action ) {
+                       return decodeURIComponent( escape( s ) );
 
-                               case 'm': // moveTo
+               } catch ( e ) { // see #16358
 
-                                       x = outline[ i ++ ] * scale + offsetX;
-                                       y = outline[ i ++ ] * scale + offsetY;
+                       return s;
 
-                                       path.moveTo( x, y );
+               }
 
-                                       break;
+       }
 
-                               case 'l': // lineTo
+       static extractUrlBase( url ) {
 
-                                       x = outline[ i ++ ] * scale + offsetX;
-                                       y = outline[ i ++ ] * scale + offsetY;
+               const index = url.lastIndexOf( '/' );
 
-                                       path.lineTo( x, y );
+               if ( index === - 1 ) return './';
 
-                                       break;
+               return url.substr( 0, index + 1 );
 
-                               case 'q': // quadraticCurveTo
+       }
 
-                                       cpx = outline[ i ++ ] * scale + offsetX;
-                                       cpy = outline[ i ++ ] * scale + offsetY;
-                                       cpx1 = outline[ i ++ ] * scale + offsetX;
-                                       cpy1 = outline[ i ++ ] * scale + offsetY;
+       static resolveURL( url, path ) {
 
-                                       path.quadraticCurveTo( cpx1, cpy1, cpx, cpy );
+               // Invalid URL
+               if ( typeof url !== 'string' || url === '' ) return '';
 
-                                       break;
+               // Host Relative URL
+               if ( /^https?:\/\//i.test( path ) && /^\//.test( url ) ) {
 
-                               case 'b': // bezierCurveTo
+                       path = path.replace( /(^https?:\/\/[^\/]+).*/i, '$1' );
 
-                                       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 );
+               // Absolute URL http://,https://,//
+               if ( /^(https?:)?\/\//i.test( url ) ) return url;
 
-                                       break;
+               // Data URI
+               if ( /^data:.*,.*$/i.test( url ) ) return url;
 
-                       }
+               // Blob URL
+               if ( /^blob:.*$/i.test( url ) ) return url;
 
-               }
+               // Relative URL
+               return path + url;
 
        }
 
-       return { offsetX: glyph.ha * scale, path: path };
-
     }
 
-    function FontLoader( manager ) {
+    class InstancedBufferGeometry extends BufferGeometry {
 
-       Loader.call( this, manager );
+       constructor() {
 
-    }
+               super();
 
-    FontLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+               this.type = 'InstancedBufferGeometry';
+               this.instanceCount = Infinity;
 
-       constructor: FontLoader,
+       }
 
-       load: function ( url, onLoad, onProgress, onError ) {
+       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;
+
+               data.isInstancedBufferGeometry = true;
+
+               return data;
+
+       }
+
+    }
+
+    InstancedBufferGeometry.prototype.isInstancedBufferGeometry = true;
+
+    class ImageBitmapLoader extends Loader {
+
+       constructor( manager ) {
+
+               super( manager );
+
+               if ( typeof createImageBitmap === 'undefined' ) {
+
+                       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 );
+
+                               scope.manager.itemEnd( url );
 
-                               console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' );
-                               json = JSON.parse( text.substring( 65, text.length - 2 ) );
+                       }, 0 );
 
-                       }
+                       return cached;
 
-                       const font = scope.parse( json );
+               }
 
-                       if ( onLoad ) onLoad( font );
+               const fetchOptions = {};
+               fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include';
+               fetchOptions.headers = this.requestHeader;
 
-               }, onProgress, onError );
+               fetch( url, fetchOptions ).then( function ( res ) {
 
-       },
+                       return res.blob();
+
+               } ).then( function ( blob ) {
 
-       parse: function ( json ) {
+                       return createImageBitmap( blob, Object.assign( scope.options, { colorSpaceConversion: 'none' } ) );
 
-               return new Font( json );
+               } ).then( function ( imageBitmap ) {
+
+                       Cache.add( url, imageBitmap );
+
+                       if ( onLoad ) onLoad( imageBitmap );
+
+                       scope.manager.itemEnd( url );
+
+               } ).catch( function ( e ) {
+
+                       if ( onError ) onError( e );
+
+                       scope.manager.itemError( url );
+                       scope.manager.itemEnd( url );
+
+               } );
+
+               scope.manager.itemStart( url );
 
        }
 
-    } );
+    }
+
+    ImageBitmapLoader.prototype.isImageBitmapLoader = true;
 
     let _context;
 
 
     };
 
-    function AudioLoader( manager ) {
-
-       Loader.call( this, manager );
+    class AudioLoader extends Loader {
 
-    }
+       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 {
 
        }
 
-    } );
-
-    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 ), {
+    class HemisphereLightProbe extends LightProbe {
 
-       constructor: HemisphereLightProbe,
+       constructor( skyColor, groundColor, intensity = 1 ) {
 
-       isHemisphereLightProbe: true,
+               super( undefined, intensity );
 
-       copy: function ( source ) { // modifying colors not currently supported
+               const color1 = new Color().set( skyColor );
+               const color2 = new Color().set( groundColor );
 
-               LightProbe.prototype.copy.call( this, source );
+               const sky = new Vector3( color1.r, color1.g, color1.b );
+               const ground = new Vector3( color2.r, color2.g, color2.b );
 
-               return this;
-
-       },
-
-       toJSON: function ( meta ) {
-
-               const data = LightProbe.prototype.toJSON.call( this, meta );
+               // 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 );
 
-               // data.sh = this.sh.toArray(); // todo
-
-               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 );
-
-               return this;
-
-       },
+               super( undefined, intensity );
 
-       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 {
 
 
     }
 
-    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
 
                this.cumulativeWeight = currentWeight;
 
-       },
+       }
 
        // accumulate data in the 'incoming' region into 'add'
-       accumulateAdditive: function ( weight ) {
+       accumulateAdditive( weight ) {
 
                const buffer = this.buffer,
                        stride = this.valueSize,
                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,
 
                }
 
-       },
+       }
 
        // remember the state of the bound property and copy it to both accus
-       saveOriginalState: function () {
+       saveOriginalState() {
 
                const binding = this.binding;
 
                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;
 
                }
 
-       },
+       }
 
-       _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;
 
                }
 
-       },
+       }
 
 
        // mix functions
 
-       _select: function ( buffer, dstOffset, srcOffset, t, stride ) {
+       _select( buffer, dstOffset, srcOffset, t, stride ) {
 
                if ( t >= 0.5 ) {
 
 
                }
 
-       },
+       }
 
-       _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;
 
                // 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;
 
 
                }
 
-       },
+       }
 
-       _lerpAdditive: function ( buffer, dstOffset, srcOffset, t, stride ) {
+       _lerpAdditive( buffer, dstOffset, srcOffset, t, stride ) {
 
                for ( let i = 0; i !== stride; ++ i ) {
 
 
        }
 
-    } );
+    }
 
     // Characters [].:/ are reserved for track binding syntax.
     const _RESERVED_CHARS_RE = '\\[\\]\\.:\\/';
 
     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
 
                // 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;
 
 
                }
 
-       },
+       }
 
-       bind: function () {
+       bind() {
 
                const bindings = this._bindings;
 
 
                }
 
-       },
+       }
 
-       unbind: function () {
+       unbind() {
 
                const bindings = this._bindings;
 
 
        }
 
-    } );
+    }
 
+    // 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 ) ) {
 
 
                }
 
-       },
+       }
 
        /**
         * Replaces spaces with underscores and removes unsupported characters from
         * @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 );
 
 
                return results;
 
-       },
+       }
 
-       findNode: function ( root, nodeName ) {
+       static findNode( root, nodeName ) {
 
                if ( ! nodeName || nodeName === '' || nodeName === '.' || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) {
 
 
        }
 
-    } );
-
-    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
-       },
+       _getValue_unavailable() {}
+       _setValue_unavailable() {}
 
-       Versioning: {
-               None: 0,
-               NeedsUpdate: 1,
-               MatrixWorldNeedsUpdate: 2
-       },
-
-       GetterByBindingType: [
-
-               function getValue_direct( buffer, offset ) {
+       // Getters
 
-                       buffer[ offset ] = this.node[ this.propertyName ];
-
-               },
+       _getValue_direct( buffer, offset ) {
 
-               function getValue_array( buffer, offset ) {
+               buffer[ offset ] = this.targetObject[ this.propertyName ];
 
-                       const source = this.resolvedProperty;
-
-                       for ( let i = 0, n = source.length; i !== n; ++ i ) {
-
-                               buffer[ offset ++ ] = source[ i ];
-
-                       }
-
-               },
-
-               function getValue_arrayElement( buffer, offset ) {
+       }
 
-                       buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
+       _getValue_array( buffer, offset ) {
 
-               },
+               const source = this.resolvedProperty;
 
-               function getValue_toArray( buffer, offset ) {
+               for ( let i = 0, n = source.length; i !== n; ++ i ) {
 
-                       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;
                this.getValue = this.GetterByBindingType[ bindingType ];
                this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
 
-       },
+       }
 
-       unbind: function () {
+       unbind() {
 
                this.node = null;
 
 
        }
 
-    } );
-
-    // DECLARE ALIAS AFTER assign prototype
-    Object.assign( PropertyBinding.prototype, {
-
-       // initial state of these methods that calls 'bind'
-       _getValue_unbound: PropertyBinding.prototype.getValue,
-       _setValue_unbound: PropertyBinding.prototype.setValue,
-
-    } );
-
-    /**
-     *
-     * A group of objects that receives a shared animation state.
-     *
-     * Usage:
-     *
-     *  - Add objects you would otherwise pass as 'root' to the
-     *    constructor or the .clipAction method of AnimationMixer.
-     *
-     *  - Instead pass this object as 'root'.
-     *
-     *  - You can also add and remove objects later when the mixer
-     *    is running.
-     *
-     * Note:
-     *
-     *    Objects of this class appear as one object to the mixer,
-     *    so cache control of the individual objects must be done
-     *    on the group.
-     *
-     * Limitation:
-     *
-     *  - The animated properties must be compatible among the
-     *    all objects in the group.
-     *
-     *  - A single property can either be controlled through a
-     *    target group or directly, but not both.
-     */
-
-    function AnimationObjectGroup() {
-
-       this.uuid = MathUtils.generateUUID();
-
-       // cached objects followed by the active ones
-       this._objects = Array.prototype.slice.call( arguments );
-
-       this.nCachedObjects_ = 0; // threshold
-       // note: read by PropertyBinding.Composite
-
-       const indices = {};
-       this._indicesByUUID = indices; // for bookkeeping
-
-       for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
-
-               indices[ arguments[ i ].uuid ] = i;
-
-       }
-
-       this._paths = []; // inside: string
-       this._parsedPaths = []; // inside: { we don't care, here }
-       this._bindings = []; // inside: Array< PropertyBinding >
-       this._bindingsIndicesByPath = {}; // inside: indices in these arrays
-
-       const scope = this;
-
-       this.stats = {
-
-               objects: {
-                       get total() {
-
-                               return scope._objects.length;
-
-                       },
-                       get inUse() {
-
-                               return this.total - scope.nCachedObjects_;
-
-                       }
-               },
-               get bindingsPerObject() {
-
-                       return scope._bindings.length;
-
-               }
-
-       };
-
     }
 
-    Object.assign( AnimationObjectGroup.prototype, {
-
-       isAnimationObjectGroup: true,
-
-       add: function () {
-
-               const objects = this._objects,
-                       indicesByUUID = this._indicesByUUID,
-                       paths = this._paths,
-                       parsedPaths = this._parsedPaths,
-                       bindings = this._bindings,
-                       nBindings = bindings.length;
-
-               let knownObject = undefined,
-                       nObjects = objects.length,
-                       nCachedObjects = this.nCachedObjects_;
-
-               for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
-
-                       const object = arguments[ i ],
-                               uuid = object.uuid;
-                       let index = indicesByUUID[ uuid ];
-
-                       if ( index === undefined ) {
-
-                               // unknown object -> add it to the ACTIVE region
-
-                               index = nObjects ++;
-                               indicesByUUID[ uuid ] = index;
-                               objects.push( object );
-
-                               // accounting is done, now do the same for all bindings
-
-                               for ( let j = 0, m = nBindings; j !== m; ++ j ) {
-
-                                       bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) );
-
-                               }
-
-                       } else if ( index < nCachedObjects ) {
-
-                               knownObject = objects[ index ];
-
-                               // move existing object to the ACTIVE region
-
-                               const firstActiveIndex = -- nCachedObjects,
-                                       lastCachedObject = objects[ firstActiveIndex ];
-
-                               indicesByUUID[ lastCachedObject.uuid ] = index;
-                               objects[ index ] = lastCachedObject;
-
-                               indicesByUUID[ uuid ] = firstActiveIndex;
-                               objects[ firstActiveIndex ] = object;
-
-                               // accounting is done, now do the same for all bindings
-
-                               for ( let j = 0, m = nBindings; j !== m; ++ j ) {
-
-                                       const bindingsForPath = bindings[ j ],
-                                               lastCached = bindingsForPath[ firstActiveIndex ];
-
-                                       let binding = bindingsForPath[ index ];
-
-                                       bindingsForPath[ index ] = lastCached;
-
-                                       if ( binding === undefined ) {
-
-                                               // since we do not bother to create new bindings
-                                               // for objects that are cached, the binding may
-                                               // or may not exist
-
-                                               binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] );
-
-                                       }
-
-                                       bindingsForPath[ firstActiveIndex ] = binding;
-
-                               }
-
-                       } else if ( objects[ index ] !== knownObject ) {
-
-                               console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' +
-                                       'detected. Clean the caches or recreate your infrastructure when reloading scenes.' );
-
-                       } // else the object is already where we want it to be
-
-               } // for arguments
-
-               this.nCachedObjects_ = nCachedObjects;
-
-       },
-
-       remove: function () {
-
-               const objects = this._objects,
-                       indicesByUUID = this._indicesByUUID,
-                       bindings = this._bindings,
-                       nBindings = bindings.length;
-
-               let nCachedObjects = this.nCachedObjects_;
-
-               for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
-
-                       const object = arguments[ i ],
-                               uuid = object.uuid,
-                               index = indicesByUUID[ uuid ];
-
-                       if ( index !== undefined && index >= nCachedObjects ) {
-
-                               // move existing object into the CACHED region
-
-                               const lastCachedIndex = nCachedObjects ++,
-                                       firstActiveObject = objects[ lastCachedIndex ];
-
-                               indicesByUUID[ firstActiveObject.uuid ] = index;
-                               objects[ index ] = firstActiveObject;
-
-                               indicesByUUID[ uuid ] = lastCachedIndex;
-                               objects[ lastCachedIndex ] = object;
-
-                               // accounting is done, now do the same for all bindings
-
-                               for ( let j = 0, m = nBindings; j !== m; ++ j ) {
-
-                                       const bindingsForPath = bindings[ j ],
-                                               firstActive = bindingsForPath[ lastCachedIndex ],
-                                               binding = bindingsForPath[ index ];
-
-                                       bindingsForPath[ index ] = firstActive;
-                                       bindingsForPath[ lastCachedIndex ] = binding;
-
-                               }
-
-                       }
-
-               } // for arguments
-
-               this.nCachedObjects_ = nCachedObjects;
-
-       },
-
-       // remove & forget
-       uncache: function () {
-
-               const objects = this._objects,
-                       indicesByUUID = this._indicesByUUID,
-                       bindings = this._bindings,
-                       nBindings = bindings.length;
-
-               let nCachedObjects = this.nCachedObjects_,
-                       nObjects = objects.length;
-
-               for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
-
-                       const object = arguments[ i ],
-                               uuid = object.uuid,
-                               index = indicesByUUID[ uuid ];
-
-                       if ( index !== undefined ) {
-
-                               delete indicesByUUID[ uuid ];
-
-                               if ( index < nCachedObjects ) {
-
-                                       // object is cached, shrink the CACHED region
-
-                                       const firstActiveIndex = -- nCachedObjects,
-                                               lastCachedObject = objects[ firstActiveIndex ],
-                                               lastIndex = -- nObjects,
-                                               lastObject = objects[ lastIndex ];
-
-                                       // last cached object takes this object's place
-                                       indicesByUUID[ lastCachedObject.uuid ] = index;
-                                       objects[ index ] = lastCachedObject;
-
-                                       // last object goes to the activated slot and pop
-                                       indicesByUUID[ lastObject.uuid ] = firstActiveIndex;
-                                       objects[ firstActiveIndex ] = lastObject;
-                                       objects.pop();
-
-                                       // accounting is done, now do the same for all bindings
-
-                                       for ( let j = 0, m = nBindings; j !== m; ++ j ) {
-
-                                               const bindingsForPath = bindings[ j ],
-                                                       lastCached = bindingsForPath[ firstActiveIndex ],
-                                                       last = bindingsForPath[ lastIndex ];
-
-                                               bindingsForPath[ index ] = lastCached;
-                                               bindingsForPath[ firstActiveIndex ] = last;
-                                               bindingsForPath.pop();
-
-                                       }
-
-                               } else {
-
-                                       // object is active, just swap with the last and pop
-
-                                       const lastIndex = -- nObjects,
-                                               lastObject = objects[ lastIndex ];
-
-                                       if ( lastIndex > 0 ) {
-
-                                               indicesByUUID[ lastObject.uuid ] = index;
-
-                                       }
-
-                                       objects[ index ] = lastObject;
-                                       objects.pop();
-
-                                       // accounting is done, now do the same for all bindings
-
-                                       for ( let j = 0, m = nBindings; j !== m; ++ j ) {
-
-                                               const bindingsForPath = bindings[ j ];
-
-                                               bindingsForPath[ index ] = bindingsForPath[ lastIndex ];
-                                               bindingsForPath.pop();
-
-                                       }
-
-                               } // cached or active
-
-                       } // if object is known
-
-               } // for arguments
-
-               this.nCachedObjects_ = nCachedObjects;
-
-       },
-
-       // Internal interface used by befriended PropertyBinding.Composite:
-
-       subscribe_: function ( path, parsedPath ) {
-
-               // returns an array of bindings for the given path that is changed
-               // according to the contained objects in the group
+    PropertyBinding.Composite = Composite;
 
-               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 {
 
 
     }
 
-    function AnimationMixer( root ) {
-
-       this._root = root;
-       this._initMemoryManager();
-       this._accuIndex = 0;
-
-       this.time = 0;
+    class AnimationMixer extends EventDispatcher {
 
-       this.timeScale = 1.0;
+       constructor( root ) {
 
-    }
+               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,
 
                }
 
-       },
+       }
 
-       _activateAction: function ( action ) {
+       _activateAction( action ) {
 
                if ( ! this._isActiveAction( action ) ) {
 
 
                }
 
-       },
+       }
 
-       _deactivateAction: function ( action ) {
+       _deactivateAction( action ) {
 
                if ( this._isActiveAction( action ) ) {
 
 
                }
 
-       },
+       }
 
        // Memory manager
 
-       _initMemoryManager: function () {
+       _initMemoryManager() {
 
                this._actions = []; // 'nActiveActions' followed by inactive ones
                this._nActiveActions = 0;
 
                };
 
-       },
+       }
 
        // 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;
 
                actionsForClip.actionByRoot[ rootUuid ] = action;
 
-       },
+       }
 
-       _removeInactiveAction: function ( action ) {
+       _removeInactiveAction( action ) {
 
                const actions = this._actions,
                        lastInactiveAction = actions[ actions.length - 1 ],
 
                this._removeInactiveBindingsForAction( action );
 
-       },
+       }
 
-       _removeInactiveBindingsForAction: function ( action ) {
+       _removeInactiveBindingsForAction( action ) {
 
                const bindings = action._propertyBindings;
 
 
                }
 
-       },
+       }
 
-       _lendAction: function ( action ) {
+       _lendAction( action ) {
 
                // [ active actions |  inactive actions  ]
                // [  active actions >| inactive actions ]
                firstInactiveAction._cacheIndex = prevIndex;
                actions[ prevIndex ] = firstInactiveAction;
 
-       },
+       }
 
-       _takeBackAction: function ( action ) {
+       _takeBackAction( action ) {
 
                // [  active actions  | inactive actions ]
                // [ active actions |< inactive actions  ]
                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;
                binding._cacheIndex = bindings.length;
                bindings.push( binding );
 
-       },
+       }
 
-       _removeInactiveBinding: function ( binding ) {
+       _removeInactiveBinding( binding ) {
 
                const bindings = this._bindings,
                        propBinding = binding.binding,
 
                }
 
-       },
+       }
 
-       _lendBinding: function ( binding ) {
+       _lendBinding( binding ) {
 
                const bindings = this._bindings,
                        prevIndex = binding._cacheIndex,
                firstInactiveBinding._cacheIndex = prevIndex;
                bindings[ prevIndex ] = firstInactiveBinding;
 
-       },
+       }
 
-       _takeBackBinding: function ( binding ) {
+       _takeBackBinding( binding ) {
 
                const bindings = this._bindings,
                        prevIndex = binding._cacheIndex,
                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 ++;
 
                return interpolant;
 
-       },
+       }
 
-       _takeBackControlInterpolant: function ( interpolant ) {
+       _takeBackControlInterpolant( interpolant ) {
 
                const interpolants = this._controlInterpolants,
                        prevIndex = interpolant.__cacheIndex,
                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;
 
                return newAction;
 
-       },
+       }
 
        // get an existing action
-       existingAction: function ( clip, optionalRoot ) {
+       existingAction( clip, optionalRoot ) {
 
                const root = optionalRoot || this._root,
                        rootUuid = root.uuid,
 
                return null;
 
-       },
+       }
 
        // deactivates all previously scheduled actions
-       stopAllAction: function () {
+       stopAllAction() {
 
                const actions = this._actions,
                        nActions = this._nActiveActions;
 
                return this;
 
-       },
+       }
 
        // advance the time and update apply the animation
-       update: function ( deltaTime ) {
+       update( deltaTime ) {
 
                deltaTime *= this.timeScale;
 
 
                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 ++ ) {
 
                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,
 
                }
 
-       },
+       }
 
        // free all resources specific to a particular root target object
-       uncacheRoot: function ( root ) {
+       uncacheRoot( root ) {
 
                const rootUuid = root.uuid,
                        actionsByClip = this._actionsByClip;
 
                }
 
-       },
+       }
 
        // remove a targeted clip from the cache
-       uncacheAction: function ( clip, optionalRoot ) {
+       uncacheAction( clip, optionalRoot ) {
 
                const action = this.existingAction( clip, optionalRoot );
 
 
        }
 
-    } );
-
-    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;
 
        }
 
-    } );
-
-    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;
 
-                       }
-               }
-       } );
+       }
 
     }
 
 
     }
 
-    Object.assign( Raycaster.prototype, {
-
-       set: function ( origin, direction ) {
-
-               // direction is assumed to be normalized (for accurate distance calculations)
-
-               this.ray.set( origin, direction );
-
-       },
-
-       setFromCamera: function ( coords, camera ) {
-
-               if ( camera && camera.isPerspectiveCamera ) {
-
-                       this.ray.origin.setFromMatrixPosition( camera.matrixWorld );
-                       this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize();
-                       this.camera = camera;
-
-               } else if ( camera && camera.isOrthographicCamera ) {
-
-                       this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera
-                       this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );
-                       this.camera = camera;
-
-               } else {
-
-                       console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type );
-
-               }
-
-       },
-
-       intersectObject: function ( object, recursive, optionalTarget ) {
-
-               const intersects = optionalTarget || [];
-
-               intersectObject( object, this, intersects, recursive );
-
-               intersects.sort( ascSort );
-
-               return intersects;
-
-       },
-
-       intersectObjects: function ( objects, recursive, optionalTarget ) {
-
-               const intersects = optionalTarget || [];
-
-               if ( Array.isArray( objects ) === false ) {
-
-                       console.warn( 'THREE.Raycaster.intersectObjects: objects is not an Array.' );
-                       return intersects;
-
-               }
-
-               for ( let i = 0, l = objects.length; i < l; i ++ ) {
-
-                       intersectObject( objects[ i ], this, intersects, recursive );
-
-               }
-
-               intersects.sort( ascSort );
-
-               return intersects;
-
-       }
-
-    } );
-
-    const _vector$8 = /*@__PURE__*/ new Vector2();
-
-    class Box2 {
+    const _vector$2 = /*@__PURE__*/ new Vector3();
+    const _boneMatrix = /*@__PURE__*/ new Matrix4();
+    const _matrixWorldInv = /*@__PURE__*/ new Matrix4();
 
-       constructor( min, max ) {
 
-               Object.defineProperty( this, 'isBox2', { value: true } );
+    class SkeletonHelper extends LineSegments {
 
-               this.min = ( min !== undefined ) ? min : new Vector2( + Infinity, + Infinity );
-               this.max = ( max !== undefined ) ? max : new Vector2( - Infinity, - Infinity );
+       constructor( object ) {
 
-       }
+               const bones = getBoneList( object );
 
-       set( min, max ) {
+               const geometry = new BufferGeometry();
 
-               this.min.copy( min );
-               this.max.copy( max );
+               const vertices = [];
+               const colors = [];
 
-               return this;
+               const color1 = new Color( 0, 0, 1 );
+               const color2 = new Color( 0, 1, 0 );
 
-       }
+               for ( let i = 0; i < bones.length; i ++ ) {
 
-       setFromPoints( points ) {
+                       const bone = bones[ i ];
 
-               this.makeEmpty();
+                       if ( bone.parent && bone.parent.isBone ) {
 
-               for ( let i = 0, il = points.length; i < il; i ++ ) {
+                               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 );
 
-                       this.expandByPoint( points[ i ] );
+                       }
 
                }
 
-               return this;
-
-       }
-
-       setFromCenterAndSize( center, size ) {
-
-               const halfSize = _vector$8.copy( size ).multiplyScalar( 0.5 );
-               this.min.copy( center ).sub( halfSize );
-               this.max.copy( center ).add( halfSize );
-
-               return this;
-
-       }
-
-       clone() {
-
-               return new this.constructor().copy( this );
-
-       }
-
-       copy( box ) {
-
-               this.min.copy( box.min );
-               this.max.copy( box.max );
+               geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
 
-               return this;
+               const material = new LineBasicMaterial( { vertexColors: true, depthTest: false, depthWrite: false, toneMapped: false, transparent: true } );
 
-       }
+               super( geometry, material );
 
-       makeEmpty() {
+               this.type = 'SkeletonHelper';
+               this.isSkeletonHelper = true;
 
-               this.min.x = this.min.y = + Infinity;
-               this.max.x = this.max.y = - Infinity;
+               this.root = object;
+               this.bones = bones;
 
-               return this;
+               this.matrix = object.matrixWorld;
+               this.matrixAutoUpdate = false;
 
        }
 
-       isEmpty() {
-
-               // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
-
-               return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y );
+       updateMatrixWorld( force ) {
 
-       }
+               const bones = this.bones;
 
-       getCenter( target ) {
+               const geometry = this.geometry;
+               const position = geometry.getAttribute( 'position' );
 
-               if ( target === undefined ) {
+               _matrixWorldInv.copy( this.root.matrixWorld ).invert();
 
-                       console.warn( 'THREE.Box2: .getCenter() target is now required' );
-                       target = new Vector2();
+               for ( let i = 0, j = 0; i < bones.length; i ++ ) {
 
-               }
+                       const bone = bones[ i ];
 
-               return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+                       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 );
 
-       getSize( target ) {
+                               _boneMatrix.multiplyMatrices( _matrixWorldInv, bone.parent.matrixWorld );
+                               _vector$2.setFromMatrixPosition( _boneMatrix );
+                               position.setXYZ( j + 1, _vector$2.x, _vector$2.y, _vector$2.z );
 
-               if ( target === undefined ) {
+                               j += 2;
 
-                       console.warn( 'THREE.Box2: .getSize() target is now required' );
-                       target = new Vector2();
+                       }
 
                }
 
-               return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min );
-
-       }
-
-       expandByPoint( point ) {
-
-               this.min.min( point );
-               this.max.max( point );
-
-               return this;
-
-       }
-
-       expandByVector( vector ) {
-
-               this.min.sub( vector );
-               this.max.add( vector );
+               geometry.getAttribute( 'position' ).needsUpdate = true;
 
-               return this;
+               super.updateMatrixWorld( force );
 
        }
 
-       expandByScalar( scalar ) {
+    }
 
-               this.min.addScalar( - scalar );
-               this.max.addScalar( scalar );
 
-               return this;
+    function getBoneList( object ) {
 
-       }
+       const boneList = [];
 
-       containsPoint( point ) {
+       if ( object && object.isBone ) {
 
-               return point.x < this.min.x || point.x > this.max.x ||
-                       point.y < this.min.y || point.y > this.max.y ? false : true;
+               boneList.push( object );
 
        }
 
-       containsBox( box ) {
+       for ( let i = 0; i < object.children.length; i ++ ) {
 
-               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;
+               boneList.push.apply( boneList, getBoneList( object.children[ i ] ) );
 
        }
 
-       getParameter( point, target ) {
-
-               // This can potentially have a divide by zero if the box
-               // has a size dimension of 0.
-
-               if ( target === undefined ) {
+       return boneList;
 
-                       console.warn( 'THREE.Box2: .getParameter() target is now required' );
-                       target = new Vector2();
-
-               }
+    }
 
-               return target.set(
-                       ( point.x - this.min.x ) / ( this.max.x - this.min.x ),
-                       ( point.y - this.min.y ) / ( this.max.y - this.min.y )
-               );
+    class GridHelper extends LineSegments {
 
-       }
+       constructor( size = 10, divisions = 10, color1 = 0x444444, color2 = 0x888888 ) {
 
-       intersectsBox( box ) {
+               color1 = new Color( color1 );
+               color2 = new Color( color2 );
 
-               // using 4 splitting planes to rule out intersections
+               const center = divisions / 2;
+               const step = size / divisions;
+               const halfSize = size / 2;
 
-               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;
+               const vertices = [], colors = [];
 
-       }
+               for ( let i = 0, j = 0, k = - halfSize; i <= divisions; i ++, k += step ) {
 
-       clampPoint( point, target ) {
+                       vertices.push( - halfSize, 0, k, halfSize, 0, k );
+                       vertices.push( k, 0, - halfSize, k, 0, halfSize );
 
-               if ( target === undefined ) {
+                       const color = i === center ? color1 : color2;
 
-                       console.warn( 'THREE.Box2: .clampPoint() target is now required' );
-                       target = new Vector2();
+                       color.toArray( colors, j ); j += 3;
+                       color.toArray( colors, j ); j += 3;
+                       color.toArray( colors, j ); j += 3;
+                       color.toArray( colors, j ); j += 3;
 
                }
 
-               return target.copy( point ).clamp( this.min, this.max );
-
-       }
-
-       distanceToPoint( point ) {
-
-               const clampedPoint = _vector$8.copy( point ).clamp( this.min, this.max );
-               return clampedPoint.sub( point ).length();
-
-       }
-
-       intersect( box ) {
-
-               this.min.max( box.min );
-               this.max.min( box.max );
-
-               return this;
-
-       }
-
-       union( box ) {
-
-               this.min.min( box.min );
-               this.max.max( box.max );
-
-               return this;
-
-       }
+               const geometry = new BufferGeometry();
+               geometry.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+               geometry.setAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
 
-       translate( offset ) {
-
-               this.min.add( offset );
-               this.max.add( offset );
-
-               return this;
-
-       }
+               const material = new LineBasicMaterial( { vertexColors: true, toneMapped: false } );
 
-       equals( box ) {
+               super( geometry, material );
 
-               return box.min.equals( this.min ) && box.max.equals( this.max );
+               this.type = 'GridHelper';
 
        }
 
     }
 
-    function ImmediateRenderObject( material ) {
-
-       Object3D.call( this );
-
-       this.material = material;
-       this.render = function ( /* renderCallback */ ) {};
-
-       this.hasPositions = false;
-       this.hasNormals = false;
-       this.hasColors = false;
-       this.hasUvs = false;
-
-       this.positionArray = null;
-       this.normalArray = null;
-       this.colorArray = null;
-       this.uvArray = null;
-
-       this.count = 0;
-
-    }
-
-    ImmediateRenderObject.prototype = Object.create( Object3D.prototype );
-    ImmediateRenderObject.prototype.constructor = ImmediateRenderObject;
-
-    ImmediateRenderObject.prototype.isImmediateRenderObject = true;
-
-    const backgroundMaterial = new MeshBasicMaterial( {
-       side: BackSide,
-       depthWrite: false,
-       depthTest: false,
-    } );
-    new Mesh( new BoxGeometry(), backgroundMaterial );
+    const _floatView = new Float32Array( 1 );
+    new Int32Array( _floatView.buffer );
 
     //
 
 
     //
 
-    Object.assign( Path.prototype, {
+    Path.prototype.fromPoints = function ( points ) {
 
-       fromPoints: function ( points ) {
+       console.warn( 'THREE.Path: .fromPoints() has been renamed to .setFromPoints().' );
+       return this.setFromPoints( points );
 
-               console.warn( 'THREE.Path: .fromPoints() has been renamed to .setFromPoints().' );
-               return this.setFromPoints( points );
+    };
 
-       }
+    GridHelper.prototype.setColors = function () {
 
-    } );
+       console.error( 'THREE.GridHelper: setColors() has been deprecated, pass them in the constructor instead.' );
 
-    //
+    };
 
-    function Spline( points ) {
+    SkeletonHelper.prototype.update = function () {
 
-       console.warn( 'THREE.Spline has been removed. Use THREE.CatmullRomCurve3 instead.' );
+       console.error( 'THREE.SkeletonHelper: update() no longer needs to be called.' );
 
-       CatmullRomCurve3.call( this, points );
-       this.type = 'catmullrom';
+    };
 
-    }
+    //
 
-    Spline.prototype = Object.create( CatmullRomCurve3.prototype );
+    Loader.prototype.extractUrlBase = function ( url ) {
 
-    Object.assign( Spline.prototype, {
+       console.warn( 'THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead.' );
+       return LoaderUtils.extractUrlBase( url );
 
-       initFromArray: function ( /* a */ ) {
+    };
 
-               console.error( 'THREE.Spline: .initFromArray() has been removed.' );
+    Loader.Handlers = {
 
-       },
-       getControlPointsArray: function ( /* optionalTarget */ ) {
+       add: function ( /* regex, loader */ ) {
 
-               console.error( 'THREE.Spline: .getControlPointsArray() has been removed.' );
+               console.error( 'THREE.Loader: Handlers.add() has been removed. Use LoadingManager.addHandler() instead.' );
 
        },
-       reparametrizeByArcLength: function ( /* samplingCoef */ ) {
 
-               console.error( 'THREE.Spline: .reparametrizeByArcLength() has been removed.' );
+       get: function ( /* file */ ) {
+
+               console.error( 'THREE.Loader: Handlers.get() has been removed. Use LoadingManager.getHandler() instead.' );
 
        }
 
-    } );
+    };
 
     //
 
-    Object.assign( Loader.prototype, {
+    Box3.prototype.center = function ( optionalTarget ) {
+
+       console.warn( 'THREE.Box3: .center() has been renamed to .getCenter().' );
+       return this.getCenter( optionalTarget );
 
-       extractUrlBase: function ( url ) {
+    };
 
-               console.warn( 'THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead.' );
-               return LoaderUtils.extractUrlBase( url );
+    Box3.prototype.empty = function () {
 
-       }
+       console.warn( 'THREE.Box3: .empty() has been renamed to .isEmpty().' );
+       return this.isEmpty();
 
-    } );
+    };
 
-    Loader.Handlers = {
+    Box3.prototype.isIntersectionBox = function ( box ) {
 
-       add: function ( /* regex, loader */ ) {
+       console.warn( 'THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().' );
+       return this.intersectsBox( box );
 
-               console.error( 'THREE.Loader: Handlers.add() has been removed. Use LoadingManager.addHandler() instead.' );
+    };
 
-       },
+    Box3.prototype.isIntersectionSphere = function ( sphere ) {
 
-       get: function ( /* file */ ) {
+       console.warn( 'THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
+       return this.intersectsSphere( sphere );
 
-               console.error( 'THREE.Loader: Handlers.get() has been removed. Use LoadingManager.getHandler() instead.' );
+    };
 
-       }
+    Box3.prototype.size = function ( optionalTarget ) {
+
+       console.warn( 'THREE.Box3: .size() has been renamed to .getSize().' );
+       return this.getSize( optionalTarget );
 
     };
 
     //
 
-    Object.assign( Box2.prototype, {
+    Sphere.prototype.empty = function () {
 
-       center: function ( optionalTarget ) {
+       console.warn( 'THREE.Sphere: .empty() has been renamed to .isEmpty().' );
+       return this.isEmpty();
 
-               console.warn( 'THREE.Box2: .center() has been renamed to .getCenter().' );
-               return this.getCenter( optionalTarget );
+    };
 
-       },
-       empty: function () {
+    //
 
-               console.warn( 'THREE.Box2: .empty() has been renamed to .isEmpty().' );
-               return this.isEmpty();
+    Frustum.prototype.setFromMatrix = function ( m ) {
 
-       },
-       isIntersectionBox: function ( box ) {
+       console.warn( 'THREE.Frustum: .setFromMatrix() has been renamed to .setFromProjectionMatrix().' );
+       return this.setFromProjectionMatrix( m );
 
-               console.warn( 'THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox().' );
-               return this.intersectsBox( box );
+    };
 
-       },
-       size: function ( optionalTarget ) {
+    //
 
-               console.warn( 'THREE.Box2: .size() has been renamed to .getSize().' );
-               return this.getSize( optionalTarget );
+    Matrix3.prototype.flattenToArrayOffset = function ( array, offset ) {
 
-       }
-    } );
+       console.warn( 'THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
+       return this.toArray( array, offset );
 
-    Object.assign( Box3.prototype, {
+    };
 
-       center: function ( optionalTarget ) {
+    Matrix3.prototype.multiplyVector3 = function ( vector ) {
 
-               console.warn( 'THREE.Box3: .center() has been renamed to .getCenter().' );
-               return this.getCenter( optionalTarget );
+       console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' );
+       return vector.applyMatrix3( this );
 
-       },
-       empty: function () {
+    };
 
-               console.warn( 'THREE.Box3: .empty() has been renamed to .isEmpty().' );
-               return this.isEmpty();
+    Matrix3.prototype.multiplyVector3Array = function ( /* a */ ) {
 
-       },
-       isIntersectionBox: function ( box ) {
+       console.error( 'THREE.Matrix3: .multiplyVector3Array() has been removed.' );
 
-               console.warn( 'THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().' );
-               return this.intersectsBox( box );
+    };
 
-       },
-       isIntersectionSphere: function ( sphere ) {
+    Matrix3.prototype.applyToBufferAttribute = function ( attribute ) {
 
-               console.warn( 'THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
-               return this.intersectsSphere( sphere );
+       console.warn( 'THREE.Matrix3: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix3( matrix ) instead.' );
+       return attribute.applyMatrix3( this );
 
-       },
-       size: function ( optionalTarget ) {
+    };
 
-               console.warn( 'THREE.Box3: .size() has been renamed to .getSize().' );
-               return this.getSize( optionalTarget );
+    Matrix3.prototype.applyToVector3Array = function ( /* array, offset, length */ ) {
 
-       }
-    } );
+       console.error( 'THREE.Matrix3: .applyToVector3Array() has been removed.' );
 
-    Object.assign( Sphere.prototype, {
+    };
 
-       empty: function () {
+    Matrix3.prototype.getInverse = function ( matrix ) {
 
-               console.warn( 'THREE.Sphere: .empty() has been renamed to .isEmpty().' );
-               return this.isEmpty();
+       console.warn( 'THREE.Matrix3: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
+       return this.copy( matrix ).invert();
 
-       },
+    };
 
-    } );
+    //
 
-    Frustum.prototype.setFromMatrix = function ( m ) {
+    Matrix4.prototype.extractPosition = function ( m ) {
 
-       console.warn( 'THREE.Frustum: .setFromMatrix() has been renamed to .setFromProjectionMatrix().' );
-       return this.setFromProjectionMatrix( m );
+       console.warn( 'THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().' );
+       return this.copyPosition( m );
 
     };
 
-    Object.assign( MathUtils, {
+    Matrix4.prototype.flattenToArrayOffset = function ( array, offset ) {
 
-       random16: function () {
+       console.warn( 'THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
+       return this.toArray( array, offset );
 
-               console.warn( 'THREE.Math: .random16() has been deprecated. Use Math.random() instead.' );
-               return Math.random();
-
-       },
+    };
 
-       nearestPowerOfTwo: function ( value ) {
+    Matrix4.prototype.getPosition = function () {
 
-               console.warn( 'THREE.Math: .nearestPowerOfTwo() has been renamed to .floorPowerOfTwo().' );
-               return MathUtils.floorPowerOfTwo( value );
+       console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' );
+       return new Vector3().setFromMatrixColumn( this, 3 );
 
-       },
+    };
 
-       nextPowerOfTwo: function ( value ) {
+    Matrix4.prototype.setRotationFromQuaternion = function ( q ) {
 
-               console.warn( 'THREE.Math: .nextPowerOfTwo() has been renamed to .ceilPowerOfTwo().' );
-               return MathUtils.ceilPowerOfTwo( value );
+       console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' );
+       return this.makeRotationFromQuaternion( q );
 
-       }
+    };
 
-    } );
+    Matrix4.prototype.multiplyToArray = function () {
 
-    Object.assign( Matrix3.prototype, {
+       console.warn( 'THREE.Matrix4: .multiplyToArray() has been removed.' );
 
-       flattenToArrayOffset: function ( array, offset ) {
+    };
 
-               console.warn( 'THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
-               return this.toArray( array, offset );
+    Matrix4.prototype.multiplyVector3 = function ( vector ) {
 
-       },
-       multiplyVector3: function ( vector ) {
+       console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+       return vector.applyMatrix4( this );
 
-               console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' );
-               return vector.applyMatrix3( this );
+    };
 
-       },
-       multiplyVector3Array: function ( /* a */ ) {
+    Matrix4.prototype.multiplyVector4 = function ( vector ) {
 
-               console.error( 'THREE.Matrix3: .multiplyVector3Array() has been removed.' );
+       console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+       return vector.applyMatrix4( this );
 
-       },
-       applyToBufferAttribute: function ( attribute ) {
+    };
 
-               console.warn( 'THREE.Matrix3: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix3( matrix ) instead.' );
-               return attribute.applyMatrix3( this );
+    Matrix4.prototype.multiplyVector3Array = function ( /* a */ ) {
 
-       },
-       applyToVector3Array: function ( /* array, offset, length */ ) {
+       console.error( 'THREE.Matrix4: .multiplyVector3Array() has been removed.' );
 
-               console.error( 'THREE.Matrix3: .applyToVector3Array() has been removed.' );
+    };
 
-       },
-       getInverse: function ( matrix ) {
+    Matrix4.prototype.rotateAxis = function ( v ) {
 
-               console.warn( 'THREE.Matrix3: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
-               return this.copy( matrix ).invert();
+       console.warn( 'THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' );
+       v.transformDirection( this );
 
-       }
+    };
 
-    } );
+    Matrix4.prototype.crossVector = function ( vector ) {
 
-    Object.assign( Matrix4.prototype, {
+       console.warn( 'THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+       return vector.applyMatrix4( this );
 
-       extractPosition: function ( m ) {
+    };
 
-               console.warn( 'THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().' );
-               return this.copyPosition( m );
+    Matrix4.prototype.translate = function () {
 
-       },
-       flattenToArrayOffset: function ( array, offset ) {
+       console.error( 'THREE.Matrix4: .translate() has been removed.' );
 
-               console.warn( 'THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
-               return this.toArray( array, offset );
+    };
 
-       },
-       getPosition: function () {
+    Matrix4.prototype.rotateX = function () {
 
-               console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' );
-               return new Vector3().setFromMatrixColumn( this, 3 );
+       console.error( 'THREE.Matrix4: .rotateX() has been removed.' );
 
-       },
-       setRotationFromQuaternion: function ( q ) {
+    };
 
-               console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' );
-               return this.makeRotationFromQuaternion( q );
+    Matrix4.prototype.rotateY = function () {
 
-       },
-       multiplyToArray: function () {
+       console.error( 'THREE.Matrix4: .rotateY() has been removed.' );
 
-               console.warn( 'THREE.Matrix4: .multiplyToArray() has been removed.' );
+    };
 
-       },
-       multiplyVector3: function ( vector ) {
+    Matrix4.prototype.rotateZ = function () {
 
-               console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
-               return vector.applyMatrix4( this );
+       console.error( 'THREE.Matrix4: .rotateZ() has been removed.' );
 
-       },
-       multiplyVector4: function ( vector ) {
+    };
 
-               console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
-               return vector.applyMatrix4( this );
+    Matrix4.prototype.rotateByAxis = function () {
 
-       },
-       multiplyVector3Array: function ( /* a */ ) {
+       console.error( 'THREE.Matrix4: .rotateByAxis() has been removed.' );
 
-               console.error( 'THREE.Matrix4: .multiplyVector3Array() has been removed.' );
+    };
 
-       },
-       rotateAxis: function ( v ) {
+    Matrix4.prototype.applyToBufferAttribute = function ( attribute ) {
 
-               console.warn( 'THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' );
-               v.transformDirection( this );
+       console.warn( 'THREE.Matrix4: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix4( matrix ) instead.' );
+       return attribute.applyMatrix4( this );
 
-       },
-       crossVector: function ( vector ) {
+    };
 
-               console.warn( 'THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
-               return vector.applyMatrix4( this );
+    Matrix4.prototype.applyToVector3Array = function ( /* array, offset, length */ ) {
 
-       },
-       translate: function () {
+       console.error( 'THREE.Matrix4: .applyToVector3Array() has been removed.' );
 
-               console.error( 'THREE.Matrix4: .translate() has been removed.' );
+    };
 
-       },
-       rotateX: function () {
+    Matrix4.prototype.makeFrustum = function ( left, right, bottom, top, near, far ) {
 
-               console.error( 'THREE.Matrix4: .rotateX() has been removed.' );
+       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 );
 
-       },
-       rotateY: function () {
+    };
 
-               console.error( 'THREE.Matrix4: .rotateY() has been removed.' );
+    Matrix4.prototype.getInverse = function ( matrix ) {
 
-       },
-       rotateZ: function () {
+       console.warn( 'THREE.Matrix4: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
+       return this.copy( matrix ).invert();
 
-               console.error( 'THREE.Matrix4: .rotateZ() has been removed.' );
+    };
 
-       },
-       rotateByAxis: function () {
+    //
 
-               console.error( 'THREE.Matrix4: .rotateByAxis() has been removed.' );
+    Plane.prototype.isIntersectionLine = function ( line ) {
 
-       },
-       applyToBufferAttribute: function ( attribute ) {
+       console.warn( 'THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().' );
+       return this.intersectsLine( line );
 
-               console.warn( 'THREE.Matrix4: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix4( matrix ) instead.' );
-               return attribute.applyMatrix4( this );
+    };
 
-       },
-       applyToVector3Array: function ( /* array, offset, length */ ) {
+    //
 
-               console.error( 'THREE.Matrix4: .applyToVector3Array() has been removed.' );
+    Quaternion.prototype.multiplyVector3 = function ( vector ) {
 
-       },
-       makeFrustum: function ( left, right, bottom, top, near, far ) {
+       console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
+       return vector.applyQuaternion( this );
 
-               console.warn( 'THREE.Matrix4: .makeFrustum() has been removed. Use .makePerspective( left, right, top, bottom, near, far ) instead.' );
-               return this.makePerspective( left, right, top, bottom, near, far );
+    };
 
-       },
-       getInverse: function ( matrix ) {
+    Quaternion.prototype.inverse = function ( ) {
 
-               console.warn( 'THREE.Matrix4: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
-               return this.copy( matrix ).invert();
+       console.warn( 'THREE.Quaternion: .inverse() has been renamed to invert().' );
+       return this.invert();
 
-       }
+    };
 
-    } );
+    //
 
-    Plane.prototype.isIntersectionLine = function ( line ) {
+    Ray.prototype.isIntersectionBox = function ( box ) {
 
-       console.warn( 'THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().' );
-       return this.intersectsLine( line );
+       console.warn( 'THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().' );
+       return this.intersectsBox( box );
 
     };
 
-    Object.assign( Quaternion.prototype, {
+    Ray.prototype.isIntersectionPlane = function ( plane ) {
 
-       multiplyVector3: function ( vector ) {
+       console.warn( 'THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().' );
+       return this.intersectsPlane( plane );
 
-               console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
-               return vector.applyQuaternion( this );
+    };
 
-       },
-       inverse: function ( ) {
+    Ray.prototype.isIntersectionSphere = function ( sphere ) {
 
-               console.warn( 'THREE.Quaternion: .inverse() has been renamed to invert().' );
-               return this.invert();
+       console.warn( 'THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
+       return this.intersectsSphere( sphere );
 
-       }
+    };
 
-    } );
+    //
 
-    Object.assign( Ray.prototype, {
+    Triangle.prototype.area = function () {
 
-       isIntersectionBox: function ( box ) {
+       console.warn( 'THREE.Triangle: .area() has been renamed to .getArea().' );
+       return this.getArea();
 
-               console.warn( 'THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().' );
-               return this.intersectsBox( box );
+    };
 
-       },
-       isIntersectionPlane: function ( plane ) {
+    Triangle.prototype.barycoordFromPoint = function ( point, target ) {
 
-               console.warn( 'THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().' );
-               return this.intersectsPlane( plane );
+       console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
+       return this.getBarycoord( point, target );
 
-       },
-       isIntersectionSphere: function ( sphere ) {
+    };
 
-               console.warn( 'THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
-               return this.intersectsSphere( sphere );
+    Triangle.prototype.midpoint = function ( target ) {
 
-       }
+       console.warn( 'THREE.Triangle: .midpoint() has been renamed to .getMidpoint().' );
+       return this.getMidpoint( target );
 
-    } );
+    };
 
-    Object.assign( Triangle.prototype, {
+    Triangle.prototypenormal = function ( target ) {
 
-       area: function () {
+       console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
+       return this.getNormal( target );
 
-               console.warn( 'THREE.Triangle: .area() has been renamed to .getArea().' );
-               return this.getArea();
+    };
 
-       },
-       barycoordFromPoint: function ( point, target ) {
+    Triangle.prototype.plane = function ( target ) {
 
-               console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
-               return this.getBarycoord( point, target );
+       console.warn( 'THREE.Triangle: .plane() has been renamed to .getPlane().' );
+       return this.getPlane( target );
 
-       },
-       midpoint: function ( target ) {
+    };
 
-               console.warn( 'THREE.Triangle: .midpoint() has been renamed to .getMidpoint().' );
-               return this.getMidpoint( target );
+    Triangle.barycoordFromPoint = function ( point, a, b, c, target ) {
 
-       },
-       normal: function ( target ) {
+       console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
+       return Triangle.getBarycoord( point, a, b, c, target );
 
-               console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
-               return this.getNormal( target );
+    };
 
-       },
-       plane: function ( target ) {
+    Triangle.normal = function ( a, b, c, target ) {
 
-               console.warn( 'THREE.Triangle: .plane() has been renamed to .getPlane().' );
-               return this.getPlane( target );
+       console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
+       return Triangle.getNormal( a, b, c, target );
 
-       }
+    };
 
-    } );
+    //
 
-    Object.assign( Triangle, {
+    Shape.prototype.extractAllPoints = function ( divisions ) {
 
-       barycoordFromPoint: function ( point, a, b, c, target ) {
+       console.warn( 'THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.' );
+       return this.extractPoints( divisions );
 
-               console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
-               return Triangle.getBarycoord( point, a, b, c, target );
+    };
 
-       },
-       normal: function ( a, b, c, target ) {
+    Shape.prototype.extrude = function ( options ) {
 
-               console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
-               return Triangle.getNormal( a, b, c, target );
+       console.warn( 'THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.' );
+       return new ExtrudeGeometry( this, options );
 
-       }
+    };
 
-    } );
+    Shape.prototype.makeGeometry = function ( options ) {
+
+       console.warn( 'THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.' );
+       return new ShapeGeometry( this, options );
 
-    Object.assign( Shape.prototype, {
+    };
 
-       extractAllPoints: function ( divisions ) {
+    //
 
-               console.warn( 'THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.' );
-               return this.extractPoints( divisions );
+    Vector2.prototype.fromAttribute = function ( attribute, index, offset ) {
 
-       },
-       extrude: function ( options ) {
+       console.warn( 'THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute().' );
+       return this.fromBufferAttribute( attribute, index, offset );
 
-               console.warn( 'THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.' );
-               return new ExtrudeGeometry( this, options );
+    };
 
-       },
-       makeGeometry: function ( options ) {
+    Vector2.prototype.distanceToManhattan = function ( v ) {
 
-               console.warn( 'THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.' );
-               return new ShapeGeometry( this, options );
+       console.warn( 'THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );
+       return this.manhattanDistanceTo( v );
 
-       }
+    };
 
-    } );
+    Vector2.prototype.lengthManhattan = function () {
 
-    Object.assign( Vector2.prototype, {
+       console.warn( 'THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength().' );
+       return this.manhattanLength();
 
-       fromAttribute: function ( attribute, index, offset ) {
+    };
 
-               console.warn( 'THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute().' );
-               return this.fromBufferAttribute( attribute, index, offset );
+    //
 
-       },
-       distanceToManhattan: function ( v ) {
+    Vector3.prototype.setEulerFromRotationMatrix = function () {
 
-               console.warn( 'THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );
-               return this.manhattanDistanceTo( v );
+       console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' );
 
-       },
-       lengthManhattan: function () {
+    };
 
-               console.warn( 'THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength().' );
-               return this.manhattanLength();
+    Vector3.prototype.setEulerFromQuaternion = function () {
 
-       }
+       console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' );
 
-    } );
+    };
 
-    Object.assign( Vector3.prototype, {
+    Vector3.prototype.getPositionFromMatrix = function ( m ) {
 
-       setEulerFromRotationMatrix: function () {
+       console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' );
+       return this.setFromMatrixPosition( m );
 
-               console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' );
+    };
 
-       },
-       setEulerFromQuaternion: function () {
+    Vector3.prototype.getScaleFromMatrix = function ( m ) {
 
-               console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' );
+       console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' );
+       return this.setFromMatrixScale( m );
 
-       },
-       getPositionFromMatrix: function ( m ) {
+    };
 
-               console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' );
-               return this.setFromMatrixPosition( m );
+    Vector3.prototype.getColumnFromMatrix = function ( index, matrix ) {
 
-       },
-       getScaleFromMatrix: function ( m ) {
+       console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' );
+       return this.setFromMatrixColumn( matrix, index );
 
-               console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' );
-               return this.setFromMatrixScale( m );
+    };
 
-       },
-       getColumnFromMatrix: function ( index, matrix ) {
+    Vector3.prototype.applyProjection = function ( m ) {
 
-               console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' );
-               return this.setFromMatrixColumn( matrix, index );
+       console.warn( 'THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead.' );
+       return this.applyMatrix4( m );
 
-       },
-       applyProjection: function ( m ) {
+    };
 
-               console.warn( 'THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead.' );
-               return this.applyMatrix4( m );
+    Vector3.prototype.fromAttribute = function ( attribute, index, offset ) {
 
-       },
-       fromAttribute: function ( attribute, index, offset ) {
+       console.warn( 'THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute().' );
+       return this.fromBufferAttribute( attribute, index, offset );
 
-               console.warn( 'THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute().' );
-               return this.fromBufferAttribute( attribute, index, offset );
+    };
 
-       },
-       distanceToManhattan: function ( v ) {
+    Vector3.prototype.distanceToManhattan = function ( v ) {
 
-               console.warn( 'THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );
-               return this.manhattanDistanceTo( 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();
+    Vector3.prototype.lengthManhattan = function () {
 
-       }
+       console.warn( 'THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength().' );
+       return this.manhattanLength();
 
-    } );
+    };
 
-    Object.assign( Vector4.prototype, {
+    //
 
-       fromAttribute: function ( attribute, index, offset ) {
+    Vector4.prototype.fromAttribute = function ( attribute, index, offset ) {
 
-               console.warn( 'THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute().' );
-               return this.fromBufferAttribute( 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();
+    Vector4.prototype.lengthManhattan = function () {
 
-       }
+       console.warn( 'THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength().' );
+       return this.manhattanLength();
 
-    } );
+    };
 
     //
 
-    Object.assign( Object3D.prototype, {
+    Object3D.prototype.getChildByName = function ( name ) {
+
+       console.warn( 'THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().' );
+       return this.getObjectByName( name );
 
-       getChildByName: function ( name ) {
+    };
 
-               console.warn( 'THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().' );
-               return this.getObjectByName( name );
+    Object3D.prototype.renderDepth = function () {
 
-       },
-       renderDepth: function () {
+       console.warn( 'THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.' );
 
-               console.warn( 'THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.' );
+    };
 
-       },
-       translate: function ( distance, axis ) {
+    Object3D.prototype.translate = function ( distance, axis ) {
 
-               console.warn( 'THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.' );
-               return this.translateOnAxis( axis, distance );
+       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.' );
+    Object3D.prototype.getWorldRotation = function () {
 
-       },
-       applyMatrix: function ( matrix ) {
+       console.error( 'THREE.Object3D: .getWorldRotation() has been removed. Use THREE.Object3D.getWorldQuaternion( target ) instead.' );
+
+    };
 
-               console.warn( 'THREE.Object3D: .applyMatrix() has been renamed to .applyMatrix4().' );
-               return this.applyMatrix4( matrix );
+    Object3D.prototype.applyMatrix = function ( matrix ) {
 
-       }
+       console.warn( 'THREE.Object3D: .applyMatrix() has been renamed to .applyMatrix4().' );
+       return this.applyMatrix4( matrix );
 
-    } );
+    };
 
     Object.defineProperties( Object3D.prototype, {
 
 
     } );
 
-    Object.assign( Mesh.prototype, {
+    Mesh.prototype.setDrawMode = function () {
 
-       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.' );
 
-               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, {
 
 
     } );
 
-    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 ) {
 
     } );
 
-    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 ) {
+       return this.setAttribute( name, attribute );
 
-                       console.warn( 'THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.' );
+    };
 
-               }
+    BufferGeometry.prototype.addDrawCall = function ( start, count, indexOffset ) {
 
-               console.warn( 'THREE.BufferGeometry: .addDrawCall() is now .addGroup().' );
-               this.addGroup( start, count );
+       if ( indexOffset !== undefined ) {
 
-       },
-       clearDrawCalls: function () {
+               console.warn( 'THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.' );
 
-               console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' );
-               this.clearGroups();
+       }
 
-       },
-       computeOffsets: function () {
+       console.warn( 'THREE.BufferGeometry: .addDrawCall() is now .addGroup().' );
+       this.addGroup( start, count );
 
-               console.warn( 'THREE.BufferGeometry: .computeOffsets() has been removed.' );
+    };
 
-       },
-       removeAttribute: function ( name ) {
+    BufferGeometry.prototype.clearDrawCalls = function () {
 
-               console.warn( 'THREE.BufferGeometry: .removeAttribute() has been renamed to .deleteAttribute().' );
+       console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' );
+       this.clearGroups();
 
-               return this.deleteAttribute( name );
+    };
 
-       },
-       applyMatrix: function ( matrix ) {
+    BufferGeometry.prototype.computeOffsets = function () {
 
-               console.warn( 'THREE.BufferGeometry: .applyMatrix() has been renamed to .applyMatrix4().' );
-               return this.applyMatrix4( matrix );
+       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, {
 
 
     } );
 
-    Object.defineProperties( InstancedBufferGeometry.prototype, {
+    InterleavedBuffer.prototype.setDynamic = function ( value ) {
 
-       maxInstancedCount: {
-               get: function () {
+       console.warn( 'THREE.InterleavedBuffer: .setDynamic() has been deprecated. Use .setUsage() instead.' );
+       this.setUsage( value === true ? DynamicDrawUsage : StaticDrawUsage );
+       return this;
 
-                       console.warn( 'THREE.InstancedBufferGeometry: .maxInstancedCount has been renamed to .instanceCount.' );
-                       return this.instanceCount;
+    };
 
-               },
-               set: function ( value ) {
+    InterleavedBuffer.prototype.setArray = function ( /* array */ ) {
 
-                       console.warn( 'THREE.InstancedBufferGeometry: .maxInstancedCount has been renamed to .instanceCount.' );
-                       this.instanceCount = value;
+       console.error( 'THREE.InterleavedBuffer: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers' );
 
-               }
-       }
+    };
 
-    } );
+    //
 
-    Object.defineProperties( Raycaster.prototype, {
+    ExtrudeGeometry.prototype.getArrays = function () {
 
-       linePrecision: {
-               get: function () {
+       console.error( 'THREE.ExtrudeGeometry: .getArrays() has been removed.' );
 
-                       console.warn( 'THREE.Raycaster: .linePrecision has been deprecated. Use .params.Line.threshold instead.' );
-                       return this.params.Line.threshold;
+    };
 
-               },
-               set: function ( value ) {
+    ExtrudeGeometry.prototype.addShapeList = function () {
 
-                       console.warn( 'THREE.Raycaster: .linePrecision has been deprecated. Use .params.Line.threshold instead.' );
-                       this.params.Line.threshold = value;
+       console.error( 'THREE.ExtrudeGeometry: .addShapeList() has been removed.' );
 
-               }
-       }
+    };
 
-    } );
+    ExtrudeGeometry.prototype.addShape = function () {
 
-    Object.defineProperties( InterleavedBuffer.prototype, {
+       console.error( 'THREE.ExtrudeGeometry: .addShape() has been removed.' );
 
-       dynamic: {
-               get: function () {
+    };
 
-                       console.warn( 'THREE.InterleavedBuffer: .length has been deprecated. Use .usage instead.' );
-                       return this.usage === DynamicDrawUsage;
+    //
 
-               },
-               set: function ( value ) {
+    Scene.prototype.dispose = function () {
 
-                       console.warn( 'THREE.InterleavedBuffer: .length has been deprecated. Use .usage instead.' );
-                       this.setUsage( value );
+       console.error( 'THREE.Scene: .dispose() has been removed.' );
 
-               }
-       }
-
-    } );
-
-    Object.assign( InterleavedBuffer.prototype, {
-       setDynamic: function ( value ) {
-
-               console.warn( 'THREE.InterleavedBuffer: .setDynamic() has been deprecated. Use .setUsage() instead.' );
-               this.setUsage( value === true ? DynamicDrawUsage : StaticDrawUsage );
-               return this;
-
-       },
-       setArray: function ( /* array */ ) {
-
-               console.error( 'THREE.InterleavedBuffer: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers' );
-
-       }
-    } );
-
-    //
-
-    Object.assign( ExtrudeGeometry.prototype, {
-
-       getArrays: function () {
-
-               console.error( 'THREE.ExtrudeGeometry: .getArrays() has been removed.' );
-
-       },
-
-       addShapeList: function () {
-
-               console.error( 'THREE.ExtrudeGeometry: .addShapeList() has been removed.' );
-
-       },
-
-       addShape: function () {
-
-               console.error( 'THREE.ExtrudeGeometry: .addShape() has been removed.' );
-
-       }
-
-    } );
-
-    //
-
-    Object.assign( Scene.prototype, {
-
-       dispose: function () {
-
-               console.error( 'THREE.Scene: .dispose() has been removed.' );
-
-       }
-
-    } );
-
-    //
-
-    Object.defineProperties( Uniform.prototype, {
-
-       dynamic: {
-               set: function () {
-
-                       console.warn( 'THREE.Uniform: .dynamic has been removed. Use object.onBeforeRender() instead.' );
-
-               }
-       },
-       onUpdate: {
-               value: function () {
-
-                       console.warn( 'THREE.Uniform: .onUpdate() has been removed. Use object.onBeforeRender() instead.' );
-                       return this;
-
-               }
-       }
-
-    } );
+    };
 
     //
 
                        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' );
-
-               }
-       }
-
-    } );
-
-    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;
+                       console.warn( 'THREE.' + this.type + ': .vertexTangents has been removed.' );
 
                }
-       }
+       },
 
     } );
 
 
     //
 
-    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.' );
+    WebGLRenderer.prototype.initMaterial = function () {
 
-       },
-       setTexture2D: function () {
+       console.warn( 'THREE.WebGLRenderer: .initMaterial() has been removed.' );
 
-               console.warn( 'THREE.WebGLRenderer: .setTexture2D() has been removed.' );
+    };
 
-       },
-       setTextureCube: function () {
+    WebGLRenderer.prototype.addPrePlugin = function () {
 
-               console.warn( 'THREE.WebGLRenderer: .setTextureCube() has been removed.' );
+       console.warn( 'THREE.WebGLRenderer: .addPrePlugin() has been removed.' );
 
-       },
-       getActiveMipMapLevel: function () {
+    };
 
-               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, {
 
 
     //
 
-    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 );
+    Audio.prototype.load = function ( file ) {
 
-                       } );
-                       return this;
-
-               }
-       },
-       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;
 
-    } );
+    };
 
     //
 
         }
     }
 
-    /*
-     * 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 = {};
 
-    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;
+    var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
 
-        this.cy = 3.0 * p1y;
-        this.by = 3.0 * (p2y - p1y) - this.cy;
-        this.ay = 1.0 - this.cy - this.by;
+    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;
+    }
 
-        this.p1x = p1x;
-        this.p1y = p2y;
-        this.p2x = p2x;
-        this.p2y = p2y;
+    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.');
     }
 
-    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 nativeIsArray = Array.isArray;
+    var toString$2 = Object.prototype.toString;
 
-    UnitBezier.prototype.sampleCurveY = function(t) {
-        return ((this.ay * t + this.by) * t + this.cy) * t;
-    };
+    var xIsArray = nativeIsArray || isArray$3;
 
-    UnitBezier.prototype.sampleCurveDerivativeX = function(t) {
-        return (3.0 * this.ax * t + 2.0 * this.bx) * t + this.cx;
-    };
+    function isArray$3(obj) {
+        return toString$2.call(obj) === "[object Array]"
+    }
+
+    var version$5 = "2";
+
+    var version$4 = version$5;
+
+    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";
+
+    var version$3 = version$5;
+
+    var isVnode = isVirtualNode;
+
+    function isVirtualNode(x) {
+        return x && x.type === "VirtualNode" && x.version === version$3
+    }
+
+    var version$2 = version$5;
+
+    var isVtext = isVirtualText;
+
+    function isVirtualText(x) {
+        return x && x.type === "VirtualText" && x.version === version$2
+    }
+
+    var isWidget_1 = isWidget$7;
+
+    function isWidget$7(w) {
+        return w && w.type === "Widget"
+    }
+
+    var isThunk_1 = isThunk$3;
+
+    function isThunk$3(t) {
+        return t && t.type === "Thunk"
+    }
+
+    var isVNode$4 = isVnode;
+    var isVText$3 = isVtext;
+    var isWidget$6 = isWidget_1;
+    var isThunk$2 = isThunk_1;
+
+    var handleThunk_1 = handleThunk$2;
+
+    function handleThunk$2(a, b) {
+        var renderedA = a;
+        var renderedB = b;
 
-    UnitBezier.prototype.solveCurveX = function(x, epsilon) {
-        if (typeof epsilon === 'undefined') epsilon = 1e-6;
+        if (isThunk$2(b)) {
+            renderedB = renderThunk(b, a);
+        }
 
-        var t0, t1, t2, x2, i;
+        if (isThunk$2(a)) {
+            renderedA = renderThunk(a, null);
+        }
 
-        // First try a few iterations of Newton's method -- normally very fast.
-        for (t2 = x, i = 0; i < 8; i++) {
+        return {
+            a: renderedA,
+            b: renderedB
+        }
+    }
 
-            x2 = this.sampleCurveX(t2) - x;
-            if (Math.abs(x2) < epsilon) return t2;
+    function renderThunk(thunk, previous) {
+        var renderedThunk = thunk.vnode;
 
-            var d2 = this.sampleCurveDerivativeX(t2);
-            if (Math.abs(d2) < 1e-6) break;
+        if (!renderedThunk) {
+            renderedThunk = thunk.vnode = thunk.render(previous);
+        }
 
-            t2 = t2 - x2 / d2;
+        if (!(isVNode$4(renderedThunk) ||
+                isVText$3(renderedThunk) ||
+                isWidget$6(renderedThunk))) {
+            throw new Error("thunk did not return a valid node");
         }
 
-        // Fall back to the bisection method for reliability.
-        t0 = 0.0;
-        t1 = 1.0;
-        t2 = x;
+        return renderedThunk
+    }
+
+    var isObject$2 = function isObject(x) {
+       return typeof x === 'object' && x !== null;
+    };
+
+    var isVhook = isHook$3;
+
+    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;
 
-        if (t2 < t0) return t0;
-        if (t2 > t1) return t1;
+    var diffProps_1 = diffProps$1;
 
-        while (t0 < t1) {
+    function diffProps$1(a, b) {
+        var diff;
+
+        for (var aKey in a) {
+            if (!(aKey in b)) {
+                diff = diff || {};
+                diff[aKey] = undefined;
+            }
 
-            x2 = this.sampleCurveX(t2);
-            if (Math.abs(x2 - x) < epsilon) return t2;
+            var aValue = a[aKey];
+            var bValue = b[aKey];
 
-            if (x > x2) {
-                t0 = t2;
+            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 {
-                t1 = t2;
+                diff = diff || {};
+                diff[aKey] = bValue;
             }
+        }
 
-            t2 = (t1 - t0) * 0.5 + t0;
+        for (var bKey in b) {
+            if (!(bKey in a)) {
+                diff = diff || {};
+                diff[bKey] = b[bKey];
+            }
         }
 
-        // Failure.
-        return t2;
-    };
+        return diff
+    }
 
-    UnitBezier.prototype.solve = function(x, epsilon) {
-        return this.sampleCurveY(this.solveCurveX(x, epsilon));
-    };
+    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
+      }
+    }
 
-    /**
-     * Enumeration for transition mode
-     * @enum {number}
-     * @readonly
-     * @description Modes for specifying how transitions
-     * between images are performed.
-     */
-    exports.TransitionMode = void 0;
-    (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";
-    })(exports.TransitionMode || (exports.TransitionMode = {}));
+    var isArray$2 = xIsArray;
 
-    /**
-     * @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);
+    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
+        }
+
+        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];
             }
-            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;
+
+            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 position.
-         * @returns {THREE.Vector3} The position vector.
-         */
-        get position() {
-            return this._position;
+
+        if (apply) {
+            patch[index] = apply;
         }
-        /**
-         * Get lookat.
-         * @returns {THREE.Vector3} The lookat vector.
-         */
-        get lookat() {
-            return this._lookat;
-        }
-        /**
-         * Get up.
-         * @returns {THREE.Vector3} The up vector.
-         */
-        get up() {
-            return this._up;
-        }
-        /**
-         * Get focal.
-         * @returns {number} The focal length.
-         */
-        get focal() {
-            return this._focal;
-        }
-        /**
-         * Set focal.
-         */
-        set focal(value) {
-            this._focal = value;
+
+        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);
-        }
-        get ck1() {
-            return this._ck1;
+    // 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 ck2() {
-            return this._ck2;
+    }
+
+    function undefinedKeys(obj) {
+        var result = {};
+
+        for (var key in obj) {
+            result[key] = undefined;
         }
-        get cameraType() {
-            return this._cameraType;
+
+        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 basic aspect.
-         * @returns {number} The orientation adjusted aspect ratio.
-         */
-        get basicAspect() {
-            return this._basicAspect;
+
+        // 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 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;
+
+        // 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 basicRt() {
-            return this._basicWorldToCamera;
+
+        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 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;
+
+        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 focal.
-         * @returns {number} The image focal length.
-         */
-        get focal() {
-            return this._focal;
+
+        // remove all the remaining nodes from simulate
+        while(simulateIndex < simulate.length) {
+            simulateItem = simulate[simulateIndex];
+            removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key));
         }
-        /**
-         * 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;
+
+        // 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 orientation.
-         * @returns {number} The image orientation.
-         */
-        get orientation() {
-            return this._orientation;
+
+        return {
+            children: newChildren,
+            moves: {
+                removes: removes,
+                inserts: inserts
+            }
         }
-        /**
-         * Get rt.
-         * @returns {THREE.Matrix4} The extrinsic camera matrix.
-         */
-        get rt() {
-            return this._worldToCamera;
+    }
+
+    function remove(arr, index, key) {
+        arr.splice(index, 1);
+
+        return {
+            from: index,
+            key: key
         }
-        /**
-         * Get srt.
-         * @returns {THREE.Matrix4} The scaled extrinsic camera matrix.
-         */
-        get srt() {
-            return this._scaledWorldToCamera;
+    }
+
+    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 srtInverse.
-         * @returns {THREE.Matrix4} The scaled extrinsic camera matrix.
-         */
-        get srtInverse() {
-            return this._scaledWorldToCameraInverse;
-        }
-        /**
-         * Get scale.
-         * @returns {number} The image atomic reconstruction scale.
-         */
-        get scale() {
-            return this._scale;
-        }
-        /**
-         * Get has valid scale.
-         * @returns {boolean} Value indicating if the scale of the transform is valid.
-         */
-        get hasValidScale() {
-            return this._scale > 1e-2 && this._scale < 50;
-        }
-        /**
-         * Get radial peak.
-         * @returns {number} Value indicating the radius where the radial
-         * undistortion function peaks.
-         */
-        get radialPeak() {
-            return this._radialPeak;
-        }
-        /**
-         * Get width.
-         *
-         * @description Falls back to the image image width if
-         * the API data is faulty.
-         *
-         * @returns {number} The orientation adjusted image width.
-         */
-        get width() {
-            return this._width;
-        }
-        /**
-         * Calculate the up vector for the image transform.
-         *
-         * @returns {THREE.Vector3} Normalized and orientation adjusted up vector.
-         */
-        upVector() {
-            let rte = this._worldToCamera.elements;
-            switch (this._orientation) {
-                case 1:
-                    return new Vector3(-rte[1], -rte[5], -rte[9]);
-                case 3:
-                    return new Vector3(rte[1], rte[5], rte[9]);
-                case 6:
-                    return new Vector3(-rte[0], -rte[4], -rte[8]);
-                case 8:
-                    return new Vector3(rte[0], rte[4], rte[8]);
-                default:
-                    return new Vector3(-rte[1], -rte[5], -rte[9]);
-            }
-        }
-        /**
-         * Calculate projector matrix for projecting 3D points to texture map
-         * coordinates (u and v).
-         *
-         * @returns {THREE.Matrix4} Projection matrix for 3D point to texture
-         * map coordinate calculations.
-         */
-        projectorMatrix() {
-            let projector = this._normalizedToTextureMatrix();
-            let f = this._focal;
-            let projection = new Matrix4().set(f, 0, 0, 0, 0, f, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
-            projector.multiply(projection);
-            projector.multiply(this._worldToCamera);
-            return projector;
-        }
-        /**
-         * Project 3D world coordinates to basic coordinates.
-         *
-         * @param {Array<number>} point3d - 3D world coordinates.
-         * @return {Array<number>} 2D basic coordinates.
-         */
-        projectBasic(point3d) {
-            let sfm = this.projectSfM(point3d);
-            return this._sfmToBasic(sfm);
-        }
-        /**
-         * Unproject basic coordinates to 3D world coordinates.
-         *
-         * @param {Array<number>} basic - 2D basic coordinates.
-         * @param {Array<number>} distance - Distance to unproject from camera center.
-         * @param {boolean} [depth] - Treat the distance value as depth from camera center.
-         *                            Only applicable for perspective images. Will be
-         *                            ignored for spherical.
-         * @returns {Array<number>} Unprojected 3D world coordinates.
-         */
-        unprojectBasic(basic, distance, depth) {
-            let sfm = this._basicToSfm(basic);
-            return this.unprojectSfM(sfm, distance, depth);
-        }
-        /**
-         * Project 3D world coordinates to SfM coordinates.
-         *
-         * @param {Array<number>} point3d - 3D world coordinates.
-         * @return {Array<number>} 2D SfM coordinates.
-         */
-        projectSfM(point3d) {
-            let v = new Vector4(point3d[0], point3d[1], point3d[2], 1);
-            v.applyMatrix4(this._worldToCamera);
-            return this._bearingToSfm([v.x, v.y, v.z]);
-        }
-        /**
-         * Unproject SfM coordinates to a 3D world coordinates.
-         *
-         * @param {Array<number>} sfm - 2D SfM coordinates.
-         * @param {Array<number>} distance - Distance to unproject
-         * from camera center.
-         * @param {boolean} [depth] - Treat the distance value as
-         * depth from camera center. Only applicable for perspective
-         * images. Will be ignored for spherical.
-         * @returns {Array<number>} Unprojected 3D world coordinates.
-         */
-        unprojectSfM(sfm, distance, depth) {
-            const bearing = this._sfmToBearing(sfm);
-            const unprojectedCamera = depth && !isSpherical(this._cameraType) ?
-                new Vector4(distance * bearing[0] / bearing[2], distance * bearing[1] / bearing[2], distance, 1) :
-                new Vector4(distance * bearing[0], distance * bearing[1], distance * bearing[2], 1);
-            const unprojectedWorld = unprojectedCamera
-                .applyMatrix4(this._worldToCameraInverse);
-            return [
-                unprojectedWorld.x / unprojectedWorld.w,
-                unprojectedWorld.y / unprojectedWorld.w,
-                unprojectedWorld.z / unprojectedWorld.w,
-            ];
-        }
-        /**
-         * Transform SfM coordinates to bearing vector (3D cartesian
-         * coordinates on the unit sphere).
-         *
-         * @param {Array<number>} sfm - 2D SfM coordinates.
-         * @returns {Array<number>} Bearing vector (3D cartesian coordinates
-         * on the unit sphere).
-         */
-        _sfmToBearing(sfm) {
-            if (isSpherical(this._cameraType)) {
-                let lng = sfm[0] * 2 * Math.PI;
-                let lat = -sfm[1] * 2 * Math.PI;
-                let x = Math.cos(lat) * Math.sin(lng);
-                let y = -Math.sin(lat);
-                let z = Math.cos(lat) * Math.cos(lng);
-                return [x, y, z];
-            }
-            else if (isFisheye(this._cameraType)) {
-                let [dxn, dyn] = [sfm[0] / this._focal, sfm[1] / this._focal];
-                const dTheta = Math.sqrt(dxn * dxn + dyn * dyn);
-                let d = this._distortionFromDistortedRadius(dTheta, this._ck1, this._ck2, this._radialPeak);
-                let theta = dTheta / d;
-                let z = Math.cos(theta);
-                let r = Math.sin(theta);
-                const denomTheta = dTheta > EPSILON ? 1 / dTheta : 1;
-                let x = r * dxn * denomTheta;
-                let y = r * dyn * denomTheta;
-                return [x, y, z];
-            }
-            else {
-                let [dxn, dyn] = [sfm[0] / this._focal, sfm[1] / this._focal];
-                const dr = Math.sqrt(dxn * dxn + dyn * dyn);
-                let d = this._distortionFromDistortedRadius(dr, this._ck1, this._ck2, this._radialPeak);
-                const xn = dxn / d;
-                const yn = dyn / d;
-                let v = new Vector3(xn, yn, 1);
-                v.normalize();
-                return [v.x, v.y, v.z];
-            }
-        }
-        /** Compute distortion given the distorted radius.
-         *
-         *  Solves for d in the equation
-         *    y = d(x, k1, k2) * x
-         * given the distorted radius, y.
-         */
-        _distortionFromDistortedRadius(distortedRadius, k1, k2, radialPeak) {
-            let d = 1.0;
-            for (let i = 0; i < 10; i++) {
-                let radius = distortedRadius / d;
-                if (radius > radialPeak) {
-                    radius = radialPeak;
-                }
-                d = 1 + k1 * Math.pow(radius, 2) + k2 * Math.pow(radius, 4);
-            }
-            return d;
-        }
-        /**
-         * Transform bearing vector (3D cartesian coordiantes on the unit sphere) to
-         * SfM coordinates.
-         *
-         * @param {Array<number>} bearing - Bearing vector (3D cartesian coordinates on the
-         * unit sphere).
-         * @returns {Array<number>} 2D SfM coordinates.
-         */
-        _bearingToSfm(bearing) {
-            if (isSpherical(this._cameraType)) {
-                let x = bearing[0];
-                let y = bearing[1];
-                let z = bearing[2];
-                let lng = Math.atan2(x, z);
-                let lat = Math.atan2(-y, Math.sqrt(x * x + z * z));
-                return [lng / (2 * Math.PI), -lat / (2 * Math.PI)];
-            }
-            else if (isFisheye(this._cameraType)) {
-                if (bearing[2] > 0) {
-                    const [x, y, z] = bearing;
-                    const r = Math.sqrt(x * x + y * y);
-                    let theta = Math.atan2(r, z);
-                    if (theta > this._radialPeak) {
-                        theta = this._radialPeak;
-                    }
-                    const distortion = 1.0 + Math.pow(theta, 2) * (this._ck1 + Math.pow(theta, 2) * this._ck2);
-                    const s = this._focal * distortion * theta / r;
-                    return [s * x, s * y];
-                }
-                else {
-                    return [
-                        bearing[0] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
-                        bearing[1] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
-                    ];
-                }
-            }
-            else {
-                if (bearing[2] > 0) {
-                    let [xn, yn] = [bearing[0] / bearing[2], bearing[1] / bearing[2]];
-                    let r2 = xn * xn + yn * yn;
-                    const rp2 = Math.pow(this._radialPeak, 2);
-                    if (r2 > rp2) {
-                        r2 = rp2;
-                    }
-                    const d = 1 + this._ck1 * r2 + this._ck2 * Math.pow(r2, 2);
-                    return [
-                        this._focal * d * xn,
-                        this._focal * d * yn,
-                    ];
-                }
-                else {
-                    return [
-                        bearing[0] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
-                        bearing[1] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
-                    ];
-                }
-            }
-        }
-        /**
-         * Convert basic coordinates to SfM coordinates.
-         *
-         * @param {Array<number>} basic - 2D basic coordinates.
-         * @returns {Array<number>} 2D SfM coordinates.
-         */
-        _basicToSfm(basic) {
-            let rotatedX;
-            let rotatedY;
-            switch (this._orientation) {
-                case 1:
-                    rotatedX = basic[0];
-                    rotatedY = basic[1];
-                    break;
-                case 3:
-                    rotatedX = 1 - basic[0];
-                    rotatedY = 1 - basic[1];
-                    break;
-                case 6:
-                    rotatedX = basic[1];
-                    rotatedY = 1 - basic[0];
-                    break;
-                case 8:
-                    rotatedX = 1 - basic[1];
-                    rotatedY = basic[0];
-                    break;
-                default:
-                    rotatedX = basic[0];
-                    rotatedY = basic[1];
-                    break;
-            }
-            let w = this._width;
-            let h = this._height;
-            let s = Math.max(w, h);
-            let sfmX = rotatedX * w / s - w / s / 2;
-            let sfmY = rotatedY * h / s - h / s / 2;
-            return [sfmX, sfmY];
-        }
-        /**
-         * Convert SfM coordinates to basic coordinates.
-         *
-         * @param {Array<number>} sfm - 2D SfM coordinates.
-         * @returns {Array<number>} 2D basic coordinates.
-         */
-        _sfmToBasic(sfm) {
-            let w = this._width;
-            let h = this._height;
-            let s = Math.max(w, h);
-            let rotatedX = (sfm[0] + w / s / 2) / w * s;
-            let rotatedY = (sfm[1] + h / s / 2) / h * s;
-            let basicX;
-            let basicY;
-            switch (this._orientation) {
-                case 1:
-                    basicX = rotatedX;
-                    basicY = rotatedY;
-                    break;
-                case 3:
-                    basicX = 1 - rotatedX;
-                    basicY = 1 - rotatedY;
-                    break;
-                case 6:
-                    basicX = 1 - rotatedY;
-                    basicY = rotatedX;
-                    break;
-                case 8:
-                    basicX = rotatedY;
-                    basicY = 1 - rotatedX;
-                    break;
-                default:
-                    basicX = rotatedX;
-                    basicY = rotatedY;
-                    break;
-            }
-            return [basicX, basicY];
-        }
-        /**
-         * Checks a value and returns it if it exists and is larger than 0.
-         * Fallbacks if it is null.
-         *
-         * @param {number} value - Value to check.
-         * @param {number} fallback - Value to fall back to.
-         * @returns {number} The value or its fallback value if it is not defined or negative.
-         */
-        _getValue(value, fallback) {
-            return value != null && value > 0 ? value : fallback;
-        }
-        _getCameraParameters(value, cameraType) {
-            if (isSpherical(cameraType)) {
-                return [];
-            }
-            if (!value || value.length === 0) {
-                return [1, 0, 0];
-            }
-            const padding = 3 - value.length;
-            if (padding <= 0) {
-                return value;
-            }
-            return value
-                .concat(new Array(padding)
-                .fill(0));
-        }
-        /**
-         * Creates the extrinsic camera matrix [ R | t ].
-         *
-         * @param {Array<number>} rotation - Rotation vector in angle axis representation.
-         * @param {Array<number>} translation - Translation vector.
-         * @returns {THREE.Matrix4} Extrisic camera matrix.
-         */
-        createWorldToCamera(rotation, translation) {
-            const axis = new Vector3(rotation[0], rotation[1], rotation[2]);
-            const angle = axis.length();
-            if (angle > 0) {
-                axis.normalize();
-            }
-            const worldToCamera = new Matrix4();
-            worldToCamera.makeRotationAxis(axis, angle);
-            worldToCamera.setPosition(new Vector3(translation[0], translation[1], translation[2]));
-            return worldToCamera;
-        }
-        /**
-         * Calculates the scaled extrinsic camera matrix scale * [ R | t ].
-         *
-         * @param {THREE.Matrix4} worldToCamera - Extrisic camera matrix.
-         * @param {number} scale - Scale factor.
-         * @returns {THREE.Matrix4} Scaled extrisic camera matrix.
-         */
-        _createScaledWorldToCamera(worldToCamera, scale) {
-            const scaledWorldToCamera = worldToCamera.clone();
-            const elements = scaledWorldToCamera.elements;
-            elements[12] = scale * elements[12];
-            elements[13] = scale * elements[13];
-            elements[14] = scale * elements[14];
-            scaledWorldToCamera.scale(new Vector3(scale, scale, scale));
-            return scaledWorldToCamera;
-        }
-        _createBasicWorldToCamera(rt, orientation) {
-            const axis = new Vector3(0, 0, 1);
-            let angle = 0;
-            switch (orientation) {
-                case 3:
-                    angle = Math.PI;
-                    break;
-                case 6:
-                    angle = Math.PI / 2;
-                    break;
-                case 8:
-                    angle = 3 * Math.PI / 2;
-                    break;
-            }
-            return new Matrix4()
-                .makeRotationAxis(axis, angle)
-                .multiply(rt);
-        }
-        _getRadialPeak(k1, k2) {
-            const a = 5 * k2;
-            const b = 3 * k1;
-            const c = 1;
-            const d = Math.pow(b, 2) - 4 * a * c;
-            if (d < 0) {
-                return undefined;
-            }
-            const root1 = (-b - Math.sqrt(d)) / 2 / a;
-            const root2 = (-b + Math.sqrt(d)) / 2 / a;
-            const minRoot = Math.min(root1, root2);
-            const maxRoot = Math.max(root1, root2);
-            return minRoot > 0 ?
-                Math.sqrt(minRoot) :
-                maxRoot > 0 ?
-                    Math.sqrt(maxRoot) :
-                    undefined;
-        }
-        /**
-         * Calculate a transformation matrix from normalized coordinates for
-         * texture map coordinates.
-         *
-         * @returns {THREE.Matrix4} Normalized coordinates to texture map
-         * coordinates transformation matrix.
-         */
-        _normalizedToTextureMatrix() {
-            const size = Math.max(this._width, this._height);
-            const scaleX = this._orientation < 5 ? this._textureScale[0] : this._textureScale[1];
-            const scaleY = this._orientation < 5 ? this._textureScale[1] : this._textureScale[0];
-            const w = size / this._width * scaleX;
-            const h = size / this._height * scaleY;
-            switch (this._orientation) {
-                case 1:
-                    return new Matrix4().set(w, 0, 0, 0.5, 0, -h, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
-                case 3:
-                    return new Matrix4().set(-w, 0, 0, 0.5, 0, h, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
-                case 6:
-                    return new Matrix4().set(0, -h, 0, 0.5, -w, 0, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
-                case 8:
-                    return new Matrix4().set(0, h, 0, 0.5, w, 0, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
-                default:
-                    return new Matrix4().set(w, 0, 0, 0.5, 0, -h, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
-            }
-        }
-    }
-
-    class StateBase {
-        constructor(state) {
-            this._spatial = new Spatial();
-            this._referenceThreshold = 0.01;
-            this._transitionMode = state.transitionMode;
-            this._reference = state.reference;
-            this._alpha = state.alpha;
-            this._camera = state.camera.clone();
-            this._zoom = state.zoom;
-            this._currentIndex = state.currentIndex;
-            this._trajectory = state.trajectory.slice();
-            this._trajectoryTransforms = [];
-            this._trajectoryCameras = [];
-            for (let image of this._trajectory) {
-                let translation = this._imageToTranslation(image, this._reference);
-                let transform = new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, image.image, undefined, image.cameraParameters, image.cameraType);
-                this._trajectoryTransforms.push(transform);
-                this._trajectoryCameras.push(new Camera(transform));
-            }
-            this._currentImage = this._trajectory.length > 0 ?
-                this._trajectory[this._currentIndex] :
-                null;
-            this._previousImage = this._trajectory.length > 1 && this.currentIndex > 0 ?
-                this._trajectory[this._currentIndex - 1] :
-                null;
-            this._currentCamera = this._trajectoryCameras.length > 0 ?
-                this._trajectoryCameras[this._currentIndex].clone() :
-                new Camera();
-            this._previousCamera = this._trajectoryCameras.length > 1 && this.currentIndex > 0 ?
-                this._trajectoryCameras[this._currentIndex - 1].clone() :
-                this._currentCamera.clone();
-        }
-        get reference() {
-            return this._reference;
-        }
-        get alpha() {
-            return this._getAlpha();
-        }
-        get camera() {
-            return this._camera;
-        }
-        get zoom() {
-            return this._zoom;
-        }
-        get trajectory() {
-            return this._trajectory;
-        }
-        get currentIndex() {
-            return this._currentIndex;
-        }
-        get currentImage() {
-            return this._currentImage;
-        }
-        get previousImage() {
-            return this._previousImage;
-        }
-        get currentCamera() {
-            return this._currentCamera;
-        }
-        get currentTransform() {
-            return this._trajectoryTransforms.length > 0 ?
-                this._trajectoryTransforms[this.currentIndex] : null;
-        }
-        get previousTransform() {
-            return this._trajectoryTransforms.length > 1 && this.currentIndex > 0 ?
-                this._trajectoryTransforms[this.currentIndex - 1] : null;
-        }
-        get motionless() {
-            return this._motionless;
-        }
-        get transitionMode() {
-            return this._transitionMode;
-        }
-        move(delta) { }
-        moveTo(position) { }
-        rotate(delta) { }
-        rotateUnbounded(delta) { }
-        rotateWithoutInertia(delta) { }
-        rotateBasic(basicRotation) { }
-        rotateBasicUnbounded(basicRotation) { }
-        rotateBasicWithoutInertia(basicRotation) { }
-        rotateToBasic(basic) { }
-        setSpeed(speed) { }
-        zoomIn(delta, reference) { }
-        update(fps) { }
-        setCenter(center) { }
-        setZoom(zoom) { }
-        dolly(delta) { }
-        orbit(rotation) { }
-        setViewMatrix(matrix) { }
-        truck(direction) { }
-        append(images) {
-            if (images.length < 1) {
-                throw Error("Trajectory can not be empty");
-            }
-            if (this._currentIndex < 0) {
-                this.set(images);
-            }
-            else {
-                this._trajectory = this._trajectory.concat(images);
-                this._appendToTrajectories(images);
-            }
-        }
-        prepend(images) {
-            if (images.length < 1) {
-                throw Error("Trajectory can not be empty");
-            }
-            this._trajectory = images.slice().concat(this._trajectory);
-            this._currentIndex += images.length;
-            this._setCurrentImage();
-            let referenceReset = this._setReference(this._currentImage);
-            if (referenceReset) {
-                this._setTrajectories();
-            }
-            else {
-                this._prependToTrajectories(images);
-            }
-            this._setCurrentCamera();
-        }
-        remove(n) {
-            if (n < 0) {
-                throw Error("n must be a positive integer");
-            }
-            if (this._currentIndex - 1 < n) {
-                throw Error("Current and previous images can not be removed");
-            }
-            for (let i = 0; i < n; i++) {
-                this._trajectory.shift();
-                this._trajectoryTransforms.shift();
-                this._trajectoryCameras.shift();
-                this._currentIndex--;
-            }
-            this._setCurrentImage();
-        }
-        clearPrior() {
-            if (this._currentIndex > 0) {
-                this.remove(this._currentIndex - 1);
-            }
-        }
-        clear() {
-            this.cut();
-            if (this._currentIndex > 0) {
-                this.remove(this._currentIndex - 1);
-            }
-        }
-        cut() {
-            while (this._trajectory.length - 1 > this._currentIndex) {
-                this._trajectory.pop();
-                this._trajectoryTransforms.pop();
-                this._trajectoryCameras.pop();
-            }
-        }
-        set(images) {
-            this._setTrajectory(images);
-            this._setCurrentImage();
-            this._setReference(this._currentImage);
-            this._setTrajectories();
-            this._setCurrentCamera();
-        }
-        getCenter() {
-            return this._currentImage != null ?
-                this.currentTransform.projectBasic(this._camera.lookat.toArray()) :
-                [0.5, 0.5];
-        }
-        setTransitionMode(mode) {
-            this._transitionMode = mode;
-        }
-        _getAlpha() { return 1; }
-        _setCurrent() {
-            this._setCurrentImage();
-            let referenceReset = this._setReference(this._currentImage);
-            if (referenceReset) {
-                this._setTrajectories();
-            }
-            this._setCurrentCamera();
-        }
-        _setCurrentCamera() {
-            this._currentCamera = this._trajectoryCameras[this._currentIndex].clone();
-            this._previousCamera = this._currentIndex > 0 ?
-                this._trajectoryCameras[this._currentIndex - 1].clone() :
-                this._currentCamera.clone();
-        }
-        _motionlessTransition() {
-            let imagesSet = this._currentImage != null && this._previousImage != null;
-            return imagesSet && (this._transitionMode === exports.TransitionMode.Instantaneous || !(this._currentImage.merged &&
-                this._previousImage.merged &&
-                this._withinOriginalDistance() &&
-                this._sameConnectedComponent()));
-        }
-        _setReference(image) {
-            // do not reset reference if image is within threshold distance
-            if (Math.abs(image.lngLat.lat - this.reference.lat) < this._referenceThreshold &&
-                Math.abs(image.lngLat.lng - this.reference.lng) < this._referenceThreshold) {
-                return false;
-            }
-            // do not reset reference if previous image exist and transition is with motion
-            if (this._previousImage != null && !this._motionlessTransition()) {
-                return false;
-            }
-            this._reference.lat = image.lngLat.lat;
-            this._reference.lng = image.lngLat.lng;
-            this._reference.alt = image.computedAltitude;
-            return true;
-        }
-        _setCurrentImage() {
-            this._currentImage = this._trajectory.length > 0 ?
-                this._trajectory[this._currentIndex] :
-                null;
-            this._previousImage = this._currentIndex > 0 ?
-                this._trajectory[this._currentIndex - 1] :
-                null;
-        }
-        _setTrajectory(images) {
-            if (images.length < 1) {
-                throw new ArgumentMapillaryError("Trajectory can not be empty");
-            }
-            if (this._currentImage != null) {
-                this._trajectory = [this._currentImage].concat(images);
-                this._currentIndex = 1;
-            }
-            else {
-                this._trajectory = images.slice();
-                this._currentIndex = 0;
-            }
-        }
-        _setTrajectories() {
-            this._trajectoryTransforms.length = 0;
-            this._trajectoryCameras.length = 0;
-            this._appendToTrajectories(this._trajectory);
-        }
-        _appendToTrajectories(images) {
-            for (let image of images) {
-                if (!image.assetsCached) {
-                    throw new ArgumentMapillaryError("Assets must be cached when image is added to trajectory");
-                }
-                let translation = this._imageToTranslation(image, this.reference);
-                let transform = new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, image.image, undefined, image.cameraParameters, image.cameraType);
-                this._trajectoryTransforms.push(transform);
-                this._trajectoryCameras.push(new Camera(transform));
-            }
-        }
-        _prependToTrajectories(images) {
-            for (let image of images.reverse()) {
-                if (!image.assetsCached) {
-                    throw new ArgumentMapillaryError("Assets must be cached when added to trajectory");
-                }
-                let translation = this._imageToTranslation(image, this.reference);
-                let transform = new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, image.image, undefined, image.cameraParameters, image.cameraType);
-                this._trajectoryTransforms.unshift(transform);
-                this._trajectoryCameras.unshift(new Camera(transform));
-            }
-        }
-        _imageToTranslation(image, reference) {
-            return computeTranslation({ alt: image.computedAltitude, lat: image.lngLat.lat, lng: image.lngLat.lng }, image.rotation, reference);
-        }
-        _sameConnectedComponent() {
-            let current = this._currentImage;
-            let previous = this._previousImage;
-            return !!current && !!previous &&
-                current.mergeId === previous.mergeId;
-        }
-        _withinOriginalDistance() {
-            let current = this._currentImage;
-            let previous = this._previousImage;
-            if (!current || !previous) {
-                return true;
-            }
-            // 50 km/h moves 28m in 2s
-            let distance = this._spatial.distanceFromLngLat(current.originalLngLat.lng, current.originalLngLat.lat, previous.originalLngLat.lng, previous.originalLngLat.lat);
-            return distance < 25;
-        }
-    }
-
-    class EulerRotationDelta {
-        constructor(phi, theta) {
-            this._phi = phi;
-            this._theta = theta;
-        }
-        get phi() {
-            return this._phi;
-        }
-        set phi(value) {
-            this._phi = value;
-        }
-        get theta() {
-            return this._theta;
-        }
-        set theta(value) {
-            this._theta = value;
-        }
-        get isZero() {
-            return this._phi === 0 && this._theta === 0;
-        }
-        copy(delta) {
-            this._phi = delta.phi;
-            this._theta = delta.theta;
-        }
-        lerp(other, alpha) {
-            this._phi = (1 - alpha) * this._phi + alpha * other.phi;
-            this._theta = (1 - alpha) * this._theta + alpha * other.theta;
-        }
-        multiply(value) {
-            this._phi *= value;
-            this._theta *= value;
-        }
-        threshold(value) {
-            this._phi = Math.abs(this._phi) > value ? this._phi : 0;
-            this._theta = Math.abs(this._theta) > value ? this._theta : 0;
-        }
-        lengthSquared() {
-            return this._phi * this._phi + this._theta * this._theta;
-        }
-        reset() {
-            this._phi = 0;
-            this._theta = 0;
-        }
-    }
-
-    class InteractiveStateBase extends StateBase {
-        constructor(state) {
-            super(state);
-            this._animationSpeed = 1 / 40;
-            this._rotationDelta = new EulerRotationDelta(0, 0);
-            this._requestedRotationDelta = null;
-            this._basicRotation = [0, 0];
-            this._requestedBasicRotation = null;
-            this._requestedBasicRotationUnbounded = null;
-            this._rotationAcceleration = 0.86;
-            this._rotationIncreaseAlpha = 0.97;
-            this._rotationDecreaseAlpha = 0.9;
-            this._rotationThreshold = 1e-3;
-            this._unboundedRotationAlpha = 0.8;
-            this._desiredZoom = state.zoom;
-            this._minZoom = 0;
-            this._maxZoom = 3;
-            this._lookatDepth = 10;
-            this._desiredLookat = null;
-            this._desiredCenter = null;
-        }
-        rotate(rotationDelta) {
-            if (this._currentImage == null) {
-                return;
-            }
-            if (rotationDelta.phi === 0 && rotationDelta.theta === 0) {
-                return;
-            }
-            this._desiredZoom = this._zoom;
-            this._desiredLookat = null;
-            this._requestedBasicRotation = null;
-            if (this._requestedRotationDelta != null) {
-                this._requestedRotationDelta.phi = this._requestedRotationDelta.phi + rotationDelta.phi;
-                this._requestedRotationDelta.theta = this._requestedRotationDelta.theta + rotationDelta.theta;
-            }
-            else {
-                this._requestedRotationDelta = new EulerRotationDelta(rotationDelta.phi, rotationDelta.theta);
-            }
-        }
-        rotateUnbounded(delta) {
-            if (this._currentImage == null) {
-                return;
-            }
-            this._requestedBasicRotation = null;
-            this._requestedRotationDelta = null;
-            this._applyRotation(delta, this._currentCamera);
-            this._applyRotation(delta, this._previousCamera);
-            if (!this._desiredLookat) {
-                return;
-            }
-            const q = new Quaternion().setFromUnitVectors(this._currentCamera.up, new Vector3(0, 0, 1));
-            const qInverse = q.clone().invert();
-            const offset = new Vector3()
-                .copy(this._desiredLookat)
-                .sub(this._camera.position)
-                .applyQuaternion(q);
-            const length = offset.length();
-            let phi = Math.atan2(offset.y, offset.x);
-            phi += delta.phi;
-            let theta = Math.atan2(Math.sqrt(offset.x * offset.x + offset.y * offset.y), offset.z);
-            theta += delta.theta;
-            theta = Math.max(0.1, Math.min(Math.PI - 0.1, theta));
-            offset.x = Math.sin(theta) * Math.cos(phi);
-            offset.y = Math.sin(theta) * Math.sin(phi);
-            offset.z = Math.cos(theta);
-            offset.applyQuaternion(qInverse);
-            this._desiredLookat
-                .copy(this._camera.position)
-                .add(offset.multiplyScalar(length));
-        }
-        rotateWithoutInertia(rotationDelta) {
-            if (this._currentImage == null) {
-                return;
-            }
-            this._desiredZoom = this._zoom;
-            this._desiredLookat = null;
-            this._requestedBasicRotation = null;
-            this._requestedRotationDelta = null;
-            const threshold = Math.PI / (10 * Math.pow(2, this._zoom));
-            const delta = {
-                phi: this._spatial.clamp(rotationDelta.phi, -threshold, threshold),
-                theta: this._spatial.clamp(rotationDelta.theta, -threshold, threshold),
-            };
-            this._applyRotation(delta, this._currentCamera);
-            this._applyRotation(delta, this._previousCamera);
-        }
-        rotateBasic(basicRotation) {
-            if (this._currentImage == null) {
-                return;
-            }
-            this._desiredZoom = this._zoom;
-            this._desiredLookat = null;
-            this._requestedRotationDelta = null;
-            if (this._requestedBasicRotation != null) {
-                this._requestedBasicRotation[0] += basicRotation[0];
-                this._requestedBasicRotation[1] += basicRotation[1];
-                let threshold = 0.05 / Math.pow(2, this._zoom);
-                this._requestedBasicRotation[0] =
-                    this._spatial.clamp(this._requestedBasicRotation[0], -threshold, threshold);
-                this._requestedBasicRotation[1] =
-                    this._spatial.clamp(this._requestedBasicRotation[1], -threshold, threshold);
-            }
-            else {
-                this._requestedBasicRotation = basicRotation.slice();
-            }
-        }
-        rotateBasicUnbounded(basicRotation) {
-            if (this._currentImage == null) {
-                return;
-            }
-            if (this._requestedBasicRotationUnbounded != null) {
-                this._requestedBasicRotationUnbounded[0] += basicRotation[0];
-                this._requestedBasicRotationUnbounded[1] += basicRotation[1];
-            }
-            else {
-                this._requestedBasicRotationUnbounded = basicRotation.slice();
-            }
-        }
-        rotateBasicWithoutInertia(basic) {
-            if (this._currentImage == null) {
-                return;
-            }
-            this._desiredZoom = this._zoom;
-            this._desiredLookat = null;
-            this._requestedRotationDelta = null;
-            this._requestedBasicRotation = null;
-            const threshold = 0.05 / Math.pow(2, this._zoom);
-            const basicRotation = basic.slice();
-            basicRotation[0] = this._spatial.clamp(basicRotation[0], -threshold, threshold);
-            basicRotation[1] = this._spatial.clamp(basicRotation[1], -threshold, threshold);
-            this._applyRotationBasic(basicRotation);
-        }
-        rotateToBasic(basic) {
-            if (this._currentImage == null) {
-                return;
-            }
-            this._desiredZoom = this._zoom;
-            this._desiredLookat = null;
-            basic[0] = this._spatial.clamp(basic[0], 0, 1);
-            basic[1] = this._spatial.clamp(basic[1], 0, 1);
-            let lookat = this.currentTransform.unprojectBasic(basic, this._lookatDepth);
-            this._currentCamera.lookat.fromArray(lookat);
-        }
-        zoomIn(delta, reference) {
-            if (this._currentImage == null) {
-                return;
-            }
-            this._desiredZoom = Math.max(this._minZoom, Math.min(this._maxZoom, this._desiredZoom + delta));
-            let currentCenter = this.currentTransform.projectBasic(this._currentCamera.lookat.toArray());
-            let currentCenterX = currentCenter[0];
-            let currentCenterY = currentCenter[1];
-            let zoom0 = Math.pow(2, this._zoom);
-            let zoom1 = Math.pow(2, this._desiredZoom);
-            let refX = reference[0];
-            let refY = reference[1];
-            if (isSpherical(this.currentTransform.cameraType)) {
-                if (refX - currentCenterX > 0.5) {
-                    refX = refX - 1;
-                }
-                else if (currentCenterX - refX > 0.5) {
-                    refX = 1 + refX;
-                }
-            }
-            let newCenterX = refX - zoom0 / zoom1 * (refX - currentCenterX);
-            let newCenterY = refY - zoom0 / zoom1 * (refY - currentCenterY);
-            if (isSpherical(this._currentImage.cameraType)) {
-                newCenterX = this._spatial
-                    .wrap(newCenterX + this._basicRotation[0], 0, 1);
-                newCenterY = this._spatial
-                    .clamp(newCenterY + this._basicRotation[1], 0.05, 0.95);
-            }
-            else {
-                newCenterX = this._spatial.clamp(newCenterX, 0, 1);
-                newCenterY = this._spatial.clamp(newCenterY, 0, 1);
-            }
-            this._desiredLookat = new Vector3()
-                .fromArray(this.currentTransform.unprojectBasic([newCenterX, newCenterY], this._lookatDepth));
-        }
-        setCenter(center) {
-            this._desiredLookat = null;
-            this._requestedRotationDelta = null;
-            this._requestedBasicRotation = null;
-            this._desiredZoom = this._zoom;
-            let clamped = [
-                this._spatial.clamp(center[0], 0, 1),
-                this._spatial.clamp(center[1], 0, 1),
-            ];
-            if (this._currentImage == null) {
-                this._desiredCenter = clamped;
-                return;
-            }
-            this._desiredCenter = null;
-            let currentLookat = new Vector3()
-                .fromArray(this.currentTransform.unprojectBasic(clamped, this._lookatDepth));
-            let previousTransform = this.previousTransform != null ?
-                this.previousTransform :
-                this.currentTransform;
-            let previousLookat = new Vector3()
-                .fromArray(previousTransform.unprojectBasic(clamped, this._lookatDepth));
-            this._currentCamera.lookat.copy(currentLookat);
-            this._previousCamera.lookat.copy(previousLookat);
-        }
-        setZoom(zoom) {
-            this._desiredLookat = null;
-            this._requestedRotationDelta = null;
-            this._requestedBasicRotation = null;
-            this._zoom = this._spatial.clamp(zoom, this._minZoom, this._maxZoom);
-            this._desiredZoom = this._zoom;
-        }
-        _applyRotation(delta, camera) {
-            if (camera == null) {
-                return;
-            }
-            let q = new Quaternion().setFromUnitVectors(camera.up, new Vector3(0, 0, 1));
-            let qInverse = q.clone().invert();
-            let offset = new Vector3();
-            offset.copy(camera.lookat).sub(camera.position);
-            offset.applyQuaternion(q);
-            let length = offset.length();
-            let phi = Math.atan2(offset.y, offset.x);
-            phi += delta.phi;
-            let theta = Math.atan2(Math.sqrt(offset.x * offset.x + offset.y * offset.y), offset.z);
-            theta += delta.theta;
-            theta = Math.max(0.1, Math.min(Math.PI - 0.1, theta));
-            offset.x = Math.sin(theta) * Math.cos(phi);
-            offset.y = Math.sin(theta) * Math.sin(phi);
-            offset.z = Math.cos(theta);
-            offset.applyQuaternion(qInverse);
-            camera.lookat.copy(camera.position).add(offset.multiplyScalar(length));
-        }
-        _applyRotationBasic(basicRotation) {
-            let currentImage = this._currentImage;
-            let previousImage = this._previousImage != null ?
-                this.previousImage :
-                this.currentImage;
-            let currentCamera = this._currentCamera;
-            let previousCamera = this._previousCamera;
-            let currentTransform = this.currentTransform;
-            let previousTransform = this.previousTransform != null ?
-                this.previousTransform :
-                this.currentTransform;
-            let currentBasic = currentTransform.projectBasic(currentCamera.lookat.toArray());
-            let previousBasic = previousTransform.projectBasic(previousCamera.lookat.toArray());
-            if (isSpherical(currentImage.cameraType)) {
-                currentBasic[0] = this._spatial.wrap(currentBasic[0] + basicRotation[0], 0, 1);
-                currentBasic[1] = this._spatial.clamp(currentBasic[1] + basicRotation[1], 0.05, 0.95);
-            }
-            else {
-                currentBasic[0] = this._spatial.clamp(currentBasic[0] + basicRotation[0], 0, 1);
-                currentBasic[1] = this._spatial.clamp(currentBasic[1] + basicRotation[1], 0, 1);
-            }
-            if (isSpherical(previousImage.cameraType)) {
-                previousBasic[0] = this._spatial.wrap(previousBasic[0] + basicRotation[0], 0, 1);
-                previousBasic[1] = this._spatial.clamp(previousBasic[1] + basicRotation[1], 0.05, 0.95);
-            }
-            else {
-                previousBasic[0] = this._spatial.clamp(previousBasic[0] + basicRotation[0], 0, 1);
-                previousBasic[1] = this._spatial.clamp(currentBasic[1] + basicRotation[1], 0, 1);
-            }
-            let currentLookat = currentTransform.unprojectBasic(currentBasic, this._lookatDepth);
-            currentCamera.lookat.fromArray(currentLookat);
-            let previousLookat = previousTransform.unprojectBasic(previousBasic, this._lookatDepth);
-            previousCamera.lookat.fromArray(previousLookat);
-        }
-        _updateZoom(animationSpeed) {
-            let diff = this._desiredZoom - this._zoom;
-            let sign = diff > 0 ? 1 : diff < 0 ? -1 : 0;
-            if (diff === 0) {
-                return;
-            }
-            else if (Math.abs(diff) < 2e-3) {
-                this._zoom = this._desiredZoom;
-                if (this._desiredLookat != null) {
-                    this._desiredLookat = null;
-                }
-            }
-            else {
-                this._zoom += sign * Math.max(Math.abs(5 * animationSpeed * diff), 2e-3);
-            }
-        }
-        _updateLookat(animationSpeed) {
-            if (this._desiredLookat === null) {
-                return;
-            }
-            let diff = this._desiredLookat.distanceToSquared(this._currentCamera.lookat);
-            if (Math.abs(diff) < 1e-6) {
-                this._currentCamera.lookat.copy(this._desiredLookat);
-                this._desiredLookat = null;
-            }
-            else {
-                this._currentCamera.lookat.lerp(this._desiredLookat, 5 * animationSpeed);
-            }
-        }
-        _updateRotation() {
-            if (this._requestedRotationDelta != null) {
-                let length = this._rotationDelta.lengthSquared();
-                let requestedLength = this._requestedRotationDelta.lengthSquared();
-                if (requestedLength > length) {
-                    this._rotationDelta.lerp(this._requestedRotationDelta, this._rotationIncreaseAlpha);
-                }
-                else {
-                    this._rotationDelta.lerp(this._requestedRotationDelta, this._rotationDecreaseAlpha);
-                }
-                this._requestedRotationDelta = null;
-                return;
-            }
-            if (this._rotationDelta.isZero) {
-                return;
-            }
-            const alpha = isSpherical(this.currentImage.cameraType) ?
-                1 : this._alpha;
-            this._rotationDelta.multiply(this._rotationAcceleration * alpha);
-            this._rotationDelta.threshold(this._rotationThreshold);
-        }
-        _updateRotationBasic() {
-            if (this._requestedBasicRotation != null) {
-                let x = this._basicRotation[0];
-                let y = this._basicRotation[1];
-                let reqX = this._requestedBasicRotation[0];
-                let reqY = this._requestedBasicRotation[1];
-                if (Math.abs(reqX) > Math.abs(x)) {
-                    this._basicRotation[0] = (1 - this._rotationIncreaseAlpha) * x + this._rotationIncreaseAlpha * reqX;
-                }
-                else {
-                    this._basicRotation[0] = (1 - this._rotationDecreaseAlpha) * x + this._rotationDecreaseAlpha * reqX;
-                }
-                if (Math.abs(reqY) > Math.abs(y)) {
-                    this._basicRotation[1] = (1 - this._rotationIncreaseAlpha) * y + this._rotationIncreaseAlpha * reqY;
-                }
-                else {
-                    this._basicRotation[1] = (1 - this._rotationDecreaseAlpha) * y + this._rotationDecreaseAlpha * reqY;
-                }
-                this._requestedBasicRotation = null;
-                return;
-            }
-            if (this._requestedBasicRotationUnbounded != null) {
-                let reqX = this._requestedBasicRotationUnbounded[0];
-                let reqY = this._requestedBasicRotationUnbounded[1];
-                if (Math.abs(reqX) > 0) {
-                    this._basicRotation[0] = (1 - this._unboundedRotationAlpha) * this._basicRotation[0] + this._unboundedRotationAlpha * reqX;
-                }
-                if (Math.abs(reqY) > 0) {
-                    this._basicRotation[1] = (1 - this._unboundedRotationAlpha) * this._basicRotation[1] + this._unboundedRotationAlpha * reqY;
-                }
-                if (this._desiredLookat != null) {
-                    let desiredBasicLookat = this.currentTransform.projectBasic(this._desiredLookat.toArray());
-                    desiredBasicLookat[0] += reqX;
-                    desiredBasicLookat[1] += reqY;
-                    this._desiredLookat = new Vector3()
-                        .fromArray(this.currentTransform.unprojectBasic(desiredBasicLookat, this._lookatDepth));
-                }
-                this._requestedBasicRotationUnbounded = null;
-            }
-            if (this._basicRotation[0] === 0 && this._basicRotation[1] === 0) {
-                return;
-            }
-            this._basicRotation[0] = this._rotationAcceleration * this._basicRotation[0];
-            this._basicRotation[1] = this._rotationAcceleration * this._basicRotation[1];
-            if (Math.abs(this._basicRotation[0]) < this._rotationThreshold / Math.pow(2, this._zoom) &&
-                Math.abs(this._basicRotation[1]) < this._rotationThreshold / Math.pow(2, this._zoom)) {
-                this._basicRotation = [0, 0];
-            }
-        }
-        _clearRotation() {
-            if (isSpherical(this._currentImage.cameraType)) {
-                return;
-            }
-            if (this._requestedRotationDelta != null) {
-                this._requestedRotationDelta = null;
-            }
-            if (!this._rotationDelta.isZero) {
-                this._rotationDelta.reset();
-            }
-            if (this._requestedBasicRotation != null) {
-                this._requestedBasicRotation = null;
-            }
-            if (this._basicRotation[0] > 0 || this._basicRotation[1] > 0) {
-                this._basicRotation = [0, 0];
-            }
-        }
-        _setDesiredCenter() {
-            if (this._desiredCenter == null) {
-                return;
-            }
-            let lookatDirection = new Vector3()
-                .fromArray(this.currentTransform.unprojectBasic(this._desiredCenter, this._lookatDepth))
-                .sub(this._currentCamera.position);
-            this._currentCamera.lookat.copy(this._currentCamera.position.clone().add(lookatDirection));
-            this._previousCamera.lookat.copy(this._previousCamera.position.clone().add(lookatDirection));
-            this._desiredCenter = null;
-        }
-        _setDesiredZoom() {
-            this._desiredZoom =
-                isSpherical(this._currentImage.cameraType) ||
-                    this._previousImage == null ?
-                    this._zoom : 0;
-        }
-    }
-
-    class TraversingState extends InteractiveStateBase {
-        constructor(state) {
-            super(state);
-            this._adjustCameras();
-            this._motionless = this._motionlessTransition();
-            this._baseAlpha = this._alpha;
-            this._speedCoefficient = 1;
-            this._unitBezier =
-                new TraversingState._interpolator(0.74, 0.67, 0.38, 0.96);
-            this._useBezier = false;
-        }
-        static register(interpolator) {
-            TraversingState._interpolator = interpolator;
-        }
-        append(images) {
-            let emptyTrajectory = this._trajectory.length === 0;
-            if (emptyTrajectory) {
-                this._resetTransition();
-            }
-            super.append(images);
-            if (emptyTrajectory) {
-                this._setDesiredCenter();
-                this._setDesiredZoom();
-            }
-        }
-        prepend(images) {
-            let emptyTrajectory = this._trajectory.length === 0;
-            if (emptyTrajectory) {
-                this._resetTransition();
-            }
-            super.prepend(images);
-            if (emptyTrajectory) {
-                this._setDesiredCenter();
-                this._setDesiredZoom();
-            }
-        }
-        set(images) {
-            super.set(images);
-            this._desiredLookat = null;
-            this._resetTransition();
-            this._clearRotation();
-            this._setDesiredCenter();
-            this._setDesiredZoom();
-            if (this._trajectory.length < 3) {
-                this._useBezier = true;
-            }
-        }
-        setSpeed(speed) {
-            this._speedCoefficient = this._spatial.clamp(speed, 0, 10);
-        }
-        update(fps) {
-            if (this._alpha === 1 && this._currentIndex + this._alpha < this._trajectory.length) {
-                this._currentIndex += 1;
-                this._useBezier = this._trajectory.length < 3 &&
-                    this._currentIndex + 1 === this._trajectory.length;
-                this._setCurrent();
-                this._resetTransition();
-                this._clearRotation();
-                this._desiredZoom =
-                    isSpherical(this._currentImage.cameraType) ?
-                        this._zoom : 0;
-                this._desiredLookat = null;
-            }
-            let animationSpeed = this._animationSpeed * (60 / fps);
-            this._baseAlpha = Math.min(1, this._baseAlpha + this._speedCoefficient * animationSpeed);
-            if (this._useBezier) {
-                this._alpha = this._unitBezier.solve(this._baseAlpha);
-            }
-            else {
-                this._alpha = this._baseAlpha;
-            }
-            this._updateRotation();
-            if (!this._rotationDelta.isZero) {
-                this._applyRotation(this._rotationDelta, this._previousCamera);
-                this._applyRotation(this._rotationDelta, this._currentCamera);
-            }
-            this._updateRotationBasic();
-            if (this._basicRotation[0] !== 0 || this._basicRotation[1] !== 0) {
-                this._applyRotationBasic(this._basicRotation);
-            }
-            this._updateZoom(animationSpeed);
-            this._updateLookat(animationSpeed);
-            this._camera.lerpCameras(this._previousCamera, this._currentCamera, this.alpha);
-        }
-        _getAlpha() {
-            return this._motionless ? Math.ceil(this._alpha) : this._alpha;
-        }
-        _setCurrentCamera() {
-            super._setCurrentCamera();
-            this._adjustCameras();
-        }
-        _adjustCameras() {
-            if (this._previousImage == null) {
-                return;
-            }
-            let lookat = this._camera.lookat.clone().sub(this._camera.position);
-            this._previousCamera.lookat.copy(lookat.clone().add(this._previousCamera.position));
-            if (isSpherical(this._currentImage.cameraType)) {
-                this._currentCamera.lookat.copy(lookat.clone().add(this._currentCamera.position));
-            }
-        }
-        _resetTransition() {
-            this._alpha = 0;
-            this._baseAlpha = 0;
-            this._motionless = this._motionlessTransition();
-        }
-    }
-
-    class ComponentService {
-        constructor(container, navigator) {
-            this._components = {};
-            for (const componentName in ComponentService.registeredComponents) {
-                if (!ComponentService.registeredComponents.hasOwnProperty(componentName)) {
-                    continue;
-                }
-                const component = ComponentService.registeredComponents[componentName];
-                this._components[componentName] = {
-                    active: false,
-                    component: new component(componentName, container, navigator),
-                };
-            }
-            this._coverComponent = new ComponentService.registeredCoverComponent("cover", container, navigator);
-            this._coverComponent.activate();
-            this._coverActivated = true;
-        }
-        static register(component) {
-            if (ComponentService.registeredComponents[component.componentName] === undefined) {
-                ComponentService.registeredComponents[component.componentName] = component;
-            }
-        }
-        static registerCover(coverComponent) {
-            ComponentService.registeredCoverComponent = coverComponent;
-        }
-        get coverActivated() {
-            return this._coverActivated;
-        }
-        activateCover() {
-            if (this._coverActivated) {
-                return;
-            }
-            this._coverActivated = true;
-            for (const componentName in this._components) {
-                if (!this._components.hasOwnProperty(componentName)) {
-                    continue;
-                }
-                const component = this._components[componentName];
-                if (component.active) {
-                    component.component.deactivate();
-                }
-            }
-        }
-        deactivateCover() {
-            if (!this._coverActivated) {
-                return;
-            }
-            this._coverActivated = false;
-            for (const componentName in this._components) {
-                if (!this._components.hasOwnProperty(componentName)) {
-                    continue;
-                }
-                const component = this._components[componentName];
-                if (component.active) {
-                    component.component.activate();
-                }
-            }
-        }
-        activate(name) {
-            this._checkName(name);
-            this._components[name].active = true;
-            if (!this._coverActivated) {
-                this.get(name).activate();
-            }
-        }
-        configure(name, conf) {
-            this._checkName(name);
-            this.get(name).configure(conf);
-        }
-        deactivate(name) {
-            this._checkName(name);
-            this._components[name].active = false;
-            if (!this._coverActivated) {
-                this.get(name).deactivate();
-            }
-        }
-        get(name) {
-            return this._components[name].component;
-        }
-        getCover() {
-            return this._coverComponent;
-        }
-        remove() {
-            this._coverComponent.deactivate();
-            for (const componentName in this._components) {
-                if (!this._components.hasOwnProperty(componentName)) {
-                    continue;
-                }
-                this._components[componentName].component.deactivate();
-            }
-        }
-        _checkName(name) {
-            if (!(name in this._components)) {
-                throw new ArgumentMapillaryError(`Component does not exist: ${name}`);
-            }
-        }
-    }
-    ComponentService.registeredComponents = {};
-
-    var nativeIsArray = Array.isArray;
-    var toString$2 = Object.prototype.toString;
-
-    var xIsArray = nativeIsArray || isArray;
-
-    function isArray(obj) {
-        return toString$2.call(obj) === "[object Array]"
-    }
-
-    var version = "2";
-
-    VirtualPatch.NONE = 0;
-    VirtualPatch.VTEXT = 1;
-    VirtualPatch.VNODE = 2;
-    VirtualPatch.WIDGET = 3;
-    VirtualPatch.PROPS = 4;
-    VirtualPatch.ORDER = 5;
-    VirtualPatch.INSERT = 6;
-    VirtualPatch.REMOVE = 7;
-    VirtualPatch.THUNK = 8;
-
-    var vpatch = VirtualPatch;
-
-    function VirtualPatch(type, vNode, patch) {
-        this.type = Number(type);
-        this.vNode = vNode;
-        this.patch = patch;
-    }
-
-    VirtualPatch.prototype.version = version;
-    VirtualPatch.prototype.type = "VirtualPatch";
-
-    var isVnode = isVirtualNode;
-
-    function isVirtualNode(x) {
-        return x && x.type === "VirtualNode" && x.version === version
-    }
-
-    var isVtext = isVirtualText;
-
-    function isVirtualText(x) {
-        return x && x.type === "VirtualText" && x.version === version
-    }
-
-    var isWidget_1 = isWidget;
-
-    function isWidget(w) {
-        return w && w.type === "Widget"
-    }
-
-    var isThunk_1 = isThunk;
-
-    function isThunk(t) {
-        return t && t.type === "Thunk"
-    }
-
-    var handleThunk_1 = handleThunk;
-
-    function handleThunk(a, b) {
-        var renderedA = a;
-        var renderedB = b;
-
-        if (isThunk_1(b)) {
-            renderedB = renderThunk(b, a);
-        }
-
-        if (isThunk_1(a)) {
-            renderedA = renderThunk(a, null);
-        }
-
-        return {
-            a: renderedA,
-            b: renderedB
-        }
-    }
-
-    function renderThunk(thunk, previous) {
-        var renderedThunk = thunk.vnode;
-
-        if (!renderedThunk) {
-            renderedThunk = thunk.vnode = thunk.render(previous);
-        }
-
-        if (!(isVnode(renderedThunk) ||
-                isVtext(renderedThunk) ||
-                isWidget_1(renderedThunk))) {
-            throw new Error("thunk did not return a valid node");
-        }
-
-        return renderedThunk
-    }
-
-    var isObject = function isObject(x) {
-       return typeof x === 'object' && x !== null;
-    };
-
-    var isVhook = isHook;
-
-    function isHook(hook) {
-        return hook &&
-          (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") ||
-           typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook"))
-    }
-
-    var diffProps_1 = diffProps;
-
-    function diffProps(a, b) {
-        var diff;
-
-        for (var aKey in a) {
-            if (!(aKey in b)) {
-                diff = diff || {};
-                diff[aKey] = undefined;
-            }
-
-            var aValue = a[aKey];
-            var bValue = b[aKey];
-
-            if (aValue === bValue) {
-                continue
-            } else if (isObject(aValue) && isObject(bValue)) {
-                if (getPrototype$1(bValue) !== getPrototype$1(aValue)) {
-                    diff = diff || {};
-                    diff[aKey] = bValue;
-                } else if (isVhook(bValue)) {
-                     diff = diff || {};
-                     diff[aKey] = bValue;
-                } else {
-                    var objectDiff = diffProps(aValue, bValue);
-                    if (objectDiff) {
-                        diff = diff || {};
-                        diff[aKey] = objectDiff;
-                    }
-                }
-            } else {
-                diff = diff || {};
-                diff[aKey] = bValue;
-            }
-        }
-
-        for (var bKey in b) {
-            if (!(bKey in a)) {
-                diff = diff || {};
-                diff[bKey] = b[bKey];
-            }
-        }
-
-        return diff
-    }
-
-    function getPrototype$1(value) {
-      if (Object.getPrototypeOf) {
-        return Object.getPrototypeOf(value)
-      } else if (value.__proto__) {
-        return value.__proto__
-      } else if (value.constructor) {
-        return value.constructor.prototype
-      }
-    }
-
-    var diff_1$1 = diff;
-
-    function diff(a, b) {
-        var patch = { a: a };
-        walk(a, b, patch, 0);
-        return patch
-    }
-
-    function walk(a, b, patch, index) {
-        if (a === b) {
-            return
-        }
-
-        var apply = patch[index];
-        var applyClear = false;
-
-        if (isThunk_1(a) || isThunk_1(b)) {
-            thunks(a, b, patch, index);
-        } else if (b == null) {
-
-            // If a is a widget we will add a remove patch for it
-            // Otherwise any child widgets/hooks must be destroyed.
-            // This prevents adding two remove patches for a widget.
-            if (!isWidget_1(a)) {
-                clearState(a, patch, index);
-                apply = patch[index];
-            }
-
-            apply = appendPatch(apply, new vpatch(vpatch.REMOVE, a, b));
-        } else if (isVnode(b)) {
-            if (isVnode(a)) {
-                if (a.tagName === b.tagName &&
-                    a.namespace === b.namespace &&
-                    a.key === b.key) {
-                    var propsPatch = diffProps_1(a.properties, b.properties);
-                    if (propsPatch) {
-                        apply = appendPatch(apply,
-                            new vpatch(vpatch.PROPS, a, propsPatch));
-                    }
-                    apply = diffChildren(a, b, patch, apply, index);
-                } else {
-                    apply = appendPatch(apply, new vpatch(vpatch.VNODE, a, b));
-                    applyClear = true;
-                }
-            } else {
-                apply = appendPatch(apply, new vpatch(vpatch.VNODE, a, b));
-                applyClear = true;
-            }
-        } else if (isVtext(b)) {
-            if (!isVtext(a)) {
-                apply = appendPatch(apply, new vpatch(vpatch.VTEXT, a, b));
-                applyClear = true;
-            } else if (a.text !== b.text) {
-                apply = appendPatch(apply, new vpatch(vpatch.VTEXT, a, b));
-            }
-        } else if (isWidget_1(b)) {
-            if (!isWidget_1(a)) {
-                applyClear = true;
-            }
-
-            apply = appendPatch(apply, new vpatch(vpatch.WIDGET, a, b));
-        }
-
-        if (apply) {
-            patch[index] = apply;
-        }
-
-        if (applyClear) {
-            clearState(a, patch, index);
-        }
-    }
-
-    function diffChildren(a, b, patch, apply, index) {
-        var aChildren = a.children;
-        var orderedSet = reorder(aChildren, b.children);
-        var bChildren = orderedSet.children;
-
-        var aLen = aChildren.length;
-        var bLen = bChildren.length;
-        var len = aLen > bLen ? aLen : bLen;
-
-        for (var i = 0; i < len; i++) {
-            var leftNode = aChildren[i];
-            var rightNode = bChildren[i];
-            index += 1;
-
-            if (!leftNode) {
-                if (rightNode) {
-                    // Excess nodes in b need to be added
-                    apply = appendPatch(apply,
-                        new vpatch(vpatch.INSERT, null, rightNode));
-                }
-            } else {
-                walk(leftNode, rightNode, patch, index);
-            }
-
-            if (isVnode(leftNode) && leftNode.count) {
-                index += leftNode.count;
-            }
-        }
-
-        if (orderedSet.moves) {
-            // Reorder nodes last
-            apply = appendPatch(apply, new vpatch(
-                vpatch.ORDER,
-                a,
-                orderedSet.moves
-            ));
-        }
-
-        return apply
-    }
-
-    function clearState(vNode, patch, index) {
-        // TODO: Make this a single walk, not two
-        unhook(vNode, patch, index);
-        destroyWidgets(vNode, patch, index);
-    }
-
-    // Patch records for all destroyed widgets must be added because we need
-    // a DOM node reference for the destroy function
-    function destroyWidgets(vNode, patch, index) {
-        if (isWidget_1(vNode)) {
-            if (typeof vNode.destroy === "function") {
-                patch[index] = appendPatch(
-                    patch[index],
-                    new vpatch(vpatch.REMOVE, vNode, null)
-                );
-            }
-        } else if (isVnode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) {
-            var children = vNode.children;
-            var len = children.length;
-            for (var i = 0; i < len; i++) {
-                var child = children[i];
-                index += 1;
-
-                destroyWidgets(child, patch, index);
-
-                if (isVnode(child) && child.count) {
-                    index += child.count;
-                }
-            }
-        } else if (isThunk_1(vNode)) {
-            thunks(vNode, null, patch, index);
-        }
-    }
-
-    // Create a sub-patch for thunks
-    function thunks(a, b, patch, index) {
-        var nodes = handleThunk_1(a, b);
-        var thunkPatch = diff(nodes.a, nodes.b);
-        if (hasPatches(thunkPatch)) {
-            patch[index] = new vpatch(vpatch.THUNK, null, thunkPatch);
-        }
-    }
-
-    function hasPatches(patch) {
-        for (var index in patch) {
-            if (index !== "a") {
-                return true
-            }
-        }
-
-        return false
-    }
-
-    // Execute hooks when two nodes are identical
-    function unhook(vNode, patch, index) {
-        if (isVnode(vNode)) {
-            if (vNode.hooks) {
-                patch[index] = appendPatch(
-                    patch[index],
-                    new vpatch(
-                        vpatch.PROPS,
-                        vNode,
-                        undefinedKeys(vNode.hooks)
-                    )
-                );
-            }
-
-            if (vNode.descendantHooks || vNode.hasThunks) {
-                var children = vNode.children;
-                var len = children.length;
-                for (var i = 0; i < len; i++) {
-                    var child = children[i];
-                    index += 1;
-
-                    unhook(child, patch, index);
-
-                    if (isVnode(child) && child.count) {
-                        index += child.count;
-                    }
-                }
-            }
-        } else if (isThunk_1(vNode)) {
-            thunks(vNode, null, patch, index);
-        }
-    }
-
-    function undefinedKeys(obj) {
-        var result = {};
-
-        for (var key in obj) {
-            result[key] = undefined;
-        }
-
-        return result
-    }
-
-    // List diff, naive left to right reordering
-    function reorder(aChildren, bChildren) {
-        // O(M) time, O(M) memory
-        var bChildIndex = keyIndex(bChildren);
-        var bKeys = bChildIndex.keys;
-        var bFree = bChildIndex.free;
-
-        if (bFree.length === bChildren.length) {
-            return {
-                children: bChildren,
-                moves: null
-            }
-        }
-
-        // O(N) time, O(N) memory
-        var aChildIndex = keyIndex(aChildren);
-        var aKeys = aChildIndex.keys;
-        var aFree = aChildIndex.free;
-
-        if (aFree.length === aChildren.length) {
-            return {
-                children: bChildren,
-                moves: null
-            }
-        }
-
-        // O(MAX(N, M)) memory
-        var newChildren = [];
-
-        var freeIndex = 0;
-        var freeCount = bFree.length;
-        var deletedItems = 0;
-
-        // Iterate through a and match a node in b
-        // O(N) time,
-        for (var i = 0 ; i < aChildren.length; i++) {
-            var aItem = aChildren[i];
-            var itemIndex;
-
-            if (aItem.key) {
-                if (bKeys.hasOwnProperty(aItem.key)) {
-                    // Match up the old keys
-                    itemIndex = bKeys[aItem.key];
-                    newChildren.push(bChildren[itemIndex]);
-
-                } else {
-                    // Remove old keyed items
-                    itemIndex = i - deletedItems++;
-                    newChildren.push(null);
-                }
-            } else {
-                // Match the item in a with the next free item in b
-                if (freeIndex < freeCount) {
-                    itemIndex = bFree[freeIndex++];
-                    newChildren.push(bChildren[itemIndex]);
-                } else {
-                    // There are no free items in b to match with
-                    // the free items in a, so the extra free nodes
-                    // are deleted.
-                    itemIndex = i - deletedItems++;
-                    newChildren.push(null);
-                }
-            }
-        }
-
-        var lastFreeIndex = freeIndex >= bFree.length ?
-            bChildren.length :
-            bFree[freeIndex];
-
-        // Iterate through b and append any new keys
-        // O(M) time
-        for (var j = 0; j < bChildren.length; j++) {
-            var newItem = bChildren[j];
-
-            if (newItem.key) {
-                if (!aKeys.hasOwnProperty(newItem.key)) {
-                    // Add any new keyed items
-                    // We are adding new items to the end and then sorting them
-                    // in place. In future we should insert new items in place.
-                    newChildren.push(newItem);
-                }
-            } else if (j >= lastFreeIndex) {
-                // Add any leftover non-keyed items
-                newChildren.push(newItem);
-            }
-        }
-
-        var simulate = newChildren.slice();
-        var simulateIndex = 0;
-        var removes = [];
-        var inserts = [];
-        var simulateItem;
-
-        for (var k = 0; k < bChildren.length;) {
-            var wantedItem = bChildren[k];
-            simulateItem = simulate[simulateIndex];
-
-            // remove items
-            while (simulateItem === null && simulate.length) {
-                removes.push(remove(simulate, simulateIndex, null));
-                simulateItem = simulate[simulateIndex];
-            }
-
-            if (!simulateItem || simulateItem.key !== wantedItem.key) {
-                // if we need a key in this position...
-                if (wantedItem.key) {
-                    if (simulateItem && simulateItem.key) {
-                        // if an insert doesn't put this key in place, it needs to move
-                        if (bKeys[simulateItem.key] !== k + 1) {
-                            removes.push(remove(simulate, simulateIndex, simulateItem.key));
-                            simulateItem = simulate[simulateIndex];
-                            // if the remove didn't put the wanted item in place, we need to insert it
-                            if (!simulateItem || simulateItem.key !== wantedItem.key) {
-                                inserts.push({key: wantedItem.key, to: k});
-                            }
-                            // items are matching, so skip ahead
-                            else {
-                                simulateIndex++;
-                            }
-                        }
-                        else {
-                            inserts.push({key: wantedItem.key, to: k});
-                        }
-                    }
-                    else {
-                        inserts.push({key: wantedItem.key, to: k});
-                    }
-                    k++;
-                }
-                // a key in simulate has no matching wanted key, remove it
-                else if (simulateItem && simulateItem.key) {
-                    removes.push(remove(simulate, simulateIndex, simulateItem.key));
-                }
-            }
-            else {
-                simulateIndex++;
-                k++;
-            }
-        }
-
-        // remove all the remaining nodes from simulate
-        while(simulateIndex < simulate.length) {
-            simulateItem = simulate[simulateIndex];
-            removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key));
-        }
-
-        // If the only moves we have are deletes then we can just
-        // let the delete patch remove these items.
-        if (removes.length === deletedItems && !inserts.length) {
-            return {
-                children: newChildren,
-                moves: null
-            }
-        }
-
-        return {
-            children: newChildren,
-            moves: {
-                removes: removes,
-                inserts: inserts
-            }
-        }
-    }
-
-    function remove(arr, index, key) {
-        arr.splice(index, 1);
-
-        return {
-            from: index,
-            key: key
-        }
-    }
-
-    function keyIndex(children) {
-        var keys = {};
-        var free = [];
-        var length = children.length;
-
-        for (var i = 0; i < length; i++) {
-            var child = children[i];
-
-            if (child.key) {
-                keys[child.key] = i;
-            } else {
-                free.push(i);
-            }
-        }
-
-        return {
-            keys: keys,     // A hash of key name to index
-            free: free      // An array of unkeyed item indices
+
+        return {
+            keys: keys,     // A hash of key name to index
+            free: free      // An array of unkeyed item indices
         }
     }
 
     function appendPatch(apply, patch) {
         if (apply) {
-            if (xIsArray(apply)) {
+            if (isArray$2(apply)) {
                 apply.push(patch);
             } else {
                 apply = [apply, patch];
         }
     }
 
-    var diff_1 = diff_1$1;
-
-    var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
-
-    function getAugmentedNamespace(n) {
-       if (n.__esModule) return n;
-       var a = Object.defineProperty({}, '__esModule', {value: true});
-       Object.keys(n).forEach(function (k) {
-               var d = Object.getOwnPropertyDescriptor(n, k);
-               Object.defineProperty(a, k, d.get ? d : {
-                       enumerable: true,
-                       get: function () {
-                               return n[k];
-                       }
-               });
-       });
-       return a;
-    }
-
-    function createCommonjsModule(fn) {
-      var module = { exports: {} };
-       return fn(module, module.exports), module.exports;
-    }
+    var diff$1 = diff_1$1;
 
-    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 diff_1 = diff$1;
 
     var slice = Array.prototype.slice;
 
-    var domWalk = iterativelyWalk;
+    var domWalk$2 = iterativelyWalk;
 
     function iterativelyWalk(nodes, cb) {
         if (!('length' in nodes)) {
         }
     }
 
-    var domComment = Comment;
+    var domComment = Comment$1;
 
-    function Comment(data, owner) {
-        if (!(this instanceof Comment)) {
-            return new Comment(data, owner)
+    function Comment$1(data, owner) {
+        if (!(this instanceof Comment$1)) {
+            return new Comment$1(data, owner)
         }
 
         this.data = data;
         this.ownerDocument = owner || null;
     }
 
-    Comment.prototype.nodeType = 8;
-    Comment.prototype.nodeName = "#comment";
+    Comment$1.prototype.nodeType = 8;
+    Comment$1.prototype.nodeName = "#comment";
 
-    Comment.prototype.toString = function _Comment_toString() {
+    Comment$1.prototype.toString = function _Comment_toString() {
         return "[object Comment]"
     };
 
-    var domText = DOMText;
+    var domText = DOMText$1;
 
-    function DOMText(value, owner) {
-        if (!(this instanceof DOMText)) {
-            return new DOMText(value)
+    function DOMText$1(value, owner) {
+        if (!(this instanceof DOMText$1)) {
+            return new DOMText$1(value)
         }
 
         this.data = value || "";
         this.ownerDocument = owner || null;
     }
 
-    DOMText.prototype.type = "DOMTextNode";
-    DOMText.prototype.nodeType = 3;
-    DOMText.prototype.nodeName = "#text";
+    DOMText$1.prototype.type = "DOMTextNode";
+    DOMText$1.prototype.nodeType = 3;
+    DOMText$1.prototype.nodeName = "#text";
 
-    DOMText.prototype.toString = function _Text_toString() {
+    DOMText$1.prototype.toString = function _Text_toString() {
         return this.data
     };
 
-    DOMText.prototype.replaceData = function replaceData(index, length, value) {
+    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.length = this.data.length;
     };
 
-    var dispatchEvent_1 = dispatchEvent;
+    var dispatchEvent_1 = dispatchEvent$2;
 
-    function dispatchEvent(ev) {
+    function dispatchEvent$2(ev) {
         var elem = this;
         var type = ev.type;
 
         }
     }
 
-    var addEventListener_1 = addEventListener;
+    var addEventListener_1 = addEventListener$2;
 
-    function addEventListener(type, listener) {
+    function addEventListener$2(type, listener) {
         var elem = this;
 
         if (!elem.listeners) {
         }
     }
 
-    var removeEventListener_1 = removeEventListener;
+    var removeEventListener_1 = removeEventListener$2;
 
-    function removeEventListener(type, listener) {
+    function removeEventListener$2(type, listener) {
         var elem = this;
 
         if (!elem.listeners) {
         }
     }
 
-    var serialize = serializeNode;
+    var serialize = serializeNode$1;
 
     var voidElements = ["area","base","br","col","embed","hr","img","input","keygen","link","menuitem","meta","param","source","track","wbr"];
 
-    function serializeNode(node) {
+    function serializeNode$1(node) {
         switch (node.nodeType) {
             case 3:
                 return escapeText(node.data)
             strings.push(">");
 
             if (elem.childNodes.length) {
-                strings.push.apply(strings, elem.childNodes.map(serializeNode));
+                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) {
         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;
+    var domElement = DOMElement$2;
 
-    function DOMElement(tagName, owner, namespace) {
-        if (!(this instanceof DOMElement)) {
-            return new DOMElement(tagName)
+    function DOMElement$2(tagName, owner, namespace) {
+        if (!(this instanceof DOMElement$2)) {
+            return new DOMElement$2(tagName)
         }
 
         var ns = namespace === undefined ? htmlns : (namespace || null);
         }
     }
 
-    DOMElement.prototype.type = "DOMElement";
-    DOMElement.prototype.nodeType = 1;
+    DOMElement$2.prototype.type = "DOMElement";
+    DOMElement$2.prototype.nodeType = 1;
 
-    DOMElement.prototype.appendChild = function _Element_appendChild(child) {
+    DOMElement$2.prototype.appendChild = function _Element_appendChild(child) {
         if (child.parentNode) {
             child.parentNode.removeChild(child);
         }
         return child
     };
 
-    DOMElement.prototype.replaceChild =
+    DOMElement$2.prototype.replaceChild =
         function _Element_replaceChild(elem, needle) {
             // TODO: Throw NotFoundError if needle.parentNode !== this
 
             return needle
         };
 
-    DOMElement.prototype.removeChild = function _Element_removeChild(elem) {
+    DOMElement$2.prototype.removeChild = function _Element_removeChild(elem) {
         // TODO: Throw NotFoundError if elem.parentNode !== this
 
         var index = this.childNodes.indexOf(elem);
         return elem
     };
 
-    DOMElement.prototype.insertBefore =
+    DOMElement$2.prototype.insertBefore =
         function _Element_insertBefore(elem, needle) {
             // TODO: Throw NotFoundError if referenceElement is a dom node
             // and parentNode !== this
             return elem
         };
 
-    DOMElement.prototype.setAttributeNS =
+    DOMElement$2.prototype.setAttributeNS =
         function _Element_setAttributeNS(namespace, name, value) {
             var prefix = null;
             var localName = name;
             }
         };
 
-    DOMElement.prototype.getAttributeNS =
+    DOMElement$2.prototype.getAttributeNS =
         function _Element_getAttributeNS(namespace, name) {
             var attributes = this._attributes[namespace];
             var value = attributes && attributes[name] && attributes[name].value;
             return value
         };
 
-    DOMElement.prototype.removeAttributeNS =
+    DOMElement$2.prototype.removeAttributeNS =
         function _Element_removeAttributeNS(namespace, name) {
             var attributes = this._attributes[namespace];
             if (attributes) {
             }
         };
 
-    DOMElement.prototype.hasAttributeNS =
+    DOMElement$2.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) {
+    DOMElement$2.prototype.setAttribute = function _Element_setAttribute(name, value) {
         return this.setAttributeNS(null, name, value)
     };
 
-    DOMElement.prototype.getAttribute = function _Element_getAttribute(name) {
+    DOMElement$2.prototype.getAttribute = function _Element_getAttribute(name) {
         return this.getAttributeNS(null, name)
     };
 
-    DOMElement.prototype.removeAttribute = function _Element_removeAttribute(name) {
+    DOMElement$2.prototype.removeAttribute = function _Element_removeAttribute(name) {
         return this.removeAttributeNS(null, name)
     };
 
-    DOMElement.prototype.hasAttribute = function _Element_hasAttribute(name) {
+    DOMElement$2.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;
+    DOMElement$2.prototype.removeEventListener = removeEventListener$1;
+    DOMElement$2.prototype.addEventListener = addEventListener$1;
+    DOMElement$2.prototype.dispatchEvent = dispatchEvent$1;
 
     // Un-implemented
-    DOMElement.prototype.focus = function _Element_focus() {
+    DOMElement$2.prototype.focus = function _Element_focus() {
         return void 0
     };
 
-    DOMElement.prototype.toString = function _Element_toString() {
-        return serialize(this)
+    DOMElement$2.prototype.toString = function _Element_toString() {
+        return serializeNode(this)
     };
 
-    DOMElement.prototype.getElementsByClassName = function _Element_getElementsByClassName(classNames) {
+    DOMElement$2.prototype.getElementsByClassName = function _Element_getElementsByClassName(classNames) {
         var classes = classNames.split(" ");
         var elems = [];
 
-        domWalk(this, function (node) {
+        domWalk$1(this, function (node) {
             if (node.nodeType === 1) {
                 var nodeClassName = node.className || "";
                 var nodeClasses = nodeClassName.split(" ");
         return elems
     };
 
-    DOMElement.prototype.getElementsByTagName = function _Element_getElementsByTagName(tagName) {
+    DOMElement$2.prototype.getElementsByTagName = function _Element_getElementsByTagName(tagName) {
         tagName = tagName.toLowerCase();
         var elems = [];
 
-        domWalk(this.childNodes, function (node) {
+        domWalk$1(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) {
+    DOMElement$2.prototype.contains = function _Element_contains(element) {
+        return domWalk$1(this, function (node) {
             return element === node
         }) || false
     };
 
-    var domFragment = DocumentFragment;
+    var DOMElement$1 = domElement;
 
-    function DocumentFragment(owner) {
-        if (!(this instanceof DocumentFragment)) {
-            return new DocumentFragment()
+    var domFragment = DocumentFragment$1;
+
+    function DocumentFragment$1(owner) {
+        if (!(this instanceof DocumentFragment$1)) {
+            return new DocumentFragment$1()
         }
 
         this.childNodes = [];
         this.ownerDocument = owner || null;
     }
 
-    DocumentFragment.prototype.type = "DocumentFragment";
-    DocumentFragment.prototype.nodeType = 11;
-    DocumentFragment.prototype.nodeName = "#document-fragment";
+    DocumentFragment$1.prototype.type = "DocumentFragment";
+    DocumentFragment$1.prototype.nodeType = 11;
+    DocumentFragment$1.prototype.nodeName = "#document-fragment";
 
-    DocumentFragment.prototype.appendChild  = domElement.prototype.appendChild;
-    DocumentFragment.prototype.replaceChild = domElement.prototype.replaceChild;
-    DocumentFragment.prototype.removeChild  = domElement.prototype.removeChild;
+    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.prototype.toString =
+    DocumentFragment$1.prototype.toString =
         function _DocumentFragment_toString() {
             return this.childNodes.map(function (node) {
                 return String(node)
             }).join("")
         };
 
-    var event = Event;
+    var event = Event$1;
 
-    function Event(family) {}
+    function Event$1(family) {}
 
-    Event.prototype.initEvent = function _Event_initEvent(type, bubbles, cancelable) {
+    Event$1.prototype.initEvent = function _Event_initEvent(type, bubbles, cancelable) {
         this.type = type;
         this.bubbles = bubbles;
         this.cancelable = cancelable;
     };
 
-    Event.prototype.preventDefault = function _Event_preventDefault() {
+    Event$1.prototype.preventDefault = function _Event_preventDefault() {
         
     };
 
-    var document$1 = Document;
+    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;
 
-    function Document() {
-        if (!(this instanceof Document)) {
-            return new Document();
+    var document$3 = Document$1;
+
+    function Document$1() {
+        if (!(this instanceof Document$1)) {
+            return new Document$1();
         }
 
         this.head = this.createElement("head");
         this.nodeType = 9;
     }
 
-    var proto = Document.prototype;
+    var proto = Document$1.prototype;
     proto.createTextNode = function createTextNode(value) {
-        return new domText(value, this)
+        return new DOMText(value, this)
     };
 
     proto.createElementNS = function createElementNS(namespace, tagName) {
         var ns = namespace === null ? null : String(namespace);
-        return new domElement(tagName, this, ns)
+        return new DOMElement(tagName, this, ns)
     };
 
     proto.createElement = function createElement(tagName) {
-        return new domElement(tagName, this)
+        return new DOMElement(tagName, this)
     };
 
     proto.createDocumentFragment = function createDocumentFragment() {
-        return new domFragment(this)
+        return new DocumentFragment(this)
     };
 
     proto.createEvent = function createEvent(family) {
-        return new event(family)
+        return new Event(family)
     };
 
     proto.createComment = function createComment(data) {
-        return new domComment(data, this)
+        return new Comment(data, this)
     };
 
     proto.getElementById = function getElementById(id) {
         return result || null
     };
 
-    proto.getElementsByClassName = domElement.prototype.getElementsByClassName;
-    proto.getElementsByTagName = domElement.prototype.getElementsByTagName;
-    proto.contains = domElement.prototype.contains;
+    proto.getElementsByClassName = DOMElement.prototype.getElementsByClassName;
+    proto.getElementsByTagName = DOMElement.prototype.getElementsByTagName;
+    proto.contains = DOMElement.prototype.contains;
+
+    proto.removeEventListener = removeEventListener;
+    proto.addEventListener = addEventListener;
+    proto.dispatchEvent = dispatchEvent;
 
-    proto.removeEventListener = removeEventListener_1;
-    proto.addEventListener = addEventListener_1;
-    proto.dispatchEvent = dispatchEvent_1;
+    var Document = document$3;
 
-    var minDocument = new document$1();
+    var minDocument = new Document();
 
     var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal :
         typeof window !== 'undefined' ? window : {};
-
+    var minDoc = minDocument;
 
     var doccy;
 
         doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
 
         if (!doccy) {
-            doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDocument;
+            doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDoc;
         }
     }
 
     var document_1 = doccy;
 
-    var applyProperties_1 = applyProperties;
+    var isObject = isObject$2;
+    var isHook$1 = isVhook;
+
+    var applyProperties_1 = applyProperties$2;
 
-    function applyProperties(node, props, previous) {
+    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 (isVhook(propValue)) {
+            } else if (isHook$1(propValue)) {
                 removeProperty(node, propName, propValue, previous);
                 if (propValue.hook) {
                     propValue.hook(node,
         if (previous) {
             var previousValue = previous[propName];
 
-            if (!isVhook(previousValue)) {
+            if (!isHook$1(previousValue)) {
                 if (propName === "attributes") {
                     for (var attrName in previousValue) {
                         node.removeAttribute(attrName);
         }
     }
 
-    var createElement_1$1 = createElement;
+    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;
 
-    function createElement(vnode, opts) {
-        var doc = opts ? opts.document || document_1 : document_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_1(vnode).a;
+        vnode = handleThunk(vnode).a;
 
-        if (isWidget_1(vnode)) {
+        if (isWidget$4(vnode)) {
             return vnode.init()
-        } else if (isVtext(vnode)) {
+        } else if (isVText$1(vnode)) {
             return doc.createTextNode(vnode.text)
-        } else if (!isVnode(vnode)) {
+        } else if (!isVNode$2(vnode)) {
             if (warn) {
                 warn("Item is not a valid virtual dom node", vnode);
             }
             doc.createElementNS(vnode.namespace, vnode.tagName);
 
         var props = vnode.properties;
-        applyProperties_1(node, props);
+        applyProperties$1(node, props);
 
         var children = vnode.children;
 
         for (var i = 0; i < children.length; i++) {
-            var childNode = createElement(children[i], opts);
+            var childNode = createElement$1(children[i], opts);
             if (childNode) {
                 node.appendChild(childNode);
             }
 
     var noChild = {};
 
-    var domIndex_1 = domIndex;
+    var domIndex_1 = domIndex$1;
 
-    function domIndex(rootNode, tree, indices, nodes) {
+    function domIndex$1(rootNode, tree, indices, nodes) {
         if (!indices || indices.length === 0) {
             return {}
         } else {
         return a > b ? 1 : -1
     }
 
-    var updateWidget_1 = updateWidget;
+    var isWidget$3 = isWidget_1;
+
+    var updateWidget_1 = updateWidget$1;
 
-    function updateWidget(a, b) {
-        if (isWidget_1(a) && isWidget_1(b)) {
+    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 false
     }
 
-    var patchOp = applyPatch$1;
+    var applyProperties = applyProperties_1;
+
+    var isWidget$2 = isWidget_1;
+    var VPatch = vpatch;
+
+    var updateWidget = updateWidget_1;
 
-    function applyPatch$1(vpatch$1, domNode, renderOptions) {
-        var type = vpatch$1.type;
-        var vNode = vpatch$1.vNode;
-        var patch = vpatch$1.patch;
+    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:
+            case VPatch.REMOVE:
                 return removeNode$1(domNode, vNode)
-            case vpatch.INSERT:
+            case VPatch.INSERT:
                 return insertNode$1(domNode, patch, renderOptions)
-            case vpatch.VTEXT:
+            case VPatch.VTEXT:
                 return stringPatch(domNode, vNode, patch, renderOptions)
-            case vpatch.WIDGET:
+            case VPatch.WIDGET:
                 return widgetPatch(domNode, vNode, patch, renderOptions)
-            case vpatch.VNODE:
+            case VPatch.VNODE:
                 return vNodePatch(domNode, vNode, patch, renderOptions)
-            case vpatch.ORDER:
+            case VPatch.ORDER:
                 reorderChildren(domNode, patch);
                 return domNode
-            case vpatch.PROPS:
-                applyProperties_1(domNode, patch, vNode.properties);
+            case VPatch.PROPS:
+                applyProperties(domNode, patch, vNode.properties);
                 return domNode
-            case vpatch.THUNK:
+            case VPatch.THUNK:
                 return replaceRoot(domNode,
                     renderOptions.patch(domNode, patch, renderOptions))
             default:
     }
 
     function widgetPatch(domNode, leftVNode, widget, renderOptions) {
-        var updating = updateWidget_1(leftVNode, widget);
+        var updating = updateWidget(leftVNode, widget);
         var newNode;
 
         if (updating) {
     }
 
     function destroyWidget(domNode, w) {
-        if (typeof w.destroy === "function" && isWidget_1(w)) {
+        if (typeof w.destroy === "function" && isWidget$2(w)) {
             w.destroy(domNode);
         }
     }
         return newRoot;
     }
 
-    var patch_1$1 = patch;
+    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(rootNode, patches, renderOptions) {
+    function patch$2(rootNode, patches, renderOptions) {
         renderOptions = renderOptions || {};
-        renderOptions.patch = renderOptions.patch && renderOptions.patch !== patch
+        renderOptions.patch = renderOptions.patch && renderOptions.patch !== patch$2
             ? renderOptions.patch
             : patchRecursive;
-        renderOptions.render = renderOptions.render || createElement_1$1;
+        renderOptions.render = renderOptions.render || render;
 
         return renderOptions.patch(rootNode, patches, renderOptions)
     }
             return rootNode
         }
 
-        var index = domIndex_1(rootNode, patches.a, indices);
+        var index = domIndex(rootNode, patches.a, indices);
         var ownerDocument = rootNode.ownerDocument;
 
-        if (!renderOptions.document && ownerDocument !== document_1) {
+        if (!renderOptions.document && ownerDocument !== document$1) {
             renderOptions.document = ownerDocument;
         }
 
 
         var newNode;
 
-        if (xIsArray(patchList)) {
+        if (isArray$1(patchList)) {
             for (var i = 0; i < patchList.length; i++) {
                 newNode = patchOp(patchList[i], domNode, renderOptions);
 
         return indices
     }
 
-    var patch_1 = patch_1$1;
+    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;
 
         for (var propName in properties) {
             if (properties.hasOwnProperty(propName)) {
                 var property = properties[propName];
-                if (isVhook(property) && property.unhook) {
+                if (isVHook(property) && property.unhook) {
                     if (!hooks) {
                         hooks = {};
                     }
 
         for (var i = 0; i < count; i++) {
             var child = children[i];
-            if (isVnode(child)) {
+            if (isVNode$1(child)) {
                 descendants += child.count || 0;
 
                 if (!hasWidgets && child.hasWidgets) {
                 if (!descendantHooks && (child.hooks || child.descendantHooks)) {
                     descendantHooks = true;
                 }
-            } else if (!hasWidgets && isWidget_1(child)) {
+            } else if (!hasWidgets && isWidget$1(child)) {
                 if (typeof child.destroy === "function") {
                     hasWidgets = true;
                 }
-            } else if (!hasThunks && isThunk_1(child)) {
+            } else if (!hasThunks && isThunk(child)) {
                 hasThunks = true;
             }
         }
         this.descendantHooks = descendantHooks;
     }
 
-    VirtualNode.prototype.version = version;
+    VirtualNode.prototype.version = version$1;
     VirtualNode.prototype.type = "VirtualNode";
 
+    var version = version$5;
+
     var vtext = VirtualText;
 
     function VirtualText(text) {
      * 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
       return self;
     })();
 
+    var split = browserSplit;
+
     var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/;
     var notClassId = /^\.|#/;
 
-    var parseTag_1 = parseTag;
+    var parseTag_1 = parseTag$1;
 
-    function parseTag(tag, props) {
+    function parseTag$1(tag, props) {
         if (!tag) {
             return 'DIV';
         }
 
         var noId = !(props.hasOwnProperty('id'));
 
-        var tagParts = browserSplit(tag, classIdSplit);
+        var tagParts = split(tag, classIdSplit);
         var tagName = null;
 
         if (notClassId.test(tagParts[1])) {
         return props.namespace ? tagName : tagName.toUpperCase();
     }
 
-    var softSetHook = SoftSetHook;
+    var softSetHook$1 = SoftSetHook;
 
     function SoftSetHook(value) {
         if (!(this instanceof SoftSetHook)) {
         window : typeof commonjsGlobal !== 'undefined' ?
         commonjsGlobal : {};
 
-    var individual = Individual;
+    var individual = Individual$1;
 
-    function Individual(key, value) {
+    function Individual$1(key, value) {
         if (key in root) {
             return root[key];
         }
         return value;
     }
 
+    var Individual = individual;
+
     var oneVersion = OneVersion;
 
     function OneVersion(moduleName, version, defaultValue) {
         var key = '__INDIVIDUAL_ONE_VERSION_' + moduleName;
         var enforceKey = key + '_ENFORCE_SINGLETON';
 
-        var versionValue = individual(enforceKey, version);
+        var versionValue = Individual(enforceKey, version);
 
         if (versionValue !== version) {
             throw new Error('Can only have one copy of ' +
                 'This means you cannot install version ' + version);
         }
 
-        return individual(key, defaultValue);
+        return Individual(key, defaultValue);
     }
 
+    var OneVersionConstraint = oneVersion;
+
     var MY_VERSION = '7';
-    oneVersion('ev-store', MY_VERSION);
+    OneVersionConstraint('ev-store', MY_VERSION);
 
     var hashKey = '__EV_STORE_KEY@' + MY_VERSION;
 
-    var evStore = EvStore;
+    var evStore = EvStore$1;
 
-    function EvStore(elem) {
+    function EvStore$1(elem) {
         var hash = elem[hashKey];
 
         if (!hash) {
         return hash;
     }
 
-    var evHook = EvHook;
+    var EvStore = evStore;
+
+    var evHook$1 = EvHook;
 
     function EvHook(value) {
         if (!(this instanceof EvHook)) {
     }
 
     EvHook.prototype.hook = function (node, propertyName) {
-        var es = evStore(node);
+        var es = EvStore(node);
         var propName = propertyName.substr(3);
 
         es[propName] = this.value;
     };
 
     EvHook.prototype.unhook = function(node, propertyName) {
-        var es = evStore(node);
+        var es = EvStore(node);
         var propName = propertyName.substr(3);
 
         es[propName] = undefined;
     };
 
-    var virtualHyperscript = h;
+    var isArray = xIsArray;
+
+    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;
+
+    var parseTag = parseTag_1;
+    var softSetHook = softSetHook$1;
+    var evHook = evHook$1;
 
-    function h(tagName, properties, children) {
+    var virtualHyperscript = h$2;
+
+    function h$2(tagName, properties, children) {
         var childNodes = [];
         var tag, props, key, namespace;
 
         }
 
         props = props || properties || {};
-        tag = parseTag_1(tagName, props);
+        tag = parseTag(tagName, props);
 
         // support keys
         if (props.hasOwnProperty('key')) {
             !namespace &&
             props.hasOwnProperty('value') &&
             props.value !== undefined &&
-            !isVhook(props.value)
+            !isHook(props.value)
         ) {
             props.value = softSetHook(props.value);
         }
         }
 
 
-        return new vnode(tag, props, childNodes, key, namespace);
+        return new VNode$1(tag, props, childNodes, key, namespace);
     }
 
     function addChild(c, childNodes, tag, props) {
         if (typeof c === 'string') {
-            childNodes.push(new vtext(c));
+            childNodes.push(new VText$1(c));
         } else if (typeof c === 'number') {
-            childNodes.push(new vtext(String(c)));
+            childNodes.push(new VText$1(String(c)));
         } else if (isChild(c)) {
             childNodes.push(c);
-        } else if (xIsArray(c)) {
+        } else if (isArray(c)) {
             for (var i = 0; i < c.length; i++) {
                 addChild(c[i], childNodes, tag, props);
             }
             if (props.hasOwnProperty(propName)) {
                 var value = props[propName];
 
-                if (isVhook(value)) {
+                if (isHook(value)) {
                     continue;
                 }
 
     }
 
     function isChild(x) {
-        return isVnode(x) || isVtext(x) || isWidget_1(x) || isThunk_1(x);
+        return isVNode(x) || isVText(x) || isWidget(x) || isVThunk(x);
     }
 
     function isChildren(x) {
-        return typeof x === 'string' || xIsArray(x) || isChild(x);
+        return typeof x === 'string' || isArray(x) || isChild(x);
     }
 
     function UnexpectedVirtualElement(data) {
         }
     }
 
-    var h_1 = virtualHyperscript;
+    var h$1 = virtualHyperscript;
+
+    var h_1 = h$1;
+
+    var createElement = createElement_1$1;
+
+    var createElement_1 = createElement;
 
-    var createElement_1 = createElement_1$1;
+    var diff = diff_1;
+    var patch = patch_1;
+    var h = h_1;
+    var create = createElement_1;
+    var VNode = vnode;
+    var VText = vtext;
 
     var virtualDom = {
-        diff: diff_1,
-        patch: patch_1,
-        h: h_1,
-        create: createElement_1,
-        VNode: vnode,
-        VText: vtext
+        diff: diff,
+        patch: patch,
+        h: h,
+        create: create,
+        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.
+         * @ignore
          */
-        on(type, handler) {
-            this._events[type] = this._events[type] || [];
-            this._events[type].push(handler);
+        fire(type, event) {
+            if (!this._listens(type)) {
+                return;
+            }
+            for (const handler of this._events[type]) {
+                handler(event);
+            }
         }
         /**
          * Unsubscribe from an event by its name.
             }
         }
         /**
-         * @ignore
+         * 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.
          */
-        fire(type, event) {
-            if (!this._listens(type)) {
-                return;
-            }
-            for (const handler of this._events[type]) {
-                handler(event);
-            }
+        on(type, handler) {
+            this._events[type] = this._events[type] || [];
+            this._events[type].push(handler);
         }
         _listens(eventType) {
             return eventType in this._events;
                 this._navigator.api.getImages$([id])
                     .subscribe((items) => {
                     for (const item of items) {
-                        if (item.node_id !== id) {
+                        const imageId = typeof id === "number" ?
+                            id.toString() : id;
+                        if (item.node_id !== imageId) {
                             continue;
                         }
                         this._navigator.api.data
             this._svgNamespace = "http://www.w3.org/2000/svg";
             this._distinctThreshold = Math.PI / 360;
             this._animationSpeed = 0.075;
-            this._unitBezier = new unitbezier(0.74, 0.67, 0.38, 0.96);
         }
         _activate() {
             const subs = this._subscriptions;
             const 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 [
             }));
             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 = [
@@ -61825,123 +58421,709 @@ void main()
                     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: exports.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.
+     */
+    exports.SliderConfigurationMode = void 0;
+    (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";
+    })(exports.SliderConfigurationMode || (exports.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: exports.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.
-     */
-    exports.SliderConfigurationMode = void 0;
-    (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";
-    })(exports.SliderConfigurationMode || (exports.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() {
@@ -63121,13 +60303,10 @@ void main()
         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();
@@ -63137,6 +60316,11 @@ void main()
             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;
@@ -63589,12 +60773,11 @@ void main()
             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;
@@ -63607,11 +60790,10 @@ void main()
             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;
@@ -63641,6 +60823,12 @@ void main()
         }
     }
 
+    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;
@@ -63766,6 +60954,24 @@ void main()
         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;
@@ -63857,6 +61063,23 @@ void main()
         return state === State.Custom || state === State.Earth;
     }
 
+    exports.PointVisualizationMode = void 0;
+    (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";
+    })(exports.PointVisualizationMode || (exports.PointVisualizationMode = {}));
+
     const NO_CLUSTER_ID = "NO_CLUSTER_ID";
     const NO_MERGE_ID = "NO_MERGE_ID";
     const NO_SEQUENCE_ID = "NO_SEQUENCE_ID";
@@ -63881,7 +61104,10 @@ void main()
                     exports.CameraVisualizationMode.Homogeneous;
             this._cameraSize = configuration.cameraSize;
             this._pointSize = configuration.pointSize;
-            this._pointsVisible = configuration.pointsVisible;
+            this._pointVisualizationMode =
+                !!configuration.pointVisualizationMode ?
+                    configuration.pointVisualizationMode :
+                    exports.PointVisualizationMode.Original;
             this._positionMode = configuration.originalPositionMode;
             this._cellsVisible = configuration.cellsVisible;
             this._hoveredId = null;
@@ -63904,14 +61130,18 @@ void main()
                     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 === exports.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);
@@ -63995,6 +61225,36 @@ void main()
             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;
@@ -64023,7 +61283,7 @@ void main()
                     clusterVisibles[clusterId] || (clusterVisibles[clusterId] = imageCV[clusterId]);
                 }
             }
-            const pointsVisible = this._pointsVisible;
+            const pointsVisible = this._pointVisualizationMode !== exports.PointVisualizationMode.Hidden;
             for (const clusterId in clusterVisibles) {
                 if (!clusterVisibles.hasOwnProperty(clusterId)) {
                     continue;
@@ -64074,17 +61334,23 @@ void main()
             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 === exports.PointVisualizationMode.Cluster ?
+                        this._assets.getColor(clusterId) : null;
+                    points.setColor(color);
+                }
             }
-            this._pointsVisible = visible;
             this._needsRender = true;
         }
         setPositionMode(mode) {
@@ -64142,10 +61408,6 @@ void main()
             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) {
@@ -64174,7 +61436,7 @@ void main()
             this._needsRender = true;
         }
         _getClusterVisible(clusterId) {
-            if (!this._pointsVisible) {
+            if (this._pointVisualizationMode === exports.PointVisualizationMode.Hidden) {
                 return false;
             }
             let visible = false;
@@ -64563,8 +61825,9 @@ void main()
             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); }));
@@ -64659,15 +61922,19 @@ void main()
                 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 : exports.PointVisualizationMode.Original :
+                    exports.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 &&
@@ -64675,15 +61942,16 @@ void main()
                     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);
             }));
@@ -64812,6 +62080,7 @@ void main()
                 originalPositionMode: exports.OriginalPositionMode.Hidden,
                 pointSize: 0.1,
                 pointsVisible: true,
+                pointVisualizationMode: exports.PointVisualizationMode.Original,
                 cellsVisible: false,
             };
         }
@@ -65168,8 +62437,10 @@ void main()
         }
     }
 
-    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) {
 
@@ -65445,7 +62716,7 @@ void main()
 
         // 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);
         }
 
@@ -65458,14 +62729,19 @@ void main()
 
     // 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
@@ -65845,7 +63121,10 @@ void main()
         }
         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) {
@@ -65933,12 +63212,12 @@ void main()
 
     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;
@@ -65965,7 +63244,7 @@ void main()
         }
 
         // 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) {
@@ -66095,7 +63374,8 @@ void main()
 
         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; }
 
@@ -67892,8 +65172,10 @@ void main()
       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);
@@ -67978,7 +65260,8 @@ void main()
             data[pos] = item;
         }
     };
-    tinyqueue.default = _default;
+
+    var Queue = tinyqueue.exports;
 
     const max = Math.max;
     const min = Math.min;
@@ -68025,7 +65308,7 @@ void main()
 
 
     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++) {
@@ -68166,7 +65449,7 @@ void main()
          * @ignore
          */
         _getPoleOfInaccessibility2d(points2d) {
-            let pole2d = polylabel_1([points2d], 3e-2);
+            let pole2d = polylabel$1([points2d], 3e-2);
             return pole2d;
         }
         _project(points2d, transform) {
@@ -68214,8 +65497,8 @@ void main()
             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]];
@@ -72369,7 +69652,7 @@ void main()
     }
     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
     //
@@ -72398,19 +69681,19 @@ void main()
     //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
 
@@ -72419,8 +69702,8 @@ void main()
     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
@@ -72428,25 +69711,25 @@ void main()
      * 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;
@@ -72499,37 +69782,37 @@ void main()
     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) */
 
 
@@ -72664,7 +69947,7 @@ void main()
       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;
       }
 
@@ -72673,7 +69956,7 @@ void main()
        */
       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) {
@@ -72748,7 +70031,7 @@ void main()
     //    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 */
@@ -72756,7 +70039,7 @@ void main()
       /* 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
@@ -72788,7 +70071,7 @@ void main()
       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()
@@ -72805,7 +70088,7 @@ void main()
 
       /* 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;
@@ -72828,7 +70111,7 @@ void main()
       }
       //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;
@@ -72837,7 +70120,7 @@ void main()
       //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;
       }
 
@@ -72866,18 +70149,18 @@ void main()
        * 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;
     };
@@ -72891,9 +70174,9 @@ void main()
       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;
@@ -73013,7 +70296,7 @@ void main()
           } 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];
@@ -73067,7 +70350,7 @@ void main()
        * 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) {
@@ -73301,7 +70584,7 @@ void main()
        * 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;
         }
@@ -73380,7 +70663,7 @@ void main()
           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;
         }
@@ -73398,7 +70681,7 @@ void main()
     /* ===========================================================================
      * Initialize the tree data structures for a new zlib stream.
      */
-    const _tr_init = (s) =>
+    const _tr_init$1 = (s) =>
     {
 
       if (!static_init_done) {
@@ -73421,7 +70704,7 @@ void main()
     /* ===========================================================================
      * 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 */
@@ -73436,7 +70719,7 @@ void main()
      * 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);
@@ -73447,7 +70730,7 @@ void main()
      * 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 */
@@ -73460,7 +70743,7 @@ void main()
       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);
         }
 
@@ -73505,9 +70788,9 @@ void main()
          * 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);
@@ -73534,7 +70817,7 @@ void main()
      * 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) */
@@ -73558,7 +70841,7 @@ void main()
         //       (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*/++;
       }
 
@@ -73592,11 +70875,11 @@ void main()
        */
     };
 
-    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,
@@ -73764,7 +71047,7 @@ void main()
     //   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,
@@ -73831,7 +71114,7 @@ void main()
     //   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;
 
 
 
@@ -73840,42 +71123,42 @@ void main()
     /* ===========================================================================*/
 
     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;
 
@@ -73903,7 +71186,7 @@ void main()
       return ((f) << 1) - ((f) > 4 ? 9 : 0);
     };
 
-    const zero$1 = (buf) => {
+    const zero = (buf) => {
       let len = buf.length; while (--len >= 0) { buf[len] = 0; }
     };
 
@@ -73944,7 +71227,7 @@ void main()
 
 
     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);
     };
@@ -74031,7 +71314,7 @@ void main()
        * 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];
 
@@ -74094,8 +71377,8 @@ void main()
 
         // 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;
@@ -74209,7 +71492,7 @@ void main()
         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];
 
@@ -74220,13 +71503,13 @@ void main()
     //#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;
             }
           }
@@ -74308,7 +71591,7 @@ void main()
     //      }
 
           fill_window(s);
-          if (s.lookahead === 0 && flush === Z_NO_FLUSH) {
+          if (s.lookahead === 0 && flush === Z_NO_FLUSH$2) {
             return BS_NEED_MORE;
           }
 
@@ -74354,7 +71637,7 @@ void main()
 
       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) {
@@ -74396,7 +71679,7 @@ void main()
          */
         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) {
@@ -74408,9 +71691,9 @@ void main()
          * 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;
           /***/
@@ -74427,24 +71710,24 @@ void main()
           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;
               /***/
@@ -74472,7 +71755,7 @@ void main()
           /* 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++;
@@ -74486,8 +71769,8 @@ void main()
           /***/
         }
       }
-      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) {
@@ -74528,7 +71811,7 @@ void main()
          */
         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 */
@@ -74538,9 +71821,9 @@ void main()
          * 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;
           /***/
@@ -74550,7 +71833,7 @@ void main()
          */
         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)*/) {
@@ -74562,26 +71845,26 @@ void main()
           /* 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
@@ -74592,14 +71875,14 @@ void main()
           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) {
@@ -74618,7 +71901,7 @@ void main()
            */
           //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) ***/
@@ -74643,12 +71926,12 @@ void main()
       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) {
@@ -74688,9 +71971,9 @@ void main()
          * 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 */
@@ -74698,11 +71981,11 @@ void main()
 
         /* 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] &&
@@ -74710,7 +71993,7 @@ void main()
                      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;
             }
@@ -74719,11 +72002,11 @@ void main()
         }
 
         /* 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;
@@ -74732,7 +72015,7 @@ void main()
           /* 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++;
@@ -74747,7 +72030,7 @@ void main()
         }
       }
       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) {
@@ -74780,7 +72063,7 @@ void main()
         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 */
@@ -74791,7 +72074,7 @@ void main()
         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) {
@@ -74804,7 +72087,7 @@ void main()
         }
       }
       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) {
@@ -74862,7 +72145,7 @@ void main()
       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:
        */
@@ -74875,7 +72158,7 @@ void main()
       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;
     };
@@ -74891,7 +72174,7 @@ void main()
       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) */
@@ -74984,24 +72267,24 @@ void main()
 
       // 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 */
@@ -75009,8 +72292,8 @@ void main()
        * 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
        */
 
@@ -75073,11 +72356,11 @@ void main()
     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;
@@ -75092,16 +72375,16 @@ void main()
         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;
@@ -75110,21 +72393,21 @@ void main()
 
     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;
       }
 
@@ -75139,10 +72422,10 @@ void main()
       }
 
 
-      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);
       }
 
 
@@ -75165,7 +72448,7 @@ void main()
       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);
@@ -75198,25 +72481,25 @@ void main()
 
     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 */
@@ -75271,7 +72554,7 @@ void main()
         }
         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) {
@@ -75434,7 +72717,7 @@ void main()
            * 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
@@ -75442,19 +72725,19 @@ void main()
          * 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));
@@ -75467,7 +72750,7 @@ void main()
             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
@@ -75478,17 +72761,17 @@ void main()
         }
         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;
@@ -75500,15 +72783,15 @@ void main()
           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) {
@@ -75533,14 +72816,14 @@ void main()
        */
       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;
@@ -75552,12 +72835,12 @@ void main()
         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;
     };
 
 
@@ -75570,14 +72853,14 @@ void main()
       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 */
@@ -75592,7 +72875,7 @@ void main()
       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;
@@ -75612,12 +72895,12 @@ void main()
       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];
 
@@ -75625,20 +72908,20 @@ void main()
           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;
     };
 
 
@@ -75647,7 +72930,7 @@ void main()
     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)';
@@ -75661,13 +72944,13 @@ void main()
     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
@@ -75749,6 +73032,10 @@ void main()
 
     // 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
@@ -75822,9 +73109,14 @@ void main()
 
     // 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.
@@ -75941,18 +73233,18 @@ void main()
 
     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;
 
     /* ===========================================================================*/
 
@@ -76042,14 +73334,14 @@ void main()
      * 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;
@@ -76070,7 +73362,7 @@ void main()
       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,
@@ -76079,12 +73371,12 @@ void main()
         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) {
@@ -76093,15 +73385,15 @@ void main()
         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]);
         }
 
@@ -76131,7 +73423,7 @@ void main()
      * 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;
@@ -76139,13 +73431,13 @@ void main()
       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;
@@ -76162,23 +73454,23 @@ void main()
         }
 
         // 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
@@ -76208,7 +73500,7 @@ void main()
      * 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);
     };
 
@@ -76222,9 +73514,9 @@ void main()
      * 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 = [];
@@ -76252,8 +73544,8 @@ void main()
     // 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
@@ -76415,7 +73707,7 @@ void main()
     //#ifdef INFLATE_STRICT
                 if (dist > dmax) {
                   strm.msg = 'invalid distance too far back';
-                  state.mode = BAD;
+                  state.mode = BAD$1;
                   break top;
                 }
     //#endif
@@ -76428,7 +73720,7 @@ void main()
                   if (op > whave) {
                     if (state.sane) {
                       strm.msg = 'invalid distance too far back';
-                      state.mode = BAD;
+                      state.mode = BAD$1;
                       break top;
                     }
 
@@ -76533,7 +73825,7 @@ void main()
               }
               else {
                 strm.msg = 'invalid distance code';
-                state.mode = BAD;
+                state.mode = BAD$1;
                 break top;
               }
 
@@ -76546,12 +73838,12 @@ void main()
           }
           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;
           }
 
@@ -76595,13 +73887,13 @@ void main()
     // 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,
@@ -76733,7 +74025,7 @@ void main()
           return -1;
         }        /* over-subscribed */
       }
-      if (left > 0 && (type === CODES || max !== 1)) {
+      if (left > 0 && (type === CODES$1 || max !== 1)) {
         return -1;                      /* incomplete set */
       }
 
@@ -76784,11 +74076,11 @@ void main()
       /* 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;
@@ -76813,8 +74105,8 @@ void main()
       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;
       }
 
@@ -76885,8 +74177,8 @@ void main()
 
           /* 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;
           }
 
@@ -76942,18 +74234,18 @@ void main()
 
 
 
-    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 ====================================================================*/
@@ -76971,7 +74263,7 @@ void main()
     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 */
@@ -76989,7 +74281,7 @@ void main()
     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() */
 
@@ -76997,13 +74289,13 @@ void main()
 
 
 
-    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) => {
@@ -77091,13 +74383,13 @@ void main()
       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;
     };
 
 
@@ -77159,7 +74451,7 @@ void main()
       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;
@@ -77201,13 +74493,13 @@ void main()
         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;
@@ -77278,7 +74570,7 @@ void main()
     };
 
 
-    const inflate = (strm, flush) => {
+    const inflate$2 = (strm, flush) => {
 
       let state;
       let input, output;          // input/output buffers
@@ -77312,7 +74604,7 @@ void main()
       }
 
       state = strm.state;
-      if (state.mode === TYPE$1) { state.mode = TYPEDO; }    /* skip check */
+      if (state.mode === TYPE) { state.mode = TYPEDO; }    /* skip check */
 
 
       //--- LOAD() ---
@@ -77328,7 +74620,7 @@ void main()
 
       _in = have;
       _out = left;
-      ret = Z_OK$2;
+      ret = Z_OK$1;
 
       inf_leave: // goto emulation
       for (;;) {
@@ -77368,12 +74660,12 @@ void main()
             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) ---//
@@ -77386,7 +74678,7 @@ void main()
             }
             else if (len > state.wbits) {
               strm.msg = 'invalid window size';
-              state.mode = BAD$1;
+              state.mode = BAD;
               break;
             }
 
@@ -77397,7 +74689,7 @@ void main()
 
             //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;
@@ -77413,14 +74705,14 @@ void main()
             }
             //===//
             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) {
@@ -77623,7 +74915,7 @@ void main()
               //===//
               if (hold !== (state.check & 0xffff)) {
                 strm.msg = 'header crc mismatch';
-                state.mode = BAD$1;
+                state.mode = BAD;
                 break;
               }
               //=== INITBITS();
@@ -77636,7 +74928,7 @@ void main()
               state.head.done = true;
             }
             strm.adler = state.check = 0;
-            state.mode = TYPE$1;
+            state.mode = TYPE;
             break;
           case DICTID:
             //=== NEEDBITS(32); */
@@ -77664,13 +74956,13 @@ void main()
               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) {
@@ -77721,7 +75013,7 @@ void main()
                 break;
               case 3:
                 strm.msg = 'invalid block type';
-                state.mode = BAD$1;
+                state.mode = BAD;
             }
             //--- DROPBITS(2) ---//
             hold >>>= 2;
@@ -77743,7 +75035,7 @@ void main()
             //===//
             if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {
               strm.msg = 'invalid stored block lengths';
-              state.mode = BAD$1;
+              state.mode = BAD;
               break;
             }
             state.length = hold & 0xffff;
@@ -77776,7 +75068,7 @@ void main()
               break;
             }
             //Tracev((stderr, "inflate:       stored end\n"));
-            state.mode = TYPE$1;
+            state.mode = TYPE;
             break;
           case TABLE:
             //=== NEEDBITS(14); */
@@ -77805,7 +75097,7 @@ void main()
     //#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
@@ -77840,12 +75132,12 @@ void main()
             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"));
@@ -77892,7 +75184,7 @@ void main()
                   //---//
                   if (state.have === 0) {
                     strm.msg = 'invalid bit length repeat';
-                    state.mode = BAD$1;
+                    state.mode = BAD;
                     break;
                   }
                   len = state.lens[state.have - 1];
@@ -77946,7 +75238,7 @@ void main()
                 }
                 if (state.have + copy > state.nlen + state.ndist) {
                   strm.msg = 'invalid bit length repeat';
-                  state.mode = BAD$1;
+                  state.mode = BAD;
                   break;
                 }
                 while (copy--) {
@@ -77956,12 +75248,12 @@ void main()
             }
 
             /* 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;
             }
 
@@ -77971,7 +75263,7 @@ void main()
             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;
@@ -77979,7 +75271,7 @@ void main()
 
             if (ret) {
               strm.msg = 'invalid literal/lengths set';
-              state.mode = BAD$1;
+              state.mode = BAD;
               break;
             }
 
@@ -77988,7 +75280,7 @@ void main()
             // 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;
@@ -77996,7 +75288,7 @@ void main()
 
             if (ret) {
               strm.msg = 'invalid distances set';
-              state.mode = BAD$1;
+              state.mode = BAD;
               break;
             }
             //Tracev((stderr, 'inflate:       codes ok\n'));
@@ -78028,7 +75320,7 @@ void main()
               bits = state.bits;
               //---
 
-              if (state.mode === TYPE$1) {
+              if (state.mode === TYPE) {
                 state.back = -1;
               }
               break;
@@ -78089,12 +75381,12 @@ void main()
             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;
@@ -78169,7 +75461,7 @@ void main()
             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;
@@ -78197,7 +75489,7 @@ void main()
     //#ifdef INFLATE_STRICT
             if (state.offset > state.dmax) {
               strm.msg = 'invalid distance too far back';
-              state.mode = BAD$1;
+              state.mode = BAD;
               break;
             }
     //#endif
@@ -78212,7 +75504,7 @@ void main()
               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,
@@ -78284,7 +75576,7 @@ void main()
               // 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();
@@ -78307,7 +75599,7 @@ void main()
               //===//
               if (hold !== (state.total & 0xffffffff)) {
                 strm.msg = 'incorrect length check';
-                state.mode = BAD$1;
+                state.mode = BAD;
                 break;
               }
               //=== INITBITS();
@@ -78319,13 +75611,13 @@ void main()
             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:
@@ -78351,8 +75643,8 @@ void main()
       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;
@@ -78365,10 +75657,10 @@ void main()
           (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;
     };
@@ -78385,7 +75677,7 @@ void main()
         state.window = null;
       }
       strm.state = null;
-      return Z_OK$2;
+      return Z_OK$1;
     };
 
 
@@ -78399,7 +75691,7 @@ void main()
       /* save header structure */
       state.head = head;
       head.done = false;
-      return Z_OK$2;
+      return Z_OK$1;
     };
 
 
@@ -78432,11 +75724,11 @@ void main()
       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;
     };
 
 
@@ -78445,7 +75737,7 @@ void main()
     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;
@@ -78461,13 +75753,13 @@ void main()
     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,
@@ -78531,15 +75823,15 @@ void main()
 
     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;
 
     /* ===========================================================================*/
 
@@ -78621,7 +75913,7 @@ void main()
      * console.log(inflate.result);
      * ```
      **/
-    function Inflate(options) {
+    function Inflate$1(options) {
       this.options = common.assign({
         chunkSize: 1024 * 64,
         windowBits: 15,
@@ -78661,30 +75953,30 @@ void main()
       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]);
           }
         }
@@ -78716,7 +76008,7 @@ void main()
      * 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;
@@ -78725,10 +76017,10 @@ void main()
       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;
@@ -78744,34 +76036,34 @@ void main()
           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;
@@ -78782,7 +76074,7 @@ void main()
         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') {
 
@@ -78805,11 +76097,11 @@ void main()
         }
 
         // 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;
@@ -78830,7 +76122,7 @@ void main()
      * 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);
     };
 
@@ -78844,9 +76136,9 @@ void main()
      * 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 {
@@ -78893,13 +76185,13 @@ void main()
      *
      * 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);
 
@@ -78918,7 +76210,7 @@ void main()
      * 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);
@@ -78935,25 +76227,28 @@ void main()
      **/
 
 
-    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;
@@ -78986,7 +76281,7 @@ void main()
       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;
@@ -79038,14 +76333,9 @@ void main()
       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);
@@ -79695,7 +76985,7 @@ void main()
      * @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);
     }
     /**
@@ -79776,131 +77066,6 @@ void main()
         }
     }
 
-    /**
-     * @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
      *
@@ -79923,20 +77088,17 @@ void main()
         /**
          * 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;
@@ -80067,6 +77229,135 @@ void main()
         }
     }
 
+    /**
+     * @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.
@@ -80084,7 +77375,7 @@ void main()
      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
@@ -81274,9 +78565,9 @@ void main()
 
         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
@@ -81722,7 +79013,7 @@ void main()
     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;
@@ -81770,7 +79061,7 @@ void main()
     = 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)) {
@@ -81830,7 +79121,7 @@ void main()
     };
 
     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];
@@ -81869,7 +79160,7 @@ void main()
     };
 
     })(module.exports );
-    });
+    }(s2geometry));
 
     /**
      * @class S2GeometryProvider
@@ -81900,7 +79191,7 @@ void main()
         }
         /** @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);
@@ -81926,12 +79217,12 @@ void main()
                     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) => {
@@ -81943,13 +79234,13 @@ void main()
             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);
         }
     }
 
@@ -82012,7 +79303,7 @@ void main()
             source.thumb = (_a = source.thumb) !== null && _a !== void 0 ? _a : { id: null, url: thumbUrl };
             source.cluster = (_b = source.sfm_cluster) !== null && _b !== void 0 ? _b : { id: null, url: null };
             source.creator = { id: null, username: null };
-            source.owner = (_c = source.owner) !== null && _c !== void 0 ? _c : { id: null };
+            source.owner = (_c = source.organization) !== null && _c !== void 0 ? _c : { id: null };
             source.mesh = (_d = source.mesh) !== null && _d !== void 0 ? _d : { id: null, url: null };
             return source;
         }
@@ -82048,6 +79339,7 @@ void main()
                 'height',
                 'merge_cc',
                 'mesh',
+                'organization',
                 'quality_score',
                 'sfm_cluster',
                 'thumb_1024_url',
@@ -82205,3667 +79497,4611 @@ void main()
                 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 !== exports.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 exports.Alignment.Bottom:
+                    return "bottom";
+                case exports.Alignment.BottomLeft:
+                    return "bottom-left";
+                case exports.Alignment.BottomRight:
+                    return "bottom-right";
+                case exports.Alignment.Center:
+                    return "center";
+                case exports.Alignment.Left:
+                    return "left";
+                case exports.Alignment.Right:
+                    return "right";
+                case exports.Alignment.Top:
+                    return "top";
+                case exports.Alignment.TopLeft:
+                    return "top-left";
+                case exports.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();
-        }
+    exports.CameraControls = void 0;
+    (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";
+    })(exports.CameraControls || (exports.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.
+     */
+    exports.RenderMode = void 0;
+    (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";
+    })(exports.RenderMode || (exports.RenderMode = {}));
+
+    exports.RenderPass = void 0;
+    (function (RenderPass) {
+        /**
+         * Occurs after the background render pass.
+         */
+        RenderPass[RenderPass["Opaque"] = 0] = "Opaque";
+    })(exports.RenderPass || (exports.RenderPass = {}));
+
+    /**
+     * Enumeration for transition mode
+     * @enum {number}
+     * @readonly
+     * @description Modes for specifying how transitions
+     * between images are performed.
+     */
+    exports.TransitionMode = void 0;
+    (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";
+    })(exports.TransitionMode || (exports.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 !== exports.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: exports.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 === exports.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.Background) {
+                        backgroundRenders.push(render.render);
+                    }
+                    else if (render.pass === RenderPass.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 exports.Alignment.Bottom:
-                    return "bottom";
-                case exports.Alignment.BottomLeft:
-                    return "bottom-left";
-                case exports.Alignment.BottomRight:
-                    return "bottom-right";
-                case exports.Alignment.Center:
-                    return "center";
-                case exports.Alignment.Left:
-                    return "left";
-                case exports.Alignment.Right:
-                    return "right";
-                case exports.Alignment.Top:
-                    return "top";
-                case exports.Alignment.TopLeft:
-                    return "top-left";
-                case exports.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.
      */
-    exports.CameraControls = void 0;
-    (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";
-    })(exports.CameraControls || (exports.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.
-     */
-    exports.RenderMode = void 0;
-    (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";
-    })(exports.RenderMode || (exports.RenderMode = {}));
-
-    exports.RenderPass = void 0;
-    (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";
-    })(exports.RenderPass || (exports.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 === exports.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 : exports.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: exports.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 === exports.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.Background) {
-                        backgroundRenders.push(render.render);
-                    }
-                    else if (render.pass === RenderPass.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 === exports.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 = exports.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 exports.Alignment.Bottom:
+                case exports.Alignment.Center:
+                case exports.Alignment.Top:
+                    left -= definition.width / 2;
+                    break;
+                case exports.Alignment.BottomLeft:
+                case exports.Alignment.Left:
+                case exports.Alignment.TopLeft:
+                    left -= definition.width;
+                    break;
+                case exports.Alignment.BottomRight:
+                case exports.Alignment.Right:
+                case exports.Alignment.TopRight:
+            }
+            switch (float) {
+                case exports.Alignment.Center:
+                case exports.Alignment.Left:
+                case exports.Alignment.Right:
+                    top -= definition.height / 2;
+                    break;
+                case exports.Alignment.Top:
+                case exports.Alignment.TopLeft:
+                case exports.Alignment.TopRight:
+                    top -= definition.height;
+                    break;
+                case exports.Alignment.Bottom:
+                case exports.Alignment.BottomLeft:
+                case exports.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 : exports.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 = exports.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 exports.Alignment.Bottom:
-                case exports.Alignment.Center:
-                case exports.Alignment.Top:
-                    left -= definition.width / 2;
-                    break;
-                case exports.Alignment.BottomLeft:
-                case exports.Alignment.Left:
-                case exports.Alignment.TopLeft:
-                    left -= definition.width;
-                    break;
-                case exports.Alignment.BottomRight:
-                case exports.Alignment.Right:
-                case exports.Alignment.TopRight:
-            }
-            switch (float) {
-                case exports.Alignment.Center:
-                case exports.Alignment.Left:
-                case exports.Alignment.Right:
-                    top -= definition.height / 2;
-                    break;
-                case exports.Alignment.Top:
-                case exports.Alignment.TopLeft:
-                case exports.Alignment.TopRight:
-                    top -= definition.height;
-                    break;
-                case exports.Alignment.Bottom:
-                case exports.Alignment.BottomLeft:
-                case exports.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 === exports.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 {
@@ -85888,7 +84124,7 @@ void main()
         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);
@@ -85898,7 +84134,7 @@ void main()
             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);
@@ -85925,6 +84161,109 @@ void main()
         }
     }
 
+    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);
@@ -85946,7 +84285,7 @@ void main()
         moveTo(position) {
             this._alpha = Math.max(0, Math.min(1, position));
         }
-        update(fps) {
+        update() {
             this._camera.lerpCameras(this._previousCamera, this._currentCamera, this.alpha);
         }
         _getAlpha() {
@@ -86052,6 +84391,9 @@ void main()
         get alpha() {
             return this._state.alpha;
         }
+        get stateTransitionAlpha() {
+            return this._state.stateTransitionAlpha;
+        }
         get camera() {
             return this._state.camera;
         }
@@ -86112,8 +84454,8 @@ void main()
         setZoom(zoom) {
             this._state.setZoom(zoom);
         }
-        update(fps) {
-            this._state.update(fps);
+        update(delta) {
+            this._state.update(delta);
         }
         append(images) {
             this._state.append(images);
@@ -86198,11 +84540,11 @@ void main()
     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;
             });
@@ -86212,21 +84554,14 @@ void main()
             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) => {
@@ -86487,6 +84822,7 @@ void main()
             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));
@@ -86494,6 +84830,7 @@ void main()
             }
         }
         stop() {
+            this._clock.stop();
             if (this._frameId != null) {
                 this._frameGenerator.cancelAnimationFrame(this._frameId);
                 this._frameId = null;
@@ -86532,12 +84869,7 @@ void main()
                 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({
@@ -86883,7 +85215,7 @@ void main()
                 .subscribe((image) => {
                 const type = "image";
                 const event = {
-                    image: image,
+                    image,
                     target: this._viewer,
                     type,
                 };
@@ -86913,6 +85245,16 @@ void main()
                 };
                 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())
@@ -87089,6 +85431,7 @@ void main()
             if (this._controls) {
                 throw new MapillaryError('Custom camera controls already attached');
             }
+            this._controls = controls;
             const attach$ = new Subject();
             const active$ = attach$
                 .pipe(switchMap(() => {
@@ -87160,25 +85503,33 @@ void main()
                 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())
@@ -87212,7 +85563,7 @@ void main()
      * @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.
      *
@@ -87223,38 +85574,43 @@ void main()
         /**
          * 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
@@ -87279,6 +85635,19 @@ void main()
             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.
          *
@@ -87286,9 +85655,9 @@ void main()
          * 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.
          */
@@ -87409,7 +85778,7 @@ void main()
          * control instance.
          */
         detachCustomCameraControls() {
-            this._customCameraControls.detach(this);
+            return this._customCameraControls.detach(this);
         }
         fire(type, event) {
             super.fire(type, event);
@@ -87448,7 +85817,7 @@ void main()
          *
          * @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.
          *
@@ -87635,6 +86004,25 @@ void main()
                 });
             });
         }
+        /**
+         * 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.
          *
@@ -87656,11 +86044,22 @@ void main()
                 });
             });
         }
+        /**
+         * 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.
          */
@@ -87699,14 +86098,14 @@ void main()
             });
         }
         /**
-         * 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
@@ -87841,7 +86240,7 @@ void main()
         /**
          * 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);
@@ -87873,7 +86272,7 @@ void main()
          *
          * @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
@@ -87986,7 +86385,8 @@ void main()
          * 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
@@ -88170,13 +86570,12 @@ void main()
      *
      * 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);
@@ -88204,6 +86603,7 @@ void main()
     exports.DataProviderBase = DataProviderBase;
     exports.DirectionComponent = DirectionComponent;
     exports.DragPanHandler = DragPanHandler;
+    exports.EventEmitter = EventEmitter;
     exports.ExtremePointTag = ExtremePointTag;
     exports.Geometry = Geometry;
     exports.GeometryProviderBase = GeometryProviderBase;
@@ -88241,8 +86641,12 @@ void main()
     exports.Viewer = Viewer;
     exports.ZoomComponent = ZoomComponent;
     exports.decompress = decompress;
+    exports.ecefToEnu = ecefToEnu;
+    exports.ecefToGeodetic = ecefToGeodetic;
+    exports.enuToEcef = enuToEcef;
     exports.enuToGeodetic = enuToGeodetic;
     exports.fetchArrayBuffer = fetchArrayBuffer;
+    exports.geodeticToEcef = geodeticToEcef;
     exports.geodeticToEnu = geodeticToEnu;
     exports.isFallbackSupported = isFallbackSupported;
     exports.isSupported = isSupported;
@@ -88250,5 +86654,5 @@ void main()
 
     Object.defineProperty(exports, '__esModule', { value: true });
 
-})));
+}));
 //# sourceMappingURL=mapillary.unminified.js.map