--- /dev/null
+/*! *****************************************************************************
+Copyright (c) Microsoft Corporation.
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
+***************************************************************************** */
+/* global Reflect, Promise */
+
+var extendStatics = function(d, b) {
+ extendStatics = Object.setPrototypeOf ||
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
+ function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
+ return extendStatics(d, b);
+};
+
+function __extends(d, b) {
+ extendStatics(d, b);
+ function __() { this.constructor = d; }
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
+}
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+function isFunction(x) {
+ return typeof x === 'function';
+}
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+var _enable_super_gross_mode_that_will_cause_bad_things = false;
+var config = {
+ Promise: undefined,
+ set useDeprecatedSynchronousErrorHandling(value) {
+ if (value) {
+ var error = /*@__PURE__*/ new Error();
+ /*@__PURE__*/ console.warn('DEPRECATED! RxJS was set to use deprecated synchronous error handling behavior by code at: \n' + error.stack);
+ }
+ _enable_super_gross_mode_that_will_cause_bad_things = value;
+ },
+ get useDeprecatedSynchronousErrorHandling() {
+ return _enable_super_gross_mode_that_will_cause_bad_things;
+ },
+};
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+function hostReportError(err) {
+ setTimeout(function () { throw err; }, 0);
+}
+
+/** PURE_IMPORTS_START _config,_util_hostReportError PURE_IMPORTS_END */
+var empty$1 = {
+ closed: true,
+ next: function (value) { },
+ error: function (err) {
+ if (config.useDeprecatedSynchronousErrorHandling) {
+ throw err;
+ }
+ else {
+ hostReportError(err);
+ }
+ },
+ complete: function () { }
+};
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+var isArray$1 = /*@__PURE__*/ (function () { return Array.isArray || (function (x) { return x && typeof x.length === 'number'; }); })();
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+function isObject$1(x) {
+ return x !== null && typeof x === 'object';
+}
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+var UnsubscriptionErrorImpl = /*@__PURE__*/ (function () {
+ function UnsubscriptionErrorImpl(errors) {
+ Error.call(this);
+ this.message = errors ?
+ errors.length + " errors occurred during unsubscription:\n" + errors.map(function (err, i) { return i + 1 + ") " + err.toString(); }).join('\n ') : '';
+ this.name = 'UnsubscriptionError';
+ this.errors = errors;
+ return this;
+ }
+ UnsubscriptionErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype);
+ return UnsubscriptionErrorImpl;
+})();
+var UnsubscriptionError = UnsubscriptionErrorImpl;
+
+/** PURE_IMPORTS_START _util_isArray,_util_isObject,_util_isFunction,_util_UnsubscriptionError PURE_IMPORTS_END */
+var Subscription = /*@__PURE__*/ (function () {
+ function Subscription(unsubscribe) {
+ this.closed = false;
+ this._parentOrParents = null;
+ this._subscriptions = null;
+ if (unsubscribe) {
+ this._ctorUnsubscribe = true;
+ this._unsubscribe = unsubscribe;
+ }
+ }
+ Subscription.prototype.unsubscribe = function () {
+ var errors;
+ if (this.closed) {
+ return;
+ }
+ var _a = this, _parentOrParents = _a._parentOrParents, _ctorUnsubscribe = _a._ctorUnsubscribe, _unsubscribe = _a._unsubscribe, _subscriptions = _a._subscriptions;
+ this.closed = true;
+ this._parentOrParents = null;
+ this._subscriptions = null;
+ if (_parentOrParents instanceof Subscription) {
+ _parentOrParents.remove(this);
+ }
+ else if (_parentOrParents !== null) {
+ for (var index = 0; index < _parentOrParents.length; ++index) {
+ var parent_1 = _parentOrParents[index];
+ parent_1.remove(this);
+ }
+ }
+ if (isFunction(_unsubscribe)) {
+ if (_ctorUnsubscribe) {
+ this._unsubscribe = undefined;
+ }
+ try {
+ _unsubscribe.call(this);
+ }
+ catch (e) {
+ errors = e instanceof UnsubscriptionError ? flattenUnsubscriptionErrors(e.errors) : [e];
+ }
+ }
+ if (isArray$1(_subscriptions)) {
+ var index = -1;
+ var len = _subscriptions.length;
+ while (++index < len) {
+ var sub = _subscriptions[index];
+ if (isObject$1(sub)) {
+ try {
+ sub.unsubscribe();
+ }
+ catch (e) {
+ errors = errors || [];
+ if (e instanceof UnsubscriptionError) {
+ errors = errors.concat(flattenUnsubscriptionErrors(e.errors));
+ }
+ else {
+ errors.push(e);
+ }
+ }
+ }
+ }
+ }
+ if (errors) {
+ throw new UnsubscriptionError(errors);
+ }
+ };
+ Subscription.prototype.add = function (teardown) {
+ var subscription = teardown;
+ if (!teardown) {
+ return Subscription.EMPTY;
+ }
+ switch (typeof teardown) {
+ case 'function':
+ subscription = new Subscription(teardown);
+ case 'object':
+ if (subscription === this || subscription.closed || typeof subscription.unsubscribe !== 'function') {
+ return subscription;
+ }
+ else if (this.closed) {
+ subscription.unsubscribe();
+ return subscription;
+ }
+ else if (!(subscription instanceof Subscription)) {
+ var tmp = subscription;
+ subscription = new Subscription();
+ subscription._subscriptions = [tmp];
+ }
+ break;
+ default: {
+ throw new Error('unrecognized teardown ' + teardown + ' added to Subscription.');
+ }
+ }
+ var _parentOrParents = subscription._parentOrParents;
+ if (_parentOrParents === null) {
+ subscription._parentOrParents = this;
+ }
+ else if (_parentOrParents instanceof Subscription) {
+ if (_parentOrParents === this) {
+ return subscription;
+ }
+ subscription._parentOrParents = [_parentOrParents, this];
+ }
+ else if (_parentOrParents.indexOf(this) === -1) {
+ _parentOrParents.push(this);
+ }
+ else {
+ return subscription;
+ }
+ var subscriptions = this._subscriptions;
+ if (subscriptions === null) {
+ this._subscriptions = [subscription];
+ }
+ else {
+ subscriptions.push(subscription);
+ }
+ return subscription;
+ };
+ Subscription.prototype.remove = function (subscription) {
+ var subscriptions = this._subscriptions;
+ if (subscriptions) {
+ var subscriptionIndex = subscriptions.indexOf(subscription);
+ if (subscriptionIndex !== -1) {
+ subscriptions.splice(subscriptionIndex, 1);
+ }
+ }
+ };
+ Subscription.EMPTY = (function (empty) {
+ empty.closed = true;
+ return empty;
+ }(new Subscription()));
+ return Subscription;
+}());
+function flattenUnsubscriptionErrors(errors) {
+ return errors.reduce(function (errs, err) { return errs.concat((err instanceof UnsubscriptionError) ? err.errors : err); }, []);
+}
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+var rxSubscriber = /*@__PURE__*/ (function () {
+ return typeof Symbol === 'function'
+ ? /*@__PURE__*/ Symbol('rxSubscriber')
+ : '@@rxSubscriber_' + /*@__PURE__*/ Math.random();
+})();
+
+/** PURE_IMPORTS_START tslib,_util_isFunction,_Observer,_Subscription,_internal_symbol_rxSubscriber,_config,_util_hostReportError PURE_IMPORTS_END */
+var Subscriber = /*@__PURE__*/ (function (_super) {
+ __extends(Subscriber, _super);
+ function Subscriber(destinationOrNext, error, complete) {
+ var _this = _super.call(this) || this;
+ _this.syncErrorValue = null;
+ _this.syncErrorThrown = false;
+ _this.syncErrorThrowable = false;
+ _this.isStopped = false;
+ switch (arguments.length) {
+ case 0:
+ _this.destination = empty$1;
+ break;
+ case 1:
+ if (!destinationOrNext) {
+ _this.destination = empty$1;
+ break;
+ }
+ if (typeof destinationOrNext === 'object') {
+ if (destinationOrNext instanceof Subscriber) {
+ _this.syncErrorThrowable = destinationOrNext.syncErrorThrowable;
+ _this.destination = destinationOrNext;
+ destinationOrNext.add(_this);
+ }
+ else {
+ _this.syncErrorThrowable = true;
+ _this.destination = new SafeSubscriber(_this, destinationOrNext);
+ }
+ break;
+ }
+ default:
+ _this.syncErrorThrowable = true;
+ _this.destination = new SafeSubscriber(_this, destinationOrNext, error, complete);
+ break;
+ }
+ return _this;
+ }
+ Subscriber.prototype[rxSubscriber] = function () { return this; };
+ Subscriber.create = function (next, error, complete) {
+ var subscriber = new Subscriber(next, error, complete);
+ subscriber.syncErrorThrowable = false;
+ return subscriber;
+ };
+ Subscriber.prototype.next = function (value) {
+ if (!this.isStopped) {
+ this._next(value);
+ }
+ };
+ Subscriber.prototype.error = function (err) {
+ if (!this.isStopped) {
+ this.isStopped = true;
+ this._error(err);
+ }
+ };
+ Subscriber.prototype.complete = function () {
+ if (!this.isStopped) {
+ this.isStopped = true;
+ this._complete();
+ }
+ };
+ Subscriber.prototype.unsubscribe = function () {
+ if (this.closed) {
+ return;
+ }
+ this.isStopped = true;
+ _super.prototype.unsubscribe.call(this);
+ };
+ Subscriber.prototype._next = function (value) {
+ this.destination.next(value);
+ };
+ Subscriber.prototype._error = function (err) {
+ this.destination.error(err);
+ this.unsubscribe();
+ };
+ Subscriber.prototype._complete = function () {
+ this.destination.complete();
+ this.unsubscribe();
+ };
+ Subscriber.prototype._unsubscribeAndRecycle = function () {
+ var _parentOrParents = this._parentOrParents;
+ this._parentOrParents = null;
+ this.unsubscribe();
+ this.closed = false;
+ this.isStopped = false;
+ this._parentOrParents = _parentOrParents;
+ return this;
+ };
+ return Subscriber;
+}(Subscription));
+var SafeSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(SafeSubscriber, _super);
+ function SafeSubscriber(_parentSubscriber, observerOrNext, error, complete) {
+ var _this = _super.call(this) || this;
+ _this._parentSubscriber = _parentSubscriber;
+ var next;
+ var context = _this;
+ if (isFunction(observerOrNext)) {
+ next = observerOrNext;
+ }
+ else if (observerOrNext) {
+ next = observerOrNext.next;
+ error = observerOrNext.error;
+ complete = observerOrNext.complete;
+ if (observerOrNext !== empty$1) {
+ context = Object.create(observerOrNext);
+ if (isFunction(context.unsubscribe)) {
+ _this.add(context.unsubscribe.bind(context));
+ }
+ context.unsubscribe = _this.unsubscribe.bind(_this);
+ }
+ }
+ _this._context = context;
+ _this._next = next;
+ _this._error = error;
+ _this._complete = complete;
+ return _this;
+ }
+ SafeSubscriber.prototype.next = function (value) {
+ if (!this.isStopped && this._next) {
+ var _parentSubscriber = this._parentSubscriber;
+ if (!config.useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) {
+ this.__tryOrUnsub(this._next, value);
+ }
+ else if (this.__tryOrSetError(_parentSubscriber, this._next, value)) {
+ this.unsubscribe();
+ }
+ }
+ };
+ SafeSubscriber.prototype.error = function (err) {
+ if (!this.isStopped) {
+ var _parentSubscriber = this._parentSubscriber;
+ var useDeprecatedSynchronousErrorHandling = config.useDeprecatedSynchronousErrorHandling;
+ if (this._error) {
+ if (!useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) {
+ this.__tryOrUnsub(this._error, err);
+ this.unsubscribe();
+ }
+ else {
+ this.__tryOrSetError(_parentSubscriber, this._error, err);
+ this.unsubscribe();
+ }
+ }
+ else if (!_parentSubscriber.syncErrorThrowable) {
+ this.unsubscribe();
+ if (useDeprecatedSynchronousErrorHandling) {
+ throw err;
+ }
+ hostReportError(err);
+ }
+ else {
+ if (useDeprecatedSynchronousErrorHandling) {
+ _parentSubscriber.syncErrorValue = err;
+ _parentSubscriber.syncErrorThrown = true;
+ }
+ else {
+ hostReportError(err);
+ }
+ this.unsubscribe();
+ }
+ }
+ };
+ SafeSubscriber.prototype.complete = function () {
+ var _this = this;
+ if (!this.isStopped) {
+ var _parentSubscriber = this._parentSubscriber;
+ if (this._complete) {
+ var wrappedComplete = function () { return _this._complete.call(_this._context); };
+ if (!config.useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) {
+ this.__tryOrUnsub(wrappedComplete);
+ this.unsubscribe();
+ }
+ else {
+ this.__tryOrSetError(_parentSubscriber, wrappedComplete);
+ this.unsubscribe();
+ }
+ }
+ else {
+ this.unsubscribe();
+ }
+ }
+ };
+ SafeSubscriber.prototype.__tryOrUnsub = function (fn, value) {
+ try {
+ fn.call(this._context, value);
+ }
+ catch (err) {
+ this.unsubscribe();
+ if (config.useDeprecatedSynchronousErrorHandling) {
+ throw err;
+ }
+ else {
+ hostReportError(err);
+ }
+ }
+ };
+ SafeSubscriber.prototype.__tryOrSetError = function (parent, fn, value) {
+ if (!config.useDeprecatedSynchronousErrorHandling) {
+ throw new Error('bad call');
+ }
+ try {
+ fn.call(this._context, value);
+ }
+ catch (err) {
+ if (config.useDeprecatedSynchronousErrorHandling) {
+ parent.syncErrorValue = err;
+ parent.syncErrorThrown = true;
+ return true;
+ }
+ else {
+ hostReportError(err);
+ return true;
+ }
+ }
+ return false;
+ };
+ SafeSubscriber.prototype._unsubscribe = function () {
+ var _parentSubscriber = this._parentSubscriber;
+ this._context = null;
+ this._parentSubscriber = null;
+ _parentSubscriber.unsubscribe();
+ };
+ return SafeSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _Subscriber PURE_IMPORTS_END */
+function canReportError(observer) {
+ while (observer) {
+ var _a = observer, closed_1 = _a.closed, destination = _a.destination, isStopped = _a.isStopped;
+ if (closed_1 || isStopped) {
+ return false;
+ }
+ else if (destination && destination instanceof Subscriber) {
+ observer = destination;
+ }
+ else {
+ observer = null;
+ }
+ }
+ return true;
+}
+
+/** PURE_IMPORTS_START _Subscriber,_symbol_rxSubscriber,_Observer PURE_IMPORTS_END */
+function toSubscriber(nextOrObserver, error, complete) {
+ if (nextOrObserver) {
+ if (nextOrObserver instanceof Subscriber) {
+ return nextOrObserver;
+ }
+ if (nextOrObserver[rxSubscriber]) {
+ return nextOrObserver[rxSubscriber]();
+ }
+ }
+ if (!nextOrObserver && !error && !complete) {
+ return new Subscriber(empty$1);
+ }
+ return new Subscriber(nextOrObserver, error, complete);
+}
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+var observable = /*@__PURE__*/ (function () { return typeof Symbol === 'function' && Symbol.observable || '@@observable'; })();
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+function identity(x) {
+ return x;
+}
+
+/** PURE_IMPORTS_START _identity PURE_IMPORTS_END */
+function pipe() {
+ var fns = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ fns[_i] = arguments[_i];
+ }
+ return pipeFromArray(fns);
+}
+function pipeFromArray(fns) {
+ if (fns.length === 0) {
+ return identity;
+ }
+ if (fns.length === 1) {
+ return fns[0];
+ }
+ return function piped(input) {
+ return fns.reduce(function (prev, fn) { return fn(prev); }, input);
+ };
+}
+
+/** PURE_IMPORTS_START _util_canReportError,_util_toSubscriber,_symbol_observable,_util_pipe,_config PURE_IMPORTS_END */
+var Observable = /*@__PURE__*/ (function () {
+ function Observable(subscribe) {
+ this._isScalar = false;
+ if (subscribe) {
+ this._subscribe = subscribe;
+ }
+ }
+ Observable.prototype.lift = function (operator) {
+ var observable = new Observable();
+ observable.source = this;
+ observable.operator = operator;
+ return observable;
+ };
+ Observable.prototype.subscribe = function (observerOrNext, error, complete) {
+ var operator = this.operator;
+ var sink = toSubscriber(observerOrNext, error, complete);
+ if (operator) {
+ sink.add(operator.call(sink, this.source));
+ }
+ else {
+ sink.add(this.source || (config.useDeprecatedSynchronousErrorHandling && !sink.syncErrorThrowable) ?
+ this._subscribe(sink) :
+ this._trySubscribe(sink));
+ }
+ if (config.useDeprecatedSynchronousErrorHandling) {
+ if (sink.syncErrorThrowable) {
+ sink.syncErrorThrowable = false;
+ if (sink.syncErrorThrown) {
+ throw sink.syncErrorValue;
+ }
+ }
+ }
+ return sink;
+ };
+ Observable.prototype._trySubscribe = function (sink) {
+ try {
+ return this._subscribe(sink);
+ }
+ catch (err) {
+ if (config.useDeprecatedSynchronousErrorHandling) {
+ sink.syncErrorThrown = true;
+ sink.syncErrorValue = err;
+ }
+ if (canReportError(sink)) {
+ sink.error(err);
+ }
+ else {
+ console.warn(err);
+ }
+ }
+ };
+ Observable.prototype.forEach = function (next, promiseCtor) {
+ var _this = this;
+ promiseCtor = getPromiseCtor(promiseCtor);
+ return new promiseCtor(function (resolve, reject) {
+ var subscription;
+ subscription = _this.subscribe(function (value) {
+ try {
+ next(value);
+ }
+ catch (err) {
+ reject(err);
+ if (subscription) {
+ subscription.unsubscribe();
+ }
+ }
+ }, reject, resolve);
+ });
+ };
+ Observable.prototype._subscribe = function (subscriber) {
+ var source = this.source;
+ return source && source.subscribe(subscriber);
+ };
+ Observable.prototype[observable] = function () {
+ return this;
+ };
+ Observable.prototype.pipe = function () {
+ var operations = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ operations[_i] = arguments[_i];
+ }
+ if (operations.length === 0) {
+ return this;
+ }
+ return pipeFromArray(operations)(this);
+ };
+ Observable.prototype.toPromise = function (promiseCtor) {
+ var _this = this;
+ promiseCtor = getPromiseCtor(promiseCtor);
+ return new promiseCtor(function (resolve, reject) {
+ var value;
+ _this.subscribe(function (x) { return value = x; }, function (err) { return reject(err); }, function () { return resolve(value); });
+ });
+ };
+ Observable.create = function (subscribe) {
+ return new Observable(subscribe);
+ };
+ return Observable;
+}());
+function getPromiseCtor(promiseCtor) {
+ if (!promiseCtor) {
+ promiseCtor = Promise;
+ }
+ if (!promiseCtor) {
+ throw new Error('no Promise impl found');
+ }
+ return promiseCtor;
+}
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+var ObjectUnsubscribedErrorImpl = /*@__PURE__*/ (function () {
+ function ObjectUnsubscribedErrorImpl() {
+ Error.call(this);
+ this.message = 'object unsubscribed';
+ this.name = 'ObjectUnsubscribedError';
+ return this;
+ }
+ ObjectUnsubscribedErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype);
+ return ObjectUnsubscribedErrorImpl;
+})();
+var ObjectUnsubscribedError = ObjectUnsubscribedErrorImpl;
+
+/** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */
+var SubjectSubscription = /*@__PURE__*/ (function (_super) {
+ __extends(SubjectSubscription, _super);
+ function SubjectSubscription(subject, subscriber) {
+ var _this = _super.call(this) || this;
+ _this.subject = subject;
+ _this.subscriber = subscriber;
+ _this.closed = false;
+ return _this;
+ }
+ SubjectSubscription.prototype.unsubscribe = function () {
+ if (this.closed) {
+ return;
+ }
+ this.closed = true;
+ var subject = this.subject;
+ var observers = subject.observers;
+ this.subject = null;
+ if (!observers || observers.length === 0 || subject.isStopped || subject.closed) {
+ return;
+ }
+ var subscriberIndex = observers.indexOf(this.subscriber);
+ if (subscriberIndex !== -1) {
+ observers.splice(subscriberIndex, 1);
+ }
+ };
+ return SubjectSubscription;
+}(Subscription));
+
+/** PURE_IMPORTS_START tslib,_Observable,_Subscriber,_Subscription,_util_ObjectUnsubscribedError,_SubjectSubscription,_internal_symbol_rxSubscriber PURE_IMPORTS_END */
+var SubjectSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(SubjectSubscriber, _super);
+ function SubjectSubscriber(destination) {
+ var _this = _super.call(this, destination) || this;
+ _this.destination = destination;
+ return _this;
+ }
+ return SubjectSubscriber;
+}(Subscriber));
+var Subject = /*@__PURE__*/ (function (_super) {
+ __extends(Subject, _super);
+ function Subject() {
+ var _this = _super.call(this) || this;
+ _this.observers = [];
+ _this.closed = false;
+ _this.isStopped = false;
+ _this.hasError = false;
+ _this.thrownError = null;
+ return _this;
+ }
+ Subject.prototype[rxSubscriber] = function () {
+ return new SubjectSubscriber(this);
+ };
+ Subject.prototype.lift = function (operator) {
+ var subject = new AnonymousSubject(this, this);
+ subject.operator = operator;
+ return subject;
+ };
+ Subject.prototype.next = function (value) {
+ if (this.closed) {
+ throw new ObjectUnsubscribedError();
+ }
+ if (!this.isStopped) {
+ var observers = this.observers;
+ var len = observers.length;
+ var copy = observers.slice();
+ for (var i = 0; i < len; i++) {
+ copy[i].next(value);
+ }
+ }
+ };
+ Subject.prototype.error = function (err) {
+ if (this.closed) {
+ throw new ObjectUnsubscribedError();
+ }
+ this.hasError = true;
+ this.thrownError = err;
+ this.isStopped = true;
+ var observers = this.observers;
+ var len = observers.length;
+ var copy = observers.slice();
+ for (var i = 0; i < len; i++) {
+ copy[i].error(err);
+ }
+ this.observers.length = 0;
+ };
+ Subject.prototype.complete = function () {
+ if (this.closed) {
+ throw new ObjectUnsubscribedError();
+ }
+ this.isStopped = true;
+ var observers = this.observers;
+ var len = observers.length;
+ var copy = observers.slice();
+ for (var i = 0; i < len; i++) {
+ copy[i].complete();
+ }
+ this.observers.length = 0;
+ };
+ Subject.prototype.unsubscribe = function () {
+ this.isStopped = true;
+ this.closed = true;
+ this.observers = null;
+ };
+ Subject.prototype._trySubscribe = function (subscriber) {
+ if (this.closed) {
+ throw new ObjectUnsubscribedError();
+ }
+ else {
+ return _super.prototype._trySubscribe.call(this, subscriber);
+ }
+ };
+ Subject.prototype._subscribe = function (subscriber) {
+ if (this.closed) {
+ throw new ObjectUnsubscribedError();
+ }
+ else if (this.hasError) {
+ subscriber.error(this.thrownError);
+ return Subscription.EMPTY;
+ }
+ else if (this.isStopped) {
+ subscriber.complete();
+ return Subscription.EMPTY;
+ }
+ else {
+ this.observers.push(subscriber);
+ return new SubjectSubscription(this, subscriber);
+ }
+ };
+ Subject.prototype.asObservable = function () {
+ var observable = new Observable();
+ observable.source = this;
+ return observable;
+ };
+ Subject.create = function (destination, source) {
+ return new AnonymousSubject(destination, source);
+ };
+ return Subject;
+}(Observable));
+var AnonymousSubject = /*@__PURE__*/ (function (_super) {
+ __extends(AnonymousSubject, _super);
+ function AnonymousSubject(destination, source) {
+ var _this = _super.call(this) || this;
+ _this.destination = destination;
+ _this.source = source;
+ return _this;
+ }
+ AnonymousSubject.prototype.next = function (value) {
+ var destination = this.destination;
+ if (destination && destination.next) {
+ destination.next(value);
+ }
+ };
+ AnonymousSubject.prototype.error = function (err) {
+ var destination = this.destination;
+ if (destination && destination.error) {
+ this.destination.error(err);
+ }
+ };
+ AnonymousSubject.prototype.complete = function () {
+ var destination = this.destination;
+ if (destination && destination.complete) {
+ this.destination.complete();
+ }
+ };
+ AnonymousSubject.prototype._subscribe = function (subscriber) {
+ var source = this.source;
+ if (source) {
+ return this.source.subscribe(subscriber);
+ }
+ else {
+ return Subscription.EMPTY;
+ }
+ };
+ return AnonymousSubject;
+}(Subject));
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function refCount() {
+ return function refCountOperatorFunction(source) {
+ return source.lift(new RefCountOperator(source));
+ };
+}
+var RefCountOperator = /*@__PURE__*/ (function () {
+ function RefCountOperator(connectable) {
+ this.connectable = connectable;
+ }
+ RefCountOperator.prototype.call = function (subscriber, source) {
+ var connectable = this.connectable;
+ connectable._refCount++;
+ var refCounter = new RefCountSubscriber(subscriber, connectable);
+ var subscription = source.subscribe(refCounter);
+ if (!refCounter.closed) {
+ refCounter.connection = connectable.connect();
+ }
+ return subscription;
+ };
+ return RefCountOperator;
+}());
+var RefCountSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(RefCountSubscriber, _super);
+ function RefCountSubscriber(destination, connectable) {
+ var _this = _super.call(this, destination) || this;
+ _this.connectable = connectable;
+ return _this;
+ }
+ RefCountSubscriber.prototype._unsubscribe = function () {
+ var connectable = this.connectable;
+ if (!connectable) {
+ this.connection = null;
+ return;
+ }
+ this.connectable = null;
+ var refCount = connectable._refCount;
+ if (refCount <= 0) {
+ this.connection = null;
+ return;
+ }
+ connectable._refCount = refCount - 1;
+ if (refCount > 1) {
+ this.connection = null;
+ return;
+ }
+ var connection = this.connection;
+ var sharedConnection = connectable._connection;
+ this.connection = null;
+ if (sharedConnection && (!connection || sharedConnection === connection)) {
+ sharedConnection.unsubscribe();
+ }
+ };
+ return RefCountSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subject,_Observable,_Subscriber,_Subscription,_operators_refCount PURE_IMPORTS_END */
+var ConnectableObservable = /*@__PURE__*/ (function (_super) {
+ __extends(ConnectableObservable, _super);
+ function ConnectableObservable(source, subjectFactory) {
+ var _this = _super.call(this) || this;
+ _this.source = source;
+ _this.subjectFactory = subjectFactory;
+ _this._refCount = 0;
+ _this._isComplete = false;
+ return _this;
+ }
+ ConnectableObservable.prototype._subscribe = function (subscriber) {
+ return this.getSubject().subscribe(subscriber);
+ };
+ ConnectableObservable.prototype.getSubject = function () {
+ var subject = this._subject;
+ if (!subject || subject.isStopped) {
+ this._subject = this.subjectFactory();
+ }
+ return this._subject;
+ };
+ ConnectableObservable.prototype.connect = function () {
+ var connection = this._connection;
+ if (!connection) {
+ this._isComplete = false;
+ connection = this._connection = new Subscription();
+ connection.add(this.source
+ .subscribe(new ConnectableSubscriber(this.getSubject(), this)));
+ if (connection.closed) {
+ this._connection = null;
+ connection = Subscription.EMPTY;
+ }
+ }
+ return connection;
+ };
+ ConnectableObservable.prototype.refCount = function () {
+ return refCount()(this);
+ };
+ return ConnectableObservable;
+}(Observable));
+var connectableObservableDescriptor = /*@__PURE__*/ (function () {
+ var connectableProto = ConnectableObservable.prototype;
+ return {
+ operator: { value: null },
+ _refCount: { value: 0, writable: true },
+ _subject: { value: null, writable: true },
+ _connection: { value: null, writable: true },
+ _subscribe: { value: connectableProto._subscribe },
+ _isComplete: { value: connectableProto._isComplete, writable: true },
+ getSubject: { value: connectableProto.getSubject },
+ connect: { value: connectableProto.connect },
+ refCount: { value: connectableProto.refCount }
+ };
+})();
+var ConnectableSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(ConnectableSubscriber, _super);
+ function ConnectableSubscriber(destination, connectable) {
+ var _this = _super.call(this, destination) || this;
+ _this.connectable = connectable;
+ return _this;
+ }
+ ConnectableSubscriber.prototype._error = function (err) {
+ this._unsubscribe();
+ _super.prototype._error.call(this, err);
+ };
+ ConnectableSubscriber.prototype._complete = function () {
+ this.connectable._isComplete = true;
+ this._unsubscribe();
+ _super.prototype._complete.call(this);
+ };
+ ConnectableSubscriber.prototype._unsubscribe = function () {
+ var connectable = this.connectable;
+ if (connectable) {
+ this.connectable = null;
+ var connection = connectable._connection;
+ connectable._refCount = 0;
+ connectable._subject = null;
+ connectable._connection = null;
+ if (connection) {
+ connection.unsubscribe();
+ }
+ }
+ };
+ return ConnectableSubscriber;
+}(SubjectSubscriber));
+
+/** PURE_IMPORTS_START tslib,_Subject,_util_ObjectUnsubscribedError PURE_IMPORTS_END */
+var BehaviorSubject = /*@__PURE__*/ (function (_super) {
+ __extends(BehaviorSubject, _super);
+ function BehaviorSubject(_value) {
+ var _this = _super.call(this) || this;
+ _this._value = _value;
+ return _this;
+ }
+ Object.defineProperty(BehaviorSubject.prototype, "value", {
+ get: function () {
+ return this.getValue();
+ },
+ enumerable: true,
+ configurable: true
+ });
+ BehaviorSubject.prototype._subscribe = function (subscriber) {
+ var subscription = _super.prototype._subscribe.call(this, subscriber);
+ if (subscription && !subscription.closed) {
+ subscriber.next(this._value);
+ }
+ return subscription;
+ };
+ BehaviorSubject.prototype.getValue = function () {
+ if (this.hasError) {
+ throw this.thrownError;
+ }
+ else if (this.closed) {
+ throw new ObjectUnsubscribedError();
+ }
+ else {
+ return this._value;
+ }
+ };
+ BehaviorSubject.prototype.next = function (value) {
+ _super.prototype.next.call(this, this._value = value);
+ };
+ return BehaviorSubject;
+}(Subject));
+
+/** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */
+var Action = /*@__PURE__*/ (function (_super) {
+ __extends(Action, _super);
+ function Action(scheduler, work) {
+ return _super.call(this) || this;
+ }
+ Action.prototype.schedule = function (state, delay) {
+ return this;
+ };
+ return Action;
+}(Subscription));
+
+/** PURE_IMPORTS_START tslib,_Action PURE_IMPORTS_END */
+var AsyncAction = /*@__PURE__*/ (function (_super) {
+ __extends(AsyncAction, _super);
+ function AsyncAction(scheduler, work) {
+ var _this = _super.call(this, scheduler, work) || this;
+ _this.scheduler = scheduler;
+ _this.work = work;
+ _this.pending = false;
+ return _this;
+ }
+ AsyncAction.prototype.schedule = function (state, delay) {
+ if (delay === void 0) {
+ delay = 0;
+ }
+ if (this.closed) {
+ return this;
+ }
+ this.state = state;
+ var id = this.id;
+ var scheduler = this.scheduler;
+ if (id != null) {
+ this.id = this.recycleAsyncId(scheduler, id, delay);
+ }
+ this.pending = true;
+ this.delay = delay;
+ this.id = this.id || this.requestAsyncId(scheduler, this.id, delay);
+ return this;
+ };
+ AsyncAction.prototype.requestAsyncId = function (scheduler, id, delay) {
+ if (delay === void 0) {
+ delay = 0;
+ }
+ return setInterval(scheduler.flush.bind(scheduler, this), delay);
+ };
+ AsyncAction.prototype.recycleAsyncId = function (scheduler, id, delay) {
+ if (delay === void 0) {
+ delay = 0;
+ }
+ if (delay !== null && this.delay === delay && this.pending === false) {
+ return id;
+ }
+ clearInterval(id);
+ return undefined;
+ };
+ AsyncAction.prototype.execute = function (state, delay) {
+ if (this.closed) {
+ return new Error('executing a cancelled action');
+ }
+ this.pending = false;
+ var error = this._execute(state, delay);
+ if (error) {
+ return error;
+ }
+ else if (this.pending === false && this.id != null) {
+ this.id = this.recycleAsyncId(this.scheduler, this.id, null);
+ }
+ };
+ AsyncAction.prototype._execute = function (state, delay) {
+ var errored = false;
+ var errorValue = undefined;
+ try {
+ this.work(state);
+ }
+ catch (e) {
+ errored = true;
+ errorValue = !!e && e || new Error(e);
+ }
+ if (errored) {
+ this.unsubscribe();
+ return errorValue;
+ }
+ };
+ AsyncAction.prototype._unsubscribe = function () {
+ var id = this.id;
+ var scheduler = this.scheduler;
+ var actions = scheduler.actions;
+ var index = actions.indexOf(this);
+ this.work = null;
+ this.state = null;
+ this.pending = false;
+ this.scheduler = null;
+ if (index !== -1) {
+ actions.splice(index, 1);
+ }
+ if (id != null) {
+ this.id = this.recycleAsyncId(scheduler, id, null);
+ }
+ this.delay = null;
+ };
+ return AsyncAction;
+}(Action));
+
+/** PURE_IMPORTS_START tslib,_AsyncAction PURE_IMPORTS_END */
+var QueueAction = /*@__PURE__*/ (function (_super) {
+ __extends(QueueAction, _super);
+ function QueueAction(scheduler, work) {
+ var _this = _super.call(this, scheduler, work) || this;
+ _this.scheduler = scheduler;
+ _this.work = work;
+ return _this;
+ }
+ QueueAction.prototype.schedule = function (state, delay) {
+ if (delay === void 0) {
+ delay = 0;
+ }
+ if (delay > 0) {
+ return _super.prototype.schedule.call(this, state, delay);
+ }
+ this.delay = delay;
+ this.state = state;
+ this.scheduler.flush(this);
+ return this;
+ };
+ QueueAction.prototype.execute = function (state, delay) {
+ return (delay > 0 || this.closed) ?
+ _super.prototype.execute.call(this, state, delay) :
+ this._execute(state, delay);
+ };
+ QueueAction.prototype.requestAsyncId = function (scheduler, id, delay) {
+ if (delay === void 0) {
+ delay = 0;
+ }
+ if ((delay !== null && delay > 0) || (delay === null && this.delay > 0)) {
+ return _super.prototype.requestAsyncId.call(this, scheduler, id, delay);
+ }
+ return scheduler.flush(this);
+ };
+ return QueueAction;
+}(AsyncAction));
+
+var Scheduler = /*@__PURE__*/ (function () {
+ function Scheduler(SchedulerAction, now) {
+ if (now === void 0) {
+ now = Scheduler.now;
+ }
+ this.SchedulerAction = SchedulerAction;
+ this.now = now;
+ }
+ Scheduler.prototype.schedule = function (work, delay, state) {
+ if (delay === void 0) {
+ delay = 0;
+ }
+ return new this.SchedulerAction(this, work).schedule(state, delay);
+ };
+ Scheduler.now = function () { return Date.now(); };
+ return Scheduler;
+}());
+
+/** PURE_IMPORTS_START tslib,_Scheduler PURE_IMPORTS_END */
+var AsyncScheduler = /*@__PURE__*/ (function (_super) {
+ __extends(AsyncScheduler, _super);
+ function AsyncScheduler(SchedulerAction, now) {
+ if (now === void 0) {
+ now = Scheduler.now;
+ }
+ var _this = _super.call(this, SchedulerAction, function () {
+ if (AsyncScheduler.delegate && AsyncScheduler.delegate !== _this) {
+ return AsyncScheduler.delegate.now();
+ }
+ else {
+ return now();
+ }
+ }) || this;
+ _this.actions = [];
+ _this.active = false;
+ _this.scheduled = undefined;
+ return _this;
+ }
+ AsyncScheduler.prototype.schedule = function (work, delay, state) {
+ if (delay === void 0) {
+ delay = 0;
+ }
+ if (AsyncScheduler.delegate && AsyncScheduler.delegate !== this) {
+ return AsyncScheduler.delegate.schedule(work, delay, state);
+ }
+ else {
+ return _super.prototype.schedule.call(this, work, delay, state);
+ }
+ };
+ AsyncScheduler.prototype.flush = function (action) {
+ var actions = this.actions;
+ if (this.active) {
+ actions.push(action);
+ return;
+ }
+ var error;
+ this.active = true;
+ do {
+ if (error = action.execute(action.state, action.delay)) {
+ break;
+ }
+ } while (action = actions.shift());
+ this.active = false;
+ if (error) {
+ while (action = actions.shift()) {
+ action.unsubscribe();
+ }
+ throw error;
+ }
+ };
+ return AsyncScheduler;
+}(Scheduler));
+
+/** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */
+var QueueScheduler = /*@__PURE__*/ (function (_super) {
+ __extends(QueueScheduler, _super);
+ function QueueScheduler() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ return QueueScheduler;
+}(AsyncScheduler));
+
+/** PURE_IMPORTS_START _QueueAction,_QueueScheduler PURE_IMPORTS_END */
+var queueScheduler = /*@__PURE__*/ new QueueScheduler(QueueAction);
+var queue = queueScheduler;
+
+/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */
+var EMPTY$1 = /*@__PURE__*/ new Observable(function (subscriber) { return subscriber.complete(); });
+function empty(scheduler) {
+ return scheduler ? emptyScheduled(scheduler) : EMPTY$1;
+}
+function emptyScheduled(scheduler) {
+ return new Observable(function (subscriber) { return scheduler.schedule(function () { return subscriber.complete(); }); });
+}
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+function isScheduler(value) {
+ return value && typeof value.schedule === 'function';
+}
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+var subscribeToArray = function (array) {
+ return function (subscriber) {
+ for (var i = 0, len = array.length; i < len && !subscriber.closed; i++) {
+ subscriber.next(array[i]);
+ }
+ subscriber.complete();
+ };
+};
+
+/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */
+function scheduleArray(input, scheduler) {
+ return new Observable(function (subscriber) {
+ var sub = new Subscription();
+ var i = 0;
+ sub.add(scheduler.schedule(function () {
+ if (i === input.length) {
+ subscriber.complete();
+ return;
+ }
+ subscriber.next(input[i++]);
+ if (!subscriber.closed) {
+ sub.add(this.schedule());
+ }
+ }));
+ return sub;
+ });
+}
+
+/** PURE_IMPORTS_START _Observable,_util_subscribeToArray,_scheduled_scheduleArray PURE_IMPORTS_END */
+function fromArray(input, scheduler) {
+ if (!scheduler) {
+ return new Observable(subscribeToArray(input));
+ }
+ else {
+ return scheduleArray(input, scheduler);
+ }
+}
+
+/** PURE_IMPORTS_START _util_isScheduler,_fromArray,_scheduled_scheduleArray PURE_IMPORTS_END */
+function of() {
+ var args = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ args[_i] = arguments[_i];
+ }
+ var scheduler = args[args.length - 1];
+ if (isScheduler(scheduler)) {
+ args.pop();
+ return scheduleArray(args, scheduler);
+ }
+ else {
+ return fromArray(args);
+ }
+}
+
+/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */
+function throwError(error, scheduler) {
+ if (!scheduler) {
+ return new Observable(function (subscriber) { return subscriber.error(error); });
+ }
+ else {
+ return new Observable(function (subscriber) { return scheduler.schedule(dispatch$1, 0, { error: error, subscriber: subscriber }); });
+ }
+}
+function dispatch$1(_a) {
+ var error = _a.error, subscriber = _a.subscriber;
+ subscriber.error(error);
+}
+
+/** PURE_IMPORTS_START _observable_empty,_observable_of,_observable_throwError PURE_IMPORTS_END */
+var Notification = /*@__PURE__*/ (function () {
+ function Notification(kind, value, error) {
+ this.kind = kind;
+ this.value = value;
+ this.error = error;
+ this.hasValue = kind === 'N';
+ }
+ Notification.prototype.observe = function (observer) {
+ switch (this.kind) {
+ case 'N':
+ return observer.next && observer.next(this.value);
+ case 'E':
+ return observer.error && observer.error(this.error);
+ case 'C':
+ return observer.complete && observer.complete();
+ }
+ };
+ Notification.prototype.do = function (next, error, complete) {
+ var kind = this.kind;
+ switch (kind) {
+ case 'N':
+ return next && next(this.value);
+ case 'E':
+ return error && error(this.error);
+ case 'C':
+ return complete && complete();
+ }
+ };
+ Notification.prototype.accept = function (nextOrObserver, error, complete) {
+ if (nextOrObserver && typeof nextOrObserver.next === 'function') {
+ return this.observe(nextOrObserver);
+ }
+ else {
+ return this.do(nextOrObserver, error, complete);
+ }
+ };
+ Notification.prototype.toObservable = function () {
+ var kind = this.kind;
+ switch (kind) {
+ case 'N':
+ return of(this.value);
+ case 'E':
+ return throwError(this.error);
+ case 'C':
+ return empty();
+ }
+ throw new Error('unexpected notification kind value');
+ };
+ Notification.createNext = function (value) {
+ if (typeof value !== 'undefined') {
+ return new Notification('N', value);
+ }
+ return Notification.undefinedValueNotification;
+ };
+ Notification.createError = function (err) {
+ return new Notification('E', undefined, err);
+ };
+ Notification.createComplete = function () {
+ return Notification.completeNotification;
+ };
+ Notification.completeNotification = new Notification('C');
+ Notification.undefinedValueNotification = new Notification('N', undefined);
+ return Notification;
+}());
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */
+var ObserveOnSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(ObserveOnSubscriber, _super);
+ function ObserveOnSubscriber(destination, scheduler, delay) {
+ if (delay === void 0) {
+ delay = 0;
+ }
+ var _this = _super.call(this, destination) || this;
+ _this.scheduler = scheduler;
+ _this.delay = delay;
+ return _this;
+ }
+ ObserveOnSubscriber.dispatch = function (arg) {
+ var notification = arg.notification, destination = arg.destination;
+ notification.observe(destination);
+ this.unsubscribe();
+ };
+ ObserveOnSubscriber.prototype.scheduleMessage = function (notification) {
+ var destination = this.destination;
+ destination.add(this.scheduler.schedule(ObserveOnSubscriber.dispatch, this.delay, new ObserveOnMessage(notification, this.destination)));
+ };
+ ObserveOnSubscriber.prototype._next = function (value) {
+ this.scheduleMessage(Notification.createNext(value));
+ };
+ ObserveOnSubscriber.prototype._error = function (err) {
+ this.scheduleMessage(Notification.createError(err));
+ this.unsubscribe();
+ };
+ ObserveOnSubscriber.prototype._complete = function () {
+ this.scheduleMessage(Notification.createComplete());
+ this.unsubscribe();
+ };
+ return ObserveOnSubscriber;
+}(Subscriber));
+var ObserveOnMessage = /*@__PURE__*/ (function () {
+ function ObserveOnMessage(notification, destination) {
+ this.notification = notification;
+ this.destination = destination;
+ }
+ return ObserveOnMessage;
+}());
+
+/** PURE_IMPORTS_START tslib,_Subject,_scheduler_queue,_Subscription,_operators_observeOn,_util_ObjectUnsubscribedError,_SubjectSubscription PURE_IMPORTS_END */
+var ReplaySubject = /*@__PURE__*/ (function (_super) {
+ __extends(ReplaySubject, _super);
+ function ReplaySubject(bufferSize, windowTime, scheduler) {
+ if (bufferSize === void 0) {
+ bufferSize = Number.POSITIVE_INFINITY;
+ }
+ if (windowTime === void 0) {
+ windowTime = Number.POSITIVE_INFINITY;
+ }
+ var _this = _super.call(this) || this;
+ _this.scheduler = scheduler;
+ _this._events = [];
+ _this._infiniteTimeWindow = false;
+ _this._bufferSize = bufferSize < 1 ? 1 : bufferSize;
+ _this._windowTime = windowTime < 1 ? 1 : windowTime;
+ if (windowTime === Number.POSITIVE_INFINITY) {
+ _this._infiniteTimeWindow = true;
+ _this.next = _this.nextInfiniteTimeWindow;
+ }
+ else {
+ _this.next = _this.nextTimeWindow;
+ }
+ return _this;
+ }
+ ReplaySubject.prototype.nextInfiniteTimeWindow = function (value) {
+ if (!this.isStopped) {
+ var _events = this._events;
+ _events.push(value);
+ if (_events.length > this._bufferSize) {
+ _events.shift();
+ }
+ }
+ _super.prototype.next.call(this, value);
+ };
+ ReplaySubject.prototype.nextTimeWindow = function (value) {
+ if (!this.isStopped) {
+ this._events.push(new ReplayEvent(this._getNow(), value));
+ this._trimBufferThenGetEvents();
+ }
+ _super.prototype.next.call(this, value);
+ };
+ ReplaySubject.prototype._subscribe = function (subscriber) {
+ var _infiniteTimeWindow = this._infiniteTimeWindow;
+ var _events = _infiniteTimeWindow ? this._events : this._trimBufferThenGetEvents();
+ var scheduler = this.scheduler;
+ var len = _events.length;
+ var subscription;
+ if (this.closed) {
+ throw new ObjectUnsubscribedError();
+ }
+ else if (this.isStopped || this.hasError) {
+ subscription = Subscription.EMPTY;
+ }
+ else {
+ this.observers.push(subscriber);
+ subscription = new SubjectSubscription(this, subscriber);
+ }
+ if (scheduler) {
+ subscriber.add(subscriber = new ObserveOnSubscriber(subscriber, scheduler));
+ }
+ if (_infiniteTimeWindow) {
+ for (var i = 0; i < len && !subscriber.closed; i++) {
+ subscriber.next(_events[i]);
+ }
+ }
+ else {
+ for (var i = 0; i < len && !subscriber.closed; i++) {
+ subscriber.next(_events[i].value);
+ }
+ }
+ if (this.hasError) {
+ subscriber.error(this.thrownError);
+ }
+ else if (this.isStopped) {
+ subscriber.complete();
+ }
+ return subscription;
+ };
+ ReplaySubject.prototype._getNow = function () {
+ return (this.scheduler || queue).now();
+ };
+ ReplaySubject.prototype._trimBufferThenGetEvents = function () {
+ var now = this._getNow();
+ var _bufferSize = this._bufferSize;
+ var _windowTime = this._windowTime;
+ var _events = this._events;
+ var eventsCount = _events.length;
+ var spliceCount = 0;
+ while (spliceCount < eventsCount) {
+ if ((now - _events[spliceCount].time) < _windowTime) {
+ break;
+ }
+ spliceCount++;
+ }
+ if (eventsCount > _bufferSize) {
+ spliceCount = Math.max(spliceCount, eventsCount - _bufferSize);
+ }
+ if (spliceCount > 0) {
+ _events.splice(0, spliceCount);
+ }
+ return _events;
+ };
+ return ReplaySubject;
+}(Subject));
+var ReplayEvent = /*@__PURE__*/ (function () {
+ function ReplayEvent(time, value) {
+ this.time = time;
+ this.value = value;
+ }
+ return ReplayEvent;
+}());
+
+/** PURE_IMPORTS_START _AsyncAction,_AsyncScheduler PURE_IMPORTS_END */
+var asyncScheduler = /*@__PURE__*/ new AsyncScheduler(AsyncAction);
+var async = asyncScheduler;
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+function noop() { }
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+var ArgumentOutOfRangeErrorImpl = /*@__PURE__*/ (function () {
+ function ArgumentOutOfRangeErrorImpl() {
+ Error.call(this);
+ this.message = 'argument out of range';
+ this.name = 'ArgumentOutOfRangeError';
+ return this;
+ }
+ ArgumentOutOfRangeErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype);
+ return ArgumentOutOfRangeErrorImpl;
+})();
+var ArgumentOutOfRangeError = ArgumentOutOfRangeErrorImpl;
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+var EmptyErrorImpl = /*@__PURE__*/ (function () {
+ function EmptyErrorImpl() {
+ Error.call(this);
+ this.message = 'no elements in sequence';
+ this.name = 'EmptyError';
+ return this;
+ }
+ EmptyErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype);
+ return EmptyErrorImpl;
+})();
+var EmptyError = EmptyErrorImpl;
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+var TimeoutErrorImpl = /*@__PURE__*/ (function () {
+ function TimeoutErrorImpl() {
+ Error.call(this);
+ this.message = 'Timeout has occurred';
+ this.name = 'TimeoutError';
+ return this;
+ }
+ TimeoutErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype);
+ return TimeoutErrorImpl;
+})();
+var TimeoutError = TimeoutErrorImpl;
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function map(project, thisArg) {
+ return function mapOperation(source) {
+ if (typeof project !== 'function') {
+ throw new TypeError('argument is not a function. Are you looking for `mapTo()`?');
+ }
+ return source.lift(new MapOperator(project, thisArg));
+ };
+}
+var MapOperator = /*@__PURE__*/ (function () {
+ function MapOperator(project, thisArg) {
+ this.project = project;
+ this.thisArg = thisArg;
+ }
+ MapOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new MapSubscriber(subscriber, this.project, this.thisArg));
+ };
+ return MapOperator;
+}());
+var MapSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(MapSubscriber, _super);
+ function MapSubscriber(destination, project, thisArg) {
+ var _this = _super.call(this, destination) || this;
+ _this.project = project;
+ _this.count = 0;
+ _this.thisArg = thisArg || _this;
+ return _this;
+ }
+ MapSubscriber.prototype._next = function (value) {
+ var result;
+ try {
+ result = this.project.call(this.thisArg, value, this.count++);
+ }
+ catch (err) {
+ this.destination.error(err);
+ return;
+ }
+ this.destination.next(result);
+ };
+ return MapSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+var OuterSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(OuterSubscriber, _super);
+ function OuterSubscriber() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ OuterSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) {
+ this.destination.next(innerValue);
+ };
+ OuterSubscriber.prototype.notifyError = function (error, innerSub) {
+ this.destination.error(error);
+ };
+ OuterSubscriber.prototype.notifyComplete = function (innerSub) {
+ this.destination.complete();
+ };
+ return OuterSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+var InnerSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(InnerSubscriber, _super);
+ function InnerSubscriber(parent, outerValue, outerIndex) {
+ var _this = _super.call(this) || this;
+ _this.parent = parent;
+ _this.outerValue = outerValue;
+ _this.outerIndex = outerIndex;
+ _this.index = 0;
+ return _this;
+ }
+ InnerSubscriber.prototype._next = function (value) {
+ this.parent.notifyNext(this.outerValue, value, this.outerIndex, this.index++, this);
+ };
+ InnerSubscriber.prototype._error = function (error) {
+ this.parent.notifyError(error, this);
+ this.unsubscribe();
+ };
+ InnerSubscriber.prototype._complete = function () {
+ this.parent.notifyComplete(this);
+ this.unsubscribe();
+ };
+ return InnerSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _hostReportError PURE_IMPORTS_END */
+var subscribeToPromise = function (promise) {
+ return function (subscriber) {
+ promise.then(function (value) {
+ if (!subscriber.closed) {
+ subscriber.next(value);
+ subscriber.complete();
+ }
+ }, function (err) { return subscriber.error(err); })
+ .then(null, hostReportError);
+ return subscriber;
+ };
+};
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+function getSymbolIterator() {
+ if (typeof Symbol !== 'function' || !Symbol.iterator) {
+ return '@@iterator';
+ }
+ return Symbol.iterator;
+}
+var iterator = /*@__PURE__*/ getSymbolIterator();
+
+/** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */
+var subscribeToIterable = function (iterable) {
+ return function (subscriber) {
+ var iterator$1 = iterable[iterator]();
+ do {
+ var item = void 0;
+ try {
+ item = iterator$1.next();
+ }
+ catch (err) {
+ subscriber.error(err);
+ return subscriber;
+ }
+ if (item.done) {
+ subscriber.complete();
+ break;
+ }
+ subscriber.next(item.value);
+ if (subscriber.closed) {
+ break;
+ }
+ } while (true);
+ if (typeof iterator$1.return === 'function') {
+ subscriber.add(function () {
+ if (iterator$1.return) {
+ iterator$1.return();
+ }
+ });
+ }
+ return subscriber;
+ };
+};
+
+/** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */
+var subscribeToObservable = function (obj) {
+ return function (subscriber) {
+ var obs = obj[observable]();
+ if (typeof obs.subscribe !== 'function') {
+ throw new TypeError('Provided object does not correctly implement Symbol.observable');
+ }
+ else {
+ return obs.subscribe(subscriber);
+ }
+ };
+};
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+var isArrayLike = (function (x) { return x && typeof x.length === 'number' && typeof x !== 'function'; });
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+function isPromise(value) {
+ return !!value && typeof value.subscribe !== 'function' && typeof value.then === 'function';
+}
+
+/** PURE_IMPORTS_START _subscribeToArray,_subscribeToPromise,_subscribeToIterable,_subscribeToObservable,_isArrayLike,_isPromise,_isObject,_symbol_iterator,_symbol_observable PURE_IMPORTS_END */
+var subscribeTo = function (result) {
+ if (!!result && typeof result[observable] === 'function') {
+ return subscribeToObservable(result);
+ }
+ else if (isArrayLike(result)) {
+ return subscribeToArray(result);
+ }
+ else if (isPromise(result)) {
+ return subscribeToPromise(result);
+ }
+ else if (!!result && typeof result[iterator] === 'function') {
+ return subscribeToIterable(result);
+ }
+ else {
+ var value = isObject$1(result) ? 'an invalid object' : "'" + result + "'";
+ var msg = "You provided " + value + " where a stream was expected."
+ + ' You can provide an Observable, Promise, Array, or Iterable.';
+ throw new TypeError(msg);
+ }
+};
+
+/** PURE_IMPORTS_START _InnerSubscriber,_subscribeTo,_Observable PURE_IMPORTS_END */
+function subscribeToResult(outerSubscriber, result, outerValue, outerIndex, innerSubscriber) {
+ if (innerSubscriber === void 0) {
+ innerSubscriber = new InnerSubscriber(outerSubscriber, outerValue, outerIndex);
+ }
+ if (innerSubscriber.closed) {
+ return undefined;
+ }
+ if (result instanceof Observable) {
+ return result.subscribe(innerSubscriber);
+ }
+ return subscribeTo(result)(innerSubscriber);
+}
+
+/** PURE_IMPORTS_START tslib,_util_isScheduler,_util_isArray,_OuterSubscriber,_util_subscribeToResult,_fromArray PURE_IMPORTS_END */
+var NONE = {};
+function combineLatest() {
+ var observables = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ observables[_i] = arguments[_i];
+ }
+ var resultSelector = undefined;
+ var scheduler = undefined;
+ if (isScheduler(observables[observables.length - 1])) {
+ scheduler = observables.pop();
+ }
+ if (typeof observables[observables.length - 1] === 'function') {
+ resultSelector = observables.pop();
+ }
+ if (observables.length === 1 && isArray$1(observables[0])) {
+ observables = observables[0];
+ }
+ return fromArray(observables, scheduler).lift(new CombineLatestOperator(resultSelector));
+}
+var CombineLatestOperator = /*@__PURE__*/ (function () {
+ function CombineLatestOperator(resultSelector) {
+ this.resultSelector = resultSelector;
+ }
+ CombineLatestOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new CombineLatestSubscriber(subscriber, this.resultSelector));
+ };
+ return CombineLatestOperator;
+}());
+var CombineLatestSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(CombineLatestSubscriber, _super);
+ function CombineLatestSubscriber(destination, resultSelector) {
+ var _this = _super.call(this, destination) || this;
+ _this.resultSelector = resultSelector;
+ _this.active = 0;
+ _this.values = [];
+ _this.observables = [];
+ return _this;
+ }
+ CombineLatestSubscriber.prototype._next = function (observable) {
+ this.values.push(NONE);
+ this.observables.push(observable);
+ };
+ CombineLatestSubscriber.prototype._complete = function () {
+ var observables = this.observables;
+ var len = observables.length;
+ if (len === 0) {
+ this.destination.complete();
+ }
+ else {
+ this.active = len;
+ this.toRespond = len;
+ for (var i = 0; i < len; i++) {
+ var observable = observables[i];
+ this.add(subscribeToResult(this, observable, undefined, i));
+ }
+ }
+ };
+ CombineLatestSubscriber.prototype.notifyComplete = function (unused) {
+ if ((this.active -= 1) === 0) {
+ this.destination.complete();
+ }
+ };
+ CombineLatestSubscriber.prototype.notifyNext = function (_outerValue, innerValue, outerIndex) {
+ var values = this.values;
+ var oldVal = values[outerIndex];
+ var toRespond = !this.toRespond
+ ? 0
+ : oldVal === NONE ? --this.toRespond : this.toRespond;
+ values[outerIndex] = innerValue;
+ if (toRespond === 0) {
+ if (this.resultSelector) {
+ this._tryResultSelector(values);
+ }
+ else {
+ this.destination.next(values.slice());
+ }
+ }
+ };
+ CombineLatestSubscriber.prototype._tryResultSelector = function (values) {
+ var result;
+ try {
+ result = this.resultSelector.apply(this, values);
+ }
+ catch (err) {
+ this.destination.error(err);
+ return;
+ }
+ this.destination.next(result);
+ };
+ return CombineLatestSubscriber;
+}(OuterSubscriber));
+
+/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_observable PURE_IMPORTS_END */
+function scheduleObservable(input, scheduler) {
+ return new Observable(function (subscriber) {
+ var sub = new Subscription();
+ sub.add(scheduler.schedule(function () {
+ var observable$1 = input[observable]();
+ sub.add(observable$1.subscribe({
+ next: function (value) { sub.add(scheduler.schedule(function () { return subscriber.next(value); })); },
+ error: function (err) { sub.add(scheduler.schedule(function () { return subscriber.error(err); })); },
+ complete: function () { sub.add(scheduler.schedule(function () { return subscriber.complete(); })); },
+ }));
+ }));
+ return sub;
+ });
+}
+
+/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */
+function schedulePromise(input, scheduler) {
+ return new Observable(function (subscriber) {
+ var sub = new Subscription();
+ sub.add(scheduler.schedule(function () {
+ return input.then(function (value) {
+ sub.add(scheduler.schedule(function () {
+ subscriber.next(value);
+ sub.add(scheduler.schedule(function () { return subscriber.complete(); }));
+ }));
+ }, function (err) {
+ sub.add(scheduler.schedule(function () { return subscriber.error(err); }));
+ });
+ }));
+ return sub;
+ });
+}
+
+/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_iterator PURE_IMPORTS_END */
+function scheduleIterable(input, scheduler) {
+ if (!input) {
+ throw new Error('Iterable cannot be null');
+ }
+ return new Observable(function (subscriber) {
+ var sub = new Subscription();
+ var iterator$1;
+ sub.add(function () {
+ if (iterator$1 && typeof iterator$1.return === 'function') {
+ iterator$1.return();
+ }
+ });
+ sub.add(scheduler.schedule(function () {
+ iterator$1 = input[iterator]();
+ sub.add(scheduler.schedule(function () {
+ if (subscriber.closed) {
+ return;
+ }
+ var value;
+ var done;
+ try {
+ var result = iterator$1.next();
+ value = result.value;
+ done = result.done;
+ }
+ catch (err) {
+ subscriber.error(err);
+ return;
+ }
+ if (done) {
+ subscriber.complete();
+ }
+ else {
+ subscriber.next(value);
+ this.schedule();
+ }
+ }));
+ }));
+ return sub;
+ });
+}
+
+/** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */
+function isInteropObservable(input) {
+ return input && typeof input[observable] === 'function';
+}
+
+/** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */
+function isIterable(input) {
+ return input && typeof input[iterator] === 'function';
+}
+
+/** PURE_IMPORTS_START _scheduleObservable,_schedulePromise,_scheduleArray,_scheduleIterable,_util_isInteropObservable,_util_isPromise,_util_isArrayLike,_util_isIterable PURE_IMPORTS_END */
+function scheduled(input, scheduler) {
+ if (input != null) {
+ if (isInteropObservable(input)) {
+ return scheduleObservable(input, scheduler);
+ }
+ else if (isPromise(input)) {
+ return schedulePromise(input, scheduler);
+ }
+ else if (isArrayLike(input)) {
+ return scheduleArray(input, scheduler);
+ }
+ else if (isIterable(input) || typeof input === 'string') {
+ return scheduleIterable(input, scheduler);
+ }
+ }
+ throw new TypeError((input !== null && typeof input || input) + ' is not observable');
+}
+
+/** PURE_IMPORTS_START _Observable,_util_subscribeTo,_scheduled_scheduled PURE_IMPORTS_END */
+function from(input, scheduler) {
+ if (!scheduler) {
+ if (input instanceof Observable) {
+ return input;
+ }
+ return new Observable(subscribeTo(input));
+ }
+ else {
+ return scheduled(input, scheduler);
+ }
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_Observable,_util_subscribeTo PURE_IMPORTS_END */
+var SimpleInnerSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(SimpleInnerSubscriber, _super);
+ function SimpleInnerSubscriber(parent) {
+ var _this = _super.call(this) || this;
+ _this.parent = parent;
+ return _this;
+ }
+ SimpleInnerSubscriber.prototype._next = function (value) {
+ this.parent.notifyNext(value);
+ };
+ SimpleInnerSubscriber.prototype._error = function (error) {
+ this.parent.notifyError(error);
+ this.unsubscribe();
+ };
+ SimpleInnerSubscriber.prototype._complete = function () {
+ this.parent.notifyComplete();
+ this.unsubscribe();
+ };
+ return SimpleInnerSubscriber;
+}(Subscriber));
+var SimpleOuterSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(SimpleOuterSubscriber, _super);
+ function SimpleOuterSubscriber() {
+ return _super !== null && _super.apply(this, arguments) || this;
+ }
+ SimpleOuterSubscriber.prototype.notifyNext = function (innerValue) {
+ this.destination.next(innerValue);
+ };
+ SimpleOuterSubscriber.prototype.notifyError = function (err) {
+ this.destination.error(err);
+ };
+ SimpleOuterSubscriber.prototype.notifyComplete = function () {
+ this.destination.complete();
+ };
+ return SimpleOuterSubscriber;
+}(Subscriber));
+function innerSubscribe(result, innerSubscriber) {
+ if (innerSubscriber.closed) {
+ return undefined;
+ }
+ if (result instanceof Observable) {
+ return result.subscribe(innerSubscriber);
+ }
+ return subscribeTo(result)(innerSubscriber);
+}
+
+/** PURE_IMPORTS_START tslib,_map,_observable_from,_innerSubscribe PURE_IMPORTS_END */
+function mergeMap(project, resultSelector, concurrent) {
+ if (concurrent === void 0) {
+ concurrent = Number.POSITIVE_INFINITY;
+ }
+ if (typeof resultSelector === 'function') {
+ return function (source) { return source.pipe(mergeMap(function (a, i) { return from(project(a, i)).pipe(map(function (b, ii) { return resultSelector(a, b, i, ii); })); }, concurrent)); };
+ }
+ else if (typeof resultSelector === 'number') {
+ concurrent = resultSelector;
+ }
+ return function (source) { return source.lift(new MergeMapOperator(project, concurrent)); };
+}
+var MergeMapOperator = /*@__PURE__*/ (function () {
+ function MergeMapOperator(project, concurrent) {
+ if (concurrent === void 0) {
+ concurrent = Number.POSITIVE_INFINITY;
+ }
+ this.project = project;
+ this.concurrent = concurrent;
+ }
+ MergeMapOperator.prototype.call = function (observer, source) {
+ return source.subscribe(new MergeMapSubscriber(observer, this.project, this.concurrent));
+ };
+ return MergeMapOperator;
+}());
+var MergeMapSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(MergeMapSubscriber, _super);
+ function MergeMapSubscriber(destination, project, concurrent) {
+ if (concurrent === void 0) {
+ concurrent = Number.POSITIVE_INFINITY;
+ }
+ var _this = _super.call(this, destination) || this;
+ _this.project = project;
+ _this.concurrent = concurrent;
+ _this.hasCompleted = false;
+ _this.buffer = [];
+ _this.active = 0;
+ _this.index = 0;
+ return _this;
+ }
+ MergeMapSubscriber.prototype._next = function (value) {
+ if (this.active < this.concurrent) {
+ this._tryNext(value);
+ }
+ else {
+ this.buffer.push(value);
+ }
+ };
+ MergeMapSubscriber.prototype._tryNext = function (value) {
+ var result;
+ var index = this.index++;
+ try {
+ result = this.project(value, index);
+ }
+ catch (err) {
+ this.destination.error(err);
+ return;
+ }
+ this.active++;
+ this._innerSub(result);
+ };
+ MergeMapSubscriber.prototype._innerSub = function (ish) {
+ var innerSubscriber = new SimpleInnerSubscriber(this);
+ var destination = this.destination;
+ destination.add(innerSubscriber);
+ var innerSubscription = innerSubscribe(ish, innerSubscriber);
+ if (innerSubscription !== innerSubscriber) {
+ destination.add(innerSubscription);
+ }
+ };
+ MergeMapSubscriber.prototype._complete = function () {
+ this.hasCompleted = true;
+ if (this.active === 0 && this.buffer.length === 0) {
+ this.destination.complete();
+ }
+ this.unsubscribe();
+ };
+ MergeMapSubscriber.prototype.notifyNext = function (innerValue) {
+ this.destination.next(innerValue);
+ };
+ MergeMapSubscriber.prototype.notifyComplete = function () {
+ var buffer = this.buffer;
+ this.active--;
+ if (buffer.length > 0) {
+ this._next(buffer.shift());
+ }
+ else if (this.active === 0 && this.hasCompleted) {
+ this.destination.complete();
+ }
+ };
+ return MergeMapSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START _mergeMap,_util_identity PURE_IMPORTS_END */
+function mergeAll(concurrent) {
+ if (concurrent === void 0) {
+ concurrent = Number.POSITIVE_INFINITY;
+ }
+ return mergeMap(identity, concurrent);
+}
+
+/** PURE_IMPORTS_START _mergeAll PURE_IMPORTS_END */
+function concatAll() {
+ return mergeAll(1);
+}
+
+/** PURE_IMPORTS_START _of,_operators_concatAll PURE_IMPORTS_END */
+function concat() {
+ var observables = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ observables[_i] = arguments[_i];
+ }
+ return concatAll()(of.apply(void 0, observables));
+}
+
+/** PURE_IMPORTS_START _Observable,_util_isArray,_util_isFunction,_operators_map PURE_IMPORTS_END */
+function fromEvent(target, eventName, options, resultSelector) {
+ if (isFunction(options)) {
+ resultSelector = options;
+ options = undefined;
+ }
+ if (resultSelector) {
+ return fromEvent(target, eventName, options).pipe(map(function (args) { return isArray$1(args) ? resultSelector.apply(void 0, args) : resultSelector(args); }));
+ }
+ return new Observable(function (subscriber) {
+ function handler(e) {
+ if (arguments.length > 1) {
+ subscriber.next(Array.prototype.slice.call(arguments));
+ }
+ else {
+ subscriber.next(e);
+ }
+ }
+ setupSubscription(target, eventName, handler, subscriber, options);
+ });
+}
+function setupSubscription(sourceObj, eventName, handler, subscriber, options) {
+ var unsubscribe;
+ if (isEventTarget(sourceObj)) {
+ var source_1 = sourceObj;
+ sourceObj.addEventListener(eventName, handler, options);
+ unsubscribe = function () { return source_1.removeEventListener(eventName, handler, options); };
+ }
+ else if (isJQueryStyleEventEmitter(sourceObj)) {
+ var source_2 = sourceObj;
+ sourceObj.on(eventName, handler);
+ unsubscribe = function () { return source_2.off(eventName, handler); };
+ }
+ else if (isNodeStyleEventEmitter(sourceObj)) {
+ var source_3 = sourceObj;
+ sourceObj.addListener(eventName, handler);
+ unsubscribe = function () { return source_3.removeListener(eventName, handler); };
+ }
+ else if (sourceObj && sourceObj.length) {
+ for (var i = 0, len = sourceObj.length; i < len; i++) {
+ setupSubscription(sourceObj[i], eventName, handler, subscriber, options);
+ }
+ }
+ else {
+ throw new TypeError('Invalid event target');
+ }
+ subscriber.add(unsubscribe);
+}
+function isNodeStyleEventEmitter(sourceObj) {
+ return sourceObj && typeof sourceObj.addListener === 'function' && typeof sourceObj.removeListener === 'function';
+}
+function isJQueryStyleEventEmitter(sourceObj) {
+ return sourceObj && typeof sourceObj.on === 'function' && typeof sourceObj.off === 'function';
+}
+function isEventTarget(sourceObj) {
+ return sourceObj && typeof sourceObj.addEventListener === 'function' && typeof sourceObj.removeEventListener === 'function';
+}
+
+/** PURE_IMPORTS_START _isArray PURE_IMPORTS_END */
+function isNumeric(val) {
+ return !isArray$1(val) && (val - parseFloat(val) + 1) >= 0;
+}
+
+/** PURE_IMPORTS_START _Observable,_util_isScheduler,_operators_mergeAll,_fromArray PURE_IMPORTS_END */
+function merge() {
+ var observables = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ observables[_i] = arguments[_i];
+ }
+ var concurrent = Number.POSITIVE_INFINITY;
+ var scheduler = null;
+ var last = observables[observables.length - 1];
+ if (isScheduler(last)) {
+ scheduler = observables.pop();
+ if (observables.length > 1 && typeof observables[observables.length - 1] === 'number') {
+ concurrent = observables.pop();
+ }
+ }
+ else if (typeof last === 'number') {
+ concurrent = observables.pop();
+ }
+ if (scheduler === null && observables.length === 1 && observables[0] instanceof Observable) {
+ return observables[0];
+ }
+ return mergeAll(concurrent)(fromArray(observables, scheduler));
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function filter(predicate, thisArg) {
+ return function filterOperatorFunction(source) {
+ return source.lift(new FilterOperator(predicate, thisArg));
+ };
+}
+var FilterOperator = /*@__PURE__*/ (function () {
+ function FilterOperator(predicate, thisArg) {
+ this.predicate = predicate;
+ this.thisArg = thisArg;
+ }
+ FilterOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new FilterSubscriber(subscriber, this.predicate, this.thisArg));
+ };
+ return FilterOperator;
+}());
+var FilterSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(FilterSubscriber, _super);
+ function FilterSubscriber(destination, predicate, thisArg) {
+ var _this = _super.call(this, destination) || this;
+ _this.predicate = predicate;
+ _this.thisArg = thisArg;
+ _this.count = 0;
+ return _this;
+ }
+ FilterSubscriber.prototype._next = function (value) {
+ var result;
+ try {
+ result = this.predicate.call(this.thisArg, value, this.count++);
+ }
+ catch (err) {
+ this.destination.error(err);
+ return;
+ }
+ if (result) {
+ this.destination.next(value);
+ }
+ };
+ return FilterSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _Observable,_scheduler_async,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */
+function timer(dueTime, periodOrScheduler, scheduler) {
+ if (dueTime === void 0) {
+ dueTime = 0;
+ }
+ var period = -1;
+ if (isNumeric(periodOrScheduler)) {
+ period = Number(periodOrScheduler) < 1 && 1 || Number(periodOrScheduler);
+ }
+ else if (isScheduler(periodOrScheduler)) {
+ scheduler = periodOrScheduler;
+ }
+ if (!isScheduler(scheduler)) {
+ scheduler = async;
+ }
+ return new Observable(function (subscriber) {
+ var due = isNumeric(dueTime)
+ ? dueTime
+ : (+dueTime - scheduler.now());
+ return scheduler.schedule(dispatch, due, {
+ index: 0, period: period, subscriber: subscriber
+ });
+ });
+}
+function dispatch(state) {
+ var index = state.index, period = state.period, subscriber = state.subscriber;
+ subscriber.next(index);
+ if (subscriber.closed) {
+ return;
+ }
+ else if (period === -1) {
+ return subscriber.complete();
+ }
+ state.index = index + 1;
+ this.schedule(state, period);
+}
+
+/** PURE_IMPORTS_START tslib,_fromArray,_util_isArray,_Subscriber,_.._internal_symbol_iterator,_innerSubscribe PURE_IMPORTS_END */
+function zip() {
+ var observables = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ observables[_i] = arguments[_i];
+ }
+ var resultSelector = observables[observables.length - 1];
+ if (typeof resultSelector === 'function') {
+ observables.pop();
+ }
+ return fromArray(observables, undefined).lift(new ZipOperator(resultSelector));
+}
+var ZipOperator = /*@__PURE__*/ (function () {
+ function ZipOperator(resultSelector) {
+ this.resultSelector = resultSelector;
+ }
+ ZipOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new ZipSubscriber(subscriber, this.resultSelector));
+ };
+ return ZipOperator;
+}());
+var ZipSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(ZipSubscriber, _super);
+ function ZipSubscriber(destination, resultSelector, values) {
+ var _this = _super.call(this, destination) || this;
+ _this.resultSelector = resultSelector;
+ _this.iterators = [];
+ _this.active = 0;
+ _this.resultSelector = (typeof resultSelector === 'function') ? resultSelector : undefined;
+ return _this;
+ }
+ ZipSubscriber.prototype._next = function (value) {
+ var iterators = this.iterators;
+ if (isArray$1(value)) {
+ iterators.push(new StaticArrayIterator(value));
+ }
+ else if (typeof value[iterator] === 'function') {
+ iterators.push(new StaticIterator(value[iterator]()));
+ }
+ else {
+ iterators.push(new ZipBufferIterator(this.destination, this, value));
+ }
+ };
+ ZipSubscriber.prototype._complete = function () {
+ var iterators = this.iterators;
+ var len = iterators.length;
+ this.unsubscribe();
+ if (len === 0) {
+ this.destination.complete();
+ return;
+ }
+ this.active = len;
+ for (var i = 0; i < len; i++) {
+ var iterator = iterators[i];
+ if (iterator.stillUnsubscribed) {
+ var destination = this.destination;
+ destination.add(iterator.subscribe());
+ }
+ else {
+ this.active--;
+ }
+ }
+ };
+ ZipSubscriber.prototype.notifyInactive = function () {
+ this.active--;
+ if (this.active === 0) {
+ this.destination.complete();
+ }
+ };
+ ZipSubscriber.prototype.checkIterators = function () {
+ var iterators = this.iterators;
+ var len = iterators.length;
+ var destination = this.destination;
+ for (var i = 0; i < len; i++) {
+ var iterator = iterators[i];
+ if (typeof iterator.hasValue === 'function' && !iterator.hasValue()) {
+ return;
+ }
+ }
+ var shouldComplete = false;
+ var args = [];
+ for (var i = 0; i < len; i++) {
+ var iterator = iterators[i];
+ var result = iterator.next();
+ if (iterator.hasCompleted()) {
+ shouldComplete = true;
+ }
+ if (result.done) {
+ destination.complete();
+ return;
+ }
+ args.push(result.value);
+ }
+ if (this.resultSelector) {
+ this._tryresultSelector(args);
+ }
+ else {
+ destination.next(args);
+ }
+ if (shouldComplete) {
+ destination.complete();
+ }
+ };
+ ZipSubscriber.prototype._tryresultSelector = function (args) {
+ var result;
+ try {
+ result = this.resultSelector.apply(this, args);
+ }
+ catch (err) {
+ this.destination.error(err);
+ return;
+ }
+ this.destination.next(result);
+ };
+ return ZipSubscriber;
+}(Subscriber));
+var StaticIterator = /*@__PURE__*/ (function () {
+ function StaticIterator(iterator) {
+ this.iterator = iterator;
+ this.nextResult = iterator.next();
+ }
+ StaticIterator.prototype.hasValue = function () {
+ return true;
+ };
+ StaticIterator.prototype.next = function () {
+ var result = this.nextResult;
+ this.nextResult = this.iterator.next();
+ return result;
+ };
+ StaticIterator.prototype.hasCompleted = function () {
+ var nextResult = this.nextResult;
+ return Boolean(nextResult && nextResult.done);
+ };
+ return StaticIterator;
+}());
+var StaticArrayIterator = /*@__PURE__*/ (function () {
+ function StaticArrayIterator(array) {
+ this.array = array;
+ this.index = 0;
+ this.length = 0;
+ this.length = array.length;
+ }
+ StaticArrayIterator.prototype[iterator] = function () {
+ return this;
+ };
+ StaticArrayIterator.prototype.next = function (value) {
+ var i = this.index++;
+ var array = this.array;
+ return i < this.length ? { value: array[i], done: false } : { value: null, done: true };
+ };
+ StaticArrayIterator.prototype.hasValue = function () {
+ return this.array.length > this.index;
+ };
+ StaticArrayIterator.prototype.hasCompleted = function () {
+ return this.array.length === this.index;
+ };
+ return StaticArrayIterator;
+}());
+var ZipBufferIterator = /*@__PURE__*/ (function (_super) {
+ __extends(ZipBufferIterator, _super);
+ function ZipBufferIterator(destination, parent, observable) {
+ var _this = _super.call(this, destination) || this;
+ _this.parent = parent;
+ _this.observable = observable;
+ _this.stillUnsubscribed = true;
+ _this.buffer = [];
+ _this.isComplete = false;
+ return _this;
+ }
+ ZipBufferIterator.prototype[iterator] = function () {
+ return this;
+ };
+ ZipBufferIterator.prototype.next = function () {
+ var buffer = this.buffer;
+ if (buffer.length === 0 && this.isComplete) {
+ return { value: null, done: true };
+ }
+ else {
+ return { value: buffer.shift(), done: false };
+ }
+ };
+ ZipBufferIterator.prototype.hasValue = function () {
+ return this.buffer.length > 0;
+ };
+ ZipBufferIterator.prototype.hasCompleted = function () {
+ return this.buffer.length === 0 && this.isComplete;
+ };
+ ZipBufferIterator.prototype.notifyComplete = function () {
+ if (this.buffer.length > 0) {
+ this.isComplete = true;
+ this.parent.notifyInactive();
+ }
+ else {
+ this.destination.complete();
+ }
+ };
+ ZipBufferIterator.prototype.notifyNext = function (innerValue) {
+ this.buffer.push(innerValue);
+ this.parent.checkIterators();
+ };
+ ZipBufferIterator.prototype.subscribe = function () {
+ return innerSubscribe(this.observable, new SimpleInnerSubscriber(this));
+ };
+ return ZipBufferIterator;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */
+function audit(durationSelector) {
+ return function auditOperatorFunction(source) {
+ return source.lift(new AuditOperator(durationSelector));
+ };
+}
+var AuditOperator = /*@__PURE__*/ (function () {
+ function AuditOperator(durationSelector) {
+ this.durationSelector = durationSelector;
+ }
+ AuditOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new AuditSubscriber(subscriber, this.durationSelector));
+ };
+ return AuditOperator;
+}());
+var AuditSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(AuditSubscriber, _super);
+ function AuditSubscriber(destination, durationSelector) {
+ var _this = _super.call(this, destination) || this;
+ _this.durationSelector = durationSelector;
+ _this.hasValue = false;
+ return _this;
+ }
+ AuditSubscriber.prototype._next = function (value) {
+ this.value = value;
+ this.hasValue = true;
+ if (!this.throttled) {
+ var duration = void 0;
+ try {
+ var durationSelector = this.durationSelector;
+ duration = durationSelector(value);
+ }
+ catch (err) {
+ return this.destination.error(err);
+ }
+ var innerSubscription = innerSubscribe(duration, new SimpleInnerSubscriber(this));
+ if (!innerSubscription || innerSubscription.closed) {
+ this.clearThrottle();
+ }
+ else {
+ this.add(this.throttled = innerSubscription);
+ }
+ }
+ };
+ AuditSubscriber.prototype.clearThrottle = function () {
+ var _a = this, value = _a.value, hasValue = _a.hasValue, throttled = _a.throttled;
+ if (throttled) {
+ this.remove(throttled);
+ this.throttled = undefined;
+ throttled.unsubscribe();
+ }
+ if (hasValue) {
+ this.value = undefined;
+ this.hasValue = false;
+ this.destination.next(value);
+ }
+ };
+ AuditSubscriber.prototype.notifyNext = function () {
+ this.clearThrottle();
+ };
+ AuditSubscriber.prototype.notifyComplete = function () {
+ this.clearThrottle();
+ };
+ return AuditSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */
+function auditTime(duration, scheduler) {
+ if (scheduler === void 0) {
+ scheduler = async;
+ }
+ return audit(function () { return timer(duration, scheduler); });
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function bufferCount(bufferSize, startBufferEvery) {
+ if (startBufferEvery === void 0) {
+ startBufferEvery = null;
+ }
+ return function bufferCountOperatorFunction(source) {
+ return source.lift(new BufferCountOperator(bufferSize, startBufferEvery));
+ };
+}
+var BufferCountOperator = /*@__PURE__*/ (function () {
+ function BufferCountOperator(bufferSize, startBufferEvery) {
+ this.bufferSize = bufferSize;
+ this.startBufferEvery = startBufferEvery;
+ if (!startBufferEvery || bufferSize === startBufferEvery) {
+ this.subscriberClass = BufferCountSubscriber;
+ }
+ else {
+ this.subscriberClass = BufferSkipCountSubscriber;
+ }
+ }
+ BufferCountOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new this.subscriberClass(subscriber, this.bufferSize, this.startBufferEvery));
+ };
+ return BufferCountOperator;
+}());
+var BufferCountSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(BufferCountSubscriber, _super);
+ function BufferCountSubscriber(destination, bufferSize) {
+ var _this = _super.call(this, destination) || this;
+ _this.bufferSize = bufferSize;
+ _this.buffer = [];
+ return _this;
+ }
+ BufferCountSubscriber.prototype._next = function (value) {
+ var buffer = this.buffer;
+ buffer.push(value);
+ if (buffer.length == this.bufferSize) {
+ this.destination.next(buffer);
+ this.buffer = [];
+ }
+ };
+ BufferCountSubscriber.prototype._complete = function () {
+ var buffer = this.buffer;
+ if (buffer.length > 0) {
+ this.destination.next(buffer);
+ }
+ _super.prototype._complete.call(this);
+ };
+ return BufferCountSubscriber;
+}(Subscriber));
+var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(BufferSkipCountSubscriber, _super);
+ function BufferSkipCountSubscriber(destination, bufferSize, startBufferEvery) {
+ var _this = _super.call(this, destination) || this;
+ _this.bufferSize = bufferSize;
+ _this.startBufferEvery = startBufferEvery;
+ _this.buffers = [];
+ _this.count = 0;
+ return _this;
+ }
+ BufferSkipCountSubscriber.prototype._next = function (value) {
+ var _a = this, bufferSize = _a.bufferSize, startBufferEvery = _a.startBufferEvery, buffers = _a.buffers, count = _a.count;
+ this.count++;
+ if (count % startBufferEvery === 0) {
+ buffers.push([]);
+ }
+ for (var i = buffers.length; i--;) {
+ var buffer = buffers[i];
+ buffer.push(value);
+ if (buffer.length === bufferSize) {
+ buffers.splice(i, 1);
+ this.destination.next(buffer);
+ }
+ }
+ };
+ BufferSkipCountSubscriber.prototype._complete = function () {
+ var _a = this, buffers = _a.buffers, destination = _a.destination;
+ while (buffers.length > 0) {
+ var buffer = buffers.shift();
+ if (buffer.length > 0) {
+ destination.next(buffer);
+ }
+ }
+ _super.prototype._complete.call(this);
+ };
+ return BufferSkipCountSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscription,_innerSubscribe PURE_IMPORTS_END */
+function bufferWhen(closingSelector) {
+ return function (source) {
+ return source.lift(new BufferWhenOperator(closingSelector));
+ };
+}
+var BufferWhenOperator = /*@__PURE__*/ (function () {
+ function BufferWhenOperator(closingSelector) {
+ this.closingSelector = closingSelector;
+ }
+ BufferWhenOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new BufferWhenSubscriber(subscriber, this.closingSelector));
+ };
+ return BufferWhenOperator;
+}());
+var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(BufferWhenSubscriber, _super);
+ function BufferWhenSubscriber(destination, closingSelector) {
+ var _this = _super.call(this, destination) || this;
+ _this.closingSelector = closingSelector;
+ _this.subscribing = false;
+ _this.openBuffer();
+ return _this;
+ }
+ BufferWhenSubscriber.prototype._next = function (value) {
+ this.buffer.push(value);
+ };
+ BufferWhenSubscriber.prototype._complete = function () {
+ var buffer = this.buffer;
+ if (buffer) {
+ this.destination.next(buffer);
+ }
+ _super.prototype._complete.call(this);
+ };
+ BufferWhenSubscriber.prototype._unsubscribe = function () {
+ this.buffer = undefined;
+ this.subscribing = false;
+ };
+ BufferWhenSubscriber.prototype.notifyNext = function () {
+ this.openBuffer();
+ };
+ BufferWhenSubscriber.prototype.notifyComplete = function () {
+ if (this.subscribing) {
+ this.complete();
+ }
+ else {
+ this.openBuffer();
+ }
+ };
+ BufferWhenSubscriber.prototype.openBuffer = function () {
+ var closingSubscription = this.closingSubscription;
+ if (closingSubscription) {
+ this.remove(closingSubscription);
+ closingSubscription.unsubscribe();
+ }
+ var buffer = this.buffer;
+ if (this.buffer) {
+ this.destination.next(buffer);
+ }
+ this.buffer = [];
+ var closingNotifier;
+ try {
+ var closingSelector = this.closingSelector;
+ closingNotifier = closingSelector();
+ }
+ catch (err) {
+ return this.error(err);
+ }
+ closingSubscription = new Subscription();
+ this.closingSubscription = closingSubscription;
+ this.add(closingSubscription);
+ this.subscribing = true;
+ closingSubscription.add(innerSubscribe(closingNotifier, new SimpleInnerSubscriber(this)));
+ this.subscribing = false;
+ };
+ return BufferWhenSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */
+function catchError(selector) {
+ return function catchErrorOperatorFunction(source) {
+ var operator = new CatchOperator(selector);
+ var caught = source.lift(operator);
+ return (operator.caught = caught);
+ };
+}
+var CatchOperator = /*@__PURE__*/ (function () {
+ function CatchOperator(selector) {
+ this.selector = selector;
+ }
+ CatchOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new CatchSubscriber(subscriber, this.selector, this.caught));
+ };
+ return CatchOperator;
+}());
+var CatchSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(CatchSubscriber, _super);
+ function CatchSubscriber(destination, selector, caught) {
+ var _this = _super.call(this, destination) || this;
+ _this.selector = selector;
+ _this.caught = caught;
+ return _this;
+ }
+ CatchSubscriber.prototype.error = function (err) {
+ if (!this.isStopped) {
+ var result = void 0;
+ try {
+ result = this.selector(err, this.caught);
+ }
+ catch (err2) {
+ _super.prototype.error.call(this, err2);
+ return;
+ }
+ this._unsubscribeAndRecycle();
+ var innerSubscriber = new SimpleInnerSubscriber(this);
+ this.add(innerSubscriber);
+ var innerSubscription = innerSubscribe(result, innerSubscriber);
+ if (innerSubscription !== innerSubscriber) {
+ this.add(innerSubscription);
+ }
+ }
+ };
+ return CatchSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */
+function concatMap(project, resultSelector) {
+ return mergeMap(project, resultSelector, 1);
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */
+function debounceTime(dueTime, scheduler) {
+ if (scheduler === void 0) {
+ scheduler = async;
+ }
+ return function (source) { return source.lift(new DebounceTimeOperator(dueTime, scheduler)); };
+}
+var DebounceTimeOperator = /*@__PURE__*/ (function () {
+ function DebounceTimeOperator(dueTime, scheduler) {
+ this.dueTime = dueTime;
+ this.scheduler = scheduler;
+ }
+ DebounceTimeOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new DebounceTimeSubscriber(subscriber, this.dueTime, this.scheduler));
+ };
+ return DebounceTimeOperator;
+}());
+var DebounceTimeSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(DebounceTimeSubscriber, _super);
+ function DebounceTimeSubscriber(destination, dueTime, scheduler) {
+ var _this = _super.call(this, destination) || this;
+ _this.dueTime = dueTime;
+ _this.scheduler = scheduler;
+ _this.debouncedSubscription = null;
+ _this.lastValue = null;
+ _this.hasValue = false;
+ return _this;
+ }
+ DebounceTimeSubscriber.prototype._next = function (value) {
+ this.clearDebounce();
+ this.lastValue = value;
+ this.hasValue = true;
+ this.add(this.debouncedSubscription = this.scheduler.schedule(dispatchNext, this.dueTime, this));
+ };
+ DebounceTimeSubscriber.prototype._complete = function () {
+ this.debouncedNext();
+ this.destination.complete();
+ };
+ DebounceTimeSubscriber.prototype.debouncedNext = function () {
+ this.clearDebounce();
+ if (this.hasValue) {
+ var lastValue = this.lastValue;
+ this.lastValue = null;
+ this.hasValue = false;
+ this.destination.next(lastValue);
+ }
+ };
+ DebounceTimeSubscriber.prototype.clearDebounce = function () {
+ var debouncedSubscription = this.debouncedSubscription;
+ if (debouncedSubscription !== null) {
+ this.remove(debouncedSubscription);
+ debouncedSubscription.unsubscribe();
+ this.debouncedSubscription = null;
+ }
+ };
+ return DebounceTimeSubscriber;
+}(Subscriber));
+function dispatchNext(subscriber) {
+ subscriber.debouncedNext();
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function defaultIfEmpty(defaultValue) {
+ if (defaultValue === void 0) {
+ defaultValue = null;
+ }
+ return function (source) { return source.lift(new DefaultIfEmptyOperator(defaultValue)); };
+}
+var DefaultIfEmptyOperator = /*@__PURE__*/ (function () {
+ function DefaultIfEmptyOperator(defaultValue) {
+ this.defaultValue = defaultValue;
+ }
+ DefaultIfEmptyOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new DefaultIfEmptySubscriber(subscriber, this.defaultValue));
+ };
+ return DefaultIfEmptyOperator;
+}());
+var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(DefaultIfEmptySubscriber, _super);
+ function DefaultIfEmptySubscriber(destination, defaultValue) {
+ var _this = _super.call(this, destination) || this;
+ _this.defaultValue = defaultValue;
+ _this.isEmpty = true;
+ return _this;
+ }
+ DefaultIfEmptySubscriber.prototype._next = function (value) {
+ this.isEmpty = false;
+ this.destination.next(value);
+ };
+ DefaultIfEmptySubscriber.prototype._complete = function () {
+ if (this.isEmpty) {
+ this.destination.next(this.defaultValue);
+ }
+ this.destination.complete();
+ };
+ return DefaultIfEmptySubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START PURE_IMPORTS_END */
+function isDate(value) {
+ return value instanceof Date && !isNaN(+value);
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function distinctUntilChanged(compare, keySelector) {
+ return function (source) { return source.lift(new DistinctUntilChangedOperator(compare, keySelector)); };
+}
+var DistinctUntilChangedOperator = /*@__PURE__*/ (function () {
+ function DistinctUntilChangedOperator(compare, keySelector) {
+ this.compare = compare;
+ this.keySelector = keySelector;
+ }
+ DistinctUntilChangedOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new DistinctUntilChangedSubscriber(subscriber, this.compare, this.keySelector));
+ };
+ return DistinctUntilChangedOperator;
+}());
+var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(DistinctUntilChangedSubscriber, _super);
+ function DistinctUntilChangedSubscriber(destination, compare, keySelector) {
+ var _this = _super.call(this, destination) || this;
+ _this.keySelector = keySelector;
+ _this.hasKey = false;
+ if (typeof compare === 'function') {
+ _this.compare = compare;
+ }
+ return _this;
+ }
+ DistinctUntilChangedSubscriber.prototype.compare = function (x, y) {
+ return x === y;
+ };
+ DistinctUntilChangedSubscriber.prototype._next = function (value) {
+ var key;
+ try {
+ var keySelector = this.keySelector;
+ key = keySelector ? keySelector(value) : value;
+ }
+ catch (err) {
+ return this.destination.error(err);
+ }
+ var result = false;
+ if (this.hasKey) {
+ try {
+ var compare = this.compare;
+ result = compare(this.key, key);
+ }
+ catch (err) {
+ return this.destination.error(err);
+ }
+ }
+ else {
+ this.hasKey = true;
+ }
+ if (!result) {
+ this.key = key;
+ this.destination.next(value);
+ }
+ };
+ return DistinctUntilChangedSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_util_EmptyError,_Subscriber PURE_IMPORTS_END */
+function throwIfEmpty(errorFactory) {
+ if (errorFactory === void 0) {
+ errorFactory = defaultErrorFactory;
+ }
+ return function (source) {
+ return source.lift(new ThrowIfEmptyOperator(errorFactory));
+ };
+}
+var ThrowIfEmptyOperator = /*@__PURE__*/ (function () {
+ function ThrowIfEmptyOperator(errorFactory) {
+ this.errorFactory = errorFactory;
+ }
+ ThrowIfEmptyOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new ThrowIfEmptySubscriber(subscriber, this.errorFactory));
+ };
+ return ThrowIfEmptyOperator;
+}());
+var ThrowIfEmptySubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(ThrowIfEmptySubscriber, _super);
+ function ThrowIfEmptySubscriber(destination, errorFactory) {
+ var _this = _super.call(this, destination) || this;
+ _this.errorFactory = errorFactory;
+ _this.hasValue = false;
+ return _this;
+ }
+ ThrowIfEmptySubscriber.prototype._next = function (value) {
+ this.hasValue = true;
+ this.destination.next(value);
+ };
+ ThrowIfEmptySubscriber.prototype._complete = function () {
+ if (!this.hasValue) {
+ var err = void 0;
+ try {
+ err = this.errorFactory();
+ }
+ catch (e) {
+ err = e;
+ }
+ this.destination.error(err);
+ }
+ else {
+ return this.destination.complete();
+ }
+ };
+ return ThrowIfEmptySubscriber;
+}(Subscriber));
+function defaultErrorFactory() {
+ return new EmptyError();
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */
+function take(count) {
+ return function (source) {
+ if (count === 0) {
+ return empty();
+ }
+ else {
+ return source.lift(new TakeOperator(count));
+ }
+ };
+}
+var TakeOperator = /*@__PURE__*/ (function () {
+ function TakeOperator(total) {
+ this.total = total;
+ if (this.total < 0) {
+ throw new ArgumentOutOfRangeError;
+ }
+ }
+ TakeOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new TakeSubscriber(subscriber, this.total));
+ };
+ return TakeOperator;
+}());
+var TakeSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(TakeSubscriber, _super);
+ function TakeSubscriber(destination, total) {
+ var _this = _super.call(this, destination) || this;
+ _this.total = total;
+ _this.count = 0;
+ return _this;
+ }
+ TakeSubscriber.prototype._next = function (value) {
+ var total = this.total;
+ var count = ++this.count;
+ if (count <= total) {
+ this.destination.next(value);
+ if (count === total) {
+ this.destination.complete();
+ this.unsubscribe();
+ }
+ }
+ };
+ return TakeSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */
+function expand(project, concurrent, scheduler) {
+ if (concurrent === void 0) {
+ concurrent = Number.POSITIVE_INFINITY;
+ }
+ concurrent = (concurrent || 0) < 1 ? Number.POSITIVE_INFINITY : concurrent;
+ return function (source) { return source.lift(new ExpandOperator(project, concurrent, scheduler)); };
+}
+var ExpandOperator = /*@__PURE__*/ (function () {
+ function ExpandOperator(project, concurrent, scheduler) {
+ this.project = project;
+ this.concurrent = concurrent;
+ this.scheduler = scheduler;
+ }
+ ExpandOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new ExpandSubscriber(subscriber, this.project, this.concurrent, this.scheduler));
+ };
+ return ExpandOperator;
+}());
+var ExpandSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(ExpandSubscriber, _super);
+ function ExpandSubscriber(destination, project, concurrent, scheduler) {
+ var _this = _super.call(this, destination) || this;
+ _this.project = project;
+ _this.concurrent = concurrent;
+ _this.scheduler = scheduler;
+ _this.index = 0;
+ _this.active = 0;
+ _this.hasCompleted = false;
+ if (concurrent < Number.POSITIVE_INFINITY) {
+ _this.buffer = [];
+ }
+ return _this;
+ }
+ ExpandSubscriber.dispatch = function (arg) {
+ var subscriber = arg.subscriber, result = arg.result, value = arg.value, index = arg.index;
+ subscriber.subscribeToProjection(result, value, index);
+ };
+ ExpandSubscriber.prototype._next = function (value) {
+ var destination = this.destination;
+ if (destination.closed) {
+ this._complete();
+ return;
+ }
+ var index = this.index++;
+ if (this.active < this.concurrent) {
+ destination.next(value);
+ try {
+ var project = this.project;
+ var result = project(value, index);
+ if (!this.scheduler) {
+ this.subscribeToProjection(result, value, index);
+ }
+ else {
+ var state = { subscriber: this, result: result, value: value, index: index };
+ var destination_1 = this.destination;
+ destination_1.add(this.scheduler.schedule(ExpandSubscriber.dispatch, 0, state));
+ }
+ }
+ catch (e) {
+ destination.error(e);
+ }
+ }
+ else {
+ this.buffer.push(value);
+ }
+ };
+ ExpandSubscriber.prototype.subscribeToProjection = function (result, value, index) {
+ this.active++;
+ var destination = this.destination;
+ destination.add(innerSubscribe(result, new SimpleInnerSubscriber(this)));
+ };
+ ExpandSubscriber.prototype._complete = function () {
+ this.hasCompleted = true;
+ if (this.hasCompleted && this.active === 0) {
+ this.destination.complete();
+ }
+ this.unsubscribe();
+ };
+ ExpandSubscriber.prototype.notifyNext = function (innerValue) {
+ this._next(innerValue);
+ };
+ ExpandSubscriber.prototype.notifyComplete = function () {
+ var buffer = this.buffer;
+ this.active--;
+ if (buffer && buffer.length > 0) {
+ this._next(buffer.shift());
+ }
+ if (this.hasCompleted && this.active === 0) {
+ this.destination.complete();
+ }
+ };
+ return ExpandSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_Subscription PURE_IMPORTS_END */
+function finalize(callback) {
+ return function (source) { return source.lift(new FinallyOperator(callback)); };
+}
+var FinallyOperator = /*@__PURE__*/ (function () {
+ function FinallyOperator(callback) {
+ this.callback = callback;
+ }
+ FinallyOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new FinallySubscriber(subscriber, this.callback));
+ };
+ return FinallyOperator;
+}());
+var FinallySubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(FinallySubscriber, _super);
+ function FinallySubscriber(destination, callback) {
+ var _this = _super.call(this, destination) || this;
+ _this.add(new Subscription(callback));
+ return _this;
+ }
+ return FinallySubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */
+function first(predicate, defaultValue) {
+ var hasDefaultValue = arguments.length >= 2;
+ return function (source) { return source.pipe(predicate ? filter(function (v, i) { return predicate(v, i, source); }) : identity, take(1), hasDefaultValue ? defaultIfEmpty(defaultValue) : throwIfEmpty(function () { return new EmptyError(); })); };
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */
+function takeLast(count) {
+ return function takeLastOperatorFunction(source) {
+ if (count === 0) {
+ return empty();
+ }
+ else {
+ return source.lift(new TakeLastOperator(count));
+ }
+ };
+}
+var TakeLastOperator = /*@__PURE__*/ (function () {
+ function TakeLastOperator(total) {
+ this.total = total;
+ if (this.total < 0) {
+ throw new ArgumentOutOfRangeError;
+ }
+ }
+ TakeLastOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new TakeLastSubscriber(subscriber, this.total));
+ };
+ return TakeLastOperator;
+}());
+var TakeLastSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(TakeLastSubscriber, _super);
+ function TakeLastSubscriber(destination, total) {
+ var _this = _super.call(this, destination) || this;
+ _this.total = total;
+ _this.ring = new Array();
+ _this.count = 0;
+ return _this;
+ }
+ TakeLastSubscriber.prototype._next = function (value) {
+ var ring = this.ring;
+ var total = this.total;
+ var count = this.count++;
+ if (ring.length < total) {
+ ring.push(value);
+ }
+ else {
+ var index = count % total;
+ ring[index] = value;
+ }
+ };
+ TakeLastSubscriber.prototype._complete = function () {
+ var destination = this.destination;
+ var count = this.count;
+ if (count > 0) {
+ var total = this.count >= this.total ? this.total : this.count;
+ var ring = this.ring;
+ for (var i = 0; i < total; i++) {
+ var idx = (count++) % total;
+ destination.next(ring[idx]);
+ }
+ }
+ destination.complete();
+ };
+ return TakeLastSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */
+function last(predicate, defaultValue) {
+ var hasDefaultValue = arguments.length >= 2;
+ return function (source) { return source.pipe(predicate ? filter(function (v, i) { return predicate(v, i, source); }) : identity, takeLast(1), hasDefaultValue ? defaultIfEmpty(defaultValue) : throwIfEmpty(function () { return new EmptyError(); })); };
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function scan(accumulator, seed) {
+ var hasSeed = false;
+ if (arguments.length >= 2) {
+ hasSeed = true;
+ }
+ return function scanOperatorFunction(source) {
+ return source.lift(new ScanOperator(accumulator, seed, hasSeed));
+ };
+}
+var ScanOperator = /*@__PURE__*/ (function () {
+ function ScanOperator(accumulator, seed, hasSeed) {
+ if (hasSeed === void 0) {
+ hasSeed = false;
+ }
+ this.accumulator = accumulator;
+ this.seed = seed;
+ this.hasSeed = hasSeed;
+ }
+ ScanOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new ScanSubscriber(subscriber, this.accumulator, this.seed, this.hasSeed));
+ };
+ return ScanOperator;
+}());
+var ScanSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(ScanSubscriber, _super);
+ function ScanSubscriber(destination, accumulator, _seed, hasSeed) {
+ var _this = _super.call(this, destination) || this;
+ _this.accumulator = accumulator;
+ _this._seed = _seed;
+ _this.hasSeed = hasSeed;
+ _this.index = 0;
+ return _this;
+ }
+ Object.defineProperty(ScanSubscriber.prototype, "seed", {
+ get: function () {
+ return this._seed;
+ },
+ set: function (value) {
+ this.hasSeed = true;
+ this._seed = value;
+ },
+ enumerable: true,
+ configurable: true
+ });
+ ScanSubscriber.prototype._next = function (value) {
+ if (!this.hasSeed) {
+ this.seed = value;
+ this.destination.next(value);
+ }
+ else {
+ return this._tryNext(value);
+ }
+ };
+ ScanSubscriber.prototype._tryNext = function (value) {
+ var index = this.index++;
+ var result;
+ try {
+ result = this.accumulator(this.seed, value, index);
+ }
+ catch (err) {
+ this.destination.error(err);
+ }
+ this.seed = result;
+ this.destination.next(result);
+ };
+ return ScanSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */
+function reduce(accumulator, seed) {
+ if (arguments.length >= 2) {
+ return function reduceOperatorFunctionWithSeed(source) {
+ return pipe(scan(accumulator, seed), takeLast(1), defaultIfEmpty(seed))(source);
+ };
+ }
+ return function reduceOperatorFunction(source) {
+ return pipe(scan(function (acc, value, index) { return accumulator(acc, value, index + 1); }), takeLast(1))(source);
+ };
+}
+
+/** PURE_IMPORTS_START _observable_ConnectableObservable PURE_IMPORTS_END */
+function multicast(subjectOrSubjectFactory, selector) {
+ return function multicastOperatorFunction(source) {
+ var subjectFactory;
+ if (typeof subjectOrSubjectFactory === 'function') {
+ subjectFactory = subjectOrSubjectFactory;
+ }
+ else {
+ subjectFactory = function subjectFactory() {
+ return subjectOrSubjectFactory;
+ };
+ }
+ if (typeof selector === 'function') {
+ return source.lift(new MulticastOperator(subjectFactory, selector));
+ }
+ var connectable = Object.create(source, connectableObservableDescriptor);
+ connectable.source = source;
+ connectable.subjectFactory = subjectFactory;
+ return connectable;
+ };
+}
+var MulticastOperator = /*@__PURE__*/ (function () {
+ function MulticastOperator(subjectFactory, selector) {
+ this.subjectFactory = subjectFactory;
+ this.selector = selector;
+ }
+ MulticastOperator.prototype.call = function (subscriber, source) {
+ var selector = this.selector;
+ var subject = this.subjectFactory();
+ var subscription = selector(subject).subscribe(subscriber);
+ subscription.add(source.subscribe(subject));
+ return subscription;
+ };
+ return MulticastOperator;
+}());
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function pairwise() {
+ return function (source) { return source.lift(new PairwiseOperator()); };
+}
+var PairwiseOperator = /*@__PURE__*/ (function () {
+ function PairwiseOperator() {
+ }
+ PairwiseOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new PairwiseSubscriber(subscriber));
+ };
+ return PairwiseOperator;
+}());
+var PairwiseSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(PairwiseSubscriber, _super);
+ function PairwiseSubscriber(destination) {
+ var _this = _super.call(this, destination) || this;
+ _this.hasPrev = false;
+ return _this;
+ }
+ PairwiseSubscriber.prototype._next = function (value) {
+ var pair;
+ if (this.hasPrev) {
+ pair = [this.prev, value];
+ }
+ else {
+ this.hasPrev = true;
+ }
+ this.prev = value;
+ if (pair) {
+ this.destination.next(pair);
+ }
+ };
+ return PairwiseSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _map PURE_IMPORTS_END */
+function pluck() {
+ var properties = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ properties[_i] = arguments[_i];
+ }
+ var length = properties.length;
+ if (length === 0) {
+ throw new Error('list of properties cannot be empty.');
+ }
+ return function (source) { return map(plucker(properties, length))(source); };
+}
+function plucker(props, length) {
+ var mapper = function (x) {
+ var currentProp = x;
+ for (var i = 0; i < length; i++) {
+ var p = currentProp != null ? currentProp[props[i]] : undefined;
+ if (p !== void 0) {
+ currentProp = p;
+ }
+ else {
+ return undefined;
+ }
+ }
+ return currentProp;
+ };
+ return mapper;
+}
+
+/** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */
+function publish(selector) {
+ return selector ?
+ multicast(function () { return new Subject(); }, selector) :
+ multicast(new Subject());
+}
+
+/** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */
+function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) {
+ if (selectorOrScheduler && typeof selectorOrScheduler !== 'function') {
+ scheduler = selectorOrScheduler;
+ }
+ var selector = typeof selectorOrScheduler === 'function' ? selectorOrScheduler : undefined;
+ var subject = new ReplaySubject(bufferSize, windowTime, scheduler);
+ return function (source) { return multicast(function () { return subject; }, selector)(source); };
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function retry(count) {
+ if (count === void 0) {
+ count = -1;
+ }
+ return function (source) { return source.lift(new RetryOperator(count, source)); };
+}
+var RetryOperator = /*@__PURE__*/ (function () {
+ function RetryOperator(count, source) {
+ this.count = count;
+ this.source = source;
+ }
+ RetryOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new RetrySubscriber(subscriber, this.count, this.source));
+ };
+ return RetryOperator;
+}());
+var RetrySubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(RetrySubscriber, _super);
+ function RetrySubscriber(destination, count, source) {
+ var _this = _super.call(this, destination) || this;
+ _this.count = count;
+ _this.source = source;
+ return _this;
+ }
+ RetrySubscriber.prototype.error = function (err) {
+ if (!this.isStopped) {
+ var _a = this, source = _a.source, count = _a.count;
+ if (count === 0) {
+ return _super.prototype.error.call(this, err);
+ }
+ else if (count > -1) {
+ this.count = count - 1;
+ }
+ source.subscribe(this._unsubscribeAndRecycle());
+ }
+ };
+ return RetrySubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */
+function sample(notifier) {
+ return function (source) { return source.lift(new SampleOperator(notifier)); };
+}
+var SampleOperator = /*@__PURE__*/ (function () {
+ function SampleOperator(notifier) {
+ this.notifier = notifier;
+ }
+ SampleOperator.prototype.call = function (subscriber, source) {
+ var sampleSubscriber = new SampleSubscriber(subscriber);
+ var subscription = source.subscribe(sampleSubscriber);
+ subscription.add(innerSubscribe(this.notifier, new SimpleInnerSubscriber(sampleSubscriber)));
+ return subscription;
+ };
+ return SampleOperator;
+}());
+var SampleSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(SampleSubscriber, _super);
+ function SampleSubscriber() {
+ var _this = _super !== null && _super.apply(this, arguments) || this;
+ _this.hasValue = false;
+ return _this;
+ }
+ SampleSubscriber.prototype._next = function (value) {
+ this.value = value;
+ this.hasValue = true;
+ };
+ SampleSubscriber.prototype.notifyNext = function () {
+ this.emitValue();
+ };
+ SampleSubscriber.prototype.notifyComplete = function () {
+ this.emitValue();
+ };
+ SampleSubscriber.prototype.emitValue = function () {
+ if (this.hasValue) {
+ this.hasValue = false;
+ this.destination.next(this.value);
+ }
+ };
+ return SampleSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */
+function shareSubjectFactory() {
+ return new Subject();
+}
+function share() {
+ return function (source) { return refCount()(multicast(shareSubjectFactory)(source)); };
+}
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function skip(count) {
+ return function (source) { return source.lift(new SkipOperator(count)); };
+}
+var SkipOperator = /*@__PURE__*/ (function () {
+ function SkipOperator(total) {
+ this.total = total;
+ }
+ SkipOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new SkipSubscriber(subscriber, this.total));
+ };
+ return SkipOperator;
+}());
+var SkipSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(SkipSubscriber, _super);
+ function SkipSubscriber(destination, total) {
+ var _this = _super.call(this, destination) || this;
+ _this.total = total;
+ _this.count = 0;
+ return _this;
+ }
+ SkipSubscriber.prototype._next = function (x) {
+ if (++this.count > this.total) {
+ this.destination.next(x);
+ }
+ };
+ return SkipSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function skipWhile(predicate) {
+ return function (source) { return source.lift(new SkipWhileOperator(predicate)); };
+}
+var SkipWhileOperator = /*@__PURE__*/ (function () {
+ function SkipWhileOperator(predicate) {
+ this.predicate = predicate;
+ }
+ SkipWhileOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new SkipWhileSubscriber(subscriber, this.predicate));
+ };
+ return SkipWhileOperator;
+}());
+var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(SkipWhileSubscriber, _super);
+ function SkipWhileSubscriber(destination, predicate) {
+ var _this = _super.call(this, destination) || this;
+ _this.predicate = predicate;
+ _this.skipping = true;
+ _this.index = 0;
+ return _this;
+ }
+ SkipWhileSubscriber.prototype._next = function (value) {
+ var destination = this.destination;
+ if (this.skipping) {
+ this.tryCallPredicate(value);
+ }
+ if (!this.skipping) {
+ destination.next(value);
+ }
+ };
+ SkipWhileSubscriber.prototype.tryCallPredicate = function (value) {
+ try {
+ var result = this.predicate(value, this.index++);
+ this.skipping = Boolean(result);
+ }
+ catch (err) {
+ this.destination.error(err);
+ }
+ };
+ return SkipWhileSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START _observable_concat,_util_isScheduler PURE_IMPORTS_END */
+function startWith() {
+ var array = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ array[_i] = arguments[_i];
+ }
+ var scheduler = array[array.length - 1];
+ if (isScheduler(scheduler)) {
+ array.pop();
+ return function (source) { return concat(array, source, scheduler); };
+ }
+ else {
+ return function (source) { return concat(array, source); };
+ }
+}
+
+/** PURE_IMPORTS_START tslib,_map,_observable_from,_innerSubscribe PURE_IMPORTS_END */
+function switchMap(project, resultSelector) {
+ if (typeof resultSelector === 'function') {
+ return function (source) { return source.pipe(switchMap(function (a, i) { return from(project(a, i)).pipe(map(function (b, ii) { return resultSelector(a, b, i, ii); })); })); };
+ }
+ return function (source) { return source.lift(new SwitchMapOperator(project)); };
+}
+var SwitchMapOperator = /*@__PURE__*/ (function () {
+ function SwitchMapOperator(project) {
+ this.project = project;
+ }
+ SwitchMapOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new SwitchMapSubscriber(subscriber, this.project));
+ };
+ return SwitchMapOperator;
+}());
+var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(SwitchMapSubscriber, _super);
+ function SwitchMapSubscriber(destination, project) {
+ var _this = _super.call(this, destination) || this;
+ _this.project = project;
+ _this.index = 0;
+ return _this;
+ }
+ SwitchMapSubscriber.prototype._next = function (value) {
+ var result;
+ var index = this.index++;
+ try {
+ result = this.project(value, index);
+ }
+ catch (error) {
+ this.destination.error(error);
+ return;
+ }
+ this._innerSub(result);
+ };
+ SwitchMapSubscriber.prototype._innerSub = function (result) {
+ var innerSubscription = this.innerSubscription;
+ if (innerSubscription) {
+ innerSubscription.unsubscribe();
+ }
+ var innerSubscriber = new SimpleInnerSubscriber(this);
+ var destination = this.destination;
+ destination.add(innerSubscriber);
+ this.innerSubscription = innerSubscribe(result, innerSubscriber);
+ if (this.innerSubscription !== innerSubscriber) {
+ destination.add(this.innerSubscription);
+ }
+ };
+ SwitchMapSubscriber.prototype._complete = function () {
+ var innerSubscription = this.innerSubscription;
+ if (!innerSubscription || innerSubscription.closed) {
+ _super.prototype._complete.call(this);
+ }
+ this.unsubscribe();
+ };
+ SwitchMapSubscriber.prototype._unsubscribe = function () {
+ this.innerSubscription = undefined;
+ };
+ SwitchMapSubscriber.prototype.notifyComplete = function () {
+ this.innerSubscription = undefined;
+ if (this.isStopped) {
+ _super.prototype._complete.call(this);
+ }
+ };
+ SwitchMapSubscriber.prototype.notifyNext = function (innerValue) {
+ this.destination.next(innerValue);
+ };
+ return SwitchMapSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */
+function takeUntil(notifier) {
+ return function (source) { return source.lift(new TakeUntilOperator(notifier)); };
+}
+var TakeUntilOperator = /*@__PURE__*/ (function () {
+ function TakeUntilOperator(notifier) {
+ this.notifier = notifier;
+ }
+ TakeUntilOperator.prototype.call = function (subscriber, source) {
+ var takeUntilSubscriber = new TakeUntilSubscriber(subscriber);
+ var notifierSubscription = innerSubscribe(this.notifier, new SimpleInnerSubscriber(takeUntilSubscriber));
+ if (notifierSubscription && !takeUntilSubscriber.seenValue) {
+ takeUntilSubscriber.add(notifierSubscription);
+ return source.subscribe(takeUntilSubscriber);
+ }
+ return takeUntilSubscriber;
+ };
+ return TakeUntilOperator;
+}());
+var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(TakeUntilSubscriber, _super);
+ function TakeUntilSubscriber(destination) {
+ var _this = _super.call(this, destination) || this;
+ _this.seenValue = false;
+ return _this;
+ }
+ TakeUntilSubscriber.prototype.notifyNext = function () {
+ this.seenValue = true;
+ this.complete();
+ };
+ TakeUntilSubscriber.prototype.notifyComplete = function () {
+ };
+ return TakeUntilSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */
+function takeWhile(predicate, inclusive) {
+ if (inclusive === void 0) {
+ inclusive = false;
+ }
+ return function (source) {
+ return source.lift(new TakeWhileOperator(predicate, inclusive));
+ };
+}
+var TakeWhileOperator = /*@__PURE__*/ (function () {
+ function TakeWhileOperator(predicate, inclusive) {
+ this.predicate = predicate;
+ this.inclusive = inclusive;
+ }
+ TakeWhileOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new TakeWhileSubscriber(subscriber, this.predicate, this.inclusive));
+ };
+ return TakeWhileOperator;
+}());
+var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(TakeWhileSubscriber, _super);
+ function TakeWhileSubscriber(destination, predicate, inclusive) {
+ var _this = _super.call(this, destination) || this;
+ _this.predicate = predicate;
+ _this.inclusive = inclusive;
+ _this.index = 0;
+ return _this;
+ }
+ TakeWhileSubscriber.prototype._next = function (value) {
+ var destination = this.destination;
+ var result;
+ try {
+ result = this.predicate(value, this.index++);
+ }
+ catch (err) {
+ destination.error(err);
+ return;
+ }
+ this.nextOrComplete(value, result);
+ };
+ TakeWhileSubscriber.prototype.nextOrComplete = function (value, predicateResult) {
+ var destination = this.destination;
+ if (Boolean(predicateResult)) {
+ destination.next(value);
+ }
+ else {
+ if (this.inclusive) {
+ destination.next(value);
+ }
+ destination.complete();
+ }
+ };
+ return TakeWhileSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_Subscriber,_util_noop,_util_isFunction PURE_IMPORTS_END */
+function tap(nextOrObserver, error, complete) {
+ return function tapOperatorFunction(source) {
+ return source.lift(new DoOperator(nextOrObserver, error, complete));
+ };
+}
+var DoOperator = /*@__PURE__*/ (function () {
+ function DoOperator(nextOrObserver, error, complete) {
+ this.nextOrObserver = nextOrObserver;
+ this.error = error;
+ this.complete = complete;
+ }
+ DoOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new TapSubscriber(subscriber, this.nextOrObserver, this.error, this.complete));
+ };
+ return DoOperator;
+}());
+var TapSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(TapSubscriber, _super);
+ function TapSubscriber(destination, observerOrNext, error, complete) {
+ var _this = _super.call(this, destination) || this;
+ _this._tapNext = noop;
+ _this._tapError = noop;
+ _this._tapComplete = noop;
+ _this._tapError = error || noop;
+ _this._tapComplete = complete || noop;
+ if (isFunction(observerOrNext)) {
+ _this._context = _this;
+ _this._tapNext = observerOrNext;
+ }
+ else if (observerOrNext) {
+ _this._context = observerOrNext;
+ _this._tapNext = observerOrNext.next || noop;
+ _this._tapError = observerOrNext.error || noop;
+ _this._tapComplete = observerOrNext.complete || noop;
+ }
+ return _this;
+ }
+ TapSubscriber.prototype._next = function (value) {
+ try {
+ this._tapNext.call(this._context, value);
+ }
+ catch (err) {
+ this.destination.error(err);
+ return;
+ }
+ this.destination.next(value);
+ };
+ TapSubscriber.prototype._error = function (err) {
+ try {
+ this._tapError.call(this._context, err);
+ }
+ catch (err) {
+ this.destination.error(err);
+ return;
+ }
+ this.destination.error(err);
+ };
+ TapSubscriber.prototype._complete = function () {
+ try {
+ this._tapComplete.call(this._context);
+ }
+ catch (err) {
+ this.destination.error(err);
+ return;
+ }
+ return this.destination.complete();
+ };
+ return TapSubscriber;
+}(Subscriber));
+
+/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_innerSubscribe PURE_IMPORTS_END */
+function timeoutWith(due, withObservable, scheduler) {
+ if (scheduler === void 0) {
+ scheduler = async;
+ }
+ return function (source) {
+ var absoluteTimeout = isDate(due);
+ var waitFor = absoluteTimeout ? (+due - scheduler.now()) : Math.abs(due);
+ return source.lift(new TimeoutWithOperator(waitFor, absoluteTimeout, withObservable, scheduler));
+ };
+}
+var TimeoutWithOperator = /*@__PURE__*/ (function () {
+ function TimeoutWithOperator(waitFor, absoluteTimeout, withObservable, scheduler) {
+ this.waitFor = waitFor;
+ this.absoluteTimeout = absoluteTimeout;
+ this.withObservable = withObservable;
+ this.scheduler = scheduler;
+ }
+ TimeoutWithOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new TimeoutWithSubscriber(subscriber, this.absoluteTimeout, this.waitFor, this.withObservable, this.scheduler));
+ };
+ return TimeoutWithOperator;
+}());
+var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(TimeoutWithSubscriber, _super);
+ function TimeoutWithSubscriber(destination, absoluteTimeout, waitFor, withObservable, scheduler) {
+ var _this = _super.call(this, destination) || this;
+ _this.absoluteTimeout = absoluteTimeout;
+ _this.waitFor = waitFor;
+ _this.withObservable = withObservable;
+ _this.scheduler = scheduler;
+ _this.scheduleTimeout();
+ return _this;
+ }
+ TimeoutWithSubscriber.dispatchTimeout = function (subscriber) {
+ var withObservable = subscriber.withObservable;
+ subscriber._unsubscribeAndRecycle();
+ subscriber.add(innerSubscribe(withObservable, new SimpleInnerSubscriber(subscriber)));
+ };
+ TimeoutWithSubscriber.prototype.scheduleTimeout = function () {
+ var action = this.action;
+ if (action) {
+ this.action = action.schedule(this, this.waitFor);
+ }
+ else {
+ this.add(this.action = this.scheduler.schedule(TimeoutWithSubscriber.dispatchTimeout, this.waitFor, this));
+ }
+ };
+ TimeoutWithSubscriber.prototype._next = function (value) {
+ if (!this.absoluteTimeout) {
+ this.scheduleTimeout();
+ }
+ _super.prototype._next.call(this, value);
+ };
+ TimeoutWithSubscriber.prototype._unsubscribe = function () {
+ this.action = undefined;
+ this.scheduler = null;
+ this.withObservable = null;
+ };
+ return TimeoutWithSubscriber;
+}(SimpleOuterSubscriber));
+
+/** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */
+function timeout(due, scheduler) {
+ if (scheduler === void 0) {
+ scheduler = async;
+ }
+ return timeoutWith(due, throwError(new TimeoutError()), scheduler);
+}
+
+/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */
+function withLatestFrom() {
+ var args = [];
+ for (var _i = 0; _i < arguments.length; _i++) {
+ args[_i] = arguments[_i];
+ }
+ return function (source) {
+ var project;
+ if (typeof args[args.length - 1] === 'function') {
+ project = args.pop();
+ }
+ var observables = args;
+ return source.lift(new WithLatestFromOperator(observables, project));
+ };
+}
+var WithLatestFromOperator = /*@__PURE__*/ (function () {
+ function WithLatestFromOperator(observables, project) {
+ this.observables = observables;
+ this.project = project;
+ }
+ WithLatestFromOperator.prototype.call = function (subscriber, source) {
+ return source.subscribe(new WithLatestFromSubscriber(subscriber, this.observables, this.project));
+ };
+ return WithLatestFromOperator;
+}());
+var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) {
+ __extends(WithLatestFromSubscriber, _super);
+ function WithLatestFromSubscriber(destination, observables, project) {
+ var _this = _super.call(this, destination) || this;
+ _this.observables = observables;
+ _this.project = project;
+ _this.toRespond = [];
+ var len = observables.length;
+ _this.values = new Array(len);
+ for (var i = 0; i < len; i++) {
+ _this.toRespond.push(i);
+ }
+ for (var i = 0; i < len; i++) {
+ var observable = observables[i];
+ _this.add(subscribeToResult(_this, observable, undefined, i));
+ }
+ return _this;
+ }
+ WithLatestFromSubscriber.prototype.notifyNext = function (_outerValue, innerValue, outerIndex) {
+ this.values[outerIndex] = innerValue;
+ var toRespond = this.toRespond;
+ if (toRespond.length > 0) {
+ var found = toRespond.indexOf(outerIndex);
+ if (found !== -1) {
+ toRespond.splice(found, 1);
+ }
+ }
+ };
+ WithLatestFromSubscriber.prototype.notifyComplete = function () {
+ };
+ WithLatestFromSubscriber.prototype._next = function (value) {
+ if (this.toRespond.length === 0) {
+ var args = [value].concat(this.values);
+ if (this.project) {
+ this._tryProject(args);
+ }
+ else {
+ this.destination.next(args);
+ }
+ }
+ };
+ WithLatestFromSubscriber.prototype._tryProject = function (args) {
+ var result;
+ try {
+ result = this.project.apply(this, args);
+ }
+ catch (err) {
+ this.destination.error(err);
+ return;
+ }
+ this.destination.next(result);
+ };
+ return WithLatestFromSubscriber;
+}(OuterSubscriber));
+
+/**
+ * @class Filter
+ *
+ * @classdesc Represents a class for creating image filters. Implementation and
+ * definitions based on https://github.com/mapbox/feature-filter.
+ */
+class FilterCreator {
+ /**
+ * Create a filter from a filter expression.
+ *
+ * @description The following filters are supported:
+ *
+ * Comparison
+ * `==`
+ * `!=`
+ * `<`
+ * `<=`
+ * `>`
+ * `>=`
+ *
+ * Set membership
+ * `in`
+ * `!in`
+ *
+ * Combining
+ * `all`
+ *
+ * @param {FilterExpression} filter - Comparison, set membership or combinding filter
+ * expression.
+ * @returns {FilterFunction} Function taking a image and returning a boolean that
+ * indicates whether the image passed the test or not.
+ */
+ createFilter(filter) {
+ return new Function("node", "return " + this._compile(filter) + ";");
+ }
+ _compile(filter) {
+ if (filter == null || filter.length <= 1) {
+ return "true";
+ }
+ const operator = filter[0];
+ const operation = operator === "==" ? this._compileComparisonOp("===", filter[1], filter[2], false) :
+ operator === "!=" ? this._compileComparisonOp("!==", filter[1], filter[2], false) :
+ operator === ">" ||
+ operator === ">=" ||
+ operator === "<" ||
+ operator === "<=" ? this._compileComparisonOp(operator, filter[1], filter[2], true) :
+ operator === "in" ?
+ this._compileInOp(filter[1], filter.slice(2)) :
+ operator === "!in" ?
+ this._compileNegation(this._compileInOp(filter[1], filter.slice(2))) :
+ operator === "all" ? this._compileLogicalOp(filter.slice(1), "&&") :
+ "true";
+ return "(" + operation + ")";
+ }
+ _compare(a, b) {
+ return a < b ? -1 : a > b ? 1 : 0;
+ }
+ _compileComparisonOp(operator, property, value, checkType) {
+ const left = this._compilePropertyReference(property);
+ const right = JSON.stringify(value);
+ return (checkType ? "typeof " + left + "===typeof " + right + "&&" : "") + left + operator + right;
+ }
+ _compileInOp(property, values) {
+ const compare = this._compare;
+ const left = JSON.stringify(values.sort(compare));
+ const right = this._compilePropertyReference(property);
+ return left + ".indexOf(" + right + ")!==-1";
+ }
+ _compileLogicalOp(filters, operator) {
+ const compile = this._compile.bind(this);
+ return filters.map(compile).join(operator);
+ }
+ _compileNegation(expression) {
+ return "!(" + expression + ")";
+ }
+ _compilePropertyReference(property) {
+ return "node[" + JSON.stringify(property) + "]";
+ }
+}
+
+// threejs.org/license
+const REVISION = '125';
+const CullFaceNone = 0;
+const CullFaceBack = 1;
+const CullFaceFront = 2;
+const PCFShadowMap = 1;
+const PCFSoftShadowMap = 2;
+const VSMShadowMap = 3;
+const FrontSide = 0;
+const BackSide = 1;
+const DoubleSide = 2;
+const FlatShading = 1;
+const NoBlending = 0;
+const NormalBlending = 1;
+const AdditiveBlending = 2;
+const SubtractiveBlending = 3;
+const MultiplyBlending = 4;
+const CustomBlending = 5;
+const AddEquation = 100;
+const SubtractEquation = 101;
+const ReverseSubtractEquation = 102;
+const MinEquation = 103;
+const MaxEquation = 104;
+const ZeroFactor = 200;
+const OneFactor = 201;
+const SrcColorFactor = 202;
+const OneMinusSrcColorFactor = 203;
+const SrcAlphaFactor = 204;
+const OneMinusSrcAlphaFactor = 205;
+const DstAlphaFactor = 206;
+const OneMinusDstAlphaFactor = 207;
+const DstColorFactor = 208;
+const OneMinusDstColorFactor = 209;
+const SrcAlphaSaturateFactor = 210;
+const NeverDepth = 0;
+const AlwaysDepth = 1;
+const LessDepth = 2;
+const LessEqualDepth = 3;
+const EqualDepth = 4;
+const GreaterEqualDepth = 5;
+const GreaterDepth = 6;
+const NotEqualDepth = 7;
+const MultiplyOperation = 0;
+const MixOperation = 1;
+const AddOperation = 2;
+const NoToneMapping = 0;
+const LinearToneMapping = 1;
+const ReinhardToneMapping = 2;
+const CineonToneMapping = 3;
+const ACESFilmicToneMapping = 4;
+const CustomToneMapping = 5;
+
+const UVMapping = 300;
+const CubeReflectionMapping = 301;
+const CubeRefractionMapping = 302;
+const EquirectangularReflectionMapping = 303;
+const EquirectangularRefractionMapping = 304;
+const CubeUVReflectionMapping = 306;
+const CubeUVRefractionMapping = 307;
+const RepeatWrapping = 1000;
+const ClampToEdgeWrapping = 1001;
+const MirroredRepeatWrapping = 1002;
+const NearestFilter = 1003;
+const NearestMipmapNearestFilter = 1004;
+const NearestMipmapLinearFilter = 1005;
+const LinearFilter = 1006;
+const LinearMipmapNearestFilter = 1007;
+const LinearMipmapLinearFilter = 1008;
+const UnsignedByteType = 1009;
+const ByteType = 1010;
+const ShortType = 1011;
+const UnsignedShortType = 1012;
+const IntType = 1013;
+const UnsignedIntType = 1014;
+const FloatType = 1015;
+const HalfFloatType = 1016;
+const UnsignedShort4444Type = 1017;
+const UnsignedShort5551Type = 1018;
+const UnsignedShort565Type = 1019;
+const UnsignedInt248Type = 1020;
+const AlphaFormat = 1021;
+const RGBFormat = 1022;
+const RGBAFormat = 1023;
+const LuminanceFormat = 1024;
+const LuminanceAlphaFormat = 1025;
+const DepthFormat = 1026;
+const DepthStencilFormat = 1027;
+const RedFormat = 1028;
+const RedIntegerFormat = 1029;
+const RGFormat = 1030;
+const RGIntegerFormat = 1031;
+const RGBIntegerFormat = 1032;
+const RGBAIntegerFormat = 1033;
+
+const RGB_S3TC_DXT1_Format = 33776;
+const RGBA_S3TC_DXT1_Format = 33777;
+const RGBA_S3TC_DXT3_Format = 33778;
+const RGBA_S3TC_DXT5_Format = 33779;
+const RGB_PVRTC_4BPPV1_Format = 35840;
+const RGB_PVRTC_2BPPV1_Format = 35841;
+const RGBA_PVRTC_4BPPV1_Format = 35842;
+const RGBA_PVRTC_2BPPV1_Format = 35843;
+const RGB_ETC1_Format = 36196;
+const RGB_ETC2_Format = 37492;
+const RGBA_ETC2_EAC_Format = 37496;
+const RGBA_ASTC_4x4_Format = 37808;
+const RGBA_ASTC_5x4_Format = 37809;
+const RGBA_ASTC_5x5_Format = 37810;
+const RGBA_ASTC_6x5_Format = 37811;
+const RGBA_ASTC_6x6_Format = 37812;
+const RGBA_ASTC_8x5_Format = 37813;
+const RGBA_ASTC_8x6_Format = 37814;
+const RGBA_ASTC_8x8_Format = 37815;
+const RGBA_ASTC_10x5_Format = 37816;
+const RGBA_ASTC_10x6_Format = 37817;
+const RGBA_ASTC_10x8_Format = 37818;
+const RGBA_ASTC_10x10_Format = 37819;
+const RGBA_ASTC_12x10_Format = 37820;
+const RGBA_ASTC_12x12_Format = 37821;
+const RGBA_BPTC_Format = 36492;
+const SRGB8_ALPHA8_ASTC_4x4_Format = 37840;
+const SRGB8_ALPHA8_ASTC_5x4_Format = 37841;
+const SRGB8_ALPHA8_ASTC_5x5_Format = 37842;
+const SRGB8_ALPHA8_ASTC_6x5_Format = 37843;
+const SRGB8_ALPHA8_ASTC_6x6_Format = 37844;
+const SRGB8_ALPHA8_ASTC_8x5_Format = 37845;
+const SRGB8_ALPHA8_ASTC_8x6_Format = 37846;
+const SRGB8_ALPHA8_ASTC_8x8_Format = 37847;
+const SRGB8_ALPHA8_ASTC_10x5_Format = 37848;
+const SRGB8_ALPHA8_ASTC_10x6_Format = 37849;
+const SRGB8_ALPHA8_ASTC_10x8_Format = 37850;
+const SRGB8_ALPHA8_ASTC_10x10_Format = 37851;
+const SRGB8_ALPHA8_ASTC_12x10_Format = 37852;
+const SRGB8_ALPHA8_ASTC_12x12_Format = 37853;
+const LoopOnce = 2200;
+const LoopRepeat = 2201;
+const LoopPingPong = 2202;
+const InterpolateDiscrete = 2300;
+const InterpolateLinear = 2301;
+const InterpolateSmooth = 2302;
+const ZeroCurvatureEnding = 2400;
+const ZeroSlopeEnding = 2401;
+const WrapAroundEnding = 2402;
+const NormalAnimationBlendMode = 2500;
+const AdditiveAnimationBlendMode = 2501;
+const TrianglesDrawMode = 0;
+const LinearEncoding = 3000;
+const sRGBEncoding = 3001;
+const GammaEncoding = 3007;
+const RGBEEncoding = 3002;
+const LogLuvEncoding = 3003;
+const RGBM7Encoding = 3004;
+const RGBM16Encoding = 3005;
+const RGBDEncoding = 3006;
+const BasicDepthPacking = 3200;
+const RGBADepthPacking = 3201;
+const TangentSpaceNormalMap = 0;
+const ObjectSpaceNormalMap = 1;
+const KeepStencilOp = 7680;
+const AlwaysStencilFunc = 519;
+
+const StaticDrawUsage = 35044;
+const DynamicDrawUsage = 35048;
+const GLSL3 = '300 es';
+
+/**
+ * https://github.com/mrdoob/eventdispatcher.js/
+ */
+
+function EventDispatcher() {}
+
+Object.assign( EventDispatcher.prototype, {
+
+ addEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) this._listeners = {};
+
+ const listeners = this._listeners;
+
+ if ( listeners[ type ] === undefined ) {
+
+ listeners[ type ] = [];
+
+ }
+
+ if ( listeners[ type ].indexOf( listener ) === - 1 ) {
+
+ listeners[ type ].push( listener );
+
+ }
+
+ },
+
+ hasEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) return false;
+
+ const listeners = this._listeners;
+
+ return listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1;
+
+ },
+
+ removeEventListener: function ( type, listener ) {
+
+ if ( this._listeners === undefined ) return;
+
+ const listeners = this._listeners;
+ const listenerArray = listeners[ type ];
+
+ if ( listenerArray !== undefined ) {
+
+ const index = listenerArray.indexOf( listener );
+
+ if ( index !== - 1 ) {
+
+ listenerArray.splice( index, 1 );
+
+ }
+
+ }
+
+ },
+
+ dispatchEvent: function ( event ) {
+
+ if ( this._listeners === undefined ) return;
+
+ const listeners = this._listeners;
+ const listenerArray = listeners[ event.type ];
+
+ if ( listenerArray !== undefined ) {
+
+ event.target = this;
+
+ // Make a copy, in case listeners are removed while iterating.
+ const array = listenerArray.slice( 0 );
+
+ for ( let i = 0, l = array.length; i < l; i ++ ) {
+
+ array[ i ].call( this, event );
+
+ }
+
+ }
+
+ }
+
+} );
+
+const _lut = [];
+
+for ( let i = 0; i < 256; i ++ ) {
+
+ _lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 );
+
+}
+
+let _seed = 1234567;
+
+const MathUtils = {
+
+ DEG2RAD: Math.PI / 180,
+ RAD2DEG: 180 / Math.PI,
+
+ generateUUID: function () {
+
+ // http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
+
+ const d0 = Math.random() * 0xffffffff | 0;
+ const d1 = Math.random() * 0xffffffff | 0;
+ const d2 = Math.random() * 0xffffffff | 0;
+ const d3 = Math.random() * 0xffffffff | 0;
+ const uuid = _lut[ d0 & 0xff ] + _lut[ d0 >> 8 & 0xff ] + _lut[ d0 >> 16 & 0xff ] + _lut[ d0 >> 24 & 0xff ] + '-' +
+ _lut[ d1 & 0xff ] + _lut[ d1 >> 8 & 0xff ] + '-' + _lut[ d1 >> 16 & 0x0f | 0x40 ] + _lut[ d1 >> 24 & 0xff ] + '-' +
+ _lut[ d2 & 0x3f | 0x80 ] + _lut[ d2 >> 8 & 0xff ] + '-' + _lut[ d2 >> 16 & 0xff ] + _lut[ d2 >> 24 & 0xff ] +
+ _lut[ d3 & 0xff ] + _lut[ d3 >> 8 & 0xff ] + _lut[ d3 >> 16 & 0xff ] + _lut[ d3 >> 24 & 0xff ];
+
+ // .toUpperCase() here flattens concatenated strings to save heap memory space.
+ return uuid.toUpperCase();
+
+ },
+
+ clamp: function ( value, min, max ) {
+
+ return Math.max( min, Math.min( max, value ) );
+
+ },
+
+ // compute euclidian modulo of m % n
+ // https://en.wikipedia.org/wiki/Modulo_operation
+
+ euclideanModulo: function ( n, m ) {
+
+ return ( ( n % m ) + m ) % m;
+
+ },
+
+ // Linear mapping from range <a1, a2> to range <b1, b2>
+
+ mapLinear: function ( x, a1, a2, b1, b2 ) {
+
+ return b1 + ( x - a1 ) * ( b2 - b1 ) / ( a2 - a1 );
+
+ },
+
+ // https://en.wikipedia.org/wiki/Linear_interpolation
+
+ lerp: function ( x, y, t ) {
+
+ return ( 1 - t ) * x + t * y;
+
+ },
+
+ // http://www.rorydriscoll.com/2016/03/07/frame-rate-independent-damping-using-lerp/
+
+ damp: function ( x, y, lambda, dt ) {
+
+ return MathUtils.lerp( x, y, 1 - Math.exp( - lambda * dt ) );
+
+ },
+
+ // https://www.desmos.com/calculator/vcsjnyz7x4
+
+ pingpong: function ( x, length = 1 ) {
+
+ return length - Math.abs( MathUtils.euclideanModulo( x, length * 2 ) - length );
+
+ },
+
+ // http://en.wikipedia.org/wiki/Smoothstep
+
+ smoothstep: function ( x, min, max ) {
+
+ if ( x <= min ) return 0;
+ if ( x >= max ) return 1;
+
+ x = ( x - min ) / ( max - min );
+
+ return x * x * ( 3 - 2 * x );
+
+ },
+
+ smootherstep: function ( x, min, max ) {
+
+ if ( x <= min ) return 0;
+ if ( x >= max ) return 1;
+
+ x = ( x - min ) / ( max - min );
+
+ return x * x * x * ( x * ( x * 6 - 15 ) + 10 );
+
+ },
+
+ // Random integer from <low, high> interval
+
+ randInt: function ( low, high ) {
+
+ return low + Math.floor( Math.random() * ( high - low + 1 ) );
+
+ },
+
+ // Random float from <low, high> interval
+
+ randFloat: function ( low, high ) {
+
+ return low + Math.random() * ( high - low );
+
+ },
+
+ // Random float from <-range/2, range/2> interval
+
+ randFloatSpread: function ( range ) {
+
+ return range * ( 0.5 - Math.random() );
+
+ },
+
+ // Deterministic pseudo-random float in the interval [ 0, 1 ]
+
+ seededRandom: function ( s ) {
+
+ if ( s !== undefined ) _seed = s % 2147483647;
+
+ // Park-Miller algorithm
+
+ _seed = _seed * 16807 % 2147483647;
+
+ return ( _seed - 1 ) / 2147483646;
+
+ },
+
+ degToRad: function ( degrees ) {
+
+ return degrees * MathUtils.DEG2RAD;
+
+ },
+
+ radToDeg: function ( radians ) {
+
+ return radians * MathUtils.RAD2DEG;
+
+ },
+
+ isPowerOfTwo: function ( value ) {
+
+ return ( value & ( value - 1 ) ) === 0 && value !== 0;
+
+ },
+
+ ceilPowerOfTwo: function ( value ) {
+
+ return Math.pow( 2, Math.ceil( Math.log( value ) / Math.LN2 ) );
+
+ },
+
+ floorPowerOfTwo: function ( value ) {
+
+ return Math.pow( 2, Math.floor( Math.log( value ) / Math.LN2 ) );
+
+ },
+
+ setQuaternionFromProperEuler: function ( q, a, b, c, order ) {
+
+ // Intrinsic Proper Euler Angles - see https://en.wikipedia.org/wiki/Euler_angles
+
+ // rotations are applied to the axes in the order specified by 'order'
+ // rotation by angle 'a' is applied first, then by angle 'b', then by angle 'c'
+ // angles are in radians
+
+ const cos = Math.cos;
+ const sin = Math.sin;
+
+ const c2 = cos( b / 2 );
+ const s2 = sin( b / 2 );
+
+ const c13 = cos( ( a + c ) / 2 );
+ const s13 = sin( ( a + c ) / 2 );
+
+ const c1_3 = cos( ( a - c ) / 2 );
+ const s1_3 = sin( ( a - c ) / 2 );
+
+ const c3_1 = cos( ( c - a ) / 2 );
+ const s3_1 = sin( ( c - a ) / 2 );
+
+ switch ( order ) {
+
+ case 'XYX':
+ q.set( c2 * s13, s2 * c1_3, s2 * s1_3, c2 * c13 );
+ break;
+
+ case 'YZY':
+ q.set( s2 * s1_3, c2 * s13, s2 * c1_3, c2 * c13 );
+ break;
+
+ case 'ZXZ':
+ q.set( s2 * c1_3, s2 * s1_3, c2 * s13, c2 * c13 );
+ break;
+
+ case 'XZX':
+ q.set( c2 * s13, s2 * s3_1, s2 * c3_1, c2 * c13 );
+ break;
+
+ case 'YXY':
+ q.set( s2 * c3_1, c2 * s13, s2 * s3_1, c2 * c13 );
+ break;
+
+ case 'ZYZ':
+ q.set( s2 * s3_1, s2 * c3_1, c2 * s13, c2 * c13 );
+ break;
+
+ default:
+ console.warn( 'THREE.MathUtils: .setQuaternionFromProperEuler() encountered an unknown order: ' + order );
+
+ }
+
+ }
+
+};
+
+class Vector2 {
+
+ constructor( x = 0, y = 0 ) {
+
+ Object.defineProperty( this, 'isVector2', { value: true } );
+
+ this.x = x;
+ this.y = y;
+
+ }
+
+ get width() {
+
+ return this.x;
+
+ }
+
+ set width( value ) {
+
+ this.x = value;
+
+ }
+
+ get height() {
+
+ return this.y;
+
+ }
+
+ set height( value ) {
+
+ this.y = value;
+
+ }
+
+ set( x, y ) {
+
+ this.x = x;
+ this.y = y;
+
+ return this;
+
+ }
+
+ setScalar( scalar ) {
+
+ this.x = scalar;
+ this.y = scalar;
+
+ return this;
+
+ }
+
+ setX( x ) {
+
+ this.x = x;
+
+ return this;
+
+ }
+
+ setY( y ) {
+
+ this.y = y;
+
+ return this;
+
+ }
+
+ setComponent( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ return this;
+
+ }
+
+ getComponent( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ }
+
+ clone() {
+
+ return new this.constructor( this.x, this.y );
+
+ }
+
+ copy( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+
+ return this;
+
+ }
+
+ add( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector2: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+
+ return this;
+
+ }
+
+ addScalar( s ) {
+
+ this.x += s;
+ this.y += s;
+
+ return this;
+
+ }
+
+ addVectors( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+
+ return this;
+
+ }
+
+ addScaledVector( v, s ) {
+
+ this.x += v.x * s;
+ this.y += v.y * s;
+
+ return this;
+
+ }
+
+ sub( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector2: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+
+ return this;
+
+ }
+
+ subScalar( s ) {
+
+ this.x -= s;
+ this.y -= s;
+
+ return this;
+
+ }
+
+ subVectors( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+
+ return this;
+
+ }
+
+ multiply( v ) {
+
+ this.x *= v.x;
+ this.y *= v.y;
+
+ return this;
+
+ }
+
+ multiplyScalar( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+
+ return this;
+
+ }
+
+ divide( v ) {
+
+ this.x /= v.x;
+ this.y /= v.y;
+
+ return this;
+
+ }
+
+ divideScalar( scalar ) {
+
+ return this.multiplyScalar( 1 / scalar );
+
+ }
+
+ applyMatrix3( m ) {
+
+ const x = this.x, y = this.y;
+ const e = m.elements;
+
+ this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ];
+ this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ];
+
+ return this;
+
+ }
+
+ min( v ) {
+
+ this.x = Math.min( this.x, v.x );
+ this.y = Math.min( this.y, v.y );
+
+ return this;
+
+ }
+
+ max( v ) {
+
+ this.x = Math.max( this.x, v.x );
+ this.y = Math.max( this.y, v.y );
+
+ return this;
+
+ }
+
+ clamp( min, max ) {
+
+ // assumes min < max, componentwise
+
+ this.x = Math.max( min.x, Math.min( max.x, this.x ) );
+ this.y = Math.max( min.y, Math.min( max.y, this.y ) );
+
+ return this;
+
+ }
+
+ clampScalar( minVal, maxVal ) {
+
+ this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
+ this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
+
+ return this;
+
+ }
+
+ clampLength( min, max ) {
+
+ const length = this.length();
+
+ return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
+
+ }
+
+ floor() {
+
+ this.x = Math.floor( this.x );
+ this.y = Math.floor( this.y );
+
+ return this;
+
+ }
+
+ ceil() {
+
+ this.x = Math.ceil( this.x );
+ this.y = Math.ceil( this.y );
+
+ return this;
+
+ }
+
+ round() {
+
+ this.x = Math.round( this.x );
+ this.y = Math.round( this.y );
+
+ return this;
+
+ }
+
+ roundToZero() {
+
+ this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
+ this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
+
+ return this;
+
+ }
+
+ negate() {
+
+ this.x = - this.x;
+ this.y = - this.y;
+
+ return this;
+
+ }
+
+ dot( v ) {
+
+ return this.x * v.x + this.y * v.y;
+
+ }
+
+ cross( v ) {
+
+ return this.x * v.y - this.y * v.x;
+
+ }
+
+ lengthSq() {
+
+ return this.x * this.x + this.y * this.y;
+
+ }
+
+ length() {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y );
+
+ }
+
+ manhattanLength() {
+
+ return Math.abs( this.x ) + Math.abs( this.y );
+
+ }
+
+ normalize() {
+
+ return this.divideScalar( this.length() || 1 );
+
+ }
+
+ angle() {
+
+ // computes the angle in radians with respect to the positive x-axis
+
+ const angle = Math.atan2( - this.y, - this.x ) + Math.PI;
+
+ return angle;
+
+ }
+
+ distanceTo( v ) {
+
+ return Math.sqrt( this.distanceToSquared( v ) );
+
+ }
+
+ distanceToSquared( v ) {
+
+ const dx = this.x - v.x, dy = this.y - v.y;
+ return dx * dx + dy * dy;
+
+ }
+
+ manhattanDistanceTo( v ) {
+
+ return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y );
+
+ }
+
+ setLength( length ) {
+
+ return this.normalize().multiplyScalar( length );
+
+ }
+
+ lerp( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+
+ return this;
+
+ }
+
+ lerpVectors( v1, v2, alpha ) {
+
+ this.x = v1.x + ( v2.x - v1.x ) * alpha;
+ this.y = v1.y + ( v2.y - v1.y ) * alpha;
+
+ return this;
+
+ }
+
+ equals( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) );
+
+ }
+
+ fromArray( array, offset = 0 ) {
+
+ this.x = array[ offset ];
+ this.y = array[ offset + 1 ];
+
+ return this;
+
+ }
+
+ toArray( array = [], offset = 0 ) {
+
+ array[ offset ] = this.x;
+ array[ offset + 1 ] = this.y;
+
+ return array;
+
+ }
+
+ fromBufferAttribute( attribute, index, offset ) {
+
+ if ( offset !== undefined ) {
+
+ console.warn( 'THREE.Vector2: offset has been removed from .fromBufferAttribute().' );
+
+ }
+
+ this.x = attribute.getX( index );
+ this.y = attribute.getY( index );
+
+ return this;
+
+ }
+
+ rotateAround( center, angle ) {
+
+ const c = Math.cos( angle ), s = Math.sin( angle );
+
+ const x = this.x - center.x;
+ const y = this.y - center.y;
+
+ this.x = x * c - y * s + center.x;
+ this.y = x * s + y * c + center.y;
+
+ return this;
+
+ }
+
+ random() {
+
+ this.x = Math.random();
+ this.y = Math.random();
+
+ return this;
+
+ }
+
+}
+
+class Matrix3 {
+
+ constructor() {
+
+ Object.defineProperty( this, 'isMatrix3', { value: true } );
+
+ this.elements = [
+
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1
+
+ ];
+
+ if ( arguments.length > 0 ) {
+
+ console.error( 'THREE.Matrix3: the constructor no longer reads arguments. use .set() instead.' );
+
+ }
+
+ }
+
+ set( n11, n12, n13, n21, n22, n23, n31, n32, n33 ) {
+
+ const te = this.elements;
+
+ te[ 0 ] = n11; te[ 1 ] = n21; te[ 2 ] = n31;
+ te[ 3 ] = n12; te[ 4 ] = n22; te[ 5 ] = n32;
+ te[ 6 ] = n13; te[ 7 ] = n23; te[ 8 ] = n33;
+
+ return this;
+
+ }
+
+ identity() {
+
+ this.set(
+
+ 1, 0, 0,
+ 0, 1, 0,
+ 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ clone() {
+
+ return new this.constructor().fromArray( this.elements );
+
+ }
+
+ copy( m ) {
+
+ const te = this.elements;
+ const me = m.elements;
+
+ te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ];
+ te[ 3 ] = me[ 3 ]; te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ];
+ te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ]; te[ 8 ] = me[ 8 ];
+
+ return this;
+
+ }
+
+ extractBasis( xAxis, yAxis, zAxis ) {
+
+ xAxis.setFromMatrix3Column( this, 0 );
+ yAxis.setFromMatrix3Column( this, 1 );
+ zAxis.setFromMatrix3Column( this, 2 );
+
+ return this;
+
+ }
+
+ setFromMatrix4( m ) {
+
+ const me = m.elements;
+
+ this.set(
+
+ me[ 0 ], me[ 4 ], me[ 8 ],
+ me[ 1 ], me[ 5 ], me[ 9 ],
+ me[ 2 ], me[ 6 ], me[ 10 ]
+
+ );
+
+ return this;
+
+ }
+
+ multiply( m ) {
+
+ return this.multiplyMatrices( this, m );
+
+ }
+
+ premultiply( m ) {
+
+ return this.multiplyMatrices( m, this );
+
+ }
+
+ multiplyMatrices( a, b ) {
+
+ const ae = a.elements;
+ const be = b.elements;
+ const te = this.elements;
+
+ const a11 = ae[ 0 ], a12 = ae[ 3 ], a13 = ae[ 6 ];
+ const a21 = ae[ 1 ], a22 = ae[ 4 ], a23 = ae[ 7 ];
+ const a31 = ae[ 2 ], a32 = ae[ 5 ], a33 = ae[ 8 ];
+
+ const b11 = be[ 0 ], b12 = be[ 3 ], b13 = be[ 6 ];
+ const b21 = be[ 1 ], b22 = be[ 4 ], b23 = be[ 7 ];
+ const b31 = be[ 2 ], b32 = be[ 5 ], b33 = be[ 8 ];
+
+ te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31;
+ te[ 3 ] = a11 * b12 + a12 * b22 + a13 * b32;
+ te[ 6 ] = a11 * b13 + a12 * b23 + a13 * b33;
+
+ te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31;
+ te[ 4 ] = a21 * b12 + a22 * b22 + a23 * b32;
+ te[ 7 ] = a21 * b13 + a22 * b23 + a23 * b33;
+
+ te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31;
+ te[ 5 ] = a31 * b12 + a32 * b22 + a33 * b32;
+ te[ 8 ] = a31 * b13 + a32 * b23 + a33 * b33;
+
+ return this;
+
+ }
+
+ multiplyScalar( s ) {
+
+ const te = this.elements;
+
+ te[ 0 ] *= s; te[ 3 ] *= s; te[ 6 ] *= s;
+ te[ 1 ] *= s; te[ 4 ] *= s; te[ 7 ] *= s;
+ te[ 2 ] *= s; te[ 5 ] *= s; te[ 8 ] *= s;
+
+ return this;
+
+ }
+
+ determinant() {
+
+ const te = this.elements;
+
+ const a = te[ 0 ], b = te[ 1 ], c = te[ 2 ],
+ d = te[ 3 ], e = te[ 4 ], f = te[ 5 ],
+ g = te[ 6 ], h = te[ 7 ], i = te[ 8 ];
+
+ return a * e * i - a * f * h - b * d * i + b * f * g + c * d * h - c * e * g;
+
+ }
+
+ invert() {
+
+ const te = this.elements,
+
+ n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ],
+ n12 = te[ 3 ], n22 = te[ 4 ], n32 = te[ 5 ],
+ n13 = te[ 6 ], n23 = te[ 7 ], n33 = te[ 8 ],
+
+ t11 = n33 * n22 - n32 * n23,
+ t12 = n32 * n13 - n33 * n12,
+ t13 = n23 * n12 - n22 * n13,
+
+ det = n11 * t11 + n21 * t12 + n31 * t13;
+
+ if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+
+ const detInv = 1 / det;
+
+ te[ 0 ] = t11 * detInv;
+ te[ 1 ] = ( n31 * n23 - n33 * n21 ) * detInv;
+ te[ 2 ] = ( n32 * n21 - n31 * n22 ) * detInv;
+
+ te[ 3 ] = t12 * detInv;
+ te[ 4 ] = ( n33 * n11 - n31 * n13 ) * detInv;
+ te[ 5 ] = ( n31 * n12 - n32 * n11 ) * detInv;
+
+ te[ 6 ] = t13 * detInv;
+ te[ 7 ] = ( n21 * n13 - n23 * n11 ) * detInv;
+ te[ 8 ] = ( n22 * n11 - n21 * n12 ) * detInv;
+
+ return this;
+
+ }
+
+ transpose() {
+
+ let tmp;
+ const m = this.elements;
+
+ tmp = m[ 1 ]; m[ 1 ] = m[ 3 ]; m[ 3 ] = tmp;
+ tmp = m[ 2 ]; m[ 2 ] = m[ 6 ]; m[ 6 ] = tmp;
+ tmp = m[ 5 ]; m[ 5 ] = m[ 7 ]; m[ 7 ] = tmp;
+
+ return this;
+
+ }
+
+ getNormalMatrix( matrix4 ) {
+
+ return this.setFromMatrix4( matrix4 ).copy( this ).invert().transpose();
+
+ }
+
+ transposeIntoArray( r ) {
+
+ const m = this.elements;
+
+ r[ 0 ] = m[ 0 ];
+ r[ 1 ] = m[ 3 ];
+ r[ 2 ] = m[ 6 ];
+ r[ 3 ] = m[ 1 ];
+ r[ 4 ] = m[ 4 ];
+ r[ 5 ] = m[ 7 ];
+ r[ 6 ] = m[ 2 ];
+ r[ 7 ] = m[ 5 ];
+ r[ 8 ] = m[ 8 ];
+
+ return this;
+
+ }
+
+ setUvTransform( tx, ty, sx, sy, rotation, cx, cy ) {
+
+ const c = Math.cos( rotation );
+ const s = Math.sin( rotation );
+
+ this.set(
+ sx * c, sx * s, - sx * ( c * cx + s * cy ) + cx + tx,
+ - sy * s, sy * c, - sy * ( - s * cx + c * cy ) + cy + ty,
+ 0, 0, 1
+ );
+
+ return this;
+
+ }
+
+ scale( sx, sy ) {
+
+ const te = this.elements;
+
+ te[ 0 ] *= sx; te[ 3 ] *= sx; te[ 6 ] *= sx;
+ te[ 1 ] *= sy; te[ 4 ] *= sy; te[ 7 ] *= sy;
+
+ return this;
+
+ }
+
+ rotate( theta ) {
+
+ const c = Math.cos( theta );
+ const s = Math.sin( theta );
+
+ const te = this.elements;
+
+ const a11 = te[ 0 ], a12 = te[ 3 ], a13 = te[ 6 ];
+ const a21 = te[ 1 ], a22 = te[ 4 ], a23 = te[ 7 ];
+
+ te[ 0 ] = c * a11 + s * a21;
+ te[ 3 ] = c * a12 + s * a22;
+ te[ 6 ] = c * a13 + s * a23;
+
+ te[ 1 ] = - s * a11 + c * a21;
+ te[ 4 ] = - s * a12 + c * a22;
+ te[ 7 ] = - s * a13 + c * a23;
+
+ return this;
+
+ }
+
+ translate( tx, ty ) {
+
+ const te = this.elements;
+
+ te[ 0 ] += tx * te[ 2 ]; te[ 3 ] += tx * te[ 5 ]; te[ 6 ] += tx * te[ 8 ];
+ te[ 1 ] += ty * te[ 2 ]; te[ 4 ] += ty * te[ 5 ]; te[ 7 ] += ty * te[ 8 ];
+
+ return this;
+
+ }
+
+ equals( matrix ) {
+
+ const te = this.elements;
+ const me = matrix.elements;
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ if ( te[ i ] !== me[ i ] ) return false;
+
+ }
+
+ return true;
+
+ }
+
+ fromArray( array, offset = 0 ) {
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ this.elements[ i ] = array[ i + offset ];
+
+ }
+
+ return this;
+
+ }
+
+ toArray( array = [], offset = 0 ) {
+
+ const te = this.elements;
+
+ array[ offset ] = te[ 0 ];
+ array[ offset + 1 ] = te[ 1 ];
+ array[ offset + 2 ] = te[ 2 ];
+
+ array[ offset + 3 ] = te[ 3 ];
+ array[ offset + 4 ] = te[ 4 ];
+ array[ offset + 5 ] = te[ 5 ];
+
+ array[ offset + 6 ] = te[ 6 ];
+ array[ offset + 7 ] = te[ 7 ];
+ array[ offset + 8 ] = te[ 8 ];
+
+ return array;
+
+ }
+
+}
+
+let _canvas;
+
+const ImageUtils = {
+
+ getDataURL: function ( image ) {
+
+ if ( /^data:/i.test( image.src ) ) {
+
+ return image.src;
+
+ }
+
+ if ( typeof HTMLCanvasElement == 'undefined' ) {
+
+ return image.src;
+
+ }
+
+ let canvas;
+
+ if ( image instanceof HTMLCanvasElement ) {
+
+ canvas = image;
+
+ } else {
+
+ if ( _canvas === undefined ) _canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+
+ _canvas.width = image.width;
+ _canvas.height = image.height;
+
+ const context = _canvas.getContext( '2d' );
+
+ if ( image instanceof ImageData ) {
+
+ context.putImageData( image, 0, 0 );
+
+ } else {
+
+ context.drawImage( image, 0, 0, image.width, image.height );
+
+ }
+
+ canvas = _canvas;
+
+ }
+
+ if ( canvas.width > 2048 || canvas.height > 2048 ) {
+
+ return canvas.toDataURL( 'image/jpeg', 0.6 );
+
+ } else {
+
+ return canvas.toDataURL( 'image/png' );
+
+ }
+
+ }
+
+};
+
+let textureId = 0;
+
+function Texture( image = Texture.DEFAULT_IMAGE, mapping = Texture.DEFAULT_MAPPING, wrapS = ClampToEdgeWrapping, wrapT = ClampToEdgeWrapping, magFilter = LinearFilter, minFilter = LinearMipmapLinearFilter, format = RGBAFormat, type = UnsignedByteType, anisotropy = 1, encoding = LinearEncoding ) {
+
+ Object.defineProperty( this, 'id', { value: textureId ++ } );
+
+ this.uuid = MathUtils.generateUUID();
+
+ this.name = '';
+
+ this.image = image;
+ this.mipmaps = [];
+
+ this.mapping = mapping;
+
+ this.wrapS = wrapS;
+ this.wrapT = wrapT;
+
+ this.magFilter = magFilter;
+ this.minFilter = minFilter;
+
+ this.anisotropy = anisotropy;
+
+ this.format = format;
+ this.internalFormat = null;
+ this.type = type;
+
+ this.offset = new Vector2( 0, 0 );
+ this.repeat = new Vector2( 1, 1 );
+ this.center = new Vector2( 0, 0 );
+ this.rotation = 0;
+
+ this.matrixAutoUpdate = true;
+ this.matrix = new Matrix3();
+
+ this.generateMipmaps = true;
+ this.premultiplyAlpha = false;
+ this.flipY = true;
+ this.unpackAlignment = 4; // valid values: 1, 2, 4, 8 (see http://www.khronos.org/opengles/sdk/docs/man/xhtml/glPixelStorei.xml)
+
+ // Values of encoding !== THREE.LinearEncoding only supported on map, envMap and emissiveMap.
+ //
+ // Also changing the encoding after already used by a Material will not automatically make the Material
+ // update. You need to explicitly call Material.needsUpdate to trigger it to recompile.
+ this.encoding = encoding;
+
+ this.version = 0;
+ this.onUpdate = null;
+
+}
+
+Texture.DEFAULT_IMAGE = undefined;
+Texture.DEFAULT_MAPPING = UVMapping;
+
+Texture.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+ constructor: Texture,
+
+ isTexture: true,
+
+ updateMatrix: function () {
+
+ this.matrix.setUvTransform( this.offset.x, this.offset.y, this.repeat.x, this.repeat.y, this.rotation, this.center.x, this.center.y );
+
+ },
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ },
+
+ copy: function ( source ) {
+
+ this.name = source.name;
+
+ this.image = source.image;
+ this.mipmaps = source.mipmaps.slice( 0 );
+
+ this.mapping = source.mapping;
+
+ this.wrapS = source.wrapS;
+ this.wrapT = source.wrapT;
+
+ this.magFilter = source.magFilter;
+ this.minFilter = source.minFilter;
+
+ this.anisotropy = source.anisotropy;
+
+ this.format = source.format;
+ this.internalFormat = source.internalFormat;
+ this.type = source.type;
+
+ this.offset.copy( source.offset );
+ this.repeat.copy( source.repeat );
+ this.center.copy( source.center );
+ this.rotation = source.rotation;
+
+ this.matrixAutoUpdate = source.matrixAutoUpdate;
+ this.matrix.copy( source.matrix );
+
+ this.generateMipmaps = source.generateMipmaps;
+ this.premultiplyAlpha = source.premultiplyAlpha;
+ this.flipY = source.flipY;
+ this.unpackAlignment = source.unpackAlignment;
+ this.encoding = source.encoding;
+
+ return this;
+
+ },
+
+ toJSON: function ( meta ) {
+
+ const isRootObject = ( meta === undefined || typeof meta === 'string' );
+
+ if ( ! isRootObject && meta.textures[ this.uuid ] !== undefined ) {
+
+ return meta.textures[ this.uuid ];
+
+ }
+
+ const output = {
+
+ metadata: {
+ version: 4.5,
+ type: 'Texture',
+ generator: 'Texture.toJSON'
+ },
+
+ uuid: this.uuid,
+ name: this.name,
+
+ mapping: this.mapping,
+
+ repeat: [ this.repeat.x, this.repeat.y ],
+ offset: [ this.offset.x, this.offset.y ],
+ center: [ this.center.x, this.center.y ],
+ rotation: this.rotation,
+
+ wrap: [ this.wrapS, this.wrapT ],
+
+ format: this.format,
+ type: this.type,
+ encoding: this.encoding,
+
+ minFilter: this.minFilter,
+ magFilter: this.magFilter,
+ anisotropy: this.anisotropy,
+
+ flipY: this.flipY,
+
+ premultiplyAlpha: this.premultiplyAlpha,
+ unpackAlignment: this.unpackAlignment
+
+ };
+
+ if ( this.image !== undefined ) {
+
+ // TODO: Move to THREE.Image
+
+ const image = this.image;
+
+ if ( image.uuid === undefined ) {
+
+ image.uuid = MathUtils.generateUUID(); // UGH
+
+ }
+
+ if ( ! isRootObject && meta.images[ image.uuid ] === undefined ) {
+
+ let url;
+
+ if ( Array.isArray( image ) ) {
+
+ // process array of images e.g. CubeTexture
+
+ url = [];
+
+ for ( let i = 0, l = image.length; i < l; i ++ ) {
+
+ // check cube texture with data textures
+
+ if ( image[ i ].isDataTexture ) {
+
+ url.push( serializeImage( image[ i ].image ) );
+
+ } else {
+
+ url.push( serializeImage( image[ i ] ) );
+
+ }
+
+ }
+
+ } else {
+
+ // process single image
+
+ url = serializeImage( image );
+
+ }
+
+ meta.images[ image.uuid ] = {
+ uuid: image.uuid,
+ url: url
+ };
+
+ }
+
+ output.image = image.uuid;
+
+ }
+
+ if ( ! isRootObject ) {
+
+ meta.textures[ this.uuid ] = output;
+
+ }
+
+ return output;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ },
+
+ transformUv: function ( uv ) {
+
+ if ( this.mapping !== UVMapping ) return uv;
+
+ uv.applyMatrix3( this.matrix );
+
+ if ( uv.x < 0 || uv.x > 1 ) {
+
+ switch ( this.wrapS ) {
+
+ case RepeatWrapping:
+
+ uv.x = uv.x - Math.floor( uv.x );
+ break;
+
+ case ClampToEdgeWrapping:
+
+ uv.x = uv.x < 0 ? 0 : 1;
+ break;
+
+ case MirroredRepeatWrapping:
+
+ if ( Math.abs( Math.floor( uv.x ) % 2 ) === 1 ) {
+
+ uv.x = Math.ceil( uv.x ) - uv.x;
+
+ } else {
+
+ uv.x = uv.x - Math.floor( uv.x );
+
+ }
+
+ break;
+
+ }
+
+ }
+
+ if ( uv.y < 0 || uv.y > 1 ) {
+
+ switch ( this.wrapT ) {
+
+ case RepeatWrapping:
+
+ uv.y = uv.y - Math.floor( uv.y );
+ break;
+
+ case ClampToEdgeWrapping:
+
+ uv.y = uv.y < 0 ? 0 : 1;
+ break;
+
+ case MirroredRepeatWrapping:
+
+ if ( Math.abs( Math.floor( uv.y ) % 2 ) === 1 ) {
+
+ uv.y = Math.ceil( uv.y ) - uv.y;
+
+ } else {
+
+ uv.y = uv.y - Math.floor( uv.y );
+
+ }
+
+ break;
+
+ }
+
+ }
+
+ if ( this.flipY ) {
+
+ uv.y = 1 - uv.y;
+
+ }
+
+ return uv;
+
+ }
+
+} );
+
+Object.defineProperty( Texture.prototype, 'needsUpdate', {
+
+ set: function ( value ) {
+
+ if ( value === true ) this.version ++;
+
+ }
+
+} );
+
+function serializeImage( image ) {
+
+ if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
+ ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
+ ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {
+
+ // default images
+
+ return ImageUtils.getDataURL( image );
+
+ } else {
+
+ if ( image.data ) {
+
+ // images of DataTexture
+
+ return {
+ data: Array.prototype.slice.call( image.data ),
+ width: image.width,
+ height: image.height,
+ type: image.data.constructor.name
+ };
+
+ } else {
+
+ console.warn( 'THREE.Texture: Unable to serialize Texture.' );
+ return {};
+
+ }
+
+ }
+
+}
+
+class Vector4 {
+
+ constructor( x = 0, y = 0, z = 0, w = 1 ) {
+
+ Object.defineProperty( this, 'isVector4', { value: true } );
+
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+
+ }
+
+ get width() {
+
+ return this.z;
+
+ }
+
+ set width( value ) {
+
+ this.z = value;
+
+ }
+
+ get height() {
+
+ return this.w;
+
+ }
+
+ set height( value ) {
+
+ this.w = value;
+
+ }
+
+ set( x, y, z, w ) {
+
+ this.x = x;
+ this.y = y;
+ this.z = z;
+ this.w = w;
+
+ return this;
+
+ }
+
+ setScalar( scalar ) {
+
+ this.x = scalar;
+ this.y = scalar;
+ this.z = scalar;
+ this.w = scalar;
+
+ return this;
+
+ }
+
+ setX( x ) {
+
+ this.x = x;
+
+ return this;
+
+ }
+
+ setY( y ) {
+
+ this.y = y;
+
+ return this;
+
+ }
+
+ setZ( z ) {
+
+ this.z = z;
+
+ return this;
+
+ }
+
+ setW( w ) {
+
+ this.w = w;
+
+ return this;
+
+ }
+
+ setComponent( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ case 2: this.z = value; break;
+ case 3: this.w = value; break;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ return this;
+
+ }
+
+ getComponent( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ case 2: return this.z;
+ case 3: return this.w;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ }
+
+ clone() {
+
+ return new this.constructor( this.x, this.y, this.z, this.w );
+
+ }
+
+ copy( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+ this.w = ( v.w !== undefined ) ? v.w : 1;
+
+ return this;
+
+ }
+
+ add( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector4: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+ this.w += v.w;
+
+ return this;
+
+ }
+
+ addScalar( s ) {
+
+ this.x += s;
+ this.y += s;
+ this.z += s;
+ this.w += s;
+
+ return this;
+
+ }
+
+ addVectors( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+ this.z = a.z + b.z;
+ this.w = a.w + b.w;
+
+ return this;
+
+ }
+
+ addScaledVector( v, s ) {
+
+ this.x += v.x * s;
+ this.y += v.y * s;
+ this.z += v.z * s;
+ this.w += v.w * s;
+
+ return this;
+
+ }
+
+ sub( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector4: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+ this.w -= v.w;
+
+ return this;
+
+ }
+
+ subScalar( s ) {
+
+ this.x -= s;
+ this.y -= s;
+ this.z -= s;
+ this.w -= s;
+
+ return this;
+
+ }
+
+ subVectors( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+ this.z = a.z - b.z;
+ this.w = a.w - b.w;
+
+ return this;
+
+ }
+
+ multiply( v ) {
+
+ this.x *= v.x;
+ this.y *= v.y;
+ this.z *= v.z;
+ this.w *= v.w;
+
+ return this;
+
+ }
+
+ multiplyScalar( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+ this.w *= scalar;
+
+ return this;
+
+ }
+
+ applyMatrix4( m ) {
+
+ const x = this.x, y = this.y, z = this.z, w = this.w;
+ const e = m.elements;
+
+ this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] * w;
+ this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] * w;
+ this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] * w;
+ this.w = e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] * w;
+
+ return this;
+
+ }
+
+ divideScalar( scalar ) {
+
+ return this.multiplyScalar( 1 / scalar );
+
+ }
+
+ setAxisAngleFromQuaternion( q ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToAngle/index.htm
+
+ // q is assumed to be normalized
+
+ this.w = 2 * Math.acos( q.w );
+
+ const s = Math.sqrt( 1 - q.w * q.w );
+
+ if ( s < 0.0001 ) {
+
+ this.x = 1;
+ this.y = 0;
+ this.z = 0;
+
+ } else {
+
+ this.x = q.x / s;
+ this.y = q.y / s;
+ this.z = q.z / s;
+
+ }
+
+ return this;
+
+ }
+
+ setAxisAngleFromRotationMatrix( m ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToAngle/index.htm
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ let angle, x, y, z; // variables for result
+ const epsilon = 0.01, // margin to allow for rounding errors
+ epsilon2 = 0.1, // margin to distinguish between 0 and 180 degrees
+
+ te = m.elements,
+
+ m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
+ m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
+ m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];
+
+ if ( ( Math.abs( m12 - m21 ) < epsilon ) &&
+ ( Math.abs( m13 - m31 ) < epsilon ) &&
+ ( Math.abs( m23 - m32 ) < epsilon ) ) {
+
+ // singularity found
+ // first check for identity matrix which must have +1 for all terms
+ // in leading diagonal and zero in other terms
+
+ if ( ( Math.abs( m12 + m21 ) < epsilon2 ) &&
+ ( Math.abs( m13 + m31 ) < epsilon2 ) &&
+ ( Math.abs( m23 + m32 ) < epsilon2 ) &&
+ ( Math.abs( m11 + m22 + m33 - 3 ) < epsilon2 ) ) {
+
+ // this singularity is identity matrix so angle = 0
+
+ this.set( 1, 0, 0, 0 );
+
+ return this; // zero angle, arbitrary axis
+
+ }
+
+ // otherwise this singularity is angle = 180
+
+ angle = Math.PI;
+
+ const xx = ( m11 + 1 ) / 2;
+ const yy = ( m22 + 1 ) / 2;
+ const zz = ( m33 + 1 ) / 2;
+ const xy = ( m12 + m21 ) / 4;
+ const xz = ( m13 + m31 ) / 4;
+ const yz = ( m23 + m32 ) / 4;
+
+ if ( ( xx > yy ) && ( xx > zz ) ) {
+
+ // m11 is the largest diagonal term
+
+ if ( xx < epsilon ) {
+
+ x = 0;
+ y = 0.707106781;
+ z = 0.707106781;
+
+ } else {
+
+ x = Math.sqrt( xx );
+ y = xy / x;
+ z = xz / x;
+
+ }
+
+ } else if ( yy > zz ) {
+
+ // m22 is the largest diagonal term
+
+ if ( yy < epsilon ) {
+
+ x = 0.707106781;
+ y = 0;
+ z = 0.707106781;
+
+ } else {
+
+ y = Math.sqrt( yy );
+ x = xy / y;
+ z = yz / y;
+
+ }
+
+ } else {
+
+ // m33 is the largest diagonal term so base result on this
+
+ if ( zz < epsilon ) {
+
+ x = 0.707106781;
+ y = 0.707106781;
+ z = 0;
+
+ } else {
+
+ z = Math.sqrt( zz );
+ x = xz / z;
+ y = yz / z;
+
+ }
+
+ }
+
+ this.set( x, y, z, angle );
+
+ return this; // return 180 deg rotation
+
+ }
+
+ // as we have reached here there are no singularities so we can handle normally
+
+ let s = Math.sqrt( ( m32 - m23 ) * ( m32 - m23 ) +
+ ( m13 - m31 ) * ( m13 - m31 ) +
+ ( m21 - m12 ) * ( m21 - m12 ) ); // used to normalize
+
+ if ( Math.abs( s ) < 0.001 ) s = 1;
+
+ // prevent divide by zero, should not happen if matrix is orthogonal and should be
+ // caught by singularity test above, but I've left it in just in case
+
+ this.x = ( m32 - m23 ) / s;
+ this.y = ( m13 - m31 ) / s;
+ this.z = ( m21 - m12 ) / s;
+ this.w = Math.acos( ( m11 + m22 + m33 - 1 ) / 2 );
+
+ return this;
+
+ }
+
+ min( v ) {
+
+ this.x = Math.min( this.x, v.x );
+ this.y = Math.min( this.y, v.y );
+ this.z = Math.min( this.z, v.z );
+ this.w = Math.min( this.w, v.w );
+
+ return this;
+
+ }
+
+ max( v ) {
+
+ this.x = Math.max( this.x, v.x );
+ this.y = Math.max( this.y, v.y );
+ this.z = Math.max( this.z, v.z );
+ this.w = Math.max( this.w, v.w );
+
+ return this;
+
+ }
+
+ clamp( min, max ) {
+
+ // assumes min < max, componentwise
+
+ this.x = Math.max( min.x, Math.min( max.x, this.x ) );
+ this.y = Math.max( min.y, Math.min( max.y, this.y ) );
+ this.z = Math.max( min.z, Math.min( max.z, this.z ) );
+ this.w = Math.max( min.w, Math.min( max.w, this.w ) );
+
+ return this;
+
+ }
+
+ clampScalar( minVal, maxVal ) {
+
+ this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
+ this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
+ this.z = Math.max( minVal, Math.min( maxVal, this.z ) );
+ this.w = Math.max( minVal, Math.min( maxVal, this.w ) );
+
+ return this;
+
+ }
+
+ clampLength( min, max ) {
+
+ const length = this.length();
+
+ return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
+
+ }
+
+ floor() {
+
+ this.x = Math.floor( this.x );
+ this.y = Math.floor( this.y );
+ this.z = Math.floor( this.z );
+ this.w = Math.floor( this.w );
+
+ return this;
+
+ }
+
+ ceil() {
+
+ this.x = Math.ceil( this.x );
+ this.y = Math.ceil( this.y );
+ this.z = Math.ceil( this.z );
+ this.w = Math.ceil( this.w );
+
+ return this;
+
+ }
+
+ round() {
+
+ this.x = Math.round( this.x );
+ this.y = Math.round( this.y );
+ this.z = Math.round( this.z );
+ this.w = Math.round( this.w );
+
+ return this;
+
+ }
+
+ roundToZero() {
+
+ this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
+ this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
+ this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );
+ this.w = ( this.w < 0 ) ? Math.ceil( this.w ) : Math.floor( this.w );
+
+ return this;
+
+ }
+
+ negate() {
+
+ this.x = - this.x;
+ this.y = - this.y;
+ this.z = - this.z;
+ this.w = - this.w;
+
+ return this;
+
+ }
+
+ dot( v ) {
+
+ return this.x * v.x + this.y * v.y + this.z * v.z + this.w * v.w;
+
+ }
+
+ lengthSq() {
+
+ return this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w;
+
+ }
+
+ length() {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z + this.w * this.w );
+
+ }
+
+ manhattanLength() {
+
+ return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z ) + Math.abs( this.w );
+
+ }
+
+ normalize() {
+
+ return this.divideScalar( this.length() || 1 );
+
+ }
+
+ setLength( length ) {
+
+ return this.normalize().multiplyScalar( length );
+
+ }
+
+ lerp( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+ this.z += ( v.z - this.z ) * alpha;
+ this.w += ( v.w - this.w ) * alpha;
+
+ return this;
+
+ }
+
+ lerpVectors( v1, v2, alpha ) {
+
+ this.x = v1.x + ( v2.x - v1.x ) * alpha;
+ this.y = v1.y + ( v2.y - v1.y ) * alpha;
+ this.z = v1.z + ( v2.z - v1.z ) * alpha;
+ this.w = v1.w + ( v2.w - v1.w ) * alpha;
+
+ return this;
+
+ }
+
+ equals( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) && ( v.w === this.w ) );
+
+ }
+
+ fromArray( array, offset = 0 ) {
+
+ this.x = array[ offset ];
+ this.y = array[ offset + 1 ];
+ this.z = array[ offset + 2 ];
+ this.w = array[ offset + 3 ];
+
+ return this;
+
+ }
+
+ toArray( array = [], offset = 0 ) {
+
+ array[ offset ] = this.x;
+ array[ offset + 1 ] = this.y;
+ array[ offset + 2 ] = this.z;
+ array[ offset + 3 ] = this.w;
+
+ return array;
+
+ }
+
+ fromBufferAttribute( attribute, index, offset ) {
+
+ if ( offset !== undefined ) {
+
+ console.warn( 'THREE.Vector4: offset has been removed from .fromBufferAttribute().' );
+
+ }
+
+ this.x = attribute.getX( index );
+ this.y = attribute.getY( index );
+ this.z = attribute.getZ( index );
+ this.w = attribute.getW( index );
+
+ return this;
+
+ }
+
+ random() {
+
+ this.x = Math.random();
+ this.y = Math.random();
+ this.z = Math.random();
+ this.w = Math.random();
+
+ return this;
+
+ }
+
+}
+
+/*
+ In options, we can specify:
+ * Texture parameters for an auto-generated target texture
+ * depthBuffer/stencilBuffer: Booleans to indicate if we should generate these buffers
+*/
+class WebGLRenderTarget extends EventDispatcher {
+
+ constructor( width, height, options ) {
+
+ super();
+
+ Object.defineProperty( this, 'isWebGLRenderTarget', { value: true } );
+
+ this.width = width;
+ this.height = height;
+
+ this.scissor = new Vector4( 0, 0, width, height );
+ this.scissorTest = false;
+
+ this.viewport = new Vector4( 0, 0, width, height );
+
+ options = options || {};
+
+ this.texture = new Texture( undefined, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding );
+
+ this.texture.image = {};
+ this.texture.image.width = width;
+ this.texture.image.height = height;
+
+ this.texture.generateMipmaps = options.generateMipmaps !== undefined ? options.generateMipmaps : false;
+ this.texture.minFilter = options.minFilter !== undefined ? options.minFilter : LinearFilter;
+
+ this.depthBuffer = options.depthBuffer !== undefined ? options.depthBuffer : true;
+ this.stencilBuffer = options.stencilBuffer !== undefined ? options.stencilBuffer : false;
+ this.depthTexture = options.depthTexture !== undefined ? options.depthTexture : null;
+
+ }
+
+ setSize( width, height ) {
+
+ if ( this.width !== width || this.height !== height ) {
+
+ this.width = width;
+ this.height = height;
+
+ this.texture.image.width = width;
+ this.texture.image.height = height;
+
+ this.dispose();
+
+ }
+
+ this.viewport.set( 0, 0, width, height );
+ this.scissor.set( 0, 0, width, height );
+
+ }
+
+ clone() {
+
+ return new this.constructor().copy( this );
+
+ }
+
+ copy( source ) {
+
+ this.width = source.width;
+ this.height = source.height;
+
+ this.viewport.copy( source.viewport );
+
+ this.texture = source.texture.clone();
+
+ this.depthBuffer = source.depthBuffer;
+ this.stencilBuffer = source.stencilBuffer;
+ this.depthTexture = source.depthTexture;
+
+ return this;
+
+ }
+
+ dispose() {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+}
+
+class Quaternion {
+
+ constructor( x = 0, y = 0, z = 0, w = 1 ) {
+
+ Object.defineProperty( this, 'isQuaternion', { value: true } );
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._w = w;
+
+ }
+
+ static slerp( qa, qb, qm, t ) {
+
+ return qm.copy( qa ).slerp( qb, t );
+
+ }
+
+ static slerpFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1, t ) {
+
+ // fuzz-free, array-based Quaternion SLERP operation
+
+ let x0 = src0[ srcOffset0 + 0 ],
+ y0 = src0[ srcOffset0 + 1 ],
+ z0 = src0[ srcOffset0 + 2 ],
+ w0 = src0[ srcOffset0 + 3 ];
+
+ const x1 = src1[ srcOffset1 + 0 ],
+ y1 = src1[ srcOffset1 + 1 ],
+ z1 = src1[ srcOffset1 + 2 ],
+ w1 = src1[ srcOffset1 + 3 ];
+
+ if ( w0 !== w1 || x0 !== x1 || y0 !== y1 || z0 !== z1 ) {
+
+ let s = 1 - t;
+ const cos = x0 * x1 + y0 * y1 + z0 * z1 + w0 * w1,
+ dir = ( cos >= 0 ? 1 : - 1 ),
+ sqrSin = 1 - cos * cos;
+
+ // Skip the Slerp for tiny steps to avoid numeric problems:
+ if ( sqrSin > Number.EPSILON ) {
+
+ const sin = Math.sqrt( sqrSin ),
+ len = Math.atan2( sin, cos * dir );
+
+ s = Math.sin( s * len ) / sin;
+ t = Math.sin( t * len ) / sin;
+
+ }
+
+ const tDir = t * dir;
+
+ x0 = x0 * s + x1 * tDir;
+ y0 = y0 * s + y1 * tDir;
+ z0 = z0 * s + z1 * tDir;
+ w0 = w0 * s + w1 * tDir;
+
+ // Normalize in case we just did a lerp:
+ if ( s === 1 - t ) {
+
+ const f = 1 / Math.sqrt( x0 * x0 + y0 * y0 + z0 * z0 + w0 * w0 );
+
+ x0 *= f;
+ y0 *= f;
+ z0 *= f;
+ w0 *= f;
+
+ }
+
+ }
+
+ dst[ dstOffset ] = x0;
+ dst[ dstOffset + 1 ] = y0;
+ dst[ dstOffset + 2 ] = z0;
+ dst[ dstOffset + 3 ] = w0;
+
+ }
+
+ static multiplyQuaternionsFlat( dst, dstOffset, src0, srcOffset0, src1, srcOffset1 ) {
+
+ const x0 = src0[ srcOffset0 ];
+ const y0 = src0[ srcOffset0 + 1 ];
+ const z0 = src0[ srcOffset0 + 2 ];
+ const w0 = src0[ srcOffset0 + 3 ];
+
+ const x1 = src1[ srcOffset1 ];
+ const y1 = src1[ srcOffset1 + 1 ];
+ const z1 = src1[ srcOffset1 + 2 ];
+ const w1 = src1[ srcOffset1 + 3 ];
+
+ dst[ dstOffset ] = x0 * w1 + w0 * x1 + y0 * z1 - z0 * y1;
+ dst[ dstOffset + 1 ] = y0 * w1 + w0 * y1 + z0 * x1 - x0 * z1;
+ dst[ dstOffset + 2 ] = z0 * w1 + w0 * z1 + x0 * y1 - y0 * x1;
+ dst[ dstOffset + 3 ] = w0 * w1 - x0 * x1 - y0 * y1 - z0 * z1;
+
+ return dst;
+
+ }
+
+ get x() {
+
+ return this._x;
+
+ }
+
+ set x( value ) {
+
+ this._x = value;
+ this._onChangeCallback();
+
+ }
+
+ get y() {
+
+ return this._y;
+
+ }
+
+ set y( value ) {
+
+ this._y = value;
+ this._onChangeCallback();
+
+ }
+
+ get z() {
+
+ return this._z;
+
+ }
+
+ set z( value ) {
+
+ this._z = value;
+ this._onChangeCallback();
+
+ }
+
+ get w() {
+
+ return this._w;
+
+ }
+
+ set w( value ) {
+
+ this._w = value;
+ this._onChangeCallback();
+
+ }
+
+ set( x, y, z, w ) {
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._w = w;
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ clone() {
+
+ return new this.constructor( this._x, this._y, this._z, this._w );
+
+ }
+
+ copy( quaternion ) {
+
+ this._x = quaternion.x;
+ this._y = quaternion.y;
+ this._z = quaternion.z;
+ this._w = quaternion.w;
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ setFromEuler( euler, update ) {
+
+ if ( ! ( euler && euler.isEuler ) ) {
+
+ throw new Error( 'THREE.Quaternion: .setFromEuler() now expects an Euler rotation rather than a Vector3 and order.' );
+
+ }
+
+ const x = euler._x, y = euler._y, z = euler._z, order = euler._order;
+
+ // http://www.mathworks.com/matlabcentral/fileexchange/
+ // 20696-function-to-convert-between-dcm-euler-angles-quaternions-and-euler-vectors/
+ // content/SpinCalc.m
+
+ const cos = Math.cos;
+ const sin = Math.sin;
+
+ const c1 = cos( x / 2 );
+ const c2 = cos( y / 2 );
+ const c3 = cos( z / 2 );
+
+ const s1 = sin( x / 2 );
+ const s2 = sin( y / 2 );
+ const s3 = sin( z / 2 );
+
+ switch ( order ) {
+
+ case 'XYZ':
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+ break;
+
+ case 'YXZ':
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+ break;
+
+ case 'ZXY':
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+ break;
+
+ case 'ZYX':
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+ break;
+
+ case 'YZX':
+ this._x = s1 * c2 * c3 + c1 * s2 * s3;
+ this._y = c1 * s2 * c3 + s1 * c2 * s3;
+ this._z = c1 * c2 * s3 - s1 * s2 * c3;
+ this._w = c1 * c2 * c3 - s1 * s2 * s3;
+ break;
+
+ case 'XZY':
+ this._x = s1 * c2 * c3 - c1 * s2 * s3;
+ this._y = c1 * s2 * c3 - s1 * c2 * s3;
+ this._z = c1 * c2 * s3 + s1 * s2 * c3;
+ this._w = c1 * c2 * c3 + s1 * s2 * s3;
+ break;
+
+ default:
+ console.warn( 'THREE.Quaternion: .setFromEuler() encountered an unknown order: ' + order );
+
+ }
+
+ if ( update !== false ) this._onChangeCallback();
+
+ return this;
+
+ }
+
+ setFromAxisAngle( axis, angle ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/angleToQuaternion/index.htm
+
+ // assumes axis is normalized
+
+ const halfAngle = angle / 2, s = Math.sin( halfAngle );
+
+ this._x = axis.x * s;
+ this._y = axis.y * s;
+ this._z = axis.z * s;
+ this._w = Math.cos( halfAngle );
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ setFromRotationMatrix( m ) {
+
+ // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ const te = m.elements,
+
+ m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ],
+ m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ],
+ m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ],
+
+ trace = m11 + m22 + m33;
+
+ if ( trace > 0 ) {
+
+ const s = 0.5 / Math.sqrt( trace + 1.0 );
+
+ this._w = 0.25 / s;
+ this._x = ( m32 - m23 ) * s;
+ this._y = ( m13 - m31 ) * s;
+ this._z = ( m21 - m12 ) * s;
+
+ } else if ( m11 > m22 && m11 > m33 ) {
+
+ const s = 2.0 * Math.sqrt( 1.0 + m11 - m22 - m33 );
+
+ this._w = ( m32 - m23 ) / s;
+ this._x = 0.25 * s;
+ this._y = ( m12 + m21 ) / s;
+ this._z = ( m13 + m31 ) / s;
+
+ } else if ( m22 > m33 ) {
+
+ const s = 2.0 * Math.sqrt( 1.0 + m22 - m11 - m33 );
+
+ this._w = ( m13 - m31 ) / s;
+ this._x = ( m12 + m21 ) / s;
+ this._y = 0.25 * s;
+ this._z = ( m23 + m32 ) / s;
+
+ } else {
+
+ const s = 2.0 * Math.sqrt( 1.0 + m33 - m11 - m22 );
+
+ this._w = ( m21 - m12 ) / s;
+ this._x = ( m13 + m31 ) / s;
+ this._y = ( m23 + m32 ) / s;
+ this._z = 0.25 * s;
+
+ }
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ setFromUnitVectors( vFrom, vTo ) {
+
+ // assumes direction vectors vFrom and vTo are normalized
+
+ const EPS = 0.000001;
+
+ let r = vFrom.dot( vTo ) + 1;
+
+ if ( r < EPS ) {
+
+ r = 0;
+
+ if ( Math.abs( vFrom.x ) > Math.abs( vFrom.z ) ) {
+
+ this._x = - vFrom.y;
+ this._y = vFrom.x;
+ this._z = 0;
+ this._w = r;
+
+ } else {
+
+ this._x = 0;
+ this._y = - vFrom.z;
+ this._z = vFrom.y;
+ this._w = r;
+
+ }
+
+ } else {
+
+ // crossVectors( vFrom, vTo ); // inlined to avoid cyclic dependency on Vector3
+
+ this._x = vFrom.y * vTo.z - vFrom.z * vTo.y;
+ this._y = vFrom.z * vTo.x - vFrom.x * vTo.z;
+ this._z = vFrom.x * vTo.y - vFrom.y * vTo.x;
+ this._w = r;
+
+ }
+
+ return this.normalize();
+
+ }
+
+ angleTo( q ) {
+
+ return 2 * Math.acos( Math.abs( MathUtils.clamp( this.dot( q ), - 1, 1 ) ) );
+
+ }
+
+ rotateTowards( q, step ) {
+
+ const angle = this.angleTo( q );
+
+ if ( angle === 0 ) return this;
+
+ const t = Math.min( 1, step / angle );
+
+ this.slerp( q, t );
+
+ return this;
+
+ }
+
+ identity() {
+
+ return this.set( 0, 0, 0, 1 );
+
+ }
+
+ invert() {
+
+ // quaternion is assumed to have unit length
+
+ return this.conjugate();
+
+ }
+
+ conjugate() {
+
+ this._x *= - 1;
+ this._y *= - 1;
+ this._z *= - 1;
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ dot( v ) {
+
+ return this._x * v._x + this._y * v._y + this._z * v._z + this._w * v._w;
+
+ }
+
+ lengthSq() {
+
+ return this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w;
+
+ }
+
+ length() {
+
+ return Math.sqrt( this._x * this._x + this._y * this._y + this._z * this._z + this._w * this._w );
+
+ }
+
+ normalize() {
+
+ let l = this.length();
+
+ if ( l === 0 ) {
+
+ this._x = 0;
+ this._y = 0;
+ this._z = 0;
+ this._w = 1;
+
+ } else {
+
+ l = 1 / l;
+
+ this._x = this._x * l;
+ this._y = this._y * l;
+ this._z = this._z * l;
+ this._w = this._w * l;
+
+ }
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ multiply( q, p ) {
+
+ if ( p !== undefined ) {
+
+ console.warn( 'THREE.Quaternion: .multiply() now only accepts one argument. Use .multiplyQuaternions( a, b ) instead.' );
+ return this.multiplyQuaternions( q, p );
+
+ }
+
+ return this.multiplyQuaternions( this, q );
+
+ }
+
+ premultiply( q ) {
+
+ return this.multiplyQuaternions( q, this );
+
+ }
+
+ multiplyQuaternions( a, b ) {
+
+ // from http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/code/index.htm
+
+ const qax = a._x, qay = a._y, qaz = a._z, qaw = a._w;
+ const qbx = b._x, qby = b._y, qbz = b._z, qbw = b._w;
+
+ this._x = qax * qbw + qaw * qbx + qay * qbz - qaz * qby;
+ this._y = qay * qbw + qaw * qby + qaz * qbx - qax * qbz;
+ this._z = qaz * qbw + qaw * qbz + qax * qby - qay * qbx;
+ this._w = qaw * qbw - qax * qbx - qay * qby - qaz * qbz;
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ slerp( qb, t ) {
+
+ if ( t === 0 ) return this;
+ if ( t === 1 ) return this.copy( qb );
+
+ const x = this._x, y = this._y, z = this._z, w = this._w;
+
+ // http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
+
+ let cosHalfTheta = w * qb._w + x * qb._x + y * qb._y + z * qb._z;
+
+ if ( cosHalfTheta < 0 ) {
+
+ this._w = - qb._w;
+ this._x = - qb._x;
+ this._y = - qb._y;
+ this._z = - qb._z;
+
+ cosHalfTheta = - cosHalfTheta;
+
+ } else {
+
+ this.copy( qb );
+
+ }
+
+ if ( cosHalfTheta >= 1.0 ) {
+
+ this._w = w;
+ this._x = x;
+ this._y = y;
+ this._z = z;
+
+ return this;
+
+ }
+
+ const sqrSinHalfTheta = 1.0 - cosHalfTheta * cosHalfTheta;
+
+ if ( sqrSinHalfTheta <= Number.EPSILON ) {
+
+ const s = 1 - t;
+ this._w = s * w + t * this._w;
+ this._x = s * x + t * this._x;
+ this._y = s * y + t * this._y;
+ this._z = s * z + t * this._z;
+
+ this.normalize();
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ const sinHalfTheta = Math.sqrt( sqrSinHalfTheta );
+ const halfTheta = Math.atan2( sinHalfTheta, cosHalfTheta );
+ const ratioA = Math.sin( ( 1 - t ) * halfTheta ) / sinHalfTheta,
+ ratioB = Math.sin( t * halfTheta ) / sinHalfTheta;
+
+ this._w = ( w * ratioA + this._w * ratioB );
+ this._x = ( x * ratioA + this._x * ratioB );
+ this._y = ( y * ratioA + this._y * ratioB );
+ this._z = ( z * ratioA + this._z * ratioB );
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ equals( quaternion ) {
+
+ return ( quaternion._x === this._x ) && ( quaternion._y === this._y ) && ( quaternion._z === this._z ) && ( quaternion._w === this._w );
+
+ }
+
+ fromArray( array, offset = 0 ) {
+
+ this._x = array[ offset ];
+ this._y = array[ offset + 1 ];
+ this._z = array[ offset + 2 ];
+ this._w = array[ offset + 3 ];
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ toArray( array = [], offset = 0 ) {
+
+ array[ offset ] = this._x;
+ array[ offset + 1 ] = this._y;
+ array[ offset + 2 ] = this._z;
+ array[ offset + 3 ] = this._w;
+
+ return array;
+
+ }
+
+ fromBufferAttribute( attribute, index ) {
+
+ this._x = attribute.getX( index );
+ this._y = attribute.getY( index );
+ this._z = attribute.getZ( index );
+ this._w = attribute.getW( index );
+
+ return this;
+
+ }
+
+ _onChange( callback ) {
+
+ this._onChangeCallback = callback;
+
+ return this;
+
+ }
+
+ _onChangeCallback() {}
+
+}
+
+class Vector3 {
+
+ constructor( x = 0, y = 0, z = 0 ) {
+
+ Object.defineProperty( this, 'isVector3', { value: true } );
+
+ this.x = x;
+ this.y = y;
+ this.z = z;
+
+ }
+
+ set( x, y, z ) {
+
+ if ( z === undefined ) z = this.z; // sprite.scale.set(x,y)
+
+ this.x = x;
+ this.y = y;
+ this.z = z;
+
+ return this;
+
+ }
+
+ setScalar( scalar ) {
+
+ this.x = scalar;
+ this.y = scalar;
+ this.z = scalar;
+
+ return this;
+
+ }
+
+ setX( x ) {
+
+ this.x = x;
+
+ return this;
+
+ }
+
+ setY( y ) {
+
+ this.y = y;
+
+ return this;
+
+ }
+
+ setZ( z ) {
+
+ this.z = z;
+
+ return this;
+
+ }
+
+ setComponent( index, value ) {
+
+ switch ( index ) {
+
+ case 0: this.x = value; break;
+ case 1: this.y = value; break;
+ case 2: this.z = value; break;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ return this;
+
+ }
+
+ getComponent( index ) {
+
+ switch ( index ) {
+
+ case 0: return this.x;
+ case 1: return this.y;
+ case 2: return this.z;
+ default: throw new Error( 'index is out of range: ' + index );
+
+ }
+
+ }
+
+ clone() {
+
+ return new this.constructor( this.x, this.y, this.z );
+
+ }
+
+ copy( v ) {
+
+ this.x = v.x;
+ this.y = v.y;
+ this.z = v.z;
+
+ return this;
+
+ }
+
+ add( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector3: .add() now only accepts one argument. Use .addVectors( a, b ) instead.' );
+ return this.addVectors( v, w );
+
+ }
+
+ this.x += v.x;
+ this.y += v.y;
+ this.z += v.z;
+
+ return this;
+
+ }
+
+ addScalar( s ) {
+
+ this.x += s;
+ this.y += s;
+ this.z += s;
+
+ return this;
+
+ }
+
+ addVectors( a, b ) {
+
+ this.x = a.x + b.x;
+ this.y = a.y + b.y;
+ this.z = a.z + b.z;
+
+ return this;
+
+ }
+
+ addScaledVector( v, s ) {
+
+ this.x += v.x * s;
+ this.y += v.y * s;
+ this.z += v.z * s;
+
+ return this;
+
+ }
+
+ sub( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector3: .sub() now only accepts one argument. Use .subVectors( a, b ) instead.' );
+ return this.subVectors( v, w );
+
+ }
+
+ this.x -= v.x;
+ this.y -= v.y;
+ this.z -= v.z;
+
+ return this;
+
+ }
+
+ subScalar( s ) {
+
+ this.x -= s;
+ this.y -= s;
+ this.z -= s;
+
+ return this;
+
+ }
+
+ subVectors( a, b ) {
+
+ this.x = a.x - b.x;
+ this.y = a.y - b.y;
+ this.z = a.z - b.z;
+
+ return this;
+
+ }
+
+ multiply( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector3: .multiply() now only accepts one argument. Use .multiplyVectors( a, b ) instead.' );
+ return this.multiplyVectors( v, w );
+
+ }
+
+ this.x *= v.x;
+ this.y *= v.y;
+ this.z *= v.z;
+
+ return this;
+
+ }
+
+ multiplyScalar( scalar ) {
+
+ this.x *= scalar;
+ this.y *= scalar;
+ this.z *= scalar;
+
+ return this;
+
+ }
+
+ multiplyVectors( a, b ) {
+
+ this.x = a.x * b.x;
+ this.y = a.y * b.y;
+ this.z = a.z * b.z;
+
+ return this;
+
+ }
+
+ applyEuler( euler ) {
+
+ if ( ! ( euler && euler.isEuler ) ) {
+
+ console.error( 'THREE.Vector3: .applyEuler() now expects an Euler rotation rather than a Vector3 and order.' );
+
+ }
+
+ return this.applyQuaternion( _quaternion.setFromEuler( euler ) );
+
+ }
+
+ applyAxisAngle( axis, angle ) {
+
+ return this.applyQuaternion( _quaternion.setFromAxisAngle( axis, angle ) );
+
+ }
+
+ applyMatrix3( m ) {
+
+ const x = this.x, y = this.y, z = this.z;
+ const e = m.elements;
+
+ this.x = e[ 0 ] * x + e[ 3 ] * y + e[ 6 ] * z;
+ this.y = e[ 1 ] * x + e[ 4 ] * y + e[ 7 ] * z;
+ this.z = e[ 2 ] * x + e[ 5 ] * y + e[ 8 ] * z;
+
+ return this;
+
+ }
+
+ applyNormalMatrix( m ) {
+
+ return this.applyMatrix3( m ).normalize();
+
+ }
+
+ applyMatrix4( m ) {
+
+ const x = this.x, y = this.y, z = this.z;
+ const e = m.elements;
+
+ const w = 1 / ( e[ 3 ] * x + e[ 7 ] * y + e[ 11 ] * z + e[ 15 ] );
+
+ this.x = ( e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z + e[ 12 ] ) * w;
+ this.y = ( e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z + e[ 13 ] ) * w;
+ this.z = ( e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z + e[ 14 ] ) * w;
+
+ return this;
+
+ }
+
+ applyQuaternion( q ) {
+
+ const x = this.x, y = this.y, z = this.z;
+ const qx = q.x, qy = q.y, qz = q.z, qw = q.w;
+
+ // calculate quat * vector
+
+ const ix = qw * x + qy * z - qz * y;
+ const iy = qw * y + qz * x - qx * z;
+ const iz = qw * z + qx * y - qy * x;
+ const iw = - qx * x - qy * y - qz * z;
+
+ // calculate result * inverse quat
+
+ this.x = ix * qw + iw * - qx + iy * - qz - iz * - qy;
+ this.y = iy * qw + iw * - qy + iz * - qx - ix * - qz;
+ this.z = iz * qw + iw * - qz + ix * - qy - iy * - qx;
+
+ return this;
+
+ }
+
+ project( camera ) {
+
+ return this.applyMatrix4( camera.matrixWorldInverse ).applyMatrix4( camera.projectionMatrix );
+
+ }
+
+ unproject( camera ) {
+
+ return this.applyMatrix4( camera.projectionMatrixInverse ).applyMatrix4( camera.matrixWorld );
+
+ }
+
+ transformDirection( m ) {
+
+ // input: THREE.Matrix4 affine matrix
+ // vector interpreted as a direction
+
+ const x = this.x, y = this.y, z = this.z;
+ const e = m.elements;
+
+ this.x = e[ 0 ] * x + e[ 4 ] * y + e[ 8 ] * z;
+ this.y = e[ 1 ] * x + e[ 5 ] * y + e[ 9 ] * z;
+ this.z = e[ 2 ] * x + e[ 6 ] * y + e[ 10 ] * z;
+
+ return this.normalize();
+
+ }
+
+ divide( v ) {
+
+ this.x /= v.x;
+ this.y /= v.y;
+ this.z /= v.z;
+
+ return this;
+
+ }
+
+ divideScalar( scalar ) {
+
+ return this.multiplyScalar( 1 / scalar );
+
+ }
+
+ min( v ) {
+
+ this.x = Math.min( this.x, v.x );
+ this.y = Math.min( this.y, v.y );
+ this.z = Math.min( this.z, v.z );
+
+ return this;
+
+ }
+
+ max( v ) {
+
+ this.x = Math.max( this.x, v.x );
+ this.y = Math.max( this.y, v.y );
+ this.z = Math.max( this.z, v.z );
+
+ return this;
+
+ }
+
+ clamp( min, max ) {
+
+ // assumes min < max, componentwise
+
+ this.x = Math.max( min.x, Math.min( max.x, this.x ) );
+ this.y = Math.max( min.y, Math.min( max.y, this.y ) );
+ this.z = Math.max( min.z, Math.min( max.z, this.z ) );
+
+ return this;
+
+ }
+
+ clampScalar( minVal, maxVal ) {
+
+ this.x = Math.max( minVal, Math.min( maxVal, this.x ) );
+ this.y = Math.max( minVal, Math.min( maxVal, this.y ) );
+ this.z = Math.max( minVal, Math.min( maxVal, this.z ) );
+
+ return this;
+
+ }
+
+ clampLength( min, max ) {
+
+ const length = this.length();
+
+ return this.divideScalar( length || 1 ).multiplyScalar( Math.max( min, Math.min( max, length ) ) );
+
+ }
+
+ floor() {
+
+ this.x = Math.floor( this.x );
+ this.y = Math.floor( this.y );
+ this.z = Math.floor( this.z );
+
+ return this;
+
+ }
+
+ ceil() {
+
+ this.x = Math.ceil( this.x );
+ this.y = Math.ceil( this.y );
+ this.z = Math.ceil( this.z );
+
+ return this;
+
+ }
+
+ round() {
+
+ this.x = Math.round( this.x );
+ this.y = Math.round( this.y );
+ this.z = Math.round( this.z );
+
+ return this;
+
+ }
+
+ roundToZero() {
+
+ this.x = ( this.x < 0 ) ? Math.ceil( this.x ) : Math.floor( this.x );
+ this.y = ( this.y < 0 ) ? Math.ceil( this.y ) : Math.floor( this.y );
+ this.z = ( this.z < 0 ) ? Math.ceil( this.z ) : Math.floor( this.z );
+
+ return this;
+
+ }
+
+ negate() {
+
+ this.x = - this.x;
+ this.y = - this.y;
+ this.z = - this.z;
+
+ return this;
+
+ }
+
+ dot( v ) {
+
+ return this.x * v.x + this.y * v.y + this.z * v.z;
+
+ }
+
+ // TODO lengthSquared?
+
+ lengthSq() {
+
+ return this.x * this.x + this.y * this.y + this.z * this.z;
+
+ }
+
+ length() {
+
+ return Math.sqrt( this.x * this.x + this.y * this.y + this.z * this.z );
+
+ }
+
+ manhattanLength() {
+
+ return Math.abs( this.x ) + Math.abs( this.y ) + Math.abs( this.z );
+
+ }
+
+ normalize() {
+
+ return this.divideScalar( this.length() || 1 );
+
+ }
+
+ setLength( length ) {
+
+ return this.normalize().multiplyScalar( length );
+
+ }
+
+ lerp( v, alpha ) {
+
+ this.x += ( v.x - this.x ) * alpha;
+ this.y += ( v.y - this.y ) * alpha;
+ this.z += ( v.z - this.z ) * alpha;
+
+ return this;
+
+ }
+
+ lerpVectors( v1, v2, alpha ) {
+
+ this.x = v1.x + ( v2.x - v1.x ) * alpha;
+ this.y = v1.y + ( v2.y - v1.y ) * alpha;
+ this.z = v1.z + ( v2.z - v1.z ) * alpha;
+
+ return this;
+
+ }
+
+ cross( v, w ) {
+
+ if ( w !== undefined ) {
+
+ console.warn( 'THREE.Vector3: .cross() now only accepts one argument. Use .crossVectors( a, b ) instead.' );
+ return this.crossVectors( v, w );
+
+ }
+
+ return this.crossVectors( this, v );
+
+ }
+
+ crossVectors( a, b ) {
+
+ const ax = a.x, ay = a.y, az = a.z;
+ const bx = b.x, by = b.y, bz = b.z;
+
+ this.x = ay * bz - az * by;
+ this.y = az * bx - ax * bz;
+ this.z = ax * by - ay * bx;
+
+ return this;
+
+ }
+
+ projectOnVector( v ) {
+
+ const denominator = v.lengthSq();
+
+ if ( denominator === 0 ) return this.set( 0, 0, 0 );
+
+ const scalar = v.dot( this ) / denominator;
+
+ return this.copy( v ).multiplyScalar( scalar );
+
+ }
+
+ projectOnPlane( planeNormal ) {
+
+ _vector.copy( this ).projectOnVector( planeNormal );
+
+ return this.sub( _vector );
+
+ }
+
+ reflect( normal ) {
+
+ // reflect incident vector off plane orthogonal to normal
+ // normal is assumed to have unit length
+
+ return this.sub( _vector.copy( normal ).multiplyScalar( 2 * this.dot( normal ) ) );
+
+ }
+
+ angleTo( v ) {
+
+ const denominator = Math.sqrt( this.lengthSq() * v.lengthSq() );
+
+ if ( denominator === 0 ) return Math.PI / 2;
+
+ const theta = this.dot( v ) / denominator;
+
+ // clamp, to handle numerical problems
+
+ return Math.acos( MathUtils.clamp( theta, - 1, 1 ) );
+
+ }
+
+ distanceTo( v ) {
+
+ return Math.sqrt( this.distanceToSquared( v ) );
+
+ }
+
+ distanceToSquared( v ) {
+
+ const dx = this.x - v.x, dy = this.y - v.y, dz = this.z - v.z;
+
+ return dx * dx + dy * dy + dz * dz;
+
+ }
+
+ manhattanDistanceTo( v ) {
+
+ return Math.abs( this.x - v.x ) + Math.abs( this.y - v.y ) + Math.abs( this.z - v.z );
+
+ }
+
+ setFromSpherical( s ) {
+
+ return this.setFromSphericalCoords( s.radius, s.phi, s.theta );
+
+ }
+
+ setFromSphericalCoords( radius, phi, theta ) {
+
+ const sinPhiRadius = Math.sin( phi ) * radius;
+
+ this.x = sinPhiRadius * Math.sin( theta );
+ this.y = Math.cos( phi ) * radius;
+ this.z = sinPhiRadius * Math.cos( theta );
+
+ return this;
+
+ }
+
+ setFromCylindrical( c ) {
+
+ return this.setFromCylindricalCoords( c.radius, c.theta, c.y );
+
+ }
+
+ setFromCylindricalCoords( radius, theta, y ) {
+
+ this.x = radius * Math.sin( theta );
+ this.y = y;
+ this.z = radius * Math.cos( theta );
+
+ return this;
+
+ }
+
+ setFromMatrixPosition( m ) {
+
+ const e = m.elements;
+
+ this.x = e[ 12 ];
+ this.y = e[ 13 ];
+ this.z = e[ 14 ];
+
+ return this;
+
+ }
+
+ setFromMatrixScale( m ) {
+
+ const sx = this.setFromMatrixColumn( m, 0 ).length();
+ const sy = this.setFromMatrixColumn( m, 1 ).length();
+ const sz = this.setFromMatrixColumn( m, 2 ).length();
+
+ this.x = sx;
+ this.y = sy;
+ this.z = sz;
+
+ return this;
+
+ }
+
+ setFromMatrixColumn( m, index ) {
+
+ return this.fromArray( m.elements, index * 4 );
+
+ }
+
+ setFromMatrix3Column( m, index ) {
+
+ return this.fromArray( m.elements, index * 3 );
+
+ }
+
+ equals( v ) {
+
+ return ( ( v.x === this.x ) && ( v.y === this.y ) && ( v.z === this.z ) );
+
+ }
+
+ fromArray( array, offset = 0 ) {
+
+ this.x = array[ offset ];
+ this.y = array[ offset + 1 ];
+ this.z = array[ offset + 2 ];
+
+ return this;
+
+ }
+
+ toArray( array = [], offset = 0 ) {
+
+ array[ offset ] = this.x;
+ array[ offset + 1 ] = this.y;
+ array[ offset + 2 ] = this.z;
+
+ return array;
+
+ }
+
+ fromBufferAttribute( attribute, index, offset ) {
+
+ if ( offset !== undefined ) {
+
+ console.warn( 'THREE.Vector3: offset has been removed from .fromBufferAttribute().' );
+
+ }
+
+ this.x = attribute.getX( index );
+ this.y = attribute.getY( index );
+ this.z = attribute.getZ( index );
+
+ return this;
+
+ }
+
+ random() {
+
+ this.x = Math.random();
+ this.y = Math.random();
+ this.z = Math.random();
+
+ return this;
+
+ }
+
+}
+
+const _vector = /*@__PURE__*/ new Vector3();
+const _quaternion = /*@__PURE__*/ new Quaternion();
+
+class Box3 {
+
+ constructor( min, max ) {
+
+ Object.defineProperty( this, 'isBox3', { value: true } );
+
+ this.min = ( min !== undefined ) ? min : new Vector3( + Infinity, + Infinity, + Infinity );
+ this.max = ( max !== undefined ) ? max : new Vector3( - Infinity, - Infinity, - Infinity );
+
+ }
+
+ set( min, max ) {
+
+ this.min.copy( min );
+ this.max.copy( max );
+
+ return this;
+
+ }
+
+ setFromArray( array ) {
+
+ let minX = + Infinity;
+ let minY = + Infinity;
+ let minZ = + Infinity;
+
+ let maxX = - Infinity;
+ let maxY = - Infinity;
+ let maxZ = - Infinity;
+
+ for ( let i = 0, l = array.length; i < l; i += 3 ) {
+
+ const x = array[ i ];
+ const y = array[ i + 1 ];
+ const z = array[ i + 2 ];
+
+ if ( x < minX ) minX = x;
+ if ( y < minY ) minY = y;
+ if ( z < minZ ) minZ = z;
+
+ if ( x > maxX ) maxX = x;
+ if ( y > maxY ) maxY = y;
+ if ( z > maxZ ) maxZ = z;
+
+ }
+
+ this.min.set( minX, minY, minZ );
+ this.max.set( maxX, maxY, maxZ );
+
+ return this;
+
+ }
+
+ setFromBufferAttribute( attribute ) {
+
+ let minX = + Infinity;
+ let minY = + Infinity;
+ let minZ = + Infinity;
+
+ let maxX = - Infinity;
+ let maxY = - Infinity;
+ let maxZ = - Infinity;
+
+ for ( let i = 0, l = attribute.count; i < l; i ++ ) {
+
+ const x = attribute.getX( i );
+ const y = attribute.getY( i );
+ const z = attribute.getZ( i );
+
+ if ( x < minX ) minX = x;
+ if ( y < minY ) minY = y;
+ if ( z < minZ ) minZ = z;
+
+ if ( x > maxX ) maxX = x;
+ if ( y > maxY ) maxY = y;
+ if ( z > maxZ ) maxZ = z;
+
+ }
+
+ this.min.set( minX, minY, minZ );
+ this.max.set( maxX, maxY, maxZ );
+
+ return this;
+
+ }
+
+ setFromPoints( points ) {
+
+ this.makeEmpty();
+
+ for ( let i = 0, il = points.length; i < il; i ++ ) {
+
+ this.expandByPoint( points[ i ] );
+
+ }
+
+ return this;
+
+ }
+
+ setFromCenterAndSize( center, size ) {
+
+ const halfSize = _vector$1.copy( size ).multiplyScalar( 0.5 );
+
+ this.min.copy( center ).sub( halfSize );
+ this.max.copy( center ).add( halfSize );
+
+ return this;
+
+ }
+
+ setFromObject( object ) {
+
+ this.makeEmpty();
+
+ return this.expandByObject( object );
+
+ }
+
+ clone() {
+
+ return new this.constructor().copy( this );
+
+ }
+
+ copy( box ) {
+
+ this.min.copy( box.min );
+ this.max.copy( box.max );
+
+ return this;
+
+ }
+
+ makeEmpty() {
+
+ this.min.x = this.min.y = this.min.z = + Infinity;
+ this.max.x = this.max.y = this.max.z = - Infinity;
+
+ return this;
+
+ }
+
+ isEmpty() {
+
+ // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
+
+ return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y ) || ( this.max.z < this.min.z );
+
+ }
+
+ getCenter( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box3: .getCenter() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return this.isEmpty() ? target.set( 0, 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+
+ }
+
+ getSize( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box3: .getSize() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return this.isEmpty() ? target.set( 0, 0, 0 ) : target.subVectors( this.max, this.min );
+
+ }
+
+ expandByPoint( point ) {
+
+ this.min.min( point );
+ this.max.max( point );
+
+ return this;
+
+ }
+
+ expandByVector( vector ) {
+
+ this.min.sub( vector );
+ this.max.add( vector );
+
+ return this;
+
+ }
+
+ expandByScalar( scalar ) {
+
+ this.min.addScalar( - scalar );
+ this.max.addScalar( scalar );
+
+ return this;
+
+ }
+
+ expandByObject( object ) {
+
+ // Computes the world-axis-aligned bounding box of an object (including its children),
+ // accounting for both the object's, and children's, world transforms
+
+ object.updateWorldMatrix( false, false );
+
+ const geometry = object.geometry;
+
+ if ( geometry !== undefined ) {
+
+ if ( geometry.boundingBox === null ) {
+
+ geometry.computeBoundingBox();
+
+ }
+
+ _box.copy( geometry.boundingBox );
+ _box.applyMatrix4( object.matrixWorld );
+
+ this.union( _box );
+
+ }
+
+ const children = object.children;
+
+ for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+ this.expandByObject( children[ i ] );
+
+ }
+
+ return this;
+
+ }
+
+ containsPoint( point ) {
+
+ return point.x < this.min.x || point.x > this.max.x ||
+ point.y < this.min.y || point.y > this.max.y ||
+ point.z < this.min.z || point.z > this.max.z ? false : true;
+
+ }
+
+ containsBox( box ) {
+
+ return this.min.x <= box.min.x && box.max.x <= this.max.x &&
+ this.min.y <= box.min.y && box.max.y <= this.max.y &&
+ this.min.z <= box.min.z && box.max.z <= this.max.z;
+
+ }
+
+ getParameter( point, target ) {
+
+ // This can potentially have a divide by zero if the box
+ // has a size dimension of 0.
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box3: .getParameter() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return target.set(
+ ( point.x - this.min.x ) / ( this.max.x - this.min.x ),
+ ( point.y - this.min.y ) / ( this.max.y - this.min.y ),
+ ( point.z - this.min.z ) / ( this.max.z - this.min.z )
+ );
+
+ }
+
+ intersectsBox( box ) {
+
+ // using 6 splitting planes to rule out intersections.
+ return box.max.x < this.min.x || box.min.x > this.max.x ||
+ box.max.y < this.min.y || box.min.y > this.max.y ||
+ box.max.z < this.min.z || box.min.z > this.max.z ? false : true;
+
+ }
+
+ intersectsSphere( sphere ) {
+
+ // Find the point on the AABB closest to the sphere center.
+ this.clampPoint( sphere.center, _vector$1 );
+
+ // If that point is inside the sphere, the AABB and sphere intersect.
+ return _vector$1.distanceToSquared( sphere.center ) <= ( sphere.radius * sphere.radius );
+
+ }
+
+ intersectsPlane( plane ) {
+
+ // We compute the minimum and maximum dot product values. If those values
+ // are on the same side (back or front) of the plane, then there is no intersection.
+
+ let min, max;
+
+ if ( plane.normal.x > 0 ) {
+
+ min = plane.normal.x * this.min.x;
+ max = plane.normal.x * this.max.x;
+
+ } else {
+
+ min = plane.normal.x * this.max.x;
+ max = plane.normal.x * this.min.x;
+
+ }
+
+ if ( plane.normal.y > 0 ) {
+
+ min += plane.normal.y * this.min.y;
+ max += plane.normal.y * this.max.y;
+
+ } else {
+
+ min += plane.normal.y * this.max.y;
+ max += plane.normal.y * this.min.y;
+
+ }
+
+ if ( plane.normal.z > 0 ) {
+
+ min += plane.normal.z * this.min.z;
+ max += plane.normal.z * this.max.z;
+
+ } else {
+
+ min += plane.normal.z * this.max.z;
+ max += plane.normal.z * this.min.z;
+
+ }
+
+ return ( min <= - plane.constant && max >= - plane.constant );
+
+ }
+
+ intersectsTriangle( triangle ) {
+
+ if ( this.isEmpty() ) {
+
+ return false;
+
+ }
+
+ // compute box center and extents
+ this.getCenter( _center );
+ _extents.subVectors( this.max, _center );
+
+ // translate triangle to aabb origin
+ _v0.subVectors( triangle.a, _center );
+ _v1.subVectors( triangle.b, _center );
+ _v2.subVectors( triangle.c, _center );
+
+ // compute edge vectors for triangle
+ _f0.subVectors( _v1, _v0 );
+ _f1.subVectors( _v2, _v1 );
+ _f2.subVectors( _v0, _v2 );
+
+ // test against axes that are given by cross product combinations of the edges of the triangle and the edges of the aabb
+ // make an axis testing of each of the 3 sides of the aabb against each of the 3 sides of the triangle = 9 axis of separation
+ // axis_ij = u_i x f_j (u0, u1, u2 = face normals of aabb = x,y,z axes vectors since aabb is axis aligned)
+ let axes = [
+ 0, - _f0.z, _f0.y, 0, - _f1.z, _f1.y, 0, - _f2.z, _f2.y,
+ _f0.z, 0, - _f0.x, _f1.z, 0, - _f1.x, _f2.z, 0, - _f2.x,
+ - _f0.y, _f0.x, 0, - _f1.y, _f1.x, 0, - _f2.y, _f2.x, 0
+ ];
+ if ( ! satForAxes( axes, _v0, _v1, _v2, _extents ) ) {
+
+ return false;
+
+ }
+
+ // test 3 face normals from the aabb
+ axes = [ 1, 0, 0, 0, 1, 0, 0, 0, 1 ];
+ if ( ! satForAxes( axes, _v0, _v1, _v2, _extents ) ) {
+
+ return false;
+
+ }
+
+ // finally testing the face normal of the triangle
+ // use already existing triangle edge vectors here
+ _triangleNormal.crossVectors( _f0, _f1 );
+ axes = [ _triangleNormal.x, _triangleNormal.y, _triangleNormal.z ];
+
+ return satForAxes( axes, _v0, _v1, _v2, _extents );
+
+ }
+
+ clampPoint( point, target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box3: .clampPoint() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return target.copy( point ).clamp( this.min, this.max );
+
+ }
+
+ distanceToPoint( point ) {
+
+ const clampedPoint = _vector$1.copy( point ).clamp( this.min, this.max );
+
+ return clampedPoint.sub( point ).length();
+
+ }
+
+ getBoundingSphere( target ) {
+
+ if ( target === undefined ) {
+
+ console.error( 'THREE.Box3: .getBoundingSphere() target is now required' );
+ //target = new Sphere(); // removed to avoid cyclic dependency
+
+ }
+
+ this.getCenter( target.center );
+
+ target.radius = this.getSize( _vector$1 ).length() * 0.5;
+
+ return target;
+
+ }
+
+ intersect( box ) {
+
+ this.min.max( box.min );
+ this.max.min( box.max );
+
+ // ensure that if there is no overlap, the result is fully empty, not slightly empty with non-inf/+inf values that will cause subsequence intersects to erroneously return valid values.
+ if ( this.isEmpty() ) this.makeEmpty();
+
+ return this;
+
+ }
+
+ union( box ) {
+
+ this.min.min( box.min );
+ this.max.max( box.max );
+
+ return this;
+
+ }
+
+ applyMatrix4( matrix ) {
+
+ // transform of empty box is an empty box.
+ if ( this.isEmpty() ) return this;
+
+ // NOTE: I am using a binary pattern to specify all 2^3 combinations below
+ _points[ 0 ].set( this.min.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 000
+ _points[ 1 ].set( this.min.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 001
+ _points[ 2 ].set( this.min.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 010
+ _points[ 3 ].set( this.min.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 011
+ _points[ 4 ].set( this.max.x, this.min.y, this.min.z ).applyMatrix4( matrix ); // 100
+ _points[ 5 ].set( this.max.x, this.min.y, this.max.z ).applyMatrix4( matrix ); // 101
+ _points[ 6 ].set( this.max.x, this.max.y, this.min.z ).applyMatrix4( matrix ); // 110
+ _points[ 7 ].set( this.max.x, this.max.y, this.max.z ).applyMatrix4( matrix ); // 111
+
+ this.setFromPoints( _points );
+
+ return this;
+
+ }
+
+ translate( offset ) {
+
+ this.min.add( offset );
+ this.max.add( offset );
+
+ return this;
+
+ }
+
+ equals( box ) {
+
+ return box.min.equals( this.min ) && box.max.equals( this.max );
+
+ }
+
+}
+
+function satForAxes( axes, v0, v1, v2, extents ) {
+
+ for ( let i = 0, j = axes.length - 3; i <= j; i += 3 ) {
+
+ _testAxis.fromArray( axes, i );
+ // project the aabb onto the seperating axis
+ const r = extents.x * Math.abs( _testAxis.x ) + extents.y * Math.abs( _testAxis.y ) + extents.z * Math.abs( _testAxis.z );
+ // project all 3 vertices of the triangle onto the seperating axis
+ const p0 = v0.dot( _testAxis );
+ const p1 = v1.dot( _testAxis );
+ const p2 = v2.dot( _testAxis );
+ // actual test, basically see if either of the most extreme of the triangle points intersects r
+ if ( Math.max( - Math.max( p0, p1, p2 ), Math.min( p0, p1, p2 ) ) > r ) {
+
+ // points of the projected triangle are outside the projected half-length of the aabb
+ // the axis is seperating and we can exit
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+}
+
+const _points = [
+ /*@__PURE__*/ new Vector3(),
+ /*@__PURE__*/ new Vector3(),
+ /*@__PURE__*/ new Vector3(),
+ /*@__PURE__*/ new Vector3(),
+ /*@__PURE__*/ new Vector3(),
+ /*@__PURE__*/ new Vector3(),
+ /*@__PURE__*/ new Vector3(),
+ /*@__PURE__*/ new Vector3()
+];
+
+const _vector$1 = /*@__PURE__*/ new Vector3();
+
+const _box = /*@__PURE__*/ new Box3();
+
+// triangle centered vertices
+
+const _v0 = /*@__PURE__*/ new Vector3();
+const _v1 = /*@__PURE__*/ new Vector3();
+const _v2 = /*@__PURE__*/ new Vector3();
+
+// triangle edge vectors
+
+const _f0 = /*@__PURE__*/ new Vector3();
+const _f1 = /*@__PURE__*/ new Vector3();
+const _f2 = /*@__PURE__*/ new Vector3();
+
+const _center = /*@__PURE__*/ new Vector3();
+const _extents = /*@__PURE__*/ new Vector3();
+const _triangleNormal = /*@__PURE__*/ new Vector3();
+const _testAxis = /*@__PURE__*/ new Vector3();
+
+const _box$1 = /*@__PURE__*/ new Box3();
+
+class Sphere {
+
+ constructor( center, radius ) {
+
+ this.center = ( center !== undefined ) ? center : new Vector3();
+ this.radius = ( radius !== undefined ) ? radius : - 1;
+
+ }
+
+ set( center, radius ) {
+
+ this.center.copy( center );
+ this.radius = radius;
+
+ return this;
+
+ }
+
+ setFromPoints( points, optionalCenter ) {
+
+ const center = this.center;
+
+ if ( optionalCenter !== undefined ) {
+
+ center.copy( optionalCenter );
+
+ } else {
+
+ _box$1.setFromPoints( points ).getCenter( center );
+
+ }
+
+ let maxRadiusSq = 0;
+
+ for ( let i = 0, il = points.length; i < il; i ++ ) {
+
+ maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( points[ i ] ) );
+
+ }
+
+ this.radius = Math.sqrt( maxRadiusSq );
+
+ return this;
+
+ }
+
+ clone() {
+
+ return new this.constructor().copy( this );
+
+ }
+
+ copy( sphere ) {
+
+ this.center.copy( sphere.center );
+ this.radius = sphere.radius;
+
+ return this;
+
+ }
+
+ isEmpty() {
+
+ return ( this.radius < 0 );
+
+ }
+
+ makeEmpty() {
+
+ this.center.set( 0, 0, 0 );
+ this.radius = - 1;
+
+ return this;
+
+ }
+
+ containsPoint( point ) {
+
+ return ( point.distanceToSquared( this.center ) <= ( this.radius * this.radius ) );
+
+ }
+
+ distanceToPoint( point ) {
+
+ return ( point.distanceTo( this.center ) - this.radius );
+
+ }
+
+ intersectsSphere( sphere ) {
+
+ const radiusSum = this.radius + sphere.radius;
+
+ return sphere.center.distanceToSquared( this.center ) <= ( radiusSum * radiusSum );
+
+ }
+
+ intersectsBox( box ) {
+
+ return box.intersectsSphere( this );
+
+ }
+
+ intersectsPlane( plane ) {
+
+ return Math.abs( plane.distanceToPoint( this.center ) ) <= this.radius;
+
+ }
+
+ clampPoint( point, target ) {
+
+ const deltaLengthSq = this.center.distanceToSquared( point );
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Sphere: .clampPoint() target is now required' );
+ target = new Vector3();
+
+ }
+
+ target.copy( point );
+
+ if ( deltaLengthSq > ( this.radius * this.radius ) ) {
+
+ target.sub( this.center ).normalize();
+ target.multiplyScalar( this.radius ).add( this.center );
+
+ }
+
+ return target;
+
+ }
+
+ getBoundingBox( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Sphere: .getBoundingBox() target is now required' );
+ target = new Box3();
+
+ }
+
+ if ( this.isEmpty() ) {
+
+ // Empty sphere produces empty bounding box
+ target.makeEmpty();
+ return target;
+
+ }
+
+ target.set( this.center, this.center );
+ target.expandByScalar( this.radius );
+
+ return target;
+
+ }
+
+ applyMatrix4( matrix ) {
+
+ this.center.applyMatrix4( matrix );
+ this.radius = this.radius * matrix.getMaxScaleOnAxis();
+
+ return this;
+
+ }
+
+ translate( offset ) {
+
+ this.center.add( offset );
+
+ return this;
+
+ }
+
+ equals( sphere ) {
+
+ return sphere.center.equals( this.center ) && ( sphere.radius === this.radius );
+
+ }
+
+}
+
+const _vector$2 = /*@__PURE__*/ new Vector3();
+const _segCenter = /*@__PURE__*/ new Vector3();
+const _segDir = /*@__PURE__*/ new Vector3();
+const _diff = /*@__PURE__*/ new Vector3();
+
+const _edge1 = /*@__PURE__*/ new Vector3();
+const _edge2 = /*@__PURE__*/ new Vector3();
+const _normal = /*@__PURE__*/ new Vector3();
+
+class Ray {
+
+ constructor( origin, direction ) {
+
+ this.origin = ( origin !== undefined ) ? origin : new Vector3();
+ this.direction = ( direction !== undefined ) ? direction : new Vector3( 0, 0, - 1 );
+
+ }
+
+ set( origin, direction ) {
+
+ this.origin.copy( origin );
+ this.direction.copy( direction );
+
+ return this;
+
+ }
+
+ clone() {
+
+ return new this.constructor().copy( this );
+
+ }
+
+ copy( ray ) {
+
+ this.origin.copy( ray.origin );
+ this.direction.copy( ray.direction );
+
+ return this;
+
+ }
+
+ at( t, target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Ray: .at() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return target.copy( this.direction ).multiplyScalar( t ).add( this.origin );
+
+ }
+
+ lookAt( v ) {
+
+ this.direction.copy( v ).sub( this.origin ).normalize();
+
+ return this;
+
+ }
+
+ recast( t ) {
+
+ this.origin.copy( this.at( t, _vector$2 ) );
+
+ return this;
+
+ }
+
+ closestPointToPoint( point, target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Ray: .closestPointToPoint() target is now required' );
+ target = new Vector3();
+
+ }
+
+ target.subVectors( point, this.origin );
+
+ const directionDistance = target.dot( this.direction );
+
+ if ( directionDistance < 0 ) {
+
+ return target.copy( this.origin );
+
+ }
+
+ return target.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
+
+ }
+
+ distanceToPoint( point ) {
+
+ return Math.sqrt( this.distanceSqToPoint( point ) );
+
+ }
+
+ distanceSqToPoint( point ) {
+
+ const directionDistance = _vector$2.subVectors( point, this.origin ).dot( this.direction );
+
+ // point behind the ray
+
+ if ( directionDistance < 0 ) {
+
+ return this.origin.distanceToSquared( point );
+
+ }
+
+ _vector$2.copy( this.direction ).multiplyScalar( directionDistance ).add( this.origin );
+
+ return _vector$2.distanceToSquared( point );
+
+ }
+
+ distanceSqToSegment( v0, v1, optionalPointOnRay, optionalPointOnSegment ) {
+
+ // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteDistRaySegment.h
+ // It returns the min distance between the ray and the segment
+ // defined by v0 and v1
+ // It can also set two optional targets :
+ // - The closest point on the ray
+ // - The closest point on the segment
+
+ _segCenter.copy( v0 ).add( v1 ).multiplyScalar( 0.5 );
+ _segDir.copy( v1 ).sub( v0 ).normalize();
+ _diff.copy( this.origin ).sub( _segCenter );
+
+ const segExtent = v0.distanceTo( v1 ) * 0.5;
+ const a01 = - this.direction.dot( _segDir );
+ const b0 = _diff.dot( this.direction );
+ const b1 = - _diff.dot( _segDir );
+ const c = _diff.lengthSq();
+ const det = Math.abs( 1 - a01 * a01 );
+ let s0, s1, sqrDist, extDet;
+
+ if ( det > 0 ) {
+
+ // The ray and segment are not parallel.
+
+ s0 = a01 * b1 - b0;
+ s1 = a01 * b0 - b1;
+ extDet = segExtent * det;
+
+ if ( s0 >= 0 ) {
+
+ if ( s1 >= - extDet ) {
+
+ if ( s1 <= extDet ) {
+
+ // region 0
+ // Minimum at interior points of ray and segment.
+
+ const invDet = 1 / det;
+ s0 *= invDet;
+ s1 *= invDet;
+ sqrDist = s0 * ( s0 + a01 * s1 + 2 * b0 ) + s1 * ( a01 * s0 + s1 + 2 * b1 ) + c;
+
+ } else {
+
+ // region 1
+
+ s1 = segExtent;
+ s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ } else {
+
+ // region 5
+
+ s1 = - segExtent;
+ s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ } else {
+
+ if ( s1 <= - extDet ) {
+
+ // region 4
+
+ s0 = Math.max( 0, - ( - a01 * segExtent + b0 ) );
+ s1 = ( s0 > 0 ) ? - segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ } else if ( s1 <= extDet ) {
+
+ // region 3
+
+ s0 = 0;
+ s1 = Math.min( Math.max( - segExtent, - b1 ), segExtent );
+ sqrDist = s1 * ( s1 + 2 * b1 ) + c;
+
+ } else {
+
+ // region 2
+
+ s0 = Math.max( 0, - ( a01 * segExtent + b0 ) );
+ s1 = ( s0 > 0 ) ? segExtent : Math.min( Math.max( - segExtent, - b1 ), segExtent );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ }
+
+ } else {
+
+ // Ray and segment are parallel.
+
+ s1 = ( a01 > 0 ) ? - segExtent : segExtent;
+ s0 = Math.max( 0, - ( a01 * s1 + b0 ) );
+ sqrDist = - s0 * s0 + s1 * ( s1 + 2 * b1 ) + c;
+
+ }
+
+ if ( optionalPointOnRay ) {
+
+ optionalPointOnRay.copy( this.direction ).multiplyScalar( s0 ).add( this.origin );
+
+ }
+
+ if ( optionalPointOnSegment ) {
+
+ optionalPointOnSegment.copy( _segDir ).multiplyScalar( s1 ).add( _segCenter );
+
+ }
+
+ return sqrDist;
+
+ }
+
+ intersectSphere( sphere, target ) {
+
+ _vector$2.subVectors( sphere.center, this.origin );
+ const tca = _vector$2.dot( this.direction );
+ const d2 = _vector$2.dot( _vector$2 ) - tca * tca;
+ const radius2 = sphere.radius * sphere.radius;
+
+ if ( d2 > radius2 ) return null;
+
+ const thc = Math.sqrt( radius2 - d2 );
+
+ // t0 = first intersect point - entrance on front of sphere
+ const t0 = tca - thc;
+
+ // t1 = second intersect point - exit point on back of sphere
+ const t1 = tca + thc;
+
+ // test to see if both t0 and t1 are behind the ray - if so, return null
+ if ( t0 < 0 && t1 < 0 ) return null;
+
+ // test to see if t0 is behind the ray:
+ // if it is, the ray is inside the sphere, so return the second exit point scaled by t1,
+ // in order to always return an intersect point that is in front of the ray.
+ if ( t0 < 0 ) return this.at( t1, target );
+
+ // else t0 is in front of the ray, so return the first collision point scaled by t0
+ return this.at( t0, target );
+
+ }
+
+ intersectsSphere( sphere ) {
+
+ return this.distanceSqToPoint( sphere.center ) <= ( sphere.radius * sphere.radius );
+
+ }
+
+ distanceToPlane( plane ) {
+
+ const denominator = plane.normal.dot( this.direction );
+
+ if ( denominator === 0 ) {
+
+ // line is coplanar, return origin
+ if ( plane.distanceToPoint( this.origin ) === 0 ) {
+
+ return 0;
+
+ }
+
+ // Null is preferable to undefined since undefined means.... it is undefined
+
+ return null;
+
+ }
+
+ const t = - ( this.origin.dot( plane.normal ) + plane.constant ) / denominator;
+
+ // Return if the ray never intersects the plane
+
+ return t >= 0 ? t : null;
+
+ }
+
+ intersectPlane( plane, target ) {
+
+ const t = this.distanceToPlane( plane );
+
+ if ( t === null ) {
+
+ return null;
+
+ }
+
+ return this.at( t, target );
+
+ }
+
+ intersectsPlane( plane ) {
+
+ // check if the ray lies on the plane first
+
+ const distToPoint = plane.distanceToPoint( this.origin );
+
+ if ( distToPoint === 0 ) {
+
+ return true;
+
+ }
+
+ const denominator = plane.normal.dot( this.direction );
+
+ if ( denominator * distToPoint < 0 ) {
+
+ return true;
+
+ }
+
+ // ray origin is behind the plane (and is pointing behind it)
+
+ return false;
+
+ }
+
+ intersectBox( box, target ) {
+
+ let tmin, tmax, tymin, tymax, tzmin, tzmax;
+
+ const invdirx = 1 / this.direction.x,
+ invdiry = 1 / this.direction.y,
+ invdirz = 1 / this.direction.z;
+
+ const origin = this.origin;
+
+ if ( invdirx >= 0 ) {
+
+ tmin = ( box.min.x - origin.x ) * invdirx;
+ tmax = ( box.max.x - origin.x ) * invdirx;
+
+ } else {
+
+ tmin = ( box.max.x - origin.x ) * invdirx;
+ tmax = ( box.min.x - origin.x ) * invdirx;
+
+ }
+
+ if ( invdiry >= 0 ) {
+
+ tymin = ( box.min.y - origin.y ) * invdiry;
+ tymax = ( box.max.y - origin.y ) * invdiry;
+
+ } else {
+
+ tymin = ( box.max.y - origin.y ) * invdiry;
+ tymax = ( box.min.y - origin.y ) * invdiry;
+
+ }
+
+ if ( ( tmin > tymax ) || ( tymin > tmax ) ) return null;
+
+ // These lines also handle the case where tmin or tmax is NaN
+ // (result of 0 * Infinity). x !== x returns true if x is NaN
+
+ if ( tymin > tmin || tmin !== tmin ) tmin = tymin;
+
+ if ( tymax < tmax || tmax !== tmax ) tmax = tymax;
+
+ if ( invdirz >= 0 ) {
+
+ tzmin = ( box.min.z - origin.z ) * invdirz;
+ tzmax = ( box.max.z - origin.z ) * invdirz;
+
+ } else {
+
+ tzmin = ( box.max.z - origin.z ) * invdirz;
+ tzmax = ( box.min.z - origin.z ) * invdirz;
+
+ }
+
+ if ( ( tmin > tzmax ) || ( tzmin > tmax ) ) return null;
+
+ if ( tzmin > tmin || tmin !== tmin ) tmin = tzmin;
+
+ if ( tzmax < tmax || tmax !== tmax ) tmax = tzmax;
+
+ //return point closest to the ray (positive side)
+
+ if ( tmax < 0 ) return null;
+
+ return this.at( tmin >= 0 ? tmin : tmax, target );
+
+ }
+
+ intersectsBox( box ) {
+
+ return this.intersectBox( box, _vector$2 ) !== null;
+
+ }
+
+ intersectTriangle( a, b, c, backfaceCulling, target ) {
+
+ // Compute the offset origin, edges, and normal.
+
+ // from http://www.geometrictools.com/GTEngine/Include/Mathematics/GteIntrRay3Triangle3.h
+
+ _edge1.subVectors( b, a );
+ _edge2.subVectors( c, a );
+ _normal.crossVectors( _edge1, _edge2 );
+
+ // Solve Q + t*D = b1*E1 + b2*E2 (Q = kDiff, D = ray direction,
+ // E1 = kEdge1, E2 = kEdge2, N = Cross(E1,E2)) by
+ // |Dot(D,N)|*b1 = sign(Dot(D,N))*Dot(D,Cross(Q,E2))
+ // |Dot(D,N)|*b2 = sign(Dot(D,N))*Dot(D,Cross(E1,Q))
+ // |Dot(D,N)|*t = -sign(Dot(D,N))*Dot(Q,N)
+ let DdN = this.direction.dot( _normal );
+ let sign;
+
+ if ( DdN > 0 ) {
+
+ if ( backfaceCulling ) return null;
+ sign = 1;
+
+ } else if ( DdN < 0 ) {
+
+ sign = - 1;
+ DdN = - DdN;
+
+ } else {
+
+ return null;
+
+ }
+
+ _diff.subVectors( this.origin, a );
+ const DdQxE2 = sign * this.direction.dot( _edge2.crossVectors( _diff, _edge2 ) );
+
+ // b1 < 0, no intersection
+ if ( DdQxE2 < 0 ) {
+
+ return null;
+
+ }
+
+ const DdE1xQ = sign * this.direction.dot( _edge1.cross( _diff ) );
+
+ // b2 < 0, no intersection
+ if ( DdE1xQ < 0 ) {
+
+ return null;
+
+ }
+
+ // b1+b2 > 1, no intersection
+ if ( DdQxE2 + DdE1xQ > DdN ) {
+
+ return null;
+
+ }
+
+ // Line intersects triangle, check if ray does.
+ const QdN = - sign * _diff.dot( _normal );
+
+ // t < 0, no intersection
+ if ( QdN < 0 ) {
+
+ return null;
+
+ }
+
+ // Ray intersects triangle.
+ return this.at( QdN / DdN, target );
+
+ }
+
+ applyMatrix4( matrix4 ) {
+
+ this.origin.applyMatrix4( matrix4 );
+ this.direction.transformDirection( matrix4 );
+
+ return this;
+
+ }
+
+ equals( ray ) {
+
+ return ray.origin.equals( this.origin ) && ray.direction.equals( this.direction );
+
+ }
+
+}
+
+class Matrix4 {
+
+ constructor() {
+
+ Object.defineProperty( this, 'isMatrix4', { value: true } );
+
+ this.elements = [
+
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+
+ ];
+
+ if ( arguments.length > 0 ) {
+
+ console.error( 'THREE.Matrix4: the constructor no longer reads arguments. use .set() instead.' );
+
+ }
+
+ }
+
+ set( n11, n12, n13, n14, n21, n22, n23, n24, n31, n32, n33, n34, n41, n42, n43, n44 ) {
+
+ const te = this.elements;
+
+ te[ 0 ] = n11; te[ 4 ] = n12; te[ 8 ] = n13; te[ 12 ] = n14;
+ te[ 1 ] = n21; te[ 5 ] = n22; te[ 9 ] = n23; te[ 13 ] = n24;
+ te[ 2 ] = n31; te[ 6 ] = n32; te[ 10 ] = n33; te[ 14 ] = n34;
+ te[ 3 ] = n41; te[ 7 ] = n42; te[ 11 ] = n43; te[ 15 ] = n44;
+
+ return this;
+
+ }
+
+ identity() {
+
+ this.set(
+
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ clone() {
+
+ return new Matrix4().fromArray( this.elements );
+
+ }
+
+ copy( m ) {
+
+ const te = this.elements;
+ const me = m.elements;
+
+ te[ 0 ] = me[ 0 ]; te[ 1 ] = me[ 1 ]; te[ 2 ] = me[ 2 ]; te[ 3 ] = me[ 3 ];
+ te[ 4 ] = me[ 4 ]; te[ 5 ] = me[ 5 ]; te[ 6 ] = me[ 6 ]; te[ 7 ] = me[ 7 ];
+ te[ 8 ] = me[ 8 ]; te[ 9 ] = me[ 9 ]; te[ 10 ] = me[ 10 ]; te[ 11 ] = me[ 11 ];
+ te[ 12 ] = me[ 12 ]; te[ 13 ] = me[ 13 ]; te[ 14 ] = me[ 14 ]; te[ 15 ] = me[ 15 ];
+
+ return this;
+
+ }
+
+ copyPosition( m ) {
+
+ const te = this.elements, me = m.elements;
+
+ te[ 12 ] = me[ 12 ];
+ te[ 13 ] = me[ 13 ];
+ te[ 14 ] = me[ 14 ];
+
+ return this;
+
+ }
+
+ setFromMatrix3( m ) {
+
+ const me = m.elements;
+
+ this.set(
+
+ me[ 0 ], me[ 3 ], me[ 6 ], 0,
+ me[ 1 ], me[ 4 ], me[ 7 ], 0,
+ me[ 2 ], me[ 5 ], me[ 8 ], 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ extractBasis( xAxis, yAxis, zAxis ) {
+
+ xAxis.setFromMatrixColumn( this, 0 );
+ yAxis.setFromMatrixColumn( this, 1 );
+ zAxis.setFromMatrixColumn( this, 2 );
+
+ return this;
+
+ }
+
+ makeBasis( xAxis, yAxis, zAxis ) {
+
+ this.set(
+ xAxis.x, yAxis.x, zAxis.x, 0,
+ xAxis.y, yAxis.y, zAxis.y, 0,
+ xAxis.z, yAxis.z, zAxis.z, 0,
+ 0, 0, 0, 1
+ );
+
+ return this;
+
+ }
+
+ extractRotation( m ) {
+
+ // this method does not support reflection matrices
+
+ const te = this.elements;
+ const me = m.elements;
+
+ const scaleX = 1 / _v1$1.setFromMatrixColumn( m, 0 ).length();
+ const scaleY = 1 / _v1$1.setFromMatrixColumn( m, 1 ).length();
+ const scaleZ = 1 / _v1$1.setFromMatrixColumn( m, 2 ).length();
+
+ te[ 0 ] = me[ 0 ] * scaleX;
+ te[ 1 ] = me[ 1 ] * scaleX;
+ te[ 2 ] = me[ 2 ] * scaleX;
+ te[ 3 ] = 0;
+
+ te[ 4 ] = me[ 4 ] * scaleY;
+ te[ 5 ] = me[ 5 ] * scaleY;
+ te[ 6 ] = me[ 6 ] * scaleY;
+ te[ 7 ] = 0;
+
+ te[ 8 ] = me[ 8 ] * scaleZ;
+ te[ 9 ] = me[ 9 ] * scaleZ;
+ te[ 10 ] = me[ 10 ] * scaleZ;
+ te[ 11 ] = 0;
+
+ te[ 12 ] = 0;
+ te[ 13 ] = 0;
+ te[ 14 ] = 0;
+ te[ 15 ] = 1;
+
+ return this;
+
+ }
+
+ makeRotationFromEuler( euler ) {
+
+ if ( ! ( euler && euler.isEuler ) ) {
+
+ console.error( 'THREE.Matrix4: .makeRotationFromEuler() now expects a Euler rotation rather than a Vector3 and order.' );
+
+ }
+
+ const te = this.elements;
+
+ const x = euler.x, y = euler.y, z = euler.z;
+ const a = Math.cos( x ), b = Math.sin( x );
+ const c = Math.cos( y ), d = Math.sin( y );
+ const e = Math.cos( z ), f = Math.sin( z );
+
+ if ( euler.order === 'XYZ' ) {
+
+ const ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+ te[ 0 ] = c * e;
+ te[ 4 ] = - c * f;
+ te[ 8 ] = d;
+
+ te[ 1 ] = af + be * d;
+ te[ 5 ] = ae - bf * d;
+ te[ 9 ] = - b * c;
+
+ te[ 2 ] = bf - ae * d;
+ te[ 6 ] = be + af * d;
+ te[ 10 ] = a * c;
+
+ } else if ( euler.order === 'YXZ' ) {
+
+ const ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+ te[ 0 ] = ce + df * b;
+ te[ 4 ] = de * b - cf;
+ te[ 8 ] = a * d;
+
+ te[ 1 ] = a * f;
+ te[ 5 ] = a * e;
+ te[ 9 ] = - b;
+
+ te[ 2 ] = cf * b - de;
+ te[ 6 ] = df + ce * b;
+ te[ 10 ] = a * c;
+
+ } else if ( euler.order === 'ZXY' ) {
+
+ const ce = c * e, cf = c * f, de = d * e, df = d * f;
+
+ te[ 0 ] = ce - df * b;
+ te[ 4 ] = - a * f;
+ te[ 8 ] = de + cf * b;
+
+ te[ 1 ] = cf + de * b;
+ te[ 5 ] = a * e;
+ te[ 9 ] = df - ce * b;
+
+ te[ 2 ] = - a * d;
+ te[ 6 ] = b;
+ te[ 10 ] = a * c;
+
+ } else if ( euler.order === 'ZYX' ) {
+
+ const ae = a * e, af = a * f, be = b * e, bf = b * f;
+
+ te[ 0 ] = c * e;
+ te[ 4 ] = be * d - af;
+ te[ 8 ] = ae * d + bf;
+
+ te[ 1 ] = c * f;
+ te[ 5 ] = bf * d + ae;
+ te[ 9 ] = af * d - be;
+
+ te[ 2 ] = - d;
+ te[ 6 ] = b * c;
+ te[ 10 ] = a * c;
+
+ } else if ( euler.order === 'YZX' ) {
+
+ const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+ te[ 0 ] = c * e;
+ te[ 4 ] = bd - ac * f;
+ te[ 8 ] = bc * f + ad;
+
+ te[ 1 ] = f;
+ te[ 5 ] = a * e;
+ te[ 9 ] = - b * e;
+
+ te[ 2 ] = - d * e;
+ te[ 6 ] = ad * f + bc;
+ te[ 10 ] = ac - bd * f;
+
+ } else if ( euler.order === 'XZY' ) {
+
+ const ac = a * c, ad = a * d, bc = b * c, bd = b * d;
+
+ te[ 0 ] = c * e;
+ te[ 4 ] = - f;
+ te[ 8 ] = d * e;
+
+ te[ 1 ] = ac * f + bd;
+ te[ 5 ] = a * e;
+ te[ 9 ] = ad * f - bc;
+
+ te[ 2 ] = bc * f - ad;
+ te[ 6 ] = b * e;
+ te[ 10 ] = bd * f + ac;
+
+ }
+
+ // bottom row
+ te[ 3 ] = 0;
+ te[ 7 ] = 0;
+ te[ 11 ] = 0;
+
+ // last column
+ te[ 12 ] = 0;
+ te[ 13 ] = 0;
+ te[ 14 ] = 0;
+ te[ 15 ] = 1;
+
+ return this;
+
+ }
+
+ makeRotationFromQuaternion( q ) {
+
+ return this.compose( _zero, q, _one );
+
+ }
+
+ lookAt( eye, target, up ) {
+
+ const te = this.elements;
+
+ _z.subVectors( eye, target );
+
+ if ( _z.lengthSq() === 0 ) {
+
+ // eye and target are in the same position
+
+ _z.z = 1;
+
+ }
+
+ _z.normalize();
+ _x.crossVectors( up, _z );
+
+ if ( _x.lengthSq() === 0 ) {
+
+ // up and z are parallel
+
+ if ( Math.abs( up.z ) === 1 ) {
+
+ _z.x += 0.0001;
+
+ } else {
+
+ _z.z += 0.0001;
+
+ }
+
+ _z.normalize();
+ _x.crossVectors( up, _z );
+
+ }
+
+ _x.normalize();
+ _y.crossVectors( _z, _x );
+
+ te[ 0 ] = _x.x; te[ 4 ] = _y.x; te[ 8 ] = _z.x;
+ te[ 1 ] = _x.y; te[ 5 ] = _y.y; te[ 9 ] = _z.y;
+ te[ 2 ] = _x.z; te[ 6 ] = _y.z; te[ 10 ] = _z.z;
+
+ return this;
+
+ }
+
+ multiply( m, n ) {
+
+ if ( n !== undefined ) {
+
+ console.warn( 'THREE.Matrix4: .multiply() now only accepts one argument. Use .multiplyMatrices( a, b ) instead.' );
+ return this.multiplyMatrices( m, n );
+
+ }
+
+ return this.multiplyMatrices( this, m );
+
+ }
+
+ premultiply( m ) {
+
+ return this.multiplyMatrices( m, this );
+
+ }
+
+ multiplyMatrices( a, b ) {
+
+ const ae = a.elements;
+ const be = b.elements;
+ const te = this.elements;
+
+ const a11 = ae[ 0 ], a12 = ae[ 4 ], a13 = ae[ 8 ], a14 = ae[ 12 ];
+ const a21 = ae[ 1 ], a22 = ae[ 5 ], a23 = ae[ 9 ], a24 = ae[ 13 ];
+ const a31 = ae[ 2 ], a32 = ae[ 6 ], a33 = ae[ 10 ], a34 = ae[ 14 ];
+ const a41 = ae[ 3 ], a42 = ae[ 7 ], a43 = ae[ 11 ], a44 = ae[ 15 ];
+
+ const b11 = be[ 0 ], b12 = be[ 4 ], b13 = be[ 8 ], b14 = be[ 12 ];
+ const b21 = be[ 1 ], b22 = be[ 5 ], b23 = be[ 9 ], b24 = be[ 13 ];
+ const b31 = be[ 2 ], b32 = be[ 6 ], b33 = be[ 10 ], b34 = be[ 14 ];
+ const b41 = be[ 3 ], b42 = be[ 7 ], b43 = be[ 11 ], b44 = be[ 15 ];
+
+ te[ 0 ] = a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41;
+ te[ 4 ] = a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42;
+ te[ 8 ] = a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43;
+ te[ 12 ] = a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44;
+
+ te[ 1 ] = a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41;
+ te[ 5 ] = a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42;
+ te[ 9 ] = a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43;
+ te[ 13 ] = a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44;
+
+ te[ 2 ] = a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41;
+ te[ 6 ] = a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42;
+ te[ 10 ] = a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43;
+ te[ 14 ] = a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44;
+
+ te[ 3 ] = a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41;
+ te[ 7 ] = a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42;
+ te[ 11 ] = a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43;
+ te[ 15 ] = a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44;
+
+ return this;
+
+ }
+
+ multiplyScalar( s ) {
+
+ const te = this.elements;
+
+ te[ 0 ] *= s; te[ 4 ] *= s; te[ 8 ] *= s; te[ 12 ] *= s;
+ te[ 1 ] *= s; te[ 5 ] *= s; te[ 9 ] *= s; te[ 13 ] *= s;
+ te[ 2 ] *= s; te[ 6 ] *= s; te[ 10 ] *= s; te[ 14 ] *= s;
+ te[ 3 ] *= s; te[ 7 ] *= s; te[ 11 ] *= s; te[ 15 ] *= s;
+
+ return this;
+
+ }
+
+ determinant() {
+
+ const te = this.elements;
+
+ const n11 = te[ 0 ], n12 = te[ 4 ], n13 = te[ 8 ], n14 = te[ 12 ];
+ const n21 = te[ 1 ], n22 = te[ 5 ], n23 = te[ 9 ], n24 = te[ 13 ];
+ const n31 = te[ 2 ], n32 = te[ 6 ], n33 = te[ 10 ], n34 = te[ 14 ];
+ const n41 = te[ 3 ], n42 = te[ 7 ], n43 = te[ 11 ], n44 = te[ 15 ];
+
+ //TODO: make this more efficient
+ //( based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm )
+
+ return (
+ n41 * (
+ + n14 * n23 * n32
+ - n13 * n24 * n32
+ - n14 * n22 * n33
+ + n12 * n24 * n33
+ + n13 * n22 * n34
+ - n12 * n23 * n34
+ ) +
+ n42 * (
+ + n11 * n23 * n34
+ - n11 * n24 * n33
+ + n14 * n21 * n33
+ - n13 * n21 * n34
+ + n13 * n24 * n31
+ - n14 * n23 * n31
+ ) +
+ n43 * (
+ + n11 * n24 * n32
+ - n11 * n22 * n34
+ - n14 * n21 * n32
+ + n12 * n21 * n34
+ + n14 * n22 * n31
+ - n12 * n24 * n31
+ ) +
+ n44 * (
+ - n13 * n22 * n31
+ - n11 * n23 * n32
+ + n11 * n22 * n33
+ + n13 * n21 * n32
+ - n12 * n21 * n33
+ + n12 * n23 * n31
+ )
+
+ );
+
+ }
+
+ transpose() {
+
+ const te = this.elements;
+ let tmp;
+
+ tmp = te[ 1 ]; te[ 1 ] = te[ 4 ]; te[ 4 ] = tmp;
+ tmp = te[ 2 ]; te[ 2 ] = te[ 8 ]; te[ 8 ] = tmp;
+ tmp = te[ 6 ]; te[ 6 ] = te[ 9 ]; te[ 9 ] = tmp;
+
+ tmp = te[ 3 ]; te[ 3 ] = te[ 12 ]; te[ 12 ] = tmp;
+ tmp = te[ 7 ]; te[ 7 ] = te[ 13 ]; te[ 13 ] = tmp;
+ tmp = te[ 11 ]; te[ 11 ] = te[ 14 ]; te[ 14 ] = tmp;
+
+ return this;
+
+ }
+
+ setPosition( x, y, z ) {
+
+ const te = this.elements;
+
+ if ( x.isVector3 ) {
+
+ te[ 12 ] = x.x;
+ te[ 13 ] = x.y;
+ te[ 14 ] = x.z;
+
+ } else {
+
+ te[ 12 ] = x;
+ te[ 13 ] = y;
+ te[ 14 ] = z;
+
+ }
+
+ return this;
+
+ }
+
+ invert() {
+
+ // based on http://www.euclideanspace.com/maths/algebra/matrix/functions/inverse/fourD/index.htm
+ const te = this.elements,
+
+ n11 = te[ 0 ], n21 = te[ 1 ], n31 = te[ 2 ], n41 = te[ 3 ],
+ n12 = te[ 4 ], n22 = te[ 5 ], n32 = te[ 6 ], n42 = te[ 7 ],
+ n13 = te[ 8 ], n23 = te[ 9 ], n33 = te[ 10 ], n43 = te[ 11 ],
+ n14 = te[ 12 ], n24 = te[ 13 ], n34 = te[ 14 ], n44 = te[ 15 ],
+
+ t11 = n23 * n34 * n42 - n24 * n33 * n42 + n24 * n32 * n43 - n22 * n34 * n43 - n23 * n32 * n44 + n22 * n33 * n44,
+ t12 = n14 * n33 * n42 - n13 * n34 * n42 - n14 * n32 * n43 + n12 * n34 * n43 + n13 * n32 * n44 - n12 * n33 * n44,
+ t13 = n13 * n24 * n42 - n14 * n23 * n42 + n14 * n22 * n43 - n12 * n24 * n43 - n13 * n22 * n44 + n12 * n23 * n44,
+ t14 = n14 * n23 * n32 - n13 * n24 * n32 - n14 * n22 * n33 + n12 * n24 * n33 + n13 * n22 * n34 - n12 * n23 * n34;
+
+ const det = n11 * t11 + n21 * t12 + n31 * t13 + n41 * t14;
+
+ if ( det === 0 ) return this.set( 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
+
+ const detInv = 1 / det;
+
+ te[ 0 ] = t11 * detInv;
+ te[ 1 ] = ( n24 * n33 * n41 - n23 * n34 * n41 - n24 * n31 * n43 + n21 * n34 * n43 + n23 * n31 * n44 - n21 * n33 * n44 ) * detInv;
+ te[ 2 ] = ( n22 * n34 * n41 - n24 * n32 * n41 + n24 * n31 * n42 - n21 * n34 * n42 - n22 * n31 * n44 + n21 * n32 * n44 ) * detInv;
+ te[ 3 ] = ( n23 * n32 * n41 - n22 * n33 * n41 - n23 * n31 * n42 + n21 * n33 * n42 + n22 * n31 * n43 - n21 * n32 * n43 ) * detInv;
+
+ te[ 4 ] = t12 * detInv;
+ te[ 5 ] = ( n13 * n34 * n41 - n14 * n33 * n41 + n14 * n31 * n43 - n11 * n34 * n43 - n13 * n31 * n44 + n11 * n33 * n44 ) * detInv;
+ te[ 6 ] = ( n14 * n32 * n41 - n12 * n34 * n41 - n14 * n31 * n42 + n11 * n34 * n42 + n12 * n31 * n44 - n11 * n32 * n44 ) * detInv;
+ te[ 7 ] = ( n12 * n33 * n41 - n13 * n32 * n41 + n13 * n31 * n42 - n11 * n33 * n42 - n12 * n31 * n43 + n11 * n32 * n43 ) * detInv;
+
+ te[ 8 ] = t13 * detInv;
+ te[ 9 ] = ( n14 * n23 * n41 - n13 * n24 * n41 - n14 * n21 * n43 + n11 * n24 * n43 + n13 * n21 * n44 - n11 * n23 * n44 ) * detInv;
+ te[ 10 ] = ( n12 * n24 * n41 - n14 * n22 * n41 + n14 * n21 * n42 - n11 * n24 * n42 - n12 * n21 * n44 + n11 * n22 * n44 ) * detInv;
+ te[ 11 ] = ( n13 * n22 * n41 - n12 * n23 * n41 - n13 * n21 * n42 + n11 * n23 * n42 + n12 * n21 * n43 - n11 * n22 * n43 ) * detInv;
+
+ te[ 12 ] = t14 * detInv;
+ te[ 13 ] = ( n13 * n24 * n31 - n14 * n23 * n31 + n14 * n21 * n33 - n11 * n24 * n33 - n13 * n21 * n34 + n11 * n23 * n34 ) * detInv;
+ te[ 14 ] = ( n14 * n22 * n31 - n12 * n24 * n31 - n14 * n21 * n32 + n11 * n24 * n32 + n12 * n21 * n34 - n11 * n22 * n34 ) * detInv;
+ te[ 15 ] = ( n12 * n23 * n31 - n13 * n22 * n31 + n13 * n21 * n32 - n11 * n23 * n32 - n12 * n21 * n33 + n11 * n22 * n33 ) * detInv;
+
+ return this;
+
+ }
+
+ scale( v ) {
+
+ const te = this.elements;
+ const x = v.x, y = v.y, z = v.z;
+
+ te[ 0 ] *= x; te[ 4 ] *= y; te[ 8 ] *= z;
+ te[ 1 ] *= x; te[ 5 ] *= y; te[ 9 ] *= z;
+ te[ 2 ] *= x; te[ 6 ] *= y; te[ 10 ] *= z;
+ te[ 3 ] *= x; te[ 7 ] *= y; te[ 11 ] *= z;
+
+ return this;
+
+ }
+
+ getMaxScaleOnAxis() {
+
+ const te = this.elements;
+
+ const scaleXSq = te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] + te[ 2 ] * te[ 2 ];
+ const scaleYSq = te[ 4 ] * te[ 4 ] + te[ 5 ] * te[ 5 ] + te[ 6 ] * te[ 6 ];
+ const scaleZSq = te[ 8 ] * te[ 8 ] + te[ 9 ] * te[ 9 ] + te[ 10 ] * te[ 10 ];
+
+ return Math.sqrt( Math.max( scaleXSq, scaleYSq, scaleZSq ) );
+
+ }
+
+ makeTranslation( x, y, z ) {
+
+ this.set(
+
+ 1, 0, 0, x,
+ 0, 1, 0, y,
+ 0, 0, 1, z,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ makeRotationX( theta ) {
+
+ const c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ 1, 0, 0, 0,
+ 0, c, - s, 0,
+ 0, s, c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ makeRotationY( theta ) {
+
+ const c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ c, 0, s, 0,
+ 0, 1, 0, 0,
+ - s, 0, c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ makeRotationZ( theta ) {
+
+ const c = Math.cos( theta ), s = Math.sin( theta );
+
+ this.set(
+
+ c, - s, 0, 0,
+ s, c, 0, 0,
+ 0, 0, 1, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ makeRotationAxis( axis, angle ) {
+
+ // Based on http://www.gamedev.net/reference/articles/article1199.asp
+
+ const c = Math.cos( angle );
+ const s = Math.sin( angle );
+ const t = 1 - c;
+ const x = axis.x, y = axis.y, z = axis.z;
+ const tx = t * x, ty = t * y;
+
+ this.set(
+
+ tx * x + c, tx * y - s * z, tx * z + s * y, 0,
+ tx * y + s * z, ty * y + c, ty * z - s * x, 0,
+ tx * z - s * y, ty * z + s * x, t * z * z + c, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ makeScale( x, y, z ) {
+
+ this.set(
+
+ x, 0, 0, 0,
+ 0, y, 0, 0,
+ 0, 0, z, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ makeShear( x, y, z ) {
+
+ this.set(
+
+ 1, y, z, 0,
+ x, 1, z, 0,
+ x, y, 1, 0,
+ 0, 0, 0, 1
+
+ );
+
+ return this;
+
+ }
+
+ compose( position, quaternion, scale ) {
+
+ const te = this.elements;
+
+ const x = quaternion._x, y = quaternion._y, z = quaternion._z, w = quaternion._w;
+ const x2 = x + x, y2 = y + y, z2 = z + z;
+ const xx = x * x2, xy = x * y2, xz = x * z2;
+ const yy = y * y2, yz = y * z2, zz = z * z2;
+ const wx = w * x2, wy = w * y2, wz = w * z2;
+
+ const sx = scale.x, sy = scale.y, sz = scale.z;
+
+ te[ 0 ] = ( 1 - ( yy + zz ) ) * sx;
+ te[ 1 ] = ( xy + wz ) * sx;
+ te[ 2 ] = ( xz - wy ) * sx;
+ te[ 3 ] = 0;
+
+ te[ 4 ] = ( xy - wz ) * sy;
+ te[ 5 ] = ( 1 - ( xx + zz ) ) * sy;
+ te[ 6 ] = ( yz + wx ) * sy;
+ te[ 7 ] = 0;
+
+ te[ 8 ] = ( xz + wy ) * sz;
+ te[ 9 ] = ( yz - wx ) * sz;
+ te[ 10 ] = ( 1 - ( xx + yy ) ) * sz;
+ te[ 11 ] = 0;
+
+ te[ 12 ] = position.x;
+ te[ 13 ] = position.y;
+ te[ 14 ] = position.z;
+ te[ 15 ] = 1;
+
+ return this;
+
+ }
+
+ decompose( position, quaternion, scale ) {
+
+ const te = this.elements;
+
+ let sx = _v1$1.set( te[ 0 ], te[ 1 ], te[ 2 ] ).length();
+ const sy = _v1$1.set( te[ 4 ], te[ 5 ], te[ 6 ] ).length();
+ const sz = _v1$1.set( te[ 8 ], te[ 9 ], te[ 10 ] ).length();
+
+ // if determine is negative, we need to invert one scale
+ const det = this.determinant();
+ if ( det < 0 ) sx = - sx;
+
+ position.x = te[ 12 ];
+ position.y = te[ 13 ];
+ position.z = te[ 14 ];
+
+ // scale the rotation part
+ _m1.copy( this );
+
+ const invSX = 1 / sx;
+ const invSY = 1 / sy;
+ const invSZ = 1 / sz;
+
+ _m1.elements[ 0 ] *= invSX;
+ _m1.elements[ 1 ] *= invSX;
+ _m1.elements[ 2 ] *= invSX;
+
+ _m1.elements[ 4 ] *= invSY;
+ _m1.elements[ 5 ] *= invSY;
+ _m1.elements[ 6 ] *= invSY;
+
+ _m1.elements[ 8 ] *= invSZ;
+ _m1.elements[ 9 ] *= invSZ;
+ _m1.elements[ 10 ] *= invSZ;
+
+ quaternion.setFromRotationMatrix( _m1 );
+
+ scale.x = sx;
+ scale.y = sy;
+ scale.z = sz;
+
+ return this;
+
+ }
+
+ makePerspective( left, right, top, bottom, near, far ) {
+
+ if ( far === undefined ) {
+
+ console.warn( 'THREE.Matrix4: .makePerspective() has been redefined and has a new signature. Please check the docs.' );
+
+ }
+
+ const te = this.elements;
+ const x = 2 * near / ( right - left );
+ const y = 2 * near / ( top - bottom );
+
+ const a = ( right + left ) / ( right - left );
+ const b = ( top + bottom ) / ( top - bottom );
+ const c = - ( far + near ) / ( far - near );
+ const d = - 2 * far * near / ( far - near );
+
+ te[ 0 ] = x; te[ 4 ] = 0; te[ 8 ] = a; te[ 12 ] = 0;
+ te[ 1 ] = 0; te[ 5 ] = y; te[ 9 ] = b; te[ 13 ] = 0;
+ te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = c; te[ 14 ] = d;
+ te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = - 1; te[ 15 ] = 0;
+
+ return this;
+
+ }
+
+ makeOrthographic( left, right, top, bottom, near, far ) {
+
+ const te = this.elements;
+ const w = 1.0 / ( right - left );
+ const h = 1.0 / ( top - bottom );
+ const p = 1.0 / ( far - near );
+
+ const x = ( right + left ) * w;
+ const y = ( top + bottom ) * h;
+ const z = ( far + near ) * p;
+
+ te[ 0 ] = 2 * w; te[ 4 ] = 0; te[ 8 ] = 0; te[ 12 ] = - x;
+ te[ 1 ] = 0; te[ 5 ] = 2 * h; te[ 9 ] = 0; te[ 13 ] = - y;
+ te[ 2 ] = 0; te[ 6 ] = 0; te[ 10 ] = - 2 * p; te[ 14 ] = - z;
+ te[ 3 ] = 0; te[ 7 ] = 0; te[ 11 ] = 0; te[ 15 ] = 1;
+
+ return this;
+
+ }
+
+ equals( matrix ) {
+
+ const te = this.elements;
+ const me = matrix.elements;
+
+ for ( let i = 0; i < 16; i ++ ) {
+
+ if ( te[ i ] !== me[ i ] ) return false;
+
+ }
+
+ return true;
+
+ }
+
+ fromArray( array, offset = 0 ) {
+
+ for ( let i = 0; i < 16; i ++ ) {
+
+ this.elements[ i ] = array[ i + offset ];
+
+ }
+
+ return this;
+
+ }
+
+ toArray( array = [], offset = 0 ) {
+
+ const te = this.elements;
+
+ array[ offset ] = te[ 0 ];
+ array[ offset + 1 ] = te[ 1 ];
+ array[ offset + 2 ] = te[ 2 ];
+ array[ offset + 3 ] = te[ 3 ];
+
+ array[ offset + 4 ] = te[ 4 ];
+ array[ offset + 5 ] = te[ 5 ];
+ array[ offset + 6 ] = te[ 6 ];
+ array[ offset + 7 ] = te[ 7 ];
+
+ array[ offset + 8 ] = te[ 8 ];
+ array[ offset + 9 ] = te[ 9 ];
+ array[ offset + 10 ] = te[ 10 ];
+ array[ offset + 11 ] = te[ 11 ];
+
+ array[ offset + 12 ] = te[ 12 ];
+ array[ offset + 13 ] = te[ 13 ];
+ array[ offset + 14 ] = te[ 14 ];
+ array[ offset + 15 ] = te[ 15 ];
+
+ return array;
+
+ }
+
+}
+
+const _v1$1 = /*@__PURE__*/ new Vector3();
+const _m1 = /*@__PURE__*/ new Matrix4();
+const _zero = /*@__PURE__*/ new Vector3( 0, 0, 0 );
+const _one = /*@__PURE__*/ new Vector3( 1, 1, 1 );
+const _x = /*@__PURE__*/ new Vector3();
+const _y = /*@__PURE__*/ new Vector3();
+const _z = /*@__PURE__*/ new Vector3();
+
+class Euler {
+
+ constructor( x = 0, y = 0, z = 0, order = Euler.DefaultOrder ) {
+
+ Object.defineProperty( this, 'isEuler', { value: true } );
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._order = order;
+
+ }
+
+ get x() {
+
+ return this._x;
+
+ }
+
+ set x( value ) {
+
+ this._x = value;
+ this._onChangeCallback();
+
+ }
+
+ get y() {
+
+ return this._y;
+
+ }
+
+ set y( value ) {
+
+ this._y = value;
+ this._onChangeCallback();
+
+ }
+
+ get z() {
+
+ return this._z;
+
+ }
+
+ set z( value ) {
+
+ this._z = value;
+ this._onChangeCallback();
+
+ }
+
+ get order() {
+
+ return this._order;
+
+ }
+
+ set order( value ) {
+
+ this._order = value;
+ this._onChangeCallback();
+
+ }
+
+ set( x, y, z, order ) {
+
+ this._x = x;
+ this._y = y;
+ this._z = z;
+ this._order = order || this._order;
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ clone() {
+
+ return new this.constructor( this._x, this._y, this._z, this._order );
+
+ }
+
+ copy( euler ) {
+
+ this._x = euler._x;
+ this._y = euler._y;
+ this._z = euler._z;
+ this._order = euler._order;
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ setFromRotationMatrix( m, order, update ) {
+
+ const clamp = MathUtils.clamp;
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ const te = m.elements;
+ const m11 = te[ 0 ], m12 = te[ 4 ], m13 = te[ 8 ];
+ const m21 = te[ 1 ], m22 = te[ 5 ], m23 = te[ 9 ];
+ const m31 = te[ 2 ], m32 = te[ 6 ], m33 = te[ 10 ];
+
+ order = order || this._order;
+
+ switch ( order ) {
+
+ case 'XYZ':
+
+ this._y = Math.asin( clamp( m13, - 1, 1 ) );
+
+ if ( Math.abs( m13 ) < 0.9999999 ) {
+
+ this._x = Math.atan2( - m23, m33 );
+ this._z = Math.atan2( - m12, m11 );
+
+ } else {
+
+ this._x = Math.atan2( m32, m22 );
+ this._z = 0;
+
+ }
+
+ break;
+
+ case 'YXZ':
+
+ this._x = Math.asin( - clamp( m23, - 1, 1 ) );
+
+ if ( Math.abs( m23 ) < 0.9999999 ) {
+
+ this._y = Math.atan2( m13, m33 );
+ this._z = Math.atan2( m21, m22 );
+
+ } else {
+
+ this._y = Math.atan2( - m31, m11 );
+ this._z = 0;
+
+ }
+
+ break;
+
+ case 'ZXY':
+
+ this._x = Math.asin( clamp( m32, - 1, 1 ) );
+
+ if ( Math.abs( m32 ) < 0.9999999 ) {
+
+ this._y = Math.atan2( - m31, m33 );
+ this._z = Math.atan2( - m12, m22 );
+
+ } else {
+
+ this._y = 0;
+ this._z = Math.atan2( m21, m11 );
+
+ }
+
+ break;
+
+ case 'ZYX':
+
+ this._y = Math.asin( - clamp( m31, - 1, 1 ) );
+
+ if ( Math.abs( m31 ) < 0.9999999 ) {
+
+ this._x = Math.atan2( m32, m33 );
+ this._z = Math.atan2( m21, m11 );
+
+ } else {
+
+ this._x = 0;
+ this._z = Math.atan2( - m12, m22 );
+
+ }
+
+ break;
+
+ case 'YZX':
+
+ this._z = Math.asin( clamp( m21, - 1, 1 ) );
+
+ if ( Math.abs( m21 ) < 0.9999999 ) {
+
+ this._x = Math.atan2( - m23, m22 );
+ this._y = Math.atan2( - m31, m11 );
+
+ } else {
+
+ this._x = 0;
+ this._y = Math.atan2( m13, m33 );
+
+ }
+
+ break;
+
+ case 'XZY':
+
+ this._z = Math.asin( - clamp( m12, - 1, 1 ) );
+
+ if ( Math.abs( m12 ) < 0.9999999 ) {
+
+ this._x = Math.atan2( m32, m22 );
+ this._y = Math.atan2( m13, m11 );
+
+ } else {
+
+ this._x = Math.atan2( - m23, m33 );
+ this._y = 0;
+
+ }
+
+ break;
+
+ default:
+
+ console.warn( 'THREE.Euler: .setFromRotationMatrix() encountered an unknown order: ' + order );
+
+ }
+
+ this._order = order;
+
+ if ( update !== false ) this._onChangeCallback();
+
+ return this;
+
+ }
+
+ setFromQuaternion( q, order, update ) {
+
+ _matrix.makeRotationFromQuaternion( q );
+
+ return this.setFromRotationMatrix( _matrix, order, update );
+
+ }
+
+ setFromVector3( v, order ) {
+
+ return this.set( v.x, v.y, v.z, order || this._order );
+
+ }
+
+ reorder( newOrder ) {
+
+ // WARNING: this discards revolution information -bhouston
+
+ _quaternion$1.setFromEuler( this );
+
+ return this.setFromQuaternion( _quaternion$1, newOrder );
+
+ }
+
+ equals( euler ) {
+
+ return ( euler._x === this._x ) && ( euler._y === this._y ) && ( euler._z === this._z ) && ( euler._order === this._order );
+
+ }
+
+ fromArray( array ) {
+
+ this._x = array[ 0 ];
+ this._y = array[ 1 ];
+ this._z = array[ 2 ];
+ if ( array[ 3 ] !== undefined ) this._order = array[ 3 ];
+
+ this._onChangeCallback();
+
+ return this;
+
+ }
+
+ toArray( array = [], offset = 0 ) {
+
+ array[ offset ] = this._x;
+ array[ offset + 1 ] = this._y;
+ array[ offset + 2 ] = this._z;
+ array[ offset + 3 ] = this._order;
+
+ return array;
+
+ }
+
+ toVector3( optionalResult ) {
+
+ if ( optionalResult ) {
+
+ return optionalResult.set( this._x, this._y, this._z );
+
+ } else {
+
+ return new Vector3( this._x, this._y, this._z );
+
+ }
+
+ }
+
+ _onChange( callback ) {
+
+ this._onChangeCallback = callback;
+
+ return this;
+
+ }
+
+ _onChangeCallback() {}
+
+}
+
+Euler.DefaultOrder = 'XYZ';
+Euler.RotationOrders = [ 'XYZ', 'YZX', 'ZXY', 'XZY', 'YXZ', 'ZYX' ];
+
+const _matrix = /*@__PURE__*/ new Matrix4();
+const _quaternion$1 = /*@__PURE__*/ new Quaternion();
+
+class Layers {
+
+ constructor() {
+
+ this.mask = 1 | 0;
+
+ }
+
+ set( channel ) {
+
+ this.mask = 1 << channel | 0;
+
+ }
+
+ enable( channel ) {
+
+ this.mask |= 1 << channel | 0;
+
+ }
+
+ enableAll() {
+
+ this.mask = 0xffffffff | 0;
+
+ }
+
+ toggle( channel ) {
+
+ this.mask ^= 1 << channel | 0;
+
+ }
+
+ disable( channel ) {
+
+ this.mask &= ~ ( 1 << channel | 0 );
+
+ }
+
+ disableAll() {
+
+ this.mask = 0;
+
+ }
+
+ test( layers ) {
+
+ return ( this.mask & layers.mask ) !== 0;
+
+ }
+
+}
+
+let _object3DId = 0;
+
+const _v1$2 = new Vector3();
+const _q1 = new Quaternion();
+const _m1$1 = new Matrix4();
+const _target = new Vector3();
+
+const _position = new Vector3();
+const _scale = new Vector3();
+const _quaternion$2 = new Quaternion();
+
+const _xAxis = new Vector3( 1, 0, 0 );
+const _yAxis = new Vector3( 0, 1, 0 );
+const _zAxis = new Vector3( 0, 0, 1 );
+
+const _addedEvent = { type: 'added' };
+const _removedEvent = { type: 'removed' };
+
+function Object3D() {
+
+ Object.defineProperty( this, 'id', { value: _object3DId ++ } );
+
+ this.uuid = MathUtils.generateUUID();
+
+ this.name = '';
+ this.type = 'Object3D';
+
+ this.parent = null;
+ this.children = [];
+
+ this.up = Object3D.DefaultUp.clone();
+
+ const position = new Vector3();
+ const rotation = new Euler();
+ const quaternion = new Quaternion();
+ const scale = new Vector3( 1, 1, 1 );
+
+ function onRotationChange() {
+
+ quaternion.setFromEuler( rotation, false );
+
+ }
+
+ function onQuaternionChange() {
+
+ rotation.setFromQuaternion( quaternion, undefined, false );
+
+ }
+
+ rotation._onChange( onRotationChange );
+ quaternion._onChange( onQuaternionChange );
+
+ Object.defineProperties( this, {
+ position: {
+ configurable: true,
+ enumerable: true,
+ value: position
+ },
+ rotation: {
+ configurable: true,
+ enumerable: true,
+ value: rotation
+ },
+ quaternion: {
+ configurable: true,
+ enumerable: true,
+ value: quaternion
+ },
+ scale: {
+ configurable: true,
+ enumerable: true,
+ value: scale
+ },
+ modelViewMatrix: {
+ value: new Matrix4()
+ },
+ normalMatrix: {
+ value: new Matrix3()
+ }
+ } );
+
+ this.matrix = new Matrix4();
+ this.matrixWorld = new Matrix4();
+
+ this.matrixAutoUpdate = Object3D.DefaultMatrixAutoUpdate;
+ this.matrixWorldNeedsUpdate = false;
+
+ this.layers = new Layers();
+ this.visible = true;
+
+ this.castShadow = false;
+ this.receiveShadow = false;
+
+ this.frustumCulled = true;
+ this.renderOrder = 0;
+
+ this.animations = [];
+
+ this.userData = {};
+
+}
+
+Object3D.DefaultUp = new Vector3( 0, 1, 0 );
+Object3D.DefaultMatrixAutoUpdate = true;
+
+Object3D.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+ constructor: Object3D,
+
+ isObject3D: true,
+
+ onBeforeRender: function () {},
+ onAfterRender: function () {},
+
+ applyMatrix4: function ( matrix ) {
+
+ if ( this.matrixAutoUpdate ) this.updateMatrix();
+
+ this.matrix.premultiply( matrix );
+
+ this.matrix.decompose( this.position, this.quaternion, this.scale );
+
+ },
+
+ applyQuaternion: function ( q ) {
+
+ this.quaternion.premultiply( q );
+
+ return this;
+
+ },
+
+ setRotationFromAxisAngle: function ( axis, angle ) {
+
+ // assumes axis is normalized
+
+ this.quaternion.setFromAxisAngle( axis, angle );
+
+ },
+
+ setRotationFromEuler: function ( euler ) {
+
+ this.quaternion.setFromEuler( euler, true );
+
+ },
+
+ setRotationFromMatrix: function ( m ) {
+
+ // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
+
+ this.quaternion.setFromRotationMatrix( m );
+
+ },
+
+ setRotationFromQuaternion: function ( q ) {
+
+ // assumes q is normalized
+
+ this.quaternion.copy( q );
+
+ },
+
+ rotateOnAxis: function ( axis, angle ) {
+
+ // rotate object on axis in object space
+ // axis is assumed to be normalized
+
+ _q1.setFromAxisAngle( axis, angle );
+
+ this.quaternion.multiply( _q1 );
+
+ return this;
+
+ },
+
+ rotateOnWorldAxis: function ( axis, angle ) {
+
+ // rotate object on axis in world space
+ // axis is assumed to be normalized
+ // method assumes no rotated parent
+
+ _q1.setFromAxisAngle( axis, angle );
+
+ this.quaternion.premultiply( _q1 );
+
+ return this;
+
+ },
+
+ rotateX: function ( angle ) {
+
+ return this.rotateOnAxis( _xAxis, angle );
+
+ },
+
+ rotateY: function ( angle ) {
+
+ return this.rotateOnAxis( _yAxis, angle );
+
+ },
+
+ rotateZ: function ( angle ) {
+
+ return this.rotateOnAxis( _zAxis, angle );
+
+ },
+
+ translateOnAxis: function ( axis, distance ) {
+
+ // translate object by distance along axis in object space
+ // axis is assumed to be normalized
+
+ _v1$2.copy( axis ).applyQuaternion( this.quaternion );
+
+ this.position.add( _v1$2.multiplyScalar( distance ) );
+
+ return this;
+
+ },
+
+ translateX: function ( distance ) {
+
+ return this.translateOnAxis( _xAxis, distance );
+
+ },
+
+ translateY: function ( distance ) {
+
+ return this.translateOnAxis( _yAxis, distance );
+
+ },
+
+ translateZ: function ( distance ) {
+
+ return this.translateOnAxis( _zAxis, distance );
+
+ },
+
+ localToWorld: function ( vector ) {
+
+ return vector.applyMatrix4( this.matrixWorld );
+
+ },
+
+ worldToLocal: function ( vector ) {
+
+ return vector.applyMatrix4( _m1$1.copy( this.matrixWorld ).invert() );
+
+ },
+
+ lookAt: function ( x, y, z ) {
+
+ // This method does not support objects having non-uniformly-scaled parent(s)
+
+ if ( x.isVector3 ) {
+
+ _target.copy( x );
+
+ } else {
+
+ _target.set( x, y, z );
+
+ }
+
+ const parent = this.parent;
+
+ this.updateWorldMatrix( true, false );
+
+ _position.setFromMatrixPosition( this.matrixWorld );
+
+ if ( this.isCamera || this.isLight ) {
+
+ _m1$1.lookAt( _position, _target, this.up );
+
+ } else {
+
+ _m1$1.lookAt( _target, _position, this.up );
+
+ }
+
+ this.quaternion.setFromRotationMatrix( _m1$1 );
+
+ if ( parent ) {
+
+ _m1$1.extractRotation( parent.matrixWorld );
+ _q1.setFromRotationMatrix( _m1$1 );
+ this.quaternion.premultiply( _q1.invert() );
+
+ }
+
+ },
+
+ add: function ( object ) {
+
+ if ( arguments.length > 1 ) {
+
+ for ( let i = 0; i < arguments.length; i ++ ) {
+
+ this.add( arguments[ i ] );
+
+ }
+
+ return this;
+
+ }
+
+ if ( object === this ) {
+
+ console.error( 'THREE.Object3D.add: object can\'t be added as a child of itself.', object );
+ return this;
+
+ }
+
+ if ( object && object.isObject3D ) {
+
+ if ( object.parent !== null ) {
+
+ object.parent.remove( object );
+
+ }
+
+ object.parent = this;
+ this.children.push( object );
+
+ object.dispatchEvent( _addedEvent );
+
+ } else {
+
+ console.error( 'THREE.Object3D.add: object not an instance of THREE.Object3D.', object );
+
+ }
+
+ return this;
+
+ },
+
+ remove: function ( object ) {
+
+ if ( arguments.length > 1 ) {
+
+ for ( let i = 0; i < arguments.length; i ++ ) {
+
+ this.remove( arguments[ i ] );
+
+ }
+
+ return this;
+
+ }
+
+ const index = this.children.indexOf( object );
+
+ if ( index !== - 1 ) {
+
+ object.parent = null;
+ this.children.splice( index, 1 );
+
+ object.dispatchEvent( _removedEvent );
+
+ }
+
+ return this;
+
+ },
+
+ clear: function () {
+
+ for ( let i = 0; i < this.children.length; i ++ ) {
+
+ const object = this.children[ i ];
+
+ object.parent = null;
+
+ object.dispatchEvent( _removedEvent );
+
+ }
+
+ this.children.length = 0;
+
+ return this;
+
+
+ },
+
+ attach: function ( object ) {
+
+ // adds object as a child of this, while maintaining the object's world transform
+
+ this.updateWorldMatrix( true, false );
+
+ _m1$1.copy( this.matrixWorld ).invert();
+
+ if ( object.parent !== null ) {
+
+ object.parent.updateWorldMatrix( true, false );
+
+ _m1$1.multiply( object.parent.matrixWorld );
+
+ }
+
+ object.applyMatrix4( _m1$1 );
+
+ object.updateWorldMatrix( false, false );
+
+ this.add( object );
+
+ return this;
+
+ },
+
+ getObjectById: function ( id ) {
+
+ return this.getObjectByProperty( 'id', id );
+
+ },
+
+ getObjectByName: function ( name ) {
+
+ return this.getObjectByProperty( 'name', name );
+
+ },
+
+ getObjectByProperty: function ( name, value ) {
+
+ if ( this[ name ] === value ) return this;
+
+ for ( let i = 0, l = this.children.length; i < l; i ++ ) {
+
+ const child = this.children[ i ];
+ const object = child.getObjectByProperty( name, value );
+
+ if ( object !== undefined ) {
+
+ return object;
+
+ }
+
+ }
+
+ return undefined;
+
+ },
+
+ getWorldPosition: function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Object3D: .getWorldPosition() target is now required' );
+ target = new Vector3();
+
+ }
+
+ this.updateWorldMatrix( true, false );
+
+ return target.setFromMatrixPosition( this.matrixWorld );
+
+ },
+
+ getWorldQuaternion: function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Object3D: .getWorldQuaternion() target is now required' );
+ target = new Quaternion();
+
+ }
+
+ this.updateWorldMatrix( true, false );
+
+ this.matrixWorld.decompose( _position, target, _scale );
+
+ return target;
+
+ },
+
+ getWorldScale: function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Object3D: .getWorldScale() target is now required' );
+ target = new Vector3();
+
+ }
+
+ this.updateWorldMatrix( true, false );
+
+ this.matrixWorld.decompose( _position, _quaternion$2, target );
+
+ return target;
+
+ },
+
+ getWorldDirection: function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Object3D: .getWorldDirection() target is now required' );
+ target = new Vector3();
+
+ }
+
+ this.updateWorldMatrix( true, false );
+
+ const e = this.matrixWorld.elements;
+
+ return target.set( e[ 8 ], e[ 9 ], e[ 10 ] ).normalize();
+
+ },
+
+ raycast: function () {},
+
+ traverse: function ( callback ) {
+
+ callback( this );
+
+ const children = this.children;
+
+ for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+ children[ i ].traverse( callback );
+
+ }
+
+ },
+
+ traverseVisible: function ( callback ) {
+
+ if ( this.visible === false ) return;
+
+ callback( this );
+
+ const children = this.children;
+
+ for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+ children[ i ].traverseVisible( callback );
+
+ }
+
+ },
+
+ traverseAncestors: function ( callback ) {
+
+ const parent = this.parent;
+
+ if ( parent !== null ) {
+
+ callback( parent );
+
+ parent.traverseAncestors( callback );
+
+ }
+
+ },
+
+ updateMatrix: function () {
+
+ this.matrix.compose( this.position, this.quaternion, this.scale );
+
+ this.matrixWorldNeedsUpdate = true;
+
+ },
+
+ updateMatrixWorld: function ( force ) {
+
+ if ( this.matrixAutoUpdate ) this.updateMatrix();
+
+ if ( this.matrixWorldNeedsUpdate || force ) {
+
+ if ( this.parent === null ) {
+
+ this.matrixWorld.copy( this.matrix );
+
+ } else {
+
+ this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+ }
+
+ this.matrixWorldNeedsUpdate = false;
+
+ force = true;
+
+ }
+
+ // update children
+
+ const children = this.children;
+
+ for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+ children[ i ].updateMatrixWorld( force );
+
+ }
+
+ },
+
+ updateWorldMatrix: function ( updateParents, updateChildren ) {
+
+ const parent = this.parent;
+
+ if ( updateParents === true && parent !== null ) {
+
+ parent.updateWorldMatrix( true, false );
+
+ }
+
+ if ( this.matrixAutoUpdate ) this.updateMatrix();
+
+ if ( this.parent === null ) {
+
+ this.matrixWorld.copy( this.matrix );
+
+ } else {
+
+ this.matrixWorld.multiplyMatrices( this.parent.matrixWorld, this.matrix );
+
+ }
+
+ // update children
+
+ if ( updateChildren === true ) {
+
+ const children = this.children;
+
+ for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+ children[ i ].updateWorldMatrix( false, true );
+
+ }
+
+ }
+
+ },
+
+ toJSON: function ( meta ) {
+
+ // meta is a string when called from JSON.stringify
+ const isRootObject = ( meta === undefined || typeof meta === 'string' );
+
+ const output = {};
+
+ // meta is a hash used to collect geometries, materials.
+ // not providing it implies that this is the root object
+ // being serialized.
+ if ( isRootObject ) {
+
+ // initialize meta obj
+ meta = {
+ geometries: {},
+ materials: {},
+ textures: {},
+ images: {},
+ shapes: {},
+ skeletons: {},
+ animations: {}
+ };
+
+ output.metadata = {
+ version: 4.5,
+ type: 'Object',
+ generator: 'Object3D.toJSON'
+ };
+
+ }
+
+ // standard Object3D serialization
+
+ const object = {};
+
+ object.uuid = this.uuid;
+ object.type = this.type;
+
+ if ( this.name !== '' ) object.name = this.name;
+ if ( this.castShadow === true ) object.castShadow = true;
+ if ( this.receiveShadow === true ) object.receiveShadow = true;
+ if ( this.visible === false ) object.visible = false;
+ if ( this.frustumCulled === false ) object.frustumCulled = false;
+ if ( this.renderOrder !== 0 ) object.renderOrder = this.renderOrder;
+ if ( JSON.stringify( this.userData ) !== '{}' ) object.userData = this.userData;
+
+ object.layers = this.layers.mask;
+ object.matrix = this.matrix.toArray();
+
+ if ( this.matrixAutoUpdate === false ) object.matrixAutoUpdate = false;
+
+ // object specific properties
+
+ if ( this.isInstancedMesh ) {
+
+ object.type = 'InstancedMesh';
+ object.count = this.count;
+ object.instanceMatrix = this.instanceMatrix.toJSON();
+
+ }
+
+ //
+
+ function serialize( library, element ) {
+
+ if ( library[ element.uuid ] === undefined ) {
+
+ library[ element.uuid ] = element.toJSON( meta );
+
+ }
+
+ return element.uuid;
+
+ }
+
+ if ( this.isMesh || this.isLine || this.isPoints ) {
+
+ object.geometry = serialize( meta.geometries, this.geometry );
+
+ const parameters = this.geometry.parameters;
+
+ if ( parameters !== undefined && parameters.shapes !== undefined ) {
+
+ const shapes = parameters.shapes;
+
+ if ( Array.isArray( shapes ) ) {
+
+ for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+
+ const shape = shapes[ i ];
+
+ serialize( meta.shapes, shape );
+
+ }
+
+ } else {
+
+ serialize( meta.shapes, shapes );
+
+ }
+
+ }
+
+ }
+
+ if ( this.isSkinnedMesh ) {
+
+ object.bindMode = this.bindMode;
+ object.bindMatrix = this.bindMatrix.toArray();
+
+ if ( this.skeleton !== undefined ) {
+
+ serialize( meta.skeletons, this.skeleton );
+
+ object.skeleton = this.skeleton.uuid;
+
+ }
+
+ }
+
+ if ( this.material !== undefined ) {
+
+ if ( Array.isArray( this.material ) ) {
+
+ const uuids = [];
+
+ for ( let i = 0, l = this.material.length; i < l; i ++ ) {
+
+ uuids.push( serialize( meta.materials, this.material[ i ] ) );
+
+ }
+
+ object.material = uuids;
+
+ } else {
+
+ object.material = serialize( meta.materials, this.material );
+
+ }
+
+ }
+
+ //
+
+ if ( this.children.length > 0 ) {
+
+ object.children = [];
+
+ for ( let i = 0; i < this.children.length; i ++ ) {
+
+ object.children.push( this.children[ i ].toJSON( meta ).object );
+
+ }
+
+ }
+
+ //
+
+ if ( this.animations.length > 0 ) {
+
+ object.animations = [];
+
+ for ( let i = 0; i < this.animations.length; i ++ ) {
+
+ const animation = this.animations[ i ];
+
+ object.animations.push( serialize( meta.animations, animation ) );
+
+ }
+
+ }
+
+ if ( isRootObject ) {
+
+ const geometries = extractFromCache( meta.geometries );
+ const materials = extractFromCache( meta.materials );
+ const textures = extractFromCache( meta.textures );
+ const images = extractFromCache( meta.images );
+ const shapes = extractFromCache( meta.shapes );
+ const skeletons = extractFromCache( meta.skeletons );
+ const animations = extractFromCache( meta.animations );
+
+ if ( geometries.length > 0 ) output.geometries = geometries;
+ if ( materials.length > 0 ) output.materials = materials;
+ if ( textures.length > 0 ) output.textures = textures;
+ if ( images.length > 0 ) output.images = images;
+ if ( shapes.length > 0 ) output.shapes = shapes;
+ if ( skeletons.length > 0 ) output.skeletons = skeletons;
+ if ( animations.length > 0 ) output.animations = animations;
+
+ }
+
+ output.object = object;
+
+ return output;
+
+ // extract data from the cache hash
+ // remove metadata on each item
+ // and return as array
+ function extractFromCache( cache ) {
+
+ const values = [];
+ for ( const key in cache ) {
+
+ const data = cache[ key ];
+ delete data.metadata;
+ values.push( data );
+
+ }
+
+ return values;
+
+ }
+
+ },
+
+ clone: function ( recursive ) {
+
+ return new this.constructor().copy( this, recursive );
+
+ },
+
+ copy: function ( source, recursive = true ) {
+
+ this.name = source.name;
+
+ this.up.copy( source.up );
+
+ this.position.copy( source.position );
+ this.rotation.order = source.rotation.order;
+ this.quaternion.copy( source.quaternion );
+ this.scale.copy( source.scale );
+
+ this.matrix.copy( source.matrix );
+ this.matrixWorld.copy( source.matrixWorld );
+
+ this.matrixAutoUpdate = source.matrixAutoUpdate;
+ this.matrixWorldNeedsUpdate = source.matrixWorldNeedsUpdate;
+
+ this.layers.mask = source.layers.mask;
+ this.visible = source.visible;
+
+ this.castShadow = source.castShadow;
+ this.receiveShadow = source.receiveShadow;
+
+ this.frustumCulled = source.frustumCulled;
+ this.renderOrder = source.renderOrder;
+
+ this.userData = JSON.parse( JSON.stringify( source.userData ) );
+
+ if ( recursive === true ) {
+
+ for ( let i = 0; i < source.children.length; i ++ ) {
+
+ const child = source.children[ i ];
+ this.add( child.clone() );
+
+ }
+
+ }
+
+ return this;
+
+ }
+
+} );
+
+const _vector1 = /*@__PURE__*/ new Vector3();
+const _vector2 = /*@__PURE__*/ new Vector3();
+const _normalMatrix = /*@__PURE__*/ new Matrix3();
+
+class Plane {
+
+ constructor( normal, constant ) {
+
+ Object.defineProperty( this, 'isPlane', { value: true } );
+
+ // normal is assumed to be normalized
+
+ this.normal = ( normal !== undefined ) ? normal : new Vector3( 1, 0, 0 );
+ this.constant = ( constant !== undefined ) ? constant : 0;
+
+ }
+
+ set( normal, constant ) {
+
+ this.normal.copy( normal );
+ this.constant = constant;
+
+ return this;
+
+ }
+
+ setComponents( x, y, z, w ) {
+
+ this.normal.set( x, y, z );
+ this.constant = w;
+
+ return this;
+
+ }
+
+ setFromNormalAndCoplanarPoint( normal, point ) {
+
+ this.normal.copy( normal );
+ this.constant = - point.dot( this.normal );
+
+ return this;
+
+ }
+
+ setFromCoplanarPoints( a, b, c ) {
+
+ const normal = _vector1.subVectors( c, b ).cross( _vector2.subVectors( a, b ) ).normalize();
+
+ // Q: should an error be thrown if normal is zero (e.g. degenerate plane)?
+
+ this.setFromNormalAndCoplanarPoint( normal, a );
+
+ return this;
+
+ }
+
+ clone() {
+
+ return new this.constructor().copy( this );
+
+ }
+
+ copy( plane ) {
+
+ this.normal.copy( plane.normal );
+ this.constant = plane.constant;
+
+ return this;
+
+ }
+
+ normalize() {
+
+ // Note: will lead to a divide by zero if the plane is invalid.
+
+ const inverseNormalLength = 1.0 / this.normal.length();
+ this.normal.multiplyScalar( inverseNormalLength );
+ this.constant *= inverseNormalLength;
+
+ return this;
+
+ }
+
+ negate() {
+
+ this.constant *= - 1;
+ this.normal.negate();
+
+ return this;
+
+ }
+
+ distanceToPoint( point ) {
+
+ return this.normal.dot( point ) + this.constant;
+
+ }
+
+ distanceToSphere( sphere ) {
+
+ return this.distanceToPoint( sphere.center ) - sphere.radius;
+
+ }
+
+ projectPoint( point, target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Plane: .projectPoint() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return target.copy( this.normal ).multiplyScalar( - this.distanceToPoint( point ) ).add( point );
+
+ }
+
+ intersectLine( line, target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Plane: .intersectLine() target is now required' );
+ target = new Vector3();
+
+ }
+
+ const direction = line.delta( _vector1 );
+
+ const denominator = this.normal.dot( direction );
+
+ if ( denominator === 0 ) {
+
+ // line is coplanar, return origin
+ if ( this.distanceToPoint( line.start ) === 0 ) {
+
+ return target.copy( line.start );
+
+ }
+
+ // Unsure if this is the correct method to handle this case.
+ return undefined;
+
+ }
+
+ const t = - ( line.start.dot( this.normal ) + this.constant ) / denominator;
+
+ if ( t < 0 || t > 1 ) {
+
+ return undefined;
+
+ }
+
+ return target.copy( direction ).multiplyScalar( t ).add( line.start );
+
+ }
+
+ intersectsLine( line ) {
+
+ // Note: this tests if a line intersects the plane, not whether it (or its end-points) are coplanar with it.
+
+ const startSign = this.distanceToPoint( line.start );
+ const endSign = this.distanceToPoint( line.end );
+
+ return ( startSign < 0 && endSign > 0 ) || ( endSign < 0 && startSign > 0 );
+
+ }
+
+ intersectsBox( box ) {
+
+ return box.intersectsPlane( this );
+
+ }
+
+ intersectsSphere( sphere ) {
+
+ return sphere.intersectsPlane( this );
+
+ }
+
+ coplanarPoint( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Plane: .coplanarPoint() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return target.copy( this.normal ).multiplyScalar( - this.constant );
+
+ }
+
+ applyMatrix4( matrix, optionalNormalMatrix ) {
+
+ const normalMatrix = optionalNormalMatrix || _normalMatrix.getNormalMatrix( matrix );
+
+ const referencePoint = this.coplanarPoint( _vector1 ).applyMatrix4( matrix );
+
+ const normal = this.normal.applyMatrix3( normalMatrix ).normalize();
+
+ this.constant = - referencePoint.dot( normal );
+
+ return this;
+
+ }
+
+ translate( offset ) {
+
+ this.constant -= offset.dot( this.normal );
+
+ return this;
+
+ }
+
+ equals( plane ) {
+
+ return plane.normal.equals( this.normal ) && ( plane.constant === this.constant );
+
+ }
+
+}
+
+const _v0$1 = /*@__PURE__*/ new Vector3();
+const _v1$3 = /*@__PURE__*/ new Vector3();
+const _v2$1 = /*@__PURE__*/ new Vector3();
+const _v3 = /*@__PURE__*/ new Vector3();
+
+const _vab = /*@__PURE__*/ new Vector3();
+const _vac = /*@__PURE__*/ new Vector3();
+const _vbc = /*@__PURE__*/ new Vector3();
+const _vap = /*@__PURE__*/ new Vector3();
+const _vbp = /*@__PURE__*/ new Vector3();
+const _vcp = /*@__PURE__*/ new Vector3();
+
+class Triangle {
+
+ constructor( a, b, c ) {
+
+ this.a = ( a !== undefined ) ? a : new Vector3();
+ this.b = ( b !== undefined ) ? b : new Vector3();
+ this.c = ( c !== undefined ) ? c : new Vector3();
+
+ }
+
+ static getNormal( a, b, c, target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Triangle: .getNormal() target is now required' );
+ target = new Vector3();
+
+ }
+
+ target.subVectors( c, b );
+ _v0$1.subVectors( a, b );
+ target.cross( _v0$1 );
+
+ const targetLengthSq = target.lengthSq();
+ if ( targetLengthSq > 0 ) {
+
+ return target.multiplyScalar( 1 / Math.sqrt( targetLengthSq ) );
+
+ }
+
+ return target.set( 0, 0, 0 );
+
+ }
+
+ // static/instance method to calculate barycentric coordinates
+ // based on: http://www.blackpawn.com/texts/pointinpoly/default.html
+ static getBarycoord( point, a, b, c, target ) {
+
+ _v0$1.subVectors( c, a );
+ _v1$3.subVectors( b, a );
+ _v2$1.subVectors( point, a );
+
+ const dot00 = _v0$1.dot( _v0$1 );
+ const dot01 = _v0$1.dot( _v1$3 );
+ const dot02 = _v0$1.dot( _v2$1 );
+ const dot11 = _v1$3.dot( _v1$3 );
+ const dot12 = _v1$3.dot( _v2$1 );
+
+ const denom = ( dot00 * dot11 - dot01 * dot01 );
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Triangle: .getBarycoord() target is now required' );
+ target = new Vector3();
+
+ }
+
+ // collinear or singular triangle
+ if ( denom === 0 ) {
+
+ // arbitrary location outside of triangle?
+ // not sure if this is the best idea, maybe should be returning undefined
+ return target.set( - 2, - 1, - 1 );
+
+ }
+
+ const invDenom = 1 / denom;
+ const u = ( dot11 * dot02 - dot01 * dot12 ) * invDenom;
+ const v = ( dot00 * dot12 - dot01 * dot02 ) * invDenom;
+
+ // barycentric coordinates must always sum to 1
+ return target.set( 1 - u - v, v, u );
+
+ }
+
+ static containsPoint( point, a, b, c ) {
+
+ this.getBarycoord( point, a, b, c, _v3 );
+
+ return ( _v3.x >= 0 ) && ( _v3.y >= 0 ) && ( ( _v3.x + _v3.y ) <= 1 );
+
+ }
+
+ static getUV( point, p1, p2, p3, uv1, uv2, uv3, target ) {
+
+ this.getBarycoord( point, p1, p2, p3, _v3 );
+
+ target.set( 0, 0 );
+ target.addScaledVector( uv1, _v3.x );
+ target.addScaledVector( uv2, _v3.y );
+ target.addScaledVector( uv3, _v3.z );
+
+ return target;
+
+ }
+
+ static isFrontFacing( a, b, c, direction ) {
+
+ _v0$1.subVectors( c, b );
+ _v1$3.subVectors( a, b );
+
+ // strictly front facing
+ return ( _v0$1.cross( _v1$3 ).dot( direction ) < 0 ) ? true : false;
+
+ }
+
+ set( a, b, c ) {
+
+ this.a.copy( a );
+ this.b.copy( b );
+ this.c.copy( c );
+
+ return this;
+
+ }
+
+ setFromPointsAndIndices( points, i0, i1, i2 ) {
+
+ this.a.copy( points[ i0 ] );
+ this.b.copy( points[ i1 ] );
+ this.c.copy( points[ i2 ] );
+
+ return this;
+
+ }
+
+ clone() {
+
+ return new this.constructor().copy( this );
+
+ }
+
+ copy( triangle ) {
+
+ this.a.copy( triangle.a );
+ this.b.copy( triangle.b );
+ this.c.copy( triangle.c );
+
+ return this;
+
+ }
+
+ getArea() {
+
+ _v0$1.subVectors( this.c, this.b );
+ _v1$3.subVectors( this.a, this.b );
+
+ return _v0$1.cross( _v1$3 ).length() * 0.5;
+
+ }
+
+ getMidpoint( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Triangle: .getMidpoint() target is now required' );
+ target = new Vector3();
+
+ }
+
+ return target.addVectors( this.a, this.b ).add( this.c ).multiplyScalar( 1 / 3 );
+
+ }
+
+ getNormal( target ) {
+
+ return Triangle.getNormal( this.a, this.b, this.c, target );
+
+ }
+
+ getPlane( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Triangle: .getPlane() target is now required' );
+ target = new Plane();
+
+ }
+
+ return target.setFromCoplanarPoints( this.a, this.b, this.c );
+
+ }
+
+ getBarycoord( point, target ) {
+
+ return Triangle.getBarycoord( point, this.a, this.b, this.c, target );
+
+ }
+
+ getUV( point, uv1, uv2, uv3, target ) {
+
+ return Triangle.getUV( point, this.a, this.b, this.c, uv1, uv2, uv3, target );
+
+ }
+
+ containsPoint( point ) {
+
+ return Triangle.containsPoint( point, this.a, this.b, this.c );
+
+ }
+
+ isFrontFacing( direction ) {
+
+ return Triangle.isFrontFacing( this.a, this.b, this.c, direction );
+
+ }
+
+ intersectsBox( box ) {
+
+ return box.intersectsTriangle( this );
+
+ }
+
+ closestPointToPoint( p, target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Triangle: .closestPointToPoint() target is now required' );
+ target = new Vector3();
+
+ }
+
+ const a = this.a, b = this.b, c = this.c;
+ let v, w;
+
+ // algorithm thanks to Real-Time Collision Detection by Christer Ericson,
+ // published by Morgan Kaufmann Publishers, (c) 2005 Elsevier Inc.,
+ // under the accompanying license; see chapter 5.1.5 for detailed explanation.
+ // basically, we're distinguishing which of the voronoi regions of the triangle
+ // the point lies in with the minimum amount of redundant computation.
+
+ _vab.subVectors( b, a );
+ _vac.subVectors( c, a );
+ _vap.subVectors( p, a );
+ const d1 = _vab.dot( _vap );
+ const d2 = _vac.dot( _vap );
+ if ( d1 <= 0 && d2 <= 0 ) {
+
+ // vertex region of A; barycentric coords (1, 0, 0)
+ return target.copy( a );
+
+ }
+
+ _vbp.subVectors( p, b );
+ const d3 = _vab.dot( _vbp );
+ const d4 = _vac.dot( _vbp );
+ if ( d3 >= 0 && d4 <= d3 ) {
+
+ // vertex region of B; barycentric coords (0, 1, 0)
+ return target.copy( b );
+
+ }
+
+ const vc = d1 * d4 - d3 * d2;
+ if ( vc <= 0 && d1 >= 0 && d3 <= 0 ) {
+
+ v = d1 / ( d1 - d3 );
+ // edge region of AB; barycentric coords (1-v, v, 0)
+ return target.copy( a ).addScaledVector( _vab, v );
+
+ }
+
+ _vcp.subVectors( p, c );
+ const d5 = _vab.dot( _vcp );
+ const d6 = _vac.dot( _vcp );
+ if ( d6 >= 0 && d5 <= d6 ) {
+
+ // vertex region of C; barycentric coords (0, 0, 1)
+ return target.copy( c );
+
+ }
+
+ const vb = d5 * d2 - d1 * d6;
+ if ( vb <= 0 && d2 >= 0 && d6 <= 0 ) {
+
+ w = d2 / ( d2 - d6 );
+ // edge region of AC; barycentric coords (1-w, 0, w)
+ return target.copy( a ).addScaledVector( _vac, w );
+
+ }
+
+ const va = d3 * d6 - d5 * d4;
+ if ( va <= 0 && ( d4 - d3 ) >= 0 && ( d5 - d6 ) >= 0 ) {
+
+ _vbc.subVectors( c, b );
+ w = ( d4 - d3 ) / ( ( d4 - d3 ) + ( d5 - d6 ) );
+ // edge region of BC; barycentric coords (0, 1-w, w)
+ return target.copy( b ).addScaledVector( _vbc, w ); // edge region of BC
+
+ }
+
+ // face region
+ const denom = 1 / ( va + vb + vc );
+ // u = va * denom
+ v = vb * denom;
+ w = vc * denom;
+
+ return target.copy( a ).addScaledVector( _vab, v ).addScaledVector( _vac, w );
+
+ }
+
+ equals( triangle ) {
+
+ return triangle.a.equals( this.a ) && triangle.b.equals( this.b ) && triangle.c.equals( this.c );
+
+ }
+
+}
+
+const _colorKeywords = { 'aliceblue': 0xF0F8FF, 'antiquewhite': 0xFAEBD7, 'aqua': 0x00FFFF, 'aquamarine': 0x7FFFD4, 'azure': 0xF0FFFF,
+ 'beige': 0xF5F5DC, 'bisque': 0xFFE4C4, 'black': 0x000000, 'blanchedalmond': 0xFFEBCD, 'blue': 0x0000FF, 'blueviolet': 0x8A2BE2,
+ 'brown': 0xA52A2A, 'burlywood': 0xDEB887, 'cadetblue': 0x5F9EA0, 'chartreuse': 0x7FFF00, 'chocolate': 0xD2691E, 'coral': 0xFF7F50,
+ 'cornflowerblue': 0x6495ED, 'cornsilk': 0xFFF8DC, 'crimson': 0xDC143C, 'cyan': 0x00FFFF, 'darkblue': 0x00008B, 'darkcyan': 0x008B8B,
+ 'darkgoldenrod': 0xB8860B, 'darkgray': 0xA9A9A9, 'darkgreen': 0x006400, 'darkgrey': 0xA9A9A9, 'darkkhaki': 0xBDB76B, 'darkmagenta': 0x8B008B,
+ 'darkolivegreen': 0x556B2F, 'darkorange': 0xFF8C00, 'darkorchid': 0x9932CC, 'darkred': 0x8B0000, 'darksalmon': 0xE9967A, 'darkseagreen': 0x8FBC8F,
+ 'darkslateblue': 0x483D8B, 'darkslategray': 0x2F4F4F, 'darkslategrey': 0x2F4F4F, 'darkturquoise': 0x00CED1, 'darkviolet': 0x9400D3,
+ 'deeppink': 0xFF1493, 'deepskyblue': 0x00BFFF, 'dimgray': 0x696969, 'dimgrey': 0x696969, 'dodgerblue': 0x1E90FF, 'firebrick': 0xB22222,
+ 'floralwhite': 0xFFFAF0, 'forestgreen': 0x228B22, 'fuchsia': 0xFF00FF, 'gainsboro': 0xDCDCDC, 'ghostwhite': 0xF8F8FF, 'gold': 0xFFD700,
+ 'goldenrod': 0xDAA520, 'gray': 0x808080, 'green': 0x008000, 'greenyellow': 0xADFF2F, 'grey': 0x808080, 'honeydew': 0xF0FFF0, 'hotpink': 0xFF69B4,
+ 'indianred': 0xCD5C5C, 'indigo': 0x4B0082, 'ivory': 0xFFFFF0, 'khaki': 0xF0E68C, 'lavender': 0xE6E6FA, 'lavenderblush': 0xFFF0F5, 'lawngreen': 0x7CFC00,
+ 'lemonchiffon': 0xFFFACD, 'lightblue': 0xADD8E6, 'lightcoral': 0xF08080, 'lightcyan': 0xE0FFFF, 'lightgoldenrodyellow': 0xFAFAD2, 'lightgray': 0xD3D3D3,
+ 'lightgreen': 0x90EE90, 'lightgrey': 0xD3D3D3, 'lightpink': 0xFFB6C1, 'lightsalmon': 0xFFA07A, 'lightseagreen': 0x20B2AA, 'lightskyblue': 0x87CEFA,
+ 'lightslategray': 0x778899, 'lightslategrey': 0x778899, 'lightsteelblue': 0xB0C4DE, 'lightyellow': 0xFFFFE0, 'lime': 0x00FF00, 'limegreen': 0x32CD32,
+ 'linen': 0xFAF0E6, 'magenta': 0xFF00FF, 'maroon': 0x800000, 'mediumaquamarine': 0x66CDAA, 'mediumblue': 0x0000CD, 'mediumorchid': 0xBA55D3,
+ 'mediumpurple': 0x9370DB, 'mediumseagreen': 0x3CB371, 'mediumslateblue': 0x7B68EE, 'mediumspringgreen': 0x00FA9A, 'mediumturquoise': 0x48D1CC,
+ 'mediumvioletred': 0xC71585, 'midnightblue': 0x191970, 'mintcream': 0xF5FFFA, 'mistyrose': 0xFFE4E1, 'moccasin': 0xFFE4B5, 'navajowhite': 0xFFDEAD,
+ 'navy': 0x000080, 'oldlace': 0xFDF5E6, 'olive': 0x808000, 'olivedrab': 0x6B8E23, 'orange': 0xFFA500, 'orangered': 0xFF4500, 'orchid': 0xDA70D6,
+ 'palegoldenrod': 0xEEE8AA, 'palegreen': 0x98FB98, 'paleturquoise': 0xAFEEEE, 'palevioletred': 0xDB7093, 'papayawhip': 0xFFEFD5, 'peachpuff': 0xFFDAB9,
+ 'peru': 0xCD853F, 'pink': 0xFFC0CB, 'plum': 0xDDA0DD, 'powderblue': 0xB0E0E6, 'purple': 0x800080, 'rebeccapurple': 0x663399, 'red': 0xFF0000, 'rosybrown': 0xBC8F8F,
+ 'royalblue': 0x4169E1, 'saddlebrown': 0x8B4513, 'salmon': 0xFA8072, 'sandybrown': 0xF4A460, 'seagreen': 0x2E8B57, 'seashell': 0xFFF5EE,
+ 'sienna': 0xA0522D, 'silver': 0xC0C0C0, 'skyblue': 0x87CEEB, 'slateblue': 0x6A5ACD, 'slategray': 0x708090, 'slategrey': 0x708090, 'snow': 0xFFFAFA,
+ 'springgreen': 0x00FF7F, 'steelblue': 0x4682B4, 'tan': 0xD2B48C, 'teal': 0x008080, 'thistle': 0xD8BFD8, 'tomato': 0xFF6347, 'turquoise': 0x40E0D0,
+ 'violet': 0xEE82EE, 'wheat': 0xF5DEB3, 'white': 0xFFFFFF, 'whitesmoke': 0xF5F5F5, 'yellow': 0xFFFF00, 'yellowgreen': 0x9ACD32 };
+
+const _hslA = { h: 0, s: 0, l: 0 };
+const _hslB = { h: 0, s: 0, l: 0 };
+
+function hue2rgb( p, q, t ) {
+
+ if ( t < 0 ) t += 1;
+ if ( t > 1 ) t -= 1;
+ if ( t < 1 / 6 ) return p + ( q - p ) * 6 * t;
+ if ( t < 1 / 2 ) return q;
+ if ( t < 2 / 3 ) return p + ( q - p ) * 6 * ( 2 / 3 - t );
+ return p;
+
+}
+
+function SRGBToLinear( c ) {
+
+ return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
+
+}
+
+function LinearToSRGB( c ) {
+
+ return ( c < 0.0031308 ) ? c * 12.92 : 1.055 * ( Math.pow( c, 0.41666 ) ) - 0.055;
+
+}
+
+class Color {
+
+ constructor( r, g, b ) {
+
+ Object.defineProperty( this, 'isColor', { value: true } );
+
+ if ( g === undefined && b === undefined ) {
+
+ // r is THREE.Color, hex or string
+ return this.set( r );
+
+ }
+
+ return this.setRGB( r, g, b );
+
+ }
+
+ set( value ) {
+
+ if ( value && value.isColor ) {
+
+ this.copy( value );
+
+ } else if ( typeof value === 'number' ) {
+
+ this.setHex( value );
+
+ } else if ( typeof value === 'string' ) {
+
+ this.setStyle( value );
+
+ }
+
+ return this;
+
+ }
+
+ setScalar( scalar ) {
+
+ this.r = scalar;
+ this.g = scalar;
+ this.b = scalar;
+
+ return this;
+
+ }
+
+ setHex( hex ) {
+
+ hex = Math.floor( hex );
+
+ this.r = ( hex >> 16 & 255 ) / 255;
+ this.g = ( hex >> 8 & 255 ) / 255;
+ this.b = ( hex & 255 ) / 255;
+
+ return this;
+
+ }
+
+ setRGB( r, g, b ) {
+
+ this.r = r;
+ this.g = g;
+ this.b = b;
+
+ return this;
+
+ }
+
+ setHSL( h, s, l ) {
+
+ // h,s,l ranges are in 0.0 - 1.0
+ h = MathUtils.euclideanModulo( h, 1 );
+ s = MathUtils.clamp( s, 0, 1 );
+ l = MathUtils.clamp( l, 0, 1 );
+
+ if ( s === 0 ) {
+
+ this.r = this.g = this.b = l;
+
+ } else {
+
+ const p = l <= 0.5 ? l * ( 1 + s ) : l + s - ( l * s );
+ const q = ( 2 * l ) - p;
+
+ this.r = hue2rgb( q, p, h + 1 / 3 );
+ this.g = hue2rgb( q, p, h );
+ this.b = hue2rgb( q, p, h - 1 / 3 );
+
+ }
+
+ return this;
+
+ }
+
+ setStyle( style ) {
+
+ function handleAlpha( string ) {
+
+ if ( string === undefined ) return;
+
+ if ( parseFloat( string ) < 1 ) {
+
+ console.warn( 'THREE.Color: Alpha component of ' + style + ' will be ignored.' );
+
+ }
+
+ }
+
+
+ let m;
+
+ if ( m = /^((?:rgb|hsl)a?)\(([^\)]*)\)/.exec( style ) ) {
+
+ // rgb / hsl
+
+ let color;
+ const name = m[ 1 ];
+ const components = m[ 2 ];
+
+ switch ( name ) {
+
+ case 'rgb':
+ case 'rgba':
+
+ if ( color = /^\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
+
+ // rgb(255,0,0) rgba(255,0,0,0.5)
+ this.r = Math.min( 255, parseInt( color[ 1 ], 10 ) ) / 255;
+ this.g = Math.min( 255, parseInt( color[ 2 ], 10 ) ) / 255;
+ this.b = Math.min( 255, parseInt( color[ 3 ], 10 ) ) / 255;
+
+ handleAlpha( color[ 4 ] );
+
+ return this;
+
+ }
+
+ if ( color = /^\s*(\d+)\%\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
+
+ // rgb(100%,0%,0%) rgba(100%,0%,0%,0.5)
+ this.r = Math.min( 100, parseInt( color[ 1 ], 10 ) ) / 100;
+ this.g = Math.min( 100, parseInt( color[ 2 ], 10 ) ) / 100;
+ this.b = Math.min( 100, parseInt( color[ 3 ], 10 ) ) / 100;
+
+ handleAlpha( color[ 4 ] );
+
+ return this;
+
+ }
+
+ break;
+
+ case 'hsl':
+ case 'hsla':
+
+ if ( color = /^\s*(\d*\.?\d+)\s*,\s*(\d+)\%\s*,\s*(\d+)\%\s*(?:,\s*(\d*\.?\d+)\s*)?$/.exec( components ) ) {
+
+ // hsl(120,50%,50%) hsla(120,50%,50%,0.5)
+ const h = parseFloat( color[ 1 ] ) / 360;
+ const s = parseInt( color[ 2 ], 10 ) / 100;
+ const l = parseInt( color[ 3 ], 10 ) / 100;
+
+ handleAlpha( color[ 4 ] );
+
+ return this.setHSL( h, s, l );
+
+ }
+
+ break;
+
+ }
+
+ } else if ( m = /^\#([A-Fa-f\d]+)$/.exec( style ) ) {
+
+ // hex color
+
+ const hex = m[ 1 ];
+ const size = hex.length;
+
+ if ( size === 3 ) {
+
+ // #ff0
+ this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 0 ), 16 ) / 255;
+ this.g = parseInt( hex.charAt( 1 ) + hex.charAt( 1 ), 16 ) / 255;
+ this.b = parseInt( hex.charAt( 2 ) + hex.charAt( 2 ), 16 ) / 255;
+
+ return this;
+
+ } else if ( size === 6 ) {
+
+ // #ff0000
+ this.r = parseInt( hex.charAt( 0 ) + hex.charAt( 1 ), 16 ) / 255;
+ this.g = parseInt( hex.charAt( 2 ) + hex.charAt( 3 ), 16 ) / 255;
+ this.b = parseInt( hex.charAt( 4 ) + hex.charAt( 5 ), 16 ) / 255;
+
+ return this;
+
+ }
+
+ }
+
+ if ( style && style.length > 0 ) {
+
+ return this.setColorName( style );
+
+ }
+
+ return this;
+
+ }
+
+ setColorName( style ) {
+
+ // color keywords
+ const hex = _colorKeywords[ style ];
+
+ if ( hex !== undefined ) {
+
+ // red
+ this.setHex( hex );
+
+ } else {
+
+ // unknown color
+ console.warn( 'THREE.Color: Unknown color ' + style );
+
+ }
+
+ return this;
+
+ }
+
+ clone() {
+
+ return new this.constructor( this.r, this.g, this.b );
+
+ }
+
+ copy( color ) {
+
+ this.r = color.r;
+ this.g = color.g;
+ this.b = color.b;
+
+ return this;
+
+ }
+
+ copyGammaToLinear( color, gammaFactor = 2.0 ) {
+
+ this.r = Math.pow( color.r, gammaFactor );
+ this.g = Math.pow( color.g, gammaFactor );
+ this.b = Math.pow( color.b, gammaFactor );
+
+ return this;
+
+ }
+
+ copyLinearToGamma( color, gammaFactor = 2.0 ) {
+
+ const safeInverse = ( gammaFactor > 0 ) ? ( 1.0 / gammaFactor ) : 1.0;
+
+ this.r = Math.pow( color.r, safeInverse );
+ this.g = Math.pow( color.g, safeInverse );
+ this.b = Math.pow( color.b, safeInverse );
+
+ return this;
+
+ }
+
+ convertGammaToLinear( gammaFactor ) {
+
+ this.copyGammaToLinear( this, gammaFactor );
+
+ return this;
+
+ }
+
+ convertLinearToGamma( gammaFactor ) {
+
+ this.copyLinearToGamma( this, gammaFactor );
+
+ return this;
+
+ }
+
+ copySRGBToLinear( color ) {
+
+ this.r = SRGBToLinear( color.r );
+ this.g = SRGBToLinear( color.g );
+ this.b = SRGBToLinear( color.b );
+
+ return this;
+
+ }
+
+ copyLinearToSRGB( color ) {
+
+ this.r = LinearToSRGB( color.r );
+ this.g = LinearToSRGB( color.g );
+ this.b = LinearToSRGB( color.b );
+
+ return this;
+
+ }
+
+ convertSRGBToLinear() {
+
+ this.copySRGBToLinear( this );
+
+ return this;
+
+ }
+
+ convertLinearToSRGB() {
+
+ this.copyLinearToSRGB( this );
+
+ return this;
+
+ }
+
+ getHex() {
+
+ return ( this.r * 255 ) << 16 ^ ( this.g * 255 ) << 8 ^ ( this.b * 255 ) << 0;
+
+ }
+
+ getHexString() {
+
+ return ( '000000' + this.getHex().toString( 16 ) ).slice( - 6 );
+
+ }
+
+ getHSL( target ) {
+
+ // h,s,l ranges are in 0.0 - 1.0
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Color: .getHSL() target is now required' );
+ target = { h: 0, s: 0, l: 0 };
+
+ }
+
+ const r = this.r, g = this.g, b = this.b;
+
+ const max = Math.max( r, g, b );
+ const min = Math.min( r, g, b );
+
+ let hue, saturation;
+ const lightness = ( min + max ) / 2.0;
+
+ if ( min === max ) {
+
+ hue = 0;
+ saturation = 0;
+
+ } else {
+
+ const delta = max - min;
+
+ saturation = lightness <= 0.5 ? delta / ( max + min ) : delta / ( 2 - max - min );
+
+ switch ( max ) {
+
+ case r: hue = ( g - b ) / delta + ( g < b ? 6 : 0 ); break;
+ case g: hue = ( b - r ) / delta + 2; break;
+ case b: hue = ( r - g ) / delta + 4; break;
+
+ }
+
+ hue /= 6;
+
+ }
+
+ target.h = hue;
+ target.s = saturation;
+ target.l = lightness;
+
+ return target;
+
+ }
+
+ getStyle() {
+
+ return 'rgb(' + ( ( this.r * 255 ) | 0 ) + ',' + ( ( this.g * 255 ) | 0 ) + ',' + ( ( this.b * 255 ) | 0 ) + ')';
+
+ }
+
+ offsetHSL( h, s, l ) {
+
+ this.getHSL( _hslA );
+
+ _hslA.h += h; _hslA.s += s; _hslA.l += l;
+
+ this.setHSL( _hslA.h, _hslA.s, _hslA.l );
+
+ return this;
+
+ }
+
+ add( color ) {
+
+ this.r += color.r;
+ this.g += color.g;
+ this.b += color.b;
+
+ return this;
+
+ }
+
+ addColors( color1, color2 ) {
+
+ this.r = color1.r + color2.r;
+ this.g = color1.g + color2.g;
+ this.b = color1.b + color2.b;
+
+ return this;
+
+ }
+
+ addScalar( s ) {
+
+ this.r += s;
+ this.g += s;
+ this.b += s;
+
+ return this;
+
+ }
+
+ sub( color ) {
+
+ this.r = Math.max( 0, this.r - color.r );
+ this.g = Math.max( 0, this.g - color.g );
+ this.b = Math.max( 0, this.b - color.b );
+
+ return this;
+
+ }
+
+ multiply( color ) {
+
+ this.r *= color.r;
+ this.g *= color.g;
+ this.b *= color.b;
+
+ return this;
+
+ }
+
+ multiplyScalar( s ) {
+
+ this.r *= s;
+ this.g *= s;
+ this.b *= s;
+
+ return this;
+
+ }
+
+ lerp( color, alpha ) {
+
+ this.r += ( color.r - this.r ) * alpha;
+ this.g += ( color.g - this.g ) * alpha;
+ this.b += ( color.b - this.b ) * alpha;
+
+ return this;
+
+ }
+
+ lerpColors( color1, color2, alpha ) {
+
+ this.r = color1.r + ( color2.r - color1.r ) * alpha;
+ this.g = color1.g + ( color2.g - color1.g ) * alpha;
+ this.b = color1.b + ( color2.b - color1.b ) * alpha;
+
+ return this;
+
+ }
+
+ lerpHSL( color, alpha ) {
+
+ this.getHSL( _hslA );
+ color.getHSL( _hslB );
+
+ const h = MathUtils.lerp( _hslA.h, _hslB.h, alpha );
+ const s = MathUtils.lerp( _hslA.s, _hslB.s, alpha );
+ const l = MathUtils.lerp( _hslA.l, _hslB.l, alpha );
+
+ this.setHSL( h, s, l );
+
+ return this;
+
+ }
+
+ equals( c ) {
+
+ return ( c.r === this.r ) && ( c.g === this.g ) && ( c.b === this.b );
+
+ }
+
+ fromArray( array, offset = 0 ) {
+
+ this.r = array[ offset ];
+ this.g = array[ offset + 1 ];
+ this.b = array[ offset + 2 ];
+
+ return this;
+
+ }
+
+ toArray( array = [], offset = 0 ) {
+
+ array[ offset ] = this.r;
+ array[ offset + 1 ] = this.g;
+ array[ offset + 2 ] = this.b;
+
+ return array;
+
+ }
+
+ fromBufferAttribute( attribute, index ) {
+
+ this.r = attribute.getX( index );
+ this.g = attribute.getY( index );
+ this.b = attribute.getZ( index );
+
+ if ( attribute.normalized === true ) {
+
+ // assuming Uint8Array
+
+ this.r /= 255;
+ this.g /= 255;
+ this.b /= 255;
+
+ }
+
+ return this;
+
+ }
+
+ toJSON() {
+
+ return this.getHex();
+
+ }
+
+}
+
+Color.NAMES = _colorKeywords;
+Color.prototype.r = 1;
+Color.prototype.g = 1;
+Color.prototype.b = 1;
+
+class Face3 {
+
+ constructor( a, b, c, normal, color, materialIndex = 0 ) {
+
+ this.a = a;
+ this.b = b;
+ this.c = c;
+
+ this.normal = ( normal && normal.isVector3 ) ? normal : new Vector3();
+ this.vertexNormals = Array.isArray( normal ) ? normal : [];
+
+ this.color = ( color && color.isColor ) ? color : new Color();
+ this.vertexColors = Array.isArray( color ) ? color : [];
+
+ this.materialIndex = materialIndex;
+
+ }
+
+ clone() {
+
+ return new this.constructor().copy( this );
+
+ }
+
+ copy( source ) {
+
+ this.a = source.a;
+ this.b = source.b;
+ this.c = source.c;
+
+ this.normal.copy( source.normal );
+ this.color.copy( source.color );
+
+ this.materialIndex = source.materialIndex;
+
+ for ( let i = 0, il = source.vertexNormals.length; i < il; i ++ ) {
+
+ this.vertexNormals[ i ] = source.vertexNormals[ i ].clone();
+
+ }
+
+ for ( let i = 0, il = source.vertexColors.length; i < il; i ++ ) {
+
+ this.vertexColors[ i ] = source.vertexColors[ i ].clone();
+
+ }
+
+ return this;
+
+ }
+
+}
+
+let materialId = 0;
+
+function Material() {
+
+ Object.defineProperty( this, 'id', { value: materialId ++ } );
+
+ this.uuid = MathUtils.generateUUID();
+
+ this.name = '';
+ this.type = 'Material';
+
+ this.fog = true;
+
+ this.blending = NormalBlending;
+ this.side = FrontSide;
+ this.flatShading = false;
+ this.vertexColors = false;
+
+ this.opacity = 1;
+ this.transparent = false;
+
+ this.blendSrc = SrcAlphaFactor;
+ this.blendDst = OneMinusSrcAlphaFactor;
+ this.blendEquation = AddEquation;
+ this.blendSrcAlpha = null;
+ this.blendDstAlpha = null;
+ this.blendEquationAlpha = null;
+
+ this.depthFunc = LessEqualDepth;
+ this.depthTest = true;
+ this.depthWrite = true;
+
+ this.stencilWriteMask = 0xff;
+ this.stencilFunc = AlwaysStencilFunc;
+ this.stencilRef = 0;
+ this.stencilFuncMask = 0xff;
+ this.stencilFail = KeepStencilOp;
+ this.stencilZFail = KeepStencilOp;
+ this.stencilZPass = KeepStencilOp;
+ this.stencilWrite = false;
+
+ this.clippingPlanes = null;
+ this.clipIntersection = false;
+ this.clipShadows = false;
+
+ this.shadowSide = null;
+
+ this.colorWrite = true;
+
+ this.precision = null; // override the renderer's default precision for this material
+
+ this.polygonOffset = false;
+ this.polygonOffsetFactor = 0;
+ this.polygonOffsetUnits = 0;
+
+ this.dithering = false;
+
+ this.alphaTest = 0;
+ this.premultipliedAlpha = false;
+
+ this.visible = true;
+
+ this.toneMapped = true;
+
+ this.userData = {};
+
+ this.version = 0;
+
+}
+
+Material.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+ constructor: Material,
+
+ isMaterial: true,
+
+ onBeforeCompile: function ( /* shaderobject, renderer */ ) {},
+
+ customProgramCacheKey: function () {
+
+ return this.onBeforeCompile.toString();
+
+ },
+
+ setValues: function ( values ) {
+
+ if ( values === undefined ) return;
+
+ for ( const key in values ) {
+
+ const newValue = values[ key ];
+
+ if ( newValue === undefined ) {
+
+ console.warn( 'THREE.Material: \'' + key + '\' parameter is undefined.' );
+ continue;
+
+ }
+
+ // for backward compatability if shading is set in the constructor
+ if ( key === 'shading' ) {
+
+ console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );
+ this.flatShading = ( newValue === FlatShading ) ? true : false;
+ continue;
+
+ }
+
+ const currentValue = this[ key ];
+
+ if ( currentValue === undefined ) {
+
+ console.warn( 'THREE.' + this.type + ': \'' + key + '\' is not a property of this material.' );
+ continue;
+
+ }
+
+ if ( currentValue && currentValue.isColor ) {
+
+ currentValue.set( newValue );
+
+ } else if ( ( currentValue && currentValue.isVector3 ) && ( newValue && newValue.isVector3 ) ) {
+
+ currentValue.copy( newValue );
+
+ } else {
+
+ this[ key ] = newValue;
+
+ }
+
+ }
+
+ },
+
+ toJSON: function ( meta ) {
+
+ const isRoot = ( meta === undefined || typeof meta === 'string' );
+
+ if ( isRoot ) {
+
+ meta = {
+ textures: {},
+ images: {}
+ };
+
+ }
+
+ const data = {
+ metadata: {
+ version: 4.5,
+ type: 'Material',
+ generator: 'Material.toJSON'
+ }
+ };
+
+ // standard Material serialization
+ data.uuid = this.uuid;
+ data.type = this.type;
+
+ if ( this.name !== '' ) data.name = this.name;
+
+ if ( this.color && this.color.isColor ) data.color = this.color.getHex();
+
+ if ( this.roughness !== undefined ) data.roughness = this.roughness;
+ if ( this.metalness !== undefined ) data.metalness = this.metalness;
+
+ if ( this.sheen && this.sheen.isColor ) data.sheen = this.sheen.getHex();
+ if ( this.emissive && this.emissive.isColor ) data.emissive = this.emissive.getHex();
+ if ( this.emissiveIntensity && this.emissiveIntensity !== 1 ) data.emissiveIntensity = this.emissiveIntensity;
+
+ if ( this.specular && this.specular.isColor ) data.specular = this.specular.getHex();
+ if ( this.shininess !== undefined ) data.shininess = this.shininess;
+ if ( this.clearcoat !== undefined ) data.clearcoat = this.clearcoat;
+ if ( this.clearcoatRoughness !== undefined ) data.clearcoatRoughness = this.clearcoatRoughness;
+
+ if ( this.clearcoatMap && this.clearcoatMap.isTexture ) {
+
+ data.clearcoatMap = this.clearcoatMap.toJSON( meta ).uuid;
+
+ }
+
+ if ( this.clearcoatRoughnessMap && this.clearcoatRoughnessMap.isTexture ) {
+
+ data.clearcoatRoughnessMap = this.clearcoatRoughnessMap.toJSON( meta ).uuid;
+
+ }
+
+ if ( this.clearcoatNormalMap && this.clearcoatNormalMap.isTexture ) {
+
+ data.clearcoatNormalMap = this.clearcoatNormalMap.toJSON( meta ).uuid;
+ data.clearcoatNormalScale = this.clearcoatNormalScale.toArray();
+
+ }
+
+ if ( this.map && this.map.isTexture ) data.map = this.map.toJSON( meta ).uuid;
+ if ( this.matcap && this.matcap.isTexture ) data.matcap = this.matcap.toJSON( meta ).uuid;
+ if ( this.alphaMap && this.alphaMap.isTexture ) data.alphaMap = this.alphaMap.toJSON( meta ).uuid;
+ if ( this.lightMap && this.lightMap.isTexture ) data.lightMap = this.lightMap.toJSON( meta ).uuid;
+
+ if ( this.aoMap && this.aoMap.isTexture ) {
+
+ data.aoMap = this.aoMap.toJSON( meta ).uuid;
+ data.aoMapIntensity = this.aoMapIntensity;
+
+ }
+
+ if ( this.bumpMap && this.bumpMap.isTexture ) {
+
+ data.bumpMap = this.bumpMap.toJSON( meta ).uuid;
+ data.bumpScale = this.bumpScale;
+
+ }
+
+ if ( this.normalMap && this.normalMap.isTexture ) {
+
+ data.normalMap = this.normalMap.toJSON( meta ).uuid;
+ data.normalMapType = this.normalMapType;
+ data.normalScale = this.normalScale.toArray();
+
+ }
+
+ if ( this.displacementMap && this.displacementMap.isTexture ) {
+
+ data.displacementMap = this.displacementMap.toJSON( meta ).uuid;
+ data.displacementScale = this.displacementScale;
+ data.displacementBias = this.displacementBias;
+
+ }
+
+ if ( this.roughnessMap && this.roughnessMap.isTexture ) data.roughnessMap = this.roughnessMap.toJSON( meta ).uuid;
+ if ( this.metalnessMap && this.metalnessMap.isTexture ) data.metalnessMap = this.metalnessMap.toJSON( meta ).uuid;
+
+ if ( this.emissiveMap && this.emissiveMap.isTexture ) data.emissiveMap = this.emissiveMap.toJSON( meta ).uuid;
+ if ( this.specularMap && this.specularMap.isTexture ) data.specularMap = this.specularMap.toJSON( meta ).uuid;
+
+ if ( this.envMap && this.envMap.isTexture ) {
+
+ data.envMap = this.envMap.toJSON( meta ).uuid;
+ data.reflectivity = this.reflectivity; // Scale behind envMap
+ data.refractionRatio = this.refractionRatio;
+
+ if ( this.combine !== undefined ) data.combine = this.combine;
+ if ( this.envMapIntensity !== undefined ) data.envMapIntensity = this.envMapIntensity;
+
+ }
+
+ if ( this.gradientMap && this.gradientMap.isTexture ) {
+
+ data.gradientMap = this.gradientMap.toJSON( meta ).uuid;
+
+ }
+
+ if ( this.size !== undefined ) data.size = this.size;
+ if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation;
+
+ if ( this.blending !== NormalBlending ) data.blending = this.blending;
+ if ( this.flatShading === true ) data.flatShading = this.flatShading;
+ if ( this.side !== FrontSide ) data.side = this.side;
+ if ( this.vertexColors ) data.vertexColors = true;
+
+ if ( this.opacity < 1 ) data.opacity = this.opacity;
+ if ( this.transparent === true ) data.transparent = this.transparent;
+
+ data.depthFunc = this.depthFunc;
+ data.depthTest = this.depthTest;
+ data.depthWrite = this.depthWrite;
+
+ data.stencilWrite = this.stencilWrite;
+ data.stencilWriteMask = this.stencilWriteMask;
+ data.stencilFunc = this.stencilFunc;
+ data.stencilRef = this.stencilRef;
+ data.stencilFuncMask = this.stencilFuncMask;
+ data.stencilFail = this.stencilFail;
+ data.stencilZFail = this.stencilZFail;
+ data.stencilZPass = this.stencilZPass;
+
+ // rotation (SpriteMaterial)
+ if ( this.rotation && this.rotation !== 0 ) data.rotation = this.rotation;
+
+ if ( this.polygonOffset === true ) data.polygonOffset = true;
+ if ( this.polygonOffsetFactor !== 0 ) data.polygonOffsetFactor = this.polygonOffsetFactor;
+ if ( this.polygonOffsetUnits !== 0 ) data.polygonOffsetUnits = this.polygonOffsetUnits;
+
+ if ( this.linewidth && this.linewidth !== 1 ) data.linewidth = this.linewidth;
+ if ( this.dashSize !== undefined ) data.dashSize = this.dashSize;
+ if ( this.gapSize !== undefined ) data.gapSize = this.gapSize;
+ if ( this.scale !== undefined ) data.scale = this.scale;
+
+ if ( this.dithering === true ) data.dithering = true;
+
+ if ( this.alphaTest > 0 ) data.alphaTest = this.alphaTest;
+ if ( this.premultipliedAlpha === true ) data.premultipliedAlpha = this.premultipliedAlpha;
+
+ if ( this.wireframe === true ) data.wireframe = this.wireframe;
+ if ( this.wireframeLinewidth > 1 ) data.wireframeLinewidth = this.wireframeLinewidth;
+ if ( this.wireframeLinecap !== 'round' ) data.wireframeLinecap = this.wireframeLinecap;
+ if ( this.wireframeLinejoin !== 'round' ) data.wireframeLinejoin = this.wireframeLinejoin;
+
+ if ( this.morphTargets === true ) data.morphTargets = true;
+ if ( this.morphNormals === true ) data.morphNormals = true;
+ if ( this.skinning === true ) data.skinning = true;
+
+ if ( this.visible === false ) data.visible = false;
+
+ if ( this.toneMapped === false ) data.toneMapped = false;
+
+ if ( JSON.stringify( this.userData ) !== '{}' ) data.userData = this.userData;
+
+ // TODO: Copied from Object3D.toJSON
+
+ function extractFromCache( cache ) {
+
+ const values = [];
+
+ for ( const key in cache ) {
+
+ const data = cache[ key ];
+ delete data.metadata;
+ values.push( data );
+
+ }
+
+ return values;
+
+ }
+
+ if ( isRoot ) {
+
+ const textures = extractFromCache( meta.textures );
+ const images = extractFromCache( meta.images );
+
+ if ( textures.length > 0 ) data.textures = textures;
+ if ( images.length > 0 ) data.images = images;
+
+ }
+
+ return data;
+
+ },
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ },
+
+ copy: function ( source ) {
+
+ this.name = source.name;
+
+ this.fog = source.fog;
+
+ this.blending = source.blending;
+ this.side = source.side;
+ this.flatShading = source.flatShading;
+ this.vertexColors = source.vertexColors;
+
+ this.opacity = source.opacity;
+ this.transparent = source.transparent;
+
+ this.blendSrc = source.blendSrc;
+ this.blendDst = source.blendDst;
+ this.blendEquation = source.blendEquation;
+ this.blendSrcAlpha = source.blendSrcAlpha;
+ this.blendDstAlpha = source.blendDstAlpha;
+ this.blendEquationAlpha = source.blendEquationAlpha;
+
+ this.depthFunc = source.depthFunc;
+ this.depthTest = source.depthTest;
+ this.depthWrite = source.depthWrite;
+
+ this.stencilWriteMask = source.stencilWriteMask;
+ this.stencilFunc = source.stencilFunc;
+ this.stencilRef = source.stencilRef;
+ this.stencilFuncMask = source.stencilFuncMask;
+ this.stencilFail = source.stencilFail;
+ this.stencilZFail = source.stencilZFail;
+ this.stencilZPass = source.stencilZPass;
+ this.stencilWrite = source.stencilWrite;
+
+ const srcPlanes = source.clippingPlanes;
+ let dstPlanes = null;
+
+ if ( srcPlanes !== null ) {
+
+ const n = srcPlanes.length;
+ dstPlanes = new Array( n );
+
+ for ( let i = 0; i !== n; ++ i ) {
+
+ dstPlanes[ i ] = srcPlanes[ i ].clone();
+
+ }
+
+ }
+
+ this.clippingPlanes = dstPlanes;
+ this.clipIntersection = source.clipIntersection;
+ this.clipShadows = source.clipShadows;
+
+ this.shadowSide = source.shadowSide;
+
+ this.colorWrite = source.colorWrite;
+
+ this.precision = source.precision;
+
+ this.polygonOffset = source.polygonOffset;
+ this.polygonOffsetFactor = source.polygonOffsetFactor;
+ this.polygonOffsetUnits = source.polygonOffsetUnits;
+
+ this.dithering = source.dithering;
+
+ this.alphaTest = source.alphaTest;
+ this.premultipliedAlpha = source.premultipliedAlpha;
+
+ this.visible = source.visible;
+
+ this.toneMapped = source.toneMapped;
+
+ this.userData = JSON.parse( JSON.stringify( source.userData ) );
+
+ return this;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+} );
+
+Object.defineProperty( Material.prototype, 'needsUpdate', {
+
+ set: function ( value ) {
+
+ if ( value === true ) this.version ++;
+
+ }
+
+} );
+
+/**
+ * parameters = {
+ * color: <hex>,
+ * opacity: <float>,
+ * map: new THREE.Texture( <Image> ),
+ *
+ * lightMap: new THREE.Texture( <Image> ),
+ * lightMapIntensity: <float>
+ *
+ * aoMap: new THREE.Texture( <Image> ),
+ * aoMapIntensity: <float>
+ *
+ * specularMap: new THREE.Texture( <Image> ),
+ *
+ * alphaMap: new THREE.Texture( <Image> ),
+ *
+ * envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
+ * combine: THREE.Multiply,
+ * reflectivity: <float>,
+ * refractionRatio: <float>,
+ *
+ * depthTest: <bool>,
+ * depthWrite: <bool>,
+ *
+ * wireframe: <boolean>,
+ * wireframeLinewidth: <float>,
+ *
+ * skinning: <bool>,
+ * morphTargets: <bool>
+ * }
+ */
+
+function MeshBasicMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.type = 'MeshBasicMaterial';
+
+ this.color = new Color( 0xffffff ); // emissive
+
+ this.map = null;
+
+ this.lightMap = null;
+ this.lightMapIntensity = 1.0;
+
+ this.aoMap = null;
+ this.aoMapIntensity = 1.0;
+
+ this.specularMap = null;
+
+ this.alphaMap = null;
+
+ this.envMap = null;
+ this.combine = MultiplyOperation;
+ this.reflectivity = 1;
+ this.refractionRatio = 0.98;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = 'round';
+ this.wireframeLinejoin = 'round';
+
+ this.skinning = false;
+ this.morphTargets = false;
+
+ this.setValues( parameters );
+
+}
+
+MeshBasicMaterial.prototype = Object.create( Material.prototype );
+MeshBasicMaterial.prototype.constructor = MeshBasicMaterial;
+
+MeshBasicMaterial.prototype.isMeshBasicMaterial = true;
+
+MeshBasicMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.color.copy( source.color );
+
+ this.map = source.map;
+
+ this.lightMap = source.lightMap;
+ this.lightMapIntensity = source.lightMapIntensity;
+
+ this.aoMap = source.aoMap;
+ this.aoMapIntensity = source.aoMapIntensity;
+
+ this.specularMap = source.specularMap;
+
+ this.alphaMap = source.alphaMap;
+
+ this.envMap = source.envMap;
+ this.combine = source.combine;
+ this.reflectivity = source.reflectivity;
+ this.refractionRatio = source.refractionRatio;
+
+ this.wireframe = source.wireframe;
+ this.wireframeLinewidth = source.wireframeLinewidth;
+ this.wireframeLinecap = source.wireframeLinecap;
+ this.wireframeLinejoin = source.wireframeLinejoin;
+
+ this.skinning = source.skinning;
+ this.morphTargets = source.morphTargets;
+
+ return this;
+
+};
+
+const _vector$3 = new Vector3();
+const _vector2$1 = new Vector2();
+
+function BufferAttribute( array, itemSize, normalized ) {
+
+ if ( Array.isArray( array ) ) {
+
+ throw new TypeError( 'THREE.BufferAttribute: array should be a Typed Array.' );
+
+ }
+
+ this.name = '';
+
+ this.array = array;
+ this.itemSize = itemSize;
+ this.count = array !== undefined ? array.length / itemSize : 0;
+ this.normalized = normalized === true;
+
+ this.usage = StaticDrawUsage;
+ this.updateRange = { offset: 0, count: - 1 };
+
+ this.version = 0;
+
+}
+
+Object.defineProperty( BufferAttribute.prototype, 'needsUpdate', {
+
+ set: function ( value ) {
+
+ if ( value === true ) this.version ++;
+
+ }
+
+} );
+
+Object.assign( BufferAttribute.prototype, {
+
+ isBufferAttribute: true,
+
+ onUploadCallback: function () {},
+
+ setUsage: function ( value ) {
+
+ this.usage = value;
+
+ return this;
+
+ },
+
+ copy: function ( source ) {
+
+ this.name = source.name;
+ this.array = new source.array.constructor( source.array );
+ this.itemSize = source.itemSize;
+ this.count = source.count;
+ this.normalized = source.normalized;
+
+ this.usage = source.usage;
+
+ return this;
+
+ },
+
+ copyAt: function ( index1, attribute, index2 ) {
+
+ index1 *= this.itemSize;
+ index2 *= attribute.itemSize;
+
+ for ( let i = 0, l = this.itemSize; i < l; i ++ ) {
+
+ this.array[ index1 + i ] = attribute.array[ index2 + i ];
+
+ }
+
+ return this;
+
+ },
+
+ copyArray: function ( array ) {
+
+ this.array.set( array );
+
+ return this;
+
+ },
+
+ copyColorsArray: function ( colors ) {
+
+ const array = this.array;
+ let offset = 0;
+
+ for ( let i = 0, l = colors.length; i < l; i ++ ) {
+
+ let color = colors[ i ];
+
+ if ( color === undefined ) {
+
+ console.warn( 'THREE.BufferAttribute.copyColorsArray(): color is undefined', i );
+ color = new Color();
+
+ }
+
+ array[ offset ++ ] = color.r;
+ array[ offset ++ ] = color.g;
+ array[ offset ++ ] = color.b;
+
+ }
+
+ return this;
+
+ },
+
+ copyVector2sArray: function ( vectors ) {
+
+ const array = this.array;
+ let offset = 0;
+
+ for ( let i = 0, l = vectors.length; i < l; i ++ ) {
+
+ let vector = vectors[ i ];
+
+ if ( vector === undefined ) {
+
+ console.warn( 'THREE.BufferAttribute.copyVector2sArray(): vector is undefined', i );
+ vector = new Vector2();
+
+ }
+
+ array[ offset ++ ] = vector.x;
+ array[ offset ++ ] = vector.y;
+
+ }
+
+ return this;
+
+ },
+
+ copyVector3sArray: function ( vectors ) {
+
+ const array = this.array;
+ let offset = 0;
+
+ for ( let i = 0, l = vectors.length; i < l; i ++ ) {
+
+ let vector = vectors[ i ];
+
+ if ( vector === undefined ) {
+
+ console.warn( 'THREE.BufferAttribute.copyVector3sArray(): vector is undefined', i );
+ vector = new Vector3();
+
+ }
+
+ array[ offset ++ ] = vector.x;
+ array[ offset ++ ] = vector.y;
+ array[ offset ++ ] = vector.z;
+
+ }
+
+ return this;
+
+ },
+
+ copyVector4sArray: function ( vectors ) {
+
+ const array = this.array;
+ let offset = 0;
+
+ for ( let i = 0, l = vectors.length; i < l; i ++ ) {
+
+ let vector = vectors[ i ];
+
+ if ( vector === undefined ) {
+
+ console.warn( 'THREE.BufferAttribute.copyVector4sArray(): vector is undefined', i );
+ vector = new Vector4();
+
+ }
+
+ array[ offset ++ ] = vector.x;
+ array[ offset ++ ] = vector.y;
+ array[ offset ++ ] = vector.z;
+ array[ offset ++ ] = vector.w;
+
+ }
+
+ return this;
+
+ },
+
+ applyMatrix3: function ( m ) {
+
+ if ( this.itemSize === 2 ) {
+
+ for ( let i = 0, l = this.count; i < l; i ++ ) {
+
+ _vector2$1.fromBufferAttribute( this, i );
+ _vector2$1.applyMatrix3( m );
+
+ this.setXY( i, _vector2$1.x, _vector2$1.y );
+
+ }
+
+ } else if ( this.itemSize === 3 ) {
+
+ for ( let i = 0, l = this.count; i < l; i ++ ) {
+
+ _vector$3.fromBufferAttribute( this, i );
+ _vector$3.applyMatrix3( m );
+
+ this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+
+ }
+
+ }
+
+ return this;
+
+ },
+
+ applyMatrix4: function ( m ) {
+
+ for ( let i = 0, l = this.count; i < l; i ++ ) {
+
+ _vector$3.x = this.getX( i );
+ _vector$3.y = this.getY( i );
+ _vector$3.z = this.getZ( i );
+
+ _vector$3.applyMatrix4( m );
+
+ this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+
+ }
+
+ return this;
+
+ },
+
+ applyNormalMatrix: function ( m ) {
+
+ for ( let i = 0, l = this.count; i < l; i ++ ) {
+
+ _vector$3.x = this.getX( i );
+ _vector$3.y = this.getY( i );
+ _vector$3.z = this.getZ( i );
+
+ _vector$3.applyNormalMatrix( m );
+
+ this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+
+ }
+
+ return this;
+
+ },
+
+ transformDirection: function ( m ) {
+
+ for ( let i = 0, l = this.count; i < l; i ++ ) {
+
+ _vector$3.x = this.getX( i );
+ _vector$3.y = this.getY( i );
+ _vector$3.z = this.getZ( i );
+
+ _vector$3.transformDirection( m );
+
+ this.setXYZ( i, _vector$3.x, _vector$3.y, _vector$3.z );
+
+ }
+
+ return this;
+
+ },
+
+ set: function ( value, offset = 0 ) {
+
+ this.array.set( value, offset );
+
+ return this;
+
+ },
+
+ getX: function ( index ) {
+
+ return this.array[ index * this.itemSize ];
+
+ },
+
+ setX: function ( index, x ) {
+
+ this.array[ index * this.itemSize ] = x;
+
+ return this;
+
+ },
+
+ getY: function ( index ) {
+
+ return this.array[ index * this.itemSize + 1 ];
+
+ },
+
+ setY: function ( index, y ) {
+
+ this.array[ index * this.itemSize + 1 ] = y;
+
+ return this;
+
+ },
+
+ getZ: function ( index ) {
+
+ return this.array[ index * this.itemSize + 2 ];
+
+ },
+
+ setZ: function ( index, z ) {
+
+ this.array[ index * this.itemSize + 2 ] = z;
+
+ return this;
+
+ },
+
+ getW: function ( index ) {
+
+ return this.array[ index * this.itemSize + 3 ];
+
+ },
+
+ setW: function ( index, w ) {
+
+ this.array[ index * this.itemSize + 3 ] = w;
+
+ return this;
+
+ },
+
+ setXY: function ( index, x, y ) {
+
+ index *= this.itemSize;
+
+ this.array[ index + 0 ] = x;
+ this.array[ index + 1 ] = y;
+
+ return this;
+
+ },
+
+ setXYZ: function ( index, x, y, z ) {
+
+ index *= this.itemSize;
+
+ this.array[ index + 0 ] = x;
+ this.array[ index + 1 ] = y;
+ this.array[ index + 2 ] = z;
+
+ return this;
+
+ },
+
+ setXYZW: function ( index, x, y, z, w ) {
+
+ index *= this.itemSize;
+
+ this.array[ index + 0 ] = x;
+ this.array[ index + 1 ] = y;
+ this.array[ index + 2 ] = z;
+ this.array[ index + 3 ] = w;
+
+ return this;
+
+ },
+
+ onUpload: function ( callback ) {
+
+ this.onUploadCallback = callback;
+
+ return this;
+
+ },
+
+ clone: function () {
+
+ return new this.constructor( this.array, this.itemSize ).copy( this );
+
+ },
+
+ toJSON: function () {
+
+ return {
+ itemSize: this.itemSize,
+ type: this.array.constructor.name,
+ array: Array.prototype.slice.call( this.array ),
+ normalized: this.normalized
+ };
+
+ }
+
+} );
+
+//
+
+function Int8BufferAttribute( array, itemSize, normalized ) {
+
+ BufferAttribute.call( this, new Int8Array( array ), itemSize, normalized );
+
+}
+
+Int8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Int8BufferAttribute.prototype.constructor = Int8BufferAttribute;
+
+
+function Uint8BufferAttribute( array, itemSize, normalized ) {
+
+ BufferAttribute.call( this, new Uint8Array( array ), itemSize, normalized );
+
+}
+
+Uint8BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Uint8BufferAttribute.prototype.constructor = Uint8BufferAttribute;
+
+
+function Uint8ClampedBufferAttribute( array, itemSize, normalized ) {
+
+ BufferAttribute.call( this, new Uint8ClampedArray( array ), itemSize, normalized );
+
+}
+
+Uint8ClampedBufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Uint8ClampedBufferAttribute.prototype.constructor = Uint8ClampedBufferAttribute;
+
+
+function Int16BufferAttribute( array, itemSize, normalized ) {
+
+ BufferAttribute.call( this, new Int16Array( array ), itemSize, normalized );
+
+}
+
+Int16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Int16BufferAttribute.prototype.constructor = Int16BufferAttribute;
+
+
+function Uint16BufferAttribute( array, itemSize, normalized ) {
+
+ BufferAttribute.call( this, new Uint16Array( array ), itemSize, normalized );
+
+}
+
+Uint16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Uint16BufferAttribute.prototype.constructor = Uint16BufferAttribute;
+
+
+function Int32BufferAttribute( array, itemSize, normalized ) {
+
+ BufferAttribute.call( this, new Int32Array( array ), itemSize, normalized );
+
+}
+
+Int32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Int32BufferAttribute.prototype.constructor = Int32BufferAttribute;
+
+
+function Uint32BufferAttribute( array, itemSize, normalized ) {
+
+ BufferAttribute.call( this, new Uint32Array( array ), itemSize, normalized );
+
+}
+
+Uint32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Uint32BufferAttribute.prototype.constructor = Uint32BufferAttribute;
+
+function Float16BufferAttribute( array, itemSize, normalized ) {
+
+ BufferAttribute.call( this, new Uint16Array( array ), itemSize, normalized );
+
+}
+
+Float16BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Float16BufferAttribute.prototype.constructor = Float16BufferAttribute;
+Float16BufferAttribute.prototype.isFloat16BufferAttribute = true;
+
+function Float32BufferAttribute( array, itemSize, normalized ) {
+
+ BufferAttribute.call( this, new Float32Array( array ), itemSize, normalized );
+
+}
+
+Float32BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Float32BufferAttribute.prototype.constructor = Float32BufferAttribute;
+
+
+function Float64BufferAttribute( array, itemSize, normalized ) {
+
+ BufferAttribute.call( this, new Float64Array( array ), itemSize, normalized );
+
+}
+
+Float64BufferAttribute.prototype = Object.create( BufferAttribute.prototype );
+Float64BufferAttribute.prototype.constructor = Float64BufferAttribute;
+
+function arrayMax( array ) {
+
+ if ( array.length === 0 ) return - Infinity;
+
+ let max = array[ 0 ];
+
+ for ( let i = 1, l = array.length; i < l; ++ i ) {
+
+ if ( array[ i ] > max ) max = array[ i ];
+
+ }
+
+ return max;
+
+}
+
+const TYPED_ARRAYS = {
+ Int8Array: Int8Array,
+ Uint8Array: Uint8Array,
+ // Workaround for IE11 pre KB2929437. See #11440
+ Uint8ClampedArray: typeof Uint8ClampedArray !== 'undefined' ? Uint8ClampedArray : Uint8Array,
+ Int16Array: Int16Array,
+ Uint16Array: Uint16Array,
+ Int32Array: Int32Array,
+ Uint32Array: Uint32Array,
+ Float32Array: Float32Array,
+ Float64Array: Float64Array
+};
+
+function getTypedArray( type, buffer ) {
+
+ return new TYPED_ARRAYS[ type ]( buffer );
+
+}
+
+let _id = 0;
+
+const _m1$2 = new Matrix4();
+const _obj = new Object3D();
+const _offset = new Vector3();
+const _box$2 = new Box3();
+const _boxMorphTargets = new Box3();
+const _vector$4 = new Vector3();
+
+function BufferGeometry() {
+
+ Object.defineProperty( this, 'id', { value: _id ++ } );
+
+ this.uuid = MathUtils.generateUUID();
+
+ this.name = '';
+ this.type = 'BufferGeometry';
+
+ this.index = null;
+ this.attributes = {};
+
+ this.morphAttributes = {};
+ this.morphTargetsRelative = false;
+
+ this.groups = [];
+
+ this.boundingBox = null;
+ this.boundingSphere = null;
+
+ this.drawRange = { start: 0, count: Infinity };
+
+ this.userData = {};
+
+}
+
+BufferGeometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+ constructor: BufferGeometry,
+
+ isBufferGeometry: true,
+
+ getIndex: function () {
+
+ return this.index;
+
+ },
+
+ setIndex: function ( index ) {
+
+ if ( Array.isArray( index ) ) {
+
+ this.index = new ( arrayMax( index ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( index, 1 );
+
+ } else {
+
+ this.index = index;
+
+ }
+
+ return this;
+
+ },
+
+ getAttribute: function ( name ) {
+
+ return this.attributes[ name ];
+
+ },
+
+ setAttribute: function ( name, attribute ) {
+
+ this.attributes[ name ] = attribute;
+
+ return this;
+
+ },
+
+ deleteAttribute: function ( name ) {
+
+ delete this.attributes[ name ];
+
+ return this;
+
+ },
+
+ hasAttribute: function ( name ) {
+
+ return this.attributes[ name ] !== undefined;
+
+ },
+
+ addGroup: function ( start, count, materialIndex = 0 ) {
+
+ this.groups.push( {
+
+ start: start,
+ count: count,
+ materialIndex: materialIndex
+
+ } );
+
+ },
+
+ clearGroups: function () {
+
+ this.groups = [];
+
+ },
+
+ setDrawRange: function ( start, count ) {
+
+ this.drawRange.start = start;
+ this.drawRange.count = count;
+
+ },
+
+ applyMatrix4: function ( matrix ) {
+
+ const position = this.attributes.position;
+
+ if ( position !== undefined ) {
+
+ position.applyMatrix4( matrix );
+
+ position.needsUpdate = true;
+
+ }
+
+ const normal = this.attributes.normal;
+
+ if ( normal !== undefined ) {
+
+ const normalMatrix = new Matrix3().getNormalMatrix( matrix );
+
+ normal.applyNormalMatrix( normalMatrix );
+
+ normal.needsUpdate = true;
+
+ }
+
+ const tangent = this.attributes.tangent;
+
+ if ( tangent !== undefined ) {
+
+ tangent.transformDirection( matrix );
+
+ tangent.needsUpdate = true;
+
+ }
+
+ if ( this.boundingBox !== null ) {
+
+ this.computeBoundingBox();
+
+ }
+
+ if ( this.boundingSphere !== null ) {
+
+ this.computeBoundingSphere();
+
+ }
+
+ return this;
+
+ },
+
+ rotateX: function ( angle ) {
+
+ // rotate geometry around world x-axis
+
+ _m1$2.makeRotationX( angle );
+
+ this.applyMatrix4( _m1$2 );
+
+ return this;
+
+ },
+
+ rotateY: function ( angle ) {
+
+ // rotate geometry around world y-axis
+
+ _m1$2.makeRotationY( angle );
+
+ this.applyMatrix4( _m1$2 );
+
+ return this;
+
+ },
+
+ rotateZ: function ( angle ) {
+
+ // rotate geometry around world z-axis
+
+ _m1$2.makeRotationZ( angle );
+
+ this.applyMatrix4( _m1$2 );
+
+ return this;
+
+ },
+
+ translate: function ( x, y, z ) {
+
+ // translate geometry
+
+ _m1$2.makeTranslation( x, y, z );
+
+ this.applyMatrix4( _m1$2 );
+
+ return this;
+
+ },
+
+ scale: function ( x, y, z ) {
+
+ // scale geometry
+
+ _m1$2.makeScale( x, y, z );
+
+ this.applyMatrix4( _m1$2 );
+
+ return this;
+
+ },
+
+ lookAt: function ( vector ) {
+
+ _obj.lookAt( vector );
+
+ _obj.updateMatrix();
+
+ this.applyMatrix4( _obj.matrix );
+
+ return this;
+
+ },
+
+ center: function () {
+
+ this.computeBoundingBox();
+
+ this.boundingBox.getCenter( _offset ).negate();
+
+ this.translate( _offset.x, _offset.y, _offset.z );
+
+ return this;
+
+ },
+
+ setFromPoints: function ( points ) {
+
+ const position = [];
+
+ for ( let i = 0, l = points.length; i < l; i ++ ) {
+
+ const point = points[ i ];
+ position.push( point.x, point.y, point.z || 0 );
+
+ }
+
+ this.setAttribute( 'position', new Float32BufferAttribute( position, 3 ) );
+
+ return this;
+
+ },
+
+ computeBoundingBox: function () {
+
+ if ( this.boundingBox === null ) {
+
+ this.boundingBox = new Box3();
+
+ }
+
+ const position = this.attributes.position;
+ const morphAttributesPosition = this.morphAttributes.position;
+
+ if ( position && position.isGLBufferAttribute ) {
+
+ console.error( 'THREE.BufferGeometry.computeBoundingBox(): GLBufferAttribute requires a manual bounding box. Alternatively set "mesh.frustumCulled" to "false".', this );
+
+ this.boundingBox.set(
+ new Vector3( - Infinity, - Infinity, - Infinity ),
+ new Vector3( + Infinity, + Infinity, + Infinity )
+ );
+
+ return;
+
+ }
+
+ if ( position !== undefined ) {
+
+ this.boundingBox.setFromBufferAttribute( position );
+
+ // process morph attributes if present
+
+ if ( morphAttributesPosition ) {
+
+ for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
+
+ const morphAttribute = morphAttributesPosition[ i ];
+ _box$2.setFromBufferAttribute( morphAttribute );
+
+ if ( this.morphTargetsRelative ) {
+
+ _vector$4.addVectors( this.boundingBox.min, _box$2.min );
+ this.boundingBox.expandByPoint( _vector$4 );
+
+ _vector$4.addVectors( this.boundingBox.max, _box$2.max );
+ this.boundingBox.expandByPoint( _vector$4 );
+
+ } else {
+
+ this.boundingBox.expandByPoint( _box$2.min );
+ this.boundingBox.expandByPoint( _box$2.max );
+
+ }
+
+ }
+
+ }
+
+ } else {
+
+ this.boundingBox.makeEmpty();
+
+ }
+
+ if ( isNaN( this.boundingBox.min.x ) || isNaN( this.boundingBox.min.y ) || isNaN( this.boundingBox.min.z ) ) {
+
+ console.error( 'THREE.BufferGeometry.computeBoundingBox(): Computed min/max have NaN values. The "position" attribute is likely to have NaN values.', this );
+
+ }
+
+ },
+
+ computeBoundingSphere: function () {
+
+ if ( this.boundingSphere === null ) {
+
+ this.boundingSphere = new Sphere();
+
+ }
+
+ const position = this.attributes.position;
+ const morphAttributesPosition = this.morphAttributes.position;
+
+ if ( position && position.isGLBufferAttribute ) {
+
+ console.error( 'THREE.BufferGeometry.computeBoundingSphere(): GLBufferAttribute requires a manual bounding sphere. Alternatively set "mesh.frustumCulled" to "false".', this );
+
+ this.boundingSphere.set( new Vector3(), Infinity );
+
+ return;
+
+ }
+
+ if ( position ) {
+
+ // first, find the center of the bounding sphere
+
+ const center = this.boundingSphere.center;
+
+ _box$2.setFromBufferAttribute( position );
+
+ // process morph attributes if present
+
+ if ( morphAttributesPosition ) {
+
+ for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
+
+ const morphAttribute = morphAttributesPosition[ i ];
+ _boxMorphTargets.setFromBufferAttribute( morphAttribute );
+
+ if ( this.morphTargetsRelative ) {
+
+ _vector$4.addVectors( _box$2.min, _boxMorphTargets.min );
+ _box$2.expandByPoint( _vector$4 );
+
+ _vector$4.addVectors( _box$2.max, _boxMorphTargets.max );
+ _box$2.expandByPoint( _vector$4 );
+
+ } else {
+
+ _box$2.expandByPoint( _boxMorphTargets.min );
+ _box$2.expandByPoint( _boxMorphTargets.max );
+
+ }
+
+ }
+
+ }
+
+ _box$2.getCenter( center );
+
+ // second, try to find a boundingSphere with a radius smaller than the
+ // boundingSphere of the boundingBox: sqrt(3) smaller in the best case
+
+ let maxRadiusSq = 0;
+
+ for ( let i = 0, il = position.count; i < il; i ++ ) {
+
+ _vector$4.fromBufferAttribute( position, i );
+
+ maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) );
+
+ }
+
+ // process morph attributes if present
+
+ if ( morphAttributesPosition ) {
+
+ for ( let i = 0, il = morphAttributesPosition.length; i < il; i ++ ) {
+
+ const morphAttribute = morphAttributesPosition[ i ];
+ const morphTargetsRelative = this.morphTargetsRelative;
+
+ for ( let j = 0, jl = morphAttribute.count; j < jl; j ++ ) {
+
+ _vector$4.fromBufferAttribute( morphAttribute, j );
+
+ if ( morphTargetsRelative ) {
+
+ _offset.fromBufferAttribute( position, j );
+ _vector$4.add( _offset );
+
+ }
+
+ maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector$4 ) );
+
+ }
+
+ }
+
+ }
+
+ this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
+
+ if ( isNaN( this.boundingSphere.radius ) ) {
+
+ console.error( 'THREE.BufferGeometry.computeBoundingSphere(): Computed radius is NaN. The "position" attribute is likely to have NaN values.', this );
+
+ }
+
+ }
+
+ },
+
+ computeFaceNormals: function () {
+
+ // backwards compatibility
+
+ },
+
+ computeTangents: function () {
+
+ const index = this.index;
+ const attributes = this.attributes;
+
+ // based on http://www.terathon.com/code/tangent.html
+ // (per vertex tangents)
+
+ if ( index === null ||
+ attributes.position === undefined ||
+ attributes.normal === undefined ||
+ attributes.uv === undefined ) {
+
+ console.error( 'THREE.BufferGeometry: .computeTangents() failed. Missing required attributes (index, position, normal or uv)' );
+ return;
+
+ }
+
+ const indices = index.array;
+ const positions = attributes.position.array;
+ const normals = attributes.normal.array;
+ const uvs = attributes.uv.array;
+
+ const nVertices = positions.length / 3;
+
+ if ( attributes.tangent === undefined ) {
+
+ this.setAttribute( 'tangent', new BufferAttribute( new Float32Array( 4 * nVertices ), 4 ) );
+
+ }
+
+ const tangents = attributes.tangent.array;
+
+ const tan1 = [], tan2 = [];
+
+ for ( let i = 0; i < nVertices; i ++ ) {
+
+ tan1[ i ] = new Vector3();
+ tan2[ i ] = new Vector3();
+
+ }
+
+ const vA = new Vector3(),
+ vB = new Vector3(),
+ vC = new Vector3(),
+
+ uvA = new Vector2(),
+ uvB = new Vector2(),
+ uvC = new Vector2(),
+
+ sdir = new Vector3(),
+ tdir = new Vector3();
+
+ function handleTriangle( a, b, c ) {
+
+ vA.fromArray( positions, a * 3 );
+ vB.fromArray( positions, b * 3 );
+ vC.fromArray( positions, c * 3 );
+
+ uvA.fromArray( uvs, a * 2 );
+ uvB.fromArray( uvs, b * 2 );
+ uvC.fromArray( uvs, c * 2 );
+
+ vB.sub( vA );
+ vC.sub( vA );
+
+ uvB.sub( uvA );
+ uvC.sub( uvA );
+
+ const r = 1.0 / ( uvB.x * uvC.y - uvC.x * uvB.y );
+
+ // silently ignore degenerate uv triangles having coincident or colinear vertices
+
+ if ( ! isFinite( r ) ) return;
+
+ sdir.copy( vB ).multiplyScalar( uvC.y ).addScaledVector( vC, - uvB.y ).multiplyScalar( r );
+ tdir.copy( vC ).multiplyScalar( uvB.x ).addScaledVector( vB, - uvC.x ).multiplyScalar( r );
+
+ tan1[ a ].add( sdir );
+ tan1[ b ].add( sdir );
+ tan1[ c ].add( sdir );
+
+ tan2[ a ].add( tdir );
+ tan2[ b ].add( tdir );
+ tan2[ c ].add( tdir );
+
+ }
+
+ let groups = this.groups;
+
+ if ( groups.length === 0 ) {
+
+ groups = [ {
+ start: 0,
+ count: indices.length
+ } ];
+
+ }
+
+ for ( let i = 0, il = groups.length; i < il; ++ i ) {
+
+ const group = groups[ i ];
+
+ const start = group.start;
+ const count = group.count;
+
+ for ( let j = start, jl = start + count; j < jl; j += 3 ) {
+
+ handleTriangle(
+ indices[ j + 0 ],
+ indices[ j + 1 ],
+ indices[ j + 2 ]
+ );
+
+ }
+
+ }
+
+ const tmp = new Vector3(), tmp2 = new Vector3();
+ const n = new Vector3(), n2 = new Vector3();
+
+ function handleVertex( v ) {
+
+ n.fromArray( normals, v * 3 );
+ n2.copy( n );
+
+ const t = tan1[ v ];
+
+ // Gram-Schmidt orthogonalize
+
+ tmp.copy( t );
+ tmp.sub( n.multiplyScalar( n.dot( t ) ) ).normalize();
+
+ // Calculate handedness
+
+ tmp2.crossVectors( n2, t );
+ const test = tmp2.dot( tan2[ v ] );
+ const w = ( test < 0.0 ) ? - 1.0 : 1.0;
+
+ tangents[ v * 4 ] = tmp.x;
+ tangents[ v * 4 + 1 ] = tmp.y;
+ tangents[ v * 4 + 2 ] = tmp.z;
+ tangents[ v * 4 + 3 ] = w;
+
+ }
+
+ for ( let i = 0, il = groups.length; i < il; ++ i ) {
+
+ const group = groups[ i ];
+
+ const start = group.start;
+ const count = group.count;
+
+ for ( let j = start, jl = start + count; j < jl; j += 3 ) {
+
+ handleVertex( indices[ j + 0 ] );
+ handleVertex( indices[ j + 1 ] );
+ handleVertex( indices[ j + 2 ] );
+
+ }
+
+ }
+
+ },
+
+ computeVertexNormals: function () {
+
+ const index = this.index;
+ const positionAttribute = this.getAttribute( 'position' );
+
+ if ( positionAttribute !== undefined ) {
+
+ let normalAttribute = this.getAttribute( 'normal' );
+
+ if ( normalAttribute === undefined ) {
+
+ normalAttribute = new BufferAttribute( new Float32Array( positionAttribute.count * 3 ), 3 );
+ this.setAttribute( 'normal', normalAttribute );
+
+ } else {
+
+ // reset existing normals to zero
+
+ for ( let i = 0, il = normalAttribute.count; i < il; i ++ ) {
+
+ normalAttribute.setXYZ( i, 0, 0, 0 );
+
+ }
+
+ }
+
+ const pA = new Vector3(), pB = new Vector3(), pC = new Vector3();
+ const nA = new Vector3(), nB = new Vector3(), nC = new Vector3();
+ const cb = new Vector3(), ab = new Vector3();
+
+ // indexed elements
+
+ if ( index ) {
+
+ for ( let i = 0, il = index.count; i < il; i += 3 ) {
+
+ const vA = index.getX( i + 0 );
+ const vB = index.getX( i + 1 );
+ const vC = index.getX( i + 2 );
+
+ pA.fromBufferAttribute( positionAttribute, vA );
+ pB.fromBufferAttribute( positionAttribute, vB );
+ pC.fromBufferAttribute( positionAttribute, vC );
+
+ cb.subVectors( pC, pB );
+ ab.subVectors( pA, pB );
+ cb.cross( ab );
+
+ nA.fromBufferAttribute( normalAttribute, vA );
+ nB.fromBufferAttribute( normalAttribute, vB );
+ nC.fromBufferAttribute( normalAttribute, vC );
+
+ nA.add( cb );
+ nB.add( cb );
+ nC.add( cb );
+
+ normalAttribute.setXYZ( vA, nA.x, nA.y, nA.z );
+ normalAttribute.setXYZ( vB, nB.x, nB.y, nB.z );
+ normalAttribute.setXYZ( vC, nC.x, nC.y, nC.z );
+
+ }
+
+ } else {
+
+ // non-indexed elements (unconnected triangle soup)
+
+ for ( let i = 0, il = positionAttribute.count; i < il; i += 3 ) {
+
+ pA.fromBufferAttribute( positionAttribute, i + 0 );
+ pB.fromBufferAttribute( positionAttribute, i + 1 );
+ pC.fromBufferAttribute( positionAttribute, i + 2 );
+
+ cb.subVectors( pC, pB );
+ ab.subVectors( pA, pB );
+ cb.cross( ab );
+
+ normalAttribute.setXYZ( i + 0, cb.x, cb.y, cb.z );
+ normalAttribute.setXYZ( i + 1, cb.x, cb.y, cb.z );
+ normalAttribute.setXYZ( i + 2, cb.x, cb.y, cb.z );
+
+ }
+
+ }
+
+ this.normalizeNormals();
+
+ normalAttribute.needsUpdate = true;
+
+ }
+
+ },
+
+ merge: function ( geometry, offset ) {
+
+ if ( ! ( geometry && geometry.isBufferGeometry ) ) {
+
+ console.error( 'THREE.BufferGeometry.merge(): geometry not an instance of THREE.BufferGeometry.', geometry );
+ return;
+
+ }
+
+ if ( offset === undefined ) {
+
+ offset = 0;
+
+ console.warn(
+ 'THREE.BufferGeometry.merge(): Overwriting original geometry, starting at offset=0. '
+ + 'Use BufferGeometryUtils.mergeBufferGeometries() for lossless merge.'
+ );
+
+ }
+
+ const attributes = this.attributes;
+
+ for ( const key in attributes ) {
+
+ if ( geometry.attributes[ key ] === undefined ) continue;
+
+ const attribute1 = attributes[ key ];
+ const attributeArray1 = attribute1.array;
+
+ const attribute2 = geometry.attributes[ key ];
+ const attributeArray2 = attribute2.array;
+
+ const attributeOffset = attribute2.itemSize * offset;
+ const length = Math.min( attributeArray2.length, attributeArray1.length - attributeOffset );
+
+ for ( let i = 0, j = attributeOffset; i < length; i ++, j ++ ) {
+
+ attributeArray1[ j ] = attributeArray2[ i ];
+
+ }
+
+ }
+
+ return this;
+
+ },
+
+ normalizeNormals: function () {
+
+ const normals = this.attributes.normal;
+
+ for ( let i = 0, il = normals.count; i < il; i ++ ) {
+
+ _vector$4.fromBufferAttribute( normals, i );
+
+ _vector$4.normalize();
+
+ normals.setXYZ( i, _vector$4.x, _vector$4.y, _vector$4.z );
+
+ }
+
+ },
+
+ toNonIndexed: function () {
+
+ function convertBufferAttribute( attribute, indices ) {
+
+ const array = attribute.array;
+ const itemSize = attribute.itemSize;
+ const normalized = attribute.normalized;
+
+ const array2 = new array.constructor( indices.length * itemSize );
+
+ let index = 0, index2 = 0;
+
+ for ( let i = 0, l = indices.length; i < l; i ++ ) {
+
+ index = indices[ i ] * itemSize;
+
+ for ( let j = 0; j < itemSize; j ++ ) {
+
+ array2[ index2 ++ ] = array[ index ++ ];
+
+ }
+
+ }
+
+ return new BufferAttribute( array2, itemSize, normalized );
+
+ }
+
+ //
+
+ if ( this.index === null ) {
+
+ console.warn( 'THREE.BufferGeometry.toNonIndexed(): BufferGeometry is already non-indexed.' );
+ return this;
+
+ }
+
+ const geometry2 = new BufferGeometry();
+
+ const indices = this.index.array;
+ const attributes = this.attributes;
+
+ // attributes
+
+ for ( const name in attributes ) {
+
+ const attribute = attributes[ name ];
+
+ const newAttribute = convertBufferAttribute( attribute, indices );
+
+ geometry2.setAttribute( name, newAttribute );
+
+ }
+
+ // morph attributes
+
+ const morphAttributes = this.morphAttributes;
+
+ for ( const name in morphAttributes ) {
+
+ const morphArray = [];
+ const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes
+
+ for ( let i = 0, il = morphAttribute.length; i < il; i ++ ) {
+
+ const attribute = morphAttribute[ i ];
+
+ const newAttribute = convertBufferAttribute( attribute, indices );
+
+ morphArray.push( newAttribute );
+
+ }
+
+ geometry2.morphAttributes[ name ] = morphArray;
+
+ }
+
+ geometry2.morphTargetsRelative = this.morphTargetsRelative;
+
+ // groups
+
+ const groups = this.groups;
+
+ for ( let i = 0, l = groups.length; i < l; i ++ ) {
+
+ const group = groups[ i ];
+ geometry2.addGroup( group.start, group.count, group.materialIndex );
+
+ }
+
+ return geometry2;
+
+ },
+
+ toJSON: function () {
+
+ const data = {
+ metadata: {
+ version: 4.5,
+ type: 'BufferGeometry',
+ generator: 'BufferGeometry.toJSON'
+ }
+ };
+
+ // standard BufferGeometry serialization
+
+ data.uuid = this.uuid;
+ data.type = this.type;
+ if ( this.name !== '' ) data.name = this.name;
+ if ( Object.keys( this.userData ).length > 0 ) data.userData = this.userData;
+
+ if ( this.parameters !== undefined ) {
+
+ const parameters = this.parameters;
+
+ for ( const key in parameters ) {
+
+ if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ];
+
+ }
+
+ return data;
+
+ }
+
+ data.data = { attributes: {} };
+
+ const index = this.index;
+
+ if ( index !== null ) {
+
+ data.data.index = {
+ type: index.array.constructor.name,
+ array: Array.prototype.slice.call( index.array )
+ };
+
+ }
+
+ const attributes = this.attributes;
+
+ for ( const key in attributes ) {
+
+ const attribute = attributes[ key ];
+
+ const attributeData = attribute.toJSON( data.data );
+
+ if ( attribute.name !== '' ) attributeData.name = attribute.name;
+
+ data.data.attributes[ key ] = attributeData;
+
+ }
+
+ const morphAttributes = {};
+ let hasMorphAttributes = false;
+
+ for ( const key in this.morphAttributes ) {
+
+ const attributeArray = this.morphAttributes[ key ];
+
+ const array = [];
+
+ for ( let i = 0, il = attributeArray.length; i < il; i ++ ) {
+
+ const attribute = attributeArray[ i ];
+
+ const attributeData = attribute.toJSON( data.data );
+
+ if ( attribute.name !== '' ) attributeData.name = attribute.name;
+
+ array.push( attributeData );
+
+ }
+
+ if ( array.length > 0 ) {
+
+ morphAttributes[ key ] = array;
+
+ hasMorphAttributes = true;
+
+ }
+
+ }
+
+ if ( hasMorphAttributes ) {
+
+ data.data.morphAttributes = morphAttributes;
+ data.data.morphTargetsRelative = this.morphTargetsRelative;
+
+ }
+
+ const groups = this.groups;
+
+ if ( groups.length > 0 ) {
+
+ data.data.groups = JSON.parse( JSON.stringify( groups ) );
+
+ }
+
+ const boundingSphere = this.boundingSphere;
+
+ if ( boundingSphere !== null ) {
+
+ data.data.boundingSphere = {
+ center: boundingSphere.center.toArray(),
+ radius: boundingSphere.radius
+ };
+
+ }
+
+ return data;
+
+ },
+
+ clone: function () {
+
+ /*
+ // Handle primitives
+
+ const parameters = this.parameters;
+
+ if ( parameters !== undefined ) {
+
+ const values = [];
+
+ for ( const key in parameters ) {
+
+ values.push( parameters[ key ] );
+
+ }
+
+ const geometry = Object.create( this.constructor.prototype );
+ this.constructor.apply( geometry, values );
+ return geometry;
+
+ }
+
+ return new this.constructor().copy( this );
+ */
+
+ return new BufferGeometry().copy( this );
+
+ },
+
+ copy: function ( source ) {
+
+ // reset
+
+ this.index = null;
+ this.attributes = {};
+ this.morphAttributes = {};
+ this.groups = [];
+ this.boundingBox = null;
+ this.boundingSphere = null;
+
+ // used for storing cloned, shared data
+
+ const data = {};
+
+ // name
+
+ this.name = source.name;
+
+ // index
+
+ const index = source.index;
+
+ if ( index !== null ) {
+
+ this.setIndex( index.clone( data ) );
+
+ }
+
+ // attributes
+
+ const attributes = source.attributes;
+
+ for ( const name in attributes ) {
+
+ const attribute = attributes[ name ];
+ this.setAttribute( name, attribute.clone( data ) );
+
+ }
+
+ // morph attributes
+
+ const morphAttributes = source.morphAttributes;
+
+ for ( const name in morphAttributes ) {
+
+ const array = [];
+ const morphAttribute = morphAttributes[ name ]; // morphAttribute: array of Float32BufferAttributes
+
+ for ( let i = 0, l = morphAttribute.length; i < l; i ++ ) {
+
+ array.push( morphAttribute[ i ].clone( data ) );
+
+ }
+
+ this.morphAttributes[ name ] = array;
+
+ }
+
+ this.morphTargetsRelative = source.morphTargetsRelative;
+
+ // groups
+
+ const groups = source.groups;
+
+ for ( let i = 0, l = groups.length; i < l; i ++ ) {
+
+ const group = groups[ i ];
+ this.addGroup( group.start, group.count, group.materialIndex );
+
+ }
+
+ // bounding box
+
+ const boundingBox = source.boundingBox;
+
+ if ( boundingBox !== null ) {
+
+ this.boundingBox = boundingBox.clone();
+
+ }
+
+ // bounding sphere
+
+ const boundingSphere = source.boundingSphere;
+
+ if ( boundingSphere !== null ) {
+
+ this.boundingSphere = boundingSphere.clone();
+
+ }
+
+ // draw range
+
+ this.drawRange.start = source.drawRange.start;
+ this.drawRange.count = source.drawRange.count;
+
+ // user data
+
+ this.userData = source.userData;
+
+ return this;
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+} );
+
+const _inverseMatrix = new Matrix4();
+const _ray = new Ray();
+const _sphere = new Sphere();
+
+const _vA = new Vector3();
+const _vB = new Vector3();
+const _vC = new Vector3();
+
+const _tempA = new Vector3();
+const _tempB = new Vector3();
+const _tempC = new Vector3();
+
+const _morphA = new Vector3();
+const _morphB = new Vector3();
+const _morphC = new Vector3();
+
+const _uvA = new Vector2();
+const _uvB = new Vector2();
+const _uvC = new Vector2();
+
+const _intersectionPoint = new Vector3();
+const _intersectionPointWorld = new Vector3();
+
+function Mesh( geometry = new BufferGeometry(), material = new MeshBasicMaterial() ) {
+
+ Object3D.call( this );
+
+ this.type = 'Mesh';
+
+ this.geometry = geometry;
+ this.material = material;
+
+ this.updateMorphTargets();
+
+}
+
+Mesh.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+ constructor: Mesh,
+
+ isMesh: true,
+
+ copy: function ( source ) {
+
+ Object3D.prototype.copy.call( this, source );
+
+ if ( source.morphTargetInfluences !== undefined ) {
+
+ this.morphTargetInfluences = source.morphTargetInfluences.slice();
+
+ }
+
+ if ( source.morphTargetDictionary !== undefined ) {
+
+ this.morphTargetDictionary = Object.assign( {}, source.morphTargetDictionary );
+
+ }
+
+ this.material = source.material;
+ this.geometry = source.geometry;
+
+ return this;
+
+ },
+
+ updateMorphTargets: function () {
+
+ const geometry = this.geometry;
+
+ if ( geometry.isBufferGeometry ) {
+
+ const morphAttributes = geometry.morphAttributes;
+ const keys = Object.keys( morphAttributes );
+
+ if ( keys.length > 0 ) {
+
+ const morphAttribute = morphAttributes[ keys[ 0 ] ];
+
+ if ( morphAttribute !== undefined ) {
+
+ this.morphTargetInfluences = [];
+ this.morphTargetDictionary = {};
+
+ for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
+
+ const name = morphAttribute[ m ].name || String( m );
+
+ this.morphTargetInfluences.push( 0 );
+ this.morphTargetDictionary[ name ] = m;
+
+ }
+
+ }
+
+ }
+
+ } else {
+
+ const morphTargets = geometry.morphTargets;
+
+ if ( morphTargets !== undefined && morphTargets.length > 0 ) {
+
+ console.error( 'THREE.Mesh.updateMorphTargets() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+ }
+
+ }
+
+ },
+
+ raycast: function ( raycaster, intersects ) {
+
+ const geometry = this.geometry;
+ const material = this.material;
+ const matrixWorld = this.matrixWorld;
+
+ if ( material === undefined ) return;
+
+ // Checking boundingSphere distance to ray
+
+ if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+ _sphere.copy( geometry.boundingSphere );
+ _sphere.applyMatrix4( matrixWorld );
+
+ if ( raycaster.ray.intersectsSphere( _sphere ) === false ) return;
+
+ //
+
+ _inverseMatrix.copy( matrixWorld ).invert();
+ _ray.copy( raycaster.ray ).applyMatrix4( _inverseMatrix );
+
+ // Check boundingBox before continuing
+
+ if ( geometry.boundingBox !== null ) {
+
+ if ( _ray.intersectsBox( geometry.boundingBox ) === false ) return;
+
+ }
+
+ let intersection;
+
+ if ( geometry.isBufferGeometry ) {
+
+ const index = geometry.index;
+ const position = geometry.attributes.position;
+ const morphPosition = geometry.morphAttributes.position;
+ const morphTargetsRelative = geometry.morphTargetsRelative;
+ const uv = geometry.attributes.uv;
+ const uv2 = geometry.attributes.uv2;
+ const groups = geometry.groups;
+ const drawRange = geometry.drawRange;
+
+ if ( index !== null ) {
+
+ // indexed buffer geometry
+
+ if ( Array.isArray( material ) ) {
+
+ for ( let i = 0, il = groups.length; i < il; i ++ ) {
+
+ const group = groups[ i ];
+ const groupMaterial = material[ group.materialIndex ];
+
+ const start = Math.max( group.start, drawRange.start );
+ const end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
+
+ for ( let j = start, jl = end; j < jl; j += 3 ) {
+
+ const a = index.getX( j );
+ const b = index.getX( j + 1 );
+ const c = index.getX( j + 2 );
+
+ intersection = checkBufferGeometryIntersection( this, groupMaterial, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
+
+ if ( intersection ) {
+
+ intersection.faceIndex = Math.floor( j / 3 ); // triangle number in indexed buffer semantics
+ intersection.face.materialIndex = group.materialIndex;
+ intersects.push( intersection );
+
+ }
+
+ }
+
+ }
+
+ } else {
+
+ const start = Math.max( 0, drawRange.start );
+ const end = Math.min( index.count, ( drawRange.start + drawRange.count ) );
+
+ for ( let i = start, il = end; i < il; i += 3 ) {
+
+ const a = index.getX( i );
+ const b = index.getX( i + 1 );
+ const c = index.getX( i + 2 );
+
+ intersection = checkBufferGeometryIntersection( this, material, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
+
+ if ( intersection ) {
+
+ intersection.faceIndex = Math.floor( i / 3 ); // triangle number in indexed buffer semantics
+ intersects.push( intersection );
+
+ }
+
+ }
+
+ }
+
+ } else if ( position !== undefined ) {
+
+ // non-indexed buffer geometry
+
+ if ( Array.isArray( material ) ) {
+
+ for ( let i = 0, il = groups.length; i < il; i ++ ) {
+
+ const group = groups[ i ];
+ const groupMaterial = material[ group.materialIndex ];
+
+ const start = Math.max( group.start, drawRange.start );
+ const end = Math.min( ( group.start + group.count ), ( drawRange.start + drawRange.count ) );
+
+ for ( let j = start, jl = end; j < jl; j += 3 ) {
+
+ const a = j;
+ const b = j + 1;
+ const c = j + 2;
+
+ intersection = checkBufferGeometryIntersection( this, groupMaterial, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
+
+ if ( intersection ) {
+
+ intersection.faceIndex = Math.floor( j / 3 ); // triangle number in non-indexed buffer semantics
+ intersection.face.materialIndex = group.materialIndex;
+ intersects.push( intersection );
+
+ }
+
+ }
+
+ }
+
+ } else {
+
+ const start = Math.max( 0, drawRange.start );
+ const end = Math.min( position.count, ( drawRange.start + drawRange.count ) );
+
+ for ( let i = start, il = end; i < il; i += 3 ) {
+
+ const a = i;
+ const b = i + 1;
+ const c = i + 2;
+
+ intersection = checkBufferGeometryIntersection( this, material, raycaster, _ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c );
+
+ if ( intersection ) {
+
+ intersection.faceIndex = Math.floor( i / 3 ); // triangle number in non-indexed buffer semantics
+ intersects.push( intersection );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ } else if ( geometry.isGeometry ) {
+
+ console.error( 'THREE.Mesh.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+ }
+
+ }
+
+} );
+
+function checkIntersection( object, material, raycaster, ray, pA, pB, pC, point ) {
+
+ let intersect;
+
+ if ( material.side === BackSide ) {
+
+ intersect = ray.intersectTriangle( pC, pB, pA, true, point );
+
+ } else {
+
+ intersect = ray.intersectTriangle( pA, pB, pC, material.side !== DoubleSide, point );
+
+ }
+
+ if ( intersect === null ) return null;
+
+ _intersectionPointWorld.copy( point );
+ _intersectionPointWorld.applyMatrix4( object.matrixWorld );
+
+ const distance = raycaster.ray.origin.distanceTo( _intersectionPointWorld );
+
+ if ( distance < raycaster.near || distance > raycaster.far ) return null;
+
+ return {
+ distance: distance,
+ point: _intersectionPointWorld.clone(),
+ object: object
+ };
+
+}
+
+function checkBufferGeometryIntersection( object, material, raycaster, ray, position, morphPosition, morphTargetsRelative, uv, uv2, a, b, c ) {
+
+ _vA.fromBufferAttribute( position, a );
+ _vB.fromBufferAttribute( position, b );
+ _vC.fromBufferAttribute( position, c );
+
+ const morphInfluences = object.morphTargetInfluences;
+
+ if ( material.morphTargets && morphPosition && morphInfluences ) {
+
+ _morphA.set( 0, 0, 0 );
+ _morphB.set( 0, 0, 0 );
+ _morphC.set( 0, 0, 0 );
+
+ for ( let i = 0, il = morphPosition.length; i < il; i ++ ) {
+
+ const influence = morphInfluences[ i ];
+ const morphAttribute = morphPosition[ i ];
+
+ if ( influence === 0 ) continue;
+
+ _tempA.fromBufferAttribute( morphAttribute, a );
+ _tempB.fromBufferAttribute( morphAttribute, b );
+ _tempC.fromBufferAttribute( morphAttribute, c );
+
+ if ( morphTargetsRelative ) {
+
+ _morphA.addScaledVector( _tempA, influence );
+ _morphB.addScaledVector( _tempB, influence );
+ _morphC.addScaledVector( _tempC, influence );
+
+ } else {
+
+ _morphA.addScaledVector( _tempA.sub( _vA ), influence );
+ _morphB.addScaledVector( _tempB.sub( _vB ), influence );
+ _morphC.addScaledVector( _tempC.sub( _vC ), influence );
+
+ }
+
+ }
+
+ _vA.add( _morphA );
+ _vB.add( _morphB );
+ _vC.add( _morphC );
+
+ }
+
+ if ( object.isSkinnedMesh ) {
+
+ object.boneTransform( a, _vA );
+ object.boneTransform( b, _vB );
+ object.boneTransform( c, _vC );
+
+ }
+
+ const intersection = checkIntersection( object, material, raycaster, ray, _vA, _vB, _vC, _intersectionPoint );
+
+ if ( intersection ) {
+
+ if ( uv ) {
+
+ _uvA.fromBufferAttribute( uv, a );
+ _uvB.fromBufferAttribute( uv, b );
+ _uvC.fromBufferAttribute( uv, c );
+
+ intersection.uv = Triangle.getUV( _intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() );
+
+ }
+
+ if ( uv2 ) {
+
+ _uvA.fromBufferAttribute( uv2, a );
+ _uvB.fromBufferAttribute( uv2, b );
+ _uvC.fromBufferAttribute( uv2, c );
+
+ intersection.uv2 = Triangle.getUV( _intersectionPoint, _vA, _vB, _vC, _uvA, _uvB, _uvC, new Vector2() );
+
+ }
+
+ const face = new Face3( a, b, c );
+ Triangle.getNormal( _vA, _vB, _vC, face.normal );
+
+ intersection.face = face;
+
+ }
+
+ return intersection;
+
+}
+
+class BoxGeometry extends BufferGeometry {
+
+ constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) {
+
+ super();
+
+ this.type = 'BoxGeometry';
+
+ this.parameters = {
+ width: width,
+ height: height,
+ depth: depth,
+ widthSegments: widthSegments,
+ heightSegments: heightSegments,
+ depthSegments: depthSegments
+ };
+
+ const scope = this;
+
+ // segments
+
+ widthSegments = Math.floor( widthSegments );
+ heightSegments = Math.floor( heightSegments );
+ depthSegments = Math.floor( depthSegments );
+
+ // buffers
+
+ const indices = [];
+ const vertices = [];
+ const normals = [];
+ const uvs = [];
+
+ // helper variables
+
+ let numberOfVertices = 0;
+ let groupStart = 0;
+
+ // build each side of the box geometry
+
+ buildPlane( 'z', 'y', 'x', - 1, - 1, depth, height, width, depthSegments, heightSegments, 0 ); // px
+ buildPlane( 'z', 'y', 'x', 1, - 1, depth, height, - width, depthSegments, heightSegments, 1 ); // nx
+ buildPlane( 'x', 'z', 'y', 1, 1, width, depth, height, widthSegments, depthSegments, 2 ); // py
+ buildPlane( 'x', 'z', 'y', 1, - 1, width, depth, - height, widthSegments, depthSegments, 3 ); // ny
+ buildPlane( 'x', 'y', 'z', 1, - 1, width, height, depth, widthSegments, heightSegments, 4 ); // pz
+ buildPlane( 'x', 'y', 'z', - 1, - 1, width, height, - depth, widthSegments, heightSegments, 5 ); // nz
+
+ // build geometry
+
+ this.setIndex( indices );
+ this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+ this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+ this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+ function buildPlane( u, v, w, udir, vdir, width, height, depth, gridX, gridY, materialIndex ) {
+
+ const segmentWidth = width / gridX;
+ const segmentHeight = height / gridY;
+
+ const widthHalf = width / 2;
+ const heightHalf = height / 2;
+ const depthHalf = depth / 2;
+
+ const gridX1 = gridX + 1;
+ const gridY1 = gridY + 1;
+
+ let vertexCounter = 0;
+ let groupCount = 0;
+
+ const vector = new Vector3();
+
+ // generate vertices, normals and uvs
+
+ for ( let iy = 0; iy < gridY1; iy ++ ) {
+
+ const y = iy * segmentHeight - heightHalf;
+
+ for ( let ix = 0; ix < gridX1; ix ++ ) {
+
+ const x = ix * segmentWidth - widthHalf;
+
+ // set values to correct vector component
+
+ vector[ u ] = x * udir;
+ vector[ v ] = y * vdir;
+ vector[ w ] = depthHalf;
+
+ // now apply vector to vertex buffer
+
+ vertices.push( vector.x, vector.y, vector.z );
+
+ // set values to correct vector component
+
+ vector[ u ] = 0;
+ vector[ v ] = 0;
+ vector[ w ] = depth > 0 ? 1 : - 1;
+
+ // now apply vector to normal buffer
+
+ normals.push( vector.x, vector.y, vector.z );
+
+ // uvs
+
+ uvs.push( ix / gridX );
+ uvs.push( 1 - ( iy / gridY ) );
+
+ // counters
+
+ vertexCounter += 1;
+
+ }
+
+ }
+
+ // indices
+
+ // 1. you need three indices to draw a single face
+ // 2. a single segment consists of two faces
+ // 3. so we need to generate six (2*3) indices per segment
+
+ for ( let iy = 0; iy < gridY; iy ++ ) {
+
+ for ( let ix = 0; ix < gridX; ix ++ ) {
+
+ const a = numberOfVertices + ix + gridX1 * iy;
+ const b = numberOfVertices + ix + gridX1 * ( iy + 1 );
+ const c = numberOfVertices + ( ix + 1 ) + gridX1 * ( iy + 1 );
+ const d = numberOfVertices + ( ix + 1 ) + gridX1 * iy;
+
+ // faces
+
+ indices.push( a, b, d );
+ indices.push( b, c, d );
+
+ // increase counter
+
+ groupCount += 6;
+
+ }
+
+ }
+
+ // add a group to the geometry. this will ensure multi material support
+
+ scope.addGroup( groupStart, groupCount, materialIndex );
+
+ // calculate new start value for groups
+
+ groupStart += groupCount;
+
+ // update total number of vertices
+
+ numberOfVertices += vertexCounter;
+
+ }
+
+ }
+
+}
+
+/**
+ * Uniform Utilities
+ */
+
+function cloneUniforms( src ) {
+
+ const dst = {};
+
+ for ( const u in src ) {
+
+ dst[ u ] = {};
+
+ for ( const p in src[ u ] ) {
+
+ const property = src[ u ][ p ];
+
+ if ( property && ( property.isColor ||
+ property.isMatrix3 || property.isMatrix4 ||
+ property.isVector2 || property.isVector3 || property.isVector4 ||
+ property.isTexture ) ) {
+
+ dst[ u ][ p ] = property.clone();
+
+ } else if ( Array.isArray( property ) ) {
+
+ dst[ u ][ p ] = property.slice();
+
+ } else {
+
+ dst[ u ][ p ] = property;
+
+ }
+
+ }
+
+ }
+
+ return dst;
+
+}
+
+function mergeUniforms( uniforms ) {
+
+ const merged = {};
+
+ for ( let u = 0; u < uniforms.length; u ++ ) {
+
+ const tmp = cloneUniforms( uniforms[ u ] );
+
+ for ( const p in tmp ) {
+
+ merged[ p ] = tmp[ p ];
+
+ }
+
+ }
+
+ return merged;
+
+}
+
+// Legacy
+
+const UniformsUtils = { clone: cloneUniforms, merge: mergeUniforms };
+
+var default_vertex = "void main() {\n\tgl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n}";
+
+var default_fragment = "void main() {\n\tgl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n}";
+
+/**
+ * parameters = {
+ * defines: { "label" : "value" },
+ * uniforms: { "parameter1": { value: 1.0 }, "parameter2": { value2: 2 } },
+ *
+ * fragmentShader: <string>,
+ * vertexShader: <string>,
+ *
+ * wireframe: <boolean>,
+ * wireframeLinewidth: <float>,
+ *
+ * lights: <bool>,
+ *
+ * skinning: <bool>,
+ * morphTargets: <bool>,
+ * morphNormals: <bool>
+ * }
+ */
+
+function ShaderMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.type = 'ShaderMaterial';
+
+ this.defines = {};
+ this.uniforms = {};
+
+ this.vertexShader = default_vertex;
+ this.fragmentShader = default_fragment;
+
+ this.linewidth = 1;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+
+ this.fog = false; // set to use scene fog
+ this.lights = false; // set to use scene lights
+ this.clipping = false; // set to use user-defined clipping planes
+
+ this.skinning = false; // set to use skinning attribute streams
+ this.morphTargets = false; // set to use morph targets
+ this.morphNormals = false; // set to use morph normals
+
+ this.extensions = {
+ derivatives: false, // set to use derivatives
+ fragDepth: false, // set to use fragment depth values
+ drawBuffers: false, // set to use draw buffers
+ shaderTextureLOD: false // set to use shader texture LOD
+ };
+
+ // When rendered geometry doesn't include these attributes but the material does,
+ // use these default values in WebGL. This avoids errors when buffer data is missing.
+ this.defaultAttributeValues = {
+ 'color': [ 1, 1, 1 ],
+ 'uv': [ 0, 0 ],
+ 'uv2': [ 0, 0 ]
+ };
+
+ this.index0AttributeName = undefined;
+ this.uniformsNeedUpdate = false;
+
+ this.glslVersion = null;
+
+ if ( parameters !== undefined ) {
+
+ if ( parameters.attributes !== undefined ) {
+
+ console.error( 'THREE.ShaderMaterial: attributes should now be defined in THREE.BufferGeometry instead.' );
+
+ }
+
+ this.setValues( parameters );
+
+ }
+
+}
+
+ShaderMaterial.prototype = Object.create( Material.prototype );
+ShaderMaterial.prototype.constructor = ShaderMaterial;
+
+ShaderMaterial.prototype.isShaderMaterial = true;
+
+ShaderMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.fragmentShader = source.fragmentShader;
+ this.vertexShader = source.vertexShader;
+
+ this.uniforms = cloneUniforms( source.uniforms );
+
+ this.defines = Object.assign( {}, source.defines );
+
+ this.wireframe = source.wireframe;
+ this.wireframeLinewidth = source.wireframeLinewidth;
+
+ this.lights = source.lights;
+ this.clipping = source.clipping;
+
+ this.skinning = source.skinning;
+
+ this.morphTargets = source.morphTargets;
+ this.morphNormals = source.morphNormals;
+
+ this.extensions = Object.assign( {}, source.extensions );
+
+ this.glslVersion = source.glslVersion;
+
+ return this;
+
+};
+
+ShaderMaterial.prototype.toJSON = function ( meta ) {
+
+ const data = Material.prototype.toJSON.call( this, meta );
+
+ data.glslVersion = this.glslVersion;
+ data.uniforms = {};
+
+ for ( const name in this.uniforms ) {
+
+ const uniform = this.uniforms[ name ];
+ const value = uniform.value;
+
+ if ( value && value.isTexture ) {
+
+ data.uniforms[ name ] = {
+ type: 't',
+ value: value.toJSON( meta ).uuid
+ };
+
+ } else if ( value && value.isColor ) {
+
+ data.uniforms[ name ] = {
+ type: 'c',
+ value: value.getHex()
+ };
+
+ } else if ( value && value.isVector2 ) {
+
+ data.uniforms[ name ] = {
+ type: 'v2',
+ value: value.toArray()
+ };
+
+ } else if ( value && value.isVector3 ) {
+
+ data.uniforms[ name ] = {
+ type: 'v3',
+ value: value.toArray()
+ };
+
+ } else if ( value && value.isVector4 ) {
+
+ data.uniforms[ name ] = {
+ type: 'v4',
+ value: value.toArray()
+ };
+
+ } else if ( value && value.isMatrix3 ) {
+
+ data.uniforms[ name ] = {
+ type: 'm3',
+ value: value.toArray()
+ };
+
+ } else if ( value && value.isMatrix4 ) {
+
+ data.uniforms[ name ] = {
+ type: 'm4',
+ value: value.toArray()
+ };
+
+ } else {
+
+ data.uniforms[ name ] = {
+ value: value
+ };
+
+ // note: the array variants v2v, v3v, v4v, m4v and tv are not supported so far
+
+ }
+
+ }
+
+ if ( Object.keys( this.defines ).length > 0 ) data.defines = this.defines;
+
+ data.vertexShader = this.vertexShader;
+ data.fragmentShader = this.fragmentShader;
+
+ const extensions = {};
+
+ for ( const key in this.extensions ) {
+
+ if ( this.extensions[ key ] === true ) extensions[ key ] = true;
+
+ }
+
+ if ( Object.keys( extensions ).length > 0 ) data.extensions = extensions;
+
+ return data;
+
+};
+
+function Camera$1() {
+
+ Object3D.call( this );
+
+ this.type = 'Camera';
+
+ this.matrixWorldInverse = new Matrix4();
+
+ this.projectionMatrix = new Matrix4();
+ this.projectionMatrixInverse = new Matrix4();
+
+}
+
+Camera$1.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+ constructor: Camera$1,
+
+ isCamera: true,
+
+ copy: function ( source, recursive ) {
+
+ Object3D.prototype.copy.call( this, source, recursive );
+
+ this.matrixWorldInverse.copy( source.matrixWorldInverse );
+
+ this.projectionMatrix.copy( source.projectionMatrix );
+ this.projectionMatrixInverse.copy( source.projectionMatrixInverse );
+
+ return this;
+
+ },
+
+ getWorldDirection: function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Camera: .getWorldDirection() target is now required' );
+ target = new Vector3();
+
+ }
+
+ this.updateWorldMatrix( true, false );
+
+ const e = this.matrixWorld.elements;
+
+ return target.set( - e[ 8 ], - e[ 9 ], - e[ 10 ] ).normalize();
+
+ },
+
+ updateMatrixWorld: function ( force ) {
+
+ Object3D.prototype.updateMatrixWorld.call( this, force );
+
+ this.matrixWorldInverse.copy( this.matrixWorld ).invert();
+
+ },
+
+ updateWorldMatrix: function ( updateParents, updateChildren ) {
+
+ Object3D.prototype.updateWorldMatrix.call( this, updateParents, updateChildren );
+
+ this.matrixWorldInverse.copy( this.matrixWorld ).invert();
+
+ },
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ }
+
+} );
+
+function PerspectiveCamera( fov = 50, aspect = 1, near = 0.1, far = 2000 ) {
+
+ Camera$1.call( this );
+
+ this.type = 'PerspectiveCamera';
+
+ this.fov = fov;
+ this.zoom = 1;
+
+ this.near = near;
+ this.far = far;
+ this.focus = 10;
+
+ this.aspect = aspect;
+ this.view = null;
+
+ this.filmGauge = 35; // width of the film (default in millimeters)
+ this.filmOffset = 0; // horizontal film offset (same unit as gauge)
+
+ this.updateProjectionMatrix();
+
+}
+
+PerspectiveCamera.prototype = Object.assign( Object.create( Camera$1.prototype ), {
+
+ constructor: PerspectiveCamera,
+
+ isPerspectiveCamera: true,
+
+ copy: function ( source, recursive ) {
+
+ Camera$1.prototype.copy.call( this, source, recursive );
+
+ this.fov = source.fov;
+ this.zoom = source.zoom;
+
+ this.near = source.near;
+ this.far = source.far;
+ this.focus = source.focus;
+
+ this.aspect = source.aspect;
+ this.view = source.view === null ? null : Object.assign( {}, source.view );
+
+ this.filmGauge = source.filmGauge;
+ this.filmOffset = source.filmOffset;
+
+ return this;
+
+ },
+
+ /**
+ * Sets the FOV by focal length in respect to the current .filmGauge.
+ *
+ * The default film gauge is 35, so that the focal length can be specified for
+ * a 35mm (full frame) camera.
+ *
+ * Values for focal length and film gauge must have the same unit.
+ */
+ setFocalLength: function ( focalLength ) {
+
+ /** see {@link http://www.bobatkins.com/photography/technical/field_of_view.html} */
+ const vExtentSlope = 0.5 * this.getFilmHeight() / focalLength;
+
+ this.fov = MathUtils.RAD2DEG * 2 * Math.atan( vExtentSlope );
+ this.updateProjectionMatrix();
+
+ },
+
+ /**
+ * Calculates the focal length from the current .fov and .filmGauge.
+ */
+ getFocalLength: function () {
+
+ const vExtentSlope = Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov );
+
+ return 0.5 * this.getFilmHeight() / vExtentSlope;
+
+ },
+
+ getEffectiveFOV: function () {
+
+ return MathUtils.RAD2DEG * 2 * Math.atan(
+ Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov ) / this.zoom );
+
+ },
+
+ getFilmWidth: function () {
+
+ // film not completely covered in portrait format (aspect < 1)
+ return this.filmGauge * Math.min( this.aspect, 1 );
+
+ },
+
+ getFilmHeight: function () {
+
+ // film not completely covered in landscape format (aspect > 1)
+ return this.filmGauge / Math.max( this.aspect, 1 );
+
+ },
+
+ /**
+ * Sets an offset in a larger frustum. This is useful for multi-window or
+ * multi-monitor/multi-machine setups.
+ *
+ * For example, if you have 3x2 monitors and each monitor is 1920x1080 and
+ * the monitors are in grid like this
+ *
+ * +---+---+---+
+ * | A | B | C |
+ * +---+---+---+
+ * | D | E | F |
+ * +---+---+---+
+ *
+ * then for each monitor you would call it like this
+ *
+ * const w = 1920;
+ * const h = 1080;
+ * const fullWidth = w * 3;
+ * const fullHeight = h * 2;
+ *
+ * --A--
+ * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 0, w, h );
+ * --B--
+ * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 0, w, h );
+ * --C--
+ * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 0, w, h );
+ * --D--
+ * camera.setViewOffset( fullWidth, fullHeight, w * 0, h * 1, w, h );
+ * --E--
+ * camera.setViewOffset( fullWidth, fullHeight, w * 1, h * 1, w, h );
+ * --F--
+ * camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );
+ *
+ * Note there is no reason monitors have to be the same size or in a grid.
+ */
+ setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {
+
+ this.aspect = fullWidth / fullHeight;
+
+ if ( this.view === null ) {
+
+ this.view = {
+ enabled: true,
+ fullWidth: 1,
+ fullHeight: 1,
+ offsetX: 0,
+ offsetY: 0,
+ width: 1,
+ height: 1
+ };
+
+ }
+
+ this.view.enabled = true;
+ this.view.fullWidth = fullWidth;
+ this.view.fullHeight = fullHeight;
+ this.view.offsetX = x;
+ this.view.offsetY = y;
+ this.view.width = width;
+ this.view.height = height;
+
+ this.updateProjectionMatrix();
+
+ },
+
+ clearViewOffset: function () {
+
+ if ( this.view !== null ) {
+
+ this.view.enabled = false;
+
+ }
+
+ this.updateProjectionMatrix();
+
+ },
+
+ updateProjectionMatrix: function () {
+
+ const near = this.near;
+ let top = near * Math.tan( MathUtils.DEG2RAD * 0.5 * this.fov ) / this.zoom;
+ let height = 2 * top;
+ let width = this.aspect * height;
+ let left = - 0.5 * width;
+ const view = this.view;
+
+ if ( this.view !== null && this.view.enabled ) {
+
+ const fullWidth = view.fullWidth,
+ fullHeight = view.fullHeight;
+
+ left += view.offsetX * width / fullWidth;
+ top -= view.offsetY * height / fullHeight;
+ width *= view.width / fullWidth;
+ height *= view.height / fullHeight;
+
+ }
+
+ const skew = this.filmOffset;
+ if ( skew !== 0 ) left += near * skew / this.getFilmWidth();
+
+ this.projectionMatrix.makePerspective( left, left + width, top, top - height, near, this.far );
+
+ this.projectionMatrixInverse.copy( this.projectionMatrix ).invert();
+
+ },
+
+ toJSON: function ( meta ) {
+
+ const data = Object3D.prototype.toJSON.call( this, meta );
+
+ data.object.fov = this.fov;
+ data.object.zoom = this.zoom;
+
+ data.object.near = this.near;
+ data.object.far = this.far;
+ data.object.focus = this.focus;
+
+ data.object.aspect = this.aspect;
+
+ if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );
+
+ data.object.filmGauge = this.filmGauge;
+ data.object.filmOffset = this.filmOffset;
+
+ return data;
+
+ }
+
+} );
+
+const fov = 90, aspect = 1;
+
+function CubeCamera( near, far, renderTarget ) {
+
+ Object3D.call( this );
+
+ this.type = 'CubeCamera';
+
+ if ( renderTarget.isWebGLCubeRenderTarget !== true ) {
+
+ console.error( 'THREE.CubeCamera: The constructor now expects an instance of WebGLCubeRenderTarget as third parameter.' );
+ return;
+
+ }
+
+ this.renderTarget = renderTarget;
+
+ const cameraPX = new PerspectiveCamera( fov, aspect, near, far );
+ cameraPX.layers = this.layers;
+ cameraPX.up.set( 0, - 1, 0 );
+ cameraPX.lookAt( new Vector3( 1, 0, 0 ) );
+ this.add( cameraPX );
+
+ const cameraNX = new PerspectiveCamera( fov, aspect, near, far );
+ cameraNX.layers = this.layers;
+ cameraNX.up.set( 0, - 1, 0 );
+ cameraNX.lookAt( new Vector3( - 1, 0, 0 ) );
+ this.add( cameraNX );
+
+ const cameraPY = new PerspectiveCamera( fov, aspect, near, far );
+ cameraPY.layers = this.layers;
+ cameraPY.up.set( 0, 0, 1 );
+ cameraPY.lookAt( new Vector3( 0, 1, 0 ) );
+ this.add( cameraPY );
+
+ const cameraNY = new PerspectiveCamera( fov, aspect, near, far );
+ cameraNY.layers = this.layers;
+ cameraNY.up.set( 0, 0, - 1 );
+ cameraNY.lookAt( new Vector3( 0, - 1, 0 ) );
+ this.add( cameraNY );
+
+ const cameraPZ = new PerspectiveCamera( fov, aspect, near, far );
+ cameraPZ.layers = this.layers;
+ cameraPZ.up.set( 0, - 1, 0 );
+ cameraPZ.lookAt( new Vector3( 0, 0, 1 ) );
+ this.add( cameraPZ );
+
+ const cameraNZ = new PerspectiveCamera( fov, aspect, near, far );
+ cameraNZ.layers = this.layers;
+ cameraNZ.up.set( 0, - 1, 0 );
+ cameraNZ.lookAt( new Vector3( 0, 0, - 1 ) );
+ this.add( cameraNZ );
+
+ this.update = function ( renderer, scene ) {
+
+ if ( this.parent === null ) this.updateMatrixWorld();
+
+ const currentXrEnabled = renderer.xr.enabled;
+ const currentRenderTarget = renderer.getRenderTarget();
+
+ renderer.xr.enabled = false;
+
+ const generateMipmaps = renderTarget.texture.generateMipmaps;
+
+ renderTarget.texture.generateMipmaps = false;
+
+ renderer.setRenderTarget( renderTarget, 0 );
+ renderer.render( scene, cameraPX );
+
+ renderer.setRenderTarget( renderTarget, 1 );
+ renderer.render( scene, cameraNX );
+
+ renderer.setRenderTarget( renderTarget, 2 );
+ renderer.render( scene, cameraPY );
+
+ renderer.setRenderTarget( renderTarget, 3 );
+ renderer.render( scene, cameraNY );
+
+ renderer.setRenderTarget( renderTarget, 4 );
+ renderer.render( scene, cameraPZ );
+
+ renderTarget.texture.generateMipmaps = generateMipmaps;
+
+ renderer.setRenderTarget( renderTarget, 5 );
+ renderer.render( scene, cameraNZ );
+
+ renderer.setRenderTarget( currentRenderTarget );
+
+ renderer.xr.enabled = currentXrEnabled;
+
+ };
+
+}
+
+CubeCamera.prototype = Object.create( Object3D.prototype );
+CubeCamera.prototype.constructor = CubeCamera;
+
+function CubeTexture( images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding ) {
+
+ images = images !== undefined ? images : [];
+ mapping = mapping !== undefined ? mapping : CubeReflectionMapping;
+ format = format !== undefined ? format : RGBFormat;
+
+ Texture.call( this, images, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
+
+ this.flipY = false;
+
+ // Why CubeTexture._needsFlipEnvMap is necessary:
+ //
+ // By convention -- likely based on the RenderMan spec from the 1990's -- cube maps are specified by WebGL (and three.js)
+ // in a coordinate system in which positive-x is to the right when looking up the positive-z axis -- in other words,
+ // in a left-handed coordinate system. By continuing this convention, preexisting cube maps continued to render correctly.
+
+ // three.js uses a right-handed coordinate system. So environment maps used in three.js appear to have px and nx swapped
+ // and the flag _needsFlipEnvMap controls this conversion. The flip is not required (and thus _needsFlipEnvMap is set to false)
+ // when using WebGLCubeRenderTarget.texture as a cube texture.
+
+ this._needsFlipEnvMap = true;
+
+}
+
+CubeTexture.prototype = Object.create( Texture.prototype );
+CubeTexture.prototype.constructor = CubeTexture;
+
+CubeTexture.prototype.isCubeTexture = true;
+
+Object.defineProperty( CubeTexture.prototype, 'images', {
+
+ get: function () {
+
+ return this.image;
+
+ },
+
+ set: function ( value ) {
+
+ this.image = value;
+
+ }
+
+} );
+
+class WebGLCubeRenderTarget extends WebGLRenderTarget {
+
+ constructor( size, options, dummy ) {
+
+ if ( Number.isInteger( options ) ) {
+
+ console.warn( 'THREE.WebGLCubeRenderTarget: constructor signature is now WebGLCubeRenderTarget( size, options )' );
+
+ options = dummy;
+
+ }
+
+ super( size, size, options );
+
+ Object.defineProperty( this, 'isWebGLCubeRenderTarget', { value: true } );
+
+ options = options || {};
+
+ this.texture = new CubeTexture( undefined, options.mapping, options.wrapS, options.wrapT, options.magFilter, options.minFilter, options.format, options.type, options.anisotropy, options.encoding );
+
+ this.texture._needsFlipEnvMap = false;
+
+ }
+
+ fromEquirectangularTexture( renderer, texture ) {
+
+ this.texture.type = texture.type;
+ this.texture.format = RGBAFormat; // see #18859
+ this.texture.encoding = texture.encoding;
+
+ this.texture.generateMipmaps = texture.generateMipmaps;
+ this.texture.minFilter = texture.minFilter;
+ this.texture.magFilter = texture.magFilter;
+
+ const shader = {
+
+ uniforms: {
+ tEquirect: { value: null },
+ },
+
+ vertexShader: /* glsl */`
+
+ varying vec3 vWorldDirection;
+
+ vec3 transformDirection( in vec3 dir, in mat4 matrix ) {
+
+ return normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );
+
+ }
+
+ void main() {
+
+ vWorldDirection = transformDirection( position, modelMatrix );
+
+ #include <begin_vertex>
+ #include <project_vertex>
+
+ }
+ `,
+
+ fragmentShader: /* glsl */`
+
+ uniform sampler2D tEquirect;
+
+ varying vec3 vWorldDirection;
+
+ #include <common>
+
+ void main() {
+
+ vec3 direction = normalize( vWorldDirection );
+
+ vec2 sampleUV = equirectUv( direction );
+
+ gl_FragColor = texture2D( tEquirect, sampleUV );
+
+ }
+ `
+ };
+
+ const geometry = new BoxGeometry( 5, 5, 5 );
+
+ const material = new ShaderMaterial( {
+
+ name: 'CubemapFromEquirect',
+
+ uniforms: cloneUniforms( shader.uniforms ),
+ vertexShader: shader.vertexShader,
+ fragmentShader: shader.fragmentShader,
+ side: BackSide,
+ blending: NoBlending
+
+ } );
+
+ material.uniforms.tEquirect.value = texture;
+
+ const mesh = new Mesh( geometry, material );
+
+ const currentMinFilter = texture.minFilter;
+
+ // Avoid blurred poles
+ if ( texture.minFilter === LinearMipmapLinearFilter ) texture.minFilter = LinearFilter;
+
+ const camera = new CubeCamera( 1, 10, this );
+ camera.update( renderer, mesh );
+
+ texture.minFilter = currentMinFilter;
+
+ mesh.geometry.dispose();
+ mesh.material.dispose();
+
+ return this;
+
+ }
+
+ clear( renderer, color, depth, stencil ) {
+
+ const currentRenderTarget = renderer.getRenderTarget();
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ renderer.setRenderTarget( this, i );
+
+ renderer.clear( color, depth, stencil );
+
+ }
+
+ renderer.setRenderTarget( currentRenderTarget );
+
+ }
+
+}
+
+function DataTexture( data, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {
+
+ Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
+
+ this.image = { data: data || null, width: width || 1, height: height || 1 };
+
+ this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
+ this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;
+
+ this.generateMipmaps = false;
+ this.flipY = false;
+ this.unpackAlignment = 1;
+
+ this.needsUpdate = true;
+
+}
+
+DataTexture.prototype = Object.create( Texture.prototype );
+DataTexture.prototype.constructor = DataTexture;
+
+DataTexture.prototype.isDataTexture = true;
+
+const _sphere$1 = /*@__PURE__*/ new Sphere();
+const _vector$5 = /*@__PURE__*/ new Vector3();
+
+class Frustum {
+
+ constructor( p0, p1, p2, p3, p4, p5 ) {
+
+ this.planes = [
+
+ ( p0 !== undefined ) ? p0 : new Plane(),
+ ( p1 !== undefined ) ? p1 : new Plane(),
+ ( p2 !== undefined ) ? p2 : new Plane(),
+ ( p3 !== undefined ) ? p3 : new Plane(),
+ ( p4 !== undefined ) ? p4 : new Plane(),
+ ( p5 !== undefined ) ? p5 : new Plane()
+
+ ];
+
+ }
+
+ set( p0, p1, p2, p3, p4, p5 ) {
+
+ const planes = this.planes;
+
+ planes[ 0 ].copy( p0 );
+ planes[ 1 ].copy( p1 );
+ planes[ 2 ].copy( p2 );
+ planes[ 3 ].copy( p3 );
+ planes[ 4 ].copy( p4 );
+ planes[ 5 ].copy( p5 );
+
+ return this;
+
+ }
+
+ clone() {
+
+ return new this.constructor().copy( this );
+
+ }
+
+ copy( frustum ) {
+
+ const planes = this.planes;
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ planes[ i ].copy( frustum.planes[ i ] );
+
+ }
+
+ return this;
+
+ }
+
+ setFromProjectionMatrix( m ) {
+
+ const planes = this.planes;
+ const me = m.elements;
+ const me0 = me[ 0 ], me1 = me[ 1 ], me2 = me[ 2 ], me3 = me[ 3 ];
+ const me4 = me[ 4 ], me5 = me[ 5 ], me6 = me[ 6 ], me7 = me[ 7 ];
+ const me8 = me[ 8 ], me9 = me[ 9 ], me10 = me[ 10 ], me11 = me[ 11 ];
+ const me12 = me[ 12 ], me13 = me[ 13 ], me14 = me[ 14 ], me15 = me[ 15 ];
+
+ planes[ 0 ].setComponents( me3 - me0, me7 - me4, me11 - me8, me15 - me12 ).normalize();
+ planes[ 1 ].setComponents( me3 + me0, me7 + me4, me11 + me8, me15 + me12 ).normalize();
+ planes[ 2 ].setComponents( me3 + me1, me7 + me5, me11 + me9, me15 + me13 ).normalize();
+ planes[ 3 ].setComponents( me3 - me1, me7 - me5, me11 - me9, me15 - me13 ).normalize();
+ planes[ 4 ].setComponents( me3 - me2, me7 - me6, me11 - me10, me15 - me14 ).normalize();
+ planes[ 5 ].setComponents( me3 + me2, me7 + me6, me11 + me10, me15 + me14 ).normalize();
+
+ return this;
+
+ }
+
+ intersectsObject( object ) {
+
+ const geometry = object.geometry;
+
+ if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+ _sphere$1.copy( geometry.boundingSphere ).applyMatrix4( object.matrixWorld );
+
+ return this.intersectsSphere( _sphere$1 );
+
+ }
+
+ intersectsSprite( sprite ) {
+
+ _sphere$1.center.set( 0, 0, 0 );
+ _sphere$1.radius = 0.7071067811865476;
+ _sphere$1.applyMatrix4( sprite.matrixWorld );
+
+ return this.intersectsSphere( _sphere$1 );
+
+ }
+
+ intersectsSphere( sphere ) {
+
+ const planes = this.planes;
+ const center = sphere.center;
+ const negRadius = - sphere.radius;
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ const distance = planes[ i ].distanceToPoint( center );
+
+ if ( distance < negRadius ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ }
+
+ intersectsBox( box ) {
+
+ const planes = this.planes;
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ const plane = planes[ i ];
+
+ // corner at max distance
+
+ _vector$5.x = plane.normal.x > 0 ? box.max.x : box.min.x;
+ _vector$5.y = plane.normal.y > 0 ? box.max.y : box.min.y;
+ _vector$5.z = plane.normal.z > 0 ? box.max.z : box.min.z;
+
+ if ( plane.distanceToPoint( _vector$5 ) < 0 ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ }
+
+ containsPoint( point ) {
+
+ const planes = this.planes;
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ if ( planes[ i ].distanceToPoint( point ) < 0 ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ }
+
+}
+
+function WebGLAnimation() {
+
+ let context = null;
+ let isAnimating = false;
+ let animationLoop = null;
+ let requestId = null;
+
+ function onAnimationFrame( time, frame ) {
+
+ animationLoop( time, frame );
+
+ requestId = context.requestAnimationFrame( onAnimationFrame );
+
+ }
+
+ return {
+
+ start: function () {
+
+ if ( isAnimating === true ) return;
+ if ( animationLoop === null ) return;
+
+ requestId = context.requestAnimationFrame( onAnimationFrame );
+
+ isAnimating = true;
+
+ },
+
+ stop: function () {
+
+ context.cancelAnimationFrame( requestId );
+
+ isAnimating = false;
+
+ },
+
+ setAnimationLoop: function ( callback ) {
+
+ animationLoop = callback;
+
+ },
+
+ setContext: function ( value ) {
+
+ context = value;
+
+ }
+
+ };
+
+}
+
+function WebGLAttributes( gl, capabilities ) {
+
+ const isWebGL2 = capabilities.isWebGL2;
+
+ const buffers = new WeakMap();
+
+ function createBuffer( attribute, bufferType ) {
+
+ const array = attribute.array;
+ const usage = attribute.usage;
+
+ const buffer = gl.createBuffer();
+
+ gl.bindBuffer( bufferType, buffer );
+ gl.bufferData( bufferType, array, usage );
+
+ attribute.onUploadCallback();
+
+ let type = 5126;
+
+ if ( array instanceof Float32Array ) {
+
+ type = 5126;
+
+ } else if ( array instanceof Float64Array ) {
+
+ console.warn( 'THREE.WebGLAttributes: Unsupported data buffer format: Float64Array.' );
+
+ } else if ( array instanceof Uint16Array ) {
+
+ if ( attribute.isFloat16BufferAttribute ) {
+
+ if ( isWebGL2 ) {
+
+ type = 5131;
+
+ } else {
+
+ console.warn( 'THREE.WebGLAttributes: Usage of Float16BufferAttribute requires WebGL2.' );
+
+ }
+
+ } else {
+
+ type = 5123;
+
+ }
+
+ } else if ( array instanceof Int16Array ) {
+
+ type = 5122;
+
+ } else if ( array instanceof Uint32Array ) {
+
+ type = 5125;
+
+ } else if ( array instanceof Int32Array ) {
+
+ type = 5124;
+
+ } else if ( array instanceof Int8Array ) {
+
+ type = 5120;
+
+ } else if ( array instanceof Uint8Array ) {
+
+ type = 5121;
+
+ }
+
+ return {
+ buffer: buffer,
+ type: type,
+ bytesPerElement: array.BYTES_PER_ELEMENT,
+ version: attribute.version
+ };
+
+ }
+
+ function updateBuffer( buffer, attribute, bufferType ) {
+
+ const array = attribute.array;
+ const updateRange = attribute.updateRange;
+
+ gl.bindBuffer( bufferType, buffer );
+
+ if ( updateRange.count === - 1 ) {
+
+ // Not using update ranges
+
+ gl.bufferSubData( bufferType, 0, array );
+
+ } else {
+
+ if ( isWebGL2 ) {
+
+ gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT,
+ array, updateRange.offset, updateRange.count );
+
+ } else {
+
+ gl.bufferSubData( bufferType, updateRange.offset * array.BYTES_PER_ELEMENT,
+ array.subarray( updateRange.offset, updateRange.offset + updateRange.count ) );
+
+ }
+
+ updateRange.count = - 1; // reset range
+
+ }
+
+ }
+
+ //
+
+ function get( attribute ) {
+
+ if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
+
+ return buffers.get( attribute );
+
+ }
+
+ function remove( attribute ) {
+
+ if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
+
+ const data = buffers.get( attribute );
+
+ if ( data ) {
+
+ gl.deleteBuffer( data.buffer );
+
+ buffers.delete( attribute );
+
+ }
+
+ }
+
+ function update( attribute, bufferType ) {
+
+ if ( attribute.isGLBufferAttribute ) {
+
+ const cached = buffers.get( attribute );
+
+ if ( ! cached || cached.version < attribute.version ) {
+
+ buffers.set( attribute, {
+ buffer: attribute.buffer,
+ type: attribute.type,
+ bytesPerElement: attribute.elementSize,
+ version: attribute.version
+ } );
+
+ }
+
+ return;
+
+ }
+
+ if ( attribute.isInterleavedBufferAttribute ) attribute = attribute.data;
+
+ const data = buffers.get( attribute );
+
+ if ( data === undefined ) {
+
+ buffers.set( attribute, createBuffer( attribute, bufferType ) );
+
+ } else if ( data.version < attribute.version ) {
+
+ updateBuffer( data.buffer, attribute, bufferType );
+
+ data.version = attribute.version;
+
+ }
+
+ }
+
+ return {
+
+ get: get,
+ remove: remove,
+ update: update
+
+ };
+
+}
+
+class PlaneGeometry extends BufferGeometry {
+
+ constructor( width = 1, height = 1, widthSegments = 1, heightSegments = 1 ) {
+
+ super();
+ this.type = 'PlaneGeometry';
+
+ this.parameters = {
+ width: width,
+ height: height,
+ widthSegments: widthSegments,
+ heightSegments: heightSegments
+ };
+
+ const width_half = width / 2;
+ const height_half = height / 2;
+
+ const gridX = Math.floor( widthSegments );
+ const gridY = Math.floor( heightSegments );
+
+ const gridX1 = gridX + 1;
+ const gridY1 = gridY + 1;
+
+ const segment_width = width / gridX;
+ const segment_height = height / gridY;
+
+ //
+
+ const indices = [];
+ const vertices = [];
+ const normals = [];
+ const uvs = [];
+
+ for ( let iy = 0; iy < gridY1; iy ++ ) {
+
+ const y = iy * segment_height - height_half;
+
+ for ( let ix = 0; ix < gridX1; ix ++ ) {
+
+ const x = ix * segment_width - width_half;
+
+ vertices.push( x, - y, 0 );
+
+ normals.push( 0, 0, 1 );
+
+ uvs.push( ix / gridX );
+ uvs.push( 1 - ( iy / gridY ) );
+
+ }
+
+ }
+
+ for ( let iy = 0; iy < gridY; iy ++ ) {
+
+ for ( let ix = 0; ix < gridX; ix ++ ) {
+
+ const a = ix + gridX1 * iy;
+ const b = ix + gridX1 * ( iy + 1 );
+ const c = ( ix + 1 ) + gridX1 * ( iy + 1 );
+ const d = ( ix + 1 ) + gridX1 * iy;
+
+ indices.push( a, b, d );
+ indices.push( b, c, d );
+
+ }
+
+ }
+
+ this.setIndex( indices );
+ this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+ this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+ this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+ }
+
+}
+
+var alphamap_fragment = "#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, vUv ).g;\n#endif";
+
+var alphamap_pars_fragment = "#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif";
+
+var alphatest_fragment = "#ifdef ALPHATEST\n\tif ( diffuseColor.a < ALPHATEST ) discard;\n#endif";
+
+var aomap_fragment = "#ifdef USE_AOMAP\n\tfloat ambientOcclusion = ( texture2D( aoMap, vUv2 ).r - 1.0 ) * aoMapIntensity + 1.0;\n\treflectedLight.indirectDiffuse *= ambientOcclusion;\n\t#if defined( USE_ENVMAP ) && defined( STANDARD )\n\t\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular *= computeSpecularOcclusion( dotNV, ambientOcclusion, material.specularRoughness );\n\t#endif\n#endif";
+
+var aomap_pars_fragment = "#ifdef USE_AOMAP\n\tuniform sampler2D aoMap;\n\tuniform float aoMapIntensity;\n#endif";
+
+var begin_vertex = "vec3 transformed = vec3( position );";
+
+var beginnormal_vertex = "vec3 objectNormal = vec3( normal );\n#ifdef USE_TANGENT\n\tvec3 objectTangent = vec3( tangent.xyz );\n#endif";
+
+var bsdfs = "vec2 integrateSpecularBRDF( const in float dotNV, const in float roughness ) {\n\tconst vec4 c0 = vec4( - 1, - 0.0275, - 0.572, 0.022 );\n\tconst vec4 c1 = vec4( 1, 0.0425, 1.04, - 0.04 );\n\tvec4 r = roughness * c0 + c1;\n\tfloat a004 = min( r.x * r.x, exp2( - 9.28 * dotNV ) ) * r.x + r.y;\n\treturn vec2( -1.04, 1.04 ) * a004 + r.zw;\n}\nfloat punctualLightIntensityToIrradianceFactor( const in float lightDistance, const in float cutoffDistance, const in float decayExponent ) {\n#if defined ( PHYSICALLY_CORRECT_LIGHTS )\n\tfloat distanceFalloff = 1.0 / max( pow( lightDistance, decayExponent ), 0.01 );\n\tif( cutoffDistance > 0.0 ) {\n\t\tdistanceFalloff *= pow2( saturate( 1.0 - pow4( lightDistance / cutoffDistance ) ) );\n\t}\n\treturn distanceFalloff;\n#else\n\tif( cutoffDistance > 0.0 && decayExponent > 0.0 ) {\n\t\treturn pow( saturate( -lightDistance / cutoffDistance + 1.0 ), decayExponent );\n\t}\n\treturn 1.0;\n#endif\n}\nvec3 BRDF_Diffuse_Lambert( const in vec3 diffuseColor ) {\n\treturn RECIPROCAL_PI * diffuseColor;\n}\nvec3 F_Schlick( const in vec3 specularColor, const in float dotLH ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotLH - 6.98316 ) * dotLH );\n\treturn ( 1.0 - specularColor ) * fresnel + specularColor;\n}\nvec3 F_Schlick_RoughnessDependent( const in vec3 F0, const in float dotNV, const in float roughness ) {\n\tfloat fresnel = exp2( ( -5.55473 * dotNV - 6.98316 ) * dotNV );\n\tvec3 Fr = max( vec3( 1.0 - roughness ), F0 ) - F0;\n\treturn Fr * fresnel + F0;\n}\nfloat G_GGX_Smith( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gl = dotNL + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\tfloat gv = dotNV + sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\treturn 1.0 / ( gl * gv );\n}\nfloat G_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {\n\tfloat a2 = pow2( alpha );\n\tfloat gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );\n\tfloat gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );\n\treturn 0.5 / max( gv + gl, EPSILON );\n}\nfloat D_GGX( const in float alpha, const in float dotNH ) {\n\tfloat a2 = pow2( alpha );\n\tfloat denom = pow2( dotNH ) * ( a2 - 1.0 ) + 1.0;\n\treturn RECIPROCAL_PI * a2 / pow2( denom );\n}\nvec3 BRDF_Specular_GGX( const in IncidentLight incidentLight, const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {\n\tfloat alpha = pow2( roughness );\n\tvec3 halfDir = normalize( incidentLight.direction + viewDir );\n\tfloat dotNL = saturate( dot( normal, incidentLight.direction ) );\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tfloat dotNH = saturate( dot( normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_GGX_SmithCorrelated( alpha, dotNL, dotNV );\n\tfloat D = D_GGX( alpha, dotNH );\n\treturn F * ( G * D );\n}\nvec2 LTC_Uv( const in vec3 N, const in vec3 V, const in float roughness ) {\n\tconst float LUT_SIZE = 64.0;\n\tconst float LUT_SCALE = ( LUT_SIZE - 1.0 ) / LUT_SIZE;\n\tconst float LUT_BIAS = 0.5 / LUT_SIZE;\n\tfloat dotNV = saturate( dot( N, V ) );\n\tvec2 uv = vec2( roughness, sqrt( 1.0 - dotNV ) );\n\tuv = uv * LUT_SCALE + LUT_BIAS;\n\treturn uv;\n}\nfloat LTC_ClippedSphereFormFactor( const in vec3 f ) {\n\tfloat l = length( f );\n\treturn max( ( l * l + f.z ) / ( l + 1.0 ), 0.0 );\n}\nvec3 LTC_EdgeVectorFormFactor( const in vec3 v1, const in vec3 v2 ) {\n\tfloat x = dot( v1, v2 );\n\tfloat y = abs( x );\n\tfloat a = 0.8543985 + ( 0.4965155 + 0.0145206 * y ) * y;\n\tfloat b = 3.4175940 + ( 4.1616724 + y ) * y;\n\tfloat v = a / b;\n\tfloat theta_sintheta = ( x > 0.0 ) ? v : 0.5 * inversesqrt( max( 1.0 - x * x, 1e-7 ) ) - v;\n\treturn cross( v1, v2 ) * theta_sintheta;\n}\nvec3 LTC_Evaluate( const in vec3 N, const in vec3 V, const in vec3 P, const in mat3 mInv, const in vec3 rectCoords[ 4 ] ) {\n\tvec3 v1 = rectCoords[ 1 ] - rectCoords[ 0 ];\n\tvec3 v2 = rectCoords[ 3 ] - rectCoords[ 0 ];\n\tvec3 lightNormal = cross( v1, v2 );\n\tif( dot( lightNormal, P - rectCoords[ 0 ] ) < 0.0 ) return vec3( 0.0 );\n\tvec3 T1, T2;\n\tT1 = normalize( V - N * dot( V, N ) );\n\tT2 = - cross( N, T1 );\n\tmat3 mat = mInv * transposeMat3( mat3( T1, T2, N ) );\n\tvec3 coords[ 4 ];\n\tcoords[ 0 ] = mat * ( rectCoords[ 0 ] - P );\n\tcoords[ 1 ] = mat * ( rectCoords[ 1 ] - P );\n\tcoords[ 2 ] = mat * ( rectCoords[ 2 ] - P );\n\tcoords[ 3 ] = mat * ( rectCoords[ 3 ] - P );\n\tcoords[ 0 ] = normalize( coords[ 0 ] );\n\tcoords[ 1 ] = normalize( coords[ 1 ] );\n\tcoords[ 2 ] = normalize( coords[ 2 ] );\n\tcoords[ 3 ] = normalize( coords[ 3 ] );\n\tvec3 vectorFormFactor = vec3( 0.0 );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 0 ], coords[ 1 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 1 ], coords[ 2 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 2 ], coords[ 3 ] );\n\tvectorFormFactor += LTC_EdgeVectorFormFactor( coords[ 3 ], coords[ 0 ] );\n\tfloat result = LTC_ClippedSphereFormFactor( vectorFormFactor );\n\treturn vec3( result );\n}\nvec3 BRDF_Specular_GGX_Environment( const in vec3 viewDir, const in vec3 normal, const in vec3 specularColor, const in float roughness ) {\n\tfloat dotNV = saturate( dot( normal, viewDir ) );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\treturn specularColor * brdf.x + brdf.y;\n}\nvoid BRDF_Specular_Multiscattering_Environment( const in GeometricContext geometry, const in vec3 specularColor, const in float roughness, inout vec3 singleScatter, inout vec3 multiScatter ) {\n\tfloat dotNV = saturate( dot( geometry.normal, geometry.viewDir ) );\n\tvec3 F = F_Schlick_RoughnessDependent( specularColor, dotNV, roughness );\n\tvec2 brdf = integrateSpecularBRDF( dotNV, roughness );\n\tvec3 FssEss = F * brdf.x + brdf.y;\n\tfloat Ess = brdf.x + brdf.y;\n\tfloat Ems = 1.0 - Ess;\n\tvec3 Favg = specularColor + ( 1.0 - specularColor ) * 0.047619;\tvec3 Fms = FssEss * Favg / ( 1.0 - Ems * Favg );\n\tsingleScatter += FssEss;\n\tmultiScatter += Fms * Ems;\n}\nfloat G_BlinnPhong_Implicit( ) {\n\treturn 0.25;\n}\nfloat D_BlinnPhong( const in float shininess, const in float dotNH ) {\n\treturn RECIPROCAL_PI * ( shininess * 0.5 + 1.0 ) * pow( dotNH, shininess );\n}\nvec3 BRDF_Specular_BlinnPhong( const in IncidentLight incidentLight, const in GeometricContext geometry, const in vec3 specularColor, const in float shininess ) {\n\tvec3 halfDir = normalize( incidentLight.direction + geometry.viewDir );\n\tfloat dotNH = saturate( dot( geometry.normal, halfDir ) );\n\tfloat dotLH = saturate( dot( incidentLight.direction, halfDir ) );\n\tvec3 F = F_Schlick( specularColor, dotLH );\n\tfloat G = G_BlinnPhong_Implicit( );\n\tfloat D = D_BlinnPhong( shininess, dotNH );\n\treturn F * ( G * D );\n}\nfloat GGXRoughnessToBlinnExponent( const in float ggxRoughness ) {\n\treturn ( 2.0 / pow2( ggxRoughness + 0.0001 ) - 2.0 );\n}\nfloat BlinnExponentToGGXRoughness( const in float blinnExponent ) {\n\treturn sqrt( 2.0 / ( blinnExponent + 2.0 ) );\n}\n#if defined( USE_SHEEN )\nfloat D_Charlie(float roughness, float NoH) {\n\tfloat invAlpha = 1.0 / roughness;\n\tfloat cos2h = NoH * NoH;\n\tfloat sin2h = max(1.0 - cos2h, 0.0078125);\treturn (2.0 + invAlpha) * pow(sin2h, invAlpha * 0.5) / (2.0 * PI);\n}\nfloat V_Neubelt(float NoV, float NoL) {\n\treturn saturate(1.0 / (4.0 * (NoL + NoV - NoL * NoV)));\n}\nvec3 BRDF_Specular_Sheen( const in float roughness, const in vec3 L, const in GeometricContext geometry, vec3 specularColor ) {\n\tvec3 N = geometry.normal;\n\tvec3 V = geometry.viewDir;\n\tvec3 H = normalize( V + L );\n\tfloat dotNH = saturate( dot( N, H ) );\n\treturn specularColor * D_Charlie( roughness, dotNH ) * V_Neubelt( dot(N, V), dot(N, L) );\n}\n#endif";
+
+var bumpmap_pars_fragment = "#ifdef USE_BUMPMAP\n\tuniform sampler2D bumpMap;\n\tuniform float bumpScale;\n\tvec2 dHdxy_fwd() {\n\t\tvec2 dSTdx = dFdx( vUv );\n\t\tvec2 dSTdy = dFdy( vUv );\n\t\tfloat Hll = bumpScale * texture2D( bumpMap, vUv ).x;\n\t\tfloat dBx = bumpScale * texture2D( bumpMap, vUv + dSTdx ).x - Hll;\n\t\tfloat dBy = bumpScale * texture2D( bumpMap, vUv + dSTdy ).x - Hll;\n\t\treturn vec2( dBx, dBy );\n\t}\n\tvec3 perturbNormalArb( vec3 surf_pos, vec3 surf_norm, vec2 dHdxy ) {\n\t\tvec3 vSigmaX = vec3( dFdx( surf_pos.x ), dFdx( surf_pos.y ), dFdx( surf_pos.z ) );\n\t\tvec3 vSigmaY = vec3( dFdy( surf_pos.x ), dFdy( surf_pos.y ), dFdy( surf_pos.z ) );\n\t\tvec3 vN = surf_norm;\n\t\tvec3 R1 = cross( vSigmaY, vN );\n\t\tvec3 R2 = cross( vN, vSigmaX );\n\t\tfloat fDet = dot( vSigmaX, R1 );\n\t\tfDet *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\tvec3 vGrad = sign( fDet ) * ( dHdxy.x * R1 + dHdxy.y * R2 );\n\t\treturn normalize( abs( fDet ) * surf_norm - vGrad );\n\t}\n#endif";
+
+var clipping_planes_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvec4 plane;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < UNION_CLIPPING_PLANES; i ++ ) {\n\t\tplane = clippingPlanes[ i ];\n\t\tif ( dot( vClipPosition, plane.xyz ) > plane.w ) discard;\n\t}\n\t#pragma unroll_loop_end\n\t#if UNION_CLIPPING_PLANES < NUM_CLIPPING_PLANES\n\t\tbool clipped = true;\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = UNION_CLIPPING_PLANES; i < NUM_CLIPPING_PLANES; i ++ ) {\n\t\t\tplane = clippingPlanes[ i ];\n\t\t\tclipped = ( dot( vClipPosition, plane.xyz ) > plane.w ) && clipped;\n\t\t}\n\t\t#pragma unroll_loop_end\n\t\tif ( clipped ) discard;\n\t#endif\n#endif";
+
+var clipping_planes_pars_fragment = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n\tuniform vec4 clippingPlanes[ NUM_CLIPPING_PLANES ];\n#endif";
+
+var clipping_planes_pars_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvarying vec3 vClipPosition;\n#endif";
+
+var clipping_planes_vertex = "#if NUM_CLIPPING_PLANES > 0\n\tvClipPosition = - mvPosition.xyz;\n#endif";
+
+var color_fragment = "#ifdef USE_COLOR\n\tdiffuseColor.rgb *= vColor;\n#endif";
+
+var color_pars_fragment = "#ifdef USE_COLOR\n\tvarying vec3 vColor;\n#endif";
+
+var color_pars_vertex = "#if defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvarying vec3 vColor;\n#endif";
+
+var color_vertex = "#if defined( USE_COLOR ) || defined( USE_INSTANCING_COLOR )\n\tvColor = vec3( 1.0 );\n#endif\n#ifdef USE_COLOR\n\tvColor.xyz *= color.xyz;\n#endif\n#ifdef USE_INSTANCING_COLOR\n\tvColor.xyz *= instanceColor.xyz;\n#endif";
+
+var common$1 = "#define PI 3.141592653589793\n#define PI2 6.283185307179586\n#define PI_HALF 1.5707963267948966\n#define RECIPROCAL_PI 0.3183098861837907\n#define RECIPROCAL_PI2 0.15915494309189535\n#define EPSILON 1e-6\n#ifndef saturate\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\n#define whiteComplement(a) ( 1.0 - saturate( a ) )\nfloat pow2( const in float x ) { return x*x; }\nfloat pow3( const in float x ) { return x*x*x; }\nfloat pow4( const in float x ) { float x2 = x*x; return x2*x2; }\nfloat average( const in vec3 color ) { return dot( color, vec3( 0.3333 ) ); }\nhighp float rand( const in vec2 uv ) {\n\tconst highp float a = 12.9898, b = 78.233, c = 43758.5453;\n\thighp float dt = dot( uv.xy, vec2( a,b ) ), sn = mod( dt, PI );\n\treturn fract(sin(sn) * c);\n}\n#ifdef HIGH_PRECISION\n\tfloat precisionSafeLength( vec3 v ) { return length( v ); }\n#else\n\tfloat max3( vec3 v ) { return max( max( v.x, v.y ), v.z ); }\n\tfloat precisionSafeLength( vec3 v ) {\n\t\tfloat maxComponent = max3( abs( v ) );\n\t\treturn length( v / maxComponent ) * maxComponent;\n\t}\n#endif\nstruct IncidentLight {\n\tvec3 color;\n\tvec3 direction;\n\tbool visible;\n};\nstruct ReflectedLight {\n\tvec3 directDiffuse;\n\tvec3 directSpecular;\n\tvec3 indirectDiffuse;\n\tvec3 indirectSpecular;\n};\nstruct GeometricContext {\n\tvec3 position;\n\tvec3 normal;\n\tvec3 viewDir;\n#ifdef CLEARCOAT\n\tvec3 clearcoatNormal;\n#endif\n};\nvec3 transformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( matrix * vec4( dir, 0.0 ) ).xyz );\n}\nvec3 inverseTransformDirection( in vec3 dir, in mat4 matrix ) {\n\treturn normalize( ( vec4( dir, 0.0 ) * matrix ).xyz );\n}\nvec3 projectOnPlane(in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\tfloat distance = dot( planeNormal, point - pointOnPlane );\n\treturn - distance * planeNormal + point;\n}\nfloat sideOfPlane( in vec3 point, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn sign( dot( point - pointOnPlane, planeNormal ) );\n}\nvec3 linePlaneIntersect( in vec3 pointOnLine, in vec3 lineDirection, in vec3 pointOnPlane, in vec3 planeNormal ) {\n\treturn lineDirection * ( dot( planeNormal, pointOnPlane - pointOnLine ) / dot( planeNormal, lineDirection ) ) + pointOnLine;\n}\nmat3 transposeMat3( const in mat3 m ) {\n\tmat3 tmp;\n\ttmp[ 0 ] = vec3( m[ 0 ].x, m[ 1 ].x, m[ 2 ].x );\n\ttmp[ 1 ] = vec3( m[ 0 ].y, m[ 1 ].y, m[ 2 ].y );\n\ttmp[ 2 ] = vec3( m[ 0 ].z, m[ 1 ].z, m[ 2 ].z );\n\treturn tmp;\n}\nfloat linearToRelativeLuminance( const in vec3 color ) {\n\tvec3 weights = vec3( 0.2126, 0.7152, 0.0722 );\n\treturn dot( weights, color.rgb );\n}\nbool isPerspectiveMatrix( mat4 m ) {\n\treturn m[ 2 ][ 3 ] == - 1.0;\n}\nvec2 equirectUv( in vec3 dir ) {\n\tfloat u = atan( dir.z, dir.x ) * RECIPROCAL_PI2 + 0.5;\n\tfloat v = asin( clamp( dir.y, - 1.0, 1.0 ) ) * RECIPROCAL_PI + 0.5;\n\treturn vec2( u, v );\n}";
+
+var cube_uv_reflection_fragment = "#ifdef ENVMAP_TYPE_CUBE_UV\n\t#define cubeUV_maxMipLevel 8.0\n\t#define cubeUV_minMipLevel 4.0\n\t#define cubeUV_maxTileSize 256.0\n\t#define cubeUV_minTileSize 16.0\n\tfloat getFace( vec3 direction ) {\n\t\tvec3 absDirection = abs( direction );\n\t\tfloat face = - 1.0;\n\t\tif ( absDirection.x > absDirection.z ) {\n\t\t\tif ( absDirection.x > absDirection.y )\n\t\t\t\tface = direction.x > 0.0 ? 0.0 : 3.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t} else {\n\t\t\tif ( absDirection.z > absDirection.y )\n\t\t\t\tface = direction.z > 0.0 ? 2.0 : 5.0;\n\t\t\telse\n\t\t\t\tface = direction.y > 0.0 ? 1.0 : 4.0;\n\t\t}\n\t\treturn face;\n\t}\n\tvec2 getUV( vec3 direction, float face ) {\n\t\tvec2 uv;\n\t\tif ( face == 0.0 ) {\n\t\t\tuv = vec2( direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 1.0 ) {\n\t\t\tuv = vec2( - direction.x, - direction.z ) / abs( direction.y );\n\t\t} else if ( face == 2.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.y ) / abs( direction.z );\n\t\t} else if ( face == 3.0 ) {\n\t\t\tuv = vec2( - direction.z, direction.y ) / abs( direction.x );\n\t\t} else if ( face == 4.0 ) {\n\t\t\tuv = vec2( - direction.x, direction.z ) / abs( direction.y );\n\t\t} else {\n\t\t\tuv = vec2( direction.x, direction.y ) / abs( direction.z );\n\t\t}\n\t\treturn 0.5 * ( uv + 1.0 );\n\t}\n\tvec3 bilinearCubeUV( sampler2D envMap, vec3 direction, float mipInt ) {\n\t\tfloat face = getFace( direction );\n\t\tfloat filterInt = max( cubeUV_minMipLevel - mipInt, 0.0 );\n\t\tmipInt = max( mipInt, cubeUV_minMipLevel );\n\t\tfloat faceSize = exp2( mipInt );\n\t\tfloat texelSize = 1.0 / ( 3.0 * cubeUV_maxTileSize );\n\t\tvec2 uv = getUV( direction, face ) * ( faceSize - 1.0 );\n\t\tvec2 f = fract( uv );\n\t\tuv += 0.5 - f;\n\t\tif ( face > 2.0 ) {\n\t\t\tuv.y += faceSize;\n\t\t\tface -= 3.0;\n\t\t}\n\t\tuv.x += face * faceSize;\n\t\tif ( mipInt < cubeUV_maxMipLevel ) {\n\t\t\tuv.y += 2.0 * cubeUV_maxTileSize;\n\t\t}\n\t\tuv.y += filterInt * 2.0 * cubeUV_minTileSize;\n\t\tuv.x += 3.0 * max( 0.0, cubeUV_maxTileSize - 2.0 * faceSize );\n\t\tuv *= texelSize;\n\t\tvec3 tl = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.x += texelSize;\n\t\tvec3 tr = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.y += texelSize;\n\t\tvec3 br = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tuv.x -= texelSize;\n\t\tvec3 bl = envMapTexelToLinear( texture2D( envMap, uv ) ).rgb;\n\t\tvec3 tm = mix( tl, tr, f.x );\n\t\tvec3 bm = mix( bl, br, f.x );\n\t\treturn mix( tm, bm, f.y );\n\t}\n\t#define r0 1.0\n\t#define v0 0.339\n\t#define m0 - 2.0\n\t#define r1 0.8\n\t#define v1 0.276\n\t#define m1 - 1.0\n\t#define r4 0.4\n\t#define v4 0.046\n\t#define m4 2.0\n\t#define r5 0.305\n\t#define v5 0.016\n\t#define m5 3.0\n\t#define r6 0.21\n\t#define v6 0.0038\n\t#define m6 4.0\n\tfloat roughnessToMip( float roughness ) {\n\t\tfloat mip = 0.0;\n\t\tif ( roughness >= r1 ) {\n\t\t\tmip = ( r0 - roughness ) * ( m1 - m0 ) / ( r0 - r1 ) + m0;\n\t\t} else if ( roughness >= r4 ) {\n\t\t\tmip = ( r1 - roughness ) * ( m4 - m1 ) / ( r1 - r4 ) + m1;\n\t\t} else if ( roughness >= r5 ) {\n\t\t\tmip = ( r4 - roughness ) * ( m5 - m4 ) / ( r4 - r5 ) + m4;\n\t\t} else if ( roughness >= r6 ) {\n\t\t\tmip = ( r5 - roughness ) * ( m6 - m5 ) / ( r5 - r6 ) + m5;\n\t\t} else {\n\t\t\tmip = - 2.0 * log2( 1.16 * roughness );\t\t}\n\t\treturn mip;\n\t}\n\tvec4 textureCubeUV( sampler2D envMap, vec3 sampleDir, float roughness ) {\n\t\tfloat mip = clamp( roughnessToMip( roughness ), m0, cubeUV_maxMipLevel );\n\t\tfloat mipF = fract( mip );\n\t\tfloat mipInt = floor( mip );\n\t\tvec3 color0 = bilinearCubeUV( envMap, sampleDir, mipInt );\n\t\tif ( mipF == 0.0 ) {\n\t\t\treturn vec4( color0, 1.0 );\n\t\t} else {\n\t\t\tvec3 color1 = bilinearCubeUV( envMap, sampleDir, mipInt + 1.0 );\n\t\t\treturn vec4( mix( color0, color1, mipF ), 1.0 );\n\t\t}\n\t}\n#endif";
+
+var defaultnormal_vertex = "vec3 transformedNormal = objectNormal;\n#ifdef USE_INSTANCING\n\tmat3 m = mat3( instanceMatrix );\n\ttransformedNormal /= vec3( dot( m[ 0 ], m[ 0 ] ), dot( m[ 1 ], m[ 1 ] ), dot( m[ 2 ], m[ 2 ] ) );\n\ttransformedNormal = m * transformedNormal;\n#endif\ntransformedNormal = normalMatrix * transformedNormal;\n#ifdef FLIP_SIDED\n\ttransformedNormal = - transformedNormal;\n#endif\n#ifdef USE_TANGENT\n\tvec3 transformedTangent = ( modelViewMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#ifdef FLIP_SIDED\n\t\ttransformedTangent = - transformedTangent;\n\t#endif\n#endif";
+
+var displacementmap_pars_vertex = "#ifdef USE_DISPLACEMENTMAP\n\tuniform sampler2D displacementMap;\n\tuniform float displacementScale;\n\tuniform float displacementBias;\n#endif";
+
+var displacementmap_vertex = "#ifdef USE_DISPLACEMENTMAP\n\ttransformed += normalize( objectNormal ) * ( texture2D( displacementMap, vUv ).x * displacementScale + displacementBias );\n#endif";
+
+var emissivemap_fragment = "#ifdef USE_EMISSIVEMAP\n\tvec4 emissiveColor = texture2D( emissiveMap, vUv );\n\temissiveColor.rgb = emissiveMapTexelToLinear( emissiveColor ).rgb;\n\ttotalEmissiveRadiance *= emissiveColor.rgb;\n#endif";
+
+var emissivemap_pars_fragment = "#ifdef USE_EMISSIVEMAP\n\tuniform sampler2D emissiveMap;\n#endif";
+
+var encodings_fragment = "gl_FragColor = linearToOutputTexel( gl_FragColor );";
+
+var encodings_pars_fragment = "\nvec4 LinearToLinear( in vec4 value ) {\n\treturn value;\n}\nvec4 GammaToLinear( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( gammaFactor ) ), value.a );\n}\nvec4 LinearToGamma( in vec4 value, in float gammaFactor ) {\n\treturn vec4( pow( value.rgb, vec3( 1.0 / gammaFactor ) ), value.a );\n}\nvec4 sRGBToLinear( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb * 0.9478672986 + vec3( 0.0521327014 ), vec3( 2.4 ) ), value.rgb * 0.0773993808, vec3( lessThanEqual( value.rgb, vec3( 0.04045 ) ) ) ), value.a );\n}\nvec4 LinearTosRGB( in vec4 value ) {\n\treturn vec4( mix( pow( value.rgb, vec3( 0.41666 ) ) * 1.055 - vec3( 0.055 ), value.rgb * 12.92, vec3( lessThanEqual( value.rgb, vec3( 0.0031308 ) ) ) ), value.a );\n}\nvec4 RGBEToLinear( in vec4 value ) {\n\treturn vec4( value.rgb * exp2( value.a * 255.0 - 128.0 ), 1.0 );\n}\nvec4 LinearToRGBE( in vec4 value ) {\n\tfloat maxComponent = max( max( value.r, value.g ), value.b );\n\tfloat fExp = clamp( ceil( log2( maxComponent ) ), -128.0, 127.0 );\n\treturn vec4( value.rgb / exp2( fExp ), ( fExp + 128.0 ) / 255.0 );\n}\nvec4 RGBMToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * value.a * maxRange, 1.0 );\n}\nvec4 LinearToRGBM( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat M = clamp( maxRGB / maxRange, 0.0, 1.0 );\n\tM = ceil( M * 255.0 ) / 255.0;\n\treturn vec4( value.rgb / ( M * maxRange ), M );\n}\nvec4 RGBDToLinear( in vec4 value, in float maxRange ) {\n\treturn vec4( value.rgb * ( ( maxRange / 255.0 ) / value.a ), 1.0 );\n}\nvec4 LinearToRGBD( in vec4 value, in float maxRange ) {\n\tfloat maxRGB = max( value.r, max( value.g, value.b ) );\n\tfloat D = max( maxRange / maxRGB, 1.0 );\n\tD = clamp( floor( D ) / 255.0, 0.0, 1.0 );\n\treturn vec4( value.rgb * ( D * ( 255.0 / maxRange ) ), D );\n}\nconst mat3 cLogLuvM = mat3( 0.2209, 0.3390, 0.4184, 0.1138, 0.6780, 0.7319, 0.0102, 0.1130, 0.2969 );\nvec4 LinearToLogLuv( in vec4 value ) {\n\tvec3 Xp_Y_XYZp = cLogLuvM * value.rgb;\n\tXp_Y_XYZp = max( Xp_Y_XYZp, vec3( 1e-6, 1e-6, 1e-6 ) );\n\tvec4 vResult;\n\tvResult.xy = Xp_Y_XYZp.xy / Xp_Y_XYZp.z;\n\tfloat Le = 2.0 * log2(Xp_Y_XYZp.y) + 127.0;\n\tvResult.w = fract( Le );\n\tvResult.z = ( Le - ( floor( vResult.w * 255.0 ) ) / 255.0 ) / 255.0;\n\treturn vResult;\n}\nconst mat3 cLogLuvInverseM = mat3( 6.0014, -2.7008, -1.7996, -1.3320, 3.1029, -5.7721, 0.3008, -1.0882, 5.6268 );\nvec4 LogLuvToLinear( in vec4 value ) {\n\tfloat Le = value.z * 255.0 + value.w;\n\tvec3 Xp_Y_XYZp;\n\tXp_Y_XYZp.y = exp2( ( Le - 127.0 ) / 2.0 );\n\tXp_Y_XYZp.z = Xp_Y_XYZp.y / value.y;\n\tXp_Y_XYZp.x = value.x * Xp_Y_XYZp.z;\n\tvec3 vRGB = cLogLuvInverseM * Xp_Y_XYZp.rgb;\n\treturn vec4( max( vRGB, 0.0 ), 1.0 );\n}";
+
+var envmap_fragment = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvec3 cameraToFrag;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToFrag = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToFrag = normalize( vWorldPosition - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( normal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( cameraToFrag, worldNormal );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( cameraToFrag, worldNormal, refractionRatio );\n\t\t#endif\n\t#else\n\t\tvec3 reflectVec = vReflect;\n\t#endif\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tvec4 envColor = textureCube( envMap, vec3( flipEnvMap * reflectVec.x, reflectVec.yz ) );\n\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\tvec4 envColor = textureCubeUV( envMap, reflectVec, 0.0 );\n\t#else\n\t\tvec4 envColor = vec4( 0.0 );\n\t#endif\n\t#ifndef ENVMAP_TYPE_CUBE_UV\n\t\tenvColor = envMapTexelToLinear( envColor );\n\t#endif\n\t#ifdef ENVMAP_BLENDING_MULTIPLY\n\t\toutgoingLight = mix( outgoingLight, outgoingLight * envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_MIX )\n\t\toutgoingLight = mix( outgoingLight, envColor.xyz, specularStrength * reflectivity );\n\t#elif defined( ENVMAP_BLENDING_ADD )\n\t\toutgoingLight += envColor.xyz * specularStrength * reflectivity;\n\t#endif\n#endif";
+
+var envmap_common_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float envMapIntensity;\n\tuniform float flipEnvMap;\n\tuniform int maxMipLevel;\n\t#ifdef ENVMAP_TYPE_CUBE\n\t\tuniform samplerCube envMap;\n\t#else\n\t\tuniform sampler2D envMap;\n\t#endif\n\t\n#endif";
+
+var envmap_pars_fragment = "#ifdef USE_ENVMAP\n\tuniform float reflectivity;\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\tvarying vec3 vWorldPosition;\n\t\tuniform float refractionRatio;\n\t#else\n\t\tvarying vec3 vReflect;\n\t#endif\n#endif";
+
+var envmap_pars_vertex = "#ifdef USE_ENVMAP\n\t#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) ||defined( PHONG )\n\t\t#define ENV_WORLDPOS\n\t#endif\n\t#ifdef ENV_WORLDPOS\n\t\t\n\t\tvarying vec3 vWorldPosition;\n\t#else\n\t\tvarying vec3 vReflect;\n\t\tuniform float refractionRatio;\n\t#endif\n#endif";
+
+var envmap_vertex = "#ifdef USE_ENVMAP\n\t#ifdef ENV_WORLDPOS\n\t\tvWorldPosition = worldPosition.xyz;\n\t#else\n\t\tvec3 cameraToVertex;\n\t\tif ( isOrthographic ) {\n\t\t\tcameraToVertex = normalize( vec3( - viewMatrix[ 0 ][ 2 ], - viewMatrix[ 1 ][ 2 ], - viewMatrix[ 2 ][ 2 ] ) );\n\t\t} else {\n\t\t\tcameraToVertex = normalize( worldPosition.xyz - cameraPosition );\n\t\t}\n\t\tvec3 worldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvReflect = reflect( cameraToVertex, worldNormal );\n\t\t#else\n\t\t\tvReflect = refract( cameraToVertex, worldNormal, refractionRatio );\n\t\t#endif\n\t#endif\n#endif";
+
+var fog_vertex = "#ifdef USE_FOG\n\tfogDepth = - mvPosition.z;\n#endif";
+
+var fog_pars_vertex = "#ifdef USE_FOG\n\tvarying float fogDepth;\n#endif";
+
+var fog_fragment = "#ifdef USE_FOG\n\t#ifdef FOG_EXP2\n\t\tfloat fogFactor = 1.0 - exp( - fogDensity * fogDensity * fogDepth * fogDepth );\n\t#else\n\t\tfloat fogFactor = smoothstep( fogNear, fogFar, fogDepth );\n\t#endif\n\tgl_FragColor.rgb = mix( gl_FragColor.rgb, fogColor, fogFactor );\n#endif";
+
+var fog_pars_fragment = "#ifdef USE_FOG\n\tuniform vec3 fogColor;\n\tvarying float fogDepth;\n\t#ifdef FOG_EXP2\n\t\tuniform float fogDensity;\n\t#else\n\t\tuniform float fogNear;\n\t\tuniform float fogFar;\n\t#endif\n#endif";
+
+var gradientmap_pars_fragment = "#ifdef USE_GRADIENTMAP\n\tuniform sampler2D gradientMap;\n#endif\nvec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {\n\tfloat dotNL = dot( normal, lightDirection );\n\tvec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );\n\t#ifdef USE_GRADIENTMAP\n\t\treturn texture2D( gradientMap, coord ).rgb;\n\t#else\n\t\treturn ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );\n\t#endif\n}";
+
+var lightmap_fragment = "#ifdef USE_LIGHTMAP\n\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\treflectedLight.indirectDiffuse += PI * lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n#endif";
+
+var lightmap_pars_fragment = "#ifdef USE_LIGHTMAP\n\tuniform sampler2D lightMap;\n\tuniform float lightMapIntensity;\n#endif";
+
+var lights_lambert_vertex = "vec3 diffuse = vec3( 1.0 );\nGeometricContext geometry;\ngeometry.position = mvPosition.xyz;\ngeometry.normal = normalize( transformedNormal );\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( -mvPosition.xyz );\nGeometricContext backGeometry;\nbackGeometry.position = geometry.position;\nbackGeometry.normal = -geometry.normal;\nbackGeometry.viewDir = geometry.viewDir;\nvLightFront = vec3( 0.0 );\nvIndirectFront = vec3( 0.0 );\n#ifdef DOUBLE_SIDED\n\tvLightBack = vec3( 0.0 );\n\tvIndirectBack = vec3( 0.0 );\n#endif\nIncidentLight directLight;\nfloat dotNL;\nvec3 directLightColor_Diffuse;\nvIndirectFront += getAmbientLightIrradiance( ambientLightColor );\nvIndirectFront += getLightProbeIrradiance( lightProbe, geometry );\n#ifdef DOUBLE_SIDED\n\tvIndirectBack += getAmbientLightIrradiance( ambientLightColor );\n\tvIndirectBack += getLightProbeIrradiance( lightProbe, backGeometry );\n#endif\n#if NUM_POINT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tgetPointDirectLightIrradiance( pointLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tgetSpotDirectLightIrradiance( spotLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_DIR_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tgetDirectionalDirectLightIrradiance( directionalLights[ i ], geometry, directLight );\n\t\tdotNL = dot( geometry.normal, directLight.direction );\n\t\tdirectLightColor_Diffuse = PI * directLight.color;\n\t\tvLightFront += saturate( dotNL ) * directLightColor_Diffuse;\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvLightBack += saturate( -dotNL ) * directLightColor_Diffuse;\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\tvIndirectFront += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\tvIndirectBack += getHemisphereLightIrradiance( hemisphereLights[ i ], backGeometry );\n\t\t#endif\n\t}\n\t#pragma unroll_loop_end\n#endif";
+
+var lights_pars_begin = "uniform bool receiveShadow;\nuniform vec3 ambientLightColor;\nuniform vec3 lightProbe[ 9 ];\nvec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {\n\tfloat x = normal.x, y = normal.y, z = normal.z;\n\tvec3 result = shCoefficients[ 0 ] * 0.886227;\n\tresult += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;\n\tresult += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;\n\tresult += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;\n\tresult += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;\n\tresult += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;\n\tresult += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );\n\tresult += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;\n\tresult += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );\n\treturn result;\n}\nvec3 getLightProbeIrradiance( const in vec3 lightProbe[ 9 ], const in GeometricContext geometry ) {\n\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\tvec3 irradiance = shGetIrradianceAt( worldNormal, lightProbe );\n\treturn irradiance;\n}\nvec3 getAmbientLightIrradiance( const in vec3 ambientLightColor ) {\n\tvec3 irradiance = ambientLightColor;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treturn irradiance;\n}\n#if NUM_DIR_LIGHTS > 0\n\tstruct DirectionalLight {\n\t\tvec3 direction;\n\t\tvec3 color;\n\t};\n\tuniform DirectionalLight directionalLights[ NUM_DIR_LIGHTS ];\n\tvoid getDirectionalDirectLightIrradiance( const in DirectionalLight directionalLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tdirectLight.color = directionalLight.color;\n\t\tdirectLight.direction = directionalLight.direction;\n\t\tdirectLight.visible = true;\n\t}\n#endif\n#if NUM_POINT_LIGHTS > 0\n\tstruct PointLight {\n\t\tvec3 position;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t};\n\tuniform PointLight pointLights[ NUM_POINT_LIGHTS ];\n\tvoid getPointDirectLightIrradiance( const in PointLight pointLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = pointLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tdirectLight.color = pointLight.color;\n\t\tdirectLight.color *= punctualLightIntensityToIrradianceFactor( lightDistance, pointLight.distance, pointLight.decay );\n\t\tdirectLight.visible = ( directLight.color != vec3( 0.0 ) );\n\t}\n#endif\n#if NUM_SPOT_LIGHTS > 0\n\tstruct SpotLight {\n\t\tvec3 position;\n\t\tvec3 direction;\n\t\tvec3 color;\n\t\tfloat distance;\n\t\tfloat decay;\n\t\tfloat coneCos;\n\t\tfloat penumbraCos;\n\t};\n\tuniform SpotLight spotLights[ NUM_SPOT_LIGHTS ];\n\tvoid getSpotDirectLightIrradiance( const in SpotLight spotLight, const in GeometricContext geometry, out IncidentLight directLight ) {\n\t\tvec3 lVector = spotLight.position - geometry.position;\n\t\tdirectLight.direction = normalize( lVector );\n\t\tfloat lightDistance = length( lVector );\n\t\tfloat angleCos = dot( directLight.direction, spotLight.direction );\n\t\tif ( angleCos > spotLight.coneCos ) {\n\t\t\tfloat spotEffect = smoothstep( spotLight.coneCos, spotLight.penumbraCos, angleCos );\n\t\t\tdirectLight.color = spotLight.color;\n\t\t\tdirectLight.color *= spotEffect * punctualLightIntensityToIrradianceFactor( lightDistance, spotLight.distance, spotLight.decay );\n\t\t\tdirectLight.visible = true;\n\t\t} else {\n\t\t\tdirectLight.color = vec3( 0.0 );\n\t\t\tdirectLight.visible = false;\n\t\t}\n\t}\n#endif\n#if NUM_RECT_AREA_LIGHTS > 0\n\tstruct RectAreaLight {\n\t\tvec3 color;\n\t\tvec3 position;\n\t\tvec3 halfWidth;\n\t\tvec3 halfHeight;\n\t};\n\tuniform sampler2D ltc_1;\tuniform sampler2D ltc_2;\n\tuniform RectAreaLight rectAreaLights[ NUM_RECT_AREA_LIGHTS ];\n#endif\n#if NUM_HEMI_LIGHTS > 0\n\tstruct HemisphereLight {\n\t\tvec3 direction;\n\t\tvec3 skyColor;\n\t\tvec3 groundColor;\n\t};\n\tuniform HemisphereLight hemisphereLights[ NUM_HEMI_LIGHTS ];\n\tvec3 getHemisphereLightIrradiance( const in HemisphereLight hemiLight, const in GeometricContext geometry ) {\n\t\tfloat dotNL = dot( geometry.normal, hemiLight.direction );\n\t\tfloat hemiDiffuseWeight = 0.5 * dotNL + 0.5;\n\t\tvec3 irradiance = mix( hemiLight.groundColor, hemiLight.skyColor, hemiDiffuseWeight );\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tirradiance *= PI;\n\t\t#endif\n\t\treturn irradiance;\n\t}\n#endif";
+
+var envmap_physical_pars_fragment = "#if defined( USE_ENVMAP )\n\t#ifdef ENVMAP_MODE_REFRACTION\n\t\tuniform float refractionRatio;\n\t#endif\n\tvec3 getLightProbeIndirectIrradiance( const in GeometricContext geometry, const in int maxMIPLevel ) {\n\t\tvec3 worldNormal = inverseTransformDirection( geometry.normal, viewMatrix );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryVec = vec3( flipEnvMap * worldNormal.x, worldNormal.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryVec, float( maxMIPLevel ) );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, worldNormal, 1.0 );\n\t\t#else\n\t\t\tvec4 envMapColor = vec4( 0.0 );\n\t\t#endif\n\t\treturn PI * envMapColor.rgb * envMapIntensity;\n\t}\n\tfloat getSpecularMIPLevel( const in float roughness, const in int maxMIPLevel ) {\n\t\tfloat maxMIPLevelScalar = float( maxMIPLevel );\n\t\tfloat sigma = PI * roughness * roughness / ( 1.0 + roughness );\n\t\tfloat desiredMIPLevel = maxMIPLevelScalar + log2( sigma );\n\t\treturn clamp( desiredMIPLevel, 0.0, maxMIPLevelScalar );\n\t}\n\tvec3 getLightProbeIndirectRadiance( const in vec3 viewDir, const in vec3 normal, const in float roughness, const in int maxMIPLevel ) {\n\t\t#ifdef ENVMAP_MODE_REFLECTION\n\t\t\tvec3 reflectVec = reflect( -viewDir, normal );\n\t\t\treflectVec = normalize( mix( reflectVec, normal, roughness * roughness) );\n\t\t#else\n\t\t\tvec3 reflectVec = refract( -viewDir, normal, refractionRatio );\n\t\t#endif\n\t\treflectVec = inverseTransformDirection( reflectVec, viewMatrix );\n\t\tfloat specularMIPLevel = getSpecularMIPLevel( roughness, maxMIPLevel );\n\t\t#ifdef ENVMAP_TYPE_CUBE\n\t\t\tvec3 queryReflectVec = vec3( flipEnvMap * reflectVec.x, reflectVec.yz );\n\t\t\t#ifdef TEXTURE_LOD_EXT\n\t\t\t\tvec4 envMapColor = textureCubeLodEXT( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#else\n\t\t\t\tvec4 envMapColor = textureCube( envMap, queryReflectVec, specularMIPLevel );\n\t\t\t#endif\n\t\t\tenvMapColor.rgb = envMapTexelToLinear( envMapColor ).rgb;\n\t\t#elif defined( ENVMAP_TYPE_CUBE_UV )\n\t\t\tvec4 envMapColor = textureCubeUV( envMap, reflectVec, roughness );\n\t\t#endif\n\t\treturn envMapColor.rgb * envMapIntensity;\n\t}\n#endif";
+
+var lights_toon_fragment = "ToonMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;";
+
+var lights_toon_pars_fragment = "varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct ToonMaterial {\n\tvec3 diffuseColor;\n};\nvoid RE_Direct_Toon( const in IncidentLight directLight, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\tvec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Toon( const in vec3 irradiance, const in GeometricContext geometry, const in ToonMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_Toon\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Toon\n#define Material_LightProbeLOD( material )\t(0)";
+
+var lights_phong_fragment = "BlinnPhongMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb;\nmaterial.specularColor = specular;\nmaterial.specularShininess = shininess;\nmaterial.specularStrength = specularStrength;";
+
+var lights_phong_pars_fragment = "varying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\nstruct BlinnPhongMaterial {\n\tvec3 diffuseColor;\n\tvec3 specularColor;\n\tfloat specularShininess;\n\tfloat specularStrength;\n};\nvoid RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\treflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n\treflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;\n}\nvoid RE_IndirectDiffuse_BlinnPhong( const in vec3 irradiance, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\n#define RE_Direct\t\t\t\tRE_Direct_BlinnPhong\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_BlinnPhong\n#define Material_LightProbeLOD( material )\t(0)";
+
+var lights_physical_fragment = "PhysicalMaterial material;\nmaterial.diffuseColor = diffuseColor.rgb * ( 1.0 - metalnessFactor );\nvec3 dxy = max( abs( dFdx( geometryNormal ) ), abs( dFdy( geometryNormal ) ) );\nfloat geometryRoughness = max( max( dxy.x, dxy.y ), dxy.z );\nmaterial.specularRoughness = max( roughnessFactor, 0.0525 );material.specularRoughness += geometryRoughness;\nmaterial.specularRoughness = min( material.specularRoughness, 1.0 );\n#ifdef REFLECTIVITY\n\tmaterial.specularColor = mix( vec3( MAXIMUM_SPECULAR_COEFFICIENT * pow2( reflectivity ) ), diffuseColor.rgb, metalnessFactor );\n#else\n\tmaterial.specularColor = mix( vec3( DEFAULT_SPECULAR_COEFFICIENT ), diffuseColor.rgb, metalnessFactor );\n#endif\n#ifdef CLEARCOAT\n\tmaterial.clearcoat = clearcoat;\n\tmaterial.clearcoatRoughness = clearcoatRoughness;\n\t#ifdef USE_CLEARCOATMAP\n\t\tmaterial.clearcoat *= texture2D( clearcoatMap, vUv ).x;\n\t#endif\n\t#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\t\tmaterial.clearcoatRoughness *= texture2D( clearcoatRoughnessMap, vUv ).y;\n\t#endif\n\tmaterial.clearcoat = saturate( material.clearcoat );\tmaterial.clearcoatRoughness = max( material.clearcoatRoughness, 0.0525 );\n\tmaterial.clearcoatRoughness += geometryRoughness;\n\tmaterial.clearcoatRoughness = min( material.clearcoatRoughness, 1.0 );\n#endif\n#ifdef USE_SHEEN\n\tmaterial.sheenColor = sheen;\n#endif";
+
+var lights_physical_pars_fragment = "struct PhysicalMaterial {\n\tvec3 diffuseColor;\n\tfloat specularRoughness;\n\tvec3 specularColor;\n#ifdef CLEARCOAT\n\tfloat clearcoat;\n\tfloat clearcoatRoughness;\n#endif\n#ifdef USE_SHEEN\n\tvec3 sheenColor;\n#endif\n};\n#define MAXIMUM_SPECULAR_COEFFICIENT 0.16\n#define DEFAULT_SPECULAR_COEFFICIENT 0.04\nfloat clearcoatDHRApprox( const in float roughness, const in float dotNL ) {\n\treturn DEFAULT_SPECULAR_COEFFICIENT + ( 1.0 - DEFAULT_SPECULAR_COEFFICIENT ) * ( pow( 1.0 - dotNL, 5.0 ) * pow( 1.0 - roughness, 2.0 ) );\n}\n#if NUM_RECT_AREA_LIGHTS > 0\n\tvoid RE_Direct_RectArea_Physical( const in RectAreaLight rectAreaLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\t\tvec3 normal = geometry.normal;\n\t\tvec3 viewDir = geometry.viewDir;\n\t\tvec3 position = geometry.position;\n\t\tvec3 lightPos = rectAreaLight.position;\n\t\tvec3 halfWidth = rectAreaLight.halfWidth;\n\t\tvec3 halfHeight = rectAreaLight.halfHeight;\n\t\tvec3 lightColor = rectAreaLight.color;\n\t\tfloat roughness = material.specularRoughness;\n\t\tvec3 rectCoords[ 4 ];\n\t\trectCoords[ 0 ] = lightPos + halfWidth - halfHeight;\t\trectCoords[ 1 ] = lightPos - halfWidth - halfHeight;\n\t\trectCoords[ 2 ] = lightPos - halfWidth + halfHeight;\n\t\trectCoords[ 3 ] = lightPos + halfWidth + halfHeight;\n\t\tvec2 uv = LTC_Uv( normal, viewDir, roughness );\n\t\tvec4 t1 = texture2D( ltc_1, uv );\n\t\tvec4 t2 = texture2D( ltc_2, uv );\n\t\tmat3 mInv = mat3(\n\t\t\tvec3( t1.x, 0, t1.y ),\n\t\t\tvec3( 0, 1, 0 ),\n\t\t\tvec3( t1.z, 0, t1.w )\n\t\t);\n\t\tvec3 fresnel = ( material.specularColor * t2.x + ( vec3( 1.0 ) - material.specularColor ) * t2.y );\n\t\treflectedLight.directSpecular += lightColor * fresnel * LTC_Evaluate( normal, viewDir, position, mInv, rectCoords );\n\t\treflectedLight.directDiffuse += lightColor * material.diffuseColor * LTC_Evaluate( normal, viewDir, position, mat3( 1.0 ), rectCoords );\n\t}\n#endif\nvoid RE_Direct_Physical( const in IncidentLight directLight, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\tfloat dotNL = saturate( dot( geometry.normal, directLight.direction ) );\n\tvec3 irradiance = dotNL * directLight.color;\n\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\tirradiance *= PI;\n\t#endif\n\t#ifdef CLEARCOAT\n\t\tfloat ccDotNL = saturate( dot( geometry.clearcoatNormal, directLight.direction ) );\n\t\tvec3 ccIrradiance = ccDotNL * directLight.color;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tccIrradiance *= PI;\n\t\t#endif\n\t\tfloat clearcoatDHR = material.clearcoat * clearcoatDHRApprox( material.clearcoatRoughness, ccDotNL );\n\t\treflectedLight.directSpecular += ccIrradiance * material.clearcoat * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearcoatRoughness );\n\t#else\n\t\tfloat clearcoatDHR = 0.0;\n\t#endif\n\t#ifdef USE_SHEEN\n\t\treflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_Sheen(\n\t\t\tmaterial.specularRoughness,\n\t\t\tdirectLight.direction,\n\t\t\tgeometry,\n\t\t\tmaterial.sheenColor\n\t\t);\n\t#else\n\t\treflectedLight.directSpecular += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Specular_GGX( directLight, geometry.viewDir, geometry.normal, material.specularColor, material.specularRoughness);\n\t#endif\n\treflectedLight.directDiffuse += ( 1.0 - clearcoatDHR ) * irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectDiffuse_Physical( const in vec3 irradiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight ) {\n\treflectedLight.indirectDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );\n}\nvoid RE_IndirectSpecular_Physical( const in vec3 radiance, const in vec3 irradiance, const in vec3 clearcoatRadiance, const in GeometricContext geometry, const in PhysicalMaterial material, inout ReflectedLight reflectedLight) {\n\t#ifdef CLEARCOAT\n\t\tfloat ccDotNV = saturate( dot( geometry.clearcoatNormal, geometry.viewDir ) );\n\t\treflectedLight.indirectSpecular += clearcoatRadiance * material.clearcoat * BRDF_Specular_GGX_Environment( geometry.viewDir, geometry.clearcoatNormal, vec3( DEFAULT_SPECULAR_COEFFICIENT ), material.clearcoatRoughness );\n\t\tfloat ccDotNL = ccDotNV;\n\t\tfloat clearcoatDHR = material.clearcoat * clearcoatDHRApprox( material.clearcoatRoughness, ccDotNL );\n\t#else\n\t\tfloat clearcoatDHR = 0.0;\n\t#endif\n\tfloat clearcoatInv = 1.0 - clearcoatDHR;\n\tvec3 singleScattering = vec3( 0.0 );\n\tvec3 multiScattering = vec3( 0.0 );\n\tvec3 cosineWeightedIrradiance = irradiance * RECIPROCAL_PI;\n\tBRDF_Specular_Multiscattering_Environment( geometry, material.specularColor, material.specularRoughness, singleScattering, multiScattering );\n\tvec3 diffuse = material.diffuseColor * ( 1.0 - ( singleScattering + multiScattering ) );\n\treflectedLight.indirectSpecular += clearcoatInv * radiance * singleScattering;\n\treflectedLight.indirectSpecular += multiScattering * cosineWeightedIrradiance;\n\treflectedLight.indirectDiffuse += diffuse * cosineWeightedIrradiance;\n}\n#define RE_Direct\t\t\t\tRE_Direct_Physical\n#define RE_Direct_RectArea\t\tRE_Direct_RectArea_Physical\n#define RE_IndirectDiffuse\t\tRE_IndirectDiffuse_Physical\n#define RE_IndirectSpecular\t\tRE_IndirectSpecular_Physical\nfloat computeSpecularOcclusion( const in float dotNV, const in float ambientOcclusion, const in float roughness ) {\n\treturn saturate( pow( dotNV + ambientOcclusion, exp2( - 16.0 * roughness - 1.0 ) ) - 1.0 + ambientOcclusion );\n}";
+
+var lights_fragment_begin = "\nGeometricContext geometry;\ngeometry.position = - vViewPosition;\ngeometry.normal = normal;\ngeometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );\n#ifdef CLEARCOAT\n\tgeometry.clearcoatNormal = clearcoatNormal;\n#endif\nIncidentLight directLight;\n#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )\n\tPointLight pointLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {\n\t\tpointLight = pointLights[ i ];\n\t\tgetPointDirectLightIrradiance( pointLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )\n\t\tpointLightShadow = pointLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )\n\tSpotLight spotLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {\n\t\tspotLight = spotLights[ i ];\n\t\tgetSpotDirectLightIrradiance( spotLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )\n\t\tspotLightShadow = spotLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct )\n\tDirectionalLight directionalLight;\n\t#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLightShadow;\n\t#endif\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {\n\t\tdirectionalLight = directionalLights[ i ];\n\t\tgetDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );\n\t\t#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )\n\t\tdirectionalLightShadow = directionalLightShadows[ i ];\n\t\tdirectLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t\t#endif\n\t\tRE_Direct( directLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )\n\tRectAreaLight rectAreaLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {\n\t\trectAreaLight = rectAreaLights[ i ];\n\t\tRE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );\n\t}\n\t#pragma unroll_loop_end\n#endif\n#if defined( RE_IndirectDiffuse )\n\tvec3 iblIrradiance = vec3( 0.0 );\n\tvec3 irradiance = getAmbientLightIrradiance( ambientLightColor );\n\tirradiance += getLightProbeIrradiance( lightProbe, geometry );\n\t#if ( NUM_HEMI_LIGHTS > 0 )\n\t\t#pragma unroll_loop_start\n\t\tfor ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {\n\t\t\tirradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );\n\t\t}\n\t\t#pragma unroll_loop_end\n\t#endif\n#endif\n#if defined( RE_IndirectSpecular )\n\tvec3 radiance = vec3( 0.0 );\n\tvec3 clearcoatRadiance = vec3( 0.0 );\n#endif";
+
+var lights_fragment_maps = "#if defined( RE_IndirectDiffuse )\n\t#ifdef USE_LIGHTMAP\n\t\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\t\tvec3 lightMapIrradiance = lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t\t#ifndef PHYSICALLY_CORRECT_LIGHTS\n\t\t\tlightMapIrradiance *= PI;\n\t\t#endif\n\t\tirradiance += lightMapIrradiance;\n\t#endif\n\t#if defined( USE_ENVMAP ) && defined( STANDARD ) && defined( ENVMAP_TYPE_CUBE_UV )\n\t\tiblIrradiance += getLightProbeIndirectIrradiance( geometry, maxMipLevel );\n\t#endif\n#endif\n#if defined( USE_ENVMAP ) && defined( RE_IndirectSpecular )\n\tradiance += getLightProbeIndirectRadiance( geometry.viewDir, geometry.normal, material.specularRoughness, maxMipLevel );\n\t#ifdef CLEARCOAT\n\t\tclearcoatRadiance += getLightProbeIndirectRadiance( geometry.viewDir, geometry.clearcoatNormal, material.clearcoatRoughness, maxMipLevel );\n\t#endif\n#endif";
+
+var lights_fragment_end = "#if defined( RE_IndirectDiffuse )\n\tRE_IndirectDiffuse( irradiance, geometry, material, reflectedLight );\n#endif\n#if defined( RE_IndirectSpecular )\n\tRE_IndirectSpecular( radiance, iblIrradiance, clearcoatRadiance, geometry, material, reflectedLight );\n#endif";
+
+var logdepthbuf_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tgl_FragDepthEXT = vIsPerspective == 0.0 ? gl_FragCoord.z : log2( vFragDepth ) * logDepthBufFC * 0.5;\n#endif";
+
+var logdepthbuf_pars_fragment = "#if defined( USE_LOGDEPTHBUF ) && defined( USE_LOGDEPTHBUF_EXT )\n\tuniform float logDepthBufFC;\n\tvarying float vFragDepth;\n\tvarying float vIsPerspective;\n#endif";
+
+var logdepthbuf_pars_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvarying float vFragDepth;\n\t\tvarying float vIsPerspective;\n\t#else\n\t\tuniform float logDepthBufFC;\n\t#endif\n#endif";
+
+var logdepthbuf_vertex = "#ifdef USE_LOGDEPTHBUF\n\t#ifdef USE_LOGDEPTHBUF_EXT\n\t\tvFragDepth = 1.0 + gl_Position.w;\n\t\tvIsPerspective = float( isPerspectiveMatrix( projectionMatrix ) );\n\t#else\n\t\tif ( isPerspectiveMatrix( projectionMatrix ) ) {\n\t\t\tgl_Position.z = log2( max( EPSILON, gl_Position.w + 1.0 ) ) * logDepthBufFC - 1.0;\n\t\t\tgl_Position.z *= gl_Position.w;\n\t\t}\n\t#endif\n#endif";
+
+var map_fragment = "#ifdef USE_MAP\n\tvec4 texelColor = texture2D( map, vUv );\n\ttexelColor = mapTexelToLinear( texelColor );\n\tdiffuseColor *= texelColor;\n#endif";
+
+var map_pars_fragment = "#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif";
+
+var map_particle_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tvec2 uv = ( uvTransform * vec3( gl_PointCoord.x, 1.0 - gl_PointCoord.y, 1 ) ).xy;\n#endif\n#ifdef USE_MAP\n\tvec4 mapTexel = texture2D( map, uv );\n\tdiffuseColor *= mapTexelToLinear( mapTexel );\n#endif\n#ifdef USE_ALPHAMAP\n\tdiffuseColor.a *= texture2D( alphaMap, uv ).g;\n#endif";
+
+var map_particle_pars_fragment = "#if defined( USE_MAP ) || defined( USE_ALPHAMAP )\n\tuniform mat3 uvTransform;\n#endif\n#ifdef USE_MAP\n\tuniform sampler2D map;\n#endif\n#ifdef USE_ALPHAMAP\n\tuniform sampler2D alphaMap;\n#endif";
+
+var metalnessmap_fragment = "float metalnessFactor = metalness;\n#ifdef USE_METALNESSMAP\n\tvec4 texelMetalness = texture2D( metalnessMap, vUv );\n\tmetalnessFactor *= texelMetalness.b;\n#endif";
+
+var metalnessmap_pars_fragment = "#ifdef USE_METALNESSMAP\n\tuniform sampler2D metalnessMap;\n#endif";
+
+var morphnormal_vertex = "#ifdef USE_MORPHNORMALS\n\tobjectNormal *= morphTargetBaseInfluence;\n\tobjectNormal += morphNormal0 * morphTargetInfluences[ 0 ];\n\tobjectNormal += morphNormal1 * morphTargetInfluences[ 1 ];\n\tobjectNormal += morphNormal2 * morphTargetInfluences[ 2 ];\n\tobjectNormal += morphNormal3 * morphTargetInfluences[ 3 ];\n#endif";
+
+var morphtarget_pars_vertex = "#ifdef USE_MORPHTARGETS\n\tuniform float morphTargetBaseInfluence;\n\t#ifndef USE_MORPHNORMALS\n\t\tuniform float morphTargetInfluences[ 8 ];\n\t#else\n\t\tuniform float morphTargetInfluences[ 4 ];\n\t#endif\n#endif";
+
+var morphtarget_vertex = "#ifdef USE_MORPHTARGETS\n\ttransformed *= morphTargetBaseInfluence;\n\ttransformed += morphTarget0 * morphTargetInfluences[ 0 ];\n\ttransformed += morphTarget1 * morphTargetInfluences[ 1 ];\n\ttransformed += morphTarget2 * morphTargetInfluences[ 2 ];\n\ttransformed += morphTarget3 * morphTargetInfluences[ 3 ];\n\t#ifndef USE_MORPHNORMALS\n\t\ttransformed += morphTarget4 * morphTargetInfluences[ 4 ];\n\t\ttransformed += morphTarget5 * morphTargetInfluences[ 5 ];\n\t\ttransformed += morphTarget6 * morphTargetInfluences[ 6 ];\n\t\ttransformed += morphTarget7 * morphTargetInfluences[ 7 ];\n\t#endif\n#endif";
+
+var normal_fragment_begin = "#ifdef FLAT_SHADED\n\tvec3 fdx = vec3( dFdx( vViewPosition.x ), dFdx( vViewPosition.y ), dFdx( vViewPosition.z ) );\n\tvec3 fdy = vec3( dFdy( vViewPosition.x ), dFdy( vViewPosition.y ), dFdy( vViewPosition.z ) );\n\tvec3 normal = normalize( cross( fdx, fdy ) );\n#else\n\tvec3 normal = normalize( vNormal );\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n\t#ifdef USE_TANGENT\n\t\tvec3 tangent = normalize( vTangent );\n\t\tvec3 bitangent = normalize( vBitangent );\n\t\t#ifdef DOUBLE_SIDED\n\t\t\ttangent = tangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t\tbitangent = bitangent * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\t#endif\n\t\t#if defined( TANGENTSPACE_NORMALMAP ) || defined( USE_CLEARCOAT_NORMALMAP )\n\t\t\tmat3 vTBN = mat3( tangent, bitangent, normal );\n\t\t#endif\n\t#endif\n#endif\nvec3 geometryNormal = normal;";
+
+var normal_fragment_maps = "#ifdef OBJECTSPACE_NORMALMAP\n\tnormal = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\t#ifdef FLIP_SIDED\n\t\tnormal = - normal;\n\t#endif\n\t#ifdef DOUBLE_SIDED\n\t\tnormal = normal * ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t#endif\n\tnormal = normalize( normalMatrix * normal );\n#elif defined( TANGENTSPACE_NORMALMAP )\n\tvec3 mapN = texture2D( normalMap, vUv ).xyz * 2.0 - 1.0;\n\tmapN.xy *= normalScale;\n\t#ifdef USE_TANGENT\n\t\tnormal = normalize( vTBN * mapN );\n\t#else\n\t\tnormal = perturbNormal2Arb( -vViewPosition, normal, mapN );\n\t#endif\n#elif defined( USE_BUMPMAP )\n\tnormal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );\n#endif";
+
+var normalmap_pars_fragment = "#ifdef USE_NORMALMAP\n\tuniform sampler2D normalMap;\n\tuniform vec2 normalScale;\n#endif\n#ifdef OBJECTSPACE_NORMALMAP\n\tuniform mat3 normalMatrix;\n#endif\n#if ! defined ( USE_TANGENT ) && ( defined ( TANGENTSPACE_NORMALMAP ) || defined ( USE_CLEARCOAT_NORMALMAP ) )\n\tvec3 perturbNormal2Arb( vec3 eye_pos, vec3 surf_norm, vec3 mapN ) {\n\t\tvec3 q0 = vec3( dFdx( eye_pos.x ), dFdx( eye_pos.y ), dFdx( eye_pos.z ) );\n\t\tvec3 q1 = vec3( dFdy( eye_pos.x ), dFdy( eye_pos.y ), dFdy( eye_pos.z ) );\n\t\tvec2 st0 = dFdx( vUv.st );\n\t\tvec2 st1 = dFdy( vUv.st );\n\t\tfloat scale = sign( st1.t * st0.s - st0.t * st1.s );\n\t\tvec3 S = normalize( ( q0 * st1.t - q1 * st0.t ) * scale );\n\t\tvec3 T = normalize( ( - q0 * st1.s + q1 * st0.s ) * scale );\n\t\tvec3 N = normalize( surf_norm );\n\t\tmat3 tsn = mat3( S, T, N );\n\t\tmapN.xy *= ( float( gl_FrontFacing ) * 2.0 - 1.0 );\n\t\treturn normalize( tsn * mapN );\n\t}\n#endif";
+
+var clearcoat_normal_fragment_begin = "#ifdef CLEARCOAT\n\tvec3 clearcoatNormal = geometryNormal;\n#endif";
+
+var clearcoat_normal_fragment_maps = "#ifdef USE_CLEARCOAT_NORMALMAP\n\tvec3 clearcoatMapN = texture2D( clearcoatNormalMap, vUv ).xyz * 2.0 - 1.0;\n\tclearcoatMapN.xy *= clearcoatNormalScale;\n\t#ifdef USE_TANGENT\n\t\tclearcoatNormal = normalize( vTBN * clearcoatMapN );\n\t#else\n\t\tclearcoatNormal = perturbNormal2Arb( - vViewPosition, clearcoatNormal, clearcoatMapN );\n\t#endif\n#endif";
+
+var clearcoat_pars_fragment = "#ifdef USE_CLEARCOATMAP\n\tuniform sampler2D clearcoatMap;\n#endif\n#ifdef USE_CLEARCOAT_ROUGHNESSMAP\n\tuniform sampler2D clearcoatRoughnessMap;\n#endif\n#ifdef USE_CLEARCOAT_NORMALMAP\n\tuniform sampler2D clearcoatNormalMap;\n\tuniform vec2 clearcoatNormalScale;\n#endif";
+
+var packing = "vec3 packNormalToRGB( const in vec3 normal ) {\n\treturn normalize( normal ) * 0.5 + 0.5;\n}\nvec3 unpackRGBToNormal( const in vec3 rgb ) {\n\treturn 2.0 * rgb.xyz - 1.0;\n}\nconst float PackUpscale = 256. / 255.;const float UnpackDownscale = 255. / 256.;\nconst vec3 PackFactors = vec3( 256. * 256. * 256., 256. * 256., 256. );\nconst vec4 UnpackFactors = UnpackDownscale / vec4( PackFactors, 1. );\nconst float ShiftRight8 = 1. / 256.;\nvec4 packDepthToRGBA( const in float v ) {\n\tvec4 r = vec4( fract( v * PackFactors ), v );\n\tr.yzw -= r.xyz * ShiftRight8;\treturn r * PackUpscale;\n}\nfloat unpackRGBAToDepth( const in vec4 v ) {\n\treturn dot( v, UnpackFactors );\n}\nvec4 pack2HalfToRGBA( vec2 v ) {\n\tvec4 r = vec4( v.x, fract( v.x * 255.0 ), v.y, fract( v.y * 255.0 ));\n\treturn vec4( r.x - r.y / 255.0, r.y, r.z - r.w / 255.0, r.w);\n}\nvec2 unpackRGBATo2Half( vec4 v ) {\n\treturn vec2( v.x + ( v.y / 255.0 ), v.z + ( v.w / 255.0 ) );\n}\nfloat viewZToOrthographicDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn ( viewZ + near ) / ( near - far );\n}\nfloat orthographicDepthToViewZ( const in float linearClipZ, const in float near, const in float far ) {\n\treturn linearClipZ * ( near - far ) - near;\n}\nfloat viewZToPerspectiveDepth( const in float viewZ, const in float near, const in float far ) {\n\treturn (( near + viewZ ) * far ) / (( far - near ) * viewZ );\n}\nfloat perspectiveDepthToViewZ( const in float invClipZ, const in float near, const in float far ) {\n\treturn ( near * far ) / ( ( far - near ) * invClipZ - far );\n}";
+
+var premultiplied_alpha_fragment = "#ifdef PREMULTIPLIED_ALPHA\n\tgl_FragColor.rgb *= gl_FragColor.a;\n#endif";
+
+var project_vertex = "vec4 mvPosition = vec4( transformed, 1.0 );\n#ifdef USE_INSTANCING\n\tmvPosition = instanceMatrix * mvPosition;\n#endif\nmvPosition = modelViewMatrix * mvPosition;\ngl_Position = projectionMatrix * mvPosition;";
+
+var dithering_fragment = "#ifdef DITHERING\n\tgl_FragColor.rgb = dithering( gl_FragColor.rgb );\n#endif";
+
+var dithering_pars_fragment = "#ifdef DITHERING\n\tvec3 dithering( vec3 color ) {\n\t\tfloat grid_position = rand( gl_FragCoord.xy );\n\t\tvec3 dither_shift_RGB = vec3( 0.25 / 255.0, -0.25 / 255.0, 0.25 / 255.0 );\n\t\tdither_shift_RGB = mix( 2.0 * dither_shift_RGB, -2.0 * dither_shift_RGB, grid_position );\n\t\treturn color + dither_shift_RGB;\n\t}\n#endif";
+
+var roughnessmap_fragment = "float roughnessFactor = roughness;\n#ifdef USE_ROUGHNESSMAP\n\tvec4 texelRoughness = texture2D( roughnessMap, vUv );\n\troughnessFactor *= texelRoughness.g;\n#endif";
+
+var roughnessmap_pars_fragment = "#ifdef USE_ROUGHNESSMAP\n\tuniform sampler2D roughnessMap;\n#endif";
+
+var shadowmap_pars_fragment = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D directionalShadowMap[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D spotShadowMap[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform sampler2D pointShadowMap[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n\tfloat texture2DCompare( sampler2D depths, vec2 uv, float compare ) {\n\t\treturn step( compare, unpackRGBAToDepth( texture2D( depths, uv ) ) );\n\t}\n\tvec2 texture2DDistribution( sampler2D shadow, vec2 uv ) {\n\t\treturn unpackRGBATo2Half( texture2D( shadow, uv ) );\n\t}\n\tfloat VSMShadow (sampler2D shadow, vec2 uv, float compare ){\n\t\tfloat occlusion = 1.0;\n\t\tvec2 distribution = texture2DDistribution( shadow, uv );\n\t\tfloat hard_shadow = step( compare , distribution.x );\n\t\tif (hard_shadow != 1.0 ) {\n\t\t\tfloat distance = compare - distribution.x ;\n\t\t\tfloat variance = max( 0.00000, distribution.y * distribution.y );\n\t\t\tfloat softness_probability = variance / (variance + distance * distance );\t\t\tsoftness_probability = clamp( ( softness_probability - 0.3 ) / ( 0.95 - 0.3 ), 0.0, 1.0 );\t\t\tocclusion = clamp( max( hard_shadow, softness_probability ), 0.0, 1.0 );\n\t\t}\n\t\treturn occlusion;\n\t}\n\tfloat getShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord ) {\n\t\tfloat shadow = 1.0;\n\t\tshadowCoord.xyz /= shadowCoord.w;\n\t\tshadowCoord.z += shadowBias;\n\t\tbvec4 inFrustumVec = bvec4 ( shadowCoord.x >= 0.0, shadowCoord.x <= 1.0, shadowCoord.y >= 0.0, shadowCoord.y <= 1.0 );\n\t\tbool inFrustum = all( inFrustumVec );\n\t\tbvec2 frustumTestVec = bvec2( inFrustum, shadowCoord.z <= 1.0 );\n\t\tbool frustumTest = all( frustumTestVec );\n\t\tif ( frustumTest ) {\n\t\t#if defined( SHADOWMAP_TYPE_PCF )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx0 = - texelSize.x * shadowRadius;\n\t\t\tfloat dy0 = - texelSize.y * shadowRadius;\n\t\t\tfloat dx1 = + texelSize.x * shadowRadius;\n\t\t\tfloat dy1 = + texelSize.y * shadowRadius;\n\t\t\tfloat dx2 = dx0 / 2.0;\n\t\t\tfloat dy2 = dy0 / 2.0;\n\t\t\tfloat dx3 = dx1 / 2.0;\n\t\t\tfloat dy3 = dy1 / 2.0;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy2 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx2, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx3, dy3 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( 0.0, dy1 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, shadowCoord.xy + vec2( dx1, dy1 ), shadowCoord.z )\n\t\t\t) * ( 1.0 / 17.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_PCF_SOFT )\n\t\t\tvec2 texelSize = vec2( 1.0 ) / shadowMapSize;\n\t\t\tfloat dx = texelSize.x;\n\t\t\tfloat dy = texelSize.y;\n\t\t\tvec2 uv = shadowCoord.xy;\n\t\t\tvec2 f = fract( uv * shadowMapSize + 0.5 );\n\t\t\tuv -= f * texelSize;\n\t\t\tshadow = (\n\t\t\t\ttexture2DCompare( shadowMap, uv, shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( dx, 0.0 ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + vec2( 0.0, dy ), shadowCoord.z ) +\n\t\t\t\ttexture2DCompare( shadowMap, uv + texelSize, shadowCoord.z ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, 0.0 ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 0.0 ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( -dx, dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, dy ), shadowCoord.z ),\n\t\t\t\t\t f.x ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( 0.0, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 0.0, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( texture2DCompare( shadowMap, uv + vec2( dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t f.y ) +\n\t\t\t\tmix( mix( texture2DCompare( shadowMap, uv + vec2( -dx, -dy ), shadowCoord.z ), \n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, -dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t mix( texture2DCompare( shadowMap, uv + vec2( -dx, 2.0 * dy ), shadowCoord.z ), \n\t\t\t\t\t\t texture2DCompare( shadowMap, uv + vec2( 2.0 * dx, 2.0 * dy ), shadowCoord.z ),\n\t\t\t\t\t\t f.x ),\n\t\t\t\t\t f.y )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#elif defined( SHADOWMAP_TYPE_VSM )\n\t\t\tshadow = VSMShadow( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#else\n\t\t\tshadow = texture2DCompare( shadowMap, shadowCoord.xy, shadowCoord.z );\n\t\t#endif\n\t\t}\n\t\treturn shadow;\n\t}\n\tvec2 cubeToUV( vec3 v, float texelSizeY ) {\n\t\tvec3 absV = abs( v );\n\t\tfloat scaleToCube = 1.0 / max( absV.x, max( absV.y, absV.z ) );\n\t\tabsV *= scaleToCube;\n\t\tv *= scaleToCube * ( 1.0 - 2.0 * texelSizeY );\n\t\tvec2 planar = v.xy;\n\t\tfloat almostATexel = 1.5 * texelSizeY;\n\t\tfloat almostOne = 1.0 - almostATexel;\n\t\tif ( absV.z >= almostOne ) {\n\t\t\tif ( v.z > 0.0 )\n\t\t\t\tplanar.x = 4.0 - v.x;\n\t\t} else if ( absV.x >= almostOne ) {\n\t\t\tfloat signX = sign( v.x );\n\t\t\tplanar.x = v.z * signX + 2.0 * signX;\n\t\t} else if ( absV.y >= almostOne ) {\n\t\t\tfloat signY = sign( v.y );\n\t\t\tplanar.x = v.x + 2.0 * signY + 2.0;\n\t\t\tplanar.y = v.z * signY - 2.0;\n\t\t}\n\t\treturn vec2( 0.125, 0.25 ) * planar + vec2( 0.375, 0.75 );\n\t}\n\tfloat getPointShadow( sampler2D shadowMap, vec2 shadowMapSize, float shadowBias, float shadowRadius, vec4 shadowCoord, float shadowCameraNear, float shadowCameraFar ) {\n\t\tvec2 texelSize = vec2( 1.0 ) / ( shadowMapSize * vec2( 4.0, 2.0 ) );\n\t\tvec3 lightToPosition = shadowCoord.xyz;\n\t\tfloat dp = ( length( lightToPosition ) - shadowCameraNear ) / ( shadowCameraFar - shadowCameraNear );\t\tdp += shadowBias;\n\t\tvec3 bd3D = normalize( lightToPosition );\n\t\t#if defined( SHADOWMAP_TYPE_PCF ) || defined( SHADOWMAP_TYPE_PCF_SOFT ) || defined( SHADOWMAP_TYPE_VSM )\n\t\t\tvec2 offset = vec2( - 1, 1 ) * shadowRadius * texelSize.y;\n\t\t\treturn (\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yyx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxy, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.xxx, texelSize.y ), dp ) +\n\t\t\t\ttexture2DCompare( shadowMap, cubeToUV( bd3D + offset.yxx, texelSize.y ), dp )\n\t\t\t) * ( 1.0 / 9.0 );\n\t\t#else\n\t\t\treturn texture2DCompare( shadowMap, cubeToUV( bd3D, texelSize.y ), dp );\n\t\t#endif\n\t}\n#endif";
+
+var shadowmap_pars_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t\tuniform mat4 directionalShadowMatrix[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tvarying vec4 vDirectionalShadowCoord[ NUM_DIR_LIGHT_SHADOWS ];\n\t\tstruct DirectionalLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform DirectionalLightShadow directionalLightShadows[ NUM_DIR_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 spotShadowMatrix[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vSpotShadowCoord[ NUM_SPOT_LIGHT_SHADOWS ];\n\t\tstruct SpotLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t};\n\t\tuniform SpotLightShadow spotLightShadows[ NUM_SPOT_LIGHT_SHADOWS ];\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t\tuniform mat4 pointShadowMatrix[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tvarying vec4 vPointShadowCoord[ NUM_POINT_LIGHT_SHADOWS ];\n\t\tstruct PointLightShadow {\n\t\t\tfloat shadowBias;\n\t\t\tfloat shadowNormalBias;\n\t\t\tfloat shadowRadius;\n\t\t\tvec2 shadowMapSize;\n\t\t\tfloat shadowCameraNear;\n\t\t\tfloat shadowCameraFar;\n\t\t};\n\t\tuniform PointLightShadow pointLightShadows[ NUM_POINT_LIGHT_SHADOWS ];\n\t#endif\n#endif";
+
+var shadowmap_vertex = "#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0 || NUM_SPOT_LIGHT_SHADOWS > 0 || NUM_POINT_LIGHT_SHADOWS > 0\n\t\tvec3 shadowWorldNormal = inverseTransformDirection( transformedNormal, viewMatrix );\n\t\tvec4 shadowWorldPosition;\n\t#endif\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * directionalLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvDirectionalShadowCoord[ i ] = directionalShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * spotLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvSpotShadowCoord[ i ] = spotShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tshadowWorldPosition = worldPosition + vec4( shadowWorldNormal * pointLightShadows[ i ].shadowNormalBias, 0 );\n\t\tvPointShadowCoord[ i ] = pointShadowMatrix[ i ] * shadowWorldPosition;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n#endif";
+
+var shadowmask_pars_fragment = "float getShadowMask() {\n\tfloat shadow = 1.0;\n\t#ifdef USE_SHADOWMAP\n\t#if NUM_DIR_LIGHT_SHADOWS > 0\n\tDirectionalLightShadow directionalLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_DIR_LIGHT_SHADOWS; i ++ ) {\n\t\tdirectionalLight = directionalLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( directionalShadowMap[ i ], directionalLight.shadowMapSize, directionalLight.shadowBias, directionalLight.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_SPOT_LIGHT_SHADOWS > 0\n\tSpotLightShadow spotLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_SPOT_LIGHT_SHADOWS; i ++ ) {\n\t\tspotLight = spotLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getShadow( spotShadowMap[ i ], spotLight.shadowMapSize, spotLight.shadowBias, spotLight.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#if NUM_POINT_LIGHT_SHADOWS > 0\n\tPointLightShadow pointLight;\n\t#pragma unroll_loop_start\n\tfor ( int i = 0; i < NUM_POINT_LIGHT_SHADOWS; i ++ ) {\n\t\tpointLight = pointLightShadows[ i ];\n\t\tshadow *= receiveShadow ? getPointShadow( pointShadowMap[ i ], pointLight.shadowMapSize, pointLight.shadowBias, pointLight.shadowRadius, vPointShadowCoord[ i ], pointLight.shadowCameraNear, pointLight.shadowCameraFar ) : 1.0;\n\t}\n\t#pragma unroll_loop_end\n\t#endif\n\t#endif\n\treturn shadow;\n}";
+
+var skinbase_vertex = "#ifdef USE_SKINNING\n\tmat4 boneMatX = getBoneMatrix( skinIndex.x );\n\tmat4 boneMatY = getBoneMatrix( skinIndex.y );\n\tmat4 boneMatZ = getBoneMatrix( skinIndex.z );\n\tmat4 boneMatW = getBoneMatrix( skinIndex.w );\n#endif";
+
+var skinning_pars_vertex = "#ifdef USE_SKINNING\n\tuniform mat4 bindMatrix;\n\tuniform mat4 bindMatrixInverse;\n\t#ifdef BONE_TEXTURE\n\t\tuniform highp sampler2D boneTexture;\n\t\tuniform int boneTextureSize;\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tfloat j = i * 4.0;\n\t\t\tfloat x = mod( j, float( boneTextureSize ) );\n\t\t\tfloat y = floor( j / float( boneTextureSize ) );\n\t\t\tfloat dx = 1.0 / float( boneTextureSize );\n\t\t\tfloat dy = 1.0 / float( boneTextureSize );\n\t\t\ty = dy * ( y + 0.5 );\n\t\t\tvec4 v1 = texture2D( boneTexture, vec2( dx * ( x + 0.5 ), y ) );\n\t\t\tvec4 v2 = texture2D( boneTexture, vec2( dx * ( x + 1.5 ), y ) );\n\t\t\tvec4 v3 = texture2D( boneTexture, vec2( dx * ( x + 2.5 ), y ) );\n\t\t\tvec4 v4 = texture2D( boneTexture, vec2( dx * ( x + 3.5 ), y ) );\n\t\t\tmat4 bone = mat4( v1, v2, v3, v4 );\n\t\t\treturn bone;\n\t\t}\n\t#else\n\t\tuniform mat4 boneMatrices[ MAX_BONES ];\n\t\tmat4 getBoneMatrix( const in float i ) {\n\t\t\tmat4 bone = boneMatrices[ int(i) ];\n\t\t\treturn bone;\n\t\t}\n\t#endif\n#endif";
+
+var skinning_vertex = "#ifdef USE_SKINNING\n\tvec4 skinVertex = bindMatrix * vec4( transformed, 1.0 );\n\tvec4 skinned = vec4( 0.0 );\n\tskinned += boneMatX * skinVertex * skinWeight.x;\n\tskinned += boneMatY * skinVertex * skinWeight.y;\n\tskinned += boneMatZ * skinVertex * skinWeight.z;\n\tskinned += boneMatW * skinVertex * skinWeight.w;\n\ttransformed = ( bindMatrixInverse * skinned ).xyz;\n#endif";
+
+var skinnormal_vertex = "#ifdef USE_SKINNING\n\tmat4 skinMatrix = mat4( 0.0 );\n\tskinMatrix += skinWeight.x * boneMatX;\n\tskinMatrix += skinWeight.y * boneMatY;\n\tskinMatrix += skinWeight.z * boneMatZ;\n\tskinMatrix += skinWeight.w * boneMatW;\n\tskinMatrix = bindMatrixInverse * skinMatrix * bindMatrix;\n\tobjectNormal = vec4( skinMatrix * vec4( objectNormal, 0.0 ) ).xyz;\n\t#ifdef USE_TANGENT\n\t\tobjectTangent = vec4( skinMatrix * vec4( objectTangent, 0.0 ) ).xyz;\n\t#endif\n#endif";
+
+var specularmap_fragment = "float specularStrength;\n#ifdef USE_SPECULARMAP\n\tvec4 texelSpecular = texture2D( specularMap, vUv );\n\tspecularStrength = texelSpecular.r;\n#else\n\tspecularStrength = 1.0;\n#endif";
+
+var specularmap_pars_fragment = "#ifdef USE_SPECULARMAP\n\tuniform sampler2D specularMap;\n#endif";
+
+var tonemapping_fragment = "#if defined( TONE_MAPPING )\n\tgl_FragColor.rgb = toneMapping( gl_FragColor.rgb );\n#endif";
+
+var tonemapping_pars_fragment = "#ifndef saturate\n#define saturate(a) clamp( a, 0.0, 1.0 )\n#endif\nuniform float toneMappingExposure;\nvec3 LinearToneMapping( vec3 color ) {\n\treturn toneMappingExposure * color;\n}\nvec3 ReinhardToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\treturn saturate( color / ( vec3( 1.0 ) + color ) );\n}\nvec3 OptimizedCineonToneMapping( vec3 color ) {\n\tcolor *= toneMappingExposure;\n\tcolor = max( vec3( 0.0 ), color - 0.004 );\n\treturn pow( ( color * ( 6.2 * color + 0.5 ) ) / ( color * ( 6.2 * color + 1.7 ) + 0.06 ), vec3( 2.2 ) );\n}\nvec3 RRTAndODTFit( vec3 v ) {\n\tvec3 a = v * ( v + 0.0245786 ) - 0.000090537;\n\tvec3 b = v * ( 0.983729 * v + 0.4329510 ) + 0.238081;\n\treturn a / b;\n}\nvec3 ACESFilmicToneMapping( vec3 color ) {\n\tconst mat3 ACESInputMat = mat3(\n\t\tvec3( 0.59719, 0.07600, 0.02840 ),\t\tvec3( 0.35458, 0.90834, 0.13383 ),\n\t\tvec3( 0.04823, 0.01566, 0.83777 )\n\t);\n\tconst mat3 ACESOutputMat = mat3(\n\t\tvec3( 1.60475, -0.10208, -0.00327 ),\t\tvec3( -0.53108, 1.10813, -0.07276 ),\n\t\tvec3( -0.07367, -0.00605, 1.07602 )\n\t);\n\tcolor *= toneMappingExposure / 0.6;\n\tcolor = ACESInputMat * color;\n\tcolor = RRTAndODTFit( color );\n\tcolor = ACESOutputMat * color;\n\treturn saturate( color );\n}\nvec3 CustomToneMapping( vec3 color ) { return color; }";
+
+var transmissionmap_fragment = "#ifdef USE_TRANSMISSIONMAP\n\ttotalTransmission *= texture2D( transmissionMap, vUv ).r;\n#endif";
+
+var transmissionmap_pars_fragment = "#ifdef USE_TRANSMISSIONMAP\n\tuniform sampler2D transmissionMap;\n#endif";
+
+var uv_pars_fragment = "#if ( defined( USE_UV ) && ! defined( UVS_VERTEX_ONLY ) )\n\tvarying vec2 vUv;\n#endif";
+
+var uv_pars_vertex = "#ifdef USE_UV\n\t#ifdef UVS_VERTEX_ONLY\n\t\tvec2 vUv;\n\t#else\n\t\tvarying vec2 vUv;\n\t#endif\n\tuniform mat3 uvTransform;\n#endif";
+
+var uv_vertex = "#ifdef USE_UV\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n#endif";
+
+var uv2_pars_fragment = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvarying vec2 vUv2;\n#endif";
+
+var uv2_pars_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tattribute vec2 uv2;\n\tvarying vec2 vUv2;\n\tuniform mat3 uv2Transform;\n#endif";
+
+var uv2_vertex = "#if defined( USE_LIGHTMAP ) || defined( USE_AOMAP )\n\tvUv2 = ( uv2Transform * vec3( uv2, 1 ) ).xy;\n#endif";
+
+var worldpos_vertex = "#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )\n\tvec4 worldPosition = vec4( transformed, 1.0 );\n\t#ifdef USE_INSTANCING\n\t\tworldPosition = instanceMatrix * worldPosition;\n\t#endif\n\tworldPosition = modelMatrix * worldPosition;\n#endif";
+
+var background_frag = "uniform sampler2D t2D;\nvarying vec2 vUv;\nvoid main() {\n\tvec4 texColor = texture2D( t2D, vUv );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n}";
+
+var background_vert = "varying vec2 vUv;\nuniform mat3 uvTransform;\nvoid main() {\n\tvUv = ( uvTransform * vec3( uv, 1 ) ).xy;\n\tgl_Position = vec4( position.xy, 1.0, 1.0 );\n}";
+
+var cube_frag = "#include <envmap_common_pars_fragment>\nuniform float opacity;\nvarying vec3 vWorldDirection;\n#include <cube_uv_reflection_fragment>\nvoid main() {\n\tvec3 vReflect = vWorldDirection;\n\t#include <envmap_fragment>\n\tgl_FragColor = envColor;\n\tgl_FragColor.a *= opacity;\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n}";
+
+var cube_vert = "varying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include <begin_vertex>\n\t#include <project_vertex>\n\tgl_Position.z = gl_Position.w;\n}";
+
+var depth_frag = "#if DEPTH_PACKING == 3200\n\tuniform float opacity;\n#endif\n#include <common>\n#include <packing>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#if DEPTH_PACKING == 3200\n\t\tdiffuseColor.a = opacity;\n\t#endif\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <logdepthbuf_fragment>\n\tfloat fragCoordZ = 0.5 * vHighPrecisionZW[0] / vHighPrecisionZW[1] + 0.5;\n\t#if DEPTH_PACKING == 3200\n\t\tgl_FragColor = vec4( vec3( 1.0 - fragCoordZ ), opacity );\n\t#elif DEPTH_PACKING == 3201\n\t\tgl_FragColor = packDepthToRGBA( fragCoordZ );\n\t#endif\n}";
+
+var depth_vert = "#include <common>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvarying vec2 vHighPrecisionZW;\nvoid main() {\n\t#include <uv_vertex>\n\t#include <skinbase_vertex>\n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvHighPrecisionZW = gl_Position.zw;\n}";
+
+var distanceRGBA_frag = "#define DISTANCE\nuniform vec3 referencePosition;\nuniform float nearDistance;\nuniform float farDistance;\nvarying vec3 vWorldPosition;\n#include <common>\n#include <packing>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main () {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( 1.0 );\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\tfloat dist = length( vWorldPosition - referencePosition );\n\tdist = ( dist - nearDistance ) / ( farDistance - nearDistance );\n\tdist = saturate( dist );\n\tgl_FragColor = packDepthToRGBA( dist );\n}";
+
+var distanceRGBA_vert = "#define DISTANCE\nvarying vec3 vWorldPosition;\n#include <common>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <skinbase_vertex>\n\t#ifdef USE_DISPLACEMENTMAP\n\t\t#include <beginnormal_vertex>\n\t\t#include <morphnormal_vertex>\n\t\t#include <skinnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <worldpos_vertex>\n\t#include <clipping_planes_vertex>\n\tvWorldPosition = worldPosition.xyz;\n}";
+
+var equirect_frag = "uniform sampler2D tEquirect;\nvarying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvec3 direction = normalize( vWorldDirection );\n\tvec2 sampleUV = equirectUv( direction );\n\tvec4 texColor = texture2D( tEquirect, sampleUV );\n\tgl_FragColor = mapTexelToLinear( texColor );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n}";
+
+var equirect_vert = "varying vec3 vWorldDirection;\n#include <common>\nvoid main() {\n\tvWorldDirection = transformDirection( position, modelMatrix );\n\t#include <begin_vertex>\n\t#include <project_vertex>\n}";
+
+var linedashed_frag = "uniform vec3 diffuse;\nuniform float opacity;\nuniform float dashSize;\nuniform float totalSize;\nvarying float vLineDistance;\n#include <common>\n#include <color_pars_fragment>\n#include <fog_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tif ( mod( vLineDistance, totalSize ) > dashSize ) {\n\t\tdiscard;\n\t}\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <color_fragment>\n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n}";
+
+var linedashed_vert = "uniform float scale;\nattribute float lineDistance;\nvarying float vLineDistance;\n#include <common>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\tvLineDistance = scale * lineDistance;\n\t#include <color_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n}";
+
+var meshbasic_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <uv2_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <cube_uv_reflection_fragment>\n#include <fog_pars_fragment>\n#include <specularmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <specularmap_fragment>\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\t#ifdef USE_LIGHTMAP\n\t\n\t\tvec4 lightMapTexel= texture2D( lightMap, vUv2 );\n\t\treflectedLight.indirectDiffuse += lightMapTexelToLinear( lightMapTexel ).rgb * lightMapIntensity;\n\t#else\n\t\treflectedLight.indirectDiffuse += vec3( 1.0 );\n\t#endif\n\t#include <aomap_fragment>\n\treflectedLight.indirectDiffuse *= diffuseColor.rgb;\n\tvec3 outgoingLight = reflectedLight.indirectDiffuse;\n\t#include <envmap_fragment>\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshbasic_vert = "#include <common>\n#include <uv_pars_vertex>\n#include <uv2_pars_vertex>\n#include <envmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <uv2_vertex>\n\t#include <color_vertex>\n\t#include <skinbase_vertex>\n\t#ifdef USE_ENVMAP\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <worldpos_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <envmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var meshlambert_frag = "uniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\n#endif\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <uv2_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <cube_uv_reflection_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <fog_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <shadowmask_pars_fragment>\n#include <specularmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <specularmap_fragment>\n\t#include <emissivemap_fragment>\n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.indirectDiffuse += ( gl_FrontFacing ) ? vIndirectFront : vIndirectBack;\n\t#else\n\t\treflectedLight.indirectDiffuse += vIndirectFront;\n\t#endif\n\t#include <lightmap_fragment>\n\treflectedLight.indirectDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb );\n\t#ifdef DOUBLE_SIDED\n\t\treflectedLight.directDiffuse = ( gl_FrontFacing ) ? vLightFront : vLightBack;\n\t#else\n\t\treflectedLight.directDiffuse = vLightFront;\n\t#endif\n\treflectedLight.directDiffuse *= BRDF_Diffuse_Lambert( diffuseColor.rgb ) * getShadowMask();\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\t#include <envmap_fragment>\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshlambert_vert = "#define LAMBERT\nvarying vec3 vLightFront;\nvarying vec3 vIndirectFront;\n#ifdef DOUBLE_SIDED\n\tvarying vec3 vLightBack;\n\tvarying vec3 vIndirectBack;\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <uv2_pars_vertex>\n#include <envmap_pars_vertex>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <uv2_vertex>\n\t#include <color_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <worldpos_vertex>\n\t#include <envmap_vertex>\n\t#include <lights_lambert_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var meshmatcap_frag = "#define MATCAP\nuniform vec3 diffuse;\nuniform float opacity;\nuniform sampler2D matcap;\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <fog_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\tvec3 viewDir = normalize( vViewPosition );\n\tvec3 x = normalize( vec3( viewDir.z, 0.0, - viewDir.x ) );\n\tvec3 y = cross( viewDir, x );\n\tvec2 uv = vec2( dot( x, normal ), dot( y, normal ) ) * 0.495 + 0.5;\n\t#ifdef USE_MATCAP\n\t\tvec4 matcapColor = texture2D( matcap, uv );\n\t\tmatcapColor = matcapTexelToLinear( matcapColor );\n\t#else\n\t\tvec4 matcapColor = vec4( 1.0 );\n\t#endif\n\tvec3 outgoingLight = diffuseColor.rgb * matcapColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshmatcap_vert = "#define MATCAP\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <color_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <color_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#ifndef FLAT_SHADED\n\t\tvNormal = normalize( transformedNormal );\n\t#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n\tvViewPosition = - mvPosition.xyz;\n}";
+
+var meshtoon_frag = "#define TOON\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float opacity;\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <uv2_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <gradientmap_pars_fragment>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <lights_toon_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <lights_toon_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + totalEmissiveRadiance;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshtoon_vert = "#define TOON\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <uv2_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <uv2_vertex>\n\t#include <color_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var meshphong_frag = "#define PHONG\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform vec3 specular;\nuniform float shininess;\nuniform float opacity;\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <uv2_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_pars_fragment>\n#include <cube_uv_reflection_fragment>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <lights_phong_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <specularmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <specularmap_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <lights_phong_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#include <envmap_fragment>\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshphong_vert = "#define PHONG\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <uv2_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <envmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <uv2_vertex>\n\t#include <color_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <envmap_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var meshphysical_frag = "#define STANDARD\n#ifdef PHYSICAL\n\t#define REFLECTIVITY\n\t#define CLEARCOAT\n\t#define TRANSMISSION\n#endif\nuniform vec3 diffuse;\nuniform vec3 emissive;\nuniform float roughness;\nuniform float metalness;\nuniform float opacity;\n#ifdef TRANSMISSION\n\tuniform float transmission;\n#endif\n#ifdef REFLECTIVITY\n\tuniform float reflectivity;\n#endif\n#ifdef CLEARCOAT\n\tuniform float clearcoat;\n\tuniform float clearcoatRoughness;\n#endif\n#ifdef USE_SHEEN\n\tuniform vec3 sheen;\n#endif\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include <common>\n#include <packing>\n#include <dithering_pars_fragment>\n#include <color_pars_fragment>\n#include <uv_pars_fragment>\n#include <uv2_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <aomap_pars_fragment>\n#include <lightmap_pars_fragment>\n#include <emissivemap_pars_fragment>\n#include <transmissionmap_pars_fragment>\n#include <bsdfs>\n#include <cube_uv_reflection_fragment>\n#include <envmap_common_pars_fragment>\n#include <envmap_physical_pars_fragment>\n#include <fog_pars_fragment>\n#include <lights_pars_begin>\n#include <lights_physical_pars_fragment>\n#include <shadowmap_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <clearcoat_pars_fragment>\n#include <roughnessmap_pars_fragment>\n#include <metalnessmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\tReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );\n\tvec3 totalEmissiveRadiance = emissive;\n\t#ifdef TRANSMISSION\n\t\tfloat totalTransmission = transmission;\n\t#endif\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <color_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\t#include <roughnessmap_fragment>\n\t#include <metalnessmap_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\t#include <clearcoat_normal_fragment_begin>\n\t#include <clearcoat_normal_fragment_maps>\n\t#include <emissivemap_fragment>\n\t#include <transmissionmap_fragment>\n\t#include <lights_physical_fragment>\n\t#include <lights_fragment_begin>\n\t#include <lights_fragment_maps>\n\t#include <lights_fragment_end>\n\t#include <aomap_fragment>\n\tvec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;\n\t#ifdef TRANSMISSION\n\t\tdiffuseColor.a *= mix( saturate( 1. - totalTransmission + linearToRelativeLuminance( reflectedLight.directSpecular + reflectedLight.indirectSpecular ) ), 1.0, metalness );\n\t#endif\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n\t#include <dithering_fragment>\n}";
+
+var meshphysical_vert = "#define STANDARD\nvarying vec3 vViewPosition;\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <uv2_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <shadowmap_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <uv2_vertex>\n\t#include <color_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\tvViewPosition = - mvPosition.xyz;\n\t#include <worldpos_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var normal_frag = "#define NORMAL\nuniform float opacity;\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include <packing>\n#include <uv_pars_fragment>\n#include <bumpmap_pars_fragment>\n#include <normalmap_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\t#include <logdepthbuf_fragment>\n\t#include <normal_fragment_begin>\n\t#include <normal_fragment_maps>\n\tgl_FragColor = vec4( packNormalToRGB( normal ), opacity );\n}";
+
+var normal_vert = "#define NORMAL\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvarying vec3 vViewPosition;\n#endif\n#ifndef FLAT_SHADED\n\tvarying vec3 vNormal;\n\t#ifdef USE_TANGENT\n\t\tvarying vec3 vTangent;\n\t\tvarying vec3 vBitangent;\n\t#endif\n#endif\n#include <common>\n#include <uv_pars_vertex>\n#include <displacementmap_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <skinning_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n#ifndef FLAT_SHADED\n\tvNormal = normalize( transformedNormal );\n\t#ifdef USE_TANGENT\n\t\tvTangent = normalize( transformedTangent );\n\t\tvBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );\n\t#endif\n#endif\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <skinning_vertex>\n\t#include <displacementmap_vertex>\n\t#include <project_vertex>\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n#if defined( FLAT_SHADED ) || defined( USE_BUMPMAP ) || defined( TANGENTSPACE_NORMALMAP )\n\tvViewPosition = - mvPosition.xyz;\n#endif\n}";
+
+var points_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#include <common>\n#include <color_pars_fragment>\n#include <map_particle_pars_fragment>\n#include <fog_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_particle_fragment>\n\t#include <color_fragment>\n\t#include <alphatest_fragment>\n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n\t#include <premultiplied_alpha_fragment>\n}";
+
+var points_vert = "uniform float size;\nuniform float scale;\n#include <common>\n#include <color_pars_vertex>\n#include <fog_pars_vertex>\n#include <morphtarget_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <color_vertex>\n\t#include <begin_vertex>\n\t#include <morphtarget_vertex>\n\t#include <project_vertex>\n\tgl_PointSize = size;\n\t#ifdef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) gl_PointSize *= ( scale / - mvPosition.z );\n\t#endif\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <worldpos_vertex>\n\t#include <fog_vertex>\n}";
+
+var shadow_frag = "uniform vec3 color;\nuniform float opacity;\n#include <common>\n#include <packing>\n#include <fog_pars_fragment>\n#include <bsdfs>\n#include <lights_pars_begin>\n#include <shadowmap_pars_fragment>\n#include <shadowmask_pars_fragment>\nvoid main() {\n\tgl_FragColor = vec4( color, opacity * ( 1.0 - getShadowMask() ) );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n}";
+
+var shadow_vert = "#include <common>\n#include <fog_pars_vertex>\n#include <shadowmap_pars_vertex>\nvoid main() {\n\t#include <begin_vertex>\n\t#include <project_vertex>\n\t#include <worldpos_vertex>\n\t#include <beginnormal_vertex>\n\t#include <morphnormal_vertex>\n\t#include <skinbase_vertex>\n\t#include <skinnormal_vertex>\n\t#include <defaultnormal_vertex>\n\t#include <shadowmap_vertex>\n\t#include <fog_vertex>\n}";
+
+var sprite_frag = "uniform vec3 diffuse;\nuniform float opacity;\n#include <common>\n#include <uv_pars_fragment>\n#include <map_pars_fragment>\n#include <alphamap_pars_fragment>\n#include <fog_pars_fragment>\n#include <logdepthbuf_pars_fragment>\n#include <clipping_planes_pars_fragment>\nvoid main() {\n\t#include <clipping_planes_fragment>\n\tvec3 outgoingLight = vec3( 0.0 );\n\tvec4 diffuseColor = vec4( diffuse, opacity );\n\t#include <logdepthbuf_fragment>\n\t#include <map_fragment>\n\t#include <alphamap_fragment>\n\t#include <alphatest_fragment>\n\toutgoingLight = diffuseColor.rgb;\n\tgl_FragColor = vec4( outgoingLight, diffuseColor.a );\n\t#include <tonemapping_fragment>\n\t#include <encodings_fragment>\n\t#include <fog_fragment>\n}";
+
+var sprite_vert = "uniform float rotation;\nuniform vec2 center;\n#include <common>\n#include <uv_pars_vertex>\n#include <fog_pars_vertex>\n#include <logdepthbuf_pars_vertex>\n#include <clipping_planes_pars_vertex>\nvoid main() {\n\t#include <uv_vertex>\n\tvec4 mvPosition = modelViewMatrix * vec4( 0.0, 0.0, 0.0, 1.0 );\n\tvec2 scale;\n\tscale.x = length( vec3( modelMatrix[ 0 ].x, modelMatrix[ 0 ].y, modelMatrix[ 0 ].z ) );\n\tscale.y = length( vec3( modelMatrix[ 1 ].x, modelMatrix[ 1 ].y, modelMatrix[ 1 ].z ) );\n\t#ifndef USE_SIZEATTENUATION\n\t\tbool isPerspective = isPerspectiveMatrix( projectionMatrix );\n\t\tif ( isPerspective ) scale *= - mvPosition.z;\n\t#endif\n\tvec2 alignedPosition = ( position.xy - ( center - vec2( 0.5 ) ) ) * scale;\n\tvec2 rotatedPosition;\n\trotatedPosition.x = cos( rotation ) * alignedPosition.x - sin( rotation ) * alignedPosition.y;\n\trotatedPosition.y = sin( rotation ) * alignedPosition.x + cos( rotation ) * alignedPosition.y;\n\tmvPosition.xy += rotatedPosition;\n\tgl_Position = projectionMatrix * mvPosition;\n\t#include <logdepthbuf_vertex>\n\t#include <clipping_planes_vertex>\n\t#include <fog_vertex>\n}";
+
+const ShaderChunk = {
+ alphamap_fragment: alphamap_fragment,
+ alphamap_pars_fragment: alphamap_pars_fragment,
+ alphatest_fragment: alphatest_fragment,
+ aomap_fragment: aomap_fragment,
+ aomap_pars_fragment: aomap_pars_fragment,
+ begin_vertex: begin_vertex,
+ beginnormal_vertex: beginnormal_vertex,
+ bsdfs: bsdfs,
+ bumpmap_pars_fragment: bumpmap_pars_fragment,
+ clipping_planes_fragment: clipping_planes_fragment,
+ clipping_planes_pars_fragment: clipping_planes_pars_fragment,
+ clipping_planes_pars_vertex: clipping_planes_pars_vertex,
+ clipping_planes_vertex: clipping_planes_vertex,
+ color_fragment: color_fragment,
+ color_pars_fragment: color_pars_fragment,
+ color_pars_vertex: color_pars_vertex,
+ color_vertex: color_vertex,
+ common: common$1,
+ cube_uv_reflection_fragment: cube_uv_reflection_fragment,
+ defaultnormal_vertex: defaultnormal_vertex,
+ displacementmap_pars_vertex: displacementmap_pars_vertex,
+ displacementmap_vertex: displacementmap_vertex,
+ emissivemap_fragment: emissivemap_fragment,
+ emissivemap_pars_fragment: emissivemap_pars_fragment,
+ encodings_fragment: encodings_fragment,
+ encodings_pars_fragment: encodings_pars_fragment,
+ envmap_fragment: envmap_fragment,
+ envmap_common_pars_fragment: envmap_common_pars_fragment,
+ envmap_pars_fragment: envmap_pars_fragment,
+ envmap_pars_vertex: envmap_pars_vertex,
+ envmap_physical_pars_fragment: envmap_physical_pars_fragment,
+ envmap_vertex: envmap_vertex,
+ fog_vertex: fog_vertex,
+ fog_pars_vertex: fog_pars_vertex,
+ fog_fragment: fog_fragment,
+ fog_pars_fragment: fog_pars_fragment,
+ gradientmap_pars_fragment: gradientmap_pars_fragment,
+ lightmap_fragment: lightmap_fragment,
+ lightmap_pars_fragment: lightmap_pars_fragment,
+ lights_lambert_vertex: lights_lambert_vertex,
+ lights_pars_begin: lights_pars_begin,
+ lights_toon_fragment: lights_toon_fragment,
+ lights_toon_pars_fragment: lights_toon_pars_fragment,
+ lights_phong_fragment: lights_phong_fragment,
+ lights_phong_pars_fragment: lights_phong_pars_fragment,
+ lights_physical_fragment: lights_physical_fragment,
+ lights_physical_pars_fragment: lights_physical_pars_fragment,
+ lights_fragment_begin: lights_fragment_begin,
+ lights_fragment_maps: lights_fragment_maps,
+ lights_fragment_end: lights_fragment_end,
+ logdepthbuf_fragment: logdepthbuf_fragment,
+ logdepthbuf_pars_fragment: logdepthbuf_pars_fragment,
+ logdepthbuf_pars_vertex: logdepthbuf_pars_vertex,
+ logdepthbuf_vertex: logdepthbuf_vertex,
+ map_fragment: map_fragment,
+ map_pars_fragment: map_pars_fragment,
+ map_particle_fragment: map_particle_fragment,
+ map_particle_pars_fragment: map_particle_pars_fragment,
+ metalnessmap_fragment: metalnessmap_fragment,
+ metalnessmap_pars_fragment: metalnessmap_pars_fragment,
+ morphnormal_vertex: morphnormal_vertex,
+ morphtarget_pars_vertex: morphtarget_pars_vertex,
+ morphtarget_vertex: morphtarget_vertex,
+ normal_fragment_begin: normal_fragment_begin,
+ normal_fragment_maps: normal_fragment_maps,
+ normalmap_pars_fragment: normalmap_pars_fragment,
+ clearcoat_normal_fragment_begin: clearcoat_normal_fragment_begin,
+ clearcoat_normal_fragment_maps: clearcoat_normal_fragment_maps,
+ clearcoat_pars_fragment: clearcoat_pars_fragment,
+ packing: packing,
+ premultiplied_alpha_fragment: premultiplied_alpha_fragment,
+ project_vertex: project_vertex,
+ dithering_fragment: dithering_fragment,
+ dithering_pars_fragment: dithering_pars_fragment,
+ roughnessmap_fragment: roughnessmap_fragment,
+ roughnessmap_pars_fragment: roughnessmap_pars_fragment,
+ shadowmap_pars_fragment: shadowmap_pars_fragment,
+ shadowmap_pars_vertex: shadowmap_pars_vertex,
+ shadowmap_vertex: shadowmap_vertex,
+ shadowmask_pars_fragment: shadowmask_pars_fragment,
+ skinbase_vertex: skinbase_vertex,
+ skinning_pars_vertex: skinning_pars_vertex,
+ skinning_vertex: skinning_vertex,
+ skinnormal_vertex: skinnormal_vertex,
+ specularmap_fragment: specularmap_fragment,
+ specularmap_pars_fragment: specularmap_pars_fragment,
+ tonemapping_fragment: tonemapping_fragment,
+ tonemapping_pars_fragment: tonemapping_pars_fragment,
+ transmissionmap_fragment: transmissionmap_fragment,
+ transmissionmap_pars_fragment: transmissionmap_pars_fragment,
+ uv_pars_fragment: uv_pars_fragment,
+ uv_pars_vertex: uv_pars_vertex,
+ uv_vertex: uv_vertex,
+ uv2_pars_fragment: uv2_pars_fragment,
+ uv2_pars_vertex: uv2_pars_vertex,
+ uv2_vertex: uv2_vertex,
+ worldpos_vertex: worldpos_vertex,
+
+ background_frag: background_frag,
+ background_vert: background_vert,
+ cube_frag: cube_frag,
+ cube_vert: cube_vert,
+ depth_frag: depth_frag,
+ depth_vert: depth_vert,
+ distanceRGBA_frag: distanceRGBA_frag,
+ distanceRGBA_vert: distanceRGBA_vert,
+ equirect_frag: equirect_frag,
+ equirect_vert: equirect_vert,
+ linedashed_frag: linedashed_frag,
+ linedashed_vert: linedashed_vert,
+ meshbasic_frag: meshbasic_frag,
+ meshbasic_vert: meshbasic_vert,
+ meshlambert_frag: meshlambert_frag,
+ meshlambert_vert: meshlambert_vert,
+ meshmatcap_frag: meshmatcap_frag,
+ meshmatcap_vert: meshmatcap_vert,
+ meshtoon_frag: meshtoon_frag,
+ meshtoon_vert: meshtoon_vert,
+ meshphong_frag: meshphong_frag,
+ meshphong_vert: meshphong_vert,
+ meshphysical_frag: meshphysical_frag,
+ meshphysical_vert: meshphysical_vert,
+ normal_frag: normal_frag,
+ normal_vert: normal_vert,
+ points_frag: points_frag,
+ points_vert: points_vert,
+ shadow_frag: shadow_frag,
+ shadow_vert: shadow_vert,
+ sprite_frag: sprite_frag,
+ sprite_vert: sprite_vert
+};
+
+/**
+ * Uniforms library for shared webgl shaders
+ */
+
+const UniformsLib = {
+
+ common: {
+
+ diffuse: { value: new Color( 0xeeeeee ) },
+ opacity: { value: 1.0 },
+
+ map: { value: null },
+ uvTransform: { value: new Matrix3() },
+ uv2Transform: { value: new Matrix3() },
+
+ alphaMap: { value: null },
+
+ },
+
+ specularmap: {
+
+ specularMap: { value: null },
+
+ },
+
+ envmap: {
+
+ envMap: { value: null },
+ flipEnvMap: { value: - 1 },
+ reflectivity: { value: 1.0 },
+ refractionRatio: { value: 0.98 },
+ maxMipLevel: { value: 0 }
+
+ },
+
+ aomap: {
+
+ aoMap: { value: null },
+ aoMapIntensity: { value: 1 }
+
+ },
+
+ lightmap: {
+
+ lightMap: { value: null },
+ lightMapIntensity: { value: 1 }
+
+ },
+
+ emissivemap: {
+
+ emissiveMap: { value: null }
+
+ },
+
+ bumpmap: {
+
+ bumpMap: { value: null },
+ bumpScale: { value: 1 }
+
+ },
+
+ normalmap: {
+
+ normalMap: { value: null },
+ normalScale: { value: new Vector2( 1, 1 ) }
+
+ },
+
+ displacementmap: {
+
+ displacementMap: { value: null },
+ displacementScale: { value: 1 },
+ displacementBias: { value: 0 }
+
+ },
+
+ roughnessmap: {
+
+ roughnessMap: { value: null }
+
+ },
+
+ metalnessmap: {
+
+ metalnessMap: { value: null }
+
+ },
+
+ gradientmap: {
+
+ gradientMap: { value: null }
+
+ },
+
+ fog: {
+
+ fogDensity: { value: 0.00025 },
+ fogNear: { value: 1 },
+ fogFar: { value: 2000 },
+ fogColor: { value: new Color( 0xffffff ) }
+
+ },
+
+ lights: {
+
+ ambientLightColor: { value: [] },
+
+ lightProbe: { value: [] },
+
+ directionalLights: { value: [], properties: {
+ direction: {},
+ color: {}
+ } },
+
+ directionalLightShadows: { value: [], properties: {
+ shadowBias: {},
+ shadowNormalBias: {},
+ shadowRadius: {},
+ shadowMapSize: {}
+ } },
+
+ directionalShadowMap: { value: [] },
+ directionalShadowMatrix: { value: [] },
+
+ spotLights: { value: [], properties: {
+ color: {},
+ position: {},
+ direction: {},
+ distance: {},
+ coneCos: {},
+ penumbraCos: {},
+ decay: {}
+ } },
+
+ spotLightShadows: { value: [], properties: {
+ shadowBias: {},
+ shadowNormalBias: {},
+ shadowRadius: {},
+ shadowMapSize: {}
+ } },
+
+ spotShadowMap: { value: [] },
+ spotShadowMatrix: { value: [] },
+
+ pointLights: { value: [], properties: {
+ color: {},
+ position: {},
+ decay: {},
+ distance: {}
+ } },
+
+ pointLightShadows: { value: [], properties: {
+ shadowBias: {},
+ shadowNormalBias: {},
+ shadowRadius: {},
+ shadowMapSize: {},
+ shadowCameraNear: {},
+ shadowCameraFar: {}
+ } },
+
+ pointShadowMap: { value: [] },
+ pointShadowMatrix: { value: [] },
+
+ hemisphereLights: { value: [], properties: {
+ direction: {},
+ skyColor: {},
+ groundColor: {}
+ } },
+
+ // TODO (abelnation): RectAreaLight BRDF data needs to be moved from example to main src
+ rectAreaLights: { value: [], properties: {
+ color: {},
+ position: {},
+ width: {},
+ height: {}
+ } },
+
+ ltc_1: { value: null },
+ ltc_2: { value: null }
+
+ },
+
+ points: {
+
+ diffuse: { value: new Color( 0xeeeeee ) },
+ opacity: { value: 1.0 },
+ size: { value: 1.0 },
+ scale: { value: 1.0 },
+ map: { value: null },
+ alphaMap: { value: null },
+ uvTransform: { value: new Matrix3() }
+
+ },
+
+ sprite: {
+
+ diffuse: { value: new Color( 0xeeeeee ) },
+ opacity: { value: 1.0 },
+ center: { value: new Vector2( 0.5, 0.5 ) },
+ rotation: { value: 0.0 },
+ map: { value: null },
+ alphaMap: { value: null },
+ uvTransform: { value: new Matrix3() }
+
+ }
+
+};
+
+const ShaderLib = {
+
+ basic: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.common,
+ UniformsLib.specularmap,
+ UniformsLib.envmap,
+ UniformsLib.aomap,
+ UniformsLib.lightmap,
+ UniformsLib.fog
+ ] ),
+
+ vertexShader: ShaderChunk.meshbasic_vert,
+ fragmentShader: ShaderChunk.meshbasic_frag
+
+ },
+
+ lambert: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.common,
+ UniformsLib.specularmap,
+ UniformsLib.envmap,
+ UniformsLib.aomap,
+ UniformsLib.lightmap,
+ UniformsLib.emissivemap,
+ UniformsLib.fog,
+ UniformsLib.lights,
+ {
+ emissive: { value: new Color( 0x000000 ) }
+ }
+ ] ),
+
+ vertexShader: ShaderChunk.meshlambert_vert,
+ fragmentShader: ShaderChunk.meshlambert_frag
+
+ },
+
+ phong: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.common,
+ UniformsLib.specularmap,
+ UniformsLib.envmap,
+ UniformsLib.aomap,
+ UniformsLib.lightmap,
+ UniformsLib.emissivemap,
+ UniformsLib.bumpmap,
+ UniformsLib.normalmap,
+ UniformsLib.displacementmap,
+ UniformsLib.fog,
+ UniformsLib.lights,
+ {
+ emissive: { value: new Color( 0x000000 ) },
+ specular: { value: new Color( 0x111111 ) },
+ shininess: { value: 30 }
+ }
+ ] ),
+
+ vertexShader: ShaderChunk.meshphong_vert,
+ fragmentShader: ShaderChunk.meshphong_frag
+
+ },
+
+ standard: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.common,
+ UniformsLib.envmap,
+ UniformsLib.aomap,
+ UniformsLib.lightmap,
+ UniformsLib.emissivemap,
+ UniformsLib.bumpmap,
+ UniformsLib.normalmap,
+ UniformsLib.displacementmap,
+ UniformsLib.roughnessmap,
+ UniformsLib.metalnessmap,
+ UniformsLib.fog,
+ UniformsLib.lights,
+ {
+ emissive: { value: new Color( 0x000000 ) },
+ roughness: { value: 1.0 },
+ metalness: { value: 0.0 },
+ envMapIntensity: { value: 1 } // temporary
+ }
+ ] ),
+
+ vertexShader: ShaderChunk.meshphysical_vert,
+ fragmentShader: ShaderChunk.meshphysical_frag
+
+ },
+
+ toon: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.common,
+ UniformsLib.aomap,
+ UniformsLib.lightmap,
+ UniformsLib.emissivemap,
+ UniformsLib.bumpmap,
+ UniformsLib.normalmap,
+ UniformsLib.displacementmap,
+ UniformsLib.gradientmap,
+ UniformsLib.fog,
+ UniformsLib.lights,
+ {
+ emissive: { value: new Color( 0x000000 ) }
+ }
+ ] ),
+
+ vertexShader: ShaderChunk.meshtoon_vert,
+ fragmentShader: ShaderChunk.meshtoon_frag
+
+ },
+
+ matcap: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.common,
+ UniformsLib.bumpmap,
+ UniformsLib.normalmap,
+ UniformsLib.displacementmap,
+ UniformsLib.fog,
+ {
+ matcap: { value: null }
+ }
+ ] ),
+
+ vertexShader: ShaderChunk.meshmatcap_vert,
+ fragmentShader: ShaderChunk.meshmatcap_frag
+
+ },
+
+ points: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.points,
+ UniformsLib.fog
+ ] ),
+
+ vertexShader: ShaderChunk.points_vert,
+ fragmentShader: ShaderChunk.points_frag
+
+ },
+
+ dashed: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.common,
+ UniformsLib.fog,
+ {
+ scale: { value: 1 },
+ dashSize: { value: 1 },
+ totalSize: { value: 2 }
+ }
+ ] ),
+
+ vertexShader: ShaderChunk.linedashed_vert,
+ fragmentShader: ShaderChunk.linedashed_frag
+
+ },
+
+ depth: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.common,
+ UniformsLib.displacementmap
+ ] ),
+
+ vertexShader: ShaderChunk.depth_vert,
+ fragmentShader: ShaderChunk.depth_frag
+
+ },
+
+ normal: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.common,
+ UniformsLib.bumpmap,
+ UniformsLib.normalmap,
+ UniformsLib.displacementmap,
+ {
+ opacity: { value: 1.0 }
+ }
+ ] ),
+
+ vertexShader: ShaderChunk.normal_vert,
+ fragmentShader: ShaderChunk.normal_frag
+
+ },
+
+ sprite: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.sprite,
+ UniformsLib.fog
+ ] ),
+
+ vertexShader: ShaderChunk.sprite_vert,
+ fragmentShader: ShaderChunk.sprite_frag
+
+ },
+
+ background: {
+
+ uniforms: {
+ uvTransform: { value: new Matrix3() },
+ t2D: { value: null },
+ },
+
+ vertexShader: ShaderChunk.background_vert,
+ fragmentShader: ShaderChunk.background_frag
+
+ },
+ /* -------------------------------------------------------------------------
+ // Cube map shader
+ ------------------------------------------------------------------------- */
+
+ cube: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.envmap,
+ {
+ opacity: { value: 1.0 }
+ }
+ ] ),
+
+ vertexShader: ShaderChunk.cube_vert,
+ fragmentShader: ShaderChunk.cube_frag
+
+ },
+
+ equirect: {
+
+ uniforms: {
+ tEquirect: { value: null },
+ },
+
+ vertexShader: ShaderChunk.equirect_vert,
+ fragmentShader: ShaderChunk.equirect_frag
+
+ },
+
+ distanceRGBA: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.common,
+ UniformsLib.displacementmap,
+ {
+ referencePosition: { value: new Vector3() },
+ nearDistance: { value: 1 },
+ farDistance: { value: 1000 }
+ }
+ ] ),
+
+ vertexShader: ShaderChunk.distanceRGBA_vert,
+ fragmentShader: ShaderChunk.distanceRGBA_frag
+
+ },
+
+ shadow: {
+
+ uniforms: mergeUniforms( [
+ UniformsLib.lights,
+ UniformsLib.fog,
+ {
+ color: { value: new Color( 0x00000 ) },
+ opacity: { value: 1.0 }
+ },
+ ] ),
+
+ vertexShader: ShaderChunk.shadow_vert,
+ fragmentShader: ShaderChunk.shadow_frag
+
+ }
+
+};
+
+ShaderLib.physical = {
+
+ uniforms: mergeUniforms( [
+ ShaderLib.standard.uniforms,
+ {
+ clearcoat: { value: 0 },
+ clearcoatMap: { value: null },
+ clearcoatRoughness: { value: 0 },
+ clearcoatRoughnessMap: { value: null },
+ clearcoatNormalScale: { value: new Vector2( 1, 1 ) },
+ clearcoatNormalMap: { value: null },
+ sheen: { value: new Color( 0x000000 ) },
+ transmission: { value: 0 },
+ transmissionMap: { value: null },
+ }
+ ] ),
+
+ vertexShader: ShaderChunk.meshphysical_vert,
+ fragmentShader: ShaderChunk.meshphysical_frag
+
+};
+
+function WebGLBackground( renderer, cubemaps, state, objects, premultipliedAlpha ) {
+
+ const clearColor = new Color( 0x000000 );
+ let clearAlpha = 0;
+
+ let planeMesh;
+ let boxMesh;
+
+ let currentBackground = null;
+ let currentBackgroundVersion = 0;
+ let currentTonemapping = null;
+
+ function render( renderList, scene, camera, forceClear ) {
+
+ let background = scene.isScene === true ? scene.background : null;
+
+ if ( background && background.isTexture ) {
+
+ background = cubemaps.get( background );
+
+ }
+
+ // Ignore background in AR
+ // TODO: Reconsider this.
+
+ const xr = renderer.xr;
+ const session = xr.getSession && xr.getSession();
+
+ if ( session && session.environmentBlendMode === 'additive' ) {
+
+ background = null;
+
+ }
+
+ if ( background === null ) {
+
+ setClear( clearColor, clearAlpha );
+
+ } else if ( background && background.isColor ) {
+
+ setClear( background, 1 );
+ forceClear = true;
+
+ }
+
+ if ( renderer.autoClear || forceClear ) {
+
+ renderer.clear( renderer.autoClearColor, renderer.autoClearDepth, renderer.autoClearStencil );
+
+ }
+
+ if ( background && ( background.isCubeTexture || background.isWebGLCubeRenderTarget || background.mapping === CubeUVReflectionMapping ) ) {
+
+ if ( boxMesh === undefined ) {
+
+ boxMesh = new Mesh(
+ new BoxGeometry( 1, 1, 1 ),
+ new ShaderMaterial( {
+ name: 'BackgroundCubeMaterial',
+ uniforms: cloneUniforms( ShaderLib.cube.uniforms ),
+ vertexShader: ShaderLib.cube.vertexShader,
+ fragmentShader: ShaderLib.cube.fragmentShader,
+ side: BackSide,
+ depthTest: false,
+ depthWrite: false,
+ fog: false
+ } )
+ );
+
+ boxMesh.geometry.deleteAttribute( 'normal' );
+ boxMesh.geometry.deleteAttribute( 'uv' );
+
+ boxMesh.onBeforeRender = function ( renderer, scene, camera ) {
+
+ this.matrixWorld.copyPosition( camera.matrixWorld );
+
+ };
+
+ // enable code injection for non-built-in material
+ Object.defineProperty( boxMesh.material, 'envMap', {
+
+ get: function () {
+
+ return this.uniforms.envMap.value;
+
+ }
+
+ } );
+
+ objects.update( boxMesh );
+
+ }
+
+ if ( background.isWebGLCubeRenderTarget ) {
+
+ // TODO Deprecate
+
+ background = background.texture;
+
+ }
+
+ boxMesh.material.uniforms.envMap.value = background;
+ boxMesh.material.uniforms.flipEnvMap.value = ( background.isCubeTexture && background._needsFlipEnvMap ) ? - 1 : 1;
+
+ if ( currentBackground !== background ||
+ currentBackgroundVersion !== background.version ||
+ currentTonemapping !== renderer.toneMapping ) {
+
+ boxMesh.material.needsUpdate = true;
+
+ currentBackground = background;
+ currentBackgroundVersion = background.version;
+ currentTonemapping = renderer.toneMapping;
+
+ }
+
+ // push to the pre-sorted opaque render list
+ renderList.unshift( boxMesh, boxMesh.geometry, boxMesh.material, 0, 0, null );
+
+ } else if ( background && background.isTexture ) {
+
+ if ( planeMesh === undefined ) {
+
+ planeMesh = new Mesh(
+ new PlaneGeometry( 2, 2 ),
+ new ShaderMaterial( {
+ name: 'BackgroundMaterial',
+ uniforms: cloneUniforms( ShaderLib.background.uniforms ),
+ vertexShader: ShaderLib.background.vertexShader,
+ fragmentShader: ShaderLib.background.fragmentShader,
+ side: FrontSide,
+ depthTest: false,
+ depthWrite: false,
+ fog: false
+ } )
+ );
+
+ planeMesh.geometry.deleteAttribute( 'normal' );
+
+ // enable code injection for non-built-in material
+ Object.defineProperty( planeMesh.material, 'map', {
+
+ get: function () {
+
+ return this.uniforms.t2D.value;
+
+ }
+
+ } );
+
+ objects.update( planeMesh );
+
+ }
+
+ planeMesh.material.uniforms.t2D.value = background;
+
+ if ( background.matrixAutoUpdate === true ) {
+
+ background.updateMatrix();
+
+ }
+
+ planeMesh.material.uniforms.uvTransform.value.copy( background.matrix );
+
+ if ( currentBackground !== background ||
+ currentBackgroundVersion !== background.version ||
+ currentTonemapping !== renderer.toneMapping ) {
+
+ planeMesh.material.needsUpdate = true;
+
+ currentBackground = background;
+ currentBackgroundVersion = background.version;
+ currentTonemapping = renderer.toneMapping;
+
+ }
+
+
+ // push to the pre-sorted opaque render list
+ renderList.unshift( planeMesh, planeMesh.geometry, planeMesh.material, 0, 0, null );
+
+ }
+
+ }
+
+ function setClear( color, alpha ) {
+
+ state.buffers.color.setClear( color.r, color.g, color.b, alpha, premultipliedAlpha );
+
+ }
+
+ return {
+
+ getClearColor: function () {
+
+ return clearColor;
+
+ },
+ setClearColor: function ( color, alpha = 1 ) {
+
+ clearColor.set( color );
+ clearAlpha = alpha;
+ setClear( clearColor, clearAlpha );
+
+ },
+ getClearAlpha: function () {
+
+ return clearAlpha;
+
+ },
+ setClearAlpha: function ( alpha ) {
+
+ clearAlpha = alpha;
+ setClear( clearColor, clearAlpha );
+
+ },
+ render: render
+
+ };
+
+}
+
+function WebGLBindingStates( gl, extensions, attributes, capabilities ) {
+
+ const maxVertexAttributes = gl.getParameter( 34921 );
+
+ const extension = capabilities.isWebGL2 ? null : extensions.get( 'OES_vertex_array_object' );
+ const vaoAvailable = capabilities.isWebGL2 || extension !== null;
+
+ const bindingStates = {};
+
+ const defaultState = createBindingState( null );
+ let currentState = defaultState;
+
+ function setup( object, material, program, geometry, index ) {
+
+ let updateBuffers = false;
+
+ if ( vaoAvailable ) {
+
+ const state = getBindingState( geometry, program, material );
+
+ if ( currentState !== state ) {
+
+ currentState = state;
+ bindVertexArrayObject( currentState.object );
+
+ }
+
+ updateBuffers = needsUpdate( geometry, index );
+
+ if ( updateBuffers ) saveCache( geometry, index );
+
+ } else {
+
+ const wireframe = ( material.wireframe === true );
+
+ if ( currentState.geometry !== geometry.id ||
+ currentState.program !== program.id ||
+ currentState.wireframe !== wireframe ) {
+
+ currentState.geometry = geometry.id;
+ currentState.program = program.id;
+ currentState.wireframe = wireframe;
+
+ updateBuffers = true;
+
+ }
+
+ }
+
+ if ( object.isInstancedMesh === true ) {
+
+ updateBuffers = true;
+
+ }
+
+ if ( index !== null ) {
+
+ attributes.update( index, 34963 );
+
+ }
+
+ if ( updateBuffers ) {
+
+ setupVertexAttributes( object, material, program, geometry );
+
+ if ( index !== null ) {
+
+ gl.bindBuffer( 34963, attributes.get( index ).buffer );
+
+ }
+
+ }
+
+ }
+
+ function createVertexArrayObject() {
+
+ if ( capabilities.isWebGL2 ) return gl.createVertexArray();
+
+ return extension.createVertexArrayOES();
+
+ }
+
+ function bindVertexArrayObject( vao ) {
+
+ if ( capabilities.isWebGL2 ) return gl.bindVertexArray( vao );
+
+ return extension.bindVertexArrayOES( vao );
+
+ }
+
+ function deleteVertexArrayObject( vao ) {
+
+ if ( capabilities.isWebGL2 ) return gl.deleteVertexArray( vao );
+
+ return extension.deleteVertexArrayOES( vao );
+
+ }
+
+ function getBindingState( geometry, program, material ) {
+
+ const wireframe = ( material.wireframe === true );
+
+ let programMap = bindingStates[ geometry.id ];
+
+ if ( programMap === undefined ) {
+
+ programMap = {};
+ bindingStates[ geometry.id ] = programMap;
+
+ }
+
+ let stateMap = programMap[ program.id ];
+
+ if ( stateMap === undefined ) {
+
+ stateMap = {};
+ programMap[ program.id ] = stateMap;
+
+ }
+
+ let state = stateMap[ wireframe ];
+
+ if ( state === undefined ) {
+
+ state = createBindingState( createVertexArrayObject() );
+ stateMap[ wireframe ] = state;
+
+ }
+
+ return state;
+
+ }
+
+ function createBindingState( vao ) {
+
+ const newAttributes = [];
+ const enabledAttributes = [];
+ const attributeDivisors = [];
+
+ for ( let i = 0; i < maxVertexAttributes; i ++ ) {
+
+ newAttributes[ i ] = 0;
+ enabledAttributes[ i ] = 0;
+ attributeDivisors[ i ] = 0;
+
+ }
+
+ return {
+
+ // for backward compatibility on non-VAO support browser
+ geometry: null,
+ program: null,
+ wireframe: false,
+
+ newAttributes: newAttributes,
+ enabledAttributes: enabledAttributes,
+ attributeDivisors: attributeDivisors,
+ object: vao,
+ attributes: {},
+ index: null
+
+ };
+
+ }
+
+ function needsUpdate( geometry, index ) {
+
+ const cachedAttributes = currentState.attributes;
+ const geometryAttributes = geometry.attributes;
+
+ let attributesNum = 0;
+
+ for ( const key in geometryAttributes ) {
+
+ const cachedAttribute = cachedAttributes[ key ];
+ const geometryAttribute = geometryAttributes[ key ];
+
+ if ( cachedAttribute === undefined ) return true;
+
+ if ( cachedAttribute.attribute !== geometryAttribute ) return true;
+
+ if ( cachedAttribute.data !== geometryAttribute.data ) return true;
+
+ attributesNum ++;
+
+ }
+
+ if ( currentState.attributesNum !== attributesNum ) return true;
+
+ if ( currentState.index !== index ) return true;
+
+ return false;
+
+ }
+
+ function saveCache( geometry, index ) {
+
+ const cache = {};
+ const attributes = geometry.attributes;
+ let attributesNum = 0;
+
+ for ( const key in attributes ) {
+
+ const attribute = attributes[ key ];
+
+ const data = {};
+ data.attribute = attribute;
+
+ if ( attribute.data ) {
+
+ data.data = attribute.data;
+
+ }
+
+ cache[ key ] = data;
+
+ attributesNum ++;
+
+ }
+
+ currentState.attributes = cache;
+ currentState.attributesNum = attributesNum;
+
+ currentState.index = index;
+
+ }
+
+ function initAttributes() {
+
+ const newAttributes = currentState.newAttributes;
+
+ for ( let i = 0, il = newAttributes.length; i < il; i ++ ) {
+
+ newAttributes[ i ] = 0;
+
+ }
+
+ }
+
+ function enableAttribute( attribute ) {
+
+ enableAttributeAndDivisor( attribute, 0 );
+
+ }
+
+ function enableAttributeAndDivisor( attribute, meshPerAttribute ) {
+
+ const newAttributes = currentState.newAttributes;
+ const enabledAttributes = currentState.enabledAttributes;
+ const attributeDivisors = currentState.attributeDivisors;
+
+ newAttributes[ attribute ] = 1;
+
+ if ( enabledAttributes[ attribute ] === 0 ) {
+
+ gl.enableVertexAttribArray( attribute );
+ enabledAttributes[ attribute ] = 1;
+
+ }
+
+ if ( attributeDivisors[ attribute ] !== meshPerAttribute ) {
+
+ const extension = capabilities.isWebGL2 ? gl : extensions.get( 'ANGLE_instanced_arrays' );
+
+ extension[ capabilities.isWebGL2 ? 'vertexAttribDivisor' : 'vertexAttribDivisorANGLE' ]( attribute, meshPerAttribute );
+ attributeDivisors[ attribute ] = meshPerAttribute;
+
+ }
+
+ }
+
+ function disableUnusedAttributes() {
+
+ const newAttributes = currentState.newAttributes;
+ const enabledAttributes = currentState.enabledAttributes;
+
+ for ( let i = 0, il = enabledAttributes.length; i < il; i ++ ) {
+
+ if ( enabledAttributes[ i ] !== newAttributes[ i ] ) {
+
+ gl.disableVertexAttribArray( i );
+ enabledAttributes[ i ] = 0;
+
+ }
+
+ }
+
+ }
+
+ function vertexAttribPointer( index, size, type, normalized, stride, offset ) {
+
+ if ( capabilities.isWebGL2 === true && ( type === 5124 || type === 5125 ) ) {
+
+ gl.vertexAttribIPointer( index, size, type, stride, offset );
+
+ } else {
+
+ gl.vertexAttribPointer( index, size, type, normalized, stride, offset );
+
+ }
+
+ }
+
+ function setupVertexAttributes( object, material, program, geometry ) {
+
+ if ( capabilities.isWebGL2 === false && ( object.isInstancedMesh || geometry.isInstancedBufferGeometry ) ) {
+
+ if ( extensions.get( 'ANGLE_instanced_arrays' ) === null ) return;
+
+ }
+
+ initAttributes();
+
+ const geometryAttributes = geometry.attributes;
+
+ const programAttributes = program.getAttributes();
+
+ const materialDefaultAttributeValues = material.defaultAttributeValues;
+
+ for ( const name in programAttributes ) {
+
+ const programAttribute = programAttributes[ name ];
+
+ if ( programAttribute >= 0 ) {
+
+ const geometryAttribute = geometryAttributes[ name ];
+
+ if ( geometryAttribute !== undefined ) {
+
+ const normalized = geometryAttribute.normalized;
+ const size = geometryAttribute.itemSize;
+
+ const attribute = attributes.get( geometryAttribute );
+
+ // TODO Attribute may not be available on context restore
+
+ if ( attribute === undefined ) continue;
+
+ const buffer = attribute.buffer;
+ const type = attribute.type;
+ const bytesPerElement = attribute.bytesPerElement;
+
+ if ( geometryAttribute.isInterleavedBufferAttribute ) {
+
+ const data = geometryAttribute.data;
+ const stride = data.stride;
+ const offset = geometryAttribute.offset;
+
+ if ( data && data.isInstancedInterleavedBuffer ) {
+
+ enableAttributeAndDivisor( programAttribute, data.meshPerAttribute );
+
+ if ( geometry._maxInstanceCount === undefined ) {
+
+ geometry._maxInstanceCount = data.meshPerAttribute * data.count;
+
+ }
+
+ } else {
+
+ enableAttribute( programAttribute );
+
+ }
+
+ gl.bindBuffer( 34962, buffer );
+ vertexAttribPointer( programAttribute, size, type, normalized, stride * bytesPerElement, offset * bytesPerElement );
+
+ } else {
+
+ if ( geometryAttribute.isInstancedBufferAttribute ) {
+
+ enableAttributeAndDivisor( programAttribute, geometryAttribute.meshPerAttribute );
+
+ if ( geometry._maxInstanceCount === undefined ) {
+
+ geometry._maxInstanceCount = geometryAttribute.meshPerAttribute * geometryAttribute.count;
+
+ }
+
+ } else {
+
+ enableAttribute( programAttribute );
+
+ }
+
+ gl.bindBuffer( 34962, buffer );
+ vertexAttribPointer( programAttribute, size, type, normalized, 0, 0 );
+
+ }
+
+ } else if ( name === 'instanceMatrix' ) {
+
+ const attribute = attributes.get( object.instanceMatrix );
+
+ // TODO Attribute may not be available on context restore
+
+ if ( attribute === undefined ) continue;
+
+ const buffer = attribute.buffer;
+ const type = attribute.type;
+
+ enableAttributeAndDivisor( programAttribute + 0, 1 );
+ enableAttributeAndDivisor( programAttribute + 1, 1 );
+ enableAttributeAndDivisor( programAttribute + 2, 1 );
+ enableAttributeAndDivisor( programAttribute + 3, 1 );
+
+ gl.bindBuffer( 34962, buffer );
+
+ gl.vertexAttribPointer( programAttribute + 0, 4, type, false, 64, 0 );
+ gl.vertexAttribPointer( programAttribute + 1, 4, type, false, 64, 16 );
+ gl.vertexAttribPointer( programAttribute + 2, 4, type, false, 64, 32 );
+ gl.vertexAttribPointer( programAttribute + 3, 4, type, false, 64, 48 );
+
+ } else if ( name === 'instanceColor' ) {
+
+ const attribute = attributes.get( object.instanceColor );
+
+ // TODO Attribute may not be available on context restore
+
+ if ( attribute === undefined ) continue;
+
+ const buffer = attribute.buffer;
+ const type = attribute.type;
+
+ enableAttributeAndDivisor( programAttribute, 1 );
+
+ gl.bindBuffer( 34962, buffer );
+
+ gl.vertexAttribPointer( programAttribute, 3, type, false, 12, 0 );
+
+ } else if ( materialDefaultAttributeValues !== undefined ) {
+
+ const value = materialDefaultAttributeValues[ name ];
+
+ if ( value !== undefined ) {
+
+ switch ( value.length ) {
+
+ case 2:
+ gl.vertexAttrib2fv( programAttribute, value );
+ break;
+
+ case 3:
+ gl.vertexAttrib3fv( programAttribute, value );
+ break;
+
+ case 4:
+ gl.vertexAttrib4fv( programAttribute, value );
+ break;
+
+ default:
+ gl.vertexAttrib1fv( programAttribute, value );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ }
+
+ disableUnusedAttributes();
+
+ }
+
+ function dispose() {
+
+ reset();
+
+ for ( const geometryId in bindingStates ) {
+
+ const programMap = bindingStates[ geometryId ];
+
+ for ( const programId in programMap ) {
+
+ const stateMap = programMap[ programId ];
+
+ for ( const wireframe in stateMap ) {
+
+ deleteVertexArrayObject( stateMap[ wireframe ].object );
+
+ delete stateMap[ wireframe ];
+
+ }
+
+ delete programMap[ programId ];
+
+ }
+
+ delete bindingStates[ geometryId ];
+
+ }
+
+ }
+
+ function releaseStatesOfGeometry( geometry ) {
+
+ if ( bindingStates[ geometry.id ] === undefined ) return;
+
+ const programMap = bindingStates[ geometry.id ];
+
+ for ( const programId in programMap ) {
+
+ const stateMap = programMap[ programId ];
+
+ for ( const wireframe in stateMap ) {
+
+ deleteVertexArrayObject( stateMap[ wireframe ].object );
+
+ delete stateMap[ wireframe ];
+
+ }
+
+ delete programMap[ programId ];
+
+ }
+
+ delete bindingStates[ geometry.id ];
+
+ }
+
+ function releaseStatesOfProgram( program ) {
+
+ for ( const geometryId in bindingStates ) {
+
+ const programMap = bindingStates[ geometryId ];
+
+ if ( programMap[ program.id ] === undefined ) continue;
+
+ const stateMap = programMap[ program.id ];
+
+ for ( const wireframe in stateMap ) {
+
+ deleteVertexArrayObject( stateMap[ wireframe ].object );
+
+ delete stateMap[ wireframe ];
+
+ }
+
+ delete programMap[ program.id ];
+
+ }
+
+ }
+
+ function reset() {
+
+ resetDefaultState();
+
+ if ( currentState === defaultState ) return;
+
+ currentState = defaultState;
+ bindVertexArrayObject( currentState.object );
+
+ }
+
+ // for backward-compatilibity
+
+ function resetDefaultState() {
+
+ defaultState.geometry = null;
+ defaultState.program = null;
+ defaultState.wireframe = false;
+
+ }
+
+ return {
+
+ setup: setup,
+ reset: reset,
+ resetDefaultState: resetDefaultState,
+ dispose: dispose,
+ releaseStatesOfGeometry: releaseStatesOfGeometry,
+ releaseStatesOfProgram: releaseStatesOfProgram,
+
+ initAttributes: initAttributes,
+ enableAttribute: enableAttribute,
+ disableUnusedAttributes: disableUnusedAttributes
+
+ };
+
+}
+
+function WebGLBufferRenderer( gl, extensions, info, capabilities ) {
+
+ const isWebGL2 = capabilities.isWebGL2;
+
+ let mode;
+
+ function setMode( value ) {
+
+ mode = value;
+
+ }
+
+ function render( start, count ) {
+
+ gl.drawArrays( mode, start, count );
+
+ info.update( count, mode, 1 );
+
+ }
+
+ function renderInstances( start, count, primcount ) {
+
+ if ( primcount === 0 ) return;
+
+ let extension, methodName;
+
+ if ( isWebGL2 ) {
+
+ extension = gl;
+ methodName = 'drawArraysInstanced';
+
+ } else {
+
+ extension = extensions.get( 'ANGLE_instanced_arrays' );
+ methodName = 'drawArraysInstancedANGLE';
+
+ if ( extension === null ) {
+
+ console.error( 'THREE.WebGLBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );
+ return;
+
+ }
+
+ }
+
+ extension[ methodName ]( mode, start, count, primcount );
+
+ info.update( count, mode, primcount );
+
+ }
+
+ //
+
+ this.setMode = setMode;
+ this.render = render;
+ this.renderInstances = renderInstances;
+
+}
+
+function WebGLCapabilities( gl, extensions, parameters ) {
+
+ let maxAnisotropy;
+
+ function getMaxAnisotropy() {
+
+ if ( maxAnisotropy !== undefined ) return maxAnisotropy;
+
+ const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
+
+ if ( extension !== null ) {
+
+ maxAnisotropy = gl.getParameter( extension.MAX_TEXTURE_MAX_ANISOTROPY_EXT );
+
+ } else {
+
+ maxAnisotropy = 0;
+
+ }
+
+ return maxAnisotropy;
+
+ }
+
+ function getMaxPrecision( precision ) {
+
+ if ( precision === 'highp' ) {
+
+ if ( gl.getShaderPrecisionFormat( 35633, 36338 ).precision > 0 &&
+ gl.getShaderPrecisionFormat( 35632, 36338 ).precision > 0 ) {
+
+ return 'highp';
+
+ }
+
+ precision = 'mediump';
+
+ }
+
+ if ( precision === 'mediump' ) {
+
+ if ( gl.getShaderPrecisionFormat( 35633, 36337 ).precision > 0 &&
+ gl.getShaderPrecisionFormat( 35632, 36337 ).precision > 0 ) {
+
+ return 'mediump';
+
+ }
+
+ }
+
+ return 'lowp';
+
+ }
+
+ /* eslint-disable no-undef */
+ const isWebGL2 = ( typeof WebGL2RenderingContext !== 'undefined' && gl instanceof WebGL2RenderingContext ) ||
+ ( typeof WebGL2ComputeRenderingContext !== 'undefined' && gl instanceof WebGL2ComputeRenderingContext );
+ /* eslint-enable no-undef */
+
+ let precision = parameters.precision !== undefined ? parameters.precision : 'highp';
+ const maxPrecision = getMaxPrecision( precision );
+
+ if ( maxPrecision !== precision ) {
+
+ console.warn( 'THREE.WebGLRenderer:', precision, 'not supported, using', maxPrecision, 'instead.' );
+ precision = maxPrecision;
+
+ }
+
+ const logarithmicDepthBuffer = parameters.logarithmicDepthBuffer === true;
+
+ const maxTextures = gl.getParameter( 34930 );
+ const maxVertexTextures = gl.getParameter( 35660 );
+ const maxTextureSize = gl.getParameter( 3379 );
+ const maxCubemapSize = gl.getParameter( 34076 );
+
+ const maxAttributes = gl.getParameter( 34921 );
+ const maxVertexUniforms = gl.getParameter( 36347 );
+ const maxVaryings = gl.getParameter( 36348 );
+ const maxFragmentUniforms = gl.getParameter( 36349 );
+
+ const vertexTextures = maxVertexTextures > 0;
+ const floatFragmentTextures = isWebGL2 || !! extensions.get( 'OES_texture_float' );
+ const floatVertexTextures = vertexTextures && floatFragmentTextures;
+
+ const maxSamples = isWebGL2 ? gl.getParameter( 36183 ) : 0;
+
+ return {
+
+ isWebGL2: isWebGL2,
+
+ getMaxAnisotropy: getMaxAnisotropy,
+ getMaxPrecision: getMaxPrecision,
+
+ precision: precision,
+ logarithmicDepthBuffer: logarithmicDepthBuffer,
+
+ maxTextures: maxTextures,
+ maxVertexTextures: maxVertexTextures,
+ maxTextureSize: maxTextureSize,
+ maxCubemapSize: maxCubemapSize,
+
+ maxAttributes: maxAttributes,
+ maxVertexUniforms: maxVertexUniforms,
+ maxVaryings: maxVaryings,
+ maxFragmentUniforms: maxFragmentUniforms,
+
+ vertexTextures: vertexTextures,
+ floatFragmentTextures: floatFragmentTextures,
+ floatVertexTextures: floatVertexTextures,
+
+ maxSamples: maxSamples
+
+ };
+
+}
+
+function WebGLClipping( properties ) {
+
+ const scope = this;
+
+ let globalState = null,
+ numGlobalPlanes = 0,
+ localClippingEnabled = false,
+ renderingShadows = false;
+
+ const plane = new Plane(),
+ viewNormalMatrix = new Matrix3(),
+
+ uniform = { value: null, needsUpdate: false };
+
+ this.uniform = uniform;
+ this.numPlanes = 0;
+ this.numIntersection = 0;
+
+ this.init = function ( planes, enableLocalClipping, camera ) {
+
+ const enabled =
+ planes.length !== 0 ||
+ enableLocalClipping ||
+ // enable state of previous frame - the clipping code has to
+ // run another frame in order to reset the state:
+ numGlobalPlanes !== 0 ||
+ localClippingEnabled;
+
+ localClippingEnabled = enableLocalClipping;
+
+ globalState = projectPlanes( planes, camera, 0 );
+ numGlobalPlanes = planes.length;
+
+ return enabled;
+
+ };
+
+ this.beginShadows = function () {
+
+ renderingShadows = true;
+ projectPlanes( null );
+
+ };
+
+ this.endShadows = function () {
+
+ renderingShadows = false;
+ resetGlobalState();
+
+ };
+
+ this.setState = function ( material, camera, useCache ) {
+
+ const planes = material.clippingPlanes,
+ clipIntersection = material.clipIntersection,
+ clipShadows = material.clipShadows;
+
+ const materialProperties = properties.get( material );
+
+ if ( ! localClippingEnabled || planes === null || planes.length === 0 || renderingShadows && ! clipShadows ) {
+
+ // there's no local clipping
+
+ if ( renderingShadows ) {
+
+ // there's no global clipping
+
+ projectPlanes( null );
+
+ } else {
+
+ resetGlobalState();
+
+ }
+
+ } else {
+
+ const nGlobal = renderingShadows ? 0 : numGlobalPlanes,
+ lGlobal = nGlobal * 4;
+
+ let dstArray = materialProperties.clippingState || null;
+
+ uniform.value = dstArray; // ensure unique state
+
+ dstArray = projectPlanes( planes, camera, lGlobal, useCache );
+
+ for ( let i = 0; i !== lGlobal; ++ i ) {
+
+ dstArray[ i ] = globalState[ i ];
+
+ }
+
+ materialProperties.clippingState = dstArray;
+ this.numIntersection = clipIntersection ? this.numPlanes : 0;
+ this.numPlanes += nGlobal;
+
+ }
+
+
+ };
+
+ function resetGlobalState() {
+
+ if ( uniform.value !== globalState ) {
+
+ uniform.value = globalState;
+ uniform.needsUpdate = numGlobalPlanes > 0;
+
+ }
+
+ scope.numPlanes = numGlobalPlanes;
+ scope.numIntersection = 0;
+
+ }
+
+ function projectPlanes( planes, camera, dstOffset, skipTransform ) {
+
+ const nPlanes = planes !== null ? planes.length : 0;
+ let dstArray = null;
+
+ if ( nPlanes !== 0 ) {
+
+ dstArray = uniform.value;
+
+ if ( skipTransform !== true || dstArray === null ) {
+
+ const flatSize = dstOffset + nPlanes * 4,
+ viewMatrix = camera.matrixWorldInverse;
+
+ viewNormalMatrix.getNormalMatrix( viewMatrix );
+
+ if ( dstArray === null || dstArray.length < flatSize ) {
+
+ dstArray = new Float32Array( flatSize );
+
+ }
+
+ for ( let i = 0, i4 = dstOffset; i !== nPlanes; ++ i, i4 += 4 ) {
+
+ plane.copy( planes[ i ] ).applyMatrix4( viewMatrix, viewNormalMatrix );
+
+ plane.normal.toArray( dstArray, i4 );
+ dstArray[ i4 + 3 ] = plane.constant;
+
+ }
+
+ }
+
+ uniform.value = dstArray;
+ uniform.needsUpdate = true;
+
+ }
+
+ scope.numPlanes = nPlanes;
+ scope.numIntersection = 0;
+
+ return dstArray;
+
+ }
+
+}
+
+function WebGLCubeMaps( renderer ) {
+
+ let cubemaps = new WeakMap();
+
+ function mapTextureMapping( texture, mapping ) {
+
+ if ( mapping === EquirectangularReflectionMapping ) {
+
+ texture.mapping = CubeReflectionMapping;
+
+ } else if ( mapping === EquirectangularRefractionMapping ) {
+
+ texture.mapping = CubeRefractionMapping;
+
+ }
+
+ return texture;
+
+ }
+
+ function get( texture ) {
+
+ if ( texture && texture.isTexture ) {
+
+ const mapping = texture.mapping;
+
+ if ( mapping === EquirectangularReflectionMapping || mapping === EquirectangularRefractionMapping ) {
+
+ if ( cubemaps.has( texture ) ) {
+
+ const cubemap = cubemaps.get( texture ).texture;
+ return mapTextureMapping( cubemap, texture.mapping );
+
+ } else {
+
+ const image = texture.image;
+
+ if ( image && image.height > 0 ) {
+
+ const currentRenderList = renderer.getRenderList();
+ const currentRenderTarget = renderer.getRenderTarget();
+
+ const renderTarget = new WebGLCubeRenderTarget( image.height / 2 );
+ renderTarget.fromEquirectangularTexture( renderer, texture );
+ cubemaps.set( texture, renderTarget );
+
+ renderer.setRenderTarget( currentRenderTarget );
+ renderer.setRenderList( currentRenderList );
+
+ texture.addEventListener( 'dispose', onTextureDispose );
+
+ return mapTextureMapping( renderTarget.texture, texture.mapping );
+
+ } else {
+
+ // image not yet ready. try the conversion next frame
+
+ return null;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ return texture;
+
+ }
+
+ function onTextureDispose( event ) {
+
+ const texture = event.target;
+
+ texture.removeEventListener( 'dispose', onTextureDispose );
+
+ const cubemap = cubemaps.get( texture );
+
+ if ( cubemap !== undefined ) {
+
+ cubemaps.delete( texture );
+ cubemap.dispose();
+
+ }
+
+ }
+
+ function dispose() {
+
+ cubemaps = new WeakMap();
+
+ }
+
+ return {
+ get: get,
+ dispose: dispose
+ };
+
+}
+
+function WebGLExtensions( gl ) {
+
+ const extensions = {};
+
+ function getExtension( name ) {
+
+ if ( extensions[ name ] !== undefined ) {
+
+ return extensions[ name ];
+
+ }
+
+ let extension;
+
+ switch ( name ) {
+
+ case 'WEBGL_depth_texture':
+ extension = gl.getExtension( 'WEBGL_depth_texture' ) || gl.getExtension( 'MOZ_WEBGL_depth_texture' ) || gl.getExtension( 'WEBKIT_WEBGL_depth_texture' );
+ break;
+
+ case 'EXT_texture_filter_anisotropic':
+ extension = gl.getExtension( 'EXT_texture_filter_anisotropic' ) || gl.getExtension( 'MOZ_EXT_texture_filter_anisotropic' ) || gl.getExtension( 'WEBKIT_EXT_texture_filter_anisotropic' );
+ break;
+
+ case 'WEBGL_compressed_texture_s3tc':
+ extension = gl.getExtension( 'WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'MOZ_WEBGL_compressed_texture_s3tc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_s3tc' );
+ break;
+
+ case 'WEBGL_compressed_texture_pvrtc':
+ extension = gl.getExtension( 'WEBGL_compressed_texture_pvrtc' ) || gl.getExtension( 'WEBKIT_WEBGL_compressed_texture_pvrtc' );
+ break;
+
+ default:
+ extension = gl.getExtension( name );
+
+ }
+
+ extensions[ name ] = extension;
+
+ return extension;
+
+ }
+
+ return {
+
+ has: function ( name ) {
+
+ return getExtension( name ) !== null;
+
+ },
+
+ init: function ( capabilities ) {
+
+ if ( capabilities.isWebGL2 ) {
+
+ getExtension( 'EXT_color_buffer_float' );
+
+ } else {
+
+ getExtension( 'WEBGL_depth_texture' );
+ getExtension( 'OES_texture_float' );
+ getExtension( 'OES_texture_half_float' );
+ getExtension( 'OES_texture_half_float_linear' );
+ getExtension( 'OES_standard_derivatives' );
+ getExtension( 'OES_element_index_uint' );
+ getExtension( 'OES_vertex_array_object' );
+ getExtension( 'ANGLE_instanced_arrays' );
+
+ }
+
+ getExtension( 'OES_texture_float_linear' );
+ getExtension( 'EXT_color_buffer_half_float' );
+
+ },
+
+ get: function ( name ) {
+
+ const extension = getExtension( name );
+
+ if ( extension === null ) {
+
+ console.warn( 'THREE.WebGLRenderer: ' + name + ' extension not supported.' );
+
+ }
+
+ return extension;
+
+ }
+
+ };
+
+}
+
+function WebGLGeometries( gl, attributes, info, bindingStates ) {
+
+ const geometries = {};
+ const wireframeAttributes = new WeakMap();
+
+ function onGeometryDispose( event ) {
+
+ const geometry = event.target;
+
+ if ( geometry.index !== null ) {
+
+ attributes.remove( geometry.index );
+
+ }
+
+ for ( const name in geometry.attributes ) {
+
+ attributes.remove( geometry.attributes[ name ] );
+
+ }
+
+ geometry.removeEventListener( 'dispose', onGeometryDispose );
+
+ delete geometries[ geometry.id ];
+
+ const attribute = wireframeAttributes.get( geometry );
+
+ if ( attribute ) {
+
+ attributes.remove( attribute );
+ wireframeAttributes.delete( geometry );
+
+ }
+
+ bindingStates.releaseStatesOfGeometry( geometry );
+
+ if ( geometry.isInstancedBufferGeometry === true ) {
+
+ delete geometry._maxInstanceCount;
+
+ }
+
+ //
+
+ info.memory.geometries --;
+
+ }
+
+ function get( object, geometry ) {
+
+ if ( geometries[ geometry.id ] === true ) return geometry;
+
+ geometry.addEventListener( 'dispose', onGeometryDispose );
+
+ geometries[ geometry.id ] = true;
+
+ info.memory.geometries ++;
+
+ return geometry;
+
+ }
+
+ function update( geometry ) {
+
+ const geometryAttributes = geometry.attributes;
+
+ // Updating index buffer in VAO now. See WebGLBindingStates.
+
+ for ( const name in geometryAttributes ) {
+
+ attributes.update( geometryAttributes[ name ], 34962 );
+
+ }
+
+ // morph targets
+
+ const morphAttributes = geometry.morphAttributes;
+
+ for ( const name in morphAttributes ) {
+
+ const array = morphAttributes[ name ];
+
+ for ( let i = 0, l = array.length; i < l; i ++ ) {
+
+ attributes.update( array[ i ], 34962 );
+
+ }
+
+ }
+
+ }
+
+ function updateWireframeAttribute( geometry ) {
+
+ const indices = [];
+
+ const geometryIndex = geometry.index;
+ const geometryPosition = geometry.attributes.position;
+ let version = 0;
+
+ if ( geometryIndex !== null ) {
+
+ const array = geometryIndex.array;
+ version = geometryIndex.version;
+
+ for ( let i = 0, l = array.length; i < l; i += 3 ) {
+
+ const a = array[ i + 0 ];
+ const b = array[ i + 1 ];
+ const c = array[ i + 2 ];
+
+ indices.push( a, b, b, c, c, a );
+
+ }
+
+ } else {
+
+ const array = geometryPosition.array;
+ version = geometryPosition.version;
+
+ for ( let i = 0, l = ( array.length / 3 ) - 1; i < l; i += 3 ) {
+
+ const a = i + 0;
+ const b = i + 1;
+ const c = i + 2;
+
+ indices.push( a, b, b, c, c, a );
+
+ }
+
+ }
+
+ const attribute = new ( arrayMax( indices ) > 65535 ? Uint32BufferAttribute : Uint16BufferAttribute )( indices, 1 );
+ attribute.version = version;
+
+ // Updating index buffer in VAO now. See WebGLBindingStates
+
+ //
+
+ const previousAttribute = wireframeAttributes.get( geometry );
+
+ if ( previousAttribute ) attributes.remove( previousAttribute );
+
+ //
+
+ wireframeAttributes.set( geometry, attribute );
+
+ }
+
+ function getWireframeAttribute( geometry ) {
+
+ const currentAttribute = wireframeAttributes.get( geometry );
+
+ if ( currentAttribute ) {
+
+ const geometryIndex = geometry.index;
+
+ if ( geometryIndex !== null ) {
+
+ // if the attribute is obsolete, create a new one
+
+ if ( currentAttribute.version < geometryIndex.version ) {
+
+ updateWireframeAttribute( geometry );
+
+ }
+
+ }
+
+ } else {
+
+ updateWireframeAttribute( geometry );
+
+ }
+
+ return wireframeAttributes.get( geometry );
+
+ }
+
+ return {
+
+ get: get,
+ update: update,
+
+ getWireframeAttribute: getWireframeAttribute
+
+ };
+
+}
+
+function WebGLIndexedBufferRenderer( gl, extensions, info, capabilities ) {
+
+ const isWebGL2 = capabilities.isWebGL2;
+
+ let mode;
+
+ function setMode( value ) {
+
+ mode = value;
+
+ }
+
+ let type, bytesPerElement;
+
+ function setIndex( value ) {
+
+ type = value.type;
+ bytesPerElement = value.bytesPerElement;
+
+ }
+
+ function render( start, count ) {
+
+ gl.drawElements( mode, count, type, start * bytesPerElement );
+
+ info.update( count, mode, 1 );
+
+ }
+
+ function renderInstances( start, count, primcount ) {
+
+ if ( primcount === 0 ) return;
+
+ let extension, methodName;
+
+ if ( isWebGL2 ) {
+
+ extension = gl;
+ methodName = 'drawElementsInstanced';
+
+ } else {
+
+ extension = extensions.get( 'ANGLE_instanced_arrays' );
+ methodName = 'drawElementsInstancedANGLE';
+
+ if ( extension === null ) {
+
+ console.error( 'THREE.WebGLIndexedBufferRenderer: using THREE.InstancedBufferGeometry but hardware does not support extension ANGLE_instanced_arrays.' );
+ return;
+
+ }
+
+ }
+
+ extension[ methodName ]( mode, count, type, start * bytesPerElement, primcount );
+
+ info.update( count, mode, primcount );
+
+ }
+
+ //
+
+ this.setMode = setMode;
+ this.setIndex = setIndex;
+ this.render = render;
+ this.renderInstances = renderInstances;
+
+}
+
+function WebGLInfo( gl ) {
+
+ const memory = {
+ geometries: 0,
+ textures: 0
+ };
+
+ const render = {
+ frame: 0,
+ calls: 0,
+ triangles: 0,
+ points: 0,
+ lines: 0
+ };
+
+ function update( count, mode, instanceCount ) {
+
+ render.calls ++;
+
+ switch ( mode ) {
+
+ case 4:
+ render.triangles += instanceCount * ( count / 3 );
+ break;
+
+ case 1:
+ render.lines += instanceCount * ( count / 2 );
+ break;
+
+ case 3:
+ render.lines += instanceCount * ( count - 1 );
+ break;
+
+ case 2:
+ render.lines += instanceCount * count;
+ break;
+
+ case 0:
+ render.points += instanceCount * count;
+ break;
+
+ default:
+ console.error( 'THREE.WebGLInfo: Unknown draw mode:', mode );
+ break;
+
+ }
+
+ }
+
+ function reset() {
+
+ render.frame ++;
+ render.calls = 0;
+ render.triangles = 0;
+ render.points = 0;
+ render.lines = 0;
+
+ }
+
+ return {
+ memory: memory,
+ render: render,
+ programs: null,
+ autoReset: true,
+ reset: reset,
+ update: update
+ };
+
+}
+
+function numericalSort( a, b ) {
+
+ return a[ 0 ] - b[ 0 ];
+
+}
+
+function absNumericalSort( a, b ) {
+
+ return Math.abs( b[ 1 ] ) - Math.abs( a[ 1 ] );
+
+}
+
+function WebGLMorphtargets( gl ) {
+
+ const influencesList = {};
+ const morphInfluences = new Float32Array( 8 );
+
+ const workInfluences = [];
+
+ for ( let i = 0; i < 8; i ++ ) {
+
+ workInfluences[ i ] = [ i, 0 ];
+
+ }
+
+ function update( object, geometry, material, program ) {
+
+ const objectInfluences = object.morphTargetInfluences;
+
+ // When object doesn't have morph target influences defined, we treat it as a 0-length array
+ // This is important to make sure we set up morphTargetBaseInfluence / morphTargetInfluences
+
+ const length = objectInfluences === undefined ? 0 : objectInfluences.length;
+
+ let influences = influencesList[ geometry.id ];
+
+ if ( influences === undefined ) {
+
+ // initialise list
+
+ influences = [];
+
+ for ( let i = 0; i < length; i ++ ) {
+
+ influences[ i ] = [ i, 0 ];
+
+ }
+
+ influencesList[ geometry.id ] = influences;
+
+ }
+
+ // Collect influences
+
+ for ( let i = 0; i < length; i ++ ) {
+
+ const influence = influences[ i ];
+
+ influence[ 0 ] = i;
+ influence[ 1 ] = objectInfluences[ i ];
+
+ }
+
+ influences.sort( absNumericalSort );
+
+ for ( let i = 0; i < 8; i ++ ) {
+
+ if ( i < length && influences[ i ][ 1 ] ) {
+
+ workInfluences[ i ][ 0 ] = influences[ i ][ 0 ];
+ workInfluences[ i ][ 1 ] = influences[ i ][ 1 ];
+
+ } else {
+
+ workInfluences[ i ][ 0 ] = Number.MAX_SAFE_INTEGER;
+ workInfluences[ i ][ 1 ] = 0;
+
+ }
+
+ }
+
+ workInfluences.sort( numericalSort );
+
+ const morphTargets = material.morphTargets && geometry.morphAttributes.position;
+ const morphNormals = material.morphNormals && geometry.morphAttributes.normal;
+
+ let morphInfluencesSum = 0;
+
+ for ( let i = 0; i < 8; i ++ ) {
+
+ const influence = workInfluences[ i ];
+ const index = influence[ 0 ];
+ const value = influence[ 1 ];
+
+ if ( index !== Number.MAX_SAFE_INTEGER && value ) {
+
+ if ( morphTargets && geometry.getAttribute( 'morphTarget' + i ) !== morphTargets[ index ] ) {
+
+ geometry.setAttribute( 'morphTarget' + i, morphTargets[ index ] );
+
+ }
+
+ if ( morphNormals && geometry.getAttribute( 'morphNormal' + i ) !== morphNormals[ index ] ) {
+
+ geometry.setAttribute( 'morphNormal' + i, morphNormals[ index ] );
+
+ }
+
+ morphInfluences[ i ] = value;
+ morphInfluencesSum += value;
+
+ } else {
+
+ if ( morphTargets && geometry.hasAttribute( 'morphTarget' + i ) === true ) {
+
+ geometry.deleteAttribute( 'morphTarget' + i );
+
+ }
+
+ if ( morphNormals && geometry.hasAttribute( 'morphNormal' + i ) === true ) {
+
+ geometry.deleteAttribute( 'morphNormal' + i );
+
+ }
+
+ morphInfluences[ i ] = 0;
+
+ }
+
+ }
+
+ // GLSL shader uses formula baseinfluence * base + sum(target * influence)
+ // This allows us to switch between absolute morphs and relative morphs without changing shader code
+ // When baseinfluence = 1 - sum(influence), the above is equivalent to sum((target - base) * influence)
+ const morphBaseInfluence = geometry.morphTargetsRelative ? 1 : 1 - morphInfluencesSum;
+
+ program.getUniforms().setValue( gl, 'morphTargetBaseInfluence', morphBaseInfluence );
+ program.getUniforms().setValue( gl, 'morphTargetInfluences', morphInfluences );
+
+ }
+
+ return {
+
+ update: update
+
+ };
+
+}
+
+function WebGLObjects( gl, geometries, attributes, info ) {
+
+ let updateMap = new WeakMap();
+
+ function update( object ) {
+
+ const frame = info.render.frame;
+
+ const geometry = object.geometry;
+ const buffergeometry = geometries.get( object, geometry );
+
+ // Update once per frame
+
+ if ( updateMap.get( buffergeometry ) !== frame ) {
+
+ geometries.update( buffergeometry );
+
+ updateMap.set( buffergeometry, frame );
+
+ }
+
+ if ( object.isInstancedMesh ) {
+
+ if ( object.hasEventListener( 'dispose', onInstancedMeshDispose ) === false ) {
+
+ object.addEventListener( 'dispose', onInstancedMeshDispose );
+
+ }
+
+ attributes.update( object.instanceMatrix, 34962 );
+
+ if ( object.instanceColor !== null ) {
+
+ attributes.update( object.instanceColor, 34962 );
+
+ }
+
+ }
+
+ return buffergeometry;
+
+ }
+
+ function dispose() {
+
+ updateMap = new WeakMap();
+
+ }
+
+ function onInstancedMeshDispose( event ) {
+
+ const instancedMesh = event.target;
+
+ instancedMesh.removeEventListener( 'dispose', onInstancedMeshDispose );
+
+ attributes.remove( instancedMesh.instanceMatrix );
+
+ if ( instancedMesh.instanceColor !== null ) attributes.remove( instancedMesh.instanceColor );
+
+ }
+
+ return {
+
+ update: update,
+ dispose: dispose
+
+ };
+
+}
+
+function DataTexture2DArray( data = null, width = 1, height = 1, depth = 1 ) {
+
+ Texture.call( this, null );
+
+ this.image = { data, width, height, depth };
+
+ this.magFilter = NearestFilter;
+ this.minFilter = NearestFilter;
+
+ this.wrapR = ClampToEdgeWrapping;
+
+ this.generateMipmaps = false;
+ this.flipY = false;
+
+ this.needsUpdate = true;
+
+}
+
+DataTexture2DArray.prototype = Object.create( Texture.prototype );
+DataTexture2DArray.prototype.constructor = DataTexture2DArray;
+DataTexture2DArray.prototype.isDataTexture2DArray = true;
+
+function DataTexture3D( data = null, width = 1, height = 1, depth = 1 ) {
+
+ // We're going to add .setXXX() methods for setting properties later.
+ // Users can still set in DataTexture3D directly.
+ //
+ // const texture = new THREE.DataTexture3D( data, width, height, depth );
+ // texture.anisotropy = 16;
+ //
+ // See #14839
+
+ Texture.call( this, null );
+
+ this.image = { data, width, height, depth };
+
+ this.magFilter = NearestFilter;
+ this.minFilter = NearestFilter;
+
+ this.wrapR = ClampToEdgeWrapping;
+
+ this.generateMipmaps = false;
+ this.flipY = false;
+
+ this.needsUpdate = true;
+
+
+}
+
+DataTexture3D.prototype = Object.create( Texture.prototype );
+DataTexture3D.prototype.constructor = DataTexture3D;
+DataTexture3D.prototype.isDataTexture3D = true;
+
+/**
+ * Uniforms of a program.
+ * Those form a tree structure with a special top-level container for the root,
+ * which you get by calling 'new WebGLUniforms( gl, program )'.
+ *
+ *
+ * Properties of inner nodes including the top-level container:
+ *
+ * .seq - array of nested uniforms
+ * .map - nested uniforms by name
+ *
+ *
+ * Methods of all nodes except the top-level container:
+ *
+ * .setValue( gl, value, [textures] )
+ *
+ * uploads a uniform value(s)
+ * the 'textures' parameter is needed for sampler uniforms
+ *
+ *
+ * Static methods of the top-level container (textures factorizations):
+ *
+ * .upload( gl, seq, values, textures )
+ *
+ * sets uniforms in 'seq' to 'values[id].value'
+ *
+ * .seqWithValue( seq, values ) : filteredSeq
+ *
+ * filters 'seq' entries with corresponding entry in values
+ *
+ *
+ * Methods of the top-level container (textures factorizations):
+ *
+ * .setValue( gl, name, value, textures )
+ *
+ * sets uniform with name 'name' to 'value'
+ *
+ * .setOptional( gl, obj, prop )
+ *
+ * like .set for an optional property of the object
+ *
+ */
+
+const emptyTexture = new Texture();
+const emptyTexture2dArray = new DataTexture2DArray();
+const emptyTexture3d = new DataTexture3D();
+const emptyCubeTexture = new CubeTexture();
+
+// --- Utilities ---
+
+// Array Caches (provide typed arrays for temporary by size)
+
+const arrayCacheF32 = [];
+const arrayCacheI32 = [];
+
+// Float32Array caches used for uploading Matrix uniforms
+
+const mat4array = new Float32Array( 16 );
+const mat3array = new Float32Array( 9 );
+const mat2array = new Float32Array( 4 );
+
+// Flattening for arrays of vectors and matrices
+
+function flatten( array, nBlocks, blockSize ) {
+
+ const firstElem = array[ 0 ];
+
+ if ( firstElem <= 0 || firstElem > 0 ) return array;
+ // unoptimized: ! isNaN( firstElem )
+ // see http://jacksondunstan.com/articles/983
+
+ const n = nBlocks * blockSize;
+ let r = arrayCacheF32[ n ];
+
+ if ( r === undefined ) {
+
+ r = new Float32Array( n );
+ arrayCacheF32[ n ] = r;
+
+ }
+
+ if ( nBlocks !== 0 ) {
+
+ firstElem.toArray( r, 0 );
+
+ for ( let i = 1, offset = 0; i !== nBlocks; ++ i ) {
+
+ offset += blockSize;
+ array[ i ].toArray( r, offset );
+
+ }
+
+ }
+
+ return r;
+
+}
+
+function arraysEqual( a, b ) {
+
+ if ( a.length !== b.length ) return false;
+
+ for ( let i = 0, l = a.length; i < l; i ++ ) {
+
+ if ( a[ i ] !== b[ i ] ) return false;
+
+ }
+
+ return true;
+
+}
+
+function copyArray( a, b ) {
+
+ for ( let i = 0, l = b.length; i < l; i ++ ) {
+
+ a[ i ] = b[ i ];
+
+ }
+
+}
+
+// Texture unit allocation
+
+function allocTexUnits( textures, n ) {
+
+ let r = arrayCacheI32[ n ];
+
+ if ( r === undefined ) {
+
+ r = new Int32Array( n );
+ arrayCacheI32[ n ] = r;
+
+ }
+
+ for ( let i = 0; i !== n; ++ i ) {
+
+ r[ i ] = textures.allocateTextureUnit();
+
+ }
+
+ return r;
+
+}
+
+// --- Setters ---
+
+// Note: Defining these methods externally, because they come in a bunch
+// and this way their names minify.
+
+// Single scalar
+
+function setValueV1f( gl, v ) {
+
+ const cache = this.cache;
+
+ if ( cache[ 0 ] === v ) return;
+
+ gl.uniform1f( this.addr, v );
+
+ cache[ 0 ] = v;
+
+}
+
+// Single float vector (from flat array or THREE.VectorN)
+
+function setValueV2f( gl, v ) {
+
+ const cache = this.cache;
+
+ if ( v.x !== undefined ) {
+
+ if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y ) {
+
+ gl.uniform2f( this.addr, v.x, v.y );
+
+ cache[ 0 ] = v.x;
+ cache[ 1 ] = v.y;
+
+ }
+
+ } else {
+
+ if ( arraysEqual( cache, v ) ) return;
+
+ gl.uniform2fv( this.addr, v );
+
+ copyArray( cache, v );
+
+ }
+
+}
+
+function setValueV3f( gl, v ) {
+
+ const cache = this.cache;
+
+ if ( v.x !== undefined ) {
+
+ if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z ) {
+
+ gl.uniform3f( this.addr, v.x, v.y, v.z );
+
+ cache[ 0 ] = v.x;
+ cache[ 1 ] = v.y;
+ cache[ 2 ] = v.z;
+
+ }
+
+ } else if ( v.r !== undefined ) {
+
+ if ( cache[ 0 ] !== v.r || cache[ 1 ] !== v.g || cache[ 2 ] !== v.b ) {
+
+ gl.uniform3f( this.addr, v.r, v.g, v.b );
+
+ cache[ 0 ] = v.r;
+ cache[ 1 ] = v.g;
+ cache[ 2 ] = v.b;
+
+ }
+
+ } else {
+
+ if ( arraysEqual( cache, v ) ) return;
+
+ gl.uniform3fv( this.addr, v );
+
+ copyArray( cache, v );
+
+ }
+
+}
+
+function setValueV4f( gl, v ) {
+
+ const cache = this.cache;
+
+ if ( v.x !== undefined ) {
+
+ if ( cache[ 0 ] !== v.x || cache[ 1 ] !== v.y || cache[ 2 ] !== v.z || cache[ 3 ] !== v.w ) {
+
+ gl.uniform4f( this.addr, v.x, v.y, v.z, v.w );
+
+ cache[ 0 ] = v.x;
+ cache[ 1 ] = v.y;
+ cache[ 2 ] = v.z;
+ cache[ 3 ] = v.w;
+
+ }
+
+ } else {
+
+ if ( arraysEqual( cache, v ) ) return;
+
+ gl.uniform4fv( this.addr, v );
+
+ copyArray( cache, v );
+
+ }
+
+}
+
+// Single matrix (from flat array or MatrixN)
+
+function setValueM2( gl, v ) {
+
+ const cache = this.cache;
+ const elements = v.elements;
+
+ if ( elements === undefined ) {
+
+ if ( arraysEqual( cache, v ) ) return;
+
+ gl.uniformMatrix2fv( this.addr, false, v );
+
+ copyArray( cache, v );
+
+ } else {
+
+ if ( arraysEqual( cache, elements ) ) return;
+
+ mat2array.set( elements );
+
+ gl.uniformMatrix2fv( this.addr, false, mat2array );
+
+ copyArray( cache, elements );
+
+ }
+
+}
+
+function setValueM3( gl, v ) {
+
+ const cache = this.cache;
+ const elements = v.elements;
+
+ if ( elements === undefined ) {
+
+ if ( arraysEqual( cache, v ) ) return;
+
+ gl.uniformMatrix3fv( this.addr, false, v );
+
+ copyArray( cache, v );
+
+ } else {
+
+ if ( arraysEqual( cache, elements ) ) return;
+
+ mat3array.set( elements );
+
+ gl.uniformMatrix3fv( this.addr, false, mat3array );
+
+ copyArray( cache, elements );
+
+ }
+
+}
+
+function setValueM4( gl, v ) {
+
+ const cache = this.cache;
+ const elements = v.elements;
+
+ if ( elements === undefined ) {
+
+ if ( arraysEqual( cache, v ) ) return;
+
+ gl.uniformMatrix4fv( this.addr, false, v );
+
+ copyArray( cache, v );
+
+ } else {
+
+ if ( arraysEqual( cache, elements ) ) return;
+
+ mat4array.set( elements );
+
+ gl.uniformMatrix4fv( this.addr, false, mat4array );
+
+ copyArray( cache, elements );
+
+ }
+
+}
+
+// Single texture (2D / Cube)
+
+function setValueT1( gl, v, textures ) {
+
+ const cache = this.cache;
+ const unit = textures.allocateTextureUnit();
+
+ if ( cache[ 0 ] !== unit ) {
+
+ gl.uniform1i( this.addr, unit );
+ cache[ 0 ] = unit;
+
+ }
+
+ textures.safeSetTexture2D( v || emptyTexture, unit );
+
+}
+
+function setValueT2DArray1( gl, v, textures ) {
+
+ const cache = this.cache;
+ const unit = textures.allocateTextureUnit();
+
+ if ( cache[ 0 ] !== unit ) {
+
+ gl.uniform1i( this.addr, unit );
+ cache[ 0 ] = unit;
+
+ }
+
+ textures.setTexture2DArray( v || emptyTexture2dArray, unit );
+
+}
+
+function setValueT3D1( gl, v, textures ) {
+
+ const cache = this.cache;
+ const unit = textures.allocateTextureUnit();
+
+ if ( cache[ 0 ] !== unit ) {
+
+ gl.uniform1i( this.addr, unit );
+ cache[ 0 ] = unit;
+
+ }
+
+ textures.setTexture3D( v || emptyTexture3d, unit );
+
+}
+
+function setValueT6( gl, v, textures ) {
+
+ const cache = this.cache;
+ const unit = textures.allocateTextureUnit();
+
+ if ( cache[ 0 ] !== unit ) {
+
+ gl.uniform1i( this.addr, unit );
+ cache[ 0 ] = unit;
+
+ }
+
+ textures.safeSetTextureCube( v || emptyCubeTexture, unit );
+
+}
+
+// Integer / Boolean vectors or arrays thereof (always flat arrays)
+
+function setValueV1i( gl, v ) {
+
+ const cache = this.cache;
+
+ if ( cache[ 0 ] === v ) return;
+
+ gl.uniform1i( this.addr, v );
+
+ cache[ 0 ] = v;
+
+}
+
+function setValueV2i( gl, v ) {
+
+ const cache = this.cache;
+
+ if ( arraysEqual( cache, v ) ) return;
+
+ gl.uniform2iv( this.addr, v );
+
+ copyArray( cache, v );
+
+}
+
+function setValueV3i( gl, v ) {
+
+ const cache = this.cache;
+
+ if ( arraysEqual( cache, v ) ) return;
+
+ gl.uniform3iv( this.addr, v );
+
+ copyArray( cache, v );
+
+}
+
+function setValueV4i( gl, v ) {
+
+ const cache = this.cache;
+
+ if ( arraysEqual( cache, v ) ) return;
+
+ gl.uniform4iv( this.addr, v );
+
+ copyArray( cache, v );
+
+}
+
+// uint
+
+function setValueV1ui( gl, v ) {
+
+ const cache = this.cache;
+
+ if ( cache[ 0 ] === v ) return;
+
+ gl.uniform1ui( this.addr, v );
+
+ cache[ 0 ] = v;
+
+}
+
+// Helper to pick the right setter for the singular case
+
+function getSingularSetter( type ) {
+
+ switch ( type ) {
+
+ case 0x1406: return setValueV1f; // FLOAT
+ case 0x8b50: return setValueV2f; // _VEC2
+ case 0x8b51: return setValueV3f; // _VEC3
+ case 0x8b52: return setValueV4f; // _VEC4
+
+ case 0x8b5a: return setValueM2; // _MAT2
+ case 0x8b5b: return setValueM3; // _MAT3
+ case 0x8b5c: return setValueM4; // _MAT4
+
+ case 0x1404: case 0x8b56: return setValueV1i; // INT, BOOL
+ case 0x8b53: case 0x8b57: return setValueV2i; // _VEC2
+ case 0x8b54: case 0x8b58: return setValueV3i; // _VEC3
+ case 0x8b55: case 0x8b59: return setValueV4i; // _VEC4
+
+ case 0x1405: return setValueV1ui; // UINT
+
+ case 0x8b5e: // SAMPLER_2D
+ case 0x8d66: // SAMPLER_EXTERNAL_OES
+ case 0x8dca: // INT_SAMPLER_2D
+ case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D
+ case 0x8b62: // SAMPLER_2D_SHADOW
+ return setValueT1;
+
+ case 0x8b5f: // SAMPLER_3D
+ case 0x8dcb: // INT_SAMPLER_3D
+ case 0x8dd3: // UNSIGNED_INT_SAMPLER_3D
+ return setValueT3D1;
+
+ case 0x8b60: // SAMPLER_CUBE
+ case 0x8dcc: // INT_SAMPLER_CUBE
+ case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE
+ case 0x8dc5: // SAMPLER_CUBE_SHADOW
+ return setValueT6;
+
+ case 0x8dc1: // SAMPLER_2D_ARRAY
+ case 0x8dcf: // INT_SAMPLER_2D_ARRAY
+ case 0x8dd7: // UNSIGNED_INT_SAMPLER_2D_ARRAY
+ case 0x8dc4: // SAMPLER_2D_ARRAY_SHADOW
+ return setValueT2DArray1;
+
+ }
+
+}
+
+// Array of scalars
+function setValueV1fArray( gl, v ) {
+
+ gl.uniform1fv( this.addr, v );
+
+}
+
+// Integer / Boolean vectors or arrays thereof (always flat arrays)
+function setValueV1iArray( gl, v ) {
+
+ gl.uniform1iv( this.addr, v );
+
+}
+
+function setValueV2iArray( gl, v ) {
+
+ gl.uniform2iv( this.addr, v );
+
+}
+
+function setValueV3iArray( gl, v ) {
+
+ gl.uniform3iv( this.addr, v );
+
+}
+
+function setValueV4iArray( gl, v ) {
+
+ gl.uniform4iv( this.addr, v );
+
+}
+
+
+// Array of vectors (flat or from THREE classes)
+
+function setValueV2fArray( gl, v ) {
+
+ const data = flatten( v, this.size, 2 );
+
+ gl.uniform2fv( this.addr, data );
+
+}
+
+function setValueV3fArray( gl, v ) {
+
+ const data = flatten( v, this.size, 3 );
+
+ gl.uniform3fv( this.addr, data );
+
+}
+
+function setValueV4fArray( gl, v ) {
+
+ const data = flatten( v, this.size, 4 );
+
+ gl.uniform4fv( this.addr, data );
+
+}
+
+// Array of matrices (flat or from THREE clases)
+
+function setValueM2Array( gl, v ) {
+
+ const data = flatten( v, this.size, 4 );
+
+ gl.uniformMatrix2fv( this.addr, false, data );
+
+}
+
+function setValueM3Array( gl, v ) {
+
+ const data = flatten( v, this.size, 9 );
+
+ gl.uniformMatrix3fv( this.addr, false, data );
+
+}
+
+function setValueM4Array( gl, v ) {
+
+ const data = flatten( v, this.size, 16 );
+
+ gl.uniformMatrix4fv( this.addr, false, data );
+
+}
+
+// Array of textures (2D / Cube)
+
+function setValueT1Array( gl, v, textures ) {
+
+ const n = v.length;
+
+ const units = allocTexUnits( textures, n );
+
+ gl.uniform1iv( this.addr, units );
+
+ for ( let i = 0; i !== n; ++ i ) {
+
+ textures.safeSetTexture2D( v[ i ] || emptyTexture, units[ i ] );
+
+ }
+
+}
+
+function setValueT6Array( gl, v, textures ) {
+
+ const n = v.length;
+
+ const units = allocTexUnits( textures, n );
+
+ gl.uniform1iv( this.addr, units );
+
+ for ( let i = 0; i !== n; ++ i ) {
+
+ textures.safeSetTextureCube( v[ i ] || emptyCubeTexture, units[ i ] );
+
+ }
+
+}
+
+// Helper to pick the right setter for a pure (bottom-level) array
+
+function getPureArraySetter( type ) {
+
+ switch ( type ) {
+
+ case 0x1406: return setValueV1fArray; // FLOAT
+ case 0x8b50: return setValueV2fArray; // _VEC2
+ case 0x8b51: return setValueV3fArray; // _VEC3
+ case 0x8b52: return setValueV4fArray; // _VEC4
+
+ case 0x8b5a: return setValueM2Array; // _MAT2
+ case 0x8b5b: return setValueM3Array; // _MAT3
+ case 0x8b5c: return setValueM4Array; // _MAT4
+
+ case 0x1404: case 0x8b56: return setValueV1iArray; // INT, BOOL
+ case 0x8b53: case 0x8b57: return setValueV2iArray; // _VEC2
+ case 0x8b54: case 0x8b58: return setValueV3iArray; // _VEC3
+ case 0x8b55: case 0x8b59: return setValueV4iArray; // _VEC4
+
+ case 0x8b5e: // SAMPLER_2D
+ case 0x8d66: // SAMPLER_EXTERNAL_OES
+ case 0x8dca: // INT_SAMPLER_2D
+ case 0x8dd2: // UNSIGNED_INT_SAMPLER_2D
+ case 0x8b62: // SAMPLER_2D_SHADOW
+ return setValueT1Array;
+
+ case 0x8b60: // SAMPLER_CUBE
+ case 0x8dcc: // INT_SAMPLER_CUBE
+ case 0x8dd4: // UNSIGNED_INT_SAMPLER_CUBE
+ case 0x8dc5: // SAMPLER_CUBE_SHADOW
+ return setValueT6Array;
+
+ }
+
+}
+
+// --- Uniform Classes ---
+
+function SingleUniform( id, activeInfo, addr ) {
+
+ this.id = id;
+ this.addr = addr;
+ this.cache = [];
+ this.setValue = getSingularSetter( activeInfo.type );
+
+ // this.path = activeInfo.name; // DEBUG
+
+}
+
+function PureArrayUniform( id, activeInfo, addr ) {
+
+ this.id = id;
+ this.addr = addr;
+ this.cache = [];
+ this.size = activeInfo.size;
+ this.setValue = getPureArraySetter( activeInfo.type );
+
+ // this.path = activeInfo.name; // DEBUG
+
+}
+
+PureArrayUniform.prototype.updateCache = function ( data ) {
+
+ const cache = this.cache;
+
+ if ( data instanceof Float32Array && cache.length !== data.length ) {
+
+ this.cache = new Float32Array( data.length );
+
+ }
+
+ copyArray( cache, data );
+
+};
+
+function StructuredUniform( id ) {
+
+ this.id = id;
+
+ this.seq = [];
+ this.map = {};
+
+}
+
+StructuredUniform.prototype.setValue = function ( gl, value, textures ) {
+
+ const seq = this.seq;
+
+ for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+
+ const u = seq[ i ];
+ u.setValue( gl, value[ u.id ], textures );
+
+ }
+
+};
+
+// --- Top-level ---
+
+// Parser - builds up the property tree from the path strings
+
+const RePathPart = /(\w+)(\])?(\[|\.)?/g;
+
+// extracts
+// - the identifier (member name or array index)
+// - followed by an optional right bracket (found when array index)
+// - followed by an optional left bracket or dot (type of subscript)
+//
+// Note: These portions can be read in a non-overlapping fashion and
+// allow straightforward parsing of the hierarchy that WebGL encodes
+// in the uniform names.
+
+function addUniform( container, uniformObject ) {
+
+ container.seq.push( uniformObject );
+ container.map[ uniformObject.id ] = uniformObject;
+
+}
+
+function parseUniform( activeInfo, addr, container ) {
+
+ const path = activeInfo.name,
+ pathLength = path.length;
+
+ // reset RegExp object, because of the early exit of a previous run
+ RePathPart.lastIndex = 0;
+
+ while ( true ) {
+
+ const match = RePathPart.exec( path ),
+ matchEnd = RePathPart.lastIndex;
+
+ let id = match[ 1 ];
+ const idIsIndex = match[ 2 ] === ']',
+ subscript = match[ 3 ];
+
+ if ( idIsIndex ) id = id | 0; // convert to integer
+
+ if ( subscript === undefined || subscript === '[' && matchEnd + 2 === pathLength ) {
+
+ // bare name or "pure" bottom-level array "[0]" suffix
+
+ addUniform( container, subscript === undefined ?
+ new SingleUniform( id, activeInfo, addr ) :
+ new PureArrayUniform( id, activeInfo, addr ) );
+
+ break;
+
+ } else {
+
+ // step into inner node / create it in case it doesn't exist
+
+ const map = container.map;
+ let next = map[ id ];
+
+ if ( next === undefined ) {
+
+ next = new StructuredUniform( id );
+ addUniform( container, next );
+
+ }
+
+ container = next;
+
+ }
+
+ }
+
+}
+
+// Root Container
+
+function WebGLUniforms( gl, program ) {
+
+ this.seq = [];
+ this.map = {};
+
+ const n = gl.getProgramParameter( program, 35718 );
+
+ for ( let i = 0; i < n; ++ i ) {
+
+ const info = gl.getActiveUniform( program, i ),
+ addr = gl.getUniformLocation( program, info.name );
+
+ parseUniform( info, addr, this );
+
+ }
+
+}
+
+WebGLUniforms.prototype.setValue = function ( gl, name, value, textures ) {
+
+ const u = this.map[ name ];
+
+ if ( u !== undefined ) u.setValue( gl, value, textures );
+
+};
+
+WebGLUniforms.prototype.setOptional = function ( gl, object, name ) {
+
+ const v = object[ name ];
+
+ if ( v !== undefined ) this.setValue( gl, name, v );
+
+};
+
+
+// Static interface
+
+WebGLUniforms.upload = function ( gl, seq, values, textures ) {
+
+ for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+
+ const u = seq[ i ],
+ v = values[ u.id ];
+
+ if ( v.needsUpdate !== false ) {
+
+ // note: always updating when .needsUpdate is undefined
+ u.setValue( gl, v.value, textures );
+
+ }
+
+ }
+
+};
+
+WebGLUniforms.seqWithValue = function ( seq, values ) {
+
+ const r = [];
+
+ for ( let i = 0, n = seq.length; i !== n; ++ i ) {
+
+ const u = seq[ i ];
+ if ( u.id in values ) r.push( u );
+
+ }
+
+ return r;
+
+};
+
+function WebGLShader( gl, type, string ) {
+
+ const shader = gl.createShader( type );
+
+ gl.shaderSource( shader, string );
+ gl.compileShader( shader );
+
+ return shader;
+
+}
+
+let programIdCount = 0;
+
+function addLineNumbers( string ) {
+
+ const lines = string.split( '\n' );
+
+ for ( let i = 0; i < lines.length; i ++ ) {
+
+ lines[ i ] = ( i + 1 ) + ': ' + lines[ i ];
+
+ }
+
+ return lines.join( '\n' );
+
+}
+
+function getEncodingComponents( encoding ) {
+
+ switch ( encoding ) {
+
+ case LinearEncoding:
+ return [ 'Linear', '( value )' ];
+ case sRGBEncoding:
+ return [ 'sRGB', '( value )' ];
+ case RGBEEncoding:
+ return [ 'RGBE', '( value )' ];
+ case RGBM7Encoding:
+ return [ 'RGBM', '( value, 7.0 )' ];
+ case RGBM16Encoding:
+ return [ 'RGBM', '( value, 16.0 )' ];
+ case RGBDEncoding:
+ return [ 'RGBD', '( value, 256.0 )' ];
+ case GammaEncoding:
+ return [ 'Gamma', '( value, float( GAMMA_FACTOR ) )' ];
+ case LogLuvEncoding:
+ return [ 'LogLuv', '( value )' ];
+ default:
+ console.warn( 'THREE.WebGLProgram: Unsupported encoding:', encoding );
+ return [ 'Linear', '( value )' ];
+
+ }
+
+}
+
+function getShaderErrors( gl, shader, type ) {
+
+ const status = gl.getShaderParameter( shader, 35713 );
+ const log = gl.getShaderInfoLog( shader ).trim();
+
+ if ( status && log === '' ) return '';
+
+ // --enable-privileged-webgl-extension
+ // console.log( '**' + type + '**', gl.getExtension( 'WEBGL_debug_shaders' ).getTranslatedShaderSource( shader ) );
+
+ const source = gl.getShaderSource( shader );
+
+ return 'THREE.WebGLShader: gl.getShaderInfoLog() ' + type + '\n' + log + addLineNumbers( source );
+
+}
+
+function getTexelDecodingFunction( functionName, encoding ) {
+
+ const components = getEncodingComponents( encoding );
+ return 'vec4 ' + functionName + '( vec4 value ) { return ' + components[ 0 ] + 'ToLinear' + components[ 1 ] + '; }';
+
+}
+
+function getTexelEncodingFunction( functionName, encoding ) {
+
+ const components = getEncodingComponents( encoding );
+ return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }';
+
+}
+
+function getToneMappingFunction( functionName, toneMapping ) {
+
+ let toneMappingName;
+
+ switch ( toneMapping ) {
+
+ case LinearToneMapping:
+ toneMappingName = 'Linear';
+ break;
+
+ case ReinhardToneMapping:
+ toneMappingName = 'Reinhard';
+ break;
+
+ case CineonToneMapping:
+ toneMappingName = 'OptimizedCineon';
+ break;
+
+ case ACESFilmicToneMapping:
+ toneMappingName = 'ACESFilmic';
+ break;
+
+ case CustomToneMapping:
+ toneMappingName = 'Custom';
+ break;
+
+ default:
+ console.warn( 'THREE.WebGLProgram: Unsupported toneMapping:', toneMapping );
+ toneMappingName = 'Linear';
+
+ }
+
+ return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';
+
+}
+
+function generateExtensions( parameters ) {
+
+ const chunks = [
+ ( parameters.extensionDerivatives || parameters.envMapCubeUV || parameters.bumpMap || parameters.tangentSpaceNormalMap || parameters.clearcoatNormalMap || parameters.flatShading || parameters.shaderID === 'physical' ) ? '#extension GL_OES_standard_derivatives : enable' : '',
+ ( parameters.extensionFragDepth || parameters.logarithmicDepthBuffer ) && parameters.rendererExtensionFragDepth ? '#extension GL_EXT_frag_depth : enable' : '',
+ ( parameters.extensionDrawBuffers && parameters.rendererExtensionDrawBuffers ) ? '#extension GL_EXT_draw_buffers : require' : '',
+ ( parameters.extensionShaderTextureLOD || parameters.envMap ) && parameters.rendererExtensionShaderTextureLod ? '#extension GL_EXT_shader_texture_lod : enable' : ''
+ ];
+
+ return chunks.filter( filterEmptyLine ).join( '\n' );
+
+}
+
+function generateDefines( defines ) {
+
+ const chunks = [];
+
+ for ( const name in defines ) {
+
+ const value = defines[ name ];
+
+ if ( value === false ) continue;
+
+ chunks.push( '#define ' + name + ' ' + value );
+
+ }
+
+ return chunks.join( '\n' );
+
+}
+
+function fetchAttributeLocations( gl, program ) {
+
+ const attributes = {};
+
+ const n = gl.getProgramParameter( program, 35721 );
+
+ for ( let i = 0; i < n; i ++ ) {
+
+ const info = gl.getActiveAttrib( program, i );
+ const name = info.name;
+
+ // console.log( 'THREE.WebGLProgram: ACTIVE VERTEX ATTRIBUTE:', name, i );
+
+ attributes[ name ] = gl.getAttribLocation( program, name );
+
+ }
+
+ return attributes;
+
+}
+
+function filterEmptyLine( string ) {
+
+ return string !== '';
+
+}
+
+function replaceLightNums( string, parameters ) {
+
+ return string
+ .replace( /NUM_DIR_LIGHTS/g, parameters.numDirLights )
+ .replace( /NUM_SPOT_LIGHTS/g, parameters.numSpotLights )
+ .replace( /NUM_RECT_AREA_LIGHTS/g, parameters.numRectAreaLights )
+ .replace( /NUM_POINT_LIGHTS/g, parameters.numPointLights )
+ .replace( /NUM_HEMI_LIGHTS/g, parameters.numHemiLights )
+ .replace( /NUM_DIR_LIGHT_SHADOWS/g, parameters.numDirLightShadows )
+ .replace( /NUM_SPOT_LIGHT_SHADOWS/g, parameters.numSpotLightShadows )
+ .replace( /NUM_POINT_LIGHT_SHADOWS/g, parameters.numPointLightShadows );
+
+}
+
+function replaceClippingPlaneNums( string, parameters ) {
+
+ return string
+ .replace( /NUM_CLIPPING_PLANES/g, parameters.numClippingPlanes )
+ .replace( /UNION_CLIPPING_PLANES/g, ( parameters.numClippingPlanes - parameters.numClipIntersection ) );
+
+}
+
+// Resolve Includes
+
+const includePattern = /^[ \t]*#include +<([\w\d./]+)>/gm;
+
+function resolveIncludes( string ) {
+
+ return string.replace( includePattern, includeReplacer );
+
+}
+
+function includeReplacer( match, include ) {
+
+ const string = ShaderChunk[ include ];
+
+ if ( string === undefined ) {
+
+ throw new Error( 'Can not resolve #include <' + include + '>' );
+
+ }
+
+ return resolveIncludes( string );
+
+}
+
+// Unroll Loops
+
+const deprecatedUnrollLoopPattern = /#pragma unroll_loop[\s]+?for \( int i \= (\d+)\; i < (\d+)\; i \+\+ \) \{([\s\S]+?)(?=\})\}/g;
+const unrollLoopPattern = /#pragma unroll_loop_start\s+for\s*\(\s*int\s+i\s*=\s*(\d+)\s*;\s*i\s*<\s*(\d+)\s*;\s*i\s*\+\+\s*\)\s*{([\s\S]+?)}\s+#pragma unroll_loop_end/g;
+
+function unrollLoops( string ) {
+
+ return string
+ .replace( unrollLoopPattern, loopReplacer )
+ .replace( deprecatedUnrollLoopPattern, deprecatedLoopReplacer );
+
+}
+
+function deprecatedLoopReplacer( match, start, end, snippet ) {
+
+ console.warn( 'WebGLProgram: #pragma unroll_loop shader syntax is deprecated. Please use #pragma unroll_loop_start syntax instead.' );
+ return loopReplacer( match, start, end, snippet );
+
+}
+
+function loopReplacer( match, start, end, snippet ) {
+
+ let string = '';
+
+ for ( let i = parseInt( start ); i < parseInt( end ); i ++ ) {
+
+ string += snippet
+ .replace( /\[\s*i\s*\]/g, '[ ' + i + ' ]' )
+ .replace( /UNROLLED_LOOP_INDEX/g, i );
+
+ }
+
+ return string;
+
+}
+
+//
+
+function generatePrecision( parameters ) {
+
+ let precisionstring = 'precision ' + parameters.precision + ' float;\nprecision ' + parameters.precision + ' int;';
+
+ if ( parameters.precision === 'highp' ) {
+
+ precisionstring += '\n#define HIGH_PRECISION';
+
+ } else if ( parameters.precision === 'mediump' ) {
+
+ precisionstring += '\n#define MEDIUM_PRECISION';
+
+ } else if ( parameters.precision === 'lowp' ) {
+
+ precisionstring += '\n#define LOW_PRECISION';
+
+ }
+
+ return precisionstring;
+
+}
+
+function generateShadowMapTypeDefine( parameters ) {
+
+ let shadowMapTypeDefine = 'SHADOWMAP_TYPE_BASIC';
+
+ if ( parameters.shadowMapType === PCFShadowMap ) {
+
+ shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF';
+
+ } else if ( parameters.shadowMapType === PCFSoftShadowMap ) {
+
+ shadowMapTypeDefine = 'SHADOWMAP_TYPE_PCF_SOFT';
+
+ } else if ( parameters.shadowMapType === VSMShadowMap ) {
+
+ shadowMapTypeDefine = 'SHADOWMAP_TYPE_VSM';
+
+ }
+
+ return shadowMapTypeDefine;
+
+}
+
+function generateEnvMapTypeDefine( parameters ) {
+
+ let envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
+
+ if ( parameters.envMap ) {
+
+ switch ( parameters.envMapMode ) {
+
+ case CubeReflectionMapping:
+ case CubeRefractionMapping:
+ envMapTypeDefine = 'ENVMAP_TYPE_CUBE';
+ break;
+
+ case CubeUVReflectionMapping:
+ case CubeUVRefractionMapping:
+ envMapTypeDefine = 'ENVMAP_TYPE_CUBE_UV';
+ break;
+
+ }
+
+ }
+
+ return envMapTypeDefine;
+
+}
+
+function generateEnvMapModeDefine( parameters ) {
+
+ let envMapModeDefine = 'ENVMAP_MODE_REFLECTION';
+
+ if ( parameters.envMap ) {
+
+ switch ( parameters.envMapMode ) {
+
+ case CubeRefractionMapping:
+ case CubeUVRefractionMapping:
+
+ envMapModeDefine = 'ENVMAP_MODE_REFRACTION';
+ break;
+
+ }
+
+ }
+
+ return envMapModeDefine;
+
+}
+
+function generateEnvMapBlendingDefine( parameters ) {
+
+ let envMapBlendingDefine = 'ENVMAP_BLENDING_NONE';
+
+ if ( parameters.envMap ) {
+
+ switch ( parameters.combine ) {
+
+ case MultiplyOperation:
+ envMapBlendingDefine = 'ENVMAP_BLENDING_MULTIPLY';
+ break;
+
+ case MixOperation:
+ envMapBlendingDefine = 'ENVMAP_BLENDING_MIX';
+ break;
+
+ case AddOperation:
+ envMapBlendingDefine = 'ENVMAP_BLENDING_ADD';
+ break;
+
+ }
+
+ }
+
+ return envMapBlendingDefine;
+
+}
+
+function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
+
+ const gl = renderer.getContext();
+
+ const defines = parameters.defines;
+
+ let vertexShader = parameters.vertexShader;
+ let fragmentShader = parameters.fragmentShader;
+
+ const shadowMapTypeDefine = generateShadowMapTypeDefine( parameters );
+ const envMapTypeDefine = generateEnvMapTypeDefine( parameters );
+ const envMapModeDefine = generateEnvMapModeDefine( parameters );
+ const envMapBlendingDefine = generateEnvMapBlendingDefine( parameters );
+
+
+ const gammaFactorDefine = ( renderer.gammaFactor > 0 ) ? renderer.gammaFactor : 1.0;
+
+ const customExtensions = parameters.isWebGL2 ? '' : generateExtensions( parameters );
+
+ const customDefines = generateDefines( defines );
+
+ const program = gl.createProgram();
+
+ let prefixVertex, prefixFragment;
+ let versionString = parameters.glslVersion ? '#version ' + parameters.glslVersion + '\n' : '';
+
+ if ( parameters.isRawShaderMaterial ) {
+
+ prefixVertex = [
+
+ customDefines
+
+ ].filter( filterEmptyLine ).join( '\n' );
+
+ if ( prefixVertex.length > 0 ) {
+
+ prefixVertex += '\n';
+
+ }
+
+ prefixFragment = [
+
+ customExtensions,
+ customDefines
+
+ ].filter( filterEmptyLine ).join( '\n' );
+
+ if ( prefixFragment.length > 0 ) {
+
+ prefixFragment += '\n';
+
+ }
+
+ } else {
+
+ prefixVertex = [
+
+ generatePrecision( parameters ),
+
+ '#define SHADER_NAME ' + parameters.shaderName,
+
+ customDefines,
+
+ parameters.instancing ? '#define USE_INSTANCING' : '',
+ parameters.instancingColor ? '#define USE_INSTANCING_COLOR' : '',
+
+ parameters.supportsVertexTextures ? '#define VERTEX_TEXTURES' : '',
+
+ '#define GAMMA_FACTOR ' + gammaFactorDefine,
+
+ '#define MAX_BONES ' + parameters.maxBones,
+ ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
+ ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',
+
+ parameters.map ? '#define USE_MAP' : '',
+ parameters.envMap ? '#define USE_ENVMAP' : '',
+ parameters.envMap ? '#define ' + envMapModeDefine : '',
+ parameters.lightMap ? '#define USE_LIGHTMAP' : '',
+ parameters.aoMap ? '#define USE_AOMAP' : '',
+ parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',
+ parameters.bumpMap ? '#define USE_BUMPMAP' : '',
+ parameters.normalMap ? '#define USE_NORMALMAP' : '',
+ ( parameters.normalMap && parameters.objectSpaceNormalMap ) ? '#define OBJECTSPACE_NORMALMAP' : '',
+ ( parameters.normalMap && parameters.tangentSpaceNormalMap ) ? '#define TANGENTSPACE_NORMALMAP' : '',
+
+ parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '',
+ parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '',
+ parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '',
+ parameters.displacementMap && parameters.supportsVertexTextures ? '#define USE_DISPLACEMENTMAP' : '',
+ parameters.specularMap ? '#define USE_SPECULARMAP' : '',
+ parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',
+ parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',
+ parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
+ parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '',
+
+ parameters.vertexTangents ? '#define USE_TANGENT' : '',
+ parameters.vertexColors ? '#define USE_COLOR' : '',
+ parameters.vertexUvs ? '#define USE_UV' : '',
+ parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',
+
+ parameters.flatShading ? '#define FLAT_SHADED' : '',
+
+ parameters.skinning ? '#define USE_SKINNING' : '',
+ parameters.useVertexTexture ? '#define BONE_TEXTURE' : '',
+
+ parameters.morphTargets ? '#define USE_MORPHTARGETS' : '',
+ parameters.morphNormals && parameters.flatShading === false ? '#define USE_MORPHNORMALS' : '',
+ parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
+ parameters.flipSided ? '#define FLIP_SIDED' : '',
+
+ parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
+ parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
+
+ parameters.sizeAttenuation ? '#define USE_SIZEATTENUATION' : '',
+
+ parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
+ ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
+
+ 'uniform mat4 modelMatrix;',
+ 'uniform mat4 modelViewMatrix;',
+ 'uniform mat4 projectionMatrix;',
+ 'uniform mat4 viewMatrix;',
+ 'uniform mat3 normalMatrix;',
+ 'uniform vec3 cameraPosition;',
+ 'uniform bool isOrthographic;',
+
+ '#ifdef USE_INSTANCING',
+
+ ' attribute mat4 instanceMatrix;',
+
+ '#endif',
+
+ '#ifdef USE_INSTANCING_COLOR',
+
+ ' attribute vec3 instanceColor;',
+
+ '#endif',
+
+ 'attribute vec3 position;',
+ 'attribute vec3 normal;',
+ 'attribute vec2 uv;',
+
+ '#ifdef USE_TANGENT',
+
+ ' attribute vec4 tangent;',
+
+ '#endif',
+
+ '#ifdef USE_COLOR',
+
+ ' attribute vec3 color;',
+
+ '#endif',
+
+ '#ifdef USE_MORPHTARGETS',
+
+ ' attribute vec3 morphTarget0;',
+ ' attribute vec3 morphTarget1;',
+ ' attribute vec3 morphTarget2;',
+ ' attribute vec3 morphTarget3;',
+
+ ' #ifdef USE_MORPHNORMALS',
+
+ ' attribute vec3 morphNormal0;',
+ ' attribute vec3 morphNormal1;',
+ ' attribute vec3 morphNormal2;',
+ ' attribute vec3 morphNormal3;',
+
+ ' #else',
+
+ ' attribute vec3 morphTarget4;',
+ ' attribute vec3 morphTarget5;',
+ ' attribute vec3 morphTarget6;',
+ ' attribute vec3 morphTarget7;',
+
+ ' #endif',
+
+ '#endif',
+
+ '#ifdef USE_SKINNING',
+
+ ' attribute vec4 skinIndex;',
+ ' attribute vec4 skinWeight;',
+
+ '#endif',
+
+ '\n'
+
+ ].filter( filterEmptyLine ).join( '\n' );
+
+ prefixFragment = [
+
+ customExtensions,
+
+ generatePrecision( parameters ),
+
+ '#define SHADER_NAME ' + parameters.shaderName,
+
+ customDefines,
+
+ parameters.alphaTest ? '#define ALPHATEST ' + parameters.alphaTest + ( parameters.alphaTest % 1 ? '' : '.0' ) : '', // add '.0' if integer
+
+ '#define GAMMA_FACTOR ' + gammaFactorDefine,
+
+ ( parameters.useFog && parameters.fog ) ? '#define USE_FOG' : '',
+ ( parameters.useFog && parameters.fogExp2 ) ? '#define FOG_EXP2' : '',
+
+ parameters.map ? '#define USE_MAP' : '',
+ parameters.matcap ? '#define USE_MATCAP' : '',
+ parameters.envMap ? '#define USE_ENVMAP' : '',
+ parameters.envMap ? '#define ' + envMapTypeDefine : '',
+ parameters.envMap ? '#define ' + envMapModeDefine : '',
+ parameters.envMap ? '#define ' + envMapBlendingDefine : '',
+ parameters.lightMap ? '#define USE_LIGHTMAP' : '',
+ parameters.aoMap ? '#define USE_AOMAP' : '',
+ parameters.emissiveMap ? '#define USE_EMISSIVEMAP' : '',
+ parameters.bumpMap ? '#define USE_BUMPMAP' : '',
+ parameters.normalMap ? '#define USE_NORMALMAP' : '',
+ ( parameters.normalMap && parameters.objectSpaceNormalMap ) ? '#define OBJECTSPACE_NORMALMAP' : '',
+ ( parameters.normalMap && parameters.tangentSpaceNormalMap ) ? '#define TANGENTSPACE_NORMALMAP' : '',
+ parameters.clearcoatMap ? '#define USE_CLEARCOATMAP' : '',
+ parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '',
+ parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '',
+ parameters.specularMap ? '#define USE_SPECULARMAP' : '',
+ parameters.roughnessMap ? '#define USE_ROUGHNESSMAP' : '',
+ parameters.metalnessMap ? '#define USE_METALNESSMAP' : '',
+ parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
+
+ parameters.sheen ? '#define USE_SHEEN' : '',
+ parameters.transmissionMap ? '#define USE_TRANSMISSIONMAP' : '',
+
+ parameters.vertexTangents ? '#define USE_TANGENT' : '',
+ parameters.vertexColors || parameters.instancingColor ? '#define USE_COLOR' : '',
+ parameters.vertexUvs ? '#define USE_UV' : '',
+ parameters.uvsVertexOnly ? '#define UVS_VERTEX_ONLY' : '',
+
+ parameters.gradientMap ? '#define USE_GRADIENTMAP' : '',
+
+ parameters.flatShading ? '#define FLAT_SHADED' : '',
+
+ parameters.doubleSided ? '#define DOUBLE_SIDED' : '',
+ parameters.flipSided ? '#define FLIP_SIDED' : '',
+
+ parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
+ parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
+
+ parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '',
+
+ parameters.physicallyCorrectLights ? '#define PHYSICALLY_CORRECT_LIGHTS' : '',
+
+ parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
+ ( parameters.logarithmicDepthBuffer && parameters.rendererExtensionFragDepth ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
+
+ ( ( parameters.extensionShaderTextureLOD || parameters.envMap ) && parameters.rendererExtensionShaderTextureLod ) ? '#define TEXTURE_LOD_EXT' : '',
+
+ 'uniform mat4 viewMatrix;',
+ 'uniform vec3 cameraPosition;',
+ 'uniform bool isOrthographic;',
+
+ ( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '',
+ ( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below
+ ( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '',
+
+ parameters.dithering ? '#define DITHERING' : '',
+
+ ShaderChunk[ 'encodings_pars_fragment' ], // this code is required here because it is used by the various encoding/decoding function defined below
+ parameters.map ? getTexelDecodingFunction( 'mapTexelToLinear', parameters.mapEncoding ) : '',
+ parameters.matcap ? getTexelDecodingFunction( 'matcapTexelToLinear', parameters.matcapEncoding ) : '',
+ parameters.envMap ? getTexelDecodingFunction( 'envMapTexelToLinear', parameters.envMapEncoding ) : '',
+ parameters.emissiveMap ? getTexelDecodingFunction( 'emissiveMapTexelToLinear', parameters.emissiveMapEncoding ) : '',
+ parameters.lightMap ? getTexelDecodingFunction( 'lightMapTexelToLinear', parameters.lightMapEncoding ) : '',
+ getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputEncoding ),
+
+ parameters.depthPacking ? '#define DEPTH_PACKING ' + parameters.depthPacking : '',
+
+ '\n'
+
+ ].filter( filterEmptyLine ).join( '\n' );
+
+ }
+
+ vertexShader = resolveIncludes( vertexShader );
+ vertexShader = replaceLightNums( vertexShader, parameters );
+ vertexShader = replaceClippingPlaneNums( vertexShader, parameters );
+
+ fragmentShader = resolveIncludes( fragmentShader );
+ fragmentShader = replaceLightNums( fragmentShader, parameters );
+ fragmentShader = replaceClippingPlaneNums( fragmentShader, parameters );
+
+ vertexShader = unrollLoops( vertexShader );
+ fragmentShader = unrollLoops( fragmentShader );
+
+ if ( parameters.isWebGL2 && parameters.isRawShaderMaterial !== true ) {
+
+ // GLSL 3.0 conversion for built-in materials and ShaderMaterial
+
+ versionString = '#version 300 es\n';
+
+ prefixVertex = [
+ '#define attribute in',
+ '#define varying out',
+ '#define texture2D texture'
+ ].join( '\n' ) + '\n' + prefixVertex;
+
+ prefixFragment = [
+ '#define varying in',
+ ( parameters.glslVersion === GLSL3 ) ? '' : 'out highp vec4 pc_fragColor;',
+ ( parameters.glslVersion === GLSL3 ) ? '' : '#define gl_FragColor pc_fragColor',
+ '#define gl_FragDepthEXT gl_FragDepth',
+ '#define texture2D texture',
+ '#define textureCube texture',
+ '#define texture2DProj textureProj',
+ '#define texture2DLodEXT textureLod',
+ '#define texture2DProjLodEXT textureProjLod',
+ '#define textureCubeLodEXT textureLod',
+ '#define texture2DGradEXT textureGrad',
+ '#define texture2DProjGradEXT textureProjGrad',
+ '#define textureCubeGradEXT textureGrad'
+ ].join( '\n' ) + '\n' + prefixFragment;
+
+ }
+
+ const vertexGlsl = versionString + prefixVertex + vertexShader;
+ const fragmentGlsl = versionString + prefixFragment + fragmentShader;
+
+ // console.log( '*VERTEX*', vertexGlsl );
+ // console.log( '*FRAGMENT*', fragmentGlsl );
+
+ const glVertexShader = WebGLShader( gl, 35633, vertexGlsl );
+ const glFragmentShader = WebGLShader( gl, 35632, fragmentGlsl );
+
+ gl.attachShader( program, glVertexShader );
+ gl.attachShader( program, glFragmentShader );
+
+ // Force a particular attribute to index 0.
+
+ if ( parameters.index0AttributeName !== undefined ) {
+
+ gl.bindAttribLocation( program, 0, parameters.index0AttributeName );
+
+ } else if ( parameters.morphTargets === true ) {
+
+ // programs with morphTargets displace position out of attribute 0
+ gl.bindAttribLocation( program, 0, 'position' );
+
+ }
+
+ gl.linkProgram( program );
+
+ // check for link errors
+ if ( renderer.debug.checkShaderErrors ) {
+
+ const programLog = gl.getProgramInfoLog( program ).trim();
+ const vertexLog = gl.getShaderInfoLog( glVertexShader ).trim();
+ const fragmentLog = gl.getShaderInfoLog( glFragmentShader ).trim();
+
+ let runnable = true;
+ let haveDiagnostics = true;
+
+ if ( gl.getProgramParameter( program, 35714 ) === false ) {
+
+ runnable = false;
+
+ const vertexErrors = getShaderErrors( gl, glVertexShader, 'vertex' );
+ const fragmentErrors = getShaderErrors( gl, glFragmentShader, 'fragment' );
+
+ console.error( 'THREE.WebGLProgram: shader error: ', gl.getError(), '35715', gl.getProgramParameter( program, 35715 ), 'gl.getProgramInfoLog', programLog, vertexErrors, fragmentErrors );
+
+ } else if ( programLog !== '' ) {
+
+ console.warn( 'THREE.WebGLProgram: gl.getProgramInfoLog()', programLog );
+
+ } else if ( vertexLog === '' || fragmentLog === '' ) {
+
+ haveDiagnostics = false;
+
+ }
+
+ if ( haveDiagnostics ) {
+
+ this.diagnostics = {
+
+ runnable: runnable,
+
+ programLog: programLog,
+
+ vertexShader: {
+
+ log: vertexLog,
+ prefix: prefixVertex
+
+ },
+
+ fragmentShader: {
+
+ log: fragmentLog,
+ prefix: prefixFragment
+
+ }
+
+ };
+
+ }
+
+ }
+
+ // Clean up
+
+ // Crashes in iOS9 and iOS10. #18402
+ // gl.detachShader( program, glVertexShader );
+ // gl.detachShader( program, glFragmentShader );
+
+ gl.deleteShader( glVertexShader );
+ gl.deleteShader( glFragmentShader );
+
+ // set up caching for uniform locations
+
+ let cachedUniforms;
+
+ this.getUniforms = function () {
+
+ if ( cachedUniforms === undefined ) {
+
+ cachedUniforms = new WebGLUniforms( gl, program );
+
+ }
+
+ return cachedUniforms;
+
+ };
+
+ // set up caching for attribute locations
+
+ let cachedAttributes;
+
+ this.getAttributes = function () {
+
+ if ( cachedAttributes === undefined ) {
+
+ cachedAttributes = fetchAttributeLocations( gl, program );
+
+ }
+
+ return cachedAttributes;
+
+ };
+
+ // free resource
+
+ this.destroy = function () {
+
+ bindingStates.releaseStatesOfProgram( this );
+
+ gl.deleteProgram( program );
+ this.program = undefined;
+
+ };
+
+ //
+
+ this.name = parameters.shaderName;
+ this.id = programIdCount ++;
+ this.cacheKey = cacheKey;
+ this.usedTimes = 1;
+ this.program = program;
+ this.vertexShader = glVertexShader;
+ this.fragmentShader = glFragmentShader;
+
+ return this;
+
+}
+
+function WebGLPrograms( renderer, cubemaps, extensions, capabilities, bindingStates, clipping ) {
+
+ const programs = [];
+
+ const isWebGL2 = capabilities.isWebGL2;
+ const logarithmicDepthBuffer = capabilities.logarithmicDepthBuffer;
+ const floatVertexTextures = capabilities.floatVertexTextures;
+ const maxVertexUniforms = capabilities.maxVertexUniforms;
+ const vertexTextures = capabilities.vertexTextures;
+
+ let precision = capabilities.precision;
+
+ const shaderIDs = {
+ MeshDepthMaterial: 'depth',
+ MeshDistanceMaterial: 'distanceRGBA',
+ MeshNormalMaterial: 'normal',
+ MeshBasicMaterial: 'basic',
+ MeshLambertMaterial: 'lambert',
+ MeshPhongMaterial: 'phong',
+ MeshToonMaterial: 'toon',
+ MeshStandardMaterial: 'physical',
+ MeshPhysicalMaterial: 'physical',
+ MeshMatcapMaterial: 'matcap',
+ LineBasicMaterial: 'basic',
+ LineDashedMaterial: 'dashed',
+ PointsMaterial: 'points',
+ ShadowMaterial: 'shadow',
+ SpriteMaterial: 'sprite'
+ };
+
+ const parameterNames = [
+ 'precision', 'isWebGL2', 'supportsVertexTextures', 'outputEncoding', 'instancing', 'instancingColor',
+ 'map', 'mapEncoding', 'matcap', 'matcapEncoding', 'envMap', 'envMapMode', 'envMapEncoding', 'envMapCubeUV',
+ 'lightMap', 'lightMapEncoding', 'aoMap', 'emissiveMap', 'emissiveMapEncoding', 'bumpMap', 'normalMap', 'objectSpaceNormalMap', 'tangentSpaceNormalMap', 'clearcoatMap', 'clearcoatRoughnessMap', 'clearcoatNormalMap', 'displacementMap', 'specularMap',
+ 'roughnessMap', 'metalnessMap', 'gradientMap',
+ 'alphaMap', 'combine', 'vertexColors', 'vertexTangents', 'vertexUvs', 'uvsVertexOnly', 'fog', 'useFog', 'fogExp2',
+ 'flatShading', 'sizeAttenuation', 'logarithmicDepthBuffer', 'skinning',
+ 'maxBones', 'useVertexTexture', 'morphTargets', 'morphNormals',
+ 'maxMorphTargets', 'maxMorphNormals', 'premultipliedAlpha',
+ 'numDirLights', 'numPointLights', 'numSpotLights', 'numHemiLights', 'numRectAreaLights',
+ 'numDirLightShadows', 'numPointLightShadows', 'numSpotLightShadows',
+ 'shadowMapEnabled', 'shadowMapType', 'toneMapping', 'physicallyCorrectLights',
+ 'alphaTest', 'doubleSided', 'flipSided', 'numClippingPlanes', 'numClipIntersection', 'depthPacking', 'dithering',
+ 'sheen', 'transmissionMap'
+ ];
+
+ function getMaxBones( object ) {
+
+ const skeleton = object.skeleton;
+ const bones = skeleton.bones;
+
+ if ( floatVertexTextures ) {
+
+ return 1024;
+
+ } else {
+
+ // default for when object is not specified
+ // ( for example when prebuilding shader to be used with multiple objects )
+ //
+ // - leave some extra space for other uniforms
+ // - limit here is ANGLE's 254 max uniform vectors
+ // (up to 54 should be safe)
+
+ const nVertexUniforms = maxVertexUniforms;
+ const nVertexMatrices = Math.floor( ( nVertexUniforms - 20 ) / 4 );
+
+ const maxBones = Math.min( nVertexMatrices, bones.length );
+
+ if ( maxBones < bones.length ) {
+
+ console.warn( 'THREE.WebGLRenderer: Skeleton has ' + bones.length + ' bones. This GPU supports ' + maxBones + '.' );
+ return 0;
+
+ }
+
+ return maxBones;
+
+ }
+
+ }
+
+ function getTextureEncodingFromMap( map ) {
+
+ let encoding;
+
+ if ( map && map.isTexture ) {
+
+ encoding = map.encoding;
+
+ } else if ( map && map.isWebGLRenderTarget ) {
+
+ console.warn( 'THREE.WebGLPrograms.getTextureEncodingFromMap: don\'t use render targets as textures. Use their .texture property instead.' );
+ encoding = map.texture.encoding;
+
+ } else {
+
+ encoding = LinearEncoding;
+
+ }
+
+ return encoding;
+
+ }
+
+ function getParameters( material, lights, shadows, scene, object ) {
+
+ const fog = scene.fog;
+ const environment = material.isMeshStandardMaterial ? scene.environment : null;
+
+ const envMap = cubemaps.get( material.envMap || environment );
+
+ const shaderID = shaderIDs[ material.type ];
+
+ // heuristics to create shader parameters according to lights in the scene
+ // (not to blow over maxLights budget)
+
+ const maxBones = object.isSkinnedMesh ? getMaxBones( object ) : 0;
+
+ if ( material.precision !== null ) {
+
+ precision = capabilities.getMaxPrecision( material.precision );
+
+ if ( precision !== material.precision ) {
+
+ console.warn( 'THREE.WebGLProgram.getParameters:', material.precision, 'not supported, using', precision, 'instead.' );
+
+ }
+
+ }
+
+ let vertexShader, fragmentShader;
+
+ if ( shaderID ) {
+
+ const shader = ShaderLib[ shaderID ];
+
+ vertexShader = shader.vertexShader;
+ fragmentShader = shader.fragmentShader;
+
+ } else {
+
+ vertexShader = material.vertexShader;
+ fragmentShader = material.fragmentShader;
+
+ }
+
+ const currentRenderTarget = renderer.getRenderTarget();
+
+ const parameters = {
+
+ isWebGL2: isWebGL2,
+
+ shaderID: shaderID,
+ shaderName: material.type,
+
+ vertexShader: vertexShader,
+ fragmentShader: fragmentShader,
+ defines: material.defines,
+
+ isRawShaderMaterial: material.isRawShaderMaterial === true,
+ glslVersion: material.glslVersion,
+
+ precision: precision,
+
+ instancing: object.isInstancedMesh === true,
+ instancingColor: object.isInstancedMesh === true && object.instanceColor !== null,
+
+ supportsVertexTextures: vertexTextures,
+ outputEncoding: ( currentRenderTarget !== null ) ? getTextureEncodingFromMap( currentRenderTarget.texture ) : renderer.outputEncoding,
+ map: !! material.map,
+ mapEncoding: getTextureEncodingFromMap( material.map ),
+ matcap: !! material.matcap,
+ matcapEncoding: getTextureEncodingFromMap( material.matcap ),
+ envMap: !! envMap,
+ envMapMode: envMap && envMap.mapping,
+ envMapEncoding: getTextureEncodingFromMap( envMap ),
+ envMapCubeUV: ( !! envMap ) && ( ( envMap.mapping === CubeUVReflectionMapping ) || ( envMap.mapping === CubeUVRefractionMapping ) ),
+ lightMap: !! material.lightMap,
+ lightMapEncoding: getTextureEncodingFromMap( material.lightMap ),
+ aoMap: !! material.aoMap,
+ emissiveMap: !! material.emissiveMap,
+ emissiveMapEncoding: getTextureEncodingFromMap( material.emissiveMap ),
+ bumpMap: !! material.bumpMap,
+ normalMap: !! material.normalMap,
+ objectSpaceNormalMap: material.normalMapType === ObjectSpaceNormalMap,
+ tangentSpaceNormalMap: material.normalMapType === TangentSpaceNormalMap,
+ clearcoatMap: !! material.clearcoatMap,
+ clearcoatRoughnessMap: !! material.clearcoatRoughnessMap,
+ clearcoatNormalMap: !! material.clearcoatNormalMap,
+ displacementMap: !! material.displacementMap,
+ roughnessMap: !! material.roughnessMap,
+ metalnessMap: !! material.metalnessMap,
+ specularMap: !! material.specularMap,
+ alphaMap: !! material.alphaMap,
+
+ gradientMap: !! material.gradientMap,
+
+ sheen: !! material.sheen,
+
+ transmissionMap: !! material.transmissionMap,
+
+ combine: material.combine,
+
+ vertexTangents: ( material.normalMap && material.vertexTangents ),
+ vertexColors: material.vertexColors,
+ vertexUvs: !! material.map || !! material.bumpMap || !! material.normalMap || !! material.specularMap || !! material.alphaMap || !! material.emissiveMap || !! material.roughnessMap || !! material.metalnessMap || !! material.clearcoatMap || !! material.clearcoatRoughnessMap || !! material.clearcoatNormalMap || !! material.displacementMap || !! material.transmissionMap,
+ uvsVertexOnly: ! ( !! material.map || !! material.bumpMap || !! material.normalMap || !! material.specularMap || !! material.alphaMap || !! material.emissiveMap || !! material.roughnessMap || !! material.metalnessMap || !! material.clearcoatNormalMap || !! material.transmissionMap ) && !! material.displacementMap,
+
+ fog: !! fog,
+ useFog: material.fog,
+ fogExp2: ( fog && fog.isFogExp2 ),
+
+ flatShading: material.flatShading,
+
+ sizeAttenuation: material.sizeAttenuation,
+ logarithmicDepthBuffer: logarithmicDepthBuffer,
+
+ skinning: material.skinning && maxBones > 0,
+ maxBones: maxBones,
+ useVertexTexture: floatVertexTextures,
+
+ morphTargets: material.morphTargets,
+ morphNormals: material.morphNormals,
+ maxMorphTargets: renderer.maxMorphTargets,
+ maxMorphNormals: renderer.maxMorphNormals,
+
+ numDirLights: lights.directional.length,
+ numPointLights: lights.point.length,
+ numSpotLights: lights.spot.length,
+ numRectAreaLights: lights.rectArea.length,
+ numHemiLights: lights.hemi.length,
+
+ numDirLightShadows: lights.directionalShadowMap.length,
+ numPointLightShadows: lights.pointShadowMap.length,
+ numSpotLightShadows: lights.spotShadowMap.length,
+
+ numClippingPlanes: clipping.numPlanes,
+ numClipIntersection: clipping.numIntersection,
+
+ dithering: material.dithering,
+
+ shadowMapEnabled: renderer.shadowMap.enabled && shadows.length > 0,
+ shadowMapType: renderer.shadowMap.type,
+
+ toneMapping: material.toneMapped ? renderer.toneMapping : NoToneMapping,
+ physicallyCorrectLights: renderer.physicallyCorrectLights,
+
+ premultipliedAlpha: material.premultipliedAlpha,
+
+ alphaTest: material.alphaTest,
+ doubleSided: material.side === DoubleSide,
+ flipSided: material.side === BackSide,
+
+ depthPacking: ( material.depthPacking !== undefined ) ? material.depthPacking : false,
+
+ index0AttributeName: material.index0AttributeName,
+
+ extensionDerivatives: material.extensions && material.extensions.derivatives,
+ extensionFragDepth: material.extensions && material.extensions.fragDepth,
+ extensionDrawBuffers: material.extensions && material.extensions.drawBuffers,
+ extensionShaderTextureLOD: material.extensions && material.extensions.shaderTextureLOD,
+
+ rendererExtensionFragDepth: isWebGL2 || extensions.has( 'EXT_frag_depth' ),
+ rendererExtensionDrawBuffers: isWebGL2 || extensions.has( 'WEBGL_draw_buffers' ),
+ rendererExtensionShaderTextureLod: isWebGL2 || extensions.has( 'EXT_shader_texture_lod' ),
+
+ customProgramCacheKey: material.customProgramCacheKey()
+
+ };
+
+ return parameters;
+
+ }
+
+ function getProgramCacheKey( parameters ) {
+
+ const array = [];
+
+ if ( parameters.shaderID ) {
+
+ array.push( parameters.shaderID );
+
+ } else {
+
+ array.push( parameters.fragmentShader );
+ array.push( parameters.vertexShader );
+
+ }
+
+ if ( parameters.defines !== undefined ) {
+
+ for ( const name in parameters.defines ) {
+
+ array.push( name );
+ array.push( parameters.defines[ name ] );
+
+ }
+
+ }
+
+ if ( parameters.isRawShaderMaterial === false ) {
+
+ for ( let i = 0; i < parameterNames.length; i ++ ) {
+
+ array.push( parameters[ parameterNames[ i ] ] );
+
+ }
+
+ array.push( renderer.outputEncoding );
+ array.push( renderer.gammaFactor );
+
+ }
+
+ array.push( parameters.customProgramCacheKey );
+
+ return array.join();
+
+ }
+
+ function getUniforms( material ) {
+
+ const shaderID = shaderIDs[ material.type ];
+ let uniforms;
+
+ if ( shaderID ) {
+
+ const shader = ShaderLib[ shaderID ];
+ uniforms = UniformsUtils.clone( shader.uniforms );
+
+ } else {
+
+ uniforms = material.uniforms;
+
+ }
+
+ return uniforms;
+
+ }
+
+ function acquireProgram( parameters, cacheKey ) {
+
+ let program;
+
+ // Check if code has been already compiled
+ for ( let p = 0, pl = programs.length; p < pl; p ++ ) {
+
+ const preexistingProgram = programs[ p ];
+
+ if ( preexistingProgram.cacheKey === cacheKey ) {
+
+ program = preexistingProgram;
+ ++ program.usedTimes;
+
+ break;
+
+ }
+
+ }
+
+ if ( program === undefined ) {
+
+ program = new WebGLProgram( renderer, cacheKey, parameters, bindingStates );
+ programs.push( program );
+
+ }
+
+ return program;
+
+ }
+
+ function releaseProgram( program ) {
+
+ if ( -- program.usedTimes === 0 ) {
+
+ // Remove from unordered set
+ const i = programs.indexOf( program );
+ programs[ i ] = programs[ programs.length - 1 ];
+ programs.pop();
+
+ // Free WebGL resources
+ program.destroy();
+
+ }
+
+ }
+
+ return {
+ getParameters: getParameters,
+ getProgramCacheKey: getProgramCacheKey,
+ getUniforms: getUniforms,
+ acquireProgram: acquireProgram,
+ releaseProgram: releaseProgram,
+ // Exposed for resource monitoring & error feedback via renderer.info:
+ programs: programs
+ };
+
+}
+
+function WebGLProperties() {
+
+ let properties = new WeakMap();
+
+ function get( object ) {
+
+ let map = properties.get( object );
+
+ if ( map === undefined ) {
+
+ map = {};
+ properties.set( object, map );
+
+ }
+
+ return map;
+
+ }
+
+ function remove( object ) {
+
+ properties.delete( object );
+
+ }
+
+ function update( object, key, value ) {
+
+ properties.get( object )[ key ] = value;
+
+ }
+
+ function dispose() {
+
+ properties = new WeakMap();
+
+ }
+
+ return {
+ get: get,
+ remove: remove,
+ update: update,
+ dispose: dispose
+ };
+
+}
+
+function painterSortStable( a, b ) {
+
+ if ( a.groupOrder !== b.groupOrder ) {
+
+ return a.groupOrder - b.groupOrder;
+
+ } else if ( a.renderOrder !== b.renderOrder ) {
+
+ return a.renderOrder - b.renderOrder;
+
+ } else if ( a.program !== b.program ) {
+
+ return a.program.id - b.program.id;
+
+ } else if ( a.material.id !== b.material.id ) {
+
+ return a.material.id - b.material.id;
+
+ } else if ( a.z !== b.z ) {
+
+ return a.z - b.z;
+
+ } else {
+
+ return a.id - b.id;
+
+ }
+
+}
+
+function reversePainterSortStable( a, b ) {
+
+ if ( a.groupOrder !== b.groupOrder ) {
+
+ return a.groupOrder - b.groupOrder;
+
+ } else if ( a.renderOrder !== b.renderOrder ) {
+
+ return a.renderOrder - b.renderOrder;
+
+ } else if ( a.z !== b.z ) {
+
+ return b.z - a.z;
+
+ } else {
+
+ return a.id - b.id;
+
+ }
+
+}
+
+
+function WebGLRenderList( properties ) {
+
+ const renderItems = [];
+ let renderItemsIndex = 0;
+
+ const opaque = [];
+ const transparent = [];
+
+ const defaultProgram = { id: - 1 };
+
+ function init() {
+
+ renderItemsIndex = 0;
+
+ opaque.length = 0;
+ transparent.length = 0;
+
+ }
+
+ function getNextRenderItem( object, geometry, material, groupOrder, z, group ) {
+
+ let renderItem = renderItems[ renderItemsIndex ];
+ const materialProperties = properties.get( material );
+
+ if ( renderItem === undefined ) {
+
+ renderItem = {
+ id: object.id,
+ object: object,
+ geometry: geometry,
+ material: material,
+ program: materialProperties.program || defaultProgram,
+ groupOrder: groupOrder,
+ renderOrder: object.renderOrder,
+ z: z,
+ group: group
+ };
+
+ renderItems[ renderItemsIndex ] = renderItem;
+
+ } else {
+
+ renderItem.id = object.id;
+ renderItem.object = object;
+ renderItem.geometry = geometry;
+ renderItem.material = material;
+ renderItem.program = materialProperties.program || defaultProgram;
+ renderItem.groupOrder = groupOrder;
+ renderItem.renderOrder = object.renderOrder;
+ renderItem.z = z;
+ renderItem.group = group;
+
+ }
+
+ renderItemsIndex ++;
+
+ return renderItem;
+
+ }
+
+ function push( object, geometry, material, groupOrder, z, group ) {
+
+ const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
+
+ ( material.transparent === true ? transparent : opaque ).push( renderItem );
+
+ }
+
+ function unshift( object, geometry, material, groupOrder, z, group ) {
+
+ const renderItem = getNextRenderItem( object, geometry, material, groupOrder, z, group );
+
+ ( material.transparent === true ? transparent : opaque ).unshift( renderItem );
+
+ }
+
+ function sort( customOpaqueSort, customTransparentSort ) {
+
+ if ( opaque.length > 1 ) opaque.sort( customOpaqueSort || painterSortStable );
+ if ( transparent.length > 1 ) transparent.sort( customTransparentSort || reversePainterSortStable );
+
+ }
+
+ function finish() {
+
+ // Clear references from inactive renderItems in the list
+
+ for ( let i = renderItemsIndex, il = renderItems.length; i < il; i ++ ) {
+
+ const renderItem = renderItems[ i ];
+
+ if ( renderItem.id === null ) break;
+
+ renderItem.id = null;
+ renderItem.object = null;
+ renderItem.geometry = null;
+ renderItem.material = null;
+ renderItem.program = null;
+ renderItem.group = null;
+
+ }
+
+ }
+
+ return {
+
+ opaque: opaque,
+ transparent: transparent,
+
+ init: init,
+ push: push,
+ unshift: unshift,
+ finish: finish,
+
+ sort: sort
+ };
+
+}
+
+function WebGLRenderLists( properties ) {
+
+ let lists = new WeakMap();
+
+ function get( scene, camera ) {
+
+ const cameras = lists.get( scene );
+ let list;
+
+ if ( cameras === undefined ) {
+
+ list = new WebGLRenderList( properties );
+ lists.set( scene, new WeakMap() );
+ lists.get( scene ).set( camera, list );
+
+ } else {
+
+ list = cameras.get( camera );
+ if ( list === undefined ) {
+
+ list = new WebGLRenderList( properties );
+ cameras.set( camera, list );
+
+ }
+
+ }
+
+ return list;
+
+ }
+
+ function dispose() {
+
+ lists = new WeakMap();
+
+ }
+
+ return {
+ get: get,
+ dispose: dispose
+ };
+
+}
+
+function UniformsCache() {
+
+ const lights = {};
+
+ return {
+
+ get: function ( light ) {
+
+ if ( lights[ light.id ] !== undefined ) {
+
+ return lights[ light.id ];
+
+ }
+
+ let uniforms;
+
+ switch ( light.type ) {
+
+ case 'DirectionalLight':
+ uniforms = {
+ direction: new Vector3(),
+ color: new Color()
+ };
+ break;
+
+ case 'SpotLight':
+ uniforms = {
+ position: new Vector3(),
+ direction: new Vector3(),
+ color: new Color(),
+ distance: 0,
+ coneCos: 0,
+ penumbraCos: 0,
+ decay: 0
+ };
+ break;
+
+ case 'PointLight':
+ uniforms = {
+ position: new Vector3(),
+ color: new Color(),
+ distance: 0,
+ decay: 0
+ };
+ break;
+
+ case 'HemisphereLight':
+ uniforms = {
+ direction: new Vector3(),
+ skyColor: new Color(),
+ groundColor: new Color()
+ };
+ break;
+
+ case 'RectAreaLight':
+ uniforms = {
+ color: new Color(),
+ position: new Vector3(),
+ halfWidth: new Vector3(),
+ halfHeight: new Vector3()
+ };
+ break;
+
+ }
+
+ lights[ light.id ] = uniforms;
+
+ return uniforms;
+
+ }
+
+ };
+
+}
+
+function ShadowUniformsCache() {
+
+ const lights = {};
+
+ return {
+
+ get: function ( light ) {
+
+ if ( lights[ light.id ] !== undefined ) {
+
+ return lights[ light.id ];
+
+ }
+
+ let uniforms;
+
+ switch ( light.type ) {
+
+ case 'DirectionalLight':
+ uniforms = {
+ shadowBias: 0,
+ shadowNormalBias: 0,
+ shadowRadius: 1,
+ shadowMapSize: new Vector2()
+ };
+ break;
+
+ case 'SpotLight':
+ uniforms = {
+ shadowBias: 0,
+ shadowNormalBias: 0,
+ shadowRadius: 1,
+ shadowMapSize: new Vector2()
+ };
+ break;
+
+ case 'PointLight':
+ uniforms = {
+ shadowBias: 0,
+ shadowNormalBias: 0,
+ shadowRadius: 1,
+ shadowMapSize: new Vector2(),
+ shadowCameraNear: 1,
+ shadowCameraFar: 1000
+ };
+ break;
+
+ // TODO (abelnation): set RectAreaLight shadow uniforms
+
+ }
+
+ lights[ light.id ] = uniforms;
+
+ return uniforms;
+
+ }
+
+ };
+
+}
+
+
+
+let nextVersion = 0;
+
+function shadowCastingLightsFirst( lightA, lightB ) {
+
+ return ( lightB.castShadow ? 1 : 0 ) - ( lightA.castShadow ? 1 : 0 );
+
+}
+
+function WebGLLights( extensions, capabilities ) {
+
+ const cache = new UniformsCache();
+
+ const shadowCache = ShadowUniformsCache();
+
+ const state = {
+
+ version: 0,
+
+ hash: {
+ directionalLength: - 1,
+ pointLength: - 1,
+ spotLength: - 1,
+ rectAreaLength: - 1,
+ hemiLength: - 1,
+
+ numDirectionalShadows: - 1,
+ numPointShadows: - 1,
+ numSpotShadows: - 1
+ },
+
+ ambient: [ 0, 0, 0 ],
+ probe: [],
+ directional: [],
+ directionalShadow: [],
+ directionalShadowMap: [],
+ directionalShadowMatrix: [],
+ spot: [],
+ spotShadow: [],
+ spotShadowMap: [],
+ spotShadowMatrix: [],
+ rectArea: [],
+ rectAreaLTC1: null,
+ rectAreaLTC2: null,
+ point: [],
+ pointShadow: [],
+ pointShadowMap: [],
+ pointShadowMatrix: [],
+ hemi: []
+
+ };
+
+ for ( let i = 0; i < 9; i ++ ) state.probe.push( new Vector3() );
+
+ const vector3 = new Vector3();
+ const matrix4 = new Matrix4();
+ const matrix42 = new Matrix4();
+
+ function setup( lights ) {
+
+ let r = 0, g = 0, b = 0;
+
+ for ( let i = 0; i < 9; i ++ ) state.probe[ i ].set( 0, 0, 0 );
+
+ let directionalLength = 0;
+ let pointLength = 0;
+ let spotLength = 0;
+ let rectAreaLength = 0;
+ let hemiLength = 0;
+
+ let numDirectionalShadows = 0;
+ let numPointShadows = 0;
+ let numSpotShadows = 0;
+
+ lights.sort( shadowCastingLightsFirst );
+
+ for ( let i = 0, l = lights.length; i < l; i ++ ) {
+
+ const light = lights[ i ];
+
+ const color = light.color;
+ const intensity = light.intensity;
+ const distance = light.distance;
+
+ const shadowMap = ( light.shadow && light.shadow.map ) ? light.shadow.map.texture : null;
+
+ if ( light.isAmbientLight ) {
+
+ r += color.r * intensity;
+ g += color.g * intensity;
+ b += color.b * intensity;
+
+ } else if ( light.isLightProbe ) {
+
+ for ( let j = 0; j < 9; j ++ ) {
+
+ state.probe[ j ].addScaledVector( light.sh.coefficients[ j ], intensity );
+
+ }
+
+ } else if ( light.isDirectionalLight ) {
+
+ const uniforms = cache.get( light );
+
+ uniforms.color.copy( light.color ).multiplyScalar( light.intensity );
+
+ if ( light.castShadow ) {
+
+ const shadow = light.shadow;
+
+ const shadowUniforms = shadowCache.get( light );
+
+ shadowUniforms.shadowBias = shadow.bias;
+ shadowUniforms.shadowNormalBias = shadow.normalBias;
+ shadowUniforms.shadowRadius = shadow.radius;
+ shadowUniforms.shadowMapSize = shadow.mapSize;
+
+ state.directionalShadow[ directionalLength ] = shadowUniforms;
+ state.directionalShadowMap[ directionalLength ] = shadowMap;
+ state.directionalShadowMatrix[ directionalLength ] = light.shadow.matrix;
+
+ numDirectionalShadows ++;
+
+ }
+
+ state.directional[ directionalLength ] = uniforms;
+
+ directionalLength ++;
+
+ } else if ( light.isSpotLight ) {
+
+ const uniforms = cache.get( light );
+
+ uniforms.position.setFromMatrixPosition( light.matrixWorld );
+
+ uniforms.color.copy( color ).multiplyScalar( intensity );
+ uniforms.distance = distance;
+
+ uniforms.coneCos = Math.cos( light.angle );
+ uniforms.penumbraCos = Math.cos( light.angle * ( 1 - light.penumbra ) );
+ uniforms.decay = light.decay;
+
+ if ( light.castShadow ) {
+
+ const shadow = light.shadow;
+
+ const shadowUniforms = shadowCache.get( light );
+
+ shadowUniforms.shadowBias = shadow.bias;
+ shadowUniforms.shadowNormalBias = shadow.normalBias;
+ shadowUniforms.shadowRadius = shadow.radius;
+ shadowUniforms.shadowMapSize = shadow.mapSize;
+
+ state.spotShadow[ spotLength ] = shadowUniforms;
+ state.spotShadowMap[ spotLength ] = shadowMap;
+ state.spotShadowMatrix[ spotLength ] = light.shadow.matrix;
+
+ numSpotShadows ++;
+
+ }
+
+ state.spot[ spotLength ] = uniforms;
+
+ spotLength ++;
+
+ } else if ( light.isRectAreaLight ) {
+
+ const uniforms = cache.get( light );
+
+ // (a) intensity is the total visible light emitted
+ //uniforms.color.copy( color ).multiplyScalar( intensity / ( light.width * light.height * Math.PI ) );
+
+ // (b) intensity is the brightness of the light
+ uniforms.color.copy( color ).multiplyScalar( intensity );
+
+ uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
+ uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
+
+ state.rectArea[ rectAreaLength ] = uniforms;
+
+ rectAreaLength ++;
+
+ } else if ( light.isPointLight ) {
+
+ const uniforms = cache.get( light );
+
+ uniforms.color.copy( light.color ).multiplyScalar( light.intensity );
+ uniforms.distance = light.distance;
+ uniforms.decay = light.decay;
+
+ if ( light.castShadow ) {
+
+ const shadow = light.shadow;
+
+ const shadowUniforms = shadowCache.get( light );
+
+ shadowUniforms.shadowBias = shadow.bias;
+ shadowUniforms.shadowNormalBias = shadow.normalBias;
+ shadowUniforms.shadowRadius = shadow.radius;
+ shadowUniforms.shadowMapSize = shadow.mapSize;
+ shadowUniforms.shadowCameraNear = shadow.camera.near;
+ shadowUniforms.shadowCameraFar = shadow.camera.far;
+
+ state.pointShadow[ pointLength ] = shadowUniforms;
+ state.pointShadowMap[ pointLength ] = shadowMap;
+ state.pointShadowMatrix[ pointLength ] = light.shadow.matrix;
+
+ numPointShadows ++;
+
+ }
+
+ state.point[ pointLength ] = uniforms;
+
+ pointLength ++;
+
+ } else if ( light.isHemisphereLight ) {
+
+ const uniforms = cache.get( light );
+
+ uniforms.skyColor.copy( light.color ).multiplyScalar( intensity );
+ uniforms.groundColor.copy( light.groundColor ).multiplyScalar( intensity );
+
+ state.hemi[ hemiLength ] = uniforms;
+
+ hemiLength ++;
+
+ }
+
+ }
+
+ if ( rectAreaLength > 0 ) {
+
+ if ( capabilities.isWebGL2 ) {
+
+ // WebGL 2
+
+ state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
+ state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
+
+ } else {
+
+ // WebGL 1
+
+ if ( extensions.has( 'OES_texture_float_linear' ) === true ) {
+
+ state.rectAreaLTC1 = UniformsLib.LTC_FLOAT_1;
+ state.rectAreaLTC2 = UniformsLib.LTC_FLOAT_2;
+
+ } else if ( extensions.has( 'OES_texture_half_float_linear' ) === true ) {
+
+ state.rectAreaLTC1 = UniformsLib.LTC_HALF_1;
+ state.rectAreaLTC2 = UniformsLib.LTC_HALF_2;
+
+ } else {
+
+ console.error( 'THREE.WebGLRenderer: Unable to use RectAreaLight. Missing WebGL extensions.' );
+
+ }
+
+ }
+
+ }
+
+ state.ambient[ 0 ] = r;
+ state.ambient[ 1 ] = g;
+ state.ambient[ 2 ] = b;
+
+ const hash = state.hash;
+
+ if ( hash.directionalLength !== directionalLength ||
+ hash.pointLength !== pointLength ||
+ hash.spotLength !== spotLength ||
+ hash.rectAreaLength !== rectAreaLength ||
+ hash.hemiLength !== hemiLength ||
+ hash.numDirectionalShadows !== numDirectionalShadows ||
+ hash.numPointShadows !== numPointShadows ||
+ hash.numSpotShadows !== numSpotShadows ) {
+
+ state.directional.length = directionalLength;
+ state.spot.length = spotLength;
+ state.rectArea.length = rectAreaLength;
+ state.point.length = pointLength;
+ state.hemi.length = hemiLength;
+
+ state.directionalShadow.length = numDirectionalShadows;
+ state.directionalShadowMap.length = numDirectionalShadows;
+ state.pointShadow.length = numPointShadows;
+ state.pointShadowMap.length = numPointShadows;
+ state.spotShadow.length = numSpotShadows;
+ state.spotShadowMap.length = numSpotShadows;
+ state.directionalShadowMatrix.length = numDirectionalShadows;
+ state.pointShadowMatrix.length = numPointShadows;
+ state.spotShadowMatrix.length = numSpotShadows;
+
+ hash.directionalLength = directionalLength;
+ hash.pointLength = pointLength;
+ hash.spotLength = spotLength;
+ hash.rectAreaLength = rectAreaLength;
+ hash.hemiLength = hemiLength;
+
+ hash.numDirectionalShadows = numDirectionalShadows;
+ hash.numPointShadows = numPointShadows;
+ hash.numSpotShadows = numSpotShadows;
+
+ state.version = nextVersion ++;
+
+ }
+
+ }
+
+ function setupView( lights, camera ) {
+
+ let directionalLength = 0;
+ let pointLength = 0;
+ let spotLength = 0;
+ let rectAreaLength = 0;
+ let hemiLength = 0;
+
+ const viewMatrix = camera.matrixWorldInverse;
+
+ for ( let i = 0, l = lights.length; i < l; i ++ ) {
+
+ const light = lights[ i ];
+
+ if ( light.isDirectionalLight ) {
+
+ const uniforms = state.directional[ directionalLength ];
+
+ uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+ vector3.setFromMatrixPosition( light.target.matrixWorld );
+ uniforms.direction.sub( vector3 );
+ uniforms.direction.transformDirection( viewMatrix );
+
+ directionalLength ++;
+
+ } else if ( light.isSpotLight ) {
+
+ const uniforms = state.spot[ spotLength ];
+
+ uniforms.position.setFromMatrixPosition( light.matrixWorld );
+ uniforms.position.applyMatrix4( viewMatrix );
+
+ uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+ vector3.setFromMatrixPosition( light.target.matrixWorld );
+ uniforms.direction.sub( vector3 );
+ uniforms.direction.transformDirection( viewMatrix );
+
+ spotLength ++;
+
+ } else if ( light.isRectAreaLight ) {
+
+ const uniforms = state.rectArea[ rectAreaLength ];
+
+ uniforms.position.setFromMatrixPosition( light.matrixWorld );
+ uniforms.position.applyMatrix4( viewMatrix );
+
+ // extract local rotation of light to derive width/height half vectors
+ matrix42.identity();
+ matrix4.copy( light.matrixWorld );
+ matrix4.premultiply( viewMatrix );
+ matrix42.extractRotation( matrix4 );
+
+ uniforms.halfWidth.set( light.width * 0.5, 0.0, 0.0 );
+ uniforms.halfHeight.set( 0.0, light.height * 0.5, 0.0 );
+
+ uniforms.halfWidth.applyMatrix4( matrix42 );
+ uniforms.halfHeight.applyMatrix4( matrix42 );
+
+ rectAreaLength ++;
+
+ } else if ( light.isPointLight ) {
+
+ const uniforms = state.point[ pointLength ];
+
+ uniforms.position.setFromMatrixPosition( light.matrixWorld );
+ uniforms.position.applyMatrix4( viewMatrix );
+
+ pointLength ++;
+
+ } else if ( light.isHemisphereLight ) {
+
+ const uniforms = state.hemi[ hemiLength ];
+
+ uniforms.direction.setFromMatrixPosition( light.matrixWorld );
+ uniforms.direction.transformDirection( viewMatrix );
+ uniforms.direction.normalize();
+
+ hemiLength ++;
+
+ }
+
+ }
+
+ }
+
+ return {
+ setup: setup,
+ setupView: setupView,
+ state: state
+ };
+
+}
+
+function WebGLRenderState( extensions, capabilities ) {
+
+ const lights = new WebGLLights( extensions, capabilities );
+
+ const lightsArray = [];
+ const shadowsArray = [];
+
+ function init() {
+
+ lightsArray.length = 0;
+ shadowsArray.length = 0;
+
+ }
+
+ function pushLight( light ) {
+
+ lightsArray.push( light );
+
+ }
+
+ function pushShadow( shadowLight ) {
+
+ shadowsArray.push( shadowLight );
+
+ }
+
+ function setupLights() {
+
+ lights.setup( lightsArray );
+
+ }
+
+ function setupLightsView( camera ) {
+
+ lights.setupView( lightsArray, camera );
+
+ }
+
+ const state = {
+ lightsArray: lightsArray,
+ shadowsArray: shadowsArray,
+
+ lights: lights
+ };
+
+ return {
+ init: init,
+ state: state,
+ setupLights: setupLights,
+ setupLightsView: setupLightsView,
+
+ pushLight: pushLight,
+ pushShadow: pushShadow
+ };
+
+}
+
+function WebGLRenderStates( extensions, capabilities ) {
+
+ let renderStates = new WeakMap();
+
+ function get( scene, renderCallDepth = 0 ) {
+
+ let renderState;
+
+ if ( renderStates.has( scene ) === false ) {
+
+ renderState = new WebGLRenderState( extensions, capabilities );
+ renderStates.set( scene, [] );
+ renderStates.get( scene ).push( renderState );
+
+ } else {
+
+ if ( renderCallDepth >= renderStates.get( scene ).length ) {
+
+ renderState = new WebGLRenderState( extensions, capabilities );
+ renderStates.get( scene ).push( renderState );
+
+ } else {
+
+ renderState = renderStates.get( scene )[ renderCallDepth ];
+
+ }
+
+ }
+
+ return renderState;
+
+ }
+
+ function dispose() {
+
+ renderStates = new WeakMap();
+
+ }
+
+ return {
+ get: get,
+ dispose: dispose
+ };
+
+}
+
+/**
+ * parameters = {
+ *
+ * opacity: <float>,
+ *
+ * map: new THREE.Texture( <Image> ),
+ *
+ * alphaMap: new THREE.Texture( <Image> ),
+ *
+ * displacementMap: new THREE.Texture( <Image> ),
+ * displacementScale: <float>,
+ * displacementBias: <float>,
+ *
+ * wireframe: <boolean>,
+ * wireframeLinewidth: <float>
+ * }
+ */
+
+function MeshDepthMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.type = 'MeshDepthMaterial';
+
+ this.depthPacking = BasicDepthPacking;
+
+ this.skinning = false;
+ this.morphTargets = false;
+
+ this.map = null;
+
+ this.alphaMap = null;
+
+ this.displacementMap = null;
+ this.displacementScale = 1;
+ this.displacementBias = 0;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+
+ this.fog = false;
+
+ this.setValues( parameters );
+
+}
+
+MeshDepthMaterial.prototype = Object.create( Material.prototype );
+MeshDepthMaterial.prototype.constructor = MeshDepthMaterial;
+
+MeshDepthMaterial.prototype.isMeshDepthMaterial = true;
+
+MeshDepthMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.depthPacking = source.depthPacking;
+
+ this.skinning = source.skinning;
+ this.morphTargets = source.morphTargets;
+
+ this.map = source.map;
+
+ this.alphaMap = source.alphaMap;
+
+ this.displacementMap = source.displacementMap;
+ this.displacementScale = source.displacementScale;
+ this.displacementBias = source.displacementBias;
+
+ this.wireframe = source.wireframe;
+ this.wireframeLinewidth = source.wireframeLinewidth;
+
+ return this;
+
+};
+
+/**
+ * parameters = {
+ *
+ * referencePosition: <float>,
+ * nearDistance: <float>,
+ * farDistance: <float>,
+ *
+ * skinning: <bool>,
+ * morphTargets: <bool>,
+ *
+ * map: new THREE.Texture( <Image> ),
+ *
+ * alphaMap: new THREE.Texture( <Image> ),
+ *
+ * displacementMap: new THREE.Texture( <Image> ),
+ * displacementScale: <float>,
+ * displacementBias: <float>
+ *
+ * }
+ */
+
+function MeshDistanceMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.type = 'MeshDistanceMaterial';
+
+ this.referencePosition = new Vector3();
+ this.nearDistance = 1;
+ this.farDistance = 1000;
+
+ this.skinning = false;
+ this.morphTargets = false;
+
+ this.map = null;
+
+ this.alphaMap = null;
+
+ this.displacementMap = null;
+ this.displacementScale = 1;
+ this.displacementBias = 0;
+
+ this.fog = false;
+
+ this.setValues( parameters );
+
+}
+
+MeshDistanceMaterial.prototype = Object.create( Material.prototype );
+MeshDistanceMaterial.prototype.constructor = MeshDistanceMaterial;
+
+MeshDistanceMaterial.prototype.isMeshDistanceMaterial = true;
+
+MeshDistanceMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.referencePosition.copy( source.referencePosition );
+ this.nearDistance = source.nearDistance;
+ this.farDistance = source.farDistance;
+
+ this.skinning = source.skinning;
+ this.morphTargets = source.morphTargets;
+
+ this.map = source.map;
+
+ this.alphaMap = source.alphaMap;
+
+ this.displacementMap = source.displacementMap;
+ this.displacementScale = source.displacementScale;
+ this.displacementBias = source.displacementBias;
+
+ return this;
+
+};
+
+var vsm_frag = "uniform sampler2D shadow_pass;\nuniform vec2 resolution;\nuniform float radius;\n#include <packing>\nvoid main() {\n\tfloat mean = 0.0;\n\tfloat squared_mean = 0.0;\n\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy ) / resolution ) );\n\tfor ( float i = -1.0; i < 1.0 ; i += SAMPLE_RATE) {\n\t\t#ifdef HORIZONTAL_PASS\n\t\t\tvec2 distribution = unpackRGBATo2Half( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( i, 0.0 ) * radius ) / resolution ) );\n\t\t\tmean += distribution.x;\n\t\t\tsquared_mean += distribution.y * distribution.y + distribution.x * distribution.x;\n\t\t#else\n\t\t\tfloat depth = unpackRGBAToDepth( texture2D( shadow_pass, ( gl_FragCoord.xy + vec2( 0.0, i ) * radius ) / resolution ) );\n\t\t\tmean += depth;\n\t\t\tsquared_mean += depth * depth;\n\t\t#endif\n\t}\n\tmean = mean * HALF_SAMPLE_RATE;\n\tsquared_mean = squared_mean * HALF_SAMPLE_RATE;\n\tfloat std_dev = sqrt( squared_mean - mean * mean );\n\tgl_FragColor = pack2HalfToRGBA( vec2( mean, std_dev ) );\n}";
+
+var vsm_vert = "void main() {\n\tgl_Position = vec4( position, 1.0 );\n}";
+
+function WebGLShadowMap( _renderer, _objects, maxTextureSize ) {
+
+ let _frustum = new Frustum();
+
+ const _shadowMapSize = new Vector2(),
+ _viewportSize = new Vector2(),
+
+ _viewport = new Vector4(),
+
+ _depthMaterials = [],
+ _distanceMaterials = [],
+
+ _materialCache = {};
+
+ const shadowSide = { 0: BackSide, 1: FrontSide, 2: DoubleSide };
+
+ const shadowMaterialVertical = new ShaderMaterial( {
+
+ defines: {
+ SAMPLE_RATE: 2.0 / 8.0,
+ HALF_SAMPLE_RATE: 1.0 / 8.0
+ },
+
+ uniforms: {
+ shadow_pass: { value: null },
+ resolution: { value: new Vector2() },
+ radius: { value: 4.0 }
+ },
+
+ vertexShader: vsm_vert,
+
+ fragmentShader: vsm_frag
+
+ } );
+
+ const shadowMaterialHorizontal = shadowMaterialVertical.clone();
+ shadowMaterialHorizontal.defines.HORIZONTAL_PASS = 1;
+
+ const fullScreenTri = new BufferGeometry();
+ fullScreenTri.setAttribute(
+ 'position',
+ new BufferAttribute(
+ new Float32Array( [ - 1, - 1, 0.5, 3, - 1, 0.5, - 1, 3, 0.5 ] ),
+ 3
+ )
+ );
+
+ const fullScreenMesh = new Mesh( fullScreenTri, shadowMaterialVertical );
+
+ const scope = this;
+
+ this.enabled = false;
+
+ this.autoUpdate = true;
+ this.needsUpdate = false;
+
+ this.type = PCFShadowMap;
+
+ this.render = function ( lights, scene, camera ) {
+
+ if ( scope.enabled === false ) return;
+ if ( scope.autoUpdate === false && scope.needsUpdate === false ) return;
+
+ if ( lights.length === 0 ) return;
+
+ const currentRenderTarget = _renderer.getRenderTarget();
+ const activeCubeFace = _renderer.getActiveCubeFace();
+ const activeMipmapLevel = _renderer.getActiveMipmapLevel();
+
+ const _state = _renderer.state;
+
+ // Set GL state for depth map.
+ _state.setBlending( NoBlending );
+ _state.buffers.color.setClear( 1, 1, 1, 1 );
+ _state.buffers.depth.setTest( true );
+ _state.setScissorTest( false );
+
+ // render depth map
+
+ for ( let i = 0, il = lights.length; i < il; i ++ ) {
+
+ const light = lights[ i ];
+ const shadow = light.shadow;
+
+ if ( shadow === undefined ) {
+
+ console.warn( 'THREE.WebGLShadowMap:', light, 'has no shadow.' );
+ continue;
+
+ }
+
+ if ( shadow.autoUpdate === false && shadow.needsUpdate === false ) continue;
+
+ _shadowMapSize.copy( shadow.mapSize );
+
+ const shadowFrameExtents = shadow.getFrameExtents();
+
+ _shadowMapSize.multiply( shadowFrameExtents );
+
+ _viewportSize.copy( shadow.mapSize );
+
+ if ( _shadowMapSize.x > maxTextureSize || _shadowMapSize.y > maxTextureSize ) {
+
+ if ( _shadowMapSize.x > maxTextureSize ) {
+
+ _viewportSize.x = Math.floor( maxTextureSize / shadowFrameExtents.x );
+ _shadowMapSize.x = _viewportSize.x * shadowFrameExtents.x;
+ shadow.mapSize.x = _viewportSize.x;
+
+ }
+
+ if ( _shadowMapSize.y > maxTextureSize ) {
+
+ _viewportSize.y = Math.floor( maxTextureSize / shadowFrameExtents.y );
+ _shadowMapSize.y = _viewportSize.y * shadowFrameExtents.y;
+ shadow.mapSize.y = _viewportSize.y;
+
+ }
+
+ }
+
+ if ( shadow.map === null && ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
+
+ const pars = { minFilter: LinearFilter, magFilter: LinearFilter, format: RGBAFormat };
+
+ shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+ shadow.map.texture.name = light.name + '.shadowMap';
+
+ shadow.mapPass = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+
+ shadow.camera.updateProjectionMatrix();
+
+ }
+
+ if ( shadow.map === null ) {
+
+ const pars = { minFilter: NearestFilter, magFilter: NearestFilter, format: RGBAFormat };
+
+ shadow.map = new WebGLRenderTarget( _shadowMapSize.x, _shadowMapSize.y, pars );
+ shadow.map.texture.name = light.name + '.shadowMap';
+
+ shadow.camera.updateProjectionMatrix();
+
+ }
+
+ _renderer.setRenderTarget( shadow.map );
+ _renderer.clear();
+
+ const viewportCount = shadow.getViewportCount();
+
+ for ( let vp = 0; vp < viewportCount; vp ++ ) {
+
+ const viewport = shadow.getViewport( vp );
+
+ _viewport.set(
+ _viewportSize.x * viewport.x,
+ _viewportSize.y * viewport.y,
+ _viewportSize.x * viewport.z,
+ _viewportSize.y * viewport.w
+ );
+
+ _state.viewport( _viewport );
+
+ shadow.updateMatrices( light, vp );
+
+ _frustum = shadow.getFrustum();
+
+ renderObject( scene, camera, shadow.camera, light, this.type );
+
+ }
+
+ // do blur pass for VSM
+
+ if ( ! shadow.isPointLightShadow && this.type === VSMShadowMap ) {
+
+ VSMPass( shadow, camera );
+
+ }
+
+ shadow.needsUpdate = false;
+
+ }
+
+ scope.needsUpdate = false;
+
+ _renderer.setRenderTarget( currentRenderTarget, activeCubeFace, activeMipmapLevel );
+
+ };
+
+ function VSMPass( shadow, camera ) {
+
+ const geometry = _objects.update( fullScreenMesh );
+
+ // vertical pass
+
+ shadowMaterialVertical.uniforms.shadow_pass.value = shadow.map.texture;
+ shadowMaterialVertical.uniforms.resolution.value = shadow.mapSize;
+ shadowMaterialVertical.uniforms.radius.value = shadow.radius;
+ _renderer.setRenderTarget( shadow.mapPass );
+ _renderer.clear();
+ _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialVertical, fullScreenMesh, null );
+
+ // horizontal pass
+
+ shadowMaterialHorizontal.uniforms.shadow_pass.value = shadow.mapPass.texture;
+ shadowMaterialHorizontal.uniforms.resolution.value = shadow.mapSize;
+ shadowMaterialHorizontal.uniforms.radius.value = shadow.radius;
+ _renderer.setRenderTarget( shadow.map );
+ _renderer.clear();
+ _renderer.renderBufferDirect( camera, null, geometry, shadowMaterialHorizontal, fullScreenMesh, null );
+
+ }
+
+ function getDepthMaterialVariant( useMorphing, useSkinning, useInstancing ) {
+
+ const index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;
+
+ let material = _depthMaterials[ index ];
+
+ if ( material === undefined ) {
+
+ material = new MeshDepthMaterial( {
+
+ depthPacking: RGBADepthPacking,
+
+ morphTargets: useMorphing,
+ skinning: useSkinning
+
+ } );
+
+ _depthMaterials[ index ] = material;
+
+ }
+
+ return material;
+
+ }
+
+ function getDistanceMaterialVariant( useMorphing, useSkinning, useInstancing ) {
+
+ const index = useMorphing << 0 | useSkinning << 1 | useInstancing << 2;
+
+ let material = _distanceMaterials[ index ];
+
+ if ( material === undefined ) {
+
+ material = new MeshDistanceMaterial( {
+
+ morphTargets: useMorphing,
+ skinning: useSkinning
+
+ } );
+
+ _distanceMaterials[ index ] = material;
+
+ }
+
+ return material;
+
+ }
+
+ function getDepthMaterial( object, geometry, material, light, shadowCameraNear, shadowCameraFar, type ) {
+
+ let result = null;
+
+ let getMaterialVariant = getDepthMaterialVariant;
+ let customMaterial = object.customDepthMaterial;
+
+ if ( light.isPointLight === true ) {
+
+ getMaterialVariant = getDistanceMaterialVariant;
+ customMaterial = object.customDistanceMaterial;
+
+ }
+
+ if ( customMaterial === undefined ) {
+
+ let useMorphing = false;
+
+ if ( material.morphTargets === true ) {
+
+ useMorphing = geometry.morphAttributes && geometry.morphAttributes.position && geometry.morphAttributes.position.length > 0;
+
+ }
+
+ let useSkinning = false;
+
+ if ( object.isSkinnedMesh === true ) {
+
+ if ( material.skinning === true ) {
+
+ useSkinning = true;
+
+ } else {
+
+ console.warn( 'THREE.WebGLShadowMap: THREE.SkinnedMesh with material.skinning set to false:', object );
+
+ }
+
+ }
+
+ const useInstancing = object.isInstancedMesh === true;
+
+ result = getMaterialVariant( useMorphing, useSkinning, useInstancing );
+
+ } else {
+
+ result = customMaterial;
+
+ }
+
+ if ( _renderer.localClippingEnabled &&
+ material.clipShadows === true &&
+ material.clippingPlanes.length !== 0 ) {
+
+ // in this case we need a unique material instance reflecting the
+ // appropriate state
+
+ const keyA = result.uuid, keyB = material.uuid;
+
+ let materialsForVariant = _materialCache[ keyA ];
+
+ if ( materialsForVariant === undefined ) {
+
+ materialsForVariant = {};
+ _materialCache[ keyA ] = materialsForVariant;
+
+ }
+
+ let cachedMaterial = materialsForVariant[ keyB ];
+
+ if ( cachedMaterial === undefined ) {
+
+ cachedMaterial = result.clone();
+ materialsForVariant[ keyB ] = cachedMaterial;
+
+ }
+
+ result = cachedMaterial;
+
+ }
+
+ result.visible = material.visible;
+ result.wireframe = material.wireframe;
+
+ if ( type === VSMShadowMap ) {
+
+ result.side = ( material.shadowSide !== null ) ? material.shadowSide : material.side;
+
+ } else {
+
+ result.side = ( material.shadowSide !== null ) ? material.shadowSide : shadowSide[ material.side ];
+
+ }
+
+ result.clipShadows = material.clipShadows;
+ result.clippingPlanes = material.clippingPlanes;
+ result.clipIntersection = material.clipIntersection;
+
+ result.wireframeLinewidth = material.wireframeLinewidth;
+ result.linewidth = material.linewidth;
+
+ if ( light.isPointLight === true && result.isMeshDistanceMaterial === true ) {
+
+ result.referencePosition.setFromMatrixPosition( light.matrixWorld );
+ result.nearDistance = shadowCameraNear;
+ result.farDistance = shadowCameraFar;
+
+ }
+
+ return result;
+
+ }
+
+ function renderObject( object, camera, shadowCamera, light, type ) {
+
+ if ( object.visible === false ) return;
+
+ const visible = object.layers.test( camera.layers );
+
+ if ( visible && ( object.isMesh || object.isLine || object.isPoints ) ) {
+
+ if ( ( object.castShadow || ( object.receiveShadow && type === VSMShadowMap ) ) && ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) ) {
+
+ object.modelViewMatrix.multiplyMatrices( shadowCamera.matrixWorldInverse, object.matrixWorld );
+
+ const geometry = _objects.update( object );
+ const material = object.material;
+
+ if ( Array.isArray( material ) ) {
+
+ const groups = geometry.groups;
+
+ for ( let k = 0, kl = groups.length; k < kl; k ++ ) {
+
+ const group = groups[ k ];
+ const groupMaterial = material[ group.materialIndex ];
+
+ if ( groupMaterial && groupMaterial.visible ) {
+
+ const depthMaterial = getDepthMaterial( object, geometry, groupMaterial, light, shadowCamera.near, shadowCamera.far, type );
+
+ _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, group );
+
+ }
+
+ }
+
+ } else if ( material.visible ) {
+
+ const depthMaterial = getDepthMaterial( object, geometry, material, light, shadowCamera.near, shadowCamera.far, type );
+
+ _renderer.renderBufferDirect( shadowCamera, null, geometry, depthMaterial, object, null );
+
+ }
+
+ }
+
+ }
+
+ const children = object.children;
+
+ for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+ renderObject( children[ i ], camera, shadowCamera, light, type );
+
+ }
+
+ }
+
+}
+
+function WebGLState( gl, extensions, capabilities ) {
+
+ const isWebGL2 = capabilities.isWebGL2;
+
+ function ColorBuffer() {
+
+ let locked = false;
+
+ const color = new Vector4();
+ let currentColorMask = null;
+ const currentColorClear = new Vector4( 0, 0, 0, 0 );
+
+ return {
+
+ setMask: function ( colorMask ) {
+
+ if ( currentColorMask !== colorMask && ! locked ) {
+
+ gl.colorMask( colorMask, colorMask, colorMask, colorMask );
+ currentColorMask = colorMask;
+
+ }
+
+ },
+
+ setLocked: function ( lock ) {
+
+ locked = lock;
+
+ },
+
+ setClear: function ( r, g, b, a, premultipliedAlpha ) {
+
+ if ( premultipliedAlpha === true ) {
+
+ r *= a; g *= a; b *= a;
+
+ }
+
+ color.set( r, g, b, a );
+
+ if ( currentColorClear.equals( color ) === false ) {
+
+ gl.clearColor( r, g, b, a );
+ currentColorClear.copy( color );
+
+ }
+
+ },
+
+ reset: function () {
+
+ locked = false;
+
+ currentColorMask = null;
+ currentColorClear.set( - 1, 0, 0, 0 ); // set to invalid state
+
+ }
+
+ };
+
+ }
+
+ function DepthBuffer() {
+
+ let locked = false;
+
+ let currentDepthMask = null;
+ let currentDepthFunc = null;
+ let currentDepthClear = null;
+
+ return {
+
+ setTest: function ( depthTest ) {
+
+ if ( depthTest ) {
+
+ enable( 2929 );
+
+ } else {
+
+ disable( 2929 );
+
+ }
+
+ },
+
+ setMask: function ( depthMask ) {
+
+ if ( currentDepthMask !== depthMask && ! locked ) {
+
+ gl.depthMask( depthMask );
+ currentDepthMask = depthMask;
+
+ }
+
+ },
+
+ setFunc: function ( depthFunc ) {
+
+ if ( currentDepthFunc !== depthFunc ) {
+
+ if ( depthFunc ) {
+
+ switch ( depthFunc ) {
+
+ case NeverDepth:
+
+ gl.depthFunc( 512 );
+ break;
+
+ case AlwaysDepth:
+
+ gl.depthFunc( 519 );
+ break;
+
+ case LessDepth:
+
+ gl.depthFunc( 513 );
+ break;
+
+ case LessEqualDepth:
+
+ gl.depthFunc( 515 );
+ break;
+
+ case EqualDepth:
+
+ gl.depthFunc( 514 );
+ break;
+
+ case GreaterEqualDepth:
+
+ gl.depthFunc( 518 );
+ break;
+
+ case GreaterDepth:
+
+ gl.depthFunc( 516 );
+ break;
+
+ case NotEqualDepth:
+
+ gl.depthFunc( 517 );
+ break;
+
+ default:
+
+ gl.depthFunc( 515 );
+
+ }
+
+ } else {
+
+ gl.depthFunc( 515 );
+
+ }
+
+ currentDepthFunc = depthFunc;
+
+ }
+
+ },
+
+ setLocked: function ( lock ) {
+
+ locked = lock;
+
+ },
+
+ setClear: function ( depth ) {
+
+ if ( currentDepthClear !== depth ) {
+
+ gl.clearDepth( depth );
+ currentDepthClear = depth;
+
+ }
+
+ },
+
+ reset: function () {
+
+ locked = false;
+
+ currentDepthMask = null;
+ currentDepthFunc = null;
+ currentDepthClear = null;
+
+ }
+
+ };
+
+ }
+
+ function StencilBuffer() {
+
+ let locked = false;
+
+ let currentStencilMask = null;
+ let currentStencilFunc = null;
+ let currentStencilRef = null;
+ let currentStencilFuncMask = null;
+ let currentStencilFail = null;
+ let currentStencilZFail = null;
+ let currentStencilZPass = null;
+ let currentStencilClear = null;
+
+ return {
+
+ setTest: function ( stencilTest ) {
+
+ if ( ! locked ) {
+
+ if ( stencilTest ) {
+
+ enable( 2960 );
+
+ } else {
+
+ disable( 2960 );
+
+ }
+
+ }
+
+ },
+
+ setMask: function ( stencilMask ) {
+
+ if ( currentStencilMask !== stencilMask && ! locked ) {
+
+ gl.stencilMask( stencilMask );
+ currentStencilMask = stencilMask;
+
+ }
+
+ },
+
+ setFunc: function ( stencilFunc, stencilRef, stencilMask ) {
+
+ if ( currentStencilFunc !== stencilFunc ||
+ currentStencilRef !== stencilRef ||
+ currentStencilFuncMask !== stencilMask ) {
+
+ gl.stencilFunc( stencilFunc, stencilRef, stencilMask );
+
+ currentStencilFunc = stencilFunc;
+ currentStencilRef = stencilRef;
+ currentStencilFuncMask = stencilMask;
+
+ }
+
+ },
+
+ setOp: function ( stencilFail, stencilZFail, stencilZPass ) {
+
+ if ( currentStencilFail !== stencilFail ||
+ currentStencilZFail !== stencilZFail ||
+ currentStencilZPass !== stencilZPass ) {
+
+ gl.stencilOp( stencilFail, stencilZFail, stencilZPass );
+
+ currentStencilFail = stencilFail;
+ currentStencilZFail = stencilZFail;
+ currentStencilZPass = stencilZPass;
+
+ }
+
+ },
+
+ setLocked: function ( lock ) {
+
+ locked = lock;
+
+ },
+
+ setClear: function ( stencil ) {
+
+ if ( currentStencilClear !== stencil ) {
+
+ gl.clearStencil( stencil );
+ currentStencilClear = stencil;
+
+ }
+
+ },
+
+ reset: function () {
+
+ locked = false;
+
+ currentStencilMask = null;
+ currentStencilFunc = null;
+ currentStencilRef = null;
+ currentStencilFuncMask = null;
+ currentStencilFail = null;
+ currentStencilZFail = null;
+ currentStencilZPass = null;
+ currentStencilClear = null;
+
+ }
+
+ };
+
+ }
+
+ //
+
+ const colorBuffer = new ColorBuffer();
+ const depthBuffer = new DepthBuffer();
+ const stencilBuffer = new StencilBuffer();
+
+ let enabledCapabilities = {};
+
+ let currentProgram = null;
+
+ let currentBlendingEnabled = null;
+ let currentBlending = null;
+ let currentBlendEquation = null;
+ let currentBlendSrc = null;
+ let currentBlendDst = null;
+ let currentBlendEquationAlpha = null;
+ let currentBlendSrcAlpha = null;
+ let currentBlendDstAlpha = null;
+ let currentPremultipledAlpha = false;
+
+ let currentFlipSided = null;
+ let currentCullFace = null;
+
+ let currentLineWidth = null;
+
+ let currentPolygonOffsetFactor = null;
+ let currentPolygonOffsetUnits = null;
+
+ const maxTextures = gl.getParameter( 35661 );
+
+ let lineWidthAvailable = false;
+ let version = 0;
+ const glVersion = gl.getParameter( 7938 );
+
+ if ( glVersion.indexOf( 'WebGL' ) !== - 1 ) {
+
+ version = parseFloat( /^WebGL (\d)/.exec( glVersion )[ 1 ] );
+ lineWidthAvailable = ( version >= 1.0 );
+
+ } else if ( glVersion.indexOf( 'OpenGL ES' ) !== - 1 ) {
+
+ version = parseFloat( /^OpenGL ES (\d)/.exec( glVersion )[ 1 ] );
+ lineWidthAvailable = ( version >= 2.0 );
+
+ }
+
+ let currentTextureSlot = null;
+ let currentBoundTextures = {};
+
+ const currentScissor = new Vector4();
+ const currentViewport = new Vector4();
+
+ function createTexture( type, target, count ) {
+
+ const data = new Uint8Array( 4 ); // 4 is required to match default unpack alignment of 4.
+ const texture = gl.createTexture();
+
+ gl.bindTexture( type, texture );
+ gl.texParameteri( type, 10241, 9728 );
+ gl.texParameteri( type, 10240, 9728 );
+
+ for ( let i = 0; i < count; i ++ ) {
+
+ gl.texImage2D( target + i, 0, 6408, 1, 1, 0, 6408, 5121, data );
+
+ }
+
+ return texture;
+
+ }
+
+ const emptyTextures = {};
+ emptyTextures[ 3553 ] = createTexture( 3553, 3553, 1 );
+ emptyTextures[ 34067 ] = createTexture( 34067, 34069, 6 );
+
+ // init
+
+ colorBuffer.setClear( 0, 0, 0, 1 );
+ depthBuffer.setClear( 1 );
+ stencilBuffer.setClear( 0 );
+
+ enable( 2929 );
+ depthBuffer.setFunc( LessEqualDepth );
+
+ setFlipSided( false );
+ setCullFace( CullFaceBack );
+ enable( 2884 );
+
+ setBlending( NoBlending );
+
+ //
+
+ function enable( id ) {
+
+ if ( enabledCapabilities[ id ] !== true ) {
+
+ gl.enable( id );
+ enabledCapabilities[ id ] = true;
+
+ }
+
+ }
+
+ function disable( id ) {
+
+ if ( enabledCapabilities[ id ] !== false ) {
+
+ gl.disable( id );
+ enabledCapabilities[ id ] = false;
+
+ }
+
+ }
+
+ function useProgram( program ) {
+
+ if ( currentProgram !== program ) {
+
+ gl.useProgram( program );
+
+ currentProgram = program;
+
+ return true;
+
+ }
+
+ return false;
+
+ }
+
+ const equationToGL = {
+ [ AddEquation ]: 32774,
+ [ SubtractEquation ]: 32778,
+ [ ReverseSubtractEquation ]: 32779
+ };
+
+ if ( isWebGL2 ) {
+
+ equationToGL[ MinEquation ] = 32775;
+ equationToGL[ MaxEquation ] = 32776;
+
+ } else {
+
+ const extension = extensions.get( 'EXT_blend_minmax' );
+
+ if ( extension !== null ) {
+
+ equationToGL[ MinEquation ] = extension.MIN_EXT;
+ equationToGL[ MaxEquation ] = extension.MAX_EXT;
+
+ }
+
+ }
+
+ const factorToGL = {
+ [ ZeroFactor ]: 0,
+ [ OneFactor ]: 1,
+ [ SrcColorFactor ]: 768,
+ [ SrcAlphaFactor ]: 770,
+ [ SrcAlphaSaturateFactor ]: 776,
+ [ DstColorFactor ]: 774,
+ [ DstAlphaFactor ]: 772,
+ [ OneMinusSrcColorFactor ]: 769,
+ [ OneMinusSrcAlphaFactor ]: 771,
+ [ OneMinusDstColorFactor ]: 775,
+ [ OneMinusDstAlphaFactor ]: 773
+ };
+
+ function setBlending( blending, blendEquation, blendSrc, blendDst, blendEquationAlpha, blendSrcAlpha, blendDstAlpha, premultipliedAlpha ) {
+
+ if ( blending === NoBlending ) {
+
+ if ( currentBlendingEnabled ) {
+
+ disable( 3042 );
+ currentBlendingEnabled = false;
+
+ }
+
+ return;
+
+ }
+
+ if ( ! currentBlendingEnabled ) {
+
+ enable( 3042 );
+ currentBlendingEnabled = true;
+
+ }
+
+ if ( blending !== CustomBlending ) {
+
+ if ( blending !== currentBlending || premultipliedAlpha !== currentPremultipledAlpha ) {
+
+ if ( currentBlendEquation !== AddEquation || currentBlendEquationAlpha !== AddEquation ) {
+
+ gl.blendEquation( 32774 );
+
+ currentBlendEquation = AddEquation;
+ currentBlendEquationAlpha = AddEquation;
+
+ }
+
+ if ( premultipliedAlpha ) {
+
+ switch ( blending ) {
+
+ case NormalBlending:
+ gl.blendFuncSeparate( 1, 771, 1, 771 );
+ break;
+
+ case AdditiveBlending:
+ gl.blendFunc( 1, 1 );
+ break;
+
+ case SubtractiveBlending:
+ gl.blendFuncSeparate( 0, 0, 769, 771 );
+ break;
+
+ case MultiplyBlending:
+ gl.blendFuncSeparate( 0, 768, 0, 770 );
+ break;
+
+ default:
+ console.error( 'THREE.WebGLState: Invalid blending: ', blending );
+ break;
+
+ }
+
+ } else {
+
+ switch ( blending ) {
+
+ case NormalBlending:
+ gl.blendFuncSeparate( 770, 771, 1, 771 );
+ break;
+
+ case AdditiveBlending:
+ gl.blendFunc( 770, 1 );
+ break;
+
+ case SubtractiveBlending:
+ gl.blendFunc( 0, 769 );
+ break;
+
+ case MultiplyBlending:
+ gl.blendFunc( 0, 768 );
+ break;
+
+ default:
+ console.error( 'THREE.WebGLState: Invalid blending: ', blending );
+ break;
+
+ }
+
+ }
+
+ currentBlendSrc = null;
+ currentBlendDst = null;
+ currentBlendSrcAlpha = null;
+ currentBlendDstAlpha = null;
+
+ currentBlending = blending;
+ currentPremultipledAlpha = premultipliedAlpha;
+
+ }
+
+ return;
+
+ }
+
+ // custom blending
+
+ blendEquationAlpha = blendEquationAlpha || blendEquation;
+ blendSrcAlpha = blendSrcAlpha || blendSrc;
+ blendDstAlpha = blendDstAlpha || blendDst;
+
+ if ( blendEquation !== currentBlendEquation || blendEquationAlpha !== currentBlendEquationAlpha ) {
+
+ gl.blendEquationSeparate( equationToGL[ blendEquation ], equationToGL[ blendEquationAlpha ] );
+
+ currentBlendEquation = blendEquation;
+ currentBlendEquationAlpha = blendEquationAlpha;
+
+ }
+
+ if ( blendSrc !== currentBlendSrc || blendDst !== currentBlendDst || blendSrcAlpha !== currentBlendSrcAlpha || blendDstAlpha !== currentBlendDstAlpha ) {
+
+ gl.blendFuncSeparate( factorToGL[ blendSrc ], factorToGL[ blendDst ], factorToGL[ blendSrcAlpha ], factorToGL[ blendDstAlpha ] );
+
+ currentBlendSrc = blendSrc;
+ currentBlendDst = blendDst;
+ currentBlendSrcAlpha = blendSrcAlpha;
+ currentBlendDstAlpha = blendDstAlpha;
+
+ }
+
+ currentBlending = blending;
+ currentPremultipledAlpha = null;
+
+ }
+
+ function setMaterial( material, frontFaceCW ) {
+
+ material.side === DoubleSide
+ ? disable( 2884 )
+ : enable( 2884 );
+
+ let flipSided = ( material.side === BackSide );
+ if ( frontFaceCW ) flipSided = ! flipSided;
+
+ setFlipSided( flipSided );
+
+ ( material.blending === NormalBlending && material.transparent === false )
+ ? setBlending( NoBlending )
+ : setBlending( material.blending, material.blendEquation, material.blendSrc, material.blendDst, material.blendEquationAlpha, material.blendSrcAlpha, material.blendDstAlpha, material.premultipliedAlpha );
+
+ depthBuffer.setFunc( material.depthFunc );
+ depthBuffer.setTest( material.depthTest );
+ depthBuffer.setMask( material.depthWrite );
+ colorBuffer.setMask( material.colorWrite );
+
+ const stencilWrite = material.stencilWrite;
+ stencilBuffer.setTest( stencilWrite );
+ if ( stencilWrite ) {
+
+ stencilBuffer.setMask( material.stencilWriteMask );
+ stencilBuffer.setFunc( material.stencilFunc, material.stencilRef, material.stencilFuncMask );
+ stencilBuffer.setOp( material.stencilFail, material.stencilZFail, material.stencilZPass );
+
+ }
+
+ setPolygonOffset( material.polygonOffset, material.polygonOffsetFactor, material.polygonOffsetUnits );
+
+ }
+
+ //
+
+ function setFlipSided( flipSided ) {
+
+ if ( currentFlipSided !== flipSided ) {
+
+ if ( flipSided ) {
+
+ gl.frontFace( 2304 );
+
+ } else {
+
+ gl.frontFace( 2305 );
+
+ }
+
+ currentFlipSided = flipSided;
+
+ }
+
+ }
+
+ function setCullFace( cullFace ) {
+
+ if ( cullFace !== CullFaceNone ) {
+
+ enable( 2884 );
+
+ if ( cullFace !== currentCullFace ) {
+
+ if ( cullFace === CullFaceBack ) {
+
+ gl.cullFace( 1029 );
+
+ } else if ( cullFace === CullFaceFront ) {
+
+ gl.cullFace( 1028 );
+
+ } else {
+
+ gl.cullFace( 1032 );
+
+ }
+
+ }
+
+ } else {
+
+ disable( 2884 );
+
+ }
+
+ currentCullFace = cullFace;
+
+ }
+
+ function setLineWidth( width ) {
+
+ if ( width !== currentLineWidth ) {
+
+ if ( lineWidthAvailable ) gl.lineWidth( width );
+
+ currentLineWidth = width;
+
+ }
+
+ }
+
+ function setPolygonOffset( polygonOffset, factor, units ) {
+
+ if ( polygonOffset ) {
+
+ enable( 32823 );
+
+ if ( currentPolygonOffsetFactor !== factor || currentPolygonOffsetUnits !== units ) {
+
+ gl.polygonOffset( factor, units );
+
+ currentPolygonOffsetFactor = factor;
+ currentPolygonOffsetUnits = units;
+
+ }
+
+ } else {
+
+ disable( 32823 );
+
+ }
+
+ }
+
+ function setScissorTest( scissorTest ) {
+
+ if ( scissorTest ) {
+
+ enable( 3089 );
+
+ } else {
+
+ disable( 3089 );
+
+ }
+
+ }
+
+ // texture
+
+ function activeTexture( webglSlot ) {
+
+ if ( webglSlot === undefined ) webglSlot = 33984 + maxTextures - 1;
+
+ if ( currentTextureSlot !== webglSlot ) {
+
+ gl.activeTexture( webglSlot );
+ currentTextureSlot = webglSlot;
+
+ }
+
+ }
+
+ function bindTexture( webglType, webglTexture ) {
+
+ if ( currentTextureSlot === null ) {
+
+ activeTexture();
+
+ }
+
+ let boundTexture = currentBoundTextures[ currentTextureSlot ];
+
+ if ( boundTexture === undefined ) {
+
+ boundTexture = { type: undefined, texture: undefined };
+ currentBoundTextures[ currentTextureSlot ] = boundTexture;
+
+ }
+
+ if ( boundTexture.type !== webglType || boundTexture.texture !== webglTexture ) {
+
+ gl.bindTexture( webglType, webglTexture || emptyTextures[ webglType ] );
+
+ boundTexture.type = webglType;
+ boundTexture.texture = webglTexture;
+
+ }
+
+ }
+
+ function unbindTexture() {
+
+ const boundTexture = currentBoundTextures[ currentTextureSlot ];
+
+ if ( boundTexture !== undefined && boundTexture.type !== undefined ) {
+
+ gl.bindTexture( boundTexture.type, null );
+
+ boundTexture.type = undefined;
+ boundTexture.texture = undefined;
+
+ }
+
+ }
+
+ function compressedTexImage2D() {
+
+ try {
+
+ gl.compressedTexImage2D.apply( gl, arguments );
+
+ } catch ( error ) {
+
+ console.error( 'THREE.WebGLState:', error );
+
+ }
+
+ }
+
+ function texImage2D() {
+
+ try {
+
+ gl.texImage2D.apply( gl, arguments );
+
+ } catch ( error ) {
+
+ console.error( 'THREE.WebGLState:', error );
+
+ }
+
+ }
+
+ function texImage3D() {
+
+ try {
+
+ gl.texImage3D.apply( gl, arguments );
+
+ } catch ( error ) {
+
+ console.error( 'THREE.WebGLState:', error );
+
+ }
+
+ }
+
+ //
+
+ function scissor( scissor ) {
+
+ if ( currentScissor.equals( scissor ) === false ) {
+
+ gl.scissor( scissor.x, scissor.y, scissor.z, scissor.w );
+ currentScissor.copy( scissor );
+
+ }
+
+ }
+
+ function viewport( viewport ) {
+
+ if ( currentViewport.equals( viewport ) === false ) {
+
+ gl.viewport( viewport.x, viewport.y, viewport.z, viewport.w );
+ currentViewport.copy( viewport );
+
+ }
+
+ }
+
+ //
+
+ function reset() {
+
+ enabledCapabilities = {};
+
+ currentTextureSlot = null;
+ currentBoundTextures = {};
+
+ currentProgram = null;
+
+ currentBlendingEnabled = null;
+ currentBlending = null;
+ currentBlendEquation = null;
+ currentBlendSrc = null;
+ currentBlendDst = null;
+ currentBlendEquationAlpha = null;
+ currentBlendSrcAlpha = null;
+ currentBlendDstAlpha = null;
+ currentPremultipledAlpha = false;
+
+ currentFlipSided = null;
+ currentCullFace = null;
+
+ currentLineWidth = null;
+
+ currentPolygonOffsetFactor = null;
+ currentPolygonOffsetUnits = null;
+
+ colorBuffer.reset();
+ depthBuffer.reset();
+ stencilBuffer.reset();
+
+ }
+
+ return {
+
+ buffers: {
+ color: colorBuffer,
+ depth: depthBuffer,
+ stencil: stencilBuffer
+ },
+
+ enable: enable,
+ disable: disable,
+
+ useProgram: useProgram,
+
+ setBlending: setBlending,
+ setMaterial: setMaterial,
+
+ setFlipSided: setFlipSided,
+ setCullFace: setCullFace,
+
+ setLineWidth: setLineWidth,
+ setPolygonOffset: setPolygonOffset,
+
+ setScissorTest: setScissorTest,
+
+ activeTexture: activeTexture,
+ bindTexture: bindTexture,
+ unbindTexture: unbindTexture,
+ compressedTexImage2D: compressedTexImage2D,
+ texImage2D: texImage2D,
+ texImage3D: texImage3D,
+
+ scissor: scissor,
+ viewport: viewport,
+
+ reset: reset
+
+ };
+
+}
+
+function WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info ) {
+
+ const isWebGL2 = capabilities.isWebGL2;
+ const maxTextures = capabilities.maxTextures;
+ const maxCubemapSize = capabilities.maxCubemapSize;
+ const maxTextureSize = capabilities.maxTextureSize;
+ const maxSamples = capabilities.maxSamples;
+
+ const _videoTextures = new WeakMap();
+ let _canvas;
+
+ // cordova iOS (as of 5.0) still uses UIWebView, which provides OffscreenCanvas,
+ // also OffscreenCanvas.getContext("webgl"), but not OffscreenCanvas.getContext("2d")!
+ // Some implementations may only implement OffscreenCanvas partially (e.g. lacking 2d).
+
+ let useOffscreenCanvas = false;
+
+ try {
+
+ useOffscreenCanvas = typeof OffscreenCanvas !== 'undefined'
+ && ( new OffscreenCanvas( 1, 1 ).getContext( '2d' ) ) !== null;
+
+ } catch ( err ) {
+
+ // Ignore any errors
+
+ }
+
+ function createCanvas( width, height ) {
+
+ // Use OffscreenCanvas when available. Specially needed in web workers
+
+ return useOffscreenCanvas ?
+ new OffscreenCanvas( width, height ) :
+ document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+
+ }
+
+ function resizeImage( image, needsPowerOfTwo, needsNewCanvas, maxSize ) {
+
+ let scale = 1;
+
+ // handle case if texture exceeds max size
+
+ if ( image.width > maxSize || image.height > maxSize ) {
+
+ scale = maxSize / Math.max( image.width, image.height );
+
+ }
+
+ // only perform resize if necessary
+
+ if ( scale < 1 || needsPowerOfTwo === true ) {
+
+ // only perform resize for certain image types
+
+ if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
+ ( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
+ ( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {
+
+ const floor = needsPowerOfTwo ? MathUtils.floorPowerOfTwo : Math.floor;
+
+ const width = floor( scale * image.width );
+ const height = floor( scale * image.height );
+
+ if ( _canvas === undefined ) _canvas = createCanvas( width, height );
+
+ // cube textures can't reuse the same canvas
+
+ const canvas = needsNewCanvas ? createCanvas( width, height ) : _canvas;
+
+ canvas.width = width;
+ canvas.height = height;
+
+ const context = canvas.getContext( '2d' );
+ context.drawImage( image, 0, 0, width, height );
+
+ console.warn( 'THREE.WebGLRenderer: Texture has been resized from (' + image.width + 'x' + image.height + ') to (' + width + 'x' + height + ').' );
+
+ return canvas;
+
+ } else {
+
+ if ( 'data' in image ) {
+
+ console.warn( 'THREE.WebGLRenderer: Image in DataTexture is too big (' + image.width + 'x' + image.height + ').' );
+
+ }
+
+ return image;
+
+ }
+
+ }
+
+ return image;
+
+ }
+
+ function isPowerOfTwo( image ) {
+
+ return MathUtils.isPowerOfTwo( image.width ) && MathUtils.isPowerOfTwo( image.height );
+
+ }
+
+ function textureNeedsPowerOfTwo( texture ) {
+
+ if ( isWebGL2 ) return false;
+
+ return ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) ||
+ ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter );
+
+ }
+
+ function textureNeedsGenerateMipmaps( texture, supportsMips ) {
+
+ return texture.generateMipmaps && supportsMips &&
+ texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter;
+
+ }
+
+ function generateMipmap( target, texture, width, height ) {
+
+ _gl.generateMipmap( target );
+
+ const textureProperties = properties.get( texture );
+
+ // Note: Math.log( x ) * Math.LOG2E used instead of Math.log2( x ) which is not supported by IE11
+ textureProperties.__maxMipLevel = Math.log( Math.max( width, height ) ) * Math.LOG2E;
+
+ }
+
+ function getInternalFormat( internalFormatName, glFormat, glType ) {
+
+ if ( isWebGL2 === false ) return glFormat;
+
+ if ( internalFormatName !== null ) {
+
+ if ( _gl[ internalFormatName ] !== undefined ) return _gl[ internalFormatName ];
+
+ console.warn( 'THREE.WebGLRenderer: Attempt to use non-existing WebGL internal format \'' + internalFormatName + '\'' );
+
+ }
+
+ let internalFormat = glFormat;
+
+ if ( glFormat === 6403 ) {
+
+ if ( glType === 5126 ) internalFormat = 33326;
+ if ( glType === 5131 ) internalFormat = 33325;
+ if ( glType === 5121 ) internalFormat = 33321;
+
+ }
+
+ if ( glFormat === 6407 ) {
+
+ if ( glType === 5126 ) internalFormat = 34837;
+ if ( glType === 5131 ) internalFormat = 34843;
+ if ( glType === 5121 ) internalFormat = 32849;
+
+ }
+
+ if ( glFormat === 6408 ) {
+
+ if ( glType === 5126 ) internalFormat = 34836;
+ if ( glType === 5131 ) internalFormat = 34842;
+ if ( glType === 5121 ) internalFormat = 32856;
+
+ }
+
+ if ( internalFormat === 33325 || internalFormat === 33326 ||
+ internalFormat === 34842 || internalFormat === 34836 ) {
+
+ extensions.get( 'EXT_color_buffer_float' );
+
+ }
+
+ return internalFormat;
+
+ }
+
+ // Fallback filters for non-power-of-2 textures
+
+ function filterFallback( f ) {
+
+ if ( f === NearestFilter || f === NearestMipmapNearestFilter || f === NearestMipmapLinearFilter ) {
+
+ return 9728;
+
+ }
+
+ return 9729;
+
+ }
+
+ //
+
+ function onTextureDispose( event ) {
+
+ const texture = event.target;
+
+ texture.removeEventListener( 'dispose', onTextureDispose );
+
+ deallocateTexture( texture );
+
+ if ( texture.isVideoTexture ) {
+
+ _videoTextures.delete( texture );
+
+ }
+
+ info.memory.textures --;
+
+ }
+
+ function onRenderTargetDispose( event ) {
+
+ const renderTarget = event.target;
+
+ renderTarget.removeEventListener( 'dispose', onRenderTargetDispose );
+
+ deallocateRenderTarget( renderTarget );
+
+ info.memory.textures --;
+
+ }
+
+ //
+
+ function deallocateTexture( texture ) {
+
+ const textureProperties = properties.get( texture );
+
+ if ( textureProperties.__webglInit === undefined ) return;
+
+ _gl.deleteTexture( textureProperties.__webglTexture );
+
+ properties.remove( texture );
+
+ }
+
+ function deallocateRenderTarget( renderTarget ) {
+
+ const renderTargetProperties = properties.get( renderTarget );
+ const textureProperties = properties.get( renderTarget.texture );
+
+ if ( ! renderTarget ) return;
+
+ if ( textureProperties.__webglTexture !== undefined ) {
+
+ _gl.deleteTexture( textureProperties.__webglTexture );
+
+ }
+
+ if ( renderTarget.depthTexture ) {
+
+ renderTarget.depthTexture.dispose();
+
+ }
+
+ if ( renderTarget.isWebGLCubeRenderTarget ) {
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer[ i ] );
+ if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer[ i ] );
+
+ }
+
+ } else {
+
+ _gl.deleteFramebuffer( renderTargetProperties.__webglFramebuffer );
+ if ( renderTargetProperties.__webglDepthbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthbuffer );
+ if ( renderTargetProperties.__webglMultisampledFramebuffer ) _gl.deleteFramebuffer( renderTargetProperties.__webglMultisampledFramebuffer );
+ if ( renderTargetProperties.__webglColorRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglColorRenderbuffer );
+ if ( renderTargetProperties.__webglDepthRenderbuffer ) _gl.deleteRenderbuffer( renderTargetProperties.__webglDepthRenderbuffer );
+
+ }
+
+ properties.remove( renderTarget.texture );
+ properties.remove( renderTarget );
+
+ }
+
+ //
+
+ let textureUnits = 0;
+
+ function resetTextureUnits() {
+
+ textureUnits = 0;
+
+ }
+
+ function allocateTextureUnit() {
+
+ const textureUnit = textureUnits;
+
+ if ( textureUnit >= maxTextures ) {
+
+ console.warn( 'THREE.WebGLTextures: Trying to use ' + textureUnit + ' texture units while this GPU supports only ' + maxTextures );
+
+ }
+
+ textureUnits += 1;
+
+ return textureUnit;
+
+ }
+
+ //
+
+ function setTexture2D( texture, slot ) {
+
+ const textureProperties = properties.get( texture );
+
+ if ( texture.isVideoTexture ) updateVideoTexture( texture );
+
+ if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+
+ const image = texture.image;
+
+ if ( image === undefined ) {
+
+ console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is undefined' );
+
+ } else if ( image.complete === false ) {
+
+ console.warn( 'THREE.WebGLRenderer: Texture marked for update but image is incomplete' );
+
+ } else {
+
+ uploadTexture( textureProperties, texture, slot );
+ return;
+
+ }
+
+ }
+
+ state.activeTexture( 33984 + slot );
+ state.bindTexture( 3553, textureProperties.__webglTexture );
+
+ }
+
+ function setTexture2DArray( texture, slot ) {
+
+ const textureProperties = properties.get( texture );
+
+ if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+
+ uploadTexture( textureProperties, texture, slot );
+ return;
+
+ }
+
+ state.activeTexture( 33984 + slot );
+ state.bindTexture( 35866, textureProperties.__webglTexture );
+
+ }
+
+ function setTexture3D( texture, slot ) {
+
+ const textureProperties = properties.get( texture );
+
+ if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+
+ uploadTexture( textureProperties, texture, slot );
+ return;
+
+ }
+
+ state.activeTexture( 33984 + slot );
+ state.bindTexture( 32879, textureProperties.__webglTexture );
+
+ }
+
+ function setTextureCube( texture, slot ) {
+
+ const textureProperties = properties.get( texture );
+
+ if ( texture.version > 0 && textureProperties.__version !== texture.version ) {
+
+ uploadCubeTexture( textureProperties, texture, slot );
+ return;
+
+ }
+
+ state.activeTexture( 33984 + slot );
+ state.bindTexture( 34067, textureProperties.__webglTexture );
+
+ }
+
+ const wrappingToGL = {
+ [ RepeatWrapping ]: 10497,
+ [ ClampToEdgeWrapping ]: 33071,
+ [ MirroredRepeatWrapping ]: 33648
+ };
+
+ const filterToGL = {
+ [ NearestFilter ]: 9728,
+ [ NearestMipmapNearestFilter ]: 9984,
+ [ NearestMipmapLinearFilter ]: 9986,
+
+ [ LinearFilter ]: 9729,
+ [ LinearMipmapNearestFilter ]: 9985,
+ [ LinearMipmapLinearFilter ]: 9987
+ };
+
+ function setTextureParameters( textureType, texture, supportsMips ) {
+
+ if ( supportsMips ) {
+
+ _gl.texParameteri( textureType, 10242, wrappingToGL[ texture.wrapS ] );
+ _gl.texParameteri( textureType, 10243, wrappingToGL[ texture.wrapT ] );
+
+ if ( textureType === 32879 || textureType === 35866 ) {
+
+ _gl.texParameteri( textureType, 32882, wrappingToGL[ texture.wrapR ] );
+
+ }
+
+ _gl.texParameteri( textureType, 10240, filterToGL[ texture.magFilter ] );
+ _gl.texParameteri( textureType, 10241, filterToGL[ texture.minFilter ] );
+
+ } else {
+
+ _gl.texParameteri( textureType, 10242, 33071 );
+ _gl.texParameteri( textureType, 10243, 33071 );
+
+ if ( textureType === 32879 || textureType === 35866 ) {
+
+ _gl.texParameteri( textureType, 32882, 33071 );
+
+ }
+
+ if ( texture.wrapS !== ClampToEdgeWrapping || texture.wrapT !== ClampToEdgeWrapping ) {
+
+ console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.wrapS and Texture.wrapT should be set to THREE.ClampToEdgeWrapping.' );
+
+ }
+
+ _gl.texParameteri( textureType, 10240, filterFallback( texture.magFilter ) );
+ _gl.texParameteri( textureType, 10241, filterFallback( texture.minFilter ) );
+
+ if ( texture.minFilter !== NearestFilter && texture.minFilter !== LinearFilter ) {
+
+ console.warn( 'THREE.WebGLRenderer: Texture is not power of two. Texture.minFilter should be set to THREE.NearestFilter or THREE.LinearFilter.' );
+
+ }
+
+ }
+
+ const extension = extensions.get( 'EXT_texture_filter_anisotropic' );
+
+ if ( extension ) {
+
+ if ( texture.type === FloatType && extensions.get( 'OES_texture_float_linear' ) === null ) return;
+ if ( texture.type === HalfFloatType && ( isWebGL2 || extensions.get( 'OES_texture_half_float_linear' ) ) === null ) return;
+
+ if ( texture.anisotropy > 1 || properties.get( texture ).__currentAnisotropy ) {
+
+ _gl.texParameterf( textureType, extension.TEXTURE_MAX_ANISOTROPY_EXT, Math.min( texture.anisotropy, capabilities.getMaxAnisotropy() ) );
+ properties.get( texture ).__currentAnisotropy = texture.anisotropy;
+
+ }
+
+ }
+
+ }
+
+ function initTexture( textureProperties, texture ) {
+
+ if ( textureProperties.__webglInit === undefined ) {
+
+ textureProperties.__webglInit = true;
+
+ texture.addEventListener( 'dispose', onTextureDispose );
+
+ textureProperties.__webglTexture = _gl.createTexture();
+
+ info.memory.textures ++;
+
+ }
+
+ }
+
+ function uploadTexture( textureProperties, texture, slot ) {
+
+ let textureType = 3553;
+
+ if ( texture.isDataTexture2DArray ) textureType = 35866;
+ if ( texture.isDataTexture3D ) textureType = 32879;
+
+ initTexture( textureProperties, texture );
+
+ state.activeTexture( 33984 + slot );
+ state.bindTexture( textureType, textureProperties.__webglTexture );
+
+ _gl.pixelStorei( 37440, texture.flipY );
+ _gl.pixelStorei( 37441, texture.premultiplyAlpha );
+ _gl.pixelStorei( 3317, texture.unpackAlignment );
+
+ const needsPowerOfTwo = textureNeedsPowerOfTwo( texture ) && isPowerOfTwo( texture.image ) === false;
+ const image = resizeImage( texture.image, needsPowerOfTwo, false, maxTextureSize );
+
+ const supportsMips = isPowerOfTwo( image ) || isWebGL2,
+ glFormat = utils.convert( texture.format );
+
+ let glType = utils.convert( texture.type ),
+ glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType );
+
+ setTextureParameters( textureType, texture, supportsMips );
+
+ let mipmap;
+ const mipmaps = texture.mipmaps;
+
+ if ( texture.isDepthTexture ) {
+
+ // populate depth texture with dummy data
+
+ glInternalFormat = 6402;
+
+ if ( isWebGL2 ) {
+
+ if ( texture.type === FloatType ) {
+
+ glInternalFormat = 36012;
+
+ } else if ( texture.type === UnsignedIntType ) {
+
+ glInternalFormat = 33190;
+
+ } else if ( texture.type === UnsignedInt248Type ) {
+
+ glInternalFormat = 35056;
+
+ } else {
+
+ glInternalFormat = 33189; // WebGL2 requires sized internalformat for glTexImage2D
+
+ }
+
+ } else {
+
+ if ( texture.type === FloatType ) {
+
+ console.error( 'WebGLRenderer: Floating point depth texture requires WebGL2.' );
+
+ }
+
+ }
+
+ // validation checks for WebGL 1
+
+ if ( texture.format === DepthFormat && glInternalFormat === 6402 ) {
+
+ // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are
+ // DEPTH_COMPONENT and type is not UNSIGNED_SHORT or UNSIGNED_INT
+ // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
+ if ( texture.type !== UnsignedShortType && texture.type !== UnsignedIntType ) {
+
+ console.warn( 'THREE.WebGLRenderer: Use UnsignedShortType or UnsignedIntType for DepthFormat DepthTexture.' );
+
+ texture.type = UnsignedShortType;
+ glType = utils.convert( texture.type );
+
+ }
+
+ }
+
+ if ( texture.format === DepthStencilFormat && glInternalFormat === 6402 ) {
+
+ // Depth stencil textures need the DEPTH_STENCIL internal format
+ // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
+ glInternalFormat = 34041;
+
+ // The error INVALID_OPERATION is generated by texImage2D if format and internalformat are
+ // DEPTH_STENCIL and type is not UNSIGNED_INT_24_8_WEBGL.
+ // (https://www.khronos.org/registry/webgl/extensions/WEBGL_depth_texture/)
+ if ( texture.type !== UnsignedInt248Type ) {
+
+ console.warn( 'THREE.WebGLRenderer: Use UnsignedInt248Type for DepthStencilFormat DepthTexture.' );
+
+ texture.type = UnsignedInt248Type;
+ glType = utils.convert( texture.type );
+
+ }
+
+ }
+
+ //
+
+ state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, null );
+
+ } else if ( texture.isDataTexture ) {
+
+ // use manually created mipmaps if available
+ // if there are no manual mipmaps
+ // set 0 level mipmap and then use GL to generate other mipmap levels
+
+ if ( mipmaps.length > 0 && supportsMips ) {
+
+ for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+ mipmap = mipmaps[ i ];
+ state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+
+ }
+
+ texture.generateMipmaps = false;
+ textureProperties.__maxMipLevel = mipmaps.length - 1;
+
+ } else {
+
+ state.texImage2D( 3553, 0, glInternalFormat, image.width, image.height, 0, glFormat, glType, image.data );
+ textureProperties.__maxMipLevel = 0;
+
+ }
+
+ } else if ( texture.isCompressedTexture ) {
+
+ for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+ mipmap = mipmaps[ i ];
+
+ if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {
+
+ if ( glFormat !== null ) {
+
+ state.compressedTexImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+
+ } else {
+
+ console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .uploadTexture()' );
+
+ }
+
+ } else {
+
+ state.texImage2D( 3553, i, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+
+ }
+
+ }
+
+ textureProperties.__maxMipLevel = mipmaps.length - 1;
+
+ } else if ( texture.isDataTexture2DArray ) {
+
+ state.texImage3D( 35866, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
+ textureProperties.__maxMipLevel = 0;
+
+ } else if ( texture.isDataTexture3D ) {
+
+ state.texImage3D( 32879, 0, glInternalFormat, image.width, image.height, image.depth, 0, glFormat, glType, image.data );
+ textureProperties.__maxMipLevel = 0;
+
+ } else {
+
+ // regular Texture (image, video, canvas)
+
+ // use manually created mipmaps if available
+ // if there are no manual mipmaps
+ // set 0 level mipmap and then use GL to generate other mipmap levels
+
+ if ( mipmaps.length > 0 && supportsMips ) {
+
+ for ( let i = 0, il = mipmaps.length; i < il; i ++ ) {
+
+ mipmap = mipmaps[ i ];
+ state.texImage2D( 3553, i, glInternalFormat, glFormat, glType, mipmap );
+
+ }
+
+ texture.generateMipmaps = false;
+ textureProperties.__maxMipLevel = mipmaps.length - 1;
+
+ } else {
+
+ state.texImage2D( 3553, 0, glInternalFormat, glFormat, glType, image );
+ textureProperties.__maxMipLevel = 0;
+
+ }
+
+ }
+
+ if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+
+ generateMipmap( textureType, texture, image.width, image.height );
+
+ }
+
+ textureProperties.__version = texture.version;
+
+ if ( texture.onUpdate ) texture.onUpdate( texture );
+
+ }
+
+ function uploadCubeTexture( textureProperties, texture, slot ) {
+
+ if ( texture.image.length !== 6 ) return;
+
+ initTexture( textureProperties, texture );
+
+ state.activeTexture( 33984 + slot );
+ state.bindTexture( 34067, textureProperties.__webglTexture );
+
+ _gl.pixelStorei( 37440, texture.flipY );
+ _gl.pixelStorei( 37441, texture.premultiplyAlpha );
+ _gl.pixelStorei( 3317, texture.unpackAlignment );
+
+ const isCompressed = ( texture && ( texture.isCompressedTexture || texture.image[ 0 ].isCompressedTexture ) );
+ const isDataTexture = ( texture.image[ 0 ] && texture.image[ 0 ].isDataTexture );
+
+ const cubeImage = [];
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ if ( ! isCompressed && ! isDataTexture ) {
+
+ cubeImage[ i ] = resizeImage( texture.image[ i ], false, true, maxCubemapSize );
+
+ } else {
+
+ cubeImage[ i ] = isDataTexture ? texture.image[ i ].image : texture.image[ i ];
+
+ }
+
+ }
+
+ const image = cubeImage[ 0 ],
+ supportsMips = isPowerOfTwo( image ) || isWebGL2,
+ glFormat = utils.convert( texture.format ),
+ glType = utils.convert( texture.type ),
+ glInternalFormat = getInternalFormat( texture.internalFormat, glFormat, glType );
+
+ setTextureParameters( 34067, texture, supportsMips );
+
+ let mipmaps;
+
+ if ( isCompressed ) {
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ mipmaps = cubeImage[ i ].mipmaps;
+
+ for ( let j = 0; j < mipmaps.length; j ++ ) {
+
+ const mipmap = mipmaps[ j ];
+
+ if ( texture.format !== RGBAFormat && texture.format !== RGBFormat ) {
+
+ if ( glFormat !== null ) {
+
+ state.compressedTexImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, mipmap.data );
+
+ } else {
+
+ console.warn( 'THREE.WebGLRenderer: Attempt to load unsupported compressed texture format in .setTextureCube()' );
+
+ }
+
+ } else {
+
+ state.texImage2D( 34069 + i, j, glInternalFormat, mipmap.width, mipmap.height, 0, glFormat, glType, mipmap.data );
+
+ }
+
+ }
+
+ }
+
+ textureProperties.__maxMipLevel = mipmaps.length - 1;
+
+ } else {
+
+ mipmaps = texture.mipmaps;
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ if ( isDataTexture ) {
+
+ state.texImage2D( 34069 + i, 0, glInternalFormat, cubeImage[ i ].width, cubeImage[ i ].height, 0, glFormat, glType, cubeImage[ i ].data );
+
+ for ( let j = 0; j < mipmaps.length; j ++ ) {
+
+ const mipmap = mipmaps[ j ];
+ const mipmapImage = mipmap.image[ i ].image;
+
+ state.texImage2D( 34069 + i, j + 1, glInternalFormat, mipmapImage.width, mipmapImage.height, 0, glFormat, glType, mipmapImage.data );
+
+ }
+
+ } else {
+
+ state.texImage2D( 34069 + i, 0, glInternalFormat, glFormat, glType, cubeImage[ i ] );
+
+ for ( let j = 0; j < mipmaps.length; j ++ ) {
+
+ const mipmap = mipmaps[ j ];
+
+ state.texImage2D( 34069 + i, j + 1, glInternalFormat, glFormat, glType, mipmap.image[ i ] );
+
+ }
+
+ }
+
+ }
+
+ textureProperties.__maxMipLevel = mipmaps.length;
+
+ }
+
+ if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+
+ // We assume images for cube map have the same size.
+ generateMipmap( 34067, texture, image.width, image.height );
+
+ }
+
+ textureProperties.__version = texture.version;
+
+ if ( texture.onUpdate ) texture.onUpdate( texture );
+
+ }
+
+ // Render targets
+
+ // Setup storage for target texture and bind it to correct framebuffer
+ function setupFrameBufferTexture( framebuffer, renderTarget, attachment, textureTarget ) {
+
+ const glFormat = utils.convert( renderTarget.texture.format );
+ const glType = utils.convert( renderTarget.texture.type );
+ const glInternalFormat = getInternalFormat( renderTarget.texture.internalFormat, glFormat, glType );
+ state.texImage2D( textureTarget, 0, glInternalFormat, renderTarget.width, renderTarget.height, 0, glFormat, glType, null );
+ _gl.bindFramebuffer( 36160, framebuffer );
+ _gl.framebufferTexture2D( 36160, attachment, textureTarget, properties.get( renderTarget.texture ).__webglTexture, 0 );
+ _gl.bindFramebuffer( 36160, null );
+
+ }
+
+ // Setup storage for internal depth/stencil buffers and bind to correct framebuffer
+ function setupRenderBufferStorage( renderbuffer, renderTarget, isMultisample ) {
+
+ _gl.bindRenderbuffer( 36161, renderbuffer );
+
+ if ( renderTarget.depthBuffer && ! renderTarget.stencilBuffer ) {
+
+ let glInternalFormat = 33189;
+
+ if ( isMultisample ) {
+
+ const depthTexture = renderTarget.depthTexture;
+
+ if ( depthTexture && depthTexture.isDepthTexture ) {
+
+ if ( depthTexture.type === FloatType ) {
+
+ glInternalFormat = 36012;
+
+ } else if ( depthTexture.type === UnsignedIntType ) {
+
+ glInternalFormat = 33190;
+
+ }
+
+ }
+
+ const samples = getRenderTargetSamples( renderTarget );
+
+ _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+ } else {
+
+ _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
+
+ }
+
+ _gl.framebufferRenderbuffer( 36160, 36096, 36161, renderbuffer );
+
+ } else if ( renderTarget.depthBuffer && renderTarget.stencilBuffer ) {
+
+ if ( isMultisample ) {
+
+ const samples = getRenderTargetSamples( renderTarget );
+
+ _gl.renderbufferStorageMultisample( 36161, samples, 35056, renderTarget.width, renderTarget.height );
+
+ } else {
+
+ _gl.renderbufferStorage( 36161, 34041, renderTarget.width, renderTarget.height );
+
+ }
+
+
+ _gl.framebufferRenderbuffer( 36160, 33306, 36161, renderbuffer );
+
+ } else {
+
+ const glFormat = utils.convert( renderTarget.texture.format );
+ const glType = utils.convert( renderTarget.texture.type );
+ const glInternalFormat = getInternalFormat( renderTarget.texture.internalFormat, glFormat, glType );
+
+ if ( isMultisample ) {
+
+ const samples = getRenderTargetSamples( renderTarget );
+
+ _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+ } else {
+
+ _gl.renderbufferStorage( 36161, glInternalFormat, renderTarget.width, renderTarget.height );
+
+ }
+
+ }
+
+ _gl.bindRenderbuffer( 36161, null );
+
+ }
+
+ // Setup resources for a Depth Texture for a FBO (needs an extension)
+ function setupDepthTexture( framebuffer, renderTarget ) {
+
+ const isCube = ( renderTarget && renderTarget.isWebGLCubeRenderTarget );
+ if ( isCube ) throw new Error( 'Depth Texture with cube render targets is not supported' );
+
+ _gl.bindFramebuffer( 36160, framebuffer );
+
+ if ( ! ( renderTarget.depthTexture && renderTarget.depthTexture.isDepthTexture ) ) {
+
+ throw new Error( 'renderTarget.depthTexture must be an instance of THREE.DepthTexture' );
+
+ }
+
+ // upload an empty depth texture with framebuffer size
+ if ( ! properties.get( renderTarget.depthTexture ).__webglTexture ||
+ renderTarget.depthTexture.image.width !== renderTarget.width ||
+ renderTarget.depthTexture.image.height !== renderTarget.height ) {
+
+ renderTarget.depthTexture.image.width = renderTarget.width;
+ renderTarget.depthTexture.image.height = renderTarget.height;
+ renderTarget.depthTexture.needsUpdate = true;
+
+ }
+
+ setTexture2D( renderTarget.depthTexture, 0 );
+
+ const webglDepthTexture = properties.get( renderTarget.depthTexture ).__webglTexture;
+
+ if ( renderTarget.depthTexture.format === DepthFormat ) {
+
+ _gl.framebufferTexture2D( 36160, 36096, 3553, webglDepthTexture, 0 );
+
+ } else if ( renderTarget.depthTexture.format === DepthStencilFormat ) {
+
+ _gl.framebufferTexture2D( 36160, 33306, 3553, webglDepthTexture, 0 );
+
+ } else {
+
+ throw new Error( 'Unknown depthTexture format' );
+
+ }
+
+ }
+
+ // Setup GL resources for a non-texture depth buffer
+ function setupDepthRenderbuffer( renderTarget ) {
+
+ const renderTargetProperties = properties.get( renderTarget );
+
+ const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
+
+ if ( renderTarget.depthTexture ) {
+
+ if ( isCube ) throw new Error( 'target.depthTexture not supported in Cube render targets' );
+
+ setupDepthTexture( renderTargetProperties.__webglFramebuffer, renderTarget );
+
+ } else {
+
+ if ( isCube ) {
+
+ renderTargetProperties.__webglDepthbuffer = [];
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ _gl.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer[ i ] );
+ renderTargetProperties.__webglDepthbuffer[ i ] = _gl.createRenderbuffer();
+ setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer[ i ], renderTarget, false );
+
+ }
+
+ } else {
+
+ _gl.bindFramebuffer( 36160, renderTargetProperties.__webglFramebuffer );
+ renderTargetProperties.__webglDepthbuffer = _gl.createRenderbuffer();
+ setupRenderBufferStorage( renderTargetProperties.__webglDepthbuffer, renderTarget, false );
+
+ }
+
+ }
+
+ _gl.bindFramebuffer( 36160, null );
+
+ }
+
+ // Set up GL resources for the render target
+ function setupRenderTarget( renderTarget ) {
+
+ const renderTargetProperties = properties.get( renderTarget );
+ const textureProperties = properties.get( renderTarget.texture );
+
+ renderTarget.addEventListener( 'dispose', onRenderTargetDispose );
+
+ textureProperties.__webglTexture = _gl.createTexture();
+
+ info.memory.textures ++;
+
+ const isCube = ( renderTarget.isWebGLCubeRenderTarget === true );
+ const isMultisample = ( renderTarget.isWebGLMultisampleRenderTarget === true );
+ const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2;
+
+ // Handles WebGL2 RGBFormat fallback - #18858
+
+ if ( isWebGL2 && renderTarget.texture.format === RGBFormat && ( renderTarget.texture.type === FloatType || renderTarget.texture.type === HalfFloatType ) ) {
+
+ renderTarget.texture.format = RGBAFormat;
+
+ console.warn( 'THREE.WebGLRenderer: Rendering to textures with RGB format is not supported. Using RGBA format instead.' );
+
+ }
+
+ // Setup framebuffer
+
+ if ( isCube ) {
+
+ renderTargetProperties.__webglFramebuffer = [];
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ renderTargetProperties.__webglFramebuffer[ i ] = _gl.createFramebuffer();
+
+ }
+
+ } else {
+
+ renderTargetProperties.__webglFramebuffer = _gl.createFramebuffer();
+
+ if ( isMultisample ) {
+
+ if ( isWebGL2 ) {
+
+ renderTargetProperties.__webglMultisampledFramebuffer = _gl.createFramebuffer();
+ renderTargetProperties.__webglColorRenderbuffer = _gl.createRenderbuffer();
+
+ _gl.bindRenderbuffer( 36161, renderTargetProperties.__webglColorRenderbuffer );
+
+ const glFormat = utils.convert( renderTarget.texture.format );
+ const glType = utils.convert( renderTarget.texture.type );
+ const glInternalFormat = getInternalFormat( renderTarget.texture.internalFormat, glFormat, glType );
+ const samples = getRenderTargetSamples( renderTarget );
+ _gl.renderbufferStorageMultisample( 36161, samples, glInternalFormat, renderTarget.width, renderTarget.height );
+
+ _gl.bindFramebuffer( 36160, renderTargetProperties.__webglMultisampledFramebuffer );
+ _gl.framebufferRenderbuffer( 36160, 36064, 36161, renderTargetProperties.__webglColorRenderbuffer );
+ _gl.bindRenderbuffer( 36161, null );
+
+ if ( renderTarget.depthBuffer ) {
+
+ renderTargetProperties.__webglDepthRenderbuffer = _gl.createRenderbuffer();
+ setupRenderBufferStorage( renderTargetProperties.__webglDepthRenderbuffer, renderTarget, true );
+
+ }
+
+ _gl.bindFramebuffer( 36160, null );
+
+
+ } else {
+
+ console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+
+ }
+
+ }
+
+ }
+
+ // Setup color buffer
+
+ if ( isCube ) {
+
+ state.bindTexture( 34067, textureProperties.__webglTexture );
+ setTextureParameters( 34067, renderTarget.texture, supportsMips );
+
+ for ( let i = 0; i < 6; i ++ ) {
+
+ setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer[ i ], renderTarget, 36064, 34069 + i );
+
+ }
+
+ if ( textureNeedsGenerateMipmaps( renderTarget.texture, supportsMips ) ) {
+
+ generateMipmap( 34067, renderTarget.texture, renderTarget.width, renderTarget.height );
+
+ }
+
+ state.bindTexture( 34067, null );
+
+ } else {
+
+ state.bindTexture( 3553, textureProperties.__webglTexture );
+ setTextureParameters( 3553, renderTarget.texture, supportsMips );
+ setupFrameBufferTexture( renderTargetProperties.__webglFramebuffer, renderTarget, 36064, 3553 );
+
+ if ( textureNeedsGenerateMipmaps( renderTarget.texture, supportsMips ) ) {
+
+ generateMipmap( 3553, renderTarget.texture, renderTarget.width, renderTarget.height );
+
+ }
+
+ state.bindTexture( 3553, null );
+
+ }
+
+ // Setup depth and stencil buffers
+
+ if ( renderTarget.depthBuffer ) {
+
+ setupDepthRenderbuffer( renderTarget );
+
+ }
+
+ }
+
+ function updateRenderTargetMipmap( renderTarget ) {
+
+ const texture = renderTarget.texture;
+ const supportsMips = isPowerOfTwo( renderTarget ) || isWebGL2;
+
+ if ( textureNeedsGenerateMipmaps( texture, supportsMips ) ) {
+
+ const target = renderTarget.isWebGLCubeRenderTarget ? 34067 : 3553;
+ const webglTexture = properties.get( texture ).__webglTexture;
+
+ state.bindTexture( target, webglTexture );
+ generateMipmap( target, texture, renderTarget.width, renderTarget.height );
+ state.bindTexture( target, null );
+
+ }
+
+ }
+
+ function updateMultisampleRenderTarget( renderTarget ) {
+
+ if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+
+ if ( isWebGL2 ) {
+
+ const renderTargetProperties = properties.get( renderTarget );
+
+ _gl.bindFramebuffer( 36008, renderTargetProperties.__webglMultisampledFramebuffer );
+ _gl.bindFramebuffer( 36009, renderTargetProperties.__webglFramebuffer );
+
+ const width = renderTarget.width;
+ const height = renderTarget.height;
+ let mask = 16384;
+
+ if ( renderTarget.depthBuffer ) mask |= 256;
+ if ( renderTarget.stencilBuffer ) mask |= 1024;
+
+ _gl.blitFramebuffer( 0, 0, width, height, 0, 0, width, height, mask, 9728 );
+
+ _gl.bindFramebuffer( 36160, renderTargetProperties.__webglMultisampledFramebuffer ); // see #18905
+
+ } else {
+
+ console.warn( 'THREE.WebGLRenderer: WebGLMultisampleRenderTarget can only be used with WebGL2.' );
+
+ }
+
+ }
+
+ }
+
+ function getRenderTargetSamples( renderTarget ) {
+
+ return ( isWebGL2 && renderTarget.isWebGLMultisampleRenderTarget ) ?
+ Math.min( maxSamples, renderTarget.samples ) : 0;
+
+ }
+
+ function updateVideoTexture( texture ) {
+
+ const frame = info.render.frame;
+
+ // Check the last frame we updated the VideoTexture
+
+ if ( _videoTextures.get( texture ) !== frame ) {
+
+ _videoTextures.set( texture, frame );
+ texture.update();
+
+ }
+
+ }
+
+ // backwards compatibility
+
+ let warnedTexture2D = false;
+ let warnedTextureCube = false;
+
+ function safeSetTexture2D( texture, slot ) {
+
+ if ( texture && texture.isWebGLRenderTarget ) {
+
+ if ( warnedTexture2D === false ) {
+
+ console.warn( 'THREE.WebGLTextures.safeSetTexture2D: don\'t use render targets as textures. Use their .texture property instead.' );
+ warnedTexture2D = true;
+
+ }
+
+ texture = texture.texture;
+
+ }
+
+ setTexture2D( texture, slot );
+
+ }
+
+ function safeSetTextureCube( texture, slot ) {
+
+ if ( texture && texture.isWebGLCubeRenderTarget ) {
+
+ if ( warnedTextureCube === false ) {
+
+ console.warn( 'THREE.WebGLTextures.safeSetTextureCube: don\'t use cube render targets as textures. Use their .texture property instead.' );
+ warnedTextureCube = true;
+
+ }
+
+ texture = texture.texture;
+
+ }
+
+
+ setTextureCube( texture, slot );
+
+ }
+
+ //
+
+ this.allocateTextureUnit = allocateTextureUnit;
+ this.resetTextureUnits = resetTextureUnits;
+
+ this.setTexture2D = setTexture2D;
+ this.setTexture2DArray = setTexture2DArray;
+ this.setTexture3D = setTexture3D;
+ this.setTextureCube = setTextureCube;
+ this.setupRenderTarget = setupRenderTarget;
+ this.updateRenderTargetMipmap = updateRenderTargetMipmap;
+ this.updateMultisampleRenderTarget = updateMultisampleRenderTarget;
+
+ this.safeSetTexture2D = safeSetTexture2D;
+ this.safeSetTextureCube = safeSetTextureCube;
+
+}
+
+function WebGLUtils( gl, extensions, capabilities ) {
+
+ const isWebGL2 = capabilities.isWebGL2;
+
+ function convert( p ) {
+
+ let extension;
+
+ if ( p === UnsignedByteType ) return 5121;
+ if ( p === UnsignedShort4444Type ) return 32819;
+ if ( p === UnsignedShort5551Type ) return 32820;
+ if ( p === UnsignedShort565Type ) return 33635;
+
+ if ( p === ByteType ) return 5120;
+ if ( p === ShortType ) return 5122;
+ if ( p === UnsignedShortType ) return 5123;
+ if ( p === IntType ) return 5124;
+ if ( p === UnsignedIntType ) return 5125;
+ if ( p === FloatType ) return 5126;
+
+ if ( p === HalfFloatType ) {
+
+ if ( isWebGL2 ) return 5131;
+
+ extension = extensions.get( 'OES_texture_half_float' );
+
+ if ( extension !== null ) {
+
+ return extension.HALF_FLOAT_OES;
+
+ } else {
+
+ return null;
+
+ }
+
+ }
+
+ if ( p === AlphaFormat ) return 6406;
+ if ( p === RGBFormat ) return 6407;
+ if ( p === RGBAFormat ) return 6408;
+ if ( p === LuminanceFormat ) return 6409;
+ if ( p === LuminanceAlphaFormat ) return 6410;
+ if ( p === DepthFormat ) return 6402;
+ if ( p === DepthStencilFormat ) return 34041;
+ if ( p === RedFormat ) return 6403;
+
+ // WebGL2 formats.
+
+ if ( p === RedIntegerFormat ) return 36244;
+ if ( p === RGFormat ) return 33319;
+ if ( p === RGIntegerFormat ) return 33320;
+ if ( p === RGBIntegerFormat ) return 36248;
+ if ( p === RGBAIntegerFormat ) return 36249;
+
+ if ( p === RGB_S3TC_DXT1_Format || p === RGBA_S3TC_DXT1_Format ||
+ p === RGBA_S3TC_DXT3_Format || p === RGBA_S3TC_DXT5_Format ) {
+
+ extension = extensions.get( 'WEBGL_compressed_texture_s3tc' );
+
+ if ( extension !== null ) {
+
+ if ( p === RGB_S3TC_DXT1_Format ) return extension.COMPRESSED_RGB_S3TC_DXT1_EXT;
+ if ( p === RGBA_S3TC_DXT1_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
+ if ( p === RGBA_S3TC_DXT3_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
+ if ( p === RGBA_S3TC_DXT5_Format ) return extension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
+
+ } else {
+
+ return null;
+
+ }
+
+ }
+
+ if ( p === RGB_PVRTC_4BPPV1_Format || p === RGB_PVRTC_2BPPV1_Format ||
+ p === RGBA_PVRTC_4BPPV1_Format || p === RGBA_PVRTC_2BPPV1_Format ) {
+
+ extension = extensions.get( 'WEBGL_compressed_texture_pvrtc' );
+
+ if ( extension !== null ) {
+
+ if ( p === RGB_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_4BPPV1_IMG;
+ if ( p === RGB_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGB_PVRTC_2BPPV1_IMG;
+ if ( p === RGBA_PVRTC_4BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_4BPPV1_IMG;
+ if ( p === RGBA_PVRTC_2BPPV1_Format ) return extension.COMPRESSED_RGBA_PVRTC_2BPPV1_IMG;
+
+ } else {
+
+ return null;
+
+ }
+
+ }
+
+ if ( p === RGB_ETC1_Format ) {
+
+ extension = extensions.get( 'WEBGL_compressed_texture_etc1' );
+
+ if ( extension !== null ) {
+
+ return extension.COMPRESSED_RGB_ETC1_WEBGL;
+
+ } else {
+
+ return null;
+
+ }
+
+ }
+
+ if ( p === RGB_ETC2_Format || p === RGBA_ETC2_EAC_Format ) {
+
+ extension = extensions.get( 'WEBGL_compressed_texture_etc' );
+
+ if ( extension !== null ) {
+
+ if ( p === RGB_ETC2_Format ) return extension.COMPRESSED_RGB8_ETC2;
+ if ( p === RGBA_ETC2_EAC_Format ) return extension.COMPRESSED_RGBA8_ETC2_EAC;
+
+ }
+
+ }
+
+ if ( p === RGBA_ASTC_4x4_Format || p === RGBA_ASTC_5x4_Format || p === RGBA_ASTC_5x5_Format ||
+ p === RGBA_ASTC_6x5_Format || p === RGBA_ASTC_6x6_Format || p === RGBA_ASTC_8x5_Format ||
+ p === RGBA_ASTC_8x6_Format || p === RGBA_ASTC_8x8_Format || p === RGBA_ASTC_10x5_Format ||
+ p === RGBA_ASTC_10x6_Format || p === RGBA_ASTC_10x8_Format || p === RGBA_ASTC_10x10_Format ||
+ p === RGBA_ASTC_12x10_Format || p === RGBA_ASTC_12x12_Format ||
+ p === SRGB8_ALPHA8_ASTC_4x4_Format || p === SRGB8_ALPHA8_ASTC_5x4_Format || p === SRGB8_ALPHA8_ASTC_5x5_Format ||
+ p === SRGB8_ALPHA8_ASTC_6x5_Format || p === SRGB8_ALPHA8_ASTC_6x6_Format || p === SRGB8_ALPHA8_ASTC_8x5_Format ||
+ p === SRGB8_ALPHA8_ASTC_8x6_Format || p === SRGB8_ALPHA8_ASTC_8x8_Format || p === SRGB8_ALPHA8_ASTC_10x5_Format ||
+ p === SRGB8_ALPHA8_ASTC_10x6_Format || p === SRGB8_ALPHA8_ASTC_10x8_Format || p === SRGB8_ALPHA8_ASTC_10x10_Format ||
+ p === SRGB8_ALPHA8_ASTC_12x10_Format || p === SRGB8_ALPHA8_ASTC_12x12_Format ) {
+
+ extension = extensions.get( 'WEBGL_compressed_texture_astc' );
+
+ if ( extension !== null ) {
+
+ // TODO Complete?
+
+ return p;
+
+ } else {
+
+ return null;
+
+ }
+
+ }
+
+ if ( p === RGBA_BPTC_Format ) {
+
+ extension = extensions.get( 'EXT_texture_compression_bptc' );
+
+ if ( extension !== null ) {
+
+ // TODO Complete?
+
+ return p;
+
+ } else {
+
+ return null;
+
+ }
+
+ }
+
+ if ( p === UnsignedInt248Type ) {
+
+ if ( isWebGL2 ) return 34042;
+
+ extension = extensions.get( 'WEBGL_depth_texture' );
+
+ if ( extension !== null ) {
+
+ return extension.UNSIGNED_INT_24_8_WEBGL;
+
+ } else {
+
+ return null;
+
+ }
+
+ }
+
+ }
+
+ return { convert: convert };
+
+}
+
+function ArrayCamera( array = [] ) {
+
+ PerspectiveCamera.call( this );
+
+ this.cameras = array;
+
+}
+
+ArrayCamera.prototype = Object.assign( Object.create( PerspectiveCamera.prototype ), {
+
+ constructor: ArrayCamera,
+
+ isArrayCamera: true
+
+} );
+
+function Group() {
+
+ Object3D.call( this );
+
+ this.type = 'Group';
+
+}
+
+Group.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+ constructor: Group,
+
+ isGroup: true
+
+} );
+
+function WebXRController() {
+
+ this._targetRay = null;
+ this._grip = null;
+ this._hand = null;
+
+}
+
+Object.assign( WebXRController.prototype, {
+
+ constructor: WebXRController,
+
+ getHandSpace: function () {
+
+ if ( this._hand === null ) {
+
+ this._hand = new Group();
+ this._hand.matrixAutoUpdate = false;
+ this._hand.visible = false;
+
+ this._hand.joints = {};
+ this._hand.inputState = { pinching: false };
+
+ }
+
+ return this._hand;
+
+ },
+
+ getTargetRaySpace: function () {
+
+ if ( this._targetRay === null ) {
+
+ this._targetRay = new Group();
+ this._targetRay.matrixAutoUpdate = false;
+ this._targetRay.visible = false;
+
+ }
+
+ return this._targetRay;
+
+ },
+
+ getGripSpace: function () {
+
+ if ( this._grip === null ) {
+
+ this._grip = new Group();
+ this._grip.matrixAutoUpdate = false;
+ this._grip.visible = false;
+
+ }
+
+ return this._grip;
+
+ },
+
+ dispatchEvent: function ( event ) {
+
+ if ( this._targetRay !== null ) {
+
+ this._targetRay.dispatchEvent( event );
+
+ }
+
+ if ( this._grip !== null ) {
+
+ this._grip.dispatchEvent( event );
+
+ }
+
+ if ( this._hand !== null ) {
+
+ this._hand.dispatchEvent( event );
+
+ }
+
+ return this;
+
+ },
+
+ disconnect: function ( inputSource ) {
+
+ this.dispatchEvent( { type: 'disconnected', data: inputSource } );
+
+ if ( this._targetRay !== null ) {
+
+ this._targetRay.visible = false;
+
+ }
+
+ if ( this._grip !== null ) {
+
+ this._grip.visible = false;
+
+ }
+
+ if ( this._hand !== null ) {
+
+ this._hand.visible = false;
+
+ }
+
+ return this;
+
+ },
+
+ update: function ( inputSource, frame, referenceSpace ) {
+
+ let inputPose = null;
+ let gripPose = null;
+ let handPose = null;
+
+ const targetRay = this._targetRay;
+ const grip = this._grip;
+ const hand = this._hand;
+
+ if ( inputSource && frame.session.visibilityState !== 'visible-blurred' ) {
+
+ if ( hand && inputSource.hand ) {
+
+ handPose = true;
+
+ for ( const inputjoint of inputSource.hand.values() ) {
+
+ // Update the joints groups with the XRJoint poses
+ const jointPose = frame.getJointPose( inputjoint, referenceSpace );
+
+ if ( hand.joints[ inputjoint.jointName ] === undefined ) {
+
+ // The transform of this joint will be updated with the joint pose on each frame
+ const joint = new Group();
+ joint.matrixAutoUpdate = false;
+ joint.visible = false;
+ hand.joints[ inputjoint.jointName ] = joint;
+ // ??
+ hand.add( joint );
+
+ }
+
+ const joint = hand.joints[ inputjoint.jointName ];
+
+ if ( jointPose !== null ) {
+
+ joint.matrix.fromArray( jointPose.transform.matrix );
+ joint.matrix.decompose( joint.position, joint.rotation, joint.scale );
+ joint.jointRadius = jointPose.radius;
+
+ }
+
+ joint.visible = jointPose !== null;
+
+ }
+
+ // Custom events
+
+ // Check pinchz
+ const indexTip = hand.joints[ 'index-finger-tip' ];
+ const thumbTip = hand.joints[ 'thumb-tip' ];
+ const distance = indexTip.position.distanceTo( thumbTip.position );
+
+ const distanceToPinch = 0.02;
+ const threshold = 0.005;
+
+ if ( hand.inputState.pinching && distance > distanceToPinch + threshold ) {
+
+ hand.inputState.pinching = false;
+ this.dispatchEvent( {
+ type: 'pinchend',
+ handedness: inputSource.handedness,
+ target: this
+ } );
+
+ } else if ( ! hand.inputState.pinching && distance <= distanceToPinch - threshold ) {
+
+ hand.inputState.pinching = true;
+ this.dispatchEvent( {
+ type: 'pinchstart',
+ handedness: inputSource.handedness,
+ target: this
+ } );
+
+ }
+
+ } else {
+
+ if ( targetRay !== null ) {
+
+ inputPose = frame.getPose( inputSource.targetRaySpace, referenceSpace );
+
+ if ( inputPose !== null ) {
+
+ targetRay.matrix.fromArray( inputPose.transform.matrix );
+ targetRay.matrix.decompose( targetRay.position, targetRay.rotation, targetRay.scale );
+
+ }
+
+ }
+
+ if ( grip !== null && inputSource.gripSpace ) {
+
+ gripPose = frame.getPose( inputSource.gripSpace, referenceSpace );
+
+ if ( gripPose !== null ) {
+
+ grip.matrix.fromArray( gripPose.transform.matrix );
+ grip.matrix.decompose( grip.position, grip.rotation, grip.scale );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ if ( targetRay !== null ) {
+
+ targetRay.visible = ( inputPose !== null );
+
+ }
+
+ if ( grip !== null ) {
+
+ grip.visible = ( gripPose !== null );
+
+ }
+
+ if ( hand !== null ) {
+
+ hand.visible = ( handPose !== null );
+
+ }
+
+ return this;
+
+ }
+
+} );
+
+function WebXRManager( renderer, gl ) {
+
+ const scope = this;
+
+ let session = null;
+
+ let framebufferScaleFactor = 1.0;
+
+ let referenceSpace = null;
+ let referenceSpaceType = 'local-floor';
+
+ let pose = null;
+
+ const controllers = [];
+ const inputSourcesMap = new Map();
+
+ //
+
+ const cameraL = new PerspectiveCamera();
+ cameraL.layers.enable( 1 );
+ cameraL.viewport = new Vector4();
+
+ const cameraR = new PerspectiveCamera();
+ cameraR.layers.enable( 2 );
+ cameraR.viewport = new Vector4();
+
+ const cameras = [ cameraL, cameraR ];
+
+ const cameraVR = new ArrayCamera();
+ cameraVR.layers.enable( 1 );
+ cameraVR.layers.enable( 2 );
+
+ let _currentDepthNear = null;
+ let _currentDepthFar = null;
+
+ //
+
+ this.enabled = false;
+
+ this.isPresenting = false;
+
+ this.getController = function ( index ) {
+
+ let controller = controllers[ index ];
+
+ if ( controller === undefined ) {
+
+ controller = new WebXRController();
+ controllers[ index ] = controller;
+
+ }
+
+ return controller.getTargetRaySpace();
+
+ };
+
+ this.getControllerGrip = function ( index ) {
+
+ let controller = controllers[ index ];
+
+ if ( controller === undefined ) {
+
+ controller = new WebXRController();
+ controllers[ index ] = controller;
+
+ }
+
+ return controller.getGripSpace();
+
+ };
+
+ this.getHand = function ( index ) {
+
+ let controller = controllers[ index ];
+
+ if ( controller === undefined ) {
+
+ controller = new WebXRController();
+ controllers[ index ] = controller;
+
+ }
+
+ return controller.getHandSpace();
+
+ };
+
+ //
+
+ function onSessionEvent( event ) {
+
+ const controller = inputSourcesMap.get( event.inputSource );
+
+ if ( controller ) {
+
+ controller.dispatchEvent( { type: event.type, data: event.inputSource } );
+
+ }
+
+ }
+
+ function onSessionEnd() {
+
+ inputSourcesMap.forEach( function ( controller, inputSource ) {
+
+ controller.disconnect( inputSource );
+
+ } );
+
+ inputSourcesMap.clear();
+
+ _currentDepthNear = null;
+ _currentDepthFar = null;
+
+ //
+
+ renderer.setFramebuffer( null );
+ renderer.setRenderTarget( renderer.getRenderTarget() ); // Hack #15830
+ animation.stop();
+
+ scope.isPresenting = false;
+
+ scope.dispatchEvent( { type: 'sessionend' } );
+
+ }
+
+ this.setFramebufferScaleFactor = function ( value ) {
+
+ framebufferScaleFactor = value;
+
+ if ( scope.isPresenting === true ) {
+
+ console.warn( 'THREE.WebXRManager: Cannot change framebuffer scale while presenting.' );
+
+ }
+
+ };
+
+ this.setReferenceSpaceType = function ( value ) {
+
+ referenceSpaceType = value;
+
+ if ( scope.isPresenting === true ) {
+
+ console.warn( 'THREE.WebXRManager: Cannot change reference space type while presenting.' );
+
+ }
+
+ };
+
+ this.getReferenceSpace = function () {
+
+ return referenceSpace;
+
+ };
+
+ this.getSession = function () {
+
+ return session;
+
+ };
+
+ this.setSession = async function ( value ) {
+
+ session = value;
+
+ if ( session !== null ) {
+
+ session.addEventListener( 'select', onSessionEvent );
+ session.addEventListener( 'selectstart', onSessionEvent );
+ session.addEventListener( 'selectend', onSessionEvent );
+ session.addEventListener( 'squeeze', onSessionEvent );
+ session.addEventListener( 'squeezestart', onSessionEvent );
+ session.addEventListener( 'squeezeend', onSessionEvent );
+ session.addEventListener( 'end', onSessionEnd );
+ session.addEventListener( 'inputsourceschange', onInputSourcesChange );
+
+ const attributes = gl.getContextAttributes();
+
+ if ( attributes.xrCompatible !== true ) {
+
+ await gl.makeXRCompatible();
+
+ }
+
+ const layerInit = {
+ antialias: attributes.antialias,
+ alpha: attributes.alpha,
+ depth: attributes.depth,
+ stencil: attributes.stencil,
+ framebufferScaleFactor: framebufferScaleFactor
+ };
+
+ // eslint-disable-next-line no-undef
+ const baseLayer = new XRWebGLLayer( session, gl, layerInit );
+
+ session.updateRenderState( { baseLayer: baseLayer } );
+
+ referenceSpace = await session.requestReferenceSpace( referenceSpaceType );
+
+ animation.setContext( session );
+ animation.start();
+
+ scope.isPresenting = true;
+
+ scope.dispatchEvent( { type: 'sessionstart' } );
+
+ }
+
+ };
+
+ function onInputSourcesChange( event ) {
+
+ const inputSources = session.inputSources;
+
+ // Assign inputSources to available controllers
+
+ for ( let i = 0; i < controllers.length; i ++ ) {
+
+ inputSourcesMap.set( inputSources[ i ], controllers[ i ] );
+
+ }
+
+ // Notify disconnected
+
+ for ( let i = 0; i < event.removed.length; i ++ ) {
+
+ const inputSource = event.removed[ i ];
+ const controller = inputSourcesMap.get( inputSource );
+
+ if ( controller ) {
+
+ controller.dispatchEvent( { type: 'disconnected', data: inputSource } );
+ inputSourcesMap.delete( inputSource );
+
+ }
+
+ }
+
+ // Notify connected
+
+ for ( let i = 0; i < event.added.length; i ++ ) {
+
+ const inputSource = event.added[ i ];
+ const controller = inputSourcesMap.get( inputSource );
+
+ if ( controller ) {
+
+ controller.dispatchEvent( { type: 'connected', data: inputSource } );
+
+ }
+
+ }
+
+ }
+
+ //
+
+ const cameraLPos = new Vector3();
+ const cameraRPos = new Vector3();
+
+ /**
+ * Assumes 2 cameras that are parallel and share an X-axis, and that
+ * the cameras' projection and world matrices have already been set.
+ * And that near and far planes are identical for both cameras.
+ * Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
+ */
+ function setProjectionFromUnion( camera, cameraL, cameraR ) {
+
+ cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
+ cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );
+
+ const ipd = cameraLPos.distanceTo( cameraRPos );
+
+ const projL = cameraL.projectionMatrix.elements;
+ const projR = cameraR.projectionMatrix.elements;
+
+ // VR systems will have identical far and near planes, and
+ // most likely identical top and bottom frustum extents.
+ // Use the left camera for these values.
+ const near = projL[ 14 ] / ( projL[ 10 ] - 1 );
+ const far = projL[ 14 ] / ( projL[ 10 ] + 1 );
+ const topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ];
+ const bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ];
+
+ const leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
+ const rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
+ const left = near * leftFov;
+ const right = near * rightFov;
+
+ // Calculate the new camera's position offset from the
+ // left camera. xOffset should be roughly half `ipd`.
+ const zOffset = ipd / ( - leftFov + rightFov );
+ const xOffset = zOffset * - leftFov;
+
+ // TODO: Better way to apply this offset?
+ cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale );
+ camera.translateX( xOffset );
+ camera.translateZ( zOffset );
+ camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale );
+ camera.matrixWorldInverse.copy( camera.matrixWorld ).invert();
+
+ // Find the union of the frustum values of the cameras and scale
+ // the values so that the near plane's position does not change in world space,
+ // although must now be relative to the new union camera.
+ const near2 = near + zOffset;
+ const far2 = far + zOffset;
+ const left2 = left - xOffset;
+ const right2 = right + ( ipd - xOffset );
+ const top2 = topFov * far / far2 * near2;
+ const bottom2 = bottomFov * far / far2 * near2;
+
+ camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );
+
+ }
+
+ function updateCamera( camera, parent ) {
+
+ if ( parent === null ) {
+
+ camera.matrixWorld.copy( camera.matrix );
+
+ } else {
+
+ camera.matrixWorld.multiplyMatrices( parent.matrixWorld, camera.matrix );
+
+ }
+
+ camera.matrixWorldInverse.copy( camera.matrixWorld ).invert();
+
+ }
+
+ this.getCamera = function ( camera ) {
+
+ cameraVR.near = cameraR.near = cameraL.near = camera.near;
+ cameraVR.far = cameraR.far = cameraL.far = camera.far;
+
+ if ( _currentDepthNear !== cameraVR.near || _currentDepthFar !== cameraVR.far ) {
+
+ // Note that the new renderState won't apply until the next frame. See #18320
+
+ session.updateRenderState( {
+ depthNear: cameraVR.near,
+ depthFar: cameraVR.far
+ } );
+
+ _currentDepthNear = cameraVR.near;
+ _currentDepthFar = cameraVR.far;
+
+ }
+
+ const parent = camera.parent;
+ const cameras = cameraVR.cameras;
+
+ updateCamera( cameraVR, parent );
+
+ for ( let i = 0; i < cameras.length; i ++ ) {
+
+ updateCamera( cameras[ i ], parent );
+
+ }
+
+ // update camera and its children
+
+ camera.matrixWorld.copy( cameraVR.matrixWorld );
+ camera.matrix.copy( cameraVR.matrix );
+ camera.matrix.decompose( camera.position, camera.quaternion, camera.scale );
+
+ const children = camera.children;
+
+ for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+ children[ i ].updateMatrixWorld( true );
+
+ }
+
+ // update projection matrix for proper view frustum culling
+
+ if ( cameras.length === 2 ) {
+
+ setProjectionFromUnion( cameraVR, cameraL, cameraR );
+
+ } else {
+
+ // assume single camera setup (AR)
+
+ cameraVR.projectionMatrix.copy( cameraL.projectionMatrix );
+
+ }
+
+ return cameraVR;
+
+ };
+
+ // Animation Loop
+
+ let onAnimationFrameCallback = null;
+
+ function onAnimationFrame( time, frame ) {
+
+ pose = frame.getViewerPose( referenceSpace );
+
+ if ( pose !== null ) {
+
+ const views = pose.views;
+ const baseLayer = session.renderState.baseLayer;
+
+ renderer.setFramebuffer( baseLayer.framebuffer );
+
+ let cameraVRNeedsUpdate = false;
+
+ // check if it's necessary to rebuild cameraVR's camera list
+
+ if ( views.length !== cameraVR.cameras.length ) {
+
+ cameraVR.cameras.length = 0;
+ cameraVRNeedsUpdate = true;
+
+ }
+
+ for ( let i = 0; i < views.length; i ++ ) {
+
+ const view = views[ i ];
+ const viewport = baseLayer.getViewport( view );
+
+ const camera = cameras[ i ];
+ camera.matrix.fromArray( view.transform.matrix );
+ camera.projectionMatrix.fromArray( view.projectionMatrix );
+ camera.viewport.set( viewport.x, viewport.y, viewport.width, viewport.height );
+
+ if ( i === 0 ) {
+
+ cameraVR.matrix.copy( camera.matrix );
+
+ }
+
+ if ( cameraVRNeedsUpdate === true ) {
+
+ cameraVR.cameras.push( camera );
+
+ }
+
+ }
+
+ }
+
+ //
+
+ const inputSources = session.inputSources;
+
+ for ( let i = 0; i < controllers.length; i ++ ) {
+
+ const controller = controllers[ i ];
+ const inputSource = inputSources[ i ];
+
+ controller.update( inputSource, frame, referenceSpace );
+
+ }
+
+ if ( onAnimationFrameCallback ) onAnimationFrameCallback( time, frame );
+
+ }
+
+ const animation = new WebGLAnimation();
+ animation.setAnimationLoop( onAnimationFrame );
+
+ this.setAnimationLoop = function ( callback ) {
+
+ onAnimationFrameCallback = callback;
+
+ };
+
+ this.dispose = function () {};
+
+}
+
+Object.assign( WebXRManager.prototype, EventDispatcher.prototype );
+
+function WebGLMaterials( properties ) {
+
+ function refreshFogUniforms( uniforms, fog ) {
+
+ uniforms.fogColor.value.copy( fog.color );
+
+ if ( fog.isFog ) {
+
+ uniforms.fogNear.value = fog.near;
+ uniforms.fogFar.value = fog.far;
+
+ } else if ( fog.isFogExp2 ) {
+
+ uniforms.fogDensity.value = fog.density;
+
+ }
+
+ }
+
+ function refreshMaterialUniforms( uniforms, material, pixelRatio, height ) {
+
+ if ( material.isMeshBasicMaterial ) {
+
+ refreshUniformsCommon( uniforms, material );
+
+ } else if ( material.isMeshLambertMaterial ) {
+
+ refreshUniformsCommon( uniforms, material );
+ refreshUniformsLambert( uniforms, material );
+
+ } else if ( material.isMeshToonMaterial ) {
+
+ refreshUniformsCommon( uniforms, material );
+ refreshUniformsToon( uniforms, material );
+
+ } else if ( material.isMeshPhongMaterial ) {
+
+ refreshUniformsCommon( uniforms, material );
+ refreshUniformsPhong( uniforms, material );
+
+ } else if ( material.isMeshStandardMaterial ) {
+
+ refreshUniformsCommon( uniforms, material );
+
+ if ( material.isMeshPhysicalMaterial ) {
+
+ refreshUniformsPhysical( uniforms, material );
+
+ } else {
+
+ refreshUniformsStandard( uniforms, material );
+
+ }
+
+ } else if ( material.isMeshMatcapMaterial ) {
+
+ refreshUniformsCommon( uniforms, material );
+ refreshUniformsMatcap( uniforms, material );
+
+ } else if ( material.isMeshDepthMaterial ) {
+
+ refreshUniformsCommon( uniforms, material );
+ refreshUniformsDepth( uniforms, material );
+
+ } else if ( material.isMeshDistanceMaterial ) {
+
+ refreshUniformsCommon( uniforms, material );
+ refreshUniformsDistance( uniforms, material );
+
+ } else if ( material.isMeshNormalMaterial ) {
+
+ refreshUniformsCommon( uniforms, material );
+ refreshUniformsNormal( uniforms, material );
+
+ } else if ( material.isLineBasicMaterial ) {
+
+ refreshUniformsLine( uniforms, material );
+
+ if ( material.isLineDashedMaterial ) {
+
+ refreshUniformsDash( uniforms, material );
+
+ }
+
+ } else if ( material.isPointsMaterial ) {
+
+ refreshUniformsPoints( uniforms, material, pixelRatio, height );
+
+ } else if ( material.isSpriteMaterial ) {
+
+ refreshUniformsSprites( uniforms, material );
+
+ } else if ( material.isShadowMaterial ) {
+
+ uniforms.color.value.copy( material.color );
+ uniforms.opacity.value = material.opacity;
+
+ } else if ( material.isShaderMaterial ) {
+
+ material.uniformsNeedUpdate = false; // #15581
+
+ }
+
+ }
+
+ function refreshUniformsCommon( uniforms, material ) {
+
+ uniforms.opacity.value = material.opacity;
+
+ if ( material.color ) {
+
+ uniforms.diffuse.value.copy( material.color );
+
+ }
+
+ if ( material.emissive ) {
+
+ uniforms.emissive.value.copy( material.emissive ).multiplyScalar( material.emissiveIntensity );
+
+ }
+
+ if ( material.map ) {
+
+ uniforms.map.value = material.map;
+
+ }
+
+ if ( material.alphaMap ) {
+
+ uniforms.alphaMap.value = material.alphaMap;
+
+ }
+
+ if ( material.specularMap ) {
+
+ uniforms.specularMap.value = material.specularMap;
+
+ }
+
+ const envMap = properties.get( material ).envMap;
+
+ if ( envMap ) {
+
+ uniforms.envMap.value = envMap;
+
+ uniforms.flipEnvMap.value = ( envMap.isCubeTexture && envMap._needsFlipEnvMap ) ? - 1 : 1;
+
+ uniforms.reflectivity.value = material.reflectivity;
+ uniforms.refractionRatio.value = material.refractionRatio;
+
+ const maxMipLevel = properties.get( envMap ).__maxMipLevel;
+
+ if ( maxMipLevel !== undefined ) {
+
+ uniforms.maxMipLevel.value = maxMipLevel;
+
+ }
+
+ }
+
+ if ( material.lightMap ) {
+
+ uniforms.lightMap.value = material.lightMap;
+ uniforms.lightMapIntensity.value = material.lightMapIntensity;
+
+ }
+
+ if ( material.aoMap ) {
+
+ uniforms.aoMap.value = material.aoMap;
+ uniforms.aoMapIntensity.value = material.aoMapIntensity;
+
+ }
+
+ // uv repeat and offset setting priorities
+ // 1. color map
+ // 2. specular map
+ // 3. displacementMap map
+ // 4. normal map
+ // 5. bump map
+ // 6. roughnessMap map
+ // 7. metalnessMap map
+ // 8. alphaMap map
+ // 9. emissiveMap map
+ // 10. clearcoat map
+ // 11. clearcoat normal map
+ // 12. clearcoat roughnessMap map
+
+ let uvScaleMap;
+
+ if ( material.map ) {
+
+ uvScaleMap = material.map;
+
+ } else if ( material.specularMap ) {
+
+ uvScaleMap = material.specularMap;
+
+ } else if ( material.displacementMap ) {
+
+ uvScaleMap = material.displacementMap;
+
+ } else if ( material.normalMap ) {
+
+ uvScaleMap = material.normalMap;
+
+ } else if ( material.bumpMap ) {
+
+ uvScaleMap = material.bumpMap;
+
+ } else if ( material.roughnessMap ) {
+
+ uvScaleMap = material.roughnessMap;
+
+ } else if ( material.metalnessMap ) {
+
+ uvScaleMap = material.metalnessMap;
+
+ } else if ( material.alphaMap ) {
+
+ uvScaleMap = material.alphaMap;
+
+ } else if ( material.emissiveMap ) {
+
+ uvScaleMap = material.emissiveMap;
+
+ } else if ( material.clearcoatMap ) {
+
+ uvScaleMap = material.clearcoatMap;
+
+ } else if ( material.clearcoatNormalMap ) {
+
+ uvScaleMap = material.clearcoatNormalMap;
+
+ } else if ( material.clearcoatRoughnessMap ) {
+
+ uvScaleMap = material.clearcoatRoughnessMap;
+
+ }
+
+ if ( uvScaleMap !== undefined ) {
+
+ // backwards compatibility
+ if ( uvScaleMap.isWebGLRenderTarget ) {
+
+ uvScaleMap = uvScaleMap.texture;
+
+ }
+
+ if ( uvScaleMap.matrixAutoUpdate === true ) {
+
+ uvScaleMap.updateMatrix();
+
+ }
+
+ uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+
+ }
+
+ // uv repeat and offset setting priorities for uv2
+ // 1. ao map
+ // 2. light map
+
+ let uv2ScaleMap;
+
+ if ( material.aoMap ) {
+
+ uv2ScaleMap = material.aoMap;
+
+ } else if ( material.lightMap ) {
+
+ uv2ScaleMap = material.lightMap;
+
+ }
+
+ if ( uv2ScaleMap !== undefined ) {
+
+ // backwards compatibility
+ if ( uv2ScaleMap.isWebGLRenderTarget ) {
+
+ uv2ScaleMap = uv2ScaleMap.texture;
+
+ }
+
+ if ( uv2ScaleMap.matrixAutoUpdate === true ) {
+
+ uv2ScaleMap.updateMatrix();
+
+ }
+
+ uniforms.uv2Transform.value.copy( uv2ScaleMap.matrix );
+
+ }
+
+ }
+
+ function refreshUniformsLine( uniforms, material ) {
+
+ uniforms.diffuse.value.copy( material.color );
+ uniforms.opacity.value = material.opacity;
+
+ }
+
+ function refreshUniformsDash( uniforms, material ) {
+
+ uniforms.dashSize.value = material.dashSize;
+ uniforms.totalSize.value = material.dashSize + material.gapSize;
+ uniforms.scale.value = material.scale;
+
+ }
+
+ function refreshUniformsPoints( uniforms, material, pixelRatio, height ) {
+
+ uniforms.diffuse.value.copy( material.color );
+ uniforms.opacity.value = material.opacity;
+ uniforms.size.value = material.size * pixelRatio;
+ uniforms.scale.value = height * 0.5;
+
+ if ( material.map ) {
+
+ uniforms.map.value = material.map;
+
+ }
+
+ if ( material.alphaMap ) {
+
+ uniforms.alphaMap.value = material.alphaMap;
+
+ }
+
+ // uv repeat and offset setting priorities
+ // 1. color map
+ // 2. alpha map
+
+ let uvScaleMap;
+
+ if ( material.map ) {
+
+ uvScaleMap = material.map;
+
+ } else if ( material.alphaMap ) {
+
+ uvScaleMap = material.alphaMap;
+
+ }
+
+ if ( uvScaleMap !== undefined ) {
+
+ if ( uvScaleMap.matrixAutoUpdate === true ) {
+
+ uvScaleMap.updateMatrix();
+
+ }
+
+ uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+
+ }
+
+ }
+
+ function refreshUniformsSprites( uniforms, material ) {
+
+ uniforms.diffuse.value.copy( material.color );
+ uniforms.opacity.value = material.opacity;
+ uniforms.rotation.value = material.rotation;
+
+ if ( material.map ) {
+
+ uniforms.map.value = material.map;
+
+ }
+
+ if ( material.alphaMap ) {
+
+ uniforms.alphaMap.value = material.alphaMap;
+
+ }
+
+ // uv repeat and offset setting priorities
+ // 1. color map
+ // 2. alpha map
+
+ let uvScaleMap;
+
+ if ( material.map ) {
+
+ uvScaleMap = material.map;
+
+ } else if ( material.alphaMap ) {
+
+ uvScaleMap = material.alphaMap;
+
+ }
+
+ if ( uvScaleMap !== undefined ) {
+
+ if ( uvScaleMap.matrixAutoUpdate === true ) {
+
+ uvScaleMap.updateMatrix();
+
+ }
+
+ uniforms.uvTransform.value.copy( uvScaleMap.matrix );
+
+ }
+
+ }
+
+ function refreshUniformsLambert( uniforms, material ) {
+
+ if ( material.emissiveMap ) {
+
+ uniforms.emissiveMap.value = material.emissiveMap;
+
+ }
+
+ }
+
+ function refreshUniformsPhong( uniforms, material ) {
+
+ uniforms.specular.value.copy( material.specular );
+ uniforms.shininess.value = Math.max( material.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )
+
+ if ( material.emissiveMap ) {
+
+ uniforms.emissiveMap.value = material.emissiveMap;
+
+ }
+
+ if ( material.bumpMap ) {
+
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.bumpScale.value = material.bumpScale;
+ if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+
+ }
+
+ if ( material.normalMap ) {
+
+ uniforms.normalMap.value = material.normalMap;
+ uniforms.normalScale.value.copy( material.normalScale );
+ if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+
+ }
+
+ if ( material.displacementMap ) {
+
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+
+ }
+
+ }
+
+ function refreshUniformsToon( uniforms, material ) {
+
+ if ( material.gradientMap ) {
+
+ uniforms.gradientMap.value = material.gradientMap;
+
+ }
+
+ if ( material.emissiveMap ) {
+
+ uniforms.emissiveMap.value = material.emissiveMap;
+
+ }
+
+ if ( material.bumpMap ) {
+
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.bumpScale.value = material.bumpScale;
+ if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+
+ }
+
+ if ( material.normalMap ) {
+
+ uniforms.normalMap.value = material.normalMap;
+ uniforms.normalScale.value.copy( material.normalScale );
+ if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+
+ }
+
+ if ( material.displacementMap ) {
+
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+
+ }
+
+ }
+
+ function refreshUniformsStandard( uniforms, material ) {
+
+ uniforms.roughness.value = material.roughness;
+ uniforms.metalness.value = material.metalness;
+
+ if ( material.roughnessMap ) {
+
+ uniforms.roughnessMap.value = material.roughnessMap;
+
+ }
+
+ if ( material.metalnessMap ) {
+
+ uniforms.metalnessMap.value = material.metalnessMap;
+
+ }
+
+ if ( material.emissiveMap ) {
+
+ uniforms.emissiveMap.value = material.emissiveMap;
+
+ }
+
+ if ( material.bumpMap ) {
+
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.bumpScale.value = material.bumpScale;
+ if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+
+ }
+
+ if ( material.normalMap ) {
+
+ uniforms.normalMap.value = material.normalMap;
+ uniforms.normalScale.value.copy( material.normalScale );
+ if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+
+ }
+
+ if ( material.displacementMap ) {
+
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+
+ }
+
+ const envMap = properties.get( material ).envMap;
+
+ if ( envMap ) {
+
+ //uniforms.envMap.value = material.envMap; // part of uniforms common
+ uniforms.envMapIntensity.value = material.envMapIntensity;
+
+ }
+
+ }
+
+ function refreshUniformsPhysical( uniforms, material ) {
+
+ refreshUniformsStandard( uniforms, material );
+
+ uniforms.reflectivity.value = material.reflectivity; // also part of uniforms common
+
+ uniforms.clearcoat.value = material.clearcoat;
+ uniforms.clearcoatRoughness.value = material.clearcoatRoughness;
+ if ( material.sheen ) uniforms.sheen.value.copy( material.sheen );
+
+ if ( material.clearcoatMap ) {
+
+ uniforms.clearcoatMap.value = material.clearcoatMap;
+
+ }
+
+ if ( material.clearcoatRoughnessMap ) {
+
+ uniforms.clearcoatRoughnessMap.value = material.clearcoatRoughnessMap;
+
+ }
+
+ if ( material.clearcoatNormalMap ) {
+
+ uniforms.clearcoatNormalScale.value.copy( material.clearcoatNormalScale );
+ uniforms.clearcoatNormalMap.value = material.clearcoatNormalMap;
+
+ if ( material.side === BackSide ) {
+
+ uniforms.clearcoatNormalScale.value.negate();
+
+ }
+
+ }
+
+ uniforms.transmission.value = material.transmission;
+
+ if ( material.transmissionMap ) {
+
+ uniforms.transmissionMap.value = material.transmissionMap;
+
+ }
+
+ }
+
+ function refreshUniformsMatcap( uniforms, material ) {
+
+ if ( material.matcap ) {
+
+ uniforms.matcap.value = material.matcap;
+
+ }
+
+ if ( material.bumpMap ) {
+
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.bumpScale.value = material.bumpScale;
+ if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+
+ }
+
+ if ( material.normalMap ) {
+
+ uniforms.normalMap.value = material.normalMap;
+ uniforms.normalScale.value.copy( material.normalScale );
+ if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+
+ }
+
+ if ( material.displacementMap ) {
+
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+
+ }
+
+ }
+
+ function refreshUniformsDepth( uniforms, material ) {
+
+ if ( material.displacementMap ) {
+
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+
+ }
+
+ }
+
+ function refreshUniformsDistance( uniforms, material ) {
+
+ if ( material.displacementMap ) {
+
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+
+ }
+
+ uniforms.referencePosition.value.copy( material.referencePosition );
+ uniforms.nearDistance.value = material.nearDistance;
+ uniforms.farDistance.value = material.farDistance;
+
+ }
+
+ function refreshUniformsNormal( uniforms, material ) {
+
+ if ( material.bumpMap ) {
+
+ uniforms.bumpMap.value = material.bumpMap;
+ uniforms.bumpScale.value = material.bumpScale;
+ if ( material.side === BackSide ) uniforms.bumpScale.value *= - 1;
+
+ }
+
+ if ( material.normalMap ) {
+
+ uniforms.normalMap.value = material.normalMap;
+ uniforms.normalScale.value.copy( material.normalScale );
+ if ( material.side === BackSide ) uniforms.normalScale.value.negate();
+
+ }
+
+ if ( material.displacementMap ) {
+
+ uniforms.displacementMap.value = material.displacementMap;
+ uniforms.displacementScale.value = material.displacementScale;
+ uniforms.displacementBias.value = material.displacementBias;
+
+ }
+
+ }
+
+ return {
+ refreshFogUniforms: refreshFogUniforms,
+ refreshMaterialUniforms: refreshMaterialUniforms
+ };
+
+}
+
+function createCanvasElement() {
+
+ const canvas = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'canvas' );
+ canvas.style.display = 'block';
+ return canvas;
+
+}
+
+function WebGLRenderer( parameters ) {
+
+ parameters = parameters || {};
+
+ const _canvas = parameters.canvas !== undefined ? parameters.canvas : createCanvasElement(),
+ _context = parameters.context !== undefined ? parameters.context : null,
+
+ _alpha = parameters.alpha !== undefined ? parameters.alpha : false,
+ _depth = parameters.depth !== undefined ? parameters.depth : true,
+ _stencil = parameters.stencil !== undefined ? parameters.stencil : true,
+ _antialias = parameters.antialias !== undefined ? parameters.antialias : false,
+ _premultipliedAlpha = parameters.premultipliedAlpha !== undefined ? parameters.premultipliedAlpha : true,
+ _preserveDrawingBuffer = parameters.preserveDrawingBuffer !== undefined ? parameters.preserveDrawingBuffer : false,
+ _powerPreference = parameters.powerPreference !== undefined ? parameters.powerPreference : 'default',
+ _failIfMajorPerformanceCaveat = parameters.failIfMajorPerformanceCaveat !== undefined ? parameters.failIfMajorPerformanceCaveat : false;
+
+ let currentRenderList = null;
+ let currentRenderState = null;
+
+ // render() can be called from within a callback triggered by another render.
+ // We track this so that the nested render call gets its state isolated from the parent render call.
+
+ const renderStateStack = [];
+
+ // public properties
+
+ this.domElement = _canvas;
+
+ // Debug configuration container
+ this.debug = {
+
+ /**
+ * Enables error checking and reporting when shader programs are being compiled
+ * @type {boolean}
+ */
+ checkShaderErrors: true
+ };
+
+ // clearing
+
+ this.autoClear = true;
+ this.autoClearColor = true;
+ this.autoClearDepth = true;
+ this.autoClearStencil = true;
+
+ // scene graph
+
+ this.sortObjects = true;
+
+ // user-defined clipping
+
+ this.clippingPlanes = [];
+ this.localClippingEnabled = false;
+
+ // physically based shading
+
+ this.gammaFactor = 2.0; // for backwards compatibility
+ this.outputEncoding = LinearEncoding;
+
+ // physical lights
+
+ this.physicallyCorrectLights = false;
+
+ // tone mapping
+
+ this.toneMapping = NoToneMapping;
+ this.toneMappingExposure = 1.0;
+
+ // morphs
+
+ this.maxMorphTargets = 8;
+ this.maxMorphNormals = 4;
+
+ // internal properties
+
+ const _this = this;
+
+ let _isContextLost = false;
+
+ // internal state cache
+
+ let _framebuffer = null;
+
+ let _currentActiveCubeFace = 0;
+ let _currentActiveMipmapLevel = 0;
+ let _currentRenderTarget = null;
+ let _currentFramebuffer = null;
+ let _currentMaterialId = - 1;
+
+ let _currentCamera = null;
+
+ const _currentViewport = new Vector4();
+ const _currentScissor = new Vector4();
+ let _currentScissorTest = null;
+
+ //
+
+ let _width = _canvas.width;
+ let _height = _canvas.height;
+
+ let _pixelRatio = 1;
+ let _opaqueSort = null;
+ let _transparentSort = null;
+
+ const _viewport = new Vector4( 0, 0, _width, _height );
+ const _scissor = new Vector4( 0, 0, _width, _height );
+ let _scissorTest = false;
+
+ // frustum
+
+ const _frustum = new Frustum();
+
+ // clipping
+
+ let _clippingEnabled = false;
+ let _localClippingEnabled = false;
+
+ // camera matrices cache
+
+ const _projScreenMatrix = new Matrix4();
+
+ const _vector3 = new Vector3();
+
+ const _emptyScene = { background: null, fog: null, environment: null, overrideMaterial: null, isScene: true };
+
+ function getTargetPixelRatio() {
+
+ return _currentRenderTarget === null ? _pixelRatio : 1;
+
+ }
+
+ // initialize
+
+ let _gl = _context;
+
+ function getContext( contextNames, contextAttributes ) {
+
+ for ( let i = 0; i < contextNames.length; i ++ ) {
+
+ const contextName = contextNames[ i ];
+ const context = _canvas.getContext( contextName, contextAttributes );
+ if ( context !== null ) return context;
+
+ }
+
+ return null;
+
+ }
+
+ try {
+
+ const contextAttributes = {
+ alpha: _alpha,
+ depth: _depth,
+ stencil: _stencil,
+ antialias: _antialias,
+ premultipliedAlpha: _premultipliedAlpha,
+ preserveDrawingBuffer: _preserveDrawingBuffer,
+ powerPreference: _powerPreference,
+ failIfMajorPerformanceCaveat: _failIfMajorPerformanceCaveat
+ };
+
+ // event listeners must be registered before WebGL context is created, see #12753
+
+ _canvas.addEventListener( 'webglcontextlost', onContextLost, false );
+ _canvas.addEventListener( 'webglcontextrestored', onContextRestore, false );
+
+ if ( _gl === null ) {
+
+ const contextNames = [ 'webgl2', 'webgl', 'experimental-webgl' ];
+
+ if ( _this.isWebGL1Renderer === true ) {
+
+ contextNames.shift();
+
+ }
+
+ _gl = getContext( contextNames, contextAttributes );
+
+ if ( _gl === null ) {
+
+ if ( getContext( contextNames ) ) {
+
+ throw new Error( 'Error creating WebGL context with your selected attributes.' );
+
+ } else {
+
+ throw new Error( 'Error creating WebGL context.' );
+
+ }
+
+ }
+
+ }
+
+ // Some experimental-webgl implementations do not have getShaderPrecisionFormat
+
+ if ( _gl.getShaderPrecisionFormat === undefined ) {
+
+ _gl.getShaderPrecisionFormat = function () {
+
+ return { 'rangeMin': 1, 'rangeMax': 1, 'precision': 1 };
+
+ };
+
+ }
+
+ } catch ( error ) {
+
+ console.error( 'THREE.WebGLRenderer: ' + error.message );
+ throw error;
+
+ }
+
+ let extensions, capabilities, state, info;
+ let properties, textures, cubemaps, attributes, geometries, objects;
+ let programCache, materials, renderLists, renderStates, clipping;
+
+ let background, morphtargets, bufferRenderer, indexedBufferRenderer;
+
+ let utils, bindingStates;
+
+ function initGLContext() {
+
+ extensions = new WebGLExtensions( _gl );
+
+ capabilities = new WebGLCapabilities( _gl, extensions, parameters );
+
+ extensions.init( capabilities );
+
+ utils = new WebGLUtils( _gl, extensions, capabilities );
+
+ state = new WebGLState( _gl, extensions, capabilities );
+ state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() );
+ state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() );
+
+ info = new WebGLInfo( _gl );
+ properties = new WebGLProperties();
+ textures = new WebGLTextures( _gl, extensions, state, properties, capabilities, utils, info );
+ cubemaps = new WebGLCubeMaps( _this );
+ attributes = new WebGLAttributes( _gl, capabilities );
+ bindingStates = new WebGLBindingStates( _gl, extensions, attributes, capabilities );
+ geometries = new WebGLGeometries( _gl, attributes, info, bindingStates );
+ objects = new WebGLObjects( _gl, geometries, attributes, info );
+ morphtargets = new WebGLMorphtargets( _gl );
+ clipping = new WebGLClipping( properties );
+ programCache = new WebGLPrograms( _this, cubemaps, extensions, capabilities, bindingStates, clipping );
+ materials = new WebGLMaterials( properties );
+ renderLists = new WebGLRenderLists( properties );
+ renderStates = new WebGLRenderStates( extensions, capabilities );
+ background = new WebGLBackground( _this, cubemaps, state, objects, _premultipliedAlpha );
+
+ bufferRenderer = new WebGLBufferRenderer( _gl, extensions, info, capabilities );
+ indexedBufferRenderer = new WebGLIndexedBufferRenderer( _gl, extensions, info, capabilities );
+
+ info.programs = programCache.programs;
+
+ _this.capabilities = capabilities;
+ _this.extensions = extensions;
+ _this.properties = properties;
+ _this.renderLists = renderLists;
+ _this.state = state;
+ _this.info = info;
+
+ }
+
+ initGLContext();
+
+ // xr
+
+ const xr = new WebXRManager( _this, _gl );
+
+ this.xr = xr;
+
+ // shadow map
+
+ const shadowMap = new WebGLShadowMap( _this, objects, capabilities.maxTextureSize );
+
+ this.shadowMap = shadowMap;
+
+ // API
+
+ this.getContext = function () {
+
+ return _gl;
+
+ };
+
+ this.getContextAttributes = function () {
+
+ return _gl.getContextAttributes();
+
+ };
+
+ this.forceContextLoss = function () {
+
+ const extension = extensions.get( 'WEBGL_lose_context' );
+ if ( extension ) extension.loseContext();
+
+ };
+
+ this.forceContextRestore = function () {
+
+ const extension = extensions.get( 'WEBGL_lose_context' );
+ if ( extension ) extension.restoreContext();
+
+ };
+
+ this.getPixelRatio = function () {
+
+ return _pixelRatio;
+
+ };
+
+ this.setPixelRatio = function ( value ) {
+
+ if ( value === undefined ) return;
+
+ _pixelRatio = value;
+
+ this.setSize( _width, _height, false );
+
+ };
+
+ this.getSize = function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'WebGLRenderer: .getsize() now requires a Vector2 as an argument' );
+
+ target = new Vector2();
+
+ }
+
+ return target.set( _width, _height );
+
+ };
+
+ this.setSize = function ( width, height, updateStyle ) {
+
+ if ( xr.isPresenting ) {
+
+ console.warn( 'THREE.WebGLRenderer: Can\'t change size while VR device is presenting.' );
+ return;
+
+ }
+
+ _width = width;
+ _height = height;
+
+ _canvas.width = Math.floor( width * _pixelRatio );
+ _canvas.height = Math.floor( height * _pixelRatio );
+
+ if ( updateStyle !== false ) {
+
+ _canvas.style.width = width + 'px';
+ _canvas.style.height = height + 'px';
+
+ }
+
+ this.setViewport( 0, 0, width, height );
+
+ };
+
+ this.getDrawingBufferSize = function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'WebGLRenderer: .getdrawingBufferSize() now requires a Vector2 as an argument' );
+
+ target = new Vector2();
+
+ }
+
+ return target.set( _width * _pixelRatio, _height * _pixelRatio ).floor();
+
+ };
+
+ this.setDrawingBufferSize = function ( width, height, pixelRatio ) {
+
+ _width = width;
+ _height = height;
+
+ _pixelRatio = pixelRatio;
+
+ _canvas.width = Math.floor( width * pixelRatio );
+ _canvas.height = Math.floor( height * pixelRatio );
+
+ this.setViewport( 0, 0, width, height );
+
+ };
+
+ this.getCurrentViewport = function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'WebGLRenderer: .getCurrentViewport() now requires a Vector4 as an argument' );
+
+ target = new Vector4();
+
+ }
+
+ return target.copy( _currentViewport );
+
+ };
+
+ this.getViewport = function ( target ) {
+
+ return target.copy( _viewport );
+
+ };
+
+ this.setViewport = function ( x, y, width, height ) {
+
+ if ( x.isVector4 ) {
+
+ _viewport.set( x.x, x.y, x.z, x.w );
+
+ } else {
+
+ _viewport.set( x, y, width, height );
+
+ }
+
+ state.viewport( _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor() );
+
+ };
+
+ this.getScissor = function ( target ) {
+
+ return target.copy( _scissor );
+
+ };
+
+ this.setScissor = function ( x, y, width, height ) {
+
+ if ( x.isVector4 ) {
+
+ _scissor.set( x.x, x.y, x.z, x.w );
+
+ } else {
+
+ _scissor.set( x, y, width, height );
+
+ }
+
+ state.scissor( _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor() );
+
+ };
+
+ this.getScissorTest = function () {
+
+ return _scissorTest;
+
+ };
+
+ this.setScissorTest = function ( boolean ) {
+
+ state.setScissorTest( _scissorTest = boolean );
+
+ };
+
+ this.setOpaqueSort = function ( method ) {
+
+ _opaqueSort = method;
+
+ };
+
+ this.setTransparentSort = function ( method ) {
+
+ _transparentSort = method;
+
+ };
+
+ // Clearing
+
+ this.getClearColor = function ( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'WebGLRenderer: .getClearColor() now requires a Color as an argument' );
+
+ target = new Color();
+
+ }
+
+ return target.copy( background.getClearColor() );
+
+ };
+
+ this.setClearColor = function () {
+
+ background.setClearColor.apply( background, arguments );
+
+ };
+
+ this.getClearAlpha = function () {
+
+ return background.getClearAlpha();
+
+ };
+
+ this.setClearAlpha = function () {
+
+ background.setClearAlpha.apply( background, arguments );
+
+ };
+
+ this.clear = function ( color, depth, stencil ) {
+
+ let bits = 0;
+
+ if ( color === undefined || color ) bits |= 16384;
+ if ( depth === undefined || depth ) bits |= 256;
+ if ( stencil === undefined || stencil ) bits |= 1024;
+
+ _gl.clear( bits );
+
+ };
+
+ this.clearColor = function () {
+
+ this.clear( true, false, false );
+
+ };
+
+ this.clearDepth = function () {
+
+ this.clear( false, true, false );
+
+ };
+
+ this.clearStencil = function () {
+
+ this.clear( false, false, true );
+
+ };
+
+ //
+
+ this.dispose = function () {
+
+ _canvas.removeEventListener( 'webglcontextlost', onContextLost, false );
+ _canvas.removeEventListener( 'webglcontextrestored', onContextRestore, false );
+
+ renderLists.dispose();
+ renderStates.dispose();
+ properties.dispose();
+ cubemaps.dispose();
+ objects.dispose();
+ bindingStates.dispose();
+
+ xr.dispose();
+
+ animation.stop();
+
+ };
+
+ // Events
+
+ function onContextLost( event ) {
+
+ event.preventDefault();
+
+ console.log( 'THREE.WebGLRenderer: Context Lost.' );
+
+ _isContextLost = true;
+
+ }
+
+ function onContextRestore( /* event */ ) {
+
+ console.log( 'THREE.WebGLRenderer: Context Restored.' );
+
+ _isContextLost = false;
+
+ initGLContext();
+
+ }
+
+ function onMaterialDispose( event ) {
+
+ const material = event.target;
+
+ material.removeEventListener( 'dispose', onMaterialDispose );
+
+ deallocateMaterial( material );
+
+ }
+
+ // Buffer deallocation
+
+ function deallocateMaterial( material ) {
+
+ releaseMaterialProgramReference( material );
+
+ properties.remove( material );
+
+ }
+
+
+ function releaseMaterialProgramReference( material ) {
+
+ const programInfo = properties.get( material ).program;
+
+ if ( programInfo !== undefined ) {
+
+ programCache.releaseProgram( programInfo );
+
+ }
+
+ }
+
+ // Buffer rendering
+
+ function renderObjectImmediate( object, program ) {
+
+ object.render( function ( object ) {
+
+ _this.renderBufferImmediate( object, program );
+
+ } );
+
+ }
+
+ this.renderBufferImmediate = function ( object, program ) {
+
+ bindingStates.initAttributes();
+
+ const buffers = properties.get( object );
+
+ if ( object.hasPositions && ! buffers.position ) buffers.position = _gl.createBuffer();
+ if ( object.hasNormals && ! buffers.normal ) buffers.normal = _gl.createBuffer();
+ if ( object.hasUvs && ! buffers.uv ) buffers.uv = _gl.createBuffer();
+ if ( object.hasColors && ! buffers.color ) buffers.color = _gl.createBuffer();
+
+ const programAttributes = program.getAttributes();
+
+ if ( object.hasPositions ) {
+
+ _gl.bindBuffer( 34962, buffers.position );
+ _gl.bufferData( 34962, object.positionArray, 35048 );
+
+ bindingStates.enableAttribute( programAttributes.position );
+ _gl.vertexAttribPointer( programAttributes.position, 3, 5126, false, 0, 0 );
+
+ }
+
+ if ( object.hasNormals ) {
+
+ _gl.bindBuffer( 34962, buffers.normal );
+ _gl.bufferData( 34962, object.normalArray, 35048 );
+
+ bindingStates.enableAttribute( programAttributes.normal );
+ _gl.vertexAttribPointer( programAttributes.normal, 3, 5126, false, 0, 0 );
+
+ }
+
+ if ( object.hasUvs ) {
+
+ _gl.bindBuffer( 34962, buffers.uv );
+ _gl.bufferData( 34962, object.uvArray, 35048 );
+
+ bindingStates.enableAttribute( programAttributes.uv );
+ _gl.vertexAttribPointer( programAttributes.uv, 2, 5126, false, 0, 0 );
+
+ }
+
+ if ( object.hasColors ) {
+
+ _gl.bindBuffer( 34962, buffers.color );
+ _gl.bufferData( 34962, object.colorArray, 35048 );
+
+ bindingStates.enableAttribute( programAttributes.color );
+ _gl.vertexAttribPointer( programAttributes.color, 3, 5126, false, 0, 0 );
+
+ }
+
+ bindingStates.disableUnusedAttributes();
+
+ _gl.drawArrays( 4, 0, object.count );
+
+ object.count = 0;
+
+ };
+
+ this.renderBufferDirect = function ( camera, scene, geometry, material, object, group ) {
+
+ if ( scene === null ) scene = _emptyScene; // renderBufferDirect second parameter used to be fog (could be null)
+
+ const frontFaceCW = ( object.isMesh && object.matrixWorld.determinant() < 0 );
+
+ const program = setProgram( camera, scene, material, object );
+
+ state.setMaterial( material, frontFaceCW );
+
+ //
+
+ let index = geometry.index;
+ const position = geometry.attributes.position;
+
+ //
+
+ if ( index === null ) {
+
+ if ( position === undefined || position.count === 0 ) return;
+
+ } else if ( index.count === 0 ) {
+
+ return;
+
+ }
+
+ //
+
+ let rangeFactor = 1;
+
+ if ( material.wireframe === true ) {
+
+ index = geometries.getWireframeAttribute( geometry );
+ rangeFactor = 2;
+
+ }
+
+ if ( material.morphTargets || material.morphNormals ) {
+
+ morphtargets.update( object, geometry, material, program );
+
+ }
+
+ bindingStates.setup( object, material, program, geometry, index );
+
+ let attribute;
+ let renderer = bufferRenderer;
+
+ if ( index !== null ) {
+
+ attribute = attributes.get( index );
+
+ renderer = indexedBufferRenderer;
+ renderer.setIndex( attribute );
+
+ }
+
+ //
+
+ const dataCount = ( index !== null ) ? index.count : position.count;
+
+ const rangeStart = geometry.drawRange.start * rangeFactor;
+ const rangeCount = geometry.drawRange.count * rangeFactor;
+
+ const groupStart = group !== null ? group.start * rangeFactor : 0;
+ const groupCount = group !== null ? group.count * rangeFactor : Infinity;
+
+ const drawStart = Math.max( rangeStart, groupStart );
+ const drawEnd = Math.min( dataCount, rangeStart + rangeCount, groupStart + groupCount ) - 1;
+
+ const drawCount = Math.max( 0, drawEnd - drawStart + 1 );
+
+ if ( drawCount === 0 ) return;
+
+ //
+
+ if ( object.isMesh ) {
+
+ if ( material.wireframe === true ) {
+
+ state.setLineWidth( material.wireframeLinewidth * getTargetPixelRatio() );
+ renderer.setMode( 1 );
+
+ } else {
+
+ renderer.setMode( 4 );
+
+ }
+
+ } else if ( object.isLine ) {
+
+ let lineWidth = material.linewidth;
+
+ if ( lineWidth === undefined ) lineWidth = 1; // Not using Line*Material
+
+ state.setLineWidth( lineWidth * getTargetPixelRatio() );
+
+ if ( object.isLineSegments ) {
+
+ renderer.setMode( 1 );
+
+ } else if ( object.isLineLoop ) {
+
+ renderer.setMode( 2 );
+
+ } else {
+
+ renderer.setMode( 3 );
+
+ }
+
+ } else if ( object.isPoints ) {
+
+ renderer.setMode( 0 );
+
+ } else if ( object.isSprite ) {
+
+ renderer.setMode( 4 );
+
+ }
+
+ if ( object.isInstancedMesh ) {
+
+ renderer.renderInstances( drawStart, drawCount, object.count );
+
+ } else if ( geometry.isInstancedBufferGeometry ) {
+
+ const instanceCount = Math.min( geometry.instanceCount, geometry._maxInstanceCount );
+
+ renderer.renderInstances( drawStart, drawCount, instanceCount );
+
+ } else {
+
+ renderer.render( drawStart, drawCount );
+
+ }
+
+ };
+
+ // Compile
+
+ this.compile = function ( scene, camera ) {
+
+ currentRenderState = renderStates.get( scene );
+ currentRenderState.init();
+
+ scene.traverseVisible( function ( object ) {
+
+ if ( object.isLight && object.layers.test( camera.layers ) ) {
+
+ currentRenderState.pushLight( object );
+
+ if ( object.castShadow ) {
+
+ currentRenderState.pushShadow( object );
+
+ }
+
+ }
+
+ } );
+
+ currentRenderState.setupLights();
+
+ const compiled = new WeakMap();
+
+ scene.traverse( function ( object ) {
+
+ const material = object.material;
+
+ if ( material ) {
+
+ if ( Array.isArray( material ) ) {
+
+ for ( let i = 0; i < material.length; i ++ ) {
+
+ const material2 = material[ i ];
+
+ if ( compiled.has( material2 ) === false ) {
+
+ initMaterial( material2, scene, object );
+ compiled.set( material2 );
+
+ }
+
+ }
+
+ } else if ( compiled.has( material ) === false ) {
+
+ initMaterial( material, scene, object );
+ compiled.set( material );
+
+ }
+
+ }
+
+ } );
+
+ };
+
+ // Animation Loop
+
+ let onAnimationFrameCallback = null;
+
+ function onAnimationFrame( time ) {
+
+ if ( xr.isPresenting ) return;
+ if ( onAnimationFrameCallback ) onAnimationFrameCallback( time );
+
+ }
+
+ const animation = new WebGLAnimation();
+ animation.setAnimationLoop( onAnimationFrame );
+
+ if ( typeof window !== 'undefined' ) animation.setContext( window );
+
+ this.setAnimationLoop = function ( callback ) {
+
+ onAnimationFrameCallback = callback;
+ xr.setAnimationLoop( callback );
+
+ ( callback === null ) ? animation.stop() : animation.start();
+
+ };
+
+ // Rendering
+
+ this.render = function ( scene, camera ) {
+
+ let renderTarget, forceClear;
+
+ if ( arguments[ 2 ] !== undefined ) {
+
+ console.warn( 'THREE.WebGLRenderer.render(): the renderTarget argument has been removed. Use .setRenderTarget() instead.' );
+ renderTarget = arguments[ 2 ];
+
+ }
+
+ if ( arguments[ 3 ] !== undefined ) {
+
+ console.warn( 'THREE.WebGLRenderer.render(): the forceClear argument has been removed. Use .clear() instead.' );
+ forceClear = arguments[ 3 ];
+
+ }
+
+ if ( camera !== undefined && camera.isCamera !== true ) {
+
+ console.error( 'THREE.WebGLRenderer.render: camera is not an instance of THREE.Camera.' );
+ return;
+
+ }
+
+ if ( _isContextLost === true ) return;
+
+ // reset caching for this frame
+
+ bindingStates.resetDefaultState();
+ _currentMaterialId = - 1;
+ _currentCamera = null;
+
+ // update scene graph
+
+ if ( scene.autoUpdate === true ) scene.updateMatrixWorld();
+
+ // update camera matrices and frustum
+
+ if ( camera.parent === null ) camera.updateMatrixWorld();
+
+ if ( xr.enabled === true && xr.isPresenting === true ) {
+
+ camera = xr.getCamera( camera );
+
+ }
+
+ //
+ if ( scene.isScene === true ) scene.onBeforeRender( _this, scene, camera, renderTarget || _currentRenderTarget );
+
+ currentRenderState = renderStates.get( scene, renderStateStack.length );
+ currentRenderState.init();
+
+ renderStateStack.push( currentRenderState );
+
+ _projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+ _frustum.setFromProjectionMatrix( _projScreenMatrix );
+
+ _localClippingEnabled = this.localClippingEnabled;
+ _clippingEnabled = clipping.init( this.clippingPlanes, _localClippingEnabled, camera );
+
+ currentRenderList = renderLists.get( scene, camera );
+ currentRenderList.init();
+
+ projectObject( scene, camera, 0, _this.sortObjects );
+
+ currentRenderList.finish();
+
+ if ( _this.sortObjects === true ) {
+
+ currentRenderList.sort( _opaqueSort, _transparentSort );
+
+ }
+
+ //
+
+ if ( _clippingEnabled === true ) clipping.beginShadows();
+
+ const shadowsArray = currentRenderState.state.shadowsArray;
+
+ shadowMap.render( shadowsArray, scene, camera );
+
+ currentRenderState.setupLights();
+ currentRenderState.setupLightsView( camera );
+
+ if ( _clippingEnabled === true ) clipping.endShadows();
+
+ //
+
+ if ( this.info.autoReset === true ) this.info.reset();
+
+ if ( renderTarget !== undefined ) {
+
+ this.setRenderTarget( renderTarget );
+
+ }
+
+ //
+
+ background.render( currentRenderList, scene, camera, forceClear );
+
+ // render scene
+
+ const opaqueObjects = currentRenderList.opaque;
+ const transparentObjects = currentRenderList.transparent;
+
+ if ( opaqueObjects.length > 0 ) renderObjects( opaqueObjects, scene, camera );
+ if ( transparentObjects.length > 0 ) renderObjects( transparentObjects, scene, camera );
+
+ //
+
+ if ( scene.isScene === true ) scene.onAfterRender( _this, scene, camera );
+
+ //
+
+ if ( _currentRenderTarget !== null ) {
+
+ // Generate mipmap if we're using any kind of mipmap filtering
+
+ textures.updateRenderTargetMipmap( _currentRenderTarget );
+
+ // resolve multisample renderbuffers to a single-sample texture if necessary
+
+ textures.updateMultisampleRenderTarget( _currentRenderTarget );
+
+ }
+
+ // Ensure depth buffer writing is enabled so it can be cleared on next render
+
+ state.buffers.depth.setTest( true );
+ state.buffers.depth.setMask( true );
+ state.buffers.color.setMask( true );
+
+ state.setPolygonOffset( false );
+
+ // _gl.finish();
+
+ renderStateStack.pop();
+ if ( renderStateStack.length > 0 ) {
+
+ currentRenderState = renderStateStack[ renderStateStack.length - 1 ];
+
+ } else {
+
+ currentRenderState = null;
+
+ }
+
+ currentRenderList = null;
+
+ };
+
+ function projectObject( object, camera, groupOrder, sortObjects ) {
+
+ if ( object.visible === false ) return;
+
+ const visible = object.layers.test( camera.layers );
+
+ if ( visible ) {
+
+ if ( object.isGroup ) {
+
+ groupOrder = object.renderOrder;
+
+ } else if ( object.isLOD ) {
+
+ if ( object.autoUpdate === true ) object.update( camera );
+
+ } else if ( object.isLight ) {
+
+ currentRenderState.pushLight( object );
+
+ if ( object.castShadow ) {
+
+ currentRenderState.pushShadow( object );
+
+ }
+
+ } else if ( object.isSprite ) {
+
+ if ( ! object.frustumCulled || _frustum.intersectsSprite( object ) ) {
+
+ if ( sortObjects ) {
+
+ _vector3.setFromMatrixPosition( object.matrixWorld )
+ .applyMatrix4( _projScreenMatrix );
+
+ }
+
+ const geometry = objects.update( object );
+ const material = object.material;
+
+ if ( material.visible ) {
+
+ currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+
+ }
+
+ }
+
+ } else if ( object.isImmediateRenderObject ) {
+
+ if ( sortObjects ) {
+
+ _vector3.setFromMatrixPosition( object.matrixWorld )
+ .applyMatrix4( _projScreenMatrix );
+
+ }
+
+ currentRenderList.push( object, null, object.material, groupOrder, _vector3.z, null );
+
+ } else if ( object.isMesh || object.isLine || object.isPoints ) {
+
+ if ( object.isSkinnedMesh ) {
+
+ // update skeleton only once in a frame
+
+ if ( object.skeleton.frame !== info.render.frame ) {
+
+ object.skeleton.update();
+ object.skeleton.frame = info.render.frame;
+
+ }
+
+ }
+
+ if ( ! object.frustumCulled || _frustum.intersectsObject( object ) ) {
+
+ if ( sortObjects ) {
+
+ _vector3.setFromMatrixPosition( object.matrixWorld )
+ .applyMatrix4( _projScreenMatrix );
+
+ }
+
+ const geometry = objects.update( object );
+ const material = object.material;
+
+ if ( Array.isArray( material ) ) {
+
+ const groups = geometry.groups;
+
+ for ( let i = 0, l = groups.length; i < l; i ++ ) {
+
+ const group = groups[ i ];
+ const groupMaterial = material[ group.materialIndex ];
+
+ if ( groupMaterial && groupMaterial.visible ) {
+
+ currentRenderList.push( object, geometry, groupMaterial, groupOrder, _vector3.z, group );
+
+ }
+
+ }
+
+ } else if ( material.visible ) {
+
+ currentRenderList.push( object, geometry, material, groupOrder, _vector3.z, null );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ const children = object.children;
+
+ for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+ projectObject( children[ i ], camera, groupOrder, sortObjects );
+
+ }
+
+ }
+
+ function renderObjects( renderList, scene, camera ) {
+
+ const overrideMaterial = scene.isScene === true ? scene.overrideMaterial : null;
+
+ for ( let i = 0, l = renderList.length; i < l; i ++ ) {
+
+ const renderItem = renderList[ i ];
+
+ const object = renderItem.object;
+ const geometry = renderItem.geometry;
+ const material = overrideMaterial === null ? renderItem.material : overrideMaterial;
+ const group = renderItem.group;
+
+ if ( camera.isArrayCamera ) {
+
+ const cameras = camera.cameras;
+
+ for ( let j = 0, jl = cameras.length; j < jl; j ++ ) {
+
+ const camera2 = cameras[ j ];
+
+ if ( object.layers.test( camera2.layers ) ) {
+
+ state.viewport( _currentViewport.copy( camera2.viewport ) );
+
+ currentRenderState.setupLightsView( camera2 );
+
+ renderObject( object, scene, camera2, geometry, material, group );
+
+ }
+
+ }
+
+ } else {
+
+ renderObject( object, scene, camera, geometry, material, group );
+
+ }
+
+ }
+
+ }
+
+ function renderObject( object, scene, camera, geometry, material, group ) {
+
+ object.onBeforeRender( _this, scene, camera, geometry, material, group );
+
+ object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
+ object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
+
+ if ( object.isImmediateRenderObject ) {
+
+ const program = setProgram( camera, scene, material, object );
+
+ state.setMaterial( material );
+
+ bindingStates.reset();
+
+ renderObjectImmediate( object, program );
+
+ } else {
+
+ _this.renderBufferDirect( camera, scene, geometry, material, object, group );
+
+ }
+
+ object.onAfterRender( _this, scene, camera, geometry, material, group );
+
+ }
+
+ function initMaterial( material, scene, object ) {
+
+ if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
+
+ const materialProperties = properties.get( material );
+
+ const lights = currentRenderState.state.lights;
+ const shadowsArray = currentRenderState.state.shadowsArray;
+
+ const lightsStateVersion = lights.state.version;
+
+ const parameters = programCache.getParameters( material, lights.state, shadowsArray, scene, object );
+ const programCacheKey = programCache.getProgramCacheKey( parameters );
+
+ let program = materialProperties.program;
+ let programChange = true;
+
+ // always update environment and fog - changing these trigger an initMaterial call, but it's possible that the program doesn't change
+
+ materialProperties.environment = material.isMeshStandardMaterial ? scene.environment : null;
+ materialProperties.fog = scene.fog;
+ materialProperties.envMap = cubemaps.get( material.envMap || materialProperties.environment );
+
+ if ( program === undefined ) {
+
+ // new material
+ material.addEventListener( 'dispose', onMaterialDispose );
+
+ } else if ( program.cacheKey !== programCacheKey ) {
+
+ // changed glsl or parameters
+ releaseMaterialProgramReference( material );
+
+ } else if ( materialProperties.lightsStateVersion !== lightsStateVersion ) {
+
+ programChange = false;
+
+ } else if ( parameters.shaderID !== undefined ) {
+
+ // same glsl and uniform list
+ return;
+
+ } else {
+
+ // only rebuild uniform list
+ programChange = false;
+
+ }
+
+ if ( programChange ) {
+
+ parameters.uniforms = programCache.getUniforms( material );
+
+ material.onBeforeCompile( parameters, _this );
+
+ program = programCache.acquireProgram( parameters, programCacheKey );
+
+ materialProperties.program = program;
+ materialProperties.uniforms = parameters.uniforms;
+ materialProperties.outputEncoding = parameters.outputEncoding;
+
+ }
+
+ const uniforms = materialProperties.uniforms;
+
+ if ( ! material.isShaderMaterial &&
+ ! material.isRawShaderMaterial ||
+ material.clipping === true ) {
+
+ materialProperties.numClippingPlanes = clipping.numPlanes;
+ materialProperties.numIntersection = clipping.numIntersection;
+ uniforms.clippingPlanes = clipping.uniform;
+
+ }
+
+ // store the light setup it was created for
+
+ materialProperties.needsLights = materialNeedsLights( material );
+ materialProperties.lightsStateVersion = lightsStateVersion;
+
+ if ( materialProperties.needsLights ) {
+
+ // wire up the material to this renderer's lighting state
+
+ uniforms.ambientLightColor.value = lights.state.ambient;
+ uniforms.lightProbe.value = lights.state.probe;
+ uniforms.directionalLights.value = lights.state.directional;
+ uniforms.directionalLightShadows.value = lights.state.directionalShadow;
+ uniforms.spotLights.value = lights.state.spot;
+ uniforms.spotLightShadows.value = lights.state.spotShadow;
+ uniforms.rectAreaLights.value = lights.state.rectArea;
+ uniforms.ltc_1.value = lights.state.rectAreaLTC1;
+ uniforms.ltc_2.value = lights.state.rectAreaLTC2;
+ uniforms.pointLights.value = lights.state.point;
+ uniforms.pointLightShadows.value = lights.state.pointShadow;
+ uniforms.hemisphereLights.value = lights.state.hemi;
+
+ uniforms.directionalShadowMap.value = lights.state.directionalShadowMap;
+ uniforms.directionalShadowMatrix.value = lights.state.directionalShadowMatrix;
+ uniforms.spotShadowMap.value = lights.state.spotShadowMap;
+ uniforms.spotShadowMatrix.value = lights.state.spotShadowMatrix;
+ uniforms.pointShadowMap.value = lights.state.pointShadowMap;
+ uniforms.pointShadowMatrix.value = lights.state.pointShadowMatrix;
+ // TODO (abelnation): add area lights shadow info to uniforms
+
+ }
+
+ const progUniforms = materialProperties.program.getUniforms();
+ const uniformsList = WebGLUniforms.seqWithValue( progUniforms.seq, uniforms );
+
+ materialProperties.uniformsList = uniformsList;
+
+ }
+
+ function setProgram( camera, scene, material, object ) {
+
+ if ( scene.isScene !== true ) scene = _emptyScene; // scene could be a Mesh, Line, Points, ...
+
+ textures.resetTextureUnits();
+
+ const fog = scene.fog;
+ const environment = material.isMeshStandardMaterial ? scene.environment : null;
+ const encoding = ( _currentRenderTarget === null ) ? _this.outputEncoding : _currentRenderTarget.texture.encoding;
+ const envMap = cubemaps.get( material.envMap || environment );
+
+ const materialProperties = properties.get( material );
+ const lights = currentRenderState.state.lights;
+
+ if ( _clippingEnabled === true ) {
+
+ if ( _localClippingEnabled === true || camera !== _currentCamera ) {
+
+ const useCache =
+ camera === _currentCamera &&
+ material.id === _currentMaterialId;
+
+ // we might want to call this function with some ClippingGroup
+ // object instead of the material, once it becomes feasible
+ // (#8465, #8379)
+ clipping.setState( material, camera, useCache );
+
+ }
+
+ }
+
+ if ( material.version === materialProperties.__version ) {
+
+ if ( material.fog && materialProperties.fog !== fog ) {
+
+ initMaterial( material, scene, object );
+
+ } else if ( materialProperties.environment !== environment ) {
+
+ initMaterial( material, scene, object );
+
+ } else if ( materialProperties.needsLights && ( materialProperties.lightsStateVersion !== lights.state.version ) ) {
+
+ initMaterial( material, scene, object );
+
+ } else if ( materialProperties.numClippingPlanes !== undefined &&
+ ( materialProperties.numClippingPlanes !== clipping.numPlanes ||
+ materialProperties.numIntersection !== clipping.numIntersection ) ) {
+
+ initMaterial( material, scene, object );
+
+ } else if ( materialProperties.outputEncoding !== encoding ) {
+
+ initMaterial( material, scene, object );
+
+ } else if ( materialProperties.envMap !== envMap ) {
+
+ initMaterial( material, scene, object );
+
+ }
+
+ } else {
+
+ initMaterial( material, scene, object );
+ materialProperties.__version = material.version;
+
+ }
+
+ let refreshProgram = false;
+ let refreshMaterial = false;
+ let refreshLights = false;
+
+ const program = materialProperties.program,
+ p_uniforms = program.getUniforms(),
+ m_uniforms = materialProperties.uniforms;
+
+ if ( state.useProgram( program.program ) ) {
+
+ refreshProgram = true;
+ refreshMaterial = true;
+ refreshLights = true;
+
+ }
+
+ if ( material.id !== _currentMaterialId ) {
+
+ _currentMaterialId = material.id;
+
+ refreshMaterial = true;
+
+ }
+
+ if ( refreshProgram || _currentCamera !== camera ) {
+
+ p_uniforms.setValue( _gl, 'projectionMatrix', camera.projectionMatrix );
+
+ if ( capabilities.logarithmicDepthBuffer ) {
+
+ p_uniforms.setValue( _gl, 'logDepthBufFC',
+ 2.0 / ( Math.log( camera.far + 1.0 ) / Math.LN2 ) );
+
+ }
+
+ if ( _currentCamera !== camera ) {
+
+ _currentCamera = camera;
+
+ // lighting uniforms depend on the camera so enforce an update
+ // now, in case this material supports lights - or later, when
+ // the next material that does gets activated:
+
+ refreshMaterial = true; // set to true on material change
+ refreshLights = true; // remains set until update done
+
+ }
+
+ // load material specific uniforms
+ // (shader material also gets them for the sake of genericity)
+
+ if ( material.isShaderMaterial ||
+ material.isMeshPhongMaterial ||
+ material.isMeshToonMaterial ||
+ material.isMeshStandardMaterial ||
+ material.envMap ) {
+
+ const uCamPos = p_uniforms.map.cameraPosition;
+
+ if ( uCamPos !== undefined ) {
+
+ uCamPos.setValue( _gl,
+ _vector3.setFromMatrixPosition( camera.matrixWorld ) );
+
+ }
+
+ }
+
+ if ( material.isMeshPhongMaterial ||
+ material.isMeshToonMaterial ||
+ material.isMeshLambertMaterial ||
+ material.isMeshBasicMaterial ||
+ material.isMeshStandardMaterial ||
+ material.isShaderMaterial ) {
+
+ p_uniforms.setValue( _gl, 'isOrthographic', camera.isOrthographicCamera === true );
+
+ }
+
+ if ( material.isMeshPhongMaterial ||
+ material.isMeshToonMaterial ||
+ material.isMeshLambertMaterial ||
+ material.isMeshBasicMaterial ||
+ material.isMeshStandardMaterial ||
+ material.isShaderMaterial ||
+ material.isShadowMaterial ||
+ material.skinning ) {
+
+ p_uniforms.setValue( _gl, 'viewMatrix', camera.matrixWorldInverse );
+
+ }
+
+ }
+
+ // skinning uniforms must be set even if material didn't change
+ // auto-setting of texture unit for bone texture must go before other textures
+ // otherwise textures used for skinning can take over texture units reserved for other material textures
+
+ if ( material.skinning ) {
+
+ p_uniforms.setOptional( _gl, object, 'bindMatrix' );
+ p_uniforms.setOptional( _gl, object, 'bindMatrixInverse' );
+
+ const skeleton = object.skeleton;
+
+ if ( skeleton ) {
+
+ const bones = skeleton.bones;
+
+ if ( capabilities.floatVertexTextures ) {
+
+ if ( skeleton.boneTexture === null ) {
+
+ // layout (1 matrix = 4 pixels)
+ // RGBA RGBA RGBA RGBA (=> column1, column2, column3, column4)
+ // with 8x8 pixel texture max 16 bones * 4 pixels = (8 * 8)
+ // 16x16 pixel texture max 64 bones * 4 pixels = (16 * 16)
+ // 32x32 pixel texture max 256 bones * 4 pixels = (32 * 32)
+ // 64x64 pixel texture max 1024 bones * 4 pixels = (64 * 64)
+
+
+ let size = Math.sqrt( bones.length * 4 ); // 4 pixels needed for 1 matrix
+ size = MathUtils.ceilPowerOfTwo( size );
+ size = Math.max( size, 4 );
+
+ const boneMatrices = new Float32Array( size * size * 4 ); // 4 floats per RGBA pixel
+ boneMatrices.set( skeleton.boneMatrices ); // copy current values
+
+ const boneTexture = new DataTexture( boneMatrices, size, size, RGBAFormat, FloatType );
+
+ skeleton.boneMatrices = boneMatrices;
+ skeleton.boneTexture = boneTexture;
+ skeleton.boneTextureSize = size;
+
+ }
+
+ p_uniforms.setValue( _gl, 'boneTexture', skeleton.boneTexture, textures );
+ p_uniforms.setValue( _gl, 'boneTextureSize', skeleton.boneTextureSize );
+
+ } else {
+
+ p_uniforms.setOptional( _gl, skeleton, 'boneMatrices' );
+
+ }
+
+ }
+
+ }
+
+ if ( refreshMaterial || materialProperties.receiveShadow !== object.receiveShadow ) {
+
+ materialProperties.receiveShadow = object.receiveShadow;
+ p_uniforms.setValue( _gl, 'receiveShadow', object.receiveShadow );
+
+ }
+
+ if ( refreshMaterial ) {
+
+ p_uniforms.setValue( _gl, 'toneMappingExposure', _this.toneMappingExposure );
+
+ if ( materialProperties.needsLights ) {
+
+ // the current material requires lighting info
+
+ // note: all lighting uniforms are always set correctly
+ // they simply reference the renderer's state for their
+ // values
+ //
+ // use the current material's .needsUpdate flags to set
+ // the GL state when required
+
+ markUniformsLightsNeedsUpdate( m_uniforms, refreshLights );
+
+ }
+
+ // refresh uniforms common to several materials
+
+ if ( fog && material.fog ) {
+
+ materials.refreshFogUniforms( m_uniforms, fog );
+
+ }
+
+ materials.refreshMaterialUniforms( m_uniforms, material, _pixelRatio, _height );
+
+ WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
+
+ }
+
+ if ( material.isShaderMaterial && material.uniformsNeedUpdate === true ) {
+
+ WebGLUniforms.upload( _gl, materialProperties.uniformsList, m_uniforms, textures );
+ material.uniformsNeedUpdate = false;
+
+ }
+
+ if ( material.isSpriteMaterial ) {
+
+ p_uniforms.setValue( _gl, 'center', object.center );
+
+ }
+
+ // common matrices
+
+ p_uniforms.setValue( _gl, 'modelViewMatrix', object.modelViewMatrix );
+ p_uniforms.setValue( _gl, 'normalMatrix', object.normalMatrix );
+ p_uniforms.setValue( _gl, 'modelMatrix', object.matrixWorld );
+
+ return program;
+
+ }
+
+ // If uniforms are marked as clean, they don't need to be loaded to the GPU.
+
+ function markUniformsLightsNeedsUpdate( uniforms, value ) {
+
+ uniforms.ambientLightColor.needsUpdate = value;
+ uniforms.lightProbe.needsUpdate = value;
+
+ uniforms.directionalLights.needsUpdate = value;
+ uniforms.directionalLightShadows.needsUpdate = value;
+ uniforms.pointLights.needsUpdate = value;
+ uniforms.pointLightShadows.needsUpdate = value;
+ uniforms.spotLights.needsUpdate = value;
+ uniforms.spotLightShadows.needsUpdate = value;
+ uniforms.rectAreaLights.needsUpdate = value;
+ uniforms.hemisphereLights.needsUpdate = value;
+
+ }
+
+ function materialNeedsLights( material ) {
+
+ return material.isMeshLambertMaterial || material.isMeshToonMaterial || material.isMeshPhongMaterial ||
+ material.isMeshStandardMaterial || material.isShadowMaterial ||
+ ( material.isShaderMaterial && material.lights === true );
+
+ }
+
+ //
+ this.setFramebuffer = function ( value ) {
+
+ if ( _framebuffer !== value && _currentRenderTarget === null ) _gl.bindFramebuffer( 36160, value );
+
+ _framebuffer = value;
+
+ };
+
+ this.getActiveCubeFace = function () {
+
+ return _currentActiveCubeFace;
+
+ };
+
+ this.getActiveMipmapLevel = function () {
+
+ return _currentActiveMipmapLevel;
+
+ };
+
+ this.getRenderList = function () {
+
+ return currentRenderList;
+
+ };
+
+ this.setRenderList = function ( renderList ) {
+
+ currentRenderList = renderList;
+
+ };
+
+ this.getRenderTarget = function () {
+
+ return _currentRenderTarget;
+
+ };
+
+ this.setRenderTarget = function ( renderTarget, activeCubeFace = 0, activeMipmapLevel = 0 ) {
+
+ _currentRenderTarget = renderTarget;
+ _currentActiveCubeFace = activeCubeFace;
+ _currentActiveMipmapLevel = activeMipmapLevel;
+
+ if ( renderTarget && properties.get( renderTarget ).__webglFramebuffer === undefined ) {
+
+ textures.setupRenderTarget( renderTarget );
+
+ }
+
+ let framebuffer = _framebuffer;
+ let isCube = false;
+
+ if ( renderTarget ) {
+
+ const __webglFramebuffer = properties.get( renderTarget ).__webglFramebuffer;
+
+ if ( renderTarget.isWebGLCubeRenderTarget ) {
+
+ framebuffer = __webglFramebuffer[ activeCubeFace ];
+ isCube = true;
+
+ } else if ( renderTarget.isWebGLMultisampleRenderTarget ) {
+
+ framebuffer = properties.get( renderTarget ).__webglMultisampledFramebuffer;
+
+ } else {
+
+ framebuffer = __webglFramebuffer;
+
+ }
+
+ _currentViewport.copy( renderTarget.viewport );
+ _currentScissor.copy( renderTarget.scissor );
+ _currentScissorTest = renderTarget.scissorTest;
+
+ } else {
+
+ _currentViewport.copy( _viewport ).multiplyScalar( _pixelRatio ).floor();
+ _currentScissor.copy( _scissor ).multiplyScalar( _pixelRatio ).floor();
+ _currentScissorTest = _scissorTest;
+
+ }
+
+ if ( _currentFramebuffer !== framebuffer ) {
+
+ _gl.bindFramebuffer( 36160, framebuffer );
+ _currentFramebuffer = framebuffer;
+
+ }
+
+ state.viewport( _currentViewport );
+ state.scissor( _currentScissor );
+ state.setScissorTest( _currentScissorTest );
+
+ if ( isCube ) {
+
+ const textureProperties = properties.get( renderTarget.texture );
+ _gl.framebufferTexture2D( 36160, 36064, 34069 + activeCubeFace, textureProperties.__webglTexture, activeMipmapLevel );
+
+ }
+
+ };
+
+ this.readRenderTargetPixels = function ( renderTarget, x, y, width, height, buffer, activeCubeFaceIndex ) {
+
+ if ( ! ( renderTarget && renderTarget.isWebGLRenderTarget ) ) {
+
+ console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not THREE.WebGLRenderTarget.' );
+ return;
+
+ }
+
+ let framebuffer = properties.get( renderTarget ).__webglFramebuffer;
+
+ if ( renderTarget.isWebGLCubeRenderTarget && activeCubeFaceIndex !== undefined ) {
+
+ framebuffer = framebuffer[ activeCubeFaceIndex ];
+
+ }
+
+ if ( framebuffer ) {
+
+ let restore = false;
+
+ if ( framebuffer !== _currentFramebuffer ) {
+
+ _gl.bindFramebuffer( 36160, framebuffer );
+
+ restore = true;
+
+ }
+
+ try {
+
+ const texture = renderTarget.texture;
+ const textureFormat = texture.format;
+ const textureType = texture.type;
+
+ if ( textureFormat !== RGBAFormat && utils.convert( textureFormat ) !== _gl.getParameter( 35739 ) ) {
+
+ console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in RGBA or implementation defined format.' );
+ return;
+
+ }
+
+ const halfFloatSupportedByExt = ( textureType === HalfFloatType ) && ( extensions.has( 'EXT_color_buffer_half_float' ) || ( capabilities.isWebGL2 && extensions.has( 'EXT_color_buffer_float' ) ) );
+
+ if ( textureType !== UnsignedByteType && utils.convert( textureType ) !== _gl.getParameter( 35738 ) && // IE11, Edge and Chrome Mac < 52 (#9513)
+ ! ( textureType === FloatType && ( capabilities.isWebGL2 || extensions.has( 'OES_texture_float' ) || extensions.has( 'WEBGL_color_buffer_float' ) ) ) && // Chrome Mac >= 52 and Firefox
+ ! halfFloatSupportedByExt ) {
+
+ console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: renderTarget is not in UnsignedByteType or implementation defined type.' );
+ return;
+
+ }
+
+ if ( _gl.checkFramebufferStatus( 36160 ) === 36053 ) {
+
+ // the following if statement ensures valid read requests (no out-of-bounds pixels, see #8604)
+
+ if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {
+
+ _gl.readPixels( x, y, width, height, utils.convert( textureFormat ), utils.convert( textureType ), buffer );
+
+ }
+
+ } else {
+
+ console.error( 'THREE.WebGLRenderer.readRenderTargetPixels: readPixels from renderTarget failed. Framebuffer not complete.' );
+
+ }
+
+ } finally {
+
+ if ( restore ) {
+
+ _gl.bindFramebuffer( 36160, _currentFramebuffer );
+
+ }
+
+ }
+
+ }
+
+ };
+
+ this.copyFramebufferToTexture = function ( position, texture, level = 0 ) {
+
+ const levelScale = Math.pow( 2, - level );
+ const width = Math.floor( texture.image.width * levelScale );
+ const height = Math.floor( texture.image.height * levelScale );
+ const glFormat = utils.convert( texture.format );
+
+ textures.setTexture2D( texture, 0 );
+
+ _gl.copyTexImage2D( 3553, level, glFormat, position.x, position.y, width, height, 0 );
+
+ state.unbindTexture();
+
+ };
+
+ this.copyTextureToTexture = function ( position, srcTexture, dstTexture, level = 0 ) {
+
+ const width = srcTexture.image.width;
+ const height = srcTexture.image.height;
+ const glFormat = utils.convert( dstTexture.format );
+ const glType = utils.convert( dstTexture.type );
+
+ textures.setTexture2D( dstTexture, 0 );
+
+ // As another texture upload may have changed pixelStorei
+ // parameters, make sure they are correct for the dstTexture
+ _gl.pixelStorei( 37440, dstTexture.flipY );
+ _gl.pixelStorei( 37441, dstTexture.premultiplyAlpha );
+ _gl.pixelStorei( 3317, dstTexture.unpackAlignment );
+
+ if ( srcTexture.isDataTexture ) {
+
+ _gl.texSubImage2D( 3553, level, position.x, position.y, width, height, glFormat, glType, srcTexture.image.data );
+
+ } else {
+
+ if ( srcTexture.isCompressedTexture ) {
+
+ _gl.compressedTexSubImage2D( 3553, level, position.x, position.y, srcTexture.mipmaps[ 0 ].width, srcTexture.mipmaps[ 0 ].height, glFormat, srcTexture.mipmaps[ 0 ].data );
+
+ } else {
+
+ _gl.texSubImage2D( 3553, level, position.x, position.y, glFormat, glType, srcTexture.image );
+
+ }
+
+ }
+
+ // Generate mipmaps only when copying level 0
+ if ( level === 0 && dstTexture.generateMipmaps ) _gl.generateMipmap( 3553 );
+
+ state.unbindTexture();
+
+ };
+
+ this.initTexture = function ( texture ) {
+
+ textures.setTexture2D( texture, 0 );
+
+ state.unbindTexture();
+
+ };
+
+ this.resetState = function () {
+
+ state.reset();
+ bindingStates.reset();
+
+ };
+
+ if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
+
+ __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
+
+ }
+
+}
+
+function WebGL1Renderer( parameters ) {
+
+ WebGLRenderer.call( this, parameters );
+
+}
+
+WebGL1Renderer.prototype = Object.assign( Object.create( WebGLRenderer.prototype ), {
+
+ constructor: WebGL1Renderer,
+
+ isWebGL1Renderer: true
+
+} );
+
+class Scene extends Object3D {
+
+ constructor() {
+
+ super();
+
+ Object.defineProperty( this, 'isScene', { value: true } );
+
+ this.type = 'Scene';
+
+ this.background = null;
+ this.environment = null;
+ this.fog = null;
+
+ this.overrideMaterial = null;
+
+ this.autoUpdate = true; // checked by the renderer
+
+ if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
+
+ __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) ); // eslint-disable-line no-undef
+
+ }
+
+ }
+
+ copy( source, recursive ) {
+
+ super.copy( source, recursive );
+
+ if ( source.background !== null ) this.background = source.background.clone();
+ if ( source.environment !== null ) this.environment = source.environment.clone();
+ if ( source.fog !== null ) this.fog = source.fog.clone();
+
+ if ( source.overrideMaterial !== null ) this.overrideMaterial = source.overrideMaterial.clone();
+
+ this.autoUpdate = source.autoUpdate;
+ this.matrixAutoUpdate = source.matrixAutoUpdate;
+
+ return this;
+
+ }
+
+ toJSON( meta ) {
+
+ const data = super.toJSON( meta );
+
+ if ( this.background !== null ) data.object.background = this.background.toJSON( meta );
+ if ( this.environment !== null ) data.object.environment = this.environment.toJSON( meta );
+ if ( this.fog !== null ) data.object.fog = this.fog.toJSON();
+
+ return data;
+
+ }
+
+}
+
+function InterleavedBuffer( array, stride ) {
+
+ this.array = array;
+ this.stride = stride;
+ this.count = array !== undefined ? array.length / stride : 0;
+
+ this.usage = StaticDrawUsage;
+ this.updateRange = { offset: 0, count: - 1 };
+
+ this.version = 0;
+
+ this.uuid = MathUtils.generateUUID();
+
+}
+
+Object.defineProperty( InterleavedBuffer.prototype, 'needsUpdate', {
+
+ set: function ( value ) {
+
+ if ( value === true ) this.version ++;
+
+ }
+
+} );
+
+Object.assign( InterleavedBuffer.prototype, {
+
+ isInterleavedBuffer: true,
+
+ onUploadCallback: function () {},
+
+ setUsage: function ( value ) {
+
+ this.usage = value;
+
+ return this;
+
+ },
+
+ copy: function ( source ) {
+
+ this.array = new source.array.constructor( source.array );
+ this.count = source.count;
+ this.stride = source.stride;
+ this.usage = source.usage;
+
+ return this;
+
+ },
+
+ copyAt: function ( index1, attribute, index2 ) {
+
+ index1 *= this.stride;
+ index2 *= attribute.stride;
+
+ for ( let i = 0, l = this.stride; i < l; i ++ ) {
+
+ this.array[ index1 + i ] = attribute.array[ index2 + i ];
+
+ }
+
+ return this;
+
+ },
+
+ set: function ( value, offset = 0 ) {
+
+ this.array.set( value, offset );
+
+ return this;
+
+ },
+
+ clone: function ( data ) {
+
+ if ( data.arrayBuffers === undefined ) {
+
+ data.arrayBuffers = {};
+
+ }
+
+ if ( this.array.buffer._uuid === undefined ) {
+
+ this.array.buffer._uuid = MathUtils.generateUUID();
+
+ }
+
+ if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) {
+
+ data.arrayBuffers[ this.array.buffer._uuid ] = this.array.slice( 0 ).buffer;
+
+ }
+
+ const array = new this.array.constructor( data.arrayBuffers[ this.array.buffer._uuid ] );
+
+ const ib = new InterleavedBuffer( array, this.stride );
+ ib.setUsage( this.usage );
+
+ return ib;
+
+ },
+
+ onUpload: function ( callback ) {
+
+ this.onUploadCallback = callback;
+
+ return this;
+
+ },
+
+ toJSON: function ( data ) {
+
+ if ( data.arrayBuffers === undefined ) {
+
+ data.arrayBuffers = {};
+
+ }
+
+ // generate UUID for array buffer if necessary
+
+ if ( this.array.buffer._uuid === undefined ) {
+
+ this.array.buffer._uuid = MathUtils.generateUUID();
+
+ }
+
+ if ( data.arrayBuffers[ this.array.buffer._uuid ] === undefined ) {
+
+ data.arrayBuffers[ this.array.buffer._uuid ] = Array.prototype.slice.call( new Uint32Array( this.array.buffer ) );
+
+ }
+
+ //
+
+ return {
+ uuid: this.uuid,
+ buffer: this.array.buffer._uuid,
+ type: this.array.constructor.name,
+ stride: this.stride
+ };
+
+ }
+
+} );
+
+const _vector$6 = new Vector3();
+
+function InterleavedBufferAttribute( interleavedBuffer, itemSize, offset, normalized ) {
+
+ this.name = '';
+
+ this.data = interleavedBuffer;
+ this.itemSize = itemSize;
+ this.offset = offset;
+
+ this.normalized = normalized === true;
+
+}
+
+Object.defineProperties( InterleavedBufferAttribute.prototype, {
+
+ count: {
+
+ get: function () {
+
+ return this.data.count;
+
+ }
+
+ },
+
+ array: {
+
+ get: function () {
+
+ return this.data.array;
+
+ }
+
+ },
+
+ needsUpdate: {
+
+ set: function ( value ) {
+
+ this.data.needsUpdate = value;
+
+ }
+
+ }
+
+} );
+
+Object.assign( InterleavedBufferAttribute.prototype, {
+
+ isInterleavedBufferAttribute: true,
+
+ applyMatrix4: function ( m ) {
+
+ for ( let i = 0, l = this.data.count; i < l; i ++ ) {
+
+ _vector$6.x = this.getX( i );
+ _vector$6.y = this.getY( i );
+ _vector$6.z = this.getZ( i );
+
+ _vector$6.applyMatrix4( m );
+
+ this.setXYZ( i, _vector$6.x, _vector$6.y, _vector$6.z );
+
+ }
+
+ return this;
+
+ },
+
+ setX: function ( index, x ) {
+
+ this.data.array[ index * this.data.stride + this.offset ] = x;
+
+ return this;
+
+ },
+
+ setY: function ( index, y ) {
+
+ this.data.array[ index * this.data.stride + this.offset + 1 ] = y;
+
+ return this;
+
+ },
+
+ setZ: function ( index, z ) {
+
+ this.data.array[ index * this.data.stride + this.offset + 2 ] = z;
+
+ return this;
+
+ },
+
+ setW: function ( index, w ) {
+
+ this.data.array[ index * this.data.stride + this.offset + 3 ] = w;
+
+ return this;
+
+ },
+
+ getX: function ( index ) {
+
+ return this.data.array[ index * this.data.stride + this.offset ];
+
+ },
+
+ getY: function ( index ) {
+
+ return this.data.array[ index * this.data.stride + this.offset + 1 ];
+
+ },
+
+ getZ: function ( index ) {
+
+ return this.data.array[ index * this.data.stride + this.offset + 2 ];
+
+ },
+
+ getW: function ( index ) {
+
+ return this.data.array[ index * this.data.stride + this.offset + 3 ];
+
+ },
+
+ setXY: function ( index, x, y ) {
+
+ index = index * this.data.stride + this.offset;
+
+ this.data.array[ index + 0 ] = x;
+ this.data.array[ index + 1 ] = y;
+
+ return this;
+
+ },
+
+ setXYZ: function ( index, x, y, z ) {
+
+ index = index * this.data.stride + this.offset;
+
+ this.data.array[ index + 0 ] = x;
+ this.data.array[ index + 1 ] = y;
+ this.data.array[ index + 2 ] = z;
+
+ return this;
+
+ },
+
+ setXYZW: function ( index, x, y, z, w ) {
+
+ index = index * this.data.stride + this.offset;
+
+ this.data.array[ index + 0 ] = x;
+ this.data.array[ index + 1 ] = y;
+ this.data.array[ index + 2 ] = z;
+ this.data.array[ index + 3 ] = w;
+
+ return this;
+
+ },
+
+ clone: function ( data ) {
+
+ if ( data === undefined ) {
+
+ console.log( 'THREE.InterleavedBufferAttribute.clone(): Cloning an interlaved buffer attribute will deinterleave buffer data.' );
+
+ const array = [];
+
+ for ( let i = 0; i < this.count; i ++ ) {
+
+ const index = i * this.data.stride + this.offset;
+
+ for ( let j = 0; j < this.itemSize; j ++ ) {
+
+ array.push( this.data.array[ index + j ] );
+
+ }
+
+ }
+
+ return new BufferAttribute( new this.array.constructor( array ), this.itemSize, this.normalized );
+
+ } else {
+
+ if ( data.interleavedBuffers === undefined ) {
+
+ data.interleavedBuffers = {};
+
+ }
+
+ if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
+
+ data.interleavedBuffers[ this.data.uuid ] = this.data.clone( data );
+
+ }
+
+ return new InterleavedBufferAttribute( data.interleavedBuffers[ this.data.uuid ], this.itemSize, this.offset, this.normalized );
+
+ }
+
+ },
+
+ toJSON: function ( data ) {
+
+ if ( data === undefined ) {
+
+ console.log( 'THREE.InterleavedBufferAttribute.toJSON(): Serializing an interlaved buffer attribute will deinterleave buffer data.' );
+
+ const array = [];
+
+ for ( let i = 0; i < this.count; i ++ ) {
+
+ const index = i * this.data.stride + this.offset;
+
+ for ( let j = 0; j < this.itemSize; j ++ ) {
+
+ array.push( this.data.array[ index + j ] );
+
+ }
+
+ }
+
+ // deinterleave data and save it as an ordinary buffer attribute for now
+
+ return {
+ itemSize: this.itemSize,
+ type: this.array.constructor.name,
+ array: array,
+ normalized: this.normalized
+ };
+
+ } else {
+
+ // save as true interlaved attribtue
+
+ if ( data.interleavedBuffers === undefined ) {
+
+ data.interleavedBuffers = {};
+
+ }
+
+ if ( data.interleavedBuffers[ this.data.uuid ] === undefined ) {
+
+ data.interleavedBuffers[ this.data.uuid ] = this.data.toJSON( data );
+
+ }
+
+ return {
+ isInterleavedBufferAttribute: true,
+ itemSize: this.itemSize,
+ data: this.data.uuid,
+ offset: this.offset,
+ normalized: this.normalized
+ };
+
+ }
+
+ }
+
+} );
+
+/**
+ * parameters = {
+ * color: <hex>,
+ * map: new THREE.Texture( <Image> ),
+ * alphaMap: new THREE.Texture( <Image> ),
+ * rotation: <float>,
+ * sizeAttenuation: <bool>
+ * }
+ */
+
+function SpriteMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.type = 'SpriteMaterial';
+
+ this.color = new Color( 0xffffff );
+
+ this.map = null;
+
+ this.alphaMap = null;
+
+ this.rotation = 0;
+
+ this.sizeAttenuation = true;
+
+ this.transparent = true;
+
+ this.setValues( parameters );
+
+}
+
+SpriteMaterial.prototype = Object.create( Material.prototype );
+SpriteMaterial.prototype.constructor = SpriteMaterial;
+SpriteMaterial.prototype.isSpriteMaterial = true;
+
+SpriteMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.color.copy( source.color );
+
+ this.map = source.map;
+
+ this.alphaMap = source.alphaMap;
+
+ this.rotation = source.rotation;
+
+ this.sizeAttenuation = source.sizeAttenuation;
+
+ return this;
+
+};
+
+let _geometry;
+
+const _intersectPoint = new Vector3();
+const _worldScale = new Vector3();
+const _mvPosition = new Vector3();
+
+const _alignedPosition = new Vector2();
+const _rotatedPosition = new Vector2();
+const _viewWorldMatrix = new Matrix4();
+
+const _vA$1 = new Vector3();
+const _vB$1 = new Vector3();
+const _vC$1 = new Vector3();
+
+const _uvA$1 = new Vector2();
+const _uvB$1 = new Vector2();
+const _uvC$1 = new Vector2();
+
+function Sprite( material ) {
+
+ Object3D.call( this );
+
+ this.type = 'Sprite';
+
+ if ( _geometry === undefined ) {
+
+ _geometry = new BufferGeometry();
+
+ const float32Array = new Float32Array( [
+ - 0.5, - 0.5, 0, 0, 0,
+ 0.5, - 0.5, 0, 1, 0,
+ 0.5, 0.5, 0, 1, 1,
+ - 0.5, 0.5, 0, 0, 1
+ ] );
+
+ const interleavedBuffer = new InterleavedBuffer( float32Array, 5 );
+
+ _geometry.setIndex( [ 0, 1, 2, 0, 2, 3 ] );
+ _geometry.setAttribute( 'position', new InterleavedBufferAttribute( interleavedBuffer, 3, 0, false ) );
+ _geometry.setAttribute( 'uv', new InterleavedBufferAttribute( interleavedBuffer, 2, 3, false ) );
+
+ }
+
+ this.geometry = _geometry;
+ this.material = ( material !== undefined ) ? material : new SpriteMaterial();
+
+ this.center = new Vector2( 0.5, 0.5 );
+
+}
+
+Sprite.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+ constructor: Sprite,
+
+ isSprite: true,
+
+ raycast: function ( raycaster, intersects ) {
+
+ if ( raycaster.camera === null ) {
+
+ console.error( 'THREE.Sprite: "Raycaster.camera" needs to be set in order to raycast against sprites.' );
+
+ }
+
+ _worldScale.setFromMatrixScale( this.matrixWorld );
+
+ _viewWorldMatrix.copy( raycaster.camera.matrixWorld );
+ this.modelViewMatrix.multiplyMatrices( raycaster.camera.matrixWorldInverse, this.matrixWorld );
+
+ _mvPosition.setFromMatrixPosition( this.modelViewMatrix );
+
+ if ( raycaster.camera.isPerspectiveCamera && this.material.sizeAttenuation === false ) {
+
+ _worldScale.multiplyScalar( - _mvPosition.z );
+
+ }
+
+ const rotation = this.material.rotation;
+ let sin, cos;
+
+ if ( rotation !== 0 ) {
+
+ cos = Math.cos( rotation );
+ sin = Math.sin( rotation );
+
+ }
+
+ const center = this.center;
+
+ transformVertex( _vA$1.set( - 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+ transformVertex( _vB$1.set( 0.5, - 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+ transformVertex( _vC$1.set( 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+
+ _uvA$1.set( 0, 0 );
+ _uvB$1.set( 1, 0 );
+ _uvC$1.set( 1, 1 );
+
+ // check first triangle
+ let intersect = raycaster.ray.intersectTriangle( _vA$1, _vB$1, _vC$1, false, _intersectPoint );
+
+ if ( intersect === null ) {
+
+ // check second triangle
+ transformVertex( _vB$1.set( - 0.5, 0.5, 0 ), _mvPosition, center, _worldScale, sin, cos );
+ _uvB$1.set( 0, 1 );
+
+ intersect = raycaster.ray.intersectTriangle( _vA$1, _vC$1, _vB$1, false, _intersectPoint );
+ if ( intersect === null ) {
+
+ return;
+
+ }
+
+ }
+
+ const distance = raycaster.ray.origin.distanceTo( _intersectPoint );
+
+ if ( distance < raycaster.near || distance > raycaster.far ) return;
+
+ intersects.push( {
+
+ distance: distance,
+ point: _intersectPoint.clone(),
+ uv: Triangle.getUV( _intersectPoint, _vA$1, _vB$1, _vC$1, _uvA$1, _uvB$1, _uvC$1, new Vector2() ),
+ face: null,
+ object: this
+
+ } );
+
+ },
+
+ copy: function ( source ) {
+
+ Object3D.prototype.copy.call( this, source );
+
+ if ( source.center !== undefined ) this.center.copy( source.center );
+
+ this.material = source.material;
+
+ return this;
+
+ }
+
+} );
+
+function transformVertex( vertexPosition, mvPosition, center, scale, sin, cos ) {
+
+ // compute position in camera space
+ _alignedPosition.subVectors( vertexPosition, center ).addScalar( 0.5 ).multiply( scale );
+
+ // to check if rotation is not zero
+ if ( sin !== undefined ) {
+
+ _rotatedPosition.x = ( cos * _alignedPosition.x ) - ( sin * _alignedPosition.y );
+ _rotatedPosition.y = ( sin * _alignedPosition.x ) + ( cos * _alignedPosition.y );
+
+ } else {
+
+ _rotatedPosition.copy( _alignedPosition );
+
+ }
+
+
+ vertexPosition.copy( mvPosition );
+ vertexPosition.x += _rotatedPosition.x;
+ vertexPosition.y += _rotatedPosition.y;
+
+ // transform to world space
+ vertexPosition.applyMatrix4( _viewWorldMatrix );
+
+}
+
+const _v1$4 = new Vector3();
+const _v2$2 = new Vector3();
+
+function LOD() {
+
+ Object3D.call( this );
+
+ this._currentLevel = 0;
+
+ this.type = 'LOD';
+
+ Object.defineProperties( this, {
+ levels: {
+ enumerable: true,
+ value: []
+ }
+ } );
+
+ this.autoUpdate = true;
+
+}
+
+LOD.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+ constructor: LOD,
+
+ isLOD: true,
+
+ copy: function ( source ) {
+
+ Object3D.prototype.copy.call( this, source, false );
+
+ const levels = source.levels;
+
+ for ( let i = 0, l = levels.length; i < l; i ++ ) {
+
+ const level = levels[ i ];
+
+ this.addLevel( level.object.clone(), level.distance );
+
+ }
+
+ this.autoUpdate = source.autoUpdate;
+
+ return this;
+
+ },
+
+ addLevel: function ( object, distance = 0 ) {
+
+ distance = Math.abs( distance );
+
+ const levels = this.levels;
+
+ let l;
+
+ for ( l = 0; l < levels.length; l ++ ) {
+
+ if ( distance < levels[ l ].distance ) {
+
+ break;
+
+ }
+
+ }
+
+ levels.splice( l, 0, { distance: distance, object: object } );
+
+ this.add( object );
+
+ return this;
+
+ },
+
+ getCurrentLevel: function () {
+
+ return this._currentLevel;
+
+ },
+
+ getObjectForDistance: function ( distance ) {
+
+ const levels = this.levels;
+
+ if ( levels.length > 0 ) {
+
+ let i, l;
+
+ for ( i = 1, l = levels.length; i < l; i ++ ) {
+
+ if ( distance < levels[ i ].distance ) {
+
+ break;
+
+ }
+
+ }
+
+ return levels[ i - 1 ].object;
+
+ }
+
+ return null;
+
+ },
+
+ raycast: function ( raycaster, intersects ) {
+
+ const levels = this.levels;
+
+ if ( levels.length > 0 ) {
+
+ _v1$4.setFromMatrixPosition( this.matrixWorld );
+
+ const distance = raycaster.ray.origin.distanceTo( _v1$4 );
+
+ this.getObjectForDistance( distance ).raycast( raycaster, intersects );
+
+ }
+
+ },
+
+ update: function ( camera ) {
+
+ const levels = this.levels;
+
+ if ( levels.length > 1 ) {
+
+ _v1$4.setFromMatrixPosition( camera.matrixWorld );
+ _v2$2.setFromMatrixPosition( this.matrixWorld );
+
+ const distance = _v1$4.distanceTo( _v2$2 ) / camera.zoom;
+
+ levels[ 0 ].object.visible = true;
+
+ let i, l;
+
+ for ( i = 1, l = levels.length; i < l; i ++ ) {
+
+ if ( distance >= levels[ i ].distance ) {
+
+ levels[ i - 1 ].object.visible = false;
+ levels[ i ].object.visible = true;
+
+ } else {
+
+ break;
+
+ }
+
+ }
+
+ this._currentLevel = i - 1;
+
+ for ( ; i < l; i ++ ) {
+
+ levels[ i ].object.visible = false;
+
+ }
+
+ }
+
+ },
+
+ toJSON: function ( meta ) {
+
+ const data = Object3D.prototype.toJSON.call( this, meta );
+
+ if ( this.autoUpdate === false ) data.object.autoUpdate = false;
+
+ data.object.levels = [];
+
+ const levels = this.levels;
+
+ for ( let i = 0, l = levels.length; i < l; i ++ ) {
+
+ const level = levels[ i ];
+
+ data.object.levels.push( {
+ object: level.object.uuid,
+ distance: level.distance
+ } );
+
+ }
+
+ return data;
+
+ }
+
+} );
+
+const _basePosition = new Vector3();
+
+const _skinIndex = new Vector4();
+const _skinWeight = new Vector4();
+
+const _vector$7 = new Vector3();
+const _matrix$1 = new Matrix4();
+
+function SkinnedMesh( geometry, material ) {
+
+ if ( geometry && geometry.isGeometry ) {
+
+ console.error( 'THREE.SkinnedMesh no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+ }
+
+ Mesh.call( this, geometry, material );
+
+ this.type = 'SkinnedMesh';
+
+ this.bindMode = 'attached';
+ this.bindMatrix = new Matrix4();
+ this.bindMatrixInverse = new Matrix4();
+
+}
+
+SkinnedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
+
+ constructor: SkinnedMesh,
+
+ isSkinnedMesh: true,
+
+ copy: function ( source ) {
+
+ Mesh.prototype.copy.call( this, source );
+
+ this.bindMode = source.bindMode;
+ this.bindMatrix.copy( source.bindMatrix );
+ this.bindMatrixInverse.copy( source.bindMatrixInverse );
+
+ this.skeleton = source.skeleton;
+
+ return this;
+
+ },
+
+ bind: function ( skeleton, bindMatrix ) {
+
+ this.skeleton = skeleton;
+
+ if ( bindMatrix === undefined ) {
+
+ this.updateMatrixWorld( true );
+
+ this.skeleton.calculateInverses();
+
+ bindMatrix = this.matrixWorld;
+
+ }
+
+ this.bindMatrix.copy( bindMatrix );
+ this.bindMatrixInverse.copy( bindMatrix ).invert();
+
+ },
+
+ pose: function () {
+
+ this.skeleton.pose();
+
+ },
+
+ normalizeSkinWeights: function () {
+
+ const vector = new Vector4();
+
+ const skinWeight = this.geometry.attributes.skinWeight;
+
+ for ( let i = 0, l = skinWeight.count; i < l; i ++ ) {
+
+ vector.x = skinWeight.getX( i );
+ vector.y = skinWeight.getY( i );
+ vector.z = skinWeight.getZ( i );
+ vector.w = skinWeight.getW( i );
+
+ const scale = 1.0 / vector.manhattanLength();
+
+ if ( scale !== Infinity ) {
+
+ vector.multiplyScalar( scale );
+
+ } else {
+
+ vector.set( 1, 0, 0, 0 ); // do something reasonable
+
+ }
+
+ skinWeight.setXYZW( i, vector.x, vector.y, vector.z, vector.w );
+
+ }
+
+ },
+
+ updateMatrixWorld: function ( force ) {
+
+ Mesh.prototype.updateMatrixWorld.call( this, force );
+
+ if ( this.bindMode === 'attached' ) {
+
+ this.bindMatrixInverse.copy( this.matrixWorld ).invert();
+
+ } else if ( this.bindMode === 'detached' ) {
+
+ this.bindMatrixInverse.copy( this.bindMatrix ).invert();
+
+ } else {
+
+ console.warn( 'THREE.SkinnedMesh: Unrecognized bindMode: ' + this.bindMode );
+
+ }
+
+ },
+
+ boneTransform: function ( index, target ) {
+
+ const skeleton = this.skeleton;
+ const geometry = this.geometry;
+
+ _skinIndex.fromBufferAttribute( geometry.attributes.skinIndex, index );
+ _skinWeight.fromBufferAttribute( geometry.attributes.skinWeight, index );
+
+ _basePosition.fromBufferAttribute( geometry.attributes.position, index ).applyMatrix4( this.bindMatrix );
+
+ target.set( 0, 0, 0 );
+
+ for ( let i = 0; i < 4; i ++ ) {
+
+ const weight = _skinWeight.getComponent( i );
+
+ if ( weight !== 0 ) {
+
+ const boneIndex = _skinIndex.getComponent( i );
+
+ _matrix$1.multiplyMatrices( skeleton.bones[ boneIndex ].matrixWorld, skeleton.boneInverses[ boneIndex ] );
+
+ target.addScaledVector( _vector$7.copy( _basePosition ).applyMatrix4( _matrix$1 ), weight );
+
+ }
+
+ }
+
+ return target.applyMatrix4( this.bindMatrixInverse );
+
+ }
+
+} );
+
+function Bone() {
+
+ Object3D.call( this );
+
+ this.type = 'Bone';
+
+}
+
+Bone.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+ constructor: Bone,
+
+ isBone: true
+
+} );
+
+const _offsetMatrix = new Matrix4();
+const _identityMatrix = new Matrix4();
+
+function Skeleton( bones = [], boneInverses = [] ) {
+
+ this.uuid = MathUtils.generateUUID();
+
+ this.bones = bones.slice( 0 );
+ this.boneInverses = boneInverses;
+ this.boneMatrices = null;
+
+ this.boneTexture = null;
+ this.boneTextureSize = 0;
+
+ this.frame = - 1;
+
+ this.init();
+
+}
+
+Object.assign( Skeleton.prototype, {
+
+ init: function () {
+
+ const bones = this.bones;
+ const boneInverses = this.boneInverses;
+
+ this.boneMatrices = new Float32Array( bones.length * 16 );
+
+ // calculate inverse bone matrices if necessary
+
+ if ( boneInverses.length === 0 ) {
+
+ this.calculateInverses();
+
+ } else {
+
+ // handle special case
+
+ if ( bones.length !== boneInverses.length ) {
+
+ console.warn( 'THREE.Skeleton: Number of inverse bone matrices does not match amount of bones.' );
+
+ this.boneInverses = [];
+
+ for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+
+ this.boneInverses.push( new Matrix4() );
+
+ }
+
+ }
+
+ }
+
+ },
+
+ calculateInverses: function () {
+
+ this.boneInverses.length = 0;
+
+ for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+
+ const inverse = new Matrix4();
+
+ if ( this.bones[ i ] ) {
+
+ inverse.copy( this.bones[ i ].matrixWorld ).invert();
+
+ }
+
+ this.boneInverses.push( inverse );
+
+ }
+
+ },
+
+ pose: function () {
+
+ // recover the bind-time world matrices
+
+ for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+
+ const bone = this.bones[ i ];
+
+ if ( bone ) {
+
+ bone.matrixWorld.copy( this.boneInverses[ i ] ).invert();
+
+ }
+
+ }
+
+ // compute the local matrices, positions, rotations and scales
+
+ for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+
+ const bone = this.bones[ i ];
+
+ if ( bone ) {
+
+ if ( bone.parent && bone.parent.isBone ) {
+
+ bone.matrix.copy( bone.parent.matrixWorld ).invert();
+ bone.matrix.multiply( bone.matrixWorld );
+
+ } else {
+
+ bone.matrix.copy( bone.matrixWorld );
+
+ }
+
+ bone.matrix.decompose( bone.position, bone.quaternion, bone.scale );
+
+ }
+
+ }
+
+ },
+
+ update: function () {
+
+ const bones = this.bones;
+ const boneInverses = this.boneInverses;
+ const boneMatrices = this.boneMatrices;
+ const boneTexture = this.boneTexture;
+
+ // flatten bone matrices to array
+
+ for ( let i = 0, il = bones.length; i < il; i ++ ) {
+
+ // compute the offset between the current and the original transform
+
+ const matrix = bones[ i ] ? bones[ i ].matrixWorld : _identityMatrix;
+
+ _offsetMatrix.multiplyMatrices( matrix, boneInverses[ i ] );
+ _offsetMatrix.toArray( boneMatrices, i * 16 );
+
+ }
+
+ if ( boneTexture !== null ) {
+
+ boneTexture.needsUpdate = true;
+
+ }
+
+ },
+
+ clone: function () {
+
+ return new Skeleton( this.bones, this.boneInverses );
+
+ },
+
+ getBoneByName: function ( name ) {
+
+ for ( let i = 0, il = this.bones.length; i < il; i ++ ) {
+
+ const bone = this.bones[ i ];
+
+ if ( bone.name === name ) {
+
+ return bone;
+
+ }
+
+ }
+
+ return undefined;
+
+ },
+
+ dispose: function ( ) {
+
+ if ( this.boneTexture !== null ) {
+
+ this.boneTexture.dispose();
+
+ this.boneTexture = null;
+
+ }
+
+ },
+
+ fromJSON: function ( json, bones ) {
+
+ this.uuid = json.uuid;
+
+ for ( let i = 0, l = json.bones.length; i < l; i ++ ) {
+
+ const uuid = json.bones[ i ];
+ let bone = bones[ uuid ];
+
+ if ( bone === undefined ) {
+
+ console.warn( 'THREE.Skeleton: No bone found with UUID:', uuid );
+ bone = new Bone();
+
+ }
+
+ this.bones.push( bone );
+ this.boneInverses.push( new Matrix4().fromArray( json.boneInverses[ i ] ) );
+
+ }
+
+ this.init();
+
+ return this;
+
+ },
+
+ toJSON: function () {
+
+ const data = {
+ metadata: {
+ version: 4.5,
+ type: 'Skeleton',
+ generator: 'Skeleton.toJSON'
+ },
+ bones: [],
+ boneInverses: []
+ };
+
+ data.uuid = this.uuid;
+
+ const bones = this.bones;
+ const boneInverses = this.boneInverses;
+
+ for ( let i = 0, l = bones.length; i < l; i ++ ) {
+
+ const bone = bones[ i ];
+ data.bones.push( bone.uuid );
+
+ const boneInverse = boneInverses[ i ];
+ data.boneInverses.push( boneInverse.toArray() );
+
+ }
+
+ return data;
+
+ }
+
+} );
+
+const _instanceLocalMatrix = new Matrix4();
+const _instanceWorldMatrix = new Matrix4();
+
+const _instanceIntersects = [];
+
+const _mesh = new Mesh();
+
+function InstancedMesh( geometry, material, count ) {
+
+ Mesh.call( this, geometry, material );
+
+ this.instanceMatrix = new BufferAttribute( new Float32Array( count * 16 ), 16 );
+ this.instanceColor = null;
+
+ this.count = count;
+
+ this.frustumCulled = false;
+
+}
+
+InstancedMesh.prototype = Object.assign( Object.create( Mesh.prototype ), {
+
+ constructor: InstancedMesh,
+
+ isInstancedMesh: true,
+
+ copy: function ( source ) {
+
+ Mesh.prototype.copy.call( this, source );
+
+ this.instanceMatrix.copy( source.instanceMatrix );
+
+ if ( source.instanceColor !== null ) this.instanceColor = source.instanceColor.clone();
+
+ this.count = source.count;
+
+ return this;
+
+ },
+
+ getColorAt: function ( index, color ) {
+
+ color.fromArray( this.instanceColor.array, index * 3 );
+
+ },
+
+ getMatrixAt: function ( index, matrix ) {
+
+ matrix.fromArray( this.instanceMatrix.array, index * 16 );
+
+ },
+
+ raycast: function ( raycaster, intersects ) {
+
+ const matrixWorld = this.matrixWorld;
+ const raycastTimes = this.count;
+
+ _mesh.geometry = this.geometry;
+ _mesh.material = this.material;
+
+ if ( _mesh.material === undefined ) return;
+
+ for ( let instanceId = 0; instanceId < raycastTimes; instanceId ++ ) {
+
+ // calculate the world matrix for each instance
+
+ this.getMatrixAt( instanceId, _instanceLocalMatrix );
+
+ _instanceWorldMatrix.multiplyMatrices( matrixWorld, _instanceLocalMatrix );
+
+ // the mesh represents this single instance
+
+ _mesh.matrixWorld = _instanceWorldMatrix;
+
+ _mesh.raycast( raycaster, _instanceIntersects );
+
+ // process the result of raycast
+
+ for ( let i = 0, l = _instanceIntersects.length; i < l; i ++ ) {
+
+ const intersect = _instanceIntersects[ i ];
+ intersect.instanceId = instanceId;
+ intersect.object = this;
+ intersects.push( intersect );
+
+ }
+
+ _instanceIntersects.length = 0;
+
+ }
+
+ },
+
+ setColorAt: function ( index, color ) {
+
+ if ( this.instanceColor === null ) {
+
+ this.instanceColor = new BufferAttribute( new Float32Array( this.count * 3 ), 3 );
+
+ }
+
+ color.toArray( this.instanceColor.array, index * 3 );
+
+ },
+
+ setMatrixAt: function ( index, matrix ) {
+
+ matrix.toArray( this.instanceMatrix.array, index * 16 );
+
+ },
+
+ updateMorphTargets: function () {
+
+ },
+
+ dispose: function () {
+
+ this.dispatchEvent( { type: 'dispose' } );
+
+ }
+
+} );
+
+/**
+ * parameters = {
+ * color: <hex>,
+ * opacity: <float>,
+ *
+ * linewidth: <float>,
+ * linecap: "round",
+ * linejoin: "round"
+ * }
+ */
+
+function LineBasicMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.type = 'LineBasicMaterial';
+
+ this.color = new Color( 0xffffff );
+
+ this.linewidth = 1;
+ this.linecap = 'round';
+ this.linejoin = 'round';
+
+ this.morphTargets = false;
+
+ this.setValues( parameters );
+
+}
+
+LineBasicMaterial.prototype = Object.create( Material.prototype );
+LineBasicMaterial.prototype.constructor = LineBasicMaterial;
+
+LineBasicMaterial.prototype.isLineBasicMaterial = true;
+
+LineBasicMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.color.copy( source.color );
+
+ this.linewidth = source.linewidth;
+ this.linecap = source.linecap;
+ this.linejoin = source.linejoin;
+
+ this.morphTargets = source.morphTargets;
+
+ return this;
+
+};
+
+const _start = new Vector3();
+const _end = new Vector3();
+const _inverseMatrix$1 = new Matrix4();
+const _ray$1 = new Ray();
+const _sphere$2 = new Sphere();
+
+function Line( geometry = new BufferGeometry(), material = new LineBasicMaterial() ) {
+
+ Object3D.call( this );
+
+ this.type = 'Line';
+
+ this.geometry = geometry;
+ this.material = material;
+
+ this.updateMorphTargets();
+
+}
+
+Line.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+ constructor: Line,
+
+ isLine: true,
+
+ copy: function ( source ) {
+
+ Object3D.prototype.copy.call( this, source );
+
+ this.material = source.material;
+ this.geometry = source.geometry;
+
+ return this;
+
+ },
+
+ computeLineDistances: function () {
+
+ const geometry = this.geometry;
+
+ if ( geometry.isBufferGeometry ) {
+
+ // we assume non-indexed geometry
+
+ if ( geometry.index === null ) {
+
+ const positionAttribute = geometry.attributes.position;
+ const lineDistances = [ 0 ];
+
+ for ( let i = 1, l = positionAttribute.count; i < l; i ++ ) {
+
+ _start.fromBufferAttribute( positionAttribute, i - 1 );
+ _end.fromBufferAttribute( positionAttribute, i );
+
+ lineDistances[ i ] = lineDistances[ i - 1 ];
+ lineDistances[ i ] += _start.distanceTo( _end );
+
+ }
+
+ geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
+
+ } else {
+
+ console.warn( 'THREE.Line.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
+
+ }
+
+ } else if ( geometry.isGeometry ) {
+
+ console.error( 'THREE.Line.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+ }
+
+ return this;
+
+ },
+
+ raycast: function ( raycaster, intersects ) {
+
+ const geometry = this.geometry;
+ const matrixWorld = this.matrixWorld;
+ const threshold = raycaster.params.Line.threshold;
+
+ // Checking boundingSphere distance to ray
+
+ if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+ _sphere$2.copy( geometry.boundingSphere );
+ _sphere$2.applyMatrix4( matrixWorld );
+ _sphere$2.radius += threshold;
+
+ if ( raycaster.ray.intersectsSphere( _sphere$2 ) === false ) return;
+
+ //
+
+ _inverseMatrix$1.copy( matrixWorld ).invert();
+ _ray$1.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$1 );
+
+ const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
+ const localThresholdSq = localThreshold * localThreshold;
+
+ const vStart = new Vector3();
+ const vEnd = new Vector3();
+ const interSegment = new Vector3();
+ const interRay = new Vector3();
+ const step = this.isLineSegments ? 2 : 1;
+
+ if ( geometry.isBufferGeometry ) {
+
+ const index = geometry.index;
+ const attributes = geometry.attributes;
+ const positionAttribute = attributes.position;
+
+ if ( index !== null ) {
+
+ const indices = index.array;
+
+ for ( let i = 0, l = indices.length - 1; i < l; i += step ) {
+
+ const a = indices[ i ];
+ const b = indices[ i + 1 ];
+
+ vStart.fromBufferAttribute( positionAttribute, a );
+ vEnd.fromBufferAttribute( positionAttribute, b );
+
+ const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
+
+ if ( distSq > localThresholdSq ) continue;
+
+ interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
+
+ const distance = raycaster.ray.origin.distanceTo( interRay );
+
+ if ( distance < raycaster.near || distance > raycaster.far ) continue;
+
+ intersects.push( {
+
+ distance: distance,
+ // What do we want? intersection point on the ray or on the segment??
+ // point: raycaster.ray.at( distance ),
+ point: interSegment.clone().applyMatrix4( this.matrixWorld ),
+ index: i,
+ face: null,
+ faceIndex: null,
+ object: this
+
+ } );
+
+ }
+
+ } else {
+
+ for ( let i = 0, l = positionAttribute.count - 1; i < l; i += step ) {
+
+ vStart.fromBufferAttribute( positionAttribute, i );
+ vEnd.fromBufferAttribute( positionAttribute, i + 1 );
+
+ const distSq = _ray$1.distanceSqToSegment( vStart, vEnd, interRay, interSegment );
+
+ if ( distSq > localThresholdSq ) continue;
+
+ interRay.applyMatrix4( this.matrixWorld ); //Move back to world space for distance calculation
+
+ const distance = raycaster.ray.origin.distanceTo( interRay );
+
+ if ( distance < raycaster.near || distance > raycaster.far ) continue;
+
+ intersects.push( {
+
+ distance: distance,
+ // What do we want? intersection point on the ray or on the segment??
+ // point: raycaster.ray.at( distance ),
+ point: interSegment.clone().applyMatrix4( this.matrixWorld ),
+ index: i,
+ face: null,
+ faceIndex: null,
+ object: this
+
+ } );
+
+ }
+
+ }
+
+ } else if ( geometry.isGeometry ) {
+
+ console.error( 'THREE.Line.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+ }
+
+ },
+
+ updateMorphTargets: function () {
+
+ const geometry = this.geometry;
+
+ if ( geometry.isBufferGeometry ) {
+
+ const morphAttributes = geometry.morphAttributes;
+ const keys = Object.keys( morphAttributes );
+
+ if ( keys.length > 0 ) {
+
+ const morphAttribute = morphAttributes[ keys[ 0 ] ];
+
+ if ( morphAttribute !== undefined ) {
+
+ this.morphTargetInfluences = [];
+ this.morphTargetDictionary = {};
+
+ for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
+
+ const name = morphAttribute[ m ].name || String( m );
+
+ this.morphTargetInfluences.push( 0 );
+ this.morphTargetDictionary[ name ] = m;
+
+ }
+
+ }
+
+ }
+
+ } else {
+
+ const morphTargets = geometry.morphTargets;
+
+ if ( morphTargets !== undefined && morphTargets.length > 0 ) {
+
+ console.error( 'THREE.Line.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+ }
+
+ }
+
+ }
+
+} );
+
+const _start$1 = new Vector3();
+const _end$1 = new Vector3();
+
+function LineSegments( geometry, material ) {
+
+ Line.call( this, geometry, material );
+
+ this.type = 'LineSegments';
+
+}
+
+LineSegments.prototype = Object.assign( Object.create( Line.prototype ), {
+
+ constructor: LineSegments,
+
+ isLineSegments: true,
+
+ computeLineDistances: function () {
+
+ const geometry = this.geometry;
+
+ if ( geometry.isBufferGeometry ) {
+
+ // we assume non-indexed geometry
+
+ if ( geometry.index === null ) {
+
+ const positionAttribute = geometry.attributes.position;
+ const lineDistances = [];
+
+ for ( let i = 0, l = positionAttribute.count; i < l; i += 2 ) {
+
+ _start$1.fromBufferAttribute( positionAttribute, i );
+ _end$1.fromBufferAttribute( positionAttribute, i + 1 );
+
+ lineDistances[ i ] = ( i === 0 ) ? 0 : lineDistances[ i - 1 ];
+ lineDistances[ i + 1 ] = lineDistances[ i ] + _start$1.distanceTo( _end$1 );
+
+ }
+
+ geometry.setAttribute( 'lineDistance', new Float32BufferAttribute( lineDistances, 1 ) );
+
+ } else {
+
+ console.warn( 'THREE.LineSegments.computeLineDistances(): Computation only possible with non-indexed BufferGeometry.' );
+
+ }
+
+ } else if ( geometry.isGeometry ) {
+
+ console.error( 'THREE.LineSegments.computeLineDistances() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+ }
+
+ return this;
+
+ }
+
+} );
+
+function LineLoop( geometry, material ) {
+
+ Line.call( this, geometry, material );
+
+ this.type = 'LineLoop';
+
+}
+
+LineLoop.prototype = Object.assign( Object.create( Line.prototype ), {
+
+ constructor: LineLoop,
+
+ isLineLoop: true,
+
+} );
+
+/**
+ * parameters = {
+ * color: <hex>,
+ * opacity: <float>,
+ * map: new THREE.Texture( <Image> ),
+ * alphaMap: new THREE.Texture( <Image> ),
+ *
+ * size: <float>,
+ * sizeAttenuation: <bool>
+ *
+ * morphTargets: <bool>
+ * }
+ */
+
+function PointsMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.type = 'PointsMaterial';
+
+ this.color = new Color( 0xffffff );
+
+ this.map = null;
+
+ this.alphaMap = null;
+
+ this.size = 1;
+ this.sizeAttenuation = true;
+
+ this.morphTargets = false;
+
+ this.setValues( parameters );
+
+}
+
+PointsMaterial.prototype = Object.create( Material.prototype );
+PointsMaterial.prototype.constructor = PointsMaterial;
+
+PointsMaterial.prototype.isPointsMaterial = true;
+
+PointsMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.color.copy( source.color );
+
+ this.map = source.map;
+
+ this.alphaMap = source.alphaMap;
+
+ this.size = source.size;
+ this.sizeAttenuation = source.sizeAttenuation;
+
+ this.morphTargets = source.morphTargets;
+
+ return this;
+
+};
+
+const _inverseMatrix$2 = new Matrix4();
+const _ray$2 = new Ray();
+const _sphere$3 = new Sphere();
+const _position$1 = new Vector3();
+
+function Points( geometry = new BufferGeometry(), material = new PointsMaterial() ) {
+
+ Object3D.call( this );
+
+ this.type = 'Points';
+
+ this.geometry = geometry;
+ this.material = material;
+
+ this.updateMorphTargets();
+
+}
+
+Points.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+ constructor: Points,
+
+ isPoints: true,
+
+ copy: function ( source ) {
+
+ Object3D.prototype.copy.call( this, source );
+
+ this.material = source.material;
+ this.geometry = source.geometry;
+
+ return this;
+
+ },
+
+ raycast: function ( raycaster, intersects ) {
+
+ const geometry = this.geometry;
+ const matrixWorld = this.matrixWorld;
+ const threshold = raycaster.params.Points.threshold;
+
+ // Checking boundingSphere distance to ray
+
+ if ( geometry.boundingSphere === null ) geometry.computeBoundingSphere();
+
+ _sphere$3.copy( geometry.boundingSphere );
+ _sphere$3.applyMatrix4( matrixWorld );
+ _sphere$3.radius += threshold;
+
+ if ( raycaster.ray.intersectsSphere( _sphere$3 ) === false ) return;
+
+ //
+
+ _inverseMatrix$2.copy( matrixWorld ).invert();
+ _ray$2.copy( raycaster.ray ).applyMatrix4( _inverseMatrix$2 );
+
+ const localThreshold = threshold / ( ( this.scale.x + this.scale.y + this.scale.z ) / 3 );
+ const localThresholdSq = localThreshold * localThreshold;
+
+ if ( geometry.isBufferGeometry ) {
+
+ const index = geometry.index;
+ const attributes = geometry.attributes;
+ const positionAttribute = attributes.position;
+
+ if ( index !== null ) {
+
+ const indices = index.array;
+
+ for ( let i = 0, il = indices.length; i < il; i ++ ) {
+
+ const a = indices[ i ];
+
+ _position$1.fromBufferAttribute( positionAttribute, a );
+
+ testPoint( _position$1, a, localThresholdSq, matrixWorld, raycaster, intersects, this );
+
+ }
+
+ } else {
+
+ for ( let i = 0, l = positionAttribute.count; i < l; i ++ ) {
+
+ _position$1.fromBufferAttribute( positionAttribute, i );
+
+ testPoint( _position$1, i, localThresholdSq, matrixWorld, raycaster, intersects, this );
+
+ }
+
+ }
+
+ } else {
+
+ console.error( 'THREE.Points.raycast() no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+ }
+
+ },
+
+ updateMorphTargets: function () {
+
+ const geometry = this.geometry;
+
+ if ( geometry.isBufferGeometry ) {
+
+ const morphAttributes = geometry.morphAttributes;
+ const keys = Object.keys( morphAttributes );
+
+ if ( keys.length > 0 ) {
+
+ const morphAttribute = morphAttributes[ keys[ 0 ] ];
+
+ if ( morphAttribute !== undefined ) {
+
+ this.morphTargetInfluences = [];
+ this.morphTargetDictionary = {};
+
+ for ( let m = 0, ml = morphAttribute.length; m < ml; m ++ ) {
+
+ const name = morphAttribute[ m ].name || String( m );
+
+ this.morphTargetInfluences.push( 0 );
+ this.morphTargetDictionary[ name ] = m;
+
+ }
+
+ }
+
+ }
+
+ } else {
+
+ const morphTargets = geometry.morphTargets;
+
+ if ( morphTargets !== undefined && morphTargets.length > 0 ) {
+
+ console.error( 'THREE.Points.updateMorphTargets() does not support THREE.Geometry. Use THREE.BufferGeometry instead.' );
+
+ }
+
+ }
+
+ }
+
+} );
+
+function testPoint( point, index, localThresholdSq, matrixWorld, raycaster, intersects, object ) {
+
+ const rayPointDistanceSq = _ray$2.distanceSqToPoint( point );
+
+ if ( rayPointDistanceSq < localThresholdSq ) {
+
+ const intersectPoint = new Vector3();
+
+ _ray$2.closestPointToPoint( point, intersectPoint );
+ intersectPoint.applyMatrix4( matrixWorld );
+
+ const distance = raycaster.ray.origin.distanceTo( intersectPoint );
+
+ if ( distance < raycaster.near || distance > raycaster.far ) return;
+
+ intersects.push( {
+
+ distance: distance,
+ distanceToRay: Math.sqrt( rayPointDistanceSq ),
+ point: intersectPoint,
+ index: index,
+ face: null,
+ object: object
+
+ } );
+
+ }
+
+}
+
+function VideoTexture( video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
+
+ Texture.call( this, video, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+ this.format = format !== undefined ? format : RGBFormat;
+
+ this.minFilter = minFilter !== undefined ? minFilter : LinearFilter;
+ this.magFilter = magFilter !== undefined ? magFilter : LinearFilter;
+
+ this.generateMipmaps = false;
+
+ const scope = this;
+
+ function updateVideo() {
+
+ scope.needsUpdate = true;
+ video.requestVideoFrameCallback( updateVideo );
+
+ }
+
+ if ( 'requestVideoFrameCallback' in video ) {
+
+ video.requestVideoFrameCallback( updateVideo );
+
+ }
+
+}
+
+VideoTexture.prototype = Object.assign( Object.create( Texture.prototype ), {
+
+ constructor: VideoTexture,
+
+ clone: function () {
+
+ return new this.constructor( this.image ).copy( this );
+
+ },
+
+ isVideoTexture: true,
+
+ update: function () {
+
+ const video = this.image;
+ const hasVideoFrameCallback = 'requestVideoFrameCallback' in video;
+
+ if ( hasVideoFrameCallback === false && video.readyState >= video.HAVE_CURRENT_DATA ) {
+
+ this.needsUpdate = true;
+
+ }
+
+ }
+
+} );
+
+function CompressedTexture( mipmaps, width, height, format, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, encoding ) {
+
+ Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy, encoding );
+
+ this.image = { width: width, height: height };
+ this.mipmaps = mipmaps;
+
+ // no flipping for cube textures
+ // (also flipping doesn't work for compressed textures )
+
+ this.flipY = false;
+
+ // can't generate mipmaps for compressed textures
+ // mips must be embedded in DDS files
+
+ this.generateMipmaps = false;
+
+}
+
+CompressedTexture.prototype = Object.create( Texture.prototype );
+CompressedTexture.prototype.constructor = CompressedTexture;
+
+CompressedTexture.prototype.isCompressedTexture = true;
+
+function CanvasTexture( canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy ) {
+
+ Texture.call( this, canvas, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+ this.needsUpdate = true;
+
+}
+
+CanvasTexture.prototype = Object.create( Texture.prototype );
+CanvasTexture.prototype.constructor = CanvasTexture;
+CanvasTexture.prototype.isCanvasTexture = true;
+
+function DepthTexture( width, height, type, mapping, wrapS, wrapT, magFilter, minFilter, anisotropy, format ) {
+
+ format = format !== undefined ? format : DepthFormat;
+
+ if ( format !== DepthFormat && format !== DepthStencilFormat ) {
+
+ throw new Error( 'DepthTexture format must be either THREE.DepthFormat or THREE.DepthStencilFormat' );
+
+ }
+
+ if ( type === undefined && format === DepthFormat ) type = UnsignedShortType;
+ if ( type === undefined && format === DepthStencilFormat ) type = UnsignedInt248Type;
+
+ Texture.call( this, null, mapping, wrapS, wrapT, magFilter, minFilter, format, type, anisotropy );
+
+ this.image = { width: width, height: height };
+
+ this.magFilter = magFilter !== undefined ? magFilter : NearestFilter;
+ this.minFilter = minFilter !== undefined ? minFilter : NearestFilter;
+
+ this.flipY = false;
+ this.generateMipmaps = false;
+
+}
+
+DepthTexture.prototype = Object.create( Texture.prototype );
+DepthTexture.prototype.constructor = DepthTexture;
+DepthTexture.prototype.isDepthTexture = true;
+
+class CircleGeometry extends BufferGeometry {
+
+ constructor( radius = 1, segments = 8, thetaStart = 0, thetaLength = Math.PI * 2 ) {
+
+ super();
+
+ this.type = 'CircleGeometry';
+
+ this.parameters = {
+ radius: radius,
+ segments: segments,
+ thetaStart: thetaStart,
+ thetaLength: thetaLength
+ };
+
+ segments = Math.max( 3, segments );
+
+ // buffers
+
+ const indices = [];
+ const vertices = [];
+ const normals = [];
+ const uvs = [];
+
+ // helper variables
+
+ const vertex = new Vector3();
+ const uv = new Vector2();
+
+ // center point
+
+ vertices.push( 0, 0, 0 );
+ normals.push( 0, 0, 1 );
+ uvs.push( 0.5, 0.5 );
+
+ for ( let s = 0, i = 3; s <= segments; s ++, i += 3 ) {
+
+ const segment = thetaStart + s / segments * thetaLength;
+
+ // vertex
+
+ vertex.x = radius * Math.cos( segment );
+ vertex.y = radius * Math.sin( segment );
+
+ vertices.push( vertex.x, vertex.y, vertex.z );
+
+ // normal
+
+ normals.push( 0, 0, 1 );
+
+ // uvs
+
+ uv.x = ( vertices[ i ] / radius + 1 ) / 2;
+ uv.y = ( vertices[ i + 1 ] / radius + 1 ) / 2;
+
+ uvs.push( uv.x, uv.y );
+
+ }
+
+ // indices
+
+ for ( let i = 1; i <= segments; i ++ ) {
+
+ indices.push( i, i + 1, 0 );
+
+ }
+
+ // build geometry
+
+ this.setIndex( indices );
+ this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+ this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+ this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+ }
+
+}
+
+new Vector3();
+new Vector3();
+new Vector3();
+new Triangle();
+
+/**
+ * Port from https://github.com/mapbox/earcut (v2.2.2)
+ */
+
+const Earcut = {
+
+ triangulate: function ( data, holeIndices, dim ) {
+
+ dim = dim || 2;
+
+ const hasHoles = holeIndices && holeIndices.length;
+ const outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length;
+ let outerNode = linkedList$1( data, 0, outerLen, dim, true );
+ const triangles = [];
+
+ if ( ! outerNode || outerNode.next === outerNode.prev ) return triangles;
+
+ let minX, minY, maxX, maxY, x, y, invSize;
+
+ if ( hasHoles ) outerNode = eliminateHoles$1( data, holeIndices, outerNode, dim );
+
+ // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
+ if ( data.length > 80 * dim ) {
+
+ minX = maxX = data[ 0 ];
+ minY = maxY = data[ 1 ];
+
+ for ( let i = dim; i < outerLen; i += dim ) {
+
+ x = data[ i ];
+ y = data[ i + 1 ];
+ if ( x < minX ) minX = x;
+ if ( y < minY ) minY = y;
+ if ( x > maxX ) maxX = x;
+ if ( y > maxY ) maxY = y;
+
+ }
+
+ // minX, minY and invSize are later used to transform coords into integers for z-order calculation
+ invSize = Math.max( maxX - minX, maxY - minY );
+ invSize = invSize !== 0 ? 1 / invSize : 0;
+
+ }
+
+ earcutLinked$1( outerNode, triangles, dim, minX, minY, invSize );
+
+ return triangles;
+
+ }
+
+};
+
+// create a circular doubly linked list from polygon points in the specified winding order
+function linkedList$1( data, start, end, dim, clockwise ) {
+
+ let i, last;
+
+ if ( clockwise === ( signedArea$2( data, start, end, dim ) > 0 ) ) {
+
+ for ( i = start; i < end; i += dim ) last = insertNode$2( i, data[ i ], data[ i + 1 ], last );
+
+ } else {
+
+ for ( i = end - dim; i >= start; i -= dim ) last = insertNode$2( i, data[ i ], data[ i + 1 ], last );
+
+ }
+
+ if ( last && equals$2( last, last.next ) ) {
+
+ removeNode$2( last );
+ last = last.next;
+
+ }
+
+ return last;
+
+}
+
+// eliminate colinear or duplicate points
+function filterPoints$1( start, end ) {
+
+ if ( ! start ) return start;
+ if ( ! end ) end = start;
+
+ let p = start,
+ again;
+ do {
+
+ again = false;
+
+ if ( ! p.steiner && ( equals$2( p, p.next ) || area$1( p.prev, p, p.next ) === 0 ) ) {
+
+ removeNode$2( p );
+ p = end = p.prev;
+ if ( p === p.next ) break;
+ again = true;
+
+ } else {
+
+ p = p.next;
+
+ }
+
+ } while ( again || p !== end );
+
+ return end;
+
+}
+
+// main ear slicing loop which triangulates a polygon (given as a linked list)
+function earcutLinked$1( ear, triangles, dim, minX, minY, invSize, pass ) {
+
+ if ( ! ear ) return;
+
+ // interlink polygon nodes in z-order
+ if ( ! pass && invSize ) indexCurve$1( ear, minX, minY, invSize );
+
+ let stop = ear,
+ prev, next;
+
+ // iterate through ears, slicing them one by one
+ while ( ear.prev !== ear.next ) {
+
+ prev = ear.prev;
+ next = ear.next;
+
+ if ( invSize ? isEarHashed$1( ear, minX, minY, invSize ) : isEar$1( ear ) ) {
+
+ // cut off the triangle
+ triangles.push( prev.i / dim );
+ triangles.push( ear.i / dim );
+ triangles.push( next.i / dim );
+
+ removeNode$2( ear );
+
+ // skipping the next vertex leads to less sliver triangles
+ ear = next.next;
+ stop = next.next;
+
+ continue;
+
+ }
+
+ ear = next;
+
+ // if we looped through the whole remaining polygon and can't find any more ears
+ if ( ear === stop ) {
+
+ // try filtering points and slicing again
+ if ( ! pass ) {
+
+ earcutLinked$1( filterPoints$1( ear ), triangles, dim, minX, minY, invSize, 1 );
+
+ // if this didn't work, try curing all small self-intersections locally
+
+ } else if ( pass === 1 ) {
+
+ ear = cureLocalIntersections$1( filterPoints$1( ear ), triangles, dim );
+ earcutLinked$1( ear, triangles, dim, minX, minY, invSize, 2 );
+
+ // as a last resort, try splitting the remaining polygon into two
+
+ } else if ( pass === 2 ) {
+
+ splitEarcut$1( ear, triangles, dim, minX, minY, invSize );
+
+ }
+
+ break;
+
+ }
+
+ }
+
+}
+
+// check whether a polygon node forms a valid ear with adjacent nodes
+function isEar$1( ear ) {
+
+ const a = ear.prev,
+ b = ear,
+ c = ear.next;
+
+ if ( area$1( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
+
+ // now make sure we don't have other points inside the potential ear
+ let p = ear.next.next;
+
+ while ( p !== ear.prev ) {
+
+ if ( pointInTriangle$1( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
+ area$1( p.prev, p, p.next ) >= 0 ) return false;
+ p = p.next;
+
+ }
+
+ return true;
+
+}
+
+function isEarHashed$1( ear, minX, minY, invSize ) {
+
+ const a = ear.prev,
+ b = ear,
+ c = ear.next;
+
+ if ( area$1( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
+
+ // triangle bbox; min & max are calculated like this for speed
+ const minTX = a.x < b.x ? ( a.x < c.x ? a.x : c.x ) : ( b.x < c.x ? b.x : c.x ),
+ minTY = a.y < b.y ? ( a.y < c.y ? a.y : c.y ) : ( b.y < c.y ? b.y : c.y ),
+ maxTX = a.x > b.x ? ( a.x > c.x ? a.x : c.x ) : ( b.x > c.x ? b.x : c.x ),
+ maxTY = a.y > b.y ? ( a.y > c.y ? a.y : c.y ) : ( b.y > c.y ? b.y : c.y );
+
+ // z-order range for the current triangle bbox;
+ const minZ = zOrder$1( minTX, minTY, minX, minY, invSize ),
+ maxZ = zOrder$1( maxTX, maxTY, minX, minY, invSize );
+
+ let p = ear.prevZ,
+ n = ear.nextZ;
+
+ // look for points inside the triangle in both directions
+ while ( p && p.z >= minZ && n && n.z <= maxZ ) {
+
+ if ( p !== ear.prev && p !== ear.next &&
+ pointInTriangle$1( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
+ area$1( p.prev, p, p.next ) >= 0 ) return false;
+ p = p.prevZ;
+
+ if ( n !== ear.prev && n !== ear.next &&
+ pointInTriangle$1( a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y ) &&
+ area$1( n.prev, n, n.next ) >= 0 ) return false;
+ n = n.nextZ;
+
+ }
+
+ // look for remaining points in decreasing z-order
+ while ( p && p.z >= minZ ) {
+
+ if ( p !== ear.prev && p !== ear.next &&
+ pointInTriangle$1( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
+ area$1( p.prev, p, p.next ) >= 0 ) return false;
+ p = p.prevZ;
+
+ }
+
+ // look for remaining points in increasing z-order
+ while ( n && n.z <= maxZ ) {
+
+ if ( n !== ear.prev && n !== ear.next &&
+ pointInTriangle$1( a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y ) &&
+ area$1( n.prev, n, n.next ) >= 0 ) return false;
+ n = n.nextZ;
+
+ }
+
+ return true;
+
+}
+
+// go through all polygon nodes and cure small local self-intersections
+function cureLocalIntersections$1( start, triangles, dim ) {
+
+ let p = start;
+ do {
+
+ const a = p.prev,
+ b = p.next.next;
+
+ if ( ! equals$2( a, b ) && intersects$2( a, p, p.next, b ) && locallyInside$1( a, b ) && locallyInside$1( b, a ) ) {
+
+ triangles.push( a.i / dim );
+ triangles.push( p.i / dim );
+ triangles.push( b.i / dim );
+
+ // remove two nodes involved
+ removeNode$2( p );
+ removeNode$2( p.next );
+
+ p = start = b;
+
+ }
+
+ p = p.next;
+
+ } while ( p !== start );
+
+ return filterPoints$1( p );
+
+}
+
+// try splitting polygon into two and triangulate them independently
+function splitEarcut$1( start, triangles, dim, minX, minY, invSize ) {
+
+ // look for a valid diagonal that divides the polygon into two
+ let a = start;
+ do {
+
+ let b = a.next.next;
+ while ( b !== a.prev ) {
+
+ if ( a.i !== b.i && isValidDiagonal$1( a, b ) ) {
+
+ // split the polygon in two by the diagonal
+ let c = splitPolygon$1( a, b );
+
+ // filter colinear points around the cuts
+ a = filterPoints$1( a, a.next );
+ c = filterPoints$1( c, c.next );
+
+ // run earcut on each half
+ earcutLinked$1( a, triangles, dim, minX, minY, invSize );
+ earcutLinked$1( c, triangles, dim, minX, minY, invSize );
+ return;
+
+ }
+
+ b = b.next;
+
+ }
+
+ a = a.next;
+
+ } while ( a !== start );
+
+}
+
+// link every hole into the outer loop, producing a single-ring polygon without holes
+function eliminateHoles$1( data, holeIndices, outerNode, dim ) {
+
+ const queue = [];
+ let i, len, start, end, list;
+
+ for ( i = 0, len = holeIndices.length; i < len; i ++ ) {
+
+ start = holeIndices[ i ] * dim;
+ end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length;
+ list = linkedList$1( data, start, end, dim, false );
+ if ( list === list.next ) list.steiner = true;
+ queue.push( getLeftmost$1( list ) );
+
+ }
+
+ queue.sort( compareX$1 );
+
+ // process holes from left to right
+ for ( i = 0; i < queue.length; i ++ ) {
+
+ eliminateHole$1( queue[ i ], outerNode );
+ outerNode = filterPoints$1( outerNode, outerNode.next );
+
+ }
+
+ return outerNode;
+
+}
+
+function compareX$1( a, b ) {
+
+ return a.x - b.x;
+
+}
+
+// find a bridge between vertices that connects hole with an outer ring and and link it
+function eliminateHole$1( hole, outerNode ) {
+
+ outerNode = findHoleBridge$1( hole, outerNode );
+ if ( outerNode ) {
+
+ const b = splitPolygon$1( outerNode, hole );
+
+ // filter collinear points around the cuts
+ filterPoints$1( outerNode, outerNode.next );
+ filterPoints$1( b, b.next );
+
+ }
+
+}
+
+// David Eberly's algorithm for finding a bridge between hole and outer polygon
+function findHoleBridge$1( hole, outerNode ) {
+
+ let p = outerNode;
+ const hx = hole.x;
+ const hy = hole.y;
+ let qx = - Infinity, m;
+
+ // find a segment intersected by a ray from the hole's leftmost point to the left;
+ // segment's endpoint with lesser x will be potential connection point
+ do {
+
+ if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) {
+
+ const x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );
+ if ( x <= hx && x > qx ) {
+
+ qx = x;
+ if ( x === hx ) {
+
+ if ( hy === p.y ) return p;
+ if ( hy === p.next.y ) return p.next;
+
+ }
+
+ m = p.x < p.next.x ? p : p.next;
+
+ }
+
+ }
+
+ p = p.next;
+
+ } while ( p !== outerNode );
+
+ if ( ! m ) return null;
+
+ if ( hx === qx ) return m; // hole touches outer segment; pick leftmost endpoint
+
+ // look for points inside the triangle of hole point, segment intersection and endpoint;
+ // if there are no points found, we have a valid connection;
+ // otherwise choose the point of the minimum angle with the ray as connection point
+
+ const stop = m,
+ mx = m.x,
+ my = m.y;
+ let tanMin = Infinity, tan;
+
+ p = m;
+
+ do {
+
+ if ( hx >= p.x && p.x >= mx && hx !== p.x &&
+ pointInTriangle$1( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {
+
+ tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential
+
+ if ( locallyInside$1( p, hole ) && ( tan < tanMin || ( tan === tanMin && ( p.x > m.x || ( p.x === m.x && sectorContainsSector$1( m, p ) ) ) ) ) ) {
+
+ m = p;
+ tanMin = tan;
+
+ }
+
+ }
+
+ p = p.next;
+
+ } while ( p !== stop );
+
+ return m;
+
+}
+
+// whether sector in vertex m contains sector in vertex p in the same coordinates
+function sectorContainsSector$1( m, p ) {
+
+ return area$1( m.prev, m, p.prev ) < 0 && area$1( p.next, m, m.next ) < 0;
+
+}
+
+// interlink polygon nodes in z-order
+function indexCurve$1( start, minX, minY, invSize ) {
+
+ let p = start;
+ do {
+
+ if ( p.z === null ) p.z = zOrder$1( p.x, p.y, minX, minY, invSize );
+ p.prevZ = p.prev;
+ p.nextZ = p.next;
+ p = p.next;
+
+ } while ( p !== start );
+
+ p.prevZ.nextZ = null;
+ p.prevZ = null;
+
+ sortLinked$1( p );
+
+}
+
+// Simon Tatham's linked list merge sort algorithm
+// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
+function sortLinked$1( list ) {
+
+ let i, p, q, e, tail, numMerges, pSize, qSize,
+ inSize = 1;
+
+ do {
+
+ p = list;
+ list = null;
+ tail = null;
+ numMerges = 0;
+
+ while ( p ) {
+
+ numMerges ++;
+ q = p;
+ pSize = 0;
+ for ( i = 0; i < inSize; i ++ ) {
+
+ pSize ++;
+ q = q.nextZ;
+ if ( ! q ) break;
+
+ }
+
+ qSize = inSize;
+
+ while ( pSize > 0 || ( qSize > 0 && q ) ) {
+
+ if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) {
+
+ e = p;
+ p = p.nextZ;
+ pSize --;
+
+ } else {
+
+ e = q;
+ q = q.nextZ;
+ qSize --;
+
+ }
+
+ if ( tail ) tail.nextZ = e;
+ else list = e;
+
+ e.prevZ = tail;
+ tail = e;
+
+ }
+
+ p = q;
+
+ }
+
+ tail.nextZ = null;
+ inSize *= 2;
+
+ } while ( numMerges > 1 );
+
+ return list;
+
+}
+
+// z-order of a point given coords and inverse of the longer side of data bbox
+function zOrder$1( x, y, minX, minY, invSize ) {
+
+ // coords are transformed into non-negative 15-bit integer range
+ x = 32767 * ( x - minX ) * invSize;
+ y = 32767 * ( y - minY ) * invSize;
+
+ x = ( x | ( x << 8 ) ) & 0x00FF00FF;
+ x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
+ x = ( x | ( x << 2 ) ) & 0x33333333;
+ x = ( x | ( x << 1 ) ) & 0x55555555;
+
+ y = ( y | ( y << 8 ) ) & 0x00FF00FF;
+ y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
+ y = ( y | ( y << 2 ) ) & 0x33333333;
+ y = ( y | ( y << 1 ) ) & 0x55555555;
+
+ return x | ( y << 1 );
+
+}
+
+// find the leftmost node of a polygon ring
+function getLeftmost$1( start ) {
+
+ let p = start,
+ leftmost = start;
+ do {
+
+ if ( p.x < leftmost.x || ( p.x === leftmost.x && p.y < leftmost.y ) ) leftmost = p;
+ p = p.next;
+
+ } while ( p !== start );
+
+ return leftmost;
+
+}
+
+// check if a point lies within a convex triangle
+function pointInTriangle$1( ax, ay, bx, by, cx, cy, px, py ) {
+
+ return ( cx - px ) * ( ay - py ) - ( ax - px ) * ( cy - py ) >= 0 &&
+ ( ax - px ) * ( by - py ) - ( bx - px ) * ( ay - py ) >= 0 &&
+ ( bx - px ) * ( cy - py ) - ( cx - px ) * ( by - py ) >= 0;
+
+}
+
+// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
+function isValidDiagonal$1( a, b ) {
+
+ return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon$1( a, b ) && // dones't intersect other edges
+ ( locallyInside$1( a, b ) && locallyInside$1( b, a ) && middleInside$1( a, b ) && // locally visible
+ ( area$1( a.prev, a, b.prev ) || area$1( a, b.prev, b ) ) || // does not create opposite-facing sectors
+ equals$2( a, b ) && area$1( a.prev, a, a.next ) > 0 && area$1( b.prev, b, b.next ) > 0 ); // special zero-length case
+
+}
+
+// signed area of a triangle
+function area$1( p, q, r ) {
+
+ return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );
+
+}
+
+// check if two points are equal
+function equals$2( p1, p2 ) {
+
+ return p1.x === p2.x && p1.y === p2.y;
+
+}
+
+// check if two segments intersect
+function intersects$2( p1, q1, p2, q2 ) {
+
+ const o1 = sign$2( area$1( p1, q1, p2 ) );
+ const o2 = sign$2( area$1( p1, q1, q2 ) );
+ const o3 = sign$2( area$1( p2, q2, p1 ) );
+ const o4 = sign$2( area$1( p2, q2, q1 ) );
+
+ if ( o1 !== o2 && o3 !== o4 ) return true; // general case
+
+ if ( o1 === 0 && onSegment$1( p1, p2, q1 ) ) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
+ if ( o2 === 0 && onSegment$1( p1, q2, q1 ) ) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
+ if ( o3 === 0 && onSegment$1( p2, p1, q2 ) ) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
+ if ( o4 === 0 && onSegment$1( p2, q1, q2 ) ) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2
+
+ return false;
+
+}
+
+// for collinear points p, q, r, check if point q lies on segment pr
+function onSegment$1( p, q, r ) {
+
+ return q.x <= Math.max( p.x, r.x ) && q.x >= Math.min( p.x, r.x ) && q.y <= Math.max( p.y, r.y ) && q.y >= Math.min( p.y, r.y );
+
+}
+
+function sign$2( num ) {
+
+ return num > 0 ? 1 : num < 0 ? - 1 : 0;
+
+}
+
+// check if a polygon diagonal intersects any polygon segments
+function intersectsPolygon$1( a, b ) {
+
+ let p = a;
+ do {
+
+ if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
+ intersects$2( p, p.next, a, b ) ) return true;
+ p = p.next;
+
+ } while ( p !== a );
+
+ return false;
+
+}
+
+// check if a polygon diagonal is locally inside the polygon
+function locallyInside$1( a, b ) {
+
+ return area$1( a.prev, a, a.next ) < 0 ?
+ area$1( a, b, a.next ) >= 0 && area$1( a, a.prev, b ) >= 0 :
+ area$1( a, b, a.prev ) < 0 || area$1( a, a.next, b ) < 0;
+
+}
+
+// check if the middle point of a polygon diagonal is inside the polygon
+function middleInside$1( a, b ) {
+
+ let p = a,
+ inside = false;
+ const px = ( a.x + b.x ) / 2,
+ py = ( a.y + b.y ) / 2;
+ do {
+
+ if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y &&
+ ( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) )
+ inside = ! inside;
+ p = p.next;
+
+ } while ( p !== a );
+
+ return inside;
+
+}
+
+// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
+// if one belongs to the outer ring and another to a hole, it merges it into a single ring
+function splitPolygon$1( a, b ) {
+
+ const a2 = new Node$1( a.i, a.x, a.y ),
+ b2 = new Node$1( b.i, b.x, b.y ),
+ an = a.next,
+ bp = b.prev;
+
+ a.next = b;
+ b.prev = a;
+
+ a2.next = an;
+ an.prev = a2;
+
+ b2.next = a2;
+ a2.prev = b2;
+
+ bp.next = b2;
+ b2.prev = bp;
+
+ return b2;
+
+}
+
+// create a node and optionally link it with previous one (in a circular doubly linked list)
+function insertNode$2( i, x, y, last ) {
+
+ const p = new Node$1( i, x, y );
+
+ if ( ! last ) {
+
+ p.prev = p;
+ p.next = p;
+
+ } else {
+
+ p.next = last.next;
+ p.prev = last;
+ last.next.prev = p;
+ last.next = p;
+
+ }
+
+ return p;
+
+}
+
+function removeNode$2( p ) {
+
+ p.next.prev = p.prev;
+ p.prev.next = p.next;
+
+ if ( p.prevZ ) p.prevZ.nextZ = p.nextZ;
+ if ( p.nextZ ) p.nextZ.prevZ = p.prevZ;
+
+}
+
+function Node$1( i, x, y ) {
+
+ // vertex index in coordinates array
+ this.i = i;
+
+ // vertex coordinates
+ this.x = x;
+ this.y = y;
+
+ // previous and next vertex nodes in a polygon ring
+ this.prev = null;
+ this.next = null;
+
+ // z-order curve value
+ this.z = null;
+
+ // previous and next nodes in z-order
+ this.prevZ = null;
+ this.nextZ = null;
+
+ // indicates whether this is a steiner point
+ this.steiner = false;
+
+}
+
+function signedArea$2( data, start, end, dim ) {
+
+ let sum = 0;
+ for ( let i = start, j = end - dim; i < end; i += dim ) {
+
+ sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );
+ j = i;
+
+ }
+
+ return sum;
+
+}
+
+const ShapeUtils = {
+
+ // calculate area of the contour polygon
+
+ area: function ( contour ) {
+
+ const n = contour.length;
+ let a = 0.0;
+
+ for ( let p = n - 1, q = 0; q < n; p = q ++ ) {
+
+ a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
+
+ }
+
+ return a * 0.5;
+
+ },
+
+ isClockWise: function ( pts ) {
+
+ return ShapeUtils.area( pts ) < 0;
+
+ },
+
+ triangulateShape: function ( contour, holes ) {
+
+ const vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ]
+ const holeIndices = []; // array of hole indices
+ const faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ]
+
+ removeDupEndPts( contour );
+ addContour( vertices, contour );
+
+ //
+
+ let holeIndex = contour.length;
+
+ holes.forEach( removeDupEndPts );
+
+ for ( let i = 0; i < holes.length; i ++ ) {
+
+ holeIndices.push( holeIndex );
+ holeIndex += holes[ i ].length;
+ addContour( vertices, holes[ i ] );
+
+ }
+
+ //
+
+ const triangles = Earcut.triangulate( vertices, holeIndices );
+
+ //
+
+ for ( let i = 0; i < triangles.length; i += 3 ) {
+
+ faces.push( triangles.slice( i, i + 3 ) );
+
+ }
+
+ return faces;
+
+ }
+
+};
+
+function removeDupEndPts( points ) {
+
+ const l = points.length;
+
+ if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {
+
+ points.pop();
+
+ }
+
+}
+
+function addContour( vertices, contour ) {
+
+ for ( let i = 0; i < contour.length; i ++ ) {
+
+ vertices.push( contour[ i ].x );
+ vertices.push( contour[ i ].y );
+
+ }
+
+}
+
+/**
+ * Creates extruded geometry from a path shape.
+ *
+ * parameters = {
+ *
+ * curveSegments: <int>, // number of points on the curves
+ * steps: <int>, // number of points for z-side extrusions / used for subdividing segments of extrude spline too
+ * depth: <float>, // Depth to extrude the shape
+ *
+ * bevelEnabled: <bool>, // turn on bevel
+ * bevelThickness: <float>, // how deep into the original shape bevel goes
+ * bevelSize: <float>, // how far from shape outline (including bevelOffset) is bevel
+ * bevelOffset: <float>, // how far from shape outline does bevel start
+ * bevelSegments: <int>, // number of bevel layers
+ *
+ * extrudePath: <THREE.Curve> // curve to extrude shape along
+ *
+ * UVGenerator: <Object> // object that provides UV generator functions
+ *
+ * }
+ */
+
+class ExtrudeGeometry extends BufferGeometry {
+
+ constructor( shapes, options ) {
+
+ super();
+
+ this.type = 'ExtrudeGeometry';
+
+ this.parameters = {
+ shapes: shapes,
+ options: options
+ };
+
+ shapes = Array.isArray( shapes ) ? shapes : [ shapes ];
+
+ const scope = this;
+
+ const verticesArray = [];
+ const uvArray = [];
+
+ for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+
+ const shape = shapes[ i ];
+ addShape( shape );
+
+ }
+
+ // build geometry
+
+ this.setAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) );
+ this.setAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) );
+
+ this.computeVertexNormals();
+
+ // functions
+
+ function addShape( shape ) {
+
+ const placeholder = [];
+
+ // options
+
+ const curveSegments = options.curveSegments !== undefined ? options.curveSegments : 12;
+ const steps = options.steps !== undefined ? options.steps : 1;
+ let depth = options.depth !== undefined ? options.depth : 100;
+
+ let bevelEnabled = options.bevelEnabled !== undefined ? options.bevelEnabled : true;
+ let bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6;
+ let bevelSize = options.bevelSize !== undefined ? options.bevelSize : bevelThickness - 2;
+ let bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0;
+ let bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3;
+
+ const extrudePath = options.extrudePath;
+
+ const uvgen = options.UVGenerator !== undefined ? options.UVGenerator : WorldUVGenerator;
+
+ // deprecated options
+
+ if ( options.amount !== undefined ) {
+
+ console.warn( 'THREE.ExtrudeBufferGeometry: amount has been renamed to depth.' );
+ depth = options.amount;
+
+ }
+
+ //
+
+ let extrudePts, extrudeByPath = false;
+ let splineTube, binormal, normal, position2;
+
+ if ( extrudePath ) {
+
+ extrudePts = extrudePath.getSpacedPoints( steps );
+
+ extrudeByPath = true;
+ bevelEnabled = false; // bevels not supported for path extrusion
+
+ // SETUP TNB variables
+
+ // TODO1 - have a .isClosed in spline?
+
+ splineTube = extrudePath.computeFrenetFrames( steps, false );
+
+ // console.log(splineTube, 'splineTube', splineTube.normals.length, 'steps', steps, 'extrudePts', extrudePts.length);
+
+ binormal = new Vector3();
+ normal = new Vector3();
+ position2 = new Vector3();
+
+ }
+
+ // Safeguards if bevels are not enabled
+
+ if ( ! bevelEnabled ) {
+
+ bevelSegments = 0;
+ bevelThickness = 0;
+ bevelSize = 0;
+ bevelOffset = 0;
+
+ }
+
+ // Variables initialization
+
+ const shapePoints = shape.extractPoints( curveSegments );
+
+ let vertices = shapePoints.shape;
+ const holes = shapePoints.holes;
+
+ const reverse = ! ShapeUtils.isClockWise( vertices );
+
+ if ( reverse ) {
+
+ vertices = vertices.reverse();
+
+ // Maybe we should also check if holes are in the opposite direction, just to be safe ...
+
+ for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+ const ahole = holes[ h ];
+
+ if ( ShapeUtils.isClockWise( ahole ) ) {
+
+ holes[ h ] = ahole.reverse();
+
+ }
+
+ }
+
+ }
+
+
+ const faces = ShapeUtils.triangulateShape( vertices, holes );
+
+ /* Vertices */
+
+ const contour = vertices; // vertices has all points but contour has only points of circumference
+
+ for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+ const ahole = holes[ h ];
+
+ vertices = vertices.concat( ahole );
+
+ }
+
+
+ function scalePt2( pt, vec, size ) {
+
+ if ( ! vec ) console.error( 'THREE.ExtrudeGeometry: vec does not exist' );
+
+ return vec.clone().multiplyScalar( size ).add( pt );
+
+ }
+
+ const vlen = vertices.length, flen = faces.length;
+
+
+ // Find directions for point movement
+
+
+ function getBevelVec( inPt, inPrev, inNext ) {
+
+ // computes for inPt the corresponding point inPt' on a new contour
+ // shifted by 1 unit (length of normalized vector) to the left
+ // if we walk along contour clockwise, this new contour is outside the old one
+ //
+ // inPt' is the intersection of the two lines parallel to the two
+ // adjacent edges of inPt at a distance of 1 unit on the left side.
+
+ let v_trans_x, v_trans_y, shrink_by; // resulting translation vector for inPt
+
+ // good reading for geometry algorithms (here: line-line intersection)
+ // http://geomalgorithms.com/a05-_intersect-1.html
+
+ const v_prev_x = inPt.x - inPrev.x,
+ v_prev_y = inPt.y - inPrev.y;
+ const v_next_x = inNext.x - inPt.x,
+ v_next_y = inNext.y - inPt.y;
+
+ const v_prev_lensq = ( v_prev_x * v_prev_x + v_prev_y * v_prev_y );
+
+ // check for collinear edges
+ const collinear0 = ( v_prev_x * v_next_y - v_prev_y * v_next_x );
+
+ if ( Math.abs( collinear0 ) > Number.EPSILON ) {
+
+ // not collinear
+
+ // length of vectors for normalizing
+
+ const v_prev_len = Math.sqrt( v_prev_lensq );
+ const v_next_len = Math.sqrt( v_next_x * v_next_x + v_next_y * v_next_y );
+
+ // shift adjacent points by unit vectors to the left
+
+ const ptPrevShift_x = ( inPrev.x - v_prev_y / v_prev_len );
+ const ptPrevShift_y = ( inPrev.y + v_prev_x / v_prev_len );
+
+ const ptNextShift_x = ( inNext.x - v_next_y / v_next_len );
+ const ptNextShift_y = ( inNext.y + v_next_x / v_next_len );
+
+ // scaling factor for v_prev to intersection point
+
+ const sf = ( ( ptNextShift_x - ptPrevShift_x ) * v_next_y -
+ ( ptNextShift_y - ptPrevShift_y ) * v_next_x ) /
+ ( v_prev_x * v_next_y - v_prev_y * v_next_x );
+
+ // vector from inPt to intersection point
+
+ v_trans_x = ( ptPrevShift_x + v_prev_x * sf - inPt.x );
+ v_trans_y = ( ptPrevShift_y + v_prev_y * sf - inPt.y );
+
+ // Don't normalize!, otherwise sharp corners become ugly
+ // but prevent crazy spikes
+ const v_trans_lensq = ( v_trans_x * v_trans_x + v_trans_y * v_trans_y );
+ if ( v_trans_lensq <= 2 ) {
+
+ return new Vector2( v_trans_x, v_trans_y );
+
+ } else {
+
+ shrink_by = Math.sqrt( v_trans_lensq / 2 );
+
+ }
+
+ } else {
+
+ // handle special case of collinear edges
+
+ let direction_eq = false; // assumes: opposite
+
+ if ( v_prev_x > Number.EPSILON ) {
+
+ if ( v_next_x > Number.EPSILON ) {
+
+ direction_eq = true;
+
+ }
+
+ } else {
+
+ if ( v_prev_x < - Number.EPSILON ) {
+
+ if ( v_next_x < - Number.EPSILON ) {
+
+ direction_eq = true;
+
+ }
+
+ } else {
+
+ if ( Math.sign( v_prev_y ) === Math.sign( v_next_y ) ) {
+
+ direction_eq = true;
+
+ }
+
+ }
+
+ }
+
+ if ( direction_eq ) {
+
+ // console.log("Warning: lines are a straight sequence");
+ v_trans_x = - v_prev_y;
+ v_trans_y = v_prev_x;
+ shrink_by = Math.sqrt( v_prev_lensq );
+
+ } else {
+
+ // console.log("Warning: lines are a straight spike");
+ v_trans_x = v_prev_x;
+ v_trans_y = v_prev_y;
+ shrink_by = Math.sqrt( v_prev_lensq / 2 );
+
+ }
+
+ }
+
+ return new Vector2( v_trans_x / shrink_by, v_trans_y / shrink_by );
+
+ }
+
+
+ const contourMovements = [];
+
+ for ( let i = 0, il = contour.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+
+ if ( j === il ) j = 0;
+ if ( k === il ) k = 0;
+
+ // (j)---(i)---(k)
+ // console.log('i,j,k', i, j , k)
+
+ contourMovements[ i ] = getBevelVec( contour[ i ], contour[ j ], contour[ k ] );
+
+ }
+
+ const holesMovements = [];
+ let oneHoleMovements, verticesMovements = contourMovements.concat();
+
+ for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+ const ahole = holes[ h ];
+
+ oneHoleMovements = [];
+
+ for ( let i = 0, il = ahole.length, j = il - 1, k = i + 1; i < il; i ++, j ++, k ++ ) {
+
+ if ( j === il ) j = 0;
+ if ( k === il ) k = 0;
+
+ // (j)---(i)---(k)
+ oneHoleMovements[ i ] = getBevelVec( ahole[ i ], ahole[ j ], ahole[ k ] );
+
+ }
+
+ holesMovements.push( oneHoleMovements );
+ verticesMovements = verticesMovements.concat( oneHoleMovements );
+
+ }
+
+
+ // Loop bevelSegments, 1 for the front, 1 for the back
+
+ for ( let b = 0; b < bevelSegments; b ++ ) {
+
+ //for ( b = bevelSegments; b > 0; b -- ) {
+
+ const t = b / bevelSegments;
+ const z = bevelThickness * Math.cos( t * Math.PI / 2 );
+ const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
+
+ // contract shape
+
+ for ( let i = 0, il = contour.length; i < il; i ++ ) {
+
+ const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
+
+ v( vert.x, vert.y, - z );
+
+ }
+
+ // expand holes
+
+ for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+ const ahole = holes[ h ];
+ oneHoleMovements = holesMovements[ h ];
+
+ for ( let i = 0, il = ahole.length; i < il; i ++ ) {
+
+ const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
+
+ v( vert.x, vert.y, - z );
+
+ }
+
+ }
+
+ }
+
+ const bs = bevelSize + bevelOffset;
+
+ // Back facing vertices
+
+ for ( let i = 0; i < vlen; i ++ ) {
+
+ const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+
+ if ( ! extrudeByPath ) {
+
+ v( vert.x, vert.y, 0 );
+
+ } else {
+
+ // v( vert.x, vert.y + extrudePts[ 0 ].y, extrudePts[ 0 ].x );
+
+ normal.copy( splineTube.normals[ 0 ] ).multiplyScalar( vert.x );
+ binormal.copy( splineTube.binormals[ 0 ] ).multiplyScalar( vert.y );
+
+ position2.copy( extrudePts[ 0 ] ).add( normal ).add( binormal );
+
+ v( position2.x, position2.y, position2.z );
+
+ }
+
+ }
+
+ // Add stepped vertices...
+ // Including front facing vertices
+
+ for ( let s = 1; s <= steps; s ++ ) {
+
+ for ( let i = 0; i < vlen; i ++ ) {
+
+ const vert = bevelEnabled ? scalePt2( vertices[ i ], verticesMovements[ i ], bs ) : vertices[ i ];
+
+ if ( ! extrudeByPath ) {
+
+ v( vert.x, vert.y, depth / steps * s );
+
+ } else {
+
+ // v( vert.x, vert.y + extrudePts[ s - 1 ].y, extrudePts[ s - 1 ].x );
+
+ normal.copy( splineTube.normals[ s ] ).multiplyScalar( vert.x );
+ binormal.copy( splineTube.binormals[ s ] ).multiplyScalar( vert.y );
+
+ position2.copy( extrudePts[ s ] ).add( normal ).add( binormal );
+
+ v( position2.x, position2.y, position2.z );
+
+ }
+
+ }
+
+ }
+
+
+ // Add bevel segments planes
+
+ //for ( b = 1; b <= bevelSegments; b ++ ) {
+ for ( let b = bevelSegments - 1; b >= 0; b -- ) {
+
+ const t = b / bevelSegments;
+ const z = bevelThickness * Math.cos( t * Math.PI / 2 );
+ const bs = bevelSize * Math.sin( t * Math.PI / 2 ) + bevelOffset;
+
+ // contract shape
+
+ for ( let i = 0, il = contour.length; i < il; i ++ ) {
+
+ const vert = scalePt2( contour[ i ], contourMovements[ i ], bs );
+ v( vert.x, vert.y, depth + z );
+
+ }
+
+ // expand holes
+
+ for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+ const ahole = holes[ h ];
+ oneHoleMovements = holesMovements[ h ];
+
+ for ( let i = 0, il = ahole.length; i < il; i ++ ) {
+
+ const vert = scalePt2( ahole[ i ], oneHoleMovements[ i ], bs );
+
+ if ( ! extrudeByPath ) {
+
+ v( vert.x, vert.y, depth + z );
+
+ } else {
+
+ v( vert.x, vert.y + extrudePts[ steps - 1 ].y, extrudePts[ steps - 1 ].x + z );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ /* Faces */
+
+ // Top and bottom faces
+
+ buildLidFaces();
+
+ // Sides faces
+
+ buildSideFaces();
+
+
+ ///// Internal functions
+
+ function buildLidFaces() {
+
+ const start = verticesArray.length / 3;
+
+ if ( bevelEnabled ) {
+
+ let layer = 0; // steps + 1
+ let offset = vlen * layer;
+
+ // Bottom faces
+
+ for ( let i = 0; i < flen; i ++ ) {
+
+ const face = faces[ i ];
+ f3( face[ 2 ] + offset, face[ 1 ] + offset, face[ 0 ] + offset );
+
+ }
+
+ layer = steps + bevelSegments * 2;
+ offset = vlen * layer;
+
+ // Top faces
+
+ for ( let i = 0; i < flen; i ++ ) {
+
+ const face = faces[ i ];
+ f3( face[ 0 ] + offset, face[ 1 ] + offset, face[ 2 ] + offset );
+
+ }
+
+ } else {
+
+ // Bottom faces
+
+ for ( let i = 0; i < flen; i ++ ) {
+
+ const face = faces[ i ];
+ f3( face[ 2 ], face[ 1 ], face[ 0 ] );
+
+ }
+
+ // Top faces
+
+ for ( let i = 0; i < flen; i ++ ) {
+
+ const face = faces[ i ];
+ f3( face[ 0 ] + vlen * steps, face[ 1 ] + vlen * steps, face[ 2 ] + vlen * steps );
+
+ }
+
+ }
+
+ scope.addGroup( start, verticesArray.length / 3 - start, 0 );
+
+ }
+
+ // Create faces for the z-sides of the shape
+
+ function buildSideFaces() {
+
+ const start = verticesArray.length / 3;
+ let layeroffset = 0;
+ sidewalls( contour, layeroffset );
+ layeroffset += contour.length;
+
+ for ( let h = 0, hl = holes.length; h < hl; h ++ ) {
+
+ const ahole = holes[ h ];
+ sidewalls( ahole, layeroffset );
+
+ //, true
+ layeroffset += ahole.length;
+
+ }
+
+
+ scope.addGroup( start, verticesArray.length / 3 - start, 1 );
+
+
+ }
+
+ function sidewalls( contour, layeroffset ) {
+
+ let i = contour.length;
+
+ while ( -- i >= 0 ) {
+
+ const j = i;
+ let k = i - 1;
+ if ( k < 0 ) k = contour.length - 1;
+
+ //console.log('b', i,j, i-1, k,vertices.length);
+
+ for ( let s = 0, sl = ( steps + bevelSegments * 2 ); s < sl; s ++ ) {
+
+ const slen1 = vlen * s;
+ const slen2 = vlen * ( s + 1 );
+
+ const a = layeroffset + j + slen1,
+ b = layeroffset + k + slen1,
+ c = layeroffset + k + slen2,
+ d = layeroffset + j + slen2;
+
+ f4( a, b, c, d );
+
+ }
+
+ }
+
+ }
+
+ function v( x, y, z ) {
+
+ placeholder.push( x );
+ placeholder.push( y );
+ placeholder.push( z );
+
+ }
+
+
+ function f3( a, b, c ) {
+
+ addVertex( a );
+ addVertex( b );
+ addVertex( c );
+
+ const nextIndex = verticesArray.length / 3;
+ const uvs = uvgen.generateTopUV( scope, verticesArray, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
+
+ addUV( uvs[ 0 ] );
+ addUV( uvs[ 1 ] );
+ addUV( uvs[ 2 ] );
+
+ }
+
+ function f4( a, b, c, d ) {
+
+ addVertex( a );
+ addVertex( b );
+ addVertex( d );
+
+ addVertex( b );
+ addVertex( c );
+ addVertex( d );
+
+
+ const nextIndex = verticesArray.length / 3;
+ const uvs = uvgen.generateSideWallUV( scope, verticesArray, nextIndex - 6, nextIndex - 3, nextIndex - 2, nextIndex - 1 );
+
+ addUV( uvs[ 0 ] );
+ addUV( uvs[ 1 ] );
+ addUV( uvs[ 3 ] );
+
+ addUV( uvs[ 1 ] );
+ addUV( uvs[ 2 ] );
+ addUV( uvs[ 3 ] );
+
+ }
+
+ function addVertex( index ) {
+
+ verticesArray.push( placeholder[ index * 3 + 0 ] );
+ verticesArray.push( placeholder[ index * 3 + 1 ] );
+ verticesArray.push( placeholder[ index * 3 + 2 ] );
+
+ }
+
+
+ function addUV( vector2 ) {
+
+ uvArray.push( vector2.x );
+ uvArray.push( vector2.y );
+
+ }
+
+ }
+
+ }
+
+ toJSON() {
+
+ const data = BufferGeometry.prototype.toJSON.call( this );
+
+ const shapes = this.parameters.shapes;
+ const options = this.parameters.options;
+
+ return toJSON( shapes, options, data );
+
+ }
+
+}
+
+const WorldUVGenerator = {
+
+ generateTopUV: function ( geometry, vertices, indexA, indexB, indexC ) {
+
+ const a_x = vertices[ indexA * 3 ];
+ const a_y = vertices[ indexA * 3 + 1 ];
+ const b_x = vertices[ indexB * 3 ];
+ const b_y = vertices[ indexB * 3 + 1 ];
+ const c_x = vertices[ indexC * 3 ];
+ const c_y = vertices[ indexC * 3 + 1 ];
+
+ return [
+ new Vector2( a_x, a_y ),
+ new Vector2( b_x, b_y ),
+ new Vector2( c_x, c_y )
+ ];
+
+ },
+
+ generateSideWallUV: function ( geometry, vertices, indexA, indexB, indexC, indexD ) {
+
+ const a_x = vertices[ indexA * 3 ];
+ const a_y = vertices[ indexA * 3 + 1 ];
+ const a_z = vertices[ indexA * 3 + 2 ];
+ const b_x = vertices[ indexB * 3 ];
+ const b_y = vertices[ indexB * 3 + 1 ];
+ const b_z = vertices[ indexB * 3 + 2 ];
+ const c_x = vertices[ indexC * 3 ];
+ const c_y = vertices[ indexC * 3 + 1 ];
+ const c_z = vertices[ indexC * 3 + 2 ];
+ const d_x = vertices[ indexD * 3 ];
+ const d_y = vertices[ indexD * 3 + 1 ];
+ const d_z = vertices[ indexD * 3 + 2 ];
+
+ if ( Math.abs( a_y - b_y ) < 0.01 ) {
+
+ return [
+ new Vector2( a_x, 1 - a_z ),
+ new Vector2( b_x, 1 - b_z ),
+ new Vector2( c_x, 1 - c_z ),
+ new Vector2( d_x, 1 - d_z )
+ ];
+
+ } else {
+
+ return [
+ new Vector2( a_y, 1 - a_z ),
+ new Vector2( b_y, 1 - b_z ),
+ new Vector2( c_y, 1 - c_z ),
+ new Vector2( d_y, 1 - d_z )
+ ];
+
+ }
+
+ }
+
+};
+
+function toJSON( shapes, options, data ) {
+
+ data.shapes = [];
+
+ if ( Array.isArray( shapes ) ) {
+
+ for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+
+ const shape = shapes[ i ];
+
+ data.shapes.push( shape.uuid );
+
+ }
+
+ } else {
+
+ data.shapes.push( shapes.uuid );
+
+ }
+
+ if ( options.extrudePath !== undefined ) data.options.extrudePath = options.extrudePath.toJSON();
+
+ return data;
+
+}
+
+/**
+ * Parametric Surfaces Geometry
+ * based on the brilliant article by @prideout https://prideout.net/blog/old/blog/index.html@p=44.html
+ */
+
+function ParametricGeometry( func, slices, stacks ) {
+
+ BufferGeometry.call( this );
+
+ this.type = 'ParametricGeometry';
+
+ this.parameters = {
+ func: func,
+ slices: slices,
+ stacks: stacks
+ };
+
+ // buffers
+
+ const indices = [];
+ const vertices = [];
+ const normals = [];
+ const uvs = [];
+
+ const EPS = 0.00001;
+
+ const normal = new Vector3();
+
+ const p0 = new Vector3(), p1 = new Vector3();
+ const pu = new Vector3(), pv = new Vector3();
+
+ if ( func.length < 3 ) {
+
+ console.error( 'THREE.ParametricGeometry: Function must now modify a Vector3 as third parameter.' );
+
+ }
+
+ // generate vertices, normals and uvs
+
+ const sliceCount = slices + 1;
+
+ for ( let i = 0; i <= stacks; i ++ ) {
+
+ const v = i / stacks;
+
+ for ( let j = 0; j <= slices; j ++ ) {
+
+ const u = j / slices;
+
+ // vertex
+
+ func( u, v, p0 );
+ vertices.push( p0.x, p0.y, p0.z );
+
+ // normal
+
+ // approximate tangent vectors via finite differences
+
+ if ( u - EPS >= 0 ) {
+
+ func( u - EPS, v, p1 );
+ pu.subVectors( p0, p1 );
+
+ } else {
+
+ func( u + EPS, v, p1 );
+ pu.subVectors( p1, p0 );
+
+ }
+
+ if ( v - EPS >= 0 ) {
+
+ func( u, v - EPS, p1 );
+ pv.subVectors( p0, p1 );
+
+ } else {
+
+ func( u, v + EPS, p1 );
+ pv.subVectors( p1, p0 );
+
+ }
+
+ // cross product of tangent vectors returns surface normal
+
+ normal.crossVectors( pu, pv ).normalize();
+ normals.push( normal.x, normal.y, normal.z );
+
+ // uv
+
+ uvs.push( u, v );
+
+ }
+
+ }
+
+ // generate indices
+
+ for ( let i = 0; i < stacks; i ++ ) {
+
+ for ( let j = 0; j < slices; j ++ ) {
+
+ const a = i * sliceCount + j;
+ const b = i * sliceCount + j + 1;
+ const c = ( i + 1 ) * sliceCount + j + 1;
+ const d = ( i + 1 ) * sliceCount + j;
+
+ // faces one and two
+
+ indices.push( a, b, d );
+ indices.push( b, c, d );
+
+ }
+
+ }
+
+ // build geometry
+
+ this.setIndex( indices );
+ this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+ this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+ this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+}
+
+ParametricGeometry.prototype = Object.create( BufferGeometry.prototype );
+ParametricGeometry.prototype.constructor = ParametricGeometry;
+
+class ShapeGeometry extends BufferGeometry {
+
+ constructor( shapes, curveSegments = 12 ) {
+
+ super();
+ this.type = 'ShapeGeometry';
+
+ this.parameters = {
+ shapes: shapes,
+ curveSegments: curveSegments
+ };
+
+ // buffers
+
+ const indices = [];
+ const vertices = [];
+ const normals = [];
+ const uvs = [];
+
+ // helper variables
+
+ let groupStart = 0;
+ let groupCount = 0;
+
+ // allow single and array values for "shapes" parameter
+
+ if ( Array.isArray( shapes ) === false ) {
+
+ addShape( shapes );
+
+ } else {
+
+ for ( let i = 0; i < shapes.length; i ++ ) {
+
+ addShape( shapes[ i ] );
+
+ this.addGroup( groupStart, groupCount, i ); // enables MultiMaterial support
+
+ groupStart += groupCount;
+ groupCount = 0;
+
+ }
+
+ }
+
+ // build geometry
+
+ this.setIndex( indices );
+ this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+ this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+ this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+
+ // helper functions
+
+ function addShape( shape ) {
+
+ const indexOffset = vertices.length / 3;
+ const points = shape.extractPoints( curveSegments );
+
+ let shapeVertices = points.shape;
+ const shapeHoles = points.holes;
+
+ // check direction of vertices
+
+ if ( ShapeUtils.isClockWise( shapeVertices ) === false ) {
+
+ shapeVertices = shapeVertices.reverse();
+
+ }
+
+ for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
+
+ const shapeHole = shapeHoles[ i ];
+
+ if ( ShapeUtils.isClockWise( shapeHole ) === true ) {
+
+ shapeHoles[ i ] = shapeHole.reverse();
+
+ }
+
+ }
+
+ const faces = ShapeUtils.triangulateShape( shapeVertices, shapeHoles );
+
+ // join vertices of inner and outer paths to a single array
+
+ for ( let i = 0, l = shapeHoles.length; i < l; i ++ ) {
+
+ const shapeHole = shapeHoles[ i ];
+ shapeVertices = shapeVertices.concat( shapeHole );
+
+ }
+
+ // vertices, normals, uvs
+
+ for ( let i = 0, l = shapeVertices.length; i < l; i ++ ) {
+
+ const vertex = shapeVertices[ i ];
+
+ vertices.push( vertex.x, vertex.y, 0 );
+ normals.push( 0, 0, 1 );
+ uvs.push( vertex.x, vertex.y ); // world uvs
+
+ }
+
+ // incides
+
+ for ( let i = 0, l = faces.length; i < l; i ++ ) {
+
+ const face = faces[ i ];
+
+ const a = face[ 0 ] + indexOffset;
+ const b = face[ 1 ] + indexOffset;
+ const c = face[ 2 ] + indexOffset;
+
+ indices.push( a, b, c );
+ groupCount += 3;
+
+ }
+
+ }
+
+ }
+
+ toJSON() {
+
+ const data = BufferGeometry.prototype.toJSON.call( this );
+
+ const shapes = this.parameters.shapes;
+
+ return toJSON$1( shapes, data );
+
+ }
+
+}
+
+function toJSON$1( shapes, data ) {
+
+ data.shapes = [];
+
+ if ( Array.isArray( shapes ) ) {
+
+ for ( let i = 0, l = shapes.length; i < l; i ++ ) {
+
+ const shape = shapes[ i ];
+
+ data.shapes.push( shape.uuid );
+
+ }
+
+ } else {
+
+ data.shapes.push( shapes.uuid );
+
+ }
+
+ return data;
+
+}
+
+class SphereGeometry extends BufferGeometry {
+
+ constructor( radius = 1, widthSegments = 8, heightSegments = 6, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI ) {
+
+ super();
+ this.type = 'SphereGeometry';
+
+ this.parameters = {
+ radius: radius,
+ widthSegments: widthSegments,
+ heightSegments: heightSegments,
+ phiStart: phiStart,
+ phiLength: phiLength,
+ thetaStart: thetaStart,
+ thetaLength: thetaLength
+ };
+
+ widthSegments = Math.max( 3, Math.floor( widthSegments ) );
+ heightSegments = Math.max( 2, Math.floor( heightSegments ) );
+
+ const thetaEnd = Math.min( thetaStart + thetaLength, Math.PI );
+
+ let index = 0;
+ const grid = [];
+
+ const vertex = new Vector3();
+ const normal = new Vector3();
+
+ // buffers
+
+ const indices = [];
+ const vertices = [];
+ const normals = [];
+ const uvs = [];
+
+ // generate vertices, normals and uvs
+
+ for ( let iy = 0; iy <= heightSegments; iy ++ ) {
+
+ const verticesRow = [];
+
+ const v = iy / heightSegments;
+
+ // special case for the poles
+
+ let uOffset = 0;
+
+ if ( iy == 0 && thetaStart == 0 ) {
+
+ uOffset = 0.5 / widthSegments;
+
+ } else if ( iy == heightSegments && thetaEnd == Math.PI ) {
+
+ uOffset = - 0.5 / widthSegments;
+
+ }
+
+ for ( let ix = 0; ix <= widthSegments; ix ++ ) {
+
+ const u = ix / widthSegments;
+
+ // vertex
+
+ vertex.x = - radius * Math.cos( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
+ vertex.y = radius * Math.cos( thetaStart + v * thetaLength );
+ vertex.z = radius * Math.sin( phiStart + u * phiLength ) * Math.sin( thetaStart + v * thetaLength );
+
+ vertices.push( vertex.x, vertex.y, vertex.z );
+
+ // normal
+
+ normal.copy( vertex ).normalize();
+ normals.push( normal.x, normal.y, normal.z );
+
+ // uv
+
+ uvs.push( u + uOffset, 1 - v );
+
+ verticesRow.push( index ++ );
+
+ }
+
+ grid.push( verticesRow );
+
+ }
+
+ // indices
+
+ for ( let iy = 0; iy < heightSegments; iy ++ ) {
+
+ for ( let ix = 0; ix < widthSegments; ix ++ ) {
+
+ const a = grid[ iy ][ ix + 1 ];
+ const b = grid[ iy ][ ix ];
+ const c = grid[ iy + 1 ][ ix ];
+ const d = grid[ iy + 1 ][ ix + 1 ];
+
+ if ( iy !== 0 || thetaStart > 0 ) indices.push( a, b, d );
+ if ( iy !== heightSegments - 1 || thetaEnd < Math.PI ) indices.push( b, c, d );
+
+ }
+
+ }
+
+ // build geometry
+
+ this.setIndex( indices );
+ this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+ this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+ this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+ }
+
+}
+
+/**
+ * parameters = {
+ * color: <THREE.Color>
+ * }
+ */
+
+function ShadowMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.type = 'ShadowMaterial';
+
+ this.color = new Color( 0x000000 );
+ this.transparent = true;
+
+ this.setValues( parameters );
+
+}
+
+ShadowMaterial.prototype = Object.create( Material.prototype );
+ShadowMaterial.prototype.constructor = ShadowMaterial;
+
+ShadowMaterial.prototype.isShadowMaterial = true;
+
+ShadowMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.color.copy( source.color );
+
+ return this;
+
+};
+
+function RawShaderMaterial( parameters ) {
+
+ ShaderMaterial.call( this, parameters );
+
+ this.type = 'RawShaderMaterial';
+
+}
+
+RawShaderMaterial.prototype = Object.create( ShaderMaterial.prototype );
+RawShaderMaterial.prototype.constructor = RawShaderMaterial;
+
+RawShaderMaterial.prototype.isRawShaderMaterial = true;
+
+/**
+ * parameters = {
+ * color: <hex>,
+ * roughness: <float>,
+ * metalness: <float>,
+ * opacity: <float>,
+ *
+ * map: new THREE.Texture( <Image> ),
+ *
+ * lightMap: new THREE.Texture( <Image> ),
+ * lightMapIntensity: <float>
+ *
+ * aoMap: new THREE.Texture( <Image> ),
+ * aoMapIntensity: <float>
+ *
+ * emissive: <hex>,
+ * emissiveIntensity: <float>
+ * emissiveMap: new THREE.Texture( <Image> ),
+ *
+ * bumpMap: new THREE.Texture( <Image> ),
+ * bumpScale: <float>,
+ *
+ * normalMap: new THREE.Texture( <Image> ),
+ * normalMapType: THREE.TangentSpaceNormalMap,
+ * normalScale: <Vector2>,
+ *
+ * displacementMap: new THREE.Texture( <Image> ),
+ * displacementScale: <float>,
+ * displacementBias: <float>,
+ *
+ * roughnessMap: new THREE.Texture( <Image> ),
+ *
+ * metalnessMap: new THREE.Texture( <Image> ),
+ *
+ * alphaMap: new THREE.Texture( <Image> ),
+ *
+ * envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
+ * envMapIntensity: <float>
+ *
+ * refractionRatio: <float>,
+ *
+ * wireframe: <boolean>,
+ * wireframeLinewidth: <float>,
+ *
+ * skinning: <bool>,
+ * morphTargets: <bool>,
+ * morphNormals: <bool>
+ * }
+ */
+
+function MeshStandardMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.defines = { 'STANDARD': '' };
+
+ this.type = 'MeshStandardMaterial';
+
+ this.color = new Color( 0xffffff ); // diffuse
+ this.roughness = 1.0;
+ this.metalness = 0.0;
+
+ this.map = null;
+
+ this.lightMap = null;
+ this.lightMapIntensity = 1.0;
+
+ this.aoMap = null;
+ this.aoMapIntensity = 1.0;
+
+ this.emissive = new Color( 0x000000 );
+ this.emissiveIntensity = 1.0;
+ this.emissiveMap = null;
+
+ this.bumpMap = null;
+ this.bumpScale = 1;
+
+ this.normalMap = null;
+ this.normalMapType = TangentSpaceNormalMap;
+ this.normalScale = new Vector2( 1, 1 );
+
+ this.displacementMap = null;
+ this.displacementScale = 1;
+ this.displacementBias = 0;
+
+ this.roughnessMap = null;
+
+ this.metalnessMap = null;
+
+ this.alphaMap = null;
+
+ this.envMap = null;
+ this.envMapIntensity = 1.0;
+
+ this.refractionRatio = 0.98;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = 'round';
+ this.wireframeLinejoin = 'round';
+
+ this.skinning = false;
+ this.morphTargets = false;
+ this.morphNormals = false;
+
+ this.vertexTangents = false;
+
+ this.setValues( parameters );
+
+}
+
+MeshStandardMaterial.prototype = Object.create( Material.prototype );
+MeshStandardMaterial.prototype.constructor = MeshStandardMaterial;
+
+MeshStandardMaterial.prototype.isMeshStandardMaterial = true;
+
+MeshStandardMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.defines = { 'STANDARD': '' };
+
+ this.color.copy( source.color );
+ this.roughness = source.roughness;
+ this.metalness = source.metalness;
+
+ this.map = source.map;
+
+ this.lightMap = source.lightMap;
+ this.lightMapIntensity = source.lightMapIntensity;
+
+ this.aoMap = source.aoMap;
+ this.aoMapIntensity = source.aoMapIntensity;
+
+ this.emissive.copy( source.emissive );
+ this.emissiveMap = source.emissiveMap;
+ this.emissiveIntensity = source.emissiveIntensity;
+
+ this.bumpMap = source.bumpMap;
+ this.bumpScale = source.bumpScale;
+
+ this.normalMap = source.normalMap;
+ this.normalMapType = source.normalMapType;
+ this.normalScale.copy( source.normalScale );
+
+ this.displacementMap = source.displacementMap;
+ this.displacementScale = source.displacementScale;
+ this.displacementBias = source.displacementBias;
+
+ this.roughnessMap = source.roughnessMap;
+
+ this.metalnessMap = source.metalnessMap;
+
+ this.alphaMap = source.alphaMap;
+
+ this.envMap = source.envMap;
+ this.envMapIntensity = source.envMapIntensity;
+
+ this.refractionRatio = source.refractionRatio;
+
+ this.wireframe = source.wireframe;
+ this.wireframeLinewidth = source.wireframeLinewidth;
+ this.wireframeLinecap = source.wireframeLinecap;
+ this.wireframeLinejoin = source.wireframeLinejoin;
+
+ this.skinning = source.skinning;
+ this.morphTargets = source.morphTargets;
+ this.morphNormals = source.morphNormals;
+
+ this.vertexTangents = source.vertexTangents;
+
+ return this;
+
+};
+
+/**
+ * parameters = {
+ * clearcoat: <float>,
+ * clearcoatMap: new THREE.Texture( <Image> ),
+ * clearcoatRoughness: <float>,
+ * clearcoatRoughnessMap: new THREE.Texture( <Image> ),
+ * clearcoatNormalScale: <Vector2>,
+ * clearcoatNormalMap: new THREE.Texture( <Image> ),
+ *
+ * reflectivity: <float>,
+ * ior: <float>,
+ *
+ * sheen: <Color>,
+ *
+ * transmission: <float>,
+ * transmissionMap: new THREE.Texture( <Image> )
+ * }
+ */
+
+function MeshPhysicalMaterial( parameters ) {
+
+ MeshStandardMaterial.call( this );
+
+ this.defines = {
+
+ 'STANDARD': '',
+ 'PHYSICAL': ''
+
+ };
+
+ this.type = 'MeshPhysicalMaterial';
+
+ this.clearcoat = 0.0;
+ this.clearcoatMap = null;
+ this.clearcoatRoughness = 0.0;
+ this.clearcoatRoughnessMap = null;
+ this.clearcoatNormalScale = new Vector2( 1, 1 );
+ this.clearcoatNormalMap = null;
+
+ this.reflectivity = 0.5; // maps to F0 = 0.04
+
+ Object.defineProperty( this, 'ior', {
+ get: function () {
+
+ return ( 1 + 0.4 * this.reflectivity ) / ( 1 - 0.4 * this.reflectivity );
+
+ },
+ set: function ( ior ) {
+
+ this.reflectivity = MathUtils.clamp( 2.5 * ( ior - 1 ) / ( ior + 1 ), 0, 1 );
+
+ }
+ } );
+
+ this.sheen = null; // null will disable sheen bsdf
+
+ this.transmission = 0.0;
+ this.transmissionMap = null;
+
+ this.setValues( parameters );
+
+}
+
+MeshPhysicalMaterial.prototype = Object.create( MeshStandardMaterial.prototype );
+MeshPhysicalMaterial.prototype.constructor = MeshPhysicalMaterial;
+
+MeshPhysicalMaterial.prototype.isMeshPhysicalMaterial = true;
+
+MeshPhysicalMaterial.prototype.copy = function ( source ) {
+
+ MeshStandardMaterial.prototype.copy.call( this, source );
+
+ this.defines = {
+
+ 'STANDARD': '',
+ 'PHYSICAL': ''
+
+ };
+
+ this.clearcoat = source.clearcoat;
+ this.clearcoatMap = source.clearcoatMap;
+ this.clearcoatRoughness = source.clearcoatRoughness;
+ this.clearcoatRoughnessMap = source.clearcoatRoughnessMap;
+ this.clearcoatNormalMap = source.clearcoatNormalMap;
+ this.clearcoatNormalScale.copy( source.clearcoatNormalScale );
+
+ this.reflectivity = source.reflectivity;
+
+ if ( source.sheen ) {
+
+ this.sheen = ( this.sheen || new Color() ).copy( source.sheen );
+
+ } else {
+
+ this.sheen = null;
+
+ }
+
+ this.transmission = source.transmission;
+ this.transmissionMap = source.transmissionMap;
+
+ return this;
+
+};
+
+/**
+ * parameters = {
+ * color: <hex>,
+ * specular: <hex>,
+ * shininess: <float>,
+ * opacity: <float>,
+ *
+ * map: new THREE.Texture( <Image> ),
+ *
+ * lightMap: new THREE.Texture( <Image> ),
+ * lightMapIntensity: <float>
+ *
+ * aoMap: new THREE.Texture( <Image> ),
+ * aoMapIntensity: <float>
+ *
+ * emissive: <hex>,
+ * emissiveIntensity: <float>
+ * emissiveMap: new THREE.Texture( <Image> ),
+ *
+ * bumpMap: new THREE.Texture( <Image> ),
+ * bumpScale: <float>,
+ *
+ * normalMap: new THREE.Texture( <Image> ),
+ * normalMapType: THREE.TangentSpaceNormalMap,
+ * normalScale: <Vector2>,
+ *
+ * displacementMap: new THREE.Texture( <Image> ),
+ * displacementScale: <float>,
+ * displacementBias: <float>,
+ *
+ * specularMap: new THREE.Texture( <Image> ),
+ *
+ * alphaMap: new THREE.Texture( <Image> ),
+ *
+ * envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
+ * combine: THREE.MultiplyOperation,
+ * reflectivity: <float>,
+ * refractionRatio: <float>,
+ *
+ * wireframe: <boolean>,
+ * wireframeLinewidth: <float>,
+ *
+ * skinning: <bool>,
+ * morphTargets: <bool>,
+ * morphNormals: <bool>
+ * }
+ */
+
+function MeshPhongMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.type = 'MeshPhongMaterial';
+
+ this.color = new Color( 0xffffff ); // diffuse
+ this.specular = new Color( 0x111111 );
+ this.shininess = 30;
+
+ this.map = null;
+
+ this.lightMap = null;
+ this.lightMapIntensity = 1.0;
+
+ this.aoMap = null;
+ this.aoMapIntensity = 1.0;
+
+ this.emissive = new Color( 0x000000 );
+ this.emissiveIntensity = 1.0;
+ this.emissiveMap = null;
+
+ this.bumpMap = null;
+ this.bumpScale = 1;
+
+ this.normalMap = null;
+ this.normalMapType = TangentSpaceNormalMap;
+ this.normalScale = new Vector2( 1, 1 );
+
+ this.displacementMap = null;
+ this.displacementScale = 1;
+ this.displacementBias = 0;
+
+ this.specularMap = null;
+
+ this.alphaMap = null;
+
+ this.envMap = null;
+ this.combine = MultiplyOperation;
+ this.reflectivity = 1;
+ this.refractionRatio = 0.98;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = 'round';
+ this.wireframeLinejoin = 'round';
+
+ this.skinning = false;
+ this.morphTargets = false;
+ this.morphNormals = false;
+
+ this.setValues( parameters );
+
+}
+
+MeshPhongMaterial.prototype = Object.create( Material.prototype );
+MeshPhongMaterial.prototype.constructor = MeshPhongMaterial;
+
+MeshPhongMaterial.prototype.isMeshPhongMaterial = true;
+
+MeshPhongMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.color.copy( source.color );
+ this.specular.copy( source.specular );
+ this.shininess = source.shininess;
+
+ this.map = source.map;
+
+ this.lightMap = source.lightMap;
+ this.lightMapIntensity = source.lightMapIntensity;
+
+ this.aoMap = source.aoMap;
+ this.aoMapIntensity = source.aoMapIntensity;
+
+ this.emissive.copy( source.emissive );
+ this.emissiveMap = source.emissiveMap;
+ this.emissiveIntensity = source.emissiveIntensity;
+
+ this.bumpMap = source.bumpMap;
+ this.bumpScale = source.bumpScale;
+
+ this.normalMap = source.normalMap;
+ this.normalMapType = source.normalMapType;
+ this.normalScale.copy( source.normalScale );
+
+ this.displacementMap = source.displacementMap;
+ this.displacementScale = source.displacementScale;
+ this.displacementBias = source.displacementBias;
+
+ this.specularMap = source.specularMap;
+
+ this.alphaMap = source.alphaMap;
+
+ this.envMap = source.envMap;
+ this.combine = source.combine;
+ this.reflectivity = source.reflectivity;
+ this.refractionRatio = source.refractionRatio;
+
+ this.wireframe = source.wireframe;
+ this.wireframeLinewidth = source.wireframeLinewidth;
+ this.wireframeLinecap = source.wireframeLinecap;
+ this.wireframeLinejoin = source.wireframeLinejoin;
+
+ this.skinning = source.skinning;
+ this.morphTargets = source.morphTargets;
+ this.morphNormals = source.morphNormals;
+
+ return this;
+
+};
+
+/**
+ * parameters = {
+ * color: <hex>,
+ *
+ * map: new THREE.Texture( <Image> ),
+ * gradientMap: new THREE.Texture( <Image> ),
+ *
+ * lightMap: new THREE.Texture( <Image> ),
+ * lightMapIntensity: <float>
+ *
+ * aoMap: new THREE.Texture( <Image> ),
+ * aoMapIntensity: <float>
+ *
+ * emissive: <hex>,
+ * emissiveIntensity: <float>
+ * emissiveMap: new THREE.Texture( <Image> ),
+ *
+ * bumpMap: new THREE.Texture( <Image> ),
+ * bumpScale: <float>,
+ *
+ * normalMap: new THREE.Texture( <Image> ),
+ * normalMapType: THREE.TangentSpaceNormalMap,
+ * normalScale: <Vector2>,
+ *
+ * displacementMap: new THREE.Texture( <Image> ),
+ * displacementScale: <float>,
+ * displacementBias: <float>,
+ *
+ * alphaMap: new THREE.Texture( <Image> ),
+ *
+ * wireframe: <boolean>,
+ * wireframeLinewidth: <float>,
+ *
+ * skinning: <bool>,
+ * morphTargets: <bool>,
+ * morphNormals: <bool>
+ * }
+ */
+
+function MeshToonMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.defines = { 'TOON': '' };
+
+ this.type = 'MeshToonMaterial';
+
+ this.color = new Color( 0xffffff );
+
+ this.map = null;
+ this.gradientMap = null;
+
+ this.lightMap = null;
+ this.lightMapIntensity = 1.0;
+
+ this.aoMap = null;
+ this.aoMapIntensity = 1.0;
+
+ this.emissive = new Color( 0x000000 );
+ this.emissiveIntensity = 1.0;
+ this.emissiveMap = null;
+
+ this.bumpMap = null;
+ this.bumpScale = 1;
+
+ this.normalMap = null;
+ this.normalMapType = TangentSpaceNormalMap;
+ this.normalScale = new Vector2( 1, 1 );
+
+ this.displacementMap = null;
+ this.displacementScale = 1;
+ this.displacementBias = 0;
+
+ this.alphaMap = null;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = 'round';
+ this.wireframeLinejoin = 'round';
+
+ this.skinning = false;
+ this.morphTargets = false;
+ this.morphNormals = false;
+
+ this.setValues( parameters );
+
+}
+
+MeshToonMaterial.prototype = Object.create( Material.prototype );
+MeshToonMaterial.prototype.constructor = MeshToonMaterial;
+
+MeshToonMaterial.prototype.isMeshToonMaterial = true;
+
+MeshToonMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.color.copy( source.color );
+
+ this.map = source.map;
+ this.gradientMap = source.gradientMap;
+
+ this.lightMap = source.lightMap;
+ this.lightMapIntensity = source.lightMapIntensity;
+
+ this.aoMap = source.aoMap;
+ this.aoMapIntensity = source.aoMapIntensity;
+
+ this.emissive.copy( source.emissive );
+ this.emissiveMap = source.emissiveMap;
+ this.emissiveIntensity = source.emissiveIntensity;
+
+ this.bumpMap = source.bumpMap;
+ this.bumpScale = source.bumpScale;
+
+ this.normalMap = source.normalMap;
+ this.normalMapType = source.normalMapType;
+ this.normalScale.copy( source.normalScale );
+
+ this.displacementMap = source.displacementMap;
+ this.displacementScale = source.displacementScale;
+ this.displacementBias = source.displacementBias;
+
+ this.alphaMap = source.alphaMap;
+
+ this.wireframe = source.wireframe;
+ this.wireframeLinewidth = source.wireframeLinewidth;
+ this.wireframeLinecap = source.wireframeLinecap;
+ this.wireframeLinejoin = source.wireframeLinejoin;
+
+ this.skinning = source.skinning;
+ this.morphTargets = source.morphTargets;
+ this.morphNormals = source.morphNormals;
+
+ return this;
+
+};
+
+/**
+ * parameters = {
+ * opacity: <float>,
+ *
+ * bumpMap: new THREE.Texture( <Image> ),
+ * bumpScale: <float>,
+ *
+ * normalMap: new THREE.Texture( <Image> ),
+ * normalMapType: THREE.TangentSpaceNormalMap,
+ * normalScale: <Vector2>,
+ *
+ * displacementMap: new THREE.Texture( <Image> ),
+ * displacementScale: <float>,
+ * displacementBias: <float>,
+ *
+ * wireframe: <boolean>,
+ * wireframeLinewidth: <float>
+ *
+ * skinning: <bool>,
+ * morphTargets: <bool>,
+ * morphNormals: <bool>
+ * }
+ */
+
+function MeshNormalMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.type = 'MeshNormalMaterial';
+
+ this.bumpMap = null;
+ this.bumpScale = 1;
+
+ this.normalMap = null;
+ this.normalMapType = TangentSpaceNormalMap;
+ this.normalScale = new Vector2( 1, 1 );
+
+ this.displacementMap = null;
+ this.displacementScale = 1;
+ this.displacementBias = 0;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+
+ this.fog = false;
+
+ this.skinning = false;
+ this.morphTargets = false;
+ this.morphNormals = false;
+
+ this.setValues( parameters );
+
+}
+
+MeshNormalMaterial.prototype = Object.create( Material.prototype );
+MeshNormalMaterial.prototype.constructor = MeshNormalMaterial;
+
+MeshNormalMaterial.prototype.isMeshNormalMaterial = true;
+
+MeshNormalMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.bumpMap = source.bumpMap;
+ this.bumpScale = source.bumpScale;
+
+ this.normalMap = source.normalMap;
+ this.normalMapType = source.normalMapType;
+ this.normalScale.copy( source.normalScale );
+
+ this.displacementMap = source.displacementMap;
+ this.displacementScale = source.displacementScale;
+ this.displacementBias = source.displacementBias;
+
+ this.wireframe = source.wireframe;
+ this.wireframeLinewidth = source.wireframeLinewidth;
+
+ this.skinning = source.skinning;
+ this.morphTargets = source.morphTargets;
+ this.morphNormals = source.morphNormals;
+
+ return this;
+
+};
+
+/**
+ * parameters = {
+ * color: <hex>,
+ * opacity: <float>,
+ *
+ * map: new THREE.Texture( <Image> ),
+ *
+ * lightMap: new THREE.Texture( <Image> ),
+ * lightMapIntensity: <float>
+ *
+ * aoMap: new THREE.Texture( <Image> ),
+ * aoMapIntensity: <float>
+ *
+ * emissive: <hex>,
+ * emissiveIntensity: <float>
+ * emissiveMap: new THREE.Texture( <Image> ),
+ *
+ * specularMap: new THREE.Texture( <Image> ),
+ *
+ * alphaMap: new THREE.Texture( <Image> ),
+ *
+ * envMap: new THREE.CubeTexture( [posx, negx, posy, negy, posz, negz] ),
+ * combine: THREE.Multiply,
+ * reflectivity: <float>,
+ * refractionRatio: <float>,
+ *
+ * wireframe: <boolean>,
+ * wireframeLinewidth: <float>,
+ *
+ * skinning: <bool>,
+ * morphTargets: <bool>,
+ * morphNormals: <bool>
+ * }
+ */
+
+function MeshLambertMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.type = 'MeshLambertMaterial';
+
+ this.color = new Color( 0xffffff ); // diffuse
+
+ this.map = null;
+
+ this.lightMap = null;
+ this.lightMapIntensity = 1.0;
+
+ this.aoMap = null;
+ this.aoMapIntensity = 1.0;
+
+ this.emissive = new Color( 0x000000 );
+ this.emissiveIntensity = 1.0;
+ this.emissiveMap = null;
+
+ this.specularMap = null;
+
+ this.alphaMap = null;
+
+ this.envMap = null;
+ this.combine = MultiplyOperation;
+ this.reflectivity = 1;
+ this.refractionRatio = 0.98;
+
+ this.wireframe = false;
+ this.wireframeLinewidth = 1;
+ this.wireframeLinecap = 'round';
+ this.wireframeLinejoin = 'round';
+
+ this.skinning = false;
+ this.morphTargets = false;
+ this.morphNormals = false;
+
+ this.setValues( parameters );
+
+}
+
+MeshLambertMaterial.prototype = Object.create( Material.prototype );
+MeshLambertMaterial.prototype.constructor = MeshLambertMaterial;
+
+MeshLambertMaterial.prototype.isMeshLambertMaterial = true;
+
+MeshLambertMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.color.copy( source.color );
+
+ this.map = source.map;
+
+ this.lightMap = source.lightMap;
+ this.lightMapIntensity = source.lightMapIntensity;
+
+ this.aoMap = source.aoMap;
+ this.aoMapIntensity = source.aoMapIntensity;
+
+ this.emissive.copy( source.emissive );
+ this.emissiveMap = source.emissiveMap;
+ this.emissiveIntensity = source.emissiveIntensity;
+
+ this.specularMap = source.specularMap;
+
+ this.alphaMap = source.alphaMap;
+
+ this.envMap = source.envMap;
+ this.combine = source.combine;
+ this.reflectivity = source.reflectivity;
+ this.refractionRatio = source.refractionRatio;
+
+ this.wireframe = source.wireframe;
+ this.wireframeLinewidth = source.wireframeLinewidth;
+ this.wireframeLinecap = source.wireframeLinecap;
+ this.wireframeLinejoin = source.wireframeLinejoin;
+
+ this.skinning = source.skinning;
+ this.morphTargets = source.morphTargets;
+ this.morphNormals = source.morphNormals;
+
+ return this;
+
+};
+
+/**
+ * parameters = {
+ * color: <hex>,
+ * opacity: <float>,
+ *
+ * matcap: new THREE.Texture( <Image> ),
+ *
+ * map: new THREE.Texture( <Image> ),
+ *
+ * bumpMap: new THREE.Texture( <Image> ),
+ * bumpScale: <float>,
+ *
+ * normalMap: new THREE.Texture( <Image> ),
+ * normalMapType: THREE.TangentSpaceNormalMap,
+ * normalScale: <Vector2>,
+ *
+ * displacementMap: new THREE.Texture( <Image> ),
+ * displacementScale: <float>,
+ * displacementBias: <float>,
+ *
+ * alphaMap: new THREE.Texture( <Image> ),
+ *
+ * skinning: <bool>,
+ * morphTargets: <bool>,
+ * morphNormals: <bool>
+ * }
+ */
+
+function MeshMatcapMaterial( parameters ) {
+
+ Material.call( this );
+
+ this.defines = { 'MATCAP': '' };
+
+ this.type = 'MeshMatcapMaterial';
+
+ this.color = new Color( 0xffffff ); // diffuse
+
+ this.matcap = null;
+
+ this.map = null;
+
+ this.bumpMap = null;
+ this.bumpScale = 1;
+
+ this.normalMap = null;
+ this.normalMapType = TangentSpaceNormalMap;
+ this.normalScale = new Vector2( 1, 1 );
+
+ this.displacementMap = null;
+ this.displacementScale = 1;
+ this.displacementBias = 0;
+
+ this.alphaMap = null;
+
+ this.skinning = false;
+ this.morphTargets = false;
+ this.morphNormals = false;
+
+ this.setValues( parameters );
+
+}
+
+MeshMatcapMaterial.prototype = Object.create( Material.prototype );
+MeshMatcapMaterial.prototype.constructor = MeshMatcapMaterial;
+
+MeshMatcapMaterial.prototype.isMeshMatcapMaterial = true;
+
+MeshMatcapMaterial.prototype.copy = function ( source ) {
+
+ Material.prototype.copy.call( this, source );
+
+ this.defines = { 'MATCAP': '' };
+
+ this.color.copy( source.color );
+
+ this.matcap = source.matcap;
+
+ this.map = source.map;
+
+ this.bumpMap = source.bumpMap;
+ this.bumpScale = source.bumpScale;
+
+ this.normalMap = source.normalMap;
+ this.normalMapType = source.normalMapType;
+ this.normalScale.copy( source.normalScale );
+
+ this.displacementMap = source.displacementMap;
+ this.displacementScale = source.displacementScale;
+ this.displacementBias = source.displacementBias;
+
+ this.alphaMap = source.alphaMap;
+
+ this.skinning = source.skinning;
+ this.morphTargets = source.morphTargets;
+ this.morphNormals = source.morphNormals;
+
+ return this;
+
+};
+
+/**
+ * parameters = {
+ * color: <hex>,
+ * opacity: <float>,
+ *
+ * linewidth: <float>,
+ *
+ * scale: <float>,
+ * dashSize: <float>,
+ * gapSize: <float>
+ * }
+ */
+
+function LineDashedMaterial( parameters ) {
+
+ LineBasicMaterial.call( this );
+
+ this.type = 'LineDashedMaterial';
+
+ this.scale = 1;
+ this.dashSize = 3;
+ this.gapSize = 1;
+
+ this.setValues( parameters );
+
+}
+
+LineDashedMaterial.prototype = Object.create( LineBasicMaterial.prototype );
+LineDashedMaterial.prototype.constructor = LineDashedMaterial;
+
+LineDashedMaterial.prototype.isLineDashedMaterial = true;
+
+LineDashedMaterial.prototype.copy = function ( source ) {
+
+ LineBasicMaterial.prototype.copy.call( this, source );
+
+ this.scale = source.scale;
+ this.dashSize = source.dashSize;
+ this.gapSize = source.gapSize;
+
+ return this;
+
+};
+
+var Materials = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ ShadowMaterial: ShadowMaterial,
+ SpriteMaterial: SpriteMaterial,
+ RawShaderMaterial: RawShaderMaterial,
+ ShaderMaterial: ShaderMaterial,
+ PointsMaterial: PointsMaterial,
+ MeshPhysicalMaterial: MeshPhysicalMaterial,
+ MeshStandardMaterial: MeshStandardMaterial,
+ MeshPhongMaterial: MeshPhongMaterial,
+ MeshToonMaterial: MeshToonMaterial,
+ MeshNormalMaterial: MeshNormalMaterial,
+ MeshLambertMaterial: MeshLambertMaterial,
+ MeshDepthMaterial: MeshDepthMaterial,
+ MeshDistanceMaterial: MeshDistanceMaterial,
+ MeshBasicMaterial: MeshBasicMaterial,
+ MeshMatcapMaterial: MeshMatcapMaterial,
+ LineDashedMaterial: LineDashedMaterial,
+ LineBasicMaterial: LineBasicMaterial,
+ Material: Material
+});
+
+const AnimationUtils = {
+
+ // same as Array.prototype.slice, but also works on typed arrays
+ arraySlice: function ( array, from, to ) {
+
+ if ( AnimationUtils.isTypedArray( array ) ) {
+
+ // in ios9 array.subarray(from, undefined) will return empty array
+ // but array.subarray(from) or array.subarray(from, len) is correct
+ return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) );
+
+ }
+
+ return array.slice( from, to );
+
+ },
+
+ // converts an array to a specific type
+ convertArray: function ( array, type, forceClone ) {
+
+ if ( ! array || // let 'undefined' and 'null' pass
+ ! forceClone && array.constructor === type ) return array;
+
+ if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {
+
+ return new type( array ); // create typed array
+
+ }
+
+ return Array.prototype.slice.call( array ); // create Array
+
+ },
+
+ isTypedArray: function ( object ) {
+
+ return ArrayBuffer.isView( object ) &&
+ ! ( object instanceof DataView );
+
+ },
+
+ // returns an array by which times and values can be sorted
+ getKeyframeOrder: function ( times ) {
+
+ function compareTime( i, j ) {
+
+ return times[ i ] - times[ j ];
+
+ }
+
+ const n = times.length;
+ const result = new Array( n );
+ for ( let i = 0; i !== n; ++ i ) result[ i ] = i;
+
+ result.sort( compareTime );
+
+ return result;
+
+ },
+
+ // uses the array previously returned by 'getKeyframeOrder' to sort data
+ sortedArray: function ( values, stride, order ) {
+
+ const nValues = values.length;
+ const result = new values.constructor( nValues );
+
+ for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {
+
+ const srcOffset = order[ i ] * stride;
+
+ for ( let j = 0; j !== stride; ++ j ) {
+
+ result[ dstOffset ++ ] = values[ srcOffset + j ];
+
+ }
+
+ }
+
+ return result;
+
+ },
+
+ // function for parsing AOS keyframe formats
+ flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {
+
+ let i = 1, key = jsonKeys[ 0 ];
+
+ while ( key !== undefined && key[ valuePropertyName ] === undefined ) {
+
+ key = jsonKeys[ i ++ ];
+
+ }
+
+ if ( key === undefined ) return; // no data
+
+ let value = key[ valuePropertyName ];
+ if ( value === undefined ) return; // no data
+
+ if ( Array.isArray( value ) ) {
+
+ do {
+
+ value = key[ valuePropertyName ];
+
+ if ( value !== undefined ) {
+
+ times.push( key.time );
+ values.push.apply( values, value ); // push all elements
+
+ }
+
+ key = jsonKeys[ i ++ ];
+
+ } while ( key !== undefined );
+
+ } else if ( value.toArray !== undefined ) {
+
+ // ...assume THREE.Math-ish
+
+ do {
+
+ value = key[ valuePropertyName ];
+
+ if ( value !== undefined ) {
+
+ times.push( key.time );
+ value.toArray( values, values.length );
+
+ }
+
+ key = jsonKeys[ i ++ ];
+
+ } while ( key !== undefined );
+
+ } else {
+
+ // otherwise push as-is
+
+ do {
+
+ value = key[ valuePropertyName ];
+
+ if ( value !== undefined ) {
+
+ times.push( key.time );
+ values.push( value );
+
+ }
+
+ key = jsonKeys[ i ++ ];
+
+ } while ( key !== undefined );
+
+ }
+
+ },
+
+ subclip: function ( sourceClip, name, startFrame, endFrame, fps = 30 ) {
+
+ const clip = sourceClip.clone();
+
+ clip.name = name;
+
+ const tracks = [];
+
+ for ( let i = 0; i < clip.tracks.length; ++ i ) {
+
+ const track = clip.tracks[ i ];
+ const valueSize = track.getValueSize();
+
+ const times = [];
+ const values = [];
+
+ for ( let j = 0; j < track.times.length; ++ j ) {
+
+ const frame = track.times[ j ] * fps;
+
+ if ( frame < startFrame || frame >= endFrame ) continue;
+
+ times.push( track.times[ j ] );
+
+ for ( let k = 0; k < valueSize; ++ k ) {
+
+ values.push( track.values[ j * valueSize + k ] );
+
+ }
+
+ }
+
+ if ( times.length === 0 ) continue;
+
+ track.times = AnimationUtils.convertArray( times, track.times.constructor );
+ track.values = AnimationUtils.convertArray( values, track.values.constructor );
+
+ tracks.push( track );
+
+ }
+
+ clip.tracks = tracks;
+
+ // find minimum .times value across all tracks in the trimmed clip
+
+ let minStartTime = Infinity;
+
+ for ( let i = 0; i < clip.tracks.length; ++ i ) {
+
+ if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) {
+
+ minStartTime = clip.tracks[ i ].times[ 0 ];
+
+ }
+
+ }
+
+ // shift all tracks such that clip begins at t=0
+
+ for ( let i = 0; i < clip.tracks.length; ++ i ) {
+
+ clip.tracks[ i ].shift( - 1 * minStartTime );
+
+ }
+
+ clip.resetDuration();
+
+ return clip;
+
+ },
+
+ makeClipAdditive: function ( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) {
+
+ if ( fps <= 0 ) fps = 30;
+
+ const numTracks = referenceClip.tracks.length;
+ const referenceTime = referenceFrame / fps;
+
+ // Make each track's values relative to the values at the reference frame
+ for ( let i = 0; i < numTracks; ++ i ) {
+
+ const referenceTrack = referenceClip.tracks[ i ];
+ const referenceTrackType = referenceTrack.ValueTypeName;
+
+ // Skip this track if it's non-numeric
+ if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue;
+
+ // Find the track in the target clip whose name and type matches the reference track
+ const targetTrack = targetClip.tracks.find( function ( track ) {
+
+ return track.name === referenceTrack.name
+ && track.ValueTypeName === referenceTrackType;
+
+ } );
+
+ if ( targetTrack === undefined ) continue;
+
+ let referenceOffset = 0;
+ const referenceValueSize = referenceTrack.getValueSize();
+
+ if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
+
+ referenceOffset = referenceValueSize / 3;
+
+ }
+
+ let targetOffset = 0;
+ const targetValueSize = targetTrack.getValueSize();
+
+ if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
+
+ targetOffset = targetValueSize / 3;
+
+ }
+
+ const lastIndex = referenceTrack.times.length - 1;
+ let referenceValue;
+
+ // Find the value to subtract out of the track
+ if ( referenceTime <= referenceTrack.times[ 0 ] ) {
+
+ // Reference frame is earlier than the first keyframe, so just use the first keyframe
+ const startIndex = referenceOffset;
+ const endIndex = referenceValueSize - referenceOffset;
+ referenceValue = AnimationUtils.arraySlice( referenceTrack.values, startIndex, endIndex );
+
+ } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) {
+
+ // Reference frame is after the last keyframe, so just use the last keyframe
+ const startIndex = lastIndex * referenceValueSize + referenceOffset;
+ const endIndex = startIndex + referenceValueSize - referenceOffset;
+ referenceValue = AnimationUtils.arraySlice( referenceTrack.values, startIndex, endIndex );
+
+ } else {
+
+ // Interpolate to the reference value
+ const interpolant = referenceTrack.createInterpolant();
+ const startIndex = referenceOffset;
+ const endIndex = referenceValueSize - referenceOffset;
+ interpolant.evaluate( referenceTime );
+ referenceValue = AnimationUtils.arraySlice( interpolant.resultBuffer, startIndex, endIndex );
+
+ }
+
+ // Conjugate the quaternion
+ if ( referenceTrackType === 'quaternion' ) {
+
+ const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate();
+ referenceQuat.toArray( referenceValue );
+
+ }
+
+ // Subtract the reference value from all of the track values
+
+ const numTimes = targetTrack.times.length;
+ for ( let j = 0; j < numTimes; ++ j ) {
+
+ const valueStart = j * targetValueSize + targetOffset;
+
+ if ( referenceTrackType === 'quaternion' ) {
+
+ // Multiply the conjugate for quaternion track types
+ Quaternion.multiplyQuaternionsFlat(
+ targetTrack.values,
+ valueStart,
+ referenceValue,
+ 0,
+ targetTrack.values,
+ valueStart
+ );
+
+ } else {
+
+ const valueEnd = targetValueSize - targetOffset * 2;
+
+ // Subtract each value for all other numeric track types
+ for ( let k = 0; k < valueEnd; ++ k ) {
+
+ targetTrack.values[ valueStart + k ] -= referenceValue[ k ];
+
+ }
+
+ }
+
+ }
+
+ }
+
+ targetClip.blendMode = AdditiveAnimationBlendMode;
+
+ return targetClip;
+
+ }
+
+};
+
+/**
+ * Abstract base class of interpolants over parametric samples.
+ *
+ * The parameter domain is one dimensional, typically the time or a path
+ * along a curve defined by the data.
+ *
+ * The sample values can have any dimensionality and derived classes may
+ * apply special interpretations to the data.
+ *
+ * This class provides the interval seek in a Template Method, deferring
+ * the actual interpolation to derived classes.
+ *
+ * Time complexity is O(1) for linear access crossing at most two points
+ * and O(log N) for random access, where N is the number of positions.
+ *
+ * References:
+ *
+ * http://www.oodesign.com/template-method-pattern.html
+ *
+ */
+
+function Interpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+ this.parameterPositions = parameterPositions;
+ this._cachedIndex = 0;
+
+ this.resultBuffer = resultBuffer !== undefined ?
+ resultBuffer : new sampleValues.constructor( sampleSize );
+ this.sampleValues = sampleValues;
+ this.valueSize = sampleSize;
+
+}
+
+Object.assign( Interpolant.prototype, {
+
+ evaluate: function ( t ) {
+
+ const pp = this.parameterPositions;
+ let i1 = this._cachedIndex,
+ t1 = pp[ i1 ],
+ t0 = pp[ i1 - 1 ];
+
+ validate_interval: {
+
+ seek: {
+
+ let right;
+
+ linear_scan: {
+
+ //- See http://jsperf.com/comparison-to-undefined/3
+ //- slower code:
+ //-
+ //- if ( t >= t1 || t1 === undefined ) {
+ forward_scan: if ( ! ( t < t1 ) ) {
+
+ for ( let giveUpAt = i1 + 2; ; ) {
+
+ if ( t1 === undefined ) {
+
+ if ( t < t0 ) break forward_scan;
+
+ // after end
+
+ i1 = pp.length;
+ this._cachedIndex = i1;
+ return this.afterEnd_( i1 - 1, t, t0 );
+
+ }
+
+ if ( i1 === giveUpAt ) break; // this loop
+
+ t0 = t1;
+ t1 = pp[ ++ i1 ];
+
+ if ( t < t1 ) {
+
+ // we have arrived at the sought interval
+ break seek;
+
+ }
+
+ }
+
+ // prepare binary search on the right side of the index
+ right = pp.length;
+ break linear_scan;
+
+ }
+
+ //- slower code:
+ //- if ( t < t0 || t0 === undefined ) {
+ if ( ! ( t >= t0 ) ) {
+
+ // looping?
+
+ const t1global = pp[ 1 ];
+
+ if ( t < t1global ) {
+
+ i1 = 2; // + 1, using the scan for the details
+ t0 = t1global;
+
+ }
+
+ // linear reverse scan
+
+ for ( let giveUpAt = i1 - 2; ; ) {
+
+ if ( t0 === undefined ) {
+
+ // before start
+
+ this._cachedIndex = 0;
+ return this.beforeStart_( 0, t, t1 );
+
+ }
+
+ if ( i1 === giveUpAt ) break; // this loop
+
+ t1 = t0;
+ t0 = pp[ -- i1 - 1 ];
+
+ if ( t >= t0 ) {
+
+ // we have arrived at the sought interval
+ break seek;
+
+ }
+
+ }
+
+ // prepare binary search on the left side of the index
+ right = i1;
+ i1 = 0;
+ break linear_scan;
+
+ }
+
+ // the interval is valid
+
+ break validate_interval;
+
+ } // linear scan
+
+ // binary search
+
+ while ( i1 < right ) {
+
+ const mid = ( i1 + right ) >>> 1;
+
+ if ( t < pp[ mid ] ) {
+
+ right = mid;
+
+ } else {
+
+ i1 = mid + 1;
+
+ }
+
+ }
+
+ t1 = pp[ i1 ];
+ t0 = pp[ i1 - 1 ];
+
+ // check boundary cases, again
+
+ if ( t0 === undefined ) {
+
+ this._cachedIndex = 0;
+ return this.beforeStart_( 0, t, t1 );
+
+ }
+
+ if ( t1 === undefined ) {
+
+ i1 = pp.length;
+ this._cachedIndex = i1;
+ return this.afterEnd_( i1 - 1, t0, t );
+
+ }
+
+ } // seek
+
+ this._cachedIndex = i1;
+
+ this.intervalChanged_( i1, t0, t1 );
+
+ } // validate_interval
+
+ return this.interpolate_( i1, t0, t, t1 );
+
+ },
+
+ settings: null, // optional, subclass-specific settings structure
+ // Note: The indirection allows central control of many interpolants.
+
+ // --- Protected interface
+
+ DefaultSettings_: {},
+
+ getSettings_: function () {
+
+ return this.settings || this.DefaultSettings_;
+
+ },
+
+ copySampleValue_: function ( index ) {
+
+ // copies a sample value to the result buffer
+
+ const result = this.resultBuffer,
+ values = this.sampleValues,
+ stride = this.valueSize,
+ offset = index * stride;
+
+ for ( let i = 0; i !== stride; ++ i ) {
+
+ result[ i ] = values[ offset + i ];
+
+ }
+
+ return result;
+
+ },
+
+ // Template methods for derived classes:
+
+ interpolate_: function ( /* i1, t0, t, t1 */ ) {
+
+ throw new Error( 'call to abstract method' );
+ // implementations shall return this.resultBuffer
+
+ },
+
+ intervalChanged_: function ( /* i1, t0, t1 */ ) {
+
+ // empty
+
+ }
+
+} );
+
+// DECLARE ALIAS AFTER assign prototype
+Object.assign( Interpolant.prototype, {
+
+ //( 0, t, t0 ), returns this.resultBuffer
+ beforeStart_: Interpolant.prototype.copySampleValue_,
+
+ //( N-1, tN-1, t ), returns this.resultBuffer
+ afterEnd_: Interpolant.prototype.copySampleValue_,
+
+} );
+
+/**
+ * Fast and simple cubic spline interpolant.
+ *
+ * It was derived from a Hermitian construction setting the first derivative
+ * at each sample position to the linear slope between neighboring positions
+ * over their parameter interval.
+ */
+
+function CubicInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+ Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+ this._weightPrev = - 0;
+ this._offsetPrev = - 0;
+ this._weightNext = - 0;
+ this._offsetNext = - 0;
+
+}
+
+CubicInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+
+ constructor: CubicInterpolant,
+
+ DefaultSettings_: {
+
+ endingStart: ZeroCurvatureEnding,
+ endingEnd: ZeroCurvatureEnding
+
+ },
+
+ intervalChanged_: function ( i1, t0, t1 ) {
+
+ const pp = this.parameterPositions;
+ let iPrev = i1 - 2,
+ iNext = i1 + 1,
+
+ tPrev = pp[ iPrev ],
+ tNext = pp[ iNext ];
+
+ if ( tPrev === undefined ) {
+
+ switch ( this.getSettings_().endingStart ) {
+
+ case ZeroSlopeEnding:
+
+ // f'(t0) = 0
+ iPrev = i1;
+ tPrev = 2 * t0 - t1;
+
+ break;
+
+ case WrapAroundEnding:
+
+ // use the other end of the curve
+ iPrev = pp.length - 2;
+ tPrev = t0 + pp[ iPrev ] - pp[ iPrev + 1 ];
+
+ break;
+
+ default: // ZeroCurvatureEnding
+
+ // f''(t0) = 0 a.k.a. Natural Spline
+ iPrev = i1;
+ tPrev = t1;
+
+ }
+
+ }
+
+ if ( tNext === undefined ) {
+
+ switch ( this.getSettings_().endingEnd ) {
+
+ case ZeroSlopeEnding:
+
+ // f'(tN) = 0
+ iNext = i1;
+ tNext = 2 * t1 - t0;
+
+ break;
+
+ case WrapAroundEnding:
+
+ // use the other end of the curve
+ iNext = 1;
+ tNext = t1 + pp[ 1 ] - pp[ 0 ];
+
+ break;
+
+ default: // ZeroCurvatureEnding
+
+ // f''(tN) = 0, a.k.a. Natural Spline
+ iNext = i1 - 1;
+ tNext = t0;
+
+ }
+
+ }
+
+ const halfDt = ( t1 - t0 ) * 0.5,
+ stride = this.valueSize;
+
+ this._weightPrev = halfDt / ( t0 - tPrev );
+ this._weightNext = halfDt / ( tNext - t1 );
+ this._offsetPrev = iPrev * stride;
+ this._offsetNext = iNext * stride;
+
+ },
+
+ interpolate_: function ( i1, t0, t, t1 ) {
+
+ const result = this.resultBuffer,
+ values = this.sampleValues,
+ stride = this.valueSize,
+
+ o1 = i1 * stride, o0 = o1 - stride,
+ oP = this._offsetPrev, oN = this._offsetNext,
+ wP = this._weightPrev, wN = this._weightNext,
+
+ p = ( t - t0 ) / ( t1 - t0 ),
+ pp = p * p,
+ ppp = pp * p;
+
+ // evaluate polynomials
+
+ const sP = - wP * ppp + 2 * wP * pp - wP * p;
+ const s0 = ( 1 + wP ) * ppp + ( - 1.5 - 2 * wP ) * pp + ( - 0.5 + wP ) * p + 1;
+ const s1 = ( - 1 - wN ) * ppp + ( 1.5 + wN ) * pp + 0.5 * p;
+ const sN = wN * ppp - wN * pp;
+
+ // combine data linearly
+
+ for ( let i = 0; i !== stride; ++ i ) {
+
+ result[ i ] =
+ sP * values[ oP + i ] +
+ s0 * values[ o0 + i ] +
+ s1 * values[ o1 + i ] +
+ sN * values[ oN + i ];
+
+ }
+
+ return result;
+
+ }
+
+} );
+
+function LinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+ Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+}
+
+LinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+
+ constructor: LinearInterpolant,
+
+ interpolate_: function ( i1, t0, t, t1 ) {
+
+ const result = this.resultBuffer,
+ values = this.sampleValues,
+ stride = this.valueSize,
+
+ offset1 = i1 * stride,
+ offset0 = offset1 - stride,
+
+ weight1 = ( t - t0 ) / ( t1 - t0 ),
+ weight0 = 1 - weight1;
+
+ for ( let i = 0; i !== stride; ++ i ) {
+
+ result[ i ] =
+ values[ offset0 + i ] * weight0 +
+ values[ offset1 + i ] * weight1;
+
+ }
+
+ return result;
+
+ }
+
+} );
+
+/**
+ *
+ * Interpolant that evaluates to the sample value at the position preceeding
+ * the parameter.
+ */
+
+function DiscreteInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+ Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+}
+
+DiscreteInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+
+ constructor: DiscreteInterpolant,
+
+ interpolate_: function ( i1 /*, t0, t, t1 */ ) {
+
+ return this.copySampleValue_( i1 - 1 );
+
+ }
+
+} );
+
+function KeyframeTrack( name, times, values, interpolation ) {
+
+ if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' );
+ if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name );
+
+ this.name = name;
+
+ this.times = AnimationUtils.convertArray( times, this.TimeBufferType );
+ this.values = AnimationUtils.convertArray( values, this.ValueBufferType );
+
+ this.setInterpolation( interpolation || this.DefaultInterpolation );
+
+}
+
+// Static methods
+
+Object.assign( KeyframeTrack, {
+
+ // Serialization (in static context, because of constructor invocation
+ // and automatic invocation of .toJSON):
+
+ toJSON: function ( track ) {
+
+ const trackType = track.constructor;
+
+ let json;
+
+ // derived classes can define a static toJSON method
+ if ( trackType.toJSON !== undefined ) {
+
+ json = trackType.toJSON( track );
+
+ } else {
+
+ // by default, we assume the data can be serialized as-is
+ json = {
+
+ 'name': track.name,
+ 'times': AnimationUtils.convertArray( track.times, Array ),
+ 'values': AnimationUtils.convertArray( track.values, Array )
+
+ };
+
+ const interpolation = track.getInterpolation();
+
+ if ( interpolation !== track.DefaultInterpolation ) {
+
+ json.interpolation = interpolation;
+
+ }
+
+ }
+
+ json.type = track.ValueTypeName; // mandatory
+
+ return json;
+
+ }
+
+} );
+
+Object.assign( KeyframeTrack.prototype, {
+
+ constructor: KeyframeTrack,
+
+ TimeBufferType: Float32Array,
+
+ ValueBufferType: Float32Array,
+
+ DefaultInterpolation: InterpolateLinear,
+
+ InterpolantFactoryMethodDiscrete: function ( result ) {
+
+ return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );
+
+ },
+
+ InterpolantFactoryMethodLinear: function ( result ) {
+
+ return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );
+
+ },
+
+ InterpolantFactoryMethodSmooth: function ( result ) {
+
+ return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );
+
+ },
+
+ setInterpolation: function ( interpolation ) {
+
+ let factoryMethod;
+
+ switch ( interpolation ) {
+
+ case InterpolateDiscrete:
+
+ factoryMethod = this.InterpolantFactoryMethodDiscrete;
+
+ break;
+
+ case InterpolateLinear:
+
+ factoryMethod = this.InterpolantFactoryMethodLinear;
+
+ break;
+
+ case InterpolateSmooth:
+
+ factoryMethod = this.InterpolantFactoryMethodSmooth;
+
+ break;
+
+ }
+
+ if ( factoryMethod === undefined ) {
+
+ const message = 'unsupported interpolation for ' +
+ this.ValueTypeName + ' keyframe track named ' + this.name;
+
+ if ( this.createInterpolant === undefined ) {
+
+ // fall back to default, unless the default itself is messed up
+ if ( interpolation !== this.DefaultInterpolation ) {
+
+ this.setInterpolation( this.DefaultInterpolation );
+
+ } else {
+
+ throw new Error( message ); // fatal, in this case
+
+ }
+
+ }
+
+ console.warn( 'THREE.KeyframeTrack:', message );
+ return this;
+
+ }
+
+ this.createInterpolant = factoryMethod;
+
+ return this;
+
+ },
+
+ getInterpolation: function () {
+
+ switch ( this.createInterpolant ) {
+
+ case this.InterpolantFactoryMethodDiscrete:
+
+ return InterpolateDiscrete;
+
+ case this.InterpolantFactoryMethodLinear:
+
+ return InterpolateLinear;
+
+ case this.InterpolantFactoryMethodSmooth:
+
+ return InterpolateSmooth;
+
+ }
+
+ },
+
+ getValueSize: function () {
+
+ return this.values.length / this.times.length;
+
+ },
+
+ // move all keyframes either forwards or backwards in time
+ shift: function ( timeOffset ) {
+
+ if ( timeOffset !== 0.0 ) {
+
+ const times = this.times;
+
+ for ( let i = 0, n = times.length; i !== n; ++ i ) {
+
+ times[ i ] += timeOffset;
+
+ }
+
+ }
+
+ return this;
+
+ },
+
+ // scale all keyframe times by a factor (useful for frame <-> seconds conversions)
+ scale: function ( timeScale ) {
+
+ if ( timeScale !== 1.0 ) {
+
+ const times = this.times;
+
+ for ( let i = 0, n = times.length; i !== n; ++ i ) {
+
+ times[ i ] *= timeScale;
+
+ }
+
+ }
+
+ return this;
+
+ },
+
+ // removes keyframes before and after animation without changing any values within the range [startTime, endTime].
+ // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values
+ trim: function ( startTime, endTime ) {
+
+ const times = this.times,
+ nKeys = times.length;
+
+ let from = 0,
+ to = nKeys - 1;
+
+ while ( from !== nKeys && times[ from ] < startTime ) {
+
+ ++ from;
+
+ }
+
+ while ( to !== - 1 && times[ to ] > endTime ) {
+
+ -- to;
+
+ }
+
+ ++ to; // inclusive -> exclusive bound
+
+ if ( from !== 0 || to !== nKeys ) {
+
+ // empty tracks are forbidden, so keep at least one keyframe
+ if ( from >= to ) {
+
+ to = Math.max( to, 1 );
+ from = to - 1;
+
+ }
+
+ const stride = this.getValueSize();
+ this.times = AnimationUtils.arraySlice( times, from, to );
+ this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride );
+
+ }
+
+ return this;
+
+ },
+
+ // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
+ validate: function () {
+
+ let valid = true;
+
+ const valueSize = this.getValueSize();
+ if ( valueSize - Math.floor( valueSize ) !== 0 ) {
+
+ console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
+ valid = false;
+
+ }
+
+ const times = this.times,
+ values = this.values,
+
+ nKeys = times.length;
+
+ if ( nKeys === 0 ) {
+
+ console.error( 'THREE.KeyframeTrack: Track is empty.', this );
+ valid = false;
+
+ }
+
+ let prevTime = null;
+
+ for ( let i = 0; i !== nKeys; i ++ ) {
+
+ const currTime = times[ i ];
+
+ if ( typeof currTime === 'number' && isNaN( currTime ) ) {
+
+ console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime );
+ valid = false;
+ break;
+
+ }
+
+ if ( prevTime !== null && prevTime > currTime ) {
+
+ console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime );
+ valid = false;
+ break;
+
+ }
+
+ prevTime = currTime;
+
+ }
+
+ if ( values !== undefined ) {
+
+ if ( AnimationUtils.isTypedArray( values ) ) {
+
+ for ( let i = 0, n = values.length; i !== n; ++ i ) {
+
+ const value = values[ i ];
+
+ if ( isNaN( value ) ) {
+
+ console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
+ valid = false;
+ break;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ return valid;
+
+ },
+
+ // removes equivalent sequential keys as common in morph target sequences
+ // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
+ optimize: function () {
+
+ // times or values may be shared with other tracks, so overwriting is unsafe
+ const times = AnimationUtils.arraySlice( this.times ),
+ values = AnimationUtils.arraySlice( this.values ),
+ stride = this.getValueSize(),
+
+ smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
+
+ lastIndex = times.length - 1;
+
+ let writeIndex = 1;
+
+ for ( let i = 1; i < lastIndex; ++ i ) {
+
+ let keep = false;
+
+ const time = times[ i ];
+ const timeNext = times[ i + 1 ];
+
+ // remove adjacent keyframes scheduled at the same time
+
+ if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) {
+
+ if ( ! smoothInterpolation ) {
+
+ // remove unnecessary keyframes same as their neighbors
+
+ const offset = i * stride,
+ offsetP = offset - stride,
+ offsetN = offset + stride;
+
+ for ( let j = 0; j !== stride; ++ j ) {
+
+ const value = values[ offset + j ];
+
+ if ( value !== values[ offsetP + j ] ||
+ value !== values[ offsetN + j ] ) {
+
+ keep = true;
+ break;
+
+ }
+
+ }
+
+ } else {
+
+ keep = true;
+
+ }
+
+ }
+
+ // in-place compaction
+
+ if ( keep ) {
+
+ if ( i !== writeIndex ) {
+
+ times[ writeIndex ] = times[ i ];
+
+ const readOffset = i * stride,
+ writeOffset = writeIndex * stride;
+
+ for ( let j = 0; j !== stride; ++ j ) {
+
+ values[ writeOffset + j ] = values[ readOffset + j ];
+
+ }
+
+ }
+
+ ++ writeIndex;
+
+ }
+
+ }
+
+ // flush last keyframe (compaction looks ahead)
+
+ if ( lastIndex > 0 ) {
+
+ times[ writeIndex ] = times[ lastIndex ];
+
+ for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
+
+ values[ writeOffset + j ] = values[ readOffset + j ];
+
+ }
+
+ ++ writeIndex;
+
+ }
+
+ if ( writeIndex !== times.length ) {
+
+ this.times = AnimationUtils.arraySlice( times, 0, writeIndex );
+ this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride );
+
+ } else {
+
+ this.times = times;
+ this.values = values;
+
+ }
+
+ return this;
+
+ },
+
+ clone: function () {
+
+ const times = AnimationUtils.arraySlice( this.times, 0 );
+ const values = AnimationUtils.arraySlice( this.values, 0 );
+
+ const TypedKeyframeTrack = this.constructor;
+ const track = new TypedKeyframeTrack( this.name, times, values );
+
+ // Interpolant argument to constructor is not saved, so copy the factory method directly.
+ track.createInterpolant = this.createInterpolant;
+
+ return track;
+
+ }
+
+} );
+
+/**
+ * A Track of Boolean keyframe values.
+ */
+
+function BooleanKeyframeTrack( name, times, values ) {
+
+ KeyframeTrack.call( this, name, times, values );
+
+}
+
+BooleanKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+ constructor: BooleanKeyframeTrack,
+
+ ValueTypeName: 'bool',
+ ValueBufferType: Array,
+
+ DefaultInterpolation: InterpolateDiscrete,
+
+ InterpolantFactoryMethodLinear: undefined,
+ InterpolantFactoryMethodSmooth: undefined
+
+ // Note: Actually this track could have a optimized / compressed
+ // representation of a single value and a custom interpolant that
+ // computes "firstValue ^ isOdd( index )".
+
+} );
+
+/**
+ * A Track of keyframe values that represent color.
+ */
+
+function ColorKeyframeTrack( name, times, values, interpolation ) {
+
+ KeyframeTrack.call( this, name, times, values, interpolation );
+
+}
+
+ColorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+ constructor: ColorKeyframeTrack,
+
+ ValueTypeName: 'color'
+
+ // ValueBufferType is inherited
+
+ // DefaultInterpolation is inherited
+
+ // Note: Very basic implementation and nothing special yet.
+ // However, this is the place for color space parameterization.
+
+} );
+
+/**
+ * A Track of numeric keyframe values.
+ */
+
+function NumberKeyframeTrack( name, times, values, interpolation ) {
+
+ KeyframeTrack.call( this, name, times, values, interpolation );
+
+}
+
+NumberKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+ constructor: NumberKeyframeTrack,
+
+ ValueTypeName: 'number'
+
+ // ValueBufferType is inherited
+
+ // DefaultInterpolation is inherited
+
+} );
+
+/**
+ * Spherical linear unit quaternion interpolant.
+ */
+
+function QuaternionLinearInterpolant( parameterPositions, sampleValues, sampleSize, resultBuffer ) {
+
+ Interpolant.call( this, parameterPositions, sampleValues, sampleSize, resultBuffer );
+
+}
+
+QuaternionLinearInterpolant.prototype = Object.assign( Object.create( Interpolant.prototype ), {
+
+ constructor: QuaternionLinearInterpolant,
+
+ interpolate_: function ( i1, t0, t, t1 ) {
+
+ const result = this.resultBuffer,
+ values = this.sampleValues,
+ stride = this.valueSize,
+
+ alpha = ( t - t0 ) / ( t1 - t0 );
+
+ let offset = i1 * stride;
+
+ for ( let end = offset + stride; offset !== end; offset += 4 ) {
+
+ Quaternion.slerpFlat( result, 0, values, offset - stride, values, offset, alpha );
+
+ }
+
+ return result;
+
+ }
+
+} );
+
+/**
+ * A Track of quaternion keyframe values.
+ */
+
+function QuaternionKeyframeTrack( name, times, values, interpolation ) {
+
+ KeyframeTrack.call( this, name, times, values, interpolation );
+
+}
+
+QuaternionKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+ constructor: QuaternionKeyframeTrack,
+
+ ValueTypeName: 'quaternion',
+
+ // ValueBufferType is inherited
+
+ DefaultInterpolation: InterpolateLinear,
+
+ InterpolantFactoryMethodLinear: function ( result ) {
+
+ return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result );
+
+ },
+
+ InterpolantFactoryMethodSmooth: undefined // not yet implemented
+
+} );
+
+/**
+ * A Track that interpolates Strings
+ */
+
+function StringKeyframeTrack( name, times, values, interpolation ) {
+
+ KeyframeTrack.call( this, name, times, values, interpolation );
+
+}
+
+StringKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+ constructor: StringKeyframeTrack,
+
+ ValueTypeName: 'string',
+ ValueBufferType: Array,
+
+ DefaultInterpolation: InterpolateDiscrete,
+
+ InterpolantFactoryMethodLinear: undefined,
+
+ InterpolantFactoryMethodSmooth: undefined
+
+} );
+
+/**
+ * A Track of vectored keyframe values.
+ */
+
+function VectorKeyframeTrack( name, times, values, interpolation ) {
+
+ KeyframeTrack.call( this, name, times, values, interpolation );
+
+}
+
+VectorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
+
+ constructor: VectorKeyframeTrack,
+
+ ValueTypeName: 'vector'
+
+ // ValueBufferType is inherited
+
+ // DefaultInterpolation is inherited
+
+} );
+
+function AnimationClip( name, duration = - 1, tracks, blendMode = NormalAnimationBlendMode ) {
+
+ this.name = name;
+ this.tracks = tracks;
+ this.duration = duration;
+ this.blendMode = blendMode;
+
+ this.uuid = MathUtils.generateUUID();
+
+ // this means it should figure out its duration by scanning the tracks
+ if ( this.duration < 0 ) {
+
+ this.resetDuration();
+
+ }
+
+}
+
+function getTrackTypeForValueTypeName( typeName ) {
+
+ switch ( typeName.toLowerCase() ) {
+
+ case 'scalar':
+ case 'double':
+ case 'float':
+ case 'number':
+ case 'integer':
+
+ return NumberKeyframeTrack;
+
+ case 'vector':
+ case 'vector2':
+ case 'vector3':
+ case 'vector4':
+
+ return VectorKeyframeTrack;
+
+ case 'color':
+
+ return ColorKeyframeTrack;
+
+ case 'quaternion':
+
+ return QuaternionKeyframeTrack;
+
+ case 'bool':
+ case 'boolean':
+
+ return BooleanKeyframeTrack;
+
+ case 'string':
+
+ return StringKeyframeTrack;
+
+ }
+
+ throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName );
+
+}
+
+function parseKeyframeTrack( json ) {
+
+ if ( json.type === undefined ) {
+
+ throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' );
+
+ }
+
+ const trackType = getTrackTypeForValueTypeName( json.type );
+
+ if ( json.times === undefined ) {
+
+ const times = [], values = [];
+
+ AnimationUtils.flattenJSON( json.keys, times, values, 'value' );
+
+ json.times = times;
+ json.values = values;
+
+ }
+
+ // derived classes can define a static parse method
+ if ( trackType.parse !== undefined ) {
+
+ return trackType.parse( json );
+
+ } else {
+
+ // by default, we assume a constructor compatible with the base
+ return new trackType( json.name, json.times, json.values, json.interpolation );
+
+ }
+
+}
+
+Object.assign( AnimationClip, {
+
+ parse: function ( json ) {
+
+ const tracks = [],
+ jsonTracks = json.tracks,
+ frameTime = 1.0 / ( json.fps || 1.0 );
+
+ for ( let i = 0, n = jsonTracks.length; i !== n; ++ i ) {
+
+ tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) );
+
+ }
+
+ const clip = new AnimationClip( json.name, json.duration, tracks, json.blendMode );
+ clip.uuid = json.uuid;
+
+ return clip;
+
+ },
+
+ toJSON: function ( clip ) {
+
+ const tracks = [],
+ clipTracks = clip.tracks;
+
+ const json = {
+
+ 'name': clip.name,
+ 'duration': clip.duration,
+ 'tracks': tracks,
+ 'uuid': clip.uuid,
+ 'blendMode': clip.blendMode
+
+ };
+
+ for ( let i = 0, n = clipTracks.length; i !== n; ++ i ) {
+
+ tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );
+
+ }
+
+ return json;
+
+ },
+
+ CreateFromMorphTargetSequence: function ( name, morphTargetSequence, fps, noLoop ) {
+
+ const numMorphTargets = morphTargetSequence.length;
+ const tracks = [];
+
+ for ( let i = 0; i < numMorphTargets; i ++ ) {
+
+ let times = [];
+ let values = [];
+
+ times.push(
+ ( i + numMorphTargets - 1 ) % numMorphTargets,
+ i,
+ ( i + 1 ) % numMorphTargets );
+
+ values.push( 0, 1, 0 );
+
+ const order = AnimationUtils.getKeyframeOrder( times );
+ times = AnimationUtils.sortedArray( times, 1, order );
+ values = AnimationUtils.sortedArray( values, 1, order );
+
+ // if there is a key at the first frame, duplicate it as the
+ // last frame as well for perfect loop.
+ if ( ! noLoop && times[ 0 ] === 0 ) {
+
+ times.push( numMorphTargets );
+ values.push( values[ 0 ] );
+
+ }
+
+ tracks.push(
+ new NumberKeyframeTrack(
+ '.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',
+ times, values
+ ).scale( 1.0 / fps ) );
+
+ }
+
+ return new AnimationClip( name, - 1, tracks );
+
+ },
+
+ findByName: function ( objectOrClipArray, name ) {
+
+ let clipArray = objectOrClipArray;
+
+ if ( ! Array.isArray( objectOrClipArray ) ) {
+
+ const o = objectOrClipArray;
+ clipArray = o.geometry && o.geometry.animations || o.animations;
+
+ }
+
+ for ( let i = 0; i < clipArray.length; i ++ ) {
+
+ if ( clipArray[ i ].name === name ) {
+
+ return clipArray[ i ];
+
+ }
+
+ }
+
+ return null;
+
+ },
+
+ CreateClipsFromMorphTargetSequences: function ( morphTargets, fps, noLoop ) {
+
+ const animationToMorphTargets = {};
+
+ // tested with https://regex101.com/ on trick sequences
+ // such flamingo_flyA_003, flamingo_run1_003, crdeath0059
+ const pattern = /^([\w-]*?)([\d]+)$/;
+
+ // sort morph target names into animation groups based
+ // patterns like Walk_001, Walk_002, Run_001, Run_002
+ for ( let i = 0, il = morphTargets.length; i < il; i ++ ) {
+
+ const morphTarget = morphTargets[ i ];
+ const parts = morphTarget.name.match( pattern );
+
+ if ( parts && parts.length > 1 ) {
+
+ const name = parts[ 1 ];
+
+ let animationMorphTargets = animationToMorphTargets[ name ];
+
+ if ( ! animationMorphTargets ) {
+
+ animationToMorphTargets[ name ] = animationMorphTargets = [];
+
+ }
+
+ animationMorphTargets.push( morphTarget );
+
+ }
+
+ }
+
+ const clips = [];
+
+ for ( const name in animationToMorphTargets ) {
+
+ clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );
+
+ }
+
+ return clips;
+
+ },
+
+ // parse the animation.hierarchy format
+ parseAnimation: function ( animation, bones ) {
+
+ if ( ! animation ) {
+
+ console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' );
+ return null;
+
+ }
+
+ const addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) {
+
+ // only return track if there are actually keys.
+ if ( animationKeys.length !== 0 ) {
+
+ const times = [];
+ const values = [];
+
+ AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );
+
+ // empty keys are filtered out, so check again
+ if ( times.length !== 0 ) {
+
+ destTracks.push( new trackType( trackName, times, values ) );
+
+ }
+
+ }
+
+ };
+
+ const tracks = [];
+
+ const clipName = animation.name || 'default';
+ const fps = animation.fps || 30;
+ const blendMode = animation.blendMode;
+
+ // automatic length determination in AnimationClip.
+ let duration = animation.length || - 1;
+
+ const hierarchyTracks = animation.hierarchy || [];
+
+ for ( let h = 0; h < hierarchyTracks.length; h ++ ) {
+
+ const animationKeys = hierarchyTracks[ h ].keys;
+
+ // skip empty tracks
+ if ( ! animationKeys || animationKeys.length === 0 ) continue;
+
+ // process morph targets
+ if ( animationKeys[ 0 ].morphTargets ) {
+
+ // figure out all morph targets used in this track
+ const morphTargetNames = {};
+
+ let k;
+
+ for ( k = 0; k < animationKeys.length; k ++ ) {
+
+ if ( animationKeys[ k ].morphTargets ) {
+
+ for ( let m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) {
+
+ morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1;
+
+ }
+
+ }
+
+ }
+
+ // create a track for each morph target with all zero
+ // morphTargetInfluences except for the keys in which
+ // the morphTarget is named.
+ for ( const morphTargetName in morphTargetNames ) {
+
+ const times = [];
+ const values = [];
+
+ for ( let m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) {
+
+ const animationKey = animationKeys[ k ];
+
+ times.push( animationKey.time );
+ values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );
+
+ }
+
+ tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) );
+
+ }
+
+ duration = morphTargetNames.length * ( fps || 1.0 );
+
+ } else {
+
+ // ...assume skeletal animation
+
+ const boneName = '.bones[' + bones[ h ].name + ']';
+
+ addNonemptyTrack(
+ VectorKeyframeTrack, boneName + '.position',
+ animationKeys, 'pos', tracks );
+
+ addNonemptyTrack(
+ QuaternionKeyframeTrack, boneName + '.quaternion',
+ animationKeys, 'rot', tracks );
+
+ addNonemptyTrack(
+ VectorKeyframeTrack, boneName + '.scale',
+ animationKeys, 'scl', tracks );
+
+ }
+
+ }
+
+ if ( tracks.length === 0 ) {
+
+ return null;
+
+ }
+
+ const clip = new AnimationClip( clipName, duration, tracks, blendMode );
+
+ return clip;
+
+ }
+
+} );
+
+Object.assign( AnimationClip.prototype, {
+
+ resetDuration: function () {
+
+ const tracks = this.tracks;
+ let duration = 0;
+
+ for ( let i = 0, n = tracks.length; i !== n; ++ i ) {
+
+ const track = this.tracks[ i ];
+
+ duration = Math.max( duration, track.times[ track.times.length - 1 ] );
+
+ }
+
+ this.duration = duration;
+
+ return this;
+
+ },
+
+ trim: function () {
+
+ for ( let i = 0; i < this.tracks.length; i ++ ) {
+
+ this.tracks[ i ].trim( 0, this.duration );
+
+ }
+
+ return this;
+
+ },
+
+ validate: function () {
+
+ let valid = true;
+
+ for ( let i = 0; i < this.tracks.length; i ++ ) {
+
+ valid = valid && this.tracks[ i ].validate();
+
+ }
+
+ return valid;
+
+ },
+
+ optimize: function () {
+
+ for ( let i = 0; i < this.tracks.length; i ++ ) {
+
+ this.tracks[ i ].optimize();
+
+ }
+
+ return this;
+
+ },
+
+ clone: function () {
+
+ const tracks = [];
+
+ for ( let i = 0; i < this.tracks.length; i ++ ) {
+
+ tracks.push( this.tracks[ i ].clone() );
+
+ }
+
+ return new AnimationClip( this.name, this.duration, tracks, this.blendMode );
+
+ },
+
+ toJSON: function () {
+
+ return AnimationClip.toJSON( this );
+
+ }
+
+} );
+
+const Cache = {
+
+ enabled: false,
+
+ files: {},
+
+ add: function ( key, file ) {
+
+ if ( this.enabled === false ) return;
+
+ // console.log( 'THREE.Cache', 'Adding key:', key );
+
+ this.files[ key ] = file;
+
+ },
+
+ get: function ( key ) {
+
+ if ( this.enabled === false ) return;
+
+ // console.log( 'THREE.Cache', 'Checking key:', key );
+
+ return this.files[ key ];
+
+ },
+
+ remove: function ( key ) {
+
+ delete this.files[ key ];
+
+ },
+
+ clear: function () {
+
+ this.files = {};
+
+ }
+
+};
+
+function LoadingManager( onLoad, onProgress, onError ) {
+
+ const scope = this;
+
+ let isLoading = false;
+ let itemsLoaded = 0;
+ let itemsTotal = 0;
+ let urlModifier = undefined;
+ const handlers = [];
+
+ // Refer to #5689 for the reason why we don't set .onStart
+ // in the constructor
+
+ this.onStart = undefined;
+ this.onLoad = onLoad;
+ this.onProgress = onProgress;
+ this.onError = onError;
+
+ this.itemStart = function ( url ) {
+
+ itemsTotal ++;
+
+ if ( isLoading === false ) {
+
+ if ( scope.onStart !== undefined ) {
+
+ scope.onStart( url, itemsLoaded, itemsTotal );
+
+ }
+
+ }
+
+ isLoading = true;
+
+ };
+
+ this.itemEnd = function ( url ) {
+
+ itemsLoaded ++;
+
+ if ( scope.onProgress !== undefined ) {
+
+ scope.onProgress( url, itemsLoaded, itemsTotal );
+
+ }
+
+ if ( itemsLoaded === itemsTotal ) {
+
+ isLoading = false;
+
+ if ( scope.onLoad !== undefined ) {
+
+ scope.onLoad();
+
+ }
+
+ }
+
+ };
+
+ this.itemError = function ( url ) {
+
+ if ( scope.onError !== undefined ) {
+
+ scope.onError( url );
+
+ }
+
+ };
+
+ this.resolveURL = function ( url ) {
+
+ if ( urlModifier ) {
+
+ return urlModifier( url );
+
+ }
+
+ return url;
+
+ };
+
+ this.setURLModifier = function ( transform ) {
+
+ urlModifier = transform;
+
+ return this;
+
+ };
+
+ this.addHandler = function ( regex, loader ) {
+
+ handlers.push( regex, loader );
+
+ return this;
+
+ };
+
+ this.removeHandler = function ( regex ) {
+
+ const index = handlers.indexOf( regex );
+
+ if ( index !== - 1 ) {
+
+ handlers.splice( index, 2 );
+
+ }
+
+ return this;
+
+ };
+
+ this.getHandler = function ( file ) {
+
+ for ( let i = 0, l = handlers.length; i < l; i += 2 ) {
+
+ const regex = handlers[ i ];
+ const loader = handlers[ i + 1 ];
+
+ if ( regex.global ) regex.lastIndex = 0; // see #17920
+
+ if ( regex.test( file ) ) {
+
+ return loader;
+
+ }
+
+ }
+
+ return null;
+
+ };
+
+}
+
+const DefaultLoadingManager = new LoadingManager();
+
+function Loader( manager ) {
+
+ this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+ this.crossOrigin = 'anonymous';
+ this.withCredentials = false;
+ this.path = '';
+ this.resourcePath = '';
+ this.requestHeader = {};
+
+}
+
+Object.assign( Loader.prototype, {
+
+ load: function ( /* url, onLoad, onProgress, onError */ ) {},
+
+ loadAsync: function ( url, onProgress ) {
+
+ const scope = this;
+
+ return new Promise( function ( resolve, reject ) {
+
+ scope.load( url, resolve, onProgress, reject );
+
+ } );
+
+ },
+
+ parse: function ( /* data */ ) {},
+
+ setCrossOrigin: function ( crossOrigin ) {
+
+ this.crossOrigin = crossOrigin;
+ return this;
+
+ },
+
+ setWithCredentials: function ( value ) {
+
+ this.withCredentials = value;
+ return this;
+
+ },
+
+ setPath: function ( path ) {
+
+ this.path = path;
+ return this;
+
+ },
+
+ setResourcePath: function ( resourcePath ) {
+
+ this.resourcePath = resourcePath;
+ return this;
+
+ },
+
+ setRequestHeader: function ( requestHeader ) {
+
+ this.requestHeader = requestHeader;
+ return this;
+
+ }
+
+} );
+
+const loading = {};
+
+function FileLoader( manager ) {
+
+ Loader.call( this, manager );
+
+}
+
+FileLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: FileLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ if ( url === undefined ) url = '';
+
+ if ( this.path !== undefined ) url = this.path + url;
+
+ url = this.manager.resolveURL( url );
+
+ const scope = this;
+
+ const cached = Cache.get( url );
+
+ if ( cached !== undefined ) {
+
+ scope.manager.itemStart( url );
+
+ setTimeout( function () {
+
+ if ( onLoad ) onLoad( cached );
+
+ scope.manager.itemEnd( url );
+
+ }, 0 );
+
+ return cached;
+
+ }
+
+ // Check if request is duplicate
+
+ if ( loading[ url ] !== undefined ) {
+
+ loading[ url ].push( {
+
+ onLoad: onLoad,
+ onProgress: onProgress,
+ onError: onError
+
+ } );
+
+ return;
+
+ }
+
+ // Check for data: URI
+ const dataUriRegex = /^data:(.*?)(;base64)?,(.*)$/;
+ const dataUriRegexResult = url.match( dataUriRegex );
+ let request;
+
+ // Safari can not handle Data URIs through XMLHttpRequest so process manually
+ if ( dataUriRegexResult ) {
+
+ const mimeType = dataUriRegexResult[ 1 ];
+ const isBase64 = !! dataUriRegexResult[ 2 ];
+
+ let data = dataUriRegexResult[ 3 ];
+ data = decodeURIComponent( data );
+
+ if ( isBase64 ) data = atob( data );
+
+ try {
+
+ let response;
+ const responseType = ( this.responseType || '' ).toLowerCase();
+
+ switch ( responseType ) {
+
+ case 'arraybuffer':
+ case 'blob':
+
+ const view = new Uint8Array( data.length );
+
+ for ( let i = 0; i < data.length; i ++ ) {
+
+ view[ i ] = data.charCodeAt( i );
+
+ }
+
+ if ( responseType === 'blob' ) {
+
+ response = new Blob( [ view.buffer ], { type: mimeType } );
+
+ } else {
+
+ response = view.buffer;
+
+ }
+
+ break;
+
+ case 'document':
+
+ const parser = new DOMParser();
+ response = parser.parseFromString( data, mimeType );
+
+ break;
+
+ case 'json':
+
+ response = JSON.parse( data );
+
+ break;
+
+ default: // 'text' or other
+
+ response = data;
+
+ break;
+
+ }
+
+ // Wait for next browser tick like standard XMLHttpRequest event dispatching does
+ setTimeout( function () {
+
+ if ( onLoad ) onLoad( response );
+
+ scope.manager.itemEnd( url );
+
+ }, 0 );
+
+ } catch ( error ) {
+
+ // Wait for next browser tick like standard XMLHttpRequest event dispatching does
+ setTimeout( function () {
+
+ if ( onError ) onError( error );
+
+ scope.manager.itemError( url );
+ scope.manager.itemEnd( url );
+
+ }, 0 );
+
+ }
+
+ } else {
+
+ // Initialise array for duplicate requests
+
+ loading[ url ] = [];
+
+ loading[ url ].push( {
+
+ onLoad: onLoad,
+ onProgress: onProgress,
+ onError: onError
+
+ } );
+
+ request = new XMLHttpRequest();
+
+ request.open( 'GET', url, true );
+
+ request.addEventListener( 'load', function ( event ) {
+
+ const response = this.response;
+
+ const callbacks = loading[ url ];
+
+ delete loading[ url ];
+
+ if ( this.status === 200 || this.status === 0 ) {
+
+ // Some browsers return HTTP Status 0 when using non-http protocol
+ // e.g. 'file://' or 'data://'. Handle as success.
+
+ if ( this.status === 0 ) console.warn( 'THREE.FileLoader: HTTP Status 0 received.' );
+
+ // Add to cache only on HTTP success, so that we do not cache
+ // error response bodies as proper responses to requests.
+ Cache.add( url, response );
+
+ for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+
+ const callback = callbacks[ i ];
+ if ( callback.onLoad ) callback.onLoad( response );
+
+ }
+
+ scope.manager.itemEnd( url );
+
+ } else {
+
+ for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+
+ const callback = callbacks[ i ];
+ if ( callback.onError ) callback.onError( event );
+
+ }
+
+ scope.manager.itemError( url );
+ scope.manager.itemEnd( url );
+
+ }
+
+ }, false );
+
+ request.addEventListener( 'progress', function ( event ) {
+
+ const callbacks = loading[ url ];
+
+ for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+
+ const callback = callbacks[ i ];
+ if ( callback.onProgress ) callback.onProgress( event );
+
+ }
+
+ }, false );
+
+ request.addEventListener( 'error', function ( event ) {
+
+ const callbacks = loading[ url ];
+
+ delete loading[ url ];
+
+ for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+
+ const callback = callbacks[ i ];
+ if ( callback.onError ) callback.onError( event );
+
+ }
+
+ scope.manager.itemError( url );
+ scope.manager.itemEnd( url );
+
+ }, false );
+
+ request.addEventListener( 'abort', function ( event ) {
+
+ const callbacks = loading[ url ];
+
+ delete loading[ url ];
+
+ for ( let i = 0, il = callbacks.length; i < il; i ++ ) {
+
+ const callback = callbacks[ i ];
+ if ( callback.onError ) callback.onError( event );
+
+ }
+
+ scope.manager.itemError( url );
+ scope.manager.itemEnd( url );
+
+ }, false );
+
+ if ( this.responseType !== undefined ) request.responseType = this.responseType;
+ if ( this.withCredentials !== undefined ) request.withCredentials = this.withCredentials;
+
+ if ( request.overrideMimeType ) request.overrideMimeType( this.mimeType !== undefined ? this.mimeType : 'text/plain' );
+
+ for ( const header in this.requestHeader ) {
+
+ request.setRequestHeader( header, this.requestHeader[ header ] );
+
+ }
+
+ request.send( null );
+
+ }
+
+ scope.manager.itemStart( url );
+
+ return request;
+
+ },
+
+ setResponseType: function ( value ) {
+
+ this.responseType = value;
+ return this;
+
+ },
+
+ setMimeType: function ( value ) {
+
+ this.mimeType = value;
+ return this;
+
+ }
+
+} );
+
+function AnimationLoader( manager ) {
+
+ Loader.call( this, manager );
+
+}
+
+AnimationLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: AnimationLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ const scope = this;
+
+ const loader = new FileLoader( scope.manager );
+ loader.setPath( scope.path );
+ loader.setRequestHeader( scope.requestHeader );
+ loader.setWithCredentials( scope.withCredentials );
+ loader.load( url, function ( text ) {
+
+ try {
+
+ onLoad( scope.parse( JSON.parse( text ) ) );
+
+ } catch ( e ) {
+
+ if ( onError ) {
+
+ onError( e );
+
+ } else {
+
+ console.error( e );
+
+ }
+
+ scope.manager.itemError( url );
+
+ }
+
+ }, onProgress, onError );
+
+ },
+
+ parse: function ( json ) {
+
+ const animations = [];
+
+ for ( let i = 0; i < json.length; i ++ ) {
+
+ const clip = AnimationClip.parse( json[ i ] );
+
+ animations.push( clip );
+
+ }
+
+ return animations;
+
+ }
+
+} );
+
+/**
+ * Abstract Base class to block based textures loader (dds, pvr, ...)
+ *
+ * Sub classes have to implement the parse() method which will be used in load().
+ */
+
+function CompressedTextureLoader( manager ) {
+
+ Loader.call( this, manager );
+
+}
+
+CompressedTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: CompressedTextureLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ const scope = this;
+
+ const images = [];
+
+ const texture = new CompressedTexture();
+
+ const loader = new FileLoader( this.manager );
+ loader.setPath( this.path );
+ loader.setResponseType( 'arraybuffer' );
+ loader.setRequestHeader( this.requestHeader );
+ loader.setWithCredentials( scope.withCredentials );
+
+ let loaded = 0;
+
+ function loadTexture( i ) {
+
+ loader.load( url[ i ], function ( buffer ) {
+
+ const texDatas = scope.parse( buffer, true );
+
+ images[ i ] = {
+ width: texDatas.width,
+ height: texDatas.height,
+ format: texDatas.format,
+ mipmaps: texDatas.mipmaps
+ };
+
+ loaded += 1;
+
+ if ( loaded === 6 ) {
+
+ if ( texDatas.mipmapCount === 1 ) texture.minFilter = LinearFilter;
+
+ texture.image = images;
+ texture.format = texDatas.format;
+ texture.needsUpdate = true;
+
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ }, onProgress, onError );
+
+ }
+
+ if ( Array.isArray( url ) ) {
+
+ for ( let i = 0, il = url.length; i < il; ++ i ) {
+
+ loadTexture( i );
+
+ }
+
+ } else {
+
+ // compressed cubemap texture stored in a single DDS file
+
+ loader.load( url, function ( buffer ) {
+
+ const texDatas = scope.parse( buffer, true );
+
+ if ( texDatas.isCubemap ) {
+
+ const faces = texDatas.mipmaps.length / texDatas.mipmapCount;
+
+ for ( let f = 0; f < faces; f ++ ) {
+
+ images[ f ] = { mipmaps: [] };
+
+ for ( let i = 0; i < texDatas.mipmapCount; i ++ ) {
+
+ images[ f ].mipmaps.push( texDatas.mipmaps[ f * texDatas.mipmapCount + i ] );
+ images[ f ].format = texDatas.format;
+ images[ f ].width = texDatas.width;
+ images[ f ].height = texDatas.height;
+
+ }
+
+ }
+
+ texture.image = images;
+
+ } else {
+
+ texture.image.width = texDatas.width;
+ texture.image.height = texDatas.height;
+ texture.mipmaps = texDatas.mipmaps;
+
+ }
+
+ if ( texDatas.mipmapCount === 1 ) {
+
+ texture.minFilter = LinearFilter;
+
+ }
+
+ texture.format = texDatas.format;
+ texture.needsUpdate = true;
+
+ if ( onLoad ) onLoad( texture );
+
+ }, onProgress, onError );
+
+ }
+
+ return texture;
+
+ }
+
+} );
+
+function ImageLoader( manager ) {
+
+ Loader.call( this, manager );
+
+}
+
+ImageLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: ImageLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ if ( this.path !== undefined ) url = this.path + url;
+
+ url = this.manager.resolveURL( url );
+
+ const scope = this;
+
+ const cached = Cache.get( url );
+
+ if ( cached !== undefined ) {
+
+ scope.manager.itemStart( url );
+
+ setTimeout( function () {
+
+ if ( onLoad ) onLoad( cached );
+
+ scope.manager.itemEnd( url );
+
+ }, 0 );
+
+ return cached;
+
+ }
+
+ const image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' );
+
+ function onImageLoad() {
+
+ image.removeEventListener( 'load', onImageLoad, false );
+ image.removeEventListener( 'error', onImageError, false );
+
+ Cache.add( url, this );
+
+ if ( onLoad ) onLoad( this );
+
+ scope.manager.itemEnd( url );
+
+ }
+
+ function onImageError( event ) {
+
+ image.removeEventListener( 'load', onImageLoad, false );
+ image.removeEventListener( 'error', onImageError, false );
+
+ if ( onError ) onError( event );
+
+ scope.manager.itemError( url );
+ scope.manager.itemEnd( url );
+
+ }
+
+ image.addEventListener( 'load', onImageLoad, false );
+ image.addEventListener( 'error', onImageError, false );
+
+ if ( url.substr( 0, 5 ) !== 'data:' ) {
+
+ if ( this.crossOrigin !== undefined ) image.crossOrigin = this.crossOrigin;
+
+ }
+
+ scope.manager.itemStart( url );
+
+ image.src = url;
+
+ return image;
+
+ }
+
+} );
+
+function CubeTextureLoader( manager ) {
+
+ Loader.call( this, manager );
+
+}
+
+CubeTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: CubeTextureLoader,
+
+ load: function ( urls, onLoad, onProgress, onError ) {
+
+ const texture = new CubeTexture();
+
+ const loader = new ImageLoader( this.manager );
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.setPath( this.path );
+
+ let loaded = 0;
+
+ function loadTexture( i ) {
+
+ loader.load( urls[ i ], function ( image ) {
+
+ texture.images[ i ] = image;
+
+ loaded ++;
+
+ if ( loaded === 6 ) {
+
+ texture.needsUpdate = true;
+
+ if ( onLoad ) onLoad( texture );
+
+ }
+
+ }, undefined, onError );
+
+ }
+
+ for ( let i = 0; i < urls.length; ++ i ) {
+
+ loadTexture( i );
+
+ }
+
+ return texture;
+
+ }
+
+} );
+
+/**
+ * Abstract Base class to load generic binary textures formats (rgbe, hdr, ...)
+ *
+ * Sub classes have to implement the parse() method which will be used in load().
+ */
+
+function DataTextureLoader( manager ) {
+
+ Loader.call( this, manager );
+
+}
+
+DataTextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: DataTextureLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ const scope = this;
+
+ const texture = new DataTexture();
+
+ const loader = new FileLoader( this.manager );
+ loader.setResponseType( 'arraybuffer' );
+ loader.setRequestHeader( this.requestHeader );
+ loader.setPath( this.path );
+ loader.setWithCredentials( scope.withCredentials );
+ loader.load( url, function ( buffer ) {
+
+ const texData = scope.parse( buffer );
+
+ if ( ! texData ) return;
+
+ if ( texData.image !== undefined ) {
+
+ texture.image = texData.image;
+
+ } else if ( texData.data !== undefined ) {
+
+ texture.image.width = texData.width;
+ texture.image.height = texData.height;
+ texture.image.data = texData.data;
+
+ }
+
+ texture.wrapS = texData.wrapS !== undefined ? texData.wrapS : ClampToEdgeWrapping;
+ texture.wrapT = texData.wrapT !== undefined ? texData.wrapT : ClampToEdgeWrapping;
+
+ texture.magFilter = texData.magFilter !== undefined ? texData.magFilter : LinearFilter;
+ texture.minFilter = texData.minFilter !== undefined ? texData.minFilter : LinearFilter;
+
+ texture.anisotropy = texData.anisotropy !== undefined ? texData.anisotropy : 1;
+
+ if ( texData.encoding !== undefined ) {
+
+ texture.encoding = texData.encoding;
+
+ }
+
+ if ( texData.flipY !== undefined ) {
+
+ texture.flipY = texData.flipY;
+
+ }
+
+ if ( texData.format !== undefined ) {
+
+ texture.format = texData.format;
+
+ }
+
+ if ( texData.type !== undefined ) {
+
+ texture.type = texData.type;
+
+ }
+
+ if ( texData.mipmaps !== undefined ) {
+
+ texture.mipmaps = texData.mipmaps;
+ texture.minFilter = LinearMipmapLinearFilter; // presumably...
+
+ }
+
+ if ( texData.mipmapCount === 1 ) {
+
+ texture.minFilter = LinearFilter;
+
+ }
+
+ texture.needsUpdate = true;
+
+ if ( onLoad ) onLoad( texture, texData );
+
+ }, onProgress, onError );
+
+
+ return texture;
+
+ }
+
+} );
+
+function TextureLoader( manager ) {
+
+ Loader.call( this, manager );
+
+}
+
+TextureLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: TextureLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ const texture = new Texture();
+
+ const loader = new ImageLoader( this.manager );
+ loader.setCrossOrigin( this.crossOrigin );
+ loader.setPath( this.path );
+
+ loader.load( url, function ( image ) {
+
+ texture.image = image;
+
+ // JPEGs can't have an alpha channel, so memory can be saved by storing them as RGB.
+ const isJPEG = url.search( /\.jpe?g($|\?)/i ) > 0 || url.search( /^data\:image\/jpeg/ ) === 0;
+
+ texture.format = isJPEG ? RGBFormat : RGBAFormat;
+ texture.needsUpdate = true;
+
+ if ( onLoad !== undefined ) {
+
+ onLoad( texture );
+
+ }
+
+ }, onProgress, onError );
+
+ return texture;
+
+ }
+
+} );
+
+/**
+ * Extensible curve object.
+ *
+ * Some common of curve methods:
+ * .getPoint( t, optionalTarget ), .getTangent( t, optionalTarget )
+ * .getPointAt( u, optionalTarget ), .getTangentAt( u, optionalTarget )
+ * .getPoints(), .getSpacedPoints()
+ * .getLength()
+ * .updateArcLengths()
+ *
+ * This following curves inherit from THREE.Curve:
+ *
+ * -- 2D curves --
+ * THREE.ArcCurve
+ * THREE.CubicBezierCurve
+ * THREE.EllipseCurve
+ * THREE.LineCurve
+ * THREE.QuadraticBezierCurve
+ * THREE.SplineCurve
+ *
+ * -- 3D curves --
+ * THREE.CatmullRomCurve3
+ * THREE.CubicBezierCurve3
+ * THREE.LineCurve3
+ * THREE.QuadraticBezierCurve3
+ *
+ * A series of curves can be represented as a THREE.CurvePath.
+ *
+ **/
+
+function Curve() {
+
+ this.type = 'Curve';
+
+ this.arcLengthDivisions = 200;
+
+}
+
+Object.assign( Curve.prototype, {
+
+ // Virtual base class method to overwrite and implement in subclasses
+ // - t [0 .. 1]
+
+ getPoint: function ( /* t, optionalTarget */ ) {
+
+ console.warn( 'THREE.Curve: .getPoint() not implemented.' );
+ return null;
+
+ },
+
+ // Get point at relative position in curve according to arc length
+ // - u [0 .. 1]
+
+ getPointAt: function ( u, optionalTarget ) {
+
+ const t = this.getUtoTmapping( u );
+ return this.getPoint( t, optionalTarget );
+
+ },
+
+ // Get sequence of points using getPoint( t )
+
+ getPoints: function ( divisions = 5 ) {
+
+ const points = [];
+
+ for ( let d = 0; d <= divisions; d ++ ) {
+
+ points.push( this.getPoint( d / divisions ) );
+
+ }
+
+ return points;
+
+ },
+
+ // Get sequence of points using getPointAt( u )
+
+ getSpacedPoints: function ( divisions = 5 ) {
+
+ const points = [];
+
+ for ( let d = 0; d <= divisions; d ++ ) {
+
+ points.push( this.getPointAt( d / divisions ) );
+
+ }
+
+ return points;
+
+ },
+
+ // Get total curve arc length
+
+ getLength: function () {
+
+ const lengths = this.getLengths();
+ return lengths[ lengths.length - 1 ];
+
+ },
+
+ // Get list of cumulative segment lengths
+
+ getLengths: function ( divisions ) {
+
+ if ( divisions === undefined ) divisions = this.arcLengthDivisions;
+
+ if ( this.cacheArcLengths &&
+ ( this.cacheArcLengths.length === divisions + 1 ) &&
+ ! this.needsUpdate ) {
+
+ return this.cacheArcLengths;
+
+ }
+
+ this.needsUpdate = false;
+
+ const cache = [];
+ let current, last = this.getPoint( 0 );
+ let sum = 0;
+
+ cache.push( 0 );
+
+ for ( let p = 1; p <= divisions; p ++ ) {
+
+ current = this.getPoint( p / divisions );
+ sum += current.distanceTo( last );
+ cache.push( sum );
+ last = current;
+
+ }
+
+ this.cacheArcLengths = cache;
+
+ return cache; // { sums: cache, sum: sum }; Sum is in the last element.
+
+ },
+
+ updateArcLengths: function () {
+
+ this.needsUpdate = true;
+ this.getLengths();
+
+ },
+
+ // Given u ( 0 .. 1 ), get a t to find p. This gives you points which are equidistant
+
+ getUtoTmapping: function ( u, distance ) {
+
+ const arcLengths = this.getLengths();
+
+ let i = 0;
+ const il = arcLengths.length;
+
+ let targetArcLength; // The targeted u distance value to get
+
+ if ( distance ) {
+
+ targetArcLength = distance;
+
+ } else {
+
+ targetArcLength = u * arcLengths[ il - 1 ];
+
+ }
+
+ // binary search for the index with largest value smaller than target u distance
+
+ let low = 0, high = il - 1, comparison;
+
+ while ( low <= high ) {
+
+ i = Math.floor( low + ( high - low ) / 2 ); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats
+
+ comparison = arcLengths[ i ] - targetArcLength;
+
+ if ( comparison < 0 ) {
+
+ low = i + 1;
+
+ } else if ( comparison > 0 ) {
+
+ high = i - 1;
+
+ } else {
+
+ high = i;
+ break;
+
+ // DONE
+
+ }
+
+ }
+
+ i = high;
+
+ if ( arcLengths[ i ] === targetArcLength ) {
+
+ return i / ( il - 1 );
+
+ }
+
+ // we could get finer grain at lengths, or use simple interpolation between two points
+
+ const lengthBefore = arcLengths[ i ];
+ const lengthAfter = arcLengths[ i + 1 ];
+
+ const segmentLength = lengthAfter - lengthBefore;
+
+ // determine where we are between the 'before' and 'after' points
+
+ const segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;
+
+ // add that fractional amount to t
+
+ const t = ( i + segmentFraction ) / ( il - 1 );
+
+ return t;
+
+ },
+
+ // Returns a unit vector tangent at t
+ // In case any sub curve does not implement its tangent derivation,
+ // 2 points a small delta apart will be used to find its gradient
+ // which seems to give a reasonable approximation
+
+ getTangent: function ( t, optionalTarget ) {
+
+ const delta = 0.0001;
+ let t1 = t - delta;
+ let t2 = t + delta;
+
+ // Capping in case of danger
+
+ if ( t1 < 0 ) t1 = 0;
+ if ( t2 > 1 ) t2 = 1;
+
+ const pt1 = this.getPoint( t1 );
+ const pt2 = this.getPoint( t2 );
+
+ const tangent = optionalTarget || ( ( pt1.isVector2 ) ? new Vector2() : new Vector3() );
+
+ tangent.copy( pt2 ).sub( pt1 ).normalize();
+
+ return tangent;
+
+ },
+
+ getTangentAt: function ( u, optionalTarget ) {
+
+ const t = this.getUtoTmapping( u );
+ return this.getTangent( t, optionalTarget );
+
+ },
+
+ computeFrenetFrames: function ( segments, closed ) {
+
+ // see http://www.cs.indiana.edu/pub/techreports/TR425.pdf
+
+ const normal = new Vector3();
+
+ const tangents = [];
+ const normals = [];
+ const binormals = [];
+
+ const vec = new Vector3();
+ const mat = new Matrix4();
+
+ // compute the tangent vectors for each segment on the curve
+
+ for ( let i = 0; i <= segments; i ++ ) {
+
+ const u = i / segments;
+
+ tangents[ i ] = this.getTangentAt( u, new Vector3() );
+ tangents[ i ].normalize();
+
+ }
+
+ // select an initial normal vector perpendicular to the first tangent vector,
+ // and in the direction of the minimum tangent xyz component
+
+ normals[ 0 ] = new Vector3();
+ binormals[ 0 ] = new Vector3();
+ let min = Number.MAX_VALUE;
+ const tx = Math.abs( tangents[ 0 ].x );
+ const ty = Math.abs( tangents[ 0 ].y );
+ const tz = Math.abs( tangents[ 0 ].z );
+
+ if ( tx <= min ) {
+
+ min = tx;
+ normal.set( 1, 0, 0 );
+
+ }
+
+ if ( ty <= min ) {
+
+ min = ty;
+ normal.set( 0, 1, 0 );
+
+ }
+
+ if ( tz <= min ) {
+
+ normal.set( 0, 0, 1 );
+
+ }
+
+ vec.crossVectors( tangents[ 0 ], normal ).normalize();
+
+ normals[ 0 ].crossVectors( tangents[ 0 ], vec );
+ binormals[ 0 ].crossVectors( tangents[ 0 ], normals[ 0 ] );
+
+
+ // compute the slowly-varying normal and binormal vectors for each segment on the curve
+
+ for ( let i = 1; i <= segments; i ++ ) {
+
+ normals[ i ] = normals[ i - 1 ].clone();
+
+ binormals[ i ] = binormals[ i - 1 ].clone();
+
+ vec.crossVectors( tangents[ i - 1 ], tangents[ i ] );
+
+ if ( vec.length() > Number.EPSILON ) {
+
+ vec.normalize();
+
+ const theta = Math.acos( MathUtils.clamp( tangents[ i - 1 ].dot( tangents[ i ] ), - 1, 1 ) ); // clamp for floating pt errors
+
+ normals[ i ].applyMatrix4( mat.makeRotationAxis( vec, theta ) );
+
+ }
+
+ binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+
+ }
+
+ // if the curve is closed, postprocess the vectors so the first and last normal vectors are the same
+
+ if ( closed === true ) {
+
+ let theta = Math.acos( MathUtils.clamp( normals[ 0 ].dot( normals[ segments ] ), - 1, 1 ) );
+ theta /= segments;
+
+ if ( tangents[ 0 ].dot( vec.crossVectors( normals[ 0 ], normals[ segments ] ) ) > 0 ) {
+
+ theta = - theta;
+
+ }
+
+ for ( let i = 1; i <= segments; i ++ ) {
+
+ // twist a little...
+ normals[ i ].applyMatrix4( mat.makeRotationAxis( tangents[ i ], theta * i ) );
+ binormals[ i ].crossVectors( tangents[ i ], normals[ i ] );
+
+ }
+
+ }
+
+ return {
+ tangents: tangents,
+ normals: normals,
+ binormals: binormals
+ };
+
+ },
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ },
+
+ copy: function ( source ) {
+
+ this.arcLengthDivisions = source.arcLengthDivisions;
+
+ return this;
+
+ },
+
+ toJSON: function () {
+
+ const data = {
+ metadata: {
+ version: 4.5,
+ type: 'Curve',
+ generator: 'Curve.toJSON'
+ }
+ };
+
+ data.arcLengthDivisions = this.arcLengthDivisions;
+ data.type = this.type;
+
+ return data;
+
+ },
+
+ fromJSON: function ( json ) {
+
+ this.arcLengthDivisions = json.arcLengthDivisions;
+
+ return this;
+
+ }
+
+} );
+
+function EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
+
+ Curve.call( this );
+
+ this.type = 'EllipseCurve';
+
+ this.aX = aX || 0;
+ this.aY = aY || 0;
+
+ this.xRadius = xRadius || 1;
+ this.yRadius = yRadius || 1;
+
+ this.aStartAngle = aStartAngle || 0;
+ this.aEndAngle = aEndAngle || 2 * Math.PI;
+
+ this.aClockwise = aClockwise || false;
+
+ this.aRotation = aRotation || 0;
+
+}
+
+EllipseCurve.prototype = Object.create( Curve.prototype );
+EllipseCurve.prototype.constructor = EllipseCurve;
+
+EllipseCurve.prototype.isEllipseCurve = true;
+
+EllipseCurve.prototype.getPoint = function ( t, optionalTarget ) {
+
+ const point = optionalTarget || new Vector2();
+
+ const twoPi = Math.PI * 2;
+ let deltaAngle = this.aEndAngle - this.aStartAngle;
+ const samePoints = Math.abs( deltaAngle ) < Number.EPSILON;
+
+ // ensures that deltaAngle is 0 .. 2 PI
+ while ( deltaAngle < 0 ) deltaAngle += twoPi;
+ while ( deltaAngle > twoPi ) deltaAngle -= twoPi;
+
+ if ( deltaAngle < Number.EPSILON ) {
+
+ if ( samePoints ) {
+
+ deltaAngle = 0;
+
+ } else {
+
+ deltaAngle = twoPi;
+
+ }
+
+ }
+
+ if ( this.aClockwise === true && ! samePoints ) {
+
+ if ( deltaAngle === twoPi ) {
+
+ deltaAngle = - twoPi;
+
+ } else {
+
+ deltaAngle = deltaAngle - twoPi;
+
+ }
+
+ }
+
+ const angle = this.aStartAngle + t * deltaAngle;
+ let x = this.aX + this.xRadius * Math.cos( angle );
+ let y = this.aY + this.yRadius * Math.sin( angle );
+
+ if ( this.aRotation !== 0 ) {
+
+ const cos = Math.cos( this.aRotation );
+ const sin = Math.sin( this.aRotation );
+
+ const tx = x - this.aX;
+ const ty = y - this.aY;
+
+ // Rotate the point about the center of the ellipse.
+ x = tx * cos - ty * sin + this.aX;
+ y = tx * sin + ty * cos + this.aY;
+
+ }
+
+ return point.set( x, y );
+
+};
+
+EllipseCurve.prototype.copy = function ( source ) {
+
+ Curve.prototype.copy.call( this, source );
+
+ this.aX = source.aX;
+ this.aY = source.aY;
+
+ this.xRadius = source.xRadius;
+ this.yRadius = source.yRadius;
+
+ this.aStartAngle = source.aStartAngle;
+ this.aEndAngle = source.aEndAngle;
+
+ this.aClockwise = source.aClockwise;
+
+ this.aRotation = source.aRotation;
+
+ return this;
+
+};
+
+
+EllipseCurve.prototype.toJSON = function () {
+
+ const data = Curve.prototype.toJSON.call( this );
+
+ data.aX = this.aX;
+ data.aY = this.aY;
+
+ data.xRadius = this.xRadius;
+ data.yRadius = this.yRadius;
+
+ data.aStartAngle = this.aStartAngle;
+ data.aEndAngle = this.aEndAngle;
+
+ data.aClockwise = this.aClockwise;
+
+ data.aRotation = this.aRotation;
+
+ return data;
+
+};
+
+EllipseCurve.prototype.fromJSON = function ( json ) {
+
+ Curve.prototype.fromJSON.call( this, json );
+
+ this.aX = json.aX;
+ this.aY = json.aY;
+
+ this.xRadius = json.xRadius;
+ this.yRadius = json.yRadius;
+
+ this.aStartAngle = json.aStartAngle;
+ this.aEndAngle = json.aEndAngle;
+
+ this.aClockwise = json.aClockwise;
+
+ this.aRotation = json.aRotation;
+
+ return this;
+
+};
+
+function ArcCurve( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+
+ EllipseCurve.call( this, aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
+
+ this.type = 'ArcCurve';
+
+}
+
+ArcCurve.prototype = Object.create( EllipseCurve.prototype );
+ArcCurve.prototype.constructor = ArcCurve;
+
+ArcCurve.prototype.isArcCurve = true;
+
+/**
+ * Centripetal CatmullRom Curve - which is useful for avoiding
+ * cusps and self-intersections in non-uniform catmull rom curves.
+ * http://www.cemyuksel.com/research/catmullrom_param/catmullrom.pdf
+ *
+ * curve.type accepts centripetal(default), chordal and catmullrom
+ * curve.tension is used for catmullrom which defaults to 0.5
+ */
+
+
+/*
+Based on an optimized c++ solution in
+ - http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections/
+ - http://ideone.com/NoEbVM
+
+This CubicPoly class could be used for reusing some variables and calculations,
+but for three.js curve use, it could be possible inlined and flatten into a single function call
+which can be placed in CurveUtils.
+*/
+
+function CubicPoly() {
+
+ let c0 = 0, c1 = 0, c2 = 0, c3 = 0;
+
+ /*
+ * Compute coefficients for a cubic polynomial
+ * p(s) = c0 + c1*s + c2*s^2 + c3*s^3
+ * such that
+ * p(0) = x0, p(1) = x1
+ * and
+ * p'(0) = t0, p'(1) = t1.
+ */
+ function init( x0, x1, t0, t1 ) {
+
+ c0 = x0;
+ c1 = t0;
+ c2 = - 3 * x0 + 3 * x1 - 2 * t0 - t1;
+ c3 = 2 * x0 - 2 * x1 + t0 + t1;
+
+ }
+
+ return {
+
+ initCatmullRom: function ( x0, x1, x2, x3, tension ) {
+
+ init( x1, x2, tension * ( x2 - x0 ), tension * ( x3 - x1 ) );
+
+ },
+
+ initNonuniformCatmullRom: function ( x0, x1, x2, x3, dt0, dt1, dt2 ) {
+
+ // compute tangents when parameterized in [t1,t2]
+ let t1 = ( x1 - x0 ) / dt0 - ( x2 - x0 ) / ( dt0 + dt1 ) + ( x2 - x1 ) / dt1;
+ let t2 = ( x2 - x1 ) / dt1 - ( x3 - x1 ) / ( dt1 + dt2 ) + ( x3 - x2 ) / dt2;
+
+ // rescale tangents for parametrization in [0,1]
+ t1 *= dt1;
+ t2 *= dt1;
+
+ init( x1, x2, t1, t2 );
+
+ },
+
+ calc: function ( t ) {
+
+ const t2 = t * t;
+ const t3 = t2 * t;
+ return c0 + c1 * t + c2 * t2 + c3 * t3;
+
+ }
+
+ };
+
+}
+
+//
+
+const tmp = new Vector3();
+const px = new CubicPoly(), py = new CubicPoly(), pz = new CubicPoly();
+
+function CatmullRomCurve3( points = [], closed = false, curveType = 'centripetal', tension = 0.5 ) {
+
+ Curve.call( this );
+
+ this.type = 'CatmullRomCurve3';
+
+ this.points = points;
+ this.closed = closed;
+ this.curveType = curveType;
+ this.tension = tension;
+
+}
+
+CatmullRomCurve3.prototype = Object.create( Curve.prototype );
+CatmullRomCurve3.prototype.constructor = CatmullRomCurve3;
+
+CatmullRomCurve3.prototype.isCatmullRomCurve3 = true;
+
+CatmullRomCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+
+ const point = optionalTarget;
+
+ const points = this.points;
+ const l = points.length;
+
+ const p = ( l - ( this.closed ? 0 : 1 ) ) * t;
+ let intPoint = Math.floor( p );
+ let weight = p - intPoint;
+
+ if ( this.closed ) {
+
+ intPoint += intPoint > 0 ? 0 : ( Math.floor( Math.abs( intPoint ) / l ) + 1 ) * l;
+
+ } else if ( weight === 0 && intPoint === l - 1 ) {
+
+ intPoint = l - 2;
+ weight = 1;
+
+ }
+
+ let p0, p3; // 4 points (p1 & p2 defined below)
+
+ if ( this.closed || intPoint > 0 ) {
+
+ p0 = points[ ( intPoint - 1 ) % l ];
+
+ } else {
+
+ // extrapolate first point
+ tmp.subVectors( points[ 0 ], points[ 1 ] ).add( points[ 0 ] );
+ p0 = tmp;
+
+ }
+
+ const p1 = points[ intPoint % l ];
+ const p2 = points[ ( intPoint + 1 ) % l ];
+
+ if ( this.closed || intPoint + 2 < l ) {
+
+ p3 = points[ ( intPoint + 2 ) % l ];
+
+ } else {
+
+ // extrapolate last point
+ tmp.subVectors( points[ l - 1 ], points[ l - 2 ] ).add( points[ l - 1 ] );
+ p3 = tmp;
+
+ }
+
+ if ( this.curveType === 'centripetal' || this.curveType === 'chordal' ) {
+
+ // init Centripetal / Chordal Catmull-Rom
+ const pow = this.curveType === 'chordal' ? 0.5 : 0.25;
+ let dt0 = Math.pow( p0.distanceToSquared( p1 ), pow );
+ let dt1 = Math.pow( p1.distanceToSquared( p2 ), pow );
+ let dt2 = Math.pow( p2.distanceToSquared( p3 ), pow );
+
+ // safety check for repeated points
+ if ( dt1 < 1e-4 ) dt1 = 1.0;
+ if ( dt0 < 1e-4 ) dt0 = dt1;
+ if ( dt2 < 1e-4 ) dt2 = dt1;
+
+ px.initNonuniformCatmullRom( p0.x, p1.x, p2.x, p3.x, dt0, dt1, dt2 );
+ py.initNonuniformCatmullRom( p0.y, p1.y, p2.y, p3.y, dt0, dt1, dt2 );
+ pz.initNonuniformCatmullRom( p0.z, p1.z, p2.z, p3.z, dt0, dt1, dt2 );
+
+ } else if ( this.curveType === 'catmullrom' ) {
+
+ px.initCatmullRom( p0.x, p1.x, p2.x, p3.x, this.tension );
+ py.initCatmullRom( p0.y, p1.y, p2.y, p3.y, this.tension );
+ pz.initCatmullRom( p0.z, p1.z, p2.z, p3.z, this.tension );
+
+ }
+
+ point.set(
+ px.calc( weight ),
+ py.calc( weight ),
+ pz.calc( weight )
+ );
+
+ return point;
+
+};
+
+CatmullRomCurve3.prototype.copy = function ( source ) {
+
+ Curve.prototype.copy.call( this, source );
+
+ this.points = [];
+
+ for ( let i = 0, l = source.points.length; i < l; i ++ ) {
+
+ const point = source.points[ i ];
+
+ this.points.push( point.clone() );
+
+ }
+
+ this.closed = source.closed;
+ this.curveType = source.curveType;
+ this.tension = source.tension;
+
+ return this;
+
+};
+
+CatmullRomCurve3.prototype.toJSON = function () {
+
+ const data = Curve.prototype.toJSON.call( this );
+
+ data.points = [];
+
+ for ( let i = 0, l = this.points.length; i < l; i ++ ) {
+
+ const point = this.points[ i ];
+ data.points.push( point.toArray() );
+
+ }
+
+ data.closed = this.closed;
+ data.curveType = this.curveType;
+ data.tension = this.tension;
+
+ return data;
+
+};
+
+CatmullRomCurve3.prototype.fromJSON = function ( json ) {
+
+ Curve.prototype.fromJSON.call( this, json );
+
+ this.points = [];
+
+ for ( let i = 0, l = json.points.length; i < l; i ++ ) {
+
+ const point = json.points[ i ];
+ this.points.push( new Vector3().fromArray( point ) );
+
+ }
+
+ this.closed = json.closed;
+ this.curveType = json.curveType;
+ this.tension = json.tension;
+
+ return this;
+
+};
+
+/**
+ * Bezier Curves formulas obtained from
+ * http://en.wikipedia.org/wiki/Bézier_curve
+ */
+
+function CatmullRom( t, p0, p1, p2, p3 ) {
+
+ const v0 = ( p2 - p0 ) * 0.5;
+ const v1 = ( p3 - p1 ) * 0.5;
+ const t2 = t * t;
+ const t3 = t * t2;
+ return ( 2 * p1 - 2 * p2 + v0 + v1 ) * t3 + ( - 3 * p1 + 3 * p2 - 2 * v0 - v1 ) * t2 + v0 * t + p1;
+
+}
+
+//
+
+function QuadraticBezierP0( t, p ) {
+
+ const k = 1 - t;
+ return k * k * p;
+
+}
+
+function QuadraticBezierP1( t, p ) {
+
+ return 2 * ( 1 - t ) * t * p;
+
+}
+
+function QuadraticBezierP2( t, p ) {
+
+ return t * t * p;
+
+}
+
+function QuadraticBezier( t, p0, p1, p2 ) {
+
+ return QuadraticBezierP0( t, p0 ) + QuadraticBezierP1( t, p1 ) +
+ QuadraticBezierP2( t, p2 );
+
+}
+
+//
+
+function CubicBezierP0( t, p ) {
+
+ const k = 1 - t;
+ return k * k * k * p;
+
+}
+
+function CubicBezierP1( t, p ) {
+
+ const k = 1 - t;
+ return 3 * k * k * t * p;
+
+}
+
+function CubicBezierP2( t, p ) {
+
+ return 3 * ( 1 - t ) * t * t * p;
+
+}
+
+function CubicBezierP3( t, p ) {
+
+ return t * t * t * p;
+
+}
+
+function CubicBezier( t, p0, p1, p2, p3 ) {
+
+ return CubicBezierP0( t, p0 ) + CubicBezierP1( t, p1 ) + CubicBezierP2( t, p2 ) +
+ CubicBezierP3( t, p3 );
+
+}
+
+function CubicBezierCurve( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2(), v3 = new Vector2() ) {
+
+ Curve.call( this );
+
+ this.type = 'CubicBezierCurve';
+
+ this.v0 = v0;
+ this.v1 = v1;
+ this.v2 = v2;
+ this.v3 = v3;
+
+}
+
+CubicBezierCurve.prototype = Object.create( Curve.prototype );
+CubicBezierCurve.prototype.constructor = CubicBezierCurve;
+
+CubicBezierCurve.prototype.isCubicBezierCurve = true;
+
+CubicBezierCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+
+ const point = optionalTarget;
+
+ const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
+
+ point.set(
+ CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),
+ CubicBezier( t, v0.y, v1.y, v2.y, v3.y )
+ );
+
+ return point;
+
+};
+
+CubicBezierCurve.prototype.copy = function ( source ) {
+
+ Curve.prototype.copy.call( this, source );
+
+ this.v0.copy( source.v0 );
+ this.v1.copy( source.v1 );
+ this.v2.copy( source.v2 );
+ this.v3.copy( source.v3 );
+
+ return this;
+
+};
+
+CubicBezierCurve.prototype.toJSON = function () {
+
+ const data = Curve.prototype.toJSON.call( this );
+
+ data.v0 = this.v0.toArray();
+ data.v1 = this.v1.toArray();
+ data.v2 = this.v2.toArray();
+ data.v3 = this.v3.toArray();
+
+ return data;
+
+};
+
+CubicBezierCurve.prototype.fromJSON = function ( json ) {
+
+ Curve.prototype.fromJSON.call( this, json );
+
+ this.v0.fromArray( json.v0 );
+ this.v1.fromArray( json.v1 );
+ this.v2.fromArray( json.v2 );
+ this.v3.fromArray( json.v3 );
+
+ return this;
+
+};
+
+function CubicBezierCurve3( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3(), v3 = new Vector3() ) {
+
+ Curve.call( this );
+
+ this.type = 'CubicBezierCurve3';
+
+ this.v0 = v0;
+ this.v1 = v1;
+ this.v2 = v2;
+ this.v3 = v3;
+
+}
+
+CubicBezierCurve3.prototype = Object.create( Curve.prototype );
+CubicBezierCurve3.prototype.constructor = CubicBezierCurve3;
+
+CubicBezierCurve3.prototype.isCubicBezierCurve3 = true;
+
+CubicBezierCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+
+ const point = optionalTarget;
+
+ const v0 = this.v0, v1 = this.v1, v2 = this.v2, v3 = this.v3;
+
+ point.set(
+ CubicBezier( t, v0.x, v1.x, v2.x, v3.x ),
+ CubicBezier( t, v0.y, v1.y, v2.y, v3.y ),
+ CubicBezier( t, v0.z, v1.z, v2.z, v3.z )
+ );
+
+ return point;
+
+};
+
+CubicBezierCurve3.prototype.copy = function ( source ) {
+
+ Curve.prototype.copy.call( this, source );
+
+ this.v0.copy( source.v0 );
+ this.v1.copy( source.v1 );
+ this.v2.copy( source.v2 );
+ this.v3.copy( source.v3 );
+
+ return this;
+
+};
+
+CubicBezierCurve3.prototype.toJSON = function () {
+
+ const data = Curve.prototype.toJSON.call( this );
+
+ data.v0 = this.v0.toArray();
+ data.v1 = this.v1.toArray();
+ data.v2 = this.v2.toArray();
+ data.v3 = this.v3.toArray();
+
+ return data;
+
+};
+
+CubicBezierCurve3.prototype.fromJSON = function ( json ) {
+
+ Curve.prototype.fromJSON.call( this, json );
+
+ this.v0.fromArray( json.v0 );
+ this.v1.fromArray( json.v1 );
+ this.v2.fromArray( json.v2 );
+ this.v3.fromArray( json.v3 );
+
+ return this;
+
+};
+
+function LineCurve( v1 = new Vector2(), v2 = new Vector2() ) {
+
+ Curve.call( this );
+
+ this.type = 'LineCurve';
+
+ this.v1 = v1;
+ this.v2 = v2;
+
+}
+
+LineCurve.prototype = Object.create( Curve.prototype );
+LineCurve.prototype.constructor = LineCurve;
+
+LineCurve.prototype.isLineCurve = true;
+
+LineCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+
+ const point = optionalTarget;
+
+ if ( t === 1 ) {
+
+ point.copy( this.v2 );
+
+ } else {
+
+ point.copy( this.v2 ).sub( this.v1 );
+ point.multiplyScalar( t ).add( this.v1 );
+
+ }
+
+ return point;
+
+};
+
+// Line curve is linear, so we can overwrite default getPointAt
+
+LineCurve.prototype.getPointAt = function ( u, optionalTarget ) {
+
+ return this.getPoint( u, optionalTarget );
+
+};
+
+LineCurve.prototype.getTangent = function ( t, optionalTarget ) {
+
+ const tangent = optionalTarget || new Vector2();
+
+ tangent.copy( this.v2 ).sub( this.v1 ).normalize();
+
+ return tangent;
+
+};
+
+LineCurve.prototype.copy = function ( source ) {
+
+ Curve.prototype.copy.call( this, source );
+
+ this.v1.copy( source.v1 );
+ this.v2.copy( source.v2 );
+
+ return this;
+
+};
+
+LineCurve.prototype.toJSON = function () {
+
+ const data = Curve.prototype.toJSON.call( this );
+
+ data.v1 = this.v1.toArray();
+ data.v2 = this.v2.toArray();
+
+ return data;
+
+};
+
+LineCurve.prototype.fromJSON = function ( json ) {
+
+ Curve.prototype.fromJSON.call( this, json );
+
+ this.v1.fromArray( json.v1 );
+ this.v2.fromArray( json.v2 );
+
+ return this;
+
+};
+
+function LineCurve3( v1 = new Vector3(), v2 = new Vector3() ) {
+
+ Curve.call( this );
+
+ this.type = 'LineCurve3';
+
+ this.v1 = v1;
+ this.v2 = v2;
+
+}
+
+LineCurve3.prototype = Object.create( Curve.prototype );
+LineCurve3.prototype.constructor = LineCurve3;
+
+LineCurve3.prototype.isLineCurve3 = true;
+
+LineCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+
+ const point = optionalTarget;
+
+ if ( t === 1 ) {
+
+ point.copy( this.v2 );
+
+ } else {
+
+ point.copy( this.v2 ).sub( this.v1 );
+ point.multiplyScalar( t ).add( this.v1 );
+
+ }
+
+ return point;
+
+};
+
+// Line curve is linear, so we can overwrite default getPointAt
+
+LineCurve3.prototype.getPointAt = function ( u, optionalTarget ) {
+
+ return this.getPoint( u, optionalTarget );
+
+};
+
+LineCurve3.prototype.copy = function ( source ) {
+
+ Curve.prototype.copy.call( this, source );
+
+ this.v1.copy( source.v1 );
+ this.v2.copy( source.v2 );
+
+ return this;
+
+};
+
+LineCurve3.prototype.toJSON = function () {
+
+ const data = Curve.prototype.toJSON.call( this );
+
+ data.v1 = this.v1.toArray();
+ data.v2 = this.v2.toArray();
+
+ return data;
+
+};
+
+LineCurve3.prototype.fromJSON = function ( json ) {
+
+ Curve.prototype.fromJSON.call( this, json );
+
+ this.v1.fromArray( json.v1 );
+ this.v2.fromArray( json.v2 );
+
+ return this;
+
+};
+
+function QuadraticBezierCurve( v0 = new Vector2(), v1 = new Vector2(), v2 = new Vector2() ) {
+
+ Curve.call( this );
+
+ this.type = 'QuadraticBezierCurve';
+
+ this.v0 = v0;
+ this.v1 = v1;
+ this.v2 = v2;
+
+}
+
+QuadraticBezierCurve.prototype = Object.create( Curve.prototype );
+QuadraticBezierCurve.prototype.constructor = QuadraticBezierCurve;
+
+QuadraticBezierCurve.prototype.isQuadraticBezierCurve = true;
+
+QuadraticBezierCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+
+ const point = optionalTarget;
+
+ const v0 = this.v0, v1 = this.v1, v2 = this.v2;
+
+ point.set(
+ QuadraticBezier( t, v0.x, v1.x, v2.x ),
+ QuadraticBezier( t, v0.y, v1.y, v2.y )
+ );
+
+ return point;
+
+};
+
+QuadraticBezierCurve.prototype.copy = function ( source ) {
+
+ Curve.prototype.copy.call( this, source );
+
+ this.v0.copy( source.v0 );
+ this.v1.copy( source.v1 );
+ this.v2.copy( source.v2 );
+
+ return this;
+
+};
+
+QuadraticBezierCurve.prototype.toJSON = function () {
+
+ const data = Curve.prototype.toJSON.call( this );
+
+ data.v0 = this.v0.toArray();
+ data.v1 = this.v1.toArray();
+ data.v2 = this.v2.toArray();
+
+ return data;
+
+};
+
+QuadraticBezierCurve.prototype.fromJSON = function ( json ) {
+
+ Curve.prototype.fromJSON.call( this, json );
+
+ this.v0.fromArray( json.v0 );
+ this.v1.fromArray( json.v1 );
+ this.v2.fromArray( json.v2 );
+
+ return this;
+
+};
+
+function QuadraticBezierCurve3( v0 = new Vector3(), v1 = new Vector3(), v2 = new Vector3() ) {
+
+ Curve.call( this );
+
+ this.type = 'QuadraticBezierCurve3';
+
+ this.v0 = v0;
+ this.v1 = v1;
+ this.v2 = v2;
+
+}
+
+QuadraticBezierCurve3.prototype = Object.create( Curve.prototype );
+QuadraticBezierCurve3.prototype.constructor = QuadraticBezierCurve3;
+
+QuadraticBezierCurve3.prototype.isQuadraticBezierCurve3 = true;
+
+QuadraticBezierCurve3.prototype.getPoint = function ( t, optionalTarget = new Vector3() ) {
+
+ const point = optionalTarget;
+
+ const v0 = this.v0, v1 = this.v1, v2 = this.v2;
+
+ point.set(
+ QuadraticBezier( t, v0.x, v1.x, v2.x ),
+ QuadraticBezier( t, v0.y, v1.y, v2.y ),
+ QuadraticBezier( t, v0.z, v1.z, v2.z )
+ );
+
+ return point;
+
+};
+
+QuadraticBezierCurve3.prototype.copy = function ( source ) {
+
+ Curve.prototype.copy.call( this, source );
+
+ this.v0.copy( source.v0 );
+ this.v1.copy( source.v1 );
+ this.v2.copy( source.v2 );
+
+ return this;
+
+};
+
+QuadraticBezierCurve3.prototype.toJSON = function () {
+
+ const data = Curve.prototype.toJSON.call( this );
+
+ data.v0 = this.v0.toArray();
+ data.v1 = this.v1.toArray();
+ data.v2 = this.v2.toArray();
+
+ return data;
+
+};
+
+QuadraticBezierCurve3.prototype.fromJSON = function ( json ) {
+
+ Curve.prototype.fromJSON.call( this, json );
+
+ this.v0.fromArray( json.v0 );
+ this.v1.fromArray( json.v1 );
+ this.v2.fromArray( json.v2 );
+
+ return this;
+
+};
+
+function SplineCurve( points = [] ) {
+
+ Curve.call( this );
+
+ this.type = 'SplineCurve';
+
+ this.points = points;
+
+}
+
+SplineCurve.prototype = Object.create( Curve.prototype );
+SplineCurve.prototype.constructor = SplineCurve;
+
+SplineCurve.prototype.isSplineCurve = true;
+
+SplineCurve.prototype.getPoint = function ( t, optionalTarget = new Vector2() ) {
+
+ const point = optionalTarget;
+
+ const points = this.points;
+ const p = ( points.length - 1 ) * t;
+
+ const intPoint = Math.floor( p );
+ const weight = p - intPoint;
+
+ const p0 = points[ intPoint === 0 ? intPoint : intPoint - 1 ];
+ const p1 = points[ intPoint ];
+ const p2 = points[ intPoint > points.length - 2 ? points.length - 1 : intPoint + 1 ];
+ const p3 = points[ intPoint > points.length - 3 ? points.length - 1 : intPoint + 2 ];
+
+ point.set(
+ CatmullRom( weight, p0.x, p1.x, p2.x, p3.x ),
+ CatmullRom( weight, p0.y, p1.y, p2.y, p3.y )
+ );
+
+ return point;
+
+};
+
+SplineCurve.prototype.copy = function ( source ) {
+
+ Curve.prototype.copy.call( this, source );
+
+ this.points = [];
+
+ for ( let i = 0, l = source.points.length; i < l; i ++ ) {
+
+ const point = source.points[ i ];
+
+ this.points.push( point.clone() );
+
+ }
+
+ return this;
+
+};
+
+SplineCurve.prototype.toJSON = function () {
+
+ const data = Curve.prototype.toJSON.call( this );
+
+ data.points = [];
+
+ for ( let i = 0, l = this.points.length; i < l; i ++ ) {
+
+ const point = this.points[ i ];
+ data.points.push( point.toArray() );
+
+ }
+
+ return data;
+
+};
+
+SplineCurve.prototype.fromJSON = function ( json ) {
+
+ Curve.prototype.fromJSON.call( this, json );
+
+ this.points = [];
+
+ for ( let i = 0, l = json.points.length; i < l; i ++ ) {
+
+ const point = json.points[ i ];
+ this.points.push( new Vector2().fromArray( point ) );
+
+ }
+
+ return this;
+
+};
+
+var Curves = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ ArcCurve: ArcCurve,
+ CatmullRomCurve3: CatmullRomCurve3,
+ CubicBezierCurve: CubicBezierCurve,
+ CubicBezierCurve3: CubicBezierCurve3,
+ EllipseCurve: EllipseCurve,
+ LineCurve: LineCurve,
+ LineCurve3: LineCurve3,
+ QuadraticBezierCurve: QuadraticBezierCurve,
+ QuadraticBezierCurve3: QuadraticBezierCurve3,
+ SplineCurve: SplineCurve
+});
+
+/**************************************************************
+ * Curved Path - a curve path is simply a array of connected
+ * curves, but retains the api of a curve
+ **************************************************************/
+
+function CurvePath() {
+
+ Curve.call( this );
+
+ this.type = 'CurvePath';
+
+ this.curves = [];
+ this.autoClose = false; // Automatically closes the path
+
+}
+
+CurvePath.prototype = Object.assign( Object.create( Curve.prototype ), {
+
+ constructor: CurvePath,
+
+ add: function ( curve ) {
+
+ this.curves.push( curve );
+
+ },
+
+ closePath: function () {
+
+ // Add a line curve if start and end of lines are not connected
+ const startPoint = this.curves[ 0 ].getPoint( 0 );
+ const endPoint = this.curves[ this.curves.length - 1 ].getPoint( 1 );
+
+ if ( ! startPoint.equals( endPoint ) ) {
+
+ this.curves.push( new LineCurve( endPoint, startPoint ) );
+
+ }
+
+ },
+
+ // To get accurate point with reference to
+ // entire path distance at time t,
+ // following has to be done:
+
+ // 1. Length of each sub path have to be known
+ // 2. Locate and identify type of curve
+ // 3. Get t for the curve
+ // 4. Return curve.getPointAt(t')
+
+ getPoint: function ( t ) {
+
+ const d = t * this.getLength();
+ const curveLengths = this.getCurveLengths();
+ let i = 0;
+
+ // To think about boundaries points.
+
+ while ( i < curveLengths.length ) {
+
+ if ( curveLengths[ i ] >= d ) {
+
+ const diff = curveLengths[ i ] - d;
+ const curve = this.curves[ i ];
+
+ const segmentLength = curve.getLength();
+ const u = segmentLength === 0 ? 0 : 1 - diff / segmentLength;
+
+ return curve.getPointAt( u );
+
+ }
+
+ i ++;
+
+ }
+
+ return null;
+
+ // loop where sum != 0, sum > d , sum+1 <d
+
+ },
+
+ // We cannot use the default THREE.Curve getPoint() with getLength() because in
+ // THREE.Curve, getLength() depends on getPoint() but in THREE.CurvePath
+ // getPoint() depends on getLength
+
+ getLength: function () {
+
+ const lens = this.getCurveLengths();
+ return lens[ lens.length - 1 ];
+
+ },
+
+ // cacheLengths must be recalculated.
+ updateArcLengths: function () {
+
+ this.needsUpdate = true;
+ this.cacheLengths = null;
+ this.getCurveLengths();
+
+ },
+
+ // Compute lengths and cache them
+ // We cannot overwrite getLengths() because UtoT mapping uses it.
+
+ getCurveLengths: function () {
+
+ // We use cache values if curves and cache array are same length
+
+ if ( this.cacheLengths && this.cacheLengths.length === this.curves.length ) {
+
+ return this.cacheLengths;
+
+ }
+
+ // Get length of sub-curve
+ // Push sums into cached array
+
+ const lengths = [];
+ let sums = 0;
+
+ for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
+
+ sums += this.curves[ i ].getLength();
+ lengths.push( sums );
+
+ }
+
+ this.cacheLengths = lengths;
+
+ return lengths;
+
+ },
+
+ getSpacedPoints: function ( divisions = 40 ) {
+
+ const points = [];
+
+ for ( let i = 0; i <= divisions; i ++ ) {
+
+ points.push( this.getPoint( i / divisions ) );
+
+ }
+
+ if ( this.autoClose ) {
+
+ points.push( points[ 0 ] );
+
+ }
+
+ return points;
+
+ },
+
+ getPoints: function ( divisions = 12 ) {
+
+ const points = [];
+ let last;
+
+ for ( let i = 0, curves = this.curves; i < curves.length; i ++ ) {
+
+ const curve = curves[ i ];
+ const resolution = ( curve && curve.isEllipseCurve ) ? divisions * 2
+ : ( curve && ( curve.isLineCurve || curve.isLineCurve3 ) ) ? 1
+ : ( curve && curve.isSplineCurve ) ? divisions * curve.points.length
+ : divisions;
+
+ const pts = curve.getPoints( resolution );
+
+ for ( let j = 0; j < pts.length; j ++ ) {
+
+ const point = pts[ j ];
+
+ if ( last && last.equals( point ) ) continue; // ensures no consecutive points are duplicates
+
+ points.push( point );
+ last = point;
+
+ }
+
+ }
+
+ if ( this.autoClose && points.length > 1 && ! points[ points.length - 1 ].equals( points[ 0 ] ) ) {
+
+ points.push( points[ 0 ] );
+
+ }
+
+ return points;
+
+ },
+
+ copy: function ( source ) {
+
+ Curve.prototype.copy.call( this, source );
+
+ this.curves = [];
+
+ for ( let i = 0, l = source.curves.length; i < l; i ++ ) {
+
+ const curve = source.curves[ i ];
+
+ this.curves.push( curve.clone() );
+
+ }
+
+ this.autoClose = source.autoClose;
+
+ return this;
+
+ },
+
+ toJSON: function () {
+
+ const data = Curve.prototype.toJSON.call( this );
+
+ data.autoClose = this.autoClose;
+ data.curves = [];
+
+ for ( let i = 0, l = this.curves.length; i < l; i ++ ) {
+
+ const curve = this.curves[ i ];
+ data.curves.push( curve.toJSON() );
+
+ }
+
+ return data;
+
+ },
+
+ fromJSON: function ( json ) {
+
+ Curve.prototype.fromJSON.call( this, json );
+
+ this.autoClose = json.autoClose;
+ this.curves = [];
+
+ for ( let i = 0, l = json.curves.length; i < l; i ++ ) {
+
+ const curve = json.curves[ i ];
+ this.curves.push( new Curves[ curve.type ]().fromJSON( curve ) );
+
+ }
+
+ return this;
+
+ }
+
+} );
+
+function Path( points ) {
+
+ CurvePath.call( this );
+
+ this.type = 'Path';
+
+ this.currentPoint = new Vector2();
+
+ if ( points ) {
+
+ this.setFromPoints( points );
+
+ }
+
+}
+
+Path.prototype = Object.assign( Object.create( CurvePath.prototype ), {
+
+ constructor: Path,
+
+ setFromPoints: function ( points ) {
+
+ this.moveTo( points[ 0 ].x, points[ 0 ].y );
+
+ for ( let i = 1, l = points.length; i < l; i ++ ) {
+
+ this.lineTo( points[ i ].x, points[ i ].y );
+
+ }
+
+ return this;
+
+ },
+
+ moveTo: function ( x, y ) {
+
+ this.currentPoint.set( x, y ); // TODO consider referencing vectors instead of copying?
+
+ return this;
+
+ },
+
+ lineTo: function ( x, y ) {
+
+ const curve = new LineCurve( this.currentPoint.clone(), new Vector2( x, y ) );
+ this.curves.push( curve );
+
+ this.currentPoint.set( x, y );
+
+ return this;
+
+ },
+
+ quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
+
+ const curve = new QuadraticBezierCurve(
+ this.currentPoint.clone(),
+ new Vector2( aCPx, aCPy ),
+ new Vector2( aX, aY )
+ );
+
+ this.curves.push( curve );
+
+ this.currentPoint.set( aX, aY );
+
+ return this;
+
+ },
+
+ bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
+
+ const curve = new CubicBezierCurve(
+ this.currentPoint.clone(),
+ new Vector2( aCP1x, aCP1y ),
+ new Vector2( aCP2x, aCP2y ),
+ new Vector2( aX, aY )
+ );
+
+ this.curves.push( curve );
+
+ this.currentPoint.set( aX, aY );
+
+ return this;
+
+ },
+
+ splineThru: function ( pts /*Array of Vector*/ ) {
+
+ const npts = [ this.currentPoint.clone() ].concat( pts );
+
+ const curve = new SplineCurve( npts );
+ this.curves.push( curve );
+
+ this.currentPoint.copy( pts[ pts.length - 1 ] );
+
+ return this;
+
+ },
+
+ arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+
+ const x0 = this.currentPoint.x;
+ const y0 = this.currentPoint.y;
+
+ this.absarc( aX + x0, aY + y0, aRadius,
+ aStartAngle, aEndAngle, aClockwise );
+
+ return this;
+
+ },
+
+ absarc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
+
+ this.absellipse( aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise );
+
+ return this;
+
+ },
+
+ ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
+
+ const x0 = this.currentPoint.x;
+ const y0 = this.currentPoint.y;
+
+ this.absellipse( aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
+
+ return this;
+
+ },
+
+ absellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation ) {
+
+ const curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation );
+
+ if ( this.curves.length > 0 ) {
+
+ // if a previous curve is present, attempt to join
+ const firstPoint = curve.getPoint( 0 );
+
+ if ( ! firstPoint.equals( this.currentPoint ) ) {
+
+ this.lineTo( firstPoint.x, firstPoint.y );
+
+ }
+
+ }
+
+ this.curves.push( curve );
+
+ const lastPoint = curve.getPoint( 1 );
+ this.currentPoint.copy( lastPoint );
+
+ return this;
+
+ },
+
+ copy: function ( source ) {
+
+ CurvePath.prototype.copy.call( this, source );
+
+ this.currentPoint.copy( source.currentPoint );
+
+ return this;
+
+ },
+
+ toJSON: function () {
+
+ const data = CurvePath.prototype.toJSON.call( this );
+
+ data.currentPoint = this.currentPoint.toArray();
+
+ return data;
+
+ },
+
+ fromJSON: function ( json ) {
+
+ CurvePath.prototype.fromJSON.call( this, json );
+
+ this.currentPoint.fromArray( json.currentPoint );
+
+ return this;
+
+ }
+
+} );
+
+function Shape( points ) {
+
+ Path.call( this, points );
+
+ this.uuid = MathUtils.generateUUID();
+
+ this.type = 'Shape';
+
+ this.holes = [];
+
+}
+
+Shape.prototype = Object.assign( Object.create( Path.prototype ), {
+
+ constructor: Shape,
+
+ getPointsHoles: function ( divisions ) {
+
+ const holesPts = [];
+
+ for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
+
+ holesPts[ i ] = this.holes[ i ].getPoints( divisions );
+
+ }
+
+ return holesPts;
+
+ },
+
+ // get points of shape and holes (keypoints based on segments parameter)
+
+ extractPoints: function ( divisions ) {
+
+ return {
+
+ shape: this.getPoints( divisions ),
+ holes: this.getPointsHoles( divisions )
+
+ };
+
+ },
+
+ copy: function ( source ) {
+
+ Path.prototype.copy.call( this, source );
+
+ this.holes = [];
+
+ for ( let i = 0, l = source.holes.length; i < l; i ++ ) {
+
+ const hole = source.holes[ i ];
+
+ this.holes.push( hole.clone() );
+
+ }
+
+ return this;
+
+ },
+
+ toJSON: function () {
+
+ const data = Path.prototype.toJSON.call( this );
+
+ data.uuid = this.uuid;
+ data.holes = [];
+
+ for ( let i = 0, l = this.holes.length; i < l; i ++ ) {
+
+ const hole = this.holes[ i ];
+ data.holes.push( hole.toJSON() );
+
+ }
+
+ return data;
+
+ },
+
+ fromJSON: function ( json ) {
+
+ Path.prototype.fromJSON.call( this, json );
+
+ this.uuid = json.uuid;
+ this.holes = [];
+
+ for ( let i = 0, l = json.holes.length; i < l; i ++ ) {
+
+ const hole = json.holes[ i ];
+ this.holes.push( new Path().fromJSON( hole ) );
+
+ }
+
+ return this;
+
+ }
+
+} );
+
+function Light( color, intensity = 1 ) {
+
+ Object3D.call( this );
+
+ this.type = 'Light';
+
+ this.color = new Color( color );
+ this.intensity = intensity;
+
+}
+
+Light.prototype = Object.assign( Object.create( Object3D.prototype ), {
+
+ constructor: Light,
+
+ isLight: true,
+
+ copy: function ( source ) {
+
+ Object3D.prototype.copy.call( this, source );
+
+ this.color.copy( source.color );
+ this.intensity = source.intensity;
+
+ return this;
+
+ },
+
+ toJSON: function ( meta ) {
+
+ const data = Object3D.prototype.toJSON.call( this, meta );
+
+ data.object.color = this.color.getHex();
+ data.object.intensity = this.intensity;
+
+ if ( this.groundColor !== undefined ) data.object.groundColor = this.groundColor.getHex();
+
+ if ( this.distance !== undefined ) data.object.distance = this.distance;
+ if ( this.angle !== undefined ) data.object.angle = this.angle;
+ if ( this.decay !== undefined ) data.object.decay = this.decay;
+ if ( this.penumbra !== undefined ) data.object.penumbra = this.penumbra;
+
+ if ( this.shadow !== undefined ) data.object.shadow = this.shadow.toJSON();
+
+ return data;
+
+ }
+
+} );
+
+function HemisphereLight( skyColor, groundColor, intensity ) {
+
+ Light.call( this, skyColor, intensity );
+
+ this.type = 'HemisphereLight';
+
+ this.position.copy( Object3D.DefaultUp );
+ this.updateMatrix();
+
+ this.groundColor = new Color( groundColor );
+
+}
+
+HemisphereLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+ constructor: HemisphereLight,
+
+ isHemisphereLight: true,
+
+ copy: function ( source ) {
+
+ Light.prototype.copy.call( this, source );
+
+ this.groundColor.copy( source.groundColor );
+
+ return this;
+
+ }
+
+} );
+
+function LightShadow( camera ) {
+
+ this.camera = camera;
+
+ this.bias = 0;
+ this.normalBias = 0;
+ this.radius = 1;
+
+ this.mapSize = new Vector2( 512, 512 );
+
+ this.map = null;
+ this.mapPass = null;
+ this.matrix = new Matrix4();
+
+ this.autoUpdate = true;
+ this.needsUpdate = false;
+
+ this._frustum = new Frustum();
+ this._frameExtents = new Vector2( 1, 1 );
+
+ this._viewportCount = 1;
+
+ this._viewports = [
+
+ new Vector4( 0, 0, 1, 1 )
+
+ ];
+
+}
+
+Object.assign( LightShadow.prototype, {
+
+ _projScreenMatrix: new Matrix4(),
+
+ _lightPositionWorld: new Vector3(),
+
+ _lookTarget: new Vector3(),
+
+ getViewportCount: function () {
+
+ return this._viewportCount;
+
+ },
+
+ getFrustum: function () {
+
+ return this._frustum;
+
+ },
+
+ updateMatrices: function ( light ) {
+
+ const shadowCamera = this.camera,
+ shadowMatrix = this.matrix,
+ projScreenMatrix = this._projScreenMatrix,
+ lookTarget = this._lookTarget,
+ lightPositionWorld = this._lightPositionWorld;
+
+ lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
+ shadowCamera.position.copy( lightPositionWorld );
+
+ lookTarget.setFromMatrixPosition( light.target.matrixWorld );
+ shadowCamera.lookAt( lookTarget );
+ shadowCamera.updateMatrixWorld();
+
+ projScreenMatrix.multiplyMatrices( shadowCamera.projectionMatrix, shadowCamera.matrixWorldInverse );
+ this._frustum.setFromProjectionMatrix( projScreenMatrix );
+
+ shadowMatrix.set(
+ 0.5, 0.0, 0.0, 0.5,
+ 0.0, 0.5, 0.0, 0.5,
+ 0.0, 0.0, 0.5, 0.5,
+ 0.0, 0.0, 0.0, 1.0
+ );
+
+ shadowMatrix.multiply( shadowCamera.projectionMatrix );
+ shadowMatrix.multiply( shadowCamera.matrixWorldInverse );
+
+ },
+
+ getViewport: function ( viewportIndex ) {
+
+ return this._viewports[ viewportIndex ];
+
+ },
+
+ getFrameExtents: function () {
+
+ return this._frameExtents;
+
+ },
+
+ copy: function ( source ) {
+
+ this.camera = source.camera.clone();
+
+ this.bias = source.bias;
+ this.radius = source.radius;
+
+ this.mapSize.copy( source.mapSize );
+
+ return this;
+
+ },
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ },
+
+ toJSON: function () {
+
+ const object = {};
+
+ if ( this.bias !== 0 ) object.bias = this.bias;
+ if ( this.normalBias !== 0 ) object.normalBias = this.normalBias;
+ if ( this.radius !== 1 ) object.radius = this.radius;
+ if ( this.mapSize.x !== 512 || this.mapSize.y !== 512 ) object.mapSize = this.mapSize.toArray();
+
+ object.camera = this.camera.toJSON( false ).object;
+ delete object.camera.matrix;
+
+ return object;
+
+ }
+
+} );
+
+function SpotLightShadow() {
+
+ LightShadow.call( this, new PerspectiveCamera( 50, 1, 0.5, 500 ) );
+
+ this.focus = 1;
+
+}
+
+SpotLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {
+
+ constructor: SpotLightShadow,
+
+ isSpotLightShadow: true,
+
+ updateMatrices: function ( light ) {
+
+ const camera = this.camera;
+
+ const fov = MathUtils.RAD2DEG * 2 * light.angle * this.focus;
+ const aspect = this.mapSize.width / this.mapSize.height;
+ const far = light.distance || camera.far;
+
+ if ( fov !== camera.fov || aspect !== camera.aspect || far !== camera.far ) {
+
+ camera.fov = fov;
+ camera.aspect = aspect;
+ camera.far = far;
+ camera.updateProjectionMatrix();
+
+ }
+
+ LightShadow.prototype.updateMatrices.call( this, light );
+
+ }
+
+} );
+
+function SpotLight( color, intensity, distance, angle, penumbra, decay ) {
+
+ Light.call( this, color, intensity );
+
+ this.type = 'SpotLight';
+
+ this.position.copy( Object3D.DefaultUp );
+ this.updateMatrix();
+
+ this.target = new Object3D();
+
+ Object.defineProperty( this, 'power', {
+ get: function () {
+
+ // intensity = power per solid angle.
+ // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+ return this.intensity * Math.PI;
+
+ },
+ set: function ( power ) {
+
+ // intensity = power per solid angle.
+ // ref: equation (17) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+ this.intensity = power / Math.PI;
+
+ }
+ } );
+
+ this.distance = ( distance !== undefined ) ? distance : 0;
+ this.angle = ( angle !== undefined ) ? angle : Math.PI / 3;
+ this.penumbra = ( penumbra !== undefined ) ? penumbra : 0;
+ this.decay = ( decay !== undefined ) ? decay : 1; // for physically correct lights, should be 2.
+
+ this.shadow = new SpotLightShadow();
+
+}
+
+SpotLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+ constructor: SpotLight,
+
+ isSpotLight: true,
+
+ copy: function ( source ) {
+
+ Light.prototype.copy.call( this, source );
+
+ this.distance = source.distance;
+ this.angle = source.angle;
+ this.penumbra = source.penumbra;
+ this.decay = source.decay;
+
+ this.target = source.target.clone();
+
+ this.shadow = source.shadow.clone();
+
+ return this;
+
+ }
+
+} );
+
+function PointLightShadow() {
+
+ LightShadow.call( this, new PerspectiveCamera( 90, 1, 0.5, 500 ) );
+
+ this._frameExtents = new Vector2( 4, 2 );
+
+ this._viewportCount = 6;
+
+ this._viewports = [
+ // These viewports map a cube-map onto a 2D texture with the
+ // following orientation:
+ //
+ // xzXZ
+ // y Y
+ //
+ // X - Positive x direction
+ // x - Negative x direction
+ // Y - Positive y direction
+ // y - Negative y direction
+ // Z - Positive z direction
+ // z - Negative z direction
+
+ // positive X
+ new Vector4( 2, 1, 1, 1 ),
+ // negative X
+ new Vector4( 0, 1, 1, 1 ),
+ // positive Z
+ new Vector4( 3, 1, 1, 1 ),
+ // negative Z
+ new Vector4( 1, 1, 1, 1 ),
+ // positive Y
+ new Vector4( 3, 0, 1, 1 ),
+ // negative Y
+ new Vector4( 1, 0, 1, 1 )
+ ];
+
+ this._cubeDirections = [
+ new Vector3( 1, 0, 0 ), new Vector3( - 1, 0, 0 ), new Vector3( 0, 0, 1 ),
+ new Vector3( 0, 0, - 1 ), new Vector3( 0, 1, 0 ), new Vector3( 0, - 1, 0 )
+ ];
+
+ this._cubeUps = [
+ new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ), new Vector3( 0, 1, 0 ),
+ new Vector3( 0, 1, 0 ), new Vector3( 0, 0, 1 ), new Vector3( 0, 0, - 1 )
+ ];
+
+}
+
+PointLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {
+
+ constructor: PointLightShadow,
+
+ isPointLightShadow: true,
+
+ updateMatrices: function ( light, viewportIndex = 0 ) {
+
+ const camera = this.camera,
+ shadowMatrix = this.matrix,
+ lightPositionWorld = this._lightPositionWorld,
+ lookTarget = this._lookTarget,
+ projScreenMatrix = this._projScreenMatrix;
+
+ lightPositionWorld.setFromMatrixPosition( light.matrixWorld );
+ camera.position.copy( lightPositionWorld );
+
+ lookTarget.copy( camera.position );
+ lookTarget.add( this._cubeDirections[ viewportIndex ] );
+ camera.up.copy( this._cubeUps[ viewportIndex ] );
+ camera.lookAt( lookTarget );
+ camera.updateMatrixWorld();
+
+ shadowMatrix.makeTranslation( - lightPositionWorld.x, - lightPositionWorld.y, - lightPositionWorld.z );
+
+ projScreenMatrix.multiplyMatrices( camera.projectionMatrix, camera.matrixWorldInverse );
+ this._frustum.setFromProjectionMatrix( projScreenMatrix );
+
+ }
+
+} );
+
+function PointLight( color, intensity, distance, decay ) {
+
+ Light.call( this, color, intensity );
+
+ this.type = 'PointLight';
+
+ Object.defineProperty( this, 'power', {
+ get: function () {
+
+ // intensity = power per solid angle.
+ // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+ return this.intensity * 4 * Math.PI;
+
+ },
+ set: function ( power ) {
+
+ // intensity = power per solid angle.
+ // ref: equation (15) from https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf
+ this.intensity = power / ( 4 * Math.PI );
+
+ }
+ } );
+
+ this.distance = ( distance !== undefined ) ? distance : 0;
+ this.decay = ( decay !== undefined ) ? decay : 1; // for physically correct lights, should be 2.
+
+ this.shadow = new PointLightShadow();
+
+}
+
+PointLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+ constructor: PointLight,
+
+ isPointLight: true,
+
+ copy: function ( source ) {
+
+ Light.prototype.copy.call( this, source );
+
+ this.distance = source.distance;
+ this.decay = source.decay;
+
+ this.shadow = source.shadow.clone();
+
+ return this;
+
+ }
+
+} );
+
+function OrthographicCamera( left = - 1, right = 1, top = 1, bottom = - 1, near = 0.1, far = 2000 ) {
+
+ Camera$1.call( this );
+
+ this.type = 'OrthographicCamera';
+
+ this.zoom = 1;
+ this.view = null;
+
+ this.left = left;
+ this.right = right;
+ this.top = top;
+ this.bottom = bottom;
+
+ this.near = near;
+ this.far = far;
+
+ this.updateProjectionMatrix();
+
+}
+
+OrthographicCamera.prototype = Object.assign( Object.create( Camera$1.prototype ), {
+
+ constructor: OrthographicCamera,
+
+ isOrthographicCamera: true,
+
+ copy: function ( source, recursive ) {
+
+ Camera$1.prototype.copy.call( this, source, recursive );
+
+ this.left = source.left;
+ this.right = source.right;
+ this.top = source.top;
+ this.bottom = source.bottom;
+ this.near = source.near;
+ this.far = source.far;
+
+ this.zoom = source.zoom;
+ this.view = source.view === null ? null : Object.assign( {}, source.view );
+
+ return this;
+
+ },
+
+ setViewOffset: function ( fullWidth, fullHeight, x, y, width, height ) {
+
+ if ( this.view === null ) {
+
+ this.view = {
+ enabled: true,
+ fullWidth: 1,
+ fullHeight: 1,
+ offsetX: 0,
+ offsetY: 0,
+ width: 1,
+ height: 1
+ };
+
+ }
+
+ this.view.enabled = true;
+ this.view.fullWidth = fullWidth;
+ this.view.fullHeight = fullHeight;
+ this.view.offsetX = x;
+ this.view.offsetY = y;
+ this.view.width = width;
+ this.view.height = height;
+
+ this.updateProjectionMatrix();
+
+ },
+
+ clearViewOffset: function () {
+
+ if ( this.view !== null ) {
+
+ this.view.enabled = false;
+
+ }
+
+ this.updateProjectionMatrix();
+
+ },
+
+ updateProjectionMatrix: function () {
+
+ const dx = ( this.right - this.left ) / ( 2 * this.zoom );
+ const dy = ( this.top - this.bottom ) / ( 2 * this.zoom );
+ const cx = ( this.right + this.left ) / 2;
+ const cy = ( this.top + this.bottom ) / 2;
+
+ let left = cx - dx;
+ let right = cx + dx;
+ let top = cy + dy;
+ let bottom = cy - dy;
+
+ if ( this.view !== null && this.view.enabled ) {
+
+ const scaleW = ( this.right - this.left ) / this.view.fullWidth / this.zoom;
+ const scaleH = ( this.top - this.bottom ) / this.view.fullHeight / this.zoom;
+
+ left += scaleW * this.view.offsetX;
+ right = left + scaleW * this.view.width;
+ top -= scaleH * this.view.offsetY;
+ bottom = top - scaleH * this.view.height;
+
+ }
+
+ this.projectionMatrix.makeOrthographic( left, right, top, bottom, this.near, this.far );
+
+ this.projectionMatrixInverse.copy( this.projectionMatrix ).invert();
+
+ },
+
+ toJSON: function ( meta ) {
+
+ const data = Object3D.prototype.toJSON.call( this, meta );
+
+ data.object.zoom = this.zoom;
+ data.object.left = this.left;
+ data.object.right = this.right;
+ data.object.top = this.top;
+ data.object.bottom = this.bottom;
+ data.object.near = this.near;
+ data.object.far = this.far;
+
+ if ( this.view !== null ) data.object.view = Object.assign( {}, this.view );
+
+ return data;
+
+ }
+
+} );
+
+function DirectionalLightShadow() {
+
+ LightShadow.call( this, new OrthographicCamera( - 5, 5, 5, - 5, 0.5, 500 ) );
+
+}
+
+DirectionalLightShadow.prototype = Object.assign( Object.create( LightShadow.prototype ), {
+
+ constructor: DirectionalLightShadow,
+
+ isDirectionalLightShadow: true,
+
+ updateMatrices: function ( light ) {
+
+ LightShadow.prototype.updateMatrices.call( this, light );
+
+ }
+
+} );
+
+function DirectionalLight( color, intensity ) {
+
+ Light.call( this, color, intensity );
+
+ this.type = 'DirectionalLight';
+
+ this.position.copy( Object3D.DefaultUp );
+ this.updateMatrix();
+
+ this.target = new Object3D();
+
+ this.shadow = new DirectionalLightShadow();
+
+}
+
+DirectionalLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+ constructor: DirectionalLight,
+
+ isDirectionalLight: true,
+
+ copy: function ( source ) {
+
+ Light.prototype.copy.call( this, source );
+
+ this.target = source.target.clone();
+
+ this.shadow = source.shadow.clone();
+
+ return this;
+
+ }
+
+} );
+
+function AmbientLight( color, intensity ) {
+
+ Light.call( this, color, intensity );
+
+ this.type = 'AmbientLight';
+
+}
+
+AmbientLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+ constructor: AmbientLight,
+
+ isAmbientLight: true
+
+} );
+
+function RectAreaLight( color, intensity, width, height ) {
+
+ Light.call( this, color, intensity );
+
+ this.type = 'RectAreaLight';
+
+ this.width = ( width !== undefined ) ? width : 10;
+ this.height = ( height !== undefined ) ? height : 10;
+
+}
+
+RectAreaLight.prototype = Object.assign( Object.create( Light.prototype ), {
+
+ constructor: RectAreaLight,
+
+ isRectAreaLight: true,
+
+ copy: function ( source ) {
+
+ Light.prototype.copy.call( this, source );
+
+ this.width = source.width;
+ this.height = source.height;
+
+ return this;
+
+ },
+
+ toJSON: function ( meta ) {
+
+ const data = Light.prototype.toJSON.call( this, meta );
+
+ data.object.width = this.width;
+ data.object.height = this.height;
+
+ return data;
+
+ }
+
+} );
+
+/**
+ * Primary reference:
+ * https://graphics.stanford.edu/papers/envmap/envmap.pdf
+ *
+ * Secondary reference:
+ * https://www.ppsloan.org/publications/StupidSH36.pdf
+ */
+
+// 3-band SH defined by 9 coefficients
+
+class SphericalHarmonics3 {
+
+ constructor() {
+
+ Object.defineProperty( this, 'isSphericalHarmonics3', { value: true } );
+
+ this.coefficients = [];
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ this.coefficients.push( new Vector3() );
+
+ }
+
+ }
+
+ set( coefficients ) {
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ this.coefficients[ i ].copy( coefficients[ i ] );
+
+ }
+
+ return this;
+
+ }
+
+ zero() {
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ this.coefficients[ i ].set( 0, 0, 0 );
+
+ }
+
+ return this;
+
+ }
+
+ // get the radiance in the direction of the normal
+ // target is a Vector3
+ getAt( normal, target ) {
+
+ // normal is assumed to be unit length
+
+ const x = normal.x, y = normal.y, z = normal.z;
+
+ const coeff = this.coefficients;
+
+ // band 0
+ target.copy( coeff[ 0 ] ).multiplyScalar( 0.282095 );
+
+ // band 1
+ target.addScaledVector( coeff[ 1 ], 0.488603 * y );
+ target.addScaledVector( coeff[ 2 ], 0.488603 * z );
+ target.addScaledVector( coeff[ 3 ], 0.488603 * x );
+
+ // band 2
+ target.addScaledVector( coeff[ 4 ], 1.092548 * ( x * y ) );
+ target.addScaledVector( coeff[ 5 ], 1.092548 * ( y * z ) );
+ target.addScaledVector( coeff[ 6 ], 0.315392 * ( 3.0 * z * z - 1.0 ) );
+ target.addScaledVector( coeff[ 7 ], 1.092548 * ( x * z ) );
+ target.addScaledVector( coeff[ 8 ], 0.546274 * ( x * x - y * y ) );
+
+ return target;
+
+ }
+
+ // get the irradiance (radiance convolved with cosine lobe) in the direction of the normal
+ // target is a Vector3
+ // https://graphics.stanford.edu/papers/envmap/envmap.pdf
+ getIrradianceAt( normal, target ) {
+
+ // normal is assumed to be unit length
+
+ const x = normal.x, y = normal.y, z = normal.z;
+
+ const coeff = this.coefficients;
+
+ // band 0
+ target.copy( coeff[ 0 ] ).multiplyScalar( 0.886227 ); // π * 0.282095
+
+ // band 1
+ target.addScaledVector( coeff[ 1 ], 2.0 * 0.511664 * y ); // ( 2 * π / 3 ) * 0.488603
+ target.addScaledVector( coeff[ 2 ], 2.0 * 0.511664 * z );
+ target.addScaledVector( coeff[ 3 ], 2.0 * 0.511664 * x );
+
+ // band 2
+ target.addScaledVector( coeff[ 4 ], 2.0 * 0.429043 * x * y ); // ( π / 4 ) * 1.092548
+ target.addScaledVector( coeff[ 5 ], 2.0 * 0.429043 * y * z );
+ target.addScaledVector( coeff[ 6 ], 0.743125 * z * z - 0.247708 ); // ( π / 4 ) * 0.315392 * 3
+ target.addScaledVector( coeff[ 7 ], 2.0 * 0.429043 * x * z );
+ target.addScaledVector( coeff[ 8 ], 0.429043 * ( x * x - y * y ) ); // ( π / 4 ) * 0.546274
+
+ return target;
+
+ }
+
+ add( sh ) {
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ this.coefficients[ i ].add( sh.coefficients[ i ] );
+
+ }
+
+ return this;
+
+ }
+
+ addScaledSH( sh, s ) {
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ this.coefficients[ i ].addScaledVector( sh.coefficients[ i ], s );
+
+ }
+
+ return this;
+
+ }
+
+ scale( s ) {
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ this.coefficients[ i ].multiplyScalar( s );
+
+ }
+
+ return this;
+
+ }
+
+ lerp( sh, alpha ) {
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ this.coefficients[ i ].lerp( sh.coefficients[ i ], alpha );
+
+ }
+
+ return this;
+
+ }
+
+ equals( sh ) {
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ if ( ! this.coefficients[ i ].equals( sh.coefficients[ i ] ) ) {
+
+ return false;
+
+ }
+
+ }
+
+ return true;
+
+ }
+
+ copy( sh ) {
+
+ return this.set( sh.coefficients );
+
+ }
+
+ clone() {
+
+ return new this.constructor().copy( this );
+
+ }
+
+ fromArray( array, offset = 0 ) {
+
+ const coefficients = this.coefficients;
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ coefficients[ i ].fromArray( array, offset + ( i * 3 ) );
+
+ }
+
+ return this;
+
+ }
+
+ toArray( array = [], offset = 0 ) {
+
+ const coefficients = this.coefficients;
+
+ for ( let i = 0; i < 9; i ++ ) {
+
+ coefficients[ i ].toArray( array, offset + ( i * 3 ) );
+
+ }
+
+ return array;
+
+ }
+
+ // evaluate the basis functions
+ // shBasis is an Array[ 9 ]
+ static getBasisAt( normal, shBasis ) {
+
+ // normal is assumed to be unit length
+
+ const x = normal.x, y = normal.y, z = normal.z;
+
+ // band 0
+ shBasis[ 0 ] = 0.282095;
+
+ // band 1
+ shBasis[ 1 ] = 0.488603 * y;
+ shBasis[ 2 ] = 0.488603 * z;
+ shBasis[ 3 ] = 0.488603 * x;
+
+ // band 2
+ shBasis[ 4 ] = 1.092548 * x * y;
+ shBasis[ 5 ] = 1.092548 * y * z;
+ shBasis[ 6 ] = 0.315392 * ( 3 * z * z - 1 );
+ shBasis[ 7 ] = 1.092548 * x * z;
+ shBasis[ 8 ] = 0.546274 * ( x * x - y * y );
+
+ }
+
+}
+
+function LightProbe( sh, intensity ) {
+
+ Light.call( this, undefined, intensity );
+
+ this.type = 'LightProbe';
+
+ this.sh = ( sh !== undefined ) ? sh : new SphericalHarmonics3();
+
+}
+
+LightProbe.prototype = Object.assign( Object.create( Light.prototype ), {
+
+ constructor: LightProbe,
+
+ isLightProbe: true,
+
+ copy: function ( source ) {
+
+ Light.prototype.copy.call( this, source );
+
+ this.sh.copy( source.sh );
+
+ return this;
+
+ },
+
+ fromJSON: function ( json ) {
+
+ this.intensity = json.intensity; // TODO: Move this bit to Light.fromJSON();
+ this.sh.fromArray( json.sh );
+
+ return this;
+
+ },
+
+ toJSON: function ( meta ) {
+
+ const data = Light.prototype.toJSON.call( this, meta );
+
+ data.object.sh = this.sh.toArray();
+
+ return data;
+
+ }
+
+} );
+
+function MaterialLoader( manager ) {
+
+ Loader.call( this, manager );
+
+ this.textures = {};
+
+}
+
+MaterialLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: MaterialLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ const scope = this;
+
+ const loader = new FileLoader( scope.manager );
+ loader.setPath( scope.path );
+ loader.setRequestHeader( scope.requestHeader );
+ loader.setWithCredentials( scope.withCredentials );
+ loader.load( url, function ( text ) {
+
+ try {
+
+ onLoad( scope.parse( JSON.parse( text ) ) );
+
+ } catch ( e ) {
+
+ if ( onError ) {
+
+ onError( e );
+
+ } else {
+
+ console.error( e );
+
+ }
+
+ scope.manager.itemError( url );
+
+ }
+
+ }, onProgress, onError );
+
+ },
+
+ parse: function ( json ) {
+
+ const textures = this.textures;
+
+ function getTexture( name ) {
+
+ if ( textures[ name ] === undefined ) {
+
+ console.warn( 'THREE.MaterialLoader: Undefined texture', name );
+
+ }
+
+ return textures[ name ];
+
+ }
+
+ const material = new Materials[ json.type ]();
+
+ if ( json.uuid !== undefined ) material.uuid = json.uuid;
+ if ( json.name !== undefined ) material.name = json.name;
+ if ( json.color !== undefined && material.color !== undefined ) material.color.setHex( json.color );
+ if ( json.roughness !== undefined ) material.roughness = json.roughness;
+ if ( json.metalness !== undefined ) material.metalness = json.metalness;
+ if ( json.sheen !== undefined ) material.sheen = new Color().setHex( json.sheen );
+ if ( json.emissive !== undefined && material.emissive !== undefined ) material.emissive.setHex( json.emissive );
+ if ( json.specular !== undefined && material.specular !== undefined ) material.specular.setHex( json.specular );
+ if ( json.shininess !== undefined ) material.shininess = json.shininess;
+ if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat;
+ if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness;
+ if ( json.fog !== undefined ) material.fog = json.fog;
+ if ( json.flatShading !== undefined ) material.flatShading = json.flatShading;
+ if ( json.blending !== undefined ) material.blending = json.blending;
+ if ( json.combine !== undefined ) material.combine = json.combine;
+ if ( json.side !== undefined ) material.side = json.side;
+ if ( json.opacity !== undefined ) material.opacity = json.opacity;
+ if ( json.transparent !== undefined ) material.transparent = json.transparent;
+ if ( json.alphaTest !== undefined ) material.alphaTest = json.alphaTest;
+ if ( json.depthTest !== undefined ) material.depthTest = json.depthTest;
+ if ( json.depthWrite !== undefined ) material.depthWrite = json.depthWrite;
+ if ( json.colorWrite !== undefined ) material.colorWrite = json.colorWrite;
+
+ if ( json.stencilWrite !== undefined ) material.stencilWrite = json.stencilWrite;
+ if ( json.stencilWriteMask !== undefined ) material.stencilWriteMask = json.stencilWriteMask;
+ if ( json.stencilFunc !== undefined ) material.stencilFunc = json.stencilFunc;
+ if ( json.stencilRef !== undefined ) material.stencilRef = json.stencilRef;
+ if ( json.stencilFuncMask !== undefined ) material.stencilFuncMask = json.stencilFuncMask;
+ if ( json.stencilFail !== undefined ) material.stencilFail = json.stencilFail;
+ if ( json.stencilZFail !== undefined ) material.stencilZFail = json.stencilZFail;
+ if ( json.stencilZPass !== undefined ) material.stencilZPass = json.stencilZPass;
+
+ if ( json.wireframe !== undefined ) material.wireframe = json.wireframe;
+ if ( json.wireframeLinewidth !== undefined ) material.wireframeLinewidth = json.wireframeLinewidth;
+ if ( json.wireframeLinecap !== undefined ) material.wireframeLinecap = json.wireframeLinecap;
+ if ( json.wireframeLinejoin !== undefined ) material.wireframeLinejoin = json.wireframeLinejoin;
+
+ if ( json.rotation !== undefined ) material.rotation = json.rotation;
+
+ if ( json.linewidth !== 1 ) material.linewidth = json.linewidth;
+ if ( json.dashSize !== undefined ) material.dashSize = json.dashSize;
+ if ( json.gapSize !== undefined ) material.gapSize = json.gapSize;
+ if ( json.scale !== undefined ) material.scale = json.scale;
+
+ if ( json.polygonOffset !== undefined ) material.polygonOffset = json.polygonOffset;
+ if ( json.polygonOffsetFactor !== undefined ) material.polygonOffsetFactor = json.polygonOffsetFactor;
+ if ( json.polygonOffsetUnits !== undefined ) material.polygonOffsetUnits = json.polygonOffsetUnits;
+
+ if ( json.skinning !== undefined ) material.skinning = json.skinning;
+ if ( json.morphTargets !== undefined ) material.morphTargets = json.morphTargets;
+ if ( json.morphNormals !== undefined ) material.morphNormals = json.morphNormals;
+ if ( json.dithering !== undefined ) material.dithering = json.dithering;
+
+ if ( json.vertexTangents !== undefined ) material.vertexTangents = json.vertexTangents;
+
+ if ( json.visible !== undefined ) material.visible = json.visible;
+
+ if ( json.toneMapped !== undefined ) material.toneMapped = json.toneMapped;
+
+ if ( json.userData !== undefined ) material.userData = json.userData;
+
+ if ( json.vertexColors !== undefined ) {
+
+ if ( typeof json.vertexColors === 'number' ) {
+
+ material.vertexColors = ( json.vertexColors > 0 ) ? true : false;
+
+ } else {
+
+ material.vertexColors = json.vertexColors;
+
+ }
+
+ }
+
+ // Shader Material
+
+ if ( json.uniforms !== undefined ) {
+
+ for ( const name in json.uniforms ) {
+
+ const uniform = json.uniforms[ name ];
+
+ material.uniforms[ name ] = {};
+
+ switch ( uniform.type ) {
+
+ case 't':
+ material.uniforms[ name ].value = getTexture( uniform.value );
+ break;
+
+ case 'c':
+ material.uniforms[ name ].value = new Color().setHex( uniform.value );
+ break;
+
+ case 'v2':
+ material.uniforms[ name ].value = new Vector2().fromArray( uniform.value );
+ break;
+
+ case 'v3':
+ material.uniforms[ name ].value = new Vector3().fromArray( uniform.value );
+ break;
+
+ case 'v4':
+ material.uniforms[ name ].value = new Vector4().fromArray( uniform.value );
+ break;
+
+ case 'm3':
+ material.uniforms[ name ].value = new Matrix3().fromArray( uniform.value );
+ break;
+
+ case 'm4':
+ material.uniforms[ name ].value = new Matrix4().fromArray( uniform.value );
+ break;
+
+ default:
+ material.uniforms[ name ].value = uniform.value;
+
+ }
+
+ }
+
+ }
+
+ if ( json.defines !== undefined ) material.defines = json.defines;
+ if ( json.vertexShader !== undefined ) material.vertexShader = json.vertexShader;
+ if ( json.fragmentShader !== undefined ) material.fragmentShader = json.fragmentShader;
+
+ if ( json.extensions !== undefined ) {
+
+ for ( const key in json.extensions ) {
+
+ material.extensions[ key ] = json.extensions[ key ];
+
+ }
+
+ }
+
+ // Deprecated
+
+ if ( json.shading !== undefined ) material.flatShading = json.shading === 1; // THREE.FlatShading
+
+ // for PointsMaterial
+
+ if ( json.size !== undefined ) material.size = json.size;
+ if ( json.sizeAttenuation !== undefined ) material.sizeAttenuation = json.sizeAttenuation;
+
+ // maps
+
+ if ( json.map !== undefined ) material.map = getTexture( json.map );
+ if ( json.matcap !== undefined ) material.matcap = getTexture( json.matcap );
+
+ if ( json.alphaMap !== undefined ) material.alphaMap = getTexture( json.alphaMap );
+
+ if ( json.bumpMap !== undefined ) material.bumpMap = getTexture( json.bumpMap );
+ if ( json.bumpScale !== undefined ) material.bumpScale = json.bumpScale;
+
+ if ( json.normalMap !== undefined ) material.normalMap = getTexture( json.normalMap );
+ if ( json.normalMapType !== undefined ) material.normalMapType = json.normalMapType;
+ if ( json.normalScale !== undefined ) {
+
+ let normalScale = json.normalScale;
+
+ if ( Array.isArray( normalScale ) === false ) {
+
+ // Blender exporter used to export a scalar. See #7459
+
+ normalScale = [ normalScale, normalScale ];
+
+ }
+
+ material.normalScale = new Vector2().fromArray( normalScale );
+
+ }
+
+ if ( json.displacementMap !== undefined ) material.displacementMap = getTexture( json.displacementMap );
+ if ( json.displacementScale !== undefined ) material.displacementScale = json.displacementScale;
+ if ( json.displacementBias !== undefined ) material.displacementBias = json.displacementBias;
+
+ if ( json.roughnessMap !== undefined ) material.roughnessMap = getTexture( json.roughnessMap );
+ if ( json.metalnessMap !== undefined ) material.metalnessMap = getTexture( json.metalnessMap );
+
+ if ( json.emissiveMap !== undefined ) material.emissiveMap = getTexture( json.emissiveMap );
+ if ( json.emissiveIntensity !== undefined ) material.emissiveIntensity = json.emissiveIntensity;
+
+ if ( json.specularMap !== undefined ) material.specularMap = getTexture( json.specularMap );
+
+ if ( json.envMap !== undefined ) material.envMap = getTexture( json.envMap );
+ if ( json.envMapIntensity !== undefined ) material.envMapIntensity = json.envMapIntensity;
+
+ if ( json.reflectivity !== undefined ) material.reflectivity = json.reflectivity;
+ if ( json.refractionRatio !== undefined ) material.refractionRatio = json.refractionRatio;
+
+ if ( json.lightMap !== undefined ) material.lightMap = getTexture( json.lightMap );
+ if ( json.lightMapIntensity !== undefined ) material.lightMapIntensity = json.lightMapIntensity;
+
+ if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap );
+ if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity;
+
+ if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap );
+
+ if ( json.clearcoatMap !== undefined ) material.clearcoatMap = getTexture( json.clearcoatMap );
+ if ( json.clearcoatRoughnessMap !== undefined ) material.clearcoatRoughnessMap = getTexture( json.clearcoatRoughnessMap );
+ if ( json.clearcoatNormalMap !== undefined ) material.clearcoatNormalMap = getTexture( json.clearcoatNormalMap );
+ if ( json.clearcoatNormalScale !== undefined ) material.clearcoatNormalScale = new Vector2().fromArray( json.clearcoatNormalScale );
+
+ if ( json.transmission !== undefined ) material.transmission = json.transmission;
+ if ( json.transmissionMap !== undefined ) material.transmissionMap = getTexture( json.transmissionMap );
+
+ return material;
+
+ },
+
+ setTextures: function ( value ) {
+
+ this.textures = value;
+ return this;
+
+ }
+
+} );
+
+const LoaderUtils = {
+
+ decodeText: function ( array ) {
+
+ if ( typeof TextDecoder !== 'undefined' ) {
+
+ return new TextDecoder().decode( array );
+
+ }
+
+ // Avoid the String.fromCharCode.apply(null, array) shortcut, which
+ // throws a "maximum call stack size exceeded" error for large arrays.
+
+ let s = '';
+
+ for ( let i = 0, il = array.length; i < il; i ++ ) {
+
+ // Implicitly assumes little-endian.
+ s += String.fromCharCode( array[ i ] );
+
+ }
+
+ try {
+
+ // merges multi-byte utf-8 characters.
+
+ return decodeURIComponent( escape( s ) );
+
+ } catch ( e ) { // see #16358
+
+ return s;
+
+ }
+
+ },
+
+ extractUrlBase: function ( url ) {
+
+ const index = url.lastIndexOf( '/' );
+
+ if ( index === - 1 ) return './';
+
+ return url.substr( 0, index + 1 );
+
+ }
+
+};
+
+function InstancedBufferGeometry() {
+
+ BufferGeometry.call( this );
+
+ this.type = 'InstancedBufferGeometry';
+ this.instanceCount = Infinity;
+
+}
+
+InstancedBufferGeometry.prototype = Object.assign( Object.create( BufferGeometry.prototype ), {
+
+ constructor: InstancedBufferGeometry,
+
+ isInstancedBufferGeometry: true,
+
+ copy: function ( source ) {
+
+ BufferGeometry.prototype.copy.call( this, source );
+
+ this.instanceCount = source.instanceCount;
+
+ return this;
+
+ },
+
+ clone: function () {
+
+ return new this.constructor().copy( this );
+
+ },
+
+ toJSON: function () {
+
+ const data = BufferGeometry.prototype.toJSON.call( this );
+
+ data.instanceCount = this.instanceCount;
+
+ data.isInstancedBufferGeometry = true;
+
+ return data;
+
+ }
+
+} );
+
+function InstancedBufferAttribute( array, itemSize, normalized, meshPerAttribute ) {
+
+ if ( typeof ( normalized ) === 'number' ) {
+
+ meshPerAttribute = normalized;
+
+ normalized = false;
+
+ console.error( 'THREE.InstancedBufferAttribute: The constructor now expects normalized as the third argument.' );
+
+ }
+
+ BufferAttribute.call( this, array, itemSize, normalized );
+
+ this.meshPerAttribute = meshPerAttribute || 1;
+
+}
+
+InstancedBufferAttribute.prototype = Object.assign( Object.create( BufferAttribute.prototype ), {
+
+ constructor: InstancedBufferAttribute,
+
+ isInstancedBufferAttribute: true,
+
+ copy: function ( source ) {
+
+ BufferAttribute.prototype.copy.call( this, source );
+
+ this.meshPerAttribute = source.meshPerAttribute;
+
+ return this;
+
+ },
+
+ toJSON: function () {
+
+ const data = BufferAttribute.prototype.toJSON.call( this );
+
+ data.meshPerAttribute = this.meshPerAttribute;
+
+ data.isInstancedBufferAttribute = true;
+
+ return data;
+
+ }
+
+} );
+
+function BufferGeometryLoader( manager ) {
+
+ Loader.call( this, manager );
+
+}
+
+BufferGeometryLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: BufferGeometryLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ const scope = this;
+
+ const loader = new FileLoader( scope.manager );
+ loader.setPath( scope.path );
+ loader.setRequestHeader( scope.requestHeader );
+ loader.setWithCredentials( scope.withCredentials );
+ loader.load( url, function ( text ) {
+
+ try {
+
+ onLoad( scope.parse( JSON.parse( text ) ) );
+
+ } catch ( e ) {
+
+ if ( onError ) {
+
+ onError( e );
+
+ } else {
+
+ console.error( e );
+
+ }
+
+ scope.manager.itemError( url );
+
+ }
+
+ }, onProgress, onError );
+
+ },
+
+ parse: function ( json ) {
+
+ const interleavedBufferMap = {};
+ const arrayBufferMap = {};
+
+ function getInterleavedBuffer( json, uuid ) {
+
+ if ( interleavedBufferMap[ uuid ] !== undefined ) return interleavedBufferMap[ uuid ];
+
+ const interleavedBuffers = json.interleavedBuffers;
+ const interleavedBuffer = interleavedBuffers[ uuid ];
+
+ const buffer = getArrayBuffer( json, interleavedBuffer.buffer );
+
+ const array = getTypedArray( interleavedBuffer.type, buffer );
+ const ib = new InterleavedBuffer( array, interleavedBuffer.stride );
+ ib.uuid = interleavedBuffer.uuid;
+
+ interleavedBufferMap[ uuid ] = ib;
+
+ return ib;
+
+ }
+
+ function getArrayBuffer( json, uuid ) {
+
+ if ( arrayBufferMap[ uuid ] !== undefined ) return arrayBufferMap[ uuid ];
+
+ const arrayBuffers = json.arrayBuffers;
+ const arrayBuffer = arrayBuffers[ uuid ];
+
+ const ab = new Uint32Array( arrayBuffer ).buffer;
+
+ arrayBufferMap[ uuid ] = ab;
+
+ return ab;
+
+ }
+
+ const geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry();
+
+ const index = json.data.index;
+
+ if ( index !== undefined ) {
+
+ const typedArray = getTypedArray( index.type, index.array );
+ geometry.setIndex( new BufferAttribute( typedArray, 1 ) );
+
+ }
+
+ const attributes = json.data.attributes;
+
+ for ( const key in attributes ) {
+
+ const attribute = attributes[ key ];
+ let bufferAttribute;
+
+ if ( attribute.isInterleavedBufferAttribute ) {
+
+ const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data );
+ bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized );
+
+ } else {
+
+ const typedArray = getTypedArray( attribute.type, attribute.array );
+ const bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute;
+ bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized );
+
+ }
+
+ if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name;
+ geometry.setAttribute( key, bufferAttribute );
+
+ }
+
+ const morphAttributes = json.data.morphAttributes;
+
+ if ( morphAttributes ) {
+
+ for ( const key in morphAttributes ) {
+
+ const attributeArray = morphAttributes[ key ];
+
+ const array = [];
+
+ for ( let i = 0, il = attributeArray.length; i < il; i ++ ) {
+
+ const attribute = attributeArray[ i ];
+ let bufferAttribute;
+
+ if ( attribute.isInterleavedBufferAttribute ) {
+
+ const interleavedBuffer = getInterleavedBuffer( json.data, attribute.data );
+ bufferAttribute = new InterleavedBufferAttribute( interleavedBuffer, attribute.itemSize, attribute.offset, attribute.normalized );
+
+ } else {
+
+ const typedArray = getTypedArray( attribute.type, attribute.array );
+ bufferAttribute = new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized );
+
+ }
+
+ if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name;
+ array.push( bufferAttribute );
+
+ }
+
+ geometry.morphAttributes[ key ] = array;
+
+ }
+
+ }
+
+ const morphTargetsRelative = json.data.morphTargetsRelative;
+
+ if ( morphTargetsRelative ) {
+
+ geometry.morphTargetsRelative = true;
+
+ }
+
+ const groups = json.data.groups || json.data.drawcalls || json.data.offsets;
+
+ if ( groups !== undefined ) {
+
+ for ( let i = 0, n = groups.length; i !== n; ++ i ) {
+
+ const group = groups[ i ];
+
+ geometry.addGroup( group.start, group.count, group.materialIndex );
+
+ }
+
+ }
+
+ const boundingSphere = json.data.boundingSphere;
+
+ if ( boundingSphere !== undefined ) {
+
+ const center = new Vector3();
+
+ if ( boundingSphere.center !== undefined ) {
+
+ center.fromArray( boundingSphere.center );
+
+ }
+
+ geometry.boundingSphere = new Sphere( center, boundingSphere.radius );
+
+ }
+
+ if ( json.name ) geometry.name = json.name;
+ if ( json.userData ) geometry.userData = json.userData;
+
+ return geometry;
+
+ }
+
+} );
+
+function ImageBitmapLoader( manager ) {
+
+ if ( typeof createImageBitmap === 'undefined' ) {
+
+ console.warn( 'THREE.ImageBitmapLoader: createImageBitmap() not supported.' );
+
+ }
+
+ if ( typeof fetch === 'undefined' ) {
+
+ console.warn( 'THREE.ImageBitmapLoader: fetch() not supported.' );
+
+ }
+
+ Loader.call( this, manager );
+
+ this.options = { premultiplyAlpha: 'none' };
+
+}
+
+ImageBitmapLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: ImageBitmapLoader,
+
+ isImageBitmapLoader: true,
+
+ setOptions: function setOptions( options ) {
+
+ this.options = options;
+
+ return this;
+
+ },
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ if ( url === undefined ) url = '';
+
+ if ( this.path !== undefined ) url = this.path + url;
+
+ url = this.manager.resolveURL( url );
+
+ const scope = this;
+
+ const cached = Cache.get( url );
+
+ if ( cached !== undefined ) {
+
+ scope.manager.itemStart( url );
+
+ setTimeout( function () {
+
+ if ( onLoad ) onLoad( cached );
+
+ scope.manager.itemEnd( url );
+
+ }, 0 );
+
+ return cached;
+
+ }
+
+ const fetchOptions = {};
+ fetchOptions.credentials = ( this.crossOrigin === 'anonymous' ) ? 'same-origin' : 'include';
+
+ fetch( url, fetchOptions ).then( function ( res ) {
+
+ return res.blob();
+
+ } ).then( function ( blob ) {
+
+ return createImageBitmap( blob, scope.options );
+
+ } ).then( function ( imageBitmap ) {
+
+ Cache.add( url, imageBitmap );
+
+ if ( onLoad ) onLoad( imageBitmap );
+
+ scope.manager.itemEnd( url );
+
+ } ).catch( function ( e ) {
+
+ if ( onError ) onError( e );
+
+ scope.manager.itemError( url );
+ scope.manager.itemEnd( url );
+
+ } );
+
+ scope.manager.itemStart( url );
+
+ }
+
+} );
+
+function ShapePath() {
+
+ this.type = 'ShapePath';
+
+ this.color = new Color();
+
+ this.subPaths = [];
+ this.currentPath = null;
+
+}
+
+Object.assign( ShapePath.prototype, {
+
+ moveTo: function ( x, y ) {
+
+ this.currentPath = new Path();
+ this.subPaths.push( this.currentPath );
+ this.currentPath.moveTo( x, y );
+
+ return this;
+
+ },
+
+ lineTo: function ( x, y ) {
+
+ this.currentPath.lineTo( x, y );
+
+ return this;
+
+ },
+
+ quadraticCurveTo: function ( aCPx, aCPy, aX, aY ) {
+
+ this.currentPath.quadraticCurveTo( aCPx, aCPy, aX, aY );
+
+ return this;
+
+ },
+
+ bezierCurveTo: function ( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY ) {
+
+ this.currentPath.bezierCurveTo( aCP1x, aCP1y, aCP2x, aCP2y, aX, aY );
+
+ return this;
+
+ },
+
+ splineThru: function ( pts ) {
+
+ this.currentPath.splineThru( pts );
+
+ return this;
+
+ },
+
+ toShapes: function ( isCCW, noHoles ) {
+
+ function toShapesNoHoles( inSubpaths ) {
+
+ const shapes = [];
+
+ for ( let i = 0, l = inSubpaths.length; i < l; i ++ ) {
+
+ const tmpPath = inSubpaths[ i ];
+
+ const tmpShape = new Shape();
+ tmpShape.curves = tmpPath.curves;
+
+ shapes.push( tmpShape );
+
+ }
+
+ return shapes;
+
+ }
+
+ function isPointInsidePolygon( inPt, inPolygon ) {
+
+ const polyLen = inPolygon.length;
+
+ // inPt on polygon contour => immediate success or
+ // toggling of inside/outside at every single! intersection point of an edge
+ // with the horizontal line through inPt, left of inPt
+ // not counting lowerY endpoints of edges and whole edges on that line
+ let inside = false;
+ for ( let p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) {
+
+ let edgeLowPt = inPolygon[ p ];
+ let edgeHighPt = inPolygon[ q ];
+
+ let edgeDx = edgeHighPt.x - edgeLowPt.x;
+ let edgeDy = edgeHighPt.y - edgeLowPt.y;
+
+ if ( Math.abs( edgeDy ) > Number.EPSILON ) {
+
+ // not parallel
+ if ( edgeDy < 0 ) {
+
+ edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx;
+ edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;
+
+ }
+
+ if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue;
+
+ if ( inPt.y === edgeLowPt.y ) {
+
+ if ( inPt.x === edgeLowPt.x ) return true; // inPt is on contour ?
+ // continue; // no intersection or edgeLowPt => doesn't count !!!
+
+ } else {
+
+ const perpEdge = edgeDy * ( inPt.x - edgeLowPt.x ) - edgeDx * ( inPt.y - edgeLowPt.y );
+ if ( perpEdge === 0 ) return true; // inPt is on contour ?
+ if ( perpEdge < 0 ) continue;
+ inside = ! inside; // true intersection left of inPt
+
+ }
+
+ } else {
+
+ // parallel or collinear
+ if ( inPt.y !== edgeLowPt.y ) continue; // parallel
+ // edge lies on the same horizontal line as inPt
+ if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) ||
+ ( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour !
+ // continue;
+
+ }
+
+ }
+
+ return inside;
+
+ }
+
+ const isClockWise = ShapeUtils.isClockWise;
+
+ const subPaths = this.subPaths;
+ if ( subPaths.length === 0 ) return [];
+
+ if ( noHoles === true ) return toShapesNoHoles( subPaths );
+
+
+ let solid, tmpPath, tmpShape;
+ const shapes = [];
+
+ if ( subPaths.length === 1 ) {
+
+ tmpPath = subPaths[ 0 ];
+ tmpShape = new Shape();
+ tmpShape.curves = tmpPath.curves;
+ shapes.push( tmpShape );
+ return shapes;
+
+ }
+
+ let holesFirst = ! isClockWise( subPaths[ 0 ].getPoints() );
+ holesFirst = isCCW ? ! holesFirst : holesFirst;
+
+ // console.log("Holes first", holesFirst);
+
+ const betterShapeHoles = [];
+ const newShapes = [];
+ let newShapeHoles = [];
+ let mainIdx = 0;
+ let tmpPoints;
+
+ newShapes[ mainIdx ] = undefined;
+ newShapeHoles[ mainIdx ] = [];
+
+ for ( let i = 0, l = subPaths.length; i < l; i ++ ) {
+
+ tmpPath = subPaths[ i ];
+ tmpPoints = tmpPath.getPoints();
+ solid = isClockWise( tmpPoints );
+ solid = isCCW ? ! solid : solid;
+
+ if ( solid ) {
+
+ if ( ( ! holesFirst ) && ( newShapes[ mainIdx ] ) ) mainIdx ++;
+
+ newShapes[ mainIdx ] = { s: new Shape(), p: tmpPoints };
+ newShapes[ mainIdx ].s.curves = tmpPath.curves;
+
+ if ( holesFirst ) mainIdx ++;
+ newShapeHoles[ mainIdx ] = [];
+
+ //console.log('cw', i);
+
+ } else {
+
+ newShapeHoles[ mainIdx ].push( { h: tmpPath, p: tmpPoints[ 0 ] } );
+
+ //console.log('ccw', i);
+
+ }
+
+ }
+
+ // only Holes? -> probably all Shapes with wrong orientation
+ if ( ! newShapes[ 0 ] ) return toShapesNoHoles( subPaths );
+
+
+ if ( newShapes.length > 1 ) {
+
+ let ambiguous = false;
+ const toChange = [];
+
+ for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
+
+ betterShapeHoles[ sIdx ] = [];
+
+ }
+
+ for ( let sIdx = 0, sLen = newShapes.length; sIdx < sLen; sIdx ++ ) {
+
+ const sho = newShapeHoles[ sIdx ];
+
+ for ( let hIdx = 0; hIdx < sho.length; hIdx ++ ) {
+
+ const ho = sho[ hIdx ];
+ let hole_unassigned = true;
+
+ for ( let s2Idx = 0; s2Idx < newShapes.length; s2Idx ++ ) {
+
+ if ( isPointInsidePolygon( ho.p, newShapes[ s2Idx ].p ) ) {
+
+ if ( sIdx !== s2Idx ) toChange.push( { froms: sIdx, tos: s2Idx, hole: hIdx } );
+ if ( hole_unassigned ) {
+
+ hole_unassigned = false;
+ betterShapeHoles[ s2Idx ].push( ho );
+
+ } else {
+
+ ambiguous = true;
+
+ }
+
+ }
+
+ }
+
+ if ( hole_unassigned ) {
+
+ betterShapeHoles[ sIdx ].push( ho );
+
+ }
+
+ }
+
+ }
+ // console.log("ambiguous: ", ambiguous);
+
+ if ( toChange.length > 0 ) {
+
+ // console.log("to change: ", toChange);
+ if ( ! ambiguous ) newShapeHoles = betterShapeHoles;
+
+ }
+
+ }
+
+ let tmpHoles;
+
+ for ( let i = 0, il = newShapes.length; i < il; i ++ ) {
+
+ tmpShape = newShapes[ i ].s;
+ shapes.push( tmpShape );
+ tmpHoles = newShapeHoles[ i ];
+
+ for ( let j = 0, jl = tmpHoles.length; j < jl; j ++ ) {
+
+ tmpShape.holes.push( tmpHoles[ j ].h );
+
+ }
+
+ }
+
+ //console.log("shape", shapes);
+
+ return shapes;
+
+ }
+
+} );
+
+class Font {
+
+ constructor( data ) {
+
+ Object.defineProperty( this, 'isFont', { value: true } );
+
+ this.type = 'Font';
+
+ this.data = data;
+
+ }
+
+ generateShapes( text, size = 100 ) {
+
+ const shapes = [];
+ const paths = createPaths( text, size, this.data );
+
+ for ( let p = 0, pl = paths.length; p < pl; p ++ ) {
+
+ Array.prototype.push.apply( shapes, paths[ p ].toShapes() );
+
+ }
+
+ return shapes;
+
+ }
+
+}
+
+function createPaths( text, size, data ) {
+
+ const chars = Array.from ? Array.from( text ) : String( text ).split( '' ); // workaround for IE11, see #13988
+ const scale = size / data.resolution;
+ const line_height = ( data.boundingBox.yMax - data.boundingBox.yMin + data.underlineThickness ) * scale;
+
+ const paths = [];
+
+ let offsetX = 0, offsetY = 0;
+
+ for ( let i = 0; i < chars.length; i ++ ) {
+
+ const char = chars[ i ];
+
+ if ( char === '\n' ) {
+
+ offsetX = 0;
+ offsetY -= line_height;
+
+ } else {
+
+ const ret = createPath( char, scale, offsetX, offsetY, data );
+ offsetX += ret.offsetX;
+ paths.push( ret.path );
+
+ }
+
+ }
+
+ return paths;
+
+}
+
+function createPath( char, scale, offsetX, offsetY, data ) {
+
+ const glyph = data.glyphs[ char ] || data.glyphs[ '?' ];
+
+ if ( ! glyph ) {
+
+ console.error( 'THREE.Font: character "' + char + '" does not exists in font family ' + data.familyName + '.' );
+
+ return;
+
+ }
+
+ const path = new ShapePath();
+
+ let x, y, cpx, cpy, cpx1, cpy1, cpx2, cpy2;
+
+ if ( glyph.o ) {
+
+ const outline = glyph._cachedOutline || ( glyph._cachedOutline = glyph.o.split( ' ' ) );
+
+ for ( let i = 0, l = outline.length; i < l; ) {
+
+ const action = outline[ i ++ ];
+
+ switch ( action ) {
+
+ case 'm': // moveTo
+
+ x = outline[ i ++ ] * scale + offsetX;
+ y = outline[ i ++ ] * scale + offsetY;
+
+ path.moveTo( x, y );
+
+ break;
+
+ case 'l': // lineTo
+
+ x = outline[ i ++ ] * scale + offsetX;
+ y = outline[ i ++ ] * scale + offsetY;
+
+ path.lineTo( x, y );
+
+ break;
+
+ case 'q': // quadraticCurveTo
+
+ cpx = outline[ i ++ ] * scale + offsetX;
+ cpy = outline[ i ++ ] * scale + offsetY;
+ cpx1 = outline[ i ++ ] * scale + offsetX;
+ cpy1 = outline[ i ++ ] * scale + offsetY;
+
+ path.quadraticCurveTo( cpx1, cpy1, cpx, cpy );
+
+ break;
+
+ case 'b': // bezierCurveTo
+
+ cpx = outline[ i ++ ] * scale + offsetX;
+ cpy = outline[ i ++ ] * scale + offsetY;
+ cpx1 = outline[ i ++ ] * scale + offsetX;
+ cpy1 = outline[ i ++ ] * scale + offsetY;
+ cpx2 = outline[ i ++ ] * scale + offsetX;
+ cpy2 = outline[ i ++ ] * scale + offsetY;
+
+ path.bezierCurveTo( cpx1, cpy1, cpx2, cpy2, cpx, cpy );
+
+ break;
+
+ }
+
+ }
+
+ }
+
+ return { offsetX: glyph.ha * scale, path: path };
+
+}
+
+function FontLoader( manager ) {
+
+ Loader.call( this, manager );
+
+}
+
+FontLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: FontLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ const scope = this;
+
+ const loader = new FileLoader( this.manager );
+ loader.setPath( this.path );
+ loader.setRequestHeader( this.requestHeader );
+ loader.setWithCredentials( scope.withCredentials );
+ loader.load( url, function ( text ) {
+
+ let json;
+
+ try {
+
+ json = JSON.parse( text );
+
+ } catch ( e ) {
+
+ console.warn( 'THREE.FontLoader: typeface.js support is being deprecated. Use typeface.json instead.' );
+ json = JSON.parse( text.substring( 65, text.length - 2 ) );
+
+ }
+
+ const font = scope.parse( json );
+
+ if ( onLoad ) onLoad( font );
+
+ }, onProgress, onError );
+
+ },
+
+ parse: function ( json ) {
+
+ return new Font( json );
+
+ }
+
+} );
+
+let _context;
+
+const AudioContext = {
+
+ getContext: function () {
+
+ if ( _context === undefined ) {
+
+ _context = new ( window.AudioContext || window.webkitAudioContext )();
+
+ }
+
+ return _context;
+
+ },
+
+ setContext: function ( value ) {
+
+ _context = value;
+
+ }
+
+};
+
+function AudioLoader( manager ) {
+
+ Loader.call( this, manager );
+
+}
+
+AudioLoader.prototype = Object.assign( Object.create( Loader.prototype ), {
+
+ constructor: AudioLoader,
+
+ load: function ( url, onLoad, onProgress, onError ) {
+
+ const scope = this;
+
+ const loader = new FileLoader( scope.manager );
+ loader.setResponseType( 'arraybuffer' );
+ loader.setPath( scope.path );
+ loader.setRequestHeader( scope.requestHeader );
+ loader.setWithCredentials( scope.withCredentials );
+ loader.load( url, function ( buffer ) {
+
+ try {
+
+ // Create a copy of the buffer. The `decodeAudioData` method
+ // detaches the buffer when complete, preventing reuse.
+ const bufferCopy = buffer.slice( 0 );
+
+ const context = AudioContext.getContext();
+ context.decodeAudioData( bufferCopy, function ( audioBuffer ) {
+
+ onLoad( audioBuffer );
+
+ } );
+
+ } catch ( e ) {
+
+ if ( onError ) {
+
+ onError( e );
+
+ } else {
+
+ console.error( e );
+
+ }
+
+ scope.manager.itemError( url );
+
+ }
+
+ }, onProgress, onError );
+
+ }
+
+} );
+
+function HemisphereLightProbe( skyColor, groundColor, intensity ) {
+
+ LightProbe.call( this, undefined, intensity );
+
+ const color1 = new Color().set( skyColor );
+ const color2 = new Color().set( groundColor );
+
+ const sky = new Vector3( color1.r, color1.g, color1.b );
+ const ground = new Vector3( color2.r, color2.g, color2.b );
+
+ // without extra factor of PI in the shader, should = 1 / Math.sqrt( Math.PI );
+ const c0 = Math.sqrt( Math.PI );
+ const c1 = c0 * Math.sqrt( 0.75 );
+
+ this.sh.coefficients[ 0 ].copy( sky ).add( ground ).multiplyScalar( c0 );
+ this.sh.coefficients[ 1 ].copy( sky ).sub( ground ).multiplyScalar( c1 );
+
+}
+
+HemisphereLightProbe.prototype = Object.assign( Object.create( LightProbe.prototype ), {
+
+ constructor: HemisphereLightProbe,
+
+ isHemisphereLightProbe: true,
+
+ copy: function ( source ) { // modifying colors not currently supported
+
+ LightProbe.prototype.copy.call( this, source );
+
+ return this;
+
+ },
+
+ toJSON: function ( meta ) {
+
+ const data = LightProbe.prototype.toJSON.call( this, meta );
+
+ // data.sh = this.sh.toArray(); // todo
+
+ return data;
+
+ }
+
+} );
+
+function AmbientLightProbe( color, intensity ) {
+
+ LightProbe.call( this, undefined, intensity );
+
+ const color1 = new Color().set( color );
+
+ // without extra factor of PI in the shader, would be 2 / Math.sqrt( Math.PI );
+ this.sh.coefficients[ 0 ].set( color1.r, color1.g, color1.b ).multiplyScalar( 2 * Math.sqrt( Math.PI ) );
+
+}
+
+AmbientLightProbe.prototype = Object.assign( Object.create( LightProbe.prototype ), {
+
+ constructor: AmbientLightProbe,
+
+ isAmbientLightProbe: true,
+
+ copy: function ( source ) { // modifying color not currently supported
+
+ LightProbe.prototype.copy.call( this, source );
+
+ return this;
+
+ },
+
+ toJSON: function ( meta ) {
+
+ const data = LightProbe.prototype.toJSON.call( this, meta );
+
+ // data.sh = this.sh.toArray(); // todo
+
+ return data;
+
+ }
+
+} );
+
+const _eyeRight = new Matrix4();
+const _eyeLeft = new Matrix4();
+
+function StereoCamera() {
+
+ this.type = 'StereoCamera';
+
+ this.aspect = 1;
+
+ this.eyeSep = 0.064;
+
+ this.cameraL = new PerspectiveCamera();
+ this.cameraL.layers.enable( 1 );
+ this.cameraL.matrixAutoUpdate = false;
+
+ this.cameraR = new PerspectiveCamera();
+ this.cameraR.layers.enable( 2 );
+ this.cameraR.matrixAutoUpdate = false;
+
+ this._cache = {
+ focus: null,
+ fov: null,
+ aspect: null,
+ near: null,
+ far: null,
+ zoom: null,
+ eyeSep: null
+ };
+
+}
+
+Object.assign( StereoCamera.prototype, {
+
+ update: function ( camera ) {
+
+ const cache = this._cache;
+
+ const needsUpdate = cache.focus !== camera.focus || cache.fov !== camera.fov ||
+ cache.aspect !== camera.aspect * this.aspect || cache.near !== camera.near ||
+ cache.far !== camera.far || cache.zoom !== camera.zoom || cache.eyeSep !== this.eyeSep;
+
+ if ( needsUpdate ) {
+
+ cache.focus = camera.focus;
+ cache.fov = camera.fov;
+ cache.aspect = camera.aspect * this.aspect;
+ cache.near = camera.near;
+ cache.far = camera.far;
+ cache.zoom = camera.zoom;
+ cache.eyeSep = this.eyeSep;
+
+ // Off-axis stereoscopic effect based on
+ // http://paulbourke.net/stereographics/stereorender/
+
+ const projectionMatrix = camera.projectionMatrix.clone();
+ const eyeSepHalf = cache.eyeSep / 2;
+ const eyeSepOnProjection = eyeSepHalf * cache.near / cache.focus;
+ const ymax = ( cache.near * Math.tan( MathUtils.DEG2RAD * cache.fov * 0.5 ) ) / cache.zoom;
+ let xmin, xmax;
+
+ // translate xOffset
+
+ _eyeLeft.elements[ 12 ] = - eyeSepHalf;
+ _eyeRight.elements[ 12 ] = eyeSepHalf;
+
+ // for left eye
+
+ xmin = - ymax * cache.aspect + eyeSepOnProjection;
+ xmax = ymax * cache.aspect + eyeSepOnProjection;
+
+ projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
+ projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );
+
+ this.cameraL.projectionMatrix.copy( projectionMatrix );
+
+ // for right eye
+
+ xmin = - ymax * cache.aspect - eyeSepOnProjection;
+ xmax = ymax * cache.aspect - eyeSepOnProjection;
+
+ projectionMatrix.elements[ 0 ] = 2 * cache.near / ( xmax - xmin );
+ projectionMatrix.elements[ 8 ] = ( xmax + xmin ) / ( xmax - xmin );
+
+ this.cameraR.projectionMatrix.copy( projectionMatrix );
+
+ }
+
+ this.cameraL.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeLeft );
+ this.cameraR.matrixWorld.copy( camera.matrixWorld ).multiply( _eyeRight );
+
+ }
+
+} );
+
+class Audio extends Object3D {
+
+ constructor( listener ) {
+
+ super();
+
+ this.type = 'Audio';
+
+ this.listener = listener;
+ this.context = listener.context;
+
+ this.gain = this.context.createGain();
+ this.gain.connect( listener.getInput() );
+
+ this.autoplay = false;
+
+ this.buffer = null;
+ this.detune = 0;
+ this.loop = false;
+ this.loopStart = 0;
+ this.loopEnd = 0;
+ this.offset = 0;
+ this.duration = undefined;
+ this.playbackRate = 1;
+ this.isPlaying = false;
+ this.hasPlaybackControl = true;
+ this.source = null;
+ this.sourceType = 'empty';
+
+ this._startedAt = 0;
+ this._progress = 0;
+ this._connected = false;
+
+ this.filters = [];
+
+ }
+
+ getOutput() {
+
+ return this.gain;
+
+ }
+
+ setNodeSource( audioNode ) {
+
+ this.hasPlaybackControl = false;
+ this.sourceType = 'audioNode';
+ this.source = audioNode;
+ this.connect();
+
+ return this;
+
+ }
+
+ setMediaElementSource( mediaElement ) {
+
+ this.hasPlaybackControl = false;
+ this.sourceType = 'mediaNode';
+ this.source = this.context.createMediaElementSource( mediaElement );
+ this.connect();
+
+ return this;
+
+ }
+
+ setMediaStreamSource( mediaStream ) {
+
+ this.hasPlaybackControl = false;
+ this.sourceType = 'mediaStreamNode';
+ this.source = this.context.createMediaStreamSource( mediaStream );
+ this.connect();
+
+ return this;
+
+ }
+
+ setBuffer( audioBuffer ) {
+
+ this.buffer = audioBuffer;
+ this.sourceType = 'buffer';
+
+ if ( this.autoplay ) this.play();
+
+ return this;
+
+ }
+
+ play( delay = 0 ) {
+
+ if ( this.isPlaying === true ) {
+
+ console.warn( 'THREE.Audio: Audio is already playing.' );
+ return;
+
+ }
+
+ if ( this.hasPlaybackControl === false ) {
+
+ console.warn( 'THREE.Audio: this Audio has no playback control.' );
+ return;
+
+ }
+
+ this._startedAt = this.context.currentTime + delay;
+
+ const source = this.context.createBufferSource();
+ source.buffer = this.buffer;
+ source.loop = this.loop;
+ source.loopStart = this.loopStart;
+ source.loopEnd = this.loopEnd;
+ source.onended = this.onEnded.bind( this );
+ source.start( this._startedAt, this._progress + this.offset, this.duration );
+
+ this.isPlaying = true;
+
+ this.source = source;
+
+ this.setDetune( this.detune );
+ this.setPlaybackRate( this.playbackRate );
+
+ return this.connect();
+
+ }
+
+ pause() {
+
+ if ( this.hasPlaybackControl === false ) {
+
+ console.warn( 'THREE.Audio: this Audio has no playback control.' );
+ return;
+
+ }
+
+ if ( this.isPlaying === true ) {
+
+ // update current progress
+
+ this._progress += Math.max( this.context.currentTime - this._startedAt, 0 ) * this.playbackRate;
+
+ if ( this.loop === true ) {
+
+ // ensure _progress does not exceed duration with looped audios
+
+ this._progress = this._progress % ( this.duration || this.buffer.duration );
+
+ }
+
+ this.source.stop();
+ this.source.onended = null;
+
+ this.isPlaying = false;
+
+ }
+
+ return this;
+
+ }
+
+ stop() {
+
+ if ( this.hasPlaybackControl === false ) {
+
+ console.warn( 'THREE.Audio: this Audio has no playback control.' );
+ return;
+
+ }
+
+ this._progress = 0;
+
+ this.source.stop();
+ this.source.onended = null;
+ this.isPlaying = false;
+
+ return this;
+
+ }
+
+ connect() {
+
+ if ( this.filters.length > 0 ) {
+
+ this.source.connect( this.filters[ 0 ] );
+
+ for ( let i = 1, l = this.filters.length; i < l; i ++ ) {
+
+ this.filters[ i - 1 ].connect( this.filters[ i ] );
+
+ }
+
+ this.filters[ this.filters.length - 1 ].connect( this.getOutput() );
+
+ } else {
+
+ this.source.connect( this.getOutput() );
+
+ }
+
+ this._connected = true;
+
+ return this;
+
+ }
+
+ disconnect() {
+
+ if ( this.filters.length > 0 ) {
+
+ this.source.disconnect( this.filters[ 0 ] );
+
+ for ( let i = 1, l = this.filters.length; i < l; i ++ ) {
+
+ this.filters[ i - 1 ].disconnect( this.filters[ i ] );
+
+ }
+
+ this.filters[ this.filters.length - 1 ].disconnect( this.getOutput() );
+
+ } else {
+
+ this.source.disconnect( this.getOutput() );
+
+ }
+
+ this._connected = false;
+
+ return this;
+
+ }
+
+ getFilters() {
+
+ return this.filters;
+
+ }
+
+ setFilters( value ) {
+
+ if ( ! value ) value = [];
+
+ if ( this._connected === true ) {
+
+ this.disconnect();
+ this.filters = value.slice();
+ this.connect();
+
+ } else {
+
+ this.filters = value.slice();
+
+ }
+
+ return this;
+
+ }
+
+ setDetune( value ) {
+
+ this.detune = value;
+
+ if ( this.source.detune === undefined ) return; // only set detune when available
+
+ if ( this.isPlaying === true ) {
+
+ this.source.detune.setTargetAtTime( this.detune, this.context.currentTime, 0.01 );
+
+ }
+
+ return this;
+
+ }
+
+ getDetune() {
+
+ return this.detune;
+
+ }
+
+ getFilter() {
+
+ return this.getFilters()[ 0 ];
+
+ }
+
+ setFilter( filter ) {
+
+ return this.setFilters( filter ? [ filter ] : [] );
+
+ }
+
+ setPlaybackRate( value ) {
+
+ if ( this.hasPlaybackControl === false ) {
+
+ console.warn( 'THREE.Audio: this Audio has no playback control.' );
+ return;
+
+ }
+
+ this.playbackRate = value;
+
+ if ( this.isPlaying === true ) {
+
+ this.source.playbackRate.setTargetAtTime( this.playbackRate, this.context.currentTime, 0.01 );
+
+ }
+
+ return this;
+
+ }
+
+ getPlaybackRate() {
+
+ return this.playbackRate;
+
+ }
+
+ onEnded() {
+
+ this.isPlaying = false;
+
+ }
+
+ getLoop() {
+
+ if ( this.hasPlaybackControl === false ) {
+
+ console.warn( 'THREE.Audio: this Audio has no playback control.' );
+ return false;
+
+ }
+
+ return this.loop;
+
+ }
+
+ setLoop( value ) {
+
+ if ( this.hasPlaybackControl === false ) {
+
+ console.warn( 'THREE.Audio: this Audio has no playback control.' );
+ return;
+
+ }
+
+ this.loop = value;
+
+ if ( this.isPlaying === true ) {
+
+ this.source.loop = this.loop;
+
+ }
+
+ return this;
+
+ }
+
+ setLoopStart( value ) {
+
+ this.loopStart = value;
+
+ return this;
+
+ }
+
+ setLoopEnd( value ) {
+
+ this.loopEnd = value;
+
+ return this;
+
+ }
+
+ getVolume() {
+
+ return this.gain.gain.value;
+
+ }
+
+ setVolume( value ) {
+
+ this.gain.gain.setTargetAtTime( value, this.context.currentTime, 0.01 );
+
+ return this;
+
+ }
+
+}
+
+function PropertyMixer( binding, typeName, valueSize ) {
+
+ this.binding = binding;
+ this.valueSize = valueSize;
+
+ let mixFunction,
+ mixFunctionAdditive,
+ setIdentity;
+
+ // buffer layout: [ incoming | accu0 | accu1 | orig | addAccu | (optional work) ]
+ //
+ // interpolators can use .buffer as their .result
+ // the data then goes to 'incoming'
+ //
+ // 'accu0' and 'accu1' are used frame-interleaved for
+ // the cumulative result and are compared to detect
+ // changes
+ //
+ // 'orig' stores the original state of the property
+ //
+ // 'add' is used for additive cumulative results
+ //
+ // 'work' is optional and is only present for quaternion types. It is used
+ // to store intermediate quaternion multiplication results
+
+ switch ( typeName ) {
+
+ case 'quaternion':
+ mixFunction = this._slerp;
+ mixFunctionAdditive = this._slerpAdditive;
+ setIdentity = this._setAdditiveIdentityQuaternion;
+
+ this.buffer = new Float64Array( valueSize * 6 );
+ this._workIndex = 5;
+ break;
+
+ case 'string':
+ case 'bool':
+ mixFunction = this._select;
+
+ // Use the regular mix function and for additive on these types,
+ // additive is not relevant for non-numeric types
+ mixFunctionAdditive = this._select;
+
+ setIdentity = this._setAdditiveIdentityOther;
+
+ this.buffer = new Array( valueSize * 5 );
+ break;
+
+ default:
+ mixFunction = this._lerp;
+ mixFunctionAdditive = this._lerpAdditive;
+ setIdentity = this._setAdditiveIdentityNumeric;
+
+ this.buffer = new Float64Array( valueSize * 5 );
+
+ }
+
+ this._mixBufferRegion = mixFunction;
+ this._mixBufferRegionAdditive = mixFunctionAdditive;
+ this._setIdentity = setIdentity;
+ this._origIndex = 3;
+ this._addIndex = 4;
+
+ this.cumulativeWeight = 0;
+ this.cumulativeWeightAdditive = 0;
+
+ this.useCount = 0;
+ this.referenceCount = 0;
+
+}
+
+Object.assign( PropertyMixer.prototype, {
+
+ // accumulate data in the 'incoming' region into 'accu<i>'
+ accumulate: function ( accuIndex, weight ) {
+
+ // note: happily accumulating nothing when weight = 0, the caller knows
+ // the weight and shouldn't have made the call in the first place
+
+ const buffer = this.buffer,
+ stride = this.valueSize,
+ offset = accuIndex * stride + stride;
+
+ let currentWeight = this.cumulativeWeight;
+
+ if ( currentWeight === 0 ) {
+
+ // accuN := incoming * weight
+
+ for ( let i = 0; i !== stride; ++ i ) {
+
+ buffer[ offset + i ] = buffer[ i ];
+
+ }
+
+ currentWeight = weight;
+
+ } else {
+
+ // accuN := accuN + incoming * weight
+
+ currentWeight += weight;
+ const mix = weight / currentWeight;
+ this._mixBufferRegion( buffer, offset, 0, mix, stride );
+
+ }
+
+ this.cumulativeWeight = currentWeight;
+
+ },
+
+ // accumulate data in the 'incoming' region into 'add'
+ accumulateAdditive: function ( weight ) {
+
+ const buffer = this.buffer,
+ stride = this.valueSize,
+ offset = stride * this._addIndex;
+
+ if ( this.cumulativeWeightAdditive === 0 ) {
+
+ // add = identity
+
+ this._setIdentity();
+
+ }
+
+ // add := add + incoming * weight
+
+ this._mixBufferRegionAdditive( buffer, offset, 0, weight, stride );
+ this.cumulativeWeightAdditive += weight;
+
+ },
+
+ // apply the state of 'accu<i>' to the binding when accus differ
+ apply: function ( accuIndex ) {
+
+ const stride = this.valueSize,
+ buffer = this.buffer,
+ offset = accuIndex * stride + stride,
+
+ weight = this.cumulativeWeight,
+ weightAdditive = this.cumulativeWeightAdditive,
+
+ binding = this.binding;
+
+ this.cumulativeWeight = 0;
+ this.cumulativeWeightAdditive = 0;
+
+ if ( weight < 1 ) {
+
+ // accuN := accuN + original * ( 1 - cumulativeWeight )
+
+ const originalValueOffset = stride * this._origIndex;
+
+ this._mixBufferRegion(
+ buffer, offset, originalValueOffset, 1 - weight, stride );
+
+ }
+
+ if ( weightAdditive > 0 ) {
+
+ // accuN := accuN + additive accuN
+
+ this._mixBufferRegionAdditive( buffer, offset, this._addIndex * stride, 1, stride );
+
+ }
+
+ for ( let i = stride, e = stride + stride; i !== e; ++ i ) {
+
+ if ( buffer[ i ] !== buffer[ i + stride ] ) {
+
+ // value has changed -> update scene graph
+
+ binding.setValue( buffer, offset );
+ break;
+
+ }
+
+ }
+
+ },
+
+ // remember the state of the bound property and copy it to both accus
+ saveOriginalState: function () {
+
+ const binding = this.binding;
+
+ const buffer = this.buffer,
+ stride = this.valueSize,
+
+ originalValueOffset = stride * this._origIndex;
+
+ binding.getValue( buffer, originalValueOffset );
+
+ // accu[0..1] := orig -- initially detect changes against the original
+ for ( let i = stride, e = originalValueOffset; i !== e; ++ i ) {
+
+ buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ];
+
+ }
+
+ // Add to identity for additive
+ this._setIdentity();
+
+ this.cumulativeWeight = 0;
+ this.cumulativeWeightAdditive = 0;
+
+ },
+
+ // apply the state previously taken via 'saveOriginalState' to the binding
+ restoreOriginalState: function () {
+
+ const originalValueOffset = this.valueSize * 3;
+ this.binding.setValue( this.buffer, originalValueOffset );
+
+ },
+
+ _setAdditiveIdentityNumeric: function () {
+
+ const startIndex = this._addIndex * this.valueSize;
+ const endIndex = startIndex + this.valueSize;
+
+ for ( let i = startIndex; i < endIndex; i ++ ) {
+
+ this.buffer[ i ] = 0;
+
+ }
+
+ },
+
+ _setAdditiveIdentityQuaternion: function () {
+
+ this._setAdditiveIdentityNumeric();
+ this.buffer[ this._addIndex * this.valueSize + 3 ] = 1;
+
+ },
+
+ _setAdditiveIdentityOther: function () {
+
+ const startIndex = this._origIndex * this.valueSize;
+ const targetIndex = this._addIndex * this.valueSize;
+
+ for ( let i = 0; i < this.valueSize; i ++ ) {
+
+ this.buffer[ targetIndex + i ] = this.buffer[ startIndex + i ];
+
+ }
+
+ },
+
+
+ // mix functions
+
+ _select: function ( buffer, dstOffset, srcOffset, t, stride ) {
+
+ if ( t >= 0.5 ) {
+
+ for ( let i = 0; i !== stride; ++ i ) {
+
+ buffer[ dstOffset + i ] = buffer[ srcOffset + i ];
+
+ }
+
+ }
+
+ },
+
+ _slerp: function ( buffer, dstOffset, srcOffset, t ) {
+
+ Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t );
+
+ },
+
+ _slerpAdditive: function ( buffer, dstOffset, srcOffset, t, stride ) {
+
+ const workOffset = this._workIndex * stride;
+
+ // Store result in intermediate buffer offset
+ Quaternion.multiplyQuaternionsFlat( buffer, workOffset, buffer, dstOffset, buffer, srcOffset );
+
+ // Slerp to the intermediate result
+ Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, workOffset, t );
+
+ },
+
+ _lerp: function ( buffer, dstOffset, srcOffset, t, stride ) {
+
+ const s = 1 - t;
+
+ for ( let i = 0; i !== stride; ++ i ) {
+
+ const j = dstOffset + i;
+
+ buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t;
+
+ }
+
+ },
+
+ _lerpAdditive: function ( buffer, dstOffset, srcOffset, t, stride ) {
+
+ for ( let i = 0; i !== stride; ++ i ) {
+
+ const j = dstOffset + i;
+
+ buffer[ j ] = buffer[ j ] + buffer[ srcOffset + i ] * t;
+
+ }
+
+ }
+
+} );
+
+// Characters [].:/ are reserved for track binding syntax.
+const _RESERVED_CHARS_RE = '\\[\\]\\.:\\/';
+const _reservedRe = new RegExp( '[' + _RESERVED_CHARS_RE + ']', 'g' );
+
+// Attempts to allow node names from any language. ES5's `\w` regexp matches
+// only latin characters, and the unicode \p{L} is not yet supported. So
+// instead, we exclude reserved characters and match everything else.
+const _wordChar = '[^' + _RESERVED_CHARS_RE + ']';
+const _wordCharOrDot = '[^' + _RESERVED_CHARS_RE.replace( '\\.', '' ) + ']';
+
+// Parent directories, delimited by '/' or ':'. Currently unused, but must
+// be matched to parse the rest of the track name.
+const _directoryRe = /((?:WC+[\/:])*)/.source.replace( 'WC', _wordChar );
+
+// Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'.
+const _nodeRe = /(WCOD+)?/.source.replace( 'WCOD', _wordCharOrDot );
+
+// Object on target node, and accessor. May not contain reserved
+// characters. Accessor may contain any character except closing bracket.
+const _objectRe = /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', _wordChar );
+
+// Property and accessor. May not contain reserved characters. Accessor may
+// contain any non-bracket characters.
+const _propertyRe = /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', _wordChar );
+
+const _trackRe = new RegExp( ''
+ + '^'
+ + _directoryRe
+ + _nodeRe
+ + _objectRe
+ + _propertyRe
+ + '$'
+);
+
+const _supportedObjectNames = [ 'material', 'materials', 'bones' ];
+
+function Composite( targetGroup, path, optionalParsedPath ) {
+
+ const parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path );
+
+ this._targetGroup = targetGroup;
+ this._bindings = targetGroup.subscribe_( path, parsedPath );
+
+}
+
+Object.assign( Composite.prototype, {
+
+ getValue: function ( array, offset ) {
+
+ this.bind(); // bind all binding
+
+ const firstValidIndex = this._targetGroup.nCachedObjects_,
+ binding = this._bindings[ firstValidIndex ];
+
+ // and only call .getValue on the first
+ if ( binding !== undefined ) binding.getValue( array, offset );
+
+ },
+
+ setValue: function ( array, offset ) {
+
+ const bindings = this._bindings;
+
+ for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) {
+
+ bindings[ i ].setValue( array, offset );
+
+ }
+
+ },
+
+ bind: function () {
+
+ const bindings = this._bindings;
+
+ for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) {
+
+ bindings[ i ].bind();
+
+ }
+
+ },
+
+ unbind: function () {
+
+ const bindings = this._bindings;
+
+ for ( let i = this._targetGroup.nCachedObjects_, n = bindings.length; i !== n; ++ i ) {
+
+ bindings[ i ].unbind();
+
+ }
+
+ }
+
+} );
+
+
+function PropertyBinding( rootNode, path, parsedPath ) {
+
+ this.path = path;
+ this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );
+
+ this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode;
+
+ this.rootNode = rootNode;
+
+}
+
+Object.assign( PropertyBinding, {
+
+ Composite: Composite,
+
+ create: function ( root, path, parsedPath ) {
+
+ if ( ! ( root && root.isAnimationObjectGroup ) ) {
+
+ return new PropertyBinding( root, path, parsedPath );
+
+ } else {
+
+ return new PropertyBinding.Composite( root, path, parsedPath );
+
+ }
+
+ },
+
+ /**
+ * Replaces spaces with underscores and removes unsupported characters from
+ * node names, to ensure compatibility with parseTrackName().
+ *
+ * @param {string} name Node name to be sanitized.
+ * @return {string}
+ */
+ sanitizeNodeName: function ( name ) {
+
+ return name.replace( /\s/g, '_' ).replace( _reservedRe, '' );
+
+ },
+
+ parseTrackName: function ( trackName ) {
+
+ const matches = _trackRe.exec( trackName );
+
+ if ( ! matches ) {
+
+ throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName );
+
+ }
+
+ const results = {
+ // directoryName: matches[ 1 ], // (tschw) currently unused
+ nodeName: matches[ 2 ],
+ objectName: matches[ 3 ],
+ objectIndex: matches[ 4 ],
+ propertyName: matches[ 5 ], // required
+ propertyIndex: matches[ 6 ]
+ };
+
+ const lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' );
+
+ if ( lastDot !== undefined && lastDot !== - 1 ) {
+
+ const objectName = results.nodeName.substring( lastDot + 1 );
+
+ // Object names must be checked against an allowlist. Otherwise, there
+ // is no way to parse 'foo.bar.baz': 'baz' must be a property, but
+ // 'bar' could be the objectName, or part of a nodeName (which can
+ // include '.' characters).
+ if ( _supportedObjectNames.indexOf( objectName ) !== - 1 ) {
+
+ results.nodeName = results.nodeName.substring( 0, lastDot );
+ results.objectName = objectName;
+
+ }
+
+ }
+
+ if ( results.propertyName === null || results.propertyName.length === 0 ) {
+
+ throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName );
+
+ }
+
+ return results;
+
+ },
+
+ findNode: function ( root, nodeName ) {
+
+ if ( ! nodeName || nodeName === '' || nodeName === '.' || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) {
+
+ return root;
+
+ }
+
+ // search into skeleton bones.
+ if ( root.skeleton ) {
+
+ const bone = root.skeleton.getBoneByName( nodeName );
+
+ if ( bone !== undefined ) {
+
+ return bone;
+
+ }
+
+ }
+
+ // search into node subtree.
+ if ( root.children ) {
+
+ const searchNodeSubtree = function ( children ) {
+
+ for ( let i = 0; i < children.length; i ++ ) {
+
+ const childNode = children[ i ];
+
+ if ( childNode.name === nodeName || childNode.uuid === nodeName ) {
+
+ return childNode;
+
+ }
+
+ const result = searchNodeSubtree( childNode.children );
+
+ if ( result ) return result;
+
+ }
+
+ return null;
+
+ };
+
+ const subTreeNode = searchNodeSubtree( root.children );
+
+ if ( subTreeNode ) {
+
+ return subTreeNode;
+
+ }
+
+ }
+
+ return null;
+
+ }
+
+} );
+
+Object.assign( PropertyBinding.prototype, { // prototype, continued
+
+ // these are used to "bind" a nonexistent property
+ _getValue_unavailable: function () {},
+ _setValue_unavailable: function () {},
+
+ BindingType: {
+ Direct: 0,
+ EntireArray: 1,
+ ArrayElement: 2,
+ HasFromToArray: 3
+ },
+
+ Versioning: {
+ None: 0,
+ NeedsUpdate: 1,
+ MatrixWorldNeedsUpdate: 2
+ },
+
+ GetterByBindingType: [
+
+ function getValue_direct( buffer, offset ) {
+
+ buffer[ offset ] = this.node[ this.propertyName ];
+
+ },
+
+ function getValue_array( buffer, offset ) {
+
+ const source = this.resolvedProperty;
+
+ for ( let i = 0, n = source.length; i !== n; ++ i ) {
+
+ buffer[ offset ++ ] = source[ i ];
+
+ }
+
+ },
+
+ function getValue_arrayElement( buffer, offset ) {
+
+ buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
+
+ },
+
+ function getValue_toArray( buffer, offset ) {
+
+ this.resolvedProperty.toArray( buffer, offset );
+
+ }
+
+ ],
+
+ SetterByBindingTypeAndVersioning: [
+
+ [
+ // Direct
+
+ function setValue_direct( buffer, offset ) {
+
+ this.targetObject[ this.propertyName ] = buffer[ offset ];
+
+ },
+
+ function setValue_direct_setNeedsUpdate( buffer, offset ) {
+
+ this.targetObject[ this.propertyName ] = buffer[ offset ];
+ this.targetObject.needsUpdate = true;
+
+ },
+
+ function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {
+
+ this.targetObject[ this.propertyName ] = buffer[ offset ];
+ this.targetObject.matrixWorldNeedsUpdate = true;
+
+ }
+
+ ], [
+
+ // EntireArray
+
+ function setValue_array( buffer, offset ) {
+
+ const dest = this.resolvedProperty;
+
+ for ( let i = 0, n = dest.length; i !== n; ++ i ) {
+
+ dest[ i ] = buffer[ offset ++ ];
+
+ }
+
+ },
+
+ function setValue_array_setNeedsUpdate( buffer, offset ) {
+
+ const dest = this.resolvedProperty;
+
+ for ( let i = 0, n = dest.length; i !== n; ++ i ) {
+
+ dest[ i ] = buffer[ offset ++ ];
+
+ }
+
+ this.targetObject.needsUpdate = true;
+
+ },
+
+ function setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) {
+
+ const dest = this.resolvedProperty;
+
+ for ( let i = 0, n = dest.length; i !== n; ++ i ) {
+
+ dest[ i ] = buffer[ offset ++ ];
+
+ }
+
+ this.targetObject.matrixWorldNeedsUpdate = true;
+
+ }
+
+ ], [
+
+ // ArrayElement
+
+ function setValue_arrayElement( buffer, offset ) {
+
+ this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
+
+ },
+
+ function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
+
+ this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
+ this.targetObject.needsUpdate = true;
+
+ },
+
+ function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
+
+ this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
+ this.targetObject.matrixWorldNeedsUpdate = true;
+
+ }
+
+ ], [
+
+ // HasToFromArray
+
+ function setValue_fromArray( buffer, offset ) {
+
+ this.resolvedProperty.fromArray( buffer, offset );
+
+ },
+
+ function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
+
+ this.resolvedProperty.fromArray( buffer, offset );
+ this.targetObject.needsUpdate = true;
+
+ },
+
+ function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {
+
+ this.resolvedProperty.fromArray( buffer, offset );
+ this.targetObject.matrixWorldNeedsUpdate = true;
+
+ }
+
+ ]
+
+ ],
+
+ getValue: function getValue_unbound( targetArray, offset ) {
+
+ this.bind();
+ this.getValue( targetArray, offset );
+
+ // Note: This class uses a State pattern on a per-method basis:
+ // 'bind' sets 'this.getValue' / 'setValue' and shadows the
+ // prototype version of these methods with one that represents
+ // the bound state. When the property is not found, the methods
+ // become no-ops.
+
+ },
+
+ setValue: function getValue_unbound( sourceArray, offset ) {
+
+ this.bind();
+ this.setValue( sourceArray, offset );
+
+ },
+
+ // create getter / setter pair for a property in the scene graph
+ bind: function () {
+
+ let targetObject = this.node;
+ const parsedPath = this.parsedPath;
+
+ const objectName = parsedPath.objectName;
+ const propertyName = parsedPath.propertyName;
+ let propertyIndex = parsedPath.propertyIndex;
+
+ if ( ! targetObject ) {
+
+ targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ) || this.rootNode;
+
+ this.node = targetObject;
+
+ }
+
+ // set fail state so we can just 'return' on error
+ this.getValue = this._getValue_unavailable;
+ this.setValue = this._setValue_unavailable;
+
+ // ensure there is a value node
+ if ( ! targetObject ) {
+
+ console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.' );
+ return;
+
+ }
+
+ if ( objectName ) {
+
+ let objectIndex = parsedPath.objectIndex;
+
+ // special cases were we need to reach deeper into the hierarchy to get the face materials....
+ switch ( objectName ) {
+
+ case 'materials':
+
+ if ( ! targetObject.material ) {
+
+ console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this );
+ return;
+
+ }
+
+ if ( ! targetObject.material.materials ) {
+
+ console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this );
+ return;
+
+ }
+
+ targetObject = targetObject.material.materials;
+
+ break;
+
+ case 'bones':
+
+ if ( ! targetObject.skeleton ) {
+
+ console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this );
+ return;
+
+ }
+
+ // potential future optimization: skip this if propertyIndex is already an integer
+ // and convert the integer string to a true integer.
+
+ targetObject = targetObject.skeleton.bones;
+
+ // support resolving morphTarget names into indices.
+ for ( let i = 0; i < targetObject.length; i ++ ) {
+
+ if ( targetObject[ i ].name === objectIndex ) {
+
+ objectIndex = i;
+ break;
+
+ }
+
+ }
+
+ break;
+
+ default:
+
+ if ( targetObject[ objectName ] === undefined ) {
+
+ console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this );
+ return;
+
+ }
+
+ targetObject = targetObject[ objectName ];
+
+ }
+
+
+ if ( objectIndex !== undefined ) {
+
+ if ( targetObject[ objectIndex ] === undefined ) {
+
+ console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject );
+ return;
+
+ }
+
+ targetObject = targetObject[ objectIndex ];
+
+ }
+
+ }
+
+ // resolve property
+ const nodeProperty = targetObject[ propertyName ];
+
+ if ( nodeProperty === undefined ) {
+
+ const nodeName = parsedPath.nodeName;
+
+ console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName +
+ '.' + propertyName + ' but it wasn\'t found.', targetObject );
+ return;
+
+ }
+
+ // determine versioning scheme
+ let versioning = this.Versioning.None;
+
+ this.targetObject = targetObject;
+
+ if ( targetObject.needsUpdate !== undefined ) { // material
+
+ versioning = this.Versioning.NeedsUpdate;
+
+ } else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
+
+ versioning = this.Versioning.MatrixWorldNeedsUpdate;
+
+ }
+
+ // determine how the property gets bound
+ let bindingType = this.BindingType.Direct;
+
+ if ( propertyIndex !== undefined ) {
+
+ // access a sub element of the property array (only primitives are supported right now)
+
+ if ( propertyName === 'morphTargetInfluences' ) {
+
+ // potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
+
+ // support resolving morphTarget names into indices.
+ if ( ! targetObject.geometry ) {
+
+ console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this );
+ return;
+
+ }
+
+ if ( targetObject.geometry.isBufferGeometry ) {
+
+ if ( ! targetObject.geometry.morphAttributes ) {
+
+ console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this );
+ return;
+
+ }
+
+ if ( targetObject.morphTargetDictionary[ propertyIndex ] !== undefined ) {
+
+ propertyIndex = targetObject.morphTargetDictionary[ propertyIndex ];
+
+ }
+
+
+ } else {
+
+ console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences on THREE.Geometry. Use THREE.BufferGeometry instead.', this );
+ return;
+
+ }
+
+ }
+
+ bindingType = this.BindingType.ArrayElement;
+
+ this.resolvedProperty = nodeProperty;
+ this.propertyIndex = propertyIndex;
+
+ } else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {
+
+ // must use copy for Object3D.Euler/Quaternion
+
+ bindingType = this.BindingType.HasFromToArray;
+
+ this.resolvedProperty = nodeProperty;
+
+ } else if ( Array.isArray( nodeProperty ) ) {
+
+ bindingType = this.BindingType.EntireArray;
+
+ this.resolvedProperty = nodeProperty;
+
+ } else {
+
+ this.propertyName = propertyName;
+
+ }
+
+ // select getter / setter
+ this.getValue = this.GetterByBindingType[ bindingType ];
+ this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
+
+ },
+
+ unbind: function () {
+
+ this.node = null;
+
+ // back to the prototype version of getValue / setValue
+ // note: avoiding to mutate the shape of 'this' via 'delete'
+ this.getValue = this._getValue_unbound;
+ this.setValue = this._setValue_unbound;
+
+ }
+
+} );
+
+// DECLARE ALIAS AFTER assign prototype
+Object.assign( PropertyBinding.prototype, {
+
+ // initial state of these methods that calls 'bind'
+ _getValue_unbound: PropertyBinding.prototype.getValue,
+ _setValue_unbound: PropertyBinding.prototype.setValue,
+
+} );
+
+/**
+ *
+ * A group of objects that receives a shared animation state.
+ *
+ * Usage:
+ *
+ * - Add objects you would otherwise pass as 'root' to the
+ * constructor or the .clipAction method of AnimationMixer.
+ *
+ * - Instead pass this object as 'root'.
+ *
+ * - You can also add and remove objects later when the mixer
+ * is running.
+ *
+ * Note:
+ *
+ * Objects of this class appear as one object to the mixer,
+ * so cache control of the individual objects must be done
+ * on the group.
+ *
+ * Limitation:
+ *
+ * - The animated properties must be compatible among the
+ * all objects in the group.
+ *
+ * - A single property can either be controlled through a
+ * target group or directly, but not both.
+ */
+
+function AnimationObjectGroup() {
+
+ this.uuid = MathUtils.generateUUID();
+
+ // cached objects followed by the active ones
+ this._objects = Array.prototype.slice.call( arguments );
+
+ this.nCachedObjects_ = 0; // threshold
+ // note: read by PropertyBinding.Composite
+
+ const indices = {};
+ this._indicesByUUID = indices; // for bookkeeping
+
+ for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
+
+ indices[ arguments[ i ].uuid ] = i;
+
+ }
+
+ this._paths = []; // inside: string
+ this._parsedPaths = []; // inside: { we don't care, here }
+ this._bindings = []; // inside: Array< PropertyBinding >
+ this._bindingsIndicesByPath = {}; // inside: indices in these arrays
+
+ const scope = this;
+
+ this.stats = {
+
+ objects: {
+ get total() {
+
+ return scope._objects.length;
+
+ },
+ get inUse() {
+
+ return this.total - scope.nCachedObjects_;
+
+ }
+ },
+ get bindingsPerObject() {
+
+ return scope._bindings.length;
+
+ }
+
+ };
+
+}
+
+Object.assign( AnimationObjectGroup.prototype, {
+
+ isAnimationObjectGroup: true,
+
+ add: function () {
+
+ const objects = this._objects,
+ indicesByUUID = this._indicesByUUID,
+ paths = this._paths,
+ parsedPaths = this._parsedPaths,
+ bindings = this._bindings,
+ nBindings = bindings.length;
+
+ let knownObject = undefined,
+ nObjects = objects.length,
+ nCachedObjects = this.nCachedObjects_;
+
+ for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
+
+ const object = arguments[ i ],
+ uuid = object.uuid;
+ let index = indicesByUUID[ uuid ];
+
+ if ( index === undefined ) {
+
+ // unknown object -> add it to the ACTIVE region
+
+ index = nObjects ++;
+ indicesByUUID[ uuid ] = index;
+ objects.push( object );
+
+ // accounting is done, now do the same for all bindings
+
+ for ( let j = 0, m = nBindings; j !== m; ++ j ) {
+
+ bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) );
+
+ }
+
+ } else if ( index < nCachedObjects ) {
+
+ knownObject = objects[ index ];
+
+ // move existing object to the ACTIVE region
+
+ const firstActiveIndex = -- nCachedObjects,
+ lastCachedObject = objects[ firstActiveIndex ];
+
+ indicesByUUID[ lastCachedObject.uuid ] = index;
+ objects[ index ] = lastCachedObject;
+
+ indicesByUUID[ uuid ] = firstActiveIndex;
+ objects[ firstActiveIndex ] = object;
+
+ // accounting is done, now do the same for all bindings
+
+ for ( let j = 0, m = nBindings; j !== m; ++ j ) {
+
+ const bindingsForPath = bindings[ j ],
+ lastCached = bindingsForPath[ firstActiveIndex ];
+
+ let binding = bindingsForPath[ index ];
+
+ bindingsForPath[ index ] = lastCached;
+
+ if ( binding === undefined ) {
+
+ // since we do not bother to create new bindings
+ // for objects that are cached, the binding may
+ // or may not exist
+
+ binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] );
+
+ }
+
+ bindingsForPath[ firstActiveIndex ] = binding;
+
+ }
+
+ } else if ( objects[ index ] !== knownObject ) {
+
+ console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' +
+ 'detected. Clean the caches or recreate your infrastructure when reloading scenes.' );
+
+ } // else the object is already where we want it to be
+
+ } // for arguments
+
+ this.nCachedObjects_ = nCachedObjects;
+
+ },
+
+ remove: function () {
+
+ const objects = this._objects,
+ indicesByUUID = this._indicesByUUID,
+ bindings = this._bindings,
+ nBindings = bindings.length;
+
+ let nCachedObjects = this.nCachedObjects_;
+
+ for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
+
+ const object = arguments[ i ],
+ uuid = object.uuid,
+ index = indicesByUUID[ uuid ];
+
+ if ( index !== undefined && index >= nCachedObjects ) {
+
+ // move existing object into the CACHED region
+
+ const lastCachedIndex = nCachedObjects ++,
+ firstActiveObject = objects[ lastCachedIndex ];
+
+ indicesByUUID[ firstActiveObject.uuid ] = index;
+ objects[ index ] = firstActiveObject;
+
+ indicesByUUID[ uuid ] = lastCachedIndex;
+ objects[ lastCachedIndex ] = object;
+
+ // accounting is done, now do the same for all bindings
+
+ for ( let j = 0, m = nBindings; j !== m; ++ j ) {
+
+ const bindingsForPath = bindings[ j ],
+ firstActive = bindingsForPath[ lastCachedIndex ],
+ binding = bindingsForPath[ index ];
+
+ bindingsForPath[ index ] = firstActive;
+ bindingsForPath[ lastCachedIndex ] = binding;
+
+ }
+
+ }
+
+ } // for arguments
+
+ this.nCachedObjects_ = nCachedObjects;
+
+ },
+
+ // remove & forget
+ uncache: function () {
+
+ const objects = this._objects,
+ indicesByUUID = this._indicesByUUID,
+ bindings = this._bindings,
+ nBindings = bindings.length;
+
+ let nCachedObjects = this.nCachedObjects_,
+ nObjects = objects.length;
+
+ for ( let i = 0, n = arguments.length; i !== n; ++ i ) {
+
+ const object = arguments[ i ],
+ uuid = object.uuid,
+ index = indicesByUUID[ uuid ];
+
+ if ( index !== undefined ) {
+
+ delete indicesByUUID[ uuid ];
+
+ if ( index < nCachedObjects ) {
+
+ // object is cached, shrink the CACHED region
+
+ const firstActiveIndex = -- nCachedObjects,
+ lastCachedObject = objects[ firstActiveIndex ],
+ lastIndex = -- nObjects,
+ lastObject = objects[ lastIndex ];
+
+ // last cached object takes this object's place
+ indicesByUUID[ lastCachedObject.uuid ] = index;
+ objects[ index ] = lastCachedObject;
+
+ // last object goes to the activated slot and pop
+ indicesByUUID[ lastObject.uuid ] = firstActiveIndex;
+ objects[ firstActiveIndex ] = lastObject;
+ objects.pop();
+
+ // accounting is done, now do the same for all bindings
+
+ for ( let j = 0, m = nBindings; j !== m; ++ j ) {
+
+ const bindingsForPath = bindings[ j ],
+ lastCached = bindingsForPath[ firstActiveIndex ],
+ last = bindingsForPath[ lastIndex ];
+
+ bindingsForPath[ index ] = lastCached;
+ bindingsForPath[ firstActiveIndex ] = last;
+ bindingsForPath.pop();
+
+ }
+
+ } else {
+
+ // object is active, just swap with the last and pop
+
+ const lastIndex = -- nObjects,
+ lastObject = objects[ lastIndex ];
+
+ if ( lastIndex > 0 ) {
+
+ indicesByUUID[ lastObject.uuid ] = index;
+
+ }
+
+ objects[ index ] = lastObject;
+ objects.pop();
+
+ // accounting is done, now do the same for all bindings
+
+ for ( let j = 0, m = nBindings; j !== m; ++ j ) {
+
+ const bindingsForPath = bindings[ j ];
+
+ bindingsForPath[ index ] = bindingsForPath[ lastIndex ];
+ bindingsForPath.pop();
+
+ }
+
+ } // cached or active
+
+ } // if object is known
+
+ } // for arguments
+
+ this.nCachedObjects_ = nCachedObjects;
+
+ },
+
+ // Internal interface used by befriended PropertyBinding.Composite:
+
+ subscribe_: function ( path, parsedPath ) {
+
+ // returns an array of bindings for the given path that is changed
+ // according to the contained objects in the group
+
+ const indicesByPath = this._bindingsIndicesByPath;
+ let index = indicesByPath[ path ];
+ const bindings = this._bindings;
+
+ if ( index !== undefined ) return bindings[ index ];
+
+ const paths = this._paths,
+ parsedPaths = this._parsedPaths,
+ objects = this._objects,
+ nObjects = objects.length,
+ nCachedObjects = this.nCachedObjects_,
+ bindingsForPath = new Array( nObjects );
+
+ index = bindings.length;
+
+ indicesByPath[ path ] = index;
+
+ paths.push( path );
+ parsedPaths.push( parsedPath );
+ bindings.push( bindingsForPath );
+
+ for ( let i = nCachedObjects, n = objects.length; i !== n; ++ i ) {
+
+ const object = objects[ i ];
+ bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath );
+
+ }
+
+ return bindingsForPath;
+
+ },
+
+ unsubscribe_: function ( path ) {
+
+ // tells the group to forget about a property path and no longer
+ // update the array previously obtained with 'subscribe_'
+
+ const indicesByPath = this._bindingsIndicesByPath,
+ index = indicesByPath[ path ];
+
+ if ( index !== undefined ) {
+
+ const paths = this._paths,
+ parsedPaths = this._parsedPaths,
+ bindings = this._bindings,
+ lastBindingsIndex = bindings.length - 1,
+ lastBindings = bindings[ lastBindingsIndex ],
+ lastBindingsPath = path[ lastBindingsIndex ];
+
+ indicesByPath[ lastBindingsPath ] = index;
+
+ bindings[ index ] = lastBindings;
+ bindings.pop();
+
+ parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ];
+ parsedPaths.pop();
+
+ paths[ index ] = paths[ lastBindingsIndex ];
+ paths.pop();
+
+ }
+
+ }
+
+} );
+
+class AnimationAction {
+
+ constructor( mixer, clip, localRoot = null, blendMode = clip.blendMode ) {
+
+ this._mixer = mixer;
+ this._clip = clip;
+ this._localRoot = localRoot;
+ this.blendMode = blendMode;
+
+ const tracks = clip.tracks,
+ nTracks = tracks.length,
+ interpolants = new Array( nTracks );
+
+ const interpolantSettings = {
+ endingStart: ZeroCurvatureEnding,
+ endingEnd: ZeroCurvatureEnding
+ };
+
+ for ( let i = 0; i !== nTracks; ++ i ) {
+
+ const interpolant = tracks[ i ].createInterpolant( null );
+ interpolants[ i ] = interpolant;
+ interpolant.settings = interpolantSettings;
+
+ }
+
+ this._interpolantSettings = interpolantSettings;
+
+ this._interpolants = interpolants; // bound by the mixer
+
+ // inside: PropertyMixer (managed by the mixer)
+ this._propertyBindings = new Array( nTracks );
+
+ this._cacheIndex = null; // for the memory manager
+ this._byClipCacheIndex = null; // for the memory manager
+
+ this._timeScaleInterpolant = null;
+ this._weightInterpolant = null;
+
+ this.loop = LoopRepeat;
+ this._loopCount = - 1;
+
+ // global mixer time when the action is to be started
+ // it's set back to 'null' upon start of the action
+ this._startTime = null;
+
+ // scaled local time of the action
+ // gets clamped or wrapped to 0..clip.duration according to loop
+ this.time = 0;
+
+ this.timeScale = 1;
+ this._effectiveTimeScale = 1;
+
+ this.weight = 1;
+ this._effectiveWeight = 1;
+
+ this.repetitions = Infinity; // no. of repetitions when looping
+
+ this.paused = false; // true -> zero effective time scale
+ this.enabled = true; // false -> zero effective weight
+
+ this.clampWhenFinished = false;// keep feeding the last frame?
+
+ this.zeroSlopeAtStart = true;// for smooth interpolation w/o separate
+ this.zeroSlopeAtEnd = true;// clips for start, loop and end
+
+ }
+
+ // State & Scheduling
+
+ play() {
+
+ this._mixer._activateAction( this );
+
+ return this;
+
+ }
+
+ stop() {
+
+ this._mixer._deactivateAction( this );
+
+ return this.reset();
+
+ }
+
+ reset() {
+
+ this.paused = false;
+ this.enabled = true;
+
+ this.time = 0; // restart clip
+ this._loopCount = - 1;// forget previous loops
+ this._startTime = null;// forget scheduling
+
+ return this.stopFading().stopWarping();
+
+ }
+
+ isRunning() {
+
+ return this.enabled && ! this.paused && this.timeScale !== 0 &&
+ this._startTime === null && this._mixer._isActiveAction( this );
+
+ }
+
+ // return true when play has been called
+ isScheduled() {
+
+ return this._mixer._isActiveAction( this );
+
+ }
+
+ startAt( time ) {
+
+ this._startTime = time;
+
+ return this;
+
+ }
+
+ setLoop( mode, repetitions ) {
+
+ this.loop = mode;
+ this.repetitions = repetitions;
+
+ return this;
+
+ }
+
+ // Weight
+
+ // set the weight stopping any scheduled fading
+ // although .enabled = false yields an effective weight of zero, this
+ // method does *not* change .enabled, because it would be confusing
+ setEffectiveWeight( weight ) {
+
+ this.weight = weight;
+
+ // note: same logic as when updated at runtime
+ this._effectiveWeight = this.enabled ? weight : 0;
+
+ return this.stopFading();
+
+ }
+
+ // return the weight considering fading and .enabled
+ getEffectiveWeight() {
+
+ return this._effectiveWeight;
+
+ }
+
+ fadeIn( duration ) {
+
+ return this._scheduleFading( duration, 0, 1 );
+
+ }
+
+ fadeOut( duration ) {
+
+ return this._scheduleFading( duration, 1, 0 );
+
+ }
+
+ crossFadeFrom( fadeOutAction, duration, warp ) {
+
+ fadeOutAction.fadeOut( duration );
+ this.fadeIn( duration );
+
+ if ( warp ) {
+
+ const fadeInDuration = this._clip.duration,
+ fadeOutDuration = fadeOutAction._clip.duration,
+
+ startEndRatio = fadeOutDuration / fadeInDuration,
+ endStartRatio = fadeInDuration / fadeOutDuration;
+
+ fadeOutAction.warp( 1.0, startEndRatio, duration );
+ this.warp( endStartRatio, 1.0, duration );
+
+ }
+
+ return this;
+
+ }
+
+ crossFadeTo( fadeInAction, duration, warp ) {
+
+ return fadeInAction.crossFadeFrom( this, duration, warp );
+
+ }
+
+ stopFading() {
+
+ const weightInterpolant = this._weightInterpolant;
+
+ if ( weightInterpolant !== null ) {
+
+ this._weightInterpolant = null;
+ this._mixer._takeBackControlInterpolant( weightInterpolant );
+
+ }
+
+ return this;
+
+ }
+
+ // Time Scale Control
+
+ // set the time scale stopping any scheduled warping
+ // although .paused = true yields an effective time scale of zero, this
+ // method does *not* change .paused, because it would be confusing
+ setEffectiveTimeScale( timeScale ) {
+
+ this.timeScale = timeScale;
+ this._effectiveTimeScale = this.paused ? 0 : timeScale;
+
+ return this.stopWarping();
+
+ }
+
+ // return the time scale considering warping and .paused
+ getEffectiveTimeScale() {
+
+ return this._effectiveTimeScale;
+
+ }
+
+ setDuration( duration ) {
+
+ this.timeScale = this._clip.duration / duration;
+
+ return this.stopWarping();
+
+ }
+
+ syncWith( action ) {
+
+ this.time = action.time;
+ this.timeScale = action.timeScale;
+
+ return this.stopWarping();
+
+ }
+
+ halt( duration ) {
+
+ return this.warp( this._effectiveTimeScale, 0, duration );
+
+ }
+
+ warp( startTimeScale, endTimeScale, duration ) {
+
+ const mixer = this._mixer,
+ now = mixer.time,
+ timeScale = this.timeScale;
+
+ let interpolant = this._timeScaleInterpolant;
+
+ if ( interpolant === null ) {
+
+ interpolant = mixer._lendControlInterpolant();
+ this._timeScaleInterpolant = interpolant;
+
+ }
+
+ const times = interpolant.parameterPositions,
+ values = interpolant.sampleValues;
+
+ times[ 0 ] = now;
+ times[ 1 ] = now + duration;
+
+ values[ 0 ] = startTimeScale / timeScale;
+ values[ 1 ] = endTimeScale / timeScale;
+
+ return this;
+
+ }
+
+ stopWarping() {
+
+ const timeScaleInterpolant = this._timeScaleInterpolant;
+
+ if ( timeScaleInterpolant !== null ) {
+
+ this._timeScaleInterpolant = null;
+ this._mixer._takeBackControlInterpolant( timeScaleInterpolant );
+
+ }
+
+ return this;
+
+ }
+
+ // Object Accessors
+
+ getMixer() {
+
+ return this._mixer;
+
+ }
+
+ getClip() {
+
+ return this._clip;
+
+ }
+
+ getRoot() {
+
+ return this._localRoot || this._mixer._root;
+
+ }
+
+ // Interna
+
+ _update( time, deltaTime, timeDirection, accuIndex ) {
+
+ // called by the mixer
+
+ if ( ! this.enabled ) {
+
+ // call ._updateWeight() to update ._effectiveWeight
+
+ this._updateWeight( time );
+ return;
+
+ }
+
+ const startTime = this._startTime;
+
+ if ( startTime !== null ) {
+
+ // check for scheduled start of action
+
+ const timeRunning = ( time - startTime ) * timeDirection;
+ if ( timeRunning < 0 || timeDirection === 0 ) {
+
+ return; // yet to come / don't decide when delta = 0
+
+ }
+
+ // start
+
+ this._startTime = null; // unschedule
+ deltaTime = timeDirection * timeRunning;
+
+ }
+
+ // apply time scale and advance time
+
+ deltaTime *= this._updateTimeScale( time );
+ const clipTime = this._updateTime( deltaTime );
+
+ // note: _updateTime may disable the action resulting in
+ // an effective weight of 0
+
+ const weight = this._updateWeight( time );
+
+ if ( weight > 0 ) {
+
+ const interpolants = this._interpolants;
+ const propertyMixers = this._propertyBindings;
+
+ switch ( this.blendMode ) {
+
+ case AdditiveAnimationBlendMode:
+
+ for ( let j = 0, m = interpolants.length; j !== m; ++ j ) {
+
+ interpolants[ j ].evaluate( clipTime );
+ propertyMixers[ j ].accumulateAdditive( weight );
+
+ }
+
+ break;
+
+ case NormalAnimationBlendMode:
+ default:
+
+ for ( let j = 0, m = interpolants.length; j !== m; ++ j ) {
+
+ interpolants[ j ].evaluate( clipTime );
+ propertyMixers[ j ].accumulate( accuIndex, weight );
+
+ }
+
+ }
+
+ }
+
+ }
+
+ _updateWeight( time ) {
+
+ let weight = 0;
+
+ if ( this.enabled ) {
+
+ weight = this.weight;
+ const interpolant = this._weightInterpolant;
+
+ if ( interpolant !== null ) {
+
+ const interpolantValue = interpolant.evaluate( time )[ 0 ];
+
+ weight *= interpolantValue;
+
+ if ( time > interpolant.parameterPositions[ 1 ] ) {
+
+ this.stopFading();
+
+ if ( interpolantValue === 0 ) {
+
+ // faded out, disable
+ this.enabled = false;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ this._effectiveWeight = weight;
+ return weight;
+
+ }
+
+ _updateTimeScale( time ) {
+
+ let timeScale = 0;
+
+ if ( ! this.paused ) {
+
+ timeScale = this.timeScale;
+
+ const interpolant = this._timeScaleInterpolant;
+
+ if ( interpolant !== null ) {
+
+ const interpolantValue = interpolant.evaluate( time )[ 0 ];
+
+ timeScale *= interpolantValue;
+
+ if ( time > interpolant.parameterPositions[ 1 ] ) {
+
+ this.stopWarping();
+
+ if ( timeScale === 0 ) {
+
+ // motion has halted, pause
+ this.paused = true;
+
+ } else {
+
+ // warp done - apply final time scale
+ this.timeScale = timeScale;
+
+ }
+
+ }
+
+ }
+
+ }
+
+ this._effectiveTimeScale = timeScale;
+ return timeScale;
+
+ }
+
+ _updateTime( deltaTime ) {
+
+ const duration = this._clip.duration;
+ const loop = this.loop;
+
+ let time = this.time + deltaTime;
+ let loopCount = this._loopCount;
+
+ const pingPong = ( loop === LoopPingPong );
+
+ if ( deltaTime === 0 ) {
+
+ if ( loopCount === - 1 ) return time;
+
+ return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time;
+
+ }
+
+ if ( loop === LoopOnce ) {
+
+ if ( loopCount === - 1 ) {
+
+ // just started
+
+ this._loopCount = 0;
+ this._setEndings( true, true, false );
+
+ }
+
+ handle_stop: {
+
+ if ( time >= duration ) {
+
+ time = duration;
+
+ } else if ( time < 0 ) {
+
+ time = 0;
+
+ } else {
+
+ this.time = time;
+
+ break handle_stop;
+
+ }
+
+ if ( this.clampWhenFinished ) this.paused = true;
+ else this.enabled = false;
+
+ this.time = time;
+
+ this._mixer.dispatchEvent( {
+ type: 'finished', action: this,
+ direction: deltaTime < 0 ? - 1 : 1
+ } );
+
+ }
+
+ } else { // repetitive Repeat or PingPong
+
+ if ( loopCount === - 1 ) {
+
+ // just started
+
+ if ( deltaTime >= 0 ) {
+
+ loopCount = 0;
+
+ this._setEndings( true, this.repetitions === 0, pingPong );
+
+ } else {
+
+ // when looping in reverse direction, the initial
+ // transition through zero counts as a repetition,
+ // so leave loopCount at -1
+
+ this._setEndings( this.repetitions === 0, true, pingPong );
+
+ }
+
+ }
+
+ if ( time >= duration || time < 0 ) {
+
+ // wrap around
+
+ const loopDelta = Math.floor( time / duration ); // signed
+ time -= duration * loopDelta;
+
+ loopCount += Math.abs( loopDelta );
+
+ const pending = this.repetitions - loopCount;
+
+ if ( pending <= 0 ) {
+
+ // have to stop (switch state, clamp time, fire event)
+
+ if ( this.clampWhenFinished ) this.paused = true;
+ else this.enabled = false;
+
+ time = deltaTime > 0 ? duration : 0;
+
+ this.time = time;
+
+ this._mixer.dispatchEvent( {
+ type: 'finished', action: this,
+ direction: deltaTime > 0 ? 1 : - 1
+ } );
+
+ } else {
+
+ // keep running
+
+ if ( pending === 1 ) {
+
+ // entering the last round
+
+ const atStart = deltaTime < 0;
+ this._setEndings( atStart, ! atStart, pingPong );
+
+ } else {
+
+ this._setEndings( false, false, pingPong );
+
+ }
+
+ this._loopCount = loopCount;
+
+ this.time = time;
+
+ this._mixer.dispatchEvent( {
+ type: 'loop', action: this, loopDelta: loopDelta
+ } );
+
+ }
+
+ } else {
+
+ this.time = time;
+
+ }
+
+ if ( pingPong && ( loopCount & 1 ) === 1 ) {
+
+ // invert time for the "pong round"
+
+ return duration - time;
+
+ }
+
+ }
+
+ return time;
+
+ }
+
+ _setEndings( atStart, atEnd, pingPong ) {
+
+ const settings = this._interpolantSettings;
+
+ if ( pingPong ) {
+
+ settings.endingStart = ZeroSlopeEnding;
+ settings.endingEnd = ZeroSlopeEnding;
+
+ } else {
+
+ // assuming for LoopOnce atStart == atEnd == true
+
+ if ( atStart ) {
+
+ settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding;
+
+ } else {
+
+ settings.endingStart = WrapAroundEnding;
+
+ }
+
+ if ( atEnd ) {
+
+ settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding;
+
+ } else {
+
+ settings.endingEnd = WrapAroundEnding;
+
+ }
+
+ }
+
+ }
+
+ _scheduleFading( duration, weightNow, weightThen ) {
+
+ const mixer = this._mixer, now = mixer.time;
+ let interpolant = this._weightInterpolant;
+
+ if ( interpolant === null ) {
+
+ interpolant = mixer._lendControlInterpolant();
+ this._weightInterpolant = interpolant;
+
+ }
+
+ const times = interpolant.parameterPositions,
+ values = interpolant.sampleValues;
+
+ times[ 0 ] = now;
+ values[ 0 ] = weightNow;
+ times[ 1 ] = now + duration;
+ values[ 1 ] = weightThen;
+
+ return this;
+
+ }
+
+}
+
+function AnimationMixer( root ) {
+
+ this._root = root;
+ this._initMemoryManager();
+ this._accuIndex = 0;
+
+ this.time = 0;
+
+ this.timeScale = 1.0;
+
+}
+
+AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
+
+ constructor: AnimationMixer,
+
+ _bindAction: function ( action, prototypeAction ) {
+
+ const root = action._localRoot || this._root,
+ tracks = action._clip.tracks,
+ nTracks = tracks.length,
+ bindings = action._propertyBindings,
+ interpolants = action._interpolants,
+ rootUuid = root.uuid,
+ bindingsByRoot = this._bindingsByRootAndName;
+
+ let bindingsByName = bindingsByRoot[ rootUuid ];
+
+ if ( bindingsByName === undefined ) {
+
+ bindingsByName = {};
+ bindingsByRoot[ rootUuid ] = bindingsByName;
+
+ }
+
+ for ( let i = 0; i !== nTracks; ++ i ) {
+
+ const track = tracks[ i ],
+ trackName = track.name;
+
+ let binding = bindingsByName[ trackName ];
+
+ if ( binding !== undefined ) {
+
+ bindings[ i ] = binding;
+
+ } else {
+
+ binding = bindings[ i ];
+
+ if ( binding !== undefined ) {
+
+ // existing binding, make sure the cache knows
+
+ if ( binding._cacheIndex === null ) {
+
+ ++ binding.referenceCount;
+ this._addInactiveBinding( binding, rootUuid, trackName );
+
+ }
+
+ continue;
+
+ }
+
+ const path = prototypeAction && prototypeAction.
+ _propertyBindings[ i ].binding.parsedPath;
+
+ binding = new PropertyMixer(
+ PropertyBinding.create( root, trackName, path ),
+ track.ValueTypeName, track.getValueSize() );
+
+ ++ binding.referenceCount;
+ this._addInactiveBinding( binding, rootUuid, trackName );
+
+ bindings[ i ] = binding;
+
+ }
+
+ interpolants[ i ].resultBuffer = binding.buffer;
+
+ }
+
+ },
+
+ _activateAction: function ( action ) {
+
+ if ( ! this._isActiveAction( action ) ) {
+
+ if ( action._cacheIndex === null ) {
+
+ // this action has been forgotten by the cache, but the user
+ // appears to be still using it -> rebind
+
+ const rootUuid = ( action._localRoot || this._root ).uuid,
+ clipUuid = action._clip.uuid,
+ actionsForClip = this._actionsByClip[ clipUuid ];
+
+ this._bindAction( action,
+ actionsForClip && actionsForClip.knownActions[ 0 ] );
+
+ this._addInactiveAction( action, clipUuid, rootUuid );
+
+ }
+
+ const bindings = action._propertyBindings;
+
+ // increment reference counts / sort out state
+ for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
+
+ const binding = bindings[ i ];
+
+ if ( binding.useCount ++ === 0 ) {
+
+ this._lendBinding( binding );
+ binding.saveOriginalState();
+
+ }
+
+ }
+
+ this._lendAction( action );
+
+ }
+
+ },
+
+ _deactivateAction: function ( action ) {
+
+ if ( this._isActiveAction( action ) ) {
+
+ const bindings = action._propertyBindings;
+
+ // decrement reference counts / sort out state
+ for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
+
+ const binding = bindings[ i ];
+
+ if ( -- binding.useCount === 0 ) {
+
+ binding.restoreOriginalState();
+ this._takeBackBinding( binding );
+
+ }
+
+ }
+
+ this._takeBackAction( action );
+
+ }
+
+ },
+
+ // Memory manager
+
+ _initMemoryManager: function () {
+
+ this._actions = []; // 'nActiveActions' followed by inactive ones
+ this._nActiveActions = 0;
+
+ this._actionsByClip = {};
+ // inside:
+ // {
+ // knownActions: Array< AnimationAction > - used as prototypes
+ // actionByRoot: AnimationAction - lookup
+ // }
+
+
+ this._bindings = []; // 'nActiveBindings' followed by inactive ones
+ this._nActiveBindings = 0;
+
+ this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >
+
+
+ this._controlInterpolants = []; // same game as above
+ this._nActiveControlInterpolants = 0;
+
+ const scope = this;
+
+ this.stats = {
+
+ actions: {
+ get total() {
+
+ return scope._actions.length;
+
+ },
+ get inUse() {
+
+ return scope._nActiveActions;
+
+ }
+ },
+ bindings: {
+ get total() {
+
+ return scope._bindings.length;
+
+ },
+ get inUse() {
+
+ return scope._nActiveBindings;
+
+ }
+ },
+ controlInterpolants: {
+ get total() {
+
+ return scope._controlInterpolants.length;
+
+ },
+ get inUse() {
+
+ return scope._nActiveControlInterpolants;
+
+ }
+ }
+
+ };
+
+ },
+
+ // Memory management for AnimationAction objects
+
+ _isActiveAction: function ( action ) {
+
+ const index = action._cacheIndex;
+ return index !== null && index < this._nActiveActions;
+
+ },
+
+ _addInactiveAction: function ( action, clipUuid, rootUuid ) {
+
+ const actions = this._actions,
+ actionsByClip = this._actionsByClip;
+
+ let actionsForClip = actionsByClip[ clipUuid ];
+
+ if ( actionsForClip === undefined ) {
+
+ actionsForClip = {
+
+ knownActions: [ action ],
+ actionByRoot: {}
+
+ };
+
+ action._byClipCacheIndex = 0;
+
+ actionsByClip[ clipUuid ] = actionsForClip;
+
+ } else {
+
+ const knownActions = actionsForClip.knownActions;
+
+ action._byClipCacheIndex = knownActions.length;
+ knownActions.push( action );
+
+ }
+
+ action._cacheIndex = actions.length;
+ actions.push( action );
+
+ actionsForClip.actionByRoot[ rootUuid ] = action;
+
+ },
+
+ _removeInactiveAction: function ( action ) {
+
+ const actions = this._actions,
+ lastInactiveAction = actions[ actions.length - 1 ],
+ cacheIndex = action._cacheIndex;
+
+ lastInactiveAction._cacheIndex = cacheIndex;
+ actions[ cacheIndex ] = lastInactiveAction;
+ actions.pop();
+
+ action._cacheIndex = null;
+
+
+ const clipUuid = action._clip.uuid,
+ actionsByClip = this._actionsByClip,
+ actionsForClip = actionsByClip[ clipUuid ],
+ knownActionsForClip = actionsForClip.knownActions,
+
+ lastKnownAction =
+ knownActionsForClip[ knownActionsForClip.length - 1 ],
+
+ byClipCacheIndex = action._byClipCacheIndex;
+
+ lastKnownAction._byClipCacheIndex = byClipCacheIndex;
+ knownActionsForClip[ byClipCacheIndex ] = lastKnownAction;
+ knownActionsForClip.pop();
+
+ action._byClipCacheIndex = null;
+
+
+ const actionByRoot = actionsForClip.actionByRoot,
+ rootUuid = ( action._localRoot || this._root ).uuid;
+
+ delete actionByRoot[ rootUuid ];
+
+ if ( knownActionsForClip.length === 0 ) {
+
+ delete actionsByClip[ clipUuid ];
+
+ }
+
+ this._removeInactiveBindingsForAction( action );
+
+ },
+
+ _removeInactiveBindingsForAction: function ( action ) {
+
+ const bindings = action._propertyBindings;
+
+ for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
+
+ const binding = bindings[ i ];
+
+ if ( -- binding.referenceCount === 0 ) {
+
+ this._removeInactiveBinding( binding );
+
+ }
+
+ }
+
+ },
+
+ _lendAction: function ( action ) {
+
+ // [ active actions | inactive actions ]
+ // [ active actions >| inactive actions ]
+ // s a
+ // <-swap->
+ // a s
+
+ const actions = this._actions,
+ prevIndex = action._cacheIndex,
+
+ lastActiveIndex = this._nActiveActions ++,
+
+ firstInactiveAction = actions[ lastActiveIndex ];
+
+ action._cacheIndex = lastActiveIndex;
+ actions[ lastActiveIndex ] = action;
+
+ firstInactiveAction._cacheIndex = prevIndex;
+ actions[ prevIndex ] = firstInactiveAction;
+
+ },
+
+ _takeBackAction: function ( action ) {
+
+ // [ active actions | inactive actions ]
+ // [ active actions |< inactive actions ]
+ // a s
+ // <-swap->
+ // s a
+
+ const actions = this._actions,
+ prevIndex = action._cacheIndex,
+
+ firstInactiveIndex = -- this._nActiveActions,
+
+ lastActiveAction = actions[ firstInactiveIndex ];
+
+ action._cacheIndex = firstInactiveIndex;
+ actions[ firstInactiveIndex ] = action;
+
+ lastActiveAction._cacheIndex = prevIndex;
+ actions[ prevIndex ] = lastActiveAction;
+
+ },
+
+ // Memory management for PropertyMixer objects
+
+ _addInactiveBinding: function ( binding, rootUuid, trackName ) {
+
+ const bindingsByRoot = this._bindingsByRootAndName,
+ bindings = this._bindings;
+
+ let bindingByName = bindingsByRoot[ rootUuid ];
+
+ if ( bindingByName === undefined ) {
+
+ bindingByName = {};
+ bindingsByRoot[ rootUuid ] = bindingByName;
+
+ }
+
+ bindingByName[ trackName ] = binding;
+
+ binding._cacheIndex = bindings.length;
+ bindings.push( binding );
+
+ },
+
+ _removeInactiveBinding: function ( binding ) {
+
+ const bindings = this._bindings,
+ propBinding = binding.binding,
+ rootUuid = propBinding.rootNode.uuid,
+ trackName = propBinding.path,
+ bindingsByRoot = this._bindingsByRootAndName,
+ bindingByName = bindingsByRoot[ rootUuid ],
+
+ lastInactiveBinding = bindings[ bindings.length - 1 ],
+ cacheIndex = binding._cacheIndex;
+
+ lastInactiveBinding._cacheIndex = cacheIndex;
+ bindings[ cacheIndex ] = lastInactiveBinding;
+ bindings.pop();
+
+ delete bindingByName[ trackName ];
+
+ if ( Object.keys( bindingByName ).length === 0 ) {
+
+ delete bindingsByRoot[ rootUuid ];
+
+ }
+
+ },
+
+ _lendBinding: function ( binding ) {
+
+ const bindings = this._bindings,
+ prevIndex = binding._cacheIndex,
+
+ lastActiveIndex = this._nActiveBindings ++,
+
+ firstInactiveBinding = bindings[ lastActiveIndex ];
+
+ binding._cacheIndex = lastActiveIndex;
+ bindings[ lastActiveIndex ] = binding;
+
+ firstInactiveBinding._cacheIndex = prevIndex;
+ bindings[ prevIndex ] = firstInactiveBinding;
+
+ },
+
+ _takeBackBinding: function ( binding ) {
+
+ const bindings = this._bindings,
+ prevIndex = binding._cacheIndex,
+
+ firstInactiveIndex = -- this._nActiveBindings,
+
+ lastActiveBinding = bindings[ firstInactiveIndex ];
+
+ binding._cacheIndex = firstInactiveIndex;
+ bindings[ firstInactiveIndex ] = binding;
+
+ lastActiveBinding._cacheIndex = prevIndex;
+ bindings[ prevIndex ] = lastActiveBinding;
+
+ },
+
+
+ // Memory management of Interpolants for weight and time scale
+
+ _lendControlInterpolant: function () {
+
+ const interpolants = this._controlInterpolants,
+ lastActiveIndex = this._nActiveControlInterpolants ++;
+
+ let interpolant = interpolants[ lastActiveIndex ];
+
+ if ( interpolant === undefined ) {
+
+ interpolant = new LinearInterpolant(
+ new Float32Array( 2 ), new Float32Array( 2 ),
+ 1, this._controlInterpolantsResultBuffer );
+
+ interpolant.__cacheIndex = lastActiveIndex;
+ interpolants[ lastActiveIndex ] = interpolant;
+
+ }
+
+ return interpolant;
+
+ },
+
+ _takeBackControlInterpolant: function ( interpolant ) {
+
+ const interpolants = this._controlInterpolants,
+ prevIndex = interpolant.__cacheIndex,
+
+ firstInactiveIndex = -- this._nActiveControlInterpolants,
+
+ lastActiveInterpolant = interpolants[ firstInactiveIndex ];
+
+ interpolant.__cacheIndex = firstInactiveIndex;
+ interpolants[ firstInactiveIndex ] = interpolant;
+
+ lastActiveInterpolant.__cacheIndex = prevIndex;
+ interpolants[ prevIndex ] = lastActiveInterpolant;
+
+ },
+
+ _controlInterpolantsResultBuffer: new Float32Array( 1 ),
+
+ // return an action for a clip optionally using a custom root target
+ // object (this method allocates a lot of dynamic memory in case a
+ // previously unknown clip/root combination is specified)
+ clipAction: function ( clip, optionalRoot, blendMode ) {
+
+ const root = optionalRoot || this._root,
+ rootUuid = root.uuid;
+
+ let clipObject = typeof clip === 'string' ? AnimationClip.findByName( root, clip ) : clip;
+
+ const clipUuid = clipObject !== null ? clipObject.uuid : clip;
+
+ const actionsForClip = this._actionsByClip[ clipUuid ];
+ let prototypeAction = null;
+
+ if ( blendMode === undefined ) {
+
+ if ( clipObject !== null ) {
+
+ blendMode = clipObject.blendMode;
+
+ } else {
+
+ blendMode = NormalAnimationBlendMode;
+
+ }
+
+ }
+
+ if ( actionsForClip !== undefined ) {
+
+ const existingAction = actionsForClip.actionByRoot[ rootUuid ];
+
+ if ( existingAction !== undefined && existingAction.blendMode === blendMode ) {
+
+ return existingAction;
+
+ }
+
+ // we know the clip, so we don't have to parse all
+ // the bindings again but can just copy
+ prototypeAction = actionsForClip.knownActions[ 0 ];
+
+ // also, take the clip from the prototype action
+ if ( clipObject === null )
+ clipObject = prototypeAction._clip;
+
+ }
+
+ // clip must be known when specified via string
+ if ( clipObject === null ) return null;
+
+ // allocate all resources required to run it
+ const newAction = new AnimationAction( this, clipObject, optionalRoot, blendMode );
+
+ this._bindAction( newAction, prototypeAction );
+
+ // and make the action known to the memory manager
+ this._addInactiveAction( newAction, clipUuid, rootUuid );
+
+ return newAction;
+
+ },
+
+ // get an existing action
+ existingAction: function ( clip, optionalRoot ) {
+
+ const root = optionalRoot || this._root,
+ rootUuid = root.uuid,
+
+ clipObject = typeof clip === 'string' ?
+ AnimationClip.findByName( root, clip ) : clip,
+
+ clipUuid = clipObject ? clipObject.uuid : clip,
+
+ actionsForClip = this._actionsByClip[ clipUuid ];
+
+ if ( actionsForClip !== undefined ) {
+
+ return actionsForClip.actionByRoot[ rootUuid ] || null;
+
+ }
+
+ return null;
+
+ },
+
+ // deactivates all previously scheduled actions
+ stopAllAction: function () {
+
+ const actions = this._actions,
+ nActions = this._nActiveActions;
+
+ for ( let i = nActions - 1; i >= 0; -- i ) {
+
+ actions[ i ].stop();
+
+ }
+
+ return this;
+
+ },
+
+ // advance the time and update apply the animation
+ update: function ( deltaTime ) {
+
+ deltaTime *= this.timeScale;
+
+ const actions = this._actions,
+ nActions = this._nActiveActions,
+
+ time = this.time += deltaTime,
+ timeDirection = Math.sign( deltaTime ),
+
+ accuIndex = this._accuIndex ^= 1;
+
+ // run active actions
+
+ for ( let i = 0; i !== nActions; ++ i ) {
+
+ const action = actions[ i ];
+
+ action._update( time, deltaTime, timeDirection, accuIndex );
+
+ }
+
+ // update scene graph
+
+ const bindings = this._bindings,
+ nBindings = this._nActiveBindings;
+
+ for ( let i = 0; i !== nBindings; ++ i ) {
+
+ bindings[ i ].apply( accuIndex );
+
+ }
+
+ return this;
+
+ },
+
+ // Allows you to seek to a specific time in an animation.
+ setTime: function ( timeInSeconds ) {
+
+ this.time = 0; // Zero out time attribute for AnimationMixer object;
+ for ( let i = 0; i < this._actions.length; i ++ ) {
+
+ this._actions[ i ].time = 0; // Zero out time attribute for all associated AnimationAction objects.
+
+ }
+
+ return this.update( timeInSeconds ); // Update used to set exact time. Returns "this" AnimationMixer object.
+
+ },
+
+ // return this mixer's root target object
+ getRoot: function () {
+
+ return this._root;
+
+ },
+
+ // free all resources specific to a particular clip
+ uncacheClip: function ( clip ) {
+
+ const actions = this._actions,
+ clipUuid = clip.uuid,
+ actionsByClip = this._actionsByClip,
+ actionsForClip = actionsByClip[ clipUuid ];
+
+ if ( actionsForClip !== undefined ) {
+
+ // note: just calling _removeInactiveAction would mess up the
+ // iteration state and also require updating the state we can
+ // just throw away
+
+ const actionsToRemove = actionsForClip.knownActions;
+
+ for ( let i = 0, n = actionsToRemove.length; i !== n; ++ i ) {
+
+ const action = actionsToRemove[ i ];
+
+ this._deactivateAction( action );
+
+ const cacheIndex = action._cacheIndex,
+ lastInactiveAction = actions[ actions.length - 1 ];
+
+ action._cacheIndex = null;
+ action._byClipCacheIndex = null;
+
+ lastInactiveAction._cacheIndex = cacheIndex;
+ actions[ cacheIndex ] = lastInactiveAction;
+ actions.pop();
+
+ this._removeInactiveBindingsForAction( action );
+
+ }
+
+ delete actionsByClip[ clipUuid ];
+
+ }
+
+ },
+
+ // free all resources specific to a particular root target object
+ uncacheRoot: function ( root ) {
+
+ const rootUuid = root.uuid,
+ actionsByClip = this._actionsByClip;
+
+ for ( const clipUuid in actionsByClip ) {
+
+ const actionByRoot = actionsByClip[ clipUuid ].actionByRoot,
+ action = actionByRoot[ rootUuid ];
+
+ if ( action !== undefined ) {
+
+ this._deactivateAction( action );
+ this._removeInactiveAction( action );
+
+ }
+
+ }
+
+ const bindingsByRoot = this._bindingsByRootAndName,
+ bindingByName = bindingsByRoot[ rootUuid ];
+
+ if ( bindingByName !== undefined ) {
+
+ for ( const trackName in bindingByName ) {
+
+ const binding = bindingByName[ trackName ];
+ binding.restoreOriginalState();
+ this._removeInactiveBinding( binding );
+
+ }
+
+ }
+
+ },
+
+ // remove a targeted clip from the cache
+ uncacheAction: function ( clip, optionalRoot ) {
+
+ const action = this.existingAction( clip, optionalRoot );
+
+ if ( action !== null ) {
+
+ this._deactivateAction( action );
+ this._removeInactiveAction( action );
+
+ }
+
+ }
+
+} );
+
+class Uniform {
+
+ constructor( value ) {
+
+ if ( typeof value === 'string' ) {
+
+ console.warn( 'THREE.Uniform: Type parameter is no longer needed.' );
+ value = arguments[ 1 ];
+
+ }
+
+ this.value = value;
+
+ }
+
+ clone() {
+
+ return new Uniform( this.value.clone === undefined ? this.value : this.value.clone() );
+
+ }
+
+}
+
+function InstancedInterleavedBuffer( array, stride, meshPerAttribute ) {
+
+ InterleavedBuffer.call( this, array, stride );
+
+ this.meshPerAttribute = meshPerAttribute || 1;
+
+}
+
+InstancedInterleavedBuffer.prototype = Object.assign( Object.create( InterleavedBuffer.prototype ), {
+
+ constructor: InstancedInterleavedBuffer,
+
+ isInstancedInterleavedBuffer: true,
+
+ copy: function ( source ) {
+
+ InterleavedBuffer.prototype.copy.call( this, source );
+
+ this.meshPerAttribute = source.meshPerAttribute;
+
+ return this;
+
+ },
+
+ clone: function ( data ) {
+
+ const ib = InterleavedBuffer.prototype.clone.call( this, data );
+
+ ib.meshPerAttribute = this.meshPerAttribute;
+
+ return ib;
+
+ },
+
+ toJSON: function ( data ) {
+
+ const json = InterleavedBuffer.prototype.toJSON.call( this, data );
+
+ json.isInstancedInterleavedBuffer = true;
+ json.meshPerAttribute = this.meshPerAttribute;
+
+ return json;
+
+ }
+
+} );
+
+function GLBufferAttribute( buffer, type, itemSize, elementSize, count ) {
+
+ this.buffer = buffer;
+ this.type = type;
+ this.itemSize = itemSize;
+ this.elementSize = elementSize;
+ this.count = count;
+
+ this.version = 0;
+
+}
+
+Object.defineProperty( GLBufferAttribute.prototype, 'needsUpdate', {
+
+ set: function ( value ) {
+
+ if ( value === true ) this.version ++;
+
+ }
+
+} );
+
+Object.assign( GLBufferAttribute.prototype, {
+
+ isGLBufferAttribute: true,
+
+ setBuffer: function ( buffer ) {
+
+ this.buffer = buffer;
+
+ return this;
+
+ },
+
+ setType: function ( type, elementSize ) {
+
+ this.type = type;
+ this.elementSize = elementSize;
+
+ return this;
+
+ },
+
+ setItemSize: function ( itemSize ) {
+
+ this.itemSize = itemSize;
+
+ return this;
+
+ },
+
+ setCount: function ( count ) {
+
+ this.count = count;
+
+ return this;
+
+ },
+
+} );
+
+function Raycaster( origin, direction, near, far ) {
+
+ this.ray = new Ray( origin, direction );
+ // direction is assumed to be normalized (for accurate distance calculations)
+
+ this.near = near || 0;
+ this.far = far || Infinity;
+ this.camera = null;
+ this.layers = new Layers();
+
+ this.params = {
+ Mesh: {},
+ Line: { threshold: 1 },
+ LOD: {},
+ Points: { threshold: 1 },
+ Sprite: {}
+ };
+
+ Object.defineProperties( this.params, {
+ PointCloud: {
+ get: function () {
+
+ console.warn( 'THREE.Raycaster: params.PointCloud has been renamed to params.Points.' );
+ return this.Points;
+
+ }
+ }
+ } );
+
+}
+
+function ascSort( a, b ) {
+
+ return a.distance - b.distance;
+
+}
+
+function intersectObject( object, raycaster, intersects, recursive ) {
+
+ if ( object.layers.test( raycaster.layers ) ) {
+
+ object.raycast( raycaster, intersects );
+
+ }
+
+ if ( recursive === true ) {
+
+ const children = object.children;
+
+ for ( let i = 0, l = children.length; i < l; i ++ ) {
+
+ intersectObject( children[ i ], raycaster, intersects, true );
+
+ }
+
+ }
+
+}
+
+Object.assign( Raycaster.prototype, {
+
+ set: function ( origin, direction ) {
+
+ // direction is assumed to be normalized (for accurate distance calculations)
+
+ this.ray.set( origin, direction );
+
+ },
+
+ setFromCamera: function ( coords, camera ) {
+
+ if ( camera && camera.isPerspectiveCamera ) {
+
+ this.ray.origin.setFromMatrixPosition( camera.matrixWorld );
+ this.ray.direction.set( coords.x, coords.y, 0.5 ).unproject( camera ).sub( this.ray.origin ).normalize();
+ this.camera = camera;
+
+ } else if ( camera && camera.isOrthographicCamera ) {
+
+ this.ray.origin.set( coords.x, coords.y, ( camera.near + camera.far ) / ( camera.near - camera.far ) ).unproject( camera ); // set origin in plane of camera
+ this.ray.direction.set( 0, 0, - 1 ).transformDirection( camera.matrixWorld );
+ this.camera = camera;
+
+ } else {
+
+ console.error( 'THREE.Raycaster: Unsupported camera type: ' + camera.type );
+
+ }
+
+ },
+
+ intersectObject: function ( object, recursive, optionalTarget ) {
+
+ const intersects = optionalTarget || [];
+
+ intersectObject( object, this, intersects, recursive );
+
+ intersects.sort( ascSort );
+
+ return intersects;
+
+ },
+
+ intersectObjects: function ( objects, recursive, optionalTarget ) {
+
+ const intersects = optionalTarget || [];
+
+ if ( Array.isArray( objects ) === false ) {
+
+ console.warn( 'THREE.Raycaster.intersectObjects: objects is not an Array.' );
+ return intersects;
+
+ }
+
+ for ( let i = 0, l = objects.length; i < l; i ++ ) {
+
+ intersectObject( objects[ i ], this, intersects, recursive );
+
+ }
+
+ intersects.sort( ascSort );
+
+ return intersects;
+
+ }
+
+} );
+
+const _vector$8 = /*@__PURE__*/ new Vector2();
+
+class Box2 {
+
+ constructor( min, max ) {
+
+ Object.defineProperty( this, 'isBox2', { value: true } );
+
+ this.min = ( min !== undefined ) ? min : new Vector2( + Infinity, + Infinity );
+ this.max = ( max !== undefined ) ? max : new Vector2( - Infinity, - Infinity );
+
+ }
+
+ set( min, max ) {
+
+ this.min.copy( min );
+ this.max.copy( max );
+
+ return this;
+
+ }
+
+ setFromPoints( points ) {
+
+ this.makeEmpty();
+
+ for ( let i = 0, il = points.length; i < il; i ++ ) {
+
+ this.expandByPoint( points[ i ] );
+
+ }
+
+ return this;
+
+ }
+
+ setFromCenterAndSize( center, size ) {
+
+ const halfSize = _vector$8.copy( size ).multiplyScalar( 0.5 );
+ this.min.copy( center ).sub( halfSize );
+ this.max.copy( center ).add( halfSize );
+
+ return this;
+
+ }
+
+ clone() {
+
+ return new this.constructor().copy( this );
+
+ }
+
+ copy( box ) {
+
+ this.min.copy( box.min );
+ this.max.copy( box.max );
+
+ return this;
+
+ }
+
+ makeEmpty() {
+
+ this.min.x = this.min.y = + Infinity;
+ this.max.x = this.max.y = - Infinity;
+
+ return this;
+
+ }
+
+ isEmpty() {
+
+ // this is a more robust check for empty than ( volume <= 0 ) because volume can get positive with two negative axes
+
+ return ( this.max.x < this.min.x ) || ( this.max.y < this.min.y );
+
+ }
+
+ getCenter( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box2: .getCenter() target is now required' );
+ target = new Vector2();
+
+ }
+
+ return this.isEmpty() ? target.set( 0, 0 ) : target.addVectors( this.min, this.max ).multiplyScalar( 0.5 );
+
+ }
+
+ getSize( target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box2: .getSize() target is now required' );
+ target = new Vector2();
+
+ }
+
+ return this.isEmpty() ? target.set( 0, 0 ) : target.subVectors( this.max, this.min );
+
+ }
+
+ expandByPoint( point ) {
+
+ this.min.min( point );
+ this.max.max( point );
+
+ return this;
+
+ }
+
+ expandByVector( vector ) {
+
+ this.min.sub( vector );
+ this.max.add( vector );
+
+ return this;
+
+ }
+
+ expandByScalar( scalar ) {
+
+ this.min.addScalar( - scalar );
+ this.max.addScalar( scalar );
+
+ return this;
+
+ }
+
+ containsPoint( point ) {
+
+ return point.x < this.min.x || point.x > this.max.x ||
+ point.y < this.min.y || point.y > this.max.y ? false : true;
+
+ }
+
+ containsBox( box ) {
+
+ return this.min.x <= box.min.x && box.max.x <= this.max.x &&
+ this.min.y <= box.min.y && box.max.y <= this.max.y;
+
+ }
+
+ getParameter( point, target ) {
+
+ // This can potentially have a divide by zero if the box
+ // has a size dimension of 0.
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box2: .getParameter() target is now required' );
+ target = new Vector2();
+
+ }
+
+ return target.set(
+ ( point.x - this.min.x ) / ( this.max.x - this.min.x ),
+ ( point.y - this.min.y ) / ( this.max.y - this.min.y )
+ );
+
+ }
+
+ intersectsBox( box ) {
+
+ // using 4 splitting planes to rule out intersections
+
+ return box.max.x < this.min.x || box.min.x > this.max.x ||
+ box.max.y < this.min.y || box.min.y > this.max.y ? false : true;
+
+ }
+
+ clampPoint( point, target ) {
+
+ if ( target === undefined ) {
+
+ console.warn( 'THREE.Box2: .clampPoint() target is now required' );
+ target = new Vector2();
+
+ }
+
+ return target.copy( point ).clamp( this.min, this.max );
+
+ }
+
+ distanceToPoint( point ) {
+
+ const clampedPoint = _vector$8.copy( point ).clamp( this.min, this.max );
+ return clampedPoint.sub( point ).length();
+
+ }
+
+ intersect( box ) {
+
+ this.min.max( box.min );
+ this.max.min( box.max );
+
+ return this;
+
+ }
+
+ union( box ) {
+
+ this.min.min( box.min );
+ this.max.max( box.max );
+
+ return this;
+
+ }
+
+ translate( offset ) {
+
+ this.min.add( offset );
+ this.max.add( offset );
+
+ return this;
+
+ }
+
+ equals( box ) {
+
+ return box.min.equals( this.min ) && box.max.equals( this.max );
+
+ }
+
+}
+
+function ImmediateRenderObject( material ) {
+
+ Object3D.call( this );
+
+ this.material = material;
+ this.render = function ( /* renderCallback */ ) {};
+
+ this.hasPositions = false;
+ this.hasNormals = false;
+ this.hasColors = false;
+ this.hasUvs = false;
+
+ this.positionArray = null;
+ this.normalArray = null;
+ this.colorArray = null;
+ this.uvArray = null;
+
+ this.count = 0;
+
+}
+
+ImmediateRenderObject.prototype = Object.create( Object3D.prototype );
+ImmediateRenderObject.prototype.constructor = ImmediateRenderObject;
+
+ImmediateRenderObject.prototype.isImmediateRenderObject = true;
+
+const backgroundMaterial = new MeshBasicMaterial( {
+ side: BackSide,
+ depthWrite: false,
+ depthTest: false,
+} );
+new Mesh( new BoxGeometry(), backgroundMaterial );
+
+//
+
+Curve.create = function ( construct, getPoint ) {
+
+ console.log( 'THREE.Curve.create() has been deprecated' );
+
+ construct.prototype = Object.create( Curve.prototype );
+ construct.prototype.constructor = construct;
+ construct.prototype.getPoint = getPoint;
+
+ return construct;
+
+};
+
+//
+
+Object.assign( Path.prototype, {
+
+ fromPoints: function ( points ) {
+
+ console.warn( 'THREE.Path: .fromPoints() has been renamed to .setFromPoints().' );
+ return this.setFromPoints( points );
+
+ }
+
+} );
+
+//
+
+function Spline( points ) {
+
+ console.warn( 'THREE.Spline has been removed. Use THREE.CatmullRomCurve3 instead.' );
+
+ CatmullRomCurve3.call( this, points );
+ this.type = 'catmullrom';
+
+}
+
+Spline.prototype = Object.create( CatmullRomCurve3.prototype );
+
+Object.assign( Spline.prototype, {
+
+ initFromArray: function ( /* a */ ) {
+
+ console.error( 'THREE.Spline: .initFromArray() has been removed.' );
+
+ },
+ getControlPointsArray: function ( /* optionalTarget */ ) {
+
+ console.error( 'THREE.Spline: .getControlPointsArray() has been removed.' );
+
+ },
+ reparametrizeByArcLength: function ( /* samplingCoef */ ) {
+
+ console.error( 'THREE.Spline: .reparametrizeByArcLength() has been removed.' );
+
+ }
+
+} );
+
+//
+
+Object.assign( Loader.prototype, {
+
+ extractUrlBase: function ( url ) {
+
+ console.warn( 'THREE.Loader: .extractUrlBase() has been deprecated. Use THREE.LoaderUtils.extractUrlBase() instead.' );
+ return LoaderUtils.extractUrlBase( url );
+
+ }
+
+} );
+
+Loader.Handlers = {
+
+ add: function ( /* regex, loader */ ) {
+
+ console.error( 'THREE.Loader: Handlers.add() has been removed. Use LoadingManager.addHandler() instead.' );
+
+ },
+
+ get: function ( /* file */ ) {
+
+ console.error( 'THREE.Loader: Handlers.get() has been removed. Use LoadingManager.getHandler() instead.' );
+
+ }
+
+};
+
+//
+
+Object.assign( Box2.prototype, {
+
+ center: function ( optionalTarget ) {
+
+ console.warn( 'THREE.Box2: .center() has been renamed to .getCenter().' );
+ return this.getCenter( optionalTarget );
+
+ },
+ empty: function () {
+
+ console.warn( 'THREE.Box2: .empty() has been renamed to .isEmpty().' );
+ return this.isEmpty();
+
+ },
+ isIntersectionBox: function ( box ) {
+
+ console.warn( 'THREE.Box2: .isIntersectionBox() has been renamed to .intersectsBox().' );
+ return this.intersectsBox( box );
+
+ },
+ size: function ( optionalTarget ) {
+
+ console.warn( 'THREE.Box2: .size() has been renamed to .getSize().' );
+ return this.getSize( optionalTarget );
+
+ }
+} );
+
+Object.assign( Box3.prototype, {
+
+ center: function ( optionalTarget ) {
+
+ console.warn( 'THREE.Box3: .center() has been renamed to .getCenter().' );
+ return this.getCenter( optionalTarget );
+
+ },
+ empty: function () {
+
+ console.warn( 'THREE.Box3: .empty() has been renamed to .isEmpty().' );
+ return this.isEmpty();
+
+ },
+ isIntersectionBox: function ( box ) {
+
+ console.warn( 'THREE.Box3: .isIntersectionBox() has been renamed to .intersectsBox().' );
+ return this.intersectsBox( box );
+
+ },
+ isIntersectionSphere: function ( sphere ) {
+
+ console.warn( 'THREE.Box3: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
+ return this.intersectsSphere( sphere );
+
+ },
+ size: function ( optionalTarget ) {
+
+ console.warn( 'THREE.Box3: .size() has been renamed to .getSize().' );
+ return this.getSize( optionalTarget );
+
+ }
+} );
+
+Object.assign( Sphere.prototype, {
+
+ empty: function () {
+
+ console.warn( 'THREE.Sphere: .empty() has been renamed to .isEmpty().' );
+ return this.isEmpty();
+
+ },
+
+} );
+
+Frustum.prototype.setFromMatrix = function ( m ) {
+
+ console.warn( 'THREE.Frustum: .setFromMatrix() has been renamed to .setFromProjectionMatrix().' );
+ return this.setFromProjectionMatrix( m );
+
+};
+
+Object.assign( MathUtils, {
+
+ random16: function () {
+
+ console.warn( 'THREE.Math: .random16() has been deprecated. Use Math.random() instead.' );
+ return Math.random();
+
+ },
+
+ nearestPowerOfTwo: function ( value ) {
+
+ console.warn( 'THREE.Math: .nearestPowerOfTwo() has been renamed to .floorPowerOfTwo().' );
+ return MathUtils.floorPowerOfTwo( value );
+
+ },
+
+ nextPowerOfTwo: function ( value ) {
+
+ console.warn( 'THREE.Math: .nextPowerOfTwo() has been renamed to .ceilPowerOfTwo().' );
+ return MathUtils.ceilPowerOfTwo( value );
+
+ }
+
+} );
+
+Object.assign( Matrix3.prototype, {
+
+ flattenToArrayOffset: function ( array, offset ) {
+
+ console.warn( 'THREE.Matrix3: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
+ return this.toArray( array, offset );
+
+ },
+ multiplyVector3: function ( vector ) {
+
+ console.warn( 'THREE.Matrix3: .multiplyVector3() has been removed. Use vector.applyMatrix3( matrix ) instead.' );
+ return vector.applyMatrix3( this );
+
+ },
+ multiplyVector3Array: function ( /* a */ ) {
+
+ console.error( 'THREE.Matrix3: .multiplyVector3Array() has been removed.' );
+
+ },
+ applyToBufferAttribute: function ( attribute ) {
+
+ console.warn( 'THREE.Matrix3: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix3( matrix ) instead.' );
+ return attribute.applyMatrix3( this );
+
+ },
+ applyToVector3Array: function ( /* array, offset, length */ ) {
+
+ console.error( 'THREE.Matrix3: .applyToVector3Array() has been removed.' );
+
+ },
+ getInverse: function ( matrix ) {
+
+ console.warn( 'THREE.Matrix3: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
+ return this.copy( matrix ).invert();
+
+ }
+
+} );
+
+Object.assign( Matrix4.prototype, {
+
+ extractPosition: function ( m ) {
+
+ console.warn( 'THREE.Matrix4: .extractPosition() has been renamed to .copyPosition().' );
+ return this.copyPosition( m );
+
+ },
+ flattenToArrayOffset: function ( array, offset ) {
+
+ console.warn( 'THREE.Matrix4: .flattenToArrayOffset() has been deprecated. Use .toArray() instead.' );
+ return this.toArray( array, offset );
+
+ },
+ getPosition: function () {
+
+ console.warn( 'THREE.Matrix4: .getPosition() has been removed. Use Vector3.setFromMatrixPosition( matrix ) instead.' );
+ return new Vector3().setFromMatrixColumn( this, 3 );
+
+ },
+ setRotationFromQuaternion: function ( q ) {
+
+ console.warn( 'THREE.Matrix4: .setRotationFromQuaternion() has been renamed to .makeRotationFromQuaternion().' );
+ return this.makeRotationFromQuaternion( q );
+
+ },
+ multiplyToArray: function () {
+
+ console.warn( 'THREE.Matrix4: .multiplyToArray() has been removed.' );
+
+ },
+ multiplyVector3: function ( vector ) {
+
+ console.warn( 'THREE.Matrix4: .multiplyVector3() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+ return vector.applyMatrix4( this );
+
+ },
+ multiplyVector4: function ( vector ) {
+
+ console.warn( 'THREE.Matrix4: .multiplyVector4() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+ return vector.applyMatrix4( this );
+
+ },
+ multiplyVector3Array: function ( /* a */ ) {
+
+ console.error( 'THREE.Matrix4: .multiplyVector3Array() has been removed.' );
+
+ },
+ rotateAxis: function ( v ) {
+
+ console.warn( 'THREE.Matrix4: .rotateAxis() has been removed. Use Vector3.transformDirection( matrix ) instead.' );
+ v.transformDirection( this );
+
+ },
+ crossVector: function ( vector ) {
+
+ console.warn( 'THREE.Matrix4: .crossVector() has been removed. Use vector.applyMatrix4( matrix ) instead.' );
+ return vector.applyMatrix4( this );
+
+ },
+ translate: function () {
+
+ console.error( 'THREE.Matrix4: .translate() has been removed.' );
+
+ },
+ rotateX: function () {
+
+ console.error( 'THREE.Matrix4: .rotateX() has been removed.' );
+
+ },
+ rotateY: function () {
+
+ console.error( 'THREE.Matrix4: .rotateY() has been removed.' );
+
+ },
+ rotateZ: function () {
+
+ console.error( 'THREE.Matrix4: .rotateZ() has been removed.' );
+
+ },
+ rotateByAxis: function () {
+
+ console.error( 'THREE.Matrix4: .rotateByAxis() has been removed.' );
+
+ },
+ applyToBufferAttribute: function ( attribute ) {
+
+ console.warn( 'THREE.Matrix4: .applyToBufferAttribute() has been removed. Use attribute.applyMatrix4( matrix ) instead.' );
+ return attribute.applyMatrix4( this );
+
+ },
+ applyToVector3Array: function ( /* array, offset, length */ ) {
+
+ console.error( 'THREE.Matrix4: .applyToVector3Array() has been removed.' );
+
+ },
+ makeFrustum: function ( left, right, bottom, top, near, far ) {
+
+ console.warn( 'THREE.Matrix4: .makeFrustum() has been removed. Use .makePerspective( left, right, top, bottom, near, far ) instead.' );
+ return this.makePerspective( left, right, top, bottom, near, far );
+
+ },
+ getInverse: function ( matrix ) {
+
+ console.warn( 'THREE.Matrix4: .getInverse() has been removed. Use matrixInv.copy( matrix ).invert(); instead.' );
+ return this.copy( matrix ).invert();
+
+ }
+
+} );
+
+Plane.prototype.isIntersectionLine = function ( line ) {
+
+ console.warn( 'THREE.Plane: .isIntersectionLine() has been renamed to .intersectsLine().' );
+ return this.intersectsLine( line );
+
+};
+
+Object.assign( Quaternion.prototype, {
+
+ multiplyVector3: function ( vector ) {
+
+ console.warn( 'THREE.Quaternion: .multiplyVector3() has been removed. Use is now vector.applyQuaternion( quaternion ) instead.' );
+ return vector.applyQuaternion( this );
+
+ },
+ inverse: function ( ) {
+
+ console.warn( 'THREE.Quaternion: .inverse() has been renamed to invert().' );
+ return this.invert();
+
+ }
+
+} );
+
+Object.assign( Ray.prototype, {
+
+ isIntersectionBox: function ( box ) {
+
+ console.warn( 'THREE.Ray: .isIntersectionBox() has been renamed to .intersectsBox().' );
+ return this.intersectsBox( box );
+
+ },
+ isIntersectionPlane: function ( plane ) {
+
+ console.warn( 'THREE.Ray: .isIntersectionPlane() has been renamed to .intersectsPlane().' );
+ return this.intersectsPlane( plane );
+
+ },
+ isIntersectionSphere: function ( sphere ) {
+
+ console.warn( 'THREE.Ray: .isIntersectionSphere() has been renamed to .intersectsSphere().' );
+ return this.intersectsSphere( sphere );
+
+ }
+
+} );
+
+Object.assign( Triangle.prototype, {
+
+ area: function () {
+
+ console.warn( 'THREE.Triangle: .area() has been renamed to .getArea().' );
+ return this.getArea();
+
+ },
+ barycoordFromPoint: function ( point, target ) {
+
+ console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
+ return this.getBarycoord( point, target );
+
+ },
+ midpoint: function ( target ) {
+
+ console.warn( 'THREE.Triangle: .midpoint() has been renamed to .getMidpoint().' );
+ return this.getMidpoint( target );
+
+ },
+ normal: function ( target ) {
+
+ console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
+ return this.getNormal( target );
+
+ },
+ plane: function ( target ) {
+
+ console.warn( 'THREE.Triangle: .plane() has been renamed to .getPlane().' );
+ return this.getPlane( target );
+
+ }
+
+} );
+
+Object.assign( Triangle, {
+
+ barycoordFromPoint: function ( point, a, b, c, target ) {
+
+ console.warn( 'THREE.Triangle: .barycoordFromPoint() has been renamed to .getBarycoord().' );
+ return Triangle.getBarycoord( point, a, b, c, target );
+
+ },
+ normal: function ( a, b, c, target ) {
+
+ console.warn( 'THREE.Triangle: .normal() has been renamed to .getNormal().' );
+ return Triangle.getNormal( a, b, c, target );
+
+ }
+
+} );
+
+Object.assign( Shape.prototype, {
+
+ extractAllPoints: function ( divisions ) {
+
+ console.warn( 'THREE.Shape: .extractAllPoints() has been removed. Use .extractPoints() instead.' );
+ return this.extractPoints( divisions );
+
+ },
+ extrude: function ( options ) {
+
+ console.warn( 'THREE.Shape: .extrude() has been removed. Use ExtrudeGeometry() instead.' );
+ return new ExtrudeGeometry( this, options );
+
+ },
+ makeGeometry: function ( options ) {
+
+ console.warn( 'THREE.Shape: .makeGeometry() has been removed. Use ShapeGeometry() instead.' );
+ return new ShapeGeometry( this, options );
+
+ }
+
+} );
+
+Object.assign( Vector2.prototype, {
+
+ fromAttribute: function ( attribute, index, offset ) {
+
+ console.warn( 'THREE.Vector2: .fromAttribute() has been renamed to .fromBufferAttribute().' );
+ return this.fromBufferAttribute( attribute, index, offset );
+
+ },
+ distanceToManhattan: function ( v ) {
+
+ console.warn( 'THREE.Vector2: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );
+ return this.manhattanDistanceTo( v );
+
+ },
+ lengthManhattan: function () {
+
+ console.warn( 'THREE.Vector2: .lengthManhattan() has been renamed to .manhattanLength().' );
+ return this.manhattanLength();
+
+ }
+
+} );
+
+Object.assign( Vector3.prototype, {
+
+ setEulerFromRotationMatrix: function () {
+
+ console.error( 'THREE.Vector3: .setEulerFromRotationMatrix() has been removed. Use Euler.setFromRotationMatrix() instead.' );
+
+ },
+ setEulerFromQuaternion: function () {
+
+ console.error( 'THREE.Vector3: .setEulerFromQuaternion() has been removed. Use Euler.setFromQuaternion() instead.' );
+
+ },
+ getPositionFromMatrix: function ( m ) {
+
+ console.warn( 'THREE.Vector3: .getPositionFromMatrix() has been renamed to .setFromMatrixPosition().' );
+ return this.setFromMatrixPosition( m );
+
+ },
+ getScaleFromMatrix: function ( m ) {
+
+ console.warn( 'THREE.Vector3: .getScaleFromMatrix() has been renamed to .setFromMatrixScale().' );
+ return this.setFromMatrixScale( m );
+
+ },
+ getColumnFromMatrix: function ( index, matrix ) {
+
+ console.warn( 'THREE.Vector3: .getColumnFromMatrix() has been renamed to .setFromMatrixColumn().' );
+ return this.setFromMatrixColumn( matrix, index );
+
+ },
+ applyProjection: function ( m ) {
+
+ console.warn( 'THREE.Vector3: .applyProjection() has been removed. Use .applyMatrix4( m ) instead.' );
+ return this.applyMatrix4( m );
+
+ },
+ fromAttribute: function ( attribute, index, offset ) {
+
+ console.warn( 'THREE.Vector3: .fromAttribute() has been renamed to .fromBufferAttribute().' );
+ return this.fromBufferAttribute( attribute, index, offset );
+
+ },
+ distanceToManhattan: function ( v ) {
+
+ console.warn( 'THREE.Vector3: .distanceToManhattan() has been renamed to .manhattanDistanceTo().' );
+ return this.manhattanDistanceTo( v );
+
+ },
+ lengthManhattan: function () {
+
+ console.warn( 'THREE.Vector3: .lengthManhattan() has been renamed to .manhattanLength().' );
+ return this.manhattanLength();
+
+ }
+
+} );
+
+Object.assign( Vector4.prototype, {
+
+ fromAttribute: function ( attribute, index, offset ) {
+
+ console.warn( 'THREE.Vector4: .fromAttribute() has been renamed to .fromBufferAttribute().' );
+ return this.fromBufferAttribute( attribute, index, offset );
+
+ },
+ lengthManhattan: function () {
+
+ console.warn( 'THREE.Vector4: .lengthManhattan() has been renamed to .manhattanLength().' );
+ return this.manhattanLength();
+
+ }
+
+} );
+
+//
+
+Object.assign( Object3D.prototype, {
+
+ getChildByName: function ( name ) {
+
+ console.warn( 'THREE.Object3D: .getChildByName() has been renamed to .getObjectByName().' );
+ return this.getObjectByName( name );
+
+ },
+ renderDepth: function () {
+
+ console.warn( 'THREE.Object3D: .renderDepth has been removed. Use .renderOrder, instead.' );
+
+ },
+ translate: function ( distance, axis ) {
+
+ console.warn( 'THREE.Object3D: .translate() has been removed. Use .translateOnAxis( axis, distance ) instead.' );
+ return this.translateOnAxis( axis, distance );
+
+ },
+ getWorldRotation: function () {
+
+ console.error( 'THREE.Object3D: .getWorldRotation() has been removed. Use THREE.Object3D.getWorldQuaternion( target ) instead.' );
+
+ },
+ applyMatrix: function ( matrix ) {
+
+ console.warn( 'THREE.Object3D: .applyMatrix() has been renamed to .applyMatrix4().' );
+ return this.applyMatrix4( matrix );
+
+ }
+
+} );
+
+Object.defineProperties( Object3D.prototype, {
+
+ eulerOrder: {
+ get: function () {
+
+ console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' );
+ return this.rotation.order;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.Object3D: .eulerOrder is now .rotation.order.' );
+ this.rotation.order = value;
+
+ }
+ },
+ useQuaternion: {
+ get: function () {
+
+ console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' );
+
+ },
+ set: function () {
+
+ console.warn( 'THREE.Object3D: .useQuaternion has been removed. The library now uses quaternions by default.' );
+
+ }
+ }
+
+} );
+
+Object.assign( Mesh.prototype, {
+
+ setDrawMode: function () {
+
+ console.error( 'THREE.Mesh: .setDrawMode() has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.' );
+
+ },
+
+} );
+
+Object.defineProperties( Mesh.prototype, {
+
+ drawMode: {
+ get: function () {
+
+ console.error( 'THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode.' );
+ return TrianglesDrawMode;
+
+ },
+ set: function () {
+
+ console.error( 'THREE.Mesh: .drawMode has been removed. The renderer now always assumes THREE.TrianglesDrawMode. Transform your geometry via BufferGeometryUtils.toTrianglesDrawMode() if necessary.' );
+
+ }
+ }
+
+} );
+
+Object.defineProperties( LOD.prototype, {
+
+ objects: {
+ get: function () {
+
+ console.warn( 'THREE.LOD: .objects has been renamed to .levels.' );
+ return this.levels;
+
+ }
+ }
+
+} );
+
+Object.defineProperty( Skeleton.prototype, 'useVertexTexture', {
+
+ get: function () {
+
+ console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' );
+
+ },
+ set: function () {
+
+ console.warn( 'THREE.Skeleton: useVertexTexture has been removed.' );
+
+ }
+
+} );
+
+SkinnedMesh.prototype.initBones = function () {
+
+ console.error( 'THREE.SkinnedMesh: initBones() has been removed.' );
+
+};
+
+Object.defineProperty( Curve.prototype, '__arcLengthDivisions', {
+
+ get: function () {
+
+ console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' );
+ return this.arcLengthDivisions;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.Curve: .__arcLengthDivisions is now .arcLengthDivisions.' );
+ this.arcLengthDivisions = value;
+
+ }
+
+} );
+
+//
+
+PerspectiveCamera.prototype.setLens = function ( focalLength, filmGauge ) {
+
+ console.warn( 'THREE.PerspectiveCamera.setLens is deprecated. ' +
+ 'Use .setFocalLength and .filmGauge for a photographic setup.' );
+
+ if ( filmGauge !== undefined ) this.filmGauge = filmGauge;
+ this.setFocalLength( focalLength );
+
+};
+
+//
+
+Object.defineProperties( Light.prototype, {
+ onlyShadow: {
+ set: function () {
+
+ console.warn( 'THREE.Light: .onlyShadow has been removed.' );
+
+ }
+ },
+ shadowCameraFov: {
+ set: function ( value ) {
+
+ console.warn( 'THREE.Light: .shadowCameraFov is now .shadow.camera.fov.' );
+ this.shadow.camera.fov = value;
+
+ }
+ },
+ shadowCameraLeft: {
+ set: function ( value ) {
+
+ console.warn( 'THREE.Light: .shadowCameraLeft is now .shadow.camera.left.' );
+ this.shadow.camera.left = value;
+
+ }
+ },
+ shadowCameraRight: {
+ set: function ( value ) {
+
+ console.warn( 'THREE.Light: .shadowCameraRight is now .shadow.camera.right.' );
+ this.shadow.camera.right = value;
+
+ }
+ },
+ shadowCameraTop: {
+ set: function ( value ) {
+
+ console.warn( 'THREE.Light: .shadowCameraTop is now .shadow.camera.top.' );
+ this.shadow.camera.top = value;
+
+ }
+ },
+ shadowCameraBottom: {
+ set: function ( value ) {
+
+ console.warn( 'THREE.Light: .shadowCameraBottom is now .shadow.camera.bottom.' );
+ this.shadow.camera.bottom = value;
+
+ }
+ },
+ shadowCameraNear: {
+ set: function ( value ) {
+
+ console.warn( 'THREE.Light: .shadowCameraNear is now .shadow.camera.near.' );
+ this.shadow.camera.near = value;
+
+ }
+ },
+ shadowCameraFar: {
+ set: function ( value ) {
+
+ console.warn( 'THREE.Light: .shadowCameraFar is now .shadow.camera.far.' );
+ this.shadow.camera.far = value;
+
+ }
+ },
+ shadowCameraVisible: {
+ set: function () {
+
+ console.warn( 'THREE.Light: .shadowCameraVisible has been removed. Use new THREE.CameraHelper( light.shadow.camera ) instead.' );
+
+ }
+ },
+ shadowBias: {
+ set: function ( value ) {
+
+ console.warn( 'THREE.Light: .shadowBias is now .shadow.bias.' );
+ this.shadow.bias = value;
+
+ }
+ },
+ shadowDarkness: {
+ set: function () {
+
+ console.warn( 'THREE.Light: .shadowDarkness has been removed.' );
+
+ }
+ },
+ shadowMapWidth: {
+ set: function ( value ) {
+
+ console.warn( 'THREE.Light: .shadowMapWidth is now .shadow.mapSize.width.' );
+ this.shadow.mapSize.width = value;
+
+ }
+ },
+ shadowMapHeight: {
+ set: function ( value ) {
+
+ console.warn( 'THREE.Light: .shadowMapHeight is now .shadow.mapSize.height.' );
+ this.shadow.mapSize.height = value;
+
+ }
+ }
+} );
+
+//
+
+Object.defineProperties( BufferAttribute.prototype, {
+
+ length: {
+ get: function () {
+
+ console.warn( 'THREE.BufferAttribute: .length has been deprecated. Use .count instead.' );
+ return this.array.length;
+
+ }
+ },
+ dynamic: {
+ get: function () {
+
+ console.warn( 'THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead.' );
+ return this.usage === DynamicDrawUsage;
+
+ },
+ set: function ( /* value */ ) {
+
+ console.warn( 'THREE.BufferAttribute: .dynamic has been deprecated. Use .usage instead.' );
+ this.setUsage( DynamicDrawUsage );
+
+ }
+ }
+
+} );
+
+Object.assign( BufferAttribute.prototype, {
+ setDynamic: function ( value ) {
+
+ console.warn( 'THREE.BufferAttribute: .setDynamic() has been deprecated. Use .setUsage() instead.' );
+ this.setUsage( value === true ? DynamicDrawUsage : StaticDrawUsage );
+ return this;
+
+ },
+ copyIndicesArray: function ( /* indices */ ) {
+
+ console.error( 'THREE.BufferAttribute: .copyIndicesArray() has been removed.' );
+
+ },
+ setArray: function ( /* array */ ) {
+
+ console.error( 'THREE.BufferAttribute: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers' );
+
+ }
+} );
+
+Object.assign( BufferGeometry.prototype, {
+
+ addIndex: function ( index ) {
+
+ console.warn( 'THREE.BufferGeometry: .addIndex() has been renamed to .setIndex().' );
+ this.setIndex( index );
+
+ },
+ addAttribute: function ( name, attribute ) {
+
+ console.warn( 'THREE.BufferGeometry: .addAttribute() has been renamed to .setAttribute().' );
+
+ if ( ! ( attribute && attribute.isBufferAttribute ) && ! ( attribute && attribute.isInterleavedBufferAttribute ) ) {
+
+ console.warn( 'THREE.BufferGeometry: .addAttribute() now expects ( name, attribute ).' );
+
+ return this.setAttribute( name, new BufferAttribute( arguments[ 1 ], arguments[ 2 ] ) );
+
+ }
+
+ if ( name === 'index' ) {
+
+ console.warn( 'THREE.BufferGeometry.addAttribute: Use .setIndex() for index attribute.' );
+ this.setIndex( attribute );
+
+ return this;
+
+ }
+
+ return this.setAttribute( name, attribute );
+
+ },
+ addDrawCall: function ( start, count, indexOffset ) {
+
+ if ( indexOffset !== undefined ) {
+
+ console.warn( 'THREE.BufferGeometry: .addDrawCall() no longer supports indexOffset.' );
+
+ }
+
+ console.warn( 'THREE.BufferGeometry: .addDrawCall() is now .addGroup().' );
+ this.addGroup( start, count );
+
+ },
+ clearDrawCalls: function () {
+
+ console.warn( 'THREE.BufferGeometry: .clearDrawCalls() is now .clearGroups().' );
+ this.clearGroups();
+
+ },
+ computeOffsets: function () {
+
+ console.warn( 'THREE.BufferGeometry: .computeOffsets() has been removed.' );
+
+ },
+ removeAttribute: function ( name ) {
+
+ console.warn( 'THREE.BufferGeometry: .removeAttribute() has been renamed to .deleteAttribute().' );
+
+ return this.deleteAttribute( name );
+
+ },
+ applyMatrix: function ( matrix ) {
+
+ console.warn( 'THREE.BufferGeometry: .applyMatrix() has been renamed to .applyMatrix4().' );
+ return this.applyMatrix4( matrix );
+
+ }
+
+} );
+
+Object.defineProperties( BufferGeometry.prototype, {
+
+ drawcalls: {
+ get: function () {
+
+ console.error( 'THREE.BufferGeometry: .drawcalls has been renamed to .groups.' );
+ return this.groups;
+
+ }
+ },
+ offsets: {
+ get: function () {
+
+ console.warn( 'THREE.BufferGeometry: .offsets has been renamed to .groups.' );
+ return this.groups;
+
+ }
+ }
+
+} );
+
+Object.defineProperties( InstancedBufferGeometry.prototype, {
+
+ maxInstancedCount: {
+ get: function () {
+
+ console.warn( 'THREE.InstancedBufferGeometry: .maxInstancedCount has been renamed to .instanceCount.' );
+ return this.instanceCount;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.InstancedBufferGeometry: .maxInstancedCount has been renamed to .instanceCount.' );
+ this.instanceCount = value;
+
+ }
+ }
+
+} );
+
+Object.defineProperties( Raycaster.prototype, {
+
+ linePrecision: {
+ get: function () {
+
+ console.warn( 'THREE.Raycaster: .linePrecision has been deprecated. Use .params.Line.threshold instead.' );
+ return this.params.Line.threshold;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.Raycaster: .linePrecision has been deprecated. Use .params.Line.threshold instead.' );
+ this.params.Line.threshold = value;
+
+ }
+ }
+
+} );
+
+Object.defineProperties( InterleavedBuffer.prototype, {
+
+ dynamic: {
+ get: function () {
+
+ console.warn( 'THREE.InterleavedBuffer: .length has been deprecated. Use .usage instead.' );
+ return this.usage === DynamicDrawUsage;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.InterleavedBuffer: .length has been deprecated. Use .usage instead.' );
+ this.setUsage( value );
+
+ }
+ }
+
+} );
+
+Object.assign( InterleavedBuffer.prototype, {
+ setDynamic: function ( value ) {
+
+ console.warn( 'THREE.InterleavedBuffer: .setDynamic() has been deprecated. Use .setUsage() instead.' );
+ this.setUsage( value === true ? DynamicDrawUsage : StaticDrawUsage );
+ return this;
+
+ },
+ setArray: function ( /* array */ ) {
+
+ console.error( 'THREE.InterleavedBuffer: .setArray has been removed. Use BufferGeometry .setAttribute to replace/resize attribute buffers' );
+
+ }
+} );
+
+//
+
+Object.assign( ExtrudeGeometry.prototype, {
+
+ getArrays: function () {
+
+ console.error( 'THREE.ExtrudeGeometry: .getArrays() has been removed.' );
+
+ },
+
+ addShapeList: function () {
+
+ console.error( 'THREE.ExtrudeGeometry: .addShapeList() has been removed.' );
+
+ },
+
+ addShape: function () {
+
+ console.error( 'THREE.ExtrudeGeometry: .addShape() has been removed.' );
+
+ }
+
+} );
+
+//
+
+Object.assign( Scene.prototype, {
+
+ dispose: function () {
+
+ console.error( 'THREE.Scene: .dispose() has been removed.' );
+
+ }
+
+} );
+
+//
+
+Object.defineProperties( Uniform.prototype, {
+
+ dynamic: {
+ set: function () {
+
+ console.warn( 'THREE.Uniform: .dynamic has been removed. Use object.onBeforeRender() instead.' );
+
+ }
+ },
+ onUpdate: {
+ value: function () {
+
+ console.warn( 'THREE.Uniform: .onUpdate() has been removed. Use object.onBeforeRender() instead.' );
+ return this;
+
+ }
+ }
+
+} );
+
+//
+
+Object.defineProperties( Material.prototype, {
+
+ wrapAround: {
+ get: function () {
+
+ console.warn( 'THREE.Material: .wrapAround has been removed.' );
+
+ },
+ set: function () {
+
+ console.warn( 'THREE.Material: .wrapAround has been removed.' );
+
+ }
+ },
+
+ overdraw: {
+ get: function () {
+
+ console.warn( 'THREE.Material: .overdraw has been removed.' );
+
+ },
+ set: function () {
+
+ console.warn( 'THREE.Material: .overdraw has been removed.' );
+
+ }
+ },
+
+ wrapRGB: {
+ get: function () {
+
+ console.warn( 'THREE.Material: .wrapRGB has been removed.' );
+ return new Color();
+
+ }
+ },
+
+ shading: {
+ get: function () {
+
+ console.error( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.' + this.type + ': .shading has been removed. Use the boolean .flatShading instead.' );
+ this.flatShading = ( value === FlatShading );
+
+ }
+ },
+
+ stencilMask: {
+ get: function () {
+
+ console.warn( 'THREE.' + this.type + ': .stencilMask has been removed. Use .stencilFuncMask instead.' );
+ return this.stencilFuncMask;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.' + this.type + ': .stencilMask has been removed. Use .stencilFuncMask instead.' );
+ this.stencilFuncMask = value;
+
+ }
+ }
+
+} );
+
+Object.defineProperties( MeshPhongMaterial.prototype, {
+
+ metal: {
+ get: function () {
+
+ console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead.' );
+ return false;
+
+ },
+ set: function () {
+
+ console.warn( 'THREE.MeshPhongMaterial: .metal has been removed. Use THREE.MeshStandardMaterial instead' );
+
+ }
+ }
+
+} );
+
+Object.defineProperties( MeshPhysicalMaterial.prototype, {
+
+ transparency: {
+ get: function () {
+
+ console.warn( 'THREE.MeshPhysicalMaterial: .transparency has been renamed to .transmission.' );
+ return this.transmission;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.MeshPhysicalMaterial: .transparency has been renamed to .transmission.' );
+ this.transmission = value;
+
+ }
+ }
+
+} );
+
+Object.defineProperties( ShaderMaterial.prototype, {
+
+ derivatives: {
+ get: function () {
+
+ console.warn( 'THREE.ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' );
+ return this.extensions.derivatives;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE. ShaderMaterial: .derivatives has been moved to .extensions.derivatives.' );
+ this.extensions.derivatives = value;
+
+ }
+ }
+
+} );
+
+//
+
+Object.assign( WebGLRenderer.prototype, {
+
+ clearTarget: function ( renderTarget, color, depth, stencil ) {
+
+ console.warn( 'THREE.WebGLRenderer: .clearTarget() has been deprecated. Use .setRenderTarget() and .clear() instead.' );
+ this.setRenderTarget( renderTarget );
+ this.clear( color, depth, stencil );
+
+ },
+ animate: function ( callback ) {
+
+ console.warn( 'THREE.WebGLRenderer: .animate() is now .setAnimationLoop().' );
+ this.setAnimationLoop( callback );
+
+ },
+ getCurrentRenderTarget: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .getCurrentRenderTarget() is now .getRenderTarget().' );
+ return this.getRenderTarget();
+
+ },
+ getMaxAnisotropy: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .getMaxAnisotropy() is now .capabilities.getMaxAnisotropy().' );
+ return this.capabilities.getMaxAnisotropy();
+
+ },
+ getPrecision: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .getPrecision() is now .capabilities.precision.' );
+ return this.capabilities.precision;
+
+ },
+ resetGLState: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .resetGLState() is now .state.reset().' );
+ return this.state.reset();
+
+ },
+ supportsFloatTextures: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .supportsFloatTextures() is now .extensions.get( \'OES_texture_float\' ).' );
+ return this.extensions.get( 'OES_texture_float' );
+
+ },
+ supportsHalfFloatTextures: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .supportsHalfFloatTextures() is now .extensions.get( \'OES_texture_half_float\' ).' );
+ return this.extensions.get( 'OES_texture_half_float' );
+
+ },
+ supportsStandardDerivatives: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .supportsStandardDerivatives() is now .extensions.get( \'OES_standard_derivatives\' ).' );
+ return this.extensions.get( 'OES_standard_derivatives' );
+
+ },
+ supportsCompressedTextureS3TC: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .supportsCompressedTextureS3TC() is now .extensions.get( \'WEBGL_compressed_texture_s3tc\' ).' );
+ return this.extensions.get( 'WEBGL_compressed_texture_s3tc' );
+
+ },
+ supportsCompressedTexturePVRTC: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .supportsCompressedTexturePVRTC() is now .extensions.get( \'WEBGL_compressed_texture_pvrtc\' ).' );
+ return this.extensions.get( 'WEBGL_compressed_texture_pvrtc' );
+
+ },
+ supportsBlendMinMax: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .supportsBlendMinMax() is now .extensions.get( \'EXT_blend_minmax\' ).' );
+ return this.extensions.get( 'EXT_blend_minmax' );
+
+ },
+ supportsVertexTextures: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .supportsVertexTextures() is now .capabilities.vertexTextures.' );
+ return this.capabilities.vertexTextures;
+
+ },
+ supportsInstancedArrays: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .supportsInstancedArrays() is now .extensions.get( \'ANGLE_instanced_arrays\' ).' );
+ return this.extensions.get( 'ANGLE_instanced_arrays' );
+
+ },
+ enableScissorTest: function ( boolean ) {
+
+ console.warn( 'THREE.WebGLRenderer: .enableScissorTest() is now .setScissorTest().' );
+ this.setScissorTest( boolean );
+
+ },
+ initMaterial: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .initMaterial() has been removed.' );
+
+ },
+ addPrePlugin: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .addPrePlugin() has been removed.' );
+
+ },
+ addPostPlugin: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .addPostPlugin() has been removed.' );
+
+ },
+ updateShadowMap: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .updateShadowMap() has been removed.' );
+
+ },
+ setFaceCulling: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .setFaceCulling() has been removed.' );
+
+ },
+ allocTextureUnit: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .allocTextureUnit() has been removed.' );
+
+ },
+ setTexture: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .setTexture() has been removed.' );
+
+ },
+ setTexture2D: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .setTexture2D() has been removed.' );
+
+ },
+ setTextureCube: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .setTextureCube() has been removed.' );
+
+ },
+ getActiveMipMapLevel: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .getActiveMipMapLevel() is now .getActiveMipmapLevel().' );
+ return this.getActiveMipmapLevel();
+
+ }
+
+} );
+
+Object.defineProperties( WebGLRenderer.prototype, {
+
+ shadowMapEnabled: {
+ get: function () {
+
+ return this.shadowMap.enabled;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderer: .shadowMapEnabled is now .shadowMap.enabled.' );
+ this.shadowMap.enabled = value;
+
+ }
+ },
+ shadowMapType: {
+ get: function () {
+
+ return this.shadowMap.type;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderer: .shadowMapType is now .shadowMap.type.' );
+ this.shadowMap.type = value;
+
+ }
+ },
+ shadowMapCullFace: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.' );
+ return undefined;
+
+ },
+ set: function ( /* value */ ) {
+
+ console.warn( 'THREE.WebGLRenderer: .shadowMapCullFace has been removed. Set Material.shadowSide instead.' );
+
+ }
+ },
+ context: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .context has been removed. Use .getContext() instead.' );
+ return this.getContext();
+
+ }
+ },
+ vr: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .vr has been renamed to .xr' );
+ return this.xr;
+
+ }
+ },
+ gammaInput: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead.' );
+ return false;
+
+ },
+ set: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .gammaInput has been removed. Set the encoding for textures via Texture.encoding instead.' );
+
+ }
+ },
+ gammaOutput: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead.' );
+ return false;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderer: .gammaOutput has been removed. Set WebGLRenderer.outputEncoding instead.' );
+ this.outputEncoding = ( value === true ) ? sRGBEncoding : LinearEncoding;
+
+ }
+ },
+ toneMappingWhitePoint: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .toneMappingWhitePoint has been removed.' );
+ return 1.0;
+
+ },
+ set: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .toneMappingWhitePoint has been removed.' );
+
+ }
+ },
+
+} );
+
+Object.defineProperties( WebGLShadowMap.prototype, {
+
+ cullFace: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.' );
+ return undefined;
+
+ },
+ set: function ( /* cullFace */ ) {
+
+ console.warn( 'THREE.WebGLRenderer: .shadowMap.cullFace has been removed. Set Material.shadowSide instead.' );
+
+ }
+ },
+ renderReverseSided: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.' );
+ return undefined;
+
+ },
+ set: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .shadowMap.renderReverseSided has been removed. Set Material.shadowSide instead.' );
+
+ }
+ },
+ renderSingleSided: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.' );
+ return undefined;
+
+ },
+ set: function () {
+
+ console.warn( 'THREE.WebGLRenderer: .shadowMap.renderSingleSided has been removed. Set Material.shadowSide instead.' );
+
+ }
+ }
+
+} );
+
+//
+
+Object.defineProperties( WebGLRenderTarget.prototype, {
+
+ wrapS: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' );
+ return this.texture.wrapS;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderTarget: .wrapS is now .texture.wrapS.' );
+ this.texture.wrapS = value;
+
+ }
+ },
+ wrapT: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' );
+ return this.texture.wrapT;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderTarget: .wrapT is now .texture.wrapT.' );
+ this.texture.wrapT = value;
+
+ }
+ },
+ magFilter: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' );
+ return this.texture.magFilter;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderTarget: .magFilter is now .texture.magFilter.' );
+ this.texture.magFilter = value;
+
+ }
+ },
+ minFilter: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' );
+ return this.texture.minFilter;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderTarget: .minFilter is now .texture.minFilter.' );
+ this.texture.minFilter = value;
+
+ }
+ },
+ anisotropy: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' );
+ return this.texture.anisotropy;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderTarget: .anisotropy is now .texture.anisotropy.' );
+ this.texture.anisotropy = value;
+
+ }
+ },
+ offset: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' );
+ return this.texture.offset;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderTarget: .offset is now .texture.offset.' );
+ this.texture.offset = value;
+
+ }
+ },
+ repeat: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' );
+ return this.texture.repeat;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderTarget: .repeat is now .texture.repeat.' );
+ this.texture.repeat = value;
+
+ }
+ },
+ format: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' );
+ return this.texture.format;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderTarget: .format is now .texture.format.' );
+ this.texture.format = value;
+
+ }
+ },
+ type: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' );
+ return this.texture.type;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderTarget: .type is now .texture.type.' );
+ this.texture.type = value;
+
+ }
+ },
+ generateMipmaps: {
+ get: function () {
+
+ console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' );
+ return this.texture.generateMipmaps;
+
+ },
+ set: function ( value ) {
+
+ console.warn( 'THREE.WebGLRenderTarget: .generateMipmaps is now .texture.generateMipmaps.' );
+ this.texture.generateMipmaps = value;
+
+ }
+ }
+
+} );
+
+//
+
+Object.defineProperties( Audio.prototype, {
+
+ load: {
+ value: function ( file ) {
+
+ console.warn( 'THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.' );
+ const scope = this;
+ const audioLoader = new AudioLoader();
+ audioLoader.load( file, function ( buffer ) {
+
+ scope.setBuffer( buffer );
+
+ } );
+ return this;
+
+ }
+ },
+ startTime: {
+ set: function () {
+
+ console.warn( 'THREE.Audio: .startTime is now .play( delay ).' );
+
+ }
+ }
+
+} );
+
+//
+
+CubeCamera.prototype.updateCubeMap = function ( renderer, scene ) {
+
+ console.warn( 'THREE.CubeCamera: .updateCubeMap() is now .update().' );
+ return this.update( renderer, scene );
+
+};
+
+CubeCamera.prototype.clear = function ( renderer, color, depth, stencil ) {
+
+ console.warn( 'THREE.CubeCamera: .clear() is now .renderTarget.clear().' );
+ return this.renderTarget.clear( renderer, color, depth, stencil );
+
+};
+
+ImageUtils.crossOrigin = undefined;
+
+ImageUtils.loadTexture = function ( url, mapping, onLoad, onError ) {
+
+ console.warn( 'THREE.ImageUtils.loadTexture has been deprecated. Use THREE.TextureLoader() instead.' );
+
+ const loader = new TextureLoader();
+ loader.setCrossOrigin( this.crossOrigin );
+
+ const texture = loader.load( url, onLoad, undefined, onError );
+
+ if ( mapping ) texture.mapping = mapping;
+
+ return texture;
+
+};
+
+ImageUtils.loadTextureCube = function ( urls, mapping, onLoad, onError ) {
+
+ console.warn( 'THREE.ImageUtils.loadTextureCube has been deprecated. Use THREE.CubeTextureLoader() instead.' );
+
+ const loader = new CubeTextureLoader();
+ loader.setCrossOrigin( this.crossOrigin );
+
+ const texture = loader.load( urls, onLoad, undefined, onError );
+
+ if ( mapping ) texture.mapping = mapping;
+
+ return texture;
+
+};
+
+ImageUtils.loadCompressedTexture = function () {
+
+ console.error( 'THREE.ImageUtils.loadCompressedTexture has been removed. Use THREE.DDSLoader instead.' );
+
+};
+
+ImageUtils.loadCompressedTextureCube = function () {
+
+ console.error( 'THREE.ImageUtils.loadCompressedTextureCube has been removed. Use THREE.DDSLoader instead.' );
+
+};
+
+if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
+
+ /* eslint-disable no-undef */
+ __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'register', { detail: {
+ revision: REVISION,
+ } } ) );
+ /* eslint-enable no-undef */
+
+}
+
+if ( typeof window !== 'undefined' ) {
+
+ if ( window.__THREE__ ) {
+
+ console.warn( 'WARNING: Multiple instances of Three.js being imported.' );
+
+ } else {
+
+ window.__THREE__ = REVISION;
+
+ }
+
+}
+
+const DEG2RAD = Math.PI / 180;
+const RAD2DEG = 180 / Math.PI;
+const WGS84A = 6378137.0;
+const WGS84B = 6356752.31424518;
+/**
+ * Convert coordinates from geodetic (WGS84) reference to local topocentric
+ * (ENU) reference.
+ *
+ * @param {number} lng Longitude in degrees.
+ * @param {number} lat Latitude in degrees.
+ * @param {number} alt Altitude in meters.
+ * @param {number} refLng Reference longitude in degrees.
+ * @param {number} refLat Reference latitude in degrees.
+ * @param {number} refAlt Reference altitude in meters.
+ * @returns {Array<number>} The x, y, z local topocentric ENU coordinates.
+ */
+function geodeticToEnu(lng, lat, alt, refLng, refLat, refAlt) {
+ const ecef = geodeticToEcef(lng, lat, alt);
+ return ecefToEnu(ecef[0], ecef[1], ecef[2], refLng, refLat, refAlt);
+}
+/**
+ * Convert coordinates from local topocentric (ENU) reference to
+ * geodetic (WGS84) reference.
+ *
+ * @param {number} x Topocentric ENU coordinate in East direction.
+ * @param {number} y Topocentric ENU coordinate in North direction.
+ * @param {number} z Topocentric ENU coordinate in Up direction.
+ * @param {number} refLng Reference longitude in degrees.
+ * @param {number} refLat Reference latitude in degrees.
+ * @param {number} refAlt Reference altitude in meters.
+ * @returns {Array<number>} The longitude, latitude in degrees
+ * and altitude in meters.
+ */
+function enuToGeodetic(x, y, z, refLng, refLat, refAlt) {
+ const ecef = enuToEcef(x, y, z, refLng, refLat, refAlt);
+ return ecefToGeodetic(ecef[0], ecef[1], ecef[2]);
+}
+/**
+ * Convert coordinates from Earth-Centered, Earth-Fixed (ECEF) reference
+ * to local topocentric (ENU) reference.
+ *
+ * @param {number} X ECEF X-value.
+ * @param {number} Y ECEF Y-value.
+ * @param {number} Z ECEF Z-value.
+ * @param {number} refLng Reference longitude in degrees.
+ * @param {number} refLat Reference latitude in degrees.
+ * @param {number} refAlt Reference altitude in meters.
+ * @returns {Array<number>} The x, y, z topocentric ENU coordinates in East, North
+ * and Up directions respectively.
+ */
+function ecefToEnu(X, Y, Z, refLng, refLat, refAlt) {
+ const refEcef = geodeticToEcef(refLng, refLat, refAlt);
+ const V = [
+ X - refEcef[0],
+ Y - refEcef[1],
+ Z - refEcef[2],
+ ];
+ refLng = refLng * DEG2RAD;
+ refLat = refLat * DEG2RAD;
+ const cosLng = Math.cos(refLng);
+ const sinLng = Math.sin(refLng);
+ const cosLat = Math.cos(refLat);
+ const sinLat = Math.sin(refLat);
+ const x = -sinLng * V[0] + cosLng * V[1];
+ const y = -sinLat * cosLng * V[0] - sinLat * sinLng * V[1] + cosLat * V[2];
+ const z = cosLat * cosLng * V[0] + cosLat * sinLng * V[1] + sinLat * V[2];
+ return [x, y, z];
+}
+/**
+ * Convert coordinates from local topocentric (ENU) reference
+ * to Earth-Centered, Earth-Fixed (ECEF) reference.
+ *
+ * @param {number} x Topocentric ENU coordinate in East direction.
+ * @param {number} y Topocentric ENU coordinate in North direction.
+ * @param {number} z Topocentric ENU coordinate in Up direction.
+ * @param {number} refLng Reference longitude in degrees.
+ * @param {number} refLat Reference latitude in degrees.
+ * @param {number} refAlt Reference altitude in meters.
+ * @returns {Array<number>} The X, Y, Z ECEF coordinates.
+ */
+function enuToEcef(x, y, z, refLng, refLat, refAlt) {
+ const refEcef = geodeticToEcef(refLng, refLat, refAlt);
+ refLng = refLng * DEG2RAD;
+ refLat = refLat * DEG2RAD;
+ const cosLng = Math.cos(refLng);
+ const sinLng = Math.sin(refLng);
+ const cosLat = Math.cos(refLat);
+ const sinLat = Math.sin(refLat);
+ const X = -sinLng * x
+ - sinLat * cosLng * y
+ + cosLat * cosLng * z
+ + refEcef[0];
+ const Y = cosLng * x
+ - sinLat * sinLng * y
+ + cosLat * sinLng * z
+ + refEcef[1];
+ const Z = cosLat * y +
+ sinLat * z +
+ refEcef[2];
+ return [X, Y, Z];
+}
+/**
+ * Convert coordinates from geodetic reference (WGS84) to Earth-Centered,
+ * Earth-Fixed (ECEF) reference.
+ *
+ * @param {number} lng Longitude in degrees.
+ * @param {number} lat Latitude in degrees.
+ * @param {number} alt Altitude in meters.
+ * @returns {Array<number>} The X, Y, Z ECEF coordinates.
+ */
+function geodeticToEcef(lng, lat, alt) {
+ const a = WGS84A;
+ const b = WGS84B;
+ lng = lng * DEG2RAD;
+ lat = lat * DEG2RAD;
+ const cosLng = Math.cos(lng);
+ const sinLng = Math.sin(lng);
+ const cosLat = Math.cos(lat);
+ const sinLat = Math.sin(lat);
+ const a2 = a * a;
+ const b2 = b * b;
+ const L = 1.0 / Math.sqrt(a2 * cosLat * cosLat + b2 * sinLat * sinLat);
+ const nhcl = (a2 * L + alt) * cosLat;
+ const X = nhcl * cosLng;
+ const Y = nhcl * sinLng;
+ const Z = (b2 * L + alt) * sinLat;
+ return [X, Y, Z];
+}
+/**
+ * Convert coordinates from Earth-Centered, Earth-Fixed (ECEF) reference
+ * to geodetic reference (WGS84).
+ *
+ * @param {number} X ECEF X-value.
+ * @param {number} Y ECEF Y-value.
+ * @param {number} Z ECEF Z-value.
+ * @returns {Array<number>} The longitude, latitude in degrees
+ * and altitude in meters.
+ */
+function ecefToGeodetic(X, Y, Z) {
+ const a = WGS84A;
+ const b = WGS84B;
+ const a2 = a * a;
+ const b2 = b * b;
+ const a2mb2 = a2 - b2;
+ const ea = Math.sqrt(a2mb2 / a2);
+ const eb = Math.sqrt(a2mb2 / b2);
+ const p = Math.sqrt(X * X + Y * Y);
+ const theta = Math.atan2(Z * a, p * b);
+ const sinTheta = Math.sin(theta);
+ const cosTheta = Math.cos(theta);
+ const lng = Math.atan2(Y, X);
+ const lat = Math.atan2(Z + eb * eb * b * sinTheta * sinTheta * sinTheta, p - ea * ea * a * cosTheta * cosTheta * cosTheta);
+ const sinLat = Math.sin(lat);
+ const cosLat = Math.cos(lat);
+ const N = a / Math.sqrt(1 - ea * ea * sinLat * sinLat);
+ const alt = p / cosLat - N;
+ return [
+ lng * RAD2DEG,
+ lat * RAD2DEG,
+ alt
+ ];
+}
+
+/**
+ * @class GraphCalculator
+ *
+ * @classdesc Represents a calculator for graph entities.
+ */
+class GraphCalculator {
+ /**
+ * Get the bounding box corners for a circle with radius of a threshold
+ * with center in a geodetic position.
+ *
+ * @param {LngLat} lngLat - Longitude, latitude to encode.
+ * @param {number} threshold - Threshold distance from the position in meters.
+ *
+ * @returns {Array<LngLat>} The south west and north east corners of the
+ * bounding box.
+ */
+ boundingBoxCorners(lngLat, threshold) {
+ const sw = enuToGeodetic(-threshold, -threshold, 0, lngLat.lng, lngLat.lat, 0);
+ const ne = enuToGeodetic(threshold, threshold, 0, lngLat.lng, lngLat.lat, 0);
+ return [
+ { lat: sw[1], lng: sw[0] },
+ { lat: ne[1], lng: ne[0] },
+ ];
+ }
+ /**
+ * Convert a compass angle to an angle axis rotation vector.
+ *
+ * @param {number} compassAngle - The compass angle in degrees.
+ * @param {number} orientation - The orientation of the original image.
+ *
+ * @returns {Array<number>} Angle axis rotation vector.
+ */
+ rotationFromCompass(compassAngle, orientation) {
+ let x = 0;
+ let y = 0;
+ let z = 0;
+ switch (orientation) {
+ case 1:
+ x = Math.PI / 2;
+ break;
+ case 3:
+ x = -Math.PI / 2;
+ z = Math.PI;
+ break;
+ case 6:
+ y = -Math.PI / 2;
+ z = -Math.PI / 2;
+ break;
+ case 8:
+ y = Math.PI / 2;
+ z = Math.PI / 2;
+ break;
+ }
+ const rz = new Matrix4()
+ .makeRotationZ(z);
+ const euler = new Euler(x, y, compassAngle * Math.PI / 180, "XYZ");
+ const re = new Matrix4()
+ .makeRotationFromEuler(euler);
+ const rotation = new Vector4()
+ .setAxisAngleFromRotationMatrix(re.multiply(rz));
+ return rotation
+ .multiplyScalar(rotation.w)
+ .toArray()
+ .slice(0, 3);
+ }
+}
+
+/**
+ * @class Image
+ *
+ * @classdesc Represents a image in the navigation graph.
+ *
+ * Explanation of position and bearing properties:
+ *
+ * When images are uploaded they will have GPS information in the EXIF, this is what
+ * is called `originalLngLat` {@link Image.originalLngLat}.
+ *
+ * When Structure from Motions has been run for a image a `computedLngLat` that
+ * differs from the `originalLngLat` will be created. It is different because
+ * GPS positions are not very exact and SfM aligns the camera positions according
+ * to the 3D reconstruction {@link Image.computedLngLat}.
+ *
+ * At last there exist a `lngLat` property which evaluates to
+ * the `computedLngLat` from SfM if it exists but falls back
+ * to the `originalLngLat` from the EXIF GPS otherwise {@link Image.lngLat}.
+ *
+ * Everything that is done in in the Viewer is based on the SfM positions,
+ * i.e. `computedLngLat`. That is why the smooth transitions go in the right
+ * direction (nd not in strange directions because of bad GPS).
+ *
+ * E.g. when placing a marker in the Viewer it is relative to the SfM
+ * position i.e. the `computedLngLat`.
+ *
+ * The same concept as above also applies to the compass angle (or bearing) properties
+ * `originalCa`, `computedCa` and `ca`.
+ */
+class Image$1 {
+ /**
+ * Create a new image instance.
+ *
+ * @description Images are always created internally by the library.
+ * Images can not be added to the library through any API method.
+ *
+ * @param {CoreImageEnt} core- Raw core image data.
+ * @ignore
+ */
+ constructor(core) {
+ if (!core) {
+ throw new Error(`Incorrect core image data ${core}`);
+ }
+ this._cache = null;
+ this._core = core;
+ this._spatial = null;
+ }
+ /**
+ * Get assets cached.
+ *
+ * @description The assets that need to be cached for this property
+ * to report true are the following: fill properties, image and mesh.
+ * The library ensures that the current image will always have the
+ * assets cached.
+ *
+ * @returns {boolean} Value indicating whether all assets have been
+ * cached.
+ *
+ * @ignore
+ */
+ get assetsCached() {
+ return this._core != null &&
+ this._spatial != null &&
+ this._cache != null &&
+ this._cache.image != null &&
+ this._cache.mesh != null;
+ }
+ /**
+ * Get cameraParameters.
+ *
+ * @description Will be undefined if SfM has
+ * not been run.
+ *
+ * Camera type dependent parameters.
+ *
+ * For perspective and fisheye camera types,
+ * the camera parameters array should be
+ * constructed according to
+ *
+ * `[focal, k1, k2]`
+ *
+ * where focal is the camera focal length,
+ * and k1, k2 are radial distortion parameters.
+ *
+ * For spherical camera type the camera
+ * parameters are unset or emtpy array.
+ *
+ * @returns {Array<number>} The parameters
+ * related to the camera type.
+ */
+ get cameraParameters() {
+ return this._spatial.camera_parameters;
+ }
+ /**
+ * Get cameraType.
+ *
+ * @description Will be undefined if SfM has not been run.
+ *
+ * @returns {string} The camera type that captured the image.
+ */
+ get cameraType() {
+ return this._spatial.camera_type;
+ }
+ /**
+ * Get capturedAt.
+ *
+ * @description Timestamp of the image capture date
+ * and time represented as a Unix epoch timestamp in milliseconds.
+ *
+ * @returns {number} Timestamp when the image was captured.
+ */
+ get capturedAt() {
+ return this._spatial.captured_at;
+ }
+ /**
+ * Get clusterId.
+ *
+ * @returns {string} Globally unique id of the SfM cluster to which
+ * the image belongs.
+ */
+ get clusterId() {
+ return !!this._spatial.cluster ?
+ this._spatial.cluster.id :
+ null;
+ }
+ /**
+ * Get clusterUrl.
+ *
+ * @returns {string} Url to the cluster reconstruction file.
+ *
+ * @ignore
+ */
+ get clusterUrl() {
+ return !!this._spatial.cluster ?
+ this._spatial.cluster.url :
+ null;
+ }
+ /**
+ * Get compassAngle.
+ *
+ * @description If the SfM computed compass angle exists it will
+ * be returned, otherwise the original EXIF compass angle.
+ *
+ * @returns {number} Compass angle, measured in degrees
+ * clockwise with respect to north.
+ */
+ get compassAngle() {
+ return this._spatial.computed_compass_angle != null ?
+ this._spatial.computed_compass_angle :
+ this._spatial.compass_angle;
+ }
+ /**
+ * Get complete.
+ *
+ * @description The library ensures that the current image will
+ * always be full.
+ *
+ * @returns {boolean} Value indicating whether the image has all
+ * properties filled.
+ *
+ * @ignore
+ */
+ get complete() {
+ return this._spatial != null;
+ }
+ /**
+ * Get computedAltitude.
+ *
+ * @description If SfM has not been run the computed altitude is
+ * set to a default value of two meters.
+ *
+ * @returns {number} Altitude, in meters.
+ */
+ get computedAltitude() {
+ return this._spatial.computed_altitude;
+ }
+ /**
+ * Get computedCompassAngle.
+ *
+ * @description Will not be set if SfM has not been run.
+ *
+ * @returns {number} SfM computed compass angle, measured
+ * in degrees clockwise with respect to north.
+ */
+ get computedCompassAngle() {
+ return this._spatial.computed_compass_angle;
+ }
+ /**
+ * Get computedLngLat.
+ *
+ * @description Will not be set if SfM has not been run.
+ *
+ * @returns {LngLat} SfM computed longitude, latitude in WGS84 datum,
+ * measured in degrees.
+ */
+ get computedLngLat() {
+ return this._core.computed_geometry;
+ }
+ /**
+ * Get creatorId.
+ *
+ * @description Note that the creator ID will not be set when using
+ * the Mapillary API.
+ *
+ * @returns {string} Globally unique id of the user who uploaded
+ * the image.
+ */
+ get creatorId() {
+ return this._spatial.creator.id;
+ }
+ /**
+ * Get creatorUsername.
+ *
+ * @description Note that the creator username will not be set when
+ * using the Mapillary API.
+ *
+ * @returns {string} Username of the creator who uploaded
+ * the image.
+ */
+ get creatorUsername() {
+ return this._spatial.creator.username;
+ }
+ /**
+ * Get exifOrientation.
+ *
+ * @returns {number} EXIF orientation of original image.
+ */
+ get exifOrientation() {
+ return this._spatial.exif_orientation;
+ }
+ /**
+ * Get height.
+ *
+ * @returns {number} Height of original image, not adjusted
+ * for orientation.
+ */
+ get height() {
+ return this._spatial.height;
+ }
+ /**
+ * Get image.
+ *
+ * @description The image will always be set on the current image.
+ *
+ * @returns {HTMLImageElement} Cached image element of the image.
+ */
+ get image() {
+ return this._cache.image;
+ }
+ /**
+ * Get image$.
+ *
+ * @returns {Observable<HTMLImageElement>} Observable emitting
+ * the cached image when it is updated.
+ *
+ * @ignore
+ */
+ get image$() {
+ return this._cache.image$;
+ }
+ /**
+ * Get id.
+ *
+ * @returns {string} Globally unique id of the image.
+ */
+ get id() {
+ return this._core.id;
+ }
+ /**
+ * Get lngLat.
+ *
+ * @description If the SfM computed longitude, latitude exist
+ * it will be returned, otherwise the original EXIF latitude
+ * longitude.
+ *
+ * @returns {LngLat} Longitude, latitude in WGS84 datum,
+ * measured in degrees.
+ */
+ get lngLat() {
+ return this._core.computed_geometry != null ?
+ this._core.computed_geometry :
+ this._core.geometry;
+ }
+ /**
+ * Get merged.
+ *
+ * @returns {boolean} Value indicating whether SfM has been
+ * run on the image and the image has been merged into a
+ * connected component.
+ */
+ get merged() {
+ return this._spatial != null &&
+ this._spatial.merge_id != null;
+ }
+ /**
+ * Get mergeId.
+ *
+ * @description Will not be set if SfM has not yet been run on
+ * image.
+ *
+ * @returns {stirng} Id of connected component to which image
+ * belongs after the aligning merge.
+ */
+ get mergeId() {
+ return this._spatial.merge_id;
+ }
+ /**
+ * Get mesh.
+ *
+ * @description The mesh will always be set on the current image.
+ *
+ * @returns {MeshContract} SfM triangulated mesh of reconstructed
+ * atomic 3D points.
+ */
+ get mesh() {
+ return this._cache.mesh;
+ }
+ /**
+ * Get originalAltitude.
+ *
+ * @returns {number} EXIF altitude, in meters, if available.
+ */
+ get originalAltitude() {
+ return this._spatial.altitude;
+ }
+ /**
+ * Get originalCompassAngle.
+ *
+ * @returns {number} Original EXIF compass angle, measured in
+ * degrees.
+ */
+ get originalCompassAngle() {
+ return this._spatial.compass_angle;
+ }
+ /**
+ * Get originalLngLat.
+ *
+ * @returns {LngLat} Original EXIF longitude, latitude in
+ * WGS84 datum, measured in degrees.
+ */
+ get originalLngLat() {
+ return this._core.geometry;
+ }
+ /**
+ * Get ownerId.
+ *
+ * @returns {string} Globally unique id of the owner to which
+ * the image belongs. If the image does not belong to an
+ * owner the owner id will be undefined.
+ */
+ get ownerId() {
+ return !!this._spatial.owner ?
+ this._spatial.owner.id :
+ null;
+ }
+ /**
+ * Get private.
+ *
+ * @returns {boolean} Value specifying if image is accessible to
+ * organization members only or to everyone.
+ */
+ get private() {
+ return this._spatial.private;
+ }
+ /**
+ * Get qualityScore.
+ *
+ * @returns {number} A number between zero and one
+ * determining the quality of the image. Blurriness
+ * (motion blur / out-of-focus), occlusion (camera
+ * mount, ego vehicle, water-drops), windshield
+ * reflections, bad illumination (exposure, glare),
+ * and bad weather condition (fog, rain, snow)
+ * affect the quality score.
+ *
+ * @description Value should be on the interval [0, 1].
+ */
+ get qualityScore() {
+ return this._spatial.quality_score;
+ }
+ /**
+ * Get rotation.
+ *
+ * @description Will not be set if SfM has not been run.
+ *
+ * @returns {Array<number>} Rotation vector in angle axis representation.
+ */
+ get rotation() {
+ return this._spatial.computed_rotation;
+ }
+ /**
+ * Get scale.
+ *
+ * @description Will not be set if SfM has not been run.
+ *
+ * @returns {number} Scale of reconstruction the image
+ * belongs to.
+ */
+ get scale() {
+ return this._spatial.atomic_scale;
+ }
+ /**
+ * Get sequenceId.
+ *
+ * @returns {string} Globally unique id of the sequence
+ * to which the image belongs.
+ */
+ get sequenceId() {
+ return !!this._core.sequence ?
+ this._core.sequence.id :
+ null;
+ }
+ /**
+ * Get sequenceEdges.
+ *
+ * @returns {NavigationEdgeStatus} Value describing the status of the
+ * sequence edges.
+ *
+ * @ignore
+ */
+ get sequenceEdges() {
+ return this._cache.sequenceEdges;
+ }
+ /**
+ * Get sequenceEdges$.
+ *
+ * @description Internal observable, should not be used as an API.
+ *
+ * @returns {Observable<NavigationEdgeStatus>} Observable emitting
+ * values describing the status of the sequence edges.
+ *
+ * @ignore
+ */
+ get sequenceEdges$() {
+ return this._cache.sequenceEdges$;
+ }
+ /**
+ * Get spatialEdges.
+ *
+ * @returns {NavigationEdgeStatus} Value describing the status of the
+ * spatial edges.
+ *
+ * @ignore
+ */
+ get spatialEdges() {
+ return this._cache.spatialEdges;
+ }
+ /**
+ * Get spatialEdges$.
+ *
+ * @description Internal observable, should not be used as an API.
+ *
+ * @returns {Observable<NavigationEdgeStatus>} Observable emitting
+ * values describing the status of the spatial edges.
+ *
+ * @ignore
+ */
+ get spatialEdges$() {
+ return this._cache.spatialEdges$;
+ }
+ /**
+ * Get width.
+ *
+ * @returns {number} Width of original image, not
+ * adjusted for orientation.
+ */
+ get width() {
+ return this._spatial.width;
+ }
+ /**
+ * Cache the image and mesh assets.
+ *
+ * @description The assets are always cached internally by the
+ * library prior to setting a image as the current image.
+ *
+ * @returns {Observable<Image>} Observable emitting this image whenever the
+ * load status has changed and when the mesh or image has been fully loaded.
+ *
+ * @ignore
+ */
+ cacheAssets$() {
+ return this._cache
+ .cacheAssets$(this._spatial, this.merged)
+ .pipe(map(() => { return this; }));
+ }
+ /**
+ * Cache the image asset.
+ *
+ * @description Use for caching a differently sized image than
+ * the one currently held by the image.
+ *
+ * @returns {Observable<Image>} Observable emitting this image whenever the
+ * load status has changed and when the mesh or image has been fully loaded.
+ *
+ * @ignore
+ */
+ cacheImage$() {
+ return this._cache
+ .cacheImage$(this._spatial)
+ .pipe(map(() => { return this; }));
+ }
+ /**
+ * Cache the sequence edges.
+ *
+ * @description The sequence edges are cached asynchronously
+ * internally by the library.
+ *
+ * @param {Array<NavigationEdge>} edges - Sequence edges to cache.
+ * @ignore
+ */
+ cacheSequenceEdges(edges) {
+ this._cache.cacheSequenceEdges(edges);
+ }
+ /**
+ * Cache the spatial edges.
+ *
+ * @description The spatial edges are cached asynchronously
+ * internally by the library.
+ *
+ * @param {Array<NavigationEdge>} edges - Spatial edges to cache.
+ * @ignore
+ */
+ cacheSpatialEdges(edges) {
+ this._cache.cacheSpatialEdges(edges);
+ }
+ /**
+ * Dispose the image.
+ *
+ * @description Disposes all cached assets.
+ * @ignore
+ */
+ dispose() {
+ if (this._cache != null) {
+ this._cache.dispose();
+ this._cache = null;
+ }
+ this._core = null;
+ this._spatial = null;
+ }
+ /**
+ * Initialize the image cache.
+ *
+ * @description The image cache is initialized internally by
+ * the library.
+ *
+ * @param {ImageCache} cache - The image cache to set as cache.
+ * @ignore
+ */
+ initializeCache(cache) {
+ if (this._cache != null) {
+ throw new Error(`Image cache already initialized (${this.id}).`);
+ }
+ this._cache = cache;
+ }
+ /**
+ * Complete an image with spatial properties.
+ *
+ * @description The image is completed internally by
+ * the library.
+ *
+ * @param {SpatialImageEnt} fill - The spatial image struct.
+ * @ignore
+ */
+ makeComplete(fill) {
+ if (fill == null) {
+ throw new Error("Fill can not be null.");
+ }
+ this._spatial = fill;
+ }
+ /**
+ * Reset the sequence edges.
+ *
+ * @ignore
+ */
+ resetSequenceEdges() {
+ this._cache.resetSequenceEdges();
+ }
+ /**
+ * Reset the spatial edges.
+ *
+ * @ignore
+ */
+ resetSpatialEdges() {
+ this._cache.resetSpatialEdges();
+ }
+ /**
+ * Clears the image and mesh assets, aborts
+ * any outstanding requests and resets edges.
+ *
+ * @ignore
+ */
+ uncache() {
+ if (this._cache == null) {
+ return;
+ }
+ this._cache.dispose();
+ this._cache = null;
+ }
+}
+
+/**
+ * @class ImageCache
+ *
+ * @classdesc Represents the cached properties of a image.
+ */
+class ImageCache {
+ /**
+ * Create a new image cache instance.
+ */
+ constructor(provider) {
+ this._disposed = false;
+ this._provider = provider;
+ this._image = null;
+ this._mesh = null;
+ this._sequenceEdges = { cached: false, edges: [] };
+ this._spatialEdges = { cached: false, edges: [] };
+ this._imageChanged$ = new Subject();
+ this._image$ = this._imageChanged$.pipe(startWith(null), publishReplay(1), refCount());
+ this._iamgeSubscription = this._image$.subscribe();
+ this._sequenceEdgesChanged$ = new Subject();
+ this._sequenceEdges$ = this._sequenceEdgesChanged$.pipe(startWith(this._sequenceEdges), publishReplay(1), refCount());
+ this._sequenceEdgesSubscription = this._sequenceEdges$.subscribe(() => { });
+ this._spatialEdgesChanged$ = new Subject();
+ this._spatialEdges$ = this._spatialEdgesChanged$.pipe(startWith(this._spatialEdges), publishReplay(1), refCount());
+ this._spatialEdgesSubscription = this._spatialEdges$.subscribe(() => { });
+ this._cachingAssets$ = null;
+ }
+ /**
+ * Get image.
+ *
+ * @description Will not be set when assets have not been cached
+ * or when the object has been disposed.
+ *
+ * @returns {HTMLImageElement} Cached image element of the image.
+ */
+ get image() {
+ return this._image;
+ }
+ /**
+ * Get image$.
+ *
+ * @returns {Observable<HTMLImageElement>} Observable emitting
+ * the cached image when it is updated.
+ */
+ get image$() {
+ return this._image$;
+ }
+ /**
+ * Get mesh.
+ *
+ * @description Will not be set when assets have not been cached
+ * or when the object has been disposed.
+ *
+ * @returns {MeshContract} SfM triangulated mesh of reconstructed
+ * atomic 3D points.
+ */
+ get mesh() {
+ return this._mesh;
+ }
+ /**
+ * Get sequenceEdges.
+ *
+ * @returns {NavigationEdgeStatus} Value describing the status of the
+ * sequence edges.
+ */
+ get sequenceEdges() {
+ return this._sequenceEdges;
+ }
+ /**
+ * Get sequenceEdges$.
+ *
+ * @returns {Observable<NavigationEdgeStatus>} Observable emitting
+ * values describing the status of the sequence edges.
+ */
+ get sequenceEdges$() {
+ return this._sequenceEdges$;
+ }
+ /**
+ * Get spatialEdges.
+ *
+ * @returns {NavigationEdgeStatus} Value describing the status of the
+ * spatial edges.
+ */
+ get spatialEdges() {
+ return this._spatialEdges;
+ }
+ /**
+ * Get spatialEdges$.
+ *
+ * @returns {Observable<NavigationEdgeStatus>} Observable emitting
+ * values describing the status of the spatial edges.
+ */
+ get spatialEdges$() {
+ return this._spatialEdges$;
+ }
+ /**
+ * Cache the image and mesh assets.
+ *
+ * @param {SpatialImageEnt} spatial - Spatial props of the image to cache.
+ * @param {boolean} spherical - Value indicating whether image is a spherical.
+ * @param {boolean} merged - Value indicating whether image is merged.
+ * @returns {Observable<ImageCache>} Observable emitting this image
+ * cache whenever the load status has changed and when the mesh or image
+ * has been fully loaded.
+ */
+ cacheAssets$(spatial, merged) {
+ if (this._cachingAssets$ != null) {
+ return this._cachingAssets$;
+ }
+ this._cachingAssets$ = combineLatest(this._cacheImage$(spatial), this._cacheMesh$(spatial, merged)).pipe(map(([image, mesh]) => {
+ this._image = image;
+ this._mesh = mesh;
+ return this;
+ }), finalize(() => {
+ this._cachingAssets$ = null;
+ }), publishReplay(1), refCount());
+ this._cachingAssets$.pipe(first((imageCache) => {
+ return !!imageCache._image;
+ }))
+ .subscribe(() => {
+ this._imageChanged$.next(this._image);
+ }, () => { });
+ return this._cachingAssets$;
+ }
+ /**
+ * Cache an image with a higher resolution than the current one.
+ *
+ * @param {SpatialImageEnt} spatial - Spatial props.
+ * @returns {Observable<ImageCache>} Observable emitting a single item,
+ * the image cache, when the image has been cached. If supplied image
+ * size is not larger than the current image size the image cache is
+ * returned immediately.
+ */
+ cacheImage$(spatial) {
+ if (this._image != null) {
+ return of(this);
+ }
+ const cacheImage$ = this._cacheImage$(spatial)
+ .pipe(first((image) => {
+ return !!image;
+ }), tap((image) => {
+ this._disposeImage();
+ this._image = image;
+ }), map(() => {
+ return this;
+ }), publishReplay(1), refCount());
+ cacheImage$
+ .subscribe(() => {
+ this._imageChanged$.next(this._image);
+ }, () => { });
+ return cacheImage$;
+ }
+ /**
+ * Cache the sequence edges.
+ *
+ * @param {Array<NavigationEdge>} edges - Sequence edges to cache.
+ */
+ cacheSequenceEdges(edges) {
+ this._sequenceEdges = { cached: true, edges: edges };
+ this._sequenceEdgesChanged$.next(this._sequenceEdges);
+ }
+ /**
+ * Cache the spatial edges.
+ *
+ * @param {Array<NavigationEdge>} edges - Spatial edges to cache.
+ */
+ cacheSpatialEdges(edges) {
+ this._spatialEdges = { cached: true, edges: edges };
+ this._spatialEdgesChanged$.next(this._spatialEdges);
+ }
+ /**
+ * Dispose the image cache.
+ *
+ * @description Disposes all cached assets and unsubscribes to
+ * all streams.
+ */
+ dispose() {
+ this._iamgeSubscription.unsubscribe();
+ this._sequenceEdgesSubscription.unsubscribe();
+ this._spatialEdgesSubscription.unsubscribe();
+ this._disposeImage();
+ this._mesh = null;
+ this._sequenceEdges = { cached: false, edges: [] };
+ this._spatialEdges = { cached: false, edges: [] };
+ this._imageChanged$.next(null);
+ this._sequenceEdgesChanged$.next(this._sequenceEdges);
+ this._spatialEdgesChanged$.next(this._spatialEdges);
+ this._disposed = true;
+ if (this._imageAborter != null) {
+ this._imageAborter();
+ this._imageAborter = null;
+ }
+ if (this._meshAborter != null) {
+ this._meshAborter();
+ this._meshAborter = null;
+ }
+ }
+ /**
+ * Reset the sequence edges.
+ */
+ resetSequenceEdges() {
+ this._sequenceEdges = { cached: false, edges: [] };
+ this._sequenceEdgesChanged$.next(this._sequenceEdges);
+ }
+ /**
+ * Reset the spatial edges.
+ */
+ resetSpatialEdges() {
+ this._spatialEdges = { cached: false, edges: [] };
+ this._spatialEdgesChanged$.next(this._spatialEdges);
+ }
+ /**
+ * Cache the image.
+ *
+ * @param {SpatialImageEnt} spatial - Spatial image.
+ * @param {boolean} spherical - Value indicating whether image is a spherical.
+ * @returns {Observable<ILoadStatusObject<HTMLImageElement>>} Observable
+ * emitting a load status object every time the load status changes
+ * and completes when the image is fully loaded.
+ */
+ _cacheImage$(spatial) {
+ return Observable.create((subscriber) => {
+ const abort = new Promise((_, reject) => {
+ this._imageAborter = reject;
+ });
+ const url = spatial.thumb.url;
+ if (!url) {
+ const thumbId = spatial.thumb.id;
+ const message = `Incorrect thumb URL for ${spatial.id} ` +
+ `(${thumbId}, ${url})`;
+ subscriber.error(new Error(message));
+ return;
+ }
+ this._provider.getImageBuffer(url, abort)
+ .then((buffer) => {
+ this._imageAborter = null;
+ const image = new Image();
+ image.crossOrigin = "Anonymous";
+ image.onload = () => {
+ if (this._disposed) {
+ window.URL.revokeObjectURL(image.src);
+ const message = `Image load was aborted (${url})`;
+ subscriber.error(new Error(message));
+ return;
+ }
+ subscriber.next(image);
+ subscriber.complete();
+ };
+ image.onerror = () => {
+ this._imageAborter = null;
+ subscriber.error(new Error(`Failed to load image (${url})`));
+ };
+ const blob = new Blob([buffer]);
+ image.src = window.URL.createObjectURL(blob);
+ }, (error) => {
+ this._imageAborter = null;
+ subscriber.error(error);
+ });
+ });
+ }
+ /**
+ * Cache the mesh.
+ *
+ * @param {SpatialImageEnt} spatial - Spatial props.
+ * @param {boolean} merged - Value indicating whether image is merged.
+ * @returns {Observable<ILoadStatusObject<MeshContract>>} Observable emitting
+ * a load status object every time the load status changes and completes
+ * when the mesh is fully loaded.
+ */
+ _cacheMesh$(spatial, merged) {
+ return Observable.create((subscriber) => {
+ if (!merged) {
+ subscriber.next(this._createEmptyMesh());
+ subscriber.complete();
+ return;
+ }
+ const url = spatial.mesh.url;
+ if (!url) {
+ const meshId = spatial.mesh.id;
+ const message = `Incorrect mesh URL for ${spatial.id} ` +
+ `(${meshId}, ${url})`;
+ console.warn(message);
+ subscriber.next(this._createEmptyMesh());
+ subscriber.complete();
+ return;
+ }
+ const abort = new Promise((_, reject) => {
+ this._meshAborter = reject;
+ });
+ this._provider.getMesh(url, abort)
+ .then((mesh) => {
+ this._meshAborter = null;
+ if (this._disposed) {
+ return;
+ }
+ subscriber.next(mesh);
+ subscriber.complete();
+ }, (error) => {
+ this._meshAborter = null;
+ console.error(error);
+ subscriber.next(this._createEmptyMesh());
+ subscriber.complete();
+ });
+ });
+ }
+ /**
+ * Create a load status object with an empty mesh.
+ *
+ * @returns {ILoadStatusObject<MeshContract>} Load status object
+ * with empty mesh.
+ */
+ _createEmptyMesh() {
+ return { faces: [], vertices: [] };
+ }
+ _disposeImage() {
+ if (this._image != null) {
+ window.URL.revokeObjectURL(this._image.src);
+ }
+ this._image = null;
+ }
+}
+
+/**
+ * @class Sequence
+ *
+ * @classdesc Represents a sequence of ordered images.
+ */
+class Sequence {
+ /**
+ * Create a new sequene instance.
+ *
+ * @param {SequenceEnt} sequence - Raw sequence data.
+ */
+ constructor(sequence) {
+ this._id = sequence.id;
+ this._imageIds = sequence.image_ids;
+ }
+ /**
+ * Get id.
+ *
+ * @returns {string} Unique sequence id.
+ */
+ get id() {
+ return this._id;
+ }
+ /**
+ * Get ids.
+ *
+ * @returns {Array<string>} Array of ordered image ids in the sequence.
+ */
+ get imageIds() {
+ return this._imageIds;
+ }
+ /**
+ * Dispose the sequence.
+ *
+ * @description Disposes all cached assets.
+ */
+ dispose() {
+ this._id = null;
+ this._imageIds = null;
+ }
+ /**
+ * Find the next image id in the sequence with respect to
+ * the provided image id.
+ *
+ * @param {string} id - Reference image id.
+ * @returns {string} Next id in sequence if it exists, null otherwise.
+ */
+ findNext(id) {
+ let i = this._imageIds.indexOf(id);
+ if ((i + 1) >= this._imageIds.length || i === -1) {
+ return null;
+ }
+ else {
+ return this._imageIds[i + 1];
+ }
+ }
+ /**
+ * Find the previous image id in the sequence with respect to
+ * the provided image id.
+ *
+ * @param {string} id - Reference image id.
+ * @returns {string} Previous id in sequence if it exists, null otherwise.
+ */
+ findPrev(id) {
+ let i = this._imageIds.indexOf(id);
+ if (i === 0 || i === -1) {
+ return null;
+ }
+ else {
+ return this._imageIds[i - 1];
+ }
+ }
+}
+
+class EdgeCalculatorCoefficients {
+ constructor() {
+ this.sphericalPreferredDistance = 2;
+ this.sphericalMotion = 2;
+ this.sphericalSequencePenalty = 1;
+ this.sphericalMergeCCPenalty = 4;
+ this.stepPreferredDistance = 4;
+ this.stepMotion = 3;
+ this.stepRotation = 4;
+ this.stepSequencePenalty = 2;
+ this.stepMergeCCPenalty = 6;
+ this.similarDistance = 2;
+ this.similarRotation = 3;
+ this.turnDistance = 4;
+ this.turnMotion = 2;
+ this.turnSequencePenalty = 1;
+ this.turnMergeCCPenalty = 4;
+ }
+}
+
+/**
+ * Enumeration for edge directions
+ * @enum {number}
+ * @readonly
+ * @description Directions for edges in image graph describing
+ * sequence, spatial and image type relations between nodes.
+ */
+var NavigationDirection;
+(function (NavigationDirection) {
+ /**
+ * Next image in the sequence.
+ */
+ NavigationDirection[NavigationDirection["Next"] = 0] = "Next";
+ /**
+ * Previous image in the sequence.
+ */
+ NavigationDirection[NavigationDirection["Prev"] = 1] = "Prev";
+ /**
+ * Step to the left keeping viewing direction.
+ */
+ NavigationDirection[NavigationDirection["StepLeft"] = 2] = "StepLeft";
+ /**
+ * Step to the right keeping viewing direction.
+ */
+ NavigationDirection[NavigationDirection["StepRight"] = 3] = "StepRight";
+ /**
+ * Step forward keeping viewing direction.
+ */
+ NavigationDirection[NavigationDirection["StepForward"] = 4] = "StepForward";
+ /**
+ * Step backward keeping viewing direction.
+ */
+ NavigationDirection[NavigationDirection["StepBackward"] = 5] = "StepBackward";
+ /**
+ * Turn 90 degrees counter clockwise.
+ */
+ NavigationDirection[NavigationDirection["TurnLeft"] = 6] = "TurnLeft";
+ /**
+ * Turn 90 degrees clockwise.
+ */
+ NavigationDirection[NavigationDirection["TurnRight"] = 7] = "TurnRight";
+ /**
+ * Turn 180 degrees.
+ */
+ NavigationDirection[NavigationDirection["TurnU"] = 8] = "TurnU";
+ /**
+ * Spherical in general direction.
+ */
+ NavigationDirection[NavigationDirection["Spherical"] = 9] = "Spherical";
+ /**
+ * Looking in roughly the same direction at rougly the same position.
+ */
+ NavigationDirection[NavigationDirection["Similar"] = 10] = "Similar";
+})(NavigationDirection || (NavigationDirection = {}));
+
+class EdgeCalculatorDirections {
+ constructor() {
+ this.steps = {};
+ this.turns = {};
+ this.spherical = {};
+ this.steps[NavigationDirection.StepForward] = {
+ direction: NavigationDirection.StepForward,
+ motionChange: 0,
+ useFallback: true,
+ };
+ this.steps[NavigationDirection.StepBackward] = {
+ direction: NavigationDirection.StepBackward,
+ motionChange: Math.PI,
+ useFallback: true,
+ };
+ this.steps[NavigationDirection.StepLeft] = {
+ direction: NavigationDirection.StepLeft,
+ motionChange: Math.PI / 2,
+ useFallback: false,
+ };
+ this.steps[NavigationDirection.StepRight] = {
+ direction: NavigationDirection.StepRight,
+ motionChange: -Math.PI / 2,
+ useFallback: false,
+ };
+ this.turns[NavigationDirection.TurnLeft] = {
+ direction: NavigationDirection.TurnLeft,
+ directionChange: Math.PI / 2,
+ motionChange: Math.PI / 4,
+ };
+ this.turns[NavigationDirection.TurnRight] = {
+ direction: NavigationDirection.TurnRight,
+ directionChange: -Math.PI / 2,
+ motionChange: -Math.PI / 4,
+ };
+ this.turns[NavigationDirection.TurnU] = {
+ direction: NavigationDirection.TurnU,
+ directionChange: Math.PI,
+ motionChange: null,
+ };
+ this.spherical[NavigationDirection.StepForward] = {
+ direction: NavigationDirection.StepForward,
+ directionChange: 0,
+ next: NavigationDirection.StepLeft,
+ prev: NavigationDirection.StepRight,
+ };
+ this.spherical[NavigationDirection.StepBackward] = {
+ direction: NavigationDirection.StepBackward,
+ directionChange: Math.PI,
+ next: NavigationDirection.StepRight,
+ prev: NavigationDirection.StepLeft,
+ };
+ this.spherical[NavigationDirection.StepLeft] = {
+ direction: NavigationDirection.StepLeft,
+ directionChange: Math.PI / 2,
+ next: NavigationDirection.StepBackward,
+ prev: NavigationDirection.StepForward,
+ };
+ this.spherical[NavigationDirection.StepRight] = {
+ direction: NavigationDirection.StepRight,
+ directionChange: -Math.PI / 2,
+ next: NavigationDirection.StepForward,
+ prev: NavigationDirection.StepBackward,
+ };
+ }
+}
+
+class EdgeCalculatorSettings {
+ constructor() {
+ this.sphericalMinDistance = 0.1;
+ this.sphericalMaxDistance = 20;
+ this.sphericalPreferredDistance = 5;
+ this.sphericalMaxItems = 4;
+ this.sphericalMaxStepTurnChange = Math.PI / 8;
+ this.rotationMaxDistance = this.turnMaxRigDistance;
+ this.rotationMaxDirectionChange = Math.PI / 6;
+ this.rotationMaxVerticalDirectionChange = Math.PI / 8;
+ this.similarMaxDirectionChange = Math.PI / 8;
+ this.similarMaxDistance = 12;
+ this.similarMinTimeDifference = 12 * 3600 * 1000;
+ this.stepMaxDistance = 20;
+ this.stepMaxDirectionChange = Math.PI / 6;
+ this.stepMaxDrift = Math.PI / 6;
+ this.stepPreferredDistance = 4;
+ this.turnMaxDistance = 15;
+ this.turnMaxDirectionChange = 2 * Math.PI / 9;
+ this.turnMaxRigDistance = 0.65;
+ this.turnMinRigDirectionChange = Math.PI / 6;
+ }
+ get maxDistance() {
+ return Math.max(this.sphericalMaxDistance, this.similarMaxDistance, this.stepMaxDistance, this.turnMaxDistance);
+ }
+}
+
+/**
+ * @class MapillaryError
+ *
+ * @classdesc Generic Mapillary error.
+ */
+class MapillaryError extends Error {
+ constructor(message) {
+ super(message);
+ Object.setPrototypeOf(this, MapillaryError.prototype);
+ this.name = "MapillaryError";
+ }
+}
+
+class ArgumentMapillaryError extends MapillaryError {
+ constructor(message) {
+ super(message != null ? message : "The argument is not valid.");
+ Object.setPrototypeOf(this, ArgumentMapillaryError.prototype);
+ this.name = "ArgumentMapillaryError";
+ }
+}
+
+/**
+ * @class Spatial
+ *
+ * @classdesc Provides methods for scalar, vector and matrix calculations.
+ */
+class Spatial {
+ constructor() {
+ this._epsilon = 1e-9;
+ }
+ /**
+ * Converts azimuthal phi rotation (counter-clockwise with origin on X-axis) to
+ * bearing (clockwise with origin at north or Y-axis).
+ *
+ * @param {number} phi - Azimuthal phi angle in radians.
+ * @returns {number} Bearing in radians.
+ */
+ azimuthalToBearing(phi) {
+ return -phi + Math.PI / 2;
+ }
+ /**
+ * Converts degrees to radians.
+ *
+ * @param {number} deg - Degrees.
+ * @returns {number} Radians.
+ */
+ degToRad(deg) {
+ return Math.PI * deg / 180;
+ }
+ /**
+ * Converts radians to degrees.
+ *
+ * @param {number} rad - Radians.
+ * @returns {number} Degrees.
+ */
+ radToDeg(rad) {
+ return 180 * rad / Math.PI;
+ }
+ /**
+ * Creates a rotation matrix from an angle-axis vector.
+ *
+ * @param {Array<number>} angleAxis - Angle-axis representation of a rotation.
+ * @returns {THREE.Matrix4} Rotation matrix.
+ */
+ rotationMatrix(angleAxis) {
+ let axis = new Vector3(angleAxis[0], angleAxis[1], angleAxis[2]);
+ let angle = axis.length();
+ if (angle > 0) {
+ axis.normalize();
+ }
+ return new Matrix4().makeRotationAxis(axis, angle);
+ }
+ /**
+ * Rotates a vector according to a angle-axis rotation vector.
+ *
+ * @param {Array<number>} vector - Vector to rotate.
+ * @param {Array<number>} angleAxis - Angle-axis representation of a rotation.
+ * @returns {THREE.Vector3} Rotated vector.
+ */
+ rotate(vector, angleAxis) {
+ let v = new Vector3(vector[0], vector[1], vector[2]);
+ let rotationMatrix = this.rotationMatrix(angleAxis);
+ v.applyMatrix4(rotationMatrix);
+ return v;
+ }
+ /**
+ * Calculates the optical center from a rotation vector
+ * on the angle-axis representation and a translation vector
+ * according to C = -R^T t.
+ *
+ * @param {Array<number>} rotation - Angle-axis representation of a rotation.
+ * @param {Array<number>} translation - Translation vector.
+ * @returns {THREE.Vector3} Optical center.
+ */
+ opticalCenter(rotation, translation) {
+ let angleAxis = [-rotation[0], -rotation[1], -rotation[2]];
+ let vector = [-translation[0], -translation[1], -translation[2]];
+ return this.rotate(vector, angleAxis);
+ }
+ /**
+ * Calculates the viewing direction from a rotation vector
+ * on the angle-axis representation.
+ *
+ * @param {number[]} rotation - Angle-axis representation of a rotation.
+ * @returns {THREE.Vector3} Viewing direction.
+ */
+ viewingDirection(rotation) {
+ let angleAxis = [-rotation[0], -rotation[1], -rotation[2]];
+ return this.rotate([0, 0, 1], angleAxis);
+ }
+ /**
+ * Wrap a number on the interval [min, max].
+ *
+ * @param {number} value - Value to wrap.
+ * @param {number} min - Lower endpoint of interval.
+ * @param {number} max - Upper endpoint of interval.
+ * @returns {number} The wrapped number.
+ */
+ wrap(value, min, max) {
+ if (max < min) {
+ throw new Error("Invalid arguments: max must be larger than min.");
+ }
+ let interval = (max - min);
+ while (value > max || value < min) {
+ if (value > max) {
+ value = value - interval;
+ }
+ else if (value < min) {
+ value = value + interval;
+ }
+ }
+ return value;
+ }
+ /**
+ * Wrap an angle on the interval [-Pi, Pi].
+ *
+ * @param {number} angle - Value to wrap.
+ * @returns {number} Wrapped angle.
+ */
+ wrapAngle(angle) {
+ return this.wrap(angle, -Math.PI, Math.PI);
+ }
+ /**
+ * Limit the value to the interval [min, max] by changing the value to
+ * the nearest available one when it is outside the interval.
+ *
+ * @param {number} value - Value to clamp.
+ * @param {number} min - Minimum of the interval.
+ * @param {number} max - Maximum of the interval.
+ * @returns {number} Clamped value.
+ */
+ clamp(value, min, max) {
+ if (value < min) {
+ return min;
+ }
+ if (value > max) {
+ return max;
+ }
+ return value;
+ }
+ /**
+ * Calculates the counter-clockwise angle from the first
+ * vector (x1, y1)^T to the second (x2, y2)^T.
+ *
+ * @param {number} x1 - X coordinate of first vector.
+ * @param {number} y1 - Y coordinate of first vector.
+ * @param {number} x2 - X coordinate of second vector.
+ * @param {number} y2 - Y coordinate of second vector.
+ * @returns {number} Counter clockwise angle between the vectors.
+ */
+ angleBetweenVector2(x1, y1, x2, y2) {
+ let angle = Math.atan2(y2, x2) - Math.atan2(y1, x1);
+ return this.wrapAngle(angle);
+ }
+ /**
+ * Calculates the minimum (absolute) angle change for rotation
+ * from one angle to another on the [-Pi, Pi] interval.
+ *
+ * @param {number} angle1 - Start angle.
+ * @param {number} angle2 - Destination angle.
+ * @returns {number} Absolute angle change between angles.
+ */
+ angleDifference(angle1, angle2) {
+ let angle = angle2 - angle1;
+ return this.wrapAngle(angle);
+ }
+ /**
+ * Calculates the relative rotation angle between two
+ * angle-axis vectors.
+ *
+ * @param {number} rotation1 - First angle-axis vector.
+ * @param {number} rotation2 - Second angle-axis vector.
+ * @returns {number} Relative rotation angle.
+ */
+ relativeRotationAngle(rotation1, rotation2) {
+ let R1T = this.rotationMatrix([-rotation1[0], -rotation1[1], -rotation1[2]]);
+ let R2 = this.rotationMatrix(rotation2);
+ let R = R1T.multiply(R2);
+ let elements = R.elements;
+ // from Tr(R) = 1 + 2 * cos(theta)
+ let tr = elements[0] + elements[5] + elements[10];
+ let theta = Math.acos(Math.max(Math.min((tr - 1) / 2, 1), -1));
+ return theta;
+ }
+ /**
+ * Calculates the angle from a vector to a plane.
+ *
+ * @param {Array<number>} vector - The vector.
+ * @param {Array<number>} planeNormal - Normal of the plane.
+ * @returns {number} Angle from between plane and vector.
+ */
+ angleToPlane(vector, planeNormal) {
+ let v = new Vector3().fromArray(vector);
+ let norm = v.length();
+ if (norm < this._epsilon) {
+ return 0;
+ }
+ let projection = v.dot(new Vector3().fromArray(planeNormal));
+ return Math.asin(projection / norm);
+ }
+ azimuthal(direction, up) {
+ const directionVector = new Vector3().fromArray(direction);
+ const upVector = new Vector3().fromArray(up);
+ const upProjection = directionVector.clone().dot(upVector);
+ const planeProjection = directionVector.clone().sub(upVector.clone().multiplyScalar(upProjection));
+ return Math.atan2(planeProjection.y, planeProjection.x);
+ }
+ /**
+ * Calculates the distance between two coordinates
+ * (longitude, latitude pairs) in meters according to
+ * the haversine formula.
+ *
+ * @param {number} lat1 - Latitude of the first coordinate in degrees.
+ * @param {number} lng1 - Longitude of the first coordinate in degrees.
+ * @param {number} lat2 - Latitude of the second coordinate in degrees.
+ * @param {number} lng2 - Longitude of the second coordinate in degrees.
+ * @returns {number} Distance between lat lon positions in meters.
+ */
+ distanceFromLngLat(lng1, lat1, lng2, lat2) {
+ let r = 6371000;
+ let dLat = this.degToRad(lat2 - lat1);
+ let dLng = this.degToRad(lng2 - lng1);
+ let hav = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
+ Math.cos(this.degToRad(lat1)) * Math.cos(this.degToRad(lat2)) *
+ Math.sin(dLng / 2) * Math.sin(dLng / 2);
+ let d = 2 * r * Math.atan2(Math.sqrt(hav), Math.sqrt(1 - hav));
+ return d;
+ }
+}
+
+const spatial = new Spatial();
+function isSpherical(cameraType) {
+ return cameraType === "spherical";
+}
+function isFisheye(cameraType) {
+ return cameraType === "fisheye";
+}
+function computeTranslation(position, rotation, reference) {
+ const C = geodeticToEnu(position.lng, position.lat, position.alt, reference.lng, reference.lat, reference.alt);
+ const RC = spatial.rotate(C, rotation);
+ const translation = [-RC.x, -RC.y, -RC.z];
+ return translation;
+}
+function computeProjectedPoints(transform, basicVertices, basicDirections, pointsPerLine, viewportCoords) {
+ const basicPoints = [];
+ for (let side = 0; side < basicVertices.length; ++side) {
+ const v = basicVertices[side];
+ const d = basicDirections[side];
+ for (let i = 0; i <= pointsPerLine; ++i) {
+ basicPoints.push([v[0] + d[0] * i / pointsPerLine,
+ v[1] + d[1] * i / pointsPerLine]);
+ }
+ }
+ const camera = new Camera$1();
+ camera.up.copy(transform.upVector());
+ camera.position.copy(new Vector3().fromArray(transform.unprojectSfM([0, 0], 0)));
+ camera.lookAt(new Vector3().fromArray(transform.unprojectSfM([0, 0], 10)));
+ camera.updateMatrix();
+ camera.updateMatrixWorld(true);
+ const projectedPoints = basicPoints
+ .map((basicPoint) => {
+ const worldPoint = transform.unprojectBasic(basicPoint, 10000);
+ const cameraPoint = viewportCoords.worldToCamera(worldPoint, camera);
+ return [
+ Math.abs(cameraPoint[0] / cameraPoint[2]),
+ Math.abs(cameraPoint[1] / cameraPoint[2]),
+ ];
+ });
+ return projectedPoints;
+}
+
+/**
+ * @class EdgeCalculator
+ *
+ * @classdesc Represents a class for calculating node edges.
+ */
+class EdgeCalculator {
+ /**
+ * Create a new edge calculator instance.
+ *
+ * @param {EdgeCalculatorSettings} settings - Settings struct.
+ * @param {EdgeCalculatorDirections} directions - Directions struct.
+ * @param {EdgeCalculatorCoefficients} coefficients - Coefficients struct.
+ */
+ constructor(settings, directions, coefficients) {
+ this._spatial = new Spatial();
+ this._settings = settings != null ? settings : new EdgeCalculatorSettings();
+ this._directions = directions != null ? directions : new EdgeCalculatorDirections();
+ this._coefficients = coefficients != null ? coefficients : new EdgeCalculatorCoefficients();
+ }
+ /**
+ * Returns the potential edges to destination nodes for a set
+ * of nodes with respect to a source node.
+ *
+ * @param {Image} node - Source node.
+ * @param {Array<Image>} nodes - Potential destination nodes.
+ * @param {Array<string>} fallbackIds - Ids for destination nodes
+ * that should be returned even if they do not meet the
+ * criteria for a potential edge.
+ * @throws {ArgumentMapillaryError} If node is not full.
+ */
+ getPotentialEdges(node, potentialImages, fallbackIds) {
+ if (!node.complete) {
+ throw new ArgumentMapillaryError("Image has to be full.");
+ }
+ if (!node.merged) {
+ return [];
+ }
+ let currentDirection = this._spatial.viewingDirection(node.rotation);
+ let currentVerticalDirection = this._spatial.angleToPlane(currentDirection.toArray(), [0, 0, 1]);
+ let potentialEdges = [];
+ for (let potential of potentialImages) {
+ if (!potential.merged ||
+ potential.id === node.id) {
+ continue;
+ }
+ let enu = geodeticToEnu(potential.lngLat.lng, potential.lngLat.lat, potential.computedAltitude, node.lngLat.lng, node.lngLat.lat, node.computedAltitude);
+ let motion = new Vector3(enu[0], enu[1], enu[2]);
+ let distance = motion.length();
+ if (distance > this._settings.maxDistance &&
+ fallbackIds.indexOf(potential.id) < 0) {
+ continue;
+ }
+ let motionChange = this._spatial.angleBetweenVector2(currentDirection.x, currentDirection.y, motion.x, motion.y);
+ let verticalMotion = this._spatial.angleToPlane(motion.toArray(), [0, 0, 1]);
+ let direction = this._spatial.viewingDirection(potential.rotation);
+ let directionChange = this._spatial.angleBetweenVector2(currentDirection.x, currentDirection.y, direction.x, direction.y);
+ let verticalDirection = this._spatial.angleToPlane(direction.toArray(), [0, 0, 1]);
+ let verticalDirectionChange = verticalDirection - currentVerticalDirection;
+ let rotation = this._spatial.relativeRotationAngle(node.rotation, potential.rotation);
+ let worldMotionAzimuth = this._spatial.angleBetweenVector2(1, 0, motion.x, motion.y);
+ let sameSequence = potential.sequenceId != null &&
+ node.sequenceId != null &&
+ potential.sequenceId === node.sequenceId;
+ let sameMergeCC = potential.mergeId === node.mergeId;
+ let sameUser = potential.creatorId === node.creatorId;
+ let potentialEdge = {
+ capturedAt: potential.capturedAt,
+ directionChange: directionChange,
+ distance: distance,
+ spherical: isSpherical(potential.cameraType),
+ id: potential.id,
+ motionChange: motionChange,
+ rotation: rotation,
+ sameMergeCC: sameMergeCC,
+ sameSequence: sameSequence,
+ sameUser: sameUser,
+ sequenceId: potential.sequenceId,
+ verticalDirectionChange: verticalDirectionChange,
+ verticalMotion: verticalMotion,
+ worldMotionAzimuth: worldMotionAzimuth,
+ };
+ potentialEdges.push(potentialEdge);
+ }
+ return potentialEdges;
+ }
+ /**
+ * Computes the sequence edges for a node.
+ *
+ * @param {Image} node - Source node.
+ * @throws {ArgumentMapillaryError} If node is not full.
+ */
+ computeSequenceEdges(node, sequence) {
+ if (!node.complete) {
+ throw new ArgumentMapillaryError("Image has to be full.");
+ }
+ if (node.sequenceId !== sequence.id) {
+ throw new ArgumentMapillaryError("Image and sequence does not correspond.");
+ }
+ let edges = [];
+ let nextId = sequence.findNext(node.id);
+ if (nextId != null) {
+ edges.push({
+ data: {
+ direction: NavigationDirection.Next,
+ worldMotionAzimuth: Number.NaN,
+ },
+ source: node.id,
+ target: nextId,
+ });
+ }
+ let prevId = sequence.findPrev(node.id);
+ if (prevId != null) {
+ edges.push({
+ data: {
+ direction: NavigationDirection.Prev,
+ worldMotionAzimuth: Number.NaN,
+ },
+ source: node.id,
+ target: prevId,
+ });
+ }
+ return edges;
+ }
+ /**
+ * Computes the similar edges for a node.
+ *
+ * @description Similar edges for perspective images
+ * look roughly in the same direction and are positioned closed to the node.
+ * Similar edges for spherical only target other spherical.
+ *
+ * @param {Image} node - Source node.
+ * @param {Array<PotentialEdge>} potentialEdges - Potential edges.
+ * @throws {ArgumentMapillaryError} If node is not full.
+ */
+ computeSimilarEdges(node, potentialEdges) {
+ if (!node.complete) {
+ throw new ArgumentMapillaryError("Image has to be full.");
+ }
+ let nodeSpherical = isSpherical(node.cameraType);
+ let sequenceGroups = {};
+ for (let potentialEdge of potentialEdges) {
+ if (potentialEdge.sequenceId == null) {
+ continue;
+ }
+ if (potentialEdge.sameSequence) {
+ continue;
+ }
+ if (nodeSpherical) {
+ if (!potentialEdge.spherical) {
+ continue;
+ }
+ }
+ else {
+ if (!potentialEdge.spherical &&
+ Math.abs(potentialEdge.directionChange) > this._settings.similarMaxDirectionChange) {
+ continue;
+ }
+ }
+ if (potentialEdge.distance > this._settings.similarMaxDistance) {
+ continue;
+ }
+ if (potentialEdge.sameUser &&
+ Math.abs(potentialEdge.capturedAt - node.capturedAt) <
+ this._settings.similarMinTimeDifference) {
+ continue;
+ }
+ if (sequenceGroups[potentialEdge.sequenceId] == null) {
+ sequenceGroups[potentialEdge.sequenceId] = [];
+ }
+ sequenceGroups[potentialEdge.sequenceId].push(potentialEdge);
+ }
+ let similarEdges = [];
+ let calculateScore = isSpherical(node.cameraType) ?
+ (potentialEdge) => {
+ return potentialEdge.distance;
+ } :
+ (potentialEdge) => {
+ return this._coefficients.similarDistance * potentialEdge.distance +
+ this._coefficients.similarRotation * potentialEdge.rotation;
+ };
+ for (let sequenceId in sequenceGroups) {
+ if (!sequenceGroups.hasOwnProperty(sequenceId)) {
+ continue;
+ }
+ let lowestScore = Number.MAX_VALUE;
+ let similarEdge = null;
+ for (let potentialEdge of sequenceGroups[sequenceId]) {
+ let score = calculateScore(potentialEdge);
+ if (score < lowestScore) {
+ lowestScore = score;
+ similarEdge = potentialEdge;
+ }
+ }
+ if (similarEdge == null) {
+ continue;
+ }
+ similarEdges.push(similarEdge);
+ }
+ return similarEdges
+ .map((potentialEdge) => {
+ return {
+ data: {
+ direction: NavigationDirection.Similar,
+ worldMotionAzimuth: potentialEdge.worldMotionAzimuth,
+ },
+ source: node.id,
+ target: potentialEdge.id,
+ };
+ });
+ }
+ /**
+ * Computes the step edges for a perspective node.
+ *
+ * @description Step edge targets can only be other perspective nodes.
+ * Returns an empty array for spherical.
+ *
+ * @param {Image} node - Source node.
+ * @param {Array<PotentialEdge>} potentialEdges - Potential edges.
+ * @param {string} prevId - Id of previous node in sequence.
+ * @param {string} nextId - Id of next node in sequence.
+ * @throws {ArgumentMapillaryError} If node is not full.
+ */
+ computeStepEdges(node, potentialEdges, prevId, nextId) {
+ if (!node.complete) {
+ throw new ArgumentMapillaryError("Image has to be full.");
+ }
+ let edges = [];
+ if (isSpherical(node.cameraType)) {
+ return edges;
+ }
+ for (let k in this._directions.steps) {
+ if (!this._directions.steps.hasOwnProperty(k)) {
+ continue;
+ }
+ let step = this._directions.steps[k];
+ let lowestScore = Number.MAX_VALUE;
+ let edge = null;
+ let fallback = null;
+ for (let potential of potentialEdges) {
+ if (potential.spherical) {
+ continue;
+ }
+ if (Math.abs(potential.directionChange) > this._settings.stepMaxDirectionChange) {
+ continue;
+ }
+ let motionDifference = this._spatial.angleDifference(step.motionChange, potential.motionChange);
+ let directionMotionDifference = this._spatial.angleDifference(potential.directionChange, motionDifference);
+ let drift = Math.max(Math.abs(motionDifference), Math.abs(directionMotionDifference));
+ if (Math.abs(drift) > this._settings.stepMaxDrift) {
+ continue;
+ }
+ let potentialId = potential.id;
+ if (step.useFallback && (potentialId === prevId || potentialId === nextId)) {
+ fallback = potential;
+ }
+ if (potential.distance > this._settings.stepMaxDistance) {
+ continue;
+ }
+ motionDifference = Math.sqrt(motionDifference * motionDifference +
+ potential.verticalMotion * potential.verticalMotion);
+ let score = this._coefficients.stepPreferredDistance *
+ Math.abs(potential.distance - this._settings.stepPreferredDistance) /
+ this._settings.stepMaxDistance +
+ this._coefficients.stepMotion * motionDifference / this._settings.stepMaxDrift +
+ this._coefficients.stepRotation * potential.rotation / this._settings.stepMaxDirectionChange +
+ this._coefficients.stepSequencePenalty * (potential.sameSequence ? 0 : 1) +
+ this._coefficients.stepMergeCCPenalty * (potential.sameMergeCC ? 0 : 1);
+ if (score < lowestScore) {
+ lowestScore = score;
+ edge = potential;
+ }
+ }
+ edge = edge == null ? fallback : edge;
+ if (edge != null) {
+ edges.push({
+ data: {
+ direction: step.direction,
+ worldMotionAzimuth: edge.worldMotionAzimuth,
+ },
+ source: node.id,
+ target: edge.id,
+ });
+ }
+ }
+ return edges;
+ }
+ /**
+ * Computes the turn edges for a perspective node.
+ *
+ * @description Turn edge targets can only be other perspective images.
+ * Returns an empty array for spherical.
+ *
+ * @param {Image} node - Source node.
+ * @param {Array<PotentialEdge>} potentialEdges - Potential edges.
+ * @throws {ArgumentMapillaryError} If node is not full.
+ */
+ computeTurnEdges(node, potentialEdges) {
+ if (!node.complete) {
+ throw new ArgumentMapillaryError("Image has to be full.");
+ }
+ let edges = [];
+ if (isSpherical(node.cameraType)) {
+ return edges;
+ }
+ for (let k in this._directions.turns) {
+ if (!this._directions.turns.hasOwnProperty(k)) {
+ continue;
+ }
+ let turn = this._directions.turns[k];
+ let lowestScore = Number.MAX_VALUE;
+ let edge = null;
+ for (let potential of potentialEdges) {
+ if (potential.spherical) {
+ continue;
+ }
+ if (potential.distance > this._settings.turnMaxDistance) {
+ continue;
+ }
+ let rig = turn.direction !== NavigationDirection.TurnU &&
+ potential.distance < this._settings.turnMaxRigDistance &&
+ Math.abs(potential.directionChange) > this._settings.turnMinRigDirectionChange;
+ let directionDifference = this._spatial.angleDifference(turn.directionChange, potential.directionChange);
+ let score;
+ if (rig &&
+ potential.directionChange * turn.directionChange > 0 &&
+ Math.abs(potential.directionChange) < Math.abs(turn.directionChange)) {
+ score = -Math.PI / 2 + Math.abs(potential.directionChange);
+ }
+ else {
+ if (Math.abs(directionDifference) > this._settings.turnMaxDirectionChange) {
+ continue;
+ }
+ let motionDifference = turn.motionChange ?
+ this._spatial.angleDifference(turn.motionChange, potential.motionChange) : 0;
+ motionDifference = Math.sqrt(motionDifference * motionDifference +
+ potential.verticalMotion * potential.verticalMotion);
+ score =
+ this._coefficients.turnDistance * potential.distance /
+ this._settings.turnMaxDistance +
+ this._coefficients.turnMotion * motionDifference / Math.PI +
+ this._coefficients.turnSequencePenalty * (potential.sameSequence ? 0 : 1) +
+ this._coefficients.turnMergeCCPenalty * (potential.sameMergeCC ? 0 : 1);
+ }
+ if (score < lowestScore) {
+ lowestScore = score;
+ edge = potential;
+ }
+ }
+ if (edge != null) {
+ edges.push({
+ data: {
+ direction: turn.direction,
+ worldMotionAzimuth: edge.worldMotionAzimuth,
+ },
+ source: node.id,
+ target: edge.id,
+ });
+ }
+ }
+ return edges;
+ }
+ /**
+ * Computes the spherical edges for a perspective node.
+ *
+ * @description Perspective to spherical edge targets can only be
+ * spherical nodes. Returns an empty array for spherical.
+ *
+ * @param {Image} node - Source node.
+ * @param {Array<PotentialEdge>} potentialEdges - Potential edges.
+ * @throws {ArgumentMapillaryError} If node is not full.
+ */
+ computePerspectiveToSphericalEdges(node, potentialEdges) {
+ if (!node.complete) {
+ throw new ArgumentMapillaryError("Image has to be full.");
+ }
+ if (isSpherical(node.cameraType)) {
+ return [];
+ }
+ let lowestScore = Number.MAX_VALUE;
+ let edge = null;
+ for (let potential of potentialEdges) {
+ if (!potential.spherical) {
+ continue;
+ }
+ let score = this._coefficients.sphericalPreferredDistance *
+ Math.abs(potential.distance - this._settings.sphericalPreferredDistance) /
+ this._settings.sphericalMaxDistance +
+ this._coefficients.sphericalMotion * Math.abs(potential.motionChange) / Math.PI +
+ this._coefficients.sphericalMergeCCPenalty * (potential.sameMergeCC ? 0 : 1);
+ if (score < lowestScore) {
+ lowestScore = score;
+ edge = potential;
+ }
+ }
+ if (edge == null) {
+ return [];
+ }
+ return [
+ {
+ data: {
+ direction: NavigationDirection.Spherical,
+ worldMotionAzimuth: edge.worldMotionAzimuth,
+ },
+ source: node.id,
+ target: edge.id,
+ },
+ ];
+ }
+ /**
+ * Computes the spherical and step edges for a spherical node.
+ *
+ * @description Spherical to spherical edge targets can only be
+ * spherical nodes. spherical to step edge targets can only be perspective
+ * nodes.
+ *
+ * @param {Image} node - Source node.
+ * @param {Array<PotentialEdge>} potentialEdges - Potential edges.
+ * @throws {ArgumentMapillaryError} If node is not full.
+ */
+ computeSphericalEdges(node, potentialEdges) {
+ if (!node.complete) {
+ throw new ArgumentMapillaryError("Image has to be full.");
+ }
+ if (!isSpherical(node.cameraType)) {
+ return [];
+ }
+ let sphericalEdges = [];
+ let potentialSpherical = [];
+ let potentialSteps = [];
+ for (let potential of potentialEdges) {
+ if (potential.distance > this._settings.sphericalMaxDistance) {
+ continue;
+ }
+ if (potential.spherical) {
+ if (potential.distance < this._settings.sphericalMinDistance) {
+ continue;
+ }
+ potentialSpherical.push(potential);
+ }
+ else {
+ for (let k in this._directions.spherical) {
+ if (!this._directions.spherical.hasOwnProperty(k)) {
+ continue;
+ }
+ let spherical = this._directions.spherical[k];
+ let turn = this._spatial.angleDifference(potential.directionChange, potential.motionChange);
+ let turnChange = this._spatial.angleDifference(spherical.directionChange, turn);
+ if (Math.abs(turnChange) > this._settings.sphericalMaxStepTurnChange) {
+ continue;
+ }
+ potentialSteps.push([spherical.direction, potential]);
+ // break if step direction found
+ break;
+ }
+ }
+ }
+ let maxRotationDifference = Math.PI / this._settings.sphericalMaxItems;
+ let occupiedAngles = [];
+ let stepAngles = [];
+ for (let index = 0; index < this._settings.sphericalMaxItems; index++) {
+ let rotation = index / this._settings.sphericalMaxItems * 2 * Math.PI;
+ let lowestScore = Number.MAX_VALUE;
+ let edge = null;
+ for (let potential of potentialSpherical) {
+ let motionDifference = this._spatial.angleDifference(rotation, potential.motionChange);
+ if (Math.abs(motionDifference) > maxRotationDifference) {
+ continue;
+ }
+ let occupiedDifference = Number.MAX_VALUE;
+ for (let occupiedAngle of occupiedAngles) {
+ let difference = Math.abs(this._spatial.angleDifference(occupiedAngle, potential.motionChange));
+ if (difference < occupiedDifference) {
+ occupiedDifference = difference;
+ }
+ }
+ if (occupiedDifference <= maxRotationDifference) {
+ continue;
+ }
+ let score = this._coefficients.sphericalPreferredDistance *
+ Math.abs(potential.distance - this._settings.sphericalPreferredDistance) /
+ this._settings.sphericalMaxDistance +
+ this._coefficients.sphericalMotion * Math.abs(motionDifference) / maxRotationDifference +
+ this._coefficients.sphericalSequencePenalty * (potential.sameSequence ? 0 : 1) +
+ this._coefficients.sphericalMergeCCPenalty * (potential.sameMergeCC ? 0 : 1);
+ if (score < lowestScore) {
+ lowestScore = score;
+ edge = potential;
+ }
+ }
+ if (edge != null) {
+ occupiedAngles.push(edge.motionChange);
+ sphericalEdges.push({
+ data: {
+ direction: NavigationDirection.Spherical,
+ worldMotionAzimuth: edge.worldMotionAzimuth,
+ },
+ source: node.id,
+ target: edge.id,
+ });
+ }
+ else {
+ stepAngles.push(rotation);
+ }
+ }
+ let occupiedStepAngles = {};
+ occupiedStepAngles[NavigationDirection.Spherical] = occupiedAngles;
+ occupiedStepAngles[NavigationDirection.StepForward] = [];
+ occupiedStepAngles[NavigationDirection.StepLeft] = [];
+ occupiedStepAngles[NavigationDirection.StepBackward] = [];
+ occupiedStepAngles[NavigationDirection.StepRight] = [];
+ for (let stepAngle of stepAngles) {
+ let occupations = [];
+ for (let k in this._directions.spherical) {
+ if (!this._directions.spherical.hasOwnProperty(k)) {
+ continue;
+ }
+ let spherical = this._directions.spherical[k];
+ let allOccupiedAngles = occupiedStepAngles[NavigationDirection.Spherical]
+ .concat(occupiedStepAngles[spherical.direction])
+ .concat(occupiedStepAngles[spherical.prev])
+ .concat(occupiedStepAngles[spherical.next]);
+ let lowestScore = Number.MAX_VALUE;
+ let edge = null;
+ for (let potential of potentialSteps) {
+ if (potential[0] !== spherical.direction) {
+ continue;
+ }
+ let motionChange = this._spatial.angleDifference(stepAngle, potential[1].motionChange);
+ if (Math.abs(motionChange) > maxRotationDifference) {
+ continue;
+ }
+ let minOccupiedDifference = Number.MAX_VALUE;
+ for (let occupiedAngle of allOccupiedAngles) {
+ let occupiedDifference = Math.abs(this._spatial.angleDifference(occupiedAngle, potential[1].motionChange));
+ if (occupiedDifference < minOccupiedDifference) {
+ minOccupiedDifference = occupiedDifference;
+ }
+ }
+ if (minOccupiedDifference <= maxRotationDifference) {
+ continue;
+ }
+ let score = this._coefficients.sphericalPreferredDistance *
+ Math.abs(potential[1].distance - this._settings.sphericalPreferredDistance) /
+ this._settings.sphericalMaxDistance +
+ this._coefficients.sphericalMotion * Math.abs(motionChange) / maxRotationDifference +
+ this._coefficients.sphericalMergeCCPenalty * (potential[1].sameMergeCC ? 0 : 1);
+ if (score < lowestScore) {
+ lowestScore = score;
+ edge = potential;
+ }
+ }
+ if (edge != null) {
+ occupations.push(edge);
+ sphericalEdges.push({
+ data: {
+ direction: edge[0],
+ worldMotionAzimuth: edge[1].worldMotionAzimuth,
+ },
+ source: node.id,
+ target: edge[1].id,
+ });
+ }
+ }
+ for (let occupation of occupations) {
+ occupiedStepAngles[occupation[0]].push(occupation[1].motionChange);
+ }
+ }
+ return sphericalEdges;
+ }
+}
+
+class GraphMapillaryError extends MapillaryError {
+ constructor(message) {
+ super(message);
+ Object.setPrototypeOf(this, GraphMapillaryError.prototype);
+ this.name = "GraphMapillaryError";
+ }
+}
+
+/**
+ * @class Graph
+ *
+ * @classdesc Represents a graph of nodes with edges.
+ */
+class Graph {
+ /**
+ * Create a new graph instance.
+ *
+ * @param {APIWrapper} [api] - API instance for retrieving data.
+ * @param {rbush.RBush<NodeIndexItem>} [nodeIndex] - Node index for fast spatial retreival.
+ * @param {GraphCalculator} [graphCalculator] - Instance for graph calculations.
+ * @param {EdgeCalculator} [edgeCalculator] - Instance for edge calculations.
+ * @param {FilterCreator} [filterCreator] - Instance for filter creation.
+ * @param {GraphConfiguration} [configuration] - Configuration struct.
+ */
+ constructor(api, nodeIndex, graphCalculator, edgeCalculator, filterCreator, configuration) {
+ this._api = api;
+ this._cachedNodes = {};
+ this._cachedNodeTiles = {};
+ this._cachedSequenceNodes = {};
+ this._cachedSpatialEdges = {};
+ this._cachedTiles = {};
+ this._cachingFill$ = {};
+ this._cachingFull$ = {};
+ this._cachingSequenceNodes$ = {};
+ this._cachingSequences$ = {};
+ this._cachingSpatialArea$ = {};
+ this._cachingTiles$ = {};
+ this._changed$ = new Subject();
+ this._filterCreator = filterCreator !== null && filterCreator !== void 0 ? filterCreator : new FilterCreator();
+ this._filter = this._filterCreator.createFilter(undefined);
+ this._filterSubject$ = new Subject();
+ this._filter$ =
+ concat(of(this._filter), this._filterSubject$).pipe(publishReplay(1), refCount());
+ this._filterSubscription = this._filter$.subscribe(() => { });
+ this._defaultAlt = 2;
+ this._edgeCalculator = edgeCalculator !== null && edgeCalculator !== void 0 ? edgeCalculator : new EdgeCalculator();
+ this._graphCalculator = graphCalculator !== null && graphCalculator !== void 0 ? graphCalculator : new GraphCalculator();
+ this._configuration = configuration !== null && configuration !== void 0 ? configuration : {
+ maxSequences: 50,
+ maxUnusedImages: 100,
+ maxUnusedPreStoredImages: 30,
+ maxUnusedTiles: 20,
+ };
+ this._nodes = {};
+ this._nodeIndex = nodeIndex !== null && nodeIndex !== void 0 ? nodeIndex : new Graph._spatialIndex(16);
+ this._nodeIndexTiles = {};
+ this._nodeToTile = {};
+ this._preStored = {};
+ this._requiredNodeTiles = {};
+ this._requiredSpatialArea = {};
+ this._sequences = {};
+ this._tileThreshold = 20;
+ }
+ static register(spatialIndex) {
+ Graph._spatialIndex = spatialIndex;
+ }
+ /**
+ * Get api.
+ *
+ * @returns {APIWrapper} The API instance used by
+ * the graph.
+ */
+ get api() {
+ return this._api;
+ }
+ /**
+ * Get changed$.
+ *
+ * @returns {Observable<Graph>} Observable emitting
+ * the graph every time it has changed.
+ */
+ get changed$() {
+ return this._changed$;
+ }
+ /**
+ * Get filter$.
+ *
+ * @returns {Observable<FilterFunction>} Observable emitting
+ * the filter every time it has changed.
+ */
+ get filter$() {
+ return this._filter$;
+ }
+ /**
+ * Caches the full node data for all images within a bounding
+ * box.
+ *
+ * @description The node assets are not cached.
+ *
+ * @param {LngLat} sw - South west corner of bounding box.
+ * @param {LngLat} ne - North east corner of bounding box.
+ * @returns {Observable<Array<Image>>} Observable emitting
+ * the full nodes in the bounding box.
+ */
+ cacheBoundingBox$(sw, ne) {
+ const cacheTiles$ = this._api.data.geometry.bboxToCellIds(sw, ne)
+ .filter((h) => {
+ return !(h in this._cachedTiles);
+ })
+ .map((h) => {
+ return h in this._cachingTiles$ ?
+ this._cachingTiles$[h] :
+ this._cacheTile$(h);
+ });
+ if (cacheTiles$.length === 0) {
+ cacheTiles$.push(of(this));
+ }
+ return from(cacheTiles$).pipe(mergeAll(), last(), mergeMap(() => {
+ const nodes = this._nodeIndex
+ .search({
+ maxX: ne.lng,
+ maxY: ne.lat,
+ minX: sw.lng,
+ minY: sw.lat,
+ })
+ .map((item) => {
+ return item.node;
+ });
+ const fullNodes = [];
+ const coreNodes = [];
+ for (const node of nodes) {
+ if (node.complete) {
+ fullNodes.push(node);
+ }
+ else {
+ coreNodes.push(node.id);
+ }
+ }
+ const coreNodeBatches = [];
+ const batchSize = 200;
+ while (coreNodes.length > 0) {
+ coreNodeBatches.push(coreNodes.splice(0, batchSize));
+ }
+ const fullNodes$ = of(fullNodes);
+ const fillNodes$ = coreNodeBatches
+ .map((batch) => {
+ return this._api
+ .getSpatialImages$(batch)
+ .pipe(map((items) => {
+ const result = [];
+ for (const item of items) {
+ const exists = this
+ .hasNode(item.node_id);
+ if (!exists) {
+ continue;
+ }
+ const node = this
+ .getNode(item.node_id);
+ if (!node.complete) {
+ this._makeFull(node, item.node);
+ }
+ result.push(node);
+ }
+ return result;
+ }));
+ });
+ return merge(fullNodes$, from(fillNodes$).pipe(mergeAll()));
+ }), reduce((acc, value) => {
+ return acc.concat(value);
+ }));
+ }
+ /**
+ * Caches the full node data for all images of a cell.
+ *
+ * @description The node assets are not cached.
+ *
+ * @param {string} cellId - Cell id.
+ * @returns {Observable<Array<Image>>} Observable
+ * emitting the full nodes of the cell.
+ */
+ cacheCell$(cellId) {
+ const cacheCell$ = cellId in this._cachedTiles ?
+ of(this) :
+ cellId in this._cachingTiles$ ?
+ this._cachingTiles$[cellId] :
+ this._cacheTile$(cellId);
+ return cacheCell$.pipe(mergeMap(() => {
+ const cachedCell = this._cachedTiles[cellId];
+ cachedCell.accessed = new Date().getTime();
+ const cellNodes = cachedCell.nodes;
+ const fullNodes = [];
+ const coreNodes = [];
+ for (const node of cellNodes) {
+ if (node.complete) {
+ fullNodes.push(node);
+ }
+ else {
+ coreNodes.push(node.id);
+ }
+ }
+ const coreNodeBatches = [];
+ const batchSize = 200;
+ while (coreNodes.length > 0) {
+ coreNodeBatches.push(coreNodes.splice(0, batchSize));
+ }
+ const fullNodes$ = of(fullNodes);
+ const fillNodes$ = coreNodeBatches
+ .map((batch) => {
+ return this._api.getSpatialImages$(batch).pipe(map((items) => {
+ const filled = [];
+ for (const item of items) {
+ if (!item.node) {
+ console.warn(`Image is empty (${item.node})`);
+ continue;
+ }
+ const id = item.node_id;
+ if (!this.hasNode(id)) {
+ continue;
+ }
+ const node = this.getNode(id);
+ if (!node.complete) {
+ this._makeFull(node, item.node);
+ }
+ filled.push(node);
+ }
+ return filled;
+ }));
+ });
+ return merge(fullNodes$, from(fillNodes$).pipe(mergeAll()));
+ }), reduce((acc, value) => {
+ return acc.concat(value);
+ }));
+ }
+ /**
+ * Retrieve and cache node fill properties.
+ *
+ * @param {string} key - Key of node to fill.
+ * @returns {Observable<Graph>} Observable emitting the graph
+ * when the node has been updated.
+ * @throws {GraphMapillaryError} When the operation is not valid on the
+ * current graph.
+ */
+ cacheFill$(key) {
+ if (key in this._cachingFull$) {
+ throw new GraphMapillaryError(`Cannot fill node while caching full (${key}).`);
+ }
+ if (!this.hasNode(key)) {
+ throw new GraphMapillaryError(`Cannot fill node that does not exist in graph (${key}).`);
+ }
+ if (key in this._cachingFill$) {
+ return this._cachingFill$[key];
+ }
+ const node = this.getNode(key);
+ if (node.complete) {
+ throw new GraphMapillaryError(`Cannot fill node that is already full (${key}).`);
+ }
+ this._cachingFill$[key] = this._api.getSpatialImages$([key]).pipe(tap((items) => {
+ for (const item of items) {
+ if (!item.node) {
+ console.warn(`Image is empty ${item.node_id}`);
+ }
+ if (!node.complete) {
+ this._makeFull(node, item.node);
+ }
+ delete this._cachingFill$[item.node_id];
+ }
+ }), map(() => { return this; }), finalize(() => {
+ if (key in this._cachingFill$) {
+ delete this._cachingFill$[key];
+ }
+ this._changed$.next(this);
+ }), publish(), refCount());
+ return this._cachingFill$[key];
+ }
+ /**
+ * Retrieve and cache full node properties.
+ *
+ * @param {string} key - Key of node to fill.
+ * @returns {Observable<Graph>} Observable emitting the graph
+ * when the node has been updated.
+ * @throws {GraphMapillaryError} When the operation is not valid on the
+ * current graph.
+ */
+ cacheFull$(key) {
+ if (key in this._cachingFull$) {
+ return this._cachingFull$[key];
+ }
+ if (this.hasNode(key)) {
+ throw new GraphMapillaryError(`Cannot cache full node that already exist in graph (${key}).`);
+ }
+ this._cachingFull$[key] = this._api.getImages$([key]).pipe(tap((items) => {
+ for (const item of items) {
+ if (!item.node) {
+ throw new GraphMapillaryError(`Image does not exist (${key}, ${item.node}).`);
+ }
+ const id = item.node_id;
+ if (this.hasNode(id)) {
+ const node = this.getNode(key);
+ if (!node.complete) {
+ this._makeFull(node, item.node);
+ }
+ }
+ else {
+ if (item.node.sequence.id == null) {
+ throw new GraphMapillaryError(`Image has no sequence key (${key}).`);
+ }
+ const node = new Image$1(item.node);
+ this._makeFull(node, item.node);
+ const cellId = this._api.data.geometry
+ .lngLatToCellId(node.originalLngLat);
+ this._preStore(cellId, node);
+ this._setNode(node);
+ delete this._cachingFull$[id];
+ }
+ }
+ }), map(() => this), finalize(() => {
+ if (key in this._cachingFull$) {
+ delete this._cachingFull$[key];
+ }
+ this._changed$.next(this);
+ }), publish(), refCount());
+ return this._cachingFull$[key];
+ }
+ /**
+ * Retrieve and cache a node sequence.
+ *
+ * @param {string} key - Key of node for which to retrieve sequence.
+ * @returns {Observable<Graph>} Observable emitting the graph
+ * when the sequence has been retrieved.
+ * @throws {GraphMapillaryError} When the operation is not valid on the
+ * current graph.
+ */
+ cacheNodeSequence$(key) {
+ if (!this.hasNode(key)) {
+ throw new GraphMapillaryError(`Cannot cache sequence edges of node that does not exist in graph (${key}).`);
+ }
+ let node = this.getNode(key);
+ if (node.sequenceId in this._sequences) {
+ throw new GraphMapillaryError(`Sequence already cached (${key}), (${node.sequenceId}).`);
+ }
+ return this._cacheSequence$(node.sequenceId);
+ }
+ /**
+ * Retrieve and cache a sequence.
+ *
+ * @param {string} sequenceKey - Key of sequence to cache.
+ * @returns {Observable<Graph>} Observable emitting the graph
+ * when the sequence has been retrieved.
+ * @throws {GraphMapillaryError} When the operation is not valid on the
+ * current graph.
+ */
+ cacheSequence$(sequenceKey) {
+ if (sequenceKey in this._sequences) {
+ throw new GraphMapillaryError(`Sequence already cached (${sequenceKey})`);
+ }
+ return this._cacheSequence$(sequenceKey);
+ }
+ /**
+ * Cache sequence edges for a node.
+ *
+ * @param {string} key - Key of node.
+ * @throws {GraphMapillaryError} When the operation is not valid on the
+ * current graph.
+ */
+ cacheSequenceEdges(key) {
+ let node = this.getNode(key);
+ if (!(node.sequenceId in this._sequences)) {
+ throw new GraphMapillaryError(`Sequence is not cached (${key}), (${node.sequenceId})`);
+ }
+ let sequence = this._sequences[node.sequenceId].sequence;
+ let edges = this._edgeCalculator.computeSequenceEdges(node, sequence);
+ node.cacheSequenceEdges(edges);
+ }
+ /**
+ * Retrieve and cache full nodes for all keys in a sequence.
+ *
+ * @param {string} sequenceKey - Key of sequence.
+ * @param {string} referenceNodeKey - Key of node to use as reference
+ * for optimized caching.
+ * @returns {Observable<Graph>} Observable emitting the graph
+ * when the nodes of the sequence has been cached.
+ */
+ cacheSequenceNodes$(sequenceKey, referenceNodeKey) {
+ if (!this.hasSequence(sequenceKey)) {
+ throw new GraphMapillaryError(`Cannot cache sequence nodes of sequence that does not exist in graph (${sequenceKey}).`);
+ }
+ if (this.hasSequenceNodes(sequenceKey)) {
+ throw new GraphMapillaryError(`Sequence nodes already cached (${sequenceKey}).`);
+ }
+ const sequence = this.getSequence(sequenceKey);
+ if (sequence.id in this._cachingSequenceNodes$) {
+ return this._cachingSequenceNodes$[sequence.id];
+ }
+ const batches = [];
+ const keys = sequence.imageIds.slice();
+ const referenceBatchSize = 50;
+ if (!!referenceNodeKey && keys.length > referenceBatchSize) {
+ const referenceIndex = keys.indexOf(referenceNodeKey);
+ const startIndex = Math.max(0, Math.min(referenceIndex - referenceBatchSize / 2, keys.length - referenceBatchSize));
+ batches.push(keys.splice(startIndex, referenceBatchSize));
+ }
+ const batchSize = 200;
+ while (keys.length > 0) {
+ batches.push(keys.splice(0, batchSize));
+ }
+ let batchesToCache = batches.length;
+ const sequenceNodes$ = from(batches).pipe(mergeMap((batch) => {
+ return this._api.getImages$(batch).pipe(tap((items) => {
+ for (const item of items) {
+ if (!item.node) {
+ console.warn(`Image empty (${item.node_id})`);
+ continue;
+ }
+ const id = item.node_id;
+ if (this.hasNode(id)) {
+ const node = this.getNode(id);
+ if (!node.complete) {
+ this._makeFull(node, item.node);
+ }
+ }
+ else {
+ if (item.node.sequence.id == null) {
+ console.warn(`Sequence missing, discarding node (${item.node_id})`);
+ }
+ const node = new Image$1(item.node);
+ this._makeFull(node, item.node);
+ const cellId = this._api.data.geometry
+ .lngLatToCellId(node.originalLngLat);
+ this._preStore(cellId, node);
+ this._setNode(node);
+ }
+ }
+ batchesToCache--;
+ }), map(() => this));
+ }, 6), last(), finalize(() => {
+ delete this._cachingSequenceNodes$[sequence.id];
+ if (batchesToCache === 0) {
+ this._cachedSequenceNodes[sequence.id] = true;
+ }
+ }), publish(), refCount());
+ this._cachingSequenceNodes$[sequence.id] = sequenceNodes$;
+ return sequenceNodes$;
+ }
+ /**
+ * Retrieve and cache full nodes for a node spatial area.
+ *
+ * @param {string} key - Key of node for which to retrieve sequence.
+ * @returns {Observable<Graph>} Observable emitting the graph
+ * when the nodes in the spatial area has been made full.
+ * @throws {GraphMapillaryError} When the operation is not valid on the
+ * current graph.
+ */
+ cacheSpatialArea$(key) {
+ if (!this.hasNode(key)) {
+ throw new GraphMapillaryError(`Cannot cache spatial area of node that does not exist in graph (${key}).`);
+ }
+ if (key in this._cachedSpatialEdges) {
+ throw new GraphMapillaryError(`Image already spatially cached (${key}).`);
+ }
+ if (!(key in this._requiredSpatialArea)) {
+ throw new GraphMapillaryError(`Spatial area not determined (${key}).`);
+ }
+ let spatialArea = this._requiredSpatialArea[key];
+ if (Object.keys(spatialArea.cacheNodes).length === 0) {
+ throw new GraphMapillaryError(`Spatial nodes already cached (${key}).`);
+ }
+ if (key in this._cachingSpatialArea$) {
+ return this._cachingSpatialArea$[key];
+ }
+ let batches = [];
+ while (spatialArea.cacheKeys.length > 0) {
+ batches.push(spatialArea.cacheKeys.splice(0, 200));
+ }
+ let batchesToCache = batches.length;
+ let spatialNodes$ = [];
+ for (let batch of batches) {
+ let spatialNodeBatch$ = this._api.getSpatialImages$(batch).pipe(tap((items) => {
+ for (const item of items) {
+ if (!item.node) {
+ console.warn(`Image is empty (${item.node_id})`);
+ continue;
+ }
+ const id = item.node_id;
+ const spatialNode = spatialArea.cacheNodes[id];
+ if (spatialNode.complete) {
+ delete spatialArea.cacheNodes[id];
+ continue;
+ }
+ this._makeFull(spatialNode, item.node);
+ delete spatialArea.cacheNodes[id];
+ }
+ if (--batchesToCache === 0) {
+ delete this._cachingSpatialArea$[key];
+ }
+ }), map(() => { return this; }), catchError((error) => {
+ for (let batchKey of batch) {
+ if (batchKey in spatialArea.all) {
+ delete spatialArea.all[batchKey];
+ }
+ if (batchKey in spatialArea.cacheNodes) {
+ delete spatialArea.cacheNodes[batchKey];
+ }
+ }
+ if (--batchesToCache === 0) {
+ delete this._cachingSpatialArea$[key];
+ }
+ throw error;
+ }), finalize(() => {
+ if (Object.keys(spatialArea.cacheNodes).length === 0) {
+ this._changed$.next(this);
+ }
+ }), publish(), refCount());
+ spatialNodes$.push(spatialNodeBatch$);
+ }
+ this._cachingSpatialArea$[key] = spatialNodes$;
+ return spatialNodes$;
+ }
+ /**
+ * Cache spatial edges for a node.
+ *
+ * @param {string} key - Key of node.
+ * @throws {GraphMapillaryError} When the operation is not valid on the
+ * current graph.
+ */
+ cacheSpatialEdges(key) {
+ if (key in this._cachedSpatialEdges) {
+ throw new GraphMapillaryError(`Spatial edges already cached (${key}).`);
+ }
+ let node = this.getNode(key);
+ let sequence = this._sequences[node.sequenceId].sequence;
+ let fallbackKeys = [];
+ let prevKey = sequence.findPrev(node.id);
+ if (prevKey != null) {
+ fallbackKeys.push(prevKey);
+ }
+ let nextKey = sequence.findNext(node.id);
+ if (nextKey != null) {
+ fallbackKeys.push(nextKey);
+ }
+ let allSpatialNodes = this._requiredSpatialArea[key].all;
+ let potentialNodes = [];
+ let filter = this._filter;
+ for (let spatialNodeKey in allSpatialNodes) {
+ if (!allSpatialNodes.hasOwnProperty(spatialNodeKey)) {
+ continue;
+ }
+ let spatialNode = allSpatialNodes[spatialNodeKey];
+ if (filter(spatialNode)) {
+ potentialNodes.push(spatialNode);
+ }
+ }
+ let potentialEdges = this._edgeCalculator.getPotentialEdges(node, potentialNodes, fallbackKeys);
+ let edges = this._edgeCalculator.computeStepEdges(node, potentialEdges, prevKey, nextKey);
+ edges = edges.concat(this._edgeCalculator.computeTurnEdges(node, potentialEdges));
+ edges = edges.concat(this._edgeCalculator.computeSphericalEdges(node, potentialEdges));
+ edges = edges.concat(this._edgeCalculator.computePerspectiveToSphericalEdges(node, potentialEdges));
+ edges = edges.concat(this._edgeCalculator.computeSimilarEdges(node, potentialEdges));
+ node.cacheSpatialEdges(edges);
+ this._cachedSpatialEdges[key] = node;
+ delete this._requiredSpatialArea[key];
+ delete this._cachedNodeTiles[key];
+ }
+ /**
+ * Retrieve and cache tiles for a node.
+ *
+ * @param {string} key - Key of node for which to retrieve tiles.
+ * @returns {Array<Observable<Graph>>} Array of observables emitting
+ * the graph for each tile required for the node has been cached.
+ * @throws {GraphMapillaryError} When the operation is not valid on the
+ * current graph.
+ */
+ cacheTiles$(key) {
+ if (key in this._cachedNodeTiles) {
+ throw new GraphMapillaryError(`Tiles already cached (${key}).`);
+ }
+ if (key in this._cachedSpatialEdges) {
+ throw new GraphMapillaryError(`Spatial edges already cached so tiles considered cached (${key}).`);
+ }
+ if (!(key in this._requiredNodeTiles)) {
+ throw new GraphMapillaryError(`Tiles have not been determined (${key}).`);
+ }
+ let nodeTiles = this._requiredNodeTiles[key];
+ if (nodeTiles.cache.length === 0 &&
+ nodeTiles.caching.length === 0) {
+ throw new GraphMapillaryError(`Tiles already cached (${key}).`);
+ }
+ if (!this.hasNode(key)) {
+ throw new GraphMapillaryError(`Cannot cache tiles of node that does not exist in graph (${key}).`);
+ }
+ let hs = nodeTiles.cache.slice();
+ nodeTiles.caching = this._requiredNodeTiles[key].caching.concat(hs);
+ nodeTiles.cache = [];
+ let cacheTiles$ = [];
+ for (let h of nodeTiles.caching) {
+ const cacheTile$ = h in this._cachingTiles$ ?
+ this._cachingTiles$[h] :
+ this._cacheTile$(h);
+ cacheTiles$.push(cacheTile$.pipe(tap((graph) => {
+ let index = nodeTiles.caching.indexOf(h);
+ if (index > -1) {
+ nodeTiles.caching.splice(index, 1);
+ }
+ if (nodeTiles.caching.length === 0 &&
+ nodeTiles.cache.length === 0) {
+ delete this._requiredNodeTiles[key];
+ this._cachedNodeTiles[key] = true;
+ }
+ }), catchError((error) => {
+ let index = nodeTiles.caching.indexOf(h);
+ if (index > -1) {
+ nodeTiles.caching.splice(index, 1);
+ }
+ if (nodeTiles.caching.length === 0 &&
+ nodeTiles.cache.length === 0) {
+ delete this._requiredNodeTiles[key];
+ this._cachedNodeTiles[key] = true;
+ }
+ throw error;
+ }), finalize(() => {
+ this._changed$.next(this);
+ }), publish(), refCount()));
+ }
+ return cacheTiles$;
+ }
+ /**
+ * Initialize the cache for a node.
+ *
+ * @param {string} key - Key of node.
+ * @throws {GraphMapillaryError} When the operation is not valid on the
+ * current graph.
+ */
+ initializeCache(key) {
+ if (key in this._cachedNodes) {
+ throw new GraphMapillaryError(`Image already in cache (${key}).`);
+ }
+ const node = this.getNode(key);
+ const provider = this._api.data;
+ node.initializeCache(new ImageCache(provider));
+ const accessed = new Date().getTime();
+ this._cachedNodes[key] = { accessed: accessed, node: node };
+ this._updateCachedTileAccess(key, accessed);
+ }
+ /**
+ * Get a value indicating if the graph is fill caching a node.
+ *
+ * @param {string} key - Key of node.
+ * @returns {boolean} Value indicating if the node is being fill cached.
+ */
+ isCachingFill(key) {
+ return key in this._cachingFill$;
+ }
+ /**
+ * Get a value indicating if the graph is fully caching a node.
+ *
+ * @param {string} key - Key of node.
+ * @returns {boolean} Value indicating if the node is being fully cached.
+ */
+ isCachingFull(key) {
+ return key in this._cachingFull$;
+ }
+ /**
+ * Get a value indicating if the graph is caching a sequence of a node.
+ *
+ * @param {string} key - Key of node.
+ * @returns {boolean} Value indicating if the sequence of a node is
+ * being cached.
+ */
+ isCachingNodeSequence(key) {
+ let node = this.getNode(key);
+ return node.sequenceId in this._cachingSequences$;
+ }
+ /**
+ * Get a value indicating if the graph is caching a sequence.
+ *
+ * @param {string} sequenceKey - Key of sequence.
+ * @returns {boolean} Value indicating if the sequence is
+ * being cached.
+ */
+ isCachingSequence(sequenceKey) {
+ return sequenceKey in this._cachingSequences$;
+ }
+ /**
+ * Get a value indicating if the graph is caching sequence nodes.
+ *
+ * @param {string} sequenceKey - Key of sequence.
+ * @returns {boolean} Value indicating if the sequence nodes are
+ * being cached.
+ */
+ isCachingSequenceNodes(sequenceKey) {
+ return sequenceKey in this._cachingSequenceNodes$;
+ }
+ /**
+ * Get a value indicating if the graph is caching the tiles
+ * required for calculating spatial edges of a node.
+ *
+ * @param {string} key - Key of node.
+ * @returns {boolean} Value indicating if the tiles of
+ * a node are being cached.
+ */
+ isCachingTiles(key) {
+ return key in this._requiredNodeTiles &&
+ this._requiredNodeTiles[key].cache.length === 0 &&
+ this._requiredNodeTiles[key].caching.length > 0;
+ }
+ /**
+ * Get a value indicating if the cache has been initialized
+ * for a node.
+ *
+ * @param {string} key - Key of node.
+ * @returns {boolean} Value indicating if the cache has been
+ * initialized for a node.
+ */
+ hasInitializedCache(key) {
+ return key in this._cachedNodes;
+ }
+ /**
+ * Get a value indicating if a node exist in the graph.
+ *
+ * @param {string} key - Key of node.
+ * @returns {boolean} Value indicating if a node exist in the graph.
+ */
+ hasNode(key) {
+ let accessed = new Date().getTime();
+ this._updateCachedNodeAccess(key, accessed);
+ this._updateCachedTileAccess(key, accessed);
+ return key in this._nodes;
+ }
+ /**
+ * Get a value indicating if a node sequence exist in the graph.
+ *
+ * @param {string} key - Key of node.
+ * @returns {boolean} Value indicating if a node sequence exist
+ * in the graph.
+ */
+ hasNodeSequence(key) {
+ let node = this.getNode(key);
+ let sequenceKey = node.sequenceId;
+ let hasNodeSequence = sequenceKey in this._sequences;
+ if (hasNodeSequence) {
+ this._sequences[sequenceKey].accessed = new Date().getTime();
+ }
+ return hasNodeSequence;
+ }
+ /**
+ * Get a value indicating if a sequence exist in the graph.
+ *
+ * @param {string} sequenceKey - Key of sequence.
+ * @returns {boolean} Value indicating if a sequence exist
+ * in the graph.
+ */
+ hasSequence(sequenceKey) {
+ let hasSequence = sequenceKey in this._sequences;
+ if (hasSequence) {
+ this._sequences[sequenceKey].accessed = new Date().getTime();
+ }
+ return hasSequence;
+ }
+ /**
+ * Get a value indicating if sequence nodes has been cached in the graph.
+ *
+ * @param {string} sequenceKey - Key of sequence.
+ * @returns {boolean} Value indicating if a sequence nodes has been
+ * cached in the graph.
+ */
+ hasSequenceNodes(sequenceKey) {
+ return sequenceKey in this._cachedSequenceNodes;
+ }
+ /**
+ * Get a value indicating if the graph has fully cached
+ * all nodes in the spatial area of a node.
+ *
+ * @param {string} key - Key of node.
+ * @returns {boolean} Value indicating if the spatial area
+ * of a node has been cached.
+ */
+ hasSpatialArea(key) {
+ if (!this.hasNode(key)) {
+ throw new GraphMapillaryError(`Spatial area nodes cannot be determined if node not in graph (${key}).`);
+ }
+ if (key in this._cachedSpatialEdges) {
+ return true;
+ }
+ if (key in this._requiredSpatialArea) {
+ return Object
+ .keys(this._requiredSpatialArea[key].cacheNodes)
+ .length === 0;
+ }
+ let node = this.getNode(key);
+ let bbox = this._graphCalculator
+ .boundingBoxCorners(node.lngLat, this._tileThreshold);
+ let spatialItems = this._nodeIndex
+ .search({
+ maxX: bbox[1].lng,
+ maxY: bbox[1].lat,
+ minX: bbox[0].lng,
+ minY: bbox[0].lat,
+ });
+ let spatialNodes = {
+ all: {},
+ cacheKeys: [],
+ cacheNodes: {},
+ };
+ for (let spatialItem of spatialItems) {
+ spatialNodes.all[spatialItem.node.id] = spatialItem.node;
+ if (!spatialItem.node.complete) {
+ spatialNodes.cacheKeys.push(spatialItem.node.id);
+ spatialNodes.cacheNodes[spatialItem.node.id] = spatialItem.node;
+ }
+ }
+ this._requiredSpatialArea[key] = spatialNodes;
+ return spatialNodes.cacheKeys.length === 0;
+ }
+ /**
+ * Get a value indicating if the graph has a tiles required
+ * for a node.
+ *
+ * @param {string} key - Key of node.
+ * @returns {boolean} Value indicating if the the tiles required
+ * by a node has been cached.
+ */
+ hasTiles(key) {
+ if (key in this._cachedNodeTiles) {
+ return true;
+ }
+ if (key in this._cachedSpatialEdges) {
+ return true;
+ }
+ if (!this.hasNode(key)) {
+ throw new GraphMapillaryError(`Image does not exist in graph (${key}).`);
+ }
+ let nodeTiles = { cache: [], caching: [] };
+ if (!(key in this._requiredNodeTiles)) {
+ const node = this.getNode(key);
+ const [sw, ne] = this._graphCalculator
+ .boundingBoxCorners(node.lngLat, this._tileThreshold);
+ nodeTiles.cache = this._api.data.geometry
+ .bboxToCellIds(sw, ne)
+ .filter((h) => {
+ return !(h in this._cachedTiles);
+ });
+ if (nodeTiles.cache.length > 0) {
+ this._requiredNodeTiles[key] = nodeTiles;
+ }
+ }
+ else {
+ nodeTiles = this._requiredNodeTiles[key];
+ }
+ return nodeTiles.cache.length === 0 && nodeTiles.caching.length === 0;
+ }
+ /**
+ * Get a node.
+ *
+ * @param {string} key - Key of node.
+ * @returns {Image} Retrieved node.
+ */
+ getNode(key) {
+ let accessed = new Date().getTime();
+ this._updateCachedNodeAccess(key, accessed);
+ this._updateCachedTileAccess(key, accessed);
+ return this._nodes[key];
+ }
+ /**
+ * Get a sequence.
+ *
+ * @param {string} sequenceKey - Key of sequence.
+ * @returns {Image} Retrieved sequence.
+ */
+ getSequence(sequenceKey) {
+ let sequenceAccess = this._sequences[sequenceKey];
+ sequenceAccess.accessed = new Date().getTime();
+ return sequenceAccess.sequence;
+ }
+ /**
+ * Reset all spatial edges of the graph nodes.
+ */
+ resetSpatialEdges() {
+ let cachedKeys = Object.keys(this._cachedSpatialEdges);
+ for (let cachedKey of cachedKeys) {
+ let node = this._cachedSpatialEdges[cachedKey];
+ node.resetSpatialEdges();
+ delete this._cachedSpatialEdges[cachedKey];
+ }
+ }
+ /**
+ * Reset the complete graph but keep the nodes corresponding
+ * to the supplied keys. All other nodes will be disposed.
+ *
+ * @param {Array<string>} keepKeys - Keys for nodes to keep
+ * in graph after reset.
+ */
+ reset(keepKeys) {
+ const nodes = [];
+ for (const key of keepKeys) {
+ if (!this.hasNode(key)) {
+ throw new Error(`Image does not exist ${key}`);
+ }
+ const node = this.getNode(key);
+ node.resetSequenceEdges();
+ node.resetSpatialEdges();
+ nodes.push(node);
+ }
+ for (let cachedKey of Object.keys(this._cachedNodes)) {
+ if (keepKeys.indexOf(cachedKey) !== -1) {
+ continue;
+ }
+ this._cachedNodes[cachedKey].node.dispose();
+ delete this._cachedNodes[cachedKey];
+ }
+ this._cachedNodeTiles = {};
+ this._cachedSpatialEdges = {};
+ this._cachedTiles = {};
+ this._cachingFill$ = {};
+ this._cachingFull$ = {};
+ this._cachingSequences$ = {};
+ this._cachingSpatialArea$ = {};
+ this._cachingTiles$ = {};
+ this._nodes = {};
+ this._nodeToTile = {};
+ this._preStored = {};
+ for (const node of nodes) {
+ this._nodes[node.id] = node;
+ const h = this._api.data.geometry.lngLatToCellId(node.originalLngLat);
+ this._preStore(h, node);
+ }
+ this._requiredNodeTiles = {};
+ this._requiredSpatialArea = {};
+ this._sequences = {};
+ this._nodeIndexTiles = {};
+ this._nodeIndex.clear();
+ }
+ /**
+ * Set the spatial node filter.
+ *
+ * @emits FilterFunction The filter function to the {@link Graph.filter$}
+ * observable.
+ *
+ * @param {FilterExpression} filter - Filter expression to be applied
+ * when calculating spatial edges.
+ */
+ setFilter(filter) {
+ this._filter = this._filterCreator.createFilter(filter);
+ this._filterSubject$.next(this._filter);
+ }
+ /**
+ * Uncache the graph according to the graph configuration.
+ *
+ * @description Uncaches unused tiles, unused nodes and
+ * sequences according to the numbers specified in the
+ * graph configuration. Sequences does not have a direct
+ * reference to either tiles or nodes and may be uncached
+ * even if they are related to the nodes that should be kept.
+ *
+ * @param {Array<string>} keepIds - Ids of nodes to keep in
+ * graph unrelated to last access. Tiles related to those keys
+ * will also be kept in graph.
+ * @param {Array<string>} keepCellIds - Ids of cells to keep in
+ * graph unrelated to last access. The nodes of the cells may
+ * still be uncached if not specified in the keep ids param
+ * but are guaranteed to not be disposed.
+ * @param {string} keepSequenceId - Optional id of sequence
+ * for which the belonging nodes should not be disposed or
+ * removed from the graph. These nodes may still be uncached if
+ * not specified in keep ids param but are guaranteed to not
+ * be disposed.
+ */
+ uncache(keepIds, keepCellIds, keepSequenceId) {
+ const idsInUse = {};
+ this._addNewKeys(idsInUse, this._cachingFull$);
+ this._addNewKeys(idsInUse, this._cachingFill$);
+ this._addNewKeys(idsInUse, this._cachingSpatialArea$);
+ this._addNewKeys(idsInUse, this._requiredNodeTiles);
+ this._addNewKeys(idsInUse, this._requiredSpatialArea);
+ for (const key of keepIds) {
+ if (key in idsInUse) {
+ continue;
+ }
+ idsInUse[key] = true;
+ }
+ const tileThreshold = this._tileThreshold;
+ const calculator = this._graphCalculator;
+ const geometry = this._api.data.geometry;
+ const keepCells = new Set(keepCellIds);
+ for (let id in idsInUse) {
+ if (!idsInUse.hasOwnProperty(id)) {
+ continue;
+ }
+ const node = this._nodes[id];
+ const [sw, ne] = calculator
+ .boundingBoxCorners(node.lngLat, tileThreshold);
+ const nodeCellIds = geometry.bboxToCellIds(sw, ne);
+ for (const nodeCellId of nodeCellIds) {
+ if (!keepCells.has(nodeCellId)) {
+ keepCells.add(nodeCellId);
+ }
+ }
+ }
+ const potentialCells = [];
+ for (let cellId in this._cachedTiles) {
+ if (!this._cachedTiles.hasOwnProperty(cellId) ||
+ keepCells.has(cellId)) {
+ continue;
+ }
+ potentialCells.push([cellId, this._cachedTiles[cellId]]);
+ }
+ const uncacheCells = potentialCells
+ .sort((h1, h2) => {
+ return h2[1].accessed - h1[1].accessed;
+ })
+ .slice(this._configuration.maxUnusedTiles)
+ .map((h) => {
+ return h[0];
+ });
+ for (let uncacheCell of uncacheCells) {
+ this._uncacheTile(uncacheCell, keepSequenceId);
+ }
+ const potentialPreStored = [];
+ const nonCachedPreStored = [];
+ for (let cellId in this._preStored) {
+ if (!this._preStored.hasOwnProperty(cellId) ||
+ cellId in this._cachingTiles$) {
+ continue;
+ }
+ const prestoredNodes = this._preStored[cellId];
+ for (let id in prestoredNodes) {
+ if (!prestoredNodes.hasOwnProperty(id) || id in idsInUse) {
+ continue;
+ }
+ if (prestoredNodes[id].sequenceId === keepSequenceId) {
+ continue;
+ }
+ if (id in this._cachedNodes) {
+ potentialPreStored.push([this._cachedNodes[id], cellId]);
+ }
+ else {
+ nonCachedPreStored.push([id, cellId]);
+ }
+ }
+ }
+ const uncachePreStored = potentialPreStored
+ .sort(([na1], [na2]) => {
+ return na2.accessed - na1.accessed;
+ })
+ .slice(this._configuration.maxUnusedPreStoredImages)
+ .map(([na, h]) => {
+ return [na.node.id, h];
+ });
+ this._uncachePreStored(nonCachedPreStored);
+ this._uncachePreStored(uncachePreStored);
+ const potentialNodes = [];
+ for (let id in this._cachedNodes) {
+ if (!this._cachedNodes.hasOwnProperty(id) || id in idsInUse) {
+ continue;
+ }
+ potentialNodes.push(this._cachedNodes[id]);
+ }
+ const uncacheNodes = potentialNodes
+ .sort((n1, n2) => {
+ return n2.accessed - n1.accessed;
+ })
+ .slice(this._configuration.maxUnusedImages);
+ for (const nodeAccess of uncacheNodes) {
+ nodeAccess.node.uncache();
+ const id = nodeAccess.node.id;
+ delete this._cachedNodes[id];
+ if (id in this._cachedNodeTiles) {
+ delete this._cachedNodeTiles[id];
+ }
+ if (id in this._cachedSpatialEdges) {
+ delete this._cachedSpatialEdges[id];
+ }
+ }
+ const potentialSequences = [];
+ for (let sequenceId in this._sequences) {
+ if (!this._sequences.hasOwnProperty(sequenceId) ||
+ sequenceId in this._cachingSequences$ ||
+ sequenceId === keepSequenceId) {
+ continue;
+ }
+ potentialSequences.push(this._sequences[sequenceId]);
+ }
+ const uncacheSequences = potentialSequences
+ .sort((s1, s2) => {
+ return s2.accessed - s1.accessed;
+ })
+ .slice(this._configuration.maxSequences);
+ for (const sequenceAccess of uncacheSequences) {
+ const sequenceId = sequenceAccess.sequence.id;
+ delete this._sequences[sequenceId];
+ if (sequenceId in this._cachedSequenceNodes) {
+ delete this._cachedSequenceNodes[sequenceId];
+ }
+ sequenceAccess.sequence.dispose();
+ }
+ }
+ /**
+ * Updates existing cells with new core nodes.
+ *
+ * @description Non-existing cells are discarded
+ * and not requested at all.
+ *
+ * Existing nodes are not changed.
+ *
+ * New nodes are not made full or getting assets
+ * cached.
+ *
+ * @param {Array<string>} cellIds - Cell ids.
+ * @returns {Observable<Array<Image>>} Observable
+ * emitting the updated cells.
+ */
+ updateCells$(cellIds) {
+ const cachedCells = this._cachedTiles;
+ const cachingCells = this._cachingTiles$;
+ return from(cellIds)
+ .pipe(mergeMap((cellId) => {
+ if (cellId in cachedCells) {
+ return this._updateCell$(cellId);
+ }
+ if (cellId in cachingCells) {
+ return cachingCells[cellId]
+ .pipe(catchError(() => {
+ return of(this);
+ }), mergeMap(() => this._updateCell$(cellId)));
+ }
+ return empty();
+ }));
+ }
+ /**
+ * Unsubscribes all subscriptions.
+ *
+ * @description Afterwards, you must not call any other methods
+ * on the graph instance.
+ */
+ unsubscribe() {
+ this._filterSubscription.unsubscribe();
+ }
+ _addNewKeys(keys, dict) {
+ for (let key in dict) {
+ if (!dict.hasOwnProperty(key) || !this.hasNode(key)) {
+ continue;
+ }
+ if (!(key in keys)) {
+ keys[key] = true;
+ }
+ }
+ }
+ _cacheSequence$(sequenceId) {
+ if (sequenceId in this._cachingSequences$) {
+ return this._cachingSequences$[sequenceId];
+ }
+ this._cachingSequences$[sequenceId] = this._api
+ .getSequence$(sequenceId)
+ .pipe(tap((sequence) => {
+ if (!sequence) {
+ console.warn(`Sequence does not exist ` +
+ `(${sequenceId})`);
+ }
+ else {
+ if (!(sequence.id in this._sequences)) {
+ this._sequences[sequence.id] = {
+ accessed: new Date().getTime(),
+ sequence: new Sequence(sequence),
+ };
+ }
+ delete this._cachingSequences$[sequenceId];
+ }
+ }), map(() => { return this; }), finalize(() => {
+ if (sequenceId in this._cachingSequences$) {
+ delete this._cachingSequences$[sequenceId];
+ }
+ this._changed$.next(this);
+ }), publish(), refCount());
+ return this._cachingSequences$[sequenceId];
+ }
+ _cacheTile$(cellId) {
+ this._cachingTiles$[cellId] = this._api
+ .getCoreImages$(cellId)
+ .pipe(tap((contract) => {
+ if (cellId in this._cachedTiles) {
+ return;
+ }
+ const cores = contract.images;
+ this._nodeIndexTiles[cellId] = [];
+ this._cachedTiles[cellId] = {
+ accessed: new Date().getTime(),
+ nodes: [],
+ };
+ const hCache = this._cachedTiles[cellId].nodes;
+ const preStored = this._removeFromPreStore(cellId);
+ for (const core of cores) {
+ if (!core) {
+ break;
+ }
+ if (core.sequence.id == null) {
+ console.warn(`Sequence missing, discarding ` +
+ `node (${core.id})`);
+ continue;
+ }
+ if (preStored != null && core.id in preStored) {
+ const preStoredNode = preStored[core.id];
+ delete preStored[core.id];
+ hCache.push(preStoredNode);
+ const preStoredNodeIndexItem = {
+ lat: preStoredNode.lngLat.lat,
+ lng: preStoredNode.lngLat.lng,
+ node: preStoredNode,
+ };
+ this._nodeIndex.insert(preStoredNodeIndexItem);
+ this._nodeIndexTiles[cellId]
+ .push(preStoredNodeIndexItem);
+ this._nodeToTile[preStoredNode.id] = cellId;
+ continue;
+ }
+ const node = new Image$1(core);
+ hCache.push(node);
+ const nodeIndexItem = {
+ lat: node.lngLat.lat,
+ lng: node.lngLat.lng,
+ node: node,
+ };
+ this._nodeIndex.insert(nodeIndexItem);
+ this._nodeIndexTiles[cellId].push(nodeIndexItem);
+ this._nodeToTile[node.id] = cellId;
+ this._setNode(node);
+ }
+ delete this._cachingTiles$[cellId];
+ }), map(() => this), catchError((error) => {
+ delete this._cachingTiles$[cellId];
+ throw error;
+ }), publish(), refCount());
+ return this._cachingTiles$[cellId];
+ }
+ _makeFull(node, fillNode) {
+ if (fillNode.computed_altitude == null) {
+ fillNode.computed_altitude = this._defaultAlt;
+ }
+ if (fillNode.computed_rotation == null) {
+ fillNode.computed_rotation = this._graphCalculator.rotationFromCompass(fillNode.compass_angle, fillNode.exif_orientation);
+ }
+ node.makeComplete(fillNode);
+ }
+ _preStore(h, node) {
+ if (!(h in this._preStored)) {
+ this._preStored[h] = {};
+ }
+ this._preStored[h][node.id] = node;
+ }
+ _removeFromPreStore(h) {
+ let preStored = null;
+ if (h in this._preStored) {
+ preStored = this._preStored[h];
+ delete this._preStored[h];
+ }
+ return preStored;
+ }
+ _setNode(node) {
+ let key = node.id;
+ if (this.hasNode(key)) {
+ throw new GraphMapillaryError(`Image already exist (${key}).`);
+ }
+ this._nodes[key] = node;
+ }
+ _uncacheTile(h, keepSequenceKey) {
+ for (let node of this._cachedTiles[h].nodes) {
+ let key = node.id;
+ delete this._nodeToTile[key];
+ if (key in this._cachedNodes) {
+ delete this._cachedNodes[key];
+ }
+ if (key in this._cachedNodeTiles) {
+ delete this._cachedNodeTiles[key];
+ }
+ if (key in this._cachedSpatialEdges) {
+ delete this._cachedSpatialEdges[key];
+ }
+ if (node.sequenceId === keepSequenceKey) {
+ this._preStore(h, node);
+ node.uncache();
+ }
+ else {
+ delete this._nodes[key];
+ if (node.sequenceId in this._cachedSequenceNodes) {
+ delete this._cachedSequenceNodes[node.sequenceId];
+ }
+ node.dispose();
+ }
+ }
+ for (let nodeIndexItem of this._nodeIndexTiles[h]) {
+ this._nodeIndex.remove(nodeIndexItem);
+ }
+ delete this._nodeIndexTiles[h];
+ delete this._cachedTiles[h];
+ }
+ _uncachePreStored(preStored) {
+ let hs = {};
+ for (let [key, h] of preStored) {
+ if (key in this._nodes) {
+ delete this._nodes[key];
+ }
+ if (key in this._cachedNodes) {
+ delete this._cachedNodes[key];
+ }
+ let node = this._preStored[h][key];
+ if (node.sequenceId in this._cachedSequenceNodes) {
+ delete this._cachedSequenceNodes[node.sequenceId];
+ }
+ delete this._preStored[h][key];
+ node.dispose();
+ hs[h] = true;
+ }
+ for (let h in hs) {
+ if (!hs.hasOwnProperty(h)) {
+ continue;
+ }
+ if (Object.keys(this._preStored[h]).length === 0) {
+ delete this._preStored[h];
+ }
+ }
+ }
+ _updateCachedTileAccess(key, accessed) {
+ if (key in this._nodeToTile) {
+ this._cachedTiles[this._nodeToTile[key]].accessed = accessed;
+ }
+ }
+ _updateCachedNodeAccess(key, accessed) {
+ if (key in this._cachedNodes) {
+ this._cachedNodes[key].accessed = accessed;
+ }
+ }
+ _updateCell$(cellId) {
+ return this._api.getCoreImages$(cellId).pipe(mergeMap((contract) => {
+ if (!(cellId in this._cachedTiles)) {
+ return empty();
+ }
+ const nodeIndex = this._nodeIndex;
+ const nodeIndexCell = this._nodeIndexTiles[cellId];
+ const nodeToCell = this._nodeToTile;
+ const cell = this._cachedTiles[cellId];
+ cell.accessed = new Date().getTime();
+ const cellNodes = cell.nodes;
+ const cores = contract.images;
+ for (const core of cores) {
+ if (core == null) {
+ break;
+ }
+ if (this.hasNode(core.id)) {
+ continue;
+ }
+ if (core.sequence.id == null) {
+ console.warn(`Sequence missing, discarding ` +
+ `node (${core.id})`);
+ continue;
+ }
+ const node = new Image$1(core);
+ cellNodes.push(node);
+ const nodeIndexItem = {
+ lat: node.lngLat.lat,
+ lng: node.lngLat.lng,
+ node: node,
+ };
+ nodeIndex.insert(nodeIndexItem);
+ nodeIndexCell.push(nodeIndexItem);
+ nodeToCell[node.id] = cellId;
+ this._setNode(node);
+ }
+ return of(cellId);
+ }), catchError((error) => {
+ console.error(error);
+ return empty();
+ }));
+ }
+}
+
+class MarkerSet {
+ constructor() {
+ this._hash = {};
+ this._index = new MarkerSet._spatialIndex(16);
+ this._indexChanged$ = new Subject();
+ this._updated$ = new Subject();
+ }
+ static register(spatialIndex) {
+ MarkerSet._spatialIndex = spatialIndex;
+ }
+ get changed$() {
+ return this._indexChanged$;
+ }
+ get updated$() {
+ return this._updated$;
+ }
+ add(markers) {
+ const updated = [];
+ const hash = this._hash;
+ const index = this._index;
+ for (const marker of markers) {
+ const id = marker.id;
+ if (id in hash) {
+ index.remove(hash[id]);
+ updated.push(marker);
+ }
+ const item = {
+ lat: marker.lngLat.lat,
+ lng: marker.lngLat.lng,
+ marker: marker,
+ };
+ hash[id] = item;
+ index.insert(item);
+ }
+ if (updated.length > 0) {
+ this._updated$.next(updated);
+ }
+ if (markers.length > updated.length) {
+ this._indexChanged$.next(this);
+ }
+ }
+ has(id) {
+ return id in this._hash;
+ }
+ get(id) {
+ return this.has(id) ? this._hash[id].marker : undefined;
+ }
+ getAll() {
+ return this._index
+ .all()
+ .map((indexItem) => {
+ return indexItem.marker;
+ });
+ }
+ remove(ids) {
+ const hash = this._hash;
+ const index = this._index;
+ let changed = false;
+ for (const id of ids) {
+ if (!(id in hash)) {
+ continue;
+ }
+ const item = hash[id];
+ index.remove(item);
+ delete hash[id];
+ changed = true;
+ }
+ if (changed) {
+ this._indexChanged$.next(this);
+ }
+ }
+ removeAll() {
+ this._hash = {};
+ this._index.clear();
+ this._indexChanged$.next(this);
+ }
+ search([sw, ne]) {
+ return this._index
+ .search({
+ maxX: ne.lng,
+ maxY: ne.lat,
+ minX: sw.lng,
+ minY: sw.lat,
+ })
+ .map((indexItem) => {
+ return indexItem.marker;
+ });
+ }
+ update(marker) {
+ const hash = this._hash;
+ const index = this._index;
+ const id = marker.id;
+ if (!(id in hash)) {
+ return;
+ }
+ index.remove(hash[id]);
+ const item = {
+ lat: marker.lngLat.lat,
+ lng: marker.lngLat.lng,
+ marker: marker,
+ };
+ hash[id] = item;
+ index.insert(item);
+ }
+}
+
+function quickselect(arr, k, left, right, compare) {
+ quickselectStep(arr, k, left || 0, right || (arr.length - 1), compare || defaultCompare$2);
+}
+
+function quickselectStep(arr, k, left, right, compare) {
+
+ while (right > left) {
+ if (right - left > 600) {
+ var n = right - left + 1;
+ var m = k - left + 1;
+ var z = Math.log(n);
+ var s = 0.5 * Math.exp(2 * z / 3);
+ var sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1);
+ var newLeft = Math.max(left, Math.floor(k - m * s / n + sd));
+ var newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd));
+ quickselectStep(arr, k, newLeft, newRight, compare);
+ }
+
+ var t = arr[k];
+ var i = left;
+ var j = right;
+
+ swap(arr, left, k);
+ if (compare(arr[right], t) > 0) swap(arr, left, right);
+
+ while (i < j) {
+ swap(arr, i, j);
+ i++;
+ j--;
+ while (compare(arr[i], t) < 0) i++;
+ while (compare(arr[j], t) > 0) j--;
+ }
+
+ if (compare(arr[left], t) === 0) swap(arr, left, j);
+ else {
+ j++;
+ swap(arr, j, right);
+ }
+
+ if (j <= k) left = j + 1;
+ if (k <= j) right = j - 1;
+ }
+}
+
+function swap(arr, i, j) {
+ var tmp = arr[i];
+ arr[i] = arr[j];
+ arr[j] = tmp;
+}
+
+function defaultCompare$2(a, b) {
+ return a < b ? -1 : a > b ? 1 : 0;
+}
+
+class RBush {
+ constructor(maxEntries = 9) {
+ // max entries in a node is 9 by default; min node fill is 40% for best performance
+ this._maxEntries = Math.max(4, maxEntries);
+ this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4));
+ this.clear();
+ }
+
+ all() {
+ return this._all(this.data, []);
+ }
+
+ search(bbox) {
+ let node = this.data;
+ const result = [];
+
+ if (!intersects$1(bbox, node)) return result;
+
+ const toBBox = this.toBBox;
+ const nodesToSearch = [];
+
+ while (node) {
+ for (let i = 0; i < node.children.length; i++) {
+ const child = node.children[i];
+ const childBBox = node.leaf ? toBBox(child) : child;
+
+ if (intersects$1(bbox, childBBox)) {
+ if (node.leaf) result.push(child);
+ else if (contains(bbox, childBBox)) this._all(child, result);
+ else nodesToSearch.push(child);
+ }
+ }
+ node = nodesToSearch.pop();
+ }
+
+ return result;
+ }
+
+ collides(bbox) {
+ let node = this.data;
+
+ if (!intersects$1(bbox, node)) return false;
+
+ const nodesToSearch = [];
+ while (node) {
+ for (let i = 0; i < node.children.length; i++) {
+ const child = node.children[i];
+ const childBBox = node.leaf ? this.toBBox(child) : child;
+
+ if (intersects$1(bbox, childBBox)) {
+ if (node.leaf || contains(bbox, childBBox)) return true;
+ nodesToSearch.push(child);
+ }
+ }
+ node = nodesToSearch.pop();
+ }
+
+ return false;
+ }
+
+ load(data) {
+ if (!(data && data.length)) return this;
+
+ if (data.length < this._minEntries) {
+ for (let i = 0; i < data.length; i++) {
+ this.insert(data[i]);
+ }
+ return this;
+ }
+
+ // recursively build the tree with the given data from scratch using OMT algorithm
+ let node = this._build(data.slice(), 0, data.length - 1, 0);
+
+ if (!this.data.children.length) {
+ // save as is if tree is empty
+ this.data = node;
+
+ } else if (this.data.height === node.height) {
+ // split root if trees have the same height
+ this._splitRoot(this.data, node);
+
+ } else {
+ if (this.data.height < node.height) {
+ // swap trees if inserted one is bigger
+ const tmpNode = this.data;
+ this.data = node;
+ node = tmpNode;
+ }
+
+ // insert the small tree into the large tree at appropriate level
+ this._insert(node, this.data.height - node.height - 1, true);
+ }
+
+ return this;
+ }
+
+ insert(item) {
+ if (item) this._insert(item, this.data.height - 1);
+ return this;
+ }
+
+ clear() {
+ this.data = createNode([]);
+ return this;
+ }
+
+ remove(item, equalsFn) {
+ if (!item) return this;
+
+ let node = this.data;
+ const bbox = this.toBBox(item);
+ const path = [];
+ const indexes = [];
+ let i, parent, goingUp;
+
+ // depth-first iterative tree traversal
+ while (node || path.length) {
+
+ if (!node) { // go up
+ node = path.pop();
+ parent = path[path.length - 1];
+ i = indexes.pop();
+ goingUp = true;
+ }
+
+ if (node.leaf) { // check current node
+ const index = findItem(item, node.children, equalsFn);
+
+ if (index !== -1) {
+ // item found, remove the item and condense tree upwards
+ node.children.splice(index, 1);
+ path.push(node);
+ this._condense(path);
+ return this;
+ }
+ }
+
+ if (!goingUp && !node.leaf && contains(node, bbox)) { // go down
+ path.push(node);
+ indexes.push(i);
+ i = 0;
+ parent = node;
+ node = node.children[0];
+
+ } else if (parent) { // go right
+ i++;
+ node = parent.children[i];
+ goingUp = false;
+
+ } else node = null; // nothing found
+ }
+
+ return this;
+ }
+
+ toBBox(item) { return item; }
+
+ compareMinX(a, b) { return a.minX - b.minX; }
+ compareMinY(a, b) { return a.minY - b.minY; }
+
+ toJSON() { return this.data; }
+
+ fromJSON(data) {
+ this.data = data;
+ return this;
+ }
+
+ _all(node, result) {
+ const nodesToSearch = [];
+ while (node) {
+ if (node.leaf) result.push(...node.children);
+ else nodesToSearch.push(...node.children);
+
+ node = nodesToSearch.pop();
+ }
+ return result;
+ }
+
+ _build(items, left, right, height) {
+
+ const N = right - left + 1;
+ let M = this._maxEntries;
+ let node;
+
+ if (N <= M) {
+ // reached leaf level; return leaf
+ node = createNode(items.slice(left, right + 1));
+ calcBBox(node, this.toBBox);
+ return node;
+ }
+
+ if (!height) {
+ // target height of the bulk-loaded tree
+ height = Math.ceil(Math.log(N) / Math.log(M));
+
+ // target number of root entries to maximize storage utilization
+ M = Math.ceil(N / Math.pow(M, height - 1));
+ }
+
+ node = createNode([]);
+ node.leaf = false;
+ node.height = height;
+
+ // split the items into M mostly square tiles
+
+ const N2 = Math.ceil(N / M);
+ const N1 = N2 * Math.ceil(Math.sqrt(M));
+
+ multiSelect(items, left, right, N1, this.compareMinX);
+
+ for (let i = left; i <= right; i += N1) {
+
+ const right2 = Math.min(i + N1 - 1, right);
+
+ multiSelect(items, i, right2, N2, this.compareMinY);
+
+ for (let j = i; j <= right2; j += N2) {
+
+ const right3 = Math.min(j + N2 - 1, right2);
+
+ // pack each entry recursively
+ node.children.push(this._build(items, j, right3, height - 1));
+ }
+ }
+
+ calcBBox(node, this.toBBox);
+
+ return node;
+ }
+
+ _chooseSubtree(bbox, node, level, path) {
+ while (true) {
+ path.push(node);
+
+ if (node.leaf || path.length - 1 === level) break;
+
+ let minArea = Infinity;
+ let minEnlargement = Infinity;
+ let targetNode;
+
+ for (let i = 0; i < node.children.length; i++) {
+ const child = node.children[i];
+ const area = bboxArea(child);
+ const enlargement = enlargedArea(bbox, child) - area;
+
+ // choose entry with the least area enlargement
+ if (enlargement < minEnlargement) {
+ minEnlargement = enlargement;
+ minArea = area < minArea ? area : minArea;
+ targetNode = child;
+
+ } else if (enlargement === minEnlargement) {
+ // otherwise choose one with the smallest area
+ if (area < minArea) {
+ minArea = area;
+ targetNode = child;
+ }
+ }
+ }
+
+ node = targetNode || node.children[0];
+ }
+
+ return node;
+ }
+
+ _insert(item, level, isNode) {
+ const bbox = isNode ? item : this.toBBox(item);
+ const insertPath = [];
+
+ // find the best node for accommodating the item, saving all nodes along the path too
+ const node = this._chooseSubtree(bbox, this.data, level, insertPath);
+
+ // put the item into the node
+ node.children.push(item);
+ extend(node, bbox);
+
+ // split on node overflow; propagate upwards if necessary
+ while (level >= 0) {
+ if (insertPath[level].children.length > this._maxEntries) {
+ this._split(insertPath, level);
+ level--;
+ } else break;
+ }
+
+ // adjust bboxes along the insertion path
+ this._adjustParentBBoxes(bbox, insertPath, level);
+ }
+
+ // split overflowed node into two
+ _split(insertPath, level) {
+ const node = insertPath[level];
+ const M = node.children.length;
+ const m = this._minEntries;
+
+ this._chooseSplitAxis(node, m, M);
+
+ const splitIndex = this._chooseSplitIndex(node, m, M);
+
+ const newNode = createNode(node.children.splice(splitIndex, node.children.length - splitIndex));
+ newNode.height = node.height;
+ newNode.leaf = node.leaf;
+
+ calcBBox(node, this.toBBox);
+ calcBBox(newNode, this.toBBox);
+
+ if (level) insertPath[level - 1].children.push(newNode);
+ else this._splitRoot(node, newNode);
+ }
+
+ _splitRoot(node, newNode) {
+ // split root node
+ this.data = createNode([node, newNode]);
+ this.data.height = node.height + 1;
+ this.data.leaf = false;
+ calcBBox(this.data, this.toBBox);
+ }
+
+ _chooseSplitIndex(node, m, M) {
+ let index;
+ let minOverlap = Infinity;
+ let minArea = Infinity;
+
+ for (let i = m; i <= M - m; i++) {
+ const bbox1 = distBBox(node, 0, i, this.toBBox);
+ const bbox2 = distBBox(node, i, M, this.toBBox);
+
+ const overlap = intersectionArea(bbox1, bbox2);
+ const area = bboxArea(bbox1) + bboxArea(bbox2);
+
+ // choose distribution with minimum overlap
+ if (overlap < minOverlap) {
+ minOverlap = overlap;
+ index = i;
+
+ minArea = area < minArea ? area : minArea;
+
+ } else if (overlap === minOverlap) {
+ // otherwise choose distribution with minimum area
+ if (area < minArea) {
+ minArea = area;
+ index = i;
+ }
+ }
+ }
+
+ return index || M - m;
+ }
+
+ // sorts node children by the best axis for split
+ _chooseSplitAxis(node, m, M) {
+ const compareMinX = node.leaf ? this.compareMinX : compareNodeMinX;
+ const compareMinY = node.leaf ? this.compareMinY : compareNodeMinY;
+ const xMargin = this._allDistMargin(node, m, M, compareMinX);
+ const yMargin = this._allDistMargin(node, m, M, compareMinY);
+
+ // if total distributions margin value is minimal for x, sort by minX,
+ // otherwise it's already sorted by minY
+ if (xMargin < yMargin) node.children.sort(compareMinX);
+ }
+
+ // total margin of all possible split distributions where each node is at least m full
+ _allDistMargin(node, m, M, compare) {
+ node.children.sort(compare);
+
+ const toBBox = this.toBBox;
+ const leftBBox = distBBox(node, 0, m, toBBox);
+ const rightBBox = distBBox(node, M - m, M, toBBox);
+ let margin = bboxMargin(leftBBox) + bboxMargin(rightBBox);
+
+ for (let i = m; i < M - m; i++) {
+ const child = node.children[i];
+ extend(leftBBox, node.leaf ? toBBox(child) : child);
+ margin += bboxMargin(leftBBox);
+ }
+
+ for (let i = M - m - 1; i >= m; i--) {
+ const child = node.children[i];
+ extend(rightBBox, node.leaf ? toBBox(child) : child);
+ margin += bboxMargin(rightBBox);
+ }
+
+ return margin;
+ }
+
+ _adjustParentBBoxes(bbox, path, level) {
+ // adjust bboxes along the given tree path
+ for (let i = level; i >= 0; i--) {
+ extend(path[i], bbox);
+ }
+ }
+
+ _condense(path) {
+ // go through the path, removing empty nodes and updating bboxes
+ for (let i = path.length - 1, siblings; i >= 0; i--) {
+ if (path[i].children.length === 0) {
+ if (i > 0) {
+ siblings = path[i - 1].children;
+ siblings.splice(siblings.indexOf(path[i]), 1);
+
+ } else this.clear();
+
+ } else calcBBox(path[i], this.toBBox);
+ }
+ }
+}
+
+function findItem(item, items, equalsFn) {
+ if (!equalsFn) return items.indexOf(item);
+
+ for (let i = 0; i < items.length; i++) {
+ if (equalsFn(item, items[i])) return i;
+ }
+ return -1;
+}
+
+// calculate node's bbox from bboxes of its children
+function calcBBox(node, toBBox) {
+ distBBox(node, 0, node.children.length, toBBox, node);
+}
+
+// min bounding rectangle of node children from k to p-1
+function distBBox(node, k, p, toBBox, destNode) {
+ if (!destNode) destNode = createNode(null);
+ destNode.minX = Infinity;
+ destNode.minY = Infinity;
+ destNode.maxX = -Infinity;
+ destNode.maxY = -Infinity;
+
+ for (let i = k; i < p; i++) {
+ const child = node.children[i];
+ extend(destNode, node.leaf ? toBBox(child) : child);
+ }
+
+ return destNode;
+}
+
+function extend(a, b) {
+ a.minX = Math.min(a.minX, b.minX);
+ a.minY = Math.min(a.minY, b.minY);
+ a.maxX = Math.max(a.maxX, b.maxX);
+ a.maxY = Math.max(a.maxY, b.maxY);
+ return a;
+}
+
+function compareNodeMinX(a, b) { return a.minX - b.minX; }
+function compareNodeMinY(a, b) { return a.minY - b.minY; }
+
+function bboxArea(a) { return (a.maxX - a.minX) * (a.maxY - a.minY); }
+function bboxMargin(a) { return (a.maxX - a.minX) + (a.maxY - a.minY); }
+
+function enlargedArea(a, b) {
+ return (Math.max(b.maxX, a.maxX) - Math.min(b.minX, a.minX)) *
+ (Math.max(b.maxY, a.maxY) - Math.min(b.minY, a.minY));
+}
+
+function intersectionArea(a, b) {
+ const minX = Math.max(a.minX, b.minX);
+ const minY = Math.max(a.minY, b.minY);
+ const maxX = Math.min(a.maxX, b.maxX);
+ const maxY = Math.min(a.maxY, b.maxY);
+
+ return Math.max(0, maxX - minX) *
+ Math.max(0, maxY - minY);
+}
+
+function contains(a, b) {
+ return a.minX <= b.minX &&
+ a.minY <= b.minY &&
+ b.maxX <= a.maxX &&
+ b.maxY <= a.maxY;
+}
+
+function intersects$1(a, b) {
+ return b.minX <= a.maxX &&
+ b.minY <= a.maxY &&
+ b.maxX >= a.minX &&
+ b.maxY >= a.minY;
+}
+
+function createNode(children) {
+ return {
+ children,
+ height: 1,
+ leaf: true,
+ minX: Infinity,
+ minY: Infinity,
+ maxX: -Infinity,
+ maxY: -Infinity
+ };
+}
+
+// sort an array so that items come in groups of n unsorted items, with groups sorted between each other;
+// combines selection algorithm with binary divide & conquer approach
+
+function multiSelect(arr, left, right, n, compare) {
+ const stack = [left, right];
+
+ while (stack.length) {
+ right = stack.pop();
+ left = stack.pop();
+
+ if (right - left <= n) continue;
+
+ const mid = left + Math.ceil((right - left) / n / 2) * n;
+ quickselect(arr, mid, left, right, compare);
+
+ stack.push(left, mid, mid, right);
+ }
+}
+
+class GeoRBush extends RBush {
+ compareMinX(a, b) {
+ return a.lng - b.lng;
+ }
+ compareMinY(a, b) {
+ return a.lat - b.lat;
+ }
+ toBBox(item) {
+ return {
+ minX: item.lng,
+ minY: item.lat,
+ maxX: item.lng,
+ maxY: item.lat,
+ };
+ }
+}
+
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Ported from Webkit
+ * http://svn.webkit.org/repository/webkit/trunk/Source/WebCore/platform/graphics/UnitBezier.h
+ */
+var unitbezier = UnitBezier;
+
+function UnitBezier(p1x, p1y, p2x, p2y) {
+ // Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
+ this.cx = 3.0 * p1x;
+ this.bx = 3.0 * (p2x - p1x) - this.cx;
+ this.ax = 1.0 - this.cx - this.bx;
+
+ this.cy = 3.0 * p1y;
+ this.by = 3.0 * (p2y - p1y) - this.cy;
+ this.ay = 1.0 - this.cy - this.by;
+
+ this.p1x = p1x;
+ this.p1y = p2y;
+ this.p2x = p2x;
+ this.p2y = p2y;
+}
+
+UnitBezier.prototype.sampleCurveX = function(t) {
+ // `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
+ return ((this.ax * t + this.bx) * t + this.cx) * t;
+};
+
+UnitBezier.prototype.sampleCurveY = function(t) {
+ return ((this.ay * t + this.by) * t + this.cy) * t;
+};
+
+UnitBezier.prototype.sampleCurveDerivativeX = function(t) {
+ return (3.0 * this.ax * t + 2.0 * this.bx) * t + this.cx;
+};
+
+UnitBezier.prototype.solveCurveX = function(x, epsilon) {
+ if (typeof epsilon === 'undefined') epsilon = 1e-6;
+
+ var t0, t1, t2, x2, i;
+
+ // First try a few iterations of Newton's method -- normally very fast.
+ for (t2 = x, i = 0; i < 8; i++) {
+
+ x2 = this.sampleCurveX(t2) - x;
+ if (Math.abs(x2) < epsilon) return t2;
+
+ var d2 = this.sampleCurveDerivativeX(t2);
+ if (Math.abs(d2) < 1e-6) break;
+
+ t2 = t2 - x2 / d2;
+ }
+
+ // Fall back to the bisection method for reliability.
+ t0 = 0.0;
+ t1 = 1.0;
+ t2 = x;
+
+ if (t2 < t0) return t0;
+ if (t2 > t1) return t1;
+
+ while (t0 < t1) {
+
+ x2 = this.sampleCurveX(t2);
+ if (Math.abs(x2 - x) < epsilon) return t2;
+
+ if (x > x2) {
+ t0 = t2;
+ } else {
+ t1 = t2;
+ }
+
+ t2 = (t1 - t0) * 0.5 + t0;
+ }
+
+ // Failure.
+ return t2;
+};
+
+UnitBezier.prototype.solve = function(x, epsilon) {
+ return this.sampleCurveY(this.solveCurveX(x, epsilon));
+};
+
+/**
+ * Enumeration for transition mode
+ * @enum {number}
+ * @readonly
+ * @description Modes for specifying how transitions
+ * between images are performed.
+ */
+var TransitionMode;
+(function (TransitionMode) {
+ /**
+ * Default transitions.
+ *
+ * @description The viewer dynamically determines
+ * whether transitions should be performed with or
+ * without motion and blending for each transition
+ * based on the underlying data.
+ */
+ TransitionMode[TransitionMode["Default"] = 0] = "Default";
+ /**
+ * Instantaneous transitions.
+ *
+ * @description All transitions are performed
+ * without motion or blending.
+ */
+ TransitionMode[TransitionMode["Instantaneous"] = 1] = "Instantaneous";
+})(TransitionMode || (TransitionMode = {}));
+
+/**
+ * @class Camera
+ *
+ * @classdesc Holds information about a camera.
+ */
+class Camera {
+ /**
+ * Create a new camera instance.
+ * @param {Transform} [transform] - Optional transform instance.
+ */
+ constructor(transform) {
+ if (transform != null) {
+ this._position = new Vector3().fromArray(transform.unprojectSfM([0, 0], 0));
+ this._lookat = new Vector3().fromArray(transform.unprojectSfM([0, 0], 10));
+ this._up = transform.upVector();
+ this._focal = this._getFocal(transform);
+ }
+ else {
+ this._position = new Vector3(0, 0, 0);
+ this._lookat = new Vector3(1, 0, 0);
+ this._up = new Vector3(0, 0, 1);
+ this._focal = 1;
+ }
+ }
+ /**
+ * Get position.
+ * @returns {THREE.Vector3} The position vector.
+ */
+ get position() {
+ return this._position;
+ }
+ /**
+ * Get lookat.
+ * @returns {THREE.Vector3} The lookat vector.
+ */
+ get lookat() {
+ return this._lookat;
+ }
+ /**
+ * Get up.
+ * @returns {THREE.Vector3} The up vector.
+ */
+ get up() {
+ return this._up;
+ }
+ /**
+ * Get focal.
+ * @returns {number} The focal length.
+ */
+ get focal() {
+ return this._focal;
+ }
+ /**
+ * Set focal.
+ */
+ set focal(value) {
+ this._focal = value;
+ }
+ /**
+ * Update this camera to the linearly interpolated value of two other cameras.
+ *
+ * @param {Camera} a - First camera.
+ * @param {Camera} b - Second camera.
+ * @param {number} alpha - Interpolation value on the interval [0, 1].
+ */
+ lerpCameras(a, b, alpha) {
+ this._position.subVectors(b.position, a.position).multiplyScalar(alpha).add(a.position);
+ this._lookat.subVectors(b.lookat, a.lookat).multiplyScalar(alpha).add(a.lookat);
+ this._up.subVectors(b.up, a.up).multiplyScalar(alpha).add(a.up);
+ this._focal = (1 - alpha) * a.focal + alpha * b.focal;
+ }
+ /**
+ * Copy the properties of another camera to this camera.
+ *
+ * @param {Camera} other - Another camera.
+ */
+ copy(other) {
+ this._position.copy(other.position);
+ this._lookat.copy(other.lookat);
+ this._up.copy(other.up);
+ this._focal = other.focal;
+ }
+ /**
+ * Clone this camera.
+ *
+ * @returns {Camera} A camera with cloned properties equal to this camera.
+ */
+ clone() {
+ let camera = new Camera();
+ camera.position.copy(this._position);
+ camera.lookat.copy(this._lookat);
+ camera.up.copy(this._up);
+ camera.focal = this._focal;
+ return camera;
+ }
+ /**
+ * Determine the distance between this camera and another camera.
+ *
+ * @param {Camera} other - Another camera.
+ * @returns {number} The distance between the cameras.
+ */
+ diff(other) {
+ let pd = this._position.distanceToSquared(other.position);
+ let ld = this._lookat.distanceToSquared(other.lookat);
+ let ud = this._up.distanceToSquared(other.up);
+ let fd = 100 * Math.abs(this._focal - other.focal);
+ return Math.max(pd, ld, ud, fd);
+ }
+ /**
+ * Get the focal length based on the transform.
+ *
+ * @description Returns the focal length corresponding
+ * to a 90 degree field of view for spherical
+ * transforms.
+ *
+ * Returns the transform focal length for other
+ * projection types.
+ *
+ * @returns {number} Focal length.
+ */
+ _getFocal(transform) {
+ if (!isSpherical(transform.cameraType)) {
+ return transform.focal;
+ }
+ return 0.5 / Math.tan(Math.PI / 2);
+ }
+}
+
+const EPSILON = 1e-8;
+/**
+ * @class Transform
+ *
+ * @classdesc Class used for calculating coordinate transformations
+ * and projections.
+ */
+class Transform {
+ /**
+ * Create a new transform instance.
+ * @param {number} orientation - Image orientation.
+ * @param {number} width - Image height.
+ * @param {number} height - Image width.
+ * @param {number} focal - Focal length.
+ * @param {number} scale - Atomic scale.
+ * @param {Array<number>} rotation - Rotation vector in three dimensions.
+ * @param {Array<number>} translation - Translation vector in three dimensions.
+ * @param {HTMLImageElement} image - Image for fallback size calculations.
+ */
+ constructor(orientation, width, height, scale, rotation, translation, image, textureScale, cameraParameters, cameraType) {
+ this._orientation = this._getValue(orientation, 1);
+ let imageWidth = image != null ? image.width : 4;
+ let imageHeight = image != null ? image.height : 3;
+ let keepOrientation = this._orientation < 5;
+ this._width = this._getValue(width, keepOrientation ? imageWidth : imageHeight);
+ this._height = this._getValue(height, keepOrientation ? imageHeight : imageWidth);
+ this._basicAspect = keepOrientation ?
+ this._width / this._height :
+ this._height / this._width;
+ this._basicWidth = keepOrientation ? width : height;
+ this._basicHeight = keepOrientation ? height : width;
+ const parameters = this._getCameraParameters(cameraParameters, cameraType);
+ const focal = parameters[0];
+ const ck1 = parameters[1];
+ const ck2 = parameters[2];
+ this._focal = this._getValue(focal, 1);
+ this._scale = this._getValue(scale, 0);
+ this._worldToCamera = this.createWorldToCamera(rotation, translation);
+ this._worldToCameraInverse = new Matrix4()
+ .copy(this._worldToCamera)
+ .invert();
+ this._scaledWorldToCamera =
+ this._createScaledWorldToCamera(this._worldToCamera, this._scale);
+ this._scaledWorldToCameraInverse = new Matrix4()
+ .copy(this._scaledWorldToCamera)
+ .invert();
+ this._basicWorldToCamera = this._createBasicWorldToCamera(this._worldToCamera, orientation);
+ this._textureScale = !!textureScale ? textureScale : [1, 1];
+ this._ck1 = !!ck1 ? ck1 : 0;
+ this._ck2 = !!ck2 ? ck2 : 0;
+ this._cameraType = !!cameraType ?
+ cameraType :
+ "perspective";
+ this._radialPeak = this._getRadialPeak(this._ck1, this._ck2);
+ }
+ get ck1() {
+ return this._ck1;
+ }
+ get ck2() {
+ return this._ck2;
+ }
+ get cameraType() {
+ return this._cameraType;
+ }
+ /**
+ * Get basic aspect.
+ * @returns {number} The orientation adjusted aspect ratio.
+ */
+ get basicAspect() {
+ return this._basicAspect;
+ }
+ /**
+ * Get basic height.
+ *
+ * @description Does not fall back to image image height but
+ * uses original value from API so can be faulty.
+ *
+ * @returns {number} The height of the basic version image
+ * (adjusted for orientation).
+ */
+ get basicHeight() {
+ return this._basicHeight;
+ }
+ get basicRt() {
+ return this._basicWorldToCamera;
+ }
+ /**
+ * Get basic width.
+ *
+ * @description Does not fall back to image image width but
+ * uses original value from API so can be faulty.
+ *
+ * @returns {number} The width of the basic version image
+ * (adjusted for orientation).
+ */
+ get basicWidth() {
+ return this._basicWidth;
+ }
+ /**
+ * Get focal.
+ * @returns {number} The image focal length.
+ */
+ get focal() {
+ return this._focal;
+ }
+ /**
+ * Get height.
+ *
+ * @description Falls back to the image image height if
+ * the API data is faulty.
+ *
+ * @returns {number} The orientation adjusted image height.
+ */
+ get height() {
+ return this._height;
+ }
+ /**
+ * Get orientation.
+ * @returns {number} The image orientation.
+ */
+ get orientation() {
+ return this._orientation;
+ }
+ /**
+ * Get rt.
+ * @returns {THREE.Matrix4} The extrinsic camera matrix.
+ */
+ get rt() {
+ return this._worldToCamera;
+ }
+ /**
+ * Get srt.
+ * @returns {THREE.Matrix4} The scaled extrinsic camera matrix.
+ */
+ get srt() {
+ return this._scaledWorldToCamera;
+ }
+ /**
+ * Get srtInverse.
+ * @returns {THREE.Matrix4} The scaled extrinsic camera matrix.
+ */
+ get srtInverse() {
+ return this._scaledWorldToCameraInverse;
+ }
+ /**
+ * Get scale.
+ * @returns {number} The image atomic reconstruction scale.
+ */
+ get scale() {
+ return this._scale;
+ }
+ /**
+ * Get has valid scale.
+ * @returns {boolean} Value indicating if the scale of the transform is valid.
+ */
+ get hasValidScale() {
+ return this._scale > 1e-2 && this._scale < 50;
+ }
+ /**
+ * Get radial peak.
+ * @returns {number} Value indicating the radius where the radial
+ * undistortion function peaks.
+ */
+ get radialPeak() {
+ return this._radialPeak;
+ }
+ /**
+ * Get width.
+ *
+ * @description Falls back to the image image width if
+ * the API data is faulty.
+ *
+ * @returns {number} The orientation adjusted image width.
+ */
+ get width() {
+ return this._width;
+ }
+ /**
+ * Calculate the up vector for the image transform.
+ *
+ * @returns {THREE.Vector3} Normalized and orientation adjusted up vector.
+ */
+ upVector() {
+ let rte = this._worldToCamera.elements;
+ switch (this._orientation) {
+ case 1:
+ return new Vector3(-rte[1], -rte[5], -rte[9]);
+ case 3:
+ return new Vector3(rte[1], rte[5], rte[9]);
+ case 6:
+ return new Vector3(-rte[0], -rte[4], -rte[8]);
+ case 8:
+ return new Vector3(rte[0], rte[4], rte[8]);
+ default:
+ return new Vector3(-rte[1], -rte[5], -rte[9]);
+ }
+ }
+ /**
+ * Calculate projector matrix for projecting 3D points to texture map
+ * coordinates (u and v).
+ *
+ * @returns {THREE.Matrix4} Projection matrix for 3D point to texture
+ * map coordinate calculations.
+ */
+ projectorMatrix() {
+ let projector = this._normalizedToTextureMatrix();
+ let f = this._focal;
+ let projection = new Matrix4().set(f, 0, 0, 0, 0, f, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0);
+ projector.multiply(projection);
+ projector.multiply(this._worldToCamera);
+ return projector;
+ }
+ /**
+ * Project 3D world coordinates to basic coordinates.
+ *
+ * @param {Array<number>} point3d - 3D world coordinates.
+ * @return {Array<number>} 2D basic coordinates.
+ */
+ projectBasic(point3d) {
+ let sfm = this.projectSfM(point3d);
+ return this._sfmToBasic(sfm);
+ }
+ /**
+ * Unproject basic coordinates to 3D world coordinates.
+ *
+ * @param {Array<number>} basic - 2D basic coordinates.
+ * @param {Array<number>} distance - Distance to unproject from camera center.
+ * @param {boolean} [depth] - Treat the distance value as depth from camera center.
+ * Only applicable for perspective images. Will be
+ * ignored for spherical.
+ * @returns {Array<number>} Unprojected 3D world coordinates.
+ */
+ unprojectBasic(basic, distance, depth) {
+ let sfm = this._basicToSfm(basic);
+ return this.unprojectSfM(sfm, distance, depth);
+ }
+ /**
+ * Project 3D world coordinates to SfM coordinates.
+ *
+ * @param {Array<number>} point3d - 3D world coordinates.
+ * @return {Array<number>} 2D SfM coordinates.
+ */
+ projectSfM(point3d) {
+ let v = new Vector4(point3d[0], point3d[1], point3d[2], 1);
+ v.applyMatrix4(this._worldToCamera);
+ return this._bearingToSfm([v.x, v.y, v.z]);
+ }
+ /**
+ * Unproject SfM coordinates to a 3D world coordinates.
+ *
+ * @param {Array<number>} sfm - 2D SfM coordinates.
+ * @param {Array<number>} distance - Distance to unproject
+ * from camera center.
+ * @param {boolean} [depth] - Treat the distance value as
+ * depth from camera center. Only applicable for perspective
+ * images. Will be ignored for spherical.
+ * @returns {Array<number>} Unprojected 3D world coordinates.
+ */
+ unprojectSfM(sfm, distance, depth) {
+ const bearing = this._sfmToBearing(sfm);
+ const unprojectedCamera = depth && !isSpherical(this._cameraType) ?
+ new Vector4(distance * bearing[0] / bearing[2], distance * bearing[1] / bearing[2], distance, 1) :
+ new Vector4(distance * bearing[0], distance * bearing[1], distance * bearing[2], 1);
+ const unprojectedWorld = unprojectedCamera
+ .applyMatrix4(this._worldToCameraInverse);
+ return [
+ unprojectedWorld.x / unprojectedWorld.w,
+ unprojectedWorld.y / unprojectedWorld.w,
+ unprojectedWorld.z / unprojectedWorld.w,
+ ];
+ }
+ /**
+ * Transform SfM coordinates to bearing vector (3D cartesian
+ * coordinates on the unit sphere).
+ *
+ * @param {Array<number>} sfm - 2D SfM coordinates.
+ * @returns {Array<number>} Bearing vector (3D cartesian coordinates
+ * on the unit sphere).
+ */
+ _sfmToBearing(sfm) {
+ if (isSpherical(this._cameraType)) {
+ let lng = sfm[0] * 2 * Math.PI;
+ let lat = -sfm[1] * 2 * Math.PI;
+ let x = Math.cos(lat) * Math.sin(lng);
+ let y = -Math.sin(lat);
+ let z = Math.cos(lat) * Math.cos(lng);
+ return [x, y, z];
+ }
+ else if (isFisheye(this._cameraType)) {
+ let [dxn, dyn] = [sfm[0] / this._focal, sfm[1] / this._focal];
+ const dTheta = Math.sqrt(dxn * dxn + dyn * dyn);
+ let d = this._distortionFromDistortedRadius(dTheta, this._ck1, this._ck2, this._radialPeak);
+ let theta = dTheta / d;
+ let z = Math.cos(theta);
+ let r = Math.sin(theta);
+ const denomTheta = dTheta > EPSILON ? 1 / dTheta : 1;
+ let x = r * dxn * denomTheta;
+ let y = r * dyn * denomTheta;
+ return [x, y, z];
+ }
+ else {
+ let [dxn, dyn] = [sfm[0] / this._focal, sfm[1] / this._focal];
+ const dr = Math.sqrt(dxn * dxn + dyn * dyn);
+ let d = this._distortionFromDistortedRadius(dr, this._ck1, this._ck2, this._radialPeak);
+ const xn = dxn / d;
+ const yn = dyn / d;
+ let v = new Vector3(xn, yn, 1);
+ v.normalize();
+ return [v.x, v.y, v.z];
+ }
+ }
+ /** Compute distortion given the distorted radius.
+ *
+ * Solves for d in the equation
+ * y = d(x, k1, k2) * x
+ * given the distorted radius, y.
+ */
+ _distortionFromDistortedRadius(distortedRadius, k1, k2, radialPeak) {
+ let d = 1.0;
+ for (let i = 0; i < 10; i++) {
+ let radius = distortedRadius / d;
+ if (radius > radialPeak) {
+ radius = radialPeak;
+ }
+ d = 1 + k1 * Math.pow(radius, 2) + k2 * Math.pow(radius, 4);
+ }
+ return d;
+ }
+ /**
+ * Transform bearing vector (3D cartesian coordiantes on the unit sphere) to
+ * SfM coordinates.
+ *
+ * @param {Array<number>} bearing - Bearing vector (3D cartesian coordinates on the
+ * unit sphere).
+ * @returns {Array<number>} 2D SfM coordinates.
+ */
+ _bearingToSfm(bearing) {
+ if (isSpherical(this._cameraType)) {
+ let x = bearing[0];
+ let y = bearing[1];
+ let z = bearing[2];
+ let lng = Math.atan2(x, z);
+ let lat = Math.atan2(-y, Math.sqrt(x * x + z * z));
+ return [lng / (2 * Math.PI), -lat / (2 * Math.PI)];
+ }
+ else if (isFisheye(this._cameraType)) {
+ if (bearing[2] > 0) {
+ const [x, y, z] = bearing;
+ const r = Math.sqrt(x * x + y * y);
+ let theta = Math.atan2(r, z);
+ if (theta > this._radialPeak) {
+ theta = this._radialPeak;
+ }
+ const distortion = 1.0 + Math.pow(theta, 2) * (this._ck1 + Math.pow(theta, 2) * this._ck2);
+ const s = this._focal * distortion * theta / r;
+ return [s * x, s * y];
+ }
+ else {
+ return [
+ bearing[0] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+ bearing[1] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+ ];
+ }
+ }
+ else {
+ if (bearing[2] > 0) {
+ let [xn, yn] = [bearing[0] / bearing[2], bearing[1] / bearing[2]];
+ let r2 = xn * xn + yn * yn;
+ const rp2 = Math.pow(this._radialPeak, 2);
+ if (r2 > rp2) {
+ r2 = rp2;
+ }
+ const d = 1 + this._ck1 * r2 + this._ck2 * Math.pow(r2, 2);
+ return [
+ this._focal * d * xn,
+ this._focal * d * yn,
+ ];
+ }
+ else {
+ return [
+ bearing[0] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+ bearing[1] < 0 ? Number.NEGATIVE_INFINITY : Number.POSITIVE_INFINITY,
+ ];
+ }
+ }
+ }
+ /**
+ * Convert basic coordinates to SfM coordinates.
+ *
+ * @param {Array<number>} basic - 2D basic coordinates.
+ * @returns {Array<number>} 2D SfM coordinates.
+ */
+ _basicToSfm(basic) {
+ let rotatedX;
+ let rotatedY;
+ switch (this._orientation) {
+ case 1:
+ rotatedX = basic[0];
+ rotatedY = basic[1];
+ break;
+ case 3:
+ rotatedX = 1 - basic[0];
+ rotatedY = 1 - basic[1];
+ break;
+ case 6:
+ rotatedX = basic[1];
+ rotatedY = 1 - basic[0];
+ break;
+ case 8:
+ rotatedX = 1 - basic[1];
+ rotatedY = basic[0];
+ break;
+ default:
+ rotatedX = basic[0];
+ rotatedY = basic[1];
+ break;
+ }
+ let w = this._width;
+ let h = this._height;
+ let s = Math.max(w, h);
+ let sfmX = rotatedX * w / s - w / s / 2;
+ let sfmY = rotatedY * h / s - h / s / 2;
+ return [sfmX, sfmY];
+ }
+ /**
+ * Convert SfM coordinates to basic coordinates.
+ *
+ * @param {Array<number>} sfm - 2D SfM coordinates.
+ * @returns {Array<number>} 2D basic coordinates.
+ */
+ _sfmToBasic(sfm) {
+ let w = this._width;
+ let h = this._height;
+ let s = Math.max(w, h);
+ let rotatedX = (sfm[0] + w / s / 2) / w * s;
+ let rotatedY = (sfm[1] + h / s / 2) / h * s;
+ let basicX;
+ let basicY;
+ switch (this._orientation) {
+ case 1:
+ basicX = rotatedX;
+ basicY = rotatedY;
+ break;
+ case 3:
+ basicX = 1 - rotatedX;
+ basicY = 1 - rotatedY;
+ break;
+ case 6:
+ basicX = 1 - rotatedY;
+ basicY = rotatedX;
+ break;
+ case 8:
+ basicX = rotatedY;
+ basicY = 1 - rotatedX;
+ break;
+ default:
+ basicX = rotatedX;
+ basicY = rotatedY;
+ break;
+ }
+ return [basicX, basicY];
+ }
+ /**
+ * Checks a value and returns it if it exists and is larger than 0.
+ * Fallbacks if it is null.
+ *
+ * @param {number} value - Value to check.
+ * @param {number} fallback - Value to fall back to.
+ * @returns {number} The value or its fallback value if it is not defined or negative.
+ */
+ _getValue(value, fallback) {
+ return value != null && value > 0 ? value : fallback;
+ }
+ _getCameraParameters(value, cameraType) {
+ if (isSpherical(cameraType)) {
+ return [];
+ }
+ if (!value || value.length === 0) {
+ return [1, 0, 0];
+ }
+ const padding = 3 - value.length;
+ if (padding <= 0) {
+ return value;
+ }
+ return value
+ .concat(new Array(padding)
+ .fill(0));
+ }
+ /**
+ * Creates the extrinsic camera matrix [ R | t ].
+ *
+ * @param {Array<number>} rotation - Rotation vector in angle axis representation.
+ * @param {Array<number>} translation - Translation vector.
+ * @returns {THREE.Matrix4} Extrisic camera matrix.
+ */
+ createWorldToCamera(rotation, translation) {
+ const axis = new Vector3(rotation[0], rotation[1], rotation[2]);
+ const angle = axis.length();
+ if (angle > 0) {
+ axis.normalize();
+ }
+ const worldToCamera = new Matrix4();
+ worldToCamera.makeRotationAxis(axis, angle);
+ worldToCamera.setPosition(new Vector3(translation[0], translation[1], translation[2]));
+ return worldToCamera;
+ }
+ /**
+ * Calculates the scaled extrinsic camera matrix scale * [ R | t ].
+ *
+ * @param {THREE.Matrix4} worldToCamera - Extrisic camera matrix.
+ * @param {number} scale - Scale factor.
+ * @returns {THREE.Matrix4} Scaled extrisic camera matrix.
+ */
+ _createScaledWorldToCamera(worldToCamera, scale) {
+ const scaledWorldToCamera = worldToCamera.clone();
+ const elements = scaledWorldToCamera.elements;
+ elements[12] = scale * elements[12];
+ elements[13] = scale * elements[13];
+ elements[14] = scale * elements[14];
+ scaledWorldToCamera.scale(new Vector3(scale, scale, scale));
+ return scaledWorldToCamera;
+ }
+ _createBasicWorldToCamera(rt, orientation) {
+ const axis = new Vector3(0, 0, 1);
+ let angle = 0;
+ switch (orientation) {
+ case 3:
+ angle = Math.PI;
+ break;
+ case 6:
+ angle = Math.PI / 2;
+ break;
+ case 8:
+ angle = 3 * Math.PI / 2;
+ break;
+ }
+ return new Matrix4()
+ .makeRotationAxis(axis, angle)
+ .multiply(rt);
+ }
+ _getRadialPeak(k1, k2) {
+ const a = 5 * k2;
+ const b = 3 * k1;
+ const c = 1;
+ const d = Math.pow(b, 2) - 4 * a * c;
+ if (d < 0) {
+ return undefined;
+ }
+ const root1 = (-b - Math.sqrt(d)) / 2 / a;
+ const root2 = (-b + Math.sqrt(d)) / 2 / a;
+ const minRoot = Math.min(root1, root2);
+ const maxRoot = Math.max(root1, root2);
+ return minRoot > 0 ?
+ Math.sqrt(minRoot) :
+ maxRoot > 0 ?
+ Math.sqrt(maxRoot) :
+ undefined;
+ }
+ /**
+ * Calculate a transformation matrix from normalized coordinates for
+ * texture map coordinates.
+ *
+ * @returns {THREE.Matrix4} Normalized coordinates to texture map
+ * coordinates transformation matrix.
+ */
+ _normalizedToTextureMatrix() {
+ const size = Math.max(this._width, this._height);
+ const scaleX = this._orientation < 5 ? this._textureScale[0] : this._textureScale[1];
+ const scaleY = this._orientation < 5 ? this._textureScale[1] : this._textureScale[0];
+ const w = size / this._width * scaleX;
+ const h = size / this._height * scaleY;
+ switch (this._orientation) {
+ case 1:
+ return new Matrix4().set(w, 0, 0, 0.5, 0, -h, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
+ case 3:
+ return new Matrix4().set(-w, 0, 0, 0.5, 0, h, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
+ case 6:
+ return new Matrix4().set(0, -h, 0, 0.5, -w, 0, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
+ case 8:
+ return new Matrix4().set(0, h, 0, 0.5, w, 0, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
+ default:
+ return new Matrix4().set(w, 0, 0, 0.5, 0, -h, 0, 0.5, 0, 0, 1, 0, 0, 0, 0, 1);
+ }
+ }
+}
+
+class StateBase {
+ constructor(state) {
+ this._spatial = new Spatial();
+ this._referenceThreshold = 0.01;
+ this._transitionMode = state.transitionMode;
+ this._reference = state.reference;
+ this._alpha = state.alpha;
+ this._camera = state.camera.clone();
+ this._zoom = state.zoom;
+ this._currentIndex = state.currentIndex;
+ this._trajectory = state.trajectory.slice();
+ this._trajectoryTransforms = [];
+ this._trajectoryCameras = [];
+ for (let image of this._trajectory) {
+ let translation = this._imageToTranslation(image, this._reference);
+ let transform = new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, image.image, undefined, image.cameraParameters, image.cameraType);
+ this._trajectoryTransforms.push(transform);
+ this._trajectoryCameras.push(new Camera(transform));
+ }
+ this._currentImage = this._trajectory.length > 0 ?
+ this._trajectory[this._currentIndex] :
+ null;
+ this._previousImage = this._trajectory.length > 1 && this.currentIndex > 0 ?
+ this._trajectory[this._currentIndex - 1] :
+ null;
+ this._currentCamera = this._trajectoryCameras.length > 0 ?
+ this._trajectoryCameras[this._currentIndex].clone() :
+ new Camera();
+ this._previousCamera = this._trajectoryCameras.length > 1 && this.currentIndex > 0 ?
+ this._trajectoryCameras[this._currentIndex - 1].clone() :
+ this._currentCamera.clone();
+ }
+ get reference() {
+ return this._reference;
+ }
+ get alpha() {
+ return this._getAlpha();
+ }
+ get camera() {
+ return this._camera;
+ }
+ get zoom() {
+ return this._zoom;
+ }
+ get trajectory() {
+ return this._trajectory;
+ }
+ get currentIndex() {
+ return this._currentIndex;
+ }
+ get currentImage() {
+ return this._currentImage;
+ }
+ get previousImage() {
+ return this._previousImage;
+ }
+ get currentCamera() {
+ return this._currentCamera;
+ }
+ get currentTransform() {
+ return this._trajectoryTransforms.length > 0 ?
+ this._trajectoryTransforms[this.currentIndex] : null;
+ }
+ get previousTransform() {
+ return this._trajectoryTransforms.length > 1 && this.currentIndex > 0 ?
+ this._trajectoryTransforms[this.currentIndex - 1] : null;
+ }
+ get motionless() {
+ return this._motionless;
+ }
+ get transitionMode() {
+ return this._transitionMode;
+ }
+ move(delta) { }
+ moveTo(position) { }
+ rotate(delta) { }
+ rotateUnbounded(delta) { }
+ rotateWithoutInertia(delta) { }
+ rotateBasic(basicRotation) { }
+ rotateBasicUnbounded(basicRotation) { }
+ rotateBasicWithoutInertia(basicRotation) { }
+ rotateToBasic(basic) { }
+ setSpeed(speed) { }
+ zoomIn(delta, reference) { }
+ update(fps) { }
+ setCenter(center) { }
+ setZoom(zoom) { }
+ dolly(delta) { }
+ orbit(rotation) { }
+ setViewMatrix(matrix) { }
+ truck(direction) { }
+ append(images) {
+ if (images.length < 1) {
+ throw Error("Trajectory can not be empty");
+ }
+ if (this._currentIndex < 0) {
+ this.set(images);
+ }
+ else {
+ this._trajectory = this._trajectory.concat(images);
+ this._appendToTrajectories(images);
+ }
+ }
+ prepend(images) {
+ if (images.length < 1) {
+ throw Error("Trajectory can not be empty");
+ }
+ this._trajectory = images.slice().concat(this._trajectory);
+ this._currentIndex += images.length;
+ this._setCurrentImage();
+ let referenceReset = this._setReference(this._currentImage);
+ if (referenceReset) {
+ this._setTrajectories();
+ }
+ else {
+ this._prependToTrajectories(images);
+ }
+ this._setCurrentCamera();
+ }
+ remove(n) {
+ if (n < 0) {
+ throw Error("n must be a positive integer");
+ }
+ if (this._currentIndex - 1 < n) {
+ throw Error("Current and previous images can not be removed");
+ }
+ for (let i = 0; i < n; i++) {
+ this._trajectory.shift();
+ this._trajectoryTransforms.shift();
+ this._trajectoryCameras.shift();
+ this._currentIndex--;
+ }
+ this._setCurrentImage();
+ }
+ clearPrior() {
+ if (this._currentIndex > 0) {
+ this.remove(this._currentIndex - 1);
+ }
+ }
+ clear() {
+ this.cut();
+ if (this._currentIndex > 0) {
+ this.remove(this._currentIndex - 1);
+ }
+ }
+ cut() {
+ while (this._trajectory.length - 1 > this._currentIndex) {
+ this._trajectory.pop();
+ this._trajectoryTransforms.pop();
+ this._trajectoryCameras.pop();
+ }
+ }
+ set(images) {
+ this._setTrajectory(images);
+ this._setCurrentImage();
+ this._setReference(this._currentImage);
+ this._setTrajectories();
+ this._setCurrentCamera();
+ }
+ getCenter() {
+ return this._currentImage != null ?
+ this.currentTransform.projectBasic(this._camera.lookat.toArray()) :
+ [0.5, 0.5];
+ }
+ setTransitionMode(mode) {
+ this._transitionMode = mode;
+ }
+ _getAlpha() { return 1; }
+ _setCurrent() {
+ this._setCurrentImage();
+ let referenceReset = this._setReference(this._currentImage);
+ if (referenceReset) {
+ this._setTrajectories();
+ }
+ this._setCurrentCamera();
+ }
+ _setCurrentCamera() {
+ this._currentCamera = this._trajectoryCameras[this._currentIndex].clone();
+ this._previousCamera = this._currentIndex > 0 ?
+ this._trajectoryCameras[this._currentIndex - 1].clone() :
+ this._currentCamera.clone();
+ }
+ _motionlessTransition() {
+ let imagesSet = this._currentImage != null && this._previousImage != null;
+ return imagesSet && (this._transitionMode === TransitionMode.Instantaneous || !(this._currentImage.merged &&
+ this._previousImage.merged &&
+ this._withinOriginalDistance() &&
+ this._sameConnectedComponent()));
+ }
+ _setReference(image) {
+ // do not reset reference if image is within threshold distance
+ if (Math.abs(image.lngLat.lat - this.reference.lat) < this._referenceThreshold &&
+ Math.abs(image.lngLat.lng - this.reference.lng) < this._referenceThreshold) {
+ return false;
+ }
+ // do not reset reference if previous image exist and transition is with motion
+ if (this._previousImage != null && !this._motionlessTransition()) {
+ return false;
+ }
+ this._reference.lat = image.lngLat.lat;
+ this._reference.lng = image.lngLat.lng;
+ this._reference.alt = image.computedAltitude;
+ return true;
+ }
+ _setCurrentImage() {
+ this._currentImage = this._trajectory.length > 0 ?
+ this._trajectory[this._currentIndex] :
+ null;
+ this._previousImage = this._currentIndex > 0 ?
+ this._trajectory[this._currentIndex - 1] :
+ null;
+ }
+ _setTrajectory(images) {
+ if (images.length < 1) {
+ throw new ArgumentMapillaryError("Trajectory can not be empty");
+ }
+ if (this._currentImage != null) {
+ this._trajectory = [this._currentImage].concat(images);
+ this._currentIndex = 1;
+ }
+ else {
+ this._trajectory = images.slice();
+ this._currentIndex = 0;
+ }
+ }
+ _setTrajectories() {
+ this._trajectoryTransforms.length = 0;
+ this._trajectoryCameras.length = 0;
+ this._appendToTrajectories(this._trajectory);
+ }
+ _appendToTrajectories(images) {
+ for (let image of images) {
+ if (!image.assetsCached) {
+ throw new ArgumentMapillaryError("Assets must be cached when image is added to trajectory");
+ }
+ let translation = this._imageToTranslation(image, this.reference);
+ let transform = new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, image.image, undefined, image.cameraParameters, image.cameraType);
+ this._trajectoryTransforms.push(transform);
+ this._trajectoryCameras.push(new Camera(transform));
+ }
+ }
+ _prependToTrajectories(images) {
+ for (let image of images.reverse()) {
+ if (!image.assetsCached) {
+ throw new ArgumentMapillaryError("Assets must be cached when added to trajectory");
+ }
+ let translation = this._imageToTranslation(image, this.reference);
+ let transform = new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, image.image, undefined, image.cameraParameters, image.cameraType);
+ this._trajectoryTransforms.unshift(transform);
+ this._trajectoryCameras.unshift(new Camera(transform));
+ }
+ }
+ _imageToTranslation(image, reference) {
+ return computeTranslation({ alt: image.computedAltitude, lat: image.lngLat.lat, lng: image.lngLat.lng }, image.rotation, reference);
+ }
+ _sameConnectedComponent() {
+ let current = this._currentImage;
+ let previous = this._previousImage;
+ return !!current && !!previous &&
+ current.mergeId === previous.mergeId;
+ }
+ _withinOriginalDistance() {
+ let current = this._currentImage;
+ let previous = this._previousImage;
+ if (!current || !previous) {
+ return true;
+ }
+ // 50 km/h moves 28m in 2s
+ let distance = this._spatial.distanceFromLngLat(current.originalLngLat.lng, current.originalLngLat.lat, previous.originalLngLat.lng, previous.originalLngLat.lat);
+ return distance < 25;
+ }
+}
+
+class EulerRotationDelta {
+ constructor(phi, theta) {
+ this._phi = phi;
+ this._theta = theta;
+ }
+ get phi() {
+ return this._phi;
+ }
+ set phi(value) {
+ this._phi = value;
+ }
+ get theta() {
+ return this._theta;
+ }
+ set theta(value) {
+ this._theta = value;
+ }
+ get isZero() {
+ return this._phi === 0 && this._theta === 0;
+ }
+ copy(delta) {
+ this._phi = delta.phi;
+ this._theta = delta.theta;
+ }
+ lerp(other, alpha) {
+ this._phi = (1 - alpha) * this._phi + alpha * other.phi;
+ this._theta = (1 - alpha) * this._theta + alpha * other.theta;
+ }
+ multiply(value) {
+ this._phi *= value;
+ this._theta *= value;
+ }
+ threshold(value) {
+ this._phi = Math.abs(this._phi) > value ? this._phi : 0;
+ this._theta = Math.abs(this._theta) > value ? this._theta : 0;
+ }
+ lengthSquared() {
+ return this._phi * this._phi + this._theta * this._theta;
+ }
+ reset() {
+ this._phi = 0;
+ this._theta = 0;
+ }
+}
+
+class InteractiveStateBase extends StateBase {
+ constructor(state) {
+ super(state);
+ this._animationSpeed = 1 / 40;
+ this._rotationDelta = new EulerRotationDelta(0, 0);
+ this._requestedRotationDelta = null;
+ this._basicRotation = [0, 0];
+ this._requestedBasicRotation = null;
+ this._requestedBasicRotationUnbounded = null;
+ this._rotationAcceleration = 0.86;
+ this._rotationIncreaseAlpha = 0.97;
+ this._rotationDecreaseAlpha = 0.9;
+ this._rotationThreshold = 1e-3;
+ this._unboundedRotationAlpha = 0.8;
+ this._desiredZoom = state.zoom;
+ this._minZoom = 0;
+ this._maxZoom = 3;
+ this._lookatDepth = 10;
+ this._desiredLookat = null;
+ this._desiredCenter = null;
+ }
+ rotate(rotationDelta) {
+ if (this._currentImage == null) {
+ return;
+ }
+ if (rotationDelta.phi === 0 && rotationDelta.theta === 0) {
+ return;
+ }
+ this._desiredZoom = this._zoom;
+ this._desiredLookat = null;
+ this._requestedBasicRotation = null;
+ if (this._requestedRotationDelta != null) {
+ this._requestedRotationDelta.phi = this._requestedRotationDelta.phi + rotationDelta.phi;
+ this._requestedRotationDelta.theta = this._requestedRotationDelta.theta + rotationDelta.theta;
+ }
+ else {
+ this._requestedRotationDelta = new EulerRotationDelta(rotationDelta.phi, rotationDelta.theta);
+ }
+ }
+ rotateUnbounded(delta) {
+ if (this._currentImage == null) {
+ return;
+ }
+ this._requestedBasicRotation = null;
+ this._requestedRotationDelta = null;
+ this._applyRotation(delta, this._currentCamera);
+ this._applyRotation(delta, this._previousCamera);
+ if (!this._desiredLookat) {
+ return;
+ }
+ const q = new Quaternion().setFromUnitVectors(this._currentCamera.up, new Vector3(0, 0, 1));
+ const qInverse = q.clone().invert();
+ const offset = new Vector3()
+ .copy(this._desiredLookat)
+ .sub(this._camera.position)
+ .applyQuaternion(q);
+ const length = offset.length();
+ let phi = Math.atan2(offset.y, offset.x);
+ phi += delta.phi;
+ let theta = Math.atan2(Math.sqrt(offset.x * offset.x + offset.y * offset.y), offset.z);
+ theta += delta.theta;
+ theta = Math.max(0.1, Math.min(Math.PI - 0.1, theta));
+ offset.x = Math.sin(theta) * Math.cos(phi);
+ offset.y = Math.sin(theta) * Math.sin(phi);
+ offset.z = Math.cos(theta);
+ offset.applyQuaternion(qInverse);
+ this._desiredLookat
+ .copy(this._camera.position)
+ .add(offset.multiplyScalar(length));
+ }
+ rotateWithoutInertia(rotationDelta) {
+ if (this._currentImage == null) {
+ return;
+ }
+ this._desiredZoom = this._zoom;
+ this._desiredLookat = null;
+ this._requestedBasicRotation = null;
+ this._requestedRotationDelta = null;
+ const threshold = Math.PI / (10 * Math.pow(2, this._zoom));
+ const delta = {
+ phi: this._spatial.clamp(rotationDelta.phi, -threshold, threshold),
+ theta: this._spatial.clamp(rotationDelta.theta, -threshold, threshold),
+ };
+ this._applyRotation(delta, this._currentCamera);
+ this._applyRotation(delta, this._previousCamera);
+ }
+ rotateBasic(basicRotation) {
+ if (this._currentImage == null) {
+ return;
+ }
+ this._desiredZoom = this._zoom;
+ this._desiredLookat = null;
+ this._requestedRotationDelta = null;
+ if (this._requestedBasicRotation != null) {
+ this._requestedBasicRotation[0] += basicRotation[0];
+ this._requestedBasicRotation[1] += basicRotation[1];
+ let threshold = 0.05 / Math.pow(2, this._zoom);
+ this._requestedBasicRotation[0] =
+ this._spatial.clamp(this._requestedBasicRotation[0], -threshold, threshold);
+ this._requestedBasicRotation[1] =
+ this._spatial.clamp(this._requestedBasicRotation[1], -threshold, threshold);
+ }
+ else {
+ this._requestedBasicRotation = basicRotation.slice();
+ }
+ }
+ rotateBasicUnbounded(basicRotation) {
+ if (this._currentImage == null) {
+ return;
+ }
+ if (this._requestedBasicRotationUnbounded != null) {
+ this._requestedBasicRotationUnbounded[0] += basicRotation[0];
+ this._requestedBasicRotationUnbounded[1] += basicRotation[1];
+ }
+ else {
+ this._requestedBasicRotationUnbounded = basicRotation.slice();
+ }
+ }
+ rotateBasicWithoutInertia(basic) {
+ if (this._currentImage == null) {
+ return;
+ }
+ this._desiredZoom = this._zoom;
+ this._desiredLookat = null;
+ this._requestedRotationDelta = null;
+ this._requestedBasicRotation = null;
+ const threshold = 0.05 / Math.pow(2, this._zoom);
+ const basicRotation = basic.slice();
+ basicRotation[0] = this._spatial.clamp(basicRotation[0], -threshold, threshold);
+ basicRotation[1] = this._spatial.clamp(basicRotation[1], -threshold, threshold);
+ this._applyRotationBasic(basicRotation);
+ }
+ rotateToBasic(basic) {
+ if (this._currentImage == null) {
+ return;
+ }
+ this._desiredZoom = this._zoom;
+ this._desiredLookat = null;
+ basic[0] = this._spatial.clamp(basic[0], 0, 1);
+ basic[1] = this._spatial.clamp(basic[1], 0, 1);
+ let lookat = this.currentTransform.unprojectBasic(basic, this._lookatDepth);
+ this._currentCamera.lookat.fromArray(lookat);
+ }
+ zoomIn(delta, reference) {
+ if (this._currentImage == null) {
+ return;
+ }
+ this._desiredZoom = Math.max(this._minZoom, Math.min(this._maxZoom, this._desiredZoom + delta));
+ let currentCenter = this.currentTransform.projectBasic(this._currentCamera.lookat.toArray());
+ let currentCenterX = currentCenter[0];
+ let currentCenterY = currentCenter[1];
+ let zoom0 = Math.pow(2, this._zoom);
+ let zoom1 = Math.pow(2, this._desiredZoom);
+ let refX = reference[0];
+ let refY = reference[1];
+ if (isSpherical(this.currentTransform.cameraType)) {
+ if (refX - currentCenterX > 0.5) {
+ refX = refX - 1;
+ }
+ else if (currentCenterX - refX > 0.5) {
+ refX = 1 + refX;
+ }
+ }
+ let newCenterX = refX - zoom0 / zoom1 * (refX - currentCenterX);
+ let newCenterY = refY - zoom0 / zoom1 * (refY - currentCenterY);
+ if (isSpherical(this._currentImage.cameraType)) {
+ newCenterX = this._spatial
+ .wrap(newCenterX + this._basicRotation[0], 0, 1);
+ newCenterY = this._spatial
+ .clamp(newCenterY + this._basicRotation[1], 0.05, 0.95);
+ }
+ else {
+ newCenterX = this._spatial.clamp(newCenterX, 0, 1);
+ newCenterY = this._spatial.clamp(newCenterY, 0, 1);
+ }
+ this._desiredLookat = new Vector3()
+ .fromArray(this.currentTransform.unprojectBasic([newCenterX, newCenterY], this._lookatDepth));
+ }
+ setCenter(center) {
+ this._desiredLookat = null;
+ this._requestedRotationDelta = null;
+ this._requestedBasicRotation = null;
+ this._desiredZoom = this._zoom;
+ let clamped = [
+ this._spatial.clamp(center[0], 0, 1),
+ this._spatial.clamp(center[1], 0, 1),
+ ];
+ if (this._currentImage == null) {
+ this._desiredCenter = clamped;
+ return;
+ }
+ this._desiredCenter = null;
+ let currentLookat = new Vector3()
+ .fromArray(this.currentTransform.unprojectBasic(clamped, this._lookatDepth));
+ let previousTransform = this.previousTransform != null ?
+ this.previousTransform :
+ this.currentTransform;
+ let previousLookat = new Vector3()
+ .fromArray(previousTransform.unprojectBasic(clamped, this._lookatDepth));
+ this._currentCamera.lookat.copy(currentLookat);
+ this._previousCamera.lookat.copy(previousLookat);
+ }
+ setZoom(zoom) {
+ this._desiredLookat = null;
+ this._requestedRotationDelta = null;
+ this._requestedBasicRotation = null;
+ this._zoom = this._spatial.clamp(zoom, this._minZoom, this._maxZoom);
+ this._desiredZoom = this._zoom;
+ }
+ _applyRotation(delta, camera) {
+ if (camera == null) {
+ return;
+ }
+ let q = new Quaternion().setFromUnitVectors(camera.up, new Vector3(0, 0, 1));
+ let qInverse = q.clone().invert();
+ let offset = new Vector3();
+ offset.copy(camera.lookat).sub(camera.position);
+ offset.applyQuaternion(q);
+ let length = offset.length();
+ let phi = Math.atan2(offset.y, offset.x);
+ phi += delta.phi;
+ let theta = Math.atan2(Math.sqrt(offset.x * offset.x + offset.y * offset.y), offset.z);
+ theta += delta.theta;
+ theta = Math.max(0.1, Math.min(Math.PI - 0.1, theta));
+ offset.x = Math.sin(theta) * Math.cos(phi);
+ offset.y = Math.sin(theta) * Math.sin(phi);
+ offset.z = Math.cos(theta);
+ offset.applyQuaternion(qInverse);
+ camera.lookat.copy(camera.position).add(offset.multiplyScalar(length));
+ }
+ _applyRotationBasic(basicRotation) {
+ let currentImage = this._currentImage;
+ let previousImage = this._previousImage != null ?
+ this.previousImage :
+ this.currentImage;
+ let currentCamera = this._currentCamera;
+ let previousCamera = this._previousCamera;
+ let currentTransform = this.currentTransform;
+ let previousTransform = this.previousTransform != null ?
+ this.previousTransform :
+ this.currentTransform;
+ let currentBasic = currentTransform.projectBasic(currentCamera.lookat.toArray());
+ let previousBasic = previousTransform.projectBasic(previousCamera.lookat.toArray());
+ if (isSpherical(currentImage.cameraType)) {
+ currentBasic[0] = this._spatial.wrap(currentBasic[0] + basicRotation[0], 0, 1);
+ currentBasic[1] = this._spatial.clamp(currentBasic[1] + basicRotation[1], 0.05, 0.95);
+ }
+ else {
+ currentBasic[0] = this._spatial.clamp(currentBasic[0] + basicRotation[0], 0, 1);
+ currentBasic[1] = this._spatial.clamp(currentBasic[1] + basicRotation[1], 0, 1);
+ }
+ if (isSpherical(previousImage.cameraType)) {
+ previousBasic[0] = this._spatial.wrap(previousBasic[0] + basicRotation[0], 0, 1);
+ previousBasic[1] = this._spatial.clamp(previousBasic[1] + basicRotation[1], 0.05, 0.95);
+ }
+ else {
+ previousBasic[0] = this._spatial.clamp(previousBasic[0] + basicRotation[0], 0, 1);
+ previousBasic[1] = this._spatial.clamp(currentBasic[1] + basicRotation[1], 0, 1);
+ }
+ let currentLookat = currentTransform.unprojectBasic(currentBasic, this._lookatDepth);
+ currentCamera.lookat.fromArray(currentLookat);
+ let previousLookat = previousTransform.unprojectBasic(previousBasic, this._lookatDepth);
+ previousCamera.lookat.fromArray(previousLookat);
+ }
+ _updateZoom(animationSpeed) {
+ let diff = this._desiredZoom - this._zoom;
+ let sign = diff > 0 ? 1 : diff < 0 ? -1 : 0;
+ if (diff === 0) {
+ return;
+ }
+ else if (Math.abs(diff) < 2e-3) {
+ this._zoom = this._desiredZoom;
+ if (this._desiredLookat != null) {
+ this._desiredLookat = null;
+ }
+ }
+ else {
+ this._zoom += sign * Math.max(Math.abs(5 * animationSpeed * diff), 2e-3);
+ }
+ }
+ _updateLookat(animationSpeed) {
+ if (this._desiredLookat === null) {
+ return;
+ }
+ let diff = this._desiredLookat.distanceToSquared(this._currentCamera.lookat);
+ if (Math.abs(diff) < 1e-6) {
+ this._currentCamera.lookat.copy(this._desiredLookat);
+ this._desiredLookat = null;
+ }
+ else {
+ this._currentCamera.lookat.lerp(this._desiredLookat, 5 * animationSpeed);
+ }
+ }
+ _updateRotation() {
+ if (this._requestedRotationDelta != null) {
+ let length = this._rotationDelta.lengthSquared();
+ let requestedLength = this._requestedRotationDelta.lengthSquared();
+ if (requestedLength > length) {
+ this._rotationDelta.lerp(this._requestedRotationDelta, this._rotationIncreaseAlpha);
+ }
+ else {
+ this._rotationDelta.lerp(this._requestedRotationDelta, this._rotationDecreaseAlpha);
+ }
+ this._requestedRotationDelta = null;
+ return;
+ }
+ if (this._rotationDelta.isZero) {
+ return;
+ }
+ const alpha = isSpherical(this.currentImage.cameraType) ?
+ 1 : this._alpha;
+ this._rotationDelta.multiply(this._rotationAcceleration * alpha);
+ this._rotationDelta.threshold(this._rotationThreshold);
+ }
+ _updateRotationBasic() {
+ if (this._requestedBasicRotation != null) {
+ let x = this._basicRotation[0];
+ let y = this._basicRotation[1];
+ let reqX = this._requestedBasicRotation[0];
+ let reqY = this._requestedBasicRotation[1];
+ if (Math.abs(reqX) > Math.abs(x)) {
+ this._basicRotation[0] = (1 - this._rotationIncreaseAlpha) * x + this._rotationIncreaseAlpha * reqX;
+ }
+ else {
+ this._basicRotation[0] = (1 - this._rotationDecreaseAlpha) * x + this._rotationDecreaseAlpha * reqX;
+ }
+ if (Math.abs(reqY) > Math.abs(y)) {
+ this._basicRotation[1] = (1 - this._rotationIncreaseAlpha) * y + this._rotationIncreaseAlpha * reqY;
+ }
+ else {
+ this._basicRotation[1] = (1 - this._rotationDecreaseAlpha) * y + this._rotationDecreaseAlpha * reqY;
+ }
+ this._requestedBasicRotation = null;
+ return;
+ }
+ if (this._requestedBasicRotationUnbounded != null) {
+ let reqX = this._requestedBasicRotationUnbounded[0];
+ let reqY = this._requestedBasicRotationUnbounded[1];
+ if (Math.abs(reqX) > 0) {
+ this._basicRotation[0] = (1 - this._unboundedRotationAlpha) * this._basicRotation[0] + this._unboundedRotationAlpha * reqX;
+ }
+ if (Math.abs(reqY) > 0) {
+ this._basicRotation[1] = (1 - this._unboundedRotationAlpha) * this._basicRotation[1] + this._unboundedRotationAlpha * reqY;
+ }
+ if (this._desiredLookat != null) {
+ let desiredBasicLookat = this.currentTransform.projectBasic(this._desiredLookat.toArray());
+ desiredBasicLookat[0] += reqX;
+ desiredBasicLookat[1] += reqY;
+ this._desiredLookat = new Vector3()
+ .fromArray(this.currentTransform.unprojectBasic(desiredBasicLookat, this._lookatDepth));
+ }
+ this._requestedBasicRotationUnbounded = null;
+ }
+ if (this._basicRotation[0] === 0 && this._basicRotation[1] === 0) {
+ return;
+ }
+ this._basicRotation[0] = this._rotationAcceleration * this._basicRotation[0];
+ this._basicRotation[1] = this._rotationAcceleration * this._basicRotation[1];
+ if (Math.abs(this._basicRotation[0]) < this._rotationThreshold / Math.pow(2, this._zoom) &&
+ Math.abs(this._basicRotation[1]) < this._rotationThreshold / Math.pow(2, this._zoom)) {
+ this._basicRotation = [0, 0];
+ }
+ }
+ _clearRotation() {
+ if (isSpherical(this._currentImage.cameraType)) {
+ return;
+ }
+ if (this._requestedRotationDelta != null) {
+ this._requestedRotationDelta = null;
+ }
+ if (!this._rotationDelta.isZero) {
+ this._rotationDelta.reset();
+ }
+ if (this._requestedBasicRotation != null) {
+ this._requestedBasicRotation = null;
+ }
+ if (this._basicRotation[0] > 0 || this._basicRotation[1] > 0) {
+ this._basicRotation = [0, 0];
+ }
+ }
+ _setDesiredCenter() {
+ if (this._desiredCenter == null) {
+ return;
+ }
+ let lookatDirection = new Vector3()
+ .fromArray(this.currentTransform.unprojectBasic(this._desiredCenter, this._lookatDepth))
+ .sub(this._currentCamera.position);
+ this._currentCamera.lookat.copy(this._currentCamera.position.clone().add(lookatDirection));
+ this._previousCamera.lookat.copy(this._previousCamera.position.clone().add(lookatDirection));
+ this._desiredCenter = null;
+ }
+ _setDesiredZoom() {
+ this._desiredZoom =
+ isSpherical(this._currentImage.cameraType) ||
+ this._previousImage == null ?
+ this._zoom : 0;
+ }
+}
+
+class TraversingState extends InteractiveStateBase {
+ constructor(state) {
+ super(state);
+ this._adjustCameras();
+ this._motionless = this._motionlessTransition();
+ this._baseAlpha = this._alpha;
+ this._speedCoefficient = 1;
+ this._unitBezier =
+ new TraversingState._interpolator(0.74, 0.67, 0.38, 0.96);
+ this._useBezier = false;
+ }
+ static register(interpolator) {
+ TraversingState._interpolator = interpolator;
+ }
+ append(images) {
+ let emptyTrajectory = this._trajectory.length === 0;
+ if (emptyTrajectory) {
+ this._resetTransition();
+ }
+ super.append(images);
+ if (emptyTrajectory) {
+ this._setDesiredCenter();
+ this._setDesiredZoom();
+ }
+ }
+ prepend(images) {
+ let emptyTrajectory = this._trajectory.length === 0;
+ if (emptyTrajectory) {
+ this._resetTransition();
+ }
+ super.prepend(images);
+ if (emptyTrajectory) {
+ this._setDesiredCenter();
+ this._setDesiredZoom();
+ }
+ }
+ set(images) {
+ super.set(images);
+ this._desiredLookat = null;
+ this._resetTransition();
+ this._clearRotation();
+ this._setDesiredCenter();
+ this._setDesiredZoom();
+ if (this._trajectory.length < 3) {
+ this._useBezier = true;
+ }
+ }
+ setSpeed(speed) {
+ this._speedCoefficient = this._spatial.clamp(speed, 0, 10);
+ }
+ update(fps) {
+ if (this._alpha === 1 && this._currentIndex + this._alpha < this._trajectory.length) {
+ this._currentIndex += 1;
+ this._useBezier = this._trajectory.length < 3 &&
+ this._currentIndex + 1 === this._trajectory.length;
+ this._setCurrent();
+ this._resetTransition();
+ this._clearRotation();
+ this._desiredZoom =
+ isSpherical(this._currentImage.cameraType) ?
+ this._zoom : 0;
+ this._desiredLookat = null;
+ }
+ let animationSpeed = this._animationSpeed * (60 / fps);
+ this._baseAlpha = Math.min(1, this._baseAlpha + this._speedCoefficient * animationSpeed);
+ if (this._useBezier) {
+ this._alpha = this._unitBezier.solve(this._baseAlpha);
+ }
+ else {
+ this._alpha = this._baseAlpha;
+ }
+ this._updateRotation();
+ if (!this._rotationDelta.isZero) {
+ this._applyRotation(this._rotationDelta, this._previousCamera);
+ this._applyRotation(this._rotationDelta, this._currentCamera);
+ }
+ this._updateRotationBasic();
+ if (this._basicRotation[0] !== 0 || this._basicRotation[1] !== 0) {
+ this._applyRotationBasic(this._basicRotation);
+ }
+ this._updateZoom(animationSpeed);
+ this._updateLookat(animationSpeed);
+ this._camera.lerpCameras(this._previousCamera, this._currentCamera, this.alpha);
+ }
+ _getAlpha() {
+ return this._motionless ? Math.ceil(this._alpha) : this._alpha;
+ }
+ _setCurrentCamera() {
+ super._setCurrentCamera();
+ this._adjustCameras();
+ }
+ _adjustCameras() {
+ if (this._previousImage == null) {
+ return;
+ }
+ let lookat = this._camera.lookat.clone().sub(this._camera.position);
+ this._previousCamera.lookat.copy(lookat.clone().add(this._previousCamera.position));
+ if (isSpherical(this._currentImage.cameraType)) {
+ this._currentCamera.lookat.copy(lookat.clone().add(this._currentCamera.position));
+ }
+ }
+ _resetTransition() {
+ this._alpha = 0;
+ this._baseAlpha = 0;
+ this._motionless = this._motionlessTransition();
+ }
+}
+
+class ComponentService {
+ constructor(container, navigator) {
+ this._components = {};
+ for (const componentName in ComponentService.registeredComponents) {
+ if (!ComponentService.registeredComponents.hasOwnProperty(componentName)) {
+ continue;
+ }
+ const component = ComponentService.registeredComponents[componentName];
+ this._components[componentName] = {
+ active: false,
+ component: new component(componentName, container, navigator),
+ };
+ }
+ this._coverComponent = new ComponentService.registeredCoverComponent("cover", container, navigator);
+ this._coverComponent.activate();
+ this._coverActivated = true;
+ }
+ static register(component) {
+ if (ComponentService.registeredComponents[component.componentName] === undefined) {
+ ComponentService.registeredComponents[component.componentName] = component;
+ }
+ }
+ static registerCover(coverComponent) {
+ ComponentService.registeredCoverComponent = coverComponent;
+ }
+ get coverActivated() {
+ return this._coverActivated;
+ }
+ activateCover() {
+ if (this._coverActivated) {
+ return;
+ }
+ this._coverActivated = true;
+ for (const componentName in this._components) {
+ if (!this._components.hasOwnProperty(componentName)) {
+ continue;
+ }
+ const component = this._components[componentName];
+ if (component.active) {
+ component.component.deactivate();
+ }
+ }
+ }
+ deactivateCover() {
+ if (!this._coverActivated) {
+ return;
+ }
+ this._coverActivated = false;
+ for (const componentName in this._components) {
+ if (!this._components.hasOwnProperty(componentName)) {
+ continue;
+ }
+ const component = this._components[componentName];
+ if (component.active) {
+ component.component.activate();
+ }
+ }
+ }
+ activate(name) {
+ this._checkName(name);
+ this._components[name].active = true;
+ if (!this._coverActivated) {
+ this.get(name).activate();
+ }
+ }
+ configure(name, conf) {
+ this._checkName(name);
+ this.get(name).configure(conf);
+ }
+ deactivate(name) {
+ this._checkName(name);
+ this._components[name].active = false;
+ if (!this._coverActivated) {
+ this.get(name).deactivate();
+ }
+ }
+ get(name) {
+ return this._components[name].component;
+ }
+ getCover() {
+ return this._coverComponent;
+ }
+ remove() {
+ this._coverComponent.deactivate();
+ for (const componentName in this._components) {
+ if (!this._components.hasOwnProperty(componentName)) {
+ continue;
+ }
+ this._components[componentName].component.deactivate();
+ }
+ }
+ _checkName(name) {
+ if (!(name in this._components)) {
+ throw new ArgumentMapillaryError(`Component does not exist: ${name}`);
+ }
+ }
+}
+ComponentService.registeredComponents = {};
+
+var nativeIsArray = Array.isArray;
+var toString$2 = Object.prototype.toString;
+
+var xIsArray = nativeIsArray || isArray;
+
+function isArray(obj) {
+ return toString$2.call(obj) === "[object Array]"
+}
+
+var version = "2";
+
+VirtualPatch.NONE = 0;
+VirtualPatch.VTEXT = 1;
+VirtualPatch.VNODE = 2;
+VirtualPatch.WIDGET = 3;
+VirtualPatch.PROPS = 4;
+VirtualPatch.ORDER = 5;
+VirtualPatch.INSERT = 6;
+VirtualPatch.REMOVE = 7;
+VirtualPatch.THUNK = 8;
+
+var vpatch = VirtualPatch;
+
+function VirtualPatch(type, vNode, patch) {
+ this.type = Number(type);
+ this.vNode = vNode;
+ this.patch = patch;
+}
+
+VirtualPatch.prototype.version = version;
+VirtualPatch.prototype.type = "VirtualPatch";
+
+var isVnode = isVirtualNode;
+
+function isVirtualNode(x) {
+ return x && x.type === "VirtualNode" && x.version === version
+}
+
+var isVtext = isVirtualText;
+
+function isVirtualText(x) {
+ return x && x.type === "VirtualText" && x.version === version
+}
+
+var isWidget_1 = isWidget;
+
+function isWidget(w) {
+ return w && w.type === "Widget"
+}
+
+var isThunk_1 = isThunk;
+
+function isThunk(t) {
+ return t && t.type === "Thunk"
+}
+
+var handleThunk_1 = handleThunk;
+
+function handleThunk(a, b) {
+ var renderedA = a;
+ var renderedB = b;
+
+ if (isThunk_1(b)) {
+ renderedB = renderThunk(b, a);
+ }
+
+ if (isThunk_1(a)) {
+ renderedA = renderThunk(a, null);
+ }
+
+ return {
+ a: renderedA,
+ b: renderedB
+ }
+}
+
+function renderThunk(thunk, previous) {
+ var renderedThunk = thunk.vnode;
+
+ if (!renderedThunk) {
+ renderedThunk = thunk.vnode = thunk.render(previous);
+ }
+
+ if (!(isVnode(renderedThunk) ||
+ isVtext(renderedThunk) ||
+ isWidget_1(renderedThunk))) {
+ throw new Error("thunk did not return a valid node");
+ }
+
+ return renderedThunk
+}
+
+var isObject = function isObject(x) {
+ return typeof x === 'object' && x !== null;
+};
+
+var isVhook = isHook;
+
+function isHook(hook) {
+ return hook &&
+ (typeof hook.hook === "function" && !hook.hasOwnProperty("hook") ||
+ typeof hook.unhook === "function" && !hook.hasOwnProperty("unhook"))
+}
+
+var diffProps_1 = diffProps;
+
+function diffProps(a, b) {
+ var diff;
+
+ for (var aKey in a) {
+ if (!(aKey in b)) {
+ diff = diff || {};
+ diff[aKey] = undefined;
+ }
+
+ var aValue = a[aKey];
+ var bValue = b[aKey];
+
+ if (aValue === bValue) {
+ continue
+ } else if (isObject(aValue) && isObject(bValue)) {
+ if (getPrototype$1(bValue) !== getPrototype$1(aValue)) {
+ diff = diff || {};
+ diff[aKey] = bValue;
+ } else if (isVhook(bValue)) {
+ diff = diff || {};
+ diff[aKey] = bValue;
+ } else {
+ var objectDiff = diffProps(aValue, bValue);
+ if (objectDiff) {
+ diff = diff || {};
+ diff[aKey] = objectDiff;
+ }
+ }
+ } else {
+ diff = diff || {};
+ diff[aKey] = bValue;
+ }
+ }
+
+ for (var bKey in b) {
+ if (!(bKey in a)) {
+ diff = diff || {};
+ diff[bKey] = b[bKey];
+ }
+ }
+
+ return diff
+}
+
+function getPrototype$1(value) {
+ if (Object.getPrototypeOf) {
+ return Object.getPrototypeOf(value)
+ } else if (value.__proto__) {
+ return value.__proto__
+ } else if (value.constructor) {
+ return value.constructor.prototype
+ }
+}
+
+var diff_1$1 = diff;
+
+function diff(a, b) {
+ var patch = { a: a };
+ walk(a, b, patch, 0);
+ return patch
+}
+
+function walk(a, b, patch, index) {
+ if (a === b) {
+ return
+ }
+
+ var apply = patch[index];
+ var applyClear = false;
+
+ if (isThunk_1(a) || isThunk_1(b)) {
+ thunks(a, b, patch, index);
+ } else if (b == null) {
+
+ // If a is a widget we will add a remove patch for it
+ // Otherwise any child widgets/hooks must be destroyed.
+ // This prevents adding two remove patches for a widget.
+ if (!isWidget_1(a)) {
+ clearState(a, patch, index);
+ apply = patch[index];
+ }
+
+ apply = appendPatch(apply, new vpatch(vpatch.REMOVE, a, b));
+ } else if (isVnode(b)) {
+ if (isVnode(a)) {
+ if (a.tagName === b.tagName &&
+ a.namespace === b.namespace &&
+ a.key === b.key) {
+ var propsPatch = diffProps_1(a.properties, b.properties);
+ if (propsPatch) {
+ apply = appendPatch(apply,
+ new vpatch(vpatch.PROPS, a, propsPatch));
+ }
+ apply = diffChildren(a, b, patch, apply, index);
+ } else {
+ apply = appendPatch(apply, new vpatch(vpatch.VNODE, a, b));
+ applyClear = true;
+ }
+ } else {
+ apply = appendPatch(apply, new vpatch(vpatch.VNODE, a, b));
+ applyClear = true;
+ }
+ } else if (isVtext(b)) {
+ if (!isVtext(a)) {
+ apply = appendPatch(apply, new vpatch(vpatch.VTEXT, a, b));
+ applyClear = true;
+ } else if (a.text !== b.text) {
+ apply = appendPatch(apply, new vpatch(vpatch.VTEXT, a, b));
+ }
+ } else if (isWidget_1(b)) {
+ if (!isWidget_1(a)) {
+ applyClear = true;
+ }
+
+ apply = appendPatch(apply, new vpatch(vpatch.WIDGET, a, b));
+ }
+
+ if (apply) {
+ patch[index] = apply;
+ }
+
+ if (applyClear) {
+ clearState(a, patch, index);
+ }
+}
+
+function diffChildren(a, b, patch, apply, index) {
+ var aChildren = a.children;
+ var orderedSet = reorder(aChildren, b.children);
+ var bChildren = orderedSet.children;
+
+ var aLen = aChildren.length;
+ var bLen = bChildren.length;
+ var len = aLen > bLen ? aLen : bLen;
+
+ for (var i = 0; i < len; i++) {
+ var leftNode = aChildren[i];
+ var rightNode = bChildren[i];
+ index += 1;
+
+ if (!leftNode) {
+ if (rightNode) {
+ // Excess nodes in b need to be added
+ apply = appendPatch(apply,
+ new vpatch(vpatch.INSERT, null, rightNode));
+ }
+ } else {
+ walk(leftNode, rightNode, patch, index);
+ }
+
+ if (isVnode(leftNode) && leftNode.count) {
+ index += leftNode.count;
+ }
+ }
+
+ if (orderedSet.moves) {
+ // Reorder nodes last
+ apply = appendPatch(apply, new vpatch(
+ vpatch.ORDER,
+ a,
+ orderedSet.moves
+ ));
+ }
+
+ return apply
+}
+
+function clearState(vNode, patch, index) {
+ // TODO: Make this a single walk, not two
+ unhook(vNode, patch, index);
+ destroyWidgets(vNode, patch, index);
+}
+
+// Patch records for all destroyed widgets must be added because we need
+// a DOM node reference for the destroy function
+function destroyWidgets(vNode, patch, index) {
+ if (isWidget_1(vNode)) {
+ if (typeof vNode.destroy === "function") {
+ patch[index] = appendPatch(
+ patch[index],
+ new vpatch(vpatch.REMOVE, vNode, null)
+ );
+ }
+ } else if (isVnode(vNode) && (vNode.hasWidgets || vNode.hasThunks)) {
+ var children = vNode.children;
+ var len = children.length;
+ for (var i = 0; i < len; i++) {
+ var child = children[i];
+ index += 1;
+
+ destroyWidgets(child, patch, index);
+
+ if (isVnode(child) && child.count) {
+ index += child.count;
+ }
+ }
+ } else if (isThunk_1(vNode)) {
+ thunks(vNode, null, patch, index);
+ }
+}
+
+// Create a sub-patch for thunks
+function thunks(a, b, patch, index) {
+ var nodes = handleThunk_1(a, b);
+ var thunkPatch = diff(nodes.a, nodes.b);
+ if (hasPatches(thunkPatch)) {
+ patch[index] = new vpatch(vpatch.THUNK, null, thunkPatch);
+ }
+}
+
+function hasPatches(patch) {
+ for (var index in patch) {
+ if (index !== "a") {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Execute hooks when two nodes are identical
+function unhook(vNode, patch, index) {
+ if (isVnode(vNode)) {
+ if (vNode.hooks) {
+ patch[index] = appendPatch(
+ patch[index],
+ new vpatch(
+ vpatch.PROPS,
+ vNode,
+ undefinedKeys(vNode.hooks)
+ )
+ );
+ }
+
+ if (vNode.descendantHooks || vNode.hasThunks) {
+ var children = vNode.children;
+ var len = children.length;
+ for (var i = 0; i < len; i++) {
+ var child = children[i];
+ index += 1;
+
+ unhook(child, patch, index);
+
+ if (isVnode(child) && child.count) {
+ index += child.count;
+ }
+ }
+ }
+ } else if (isThunk_1(vNode)) {
+ thunks(vNode, null, patch, index);
+ }
+}
+
+function undefinedKeys(obj) {
+ var result = {};
+
+ for (var key in obj) {
+ result[key] = undefined;
+ }
+
+ return result
+}
+
+// List diff, naive left to right reordering
+function reorder(aChildren, bChildren) {
+ // O(M) time, O(M) memory
+ var bChildIndex = keyIndex(bChildren);
+ var bKeys = bChildIndex.keys;
+ var bFree = bChildIndex.free;
+
+ if (bFree.length === bChildren.length) {
+ return {
+ children: bChildren,
+ moves: null
+ }
+ }
+
+ // O(N) time, O(N) memory
+ var aChildIndex = keyIndex(aChildren);
+ var aKeys = aChildIndex.keys;
+ var aFree = aChildIndex.free;
+
+ if (aFree.length === aChildren.length) {
+ return {
+ children: bChildren,
+ moves: null
+ }
+ }
+
+ // O(MAX(N, M)) memory
+ var newChildren = [];
+
+ var freeIndex = 0;
+ var freeCount = bFree.length;
+ var deletedItems = 0;
+
+ // Iterate through a and match a node in b
+ // O(N) time,
+ for (var i = 0 ; i < aChildren.length; i++) {
+ var aItem = aChildren[i];
+ var itemIndex;
+
+ if (aItem.key) {
+ if (bKeys.hasOwnProperty(aItem.key)) {
+ // Match up the old keys
+ itemIndex = bKeys[aItem.key];
+ newChildren.push(bChildren[itemIndex]);
+
+ } else {
+ // Remove old keyed items
+ itemIndex = i - deletedItems++;
+ newChildren.push(null);
+ }
+ } else {
+ // Match the item in a with the next free item in b
+ if (freeIndex < freeCount) {
+ itemIndex = bFree[freeIndex++];
+ newChildren.push(bChildren[itemIndex]);
+ } else {
+ // There are no free items in b to match with
+ // the free items in a, so the extra free nodes
+ // are deleted.
+ itemIndex = i - deletedItems++;
+ newChildren.push(null);
+ }
+ }
+ }
+
+ var lastFreeIndex = freeIndex >= bFree.length ?
+ bChildren.length :
+ bFree[freeIndex];
+
+ // Iterate through b and append any new keys
+ // O(M) time
+ for (var j = 0; j < bChildren.length; j++) {
+ var newItem = bChildren[j];
+
+ if (newItem.key) {
+ if (!aKeys.hasOwnProperty(newItem.key)) {
+ // Add any new keyed items
+ // We are adding new items to the end and then sorting them
+ // in place. In future we should insert new items in place.
+ newChildren.push(newItem);
+ }
+ } else if (j >= lastFreeIndex) {
+ // Add any leftover non-keyed items
+ newChildren.push(newItem);
+ }
+ }
+
+ var simulate = newChildren.slice();
+ var simulateIndex = 0;
+ var removes = [];
+ var inserts = [];
+ var simulateItem;
+
+ for (var k = 0; k < bChildren.length;) {
+ var wantedItem = bChildren[k];
+ simulateItem = simulate[simulateIndex];
+
+ // remove items
+ while (simulateItem === null && simulate.length) {
+ removes.push(remove(simulate, simulateIndex, null));
+ simulateItem = simulate[simulateIndex];
+ }
+
+ if (!simulateItem || simulateItem.key !== wantedItem.key) {
+ // if we need a key in this position...
+ if (wantedItem.key) {
+ if (simulateItem && simulateItem.key) {
+ // if an insert doesn't put this key in place, it needs to move
+ if (bKeys[simulateItem.key] !== k + 1) {
+ removes.push(remove(simulate, simulateIndex, simulateItem.key));
+ simulateItem = simulate[simulateIndex];
+ // if the remove didn't put the wanted item in place, we need to insert it
+ if (!simulateItem || simulateItem.key !== wantedItem.key) {
+ inserts.push({key: wantedItem.key, to: k});
+ }
+ // items are matching, so skip ahead
+ else {
+ simulateIndex++;
+ }
+ }
+ else {
+ inserts.push({key: wantedItem.key, to: k});
+ }
+ }
+ else {
+ inserts.push({key: wantedItem.key, to: k});
+ }
+ k++;
+ }
+ // a key in simulate has no matching wanted key, remove it
+ else if (simulateItem && simulateItem.key) {
+ removes.push(remove(simulate, simulateIndex, simulateItem.key));
+ }
+ }
+ else {
+ simulateIndex++;
+ k++;
+ }
+ }
+
+ // remove all the remaining nodes from simulate
+ while(simulateIndex < simulate.length) {
+ simulateItem = simulate[simulateIndex];
+ removes.push(remove(simulate, simulateIndex, simulateItem && simulateItem.key));
+ }
+
+ // If the only moves we have are deletes then we can just
+ // let the delete patch remove these items.
+ if (removes.length === deletedItems && !inserts.length) {
+ return {
+ children: newChildren,
+ moves: null
+ }
+ }
+
+ return {
+ children: newChildren,
+ moves: {
+ removes: removes,
+ inserts: inserts
+ }
+ }
+}
+
+function remove(arr, index, key) {
+ arr.splice(index, 1);
+
+ return {
+ from: index,
+ key: key
+ }
+}
+
+function keyIndex(children) {
+ var keys = {};
+ var free = [];
+ var length = children.length;
+
+ for (var i = 0; i < length; i++) {
+ var child = children[i];
+
+ if (child.key) {
+ keys[child.key] = i;
+ } else {
+ free.push(i);
+ }
+ }
+
+ return {
+ keys: keys, // A hash of key name to index
+ free: free // An array of unkeyed item indices
+ }
+}
+
+function appendPatch(apply, patch) {
+ if (apply) {
+ if (xIsArray(apply)) {
+ apply.push(patch);
+ } else {
+ apply = [apply, patch];
+ }
+
+ return apply
+ } else {
+ return patch
+ }
+}
+
+var diff_1 = diff_1$1;
+
+var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+
+function getAugmentedNamespace(n) {
+ if (n.__esModule) return n;
+ var a = Object.defineProperty({}, '__esModule', {value: true});
+ Object.keys(n).forEach(function (k) {
+ var d = Object.getOwnPropertyDescriptor(n, k);
+ Object.defineProperty(a, k, d.get ? d : {
+ enumerable: true,
+ get: function () {
+ return n[k];
+ }
+ });
+ });
+ return a;
+}
+
+function createCommonjsModule(fn) {
+ var module = { exports: {} };
+ return fn(module, module.exports), module.exports;
+}
+
+function commonjsRequire (target) {
+ throw new Error('Could not dynamically require "' + target + '". Please configure the dynamicRequireTargets option of @rollup/plugin-commonjs appropriately for this require call to behave properly.');
+}
+
+var slice = Array.prototype.slice;
+
+var domWalk = iterativelyWalk;
+
+function iterativelyWalk(nodes, cb) {
+ if (!('length' in nodes)) {
+ nodes = [nodes];
+ }
+
+ nodes = slice.call(nodes);
+
+ while(nodes.length) {
+ var node = nodes.shift(),
+ ret = cb(node);
+
+ if (ret) {
+ return ret
+ }
+
+ if (node.childNodes && node.childNodes.length) {
+ nodes = slice.call(node.childNodes).concat(nodes);
+ }
+ }
+}
+
+var domComment = Comment;
+
+function Comment(data, owner) {
+ if (!(this instanceof Comment)) {
+ return new Comment(data, owner)
+ }
+
+ this.data = data;
+ this.nodeValue = data;
+ this.length = data.length;
+ this.ownerDocument = owner || null;
+}
+
+Comment.prototype.nodeType = 8;
+Comment.prototype.nodeName = "#comment";
+
+Comment.prototype.toString = function _Comment_toString() {
+ return "[object Comment]"
+};
+
+var domText = DOMText;
+
+function DOMText(value, owner) {
+ if (!(this instanceof DOMText)) {
+ return new DOMText(value)
+ }
+
+ this.data = value || "";
+ this.length = this.data.length;
+ this.ownerDocument = owner || null;
+}
+
+DOMText.prototype.type = "DOMTextNode";
+DOMText.prototype.nodeType = 3;
+DOMText.prototype.nodeName = "#text";
+
+DOMText.prototype.toString = function _Text_toString() {
+ return this.data
+};
+
+DOMText.prototype.replaceData = function replaceData(index, length, value) {
+ var current = this.data;
+ var left = current.substring(0, index);
+ var right = current.substring(index + length, current.length);
+ this.data = left + value + right;
+ this.length = this.data.length;
+};
+
+var dispatchEvent_1 = dispatchEvent;
+
+function dispatchEvent(ev) {
+ var elem = this;
+ var type = ev.type;
+
+ if (!ev.target) {
+ ev.target = elem;
+ }
+
+ if (!elem.listeners) {
+ elem.listeners = {};
+ }
+
+ var listeners = elem.listeners[type];
+
+ if (listeners) {
+ return listeners.forEach(function (listener) {
+ ev.currentTarget = elem;
+ if (typeof listener === 'function') {
+ listener(ev);
+ } else {
+ listener.handleEvent(ev);
+ }
+ })
+ }
+
+ if (elem.parentNode) {
+ elem.parentNode.dispatchEvent(ev);
+ }
+}
+
+var addEventListener_1 = addEventListener;
+
+function addEventListener(type, listener) {
+ var elem = this;
+
+ if (!elem.listeners) {
+ elem.listeners = {};
+ }
+
+ if (!elem.listeners[type]) {
+ elem.listeners[type] = [];
+ }
+
+ if (elem.listeners[type].indexOf(listener) === -1) {
+ elem.listeners[type].push(listener);
+ }
+}
+
+var removeEventListener_1 = removeEventListener;
+
+function removeEventListener(type, listener) {
+ var elem = this;
+
+ if (!elem.listeners) {
+ return
+ }
+
+ if (!elem.listeners[type]) {
+ return
+ }
+
+ var list = elem.listeners[type];
+ var index = list.indexOf(listener);
+ if (index !== -1) {
+ list.splice(index, 1);
+ }
+}
+
+var serialize = serializeNode;
+
+var voidElements = ["area","base","br","col","embed","hr","img","input","keygen","link","menuitem","meta","param","source","track","wbr"];
+
+function serializeNode(node) {
+ switch (node.nodeType) {
+ case 3:
+ return escapeText(node.data)
+ case 8:
+ return "<!--" + node.data + "-->"
+ default:
+ return serializeElement(node)
+ }
+}
+
+function serializeElement(elem) {
+ var strings = [];
+
+ var tagname = elem.tagName;
+
+ if (elem.namespaceURI === "http://www.w3.org/1999/xhtml") {
+ tagname = tagname.toLowerCase();
+ }
+
+ strings.push("<" + tagname + properties(elem) + datasetify(elem));
+
+ if (voidElements.indexOf(tagname) > -1) {
+ strings.push(" />");
+ } else {
+ strings.push(">");
+
+ if (elem.childNodes.length) {
+ strings.push.apply(strings, elem.childNodes.map(serializeNode));
+ } else if (elem.textContent || elem.innerText) {
+ strings.push(escapeText(elem.textContent || elem.innerText));
+ } else if (elem.innerHTML) {
+ strings.push(elem.innerHTML);
+ }
+
+ strings.push("</" + tagname + ">");
+ }
+
+ return strings.join("")
+}
+
+function isProperty(elem, key) {
+ var type = typeof elem[key];
+
+ if (key === "style" && Object.keys(elem.style).length > 0) {
+ return true
+ }
+
+ return elem.hasOwnProperty(key) &&
+ (type === "string" || type === "boolean" || type === "number") &&
+ key !== "nodeName" && key !== "className" && key !== "tagName" &&
+ key !== "textContent" && key !== "innerText" && key !== "namespaceURI" && key !== "innerHTML"
+}
+
+function stylify(styles) {
+ if (typeof styles === 'string') return styles
+ var attr = "";
+ Object.keys(styles).forEach(function (key) {
+ var value = styles[key];
+ key = key.replace(/[A-Z]/g, function(c) {
+ return "-" + c.toLowerCase();
+ });
+ attr += key + ":" + value + ";";
+ });
+ return attr
+}
+
+function datasetify(elem) {
+ var ds = elem.dataset;
+ var props = [];
+
+ for (var key in ds) {
+ props.push({ name: "data-" + key, value: ds[key] });
+ }
+
+ return props.length ? stringify(props) : ""
+}
+
+function stringify(list) {
+ var attributes = [];
+ list.forEach(function (tuple) {
+ var name = tuple.name;
+ var value = tuple.value;
+
+ if (name === "style") {
+ value = stylify(value);
+ }
+
+ attributes.push(name + "=" + "\"" + escapeAttributeValue(value) + "\"");
+ });
+
+ return attributes.length ? " " + attributes.join(" ") : ""
+}
+
+function properties(elem) {
+ var props = [];
+ for (var key in elem) {
+ if (isProperty(elem, key)) {
+ props.push({ name: key, value: elem[key] });
+ }
+ }
+
+ for (var ns in elem._attributes) {
+ for (var attribute in elem._attributes[ns]) {
+ var prop = elem._attributes[ns][attribute];
+ var name = (prop.prefix ? prop.prefix + ":" : "") + attribute;
+ props.push({ name: name, value: prop.value });
+ }
+ }
+
+ if (elem.className) {
+ props.push({ name: "class", value: elem.className });
+ }
+
+ return props.length ? stringify(props) : ""
+}
+
+function escapeText(s) {
+ var str = '';
+
+ if (typeof(s) === 'string') {
+ str = s;
+ } else if (s) {
+ str = s.toString();
+ }
+
+ return str
+ .replace(/&/g, "&")
+ .replace(/</g, "<")
+ .replace(/>/g, ">")
+}
+
+function escapeAttributeValue(str) {
+ return escapeText(str).replace(/"/g, """)
+}
+
+var htmlns = "http://www.w3.org/1999/xhtml";
+
+var domElement = DOMElement;
+
+function DOMElement(tagName, owner, namespace) {
+ if (!(this instanceof DOMElement)) {
+ return new DOMElement(tagName)
+ }
+
+ var ns = namespace === undefined ? htmlns : (namespace || null);
+
+ this.tagName = ns === htmlns ? String(tagName).toUpperCase() : tagName;
+ this.nodeName = this.tagName;
+ this.className = "";
+ this.dataset = {};
+ this.childNodes = [];
+ this.parentNode = null;
+ this.style = {};
+ this.ownerDocument = owner || null;
+ this.namespaceURI = ns;
+ this._attributes = {};
+
+ if (this.tagName === 'INPUT') {
+ this.type = 'text';
+ }
+}
+
+DOMElement.prototype.type = "DOMElement";
+DOMElement.prototype.nodeType = 1;
+
+DOMElement.prototype.appendChild = function _Element_appendChild(child) {
+ if (child.parentNode) {
+ child.parentNode.removeChild(child);
+ }
+
+ this.childNodes.push(child);
+ child.parentNode = this;
+
+ return child
+};
+
+DOMElement.prototype.replaceChild =
+ function _Element_replaceChild(elem, needle) {
+ // TODO: Throw NotFoundError if needle.parentNode !== this
+
+ if (elem.parentNode) {
+ elem.parentNode.removeChild(elem);
+ }
+
+ var index = this.childNodes.indexOf(needle);
+
+ needle.parentNode = null;
+ this.childNodes[index] = elem;
+ elem.parentNode = this;
+
+ return needle
+ };
+
+DOMElement.prototype.removeChild = function _Element_removeChild(elem) {
+ // TODO: Throw NotFoundError if elem.parentNode !== this
+
+ var index = this.childNodes.indexOf(elem);
+ this.childNodes.splice(index, 1);
+
+ elem.parentNode = null;
+ return elem
+};
+
+DOMElement.prototype.insertBefore =
+ function _Element_insertBefore(elem, needle) {
+ // TODO: Throw NotFoundError if referenceElement is a dom node
+ // and parentNode !== this
+
+ if (elem.parentNode) {
+ elem.parentNode.removeChild(elem);
+ }
+
+ var index = needle === null || needle === undefined ?
+ -1 :
+ this.childNodes.indexOf(needle);
+
+ if (index > -1) {
+ this.childNodes.splice(index, 0, elem);
+ } else {
+ this.childNodes.push(elem);
+ }
+
+ elem.parentNode = this;
+ return elem
+ };
+
+DOMElement.prototype.setAttributeNS =
+ function _Element_setAttributeNS(namespace, name, value) {
+ var prefix = null;
+ var localName = name;
+ var colonPosition = name.indexOf(":");
+ if (colonPosition > -1) {
+ prefix = name.substr(0, colonPosition);
+ localName = name.substr(colonPosition + 1);
+ }
+ if (this.tagName === 'INPUT' && name === 'type') {
+ this.type = value;
+ }
+ else {
+ var attributes = this._attributes[namespace] || (this._attributes[namespace] = {});
+ attributes[localName] = {value: value, prefix: prefix};
+ }
+ };
+
+DOMElement.prototype.getAttributeNS =
+ function _Element_getAttributeNS(namespace, name) {
+ var attributes = this._attributes[namespace];
+ var value = attributes && attributes[name] && attributes[name].value;
+ if (this.tagName === 'INPUT' && name === 'type') {
+ return this.type;
+ }
+ if (typeof value !== "string") {
+ return null
+ }
+ return value
+ };
+
+DOMElement.prototype.removeAttributeNS =
+ function _Element_removeAttributeNS(namespace, name) {
+ var attributes = this._attributes[namespace];
+ if (attributes) {
+ delete attributes[name];
+ }
+ };
+
+DOMElement.prototype.hasAttributeNS =
+ function _Element_hasAttributeNS(namespace, name) {
+ var attributes = this._attributes[namespace];
+ return !!attributes && name in attributes;
+ };
+
+DOMElement.prototype.setAttribute = function _Element_setAttribute(name, value) {
+ return this.setAttributeNS(null, name, value)
+};
+
+DOMElement.prototype.getAttribute = function _Element_getAttribute(name) {
+ return this.getAttributeNS(null, name)
+};
+
+DOMElement.prototype.removeAttribute = function _Element_removeAttribute(name) {
+ return this.removeAttributeNS(null, name)
+};
+
+DOMElement.prototype.hasAttribute = function _Element_hasAttribute(name) {
+ return this.hasAttributeNS(null, name)
+};
+
+DOMElement.prototype.removeEventListener = removeEventListener_1;
+DOMElement.prototype.addEventListener = addEventListener_1;
+DOMElement.prototype.dispatchEvent = dispatchEvent_1;
+
+// Un-implemented
+DOMElement.prototype.focus = function _Element_focus() {
+ return void 0
+};
+
+DOMElement.prototype.toString = function _Element_toString() {
+ return serialize(this)
+};
+
+DOMElement.prototype.getElementsByClassName = function _Element_getElementsByClassName(classNames) {
+ var classes = classNames.split(" ");
+ var elems = [];
+
+ domWalk(this, function (node) {
+ if (node.nodeType === 1) {
+ var nodeClassName = node.className || "";
+ var nodeClasses = nodeClassName.split(" ");
+
+ if (classes.every(function (item) {
+ return nodeClasses.indexOf(item) !== -1
+ })) {
+ elems.push(node);
+ }
+ }
+ });
+
+ return elems
+};
+
+DOMElement.prototype.getElementsByTagName = function _Element_getElementsByTagName(tagName) {
+ tagName = tagName.toLowerCase();
+ var elems = [];
+
+ domWalk(this.childNodes, function (node) {
+ if (node.nodeType === 1 && (tagName === '*' || node.tagName.toLowerCase() === tagName)) {
+ elems.push(node);
+ }
+ });
+
+ return elems
+};
+
+DOMElement.prototype.contains = function _Element_contains(element) {
+ return domWalk(this, function (node) {
+ return element === node
+ }) || false
+};
+
+var domFragment = DocumentFragment;
+
+function DocumentFragment(owner) {
+ if (!(this instanceof DocumentFragment)) {
+ return new DocumentFragment()
+ }
+
+ this.childNodes = [];
+ this.parentNode = null;
+ this.ownerDocument = owner || null;
+}
+
+DocumentFragment.prototype.type = "DocumentFragment";
+DocumentFragment.prototype.nodeType = 11;
+DocumentFragment.prototype.nodeName = "#document-fragment";
+
+DocumentFragment.prototype.appendChild = domElement.prototype.appendChild;
+DocumentFragment.prototype.replaceChild = domElement.prototype.replaceChild;
+DocumentFragment.prototype.removeChild = domElement.prototype.removeChild;
+
+DocumentFragment.prototype.toString =
+ function _DocumentFragment_toString() {
+ return this.childNodes.map(function (node) {
+ return String(node)
+ }).join("")
+ };
+
+var event = Event;
+
+function Event(family) {}
+
+Event.prototype.initEvent = function _Event_initEvent(type, bubbles, cancelable) {
+ this.type = type;
+ this.bubbles = bubbles;
+ this.cancelable = cancelable;
+};
+
+Event.prototype.preventDefault = function _Event_preventDefault() {
+
+};
+
+var document$1 = Document;
+
+function Document() {
+ if (!(this instanceof Document)) {
+ return new Document();
+ }
+
+ this.head = this.createElement("head");
+ this.body = this.createElement("body");
+ this.documentElement = this.createElement("html");
+ this.documentElement.appendChild(this.head);
+ this.documentElement.appendChild(this.body);
+ this.childNodes = [this.documentElement];
+ this.nodeType = 9;
+}
+
+var proto = Document.prototype;
+proto.createTextNode = function createTextNode(value) {
+ return new domText(value, this)
+};
+
+proto.createElementNS = function createElementNS(namespace, tagName) {
+ var ns = namespace === null ? null : String(namespace);
+ return new domElement(tagName, this, ns)
+};
+
+proto.createElement = function createElement(tagName) {
+ return new domElement(tagName, this)
+};
+
+proto.createDocumentFragment = function createDocumentFragment() {
+ return new domFragment(this)
+};
+
+proto.createEvent = function createEvent(family) {
+ return new event(family)
+};
+
+proto.createComment = function createComment(data) {
+ return new domComment(data, this)
+};
+
+proto.getElementById = function getElementById(id) {
+ id = String(id);
+
+ var result = domWalk(this.childNodes, function (node) {
+ if (String(node.id) === id) {
+ return node
+ }
+ });
+
+ return result || null
+};
+
+proto.getElementsByClassName = domElement.prototype.getElementsByClassName;
+proto.getElementsByTagName = domElement.prototype.getElementsByTagName;
+proto.contains = domElement.prototype.contains;
+
+proto.removeEventListener = removeEventListener_1;
+proto.addEventListener = addEventListener_1;
+proto.dispatchEvent = dispatchEvent_1;
+
+var minDocument = new document$1();
+
+var topLevel = typeof commonjsGlobal !== 'undefined' ? commonjsGlobal :
+ typeof window !== 'undefined' ? window : {};
+
+
+var doccy;
+
+if (typeof document !== 'undefined') {
+ doccy = document;
+} else {
+ doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'];
+
+ if (!doccy) {
+ doccy = topLevel['__GLOBAL_DOCUMENT_CACHE@4'] = minDocument;
+ }
+}
+
+var document_1 = doccy;
+
+var applyProperties_1 = applyProperties;
+
+function applyProperties(node, props, previous) {
+ for (var propName in props) {
+ var propValue = props[propName];
+
+ if (propValue === undefined) {
+ removeProperty(node, propName, propValue, previous);
+ } else if (isVhook(propValue)) {
+ removeProperty(node, propName, propValue, previous);
+ if (propValue.hook) {
+ propValue.hook(node,
+ propName,
+ previous ? previous[propName] : undefined);
+ }
+ } else {
+ if (isObject(propValue)) {
+ patchObject(node, props, previous, propName, propValue);
+ } else {
+ node[propName] = propValue;
+ }
+ }
+ }
+}
+
+function removeProperty(node, propName, propValue, previous) {
+ if (previous) {
+ var previousValue = previous[propName];
+
+ if (!isVhook(previousValue)) {
+ if (propName === "attributes") {
+ for (var attrName in previousValue) {
+ node.removeAttribute(attrName);
+ }
+ } else if (propName === "style") {
+ for (var i in previousValue) {
+ node.style[i] = "";
+ }
+ } else if (typeof previousValue === "string") {
+ node[propName] = "";
+ } else {
+ node[propName] = null;
+ }
+ } else if (previousValue.unhook) {
+ previousValue.unhook(node, propName, propValue);
+ }
+ }
+}
+
+function patchObject(node, props, previous, propName, propValue) {
+ var previousValue = previous ? previous[propName] : undefined;
+
+ // Set attributes
+ if (propName === "attributes") {
+ for (var attrName in propValue) {
+ var attrValue = propValue[attrName];
+
+ if (attrValue === undefined) {
+ node.removeAttribute(attrName);
+ } else {
+ node.setAttribute(attrName, attrValue);
+ }
+ }
+
+ return
+ }
+
+ if(previousValue && isObject(previousValue) &&
+ getPrototype(previousValue) !== getPrototype(propValue)) {
+ node[propName] = propValue;
+ return
+ }
+
+ if (!isObject(node[propName])) {
+ node[propName] = {};
+ }
+
+ var replacer = propName === "style" ? "" : undefined;
+
+ for (var k in propValue) {
+ var value = propValue[k];
+ node[propName][k] = (value === undefined) ? replacer : value;
+ }
+}
+
+function getPrototype(value) {
+ if (Object.getPrototypeOf) {
+ return Object.getPrototypeOf(value)
+ } else if (value.__proto__) {
+ return value.__proto__
+ } else if (value.constructor) {
+ return value.constructor.prototype
+ }
+}
+
+var createElement_1$1 = createElement;
+
+function createElement(vnode, opts) {
+ var doc = opts ? opts.document || document_1 : document_1;
+ var warn = opts ? opts.warn : null;
+
+ vnode = handleThunk_1(vnode).a;
+
+ if (isWidget_1(vnode)) {
+ return vnode.init()
+ } else if (isVtext(vnode)) {
+ return doc.createTextNode(vnode.text)
+ } else if (!isVnode(vnode)) {
+ if (warn) {
+ warn("Item is not a valid virtual dom node", vnode);
+ }
+ return null
+ }
+
+ var node = (vnode.namespace === null) ?
+ doc.createElement(vnode.tagName) :
+ doc.createElementNS(vnode.namespace, vnode.tagName);
+
+ var props = vnode.properties;
+ applyProperties_1(node, props);
+
+ var children = vnode.children;
+
+ for (var i = 0; i < children.length; i++) {
+ var childNode = createElement(children[i], opts);
+ if (childNode) {
+ node.appendChild(childNode);
+ }
+ }
+
+ return node
+}
+
+// Maps a virtual DOM tree onto a real DOM tree in an efficient manner.
+// We don't want to read all of the DOM nodes in the tree so we use
+// the in-order tree indexing to eliminate recursion down certain branches.
+// We only recurse into a DOM node if we know that it contains a child of
+// interest.
+
+var noChild = {};
+
+var domIndex_1 = domIndex;
+
+function domIndex(rootNode, tree, indices, nodes) {
+ if (!indices || indices.length === 0) {
+ return {}
+ } else {
+ indices.sort(ascending);
+ return recurse(rootNode, tree, indices, nodes, 0)
+ }
+}
+
+function recurse(rootNode, tree, indices, nodes, rootIndex) {
+ nodes = nodes || {};
+
+
+ if (rootNode) {
+ if (indexInRange(indices, rootIndex, rootIndex)) {
+ nodes[rootIndex] = rootNode;
+ }
+
+ var vChildren = tree.children;
+
+ if (vChildren) {
+
+ var childNodes = rootNode.childNodes;
+
+ for (var i = 0; i < tree.children.length; i++) {
+ rootIndex += 1;
+
+ var vChild = vChildren[i] || noChild;
+ var nextIndex = rootIndex + (vChild.count || 0);
+
+ // skip recursion down the tree if there are no nodes down here
+ if (indexInRange(indices, rootIndex, nextIndex)) {
+ recurse(childNodes[i], vChild, indices, nodes, rootIndex);
+ }
+
+ rootIndex = nextIndex;
+ }
+ }
+ }
+
+ return nodes
+}
+
+// Binary search for an index in the interval [left, right]
+function indexInRange(indices, left, right) {
+ if (indices.length === 0) {
+ return false
+ }
+
+ var minIndex = 0;
+ var maxIndex = indices.length - 1;
+ var currentIndex;
+ var currentItem;
+
+ while (minIndex <= maxIndex) {
+ currentIndex = ((maxIndex + minIndex) / 2) >> 0;
+ currentItem = indices[currentIndex];
+
+ if (minIndex === maxIndex) {
+ return currentItem >= left && currentItem <= right
+ } else if (currentItem < left) {
+ minIndex = currentIndex + 1;
+ } else if (currentItem > right) {
+ maxIndex = currentIndex - 1;
+ } else {
+ return true
+ }
+ }
+
+ return false;
+}
+
+function ascending(a, b) {
+ return a > b ? 1 : -1
+}
+
+var updateWidget_1 = updateWidget;
+
+function updateWidget(a, b) {
+ if (isWidget_1(a) && isWidget_1(b)) {
+ if ("name" in a && "name" in b) {
+ return a.id === b.id
+ } else {
+ return a.init === b.init
+ }
+ }
+
+ return false
+}
+
+var patchOp = applyPatch$1;
+
+function applyPatch$1(vpatch$1, domNode, renderOptions) {
+ var type = vpatch$1.type;
+ var vNode = vpatch$1.vNode;
+ var patch = vpatch$1.patch;
+
+ switch (type) {
+ case vpatch.REMOVE:
+ return removeNode$1(domNode, vNode)
+ case vpatch.INSERT:
+ return insertNode$1(domNode, patch, renderOptions)
+ case vpatch.VTEXT:
+ return stringPatch(domNode, vNode, patch, renderOptions)
+ case vpatch.WIDGET:
+ return widgetPatch(domNode, vNode, patch, renderOptions)
+ case vpatch.VNODE:
+ return vNodePatch(domNode, vNode, patch, renderOptions)
+ case vpatch.ORDER:
+ reorderChildren(domNode, patch);
+ return domNode
+ case vpatch.PROPS:
+ applyProperties_1(domNode, patch, vNode.properties);
+ return domNode
+ case vpatch.THUNK:
+ return replaceRoot(domNode,
+ renderOptions.patch(domNode, patch, renderOptions))
+ default:
+ return domNode
+ }
+}
+
+function removeNode$1(domNode, vNode) {
+ var parentNode = domNode.parentNode;
+
+ if (parentNode) {
+ parentNode.removeChild(domNode);
+ }
+
+ destroyWidget(domNode, vNode);
+
+ return null
+}
+
+function insertNode$1(parentNode, vNode, renderOptions) {
+ var newNode = renderOptions.render(vNode, renderOptions);
+
+ if (parentNode) {
+ parentNode.appendChild(newNode);
+ }
+
+ return parentNode
+}
+
+function stringPatch(domNode, leftVNode, vText, renderOptions) {
+ var newNode;
+
+ if (domNode.nodeType === 3) {
+ domNode.replaceData(0, domNode.length, vText.text);
+ newNode = domNode;
+ } else {
+ var parentNode = domNode.parentNode;
+ newNode = renderOptions.render(vText, renderOptions);
+
+ if (parentNode && newNode !== domNode) {
+ parentNode.replaceChild(newNode, domNode);
+ }
+ }
+
+ return newNode
+}
+
+function widgetPatch(domNode, leftVNode, widget, renderOptions) {
+ var updating = updateWidget_1(leftVNode, widget);
+ var newNode;
+
+ if (updating) {
+ newNode = widget.update(leftVNode, domNode) || domNode;
+ } else {
+ newNode = renderOptions.render(widget, renderOptions);
+ }
+
+ var parentNode = domNode.parentNode;
+
+ if (parentNode && newNode !== domNode) {
+ parentNode.replaceChild(newNode, domNode);
+ }
+
+ if (!updating) {
+ destroyWidget(domNode, leftVNode);
+ }
+
+ return newNode
+}
+
+function vNodePatch(domNode, leftVNode, vNode, renderOptions) {
+ var parentNode = domNode.parentNode;
+ var newNode = renderOptions.render(vNode, renderOptions);
+
+ if (parentNode && newNode !== domNode) {
+ parentNode.replaceChild(newNode, domNode);
+ }
+
+ return newNode
+}
+
+function destroyWidget(domNode, w) {
+ if (typeof w.destroy === "function" && isWidget_1(w)) {
+ w.destroy(domNode);
+ }
+}
+
+function reorderChildren(domNode, moves) {
+ var childNodes = domNode.childNodes;
+ var keyMap = {};
+ var node;
+ var remove;
+ var insert;
+
+ for (var i = 0; i < moves.removes.length; i++) {
+ remove = moves.removes[i];
+ node = childNodes[remove.from];
+ if (remove.key) {
+ keyMap[remove.key] = node;
+ }
+ domNode.removeChild(node);
+ }
+
+ var length = childNodes.length;
+ for (var j = 0; j < moves.inserts.length; j++) {
+ insert = moves.inserts[j];
+ node = keyMap[insert.key];
+ // this is the weirdest bug i've ever seen in webkit
+ domNode.insertBefore(node, insert.to >= length++ ? null : childNodes[insert.to]);
+ }
+}
+
+function replaceRoot(oldRoot, newRoot) {
+ if (oldRoot && newRoot && oldRoot !== newRoot && oldRoot.parentNode) {
+ oldRoot.parentNode.replaceChild(newRoot, oldRoot);
+ }
+
+ return newRoot;
+}
+
+var patch_1$1 = patch;
+
+function patch(rootNode, patches, renderOptions) {
+ renderOptions = renderOptions || {};
+ renderOptions.patch = renderOptions.patch && renderOptions.patch !== patch
+ ? renderOptions.patch
+ : patchRecursive;
+ renderOptions.render = renderOptions.render || createElement_1$1;
+
+ return renderOptions.patch(rootNode, patches, renderOptions)
+}
+
+function patchRecursive(rootNode, patches, renderOptions) {
+ var indices = patchIndices(patches);
+
+ if (indices.length === 0) {
+ return rootNode
+ }
+
+ var index = domIndex_1(rootNode, patches.a, indices);
+ var ownerDocument = rootNode.ownerDocument;
+
+ if (!renderOptions.document && ownerDocument !== document_1) {
+ renderOptions.document = ownerDocument;
+ }
+
+ for (var i = 0; i < indices.length; i++) {
+ var nodeIndex = indices[i];
+ rootNode = applyPatch(rootNode,
+ index[nodeIndex],
+ patches[nodeIndex],
+ renderOptions);
+ }
+
+ return rootNode
+}
+
+function applyPatch(rootNode, domNode, patchList, renderOptions) {
+ if (!domNode) {
+ return rootNode
+ }
+
+ var newNode;
+
+ if (xIsArray(patchList)) {
+ for (var i = 0; i < patchList.length; i++) {
+ newNode = patchOp(patchList[i], domNode, renderOptions);
+
+ if (domNode === rootNode) {
+ rootNode = newNode;
+ }
+ }
+ } else {
+ newNode = patchOp(patchList, domNode, renderOptions);
+
+ if (domNode === rootNode) {
+ rootNode = newNode;
+ }
+ }
+
+ return rootNode
+}
+
+function patchIndices(patches) {
+ var indices = [];
+
+ for (var key in patches) {
+ if (key !== "a") {
+ indices.push(Number(key));
+ }
+ }
+
+ return indices
+}
+
+var patch_1 = patch_1$1;
+
+var vnode = VirtualNode;
+
+var noProperties = {};
+var noChildren = [];
+
+function VirtualNode(tagName, properties, children, key, namespace) {
+ this.tagName = tagName;
+ this.properties = properties || noProperties;
+ this.children = children || noChildren;
+ this.key = key != null ? String(key) : undefined;
+ this.namespace = (typeof namespace === "string") ? namespace : null;
+
+ var count = (children && children.length) || 0;
+ var descendants = 0;
+ var hasWidgets = false;
+ var hasThunks = false;
+ var descendantHooks = false;
+ var hooks;
+
+ for (var propName in properties) {
+ if (properties.hasOwnProperty(propName)) {
+ var property = properties[propName];
+ if (isVhook(property) && property.unhook) {
+ if (!hooks) {
+ hooks = {};
+ }
+
+ hooks[propName] = property;
+ }
+ }
+ }
+
+ for (var i = 0; i < count; i++) {
+ var child = children[i];
+ if (isVnode(child)) {
+ descendants += child.count || 0;
+
+ if (!hasWidgets && child.hasWidgets) {
+ hasWidgets = true;
+ }
+
+ if (!hasThunks && child.hasThunks) {
+ hasThunks = true;
+ }
+
+ if (!descendantHooks && (child.hooks || child.descendantHooks)) {
+ descendantHooks = true;
+ }
+ } else if (!hasWidgets && isWidget_1(child)) {
+ if (typeof child.destroy === "function") {
+ hasWidgets = true;
+ }
+ } else if (!hasThunks && isThunk_1(child)) {
+ hasThunks = true;
+ }
+ }
+
+ this.count = count + descendants;
+ this.hasWidgets = hasWidgets;
+ this.hasThunks = hasThunks;
+ this.hooks = hooks;
+ this.descendantHooks = descendantHooks;
+}
+
+VirtualNode.prototype.version = version;
+VirtualNode.prototype.type = "VirtualNode";
+
+var vtext = VirtualText;
+
+function VirtualText(text) {
+ this.text = String(text);
+}
+
+VirtualText.prototype.version = version;
+VirtualText.prototype.type = "VirtualText";
+
+/*!
+ * Cross-Browser Split 1.1.1
+ * Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
+ * Available under the MIT License
+ * ECMAScript compliant, uniform cross-browser split method
+ */
+/**
+ * Splits a string into an array of strings using a regex or string separator. Matches of the
+ * separator are not included in the result array. However, if `separator` is a regex that contains
+ * capturing groups, backreferences are spliced into the result each time `separator` is matched.
+ * Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
+ * cross-browser.
+ * @param {String} str String to split.
+ * @param {RegExp|String} separator Regex or string to use for separating the string.
+ * @param {Number} [limit] Maximum number of items to include in the result array.
+ * @returns {Array} Array of substrings.
+ * @example
+ *
+ * // Basic use
+ * split('a b c d', ' ');
+ * // -> ['a', 'b', 'c', 'd']
+ *
+ * // With limit
+ * split('a b c d', ' ', 2);
+ * // -> ['a', 'b']
+ *
+ * // Backreferences in result array
+ * split('..word1 word2..', /([a-z]+)(\d+)/i);
+ * // -> ['..', 'word', '1', ' ', 'word', '2', '..']
+ */
+var browserSplit = (function split(undef) {
+
+ var nativeSplit = String.prototype.split,
+ compliantExecNpcg = /()??/.exec("")[1] === undef,
+ // NPCG: nonparticipating capturing group
+ self;
+
+ self = function(str, separator, limit) {
+ // If `separator` is not a regex, use `nativeSplit`
+ if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
+ return nativeSplit.call(str, separator, limit);
+ }
+ var output = [],
+ flags = (separator.ignoreCase ? "i" : "") + (separator.multiline ? "m" : "") + (separator.extended ? "x" : "") + // Proposed for ES6
+ (separator.sticky ? "y" : ""),
+ // Firefox 3+
+ lastLastIndex = 0,
+ // Make `global` and avoid `lastIndex` issues by working with a copy
+ separator = new RegExp(separator.source, flags + "g"),
+ separator2, match, lastIndex, lastLength;
+ str += ""; // Type-convert
+ if (!compliantExecNpcg) {
+ // Doesn't need flags gy, but they don't hurt
+ separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
+ }
+ /* Values for `limit`, per the spec:
+ * If undefined: 4294967295 // Math.pow(2, 32) - 1
+ * If 0, Infinity, or NaN: 0
+ * If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
+ * If negative number: 4294967296 - Math.floor(Math.abs(limit))
+ * If other: Type-convert, then use the above rules
+ */
+ limit = limit === undef ? -1 >>> 0 : // Math.pow(2, 32) - 1
+ limit >>> 0; // ToUint32(limit)
+ while (match = separator.exec(str)) {
+ // `separator.lastIndex` is not reliable cross-browser
+ lastIndex = match.index + match[0].length;
+ if (lastIndex > lastLastIndex) {
+ output.push(str.slice(lastLastIndex, match.index));
+ // Fix browsers whose `exec` methods don't consistently return `undefined` for
+ // nonparticipating capturing groups
+ if (!compliantExecNpcg && match.length > 1) {
+ match[0].replace(separator2, function() {
+ for (var i = 1; i < arguments.length - 2; i++) {
+ if (arguments[i] === undef) {
+ match[i] = undef;
+ }
+ }
+ });
+ }
+ if (match.length > 1 && match.index < str.length) {
+ Array.prototype.push.apply(output, match.slice(1));
+ }
+ lastLength = match[0].length;
+ lastLastIndex = lastIndex;
+ if (output.length >= limit) {
+ break;
+ }
+ }
+ if (separator.lastIndex === match.index) {
+ separator.lastIndex++; // Avoid an infinite loop
+ }
+ }
+ if (lastLastIndex === str.length) {
+ if (lastLength || !separator.test("")) {
+ output.push("");
+ }
+ } else {
+ output.push(str.slice(lastLastIndex));
+ }
+ return output.length > limit ? output.slice(0, limit) : output;
+ };
+
+ return self;
+})();
+
+var classIdSplit = /([\.#]?[a-zA-Z0-9\u007F-\uFFFF_:-]+)/;
+var notClassId = /^\.|#/;
+
+var parseTag_1 = parseTag;
+
+function parseTag(tag, props) {
+ if (!tag) {
+ return 'DIV';
+ }
+
+ var noId = !(props.hasOwnProperty('id'));
+
+ var tagParts = browserSplit(tag, classIdSplit);
+ var tagName = null;
+
+ if (notClassId.test(tagParts[1])) {
+ tagName = 'DIV';
+ }
+
+ var classes, part, type, i;
+
+ for (i = 0; i < tagParts.length; i++) {
+ part = tagParts[i];
+
+ if (!part) {
+ continue;
+ }
+
+ type = part.charAt(0);
+
+ if (!tagName) {
+ tagName = part;
+ } else if (type === '.') {
+ classes = classes || [];
+ classes.push(part.substring(1, part.length));
+ } else if (type === '#' && noId) {
+ props.id = part.substring(1, part.length);
+ }
+ }
+
+ if (classes) {
+ if (props.className) {
+ classes.push(props.className);
+ }
+
+ props.className = classes.join(' ');
+ }
+
+ return props.namespace ? tagName : tagName.toUpperCase();
+}
+
+var softSetHook = SoftSetHook;
+
+function SoftSetHook(value) {
+ if (!(this instanceof SoftSetHook)) {
+ return new SoftSetHook(value);
+ }
+
+ this.value = value;
+}
+
+SoftSetHook.prototype.hook = function (node, propertyName) {
+ if (node[propertyName] !== this.value) {
+ node[propertyName] = this.value;
+ }
+};
+
+/*global window, global*/
+
+var root = typeof window !== 'undefined' ?
+ window : typeof commonjsGlobal !== 'undefined' ?
+ commonjsGlobal : {};
+
+var individual = Individual;
+
+function Individual(key, value) {
+ if (key in root) {
+ return root[key];
+ }
+
+ root[key] = value;
+
+ return value;
+}
+
+var oneVersion = OneVersion;
+
+function OneVersion(moduleName, version, defaultValue) {
+ var key = '__INDIVIDUAL_ONE_VERSION_' + moduleName;
+ var enforceKey = key + '_ENFORCE_SINGLETON';
+
+ var versionValue = individual(enforceKey, version);
+
+ if (versionValue !== version) {
+ throw new Error('Can only have one copy of ' +
+ moduleName + '.\n' +
+ 'You already have version ' + versionValue +
+ ' installed.\n' +
+ 'This means you cannot install version ' + version);
+ }
+
+ return individual(key, defaultValue);
+}
+
+var MY_VERSION = '7';
+oneVersion('ev-store', MY_VERSION);
+
+var hashKey = '__EV_STORE_KEY@' + MY_VERSION;
+
+var evStore = EvStore;
+
+function EvStore(elem) {
+ var hash = elem[hashKey];
+
+ if (!hash) {
+ hash = elem[hashKey] = {};
+ }
+
+ return hash;
+}
+
+var evHook = EvHook;
+
+function EvHook(value) {
+ if (!(this instanceof EvHook)) {
+ return new EvHook(value);
+ }
+
+ this.value = value;
+}
+
+EvHook.prototype.hook = function (node, propertyName) {
+ var es = evStore(node);
+ var propName = propertyName.substr(3);
+
+ es[propName] = this.value;
+};
+
+EvHook.prototype.unhook = function(node, propertyName) {
+ var es = evStore(node);
+ var propName = propertyName.substr(3);
+
+ es[propName] = undefined;
+};
+
+var virtualHyperscript = h;
+
+function h(tagName, properties, children) {
+ var childNodes = [];
+ var tag, props, key, namespace;
+
+ if (!children && isChildren(properties)) {
+ children = properties;
+ props = {};
+ }
+
+ props = props || properties || {};
+ tag = parseTag_1(tagName, props);
+
+ // support keys
+ if (props.hasOwnProperty('key')) {
+ key = props.key;
+ props.key = undefined;
+ }
+
+ // support namespace
+ if (props.hasOwnProperty('namespace')) {
+ namespace = props.namespace;
+ props.namespace = undefined;
+ }
+
+ // fix cursor bug
+ if (tag === 'INPUT' &&
+ !namespace &&
+ props.hasOwnProperty('value') &&
+ props.value !== undefined &&
+ !isVhook(props.value)
+ ) {
+ props.value = softSetHook(props.value);
+ }
+
+ transformProperties(props);
+
+ if (children !== undefined && children !== null) {
+ addChild(children, childNodes, tag, props);
+ }
+
+
+ return new vnode(tag, props, childNodes, key, namespace);
+}
+
+function addChild(c, childNodes, tag, props) {
+ if (typeof c === 'string') {
+ childNodes.push(new vtext(c));
+ } else if (typeof c === 'number') {
+ childNodes.push(new vtext(String(c)));
+ } else if (isChild(c)) {
+ childNodes.push(c);
+ } else if (xIsArray(c)) {
+ for (var i = 0; i < c.length; i++) {
+ addChild(c[i], childNodes, tag, props);
+ }
+ } else if (c === null || c === undefined) {
+ return;
+ } else {
+ throw UnexpectedVirtualElement({
+ foreignObject: c,
+ parentVnode: {
+ tagName: tag,
+ properties: props
+ }
+ });
+ }
+}
+
+function transformProperties(props) {
+ for (var propName in props) {
+ if (props.hasOwnProperty(propName)) {
+ var value = props[propName];
+
+ if (isVhook(value)) {
+ continue;
+ }
+
+ if (propName.substr(0, 3) === 'ev-') {
+ // add ev-foo support
+ props[propName] = evHook(value);
+ }
+ }
+ }
+}
+
+function isChild(x) {
+ return isVnode(x) || isVtext(x) || isWidget_1(x) || isThunk_1(x);
+}
+
+function isChildren(x) {
+ return typeof x === 'string' || xIsArray(x) || isChild(x);
+}
+
+function UnexpectedVirtualElement(data) {
+ var err = new Error();
+
+ err.type = 'virtual-hyperscript.unexpected.virtual-element';
+ err.message = 'Unexpected virtual child passed to h().\n' +
+ 'Expected a VNode / Vthunk / VWidget / string but:\n' +
+ 'got:\n' +
+ errorString(data.foreignObject) +
+ '.\n' +
+ 'The parent vnode is:\n' +
+ errorString(data.parentVnode);
+ err.foreignObject = data.foreignObject;
+ err.parentVnode = data.parentVnode;
+
+ return err;
+}
+
+function errorString(obj) {
+ try {
+ return JSON.stringify(obj, null, ' ');
+ } catch (e) {
+ return String(obj);
+ }
+}
+
+var h_1 = virtualHyperscript;
+
+var createElement_1 = createElement_1$1;
+
+var virtualDom = {
+ diff: diff_1,
+ patch: patch_1,
+ h: h_1,
+ create: createElement_1,
+ VNode: vnode,
+ VText: vtext
+};
+
+class EventEmitter {
+ constructor() { this._events = {}; }
+ /**
+ * Subscribe to an event by its name.
+ * @param {string} type - The name of the event
+ * to subscribe to.
+ * @param {(event: T) => void} handler - The
+ * handler called when the event occurs.
+ */
+ on(type, handler) {
+ this._events[type] = this._events[type] || [];
+ this._events[type].push(handler);
+ }
+ /**
+ * Unsubscribe from an event by its name.
+ * @param {string} type - The name of the event
+ * to unsubscribe from.
+ * @param {(event: T) => void} handler - The
+ * handler to remove.
+ */
+ off(type, handler) {
+ if (!type) {
+ this._events = {};
+ return;
+ }
+ if (this._listens(type)) {
+ const index = this._events[type].indexOf(handler);
+ if (index >= 0) {
+ this._events[type].splice(index, 1);
+ }
+ if (!this._events[type].length) {
+ delete this._events[type];
+ }
+ }
+ }
+ /**
+ * @ignore
+ */
+ fire(type, event) {
+ if (!this._listens(type)) {
+ return;
+ }
+ for (const handler of this._events[type]) {
+ handler(event);
+ }
+ }
+ _listens(eventType) {
+ return eventType in this._events;
+ }
+}
+
+class SubscriptionHolder {
+ constructor() {
+ this._subscriptions = [];
+ }
+ push(subscription) {
+ this._subscriptions.push(subscription);
+ }
+ unsubscribe() {
+ for (const sub of this._subscriptions) {
+ sub.unsubscribe();
+ }
+ this._subscriptions = [];
+ }
+}
+
+class Component extends EventEmitter {
+ constructor(name, container, navigator) {
+ super();
+ this._activated$ = new BehaviorSubject(false);
+ this._configurationSubject$ = new Subject();
+ this._activated = false;
+ this._container = container;
+ this._name = name;
+ this._navigator = navigator;
+ this._subscriptions = new SubscriptionHolder();
+ this._configuration$ =
+ this._configurationSubject$.pipe(startWith(this.defaultConfiguration), scan((conf, newConf) => {
+ for (let key in newConf) {
+ if (newConf.hasOwnProperty(key)) {
+ conf[key] = newConf[key];
+ }
+ }
+ return conf;
+ }), publishReplay(1), refCount());
+ this._configuration$.subscribe(() => { });
+ }
+ /**
+ * Get activated.
+ *
+ * @returns {boolean} Value indicating if the component is
+ * currently active.
+ */
+ get activated() {
+ return this._activated;
+ }
+ /** @ignore */
+ get activated$() {
+ return this._activated$;
+ }
+ /**
+ * Get default configuration.
+ *
+ * @returns {TConfiguration} Default configuration for component.
+ */
+ get defaultConfiguration() {
+ return this._getDefaultConfiguration();
+ }
+ /** @ignore */
+ get configuration$() {
+ return this._configuration$;
+ }
+ /**
+ * Get name.
+ *
+ * @description The name of the component. Used when interacting with the
+ * component through the Viewer's API.
+ */
+ get name() {
+ return this._name;
+ }
+ /** @ignore */
+ activate(conf) {
+ if (this._activated) {
+ return;
+ }
+ if (conf !== undefined) {
+ this._configurationSubject$.next(conf);
+ }
+ this._activated = true;
+ this._activate();
+ this._activated$.next(true);
+ }
+ /**
+ * Configure the component.
+ *
+ * @param configuration Component configuration.
+ */
+ configure(configuration) {
+ this._configurationSubject$.next(configuration);
+ }
+ /** @ignore */
+ deactivate() {
+ if (!this._activated) {
+ return;
+ }
+ this._activated = false;
+ this._deactivate();
+ this._container.domRenderer.clear(this._name);
+ this._container.glRenderer.clear(this._name);
+ this._activated$.next(false);
+ }
+ /** @inheritdoc */
+ fire(type, event) {
+ super.fire(type, event);
+ }
+ /** @inheritdoc */
+ off(type, handler) {
+ super.off(type, handler);
+ }
+ /** @inheritdoc */
+ on(type, handler) {
+ super.on(type, handler);
+ }
+ /**
+ * Detect the viewer's new width and height and resize the component's
+ * rendered elements accordingly if applicable.
+ *
+ * @ignore
+ */
+ resize() { return; }
+}
+
+var CoverState;
+(function (CoverState) {
+ CoverState[CoverState["Hidden"] = 0] = "Hidden";
+ CoverState[CoverState["Loading"] = 1] = "Loading";
+ CoverState[CoverState["Visible"] = 2] = "Visible";
+})(CoverState || (CoverState = {}));
+
+class CoverComponent extends Component {
+ constructor(name, container, navigator) {
+ super(name, container, navigator);
+ }
+ _activate() {
+ const originalSrc$ = this.configuration$.pipe(first((c) => {
+ return !!c.id;
+ }), filter((c) => {
+ return !c.src;
+ }), switchMap((c) => {
+ return this._getImageSrc$(c.id).pipe(catchError((error) => {
+ console.error(error);
+ return empty();
+ }));
+ }), publishReplay(1), refCount());
+ const subs = this._subscriptions;
+ subs.push(originalSrc$.pipe(map((src) => {
+ return { src: src };
+ }))
+ .subscribe((c) => {
+ this._configurationSubject$.next(c);
+ }));
+ subs.push(combineLatest(this.configuration$, originalSrc$).pipe(filter(([c, src]) => {
+ return !!c.src && c.src !== src;
+ }), first())
+ .subscribe(([, src]) => {
+ window.URL.revokeObjectURL(src);
+ }));
+ subs.push(this._configuration$.pipe(distinctUntilChanged(undefined, (configuration) => {
+ return configuration.state;
+ }), switchMap((configuration) => {
+ return combineLatest(of(configuration.state), this._navigator.stateService.currentImage$);
+ }), switchMap(([state, image]) => {
+ const keySrc$ = combineLatest(of(image.id), image.image$.pipe(filter((imageElement) => {
+ return !!imageElement;
+ }), map((imageElement) => {
+ return imageElement.src;
+ })));
+ return state === CoverState.Visible ? keySrc$.pipe(first()) : keySrc$;
+ }), distinctUntilChanged(([k1, s1], [k2, s2]) => {
+ return k1 === k2 && s1 === s2;
+ }), map(([key, src]) => {
+ return { id: key, src: src };
+ }))
+ .subscribe(this._configurationSubject$));
+ subs.push(combineLatest(this._configuration$, this._container.configurationService.exploreUrl$, this._container.renderService.size$).pipe(map(([configuration, exploreUrl, size]) => {
+ if (!configuration.src) {
+ return { name: this._name, vNode: virtualDom.h("div", []) };
+ }
+ const compactClass = size.width <= 640 || size.height <= 480 ? ".mapillary-cover-compact" : "";
+ if (configuration.state === CoverState.Hidden) {
+ const doneContainer = virtualDom.h("div.mapillary-cover-container.mapillary-cover-done" + compactClass, [this._getCoverBackgroundVNode(configuration)]);
+ return { name: this._name, vNode: doneContainer };
+ }
+ const container = virtualDom.h("div.mapillary-cover-container" + compactClass, [this._getCoverButtonVNode(configuration, exploreUrl)]);
+ return { name: this._name, vNode: container };
+ }))
+ .subscribe(this._container.domRenderer.render$));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ }
+ _getDefaultConfiguration() {
+ return { state: CoverState.Visible };
+ }
+ _getCoverButtonVNode(configuration, exploreUrl) {
+ const cover = configuration.state === CoverState.Loading ? "div.mapillary-cover.mapillary-cover-loading" : "div.mapillary-cover";
+ const coverButton = virtualDom.h("div.mapillary-cover-button", [virtualDom.h("div.mapillary-cover-button-icon", [])]);
+ const coverLogo = virtualDom.h("a.mapillary-cover-logo", { href: exploreUrl, target: "_blank" }, []);
+ const coverIndicator = virtualDom.h("div.mapillary-cover-indicator", { onclick: () => { this.configure({ state: CoverState.Loading }); } }, []);
+ return virtualDom.h(cover, [
+ this._getCoverBackgroundVNode(configuration),
+ coverIndicator,
+ coverButton,
+ coverLogo,
+ ]);
+ }
+ _getCoverBackgroundVNode(conf) {
+ const properties = {
+ style: { backgroundImage: `url(${conf.src})` },
+ };
+ const children = [];
+ if (conf.state === CoverState.Loading) {
+ children.push(virtualDom.h("div.mapillary-cover-spinner", {}, []));
+ }
+ return virtualDom.h("div.mapillary-cover-background", properties, children);
+ }
+ _getImageSrc$(id) {
+ return Observable.create((subscriber) => {
+ this._navigator.api.getImages$([id])
+ .subscribe((items) => {
+ for (const item of items) {
+ if (item.node_id !== id) {
+ continue;
+ }
+ this._navigator.api.data
+ .getImageBuffer(item.node.thumb.url)
+ .then((buffer) => {
+ const image = new Image();
+ image.crossOrigin = "Anonymous";
+ image.onload = () => {
+ subscriber.next(image.src);
+ subscriber.complete();
+ };
+ image.onerror = () => {
+ subscriber.error(new Error(`Failed to load cover ` +
+ `image (${id})`));
+ };
+ const blob = new Blob([buffer]);
+ image.src = window.URL
+ .createObjectURL(blob);
+ }, (error) => {
+ subscriber.error(error);
+ });
+ return;
+ }
+ subscriber.error(new MapillaryError(`Non existent cover key: ${id}`));
+ }, (error) => {
+ subscriber.error(error);
+ });
+ });
+ }
+}
+CoverComponent.componentName = "cover";
+
+class AttributionComponent extends Component {
+ _activate() {
+ this._subscriptions.push(combineLatest(this._container.configurationService.exploreUrl$, this._navigator.stateService.currentImage$, this._container.renderService.size$).pipe(map(([exploreUrl, image, size]) => {
+ const attribution = this._makeAttribution(image.creatorUsername, exploreUrl, image.id, image.capturedAt, size.width);
+ return {
+ name: this._name,
+ vNode: attribution,
+ };
+ }))
+ .subscribe(this._container.domRenderer.render$));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ }
+ _getDefaultConfiguration() {
+ return {};
+ }
+ makeImageUrl(exploreUrl, id) {
+ return `${exploreUrl}/app/?pKey=${id}&focus=photo`;
+ }
+ _makeAttribution(creatorUsername, exploreUrl, imageId, capturedAt, viewportWidth) {
+ const compact = viewportWidth <= 640;
+ const date = this._makeDate(capturedAt, compact);
+ const by = this._makeBy(creatorUsername, exploreUrl, imageId, compact);
+ const compactClass = compact ?
+ ".mapillary-attribution-compact" : "";
+ return virtualDom.h("div.mapillary-attribution-container" + compactClass, {}, [...by, date]);
+ }
+ _makeBy(creatorUsername, exploreUrl, imageId, compact) {
+ const icon = virtualDom.h("div.mapillary-attribution-logo", []);
+ return creatorUsername ?
+ this._makeCreatorBy(icon, creatorUsername, exploreUrl, imageId, compact) :
+ this._makeGeneralBy(icon, exploreUrl, imageId, compact);
+ }
+ _makeCreatorBy(icon, creatorUsername, exploreUrl, imageId, compact) {
+ const mapillary = virtualDom.h("a.mapillary-attribution-icon-container", { href: exploreUrl, rel: "noreferrer", target: "_blank" }, [icon]);
+ const content = compact ?
+ `${creatorUsername}` : `image by ${creatorUsername}`;
+ const imageBy = virtualDom.h("div.mapillary-attribution-username", { textContent: content }, []);
+ const image = virtualDom.h("a.mapillary-attribution-image-container", {
+ href: this.makeImageUrl(exploreUrl, imageId),
+ rel: "noreferrer",
+ target: "_blank",
+ }, [imageBy]);
+ return [mapillary, image];
+ }
+ _makeGeneralBy(icon, exploreUrl, imageId, compact) {
+ const imagesBy = virtualDom.h("div.mapillary-attribution-username", { textContent: 'images by' }, []);
+ const mapillary = virtualDom.h("div.mapillary-attribution-icon-container", {}, [icon]);
+ const contributors = virtualDom.h("div.mapillary-attribution-username", { textContent: 'contributors' }, []);
+ const children = [mapillary, contributors];
+ if (!compact) {
+ children.unshift(imagesBy);
+ }
+ const image = virtualDom.h("a.mapillary-attribution-image-container", {
+ href: this.makeImageUrl(exploreUrl, imageId),
+ rel: "noreferrer",
+ target: "_blank",
+ }, children);
+ return [image];
+ }
+ _makeDate(capturedAt, compact) {
+ const date = new Date(capturedAt)
+ .toDateString()
+ .split(" ");
+ const formatted = (date.length > 3 ?
+ compact ?
+ [date[3]] :
+ [date[1], date[2] + ",", date[3]] :
+ date).join(" ");
+ return virtualDom.h("div.mapillary-attribution-date", { textContent: formatted }, []);
+ }
+}
+AttributionComponent.componentName = "attribution";
+
+/**
+ * @class ViewportCoords
+ *
+ * @classdesc Provides methods for calculating 2D coordinate conversions
+ * as well as 3D projection and unprojection.
+ *
+ * Basic coordinates are 2D coordinates on the [0, 1] interval and
+ * have the origin point, (0, 0), at the top left corner and the
+ * maximum value, (1, 1), at the bottom right corner of the original
+ * image.
+ *
+ * Viewport coordinates are 2D coordinates on the [-1, 1] interval and
+ * have the origin point in the center. The bottom left corner point is
+ * (-1, -1) and the top right corner point is (1, 1).
+ *
+ * Canvas coordiantes are 2D pixel coordinates on the [0, canvasWidth] and
+ * [0, canvasHeight] intervals. The origin point (0, 0) is in the top left
+ * corner and the maximum value is (canvasWidth, canvasHeight) is in the
+ * bottom right corner.
+ *
+ * 3D coordinates are in the topocentric world reference frame.
+ */
+class ViewportCoords {
+ constructor() {
+ this._unprojectDepth = 200;
+ }
+ /**
+ * Convert basic coordinates to canvas coordinates.
+ *
+ * @description Transform origin and camera position needs to be the
+ * equal for reliable return value.
+ *
+ * @param {number} basicX - Basic X coordinate.
+ * @param {number} basicY - Basic Y coordinate.
+ * @param {HTMLElement} container - The viewer container.
+ * @param {Transform} transform - Transform of the image to unproject from.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 2D canvas coordinates.
+ */
+ basicToCanvas(basicX, basicY, container, transform, camera) {
+ const point3d = transform.unprojectBasic([basicX, basicY], this._unprojectDepth);
+ const canvas = this.projectToCanvas(point3d, container, camera);
+ return canvas;
+ }
+ /**
+ * Convert basic coordinates to canvas coordinates safely. If 3D point is
+ * behind camera null will be returned.
+ *
+ * @description Transform origin and camera position needs to be the
+ * equal for reliable return value.
+ *
+ * @param {number} basicX - Basic X coordinate.
+ * @param {number} basicY - Basic Y coordinate.
+ * @param {HTMLElement} container - The viewer container.
+ * @param {Transform} transform - Transform of the image to unproject from.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 2D canvas coordinates if the basic point represents a 3D point
+ * in front of the camera, otherwise null.
+ */
+ basicToCanvasSafe(basicX, basicY, container, transform, camera) {
+ const viewport = this.basicToViewportSafe(basicX, basicY, transform, camera);
+ if (viewport === null) {
+ return null;
+ }
+ const canvas = this.viewportToCanvas(viewport[0], viewport[1], container);
+ return canvas;
+ }
+ /**
+ * Convert basic coordinates to viewport coordinates.
+ *
+ * @description Transform origin and camera position needs to be the
+ * equal for reliable return value.
+ *
+ * @param {number} basicX - Basic X coordinate.
+ * @param {number} basicY - Basic Y coordinate.
+ * @param {Transform} transform - Transform of the image to unproject from.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 2D viewport coordinates.
+ */
+ basicToViewport(basicX, basicY, transform, camera) {
+ const point3d = transform.unprojectBasic([basicX, basicY], this._unprojectDepth);
+ const viewport = this.projectToViewport(point3d, camera);
+ return viewport;
+ }
+ /**
+ * Convert basic coordinates to viewport coordinates safely. If 3D point is
+ * behind camera null will be returned.
+ *
+ * @description Transform origin and camera position needs to be the
+ * equal for reliable return value.
+ *
+ * @param {number} basicX - Basic X coordinate.
+ * @param {number} basicY - Basic Y coordinate.
+ * @param {Transform} transform - Transform of the image to unproject from.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 2D viewport coordinates.
+ */
+ basicToViewportSafe(basicX, basicY, transform, camera) {
+ const point3d = transform.unprojectBasic([basicX, basicY], this._unprojectDepth);
+ const pointCamera = this.worldToCamera(point3d, camera);
+ if (pointCamera[2] > 0) {
+ return null;
+ }
+ const viewport = this.projectToViewport(point3d, camera);
+ return viewport;
+ }
+ /**
+ * Convert camera 3D coordinates to viewport coordinates.
+ *
+ * @param {number} pointCamera - 3D point in camera coordinate system.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 2D viewport coordinates.
+ */
+ cameraToViewport(pointCamera, camera) {
+ const viewport = new Vector3().fromArray(pointCamera)
+ .applyMatrix4(camera.projectionMatrix);
+ return [viewport.x, viewport.y];
+ }
+ /**
+ * Get canvas pixel position from event.
+ *
+ * @param {Event} event - Event containing clientX and clientY properties.
+ * @param {HTMLElement} element - HTML element.
+ * @returns {Array<number>} 2D canvas coordinates.
+ */
+ canvasPosition(event, element) {
+ const clientRect = element.getBoundingClientRect();
+ const canvasX = event.clientX - clientRect.left - element.clientLeft;
+ const canvasY = event.clientY - clientRect.top - element.clientTop;
+ return [canvasX, canvasY];
+ }
+ /**
+ * Convert canvas coordinates to basic coordinates.
+ *
+ * @description Transform origin and camera position needs to be the
+ * equal for reliable return value.
+ *
+ * @param {number} canvasX - Canvas X coordinate.
+ * @param {number} canvasY - Canvas Y coordinate.
+ * @param {HTMLElement} container - The viewer container.
+ * @param {Transform} transform - Transform of the image to unproject from.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 2D basic coordinates.
+ */
+ canvasToBasic(canvasX, canvasY, container, transform, camera) {
+ const point3d = this.unprojectFromCanvas(canvasX, canvasY, container, camera)
+ .toArray();
+ const basic = transform.projectBasic(point3d);
+ return basic;
+ }
+ /**
+ * Convert canvas coordinates to viewport coordinates.
+ *
+ * @param {number} canvasX - Canvas X coordinate.
+ * @param {number} canvasY - Canvas Y coordinate.
+ * @param {HTMLElement} container - The viewer container.
+ * @returns {Array<number>} 2D viewport coordinates.
+ */
+ canvasToViewport(canvasX, canvasY, container) {
+ const [canvasWidth, canvasHeight] = this.containerToCanvas(container);
+ const viewportX = 2 * canvasX / canvasWidth - 1;
+ const viewportY = 1 - 2 * canvasY / canvasHeight;
+ return [viewportX, viewportY];
+ }
+ /**
+ * Determines the width and height of the container in canvas coordinates.
+ *
+ * @param {HTMLElement} container - The viewer container.
+ * @returns {Array<number>} 2D canvas coordinates.
+ */
+ containerToCanvas(container) {
+ return [container.offsetWidth, container.offsetHeight];
+ }
+ /**
+ * Determine basic distances from image to canvas corners.
+ *
+ * @description Transform origin and camera position needs to be the
+ * equal for reliable return value.
+ *
+ * Determines the smallest basic distance for every side of the canvas.
+ *
+ * @param {Transform} transform - Transform of the image to unproject from.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} Array of basic distances as [top, right, bottom, left].
+ */
+ getBasicDistances(transform, camera) {
+ const topLeftBasic = this.viewportToBasic(-1, 1, transform, camera);
+ const topRightBasic = this.viewportToBasic(1, 1, transform, camera);
+ const bottomRightBasic = this.viewportToBasic(1, -1, transform, camera);
+ const bottomLeftBasic = this.viewportToBasic(-1, -1, transform, camera);
+ let topBasicDistance = 0;
+ let rightBasicDistance = 0;
+ let bottomBasicDistance = 0;
+ let leftBasicDistance = 0;
+ if (topLeftBasic[1] < 0 && topRightBasic[1] < 0) {
+ topBasicDistance = topLeftBasic[1] > topRightBasic[1] ?
+ -topLeftBasic[1] :
+ -topRightBasic[1];
+ }
+ if (topRightBasic[0] > 1 && bottomRightBasic[0] > 1) {
+ rightBasicDistance = topRightBasic[0] < bottomRightBasic[0] ?
+ topRightBasic[0] - 1 :
+ bottomRightBasic[0] - 1;
+ }
+ if (bottomRightBasic[1] > 1 && bottomLeftBasic[1] > 1) {
+ bottomBasicDistance = bottomRightBasic[1] < bottomLeftBasic[1] ?
+ bottomRightBasic[1] - 1 :
+ bottomLeftBasic[1] - 1;
+ }
+ if (bottomLeftBasic[0] < 0 && topLeftBasic[0] < 0) {
+ leftBasicDistance = bottomLeftBasic[0] > topLeftBasic[0] ?
+ -bottomLeftBasic[0] :
+ -topLeftBasic[0];
+ }
+ return [topBasicDistance, rightBasicDistance, bottomBasicDistance, leftBasicDistance];
+ }
+ /**
+ * Determine pixel distances from image to canvas corners.
+ *
+ * @description Transform origin and camera position needs to be the
+ * equal for reliable return value.
+ *
+ * Determines the smallest pixel distance for every side of the canvas.
+ *
+ * @param {HTMLElement} container - The viewer container.
+ * @param {Transform} transform - Transform of the image to unproject from.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} Array of pixel distances as [top, right, bottom, left].
+ */
+ getPixelDistances(container, transform, camera) {
+ const topLeftBasic = this.viewportToBasic(-1, 1, transform, camera);
+ const topRightBasic = this.viewportToBasic(1, 1, transform, camera);
+ const bottomRightBasic = this.viewportToBasic(1, -1, transform, camera);
+ const bottomLeftBasic = this.viewportToBasic(-1, -1, transform, camera);
+ let topPixelDistance = 0;
+ let rightPixelDistance = 0;
+ let bottomPixelDistance = 0;
+ let leftPixelDistance = 0;
+ const [canvasWidth, canvasHeight] = this.containerToCanvas(container);
+ if (topLeftBasic[1] < 0 && topRightBasic[1] < 0) {
+ const basicX = topLeftBasic[1] > topRightBasic[1] ?
+ topLeftBasic[0] :
+ topRightBasic[0];
+ const canvas = this.basicToCanvas(basicX, 0, container, transform, camera);
+ topPixelDistance = canvas[1] > 0 ? canvas[1] : 0;
+ }
+ if (topRightBasic[0] > 1 && bottomRightBasic[0] > 1) {
+ const basicY = topRightBasic[0] < bottomRightBasic[0] ?
+ topRightBasic[1] :
+ bottomRightBasic[1];
+ const canvas = this.basicToCanvas(1, basicY, container, transform, camera);
+ rightPixelDistance = canvas[0] < canvasWidth ? canvasWidth - canvas[0] : 0;
+ }
+ if (bottomRightBasic[1] > 1 && bottomLeftBasic[1] > 1) {
+ const basicX = bottomRightBasic[1] < bottomLeftBasic[1] ?
+ bottomRightBasic[0] :
+ bottomLeftBasic[0];
+ const canvas = this.basicToCanvas(basicX, 1, container, transform, camera);
+ bottomPixelDistance = canvas[1] < canvasHeight ? canvasHeight - canvas[1] : 0;
+ }
+ if (bottomLeftBasic[0] < 0 && topLeftBasic[0] < 0) {
+ const basicY = bottomLeftBasic[0] > topLeftBasic[0] ?
+ bottomLeftBasic[1] :
+ topLeftBasic[1];
+ const canvas = this.basicToCanvas(0, basicY, container, transform, camera);
+ leftPixelDistance = canvas[0] > 0 ? canvas[0] : 0;
+ }
+ return [topPixelDistance, rightPixelDistance, bottomPixelDistance, leftPixelDistance];
+ }
+ /**
+ * Determine if an event occured inside an element.
+ *
+ * @param {Event} event - Event containing clientX and clientY properties.
+ * @param {HTMLElement} element - HTML element.
+ * @returns {boolean} Value indicating if the event occured inside the element or not.
+ */
+ insideElement(event, element) {
+ const clientRect = element.getBoundingClientRect();
+ const minX = clientRect.left + element.clientLeft;
+ const maxX = minX + element.clientWidth;
+ const minY = clientRect.top + element.clientTop;
+ const maxY = minY + element.clientHeight;
+ return event.clientX > minX &&
+ event.clientX < maxX &&
+ event.clientY > minY &&
+ event.clientY < maxY;
+ }
+ /**
+ * Project 3D world coordinates to canvas coordinates.
+ *
+ * @param {Array<number>} point3D - 3D world coordinates.
+ * @param {HTMLElement} container - The viewer container.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 2D canvas coordinates.
+ */
+ projectToCanvas(point3d, container, camera) {
+ const viewport = this.projectToViewport(point3d, camera);
+ const canvas = this.viewportToCanvas(viewport[0], viewport[1], container);
+ return canvas;
+ }
+ /**
+ * Project 3D world coordinates to canvas coordinates safely. If 3D
+ * point is behind camera null will be returned.
+ *
+ * @param {Array<number>} point3D - 3D world coordinates.
+ * @param {HTMLElement} container - The viewer container.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 2D canvas coordinates.
+ */
+ projectToCanvasSafe(point3d, container, camera) {
+ const pointCamera = this.worldToCamera(point3d, camera);
+ if (pointCamera[2] > 0) {
+ return null;
+ }
+ const viewport = this.projectToViewport(point3d, camera);
+ const canvas = this.viewportToCanvas(viewport[0], viewport[1], container);
+ return canvas;
+ }
+ /**
+ * Project 3D world coordinates to viewport coordinates.
+ *
+ * @param {Array<number>} point3D - 3D world coordinates.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 2D viewport coordinates.
+ */
+ projectToViewport(point3d, camera) {
+ const viewport = new Vector3(point3d[0], point3d[1], point3d[2])
+ .project(camera);
+ return [viewport.x, viewport.y];
+ }
+ /**
+ * Uproject canvas coordinates to 3D world coordinates.
+ *
+ * @param {number} canvasX - Canvas X coordinate.
+ * @param {number} canvasY - Canvas Y coordinate.
+ * @param {HTMLElement} container - The viewer container.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 3D world coordinates.
+ */
+ unprojectFromCanvas(canvasX, canvasY, container, camera) {
+ const viewport = this.canvasToViewport(canvasX, canvasY, container);
+ const point3d = this.unprojectFromViewport(viewport[0], viewport[1], camera);
+ return point3d;
+ }
+ /**
+ * Unproject viewport coordinates to 3D world coordinates.
+ *
+ * @param {number} viewportX - Viewport X coordinate.
+ * @param {number} viewportY - Viewport Y coordinate.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 3D world coordinates.
+ */
+ unprojectFromViewport(viewportX, viewportY, camera) {
+ const point3d = new Vector3(viewportX, viewportY, 1)
+ .unproject(camera);
+ return point3d;
+ }
+ /**
+ * Convert viewport coordinates to basic coordinates.
+ *
+ * @description Transform origin and camera position needs to be the
+ * equal for reliable return value.
+ *
+ * @param {number} viewportX - Viewport X coordinate.
+ * @param {number} viewportY - Viewport Y coordinate.
+ * @param {Transform} transform - Transform of the image to unproject from.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 2D basic coordinates.
+ */
+ viewportToBasic(viewportX, viewportY, transform, camera) {
+ const point3d = new Vector3(viewportX, viewportY, 1)
+ .unproject(camera)
+ .toArray();
+ const basic = transform.projectBasic(point3d);
+ return basic;
+ }
+ /**
+ * Convert viewport coordinates to canvas coordinates.
+ *
+ * @param {number} viewportX - Viewport X coordinate.
+ * @param {number} viewportY - Viewport Y coordinate.
+ * @param {HTMLElement} container - The viewer container.
+ * @returns {Array<number>} 2D canvas coordinates.
+ */
+ viewportToCanvas(viewportX, viewportY, container) {
+ const [canvasWidth, canvasHeight] = this.containerToCanvas(container);
+ const canvasX = canvasWidth * (viewportX + 1) / 2;
+ const canvasY = -canvasHeight * (viewportY - 1) / 2;
+ return [canvasX, canvasY];
+ }
+ /**
+ * Convert 3D world coordinates to 3D camera coordinates.
+ *
+ * @param {number} point3D - 3D point in world coordinate system.
+ * @param {THREE.Camera} camera - Camera used in rendering.
+ * @returns {Array<number>} 3D camera coordinates.
+ */
+ worldToCamera(point3d, camera) {
+ const pointCamera = new Vector3(point3d[0], point3d[1], point3d[2])
+ .applyMatrix4(camera.matrixWorldInverse);
+ return pointCamera.toArray();
+ }
+}
+
+/**
+ * Enumeration for component size.
+ * @enum {number}
+ * @readonly
+ * @description May be used by a component to allow for resizing
+ * of the UI elements rendered by the component.
+ */
+var ComponentSize;
+(function (ComponentSize) {
+ /**
+ * Automatic size. The size of the elements will automatically
+ * change at a predefined threshold.
+ */
+ ComponentSize[ComponentSize["Automatic"] = 0] = "Automatic";
+ /**
+ * Large size. The size of the elements will be fixed until another
+ * component size is configured.
+ */
+ ComponentSize[ComponentSize["Large"] = 1] = "Large";
+ /**
+ * Small size. The size of the elements will be fixed until another
+ * component size is configured.
+ */
+ ComponentSize[ComponentSize["Small"] = 2] = "Small";
+})(ComponentSize || (ComponentSize = {}));
+
+/**
+ * @class BearingComponent
+ *
+ * @classdesc Component for indicating bearing and field of view.
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ ... });
+ * var bearingComponent = viewer.getComponent("bearing");
+ * bearingComponent.configure({ size: ComponentSize.Small });
+ * ```
+ */
+class BearingComponent extends Component {
+ /** @ignore */
+ constructor(name, container, navigator) {
+ super(name, container, navigator);
+ this._spatial = new Spatial();
+ this._viewportCoords = new ViewportCoords();
+ this._svgNamespace = "http://www.w3.org/2000/svg";
+ this._distinctThreshold = Math.PI / 360;
+ this._animationSpeed = 0.075;
+ this._unitBezier = new unitbezier(0.74, 0.67, 0.38, 0.96);
+ }
+ _activate() {
+ const subs = this._subscriptions;
+ const cameraBearingFov$ = this._container.renderService.renderCamera$.pipe(map((rc) => {
+ let vFov = this._spatial.degToRad(rc.perspective.fov);
+ let hFov = rc.perspective.aspect === Number.POSITIVE_INFINITY ?
+ Math.PI :
+ Math.atan(rc.perspective.aspect * Math.tan(0.5 * vFov)) * 2;
+ return [this._spatial.azimuthalToBearing(rc.rotation.phi), hFov];
+ }), distinctUntilChanged((a1, a2) => {
+ return Math.abs(a2[0] - a1[0]) < this._distinctThreshold &&
+ Math.abs(a2[1] - a1[1]) < this._distinctThreshold;
+ }));
+ const imageFov$ = combineLatest(this._navigator.stateService.currentState$.pipe(distinctUntilChanged(undefined, (frame) => {
+ return frame.state.currentImage.id;
+ })), this._navigator.panService.panImages$).pipe(map(([frame, panImages]) => {
+ const image = frame.state.currentImage;
+ const transform = frame.state.currentTransform;
+ if (isSpherical(image.cameraType)) {
+ return [Math.PI, Math.PI];
+ }
+ const currentProjectedPoints = this._computeProjectedPoints(transform);
+ const hFov = this._spatial
+ .degToRad(this._computeHorizontalFov(currentProjectedPoints));
+ let hFovLeft = hFov / 2;
+ let hFovRight = hFov / 2;
+ for (const [n, , f] of panImages) {
+ const diff = this._spatial.wrap(n.compassAngle - image.compassAngle, -180, 180);
+ if (diff < 0) {
+ hFovLeft = this._spatial.degToRad(Math.abs(diff)) + f / 2;
+ }
+ else {
+ hFovRight = this._spatial.degToRad(Math.abs(diff)) + f / 2;
+ }
+ }
+ return [hFovLeft, hFovRight];
+ }), distinctUntilChanged(([hFovLeft1, hFovRight1], [hFovLeft2, hFovRight2]) => {
+ return Math.abs(hFovLeft2 - hFovLeft1) < this._distinctThreshold &&
+ Math.abs(hFovRight2 - hFovRight1) < this._distinctThreshold;
+ }));
+ const offset$ = combineLatest(this._navigator.stateService.currentState$.pipe(distinctUntilChanged(undefined, (frame) => {
+ return frame.state.currentImage.id;
+ })), this._container.renderService.bearing$).pipe(map(([frame, bearing]) => {
+ const offset = this._spatial.degToRad(frame.state.currentImage.compassAngle - bearing);
+ return offset;
+ }));
+ const imageFovOperation$ = new Subject();
+ const smoothImageFov$ = imageFovOperation$.pipe(scan((state, operation) => {
+ return operation(state);
+ }, { alpha: 0, curr: [0, 0, 0], prev: [0, 0, 0] }), map((state) => {
+ const alpha = this._unitBezier.solve(state.alpha);
+ const curr = state.curr;
+ const prev = state.prev;
+ return [
+ this._interpolate(prev[0], curr[0], alpha),
+ this._interpolate(prev[1], curr[1], alpha),
+ ];
+ }));
+ subs.push(imageFov$.pipe(map((nbf) => {
+ return (state) => {
+ const a = this._unitBezier.solve(state.alpha);
+ const c = state.curr;
+ const p = state.prev;
+ const prev = [
+ this._interpolate(p[0], c[0], a),
+ this._interpolate(p[1], c[1], a),
+ ];
+ const curr = nbf.slice();
+ return {
+ alpha: 0,
+ curr: curr,
+ prev: prev,
+ };
+ };
+ }))
+ .subscribe(imageFovOperation$));
+ subs.push(imageFov$.pipe(switchMap(() => {
+ return this._container.renderService.renderCameraFrame$.pipe(skip(1), scan((alpha) => {
+ return alpha + this._animationSpeed;
+ }, 0), takeWhile((alpha) => {
+ return alpha <= 1 + this._animationSpeed;
+ }), map((alpha) => {
+ return Math.min(alpha, 1);
+ }));
+ }), map((alpha) => {
+ return (nbfState) => {
+ return {
+ alpha: alpha,
+ curr: nbfState.curr.slice(),
+ prev: nbfState.prev.slice(),
+ };
+ };
+ }))
+ .subscribe(imageFovOperation$));
+ const imageBearingFov$ = combineLatest(offset$, smoothImageFov$).pipe(map(([offset, fov]) => {
+ return [offset, fov[0], fov[1]];
+ }));
+ subs.push(combineLatest(cameraBearingFov$, imageBearingFov$, this._configuration$, this._container.renderService.size$).pipe(map(([[cb, cf], [no, nfl, nfr], configuration, size]) => {
+ const background = this._createBackground(cb);
+ const fovIndicator = this._createFovIndicator(nfl, nfr, no);
+ const north = this._createNorth(cb);
+ const cameraSector = this._createCircleSectorCompass(this._createCircleSector(Math.max(Math.PI / 20, cf), "#FFF"));
+ const compact = configuration.size === ComponentSize.Small ||
+ configuration.size === ComponentSize.Automatic && size.width < 640 ?
+ ".mapillary-bearing-compact" : "";
+ return {
+ name: this._name,
+ vNode: virtualDom.h("div.mapillary-bearing-indicator-container" + compact, { oncontextmenu: (event) => { event.preventDefault(); } }, [
+ background,
+ fovIndicator,
+ north,
+ cameraSector,
+ ]),
+ };
+ }))
+ .subscribe(this._container.domRenderer.render$));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ }
+ _getDefaultConfiguration() {
+ return { size: ComponentSize.Automatic };
+ }
+ _createFovIndicator(fovLeft, fovRigth, offset) {
+ const arc = this._createFovArc(fovLeft, fovRigth);
+ const group = virtualDom.h("g", {
+ attributes: { transform: "translate(18,18)" },
+ namespace: this._svgNamespace,
+ }, [arc]);
+ const svg = virtualDom.h("svg", {
+ attributes: { viewBox: "0 0 36 36" },
+ namespace: this._svgNamespace,
+ style: {
+ height: "36px",
+ left: "2px",
+ position: "absolute",
+ top: "2px",
+ transform: `rotateZ(${this._spatial.radToDeg(offset)}deg)`,
+ width: "36px",
+ },
+ }, [group]);
+ return svg;
+ }
+ _createFovArc(fovLeft, fovRigth) {
+ const radius = 16.75;
+ const strokeWidth = 2.5;
+ const fov = fovLeft + fovRigth;
+ if (fov > 2 * Math.PI - Math.PI / 90) {
+ return virtualDom.h("circle", {
+ attributes: {
+ cx: "0",
+ cy: "0",
+ "fill-opacity": "0",
+ r: `${radius}`,
+ stroke: "#FFF",
+ "stroke-width": `${strokeWidth}`,
+ },
+ namespace: this._svgNamespace,
+ }, []);
+ }
+ let arcStart = -Math.PI / 2 - fovLeft;
+ let arcEnd = arcStart + fov;
+ let startX = radius * Math.cos(arcStart);
+ let startY = radius * Math.sin(arcStart);
+ let endX = radius * Math.cos(arcEnd);
+ let endY = radius * Math.sin(arcEnd);
+ let largeArc = fov >= Math.PI ? 1 : 0;
+ let description = `M ${startX} ${startY} A ${radius} ${radius} 0 ${largeArc} 1 ${endX} ${endY}`;
+ return virtualDom.h("path", {
+ attributes: {
+ d: description,
+ "fill-opacity": "0",
+ stroke: "#FFF",
+ "stroke-width": `${strokeWidth}`,
+ },
+ namespace: this._svgNamespace,
+ }, []);
+ }
+ _createCircleSectorCompass(cameraSector) {
+ let group = virtualDom.h("g", {
+ attributes: { transform: "translate(1,1)" },
+ namespace: this._svgNamespace,
+ }, [cameraSector]);
+ let svg = virtualDom.h("svg", {
+ attributes: { viewBox: "0 0 2 2" },
+ namespace: this._svgNamespace,
+ style: {
+ height: "26px",
+ left: "7px",
+ position: "absolute",
+ top: "7px",
+ width: "26px",
+ },
+ }, [group]);
+ return svg;
+ }
+ _createCircleSector(fov, fill) {
+ if (fov > 2 * Math.PI - Math.PI / 90) {
+ return virtualDom.h("circle", {
+ attributes: { cx: "0", cy: "0", fill: fill, r: "1" },
+ namespace: this._svgNamespace,
+ }, []);
+ }
+ let arcStart = -Math.PI / 2 - fov / 2;
+ let arcEnd = arcStart + fov;
+ let startX = Math.cos(arcStart);
+ let startY = Math.sin(arcStart);
+ let endX = Math.cos(arcEnd);
+ let endY = Math.sin(arcEnd);
+ let largeArc = fov >= Math.PI ? 1 : 0;
+ let description = `M 0 0 ${startX} ${startY} A 1 1 0 ${largeArc} 1 ${endX} ${endY}`;
+ return virtualDom.h("path", {
+ attributes: { d: description, fill: fill },
+ namespace: this._svgNamespace,
+ }, []);
+ }
+ _createNorth(bearing) {
+ const north = virtualDom.h("div.mapillary-bearing-north", []);
+ const container = virtualDom.h("div.mapillary-bearing-north-container", { style: { transform: `rotateZ(${this._spatial.radToDeg(-bearing)}deg)` } }, [north]);
+ return container;
+ }
+ _createBackground(bearing) {
+ return virtualDom.h("div.mapillary-bearing-indicator-background", { style: { transform: `rotateZ(${this._spatial.radToDeg(-bearing)}deg)` } }, [
+ virtualDom.h("div.mapillary-bearing-indicator-background-circle", []),
+ virtualDom.h("div.mapillary-bearing-indicator-background-arrow-container", [
+ virtualDom.h("div.mapillary-bearing-indicator-background-arrow", []),
+ ]),
+ ]);
+ }
+ _computeProjectedPoints(transform) {
+ const vertices = [[1, 0]];
+ const directions = [[0, 0.5]];
+ const pointsPerLine = 12;
+ return computeProjectedPoints(transform, vertices, directions, pointsPerLine, this._viewportCoords);
+ }
+ _computeHorizontalFov(projectedPoints) {
+ const fovs = projectedPoints
+ .map((projectedPoint) => {
+ return this._coordToFov(projectedPoint[0]);
+ });
+ const fov = Math.min(...fovs);
+ return fov;
+ }
+ _coordToFov(x) {
+ return this._spatial.radToDeg(2 * Math.atan(x));
+ }
+ _interpolate(x1, x2, alpha) {
+ return (1 - alpha) * x1 + alpha * x2;
+ }
+}
+BearingComponent.componentName = "bearing";
+
+class CacheComponent extends Component {
+ /** @ignore */
+ constructor(name, container, navigator) {
+ super(name, container, navigator);
+ }
+ _activate() {
+ const subs = this._subscriptions;
+ subs.push(combineLatest(this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+ return image.sequenceEdges$;
+ }), filter((status) => {
+ return status.cached;
+ })), this._configuration$).pipe(switchMap((nc) => {
+ let status = nc[0];
+ let configuration = nc[1];
+ let sequenceDepth = Math.max(0, Math.min(4, configuration.depth.sequence));
+ let next$ = this._cache$(status.edges, NavigationDirection.Next, sequenceDepth);
+ let prev$ = this._cache$(status.edges, NavigationDirection.Prev, sequenceDepth);
+ return merge(next$, prev$).pipe(catchError((error) => {
+ console.error("Failed to cache sequence edges.", error);
+ return empty();
+ }));
+ }))
+ .subscribe(() => { }));
+ subs.push(combineLatest(this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+ return combineLatest(of(image), image.spatialEdges$.pipe(filter((status) => {
+ return status.cached;
+ })));
+ })), this._configuration$).pipe(switchMap(([[image, edgeStatus], configuration]) => {
+ let edges = edgeStatus.edges;
+ let depth = configuration.depth;
+ let sphericalDepth = Math.max(0, Math.min(2, depth.spherical));
+ let stepDepth = isSpherical(image.cameraType) ?
+ 0 : Math.max(0, Math.min(3, depth.step));
+ let turnDepth = isSpherical(image.cameraType) ?
+ 0 : Math.max(0, Math.min(1, depth.turn));
+ let spherical$ = this._cache$(edges, NavigationDirection.Spherical, sphericalDepth);
+ let forward$ = this._cache$(edges, NavigationDirection.StepForward, stepDepth);
+ let backward$ = this._cache$(edges, NavigationDirection.StepBackward, stepDepth);
+ let left$ = this._cache$(edges, NavigationDirection.StepLeft, stepDepth);
+ let right$ = this._cache$(edges, NavigationDirection.StepRight, stepDepth);
+ let turnLeft$ = this._cache$(edges, NavigationDirection.TurnLeft, turnDepth);
+ let turnRight$ = this._cache$(edges, NavigationDirection.TurnRight, turnDepth);
+ let turnU$ = this._cache$(edges, NavigationDirection.TurnU, turnDepth);
+ return merge(forward$, backward$, left$, right$, spherical$, turnLeft$, turnRight$, turnU$).pipe(catchError((error) => {
+ console.error("Failed to cache spatial edges.", error);
+ return empty();
+ }));
+ }))
+ .subscribe(() => { }));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ }
+ _getDefaultConfiguration() {
+ return { depth: { spherical: 1, sequence: 2, step: 1, turn: 0 } };
+ }
+ _cache$(edges, direction, depth) {
+ return zip(of(edges), of(depth)).pipe(expand((ed) => {
+ let es = ed[0];
+ let d = ed[1];
+ let edgesDepths$ = [];
+ if (d > 0) {
+ for (let edge of es) {
+ if (edge.data.direction === direction) {
+ edgesDepths$.push(zip(this._navigator.graphService.cacheImage$(edge.target).pipe(mergeMap((n) => {
+ return this._imageToEdges$(n, direction);
+ })), of(d - 1)));
+ }
+ }
+ }
+ return from(edgesDepths$).pipe(mergeAll());
+ }), skip(1));
+ }
+ _imageToEdges$(image, direction) {
+ return ([NavigationDirection.Next, NavigationDirection.Prev].indexOf(direction) > -1 ?
+ image.sequenceEdges$ :
+ image.spatialEdges$).pipe(first((status) => {
+ return status.cached;
+ }), map((status) => {
+ return status.edges;
+ }));
+ }
+}
+CacheComponent.componentName = "cache";
+
+/**
+ * @class CancelMapillaryError
+ *
+ * @classdesc Error thrown when a move to request has been
+ * cancelled before completing because of a subsequent request.
+ */
+class CancelMapillaryError extends MapillaryError {
+ constructor(message) {
+ super(message != null ? message : "The request was cancelled.");
+ Object.setPrototypeOf(this, CancelMapillaryError.prototype);
+ this.name = "CancelMapillaryError";
+ }
+}
+
+/**
+ * @class DirectionDOMCalculator
+ * @classdesc Helper class for calculating DOM CSS properties.
+ */
+class DirectionDOMCalculator {
+ constructor(configuration, size) {
+ this._spatial = new Spatial();
+ this._minThresholdWidth = 320;
+ this._maxThresholdWidth = 1480;
+ this._minThresholdHeight = 240;
+ this._maxThresholdHeight = 820;
+ this._configure(configuration);
+ this._resize(size);
+ this._reset();
+ }
+ get minWidth() {
+ return this._minWidth;
+ }
+ get maxWidth() {
+ return this._maxWidth;
+ }
+ get containerWidth() {
+ return this._containerWidth;
+ }
+ get containerWidthCss() {
+ return this._containerWidthCss;
+ }
+ get containerMarginCss() {
+ return this._containerMarginCss;
+ }
+ get containerLeftCss() {
+ return this._containerLeftCss;
+ }
+ get containerHeight() {
+ return this._containerHeight;
+ }
+ get containerHeightCss() {
+ return this._containerHeightCss;
+ }
+ get containerBottomCss() {
+ return this._containerBottomCss;
+ }
+ get stepCircleSize() {
+ return this._stepCircleSize;
+ }
+ get stepCircleSizeCss() {
+ return this._stepCircleSizeCss;
+ }
+ get stepCircleMarginCss() {
+ return this._stepCircleMarginCss;
+ }
+ get turnCircleSize() {
+ return this._turnCircleSize;
+ }
+ get turnCircleSizeCss() {
+ return this._turnCircleSizeCss;
+ }
+ get outerRadius() {
+ return this._outerRadius;
+ }
+ get innerRadius() {
+ return this._innerRadius;
+ }
+ get shadowOffset() {
+ return this._shadowOffset;
+ }
+ /**
+ * Configures the min and max width values.
+ *
+ * @param {DirectionConfiguration} configuration Configuration
+ * with min and max width values.
+ */
+ configure(configuration) {
+ this._configure(configuration);
+ this._reset();
+ }
+ /**
+ * Resizes all properties according to the width and height
+ * of the size object.
+ *
+ * @param {ViewportSize} size The size of the container element.
+ */
+ resize(size) {
+ this._resize(size);
+ this._reset();
+ }
+ /**
+ * Calculates the coordinates on the unit circle for an angle.
+ *
+ * @param {number} angle Angle in radians.
+ * @returns {Array<number>} The x and y coordinates on the unit circle.
+ */
+ angleToCoordinates(angle) {
+ return [Math.cos(angle), Math.sin(angle)];
+ }
+ /**
+ * Calculates the coordinates on the unit circle for the
+ * relative angle between the first and second angle.
+ *
+ * @param {number} first Angle in radians.
+ * @param {number} second Angle in radians.
+ * @returns {Array<number>} The x and y coordinates on the unit circle
+ * for the relative angle between the first and second angle.
+ */
+ relativeAngleToCoordiantes(first, second) {
+ let relativeAngle = this._spatial.wrapAngle(first - second);
+ return this.angleToCoordinates(relativeAngle);
+ }
+ _configure(configuration) {
+ this._minWidth = configuration.minWidth;
+ this._maxWidth = this._getMaxWidth(configuration.minWidth, configuration.maxWidth);
+ }
+ _resize(size) {
+ this._elementWidth = size.width;
+ this._elementHeight = size.height;
+ }
+ _reset() {
+ this._containerWidth = this._getContainerWidth(this._elementWidth, this._elementHeight);
+ this._containerHeight = this._getContainerHeight(this.containerWidth);
+ this._stepCircleSize = this._getStepCircleDiameter(this._containerHeight);
+ this._turnCircleSize = this._getTurnCircleDiameter(this.containerHeight);
+ this._outerRadius = this._getOuterRadius(this._containerHeight);
+ this._innerRadius = this._getInnerRadius(this._containerHeight);
+ this._shadowOffset = 3;
+ this._containerWidthCss = this._numberToCssPixels(this._containerWidth);
+ this._containerMarginCss = this._numberToCssPixels(-0.5 * this._containerWidth);
+ this._containerLeftCss = this._numberToCssPixels(Math.floor(0.5 * this._elementWidth));
+ this._containerHeightCss = this._numberToCssPixels(this._containerHeight);
+ this._containerBottomCss = this._numberToCssPixels(Math.floor(-0.08 * this._containerHeight));
+ this._stepCircleSizeCss = this._numberToCssPixels(this._stepCircleSize);
+ this._stepCircleMarginCss = this._numberToCssPixels(-0.5 * this._stepCircleSize);
+ this._turnCircleSizeCss = this._numberToCssPixels(this._turnCircleSize);
+ }
+ _getContainerWidth(elementWidth, elementHeight) {
+ let relativeWidth = (elementWidth - this._minThresholdWidth) / (this._maxThresholdWidth - this._minThresholdWidth);
+ let relativeHeight = (elementHeight - this._minThresholdHeight) / (this._maxThresholdHeight - this._minThresholdHeight);
+ let coeff = Math.max(0, Math.min(1, Math.min(relativeWidth, relativeHeight)));
+ coeff = 0.04 * Math.round(25 * coeff);
+ return this._minWidth + coeff * (this._maxWidth - this._minWidth);
+ }
+ _getContainerHeight(containerWidth) {
+ return 0.77 * containerWidth;
+ }
+ _getStepCircleDiameter(containerHeight) {
+ return 0.34 * containerHeight;
+ }
+ _getTurnCircleDiameter(containerHeight) {
+ return 0.3 * containerHeight;
+ }
+ _getOuterRadius(containerHeight) {
+ return 0.31 * containerHeight;
+ }
+ _getInnerRadius(containerHeight) {
+ return 0.125 * containerHeight;
+ }
+ _numberToCssPixels(value) {
+ return value + "px";
+ }
+ _getMaxWidth(value, minWidth) {
+ return value > minWidth ? value : minWidth;
+ }
+}
+
+/**
+ * @class DirectionDOMRenderer
+ * @classdesc DOM renderer for direction arrows.
+ */
+class DirectionDOMRenderer {
+ constructor(configuration, size) {
+ this._isEdge = false;
+ this._spatial = new Spatial();
+ this._calculator = new DirectionDOMCalculator(configuration, size);
+ this._image = null;
+ this._rotation = { phi: 0, theta: 0 };
+ this._epsilon = 0.5 * Math.PI / 180;
+ this._highlightKey = null;
+ this._distinguishSequence = false;
+ this._needsRender = false;
+ this._stepEdges = [];
+ this._turnEdges = [];
+ this._sphericalEdges = [];
+ this._sequenceEdgeKeys = [];
+ this._stepDirections = [
+ NavigationDirection.StepForward,
+ NavigationDirection.StepBackward,
+ NavigationDirection.StepLeft,
+ NavigationDirection.StepRight,
+ ];
+ this._turnDirections = [
+ NavigationDirection.TurnLeft,
+ NavigationDirection.TurnRight,
+ NavigationDirection.TurnU,
+ ];
+ this._turnNames = {};
+ this._turnNames[NavigationDirection.TurnLeft] = "mapillary-direction-turn-left";
+ this._turnNames[NavigationDirection.TurnRight] = "mapillary-direction-turn-right";
+ this._turnNames[NavigationDirection.TurnU] = "mapillary-direction-turn-around";
+ // detects IE 8-11, then Edge 20+.
+ let isIE = !!document.documentMode;
+ this._isEdge = !isIE && !!window.StyleMedia;
+ }
+ /**
+ * Get needs render.
+ *
+ * @returns {boolean} Value indicating whether render should be called.
+ */
+ get needsRender() {
+ return this._needsRender;
+ }
+ /**
+ * Renders virtual DOM elements.
+ *
+ * @description Calling render resets the needs render property.
+ */
+ render(navigator) {
+ this._needsRender = false;
+ let rotation = this._rotation;
+ let steps = [];
+ let turns = [];
+ if (isSpherical(this._image.cameraType)) {
+ steps = steps.concat(this._createSphericalArrows(navigator, rotation));
+ }
+ else {
+ steps = steps.concat(this._createPerspectiveToSphericalArrows(navigator, rotation));
+ steps = steps.concat(this._createStepArrows(navigator, rotation));
+ turns = turns.concat(this._createTurnArrows(navigator));
+ }
+ return this._getContainer(steps, turns, rotation);
+ }
+ setEdges(edgeStatus, sequence) {
+ this._setEdges(edgeStatus, sequence);
+ this._setNeedsRender();
+ }
+ /**
+ * Set image for which to show edges.
+ *
+ * @param {Image} image
+ */
+ setImage(image) {
+ this._image = image;
+ this._clearEdges();
+ this._setNeedsRender();
+ }
+ /**
+ * Set the render camera to use for calculating rotations.
+ *
+ * @param {RenderCamera} renderCamera
+ */
+ setRenderCamera(renderCamera) {
+ let rotation = renderCamera.rotation;
+ if (Math.abs(rotation.phi - this._rotation.phi) < this._epsilon) {
+ return;
+ }
+ this._rotation = rotation;
+ this._setNeedsRender();
+ }
+ /**
+ * Set configuration values.
+ *
+ * @param {DirectionConfiguration} configuration
+ */
+ setConfiguration(configuration) {
+ let needsRender = false;
+ if (this._highlightKey !== configuration.highlightId ||
+ this._distinguishSequence !== configuration.distinguishSequence) {
+ this._highlightKey = configuration.highlightId;
+ this._distinguishSequence = configuration.distinguishSequence;
+ needsRender = true;
+ }
+ if (this._calculator.minWidth !== configuration.minWidth ||
+ this._calculator.maxWidth !== configuration.maxWidth) {
+ this._calculator.configure(configuration);
+ needsRender = true;
+ }
+ if (needsRender) {
+ this._setNeedsRender();
+ }
+ }
+ /**
+ * Detect the element's width and height and resize
+ * elements accordingly.
+ *
+ * @param {ViewportSize} size Size of vßiewer container element.
+ */
+ resize(size) {
+ this._calculator.resize(size);
+ this._setNeedsRender();
+ }
+ _setNeedsRender() {
+ if (this._image != null) {
+ this._needsRender = true;
+ }
+ }
+ _clearEdges() {
+ this._stepEdges = [];
+ this._turnEdges = [];
+ this._sphericalEdges = [];
+ this._sequenceEdgeKeys = [];
+ }
+ _setEdges(edgeStatus, sequence) {
+ this._stepEdges = [];
+ this._turnEdges = [];
+ this._sphericalEdges = [];
+ this._sequenceEdgeKeys = [];
+ for (let edge of edgeStatus.edges) {
+ let direction = edge.data.direction;
+ if (this._stepDirections.indexOf(direction) > -1) {
+ this._stepEdges.push(edge);
+ continue;
+ }
+ if (this._turnDirections.indexOf(direction) > -1) {
+ this._turnEdges.push(edge);
+ continue;
+ }
+ if (edge.data.direction === NavigationDirection.Spherical) {
+ this._sphericalEdges.push(edge);
+ }
+ }
+ if (this._distinguishSequence && sequence != null) {
+ let edges = this._sphericalEdges
+ .concat(this._stepEdges)
+ .concat(this._turnEdges);
+ for (let edge of edges) {
+ let edgeKey = edge.target;
+ for (let sequenceKey of sequence.imageIds) {
+ if (sequenceKey === edgeKey) {
+ this._sequenceEdgeKeys.push(edgeKey);
+ break;
+ }
+ }
+ }
+ }
+ }
+ _createSphericalArrows(navigator, rotation) {
+ let arrows = [];
+ for (let sphericalEdge of this._sphericalEdges) {
+ arrows.push(this._createVNodeByKey(navigator, sphericalEdge.target, sphericalEdge.data.worldMotionAzimuth, rotation, this._calculator.outerRadius, "mapillary-direction-arrow-spherical"));
+ }
+ for (let stepEdge of this._stepEdges) {
+ arrows.push(this._createSphericalToPerspectiveArrow(navigator, stepEdge.target, stepEdge.data.worldMotionAzimuth, rotation, stepEdge.data.direction));
+ }
+ return arrows;
+ }
+ _createSphericalToPerspectiveArrow(navigator, key, azimuth, rotation, direction) {
+ let threshold = Math.PI / 8;
+ let relativePhi = rotation.phi;
+ switch (direction) {
+ case NavigationDirection.StepBackward:
+ relativePhi = rotation.phi - Math.PI;
+ break;
+ case NavigationDirection.StepLeft:
+ relativePhi = rotation.phi + Math.PI / 2;
+ break;
+ case NavigationDirection.StepRight:
+ relativePhi = rotation.phi - Math.PI / 2;
+ break;
+ }
+ if (Math.abs(this._spatial.wrapAngle(azimuth - relativePhi)) < threshold) {
+ return this._createVNodeByKey(navigator, key, azimuth, rotation, this._calculator.outerRadius, "mapillary-direction-arrow-step");
+ }
+ return this._createVNodeInactive(key, azimuth, rotation);
+ }
+ _createPerspectiveToSphericalArrows(navigator, rotation) {
+ let arrows = [];
+ for (let sphericalEdge of this._sphericalEdges) {
+ arrows.push(this._createVNodeByKey(navigator, sphericalEdge.target, sphericalEdge.data.worldMotionAzimuth, rotation, this._calculator.innerRadius, "mapillary-direction-arrow-spherical", true));
+ }
+ return arrows;
+ }
+ _createStepArrows(navigator, rotation) {
+ let arrows = [];
+ for (let stepEdge of this._stepEdges) {
+ arrows.push(this._createVNodeByDirection(navigator, stepEdge.target, stepEdge.data.worldMotionAzimuth, rotation, stepEdge.data.direction));
+ }
+ return arrows;
+ }
+ _createTurnArrows(navigator) {
+ let turns = [];
+ for (let turnEdge of this._turnEdges) {
+ let direction = turnEdge.data.direction;
+ let name = this._turnNames[direction];
+ turns.push(this._createVNodeByTurn(navigator, turnEdge.target, name, direction));
+ }
+ return turns;
+ }
+ _createVNodeByKey(navigator, key, azimuth, rotation, offset, className, shiftVertically) {
+ let onClick = (e) => {
+ navigator.moveTo$(key)
+ .subscribe(undefined, (error) => {
+ if (!(error instanceof CancelMapillaryError)) {
+ console.error(error);
+ }
+ });
+ };
+ return this._createVNode(key, azimuth, rotation, offset, className, "mapillary-direction-circle", onClick, shiftVertically);
+ }
+ _createVNodeByDirection(navigator, key, azimuth, rotation, direction) {
+ let onClick = (e) => {
+ navigator.moveDir$(direction)
+ .subscribe(undefined, (error) => {
+ if (!(error instanceof CancelMapillaryError)) {
+ console.error(error);
+ }
+ });
+ };
+ return this._createVNode(key, azimuth, rotation, this._calculator.outerRadius, "mapillary-direction-arrow-step", "mapillary-direction-circle", onClick);
+ }
+ _createVNodeByTurn(navigator, key, className, direction) {
+ let onClick = (e) => {
+ navigator.moveDir$(direction)
+ .subscribe(undefined, (error) => {
+ if (!(error instanceof CancelMapillaryError)) {
+ console.error(error);
+ }
+ });
+ };
+ let style = {
+ height: this._calculator.turnCircleSizeCss,
+ transform: "rotate(0)",
+ width: this._calculator.turnCircleSizeCss,
+ };
+ switch (direction) {
+ case NavigationDirection.TurnLeft:
+ style.left = "5px";
+ style.top = "5px";
+ break;
+ case NavigationDirection.TurnRight:
+ style.right = "5px";
+ style.top = "5px";
+ break;
+ case NavigationDirection.TurnU:
+ style.left = "5px";
+ style.bottom = "5px";
+ break;
+ }
+ let circleProperties = {
+ attributes: {
+ "data-id": key,
+ },
+ onclick: onClick,
+ style: style,
+ };
+ let circleClassName = "mapillary-direction-turn-circle";
+ if (this._sequenceEdgeKeys.indexOf(key) > -1) {
+ circleClassName += "-sequence";
+ }
+ if (this._highlightKey === key) {
+ circleClassName += "-highlight";
+ }
+ let turn = virtualDom.h(`div.${className}`, {}, []);
+ return virtualDom.h("div." + circleClassName, circleProperties, [turn]);
+ }
+ _createVNodeInactive(key, azimuth, rotation) {
+ return this._createVNode(key, azimuth, rotation, this._calculator.outerRadius, "mapillary-direction-arrow-inactive", "mapillary-direction-circle-inactive");
+ }
+ _createVNode(key, azimuth, rotation, radius, className, circleClassName, onClick, shiftVertically) {
+ let translation = this._calculator.angleToCoordinates(azimuth - rotation.phi);
+ // rotate 90 degrees clockwise and flip over X-axis
+ let translationX = Math.round(-radius * translation[1] + 0.5 * this._calculator.containerWidth);
+ let translationY = Math.round(-radius * translation[0] + 0.5 * this._calculator.containerHeight);
+ let shadowTranslation = this._calculator.relativeAngleToCoordiantes(azimuth, rotation.phi);
+ let shadowOffset = this._calculator.shadowOffset;
+ let shadowTranslationX = -shadowOffset * shadowTranslation[1];
+ let shadowTranslationY = shadowOffset * shadowTranslation[0];
+ let filter = `drop-shadow(${shadowTranslationX}px ${shadowTranslationY}px 1px rgba(0,0,0,0.8))`;
+ let properties = {
+ style: {
+ "-webkit-filter": filter,
+ filter: filter,
+ },
+ };
+ let chevron = virtualDom.h("div." + className, properties, []);
+ let azimuthDeg = -this._spatial.radToDeg(azimuth - rotation.phi);
+ let circleTransform = shiftVertically ?
+ `translate(${translationX}px, ${translationY}px) rotate(${azimuthDeg}deg) translateZ(-0.01px)` :
+ `translate(${translationX}px, ${translationY}px) rotate(${azimuthDeg}deg)`;
+ let circleProperties = {
+ attributes: { "data-id": key },
+ onclick: onClick,
+ style: {
+ height: this._calculator.stepCircleSizeCss,
+ marginLeft: this._calculator.stepCircleMarginCss,
+ marginTop: this._calculator.stepCircleMarginCss,
+ transform: circleTransform,
+ width: this._calculator.stepCircleSizeCss,
+ },
+ };
+ if (this._sequenceEdgeKeys.indexOf(key) > -1) {
+ circleClassName += "-sequence";
+ }
+ if (this._highlightKey === key) {
+ circleClassName += "-highlight";
+ }
+ return virtualDom.h("div." + circleClassName, circleProperties, [chevron]);
+ }
+ _getContainer(steps, turns, rotation) {
+ // edge does not handle hover on perspective transforms.
+ let transform = this._isEdge ?
+ "rotateX(60deg)" :
+ `perspective(${this._calculator.containerWidthCss}) rotateX(60deg)`;
+ let properties = {
+ oncontextmenu: (event) => { event.preventDefault(); },
+ style: {
+ bottom: this._calculator.containerBottomCss,
+ height: this._calculator.containerHeightCss,
+ left: this._calculator.containerLeftCss,
+ marginLeft: this._calculator.containerMarginCss,
+ transform: transform,
+ width: this._calculator.containerWidthCss,
+ },
+ };
+ return virtualDom.h("div.mapillary-direction-perspective", properties, turns.concat(steps));
+ }
+}
+
+/**
+ * @class DirectionComponent
+ * @classdesc Component showing navigation arrows for steps and turns.
+ */
+class DirectionComponent extends Component {
+ /** @ignore */
+ constructor(name, container, navigator, directionDOMRenderer) {
+ super(name, container, navigator);
+ this._renderer = !!directionDOMRenderer ?
+ directionDOMRenderer :
+ new DirectionDOMRenderer(this.defaultConfiguration, { height: container.container.offsetHeight, width: container.container.offsetWidth });
+ this._hoveredIdSubject$ = new Subject();
+ this._hoveredId$ = this._hoveredIdSubject$.pipe(share());
+ }
+ fire(type, event) {
+ super.fire(type, event);
+ }
+ off(type, handler) {
+ super.off(type, handler);
+ }
+ on(type, handler) {
+ super.on(type, handler);
+ }
+ _activate() {
+ const subs = this._subscriptions;
+ subs.push(this._configuration$
+ .subscribe((configuration) => {
+ this._renderer.setConfiguration(configuration);
+ }));
+ subs.push(this._container.renderService.size$
+ .subscribe((size) => {
+ this._renderer.resize(size);
+ }));
+ subs.push(this._navigator.stateService.currentImage$.pipe(tap((image) => {
+ this._container.domRenderer.render$.next({ name: this._name, vNode: virtualDom.h("div", {}, []) });
+ this._renderer.setImage(image);
+ }), withLatestFrom(this._configuration$), switchMap(([image, configuration]) => {
+ return combineLatest(image.spatialEdges$, configuration.distinguishSequence ?
+ this._navigator.graphService
+ .cacheSequence$(image.sequenceId).pipe(catchError((error) => {
+ console.error(`Failed to cache sequence (${image.sequenceId})`, error);
+ return of(null);
+ })) :
+ of(null));
+ }))
+ .subscribe(([edgeStatus, sequence]) => {
+ this._renderer.setEdges(edgeStatus, sequence);
+ }));
+ subs.push(this._container.renderService.renderCameraFrame$.pipe(tap((renderCamera) => {
+ this._renderer.setRenderCamera(renderCamera);
+ }), map(() => {
+ return this._renderer;
+ }), filter((renderer) => {
+ return renderer.needsRender;
+ }), map((renderer) => {
+ return { name: this._name, vNode: renderer.render(this._navigator) };
+ }))
+ .subscribe(this._container.domRenderer.render$));
+ subs.push(combineLatest(this._container.domRenderer.element$, this._container.renderService.renderCamera$, this._container.mouseService.mouseMove$.pipe(startWith(null)), this._container.mouseService.mouseUp$.pipe(startWith(null))).pipe(map(([element]) => {
+ let elements = element.getElementsByClassName("mapillary-direction-perspective");
+ for (let i = 0; i < elements.length; i++) {
+ let hovered = elements.item(i).querySelector(":hover");
+ if (hovered != null && hovered.hasAttribute("data-id")) {
+ return hovered.getAttribute("data-id");
+ }
+ }
+ return null;
+ }), distinctUntilChanged())
+ .subscribe(this._hoveredIdSubject$));
+ subs.push(this._hoveredId$
+ .subscribe((id) => {
+ const type = "hover";
+ const event = {
+ id,
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ }));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ }
+ _getDefaultConfiguration() {
+ return {
+ distinguishSequence: false,
+ maxWidth: 460,
+ minWidth: 260,
+ };
+ }
+}
+/** @inheritdoc */
+DirectionComponent.componentName = "direction";
+
+const sphericalFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+#define tau 6.28318530718
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+
+varying vec4 vRstq;
+
+void main()
+{
+ vec3 b = normalize(vRstq.xyz);
+ float lat = -asin(b.y);
+ float lng = atan(b.x, b.z);
+ float x = lng / tau + 0.5;
+ float y = lat / tau * 2.0 + 0.5;
+ vec4 baseColor = texture2D(projectorTex, vec2(x, y));
+ baseColor.a = opacity;
+ gl_FragColor = baseColor;
+}
+`;
+
+const sphericalVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+ vRstq = projectorMat * vec4(position, 1.0);
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const sphericalCurtainFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+#define tau 6.28318530718
+
+uniform sampler2D projectorTex;
+uniform float curtain;
+uniform float opacity;
+
+varying vec4 vRstq;
+
+void main()
+{
+ vec3 b = normalize(vRstq.xyz);
+ float lat = -asin(b.y);
+ float lng = atan(b.x, b.z);
+ float x = lng / tau + 0.5;
+ float y = lat / tau * 2.0 + 0.5;
+
+ bool inverted = curtain < 0.5;
+
+ float curtainMin = inverted ? curtain + 0.5 : curtain - 0.5;
+ float curtainMax = curtain;
+
+ bool insideCurtain = inverted ?
+ x > curtainMin || x < curtainMax :
+ x > curtainMin && x < curtainMax;
+
+ vec4 baseColor;
+ if (insideCurtain) {
+ baseColor = texture2D(projectorTex, vec2(x, y));
+ baseColor.a = opacity;
+ } else {
+ baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+ }
+
+ gl_FragColor = baseColor;
+}
+`;
+
+const sphericalCurtainVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+ vRstq = projectorMat * vec4(position, 1.0);
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const fisheyeFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+uniform float focal;
+uniform float k1;
+uniform float k2;
+uniform float scale_x;
+uniform float scale_y;
+uniform float radial_peak;
+
+varying vec4 vRstq;
+
+void main()
+{
+ float x = vRstq.x;
+ float y = vRstq.y;
+ float z = vRstq.z;
+
+ float r = sqrt(x * x + y * y);
+ float theta = atan(r, z);
+
+ if (radial_peak > 0. && theta > radial_peak) {
+ theta = radial_peak;
+ }
+
+ float theta2 = theta * theta;
+ float theta_d = theta * (1.0 + theta2 * (k1 + theta2 * k2));
+ float s = focal * theta_d / r;
+
+ float u = scale_x * s * x + 0.5;
+ float v = -scale_y * s * y + 0.5;
+
+ vec4 baseColor;
+ if (u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+ baseColor = texture2D(projectorTex, vec2(u, v));
+ baseColor.a = opacity;
+ } else {
+ baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+ }
+
+ gl_FragColor = baseColor;
+}
+`;
+
+const fisheyeVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+ vRstq = projectorMat * vec4(position, 1.0);
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const fisheyeCurtainFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+uniform float focal;
+uniform float k1;
+uniform float k2;
+uniform float scale_x;
+uniform float scale_y;
+uniform float radial_peak;
+uniform float curtain;
+
+varying vec4 vRstq;
+
+void main()
+{
+ float x = vRstq.x;
+ float y = vRstq.y;
+ float z = vRstq.z;
+
+ float r2 = sqrt(x * x + y * y);
+ float theta = atan(r2, z);
+
+ if (radial_peak > 0. && theta > radial_peak) {
+ theta = radial_peak;
+ }
+
+ float theta2 = theta * theta;
+ float theta_d = theta * (1.0 + theta2 * (k1 + theta2 * k2));
+ float s = focal * theta_d / r2;
+
+ float u = scale_x * s * x + 0.5;
+ float v = -scale_y * s * y + 0.5;
+
+ vec4 baseColor;
+ if ((u < curtain || curtain >= 1.0) && u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+ baseColor = texture2D(projectorTex, vec2(u, v));
+ baseColor.a = opacity;
+ } else {
+ baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+ }
+
+ gl_FragColor = baseColor;
+}
+`;
+
+const fisheyeCurtainVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+ vRstq = projectorMat * vec4(position, 1.0);
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const perspectiveFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+uniform float focal;
+uniform float k1;
+uniform float k2;
+uniform float scale_x;
+uniform float scale_y;
+uniform float radial_peak;
+
+varying vec4 vRstq;
+
+void main()
+{
+ float x = vRstq.x / vRstq.z;
+ float y = vRstq.y / vRstq.z;
+ float r2 = x * x + y * y;
+
+ if (radial_peak > 0. && r2 > radial_peak * sqrt(r2)) {
+ r2 = radial_peak * radial_peak;
+ }
+
+ float d = 1.0 + k1 * r2 + k2 * r2 * r2;
+ float u = scale_x * focal * d * x + 0.5;
+ float v = - scale_y * focal * d * y + 0.5;
+
+ vec4 baseColor;
+ if (u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+ baseColor = texture2D(projectorTex, vec2(u, v));
+ baseColor.a = opacity;
+ } else {
+ baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+ }
+
+ gl_FragColor = baseColor;
+}
+`;
+
+const perspectiveVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+ vRstq = projectorMat * vec4(position, 1.0);
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const perspectiveCurtainFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+uniform float focal;
+uniform float k1;
+uniform float k2;
+uniform float scale_x;
+uniform float scale_y;
+uniform float radial_peak;
+uniform float curtain;
+
+varying vec4 vRstq;
+
+void main()
+{
+ float x = vRstq.x / vRstq.z;
+ float y = vRstq.y / vRstq.z;
+ float r2 = x * x + y * y;
+
+ if (radial_peak > 0. && r2 > radial_peak * sqrt(r2)) {
+ r2 = radial_peak * radial_peak;
+ }
+
+ float d = 1.0 + k1 * r2 + k2 * r2 * r2;
+ float u = scale_x * focal * d * x + 0.5;
+ float v = - scale_y * focal * d * y + 0.5;
+
+ vec4 baseColor;
+ if ((u < curtain || curtain >= 1.0) && u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+ baseColor = texture2D(projectorTex, vec2(u, v));
+ baseColor.a = opacity;
+ } else {
+ baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+ }
+
+ gl_FragColor = baseColor;
+}
+`;
+
+const perspectiveCurtainVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+ vRstq = projectorMat * vec4(position, 1.0);
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const perspectiveDistortedFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+
+varying vec4 vRstq;
+
+void main()
+{
+ float u = vRstq.x / vRstq.w;
+ float v = vRstq.y / vRstq.w;
+
+ vec4 baseColor;
+ if (u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+ baseColor = texture2D(projectorTex, vec2(u, v));
+ baseColor.a = opacity;
+ } else {
+ baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+ }
+
+ gl_FragColor = baseColor;
+}
+`;
+
+const perspectiveDistortedVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+ vRstq = projectorMat * vec4(position, 1.0);
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+const perspectiveDistortedCurtainFrag = `
+#ifdef GL_FRAGMENT_PRECISION_HIGH
+precision highp float;
+#else
+precision mediump float;
+#endif
+
+uniform sampler2D projectorTex;
+uniform float opacity;
+uniform float curtain;
+
+varying vec4 vRstq;
+
+void main()
+{
+ float u = vRstq.x / vRstq.w;
+ float v = vRstq.y / vRstq.w;
+
+ vec4 baseColor;
+ if ((u < curtain || curtain >= 1.0) && u >= 0. && u <= 1. && v >= 0. && v <= 1.) {
+ baseColor = texture2D(projectorTex, vec2(u, v));
+ baseColor.a = opacity;
+ } else {
+ baseColor = vec4(0.0, 0.0, 0.0, 0.0);
+ }
+
+ gl_FragColor = baseColor;
+}
+`;
+
+const perspectiveDistortedCurtainVert = `
+#ifdef GL_ES
+precision highp float;
+#endif
+
+uniform mat4 projectorMat;
+
+varying vec4 vRstq;
+
+void main()
+{
+ vRstq = projectorMat * vec4(position, 1.0);
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
+}
+`;
+
+class Shaders {
+}
+Shaders.fisheye = {
+ fragment: fisheyeFrag,
+ vertex: fisheyeVert,
+};
+Shaders.fisheyeCurtain = {
+ fragment: fisheyeCurtainFrag,
+ vertex: fisheyeCurtainVert,
+};
+Shaders.perspective = {
+ fragment: perspectiveFrag,
+ vertex: perspectiveVert,
+};
+Shaders.perspectiveCurtain = {
+ fragment: perspectiveCurtainFrag,
+ vertex: perspectiveCurtainVert,
+};
+Shaders.perspectiveDistorted = {
+ fragment: perspectiveDistortedFrag,
+ vertex: perspectiveDistortedVert,
+};
+Shaders.perspectiveDistortedCurtain = {
+ fragment: perspectiveDistortedCurtainFrag,
+ vertex: perspectiveDistortedCurtainVert,
+};
+Shaders.spherical = {
+ fragment: sphericalFrag,
+ vertex: sphericalVert,
+};
+Shaders.sphericalCurtain = {
+ fragment: sphericalCurtainFrag,
+ vertex: sphericalCurtainVert,
+};
+
+class MeshFactory {
+ constructor(imagePlaneDepth, imageSphereRadius) {
+ this._imagePlaneDepth = imagePlaneDepth != null ? imagePlaneDepth : 200;
+ this._imageSphereRadius = imageSphereRadius != null ? imageSphereRadius : 200;
+ }
+ createMesh(image, transform) {
+ if (isSpherical(transform.cameraType)) {
+ return this._createImageSphere(image, transform);
+ }
+ else if (isFisheye(transform.cameraType)) {
+ return this._createImagePlaneFisheye(image, transform);
+ }
+ else {
+ return this._createImagePlane(image, transform);
+ }
+ }
+ createFlatMesh(image, transform, basicX0, basicX1, basicY0, basicY1) {
+ let texture = this._createTexture(image.image);
+ let materialParameters = this._createDistortedPlaneMaterialParameters(transform, texture);
+ let material = new ShaderMaterial(materialParameters);
+ let geometry = this._getFlatImagePlaneGeoFromBasic(transform, basicX0, basicX1, basicY0, basicY1);
+ return new Mesh(geometry, material);
+ }
+ createCurtainMesh(image, transform) {
+ if (isSpherical(transform.cameraType)) {
+ return this._createSphereCurtainMesh(image, transform);
+ }
+ else if (isFisheye(transform.cameraType)) {
+ return this._createCurtainMeshFisheye(image, transform);
+ }
+ else {
+ return this._createCurtainMesh(image, transform);
+ }
+ }
+ createDistortedCurtainMesh(image, transform) {
+ return this._createDistortedCurtainMesh(image, transform);
+ }
+ _createCurtainMesh(image, transform) {
+ let texture = this._createTexture(image.image);
+ let materialParameters = this._createCurtainPlaneMaterialParameters(transform, texture);
+ let material = new ShaderMaterial(materialParameters);
+ let geometry = this._useMesh(transform, image) ?
+ this._getImagePlaneGeo(transform, image) :
+ this._getRegularFlatImagePlaneGeo(transform);
+ return new Mesh(geometry, material);
+ }
+ _createCurtainMeshFisheye(image, transform) {
+ let texture = this._createTexture(image.image);
+ let materialParameters = this._createCurtainPlaneMaterialParametersFisheye(transform, texture);
+ let material = new ShaderMaterial(materialParameters);
+ let geometry = this._useMesh(transform, image) ?
+ this._getImagePlaneGeoFisheye(transform, image) :
+ this._getRegularFlatImagePlaneGeo(transform);
+ return new Mesh(geometry, material);
+ }
+ _createDistortedCurtainMesh(image, transform) {
+ let texture = this._createTexture(image.image);
+ let materialParameters = this._createDistortedCurtainPlaneMaterialParameters(transform, texture);
+ let material = new ShaderMaterial(materialParameters);
+ let geometry = this._getRegularFlatImagePlaneGeo(transform);
+ return new Mesh(geometry, material);
+ }
+ _createSphereCurtainMesh(image, transform) {
+ let texture = this._createTexture(image.image);
+ let materialParameters = this._createCurtainSphereMaterialParameters(transform, texture);
+ let material = new ShaderMaterial(materialParameters);
+ return this._useMesh(transform, image) ?
+ new Mesh(this._getImageSphereGeo(transform, image), material) :
+ new Mesh(this._getFlatImageSphereGeo(transform), material);
+ }
+ _createImageSphere(image, transform) {
+ let texture = this._createTexture(image.image);
+ let materialParameters = this._createSphereMaterialParameters(transform, texture);
+ let material = new ShaderMaterial(materialParameters);
+ let mesh = this._useMesh(transform, image) ?
+ new Mesh(this._getImageSphereGeo(transform, image), material) :
+ new Mesh(this._getFlatImageSphereGeo(transform), material);
+ return mesh;
+ }
+ _createImagePlane(image, transform) {
+ let texture = this._createTexture(image.image);
+ let materialParameters = this._createPlaneMaterialParameters(transform, texture);
+ let material = new ShaderMaterial(materialParameters);
+ let geometry = this._useMesh(transform, image) ?
+ this._getImagePlaneGeo(transform, image) :
+ this._getRegularFlatImagePlaneGeo(transform);
+ return new Mesh(geometry, material);
+ }
+ _createImagePlaneFisheye(image, transform) {
+ let texture = this._createTexture(image.image);
+ let materialParameters = this._createPlaneMaterialParametersFisheye(transform, texture);
+ let material = new ShaderMaterial(materialParameters);
+ let geometry = this._useMesh(transform, image) ?
+ this._getImagePlaneGeoFisheye(transform, image) :
+ this._getRegularFlatImagePlaneGeoFisheye(transform);
+ return new Mesh(geometry, material);
+ }
+ _createSphereMaterialParameters(transform, texture) {
+ let materialParameters = {
+ depthWrite: false,
+ fragmentShader: Shaders.spherical.fragment,
+ side: DoubleSide,
+ transparent: true,
+ uniforms: {
+ opacity: { value: 1.0 },
+ projectorMat: { value: transform.rt },
+ projectorTex: { value: texture },
+ },
+ vertexShader: Shaders.spherical.vertex,
+ };
+ return materialParameters;
+ }
+ _createCurtainSphereMaterialParameters(transform, texture) {
+ let materialParameters = {
+ depthWrite: false,
+ fragmentShader: Shaders.sphericalCurtain.fragment,
+ side: DoubleSide,
+ transparent: true,
+ uniforms: {
+ curtain: { value: 1.0 },
+ opacity: { value: 1.0 },
+ projectorMat: { value: transform.rt },
+ projectorTex: { value: texture },
+ },
+ vertexShader: Shaders.sphericalCurtain.vertex,
+ };
+ return materialParameters;
+ }
+ _createPlaneMaterialParameters(transform, texture) {
+ let materialParameters = {
+ depthWrite: false,
+ fragmentShader: Shaders.perspective.fragment,
+ side: DoubleSide,
+ transparent: true,
+ uniforms: {
+ focal: { value: transform.focal },
+ k1: { value: transform.ck1 },
+ k2: { value: transform.ck2 },
+ opacity: { value: 1.0 },
+ projectorMat: { value: transform.basicRt },
+ projectorTex: { value: texture },
+ radial_peak: { value: !!transform.radialPeak ? transform.radialPeak : 0.0 },
+ scale_x: { value: Math.max(transform.basicHeight, transform.basicWidth) / transform.basicWidth },
+ scale_y: { value: Math.max(transform.basicWidth, transform.basicHeight) / transform.basicHeight },
+ },
+ vertexShader: Shaders.perspective.vertex,
+ };
+ return materialParameters;
+ }
+ _createPlaneMaterialParametersFisheye(transform, texture) {
+ let materialParameters = {
+ depthWrite: false,
+ fragmentShader: Shaders.fisheye.fragment,
+ side: DoubleSide,
+ transparent: true,
+ uniforms: {
+ focal: { value: transform.focal },
+ k1: { value: transform.ck1 },
+ k2: { value: transform.ck2 },
+ opacity: { value: 1.0 },
+ projectorMat: { value: transform.basicRt },
+ projectorTex: { value: texture },
+ radial_peak: { value: !!transform.radialPeak ? transform.radialPeak : 0.0 },
+ scale_x: { value: Math.max(transform.basicHeight, transform.basicWidth) / transform.basicWidth },
+ scale_y: { value: Math.max(transform.basicWidth, transform.basicHeight) / transform.basicHeight },
+ },
+ vertexShader: Shaders.fisheye.vertex,
+ };
+ return materialParameters;
+ }
+ _createCurtainPlaneMaterialParametersFisheye(transform, texture) {
+ let materialParameters = {
+ depthWrite: false,
+ fragmentShader: Shaders.fisheyeCurtain.fragment,
+ side: DoubleSide,
+ transparent: true,
+ uniforms: {
+ curtain: { value: 1.0 },
+ focal: { value: transform.focal },
+ k1: { value: transform.ck1 },
+ k2: { value: transform.ck2 },
+ opacity: { value: 1.0 },
+ projectorMat: { value: transform.basicRt },
+ projectorTex: { value: texture },
+ radial_peak: { value: !!transform.radialPeak ? transform.radialPeak : 0.0 },
+ scale_x: { value: Math.max(transform.basicHeight, transform.basicWidth) / transform.basicWidth },
+ scale_y: { value: Math.max(transform.basicWidth, transform.basicHeight) / transform.basicHeight },
+ },
+ vertexShader: Shaders.fisheyeCurtain.vertex,
+ };
+ return materialParameters;
+ }
+ _createCurtainPlaneMaterialParameters(transform, texture) {
+ let materialParameters = {
+ depthWrite: false,
+ fragmentShader: Shaders.perspectiveCurtain.fragment,
+ side: DoubleSide,
+ transparent: true,
+ uniforms: {
+ curtain: { value: 1.0 },
+ focal: { value: transform.focal },
+ k1: { value: transform.ck1 },
+ k2: { value: transform.ck2 },
+ opacity: { value: 1.0 },
+ projectorMat: { value: transform.basicRt },
+ projectorTex: { value: texture },
+ radial_peak: { value: !!transform.radialPeak ? transform.radialPeak : 0.0 },
+ scale_x: { value: Math.max(transform.basicHeight, transform.basicWidth) / transform.basicWidth },
+ scale_y: { value: Math.max(transform.basicWidth, transform.basicHeight) / transform.basicHeight },
+ },
+ vertexShader: Shaders.perspectiveCurtain.vertex,
+ };
+ return materialParameters;
+ }
+ _createDistortedCurtainPlaneMaterialParameters(transform, texture) {
+ let materialParameters = {
+ depthWrite: false,
+ fragmentShader: Shaders.perspectiveDistortedCurtain.fragment,
+ side: DoubleSide,
+ transparent: true,
+ uniforms: {
+ curtain: { value: 1.0 },
+ opacity: { value: 1.0 },
+ projectorMat: { value: transform.projectorMatrix() },
+ projectorTex: { value: texture },
+ },
+ vertexShader: Shaders.perspectiveDistortedCurtain.vertex,
+ };
+ return materialParameters;
+ }
+ _createDistortedPlaneMaterialParameters(transform, texture) {
+ let materialParameters = {
+ depthWrite: false,
+ fragmentShader: Shaders.perspectiveDistorted.fragment,
+ side: DoubleSide,
+ transparent: true,
+ uniforms: {
+ opacity: { value: 1.0 },
+ projectorMat: { value: transform.projectorMatrix() },
+ projectorTex: { value: texture },
+ },
+ vertexShader: Shaders.perspectiveDistorted.vertex,
+ };
+ return materialParameters;
+ }
+ _createTexture(image) {
+ let texture = new Texture(image);
+ texture.minFilter = LinearFilter;
+ texture.needsUpdate = true;
+ return texture;
+ }
+ _useMesh(transform, image) {
+ return image.mesh.vertices.length && transform.hasValidScale;
+ }
+ _getImageSphereGeo(transform, image) {
+ const t = transform.srtInverse;
+ // push everything at least 5 meters in front of the camera
+ let minZ = 5.0 * transform.scale;
+ let maxZ = this._imageSphereRadius * transform.scale;
+ let vertices = image.mesh.vertices;
+ let numVertices = vertices.length / 3;
+ let positions = new Float32Array(vertices.length);
+ for (let i = 0; i < numVertices; ++i) {
+ let index = 3 * i;
+ let x = vertices[index + 0];
+ let y = vertices[index + 1];
+ let z = vertices[index + 2];
+ let l = Math.sqrt(x * x + y * y + z * z);
+ let boundedL = Math.max(minZ, Math.min(l, maxZ));
+ let factor = boundedL / l;
+ let p = new Vector3(x * factor, y * factor, z * factor);
+ p.applyMatrix4(t);
+ positions[index + 0] = p.x;
+ positions[index + 1] = p.y;
+ positions[index + 2] = p.z;
+ }
+ let faces = image.mesh.faces;
+ let indices = new Uint16Array(faces.length);
+ for (let i = 0; i < faces.length; ++i) {
+ indices[i] = faces[i];
+ }
+ let geometry = new BufferGeometry();
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
+ geometry.setIndex(new BufferAttribute(indices, 1));
+ return geometry;
+ }
+ _getImagePlaneGeo(transform, image) {
+ const undistortionMarginFactor = 3;
+ const t = transform.srtInverse;
+ // push everything at least 5 meters in front of the camera
+ let minZ = 5.0 * transform.scale;
+ let maxZ = this._imagePlaneDepth * transform.scale;
+ let vertices = image.mesh.vertices;
+ let numVertices = vertices.length / 3;
+ let positions = new Float32Array(vertices.length);
+ for (let i = 0; i < numVertices; ++i) {
+ let index = 3 * i;
+ let x = vertices[index + 0];
+ let y = vertices[index + 1];
+ let z = vertices[index + 2];
+ if (i < 4) {
+ x *= undistortionMarginFactor;
+ y *= undistortionMarginFactor;
+ }
+ let boundedZ = Math.max(minZ, Math.min(z, maxZ));
+ let factor = boundedZ / z;
+ let p = new Vector3(x * factor, y * factor, boundedZ);
+ p.applyMatrix4(t);
+ positions[index + 0] = p.x;
+ positions[index + 1] = p.y;
+ positions[index + 2] = p.z;
+ }
+ let faces = image.mesh.faces;
+ let indices = new Uint16Array(faces.length);
+ for (let i = 0; i < faces.length; ++i) {
+ indices[i] = faces[i];
+ }
+ let geometry = new BufferGeometry();
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
+ geometry.setIndex(new BufferAttribute(indices, 1));
+ return geometry;
+ }
+ _getImagePlaneGeoFisheye(transform, image) {
+ const t = transform.srtInverse;
+ // push everything at least 5 meters in front of the camera
+ let minZ = 5.0 * transform.scale;
+ let maxZ = this._imagePlaneDepth * transform.scale;
+ let vertices = image.mesh.vertices;
+ let numVertices = vertices.length / 3;
+ let positions = new Float32Array(vertices.length);
+ for (let i = 0; i < numVertices; ++i) {
+ let index = 3 * i;
+ let x = vertices[index + 0];
+ let y = vertices[index + 1];
+ let z = vertices[index + 2];
+ let l = Math.sqrt(x * x + y * y + z * z);
+ let boundedL = Math.max(minZ, Math.min(l, maxZ));
+ let factor = boundedL / l;
+ let p = new Vector3(x * factor, y * factor, z * factor);
+ p.applyMatrix4(t);
+ positions[index + 0] = p.x;
+ positions[index + 1] = p.y;
+ positions[index + 2] = p.z;
+ }
+ let faces = image.mesh.faces;
+ let indices = new Uint16Array(faces.length);
+ for (let i = 0; i < faces.length; ++i) {
+ indices[i] = faces[i];
+ }
+ let geometry = new BufferGeometry();
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
+ geometry.setIndex(new BufferAttribute(indices, 1));
+ return geometry;
+ }
+ _getFlatImageSphereGeo(transform) {
+ const geometry = new SphereGeometry(this._imageSphereRadius, 20, 40);
+ const t = transform.rt
+ .clone()
+ .invert();
+ geometry.applyMatrix4(t);
+ return geometry;
+ }
+ _getRegularFlatImagePlaneGeo(transform) {
+ let width = transform.width;
+ let height = transform.height;
+ let size = Math.max(width, height);
+ let dx = width / 2.0 / size;
+ let dy = height / 2.0 / size;
+ return this._getFlatImagePlaneGeo(transform, dx, dy);
+ }
+ _getFlatImagePlaneGeo(transform, dx, dy) {
+ let vertices = [];
+ vertices.push(transform.unprojectSfM([-dx, -dy], this._imagePlaneDepth));
+ vertices.push(transform.unprojectSfM([dx, -dy], this._imagePlaneDepth));
+ vertices.push(transform.unprojectSfM([dx, dy], this._imagePlaneDepth));
+ vertices.push(transform.unprojectSfM([-dx, dy], this._imagePlaneDepth));
+ return this._createFlatGeometry(vertices);
+ }
+ _getRegularFlatImagePlaneGeoFisheye(transform) {
+ let width = transform.width;
+ let height = transform.height;
+ let size = Math.max(width, height);
+ let dx = width / 2.0 / size;
+ let dy = height / 2.0 / size;
+ return this._getFlatImagePlaneGeoFisheye(transform, dx, dy);
+ }
+ _getFlatImagePlaneGeoFisheye(transform, dx, dy) {
+ let vertices = [];
+ vertices.push(transform.unprojectSfM([-dx, -dy], this._imagePlaneDepth));
+ vertices.push(transform.unprojectSfM([dx, -dy], this._imagePlaneDepth));
+ vertices.push(transform.unprojectSfM([dx, dy], this._imagePlaneDepth));
+ vertices.push(transform.unprojectSfM([-dx, dy], this._imagePlaneDepth));
+ return this._createFlatGeometry(vertices);
+ }
+ _getFlatImagePlaneGeoFromBasic(transform, basicX0, basicX1, basicY0, basicY1) {
+ let vertices = [];
+ vertices.push(transform.unprojectBasic([basicX0, basicY0], this._imagePlaneDepth));
+ vertices.push(transform.unprojectBasic([basicX1, basicY0], this._imagePlaneDepth));
+ vertices.push(transform.unprojectBasic([basicX1, basicY1], this._imagePlaneDepth));
+ vertices.push(transform.unprojectBasic([basicX0, basicY1], this._imagePlaneDepth));
+ return this._createFlatGeometry(vertices);
+ }
+ _createFlatGeometry(vertices) {
+ let positions = new Float32Array(12);
+ for (let i = 0; i < vertices.length; i++) {
+ let index = 3 * i;
+ positions[index + 0] = vertices[i][0];
+ positions[index + 1] = vertices[i][1];
+ positions[index + 2] = vertices[i][2];
+ }
+ let indices = new Uint16Array(6);
+ indices[0] = 0;
+ indices[1] = 1;
+ indices[2] = 3;
+ indices[3] = 1;
+ indices[4] = 2;
+ indices[5] = 3;
+ let geometry = new BufferGeometry();
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
+ geometry.setIndex(new BufferAttribute(indices, 1));
+ return geometry;
+ }
+}
+
+class MeshScene {
+ constructor() {
+ this._planes = {};
+ this._planesOld = {};
+ this._planesPeriphery = {};
+ this._scene = new Scene();
+ this._sceneOld = new Scene();
+ this._scenePeriphery = new Scene();
+ }
+ get planes() {
+ return this._planes;
+ }
+ get planesOld() {
+ return this._planesOld;
+ }
+ get planesPeriphery() {
+ return this._planesPeriphery;
+ }
+ get scene() {
+ return this._scene;
+ }
+ get sceneOld() {
+ return this._sceneOld;
+ }
+ get scenePeriphery() {
+ return this._scenePeriphery;
+ }
+ updateImagePlanes(planes) {
+ this._dispose(this._planesOld, this.sceneOld);
+ for (const key in this._planes) {
+ if (!this._planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = this._planes[key];
+ this._scene.remove(plane);
+ this._sceneOld.add(plane);
+ }
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ this._scene.add(planes[key]);
+ }
+ this._planesOld = this._planes;
+ this._planes = planes;
+ }
+ addImagePlanes(planes) {
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ this._scene.add(plane);
+ this._planes[key] = plane;
+ }
+ }
+ addImagePlanesOld(planes) {
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ this._sceneOld.add(plane);
+ this._planesOld[key] = plane;
+ }
+ }
+ setImagePlanes(planes) {
+ this._clear();
+ this.addImagePlanes(planes);
+ }
+ addPeripheryPlanes(planes) {
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ this._scenePeriphery.add(plane);
+ this._planesPeriphery[key] = plane;
+ }
+ }
+ setPeripheryPlanes(planes) {
+ this._clearPeriphery();
+ this.addPeripheryPlanes(planes);
+ }
+ setImagePlanesOld(planes) {
+ this._clearOld();
+ this.addImagePlanesOld(planes);
+ }
+ clear() {
+ this._clear();
+ this._clearOld();
+ }
+ _clear() {
+ this._dispose(this._planes, this._scene);
+ this._planes = {};
+ }
+ _clearOld() {
+ this._dispose(this._planesOld, this._sceneOld);
+ this._planesOld = {};
+ }
+ _clearPeriphery() {
+ this._dispose(this._planesPeriphery, this._scenePeriphery);
+ this._planesPeriphery = {};
+ }
+ _dispose(planes, scene) {
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ scene.remove(plane);
+ plane.geometry.dispose();
+ plane.material.dispose();
+ let texture = plane.material.uniforms.projectorTex.value;
+ if (texture != null) {
+ texture.dispose();
+ }
+ }
+ }
+}
+
+class ImageGLRenderer {
+ constructor() {
+ this._factory = new MeshFactory();
+ this._scene = new MeshScene();
+ this._alpha = 0;
+ this._alphaOld = 0;
+ this._fadeOutSpeed = 0.05;
+ this._currentKey = null;
+ this._previousKey = null;
+ this._providerDisposers = {};
+ this._frameId = 0;
+ this._needsRender = false;
+ }
+ get frameId() {
+ return this._frameId;
+ }
+ get needsRender() {
+ return this._needsRender;
+ }
+ indicateNeedsRender() {
+ this._needsRender = true;
+ }
+ addPeripheryPlane(image, transform) {
+ const mesh = this._factory.createMesh(image, transform);
+ const planes = {};
+ planes[image.id] = mesh;
+ this._scene.addPeripheryPlanes(planes);
+ this._needsRender = true;
+ }
+ clearPeripheryPlanes() {
+ this._scene.setPeripheryPlanes({});
+ this._needsRender = true;
+ }
+ updateFrame(frame) {
+ this._updateFrameId(frame.id);
+ this._needsRender = this._updateAlpha(frame.state.alpha) || this._needsRender;
+ this._needsRender = this._updateAlphaOld(frame.state.alpha) || this._needsRender;
+ this._needsRender = this._updateImagePlanes(frame.state) || this._needsRender;
+ }
+ setTextureProvider(key, provider) {
+ if (key !== this._currentKey) {
+ return;
+ }
+ let createdSubscription = provider.textureCreated$
+ .subscribe((texture) => {
+ this._updateTexture(texture);
+ });
+ let updatedSubscription = provider.textureUpdated$
+ .subscribe((updated) => {
+ this._needsRender = true;
+ });
+ let dispose = () => {
+ createdSubscription.unsubscribe();
+ updatedSubscription.unsubscribe();
+ provider.dispose();
+ };
+ if (key in this._providerDisposers) {
+ let disposeProvider = this._providerDisposers[key];
+ disposeProvider();
+ delete this._providerDisposers[key];
+ }
+ this._providerDisposers[key] = dispose;
+ }
+ updateTextureImage(imageElement, image) {
+ this._needsRender = true;
+ const planes = this._extend({}, this._scene.planes, this._scene.planesOld, this._scene.planesPeriphery);
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ if (key !== image.id) {
+ continue;
+ }
+ const plane = planes[key];
+ let material = plane.material;
+ let texture = material.uniforms.projectorTex.value;
+ texture.image = imageElement;
+ texture.needsUpdate = true;
+ }
+ }
+ render(perspectiveCamera, renderer) {
+ const planes = this._scene.planes;
+ const planesOld = this._scene.planesOld;
+ const planesPeriphery = this._scene.planesPeriphery;
+ const planeAlpha = Object.keys(planesOld).length ? 1 : this._alpha;
+ const peripheryAlpha = Object.keys(planesOld).length ? 1 : Math.floor(this._alpha);
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ plane.material.uniforms.opacity.value = planeAlpha;
+ }
+ for (const key in planesOld) {
+ if (!planesOld.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planesOld[key];
+ plane.material.uniforms.opacity.value = this._alphaOld;
+ }
+ for (const key in planesPeriphery) {
+ if (!planesPeriphery.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planesPeriphery[key];
+ plane.material.uniforms.opacity.value = peripheryAlpha;
+ }
+ renderer.render(this._scene.scenePeriphery, perspectiveCamera);
+ renderer.render(this._scene.scene, perspectiveCamera);
+ renderer.render(this._scene.sceneOld, perspectiveCamera);
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ plane.material.uniforms.opacity.value = this._alpha;
+ }
+ renderer.render(this._scene.scene, perspectiveCamera);
+ }
+ clearNeedsRender() {
+ this._needsRender = false;
+ }
+ dispose() {
+ this._scene.clear();
+ }
+ _updateFrameId(frameId) {
+ this._frameId = frameId;
+ }
+ _updateAlpha(alpha) {
+ if (alpha === this._alpha) {
+ return false;
+ }
+ this._alpha = alpha;
+ return true;
+ }
+ _updateAlphaOld(alpha) {
+ if (alpha < 1 || this._alphaOld === 0) {
+ return false;
+ }
+ this._alphaOld = Math.max(0, this._alphaOld - this._fadeOutSpeed);
+ return true;
+ }
+ _updateImagePlanes(state) {
+ if (state.currentImage == null ||
+ state.currentImage.id === this._currentKey) {
+ return false;
+ }
+ let previousKey = state.previousImage != null ? state.previousImage.id : null;
+ let currentKey = state.currentImage.id;
+ if (this._previousKey !== previousKey &&
+ this._previousKey !== currentKey &&
+ this._previousKey in this._providerDisposers) {
+ let disposeProvider = this._providerDisposers[this._previousKey];
+ disposeProvider();
+ delete this._providerDisposers[this._previousKey];
+ }
+ if (previousKey != null) {
+ if (previousKey !== this._currentKey && previousKey !== this._previousKey) {
+ let previousMesh = this._factory.createMesh(state.previousImage, state.previousTransform);
+ const previousPlanes = {};
+ previousPlanes[previousKey] = previousMesh;
+ this._scene.updateImagePlanes(previousPlanes);
+ }
+ this._previousKey = previousKey;
+ }
+ this._currentKey = currentKey;
+ let currentMesh = this._factory.createMesh(state.currentImage, state.currentTransform);
+ const planes = {};
+ planes[currentKey] = currentMesh;
+ this._scene.updateImagePlanes(planes);
+ this._alphaOld = 1;
+ return true;
+ }
+ _updateTexture(texture) {
+ this._needsRender = true;
+ const planes = this._scene.planes;
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ let material = plane.material;
+ let oldTexture = material.uniforms.projectorTex.value;
+ material.uniforms.projectorTex.value = null;
+ oldTexture.dispose();
+ material.uniforms.projectorTex.value = texture;
+ }
+ }
+ _extend(dest, ...sources) {
+ for (const src of sources) {
+ for (const k in src) {
+ if (!src.hasOwnProperty(k)) {
+ continue;
+ }
+ dest[k] = src[k];
+ }
+ }
+ return dest;
+ }
+}
+
+var RenderPass$1;
+(function (RenderPass) {
+ RenderPass[RenderPass["Background"] = 0] = "Background";
+ RenderPass[RenderPass["Opaque"] = 1] = "Opaque";
+})(RenderPass$1 || (RenderPass$1 = {}));
+
+/**
+ * @class ImageTileLoader
+ *
+ * @classdesc Represents a loader of image tiles.
+ */
+class TileLoader {
+ /**
+ * Create a new image image tile loader instance.
+ *
+ * @param {APIWrapper} _api - API wrapper.
+ */
+ constructor(_api) {
+ this._api = _api;
+ this._urls$ = new Map();
+ }
+ /**
+ * Retrieve an image tile.
+ *
+ * @param {string} url - URL to the image tile resource
+ */
+ getImage$(url) {
+ let aborter;
+ const abort = new Promise((_, reject) => {
+ aborter = reject;
+ });
+ return [Observable.create((subscriber) => {
+ this._api.data
+ .getImageBuffer(url, abort)
+ .then((buffer) => {
+ aborter = null;
+ const image = new Image();
+ image.crossOrigin = "Anonymous";
+ image.onload = () => {
+ window.URL.revokeObjectURL(image.src);
+ subscriber.next(image);
+ subscriber.complete();
+ };
+ image.onerror = () => {
+ aborter = null;
+ window.URL.revokeObjectURL(image.src);
+ subscriber.error(new Error(`Failed to load image tile`));
+ };
+ const blob = new Blob([buffer]);
+ image.src = window.URL.createObjectURL(blob);
+ }, (error) => {
+ aborter = null;
+ subscriber.error(error);
+ });
+ }),
+ () => {
+ if (!!aborter) {
+ aborter();
+ }
+ }];
+ }
+ getURLs$(imageId, level) {
+ const uniqueId = this._inventId(imageId, level);
+ if (this._urls$.has(uniqueId)) {
+ return this._urls$.get(uniqueId);
+ }
+ const request = { imageId, z: level };
+ const urls$ = this._api
+ .getImageTiles$(request)
+ .pipe(map(contract => contract.node), finalize(() => {
+ this._urls$.delete(uniqueId);
+ }), publish(), refCount());
+ this._urls$.set(uniqueId, urls$);
+ return urls$;
+ }
+ _inventId(imageId, level) {
+ return `${imageId}-${level}`;
+ }
+}
+
+/**
+ * @class ImageTileStore
+ *
+ * @classdesc Represents a store for image tiles.
+ */
+class TileStore {
+ /**
+ * Create a new image image tile store instance.
+ */
+ constructor() {
+ this._tiles = new Map();
+ this._urlLevels = new Set();
+ this._urls = new Map();
+ }
+ /**
+ * Add an image tile to the store.
+ *
+ * @param {string} id - The identifier for the image tile.
+ * @param {HTMLImageElement} image - The image tile.
+ */
+ add(id, image) {
+ if (this._tiles.has(id)) {
+ throw new Error(`Image tile already stored (${id})`);
+ }
+ this._tiles.set(id, image);
+ }
+ addURLs(level, ents) {
+ const urls = this._urls;
+ for (const ent of ents) {
+ const id = this.inventId(ent);
+ if (this._urls.has(id)) {
+ throw new Error(`URL already stored (${id})`);
+ }
+ urls.set(id, ent.url);
+ }
+ this._urlLevels.add(level);
+ }
+ /**
+ * Dispose the store.
+ *
+ * @description Disposes all cached assets.
+ */
+ dispose() {
+ this._tiles
+ .forEach(image => window.URL.revokeObjectURL(image.src));
+ this._tiles.clear();
+ this._urls.clear();
+ this._urlLevels.clear();
+ }
+ /**
+ * Get an image tile from the store.
+ *
+ * @param {string} id - The identifier for the tile.
+ * @param {number} level - The level of the tile.
+ */
+ get(id) {
+ return this._tiles.get(id);
+ }
+ getURL(id) {
+ return this._urls.get(id);
+ }
+ /**
+ * Check if an image tile exist in the store.
+ *
+ * @param {string} id - The identifier for the tile.
+ * @param {number} level - The level of the tile.
+ */
+ has(id) {
+ return this._tiles.has(id);
+ }
+ hasURL(id) {
+ return this._urls.has(id);
+ }
+ hasURLLevel(level) {
+ return this._urlLevels.has(level);
+ }
+ /**
+ * Create a unique tile id from a tile.
+ *
+ * @description Tile ids are used as a hash for
+ * storing the tile in a dictionary.
+ *
+ * @param {ImageTileEnt} tile - The tile.
+ * @returns {string} Unique id.
+ */
+ inventId(tile) {
+ return `${tile.z}-${tile.x}-${tile.y}`;
+ }
+}
+
+/**
+ * @class RegionOfInterestCalculator
+ *
+ * @classdesc Represents a calculator for regions of interest.
+ */
+class RegionOfInterestCalculator {
+ constructor() {
+ this._viewportCoords = new ViewportCoords();
+ }
+ /**
+ * Compute a region of interest based on the current render camera
+ * and the viewport size.
+ *
+ * @param {RenderCamera} renderCamera - Render camera used for unprojections.
+ * @param {ViewportSize} size - Viewport size in pixels.
+ * @param {Transform} transform - Transform used for projections.
+ *
+ * @returns {TileRegionOfInterest} A region of interest.
+ */
+ computeRegionOfInterest(renderCamera, size, transform) {
+ const viewportBoundaryPoints = this._viewportBoundaryPoints(4);
+ const bbox = this._viewportPointsBoundingBox(viewportBoundaryPoints, renderCamera, transform);
+ this._clipBoundingBox(bbox);
+ const viewportPixelWidth = 2 / size.width;
+ const viewportPixelHeight = 2 / size.height;
+ const centralViewportPixel = [
+ [-0.5 * viewportPixelWidth, 0.5 * viewportPixelHeight],
+ [0.5 * viewportPixelWidth, 0.5 * viewportPixelHeight],
+ [0.5 * viewportPixelWidth, -0.5 * viewportPixelHeight],
+ [-0.5 * viewportPixelWidth, -0.5 * viewportPixelHeight],
+ ];
+ const cpbox = this._viewportPointsBoundingBox(centralViewportPixel, renderCamera, transform);
+ const inverted = cpbox.minX < cpbox.maxX;
+ return {
+ bbox: bbox,
+ pixelHeight: cpbox.maxY - cpbox.minY,
+ pixelWidth: cpbox.maxX - cpbox.minX + (inverted ? 0 : 1),
+ };
+ }
+ _viewportBoundaryPoints(pointsPerSide) {
+ const points = [];
+ const os = [[-1, 1], [1, 1], [1, -1], [-1, -1]];
+ const ds = [[2, 0], [0, -2], [-2, 0], [0, 2]];
+ for (let side = 0; side < 4; ++side) {
+ const o = os[side];
+ const d = ds[side];
+ for (let i = 0; i < pointsPerSide; ++i) {
+ points.push([o[0] + d[0] * i / pointsPerSide,
+ o[1] + d[1] * i / pointsPerSide]);
+ }
+ }
+ return points;
+ }
+ _viewportPointsBoundingBox(viewportPoints, renderCamera, transform) {
+ const basicPoints = viewportPoints
+ .map((point) => {
+ return this._viewportCoords
+ .viewportToBasic(point[0], point[1], transform, renderCamera.perspective);
+ });
+ if (isSpherical(transform.cameraType)) {
+ return this._boundingBoxSpherical(basicPoints);
+ }
+ else {
+ return this._boundingBox(basicPoints);
+ }
+ }
+ _boundingBox(points) {
+ const bbox = {
+ maxX: Number.NEGATIVE_INFINITY,
+ maxY: Number.NEGATIVE_INFINITY,
+ minX: Number.POSITIVE_INFINITY,
+ minY: Number.POSITIVE_INFINITY,
+ };
+ for (let i = 0; i < points.length; ++i) {
+ bbox.minX = Math.min(bbox.minX, points[i][0]);
+ bbox.maxX = Math.max(bbox.maxX, points[i][0]);
+ bbox.minY = Math.min(bbox.minY, points[i][1]);
+ bbox.maxY = Math.max(bbox.maxY, points[i][1]);
+ }
+ return bbox;
+ }
+ _boundingBoxSpherical(points) {
+ const xs = [];
+ const ys = [];
+ for (let i = 0; i < points.length; ++i) {
+ xs.push(points[i][0]);
+ ys.push(points[i][1]);
+ }
+ xs.sort((a, b) => { return this._sign(a - b); });
+ ys.sort((a, b) => { return this._sign(a - b); });
+ const intervalX = this._intervalSpherical(xs);
+ return {
+ maxX: intervalX[1],
+ maxY: ys[ys.length - 1],
+ minX: intervalX[0],
+ minY: ys[0],
+ };
+ }
+ /**
+ * Find the max interval between consecutive numbers.
+ * Assumes numbers are between 0 and 1, sorted and that
+ * x is equivalent to x + 1.
+ */
+ _intervalSpherical(xs) {
+ let maxdx = 0;
+ let maxi = -1;
+ for (let i = 0; i < xs.length - 1; ++i) {
+ const dx = xs[i + 1] - xs[i];
+ if (dx > maxdx) {
+ maxdx = dx;
+ maxi = i;
+ }
+ }
+ const loopdx = xs[0] + 1 - xs[xs.length - 1];
+ if (loopdx > maxdx) {
+ return [xs[0], xs[xs.length - 1]];
+ }
+ else {
+ return [xs[maxi + 1], xs[maxi]];
+ }
+ }
+ _clipBoundingBox(bbox) {
+ bbox.minX = Math.max(0, Math.min(1, bbox.minX));
+ bbox.maxX = Math.max(0, Math.min(1, bbox.maxX));
+ bbox.minY = Math.max(0, Math.min(1, bbox.minY));
+ bbox.maxY = Math.max(0, Math.min(1, bbox.maxY));
+ }
+ _sign(n) {
+ return n > 0 ? 1 : n < 0 ? -1 : 0;
+ }
+}
+
+const TILE_MIN_REQUEST_LEVEL = 11;
+const TILE_SIZE = 1024;
+
+function clamp(value, min, max) {
+ return Math.max(min, Math.min(max, value));
+}
+function levelTilePixelSize(level) {
+ return TILE_SIZE / levelScale(level);
+}
+function levelScale(level) {
+ return Math.pow(2, level.z - level.max);
+}
+function rawImageLevel(size) {
+ const s = Math.max(size.w, size.h);
+ return Math.log(s) / Math.log(2);
+}
+function baseImageLevel(size) {
+ return Math.ceil(rawImageLevel(size));
+}
+function clampedImageLevel(size, min, max) {
+ return Math.max(min, Math.min(max, baseImageLevel(size)));
+}
+function basicToTileCoords2D(basic, size, level) {
+ const tilePixelSize = levelTilePixelSize(level);
+ const w = size.w;
+ const h = size.h;
+ const maxX = Math.ceil(w / tilePixelSize) - 1;
+ const maxY = Math.ceil(h / tilePixelSize) - 1;
+ const x = clamp(Math.floor(w * basic[0] / tilePixelSize), 0, maxX);
+ const y = clamp(Math.floor(h * basic[1] / tilePixelSize), 0, maxY);
+ return { x, y };
+}
+function tileToPixelCoords2D(tile, size, level) {
+ const scale = 1 / levelScale(level);
+ const scaledTS = scale * TILE_SIZE;
+ const x = scaledTS * tile.x;
+ const y = scaledTS * tile.y;
+ const w = Math.min(scaledTS, size.w - x);
+ const h = Math.min(scaledTS, size.h - y);
+ return { h, x, y, w };
+}
+function hasOverlap1D(low, base, scale) {
+ return (scale * low <= base &&
+ base < scale * (low + 1));
+}
+function hasOverlap2D(tile1, tile2) {
+ if (tile1.z === tile2.z) {
+ return tile1.x === tile2.x && tile1.y === tile2.y;
+ }
+ const low = tile1.z < tile2.z ? tile1 : tile2;
+ const base = tile1.z < tile2.z ? tile2 : tile1;
+ const scale = 1 / levelScale({ max: base.z, z: low.z });
+ const overlapX = hasOverlap1D(low.x, base.x, scale);
+ const overlapY = hasOverlap1D(low.y, base.y, scale);
+ return overlapX && overlapY;
+}
+function cornersToTilesCoords2D(topLeft, bottomRight, size, level) {
+ const xs = [];
+ if (topLeft.x > bottomRight.x) {
+ const tilePixelSize = levelTilePixelSize(level);
+ const maxX = Math.ceil(size.w / tilePixelSize) - 1;
+ for (let x = topLeft.x; x <= maxX; x++) {
+ xs.push(x);
+ }
+ for (let x = 0; x <= bottomRight.x; x++) {
+ xs.push(x);
+ }
+ }
+ else {
+ for (let x = topLeft.x; x <= bottomRight.x; x++) {
+ xs.push(x);
+ }
+ }
+ const tiles = [];
+ for (const x of xs) {
+ for (let y = topLeft.y; y <= bottomRight.y; y++) {
+ tiles.push({ x, y });
+ }
+ }
+ return tiles;
+}
+function verifySize(size) {
+ return size.w > 0 && size.h > 0;
+}
+
+/**
+ * @class TextureProvider
+ *
+ * @classdesc Represents a provider of textures.
+ */
+class TextureProvider {
+ /**
+ * Create a new image texture provider instance.
+ *
+ * @param {string} imageId - The identifier of the image for which to request tiles.
+ * @param {number} width - The full width of the original image.
+ * @param {number} height - The full height of the original image.
+ * @param {HTMLImageElement} background - Image to use as background.
+ * @param {TileLoader} loader - Loader for retrieving tiles.
+ * @param {TileStore} store - Store for saving tiles.
+ * @param {THREE.WebGLRenderer} renderer - Renderer used for rendering tiles to texture.
+ */
+ constructor(imageId, width, height, background, loader, store, renderer) {
+ const size = { h: height, w: width };
+ if (!verifySize(size)) {
+ console.warn(`Original image size (${width}, ${height}) ` +
+ `is invalid (${imageId}). Tiles will not be loaded.`);
+ }
+ this._imageId = imageId;
+ this._size = size;
+ this._level = {
+ max: baseImageLevel(this._size),
+ z: -1,
+ };
+ this._holder = new SubscriptionHolder();
+ this._updated$ = new Subject();
+ this._createdSubject$ = new Subject();
+ this._created$ = this._createdSubject$
+ .pipe(publishReplay(1), refCount());
+ this._holder.push(this._created$.subscribe(() => { }));
+ this._hasSubject$ = new Subject();
+ this._has$ = this._hasSubject$
+ .pipe(startWith(false), publishReplay(1), refCount());
+ this._holder.push(this._has$.subscribe(() => { }));
+ this._renderedLevel = new Set();
+ this._rendered = new Map();
+ this._subscriptions = new Map();
+ this._urlSubscriptions = new Map();
+ this._loader = loader;
+ this._store = store;
+ this._background = background;
+ this._renderer = renderer;
+ this._aborts = [];
+ this._render = null;
+ this._disposed = false;
+ }
+ /**
+ * Get disposed.
+ *
+ * @returns {boolean} Value indicating whether provider has
+ * been disposed.
+ */
+ get disposed() {
+ return this._disposed;
+ }
+ /**
+ * Get hasTexture$.
+ *
+ * @returns {Observable<boolean>} Observable emitting
+ * values indicating when the existance of a texture
+ * changes.
+ */
+ get hasTexture$() {
+ return this._has$;
+ }
+ /**
+ * Get id.
+ *
+ * @returns {boolean} The identifier of the image for
+ * which to render textures.
+ */
+ get id() {
+ return this._imageId;
+ }
+ /**
+ * Get textureUpdated$.
+ *
+ * @returns {Observable<boolean>} Observable emitting
+ * values when an existing texture has been updated.
+ */
+ get textureUpdated$() {
+ return this._updated$;
+ }
+ /**
+ * Get textureCreated$.
+ *
+ * @returns {Observable<boolean>} Observable emitting
+ * values when a new texture has been created.
+ */
+ get textureCreated$() {
+ return this._created$;
+ }
+ /**
+ * Abort all outstanding image tile requests.
+ */
+ abort() {
+ this._subscriptions.forEach(sub => sub.unsubscribe());
+ this._subscriptions.clear();
+ for (const abort of this._aborts) {
+ abort();
+ }
+ this._aborts = [];
+ }
+ /**
+ * Dispose the provider.
+ *
+ * @description Disposes all cached assets and
+ * aborts all outstanding image tile requests.
+ */
+ dispose() {
+ if (this._disposed) {
+ console.warn(`Texture already disposed (${this._imageId})`);
+ return;
+ }
+ this._urlSubscriptions.forEach(sub => sub.unsubscribe());
+ this._urlSubscriptions.clear();
+ this.abort();
+ if (this._render != null) {
+ this._render.target.dispose();
+ this._render.target = null;
+ this._render.camera = null;
+ this._render = null;
+ }
+ this._store.dispose();
+ this._holder.unsubscribe();
+ this._renderedLevel.clear();
+ this._background = null;
+ this._renderer = null;
+ this._disposed = true;
+ }
+ /**
+ * Set the region of interest.
+ *
+ * @description When the region of interest is set the
+ * the tile level is determined and tiles for the region
+ * are fetched from the store or the loader and renderedLevel
+ * to the texture.
+ *
+ * @param {TileRegionOfInterest} roi - Spatial edges to cache.
+ */
+ setRegionOfInterest(roi) {
+ if (!verifySize(this._size)) {
+ return;
+ }
+ const virtualWidth = 1 / roi.pixelWidth;
+ const virtualHeight = 1 / roi.pixelHeight;
+ const level = clampedImageLevel({ h: virtualHeight, w: virtualWidth }, TILE_MIN_REQUEST_LEVEL, this._level.max);
+ if (level !== this._level.z) {
+ this.abort();
+ this._level.z = level;
+ this._renderedLevel.clear();
+ this._rendered
+ .forEach((tile, id) => {
+ if (tile.z !== level) {
+ return;
+ }
+ this._renderedLevel.add(id);
+ });
+ }
+ if (this._render == null) {
+ this._initRender();
+ }
+ const topLeft = basicToTileCoords2D([roi.bbox.minX, roi.bbox.minY], this._size, this._level);
+ const bottomRight = basicToTileCoords2D([roi.bbox.maxX, roi.bbox.maxY], this._size, this._level);
+ const tiles = cornersToTilesCoords2D(topLeft, bottomRight, this._size, this._level);
+ this._fetchTiles(level, tiles);
+ }
+ /**
+ * Retrieve an image tile.
+ *
+ * @description Retrieve an image tile and render it to the
+ * texture. Add the tile to the store and emit to the updated
+ * observable.
+ *
+ * @param {ImageTileEnt} tile - The tile ent.
+ */
+ _fetchTile(tile) {
+ const getTile = this._loader.getImage$(tile.url);
+ const tile$ = getTile[0];
+ const abort = getTile[1];
+ this._aborts.push(abort);
+ const tileId = this._store.inventId(tile);
+ const subscription = tile$.subscribe((image) => {
+ const pixels = tileToPixelCoords2D(tile, this._size, this._level);
+ this._renderToTarget(pixels, image);
+ this._subscriptions.delete(tileId);
+ this._removeFromArray(abort, this._aborts);
+ this._markRendered(tile);
+ this._store.add(tileId, image);
+ this._updated$.next(true);
+ }, (error) => {
+ this._subscriptions.delete(tileId);
+ this._removeFromArray(abort, this._aborts);
+ console.error(error);
+ });
+ if (!subscription.closed) {
+ this._subscriptions.set(tileId, subscription);
+ }
+ }
+ /**
+ * Fetch image tiles.
+ *
+ * @description Retrieve a image tiles and render them to the
+ * texture. Retrieve from store if it exists, otherwise retrieve
+ * from loader.
+ *
+ * @param {Array<TileCoords2D>} tiles - Array of tile coordinates to
+ * retrieve.
+ */
+ _fetchTiles(level, tiles) {
+ const urls$ = this._store.hasURLLevel(level) ?
+ of(undefined) :
+ this._loader
+ .getURLs$(this._imageId, level)
+ .pipe(tap(ents => {
+ if (!this._store.hasURLLevel(level)) {
+ this._store.addURLs(level, ents);
+ }
+ }));
+ const subscription = urls$.subscribe(() => {
+ if (level !== this._level.z) {
+ return;
+ }
+ for (const tile of tiles) {
+ const ent = {
+ x: tile.x,
+ y: tile.y,
+ z: level,
+ url: null,
+ };
+ const id = this._store.inventId(ent);
+ if (this._renderedLevel.has(id) ||
+ this._subscriptions.has(id)) {
+ continue;
+ }
+ if (this._store.has(id)) {
+ const pixels = tileToPixelCoords2D(tile, this._size, this._level);
+ this._renderToTarget(pixels, this._store.get(id));
+ this._markRendered(ent);
+ this._updated$.next(true);
+ continue;
+ }
+ ent.url = this._store.getURL(id);
+ this._fetchTile(ent);
+ }
+ this._urlSubscriptions.delete(level);
+ }, (error) => {
+ this._urlSubscriptions.delete(level);
+ console.error(error);
+ });
+ if (!subscription.closed) {
+ this._urlSubscriptions.set(level, subscription);
+ }
+ }
+ _initRender() {
+ const dx = this._size.w / 2;
+ const dy = this._size.h / 2;
+ const near = -1;
+ const far = 1;
+ const camera = new OrthographicCamera(-dx, dx, dy, -dy, near, far);
+ camera.position.z = 1;
+ const gl = this._renderer.getContext();
+ const maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);
+ const backgroundSize = Math.max(this._size.w, this._size.h);
+ const scale = maxTextureSize > backgroundSize ?
+ 1 : maxTextureSize / backgroundSize;
+ const targetWidth = Math.floor(scale * this._size.w);
+ const targetHeight = Math.floor(scale * this._size.h);
+ const target = new WebGLRenderTarget(targetWidth, targetHeight, {
+ depthBuffer: false,
+ format: RGBFormat,
+ magFilter: LinearFilter,
+ minFilter: LinearFilter,
+ stencilBuffer: false,
+ });
+ this._render = { camera, target };
+ const pixels = tileToPixelCoords2D({ x: 0, y: 0 }, this._size, { max: this._level.max, z: 0 });
+ this._renderToTarget(pixels, this._background);
+ this._createdSubject$.next(target.texture);
+ this._hasSubject$.next(true);
+ }
+ /**
+ * Mark a tile as rendered.
+ *
+ * @description Clears tiles marked as rendered in other
+ * levels of the tile pyramid if they overlap the
+ * newly rendered tile.
+ *
+ * @param {Arrary<number>} tile - The tile ent.
+ */
+ _markRendered(tile) {
+ const others = Array.from(this._rendered.entries())
+ .filter(([_, t]) => {
+ return t.z !== tile.z;
+ });
+ for (const [otherId, other] of others) {
+ if (hasOverlap2D(tile, other)) {
+ this._rendered.delete(otherId);
+ }
+ }
+ const id = this._store.inventId(tile);
+ this._rendered.set(id, tile);
+ this._renderedLevel.add(id);
+ }
+ /**
+ * Remove an item from an array if it exists in array.
+ *
+ * @param {T} item - Item to remove.
+ * @param {Array<T>} array - Array from which item should be removed.
+ */
+ _removeFromArray(item, array) {
+ const index = array.indexOf(item);
+ if (index !== -1) {
+ array.splice(index, 1);
+ }
+ }
+ /**
+ * Render an image tile to the target texture.
+ *
+ * @param {ImageTileEnt} tile - Tile ent.
+ * @param {HTMLImageElement} image - The image tile to render.
+ */
+ _renderToTarget(pixel, image) {
+ const texture = new Texture(image);
+ texture.minFilter = LinearFilter;
+ texture.needsUpdate = true;
+ const geometry = new PlaneGeometry(pixel.w, pixel.h);
+ const material = new MeshBasicMaterial({
+ map: texture,
+ side: FrontSide,
+ });
+ const mesh = new Mesh(geometry, material);
+ mesh.position.x = -this._size.w / 2 + pixel.x + pixel.w / 2;
+ mesh.position.y = this._size.h / 2 - pixel.y - pixel.h / 2;
+ const scene = new Scene();
+ scene.add(mesh);
+ const target = this._renderer.getRenderTarget();
+ this._renderer.resetState();
+ this._renderer.setRenderTarget(this._render.target);
+ this._renderer.render(scene, this._render.camera);
+ this._renderer.setRenderTarget(target);
+ scene.remove(mesh);
+ geometry.dispose();
+ material.dispose();
+ texture.dispose();
+ }
+}
+
+var State;
+(function (State) {
+ State[State["Custom"] = 0] = "Custom";
+ State[State["Earth"] = 1] = "Earth";
+ State[State["Traversing"] = 2] = "Traversing";
+ State[State["Waiting"] = 3] = "Waiting";
+ State[State["WaitingInteractively"] = 4] = "WaitingInteractively";
+})(State || (State = {}));
+
+class ImageComponent extends Component {
+ constructor(name, container, navigator) {
+ super(name, container, navigator);
+ this._imageTileLoader = new TileLoader(navigator.api);
+ this._roiCalculator = new RegionOfInterestCalculator();
+ this._rendererOperation$ = new Subject();
+ this._rendererCreator$ = new Subject();
+ this._rendererDisposer$ = new Subject();
+ this._renderer$ = this._rendererOperation$.pipe(scan((renderer, operation) => {
+ return operation(renderer);
+ }, null), filter((renderer) => {
+ return renderer != null;
+ }), distinctUntilChanged(undefined, (renderer) => {
+ return renderer.frameId;
+ }));
+ this._rendererCreator$.pipe(map(() => {
+ return (renderer) => {
+ if (renderer != null) {
+ throw new Error("Multiple image plane states can not be created at the same time");
+ }
+ return new ImageGLRenderer();
+ };
+ }))
+ .subscribe(this._rendererOperation$);
+ this._rendererDisposer$.pipe(map(() => {
+ return (renderer) => {
+ renderer.dispose();
+ return null;
+ };
+ }))
+ .subscribe(this._rendererOperation$);
+ }
+ _activate() {
+ const subs = this._subscriptions;
+ subs.push(this._renderer$.pipe(map((renderer) => {
+ const renderHash = {
+ name: this._name,
+ renderer: {
+ frameId: renderer.frameId,
+ needsRender: renderer.needsRender,
+ render: renderer.render.bind(renderer),
+ pass: RenderPass$1.Background,
+ },
+ };
+ renderer.clearNeedsRender();
+ return renderHash;
+ }))
+ .subscribe(this._container.glRenderer.render$));
+ this._rendererCreator$.next(null);
+ subs.push(this._navigator.stateService.currentState$.pipe(map((frame) => {
+ return (renderer) => {
+ renderer.updateFrame(frame);
+ return renderer;
+ };
+ }))
+ .subscribe(this._rendererOperation$));
+ const textureProvider$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+ return active ?
+ this._navigator.stateService.currentState$ :
+ new Subject();
+ }), distinctUntilChanged(undefined, (frame) => {
+ return frame.state.currentImage.id;
+ }), withLatestFrom(this._container.glRenderer.webGLRenderer$), map(([frame, renderer]) => {
+ const state = frame.state;
+ const currentNode = state.currentImage;
+ const currentTransform = state.currentTransform;
+ return new TextureProvider(currentNode.id, currentTransform.basicWidth, currentTransform.basicHeight, currentNode.image, this._imageTileLoader, new TileStore(), renderer);
+ }), publishReplay(1), refCount());
+ subs.push(textureProvider$.subscribe(() => { }));
+ subs.push(textureProvider$.pipe(map((provider) => {
+ return (renderer) => {
+ renderer.setTextureProvider(provider.id, provider);
+ return renderer;
+ };
+ }))
+ .subscribe(this._rendererOperation$));
+ subs.push(textureProvider$.pipe(pairwise())
+ .subscribe((pair) => {
+ const previous = pair[0];
+ previous.abort();
+ }));
+ const roiTrigger$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+ return active ?
+ combineLatest(this._navigator.stateService.state$, this._navigator.stateService.inTranslation$) :
+ new Subject();
+ }), switchMap(([state, inTranslation]) => {
+ const streetState = state === State.Traversing ||
+ state === State.Waiting ||
+ state === State.WaitingInteractively;
+ const active = streetState && !inTranslation;
+ return active ?
+ this._container.renderService.renderCameraFrame$ :
+ empty();
+ }), map((camera) => {
+ return {
+ camera,
+ height: camera.size.height.valueOf(),
+ lookat: camera.camera.lookat.clone(),
+ width: camera.size.width.valueOf(),
+ zoom: camera.zoom.valueOf(),
+ };
+ }), pairwise(), map(([pl0, pl1]) => {
+ const stalled = pl0.width === pl1.width &&
+ pl0.height === pl1.height &&
+ pl0.zoom === pl1.zoom &&
+ pl0.lookat.equals(pl1.lookat);
+ return { camera: pl1.camera, stalled };
+ }), distinctUntilChanged((x, y) => {
+ return x.stalled === y.stalled;
+ }), filter((camera) => {
+ return camera.stalled;
+ }), withLatestFrom(this._container.renderService.size$, this._navigator.stateService.currentTransform$));
+ subs.push(textureProvider$.pipe(switchMap((provider) => {
+ return roiTrigger$.pipe(map(([stalled, size, transform]) => {
+ const camera = stalled.camera;
+ const basic = new ViewportCoords()
+ .viewportToBasic(0, 0, transform, camera.perspective);
+ if (basic[0] < 0 ||
+ basic[1] < 0 ||
+ basic[0] > 1 ||
+ basic[1] > 1) {
+ return undefined;
+ }
+ return [
+ this._roiCalculator
+ .computeRegionOfInterest(camera, size, transform),
+ provider,
+ ];
+ }), filter((args) => {
+ return !!args;
+ }));
+ }), filter((args) => {
+ return !args[1].disposed;
+ }))
+ .subscribe(([roi, provider]) => {
+ provider.setRegionOfInterest(roi);
+ }));
+ const hasTexture$ = textureProvider$
+ .pipe(switchMap((provider) => {
+ return provider.hasTexture$;
+ }), startWith(false), publishReplay(1), refCount());
+ subs.push(hasTexture$.subscribe(() => { }));
+ subs.push(this._navigator.panService.panImages$.pipe(filter((panNodes) => {
+ return panNodes.length === 0;
+ }), map(() => {
+ return (renderer) => {
+ renderer.clearPeripheryPlanes();
+ return renderer;
+ };
+ }))
+ .subscribe(this._rendererOperation$));
+ const cachedPanNodes$ = this._navigator.panService.panImages$.pipe(switchMap((nts) => {
+ return from(nts).pipe(mergeMap(([n, t]) => {
+ return combineLatest(this._navigator.graphService.cacheImage$(n.id).pipe(catchError((error) => {
+ console.error(`Failed to cache periphery image (${n.id})`, error);
+ return empty();
+ })), of(t));
+ }));
+ }), share());
+ subs.push(cachedPanNodes$.pipe(map(([n, t]) => {
+ return (renderer) => {
+ renderer.addPeripheryPlane(n, t);
+ return renderer;
+ };
+ }))
+ .subscribe(this._rendererOperation$));
+ subs.push(cachedPanNodes$.pipe(mergeMap(([n]) => {
+ return n.cacheImage$().pipe(catchError(() => {
+ return empty();
+ }));
+ }), map((n) => {
+ return (renderer) => {
+ renderer.updateTextureImage(n.image, n);
+ return renderer;
+ };
+ }))
+ .subscribe(this._rendererOperation$));
+ const inTransition$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+ return frame.state.alpha < 1;
+ }), distinctUntilChanged());
+ const panTrigger$ = combineLatest(this._container.mouseService.active$, this._container.touchService.active$, this._navigator.stateService.inMotion$, inTransition$).pipe(map(([mouseActive, touchActive, inMotion, inTransition]) => {
+ return !(mouseActive || touchActive || inMotion || inTransition);
+ }), filter((trigger) => {
+ return trigger;
+ }));
+ subs.push(this._navigator.stateService.state$
+ .pipe(switchMap(state => {
+ return state === State.Traversing ?
+ this._navigator.panService.panImages$ :
+ empty();
+ }), switchMap((nts) => {
+ return panTrigger$.pipe(withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentImage$, this._navigator.stateService.currentTransform$), mergeMap(([, renderCamera, currentNode, currentTransform]) => {
+ return of([
+ renderCamera,
+ currentNode,
+ currentTransform,
+ nts,
+ ]);
+ }));
+ }), switchMap(([camera, cn, ct, nts]) => {
+ const direction = camera.camera.lookat.clone().sub(camera.camera.position);
+ const cd = new Spatial().viewingDirection(cn.rotation);
+ const ca = cd.angleTo(direction);
+ const closest = [ca, undefined];
+ const basic = new ViewportCoords().viewportToBasic(0, 0, ct, camera.perspective);
+ if (basic[0] >= 0 && basic[0] <= 1 && basic[1] >= 0 && basic[1] <= 1) {
+ closest[0] = Number.NEGATIVE_INFINITY;
+ }
+ for (const [n] of nts) {
+ const d = new Spatial().viewingDirection(n.rotation);
+ const a = d.angleTo(direction);
+ if (a < closest[0]) {
+ closest[0] = a;
+ closest[1] = n.id;
+ }
+ }
+ if (!closest[1]) {
+ return empty();
+ }
+ return this._navigator.moveTo$(closest[1]).pipe(catchError(() => {
+ return empty();
+ }));
+ }))
+ .subscribe());
+ }
+ _deactivate() {
+ this._rendererDisposer$.next(null);
+ this._subscriptions.unsubscribe();
+ }
+ _getDefaultConfiguration() {
+ return {};
+ }
+}
+ImageComponent.componentName = "image";
+
+class HandlerBase {
+ /** @ignore */
+ constructor(component, container, navigator) {
+ this._component = component;
+ this._container = container;
+ this._navigator = navigator;
+ this._enabled = false;
+ }
+ /**
+ * Returns a Boolean indicating whether the interaction is enabled.
+ *
+ * @returns {boolean} `true` if the interaction is enabled.
+ */
+ get isEnabled() {
+ return this._enabled;
+ }
+ /**
+ * Enables the interaction.
+ *
+ * @example
+ * ```js
+ * <component-name>.<handler-name>.enable();
+ * ```
+ */
+ enable() {
+ if (this._enabled || !this._component.activated) {
+ return;
+ }
+ this._enable();
+ this._enabled = true;
+ this._component.configure(this._getConfiguration(true));
+ }
+ /**
+ * Disables the interaction.
+ *
+ * @example
+ * ```js
+ * <component-name>.<handler-name>.disable();
+ * ```
+ */
+ disable() {
+ if (!this._enabled) {
+ return;
+ }
+ this._disable();
+ this._enabled = false;
+ if (this._component.activated) {
+ this._component.configure(this._getConfiguration(false));
+ }
+ }
+}
+
+/**
+ * The `KeySequenceNavigationHandler` allows the user to navigate through a sequence using the
+ * following key commands:
+ *
+ * `ALT` + `Up Arrow`: Navigate to next image in the sequence.
+ * `ALT` + `Down Arrow`: Navigate to previous image in sequence.
+ *
+ * @example
+ * ```js
+ * var keyboardComponent = viewer.getComponent("keyboard");
+ *
+ * keyboardComponent.keySequenceNavigation.disable();
+ * keyboardComponent.keySequenceNavigation.enable();
+ *
+ * var isEnabled = keyboardComponent.keySequenceNavigation.isEnabled;
+ * ```
+ */
+class KeySequenceNavigationHandler extends HandlerBase {
+ _enable() {
+ const sequenceEdges$ = this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+ return image.sequenceEdges$;
+ }));
+ this._keyDownSubscription = this._container.keyboardService.keyDown$.pipe(withLatestFrom(sequenceEdges$))
+ .subscribe(([event, edgeStatus]) => {
+ let direction = null;
+ switch (event.keyCode) {
+ case 38: // up
+ direction = NavigationDirection.Next;
+ break;
+ case 40: // down
+ direction = NavigationDirection.Prev;
+ break;
+ default:
+ return;
+ }
+ event.preventDefault();
+ if (!event.altKey || event.shiftKey || !edgeStatus.cached) {
+ return;
+ }
+ for (const edge of edgeStatus.edges) {
+ if (edge.data.direction === direction) {
+ this._navigator.moveTo$(edge.target)
+ .subscribe(undefined, (error) => {
+ if (!(error instanceof CancelMapillaryError)) {
+ console.error(error);
+ }
+ });
+ return;
+ }
+ }
+ });
+ }
+ _disable() {
+ this._keyDownSubscription.unsubscribe();
+ }
+ _getConfiguration(enable) {
+ return { keySequenceNavigation: enable };
+ }
+}
+
+/**
+ * The `KeySpatialNavigationHandler` allows the user to navigate through a sequence using the
+ * following key commands:
+ *
+ * `Up Arrow`: Step forward.
+ * `Down Arrow`: Step backward.
+ * `Left Arrow`: Step to the left.
+ * `Rigth Arrow`: Step to the right.
+ * `SHIFT` + `Down Arrow`: Turn around.
+ * `SHIFT` + `Left Arrow`: Turn to the left.
+ * `SHIFT` + `Rigth Arrow`: Turn to the right.
+ *
+ * @example
+ * ```js
+ * var keyboardComponent = viewer.getComponent("keyboard");
+ *
+ * keyboardComponent.keySpatialNavigation.disable();
+ * keyboardComponent.keySpatialNavigation.enable();
+ *
+ * var isEnabled = keyboardComponent.keySpatialNavigation.isEnabled;
+ * ```
+ */
+class KeySpatialNavigationHandler extends HandlerBase {
+ /** @ignore */
+ constructor(component, container, navigator, spatial) {
+ super(component, container, navigator);
+ this._spatial = spatial;
+ }
+ _enable() {
+ const spatialEdges$ = this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+ return image.spatialEdges$;
+ }));
+ this._keyDownSubscription = this._container.keyboardService.keyDown$.pipe(withLatestFrom(spatialEdges$, this._navigator.stateService.currentState$))
+ .subscribe(([event, edgeStatus, frame]) => {
+ let spherical = isSpherical(frame.state.currentImage.cameraType);
+ let direction = null;
+ switch (event.keyCode) {
+ case 37: // left
+ direction = event.shiftKey && !spherical ? NavigationDirection.TurnLeft : NavigationDirection.StepLeft;
+ break;
+ case 38: // up
+ direction = event.shiftKey && !spherical ? NavigationDirection.Spherical : NavigationDirection.StepForward;
+ break;
+ case 39: // right
+ direction = event.shiftKey && !spherical ? NavigationDirection.TurnRight : NavigationDirection.StepRight;
+ break;
+ case 40: // down
+ direction = event.shiftKey && !spherical ? NavigationDirection.TurnU : NavigationDirection.StepBackward;
+ break;
+ default:
+ return;
+ }
+ event.preventDefault();
+ if (event.altKey || !edgeStatus.cached ||
+ (event.shiftKey && spherical)) {
+ return;
+ }
+ if (!spherical) {
+ this._moveDir(direction, edgeStatus);
+ }
+ else {
+ const shifts = {};
+ shifts[NavigationDirection.StepBackward] = Math.PI;
+ shifts[NavigationDirection.StepForward] = 0;
+ shifts[NavigationDirection.StepLeft] = Math.PI / 2;
+ shifts[NavigationDirection.StepRight] = -Math.PI / 2;
+ const phi = this._rotationFromCamera(frame.state.camera).phi;
+ const navigationAngle = this._spatial.wrapAngle(phi + shifts[direction]);
+ const threshold = Math.PI / 4;
+ const edges = edgeStatus.edges.filter((e) => {
+ return e.data.direction === NavigationDirection.Spherical || e.data.direction === direction;
+ });
+ let smallestAngle = Number.MAX_VALUE;
+ let toKey = null;
+ for (const edge of edges) {
+ const angle = Math.abs(this._spatial.wrapAngle(edge.data.worldMotionAzimuth - navigationAngle));
+ if (angle < Math.min(smallestAngle, threshold)) {
+ smallestAngle = angle;
+ toKey = edge.target;
+ }
+ }
+ if (toKey == null) {
+ return;
+ }
+ this._moveTo(toKey);
+ }
+ });
+ }
+ _disable() {
+ this._keyDownSubscription.unsubscribe();
+ }
+ _getConfiguration(enable) {
+ return { keySpatialNavigation: enable };
+ }
+ _moveDir(direction, edgeStatus) {
+ for (const edge of edgeStatus.edges) {
+ if (edge.data.direction === direction) {
+ this._moveTo(edge.target);
+ return;
+ }
+ }
+ }
+ _moveTo(id) {
+ this._navigator.moveTo$(id)
+ .subscribe(undefined, (error) => {
+ if (!(error instanceof CancelMapillaryError)) {
+ console.error(error);
+ }
+ });
+ }
+ _rotationFromCamera(camera) {
+ let direction = camera.lookat.clone().sub(camera.position);
+ let upProjection = direction.clone().dot(camera.up);
+ let planeProjection = direction.clone().sub(camera.up.clone().multiplyScalar(upProjection));
+ let phi = Math.atan2(planeProjection.y, planeProjection.x);
+ let theta = Math.PI / 2 - this._spatial.angleToPlane(direction.toArray(), [0, 0, 1]);
+ return { phi: phi, theta: theta };
+ }
+}
+
+/**
+ * The `KeyZoomHandler` allows the user to zoom in and out using the
+ * following key commands:
+ *
+ * `+`: Zoom in.
+ * `-`: Zoom out.
+ *
+ * @example
+ * ```js
+ * var keyboardComponent = viewer.getComponent("keyboard");
+ *
+ * keyboardComponent.keyZoom.disable();
+ * keyboardComponent.keyZoom.enable();
+ *
+ * var isEnabled = keyboardComponent.keyZoom.isEnabled;
+ * ```
+ */
+class KeyZoomHandler extends HandlerBase {
+ /** @ignore */
+ constructor(component, container, navigator, viewportCoords) {
+ super(component, container, navigator);
+ this._viewportCoords = viewportCoords;
+ }
+ _enable() {
+ this._keyDownSubscription = this._container.keyboardService.keyDown$.pipe(withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$))
+ .subscribe(([event, render, transform]) => {
+ if (event.altKey || event.ctrlKey || event.metaKey) {
+ return;
+ }
+ let delta = 0;
+ switch (event.key) {
+ case "+":
+ delta = 1;
+ break;
+ case "-":
+ delta = -1;
+ break;
+ default:
+ return;
+ }
+ event.preventDefault();
+ const unprojected = this._viewportCoords.unprojectFromViewport(0, 0, render.perspective);
+ const reference = transform.projectBasic(unprojected.toArray());
+ this._navigator.stateService.zoomIn(delta, reference);
+ });
+ }
+ _disable() {
+ this._keyDownSubscription.unsubscribe();
+ }
+ _getConfiguration(enable) {
+ return { keyZoom: enable };
+ }
+}
+
+/**
+ * The `KeyPlayHandler` allows the user to control the play behavior
+ * using the following key commands:
+ *
+ * `Spacebar`: Start or stop playing.
+ * `SHIFT` + `D`: Switch direction.
+ * `<`: Decrease speed.
+ * `>`: Increase speed.
+ *
+ * @example
+ * ```js
+ * var keyboardComponent = viewer.getComponent("keyboard");
+ *
+ * keyboardComponent.keyPlay.disable();
+ * keyboardComponent.keyPlay.enable();
+ *
+ * var isEnabled = keyboardComponent.keyPlay.isEnabled;
+ * ```
+ */
+class KeyPlayHandler extends HandlerBase {
+ _enable() {
+ this._keyDownSubscription = this._container.keyboardService.keyDown$.pipe(withLatestFrom(this._navigator.playService.playing$, this._navigator.playService.direction$, this._navigator.playService.speed$, this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+ return image.sequenceEdges$;
+ })), this._navigator.stateService.state$.pipe(map((state) => {
+ return state === State.Earth;
+ }), distinctUntilChanged())))
+ .subscribe(([event, playing, direction, speed, status, earth]) => {
+ if (event.altKey || event.ctrlKey || event.metaKey) {
+ return;
+ }
+ switch (event.key) {
+ case "D":
+ if (!event.shiftKey) {
+ return;
+ }
+ const newDirection = playing ?
+ null : direction === NavigationDirection.Next ?
+ NavigationDirection.Prev : direction === NavigationDirection.Prev ?
+ NavigationDirection.Next : null;
+ if (newDirection != null) {
+ this._navigator.playService.setDirection(newDirection);
+ }
+ break;
+ case " ":
+ if (event.shiftKey) {
+ return;
+ }
+ if (!earth) {
+ if (playing) {
+ this._navigator.playService.stop();
+ }
+ else {
+ for (let edge of status.edges) {
+ if (edge.data.direction === direction) {
+ this._navigator.playService.play();
+ }
+ }
+ }
+ }
+ break;
+ case "<":
+ this._navigator.playService.setSpeed(speed - 0.05);
+ break;
+ case ">":
+ this._navigator.playService.setSpeed(speed + 0.05);
+ break;
+ default:
+ return;
+ }
+ event.preventDefault();
+ });
+ }
+ _disable() {
+ this._keyDownSubscription.unsubscribe();
+ }
+ _getConfiguration(enable) {
+ return { keyPlay: enable };
+ }
+}
+
+/**
+ * @class KeyboardComponent
+ *
+ * @classdesc Component for keyboard event handling.
+ *
+ * To retrive and use the keyboard component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ ... });
+ *
+ * var keyboardComponent = viewer.getComponent("keyboard");
+ * ```
+ */
+class KeyboardComponent extends Component {
+ /** @ignore */
+ constructor(name, container, navigator) {
+ super(name, container, navigator);
+ this._keyPlayHandler =
+ new KeyPlayHandler(this, container, navigator);
+ this._keySequenceNavigationHandler =
+ new KeySequenceNavigationHandler(this, container, navigator);
+ this._keySpatialNavigationHandler =
+ new KeySpatialNavigationHandler(this, container, navigator, new Spatial());
+ this._keyZoomHandler =
+ new KeyZoomHandler(this, container, navigator, new ViewportCoords());
+ }
+ /**
+ * Get key play.
+ *
+ * @returns {KeyPlayHandler} The key play handler.
+ */
+ get keyPlay() {
+ return this._keyPlayHandler;
+ }
+ /**
+ * Get key sequence navigation.
+ *
+ * @returns {KeySequenceNavigationHandler} The key sequence navigation handler.
+ */
+ get keySequenceNavigation() {
+ return this._keySequenceNavigationHandler;
+ }
+ /**
+ * Get spatial.
+ *
+ * @returns {KeySpatialNavigationHandler} The spatial handler.
+ */
+ get keySpatialNavigation() {
+ return this._keySpatialNavigationHandler;
+ }
+ /**
+ * Get key zoom.
+ *
+ * @returns {KeyZoomHandler} The key zoom handler.
+ */
+ get keyZoom() {
+ return this._keyZoomHandler;
+ }
+ _activate() {
+ this._subscriptions.push(this._configuration$
+ .subscribe((configuration) => {
+ if (configuration.keyPlay) {
+ this._keyPlayHandler.enable();
+ }
+ else {
+ this._keyPlayHandler.disable();
+ }
+ if (configuration.keySequenceNavigation) {
+ this._keySequenceNavigationHandler.enable();
+ }
+ else {
+ this._keySequenceNavigationHandler.disable();
+ }
+ if (configuration.keySpatialNavigation) {
+ this._keySpatialNavigationHandler.enable();
+ }
+ else {
+ this._keySpatialNavigationHandler.disable();
+ }
+ if (configuration.keyZoom) {
+ this._keyZoomHandler.enable();
+ }
+ else {
+ this._keyZoomHandler.disable();
+ }
+ }));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ this._keyPlayHandler.disable();
+ this._keySequenceNavigationHandler.disable();
+ this._keySpatialNavigationHandler.disable();
+ this._keyZoomHandler.disable();
+ }
+ _getDefaultConfiguration() {
+ return { keyPlay: true, keySequenceNavigation: true, keySpatialNavigation: true, keyZoom: true };
+ }
+}
+KeyboardComponent.componentName = "keyboard";
+
+class MarkerScene {
+ constructor(scene, raycaster) {
+ this._needsRender = false;
+ this._interactiveObjects = [];
+ this._markers = {};
+ this._objectMarkers = {};
+ this._raycaster = !!raycaster ? raycaster : new Raycaster();
+ this._scene = !!scene ? scene : new Scene();
+ }
+ get markers() {
+ return this._markers;
+ }
+ get needsRender() {
+ return this._needsRender;
+ }
+ add(marker, position) {
+ if (marker.id in this._markers) {
+ this._dispose(marker.id);
+ }
+ marker.createGeometry(position);
+ this._scene.add(marker.geometry);
+ this._markers[marker.id] = marker;
+ for (let interactiveObject of marker.getInteractiveObjects()) {
+ this._interactiveObjects.push(interactiveObject);
+ this._objectMarkers[interactiveObject.uuid] = marker.id;
+ }
+ this._needsRender = true;
+ }
+ clear() {
+ for (const id in this._markers) {
+ if (!this._markers.hasOwnProperty) {
+ continue;
+ }
+ this._dispose(id);
+ }
+ this._needsRender = true;
+ }
+ get(id) {
+ return this._markers[id];
+ }
+ getAll() {
+ return Object
+ .keys(this._markers)
+ .map((id) => { return this._markers[id]; });
+ }
+ has(id) {
+ return id in this._markers;
+ }
+ intersectObjects([viewportX, viewportY], camera) {
+ this._raycaster.setFromCamera(new Vector2(viewportX, viewportY), camera);
+ const intersects = this._raycaster.intersectObjects(this._interactiveObjects);
+ for (const intersect of intersects) {
+ if (intersect.object.uuid in this._objectMarkers) {
+ return this._objectMarkers[intersect.object.uuid];
+ }
+ }
+ return null;
+ }
+ lerpAltitude(id, alt, alpha) {
+ if (!(id in this._markers)) {
+ return;
+ }
+ this._markers[id].lerpAltitude(alt, alpha);
+ this._needsRender = true;
+ }
+ remove(id) {
+ if (!(id in this._markers)) {
+ return;
+ }
+ this._dispose(id);
+ this._needsRender = true;
+ }
+ render(perspectiveCamera, renderer) {
+ renderer.render(this._scene, perspectiveCamera);
+ this._needsRender = false;
+ }
+ update(id, position, lngLat) {
+ if (!(id in this._markers)) {
+ return;
+ }
+ const marker = this._markers[id];
+ marker.updatePosition(position, lngLat);
+ this._needsRender = true;
+ }
+ _dispose(id) {
+ const marker = this._markers[id];
+ this._scene.remove(marker.geometry);
+ for (let interactiveObject of marker.getInteractiveObjects()) {
+ const index = this._interactiveObjects.indexOf(interactiveObject);
+ if (index !== -1) {
+ this._interactiveObjects.splice(index, 1);
+ }
+ else {
+ console.warn(`Object does not exist (${interactiveObject.id}) for ${id}`);
+ }
+ delete this._objectMarkers[interactiveObject.uuid];
+ }
+ marker.disposeGeometry();
+ delete this._markers[id];
+ }
+}
+
+/**
+ * @class MarkerComponent
+ *
+ * @classdesc Component for showing and editing 3D marker objects.
+ *
+ * The `add` method is used for adding new markers or replacing
+ * markers already in the set.
+ *
+ * If a marker already in the set has the same
+ * id as one of the markers added, the old marker will be removed and
+ * the added marker will take its place.
+ *
+ * It is not possible to update markers in the set by updating any properties
+ * directly on the marker object. Markers need to be replaced by
+ * re-adding them for updates to geographic position or configuration
+ * to be reflected.
+ *
+ * Markers added to the marker component can be either interactive
+ * or non-interactive. Different marker types define their behavior.
+ * Markers with interaction support can be configured with options
+ * to respond to dragging inside the viewer and be detected when
+ * retrieving markers from pixel points with the `getMarkerIdAt` method.
+ *
+ * To retrive and use the marker component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ component: { marker: true }, ... });
+ *
+ * var markerComponent = viewer.getComponent("marker");
+ * ```
+ */
+class MarkerComponent extends Component {
+ /** @ignore */
+ constructor(name, container, navigator) {
+ super(name, container, navigator);
+ this._graphCalculator = new GraphCalculator();
+ this._markerScene = new MarkerScene();
+ this._markerSet = new MarkerSet();
+ this._viewportCoords = new ViewportCoords();
+ this._relativeGroundAltitude = -2;
+ }
+ /**
+ * Add markers to the marker set or replace markers in the marker set.
+ *
+ * @description If a marker already in the set has the same
+ * id as one of the markers added, the old marker will be removed
+ * the added marker will take its place.
+ *
+ * Any marker inside the visible bounding bbox
+ * will be initialized and placed in the viewer.
+ *
+ * @param {Array<Marker>} markers - Markers to add.
+ *
+ * @example
+ * ```js
+ * markerComponent.add([marker1, marker2]);
+ * ```
+ */
+ add(markers) {
+ this._markerSet.add(markers);
+ }
+ fire(type, event) {
+ super.fire(type, event);
+ }
+ /**
+ * Returns the marker in the marker set with the specified id, or
+ * undefined if the id matches no marker.
+ *
+ * @param {string} markerId - Id of the marker.
+ *
+ * @example
+ * ```js
+ * var marker = markerComponent.get("markerId");
+ * ```
+ *
+ */
+ get(markerId) {
+ return this._markerSet.get(markerId);
+ }
+ /**
+ * Returns an array of all markers.
+ *
+ * @example
+ * ```js
+ * var markers = markerComponent.getAll();
+ * ```
+ */
+ getAll() {
+ return this._markerSet.getAll();
+ }
+ /**
+ * Returns the id of the interactive marker closest to the current camera
+ * position at the specified point.
+ *
+ * @description Notice that the pixelPoint argument requires x, y
+ * coordinates from pixel space.
+ *
+ * With this function, you can use the coordinates provided by mouse
+ * events to get information out of the marker component.
+ *
+ * If no interactive geometry of an interactive marker exist at the pixel
+ * point, `null` will be returned.
+ *
+ * @param {Array<number>} pixelPoint - Pixel coordinates on the viewer element.
+ * @returns {string} Id of the interactive marker closest to the camera. If no
+ * interactive marker exist at the pixel point, `null` will be returned.
+ *
+ * @example
+ * ```js
+ * markerComponent.getMarkerIdAt([100, 100])
+ * .then((markerId) => { console.log(markerId); });
+ * ```
+ */
+ getMarkerIdAt(pixelPoint) {
+ return new Promise((resolve, reject) => {
+ this._container.renderService.renderCamera$.pipe(first(), map((render) => {
+ const viewport = this._viewportCoords
+ .canvasToViewport(pixelPoint[0], pixelPoint[1], this._container.container);
+ const id = this._markerScene.intersectObjects(viewport, render.perspective);
+ return id;
+ }))
+ .subscribe((id) => {
+ resolve(id);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Check if a marker exist in the marker set.
+ *
+ * @param {string} markerId - Id of the marker.
+ *
+ * @example
+ * ```js
+ * var markerExists = markerComponent.has("markerId");
+ * ```
+ */
+ has(markerId) {
+ return this._markerSet.has(markerId);
+ }
+ off(type, handler) {
+ super.off(type, handler);
+ }
+ on(type, handler) {
+ super.on(type, handler);
+ }
+ /**
+ * Remove markers with the specified ids from the marker set.
+ *
+ * @param {Array<string>} markerIds - Ids for markers to remove.
+ *
+ * @example
+ * ```js
+ * markerComponent.remove(["id-1", "id-2"]);
+ * ```
+ */
+ remove(markerIds) {
+ this._markerSet.remove(markerIds);
+ }
+ /**
+ * Remove all markers from the marker set.
+ *
+ * @example
+ * ```js
+ * markerComponent.removeAll();
+ * ```
+ */
+ removeAll() {
+ this._markerSet.removeAll();
+ }
+ _activate() {
+ const groundAltitude$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+ return frame.state.camera.position.z + this._relativeGroundAltitude;
+ }), distinctUntilChanged((a1, a2) => {
+ return Math.abs(a1 - a2) < 0.01;
+ }), publishReplay(1), refCount());
+ const geoInitiated$ = combineLatest(groundAltitude$, this._navigator.stateService.reference$).pipe(first(), map(() => { }), publishReplay(1), refCount());
+ const clampedConfiguration$ = this._configuration$.pipe(map((configuration) => {
+ return { visibleBBoxSize: Math.max(1, Math.min(200, configuration.visibleBBoxSize)) };
+ }));
+ const currentLngLat$ = this._navigator.stateService.currentImage$.pipe(map((image) => { return image.lngLat; }), publishReplay(1), refCount());
+ const visibleBBox$ = combineLatest(clampedConfiguration$, currentLngLat$).pipe(map(([configuration, lngLat]) => {
+ return this._graphCalculator
+ .boundingBoxCorners(lngLat, configuration.visibleBBoxSize / 2);
+ }), publishReplay(1), refCount());
+ const visibleMarkers$ = combineLatest(concat(of(this._markerSet), this._markerSet.changed$), visibleBBox$).pipe(map(([set, bbox]) => {
+ return set.search(bbox);
+ }));
+ const subs = this._subscriptions;
+ subs.push(geoInitiated$.pipe(switchMap(() => {
+ return visibleMarkers$.pipe(withLatestFrom(this._navigator.stateService.reference$, groundAltitude$));
+ }))
+ .subscribe(([markers, reference, alt]) => {
+ const markerScene = this._markerScene;
+ const sceneMarkers = markerScene.markers;
+ const markersToRemove = Object.assign({}, sceneMarkers);
+ for (const marker of markers) {
+ if (marker.id in sceneMarkers) {
+ delete markersToRemove[marker.id];
+ }
+ else {
+ const point3d = geodeticToEnu(marker.lngLat.lng, marker.lngLat.lat, reference.alt + alt, reference.lng, reference.lat, reference.alt);
+ markerScene.add(marker, point3d);
+ }
+ }
+ for (const id in markersToRemove) {
+ if (!markersToRemove.hasOwnProperty(id)) {
+ continue;
+ }
+ markerScene.remove(id);
+ }
+ }));
+ subs.push(geoInitiated$.pipe(switchMap(() => {
+ return this._markerSet.updated$.pipe(withLatestFrom(visibleBBox$, this._navigator.stateService.reference$, groundAltitude$));
+ }))
+ .subscribe(([markers, [sw, ne], reference, alt]) => {
+ const markerScene = this._markerScene;
+ for (const marker of markers) {
+ const exists = markerScene.has(marker.id);
+ const visible = marker.lngLat.lat > sw.lat &&
+ marker.lngLat.lat < ne.lat &&
+ marker.lngLat.lng > sw.lng &&
+ marker.lngLat.lng < ne.lng;
+ if (visible) {
+ const point3d = geodeticToEnu(marker.lngLat.lng, marker.lngLat.lat, reference.alt + alt, reference.lng, reference.lat, reference.alt);
+ markerScene.add(marker, point3d);
+ }
+ else if (!visible && exists) {
+ markerScene.remove(marker.id);
+ }
+ }
+ }));
+ subs.push(this._navigator.stateService.reference$.pipe(skip(1), withLatestFrom(groundAltitude$))
+ .subscribe(([reference, alt]) => {
+ const markerScene = this._markerScene;
+ for (const marker of markerScene.getAll()) {
+ const point3d = geodeticToEnu(marker.lngLat.lng, marker.lngLat.lat, reference.alt + alt, reference.lng, reference.lat, reference.alt);
+ markerScene.update(marker.id, point3d);
+ }
+ }));
+ subs.push(groundAltitude$.pipe(skip(1), withLatestFrom(this._navigator.stateService.reference$, currentLngLat$))
+ .subscribe(([alt, reference, lngLat]) => {
+ const markerScene = this._markerScene;
+ const position = geodeticToEnu(lngLat.lng, lngLat.lat, reference.alt + alt, reference.lng, reference.lat, reference.alt);
+ for (const marker of markerScene.getAll()) {
+ const point3d = geodeticToEnu(marker.lngLat.lng, marker.lngLat.lat, reference.alt + alt, reference.lng, reference.lat, reference.alt);
+ const distanceX = point3d[0] - position[0];
+ const distanceY = point3d[1] - position[1];
+ const groundDistance = Math
+ .sqrt(distanceX * distanceX + distanceY * distanceY);
+ if (groundDistance > 50) {
+ continue;
+ }
+ markerScene.lerpAltitude(marker.id, alt, Math.min(1, Math.max(0, 1.2 - 1.2 * groundDistance / 50)));
+ }
+ }));
+ subs.push(this._navigator.stateService.currentState$
+ .pipe(map((frame) => {
+ const scene = this._markerScene;
+ return {
+ name: this._name,
+ renderer: {
+ frameId: frame.id,
+ needsRender: scene.needsRender,
+ render: scene.render.bind(scene),
+ pass: RenderPass$1.Opaque,
+ },
+ };
+ }))
+ .subscribe(this._container.glRenderer.render$));
+ const hoveredMarkerId$ = combineLatest(this._container.renderService.renderCamera$, this._container.mouseService.mouseMove$)
+ .pipe(map(([render, event]) => {
+ const element = this._container.container;
+ const [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+ const viewport = this._viewportCoords
+ .canvasToViewport(canvasX, canvasY, element);
+ const markerId = this._markerScene.intersectObjects(viewport, render.perspective);
+ return markerId;
+ }), publishReplay(1), refCount());
+ const draggingStarted$ = this._container.mouseService
+ .filtered$(this._name, this._container.mouseService.mouseDragStart$).pipe(map(() => {
+ return true;
+ }));
+ const draggingStopped$ = this._container.mouseService
+ .filtered$(this._name, this._container.mouseService.mouseDragEnd$).pipe(map(() => {
+ return false;
+ }));
+ const filteredDragging$ = merge(draggingStarted$, draggingStopped$)
+ .pipe(startWith(false));
+ subs.push(merge(draggingStarted$.pipe(withLatestFrom(hoveredMarkerId$)), combineLatest(draggingStopped$, of(null))).pipe(startWith([false, null]), pairwise())
+ .subscribe(([previous, current]) => {
+ const dragging = current[0];
+ const type = dragging ?
+ "markerdragstart" :
+ "markerdragend";
+ const id = dragging ? current[1] : previous[1];
+ const marker = this._markerScene.get(id);
+ const event = {
+ marker,
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ }));
+ const mouseDown$ = merge(this._container.mouseService.mouseDown$.pipe(map(() => { return true; })), this._container.mouseService.documentMouseUp$.pipe(map(() => { return false; }))).pipe(startWith(false));
+ subs.push(combineLatest(this._container.mouseService.active$, hoveredMarkerId$.pipe(distinctUntilChanged()), mouseDown$, filteredDragging$)
+ .pipe(map(([active, markerId, mouseDown, filteredDragging]) => {
+ return (!active && markerId != null && mouseDown) ||
+ filteredDragging;
+ }), distinctUntilChanged())
+ .subscribe((claim) => {
+ if (claim) {
+ this._container.mouseService.claimMouse(this._name, 1);
+ this._container.mouseService.claimWheel(this._name, 1);
+ }
+ else {
+ this._container.mouseService.unclaimMouse(this._name);
+ this._container.mouseService.unclaimWheel(this._name);
+ }
+ }));
+ const offset$ = this._container.mouseService
+ .filtered$(this._name, this._container.mouseService.mouseDragStart$).pipe(withLatestFrom(hoveredMarkerId$, this._container.renderService.renderCamera$), map(([e, id, r]) => {
+ const marker = this._markerScene.get(id);
+ const element = this._container.container;
+ const [groundCanvasX, groundCanvasY] = this._viewportCoords
+ .projectToCanvas(marker.geometry.position
+ .toArray(), element, r.perspective);
+ const [canvasX, canvasY] = this._viewportCoords
+ .canvasPosition(e, element);
+ const offset = [canvasX - groundCanvasX, canvasY - groundCanvasY];
+ return [marker, offset, r];
+ }), publishReplay(1), refCount());
+ subs.push(this._container.mouseService
+ .filtered$(this._name, this._container.mouseService.mouseDrag$)
+ .pipe(withLatestFrom(offset$, this._navigator.stateService.reference$, clampedConfiguration$))
+ .subscribe(([event, [marker, offset, render], reference, configuration]) => {
+ if (!this._markerScene.has(marker.id)) {
+ return;
+ }
+ const element = this._container.container;
+ const [canvasX, canvasY] = this._viewportCoords
+ .canvasPosition(event, element);
+ const groundX = canvasX - offset[0];
+ const groundY = canvasY - offset[1];
+ const [viewportX, viewportY] = this._viewportCoords
+ .canvasToViewport(groundX, groundY, element);
+ const direction = new Vector3(viewportX, viewportY, 1)
+ .unproject(render.perspective)
+ .sub(render.perspective.position)
+ .normalize();
+ const distance = Math.min(this._relativeGroundAltitude / direction.z, configuration.visibleBBoxSize / 2 - 0.1);
+ if (distance < 0) {
+ return;
+ }
+ const intersection = direction
+ .clone()
+ .multiplyScalar(distance)
+ .add(render.perspective.position);
+ intersection.z =
+ render.perspective.position.z
+ + this._relativeGroundAltitude;
+ const [lng, lat] = enuToGeodetic(intersection.x, intersection.y, intersection.z, reference.lng, reference.lat, reference.alt);
+ this._markerScene
+ .update(marker.id, intersection.toArray(), { lat, lng });
+ this._markerSet.update(marker);
+ const type = "markerposition";
+ const markerEvent = {
+ marker,
+ target: this,
+ type,
+ };
+ this.fire(type, markerEvent);
+ }));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ this._markerScene.clear();
+ }
+ _getDefaultConfiguration() {
+ return { visibleBBoxSize: 100 };
+ }
+}
+MarkerComponent.componentName = "marker";
+
+function sign$1(n) {
+ return n > 0 ? 1 : n < 0 ? -1 : 0;
+}
+function colinearPointOnSegment(p, s) {
+ return p.x <= Math.max(s.p1.x, s.p2.x) &&
+ p.x >= Math.min(s.p1.x, s.p2.x) &&
+ p.y >= Math.max(s.p1.y, s.p2.y) &&
+ p.y >= Math.min(s.p1.y, s.p2.y);
+}
+function parallel(s1, s2) {
+ const ux = s1.p2.x - s1.p1.x;
+ const uy = s1.p2.y - s1.p1.y;
+ const vx = s2.p2.x - s2.p1.x;
+ const vy = s2.p2.y - s2.p1.y;
+ const cross = ux * vy - uy * vx;
+ const u2 = ux * ux + uy * uy;
+ const v2 = vx * vx + vy * vy;
+ const epsilon2 = 1e-10;
+ return cross * cross < epsilon2 * u2 * v2;
+}
+function tripletOrientation(p1, p2, p3) {
+ const orientation = (p2.y - p1.y) * (p3.x - p2.x) -
+ (p3.y - p2.y) * (p2.x - p1.x);
+ return sign$1(orientation);
+}
+function segmentsIntersect(s1, s2) {
+ if (parallel(s1, s2)) {
+ return false;
+ }
+ const o1 = tripletOrientation(s1.p1, s1.p2, s2.p1);
+ const o2 = tripletOrientation(s1.p1, s1.p2, s2.p2);
+ const o3 = tripletOrientation(s2.p1, s2.p2, s1.p1);
+ const o4 = tripletOrientation(s2.p1, s2.p2, s1.p2);
+ if (o1 !== o2 && o3 !== o4) {
+ return true;
+ }
+ if (o1 === 0 && colinearPointOnSegment(s2.p1, s1)) {
+ return true;
+ }
+ if (o2 === 0 && colinearPointOnSegment(s2.p2, s1)) {
+ return true;
+ }
+ if (o3 === 0 && colinearPointOnSegment(s1.p1, s2)) {
+ return true;
+ }
+ if (o4 === 0 && colinearPointOnSegment(s1.p2, s2)) {
+ return true;
+ }
+ return false;
+}
+function segmentIntersection(s1, s2) {
+ if (parallel(s1, s2)) {
+ return undefined;
+ }
+ const x1 = s1.p1.x;
+ const x2 = s1.p2.x;
+ const y1 = s1.p1.y;
+ const y2 = s1.p2.y;
+ const x3 = s2.p1.x;
+ const x4 = s2.p2.x;
+ const y3 = s2.p1.y;
+ const y4 = s2.p2.y;
+ const den = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
+ const xNum = (x1 * y2 - y1 * x2) * (x3 - x4) - (x1 - x2) * (x3 * y4 - y3 * x4);
+ const yNum = (x1 * y2 - y1 * x2) * (y3 - y4) - (y1 - y2) * (x3 * y4 - y3 * x4);
+ return { x: xNum / den, y: yNum / den };
+}
+
+function basicBoundaryPoints(pointsPerSide) {
+ let points = [];
+ let os = [[0, 0], [1, 0], [1, 1], [0, 1]];
+ let ds = [[1, 0], [0, 1], [-1, 0], [0, -1]];
+ for (let side = 0; side < 4; ++side) {
+ let o = os[side];
+ let d = ds[side];
+ for (let i = 0; i < pointsPerSide; ++i) {
+ points.push([o[0] + d[0] * i / pointsPerSide,
+ o[1] + d[1] * i / pointsPerSide]);
+ }
+ }
+ return points;
+}
+function insideViewport(x, y) {
+ return x >= -1 && x <= 1 && y >= -1 && y <= 1;
+}
+function insideBasic(x, y) {
+ return x >= 0 && x <= 1 && y >= 0 && y <= 1;
+}
+function viewportDistances(transform, perspective, viewportCoords) {
+ const boundaryPointsBasic = basicBoundaryPoints(100);
+ const boundaryPointsViewport = boundaryPointsBasic
+ .map((basic) => {
+ return viewportCoords.basicToViewportSafe(basic[0], basic[1], transform, perspective);
+ });
+ const visibleBoundaryPoints = [];
+ const viewportSides = [
+ { x: -1, y: 1 },
+ { x: 1, y: 1 },
+ { x: 1, y: -1 },
+ { x: -1, y: -1 }
+ ];
+ const intersections = [false, false, false, false];
+ for (let i = 0; i < boundaryPointsViewport.length; i++) {
+ const p1 = boundaryPointsViewport[i];
+ const p2 = boundaryPointsViewport[(i + 1) % boundaryPointsViewport.length];
+ if (p1 === null) {
+ continue;
+ }
+ if (p2 === null) {
+ if (insideViewport(p1[0], p1[1])) {
+ visibleBoundaryPoints.push(p1);
+ }
+ continue;
+ }
+ const [x1, y1] = p1;
+ const [x2, y2] = p2;
+ if (insideViewport(x1, y1)) {
+ if (insideViewport(x2, y2)) {
+ visibleBoundaryPoints.push(p1);
+ }
+ else {
+ for (let side = 0; side < 4; side++) {
+ const s1 = { p1: { x: x1, y: y1 }, p2: { x: x2, y: y2 } };
+ const s2 = { p1: viewportSides[side], p2: viewportSides[(side + 1) % 4] };
+ const intersecting = segmentsIntersect(s1, s2);
+ if (intersecting) {
+ const intersection = segmentIntersection(s1, s2);
+ visibleBoundaryPoints.push(p1, [intersection.x, intersection.y]);
+ intersections[side] = true;
+ }
+ }
+ }
+ }
+ }
+ const [topLeftBasicX, topLeftBasicY] = viewportCoords.viewportToBasic(-1, 1, transform, perspective);
+ const [topRightBasicX, topRightBasicY] = viewportCoords.viewportToBasic(1, 1, transform, perspective);
+ const [bottomRightBasicX, bottomRightBasicY] = viewportCoords.viewportToBasic(1, -1, transform, perspective);
+ const [bottomLeftBasicX, bottomLeftBasicY] = viewportCoords.viewportToBasic(-1, -1, transform, perspective);
+ if (insideBasic(topLeftBasicX, topLeftBasicY)) {
+ intersections[3] = intersections[0] = true;
+ }
+ if (insideBasic(topRightBasicX, topRightBasicY)) {
+ intersections[0] = intersections[1] = true;
+ }
+ if (insideBasic(bottomRightBasicX, bottomRightBasicY)) {
+ intersections[1] = intersections[2] = true;
+ }
+ if (insideBasic(bottomLeftBasicX, bottomLeftBasicY)) {
+ intersections[2] = intersections[3] = true;
+ }
+ const maximums = [-1, -1, 1, 1];
+ for (let visibleBoundaryPoint of visibleBoundaryPoints) {
+ const x = visibleBoundaryPoint[0];
+ const y = visibleBoundaryPoint[1];
+ if (x > maximums[1]) {
+ maximums[1] = x;
+ }
+ if (x < maximums[3]) {
+ maximums[3] = x;
+ }
+ if (y > maximums[0]) {
+ maximums[0] = y;
+ }
+ if (y < maximums[2]) {
+ maximums[2] = y;
+ }
+ }
+ const boundary = [1, 1, -1, -1];
+ const distances = [];
+ for (let side = 0; side < 4; side++) {
+ if (intersections[side]) {
+ distances.push(0);
+ continue;
+ }
+ distances.push(Math.abs(boundary[side] - maximums[side]));
+ }
+ return distances;
+}
+
+/**
+ * The `BounceHandler` ensures that the viewer bounces back to the image
+ * when drag panning outside of the image edge.
+ */
+class BounceHandler extends HandlerBase {
+ constructor(component, container, navigator, viewportCoords, spatial) {
+ super(component, container, navigator);
+ this._spatial = spatial;
+ this._viewportCoords = viewportCoords;
+ }
+ _enable() {
+ const inTransition$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+ return frame.state.alpha < 1;
+ }), distinctUntilChanged());
+ this._bounceSubscription = combineLatest(inTransition$, this._navigator.stateService.inTranslation$, this._container.mouseService.active$, this._container.touchService.active$).pipe(map((noForce) => {
+ return noForce[0] || noForce[1] || noForce[2] || noForce[3];
+ }), distinctUntilChanged(), switchMap((noForce) => {
+ return noForce ?
+ empty() :
+ combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$.pipe(first()));
+ }), withLatestFrom(this._navigator.panService.panImages$))
+ .subscribe(([[render, transform], nts]) => {
+ if (!transform.hasValidScale && render.camera.focal < 0.1) {
+ return;
+ }
+ if (render.perspective.aspect === 0 || render.perspective.aspect === Number.POSITIVE_INFINITY) {
+ return;
+ }
+ const distances = viewportDistances(transform, render.perspective, this._viewportCoords);
+ const basic = this._viewportCoords.viewportToBasic(0, 0, transform, render.perspective);
+ if ((basic[0] < 0 || basic[0] > 1) && nts.length > 0) {
+ distances[0] = distances[2] = 0;
+ }
+ for (const [, t] of nts) {
+ const d = viewportDistances(t, render.perspective, this._viewportCoords);
+ for (let i = 1; i < distances.length; i += 2) {
+ if (d[i] < distances[i]) {
+ distances[i] = d[i];
+ }
+ }
+ }
+ if (Math.max(...distances) < 0.01) {
+ return;
+ }
+ const horizontalDistance = distances[1] - distances[3];
+ const verticalDistance = distances[0] - distances[2];
+ const currentDirection = this._viewportCoords
+ .unprojectFromViewport(0, 0, render.perspective)
+ .sub(render.perspective.position);
+ const directionPhi = this._viewportCoords
+ .unprojectFromViewport(horizontalDistance, 0, render.perspective)
+ .sub(render.perspective.position);
+ const directionTheta = this._viewportCoords
+ .unprojectFromViewport(0, verticalDistance, render.perspective)
+ .sub(render.perspective.position);
+ let phi = (horizontalDistance > 0 ? 1 : -1) * directionPhi.angleTo(currentDirection);
+ let theta = (verticalDistance > 0 ? 1 : -1) * directionTheta.angleTo(currentDirection);
+ const threshold = Math.PI / 60;
+ const coeff = 1e-1;
+ phi = this._spatial.clamp(coeff * phi, -threshold, threshold);
+ theta = this._spatial.clamp(coeff * theta, -threshold, threshold);
+ this._navigator.stateService.rotateUnbounded({ phi: phi, theta: theta });
+ });
+ }
+ _disable() {
+ this._bounceSubscription.unsubscribe();
+ }
+ _getConfiguration() {
+ return {};
+ }
+}
+
+class MouseOperator {
+ static filteredPairwiseMouseDrag$(name, mouseService) {
+ return this._filteredPairwiseMouseDrag$(name, mouseService, mouseService.mouseDragStart$, mouseService.mouseDrag$, mouseService.mouseDragEnd$);
+ }
+ static filteredPairwiseMouseRightDrag$(name, mouseService) {
+ return this._filteredPairwiseMouseDrag$(name, mouseService, mouseService.mouseRightDragStart$, mouseService.mouseRightDrag$, mouseService.mouseRightDragEnd$);
+ }
+ static _filteredPairwiseMouseDrag$(name, mouseService, mouseDragStart$, mouseDrag$, mouseDragEnd$) {
+ return mouseService
+ .filtered$(name, mouseDragStart$).pipe(switchMap((mouseDragStart) => {
+ const dragging$ = concat(of(mouseDragStart), mouseService
+ .filtered$(name, mouseDrag$));
+ const dragEnd$ = mouseService
+ .filtered$(name, mouseDragEnd$).pipe(map(() => {
+ return null;
+ }));
+ return merge(dragging$, dragEnd$).pipe(takeWhile((e) => {
+ return !!e;
+ }), startWith(null));
+ }), pairwise(), filter((pair) => {
+ return pair[0] != null && pair[1] != null;
+ }));
+ }
+}
+
+/**
+ * The `DragPanHandler` allows the user to pan the viewer image by clicking and dragging the cursor.
+ *
+ * @example
+ * ```js
+ * var pointerComponent = viewer.getComponent("pointer");
+ *
+ * pointerComponent.dragPan.disable();
+ * pointerComponent.dragPan.enable();
+ *
+ * var isEnabled = pointerComponent.dragPan.isEnabled;
+ * ```
+ */
+class DragPanHandler extends HandlerBase {
+ /** @ignore */
+ constructor(component, container, navigator, viewportCoords, spatial) {
+ super(component, container, navigator);
+ this._spatial = spatial;
+ this._viewportCoords = viewportCoords;
+ }
+ _enable() {
+ let draggingStarted$ = this._container.mouseService
+ .filtered$(this._component.name, this._container.mouseService.mouseDragStart$).pipe(map(() => {
+ return true;
+ }), share());
+ let draggingStopped$ = this._container.mouseService
+ .filtered$(this._component.name, this._container.mouseService.mouseDragEnd$).pipe(map(() => {
+ return false;
+ }), share());
+ this._activeMouseSubscription = merge(draggingStarted$, draggingStopped$)
+ .subscribe(this._container.mouseService.activate$);
+ const documentMouseMove$ = merge(draggingStarted$, draggingStopped$).pipe(switchMap((dragging) => {
+ return dragging ?
+ this._container.mouseService.documentMouseMove$ :
+ empty();
+ }));
+ this._preventDefaultSubscription = merge(documentMouseMove$, this._container.touchService.touchMove$)
+ .subscribe((event) => {
+ event.preventDefault(); // prevent selection of content outside the viewer
+ });
+ let touchMovingStarted$ = this._container.touchService.singleTouchDragStart$.pipe(map(() => {
+ return true;
+ }));
+ let touchMovingStopped$ = this._container.touchService.singleTouchDragEnd$.pipe(map(() => {
+ return false;
+ }));
+ this._activeTouchSubscription = merge(touchMovingStarted$, touchMovingStopped$)
+ .subscribe(this._container.touchService.activate$);
+ const rotation$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+ return isSpherical(frame.state.currentImage.cameraType) ||
+ frame.state.imagesAhead < 1;
+ }), distinctUntilChanged(), switchMap((enable) => {
+ if (!enable) {
+ return empty();
+ }
+ const mouseDrag$ = MouseOperator.filteredPairwiseMouseDrag$(this._component.name, this._container.mouseService);
+ const singleTouchDrag$ = merge(this._container.touchService.singleTouchDragStart$, this._container.touchService.singleTouchDrag$, this._container.touchService.singleTouchDragEnd$.pipe(map(() => { return null; }))).pipe(map((event) => {
+ return event != null && event.touches.length > 0 ?
+ event.touches[0] : null;
+ }), pairwise(), filter((pair) => {
+ return pair[0] != null && pair[1] != null;
+ }));
+ return merge(mouseDrag$, singleTouchDrag$);
+ }), withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$, this._navigator.panService.panImages$), map(([events, render, transform, nts]) => {
+ let previousEvent = events[0];
+ let event = events[1];
+ let movementX = event.clientX - previousEvent.clientX;
+ let movementY = event.clientY - previousEvent.clientY;
+ let element = this._container.container;
+ let [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+ let currentDirection = this._viewportCoords.unprojectFromCanvas(canvasX, canvasY, element, render.perspective)
+ .sub(render.perspective.position);
+ let directionX = this._viewportCoords.unprojectFromCanvas(canvasX - movementX, canvasY, element, render.perspective)
+ .sub(render.perspective.position);
+ let directionY = this._viewportCoords.unprojectFromCanvas(canvasX, canvasY - movementY, element, render.perspective)
+ .sub(render.perspective.position);
+ let phi = (movementX > 0 ? 1 : -1) * directionX.angleTo(currentDirection);
+ let theta = (movementY > 0 ? -1 : 1) * directionY.angleTo(currentDirection);
+ const distances = viewportDistances(transform, render.perspective, this._viewportCoords);
+ for (const [, t] of nts) {
+ const d = viewportDistances(t, render.perspective, this._viewportCoords);
+ for (let i = 0; i < distances.length; i++) {
+ if (d[i] < distances[i]) {
+ distances[i] = d[i];
+ }
+ }
+ }
+ if (distances[0] > 0 && theta < 0) {
+ theta /= Math.max(1, 2e2 * distances[0]);
+ }
+ if (distances[2] > 0 && theta > 0) {
+ theta /= Math.max(1, 2e2 * distances[2]);
+ }
+ if (distances[1] > 0 && phi < 0) {
+ phi /= Math.max(1, 2e2 * distances[1]);
+ }
+ if (distances[3] > 0 && phi > 0) {
+ phi /= Math.max(1, 2e2 * distances[3]);
+ }
+ return { phi: phi, theta: theta };
+ }), share());
+ this._rotateWithoutInertiaSubscription = rotation$
+ .subscribe((rotation) => {
+ this._navigator.stateService.rotateWithoutInertia(rotation);
+ });
+ this._rotateSubscription = rotation$.pipe(scan((rotationBuffer, rotation) => {
+ this._drainBuffer(rotationBuffer);
+ rotationBuffer.push([Date.now(), rotation]);
+ return rotationBuffer;
+ }, []), sample(merge(this._container.mouseService.filtered$(this._component.name, this._container.mouseService.mouseDragEnd$), this._container.touchService.singleTouchDragEnd$)), map((rotationBuffer) => {
+ const drainedBuffer = this._drainBuffer(rotationBuffer.slice());
+ const rotation = { phi: 0, theta: 0 };
+ for (const bufferedRotation of drainedBuffer) {
+ rotation.phi += bufferedRotation[1].phi;
+ rotation.theta += bufferedRotation[1].theta;
+ }
+ const count = drainedBuffer.length;
+ if (count > 0) {
+ rotation.phi /= count;
+ rotation.theta /= count;
+ }
+ const threshold = Math.PI / 18;
+ rotation.phi = this._spatial.clamp(rotation.phi, -threshold, threshold);
+ rotation.theta = this._spatial.clamp(rotation.theta, -threshold, threshold);
+ return rotation;
+ }))
+ .subscribe((rotation) => {
+ this._navigator.stateService.rotate(rotation);
+ });
+ }
+ _disable() {
+ this._activeMouseSubscription.unsubscribe();
+ this._activeTouchSubscription.unsubscribe();
+ this._preventDefaultSubscription.unsubscribe();
+ this._rotateSubscription.unsubscribe();
+ this._rotateWithoutInertiaSubscription.unsubscribe();
+ this._activeMouseSubscription = null;
+ this._activeTouchSubscription = null;
+ this._preventDefaultSubscription = null;
+ this._rotateSubscription = null;
+ }
+ _getConfiguration(enable) {
+ return { dragPan: enable };
+ }
+ _drainBuffer(buffer) {
+ const cutoff = 50;
+ const now = Date.now();
+ while (buffer.length > 0 && now - buffer[0][0] > cutoff) {
+ buffer.shift();
+ }
+ return buffer;
+ }
+}
+
+class EarthControlHandler extends HandlerBase {
+ /** @ignore */
+ constructor(component, container, navigator, viewportCoords, spatial) {
+ super(component, container, navigator);
+ this._spatial = spatial;
+ this._viewportCoords = viewportCoords;
+ this._subscriptions = new SubscriptionHolder();
+ }
+ _enable() {
+ const earth$ = this._navigator.stateService.state$.pipe(map((state) => {
+ return state === State.Earth;
+ }), publishReplay(1), refCount());
+ const subs = this._subscriptions;
+ subs.push(earth$.pipe(switchMap((earth) => {
+ return earth ?
+ this._container.mouseService.mouseWheel$ :
+ empty();
+ }))
+ .subscribe((event) => {
+ event.preventDefault();
+ }));
+ subs.push(earth$.pipe(switchMap((earth) => {
+ if (!earth) {
+ return empty();
+ }
+ return MouseOperator.filteredPairwiseMouseDrag$(this._component.name, this._container.mouseService).pipe(filter(([e1, e2]) => {
+ return !(e1.ctrlKey && e2.ctrlKey);
+ }));
+ }), withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$), map(([[previous, current], render, transform]) => {
+ const planeNormal = [0, 0, 1];
+ const planePoint = [0, 0, -2];
+ const currentIntersection = this._planeIntersection(current, planeNormal, planePoint, render.perspective, this._container.container);
+ const previousIntersection = this._planeIntersection(previous, planeNormal, planePoint, render.perspective, this._container.container);
+ if (!currentIntersection || !previousIntersection) {
+ return null;
+ }
+ const direction = new Vector3()
+ .subVectors(currentIntersection, previousIntersection)
+ .multiplyScalar(-1)
+ .toArray();
+ return direction;
+ }), filter((direction) => {
+ return !!direction;
+ }))
+ .subscribe((direction) => {
+ this._navigator.stateService.truck(direction);
+ }));
+ subs.push(earth$.pipe(switchMap((earth) => {
+ if (!earth) {
+ return empty();
+ }
+ return MouseOperator.filteredPairwiseMouseDrag$(this._component.name, this._container.mouseService).pipe(filter(([e1, e2]) => {
+ return e1.ctrlKey && e2.ctrlKey;
+ }));
+ }), map(([previous, current]) => {
+ return this._mousePairToRotation(previous, current);
+ }))
+ .subscribe((rotation) => {
+ this._navigator.stateService.orbit(rotation);
+ }));
+ subs.push(earth$.pipe(switchMap((earth) => {
+ if (!earth) {
+ return empty();
+ }
+ return MouseOperator.filteredPairwiseMouseRightDrag$(this._component.name, this._container.mouseService).pipe(filter(([e1, e2]) => {
+ return !e1.ctrlKey && !e2.ctrlKey;
+ }));
+ }), map(([previous, current]) => {
+ return this._mousePairToRotation(previous, current);
+ }))
+ .subscribe((rotation) => {
+ this._navigator.stateService.orbit(rotation);
+ }));
+ subs.push(earth$.pipe(switchMap((earth) => {
+ if (!earth) {
+ return empty();
+ }
+ return this._container.mouseService
+ .filteredWheel$(this._component.name, this._container.mouseService.mouseWheel$);
+ }), map((event) => {
+ let delta = event.deltaY;
+ if (event.deltaMode === 1) {
+ delta = 40 * delta;
+ }
+ else if (event.deltaMode === 2) {
+ delta = 800 * delta;
+ }
+ const canvasSize = this._viewportCoords.containerToCanvas(this._container.container);
+ return -delta / canvasSize[1];
+ }))
+ .subscribe((delta) => {
+ this._navigator.stateService.dolly(delta);
+ }));
+ }
+ _disable() {
+ this._subscriptions.unsubscribe();
+ }
+ _getConfiguration() {
+ return {};
+ }
+ _eventToViewport(event, element) {
+ const previousCanvas = this._viewportCoords.canvasPosition(event, element);
+ return this._viewportCoords.canvasToViewport(previousCanvas[0], previousCanvas[1], element);
+ }
+ _mousePairToRotation(previous, current) {
+ const [currentX, currentY] = this._eventToViewport(current, this._container.container);
+ const [previousX, previousY] = this._eventToViewport(previous, this._container.container);
+ const phi = (previousX - currentX) * Math.PI;
+ const theta = (currentY - previousY) * Math.PI / 2;
+ return { phi: phi, theta: theta };
+ }
+ _planeIntersection(event, planeNormal, planePoint, camera, element) {
+ const [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+ const direction = this._viewportCoords
+ .unprojectFromCanvas(canvasX, canvasY, element, camera)
+ .sub(camera.position)
+ .normalize();
+ if (Math.abs(this._spatial.angleToPlane(direction.toArray(), planeNormal)) < Math.PI / 90) {
+ return null;
+ }
+ const l0 = camera.position.clone();
+ const n = new Vector3().fromArray(planeNormal);
+ const p0 = new Vector3().fromArray(planePoint);
+ const d = new Vector3().subVectors(p0, l0).dot(n) / direction.clone().dot(n);
+ const intersection = new Vector3().addVectors(l0, direction.multiplyScalar(d));
+ if (this._viewportCoords.worldToCamera(intersection.toArray(), camera)[2] > 0) {
+ return null;
+ }
+ return intersection;
+ }
+}
+
+/**
+ * The `ScrollZoomHandler` allows the user to zoom the viewer image by scrolling.
+ *
+ * @example
+ * ```js
+ * var pointerComponent = viewer.getComponent("pointer");
+ *
+ * pointerComponent.scrollZoom.disable();
+ * pointerComponent.scrollZoom.enable();
+ *
+ * var isEnabled = pointerComponent.scrollZoom.isEnabled;
+ * ```
+ */
+class ScrollZoomHandler extends HandlerBase {
+ /** @ignore */
+ constructor(component, container, navigator, viewportCoords) {
+ super(component, container, navigator);
+ this._viewportCoords = viewportCoords;
+ }
+ _enable() {
+ this._container.mouseService.claimWheel(this._component.name, 0);
+ this._preventDefaultSubscription = this._container.mouseService.mouseWheel$
+ .subscribe((event) => {
+ event.preventDefault();
+ });
+ this._zoomSubscription = this._container.mouseService
+ .filteredWheel$(this._component.name, this._container.mouseService.mouseWheel$).pipe(withLatestFrom(this._navigator.stateService.currentState$, (w, f) => {
+ return [w, f];
+ }), filter((args) => {
+ let state = args[1].state;
+ return isSpherical(state.currentImage.cameraType) ||
+ state.imagesAhead < 1;
+ }), map((args) => {
+ return args[0];
+ }), withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$, (w, r, t) => {
+ return [w, r, t];
+ }))
+ .subscribe((args) => {
+ let event = args[0];
+ let render = args[1];
+ let transform = args[2];
+ let element = this._container.container;
+ let [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+ let unprojected = this._viewportCoords.unprojectFromCanvas(canvasX, canvasY, element, render.perspective);
+ let reference = transform.projectBasic(unprojected.toArray());
+ let deltaY = event.deltaY;
+ if (event.deltaMode === 1) {
+ deltaY = 40 * deltaY;
+ }
+ else if (event.deltaMode === 2) {
+ deltaY = 800 * deltaY;
+ }
+ const canvasSize = this._viewportCoords.containerToCanvas(element);
+ let zoom = -3 * deltaY / canvasSize[1];
+ this._navigator.stateService.zoomIn(zoom, reference);
+ });
+ }
+ _disable() {
+ this._container.mouseService.unclaimWheel(this._component.name);
+ this._preventDefaultSubscription.unsubscribe();
+ this._zoomSubscription.unsubscribe();
+ this._preventDefaultSubscription = null;
+ this._zoomSubscription = null;
+ }
+ _getConfiguration(enable) {
+ return { scrollZoom: enable };
+ }
+}
+
+/**
+ * The `TouchZoomHandler` allows the user to zoom the viewer image by pinching on a touchscreen.
+ *
+ * @example
+ * ```js
+ * var pointerComponent = viewer.getComponent("pointer");
+ *
+ * pointerComponent.touchZoom.disable();
+ * pointerComponent.touchZoom.enable();
+ *
+ * var isEnabled = pointerComponent.touchZoom.isEnabled;
+ * ```
+ */
+class TouchZoomHandler extends HandlerBase {
+ /** @ignore */
+ constructor(component, container, navigator, viewportCoords) {
+ super(component, container, navigator);
+ this._viewportCoords = viewportCoords;
+ }
+ _enable() {
+ this._preventDefaultSubscription = this._container.touchService.pinch$
+ .subscribe((pinch) => {
+ pinch.originalEvent.preventDefault();
+ });
+ let pinchStarted$ = this._container.touchService.pinchStart$.pipe(map((event) => {
+ return true;
+ }));
+ let pinchStopped$ = this._container.touchService.pinchEnd$.pipe(map((event) => {
+ return false;
+ }));
+ this._activeSubscription = merge(pinchStarted$, pinchStopped$)
+ .subscribe(this._container.touchService.activate$);
+ this._zoomSubscription = this._container.touchService.pinch$.pipe(withLatestFrom(this._navigator.stateService.currentState$), filter((args) => {
+ let state = args[1].state;
+ return isSpherical(state.currentImage.cameraType) ||
+ state.imagesAhead < 1;
+ }), map((args) => {
+ return args[0];
+ }), withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$))
+ .subscribe(([pinch, render, transform]) => {
+ let element = this._container.container;
+ let [canvasX, canvasY] = this._viewportCoords.canvasPosition(pinch, element);
+ let unprojected = this._viewportCoords.unprojectFromCanvas(canvasX, canvasY, element, render.perspective);
+ let reference = transform.projectBasic(unprojected.toArray());
+ const [canvasWidth, canvasHeight] = this._viewportCoords.containerToCanvas(element);
+ let zoom = 3 * pinch.distanceChange / Math.min(canvasWidth, canvasHeight);
+ this._navigator.stateService.zoomIn(zoom, reference);
+ });
+ }
+ _disable() {
+ this._activeSubscription.unsubscribe();
+ this._preventDefaultSubscription.unsubscribe();
+ this._zoomSubscription.unsubscribe();
+ this._preventDefaultSubscription = null;
+ this._zoomSubscription = null;
+ }
+ _getConfiguration(enable) {
+ return { touchZoom: enable };
+ }
+}
+
+/**
+ * @class PointerComponent
+ *
+ * @classdesc Component handling mouse, pen, and touch events for camera movement.
+ *
+ * To retrive and use the mouse component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ ... });
+ *
+ * var pointerComponent = viewer.getComponent("pointer");
+ * ```
+ */
+class PointerComponent extends Component {
+ /** @ignore */
+ constructor(name, container, navigator) {
+ super(name, container, navigator);
+ const spatial = new Spatial();
+ const viewportCoords = new ViewportCoords();
+ this._bounceHandler =
+ new BounceHandler(this, container, navigator, viewportCoords, spatial);
+ this._dragPanHandler =
+ new DragPanHandler(this, container, navigator, viewportCoords, spatial);
+ this._earthControlHandler =
+ new EarthControlHandler(this, container, navigator, viewportCoords, spatial);
+ this._scrollZoomHandler =
+ new ScrollZoomHandler(this, container, navigator, viewportCoords);
+ this._touchZoomHandler =
+ new TouchZoomHandler(this, container, navigator, viewportCoords);
+ }
+ /**
+ * Get drag pan.
+ *
+ * @returns {DragPanHandler} The drag pan handler.
+ */
+ get dragPan() {
+ return this._dragPanHandler;
+ }
+ /**
+ * Get earth control.
+ *
+ * @returns {EarthControlHandler} The earth control handler.
+ */
+ get earthControl() {
+ return this._earthControlHandler;
+ }
+ /**
+ * Get scroll zoom.
+ *
+ * @returns {ScrollZoomHandler} The scroll zoom handler.
+ */
+ get scrollZoom() {
+ return this._scrollZoomHandler;
+ }
+ /**
+ * Get touch zoom.
+ *
+ * @returns {TouchZoomHandler} The touch zoom handler.
+ */
+ get touchZoom() {
+ return this._touchZoomHandler;
+ }
+ _activate() {
+ this._bounceHandler.enable();
+ this._subscriptions.push(this._configuration$
+ .subscribe((configuration) => {
+ if (configuration.dragPan) {
+ this._dragPanHandler.enable();
+ }
+ else {
+ this._dragPanHandler.disable();
+ }
+ if (configuration.earthControl) {
+ this._earthControlHandler.enable();
+ }
+ else {
+ this._earthControlHandler.disable();
+ }
+ if (configuration.scrollZoom) {
+ this._scrollZoomHandler.enable();
+ }
+ else {
+ this._scrollZoomHandler.disable();
+ }
+ if (configuration.touchZoom) {
+ this._touchZoomHandler.enable();
+ }
+ else {
+ this._touchZoomHandler.disable();
+ }
+ }));
+ this._container.mouseService.claimMouse(this._name, 0);
+ }
+ _deactivate() {
+ this._container.mouseService.unclaimMouse(this._name);
+ this._subscriptions.unsubscribe();
+ this._bounceHandler.disable();
+ this._dragPanHandler.disable();
+ this._earthControlHandler.disable();
+ this._scrollZoomHandler.disable();
+ this._touchZoomHandler.disable();
+ }
+ _getDefaultConfiguration() {
+ return {
+ dragPan: true,
+ earthControl: true,
+ scrollZoom: true,
+ touchZoom: true,
+ };
+ }
+}
+/** @inheritdoc */
+PointerComponent.componentName = "pointer";
+
+class DOM {
+ constructor(doc) {
+ this._document = !!doc ? doc : document;
+ }
+ get document() {
+ return this._document;
+ }
+ createElement(tagName, className, container) {
+ const element = this._document.createElement(tagName);
+ if (!!className) {
+ element.className = className;
+ }
+ if (!!container) {
+ container.appendChild(element);
+ }
+ return element;
+ }
+}
+
+/**
+ * @class PopupComponent
+ *
+ * @classdesc Component for showing HTML popup objects.
+ *
+ * The `add` method is used for adding new popups. Popups are removed by reference.
+ *
+ * It is not possible to update popups in the set by updating any properties
+ * directly on the popup object. Popups need to be replaced by
+ * removing them and creating new ones with relevant changed properties and
+ * adding those instead.
+ *
+ * Popups are only relevant to a single image because they are based on
+ * 2D basic image coordinates. Popups related to a certain image should
+ * be removed when the viewer is moved to another image.
+ *
+ * To retrive and use the popup component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ component: { popup: true }, ... });
+ *
+ * var popupComponent = viewer.getComponent("popup");
+ * ```
+ */
+class PopupComponent extends Component {
+ /** @ignore */
+ constructor(name, container, navigator, dom) {
+ super(name, container, navigator);
+ this._dom = !!dom ? dom : new DOM();
+ this._popups = [];
+ this._added$ = new Subject();
+ this._popups$ = new Subject();
+ }
+ /**
+ * Add popups to the popups set.
+ *
+ * @description Adding a new popup never replaces an old one
+ * because they are stored by reference. Adding an already
+ * existing popup has no effect.
+ *
+ * @param {Array<Popup>} popups - Popups to add.
+ *
+ * @example
+ * ```js
+ * popupComponent.add([popup1, popup2]);
+ * ```
+ */
+ add(popups) {
+ for (const popup of popups) {
+ if (this._popups.indexOf(popup) !== -1) {
+ continue;
+ }
+ this._popups.push(popup);
+ if (this._activated) {
+ popup.setParentContainer(this._popupContainer);
+ }
+ }
+ this._added$.next(popups);
+ this._popups$.next(this._popups);
+ }
+ /**
+ * Returns an array of all popups.
+ *
+ * @example
+ * ```js
+ * var popups = popupComponent.getAll();
+ * ```
+ */
+ getAll() {
+ return this._popups.slice();
+ }
+ /**
+ * Remove popups based on reference from the popup set.
+ *
+ * @param {Array<Popup>} popups - Popups to remove.
+ *
+ * @example
+ * ```js
+ * popupComponent.remove([popup1, popup2]);
+ * ```
+ */
+ remove(popups) {
+ for (const popup of popups) {
+ this._remove(popup);
+ }
+ this._popups$.next(this._popups);
+ }
+ /**
+ * Remove all popups from the popup set.
+ *
+ * @example
+ * ```js
+ * popupComponent.removeAll();
+ * ```
+ */
+ removeAll() {
+ for (const popup of this._popups.slice()) {
+ this._remove(popup);
+ }
+ this._popups$.next(this._popups);
+ }
+ _activate() {
+ this._popupContainer = this._dom.createElement("div", "mapillary-popup-container", this._container.container);
+ for (const popup of this._popups) {
+ popup.setParentContainer(this._popupContainer);
+ }
+ const subs = this._subscriptions;
+ subs.push(combineLatest(this._container.renderService.renderCamera$, this._container.renderService.size$, this._navigator.stateService.currentTransform$)
+ .subscribe(([renderCamera, size, transform]) => {
+ for (const popup of this._popups) {
+ popup.update(renderCamera, size, transform);
+ }
+ }));
+ const changed$ = this._popups$.pipe(startWith(this._popups), switchMap((popups) => {
+ return from(popups).pipe(mergeMap((popup) => {
+ return popup.changed$;
+ }));
+ }), map((popup) => {
+ return [popup];
+ }));
+ subs.push(merge(this._added$, changed$).pipe(withLatestFrom(this._container.renderService.renderCamera$, this._container.renderService.size$, this._navigator.stateService.currentTransform$))
+ .subscribe(([popups, renderCamera, size, transform]) => {
+ for (const popup of popups) {
+ popup.update(renderCamera, size, transform);
+ }
+ }));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ for (const popup of this._popups) {
+ popup.remove();
+ }
+ this._container.container.removeChild(this._popupContainer);
+ delete this._popupContainer;
+ }
+ _getDefaultConfiguration() {
+ return {};
+ }
+ _remove(popup) {
+ const index = this._popups.indexOf(popup);
+ if (index === -1) {
+ return;
+ }
+ const removed = this._popups.splice(index, 1)[0];
+ if (this._activated) {
+ removed.remove();
+ }
+ }
+}
+PopupComponent.componentName = "popup";
+
+/**
+ * Enumeration for graph modes.
+ * @enum {number}
+ * @readonly
+ * @description Modes for the retrieval and caching performed
+ * by the graph service on the graph.
+ */
+var GraphMode;
+(function (GraphMode) {
+ /**
+ * Caching is performed on sequences only and sequence edges are
+ * calculated. Spatial tiles
+ * are not retrieved and spatial edges are not calculated when
+ * caching nodes. Complete sequences are being cached for requested
+ * nodes within the graph.
+ */
+ GraphMode[GraphMode["Sequence"] = 0] = "Sequence";
+ /**
+ * Caching is performed with emphasis on spatial data. Sequence edges
+ * as well as spatial edges are cached. Sequence data
+ * is still requested but complete sequences are not being cached
+ * for requested nodes.
+ *
+ * This is the initial mode of the graph service.
+ */
+ GraphMode[GraphMode["Spatial"] = 1] = "Spatial";
+})(GraphMode || (GraphMode = {}));
+
+var SequenceMode;
+(function (SequenceMode) {
+ SequenceMode[SequenceMode["Default"] = 0] = "Default";
+ SequenceMode[SequenceMode["Playback"] = 1] = "Playback";
+ SequenceMode[SequenceMode["Timeline"] = 2] = "Timeline";
+})(SequenceMode || (SequenceMode = {}));
+
+class SequenceDOMRenderer {
+ constructor(container) {
+ this._container = container;
+ this._minThresholdWidth = 320;
+ this._maxThresholdWidth = 1480;
+ this._minThresholdHeight = 240;
+ this._maxThresholdHeight = 820;
+ this._stepperDefaultWidth = 108;
+ this._controlsDefaultWidth = 88;
+ this._defaultHeight = 30;
+ this._expandControls = false;
+ this._mode = SequenceMode.Default;
+ this._speed = 0.5;
+ this._changingSpeed = false;
+ this._index = null;
+ this._changingPosition = false;
+ this._mouseEnterDirection$ = new Subject();
+ this._mouseLeaveDirection$ = new Subject();
+ this._notifyChanged$ = new Subject();
+ this._notifyChangingPositionChanged$ = new Subject();
+ this._notifySpeedChanged$ = new Subject();
+ this._notifyIndexChanged$ = new Subject();
+ }
+ get changed$() {
+ return this._notifyChanged$;
+ }
+ get changingPositionChanged$() {
+ return this._notifyChangingPositionChanged$;
+ }
+ get speed$() {
+ return this._notifySpeedChanged$;
+ }
+ get index$() {
+ return this._notifyIndexChanged$;
+ }
+ get mouseEnterDirection$() {
+ return this._mouseEnterDirection$;
+ }
+ get mouseLeaveDirection$() {
+ return this._mouseLeaveDirection$;
+ }
+ activate() {
+ if (!!this._changingSubscription) {
+ return;
+ }
+ this._changingSubscription = merge(this._container.mouseService.documentMouseUp$, this._container.touchService.touchEnd$.pipe(filter((touchEvent) => {
+ return touchEvent.touches.length === 0;
+ })))
+ .subscribe(() => {
+ if (this._changingSpeed) {
+ this._changingSpeed = false;
+ }
+ if (this._changingPosition) {
+ this._setChangingPosition(false);
+ }
+ });
+ }
+ deactivate() {
+ if (!this._changingSubscription) {
+ return;
+ }
+ this._changingSpeed = false;
+ this._changingPosition = false;
+ this._expandControls = false;
+ this._mode = SequenceMode.Default;
+ this._changingSubscription.unsubscribe();
+ this._changingSubscription = null;
+ }
+ render(edgeStatus, configuration, containerWidth, speed, index, max, playEnabled, component, navigator) {
+ if (configuration.visible === false) {
+ return virtualDom.h("div.mapillary-sequence-container", {}, []);
+ }
+ const stepper = this._createStepper(edgeStatus, configuration, playEnabled, containerWidth, component, navigator);
+ const controls = this._createSequenceControls(containerWidth);
+ const playback = this._createPlaybackControls(containerWidth, speed, component, configuration);
+ const timeline = this._createTimelineControls(containerWidth, index, max);
+ return virtualDom.h("div.mapillary-sequence-container", [stepper, controls, playback, timeline]);
+ }
+ getContainerWidth(size, configuration) {
+ let minWidth = configuration.minWidth;
+ let maxWidth = configuration.maxWidth;
+ if (maxWidth < minWidth) {
+ maxWidth = minWidth;
+ }
+ let relativeWidth = (size.width - this._minThresholdWidth) / (this._maxThresholdWidth - this._minThresholdWidth);
+ let relativeHeight = (size.height - this._minThresholdHeight) / (this._maxThresholdHeight - this._minThresholdHeight);
+ let coeff = Math.max(0, Math.min(1, Math.min(relativeWidth, relativeHeight)));
+ return minWidth + coeff * (maxWidth - minWidth);
+ }
+ _createPositionInput(index, max) {
+ this._index = index;
+ const onPosition = (e) => {
+ this._index = Number(e.target.value);
+ this._notifyIndexChanged$.next(this._index);
+ };
+ const boundingRect = this._container.domContainer.getBoundingClientRect();
+ const width = Math.max(276, Math.min(410, 5 + 0.8 * boundingRect.width)) - 65;
+ const onStart = (e) => {
+ e.stopPropagation();
+ this._setChangingPosition(true);
+ };
+ const onMove = (e) => {
+ if (this._changingPosition === true) {
+ e.stopPropagation();
+ }
+ };
+ const onKeyDown = (e) => {
+ if (e.key === "ArrowDown" || e.key === "ArrowLeft" ||
+ e.key === "ArrowRight" || e.key === "ArrowUp") {
+ e.preventDefault();
+ }
+ };
+ const positionInputProperties = {
+ max: max != null ? max : 1,
+ min: 0,
+ onchange: onPosition,
+ oninput: onPosition,
+ onkeydown: onKeyDown,
+ onpointerdown: onStart,
+ onpointermove: onMove,
+ ontouchmove: onMove,
+ ontouchstart: onStart,
+ style: {
+ width: `${width}px`,
+ },
+ type: "range",
+ value: index != null ? index : 0,
+ };
+ const disabled = index == null || max == null || max <= 1;
+ if (disabled) {
+ positionInputProperties.disabled = "true";
+ }
+ const positionInput = virtualDom.h("input.mapillary-sequence-position", positionInputProperties, []);
+ const positionContainerClass = disabled ? ".mapillary-sequence-position-container-inactive" : ".mapillary-sequence-position-container";
+ return virtualDom.h("div" + positionContainerClass, [positionInput]);
+ }
+ _createSpeedInput(speed) {
+ this._speed = speed;
+ const onSpeed = (e) => {
+ this._speed = Number(e.target.value) / 1000;
+ this._notifySpeedChanged$.next(this._speed);
+ };
+ const boundingRect = this._container.domContainer.getBoundingClientRect();
+ const width = Math.max(276, Math.min(410, 5 + 0.8 * boundingRect.width)) - 160;
+ const onStart = (e) => {
+ this._changingSpeed = true;
+ e.stopPropagation();
+ };
+ const onMove = (e) => {
+ if (this._changingSpeed === true) {
+ e.stopPropagation();
+ }
+ };
+ const onKeyDown = (e) => {
+ if (e.key === "ArrowDown" || e.key === "ArrowLeft" ||
+ e.key === "ArrowRight" || e.key === "ArrowUp") {
+ e.preventDefault();
+ }
+ };
+ const speedInput = virtualDom.h("input.mapillary-sequence-speed", {
+ max: 1000,
+ min: 0,
+ onchange: onSpeed,
+ oninput: onSpeed,
+ onkeydown: onKeyDown,
+ onpointerdown: onStart,
+ onpointermove: onMove,
+ ontouchmove: onMove,
+ ontouchstart: onStart,
+ style: {
+ width: `${width}px`,
+ },
+ type: "range",
+ value: 1000 * speed,
+ }, []);
+ return virtualDom.h("div.mapillary-sequence-speed-container", [speedInput]);
+ }
+ _createPlaybackControls(containerWidth, speed, component, configuration) {
+ if (this._mode !== SequenceMode.Playback) {
+ return virtualDom.h("div.mapillary-sequence-playback", []);
+ }
+ const switchIcon = virtualDom.h("div.mapillary-sequence-switch-icon.mapillary-sequence-icon-visible", []);
+ const direction = configuration.direction === NavigationDirection.Next ?
+ NavigationDirection.Prev : NavigationDirection.Next;
+ const playing = configuration.playing;
+ const switchButtonProperties = {
+ onclick: () => {
+ if (!playing) {
+ component.configure({ direction });
+ }
+ },
+ };
+ const switchButtonClassName = configuration.playing ? ".mapillary-sequence-switch-button-inactive" : ".mapillary-sequence-switch-button";
+ const switchButton = virtualDom.h("div" + switchButtonClassName, switchButtonProperties, [switchIcon]);
+ const slowIcon = virtualDom.h("div.mapillary-sequence-slow-icon.mapillary-sequence-icon-visible", []);
+ const slowContainer = virtualDom.h("div.mapillary-sequence-slow-container", [slowIcon]);
+ const fastIcon = virtualDom.h("div.mapillary-sequence-fast-icon.mapillary-sequence-icon-visible", []);
+ const fastContainer = virtualDom.h("div.mapillary-sequence-fast-container", [fastIcon]);
+ const closeIcon = virtualDom.h("div.mapillary-sequence-close-icon.mapillary-sequence-icon-visible", []);
+ const closeButtonProperties = {
+ onclick: () => {
+ this._mode = SequenceMode.Default;
+ this._notifyChanged$.next(this);
+ },
+ };
+ const closeButton = virtualDom.h("div.mapillary-sequence-close-button", closeButtonProperties, [closeIcon]);
+ const speedInput = this._createSpeedInput(speed);
+ const playbackChildren = [switchButton, slowContainer, speedInput, fastContainer, closeButton];
+ const top = Math.round(containerWidth / this._stepperDefaultWidth * this._defaultHeight + 10);
+ const playbackProperties = { style: { top: `${top}px` } };
+ return virtualDom.h("div.mapillary-sequence-playback", playbackProperties, playbackChildren);
+ }
+ _createPlayingButton(nextId, prevId, playEnabled, configuration, component) {
+ let canPlay = (configuration.direction === NavigationDirection.Next && nextId != null) ||
+ (configuration.direction === NavigationDirection.Prev && prevId != null);
+ canPlay = canPlay && playEnabled;
+ let onclick = configuration.playing ?
+ () => { component.stop(); } :
+ canPlay ? () => { component.play(); } : null;
+ let buttonProperties = { onclick: onclick };
+ let iconProperties = {};
+ if (configuration.direction === NavigationDirection.Prev) {
+ iconProperties.style = {
+ transform: "rotate(180deg) translate(50%, 50%)",
+ };
+ }
+ let icon = virtualDom.h("div.mapillary-sequence-icon", iconProperties, []);
+ let buttonClass = configuration.playing ?
+ "mapillary-sequence-stop" :
+ canPlay ?
+ "mapillary-sequence-play" :
+ "mapillary-sequence-play-inactive";
+ return virtualDom.h("div." + buttonClass, buttonProperties, [icon]);
+ }
+ _createSequenceControls(containerWidth) {
+ const borderRadius = Math.round(8 / this._stepperDefaultWidth * containerWidth);
+ const expanderProperties = {
+ onclick: () => {
+ this._expandControls = !this._expandControls;
+ this._mode = SequenceMode.Default;
+ this._notifyChanged$.next(this);
+ },
+ style: {
+ "border-bottom-right-radius": `${borderRadius}px`,
+ "border-top-right-radius": `${borderRadius}px`,
+ },
+ };
+ const expanderBar = virtualDom.h("div.mapillary-sequence-expander-bar", []);
+ const expander = virtualDom.h("div.mapillary-sequence-expander-button", expanderProperties, [expanderBar]);
+ const fastIconClassName = this._mode === SequenceMode.Playback ?
+ ".mapillary-sequence-fast-icon-gray.mapillary-sequence-icon-visible" : ".mapillary-sequence-fast-icon";
+ const fastIcon = virtualDom.h("div" + fastIconClassName, []);
+ const playbackProperties = {
+ onclick: () => {
+ this._mode = this._mode === SequenceMode.Playback ?
+ SequenceMode.Default :
+ SequenceMode.Playback;
+ this._notifyChanged$.next(this);
+ },
+ };
+ const playback = virtualDom.h("div.mapillary-sequence-playback-button", playbackProperties, [fastIcon]);
+ const timelineIconClassName = this._mode === SequenceMode.Timeline ?
+ ".mapillary-sequence-timeline-icon-gray.mapillary-sequence-icon-visible" : ".mapillary-sequence-timeline-icon";
+ const timelineIcon = virtualDom.h("div" + timelineIconClassName, []);
+ const timelineProperties = {
+ onclick: () => {
+ this._mode = this._mode === SequenceMode.Timeline ?
+ SequenceMode.Default :
+ SequenceMode.Timeline;
+ this._notifyChanged$.next(this);
+ },
+ };
+ const timeline = virtualDom.h("div.mapillary-sequence-timeline-button", timelineProperties, [timelineIcon]);
+ const properties = {
+ style: {
+ height: (this._defaultHeight / this._stepperDefaultWidth * containerWidth) + "px",
+ transform: `translate(${containerWidth / 2 + 2}px, 0)`,
+ width: (this._controlsDefaultWidth / this._stepperDefaultWidth * containerWidth) + "px",
+ },
+ };
+ const className = ".mapillary-sequence-controls" +
+ (this._expandControls ? ".mapillary-sequence-controls-expanded" : "");
+ return virtualDom.h("div" + className, properties, [playback, timeline, expander]);
+ }
+ _createSequenceArrows(nextId, prevId, containerWidth, configuration, navigator) {
+ let nextProperties = {
+ onclick: nextId != null ?
+ () => {
+ navigator.moveDir$(NavigationDirection.Next)
+ .subscribe(undefined, (error) => {
+ if (!(error instanceof CancelMapillaryError)) {
+ console.error(error);
+ }
+ });
+ } :
+ null,
+ onpointerenter: () => { this._mouseEnterDirection$.next(NavigationDirection.Next); },
+ onpointerleave: () => { this._mouseLeaveDirection$.next(NavigationDirection.Next); },
+ };
+ const borderRadius = Math.round(8 / this._stepperDefaultWidth * containerWidth);
+ let prevProperties = {
+ onclick: prevId != null ?
+ () => {
+ navigator.moveDir$(NavigationDirection.Prev)
+ .subscribe(undefined, (error) => {
+ if (!(error instanceof CancelMapillaryError)) {
+ console.error(error);
+ }
+ });
+ } :
+ null,
+ onpointerenter: () => { this._mouseEnterDirection$.next(NavigationDirection.Prev); },
+ onpointerleave: () => { this._mouseLeaveDirection$.next(NavigationDirection.Prev); },
+ style: {
+ "border-bottom-left-radius": `${borderRadius}px`,
+ "border-top-left-radius": `${borderRadius}px`,
+ },
+ };
+ let nextClass = this._getStepClassName(NavigationDirection.Next, nextId, configuration.highlightId);
+ let prevClass = this._getStepClassName(NavigationDirection.Prev, prevId, configuration.highlightId);
+ let nextIcon = virtualDom.h("div.mapillary-sequence-icon", []);
+ let prevIcon = virtualDom.h("div.mapillary-sequence-icon", []);
+ return [
+ virtualDom.h("div." + prevClass, prevProperties, [prevIcon]),
+ virtualDom.h("div." + nextClass, nextProperties, [nextIcon]),
+ ];
+ }
+ _createStepper(edgeStatus, configuration, playEnabled, containerWidth, component, navigator) {
+ let nextId = null;
+ let prevId = null;
+ for (let edge of edgeStatus.edges) {
+ if (edge.data.direction === NavigationDirection.Next) {
+ nextId = edge.target;
+ }
+ if (edge.data.direction === NavigationDirection.Prev) {
+ prevId = edge.target;
+ }
+ }
+ const playingButton = this._createPlayingButton(nextId, prevId, playEnabled, configuration, component);
+ const buttons = this._createSequenceArrows(nextId, prevId, containerWidth, configuration, navigator);
+ buttons.splice(1, 0, playingButton);
+ const containerProperties = {
+ oncontextmenu: (event) => { event.preventDefault(); },
+ style: {
+ height: (this._defaultHeight / this._stepperDefaultWidth * containerWidth) + "px",
+ width: containerWidth + "px",
+ },
+ };
+ return virtualDom.h("div.mapillary-sequence-stepper", containerProperties, buttons);
+ }
+ _createTimelineControls(containerWidth, index, max) {
+ if (this._mode !== SequenceMode.Timeline) {
+ return virtualDom.h("div.mapillary-sequence-timeline", []);
+ }
+ const positionInput = this._createPositionInput(index, max);
+ const closeIcon = virtualDom.h("div.mapillary-sequence-close-icon.mapillary-sequence-icon-visible", []);
+ const closeButtonProperties = {
+ onclick: () => {
+ this._mode = SequenceMode.Default;
+ this._notifyChanged$.next(this);
+ },
+ };
+ const closeButton = virtualDom.h("div.mapillary-sequence-close-button", closeButtonProperties, [closeIcon]);
+ const top = Math.round(containerWidth / this._stepperDefaultWidth * this._defaultHeight + 10);
+ const playbackProperties = { style: { top: `${top}px` } };
+ return virtualDom.h("div.mapillary-sequence-timeline", playbackProperties, [positionInput, closeButton]);
+ }
+ _getStepClassName(direction, imageId, highlightId) {
+ let className = direction === NavigationDirection.Next ?
+ "mapillary-sequence-step-next" :
+ "mapillary-sequence-step-prev";
+ if (imageId == null) {
+ className += "-inactive";
+ }
+ else {
+ if (highlightId === imageId) {
+ className += "-highlight";
+ }
+ }
+ return className;
+ }
+ _setChangingPosition(value) {
+ this._changingPosition = value;
+ this._notifyChangingPositionChanged$.next(value);
+ }
+}
+
+/**
+ * @class SequenceComponent
+ * @classdesc Component showing navigation arrows for sequence directions
+ * as well as playing button. Exposes an API to start and stop play.
+ */
+class SequenceComponent extends Component {
+ constructor(name, container, navigator, renderer, scheduler) {
+ super(name, container, navigator);
+ this._sequenceDOMRenderer = !!renderer ? renderer : new SequenceDOMRenderer(container);
+ this._scheduler = scheduler;
+ this._containerWidth$ = new Subject();
+ this._hoveredIdSubject$ = new Subject();
+ this._hoveredId$ = this._hoveredIdSubject$.pipe(share());
+ this._navigator.playService.playing$.pipe(skip(1), withLatestFrom(this._configuration$))
+ .subscribe(([playing, configuration]) => {
+ const type = "playing";
+ const event = {
+ playing,
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ if (playing === configuration.playing) {
+ return;
+ }
+ if (playing) {
+ this.play();
+ }
+ else {
+ this.stop();
+ }
+ });
+ this._navigator.playService.direction$.pipe(skip(1), withLatestFrom(this._configuration$))
+ .subscribe(([direction, configuration]) => {
+ if (direction !== configuration.direction) {
+ this.configure({ direction });
+ }
+ });
+ }
+ fire(type, event) {
+ super.fire(type, event);
+ }
+ off(type, handler) {
+ super.off(type, handler);
+ }
+ on(type, handler) {
+ super.on(type, handler);
+ }
+ /**
+ * Start playing.
+ *
+ * @fires playing
+ */
+ play() { this.configure({ playing: true }); }
+ /**
+ * Stop playing.
+ *
+ * @fires playing
+ */
+ stop() { this.configure({ playing: false }); }
+ _activate() {
+ this._sequenceDOMRenderer.activate();
+ const edgeStatus$ = this._navigator.stateService.currentImage$.pipe(switchMap((image) => {
+ return image.sequenceEdges$;
+ }), publishReplay(1), refCount());
+ const sequence$ = this._navigator.stateService.currentImage$.pipe(distinctUntilChanged(undefined, (image) => {
+ return image.sequenceId;
+ }), switchMap((image) => {
+ return concat(of(null), this._navigator.graphService.cacheSequence$(image.sequenceId).pipe(retry(3), catchError((e) => {
+ console.error("Failed to cache sequence", e);
+ return of(null);
+ })));
+ }), startWith(null), publishReplay(1), refCount());
+ const subs = this._subscriptions;
+ subs.push(sequence$.subscribe());
+ const rendererId$ = this._sequenceDOMRenderer.index$.pipe(withLatestFrom(sequence$), map(([index, sequence]) => {
+ return sequence != null ? sequence.imageIds[index] : null;
+ }), filter((id) => {
+ return !!id;
+ }), distinctUntilChanged(), publish(), refCount());
+ subs.push(merge(rendererId$.pipe(debounceTime(100, this._scheduler)), rendererId$.pipe(auditTime(400, this._scheduler))).pipe(distinctUntilChanged(), switchMap((id) => {
+ return this._navigator.moveTo$(id).pipe(catchError(() => {
+ return empty();
+ }));
+ }))
+ .subscribe());
+ subs.push(this._sequenceDOMRenderer.changingPositionChanged$.pipe(filter((changing) => {
+ return changing;
+ }))
+ .subscribe(() => {
+ this._navigator.graphService.setGraphMode(GraphMode.Sequence);
+ }));
+ subs.push(this._sequenceDOMRenderer.changingPositionChanged$.pipe(filter((changing) => {
+ return !changing;
+ }))
+ .subscribe(() => {
+ this._navigator.graphService.setGraphMode(GraphMode.Spatial);
+ }));
+ this._navigator.graphService.graphMode$.pipe(switchMap((mode) => {
+ return mode === GraphMode.Spatial ?
+ this._navigator.stateService.currentImage$.pipe(take(2)) :
+ empty();
+ }), filter((image) => {
+ return !image.spatialEdges.cached;
+ }), switchMap((image) => {
+ return this._navigator.graphService.cacheImage$(image.id).pipe(catchError(() => {
+ return empty();
+ }));
+ }))
+ .subscribe();
+ subs.push(this._sequenceDOMRenderer.changingPositionChanged$.pipe(filter((changing) => {
+ return changing;
+ }))
+ .subscribe(() => {
+ this._navigator.playService.stop();
+ }));
+ subs.push(combineLatest(this._navigator.graphService.graphMode$, this._sequenceDOMRenderer.changingPositionChanged$.pipe(startWith(false), distinctUntilChanged())).pipe(withLatestFrom(this._navigator.stateService.currentImage$), switchMap(([[mode, changing], image]) => {
+ return changing && mode === GraphMode.Sequence ?
+ this._navigator.graphService.cacheSequenceImages$(image.sequenceId, image.id).pipe(retry(3), catchError((error) => {
+ console.error("Failed to cache sequence images.", error);
+ return empty();
+ })) :
+ empty();
+ }))
+ .subscribe());
+ const position$ = sequence$.pipe(switchMap((sequence) => {
+ if (!sequence) {
+ return of({ index: null, max: null });
+ }
+ let firstCurrentId = true;
+ return this._sequenceDOMRenderer.changingPositionChanged$.pipe(startWith(false), distinctUntilChanged(), switchMap((changingPosition) => {
+ const skipCount = !changingPosition &&
+ firstCurrentId ?
+ 0 : 1;
+ firstCurrentId = false;
+ return changingPosition ?
+ rendererId$ :
+ this._navigator.stateService.currentImage$.pipe(map((image) => {
+ return image.id;
+ }), distinctUntilChanged(), skip(skipCount));
+ }), map((imageId) => {
+ const index = sequence.imageIds.indexOf(imageId);
+ if (index === -1) {
+ return { index: null, max: null };
+ }
+ return { index: index, max: sequence.imageIds.length - 1 };
+ }));
+ }));
+ const earth$ = this._navigator.stateService.state$.pipe(map((state) => {
+ return state === State.Earth;
+ }), distinctUntilChanged());
+ subs.push(combineLatest(edgeStatus$, this._configuration$, this._containerWidth$, this._sequenceDOMRenderer.changed$.pipe(startWith(this._sequenceDOMRenderer)), this._navigator.playService.speed$, position$, earth$).pipe(map(([edgeStatus, configuration, containerWidth, , speed, position, earth]) => {
+ const vNode = this._sequenceDOMRenderer
+ .render(edgeStatus, configuration, containerWidth, speed, position.index, position.max, !earth, this, this._navigator);
+ return { name: this._name, vNode: vNode };
+ }))
+ .subscribe(this._container.domRenderer.render$));
+ subs.push(this._sequenceDOMRenderer.speed$
+ .subscribe((speed) => {
+ this._navigator.playService.setSpeed(speed);
+ }));
+ subs.push(this._configuration$.pipe(map((configuration) => {
+ return configuration.direction;
+ }), distinctUntilChanged())
+ .subscribe((direction) => {
+ this._navigator.playService.setDirection(direction);
+ }));
+ subs.push(combineLatest(this._container.renderService.size$, this._configuration$.pipe(distinctUntilChanged((value1, value2) => {
+ return value1[0] === value2[0] && value1[1] === value2[1];
+ }, (configuration) => {
+ return [configuration.minWidth, configuration.maxWidth];
+ }))).pipe(map(([size, configuration]) => {
+ return this._sequenceDOMRenderer.getContainerWidth(size, configuration);
+ }))
+ .subscribe(this._containerWidth$));
+ subs.push(this._configuration$.pipe(map((configuration) => {
+ return configuration.playing;
+ }), distinctUntilChanged())
+ .subscribe((playing) => {
+ if (playing) {
+ this._navigator.playService.play();
+ }
+ else {
+ this._navigator.playService.stop();
+ }
+ }));
+ subs.push(this._sequenceDOMRenderer.mouseEnterDirection$.pipe(switchMap((direction) => {
+ const edgeTo$ = edgeStatus$.pipe(map((edgeStatus) => {
+ for (let edge of edgeStatus.edges) {
+ if (edge.data.direction === direction) {
+ return edge.target;
+ }
+ }
+ return null;
+ }), takeUntil(this._sequenceDOMRenderer.mouseLeaveDirection$));
+ return concat(edgeTo$, of(null));
+ }), distinctUntilChanged())
+ .subscribe(this._hoveredIdSubject$));
+ subs.push(this._hoveredId$
+ .subscribe((id) => {
+ const type = "hover";
+ const event = {
+ id,
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ }));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ this._sequenceDOMRenderer.deactivate();
+ }
+ _getDefaultConfiguration() {
+ return {
+ direction: NavigationDirection.Next,
+ maxWidth: 108,
+ minWidth: 70,
+ playing: false,
+ visible: true,
+ };
+ }
+}
+/** @inheritdoc */
+SequenceComponent.componentName = "sequence";
+
+/**
+ * Enumeration for slider mode.
+ *
+ * @enum {number}
+ * @readonly
+ *
+ * @description Modes for specifying how transitions
+ * between images are performed in slider mode. Only
+ * applicable when the slider component determines
+ * that transitions with motion is possilble. When it
+ * is not, the stationary mode will be applied.
+ */
+var SliderConfigurationMode;
+(function (SliderConfigurationMode) {
+ /**
+ * Transitions with motion.
+ *
+ * @description The slider component moves the
+ * camera between the image origins.
+ *
+ * In this mode it is not possible to zoom or pan.
+ *
+ * The slider component falls back to stationary
+ * mode when it determines that the pair of images
+ * does not have a strong enough relation.
+ */
+ SliderConfigurationMode[SliderConfigurationMode["Motion"] = 0] = "Motion";
+ /**
+ * Stationary transitions.
+ *
+ * @description The camera is stationary.
+ *
+ * In this mode it is possible to zoom and pan.
+ */
+ SliderConfigurationMode[SliderConfigurationMode["Stationary"] = 1] = "Stationary";
+})(SliderConfigurationMode || (SliderConfigurationMode = {}));
+
+class SliderGLRenderer {
+ constructor() {
+ this._factory = new MeshFactory();
+ this._scene = new MeshScene();
+ this._spatial = new Spatial();
+ this._currentKey = null;
+ this._previousKey = null;
+ this._disabled = false;
+ this._curtain = 1;
+ this._frameId = 0;
+ this._needsRender = false;
+ this._mode = null;
+ this._currentProviderDisposers = {};
+ this._previousProviderDisposers = {};
+ }
+ get disabled() {
+ return this._disabled;
+ }
+ get frameId() {
+ return this._frameId;
+ }
+ get needsRender() {
+ return this._needsRender;
+ }
+ setTextureProvider(key, provider) {
+ this._setTextureProvider(key, this._currentKey, provider, this._currentProviderDisposers, this._updateTexture.bind(this));
+ }
+ setTextureProviderPrev(key, provider) {
+ this._setTextureProvider(key, this._previousKey, provider, this._previousProviderDisposers, this._updateTexturePrev.bind(this));
+ }
+ update(frame, mode) {
+ this._updateFrameId(frame.id);
+ this._updateImagePlanes(frame.state, mode);
+ }
+ updateCurtain(curtain) {
+ if (this._curtain === curtain) {
+ return;
+ }
+ this._curtain = curtain;
+ this._updateCurtain();
+ this._needsRender = true;
+ }
+ updateTexture(imageElement, image) {
+ const planes = image.id === this._currentKey ?
+ this._scene.planes :
+ image.id === this._previousKey ?
+ this._scene.planesOld :
+ {};
+ if (Object.keys(planes).length === 0) {
+ return;
+ }
+ this._needsRender = true;
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ let material = plane.material;
+ let texture = material.uniforms.projectorTex.value;
+ texture.image = imageElement;
+ texture.needsUpdate = true;
+ }
+ }
+ updateTextureImage(imageElement, image) {
+ if (this._currentKey !== image.id) {
+ return;
+ }
+ this._needsRender = true;
+ const planes = this._scene.planes;
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ let material = plane.material;
+ let texture = material.uniforms.projectorTex.value;
+ texture.image = imageElement;
+ texture.needsUpdate = true;
+ }
+ }
+ render(perspectiveCamera, renderer) {
+ if (!this.disabled) {
+ renderer.render(this._scene.sceneOld, perspectiveCamera);
+ }
+ renderer.render(this._scene.scene, perspectiveCamera);
+ this._needsRender = false;
+ }
+ dispose() {
+ this._scene.clear();
+ for (const key in this._currentProviderDisposers) {
+ if (!this._currentProviderDisposers.hasOwnProperty(key)) {
+ continue;
+ }
+ this._currentProviderDisposers[key]();
+ }
+ for (const key in this._previousProviderDisposers) {
+ if (!this._previousProviderDisposers.hasOwnProperty(key)) {
+ continue;
+ }
+ this._previousProviderDisposers[key]();
+ }
+ this._currentProviderDisposers = {};
+ this._previousProviderDisposers = {};
+ }
+ _getBasicCorners(currentAspect, previousAspect) {
+ let offsetX;
+ let offsetY;
+ if (currentAspect > previousAspect) {
+ offsetX = 0.5;
+ offsetY = 0.5 * currentAspect / previousAspect;
+ }
+ else {
+ offsetX = 0.5 * previousAspect / currentAspect;
+ offsetY = 0.5;
+ }
+ return [[0.5 - offsetX, 0.5 - offsetY], [0.5 + offsetX, 0.5 + offsetY]];
+ }
+ _setDisabled(state) {
+ this._disabled = state.currentImage == null ||
+ state.previousImage == null ||
+ (isSpherical(state.currentImage.cameraType) &&
+ !isSpherical(state.previousImage.cameraType));
+ }
+ _setTextureProvider(key, originalKey, provider, providerDisposers, updateTexture) {
+ if (key !== originalKey) {
+ return;
+ }
+ let createdSubscription = provider.textureCreated$
+ .subscribe(updateTexture);
+ let updatedSubscription = provider.textureUpdated$
+ .subscribe((updated) => {
+ this._needsRender = true;
+ });
+ let dispose = () => {
+ createdSubscription.unsubscribe();
+ updatedSubscription.unsubscribe();
+ provider.dispose();
+ };
+ if (key in providerDisposers) {
+ let disposeProvider = providerDisposers[key];
+ disposeProvider();
+ delete providerDisposers[key];
+ }
+ providerDisposers[key] = dispose;
+ }
+ _updateCurtain() {
+ const planes = this._scene.planes;
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ let shaderMaterial = plane.material;
+ if (!!shaderMaterial.uniforms.curtain) {
+ shaderMaterial.uniforms.curtain.value = this._curtain;
+ }
+ }
+ }
+ _updateFrameId(frameId) {
+ this._frameId = frameId;
+ }
+ _updateImagePlanes(state, mode) {
+ const currentChanged = state.currentImage != null && this._currentKey !== state.currentImage.id;
+ const previousChanged = state.previousImage != null && this._previousKey !== state.previousImage.id;
+ const modeChanged = this._mode !== mode;
+ if (!(currentChanged || previousChanged || modeChanged)) {
+ return;
+ }
+ this._setDisabled(state);
+ this._needsRender = true;
+ this._mode = mode;
+ const motionless = state.motionless ||
+ mode === SliderConfigurationMode.Stationary ||
+ isSpherical(state.currentImage.cameraType);
+ if (this.disabled || previousChanged) {
+ if (this._previousKey in this._previousProviderDisposers) {
+ this._previousProviderDisposers[this._previousKey]();
+ delete this._previousProviderDisposers[this._previousKey];
+ }
+ }
+ if (this.disabled) {
+ this._scene.setImagePlanesOld({});
+ }
+ else {
+ if (previousChanged || modeChanged) {
+ const previousNode = state.previousImage;
+ this._previousKey = previousNode.id;
+ const elements = state.currentTransform.rt.elements;
+ let translation = [elements[12], elements[13], elements[14]];
+ const currentAspect = state.currentTransform.basicAspect;
+ const previousAspect = state.previousTransform.basicAspect;
+ const textureScale = currentAspect > previousAspect ?
+ [1, previousAspect / currentAspect] :
+ [currentAspect / previousAspect, 1];
+ let rotation = state.currentImage.rotation;
+ let width = state.currentImage.width;
+ let height = state.currentImage.height;
+ if (isSpherical(previousNode.cameraType)) {
+ rotation = state.previousImage.rotation;
+ translation = this._spatial
+ .rotate(this._spatial
+ .opticalCenter(state.currentImage.rotation, translation)
+ .toArray(), rotation)
+ .multiplyScalar(-1)
+ .toArray();
+ width = state.previousImage.width;
+ height = state.previousImage.height;
+ }
+ const transform = new Transform(state.currentImage.exifOrientation, width, height, state.currentImage.scale, rotation, translation, previousNode.image, textureScale, state.currentImage.cameraParameters, state.currentImage.cameraType);
+ let mesh = undefined;
+ if (isSpherical(previousNode.cameraType)) {
+ mesh = this._factory.createMesh(previousNode, motionless ||
+ isSpherical(state.currentImage.cameraType) ?
+ transform : state.previousTransform);
+ }
+ else {
+ if (motionless) {
+ const [[basicX0, basicY0], [basicX1, basicY1]] = this._getBasicCorners(currentAspect, previousAspect);
+ mesh = this._factory.createFlatMesh(state.previousImage, transform, basicX0, basicX1, basicY0, basicY1);
+ }
+ else {
+ mesh = this._factory.createMesh(state.previousImage, state.previousTransform);
+ }
+ }
+ const previousPlanes = {};
+ previousPlanes[previousNode.id] = mesh;
+ this._scene.setImagePlanesOld(previousPlanes);
+ }
+ }
+ if (currentChanged || modeChanged) {
+ if (this._currentKey in this._currentProviderDisposers) {
+ this._currentProviderDisposers[this._currentKey]();
+ delete this._currentProviderDisposers[this._currentKey];
+ }
+ this._currentKey = state.currentImage.id;
+ const planes = {};
+ if (isSpherical(state.currentImage.cameraType)) {
+ planes[state.currentImage.id] =
+ this._factory.createCurtainMesh(state.currentImage, state.currentTransform);
+ }
+ else {
+ if (motionless) {
+ planes[state.currentImage.id] = this._factory.createDistortedCurtainMesh(state.currentImage, state.currentTransform);
+ }
+ else {
+ planes[state.currentImage.id] = this._factory.createCurtainMesh(state.currentImage, state.currentTransform);
+ }
+ }
+ this._scene.setImagePlanes(planes);
+ this._updateCurtain();
+ }
+ }
+ _updateTexture(texture) {
+ this._needsRender = true;
+ const planes = this._scene.planes;
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ let material = plane.material;
+ let oldTexture = material.uniforms.projectorTex.value;
+ material.uniforms.projectorTex.value = null;
+ oldTexture.dispose();
+ material.uniforms.projectorTex.value = texture;
+ }
+ }
+ _updateTexturePrev(texture) {
+ this._needsRender = true;
+ const planes = this._scene.planesOld;
+ for (const key in planes) {
+ if (!planes.hasOwnProperty(key)) {
+ continue;
+ }
+ const plane = planes[key];
+ let material = plane.material;
+ let oldTexture = material.uniforms.projectorTex.value;
+ material.uniforms.projectorTex.value = null;
+ oldTexture.dispose();
+ material.uniforms.projectorTex.value = texture;
+ }
+ }
+}
+
+class SliderDOMRenderer {
+ constructor(container) {
+ this._container = container;
+ this._interacting = false;
+ this._notifyModeChanged$ = new Subject();
+ this._notifyPositionChanged$ = new Subject();
+ this._stopInteractionSubscription = null;
+ }
+ get mode$() {
+ return this._notifyModeChanged$;
+ }
+ get position$() {
+ return this._notifyPositionChanged$;
+ }
+ activate() {
+ if (!!this._stopInteractionSubscription) {
+ return;
+ }
+ this._stopInteractionSubscription = merge(this._container.mouseService.documentMouseUp$, this._container.touchService.touchEnd$.pipe(filter((touchEvent) => {
+ return touchEvent.touches.length === 0;
+ })))
+ .subscribe((event) => {
+ if (this._interacting) {
+ this._interacting = false;
+ }
+ });
+ }
+ deactivate() {
+ if (!this._stopInteractionSubscription) {
+ return;
+ }
+ this._interacting = false;
+ this._stopInteractionSubscription.unsubscribe();
+ this._stopInteractionSubscription = null;
+ }
+ render(position, mode, motionless, spherical, visible) {
+ const children = [];
+ if (visible) {
+ children.push(virtualDom.h("div.mapillary-slider-border", []));
+ const modeVisible = !(motionless || spherical);
+ if (modeVisible) {
+ children.push(this._createModeButton(mode));
+ children.push(this._createModeButton2d(mode));
+ }
+ children.push(this._createPositionInput(position, modeVisible));
+ }
+ const boundingRect = this._container.domContainer.getBoundingClientRect();
+ const width = Math.max(215, Math.min(400, boundingRect.width - 100));
+ return virtualDom.h("div.mapillary-slider-container", { style: { width: `${width}px` } }, children);
+ }
+ _createModeButton(mode) {
+ const properties = {
+ onclick: () => {
+ if (mode === SliderConfigurationMode.Motion) {
+ return;
+ }
+ this._notifyModeChanged$.next(SliderConfigurationMode.Motion);
+ },
+ };
+ const className = mode === SliderConfigurationMode.Stationary ?
+ "mapillary-slider-mode-button-inactive" :
+ "mapillary-slider-mode-button";
+ return virtualDom.h("div." + className, properties, [virtualDom.h("div.mapillary-slider-mode-icon", [])]);
+ }
+ _createModeButton2d(mode) {
+ const properties = {
+ onclick: () => {
+ if (mode === SliderConfigurationMode.Stationary) {
+ return;
+ }
+ this._notifyModeChanged$.next(SliderConfigurationMode.Stationary);
+ },
+ };
+ const className = mode === SliderConfigurationMode.Motion ?
+ "mapillary-slider-mode-button-2d-inactive" :
+ "mapillary-slider-mode-button-2d";
+ return virtualDom.h("div." + className, properties, [virtualDom.h("div.mapillary-slider-mode-icon-2d", [])]);
+ }
+ _createPositionInput(position, modeVisible) {
+ const onChange = (e) => {
+ this._notifyPositionChanged$.next(Number(e.target.value) / 1000);
+ };
+ const onStart = (e) => {
+ this._interacting = true;
+ e.stopPropagation();
+ };
+ const onMove = (e) => {
+ if (this._interacting) {
+ e.stopPropagation();
+ }
+ };
+ const onKeyDown = (e) => {
+ if (e.key === "ArrowDown" || e.key === "ArrowLeft" ||
+ e.key === "ArrowRight" || e.key === "ArrowUp") {
+ e.preventDefault();
+ }
+ };
+ const boundingRect = this._container.domContainer.getBoundingClientRect();
+ const width = Math.max(215, Math.min(400, boundingRect.width - 105)) - 84 + (modeVisible ? 0 : 52);
+ const positionInput = virtualDom.h("input.mapillary-slider-position", {
+ max: 1000,
+ min: 0,
+ onchange: onChange,
+ oninput: onChange,
+ onkeydown: onKeyDown,
+ onpointerdown: onStart,
+ onpointermove: onMove,
+ ontouchmove: onMove,
+ ontouchstart: onStart,
+ style: {
+ width: `${width}px`,
+ },
+ type: "range",
+ value: 1000 * position,
+ }, []);
+ return virtualDom.h("div.mapillary-slider-position-container", [positionInput]);
+ }
+}
+
+/**
+ * @class SliderComponent
+ *
+ * @classdesc Component for comparing pairs of images. Renders
+ * a slider for adjusting the curtain of the first image.
+ *
+ * Deactivate the sequence, direction and image plane
+ * components when activating the slider component to avoid
+ * interfering UI elements.
+ *
+ * To retrive and use the slider component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ ... });
+ *
+ * viewer.deactivateComponent("image");
+ * viewer.deactivateComponent("direction");
+ * viewer.deactivateComponent("sequence");
+ *
+ * viewer.activateComponent("slider");
+ *
+ * var sliderComponent = viewer.getComponent("slider");
+ * ```
+ */
+class SliderComponent extends Component {
+ /** @ignore */
+ constructor(name, container, navigator, viewportCoords) {
+ super(name, container, navigator);
+ this._viewportCoords = !!viewportCoords ? viewportCoords : new ViewportCoords();
+ this._domRenderer = new SliderDOMRenderer(container);
+ this._imageTileLoader = new TileLoader(navigator.api);
+ this._roiCalculator = new RegionOfInterestCalculator();
+ this._spatial = new Spatial();
+ this._glRendererOperation$ = new Subject();
+ this._glRendererCreator$ = new Subject();
+ this._glRendererDisposer$ = new Subject();
+ this._glRenderer$ = this._glRendererOperation$.pipe(scan((glRenderer, operation) => {
+ return operation(glRenderer);
+ }, null), filter((glRenderer) => {
+ return glRenderer != null;
+ }), distinctUntilChanged(undefined, (glRenderer) => {
+ return glRenderer.frameId;
+ }));
+ this._glRendererCreator$.pipe(map(() => {
+ return (glRenderer) => {
+ if (glRenderer != null) {
+ throw new Error("Multiple slider states can not be created at the same time");
+ }
+ return new SliderGLRenderer();
+ };
+ }))
+ .subscribe(this._glRendererOperation$);
+ this._glRendererDisposer$.pipe(map(() => {
+ return (glRenderer) => {
+ glRenderer.dispose();
+ return null;
+ };
+ }))
+ .subscribe(this._glRendererOperation$);
+ }
+ _activate() {
+ const subs = this._subscriptions;
+ subs.push(this._domRenderer.mode$
+ .subscribe((mode) => {
+ this.configure({ mode });
+ }));
+ subs.push(this._glRenderer$.pipe(map((glRenderer) => {
+ let renderHash = {
+ name: this._name,
+ renderer: {
+ frameId: glRenderer.frameId,
+ needsRender: glRenderer.needsRender,
+ render: glRenderer.render.bind(glRenderer),
+ pass: RenderPass$1.Background,
+ },
+ };
+ return renderHash;
+ }))
+ .subscribe(this._container.glRenderer.render$));
+ const position$ = concat(this.configuration$.pipe(map((configuration) => {
+ return configuration.initialPosition != null ?
+ configuration.initialPosition : 1;
+ }), first()), this._domRenderer.position$);
+ const mode$ = this.configuration$.pipe(map((configuration) => {
+ return configuration.mode;
+ }), distinctUntilChanged());
+ const motionless$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+ return frame.state.motionless;
+ }), distinctUntilChanged());
+ const spherical$ = this._navigator.stateService.currentState$.pipe(map((frame) => {
+ return isSpherical(frame.state.currentImage.cameraType);
+ }), distinctUntilChanged());
+ const sliderVisible$ = combineLatest(this._configuration$.pipe(map((configuration) => {
+ return configuration.sliderVisible;
+ })), this._navigator.stateService.currentState$.pipe(map((frame) => {
+ return !(frame.state.currentImage == null ||
+ frame.state.previousImage == null ||
+ (isSpherical(frame.state.currentImage.cameraType) &&
+ !isSpherical(frame.state.previousImage.cameraType)));
+ }), distinctUntilChanged())).pipe(map(([sliderVisible, enabledState]) => {
+ return sliderVisible && enabledState;
+ }), distinctUntilChanged());
+ this._waitSubscription = combineLatest(mode$, motionless$, spherical$, sliderVisible$).pipe(withLatestFrom(this._navigator.stateService.state$))
+ .subscribe(([[mode, motionless, spherical, sliderVisible], state]) => {
+ const interactive = sliderVisible &&
+ (motionless ||
+ mode === SliderConfigurationMode.Stationary ||
+ spherical);
+ if (interactive && state !== State.WaitingInteractively) {
+ this._navigator.stateService.waitInteractively();
+ }
+ else if (!interactive && state !== State.Waiting) {
+ this._navigator.stateService.wait();
+ }
+ });
+ subs.push(combineLatest(position$, mode$, motionless$, spherical$, sliderVisible$)
+ .subscribe(([position, mode, motionless, spherical]) => {
+ if (motionless || mode === SliderConfigurationMode.Stationary || spherical) {
+ this._navigator.stateService.moveTo(1);
+ }
+ else {
+ this._navigator.stateService.moveTo(position);
+ }
+ }));
+ subs.push(combineLatest(position$, mode$, motionless$, spherical$, sliderVisible$, this._container.renderService.size$).pipe(map(([position, mode, motionless, spherical, sliderVisible]) => {
+ return {
+ name: this._name,
+ vNode: this._domRenderer.render(position, mode, motionless, spherical, sliderVisible),
+ };
+ }))
+ .subscribe(this._container.domRenderer.render$));
+ this._glRendererCreator$.next(null);
+ subs.push(combineLatest(position$, spherical$, sliderVisible$, this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$).pipe(map(([position, spherical, visible, render, transform]) => {
+ if (!spherical) {
+ return visible ? position : 1;
+ }
+ const basicMin = this._viewportCoords.viewportToBasic(-1.15, 0, transform, render.perspective);
+ const basicMax = this._viewportCoords.viewportToBasic(1.15, 0, transform, render.perspective);
+ const shiftedMax = basicMax[0] < basicMin[0] ? basicMax[0] + 1 : basicMax[0];
+ const basicPosition = basicMin[0] + position * (shiftedMax - basicMin[0]);
+ return basicPosition > 1 ? basicPosition - 1 : basicPosition;
+ }), map((position) => {
+ return (glRenderer) => {
+ glRenderer.updateCurtain(position);
+ return glRenderer;
+ };
+ }))
+ .subscribe(this._glRendererOperation$));
+ subs.push(combineLatest(this._navigator.stateService.currentState$, mode$).pipe(map(([frame, mode]) => {
+ return (glRenderer) => {
+ glRenderer.update(frame, mode);
+ return glRenderer;
+ };
+ }))
+ .subscribe(this._glRendererOperation$));
+ subs.push(this._configuration$.pipe(filter((configuration) => {
+ return configuration.ids != null;
+ }), switchMap((configuration) => {
+ return zip(zip(this._catchCacheImage$(configuration.ids.background), this._catchCacheImage$(configuration.ids.foreground)).pipe(map((images) => {
+ return { background: images[0], foreground: images[1] };
+ })), this._navigator.stateService.currentState$.pipe(first())).pipe(map((nf) => {
+ return { images: nf[0], state: nf[1].state };
+ }));
+ }))
+ .subscribe((co) => {
+ if (co.state.currentImage != null &&
+ co.state.previousImage != null &&
+ co.state.currentImage.id === co.images.foreground.id &&
+ co.state.previousImage.id === co.images.background.id) {
+ return;
+ }
+ if (co.state.currentImage.id === co.images.background.id) {
+ this._navigator.stateService.setImages([co.images.foreground]);
+ return;
+ }
+ if (co.state.currentImage.id === co.images.foreground.id &&
+ co.state.trajectory.length === 1) {
+ this._navigator.stateService.prependImages([co.images.background]);
+ return;
+ }
+ this._navigator.stateService.setImages([co.images.background]);
+ this._navigator.stateService.setImages([co.images.foreground]);
+ }, (e) => {
+ console.error(e);
+ }));
+ const textureProvider$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+ return active ?
+ this._navigator.stateService.currentState$ :
+ new Subject();
+ }), distinctUntilChanged(undefined, (frame) => {
+ return frame.state.currentImage.id;
+ }), withLatestFrom(this._container.glRenderer.webGLRenderer$, this._container.renderService.size$), map(([frame, renderer, size]) => {
+ const state = frame.state;
+ Math.max(size.width, size.height);
+ const currentImage = state.currentImage;
+ const currentTransform = state.currentTransform;
+ return new TextureProvider(currentImage.id, currentTransform.basicWidth, currentTransform.basicHeight, currentImage.image, this._imageTileLoader, new TileStore(), renderer);
+ }), publishReplay(1), refCount());
+ subs.push(textureProvider$.subscribe(() => { }));
+ subs.push(textureProvider$.pipe(map((provider) => {
+ return (renderer) => {
+ renderer.setTextureProvider(provider.id, provider);
+ return renderer;
+ };
+ }))
+ .subscribe(this._glRendererOperation$));
+ subs.push(textureProvider$.pipe(pairwise())
+ .subscribe((pair) => {
+ let previous = pair[0];
+ previous.abort();
+ }));
+ const roiTrigger$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+ return active ?
+ combineLatest(this._container.renderService.renderCameraFrame$, this._container.renderService.size$.pipe(debounceTime(250))) :
+ new Subject();
+ }), map(([camera, size]) => {
+ return [
+ camera.camera.position.clone(),
+ camera.camera.lookat.clone(),
+ camera.zoom.valueOf(),
+ size.height.valueOf(),
+ size.width.valueOf()
+ ];
+ }), pairwise(), skipWhile((pls) => {
+ return pls[1][2] - pls[0][2] < 0 || pls[1][2] === 0;
+ }), map((pls) => {
+ let samePosition = pls[0][0].equals(pls[1][0]);
+ let sameLookat = pls[0][1].equals(pls[1][1]);
+ let sameZoom = pls[0][2] === pls[1][2];
+ let sameHeight = pls[0][3] === pls[1][3];
+ let sameWidth = pls[0][4] === pls[1][4];
+ return samePosition && sameLookat && sameZoom && sameHeight && sameWidth;
+ }), distinctUntilChanged(), filter((stalled) => {
+ return stalled;
+ }), switchMap(() => {
+ return this._container.renderService.renderCameraFrame$.pipe(first());
+ }), withLatestFrom(this._container.renderService.size$, this._navigator.stateService.currentTransform$));
+ subs.push(textureProvider$.pipe(switchMap((provider) => {
+ return roiTrigger$.pipe(map(([camera, size, transform]) => {
+ return [
+ this._roiCalculator.computeRegionOfInterest(camera, size, transform),
+ provider,
+ ];
+ }));
+ }), filter((args) => {
+ return !args[1].disposed;
+ }))
+ .subscribe((args) => {
+ let roi = args[0];
+ let provider = args[1];
+ provider.setRegionOfInterest(roi);
+ }));
+ const hasTexture$ = textureProvider$.pipe(switchMap((provider) => {
+ return provider.hasTexture$;
+ }), startWith(false), publishReplay(1), refCount());
+ subs.push(hasTexture$.subscribe(() => { }));
+ const textureProviderPrev$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+ return active ?
+ this._navigator.stateService.currentState$ :
+ new Subject();
+ }), filter((frame) => {
+ return !!frame.state.previousImage;
+ }), distinctUntilChanged(undefined, (frame) => {
+ return frame.state.previousImage.id;
+ }), withLatestFrom(this._container.glRenderer.webGLRenderer$, this._container.renderService.size$), map(([frame, renderer, size]) => {
+ const state = frame.state;
+ const previousImage = state.previousImage;
+ const previousTransform = state.previousTransform;
+ return new TextureProvider(previousImage.id, previousTransform.basicWidth, previousTransform.basicHeight, previousImage.image, this._imageTileLoader, new TileStore(), renderer);
+ }), publishReplay(1), refCount());
+ subs.push(textureProviderPrev$.subscribe(() => { }));
+ subs.push(textureProviderPrev$.pipe(map((provider) => {
+ return (renderer) => {
+ renderer.setTextureProviderPrev(provider.id, provider);
+ return renderer;
+ };
+ }))
+ .subscribe(this._glRendererOperation$));
+ subs.push(textureProviderPrev$.pipe(pairwise())
+ .subscribe((pair) => {
+ let previous = pair[0];
+ previous.abort();
+ }));
+ const roiTriggerPrev$ = this._container.configurationService.imageTiling$.pipe(switchMap((active) => {
+ return active ?
+ combineLatest(this._container.renderService.renderCameraFrame$, this._container.renderService.size$.pipe(debounceTime(250))) :
+ new Subject();
+ }), map(([camera, size]) => {
+ return [
+ camera.camera.position.clone(),
+ camera.camera.lookat.clone(),
+ camera.zoom.valueOf(),
+ size.height.valueOf(),
+ size.width.valueOf()
+ ];
+ }), pairwise(), skipWhile((pls) => {
+ return pls[1][2] - pls[0][2] < 0 || pls[1][2] === 0;
+ }), map((pls) => {
+ let samePosition = pls[0][0].equals(pls[1][0]);
+ let sameLookat = pls[0][1].equals(pls[1][1]);
+ let sameZoom = pls[0][2] === pls[1][2];
+ let sameHeight = pls[0][3] === pls[1][3];
+ let sameWidth = pls[0][4] === pls[1][4];
+ return samePosition && sameLookat && sameZoom && sameHeight && sameWidth;
+ }), distinctUntilChanged(), filter((stalled) => {
+ return stalled;
+ }), switchMap(() => {
+ return this._container.renderService.renderCameraFrame$.pipe(first());
+ }), withLatestFrom(this._container.renderService.size$, this._navigator.stateService.currentTransform$));
+ subs.push(textureProviderPrev$.pipe(switchMap((provider) => {
+ return roiTriggerPrev$.pipe(map(([camera, size, transform]) => {
+ return [
+ this._roiCalculator.computeRegionOfInterest(camera, size, transform),
+ provider,
+ ];
+ }));
+ }), filter((args) => {
+ return !args[1].disposed;
+ }), withLatestFrom(this._navigator.stateService.currentState$))
+ .subscribe(([[roi, provider], frame]) => {
+ let shiftedRoi = null;
+ if (isSpherical(frame.state.previousImage.cameraType)) {
+ if (isSpherical(frame.state.currentImage.cameraType)) {
+ const currentViewingDirection = this._spatial.viewingDirection(frame.state.currentImage.rotation);
+ const previousViewingDirection = this._spatial.viewingDirection(frame.state.previousImage.rotation);
+ const directionDiff = this._spatial.angleBetweenVector2(currentViewingDirection.x, currentViewingDirection.y, previousViewingDirection.x, previousViewingDirection.y);
+ const shift = directionDiff / (2 * Math.PI);
+ const bbox = {
+ maxX: this._spatial.wrap(roi.bbox.maxX + shift, 0, 1),
+ maxY: roi.bbox.maxY,
+ minX: this._spatial.wrap(roi.bbox.minX + shift, 0, 1),
+ minY: roi.bbox.minY,
+ };
+ shiftedRoi = {
+ bbox: bbox,
+ pixelHeight: roi.pixelHeight,
+ pixelWidth: roi.pixelWidth,
+ };
+ }
+ else {
+ const currentViewingDirection = this._spatial.viewingDirection(frame.state.currentImage.rotation);
+ const previousViewingDirection = this._spatial.viewingDirection(frame.state.previousImage.rotation);
+ const directionDiff = this._spatial.angleBetweenVector2(currentViewingDirection.x, currentViewingDirection.y, previousViewingDirection.x, previousViewingDirection.y);
+ const shiftX = directionDiff / (2 * Math.PI);
+ const a1 = this._spatial.angleToPlane(currentViewingDirection.toArray(), [0, 0, 1]);
+ const a2 = this._spatial.angleToPlane(previousViewingDirection.toArray(), [0, 0, 1]);
+ const shiftY = (a2 - a1) / (2 * Math.PI);
+ const currentTransform = frame.state.currentTransform;
+ const size = Math.max(currentTransform.basicWidth, currentTransform.basicHeight);
+ const hFov = size > 0 ?
+ 2 * Math.atan(0.5 * currentTransform.basicWidth / (size * currentTransform.focal)) :
+ Math.PI / 3;
+ const vFov = size > 0 ?
+ 2 * Math.atan(0.5 * currentTransform.basicHeight / (size * currentTransform.focal)) :
+ Math.PI / 3;
+ const spanningWidth = hFov / (2 * Math.PI);
+ const spanningHeight = vFov / Math.PI;
+ const basicWidth = (roi.bbox.maxX - roi.bbox.minX) * spanningWidth;
+ const basicHeight = (roi.bbox.maxY - roi.bbox.minY) * spanningHeight;
+ const pixelWidth = roi.pixelWidth * spanningWidth;
+ const pixelHeight = roi.pixelHeight * spanningHeight;
+ const zoomShiftX = (roi.bbox.minX + roi.bbox.maxX) / 2 - 0.5;
+ const zoomShiftY = (roi.bbox.minY + roi.bbox.maxY) / 2 - 0.5;
+ const minX = 0.5 + shiftX + spanningWidth * zoomShiftX - basicWidth / 2;
+ const maxX = 0.5 + shiftX + spanningWidth * zoomShiftX + basicWidth / 2;
+ const minY = 0.5 + shiftY + spanningHeight * zoomShiftY - basicHeight / 2;
+ const maxY = 0.5 + shiftY + spanningHeight * zoomShiftY + basicHeight / 2;
+ const bbox = {
+ maxX: this._spatial.wrap(maxX, 0, 1),
+ maxY: maxY,
+ minX: this._spatial.wrap(minX, 0, 1),
+ minY: minY,
+ };
+ shiftedRoi = {
+ bbox: bbox,
+ pixelHeight: pixelHeight,
+ pixelWidth: pixelWidth,
+ };
+ }
+ }
+ else {
+ const currentBasicAspect = frame.state.currentTransform.basicAspect;
+ const previousBasicAspect = frame.state.previousTransform.basicAspect;
+ const [[cornerMinX, cornerMinY], [cornerMaxX, cornerMaxY]] = this._getBasicCorners(currentBasicAspect, previousBasicAspect);
+ const basicWidth = cornerMaxX - cornerMinX;
+ const basicHeight = cornerMaxY - cornerMinY;
+ const pixelWidth = roi.pixelWidth / basicWidth;
+ const pixelHeight = roi.pixelHeight / basicHeight;
+ const minX = (basicWidth - 1) / (2 * basicWidth) + roi.bbox.minX / basicWidth;
+ const maxX = (basicWidth - 1) / (2 * basicWidth) + roi.bbox.maxX / basicWidth;
+ const minY = (basicHeight - 1) / (2 * basicHeight) + roi.bbox.minY / basicHeight;
+ const maxY = (basicHeight - 1) / (2 * basicHeight) + roi.bbox.maxY / basicHeight;
+ const bbox = {
+ maxX: maxX,
+ maxY: maxY,
+ minX: minX,
+ minY: minY,
+ };
+ this._clipBoundingBox(bbox);
+ shiftedRoi = {
+ bbox: bbox,
+ pixelHeight: pixelHeight,
+ pixelWidth: pixelWidth,
+ };
+ }
+ provider.setRegionOfInterest(shiftedRoi);
+ }));
+ const hasTexturePrev$ = textureProviderPrev$.pipe(switchMap((provider) => {
+ return provider.hasTexture$;
+ }), startWith(false), publishReplay(1), refCount());
+ subs.push(hasTexturePrev$.subscribe(() => { }));
+ }
+ _deactivate() {
+ this._waitSubscription.unsubscribe();
+ this._navigator.stateService.state$.pipe(first())
+ .subscribe((state) => {
+ if (state !== State.Traversing) {
+ this._navigator.stateService.traverse();
+ }
+ });
+ this._glRendererDisposer$.next(null);
+ this._domRenderer.deactivate();
+ this._subscriptions.unsubscribe();
+ this.configure({ ids: null });
+ }
+ _getDefaultConfiguration() {
+ return {
+ initialPosition: 1,
+ mode: SliderConfigurationMode.Motion,
+ sliderVisible: true,
+ };
+ }
+ _catchCacheImage$(imageId) {
+ return this._navigator.graphService.cacheImage$(imageId).pipe(catchError((error) => {
+ console.error(`Failed to cache slider image (${imageId})`, error);
+ return empty();
+ }));
+ }
+ _getBasicCorners(currentAspect, previousAspect) {
+ let offsetX;
+ let offsetY;
+ if (currentAspect > previousAspect) {
+ offsetX = 0.5;
+ offsetY = 0.5 * currentAspect / previousAspect;
+ }
+ else {
+ offsetX = 0.5 * previousAspect / currentAspect;
+ offsetY = 0.5;
+ }
+ return [[0.5 - offsetX, 0.5 - offsetY], [0.5 + offsetX, 0.5 + offsetY]];
+ }
+ _clipBoundingBox(bbox) {
+ bbox.minX = Math.max(0, Math.min(1, bbox.minX));
+ bbox.maxX = Math.max(0, Math.min(1, bbox.maxX));
+ bbox.minY = Math.max(0, Math.min(1, bbox.minY));
+ bbox.maxY = Math.max(0, Math.min(1, bbox.maxY));
+ }
+}
+SliderComponent.componentName = "slider";
+
+class PlayService {
+ constructor(graphService, stateService) {
+ this._subscriptions = new SubscriptionHolder();
+ this._graphService = graphService;
+ this._stateService = stateService;
+ const subs = this._subscriptions;
+ this._directionSubject$ = new Subject();
+ this._direction$ = this._directionSubject$.pipe(startWith(NavigationDirection.Next), publishReplay(1), refCount());
+ subs.push(this._direction$.subscribe());
+ this._playing = false;
+ this._playingSubject$ = new Subject();
+ this._playing$ = this._playingSubject$.pipe(startWith(this._playing), publishReplay(1), refCount());
+ subs.push(this._playing$.subscribe());
+ this._speed = 0.5;
+ this._speedSubject$ = new Subject();
+ this._speed$ = this._speedSubject$.pipe(startWith(this._speed), publishReplay(1), refCount());
+ subs.push(this._speed$.subscribe());
+ this._imagesAhead = this._mapImagesAhead(this._mapSpeed(this._speed));
+ this._bridging$ = null;
+ }
+ get playing() {
+ return this._playing;
+ }
+ get direction$() {
+ return this._direction$;
+ }
+ get playing$() {
+ return this._playing$;
+ }
+ get speed$() {
+ return this._speed$;
+ }
+ play() {
+ if (this._playing) {
+ return;
+ }
+ this._stateService.cutImages();
+ const stateSpeed = this._setSpeed(this._speed);
+ this._stateService.setSpeed(stateSpeed);
+ this._graphModeSubscription = this._speed$.pipe(map((speed) => {
+ return speed > PlayService.sequenceSpeed ? GraphMode.Sequence : GraphMode.Spatial;
+ }), distinctUntilChanged())
+ .subscribe((mode) => {
+ this._graphService.setGraphMode(mode);
+ });
+ this._cacheSubscription = combineLatest(this._stateService.currentImage$.pipe(map((image) => {
+ return [image.sequenceId, image.id];
+ }), distinctUntilChanged(undefined, ([sequenceId]) => {
+ return sequenceId;
+ })), this._graphService.graphMode$, this._direction$).pipe(switchMap(([[sequenceId, imageId], mode, direction]) => {
+ if (direction !== NavigationDirection.Next && direction !== NavigationDirection.Prev) {
+ return of([undefined, direction]);
+ }
+ const sequence$ = (mode === GraphMode.Sequence ?
+ this._graphService.cacheSequenceImages$(sequenceId, imageId) :
+ this._graphService.cacheSequence$(sequenceId)).pipe(retry(3), catchError((error) => {
+ console.error(error);
+ return of(undefined);
+ }));
+ return combineLatest(sequence$, of(direction));
+ }), switchMap(([sequence, direction]) => {
+ if (sequence === undefined) {
+ return empty();
+ }
+ const imageIds = sequence.imageIds.slice();
+ if (direction === NavigationDirection.Prev) {
+ imageIds.reverse();
+ }
+ return this._stateService.currentState$.pipe(map((frame) => {
+ return [frame.state.trajectory[frame.state.trajectory.length - 1].id, frame.state.imagesAhead];
+ }), scan(([lastRequestKey, previousRequestKeys], [lastTrajectoryKey, imagesAhead]) => {
+ if (lastRequestKey === undefined) {
+ lastRequestKey = lastTrajectoryKey;
+ }
+ const lastIndex = imageIds.length - 1;
+ if (imagesAhead >= this._imagesAhead || imageIds[lastIndex] === lastRequestKey) {
+ return [lastRequestKey, []];
+ }
+ const current = imageIds.indexOf(lastTrajectoryKey);
+ const start = imageIds.indexOf(lastRequestKey) + 1;
+ const end = Math.min(lastIndex, current + this._imagesAhead - imagesAhead) + 1;
+ if (end <= start) {
+ return [lastRequestKey, []];
+ }
+ return [imageIds[end - 1], imageIds.slice(start, end)];
+ }, [undefined, []]), mergeMap(([lastRequestKey, newRequestKeys]) => {
+ return from(newRequestKeys);
+ }));
+ }), mergeMap((key) => {
+ return this._graphService.cacheImage$(key).pipe(catchError(() => {
+ return empty();
+ }));
+ }, 6))
+ .subscribe();
+ this._playingSubscription = this._stateService.currentState$.pipe(filter((frame) => {
+ return frame.state.imagesAhead < this._imagesAhead;
+ }), distinctUntilChanged(undefined, (frame) => {
+ return frame.state.lastImage.id;
+ }), map((frame) => {
+ const lastImage = frame.state.lastImage;
+ const trajectory = frame.state.trajectory;
+ let increasingTime = undefined;
+ for (let i = trajectory.length - 2; i >= 0; i--) {
+ const image = trajectory[i];
+ if (image.sequenceId !== lastImage.sequenceId) {
+ break;
+ }
+ if (image.capturedAt !== lastImage.capturedAt) {
+ increasingTime = image.capturedAt < lastImage.capturedAt;
+ break;
+ }
+ }
+ return [frame.state.lastImage, increasingTime];
+ }), withLatestFrom(this._direction$), switchMap(([[image, increasingTime], direction]) => {
+ return zip(([NavigationDirection.Next, NavigationDirection.Prev].indexOf(direction) > -1 ?
+ image.sequenceEdges$ :
+ image.spatialEdges$).pipe(first((status) => {
+ return status.cached;
+ }), timeout(15000)), of(direction)).pipe(map(([s, d]) => {
+ for (let edge of s.edges) {
+ if (edge.data.direction === d) {
+ return edge.target;
+ }
+ }
+ return null;
+ }), switchMap((key) => {
+ return key != null ?
+ this._graphService.cacheImage$(key) :
+ empty();
+ }));
+ }))
+ .subscribe((image) => {
+ this._stateService.appendImagess([image]);
+ }, (error) => {
+ console.error(error);
+ this.stop();
+ });
+ this._clearSubscription = this._stateService.currentImage$.pipe(bufferCount(1, 10))
+ .subscribe((images) => {
+ this._stateService.clearPriorImages();
+ });
+ this._setPlaying(true);
+ const currentLastImages$ = this._stateService.currentState$.pipe(map((frame) => {
+ return frame.state;
+ }), distinctUntilChanged(([kc1, kl1], [kc2, kl2]) => {
+ return kc1 === kc2 && kl1 === kl2;
+ }, (state) => {
+ return [state.currentImage.id, state.lastImage.id];
+ }), filter((state) => {
+ return state.currentImage.id === state.lastImage.id &&
+ state.currentIndex === state.trajectory.length - 1;
+ }), map((state) => {
+ return state.currentImage;
+ }));
+ this._stopSubscription = combineLatest(currentLastImages$, this._direction$).pipe(switchMap(([image, direction]) => {
+ const edgeStatus$ = ([NavigationDirection.Next, NavigationDirection.Prev].indexOf(direction) > -1 ?
+ image.sequenceEdges$ :
+ image.spatialEdges$).pipe(first((status) => {
+ return status.cached;
+ }), timeout(15000), catchError((error) => {
+ console.error(error);
+ return of({ cached: false, edges: [] });
+ }));
+ return combineLatest(of(direction), edgeStatus$).pipe(map(([d, es]) => {
+ for (const edge of es.edges) {
+ if (edge.data.direction === d) {
+ return true;
+ }
+ }
+ return false;
+ }));
+ }), mergeMap((hasEdge) => {
+ if (hasEdge || !this._bridging$) {
+ return of(hasEdge);
+ }
+ return this._bridging$.pipe(map((image) => {
+ return image != null;
+ }), catchError((error) => {
+ console.error(error);
+ return of(false);
+ }));
+ }), first((hasEdge) => {
+ return !hasEdge;
+ }))
+ .subscribe(undefined, undefined, () => { this.stop(); });
+ if (this._stopSubscription.closed) {
+ this._stopSubscription = null;
+ }
+ this._earthSubscription = this._stateService.state$
+ .pipe(map((state) => {
+ return state === State.Earth;
+ }), distinctUntilChanged(), first((earth) => {
+ return earth;
+ }))
+ .subscribe(undefined, undefined, () => { this.stop(); });
+ if (this._earthSubscription.closed) {
+ this._earthSubscription = null;
+ }
+ }
+ dispose() {
+ this.stop();
+ this._subscriptions.unsubscribe();
+ }
+ setDirection(direction) {
+ this._directionSubject$.next(direction);
+ }
+ setSpeed(speed) {
+ speed = Math.max(0, Math.min(1, speed));
+ if (speed === this._speed) {
+ return;
+ }
+ const stateSpeed = this._setSpeed(speed);
+ if (this._playing) {
+ this._stateService.setSpeed(stateSpeed);
+ }
+ this._speedSubject$.next(this._speed);
+ }
+ stop() {
+ if (!this._playing) {
+ return;
+ }
+ if (!!this._stopSubscription) {
+ if (!this._stopSubscription.closed) {
+ this._stopSubscription.unsubscribe();
+ }
+ this._stopSubscription = null;
+ }
+ if (!!this._earthSubscription) {
+ if (!this._earthSubscription.closed) {
+ this._earthSubscription.unsubscribe();
+ }
+ this._earthSubscription = null;
+ }
+ this._graphModeSubscription.unsubscribe();
+ this._graphModeSubscription = null;
+ this._cacheSubscription.unsubscribe();
+ this._cacheSubscription = null;
+ this._playingSubscription.unsubscribe();
+ this._playingSubscription = null;
+ this._clearSubscription.unsubscribe();
+ this._clearSubscription = null;
+ this._stateService.setSpeed(1);
+ this._stateService.cutImages();
+ this._graphService.setGraphMode(GraphMode.Spatial);
+ this._setPlaying(false);
+ }
+ _mapSpeed(speed) {
+ const x = 2 * speed - 1;
+ return Math.pow(10, x) - 0.2 * x;
+ }
+ _mapImagesAhead(stateSpeed) {
+ return Math.round(Math.max(10, Math.min(50, 8 + 6 * stateSpeed)));
+ }
+ _setPlaying(playing) {
+ this._playing = playing;
+ this._playingSubject$.next(playing);
+ }
+ _setSpeed(speed) {
+ this._speed = speed;
+ const stateSpeed = this._mapSpeed(this._speed);
+ this._imagesAhead = this._mapImagesAhead(stateSpeed);
+ return stateSpeed;
+ }
+}
+PlayService.sequenceSpeed = 0.54;
+
+var CameraVisualizationMode;
+(function (CameraVisualizationMode) {
+ /**
+ * Cameras are hidden.
+ */
+ CameraVisualizationMode[CameraVisualizationMode["Hidden"] = 0] = "Hidden";
+ /**
+ * Cameras are shown, all with the same color.
+ */
+ CameraVisualizationMode[CameraVisualizationMode["Homogeneous"] = 1] = "Homogeneous";
+ /**
+ * Cameras are shown with colors based on the
+ * their clusters.
+ */
+ CameraVisualizationMode[CameraVisualizationMode["Cluster"] = 2] = "Cluster";
+ /**
+ * Cameras are shown with colors based on the
+ * their connected components.
+ */
+ CameraVisualizationMode[CameraVisualizationMode["ConnectedComponent"] = 3] = "ConnectedComponent";
+ /**
+ * Cameras are shown, with colors based on the
+ * their sequence.
+ */
+ CameraVisualizationMode[CameraVisualizationMode["Sequence"] = 4] = "Sequence";
+})(CameraVisualizationMode || (CameraVisualizationMode = {}));
+
+var OriginalPositionMode;
+(function (OriginalPositionMode) {
+ /**
+ * Original positions are hidden.
+ */
+ OriginalPositionMode[OriginalPositionMode["Hidden"] = 0] = "Hidden";
+ /**
+ * Visualize original positions with altitude change.
+ */
+ OriginalPositionMode[OriginalPositionMode["Altitude"] = 1] = "Altitude";
+ /**
+ * Visualize original positions without altitude change,
+ * i.e. as flat lines from the camera origin.
+ */
+ OriginalPositionMode[OriginalPositionMode["Flat"] = 2] = "Flat";
+})(OriginalPositionMode || (OriginalPositionMode = {}));
+
+class ClusterPoints extends Points {
+ constructor(parameters) {
+ super();
+ this._originalSize = parameters.originalSize;
+ const cluster = parameters.cluster;
+ const scale = parameters.scale;
+ const translation = parameters.translation;
+ this._makeAttributes(cluster);
+ this.material.size = scale * this._originalSize;
+ this.material.vertexColors = true;
+ this.material.needsUpdate = true;
+ this.matrixAutoUpdate = false;
+ this.position.fromArray(translation);
+ this.updateMatrix();
+ this.updateMatrixWorld(false);
+ }
+ dispose() {
+ this.geometry.dispose();
+ this.material.dispose();
+ }
+ resize(scale) {
+ this.material.size = scale * this._originalSize;
+ this.material.needsUpdate = true;
+ }
+ _makeAttributes(cluster) {
+ const positions = [];
+ const colors = [];
+ const points = cluster.points;
+ for (const pointId in points) {
+ if (!points.hasOwnProperty(pointId)) {
+ continue;
+ }
+ const point = points[pointId];
+ positions.push(...point.coordinates);
+ const color = point.color;
+ colors.push(color[0]);
+ colors.push(color[1]);
+ colors.push(color[2]);
+ }
+ const geometry = this.geometry;
+ geometry.setAttribute("position", new BufferAttribute(new Float32Array(positions), 3));
+ geometry.setAttribute("color", new BufferAttribute(new Float32Array(colors), 3));
+ }
+}
+
+class CellLine extends Line {
+ constructor(vertices) {
+ super();
+ this._makeAttributes(vertices);
+ this.matrixAutoUpdate = false;
+ this.updateMatrix();
+ this.updateMatrixWorld(false);
+ }
+ dispose() {
+ this.geometry.dispose();
+ this.material.dispose();
+ }
+ _makeAttributes(vertices) {
+ const closedPolygon = vertices.slice();
+ closedPolygon.push(vertices[0]);
+ let index = 0;
+ const positions = new Float32Array(3 * (vertices.length + 1));
+ for (const vertex of closedPolygon) {
+ positions[index++] = vertex[0];
+ positions[index++] = vertex[1];
+ positions[index++] = vertex[2];
+ }
+ this.geometry.setAttribute("position", new BufferAttribute(positions, 3));
+ }
+}
+
+// Level 0: 1 x 1 x 1 meter cubes
+const OCTREE_ROOT_LEVEL = 14; // 16384 meters
+const OCTREE_LEAF_LEVEL = 6; // 64 meters
+function isLeafLevel(level, leafLevel) {
+ return level === leafLevel;
+}
+function levelToSize(level) {
+ return Math.pow(2, level);
+}
+function levelToRootBoundingBox(level) {
+ const size = levelToSize(level);
+ const half = size / 2;
+ const min = [-half, -half, -half];
+ const max = [half, half, half];
+ return { min, max };
+}
+
+class SpatialOctreeNode {
+ constructor(level, leafLevel, boundingBox, parent) {
+ this.level = level;
+ this.leafLevel = leafLevel;
+ this.boundingBox = boundingBox;
+ this.parent = parent;
+ this.children = [];
+ this.items = [];
+ if (parent) {
+ parent.children.push(this);
+ }
+ }
+ get isEmpty() {
+ return !(this.children.length || this.items.length);
+ }
+ add(object) {
+ const self = this;
+ if (!self.boundingBox.containsPoint(object.position)) {
+ throw new Error(`Item not contained in node`);
+ }
+ if (isLeafLevel(self.level, self.leafLevel)) {
+ self.items.push(object);
+ return this;
+ }
+ for (const child of self.children) {
+ if (child.boundingBox.containsPoint(object.position)) {
+ return child.add(object);
+ }
+ }
+ for (const boundingBox of self._generateBoundingBoxes()) {
+ if (boundingBox.containsPoint(object.position)) {
+ const child = new SpatialOctreeNode(self.level - 1, self.leafLevel, boundingBox, self);
+ return child.add(object);
+ }
+ }
+ throw new Error(`Item not contained in children`);
+ }
+ intersect(ray, target, nodes) {
+ if (!ray.intersectBox(this.boundingBox, target)) {
+ return;
+ }
+ if (isLeafLevel(this.level, this.leafLevel)) {
+ nodes.push(this);
+ return;
+ }
+ for (const child of this.children) {
+ child.intersect(ray, target, nodes);
+ }
+ }
+ remove(object) {
+ const index = this.items.indexOf(object);
+ if (index < 0) {
+ throw new Error(`Item does not exist ${object.uuid}`);
+ }
+ this.items.splice(index, 1);
+ }
+ traverse() {
+ const self = this;
+ if (!self.isEmpty) {
+ return;
+ }
+ const parent = self.parent;
+ if (!parent) {
+ return;
+ }
+ const index = parent.children.indexOf(self);
+ if (index < 0) {
+ throw new Error(`Corrupt octree`);
+ }
+ parent.children.splice(index, 1);
+ this.parent = null;
+ parent.traverse();
+ }
+ _generateBoundingBoxes() {
+ const self = this;
+ const min = self.boundingBox.min;
+ const max = self.boundingBox.max;
+ const size = (max.x - min.x) / 2;
+ const mins = [
+ [min.x, min.y + size, min.z + size],
+ [min.x + size, min.y + size, min.z + size],
+ [min.x, min.y, min.z + size],
+ [min.x + size, min.y, min.z + size],
+ [min.x, min.y + size, min.z],
+ [min.x + size, min.y + size, min.z],
+ [min.x, min.y, min.z],
+ [min.x + size, min.y, min.z],
+ ];
+ const boundingBoxes = [];
+ for (const [minX, minY, minZ] of mins) {
+ boundingBoxes.push(new Box3(new Vector3(minX, minY, minZ), new Vector3(minX + size, minY + size, minZ + size)));
+ }
+ return boundingBoxes;
+ }
+}
+
+class SpatialOctree {
+ constructor(rootLevel, leafLevel) {
+ this.rootLevel = rootLevel;
+ this.leafLevel = leafLevel;
+ if (leafLevel > rootLevel) {
+ throw new Error();
+ }
+ this._index = new Map();
+ this._root = this._makeRoot();
+ }
+ get root() {
+ return this._root;
+ }
+ add(object) {
+ if (!this.root.boundingBox.containsPoint(object.position)) {
+ console.warn(`Object outside bounding box ${object.uuid}`);
+ return;
+ }
+ const leaf = this._root.add(object);
+ this._index.set(object.uuid, leaf);
+ }
+ has(object) {
+ return this._index.has(object.uuid);
+ }
+ intersect(ray) {
+ const leaves = [];
+ const target = new Vector3();
+ this._root.intersect(ray, target, leaves);
+ return leaves
+ .map(leaf => leaf.items)
+ .reduce((acc, items) => {
+ acc.push(...items);
+ return acc;
+ }, []);
+ }
+ reset() {
+ this._root = this._makeRoot();
+ this._index.clear();
+ }
+ remove(object) {
+ if (!this.has(object)) {
+ throw new Error(`Frame does not exist ${object.uuid}`);
+ }
+ const leaf = this._index.get(object.uuid);
+ leaf.remove(object);
+ leaf.traverse();
+ this._index.delete(object.uuid);
+ }
+ _makeRoot() {
+ const level = this.rootLevel;
+ const bbox = levelToRootBoundingBox(level);
+ const box = new Box3(new Vector3().fromArray(bbox.min), new Vector3().fromArray(bbox.max));
+ return new SpatialOctreeNode(level, this.leafLevel, box);
+ }
+}
+
+class SpatialIntersection {
+ constructor(octree, raycaster) {
+ this._objects = [];
+ this._objectImageMap = new Map();
+ this._octree = octree !== null && octree !== void 0 ? octree : new SpatialOctree(OCTREE_ROOT_LEVEL, OCTREE_LEAF_LEVEL);
+ this._raycaster = raycaster !== null && raycaster !== void 0 ? raycaster : new Raycaster();
+ this._interactiveLayer = 1;
+ this._raycaster = !!raycaster ?
+ raycaster :
+ new Raycaster(undefined, undefined, 1, 10000);
+ this._lineThreshold = 0.2;
+ this._largeLineThreshold = 0.4;
+ this._raycaster.params.Line.threshold = this._lineThreshold;
+ this._raycaster.layers.set(this._interactiveLayer);
+ }
+ get interactiveLayer() { return this._interactiveLayer; }
+ get octree() { return this._octree; }
+ get raycaster() { return this._raycaster; }
+ add(object, imageId) {
+ const uuid = object.uuid;
+ this._objectImageMap.set(uuid, imageId);
+ this._objects.push(object);
+ this._octree.add(object);
+ }
+ intersectObjects(viewport, camera) {
+ this._raycaster.setFromCamera(new Vector2().fromArray(viewport), camera);
+ const objects = this._octree.intersect(this.raycaster.ray);
+ const intersects = this._raycaster.intersectObjects(objects);
+ const onMap = this._objectImageMap;
+ for (const intersect of intersects) {
+ const uuid = intersect.object.uuid;
+ if (!onMap.has(uuid)) {
+ continue;
+ }
+ return onMap.get(uuid);
+ }
+ return null;
+ }
+ remove(object) {
+ const objects = this._objects;
+ const index = objects.indexOf(object);
+ if (index !== -1) {
+ const deleted = objects.splice(index, 1);
+ for (const d of deleted) {
+ this._objectImageMap.delete(d.uuid);
+ }
+ this._octree.remove(object);
+ }
+ else {
+ console.warn(`Object does not exist`);
+ }
+ }
+ resetIntersectionThreshold(useLarge) {
+ this._raycaster.params.Line.threshold = useLarge ?
+ this._largeLineThreshold :
+ this._lineThreshold;
+ }
+}
+
+class PositionLine extends Line {
+ constructor(parameters) {
+ super(parameters.geometry, parameters.material);
+ const mode = parameters.mode;
+ const originalOrigin = parameters.originalOrigin;
+ const transform = parameters.transform;
+ const origin = transform.unprojectBasic([0, 0], 0);
+ this._relativeAltitude = originalOrigin[2] - origin[2];
+ this._makeAttributes(origin, originalOrigin, mode);
+ this.matrixAutoUpdate = false;
+ this.position.fromArray(origin);
+ this.updateMatrix();
+ this.updateMatrixWorld(false);
+ }
+ dispose() {
+ this.geometry.dispose();
+ this.material.dispose();
+ }
+ setMode(mode) {
+ const positionAttribute = this.geometry.attributes.position;
+ const positions = positionAttribute.array;
+ positions[5] = this._modeToAltitude(mode);
+ positionAttribute.needsUpdate = true;
+ this.geometry.computeBoundingSphere();
+ }
+ _makeAttributes(origin, originalOrigin, mode) {
+ const positions = new Float32Array(6);
+ positions[0] = 0;
+ positions[1] = 0;
+ positions[2] = 0;
+ positions[3] = originalOrigin[0] - origin[0];
+ positions[4] = originalOrigin[1] - origin[1];
+ positions[5] = this._modeToAltitude(mode);
+ const attribute = new BufferAttribute(positions, 3);
+ this.geometry.setAttribute("position", attribute);
+ attribute.needsUpdate = true;
+ this.geometry.computeBoundingSphere();
+ }
+ _modeToAltitude(mode) {
+ return mode === OriginalPositionMode.Altitude ?
+ this._relativeAltitude : 0;
+ }
+}
+
+class CameraFrameBase extends LineSegments {
+ constructor(parameters) {
+ super(parameters.geometry, parameters.material);
+ const color = parameters.color;
+ const size = parameters.size;
+ const scale = parameters.scale;
+ const transform = parameters.transform;
+ const origin = transform.unprojectBasic([0, 0], 0);
+ const positions = this._makePositions(size, transform, origin);
+ this._makeAttributes(positions, color);
+ this.geometry.computeBoundingSphere();
+ this.geometry.computeBoundingBox();
+ this.matrixAutoUpdate = false;
+ this.position.fromArray(origin);
+ this.scale.set(scale, scale, scale);
+ this.updateMatrix();
+ this.updateMatrixWorld(false);
+ }
+ dispose() {
+ this.geometry.dispose();
+ this.material.dispose();
+ }
+ setColor(color) {
+ this._updateColorAttribute(color);
+ return this;
+ }
+ resize(scale) {
+ this.scale.set(scale, scale, scale);
+ this.updateMatrix();
+ this.updateMatrixWorld(false);
+ return this;
+ }
+ _makeAttributes(positions, color) {
+ const geometry = this.geometry;
+ const positionAttribute = new BufferAttribute(new Float32Array(positions), 3);
+ geometry.setAttribute("position", positionAttribute);
+ positionAttribute.needsUpdate = true;
+ const colorAttribute = new BufferAttribute(new Float32Array(positions.length), 3);
+ geometry.setAttribute("color", colorAttribute);
+ this._updateColorAttribute(color);
+ }
+ _updateColorAttribute(color) {
+ const [r, g, b] = new Color(color).toArray();
+ const colorAttribute = this.geometry.attributes.color;
+ const colors = colorAttribute.array;
+ const length = colors.length;
+ let index = 0;
+ for (let i = 0; i < length; i++) {
+ colors[index++] = r;
+ colors[index++] = g;
+ colors[index++] = b;
+ }
+ colorAttribute.needsUpdate = true;
+ }
+}
+
+class SphericalCameraFrame extends CameraFrameBase {
+ _makePositions(size, transform, origin) {
+ const vs = 10;
+ const positions = [];
+ positions.push(...this._makeAxis(size, transform, origin));
+ positions.push(...this._makeLat(0.5, vs, size, transform, origin));
+ for (const lat of [0, 0.25, 0.5, 0.75]) {
+ positions
+ .push(...this._makeLng(lat, vs, size, transform, origin));
+ }
+ return positions;
+ }
+ _makeAxis(size, transform, origin) {
+ const south = transform.unprojectBasic([0.5, 1], 0.8 * size);
+ const north = transform.unprojectBasic([0.5, 0], 1.2 * size);
+ return [
+ south[0] - origin[0],
+ south[1] - origin[1],
+ south[2] - origin[2],
+ north[0] - origin[0],
+ north[1] - origin[1],
+ north[2] - origin[2],
+ ];
+ }
+ _makeLat(basicY, numVertices, size, transform, origin) {
+ const dist = 0.8 * size;
+ const [originX, originY, originZ] = origin;
+ const positions = [];
+ const first = transform.unprojectBasic([0, basicY], dist);
+ first[0] -= originX;
+ first[1] -= originY;
+ first[2] -= originZ;
+ positions.push(...first);
+ for (let i = 1; i <= numVertices; i++) {
+ const position = transform.unprojectBasic([i / numVertices, basicY], dist);
+ position[0] -= originX;
+ position[1] -= originY;
+ position[2] -= originZ;
+ positions.push(...position, ...position);
+ }
+ positions.push(...first);
+ return positions;
+ }
+ _makeLng(basicX, numVertices, size, transform, origin) {
+ const dist = 0.8 * size;
+ const [originX, originY, originZ] = origin;
+ const positions = [];
+ const first = transform.unprojectBasic([basicX, 0], dist);
+ first[0] -= originX;
+ first[1] -= originY;
+ first[2] -= originZ;
+ positions.push(...first);
+ for (let i = 0; i <= numVertices; i++) {
+ const position = transform.unprojectBasic([basicX, i / numVertices], dist);
+ position[0] -= originX;
+ position[1] -= originY;
+ position[2] -= originZ;
+ positions.push(...position, ...position);
+ }
+ positions.push(...first);
+ return positions;
+ }
+}
+
+class PerspectiveCameraFrame extends CameraFrameBase {
+ _makePositions(size, transform, origin) {
+ const samples = 8;
+ const positions = [];
+ positions.push(...this._makeDiags(size, transform, origin));
+ positions.push(...this._makeFrame(size, samples, transform, origin));
+ return positions;
+ }
+ _makeDiags(size, transform, origin) {
+ const depth = size;
+ const [originX, originY, originZ] = origin;
+ const cameraCenter = [0, 0, 0];
+ const positions = [];
+ for (const vertex2d of [[0, 0], [1, 0], [1, 1], [0, 1]]) {
+ const corner = transform.unprojectBasic(vertex2d, depth, true);
+ corner[0] -= originX;
+ corner[1] -= originY;
+ corner[2] -= originZ;
+ positions.push(...cameraCenter, ...corner);
+ }
+ return positions;
+ }
+ _makeFrame(size, samples, transform, origin) {
+ const vertices2d = [];
+ vertices2d.push(...this._subsample([0, 1], [0, 0], samples));
+ vertices2d.push(...this._subsample([0, 0], [1, 0], samples));
+ vertices2d.push(...this._subsample([1, 0], [1, 1], samples));
+ const depth = size;
+ const [originX, originY, originZ] = origin;
+ const positions = [];
+ for (const vertex2d of vertices2d) {
+ const position = transform.unprojectBasic(vertex2d, depth, true);
+ position[0] -= originX;
+ position[1] -= originY;
+ position[2] -= originZ;
+ positions.push(...position);
+ }
+ return positions;
+ }
+ _interpolate(a, b, alpha) {
+ return a + alpha * (b - a);
+ }
+ _subsample(p1, p2, subsamples) {
+ if (subsamples < 1) {
+ return [p1, p2];
+ }
+ const samples = [];
+ samples.push(p1);
+ for (let i = 0; i <= subsamples; i++) {
+ const p = [];
+ for (let j = 0; j < 3; j++) {
+ p.push(this._interpolate(p1[j], p2[j], i / (subsamples + 1)));
+ }
+ samples.push(p);
+ samples.push(p);
+ }
+ samples.push(p2);
+ return samples;
+ }
+}
+
+class SpatialCell {
+ constructor(id, _scene, _intersection) {
+ this.id = id;
+ this._scene = _scene;
+ this._intersection = _intersection;
+ this.cameras = new Object3D();
+ this.keys = [];
+ this._positionLines = {};
+ this._positions = new Object3D();
+ this._cameraFrames = {};
+ this._clusters = new Map();
+ this._connectedComponents = new Map();
+ this._sequences = new Map();
+ this._props = {};
+ this.clusterVisibles = {};
+ this._frameMaterial = new LineBasicMaterial({
+ fog: false,
+ vertexColors: true,
+ });
+ this._positionMaterial = new LineBasicMaterial({
+ fog: false,
+ color: 0xff0000,
+ });
+ this._scene.add(this.cameras, this._positions);
+ }
+ addImage(props) {
+ const image = props.image;
+ const id = image.id;
+ if (this.hasImage(id)) {
+ throw new Error(`Image exists ${id}`);
+ }
+ const ccId = props.idMap.ccId;
+ if (!(this._connectedComponents.has(ccId))) {
+ this._connectedComponents.set(ccId, []);
+ }
+ const cId = props.idMap.clusterId;
+ if (!this._clusters.has(cId)) {
+ this._clusters.set(cId, []);
+ }
+ const sId = props.idMap.sequenceId;
+ if (!this._sequences.has(sId)) {
+ this._sequences.set(sId, []);
+ }
+ this._props[id] = {
+ image: image,
+ ids: { ccId, clusterId: cId, sequenceId: sId },
+ };
+ this.keys.push(id);
+ }
+ applyCameraColor(imageId, color) {
+ this._cameraFrames[imageId].setColor(color);
+ }
+ applyCameraSize(size) {
+ for (const camera of this.cameras.children) {
+ camera.resize(size);
+ }
+ }
+ applyFilter(filter) {
+ var _a;
+ const clusterVisibles = this.clusterVisibles;
+ for (const clusterId in clusterVisibles) {
+ if (!clusterVisibles.hasOwnProperty(clusterId)) {
+ continue;
+ }
+ clusterVisibles[clusterId] = false;
+ }
+ const cameraFrames = this._cameraFrames;
+ const positionLines = this._positionLines;
+ const interactiveLayer = this._intersection.interactiveLayer;
+ for (const props of Object.values(this._props)) {
+ const image = props.image;
+ const visible = filter(image);
+ const key = image.id;
+ positionLines[key].visible = visible;
+ const camera = cameraFrames[key];
+ this._setCameraVisibility(camera, visible, interactiveLayer);
+ clusterVisibles[_a = props.ids.clusterId] || (clusterVisibles[_a] = visible);
+ }
+ }
+ applyPositionMode(mode) {
+ this._positions.visible =
+ mode !== OriginalPositionMode.Hidden;
+ for (const position of this._positions.children) {
+ position.setMode(mode);
+ }
+ }
+ dispose() {
+ this._disposeCameras();
+ this._disposePositions();
+ this._scene = null;
+ this._intersection = null;
+ }
+ getCamerasByMode(mode) {
+ if (mode === CameraVisualizationMode.Cluster) {
+ return this._clusters;
+ }
+ else if (mode === CameraVisualizationMode.ConnectedComponent) {
+ return this._connectedComponents;
+ }
+ else if (mode === CameraVisualizationMode.Sequence) {
+ return this._sequences;
+ }
+ const cvm = CameraVisualizationMode;
+ const defaultId = cvm[cvm.Homogeneous];
+ const cameras = new Map();
+ cameras.set(defaultId, this.cameras.children);
+ return cameras;
+ }
+ getColorId(imageId, mode) {
+ const props = this._props[imageId];
+ const cvm = CameraVisualizationMode;
+ switch (mode) {
+ case cvm.Cluster:
+ return props.ids.clusterId;
+ case cvm.ConnectedComponent:
+ return props.ids.ccId;
+ case cvm.Sequence:
+ return props.ids.sequenceId;
+ default:
+ return cvm[cvm.Homogeneous];
+ }
+ }
+ hasImage(key) {
+ return this.keys.indexOf(key) !== -1;
+ }
+ visualize(props) {
+ var _a, _b;
+ const id = props.id;
+ const visible = props.visible;
+ const transform = props.transform;
+ const cameraParameters = {
+ color: props.color,
+ material: this._frameMaterial,
+ scale: props.scale,
+ size: props.maxSize,
+ transform,
+ };
+ const camera = isSpherical(transform.cameraType) ?
+ new SphericalCameraFrame(cameraParameters) :
+ new PerspectiveCameraFrame(cameraParameters);
+ const interactiveLayer = this._intersection.interactiveLayer;
+ this._setCameraVisibility(camera, visible, interactiveLayer);
+ this.cameras.add(camera);
+ this._cameraFrames[id] = camera;
+ const intersection = this._intersection;
+ intersection.add(camera, id);
+ const ids = this._props[id].ids;
+ (_a = this.clusterVisibles)[_b = ids.clusterId] || (_a[_b] = visible);
+ this._connectedComponents.get(ids.ccId).push(camera);
+ this._clusters.get(ids.clusterId).push(camera);
+ this._sequences.get(ids.sequenceId).push(camera);
+ const positionParameters = {
+ material: this._positionMaterial,
+ mode: props.positionMode,
+ originalOrigin: props.originalPosition,
+ transform,
+ };
+ const position = new PositionLine(positionParameters);
+ position.visible = visible;
+ this._positions.add(position);
+ this._positionLines[id] = position;
+ }
+ _disposeCameras() {
+ const intersection = this._intersection;
+ const cameras = this.cameras;
+ for (const camera of cameras.children.slice()) {
+ camera.dispose();
+ intersection.remove(camera);
+ cameras.remove(camera);
+ }
+ this._scene.remove(this.cameras);
+ }
+ _disposePositions() {
+ const positions = this._positions;
+ for (const position of positions.children.slice()) {
+ position.dispose();
+ positions.remove(position);
+ }
+ this._scene.remove(this._positions);
+ }
+ _setCameraVisibility(camera, visible, layer) {
+ camera.visible = visible;
+ if (visible) {
+ camera.layers.enable(layer);
+ }
+ else {
+ camera.layers.disable(layer);
+ }
+ }
+}
+
+class SpatialAssets {
+ constructor() {
+ this._colors = new Map();
+ const cvm = CameraVisualizationMode;
+ this._colors.set(cvm[cvm.Homogeneous], "#FFFFFF");
+ }
+ getColor(id) {
+ const colors = this._colors;
+ if (!colors.has(id)) {
+ colors.set(id, this._randomColor());
+ }
+ return colors.get(id);
+ }
+ _randomColor() {
+ return `hsl(${Math.floor(360 * Math.random())}, 100%, 50%)`;
+ }
+}
+
+function isModeVisible(mode) {
+ return mode !== CameraVisualizationMode.Hidden;
+}
+function isOverviewState(state) {
+ return state === State.Custom || state === State.Earth;
+}
+
+const NO_CLUSTER_ID = "NO_CLUSTER_ID";
+const NO_MERGE_ID = "NO_MERGE_ID";
+const NO_SEQUENCE_ID = "NO_SEQUENCE_ID";
+class SpatialScene {
+ constructor(configuration, scene) {
+ this._rayNearScale = 1.1;
+ this._originalPointSize = 2;
+ this._originalCameraSize = 2;
+ this._imageCellMap = new Map();
+ this._scene = !!scene ? scene : new Scene();
+ this._scene.autoUpdate = false;
+ this._intersection = new SpatialIntersection();
+ this._assets = new SpatialAssets();
+ this._needsRender = false;
+ this._images = {};
+ this._cells = {};
+ this._cellClusters = {};
+ this._clusters = {};
+ this._cameraVisualizationMode =
+ !!configuration.cameraVisualizationMode ?
+ configuration.cameraVisualizationMode :
+ CameraVisualizationMode.Homogeneous;
+ this._cameraSize = configuration.cameraSize;
+ this._pointSize = configuration.pointSize;
+ this._pointsVisible = configuration.pointsVisible;
+ this._positionMode = configuration.originalPositionMode;
+ this._cellsVisible = configuration.cellsVisible;
+ this._hoveredId = null;
+ this._selectedId = null;
+ this._colors = { hover: "#FF0000", select: "#FF8000" };
+ this._filter = () => true;
+ }
+ get needsRender() { return this._needsRender; }
+ get intersection() {
+ return this._intersection;
+ }
+ addCluster(reconstruction, translation, cellId) {
+ if (this.hasCluster(reconstruction.id, cellId)) {
+ return;
+ }
+ const clusterId = reconstruction.id;
+ if (!(clusterId in this._clusters)) {
+ this._clusters[clusterId] = {
+ points: new Object3D(),
+ cellIds: [],
+ };
+ const visible = this._getClusterVisible(clusterId);
+ this._clusters[clusterId].points.visible = visible;
+ this._clusters[clusterId].points.add(new ClusterPoints({
+ cluster: reconstruction,
+ originalSize: this._originalPointSize,
+ scale: this._pointSize,
+ translation,
+ }));
+ this._scene.add(this._clusters[clusterId].points);
+ }
+ if (this._clusters[clusterId].cellIds.indexOf(cellId) === -1) {
+ this._clusters[clusterId].cellIds.push(cellId);
+ }
+ if (!(cellId in this._cellClusters)) {
+ this._cellClusters[cellId] = { keys: [] };
+ }
+ if (this._cellClusters[cellId].keys.indexOf(clusterId) === -1) {
+ this._cellClusters[cellId].keys.push(clusterId);
+ }
+ this._needsRender = true;
+ }
+ addImage(image, transform, originalPosition, cellId) {
+ var _a, _b, _c;
+ const imageId = image.id;
+ const idMap = {
+ clusterId: (_a = image.clusterId) !== null && _a !== void 0 ? _a : NO_CLUSTER_ID,
+ sequenceId: (_b = image.sequenceId) !== null && _b !== void 0 ? _b : NO_SEQUENCE_ID,
+ ccId: (_c = image.mergeId) !== null && _c !== void 0 ? _c : NO_MERGE_ID,
+ };
+ if (!(cellId in this._images)) {
+ const created = new SpatialCell(cellId, this._scene, this._intersection);
+ created.cameras.visible =
+ isModeVisible(this._cameraVisualizationMode);
+ created.applyPositionMode(this._positionMode);
+ this._images[cellId] = created;
+ }
+ const cell = this._images[cellId];
+ if (cell.hasImage(imageId)) {
+ return;
+ }
+ cell.addImage({ idMap, image: image });
+ const colorId = cell.getColorId(imageId, this._cameraVisualizationMode);
+ const color = this._assets.getColor(colorId);
+ const visible = this._filter(image);
+ cell.visualize({
+ id: imageId,
+ color,
+ positionMode: this._positionMode,
+ scale: this._cameraSize,
+ transform,
+ visible,
+ maxSize: this._originalCameraSize,
+ originalPosition
+ });
+ this._imageCellMap.set(imageId, cellId);
+ if (imageId === this._selectedId) {
+ this._highlight(imageId, this._colors.select, this._cameraVisualizationMode);
+ }
+ if (idMap.clusterId in this._clusters) {
+ const clusterVisible = this._getClusterVisible(idMap.clusterId);
+ this._clusters[idMap.clusterId].points.visible = clusterVisible;
+ }
+ this._needsRender = true;
+ }
+ addCell(vertices, cellId) {
+ if (this.hasCell(cellId)) {
+ return;
+ }
+ const cell = new CellLine(vertices);
+ this._cells[cellId] = new Object3D();
+ this._cells[cellId].visible = this._cellsVisible;
+ this._cells[cellId].add(cell);
+ this._scene.add(this._cells[cellId]);
+ this._needsRender = true;
+ }
+ deactivate() {
+ this._filter = () => true;
+ this._selectedId = null;
+ this._hoveredId = null;
+ this.uncache();
+ }
+ hasCluster(clusterId, cellId) {
+ return clusterId in this._clusters &&
+ this._clusters[clusterId].cellIds.indexOf(cellId) !== -1;
+ }
+ hasCell(cellId) {
+ return cellId in this._cells;
+ }
+ hasImage(imageId, cellId) {
+ return cellId in this._images &&
+ this._images[cellId].hasImage(imageId);
+ }
+ setCameraSize(cameraSize) {
+ if (Math.abs(cameraSize - this._cameraSize) < 1e-3) {
+ return;
+ }
+ const imageCells = this._images;
+ for (const cellId of Object.keys(imageCells)) {
+ imageCells[cellId].applyCameraSize(cameraSize);
+ }
+ this._intersection.raycaster.near = this._getNear(cameraSize);
+ this._cameraSize = cameraSize;
+ this._needsRender = true;
+ }
+ setFilter(filter) {
+ this._filter = filter;
+ const clusterVisibles = {};
+ for (const imageCell of Object.values(this._images)) {
+ imageCell.applyFilter(filter);
+ const imageCV = imageCell.clusterVisibles;
+ for (const clusterId in imageCV) {
+ if (!imageCV.hasOwnProperty(clusterId)) {
+ continue;
+ }
+ if (!(clusterId in clusterVisibles)) {
+ clusterVisibles[clusterId] = false;
+ }
+ clusterVisibles[clusterId] || (clusterVisibles[clusterId] = imageCV[clusterId]);
+ }
+ }
+ const pointsVisible = this._pointsVisible;
+ for (const clusterId in clusterVisibles) {
+ if (!clusterVisibles.hasOwnProperty(clusterId)) {
+ continue;
+ }
+ clusterVisibles[clusterId] && (clusterVisibles[clusterId] = pointsVisible);
+ const visible = clusterVisibles[clusterId];
+ if (clusterId in this._clusters) {
+ this._clusters[clusterId].points.visible = visible;
+ }
+ }
+ this._needsRender = true;
+ }
+ setHoveredImage(imageId) {
+ if (imageId != null && !this._imageCellMap.has(imageId)) {
+ throw new MapillaryError(`Image does not exist: ${imageId}`);
+ }
+ if (this._hoveredId === imageId) {
+ return;
+ }
+ this._needsRender = true;
+ if (this._hoveredId != null) {
+ if (this._hoveredId === this._selectedId) {
+ this._highlight(this._hoveredId, this._colors.select, this._cameraVisualizationMode);
+ }
+ else {
+ this._resetCameraColor(this._hoveredId);
+ }
+ }
+ this._highlight(imageId, this._colors.hover, this._cameraVisualizationMode);
+ this._hoveredId = imageId;
+ }
+ setNavigationState(isOverview) {
+ this._intersection.resetIntersectionThreshold(isOverview);
+ }
+ setPointSize(pointSize) {
+ if (Math.abs(pointSize - this._pointSize) < 1e-3) {
+ return;
+ }
+ const clusters = this._clusters;
+ for (const key in clusters) {
+ if (!clusters.hasOwnProperty(key)) {
+ continue;
+ }
+ for (const points of clusters[key].points.children) {
+ points.resize(pointSize);
+ }
+ }
+ this._pointSize = pointSize;
+ this._needsRender = true;
+ }
+ setPointVisibility(visible) {
+ if (visible === this._pointsVisible) {
+ return;
+ }
+ for (const clusterId in this._clusters) {
+ if (!this._clusters.hasOwnProperty(clusterId)) {
+ continue;
+ }
+ this._clusters[clusterId].points.visible = visible;
+ }
+ this._pointsVisible = visible;
+ this._needsRender = true;
+ }
+ setPositionMode(mode) {
+ if (mode === this._positionMode) {
+ return;
+ }
+ for (const cell of Object.values(this._images)) {
+ cell.applyPositionMode(mode);
+ }
+ this._positionMode = mode;
+ this._needsRender = true;
+ }
+ setSelectedImage(id) {
+ if (this._selectedId === id) {
+ return;
+ }
+ this._needsRender = true;
+ if (this._selectedId != null) {
+ this._resetCameraColor(this._selectedId);
+ }
+ this._highlight(id, this._colors.select, this._cameraVisualizationMode);
+ this._selectedId = id;
+ }
+ setCellVisibility(visible) {
+ if (visible === this._cellsVisible) {
+ return;
+ }
+ for (const cellId in this._cells) {
+ if (!this._cells.hasOwnProperty(cellId)) {
+ continue;
+ }
+ this._cells[cellId].visible = visible;
+ }
+ this._cellsVisible = visible;
+ this._needsRender = true;
+ }
+ setCameraVisualizationMode(mode) {
+ if (mode === this._cameraVisualizationMode) {
+ return;
+ }
+ const visible = isModeVisible(mode);
+ const assets = this._assets;
+ for (const cell of Object.values(this._images)) {
+ cell.cameras.visible = visible;
+ const cameraMap = cell.getCamerasByMode(mode);
+ cameraMap.forEach((cameras, colorId) => {
+ const color = assets.getColor(colorId);
+ for (const camera of cameras) {
+ camera.setColor(color);
+ }
+ });
+ }
+ this._highlight(this._hoveredId, this._colors.hover, mode);
+ this._highlight(this._selectedId, this._colors.select, mode);
+ this._cameraVisualizationMode = mode;
+ this._needsRender = true;
+ }
+ render(camera, renderer) {
+ renderer.render(this._scene, camera);
+ this._needsRender = false;
+ }
+ uncache(keepCellIds) {
+ for (const cellId of Object.keys(this._cellClusters)) {
+ if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+ continue;
+ }
+ this._disposeReconstruction(cellId);
+ }
+ for (const cellId of Object.keys(this._images)) {
+ if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+ continue;
+ }
+ const nceMap = this._imageCellMap;
+ const keys = this._images[cellId].keys;
+ for (const key of keys) {
+ nceMap.delete(key);
+ }
+ this._images[cellId].dispose();
+ delete this._images[cellId];
+ }
+ for (const cellId of Object.keys(this._cells)) {
+ if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+ continue;
+ }
+ this._disposeCell(cellId);
+ }
+ this._needsRender = true;
+ }
+ _getClusterVisible(clusterId) {
+ if (!this._pointsVisible) {
+ return false;
+ }
+ let visible = false;
+ for (const imageCell of Object.values(this._images)) {
+ const imageCV = imageCell.clusterVisibles;
+ if (!(clusterId in imageCV)) {
+ continue;
+ }
+ visible || (visible = imageCV[clusterId]);
+ }
+ return visible;
+ }
+ _disposePoints(cellId) {
+ for (const clusterId of this._cellClusters[cellId].keys) {
+ if (!(clusterId in this._clusters)) {
+ continue;
+ }
+ const index = this._clusters[clusterId].cellIds.indexOf(cellId);
+ if (index === -1) {
+ continue;
+ }
+ this._clusters[clusterId].cellIds.splice(index, 1);
+ if (this._clusters[clusterId].cellIds.length > 0) {
+ continue;
+ }
+ for (const points of this._clusters[clusterId].points.children.slice()) {
+ points.dispose();
+ }
+ this._scene.remove(this._clusters[clusterId].points);
+ delete this._clusters[clusterId];
+ }
+ }
+ _disposeReconstruction(cellId) {
+ this._disposePoints(cellId);
+ delete this._cellClusters[cellId];
+ }
+ _disposeCell(cellId) {
+ const cell = this._cells[cellId];
+ for (const line of cell.children.slice()) {
+ line.dispose();
+ cell.remove(line);
+ }
+ this._scene.remove(cell);
+ delete this._cells[cellId];
+ }
+ _getNear(cameraSize) {
+ const near = this._rayNearScale *
+ this._originalCameraSize *
+ cameraSize;
+ return Math.max(1, near);
+ }
+ _resetCameraColor(imageId) {
+ const nceMap = this._imageCellMap;
+ if (imageId == null || !nceMap.has(imageId)) {
+ return;
+ }
+ const cellId = nceMap.get(imageId);
+ const cell = this._images[cellId];
+ const colorId = cell.getColorId(imageId, this._cameraVisualizationMode);
+ const color = this._assets.getColor(colorId);
+ cell.applyCameraColor(imageId, color);
+ }
+ _highlight(imageId, color, mode) {
+ const nceMap = this._imageCellMap;
+ if (imageId == null || !nceMap.has(imageId)) {
+ return;
+ }
+ const cellId = nceMap.get(imageId);
+ color = mode === CameraVisualizationMode.Homogeneous ?
+ color : "#FFFFFF";
+ this._images[cellId].applyCameraColor(imageId, color);
+ }
+}
+
+class SpatialCache {
+ constructor(graphService, provider) {
+ this._graphService = graphService;
+ this._data = provider;
+ this._cells = {};
+ this._cacheRequests = {};
+ this._clusters = {};
+ this._clusterCells = {};
+ this._cellClusters = {};
+ this._cachingCells$ = {};
+ this._cachingClusters$ = {};
+ }
+ cacheClusters$(cellId) {
+ if (!this.hasCell(cellId)) {
+ throw new Error("Cannot cache reconstructions of a non-existing cell.");
+ }
+ if (this.hasClusters(cellId)) {
+ throw new Error("Cannot cache reconstructions that already exists.");
+ }
+ if (this.isCachingClusters(cellId)) {
+ return this._cachingClusters$[cellId];
+ }
+ const duplicatedClusters = this.getCell(cellId)
+ .filter((n) => {
+ return !!n.clusterId && !!n.clusterUrl;
+ })
+ .map((n) => {
+ return { key: n.clusterId, url: n.clusterUrl };
+ });
+ const clusters = Array
+ .from(new Map(duplicatedClusters.map((cd) => {
+ return [cd.key, cd];
+ }))
+ .values());
+ this._cellClusters[cellId] = clusters;
+ this._cacheRequests[cellId] = [];
+ let aborter;
+ const abort = new Promise((_, reject) => {
+ aborter = reject;
+ });
+ this._cacheRequests[cellId].push(aborter);
+ this._cachingClusters$[cellId] =
+ this._cacheClusters$(clusters, cellId, abort).pipe(finalize(() => {
+ if (cellId in this._cachingClusters$) {
+ delete this._cachingClusters$[cellId];
+ }
+ if (cellId in this._cacheRequests) {
+ delete this._cacheRequests[cellId];
+ }
+ }), publish(), refCount());
+ return this._cachingClusters$[cellId];
+ }
+ cacheCell$(cellId) {
+ if (this.hasCell(cellId)) {
+ throw new Error("Cannot cache cell that already exists.");
+ }
+ if (this.isCachingCell(cellId)) {
+ return this._cachingCells$[cellId];
+ }
+ this._cachingCells$[cellId] = this._graphService.cacheCell$(cellId).pipe(catchError((error) => {
+ console.error(error);
+ return empty();
+ }), filter(() => {
+ return !(cellId in this._cells);
+ }), tap((images) => {
+ this._cells[cellId] = [];
+ this._cells[cellId].push(...images);
+ delete this._cachingCells$[cellId];
+ }), finalize(() => {
+ if (cellId in this._cachingCells$) {
+ delete this._cachingCells$[cellId];
+ }
+ }), publish(), refCount());
+ return this._cachingCells$[cellId];
+ }
+ isCachingClusters(cellId) {
+ return cellId in this._cachingClusters$;
+ }
+ isCachingCell(cellId) {
+ return cellId in this._cachingCells$;
+ }
+ hasClusters(cellId) {
+ if (cellId in this._cachingClusters$ ||
+ !(cellId in this._cellClusters)) {
+ return false;
+ }
+ for (const cd of this._cellClusters[cellId]) {
+ if (!(cd.key in this._clusters)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ hasCell(cellId) {
+ return !(cellId in this._cachingCells$) && cellId in this._cells;
+ }
+ getClusters(cellId) {
+ return cellId in this._cellClusters ?
+ this._cellClusters[cellId]
+ .map((cd) => {
+ return this._clusters[cd.key];
+ })
+ .filter((reconstruction) => {
+ return !!reconstruction;
+ }) :
+ [];
+ }
+ getCell(cellId) {
+ return cellId in this._cells ? this._cells[cellId] : [];
+ }
+ uncache(keepCellIds) {
+ for (let cellId of Object.keys(this._cacheRequests)) {
+ if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+ continue;
+ }
+ for (const aborter of this._cacheRequests[cellId]) {
+ aborter();
+ }
+ delete this._cacheRequests[cellId];
+ }
+ for (let cellId of Object.keys(this._cellClusters)) {
+ if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+ continue;
+ }
+ for (const cd of this._cellClusters[cellId]) {
+ if (!(cd.key in this._clusterCells)) {
+ continue;
+ }
+ const index = this._clusterCells[cd.key].indexOf(cellId);
+ if (index === -1) {
+ continue;
+ }
+ this._clusterCells[cd.key].splice(index, 1);
+ if (this._clusterCells[cd.key].length > 0) {
+ continue;
+ }
+ delete this._clusterCells[cd.key];
+ delete this._clusters[cd.key];
+ }
+ delete this._cellClusters[cellId];
+ }
+ for (let cellId of Object.keys(this._cells)) {
+ if (!!keepCellIds && keepCellIds.indexOf(cellId) !== -1) {
+ continue;
+ }
+ delete this._cells[cellId];
+ }
+ }
+ updateCell$(cellId) {
+ if (!this.hasCell(cellId)) {
+ throw new Error("Cannot update cell that does not exists.");
+ }
+ return this._graphService.cacheCell$(cellId).pipe(catchError((error) => {
+ console.error(error);
+ return empty();
+ }), filter(() => {
+ return cellId in this._cells;
+ }), tap((images) => {
+ this._cells[cellId] = [];
+ this._cells[cellId].push(...images);
+ }), publish(), refCount());
+ }
+ updateClusters$(cellId) {
+ if (!this.hasCell(cellId)) {
+ throw new Error("Cannot update reconstructions of a non-existing cell.");
+ }
+ if (!this.hasClusters(cellId)) {
+ throw new Error("Cannot update reconstructions for cell that is not cached.");
+ }
+ const duplicatedClusters = this.getCell(cellId)
+ .filter((n) => {
+ return !!n.clusterId && !!n.clusterUrl;
+ })
+ .map((n) => {
+ return { key: n.clusterId, url: n.clusterUrl };
+ });
+ const clusters = Array
+ .from(new Map(duplicatedClusters.map((cd) => {
+ return [cd.key, cd];
+ }))
+ .values())
+ .filter(cd => {
+ return !(cd.key in this._clusters);
+ });
+ this._cellClusters[cellId].push(...clusters);
+ return this._cacheClusters$(clusters, cellId, null);
+ }
+ _cacheClusters$(clusters, cellId, cancellation) {
+ return from(clusters).pipe(mergeMap((cd) => {
+ if (this._hasCluster(cd.key)) {
+ return of(this._getCluster(cd.key));
+ }
+ return this._getCluster$(cd.url, cd.key, cancellation)
+ .pipe(catchError((error) => {
+ if (error instanceof CancelMapillaryError) {
+ return empty();
+ }
+ console.error(error);
+ return empty();
+ }));
+ }, 6), filter(() => {
+ return cellId in this._cellClusters;
+ }), tap((reconstruction) => {
+ if (!this._hasCluster(reconstruction.id)) {
+ this._clusters[reconstruction.id] = reconstruction;
+ }
+ if (!(reconstruction.id in this._clusterCells)) {
+ this._clusterCells[reconstruction.id] = [];
+ }
+ if (this._clusterCells[reconstruction.id].indexOf(cellId) === -1) {
+ this._clusterCells[reconstruction.id].push(cellId);
+ }
+ }));
+ }
+ _getCluster(id) {
+ return this._clusters[id];
+ }
+ _getCluster$(url, clusterId, abort) {
+ return Observable.create((subscriber) => {
+ this._data.getCluster(url, abort)
+ .then((reconstruction) => {
+ reconstruction.id = clusterId;
+ subscriber.next(reconstruction);
+ subscriber.complete();
+ }, (error) => {
+ subscriber.error(error);
+ });
+ });
+ }
+ _hasCluster(id) {
+ return id in this._clusters;
+ }
+}
+
+function connectedComponent(cellId, depth, geometry) {
+ const cells = new Set();
+ cells.add(cellId);
+ connectedComponentRecursive(cells, [cellId], 0, depth, geometry);
+ return Array.from(cells);
+}
+function connectedComponentRecursive(cells, current, currentDepth, maxDepth, geometry) {
+ if (currentDepth >= maxDepth) {
+ return;
+ }
+ const adjacent = [];
+ for (const cellId of current) {
+ const aCells = geometry.getAdjacent(cellId);
+ adjacent.push(...aCells);
+ }
+ const newCells = [];
+ for (const a of adjacent) {
+ if (cells.has(a)) {
+ continue;
+ }
+ cells.add(a);
+ newCells.push(a);
+ }
+ connectedComponentRecursive(cells, newCells, currentDepth + 1, maxDepth, geometry);
+}
+
+class SpatialComponent extends Component {
+ /** @ignore */
+ constructor(name, container, navigator) {
+ super(name, container, navigator);
+ this._cache = new SpatialCache(navigator.graphService, navigator.api.data);
+ this._scene = new SpatialScene(this._getDefaultConfiguration());
+ this._viewportCoords = new ViewportCoords();
+ this._spatial = new Spatial();
+ }
+ /**
+ * Returns the image id of the camera frame closest to the current
+ * render camera position at the specified point.
+ *
+ * @description Notice that the pixelPoint argument requires x, y
+ * coordinates from pixel space.
+ *
+ * With this function, you can use the coordinates provided by mouse
+ * events to get information out of the spatial component.
+ *
+ * If no camera frame exist at the pixel
+ * point, `null` will be returned.
+ *
+ * @param {Array<number>} pixelPoint - Pixel coordinates on
+ * the viewer element.
+ * @returns {string} Image id of the camera frame closest to
+ * the camera. If no camera frame is intersected at the
+ * pixel point, `null` will be returned.
+ *
+ * @example
+ * ```js
+ * spatialComponent.getFrameIdAt([100, 125])
+ * .then((imageId) => { console.log(imageId); });
+ * ```
+ */
+ getFrameIdAt(pixelPoint) {
+ return new Promise((resolve, reject) => {
+ this._container.renderService.renderCamera$.pipe(first(), map((render) => {
+ const viewport = this._viewportCoords
+ .canvasToViewport(pixelPoint[0], pixelPoint[1], this._container.container);
+ const id = this._scene.intersection
+ .intersectObjects(viewport, render.perspective);
+ return id;
+ }))
+ .subscribe((id) => {
+ resolve(id);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ _activate() {
+ this._navigator.cacheService.configure({ cellDepth: 3 });
+ const subs = this._subscriptions;
+ subs.push(this._navigator.stateService.reference$
+ .subscribe(() => {
+ this._scene.uncache();
+ }));
+ subs.push(this._navigator.graphService.filter$
+ .subscribe(imageFilter => { this._scene.setFilter(imageFilter); }));
+ const bearing$ = this._container.renderService.bearing$.pipe(map((bearing) => {
+ const interval = 6;
+ const discrete = interval * Math.floor(bearing / interval);
+ return discrete;
+ }), distinctUntilChanged(), publishReplay(1), refCount());
+ const cellId$ = this._navigator.stateService.currentImage$
+ .pipe(map((image) => {
+ return this._navigator.api.data.geometry
+ .lngLatToCellId(image.originalLngLat);
+ }), distinctUntilChanged(), publishReplay(1), refCount());
+ const cellGridDepth$ = this._configuration$
+ .pipe(map((c) => {
+ return this._spatial.clamp(c.cellGridDepth, 1, 3);
+ }), distinctUntilChanged(), publishReplay(1), refCount());
+ const sequencePlay$ = combineLatest(this._navigator.playService.playing$, this._navigator.playService.speed$).pipe(map(([playing, speed]) => {
+ return playing && speed > PlayService.sequenceSpeed;
+ }), distinctUntilChanged(), publishReplay(1), refCount());
+ const isOverview$ = this._navigator.stateService.state$.pipe(map((state) => {
+ return isOverviewState(state);
+ }), distinctUntilChanged(), publishReplay(1), refCount());
+ subs.push(isOverview$.subscribe((isOverview) => {
+ this._scene.setNavigationState(isOverview);
+ }));
+ const cell$ = combineLatest(isOverview$, sequencePlay$, bearing$, cellGridDepth$, this._navigator.stateService.currentImage$)
+ .pipe(distinctUntilChanged(([o1, s1, b1, d1, i1], [o2, s2, b2, d2, i2]) => {
+ if (o1 !== o2) {
+ return false;
+ }
+ const isd = i1.id === i2.id && s1 === s2 && d1 === d2;
+ if (o1) {
+ return isd;
+ }
+ return isd && b1 === b2;
+ }), concatMap(([isOverview, sequencePlay, bearing, depth, image]) => {
+ if (isOverview) {
+ const geometry = this._navigator.api.data.geometry;
+ const cellId = geometry
+ .lngLatToCellId(image.originalLngLat);
+ const cells = sequencePlay ?
+ [cellId] :
+ connectedComponent(cellId, depth, geometry);
+ return of(cells);
+ }
+ const fov = sequencePlay ? 30 : 90;
+ return of(this._cellsInFov(image, bearing, fov));
+ }), switchMap((cellIds) => {
+ return from(cellIds).pipe(mergeMap((cellId) => {
+ const t$ = this._cache.hasCell(cellId) ?
+ of(this._cache.getCell(cellId)) :
+ this._cache.cacheCell$(cellId);
+ return t$.pipe(map((images) => ({ id: cellId, images })));
+ }, 6));
+ }));
+ subs.push(cell$.pipe(withLatestFrom(this._navigator.stateService.reference$))
+ .subscribe(([cell, reference]) => {
+ if (this._scene.hasCell(cell.id)) {
+ return;
+ }
+ this._scene.addCell(this._cellToTopocentric(cell.id, reference), cell.id);
+ }));
+ subs.push(cell$.pipe(withLatestFrom(this._navigator.stateService.reference$))
+ .subscribe(([cell, reference]) => {
+ this._addSceneImages(cell, reference);
+ }));
+ subs.push(cell$.pipe(concatMap((cell) => {
+ const cellId = cell.id;
+ let reconstructions$;
+ if (this._cache.hasClusters(cellId)) {
+ reconstructions$ = from(this._cache.getClusters(cellId));
+ }
+ else if (this._cache.isCachingClusters(cellId)) {
+ reconstructions$ = this._cache.cacheClusters$(cellId).pipe(last(null, {}), switchMap(() => {
+ return from(this._cache.getClusters(cellId));
+ }));
+ }
+ else if (this._cache.hasCell(cellId)) {
+ reconstructions$ = this._cache.cacheClusters$(cellId);
+ }
+ else {
+ reconstructions$ = empty();
+ }
+ return combineLatest(of(cellId), reconstructions$);
+ }), withLatestFrom(this._navigator.stateService.reference$))
+ .subscribe(([[cellId, reconstruction], reference]) => {
+ if (this._scene
+ .hasCluster(reconstruction.id, cellId)) {
+ return;
+ }
+ this._scene.addCluster(reconstruction, this._computeTranslation(reconstruction, reference), cellId);
+ }));
+ subs.push(this._configuration$.pipe(map((c) => {
+ c.cameraSize = this._spatial.clamp(c.cameraSize, 0.01, 1);
+ c.pointSize = this._spatial.clamp(c.pointSize, 0.01, 1);
+ return {
+ cameraSize: c.cameraSize,
+ cameraVisualizationMode: c.cameraVisualizationMode,
+ cellsVisible: c.cellsVisible,
+ originalPositionMode: c.originalPositionMode,
+ pointSize: c.pointSize,
+ pointsVisible: c.pointsVisible,
+ };
+ }), distinctUntilChanged((c1, c2) => {
+ return c1.cameraSize === c2.cameraSize &&
+ c1.cameraVisualizationMode === c2.cameraVisualizationMode &&
+ c1.cellsVisible === c2.cellsVisible &&
+ c1.originalPositionMode === c2.originalPositionMode &&
+ c1.pointSize === c2.pointSize &&
+ c1.pointsVisible === c2.pointsVisible;
+ }))
+ .subscribe((c) => {
+ this._scene.setCameraSize(c.cameraSize);
+ this._scene.setPointSize(c.pointSize);
+ this._scene.setPointVisibility(c.pointsVisible);
+ this._scene.setCellVisibility(c.cellsVisible);
+ const cvm = c.cameraVisualizationMode;
+ this._scene.setCameraVisualizationMode(cvm);
+ const opm = c.originalPositionMode;
+ this._scene.setPositionMode(opm);
+ }));
+ subs.push(combineLatest(cellId$, cellGridDepth$)
+ .subscribe(([cellId, depth]) => {
+ const keepCells = connectedComponent(cellId, depth, this._navigator.api.data.geometry);
+ this._scene.uncache(keepCells);
+ this._cache.uncache(keepCells);
+ }));
+ subs.push(this._navigator.playService.playing$.pipe(switchMap((playing) => {
+ return playing ?
+ empty() :
+ this._container.mouseService.dblClick$;
+ }), withLatestFrom(this._container.renderService.renderCamera$), switchMap(([event, render]) => {
+ const element = this._container.container;
+ const [canvasX, canvasY] = this._viewportCoords
+ .canvasPosition(event, element);
+ const viewport = this._viewportCoords.canvasToViewport(canvasX, canvasY, element);
+ const id = this._scene.intersection
+ .intersectObjects(viewport, render.perspective);
+ return !!id ?
+ this._navigator.moveTo$(id).pipe(catchError(() => {
+ return empty();
+ })) :
+ empty();
+ }))
+ .subscribe());
+ const intersectChange$ = combineLatest(this._configuration$, this._navigator.stateService.state$).pipe(map(([c, state]) => {
+ c.cameraSize = this._spatial.clamp(c.cameraSize, 0.01, 1);
+ return {
+ size: c.cameraSize,
+ visible: isModeVisible(c.cameraVisualizationMode),
+ state,
+ };
+ }), distinctUntilChanged((c1, c2) => {
+ return c1.size === c2.size &&
+ c1.visible === c2.visible &&
+ c1.state === c2.state;
+ }));
+ const mouseMove$ = this._container.mouseService.mouseMove$.pipe(publishReplay(1), refCount());
+ subs.push(mouseMove$.subscribe());
+ const mouseHover$ = merge(this._container.mouseService.mouseEnter$, this._container.mouseService.mouseLeave$, this._container.mouseService.windowBlur$);
+ subs.push(combineLatest(this._navigator.playService.playing$, mouseHover$, isOverview$, this._navigator.graphService.filter$)
+ .pipe(switchMap(([playing, mouseHover]) => {
+ return !playing && mouseHover.type === "pointerenter" ?
+ combineLatest(concat(mouseMove$.pipe(take(1)), this._container.mouseService.mouseMove$), this._container.renderService.renderCamera$, intersectChange$) :
+ combineLatest(of(mouseHover), of(null), of(null));
+ }))
+ .subscribe(([event, render]) => {
+ if (event.type !== "pointermove") {
+ this._scene.setHoveredImage(null);
+ return;
+ }
+ const element = this._container.container;
+ const [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+ const viewport = this._viewportCoords.canvasToViewport(canvasX, canvasY, element);
+ const key = this._scene.intersection
+ .intersectObjects(viewport, render.perspective);
+ this._scene.setHoveredImage(key);
+ }));
+ subs.push(this._navigator.stateService.currentId$
+ .subscribe((id) => {
+ this._scene.setSelectedImage(id);
+ }));
+ subs.push(this._navigator.stateService.currentState$
+ .pipe(map((frame) => {
+ const scene = this._scene;
+ return {
+ name: this._name,
+ renderer: {
+ frameId: frame.id,
+ needsRender: scene.needsRender,
+ render: scene.render.bind(scene),
+ pass: RenderPass$1.Opaque,
+ },
+ };
+ }))
+ .subscribe(this._container.glRenderer.render$));
+ const updatedCell$ = this._navigator.graphService.dataAdded$
+ .pipe(filter((cellId) => {
+ return this._cache.hasCell(cellId);
+ }), mergeMap((cellId) => {
+ return this._cache.updateCell$(cellId).pipe(map((images) => ({ id: cellId, images })), withLatestFrom(this._navigator.stateService.reference$));
+ }), publish(), refCount());
+ subs.push(updatedCell$
+ .subscribe(([cell, reference]) => {
+ this._addSceneImages(cell, reference);
+ }));
+ subs.push(updatedCell$
+ .pipe(concatMap(([cell]) => {
+ const cellId = cell.id;
+ const cache = this._cache;
+ let reconstructions$;
+ if (cache.hasClusters(cellId)) {
+ reconstructions$ =
+ cache.updateClusters$(cellId);
+ }
+ else if (cache.isCachingClusters(cellId)) {
+ reconstructions$ = this._cache.cacheClusters$(cellId).pipe(last(null, {}), switchMap(() => {
+ return from(cache.updateClusters$(cellId));
+ }));
+ }
+ else {
+ reconstructions$ = empty();
+ }
+ return combineLatest(of(cellId), reconstructions$);
+ }), withLatestFrom(this._navigator.stateService.reference$))
+ .subscribe(([[cellId, reconstruction], reference]) => {
+ if (this._scene.hasCluster(reconstruction.id, cellId)) {
+ return;
+ }
+ this._scene.addCluster(reconstruction, this._computeTranslation(reconstruction, reference), cellId);
+ }));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ this._cache.uncache();
+ this._scene.deactivate();
+ this._navigator.cacheService.configure();
+ }
+ _getDefaultConfiguration() {
+ return {
+ cameraSize: 0.1,
+ cameraVisualizationMode: CameraVisualizationMode.Homogeneous,
+ cellGridDepth: 1,
+ originalPositionMode: OriginalPositionMode.Hidden,
+ pointSize: 0.1,
+ pointsVisible: true,
+ cellsVisible: false,
+ };
+ }
+ _addSceneImages(cell, reference) {
+ const cellId = cell.id;
+ const images = cell.images;
+ for (const image of images) {
+ if (this._scene.hasImage(image.id, cellId)) {
+ continue;
+ }
+ this._scene.addImage(image, this._createTransform(image, reference), this._computeOriginalPosition(image, reference), cellId);
+ }
+ }
+ _cellsInFov(image, bearing, fov) {
+ const spatial = this._spatial;
+ const geometry = this._navigator.api.data.geometry;
+ const cell = geometry.lngLatToCellId(image.originalLngLat);
+ const cells = [cell];
+ const threshold = fov / 2;
+ const adjacent = geometry.getAdjacent(cell);
+ for (const a of adjacent) {
+ const vertices = geometry.getVertices(a);
+ for (const vertex of vertices) {
+ const [x, y] = geodeticToEnu(vertex.lng, vertex.lat, 0, image.lngLat.lng, image.lngLat.lat, 0);
+ const azimuthal = Math.atan2(y, x);
+ const vertexBearing = spatial.radToDeg(spatial.azimuthalToBearing(azimuthal));
+ if (Math.abs(vertexBearing - bearing) < threshold) {
+ cells.push(a);
+ }
+ }
+ }
+ return cells;
+ }
+ _computeOriginalPosition(image, reference) {
+ return geodeticToEnu(image.originalLngLat.lng, image.originalLngLat.lat, image.originalAltitude != null ? image.originalAltitude : image.computedAltitude, reference.lng, reference.lat, reference.alt);
+ }
+ _cellToTopocentric(cellId, reference) {
+ const vertices = this._navigator.api.data.geometry
+ .getVertices(cellId)
+ .map((vertex) => {
+ return geodeticToEnu(vertex.lng, vertex.lat, -2, reference.lng, reference.lat, reference.alt);
+ });
+ return vertices;
+ }
+ _computeTranslation(reconstruction, reference) {
+ return geodeticToEnu(reconstruction.reference.lng, reconstruction.reference.lat, reconstruction.reference.alt, reference.lng, reference.lat, reference.alt);
+ }
+ _createTransform(image, reference) {
+ const translation = computeTranslation({ alt: image.computedAltitude, lat: image.lngLat.lat, lng: image.lngLat.lng }, image.rotation, reference);
+ const transform = new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, undefined, undefined, image.cameraParameters, image.cameraType);
+ return transform;
+ }
+}
+SpatialComponent.componentName = "spatial";
+
+/**
+ * @class Geometry
+ * @abstract
+ * @classdesc Represents a geometry.
+ */
+class Geometry {
+ /**
+ * Create a geometry.
+ *
+ * @constructor
+ * @ignore
+ */
+ constructor() {
+ this._notifyChanged$ = new Subject();
+ }
+ /**
+ * Get changed observable.
+ *
+ * @description Emits the geometry itself every time the geometry
+ * has changed.
+ *
+ * @returns {Observable<Geometry>} Observable emitting the geometry instance.
+ * @ignore
+ */
+ get changed$() {
+ return this._notifyChanged$;
+ }
+}
+
+class GeometryTagError extends MapillaryError {
+ constructor(message) {
+ super(message != null ? message : "The provided geometry value is incorrect");
+ Object.setPrototypeOf(this, GeometryTagError.prototype);
+ this.name = "GeometryTagError";
+ }
+}
+
+/**
+ * @class PointsGeometry
+ *
+ * @classdesc Represents a point set in the 2D basic image coordinate system.
+ *
+ * @example
+ * ```js
+ * var points = [[0.5, 0.3], [0.7, 0.3], [0.6, 0.5]];
+ * var pointsGeometry = new PointsGeometry(points);
+ * ```
+ */
+class PointsGeometry extends Geometry {
+ /**
+ * Create a points geometry.
+ *
+ * @constructor
+ * @param {Array<Array<number>>} points - Array of 2D points on the basic coordinate
+ * system. The number of points must be greater than or equal to two.
+ *
+ * @throws {GeometryTagError} Point coordinates must be valid basic coordinates.
+ */
+ constructor(points) {
+ super();
+ const pointsLength = points.length;
+ if (pointsLength < 2) {
+ throw new GeometryTagError("A points geometry must have two or more positions.");
+ }
+ this._points = [];
+ for (const point of points) {
+ if (point[0] < 0 || point[0] > 1 ||
+ point[1] < 0 || point[1] > 1) {
+ throw new GeometryTagError("Basic coordinates of points must be on the interval [0, 1].");
+ }
+ this._points.push(point.slice());
+ }
+ }
+ /**
+ * Get points property.
+ * @returns {Array<Array<number>>} Array of 2d points.
+ */
+ get points() {
+ return this._points;
+ }
+ /**
+ * Add a point to the point set.
+ *
+ * @param {Array<number>} point - Point to add.
+ * @ignore
+ */
+ addPoint2d(point) {
+ const clamped = [
+ Math.max(0, Math.min(1, point[0])),
+ Math.max(0, Math.min(1, point[1])),
+ ];
+ this._points.push(clamped);
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get the coordinates of a point from the point set representation of the geometry.
+ *
+ * @param {number} index - Point index.
+ * @returns {Array<number>} Array representing the 2D basic coordinates of the point.
+ * @ignore
+ */
+ getPoint2d(index) {
+ return this._points[index].slice();
+ }
+ /**
+ * Remove a point from the point set.
+ *
+ * @param {number} index - The index of the point to remove.
+ * @ignore
+ */
+ removePoint2d(index) {
+ if (index < 0 ||
+ index >= this._points.length ||
+ this._points.length < 3) {
+ throw new GeometryTagError("Index for removed point must be valid.");
+ }
+ this._points.splice(index, 1);
+ this._notifyChanged$.next(this);
+ }
+ /** @ignore */
+ setVertex2d(index, value, transform) {
+ this.setPoint2d(index, value, transform);
+ }
+ /** @ignore */
+ setPoint2d(index, value, transform) {
+ const changed = [
+ Math.max(0, Math.min(1, value[0])),
+ Math.max(0, Math.min(1, value[1])),
+ ];
+ this._points[index] = changed;
+ this._notifyChanged$.next(this);
+ }
+ /** @ignore */
+ getPoints3d(transform) {
+ return this._getPoints3d(this._points, transform);
+ }
+ /** @ignore */
+ getPoint3d(index, transform) {
+ return transform.unprojectBasic(this._points[index], 200);
+ }
+ /** @ignore */
+ getPoints2d() {
+ return this._points.slice();
+ }
+ /** @ignore */
+ getCentroid2d(transform) {
+ if (!transform) {
+ throw new GeometryTagError("Get centroid must be called with a transform for points geometries.");
+ }
+ const [minX, minY, maxX, maxY] = this.getRect2d(transform);
+ const centroidX = minX < maxX ?
+ (minX + maxX) / 2 :
+ ((minX + maxX + 1) / 2) % 1;
+ const centroidY = (minY + maxY) / 2;
+ return [centroidX, centroidY];
+ }
+ /** @ignore */
+ getCentroid3d(transform) {
+ let centroid2d = this.getCentroid2d();
+ return transform.unprojectBasic(centroid2d, 200);
+ }
+ /** @ignore */
+ getRect2d(transform) {
+ let minX = 1;
+ let maxX = 0;
+ let minY = 1;
+ let maxY = 0;
+ const points = this._points;
+ for (const point of points) {
+ if (point[0] < minX) {
+ minX = point[0];
+ }
+ if (point[0] > maxX) {
+ maxX = point[0];
+ }
+ if (point[1] < minY) {
+ minY = point[1];
+ }
+ if (point[1] > maxY) {
+ maxY = point[1];
+ }
+ }
+ if (isSpherical(transform.cameraType)) {
+ const indices = [];
+ for (let i = 0; i < points.length; i++) {
+ indices[i] = i;
+ }
+ indices.sort((a, b) => {
+ return points[a][0] < points[b][0] ?
+ -1 :
+ points[a][0] > points[b][0] ?
+ 1 :
+ a < b ? -1 : 1;
+ });
+ let maxDistanceX = points[indices[0]][0] + 1 - points[indices[indices.length - 1]][0];
+ let leftMostIndex = 0;
+ for (let i = 0; i < indices.length - 1; i++) {
+ const index1 = indices[i];
+ const index2 = indices[i + 1];
+ const distanceX = points[index2][0] - points[index1][0];
+ if (distanceX > maxDistanceX) {
+ maxDistanceX = distanceX;
+ leftMostIndex = i + 1;
+ }
+ }
+ if (leftMostIndex > 0) {
+ minX = points[indices[leftMostIndex]][0];
+ maxX = points[indices[leftMostIndex - 1]][0];
+ }
+ }
+ return [minX, minY, maxX, maxY];
+ }
+ /** @ignore */
+ setCentroid2d(value, transform) {
+ throw new Error("Not implemented");
+ }
+ _getPoints3d(points2d, transform) {
+ return points2d
+ .map((point) => {
+ return transform.unprojectBasic(point, 200);
+ });
+ }
+}
+
+class CreateTag {
+ constructor(geometry, transform, viewportCoords) {
+ this._geometry = geometry;
+ this._transform = transform;
+ this._viewportCoords = !!viewportCoords ? viewportCoords : new ViewportCoords();
+ this._aborted$ = new Subject();
+ this._created$ = new Subject();
+ this._glObjectsChanged$ = new Subject();
+ this._geometryChangedSubscription = this._geometry.changed$
+ .subscribe(() => {
+ this._onGeometryChanged();
+ this._glObjectsChanged$.next(this);
+ });
+ }
+ get geometry() {
+ return this._geometry;
+ }
+ get glObjects() {
+ return this._glObjects;
+ }
+ get aborted$() {
+ return this._aborted$;
+ }
+ get created$() {
+ return this._created$;
+ }
+ get glObjectsChanged$() {
+ return this._glObjectsChanged$;
+ }
+ get geometryChanged$() {
+ return this._geometry.changed$.pipe(map(() => {
+ return this;
+ }));
+ }
+ dispose() {
+ this._geometryChangedSubscription.unsubscribe();
+ }
+ _canvasToTransform(canvas) {
+ const canvasX = Math.round(canvas[0]);
+ const canvasY = Math.round(canvas[1]);
+ const transform = `translate(-50%,-50%) translate(${canvasX}px,${canvasY}px)`;
+ return transform;
+ }
+ _colorToBackground(color) {
+ return "#" + ("000000" + color.toString(16)).substr(-6);
+ }
+ _createOutine(polygon3d, color) {
+ const positions = this._getLinePositions(polygon3d);
+ const geometry = new BufferGeometry();
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
+ const material = new LineBasicMaterial({
+ color: color,
+ linewidth: 1,
+ });
+ return new Line(geometry, material);
+ }
+ _disposeLine(line) {
+ if (line == null) {
+ return;
+ }
+ line.geometry.dispose();
+ line.material.dispose();
+ }
+ _getLinePositions(polygon3d) {
+ const length = polygon3d.length;
+ const positions = new Float32Array(length * 3);
+ for (let i = 0; i < length; ++i) {
+ const index = 3 * i;
+ const position = polygon3d[i];
+ positions[index] = position[0];
+ positions[index + 1] = position[1];
+ positions[index + 2] = position[2];
+ }
+ return positions;
+ }
+}
+
+var earcut_1 = earcut;
+var _default$2 = earcut;
+
+function earcut(data, holeIndices, dim) {
+
+ dim = dim || 2;
+
+ var hasHoles = holeIndices && holeIndices.length,
+ outerLen = hasHoles ? holeIndices[0] * dim : data.length,
+ outerNode = linkedList(data, 0, outerLen, dim, true),
+ triangles = [];
+
+ if (!outerNode || outerNode.next === outerNode.prev) return triangles;
+
+ var minX, minY, maxX, maxY, x, y, invSize;
+
+ if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
+
+ // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
+ if (data.length > 80 * dim) {
+ minX = maxX = data[0];
+ minY = maxY = data[1];
+
+ for (var i = dim; i < outerLen; i += dim) {
+ x = data[i];
+ y = data[i + 1];
+ if (x < minX) minX = x;
+ if (y < minY) minY = y;
+ if (x > maxX) maxX = x;
+ if (y > maxY) maxY = y;
+ }
+
+ // minX, minY and invSize are later used to transform coords into integers for z-order calculation
+ invSize = Math.max(maxX - minX, maxY - minY);
+ invSize = invSize !== 0 ? 1 / invSize : 0;
+ }
+
+ earcutLinked(outerNode, triangles, dim, minX, minY, invSize);
+
+ return triangles;
+}
+
+// create a circular doubly linked list from polygon points in the specified winding order
+function linkedList(data, start, end, dim, clockwise) {
+ var i, last;
+
+ if (clockwise === (signedArea$1(data, start, end, dim) > 0)) {
+ for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
+ } else {
+ for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
+ }
+
+ if (last && equals$1(last, last.next)) {
+ removeNode(last);
+ last = last.next;
+ }
+
+ return last;
+}
+
+// eliminate colinear or duplicate points
+function filterPoints(start, end) {
+ if (!start) return start;
+ if (!end) end = start;
+
+ var p = start,
+ again;
+ do {
+ again = false;
+
+ if (!p.steiner && (equals$1(p, p.next) || area(p.prev, p, p.next) === 0)) {
+ removeNode(p);
+ p = end = p.prev;
+ if (p === p.next) break;
+ again = true;
+
+ } else {
+ p = p.next;
+ }
+ } while (again || p !== end);
+
+ return end;
+}
+
+// main ear slicing loop which triangulates a polygon (given as a linked list)
+function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
+ if (!ear) return;
+
+ // interlink polygon nodes in z-order
+ if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
+
+ var stop = ear,
+ prev, next;
+
+ // iterate through ears, slicing them one by one
+ while (ear.prev !== ear.next) {
+ prev = ear.prev;
+ next = ear.next;
+
+ if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
+ // cut off the triangle
+ triangles.push(prev.i / dim);
+ triangles.push(ear.i / dim);
+ triangles.push(next.i / dim);
+
+ removeNode(ear);
+
+ // skipping the next vertex leads to less sliver triangles
+ ear = next.next;
+ stop = next.next;
+
+ continue;
+ }
+
+ ear = next;
+
+ // if we looped through the whole remaining polygon and can't find any more ears
+ if (ear === stop) {
+ // try filtering points and slicing again
+ if (!pass) {
+ earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
+
+ // if this didn't work, try curing all small self-intersections locally
+ } else if (pass === 1) {
+ ear = cureLocalIntersections(filterPoints(ear), triangles, dim);
+ earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
+
+ // as a last resort, try splitting the remaining polygon into two
+ } else if (pass === 2) {
+ splitEarcut(ear, triangles, dim, minX, minY, invSize);
+ }
+
+ break;
+ }
+ }
+}
+
+// check whether a polygon node forms a valid ear with adjacent nodes
+function isEar(ear) {
+ var a = ear.prev,
+ b = ear,
+ c = ear.next;
+
+ if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
+
+ // now make sure we don't have other points inside the potential ear
+ var p = ear.next.next;
+
+ while (p !== ear.prev) {
+ if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
+ area(p.prev, p, p.next) >= 0) return false;
+ p = p.next;
+ }
+
+ return true;
+}
+
+function isEarHashed(ear, minX, minY, invSize) {
+ var a = ear.prev,
+ b = ear,
+ c = ear.next;
+
+ if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
+
+ // triangle bbox; min & max are calculated like this for speed
+ var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x),
+ minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y),
+ maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x),
+ maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);
+
+ // z-order range for the current triangle bbox;
+ var minZ = zOrder(minTX, minTY, minX, minY, invSize),
+ maxZ = zOrder(maxTX, maxTY, minX, minY, invSize);
+
+ var p = ear.prevZ,
+ n = ear.nextZ;
+
+ // look for points inside the triangle in both directions
+ while (p && p.z >= minZ && n && n.z <= maxZ) {
+ if (p !== ear.prev && p !== ear.next &&
+ pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
+ area(p.prev, p, p.next) >= 0) return false;
+ p = p.prevZ;
+
+ if (n !== ear.prev && n !== ear.next &&
+ pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
+ area(n.prev, n, n.next) >= 0) return false;
+ n = n.nextZ;
+ }
+
+ // look for remaining points in decreasing z-order
+ while (p && p.z >= minZ) {
+ if (p !== ear.prev && p !== ear.next &&
+ pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
+ area(p.prev, p, p.next) >= 0) return false;
+ p = p.prevZ;
+ }
+
+ // look for remaining points in increasing z-order
+ while (n && n.z <= maxZ) {
+ if (n !== ear.prev && n !== ear.next &&
+ pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, n.x, n.y) &&
+ area(n.prev, n, n.next) >= 0) return false;
+ n = n.nextZ;
+ }
+
+ return true;
+}
+
+// go through all polygon nodes and cure small local self-intersections
+function cureLocalIntersections(start, triangles, dim) {
+ var p = start;
+ do {
+ var a = p.prev,
+ b = p.next.next;
+
+ if (!equals$1(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
+
+ triangles.push(a.i / dim);
+ triangles.push(p.i / dim);
+ triangles.push(b.i / dim);
+
+ // remove two nodes involved
+ removeNode(p);
+ removeNode(p.next);
+
+ p = start = b;
+ }
+ p = p.next;
+ } while (p !== start);
+
+ return filterPoints(p);
+}
+
+// try splitting polygon into two and triangulate them independently
+function splitEarcut(start, triangles, dim, minX, minY, invSize) {
+ // look for a valid diagonal that divides the polygon into two
+ var a = start;
+ do {
+ var b = a.next.next;
+ while (b !== a.prev) {
+ if (a.i !== b.i && isValidDiagonal(a, b)) {
+ // split the polygon in two by the diagonal
+ var c = splitPolygon(a, b);
+
+ // filter colinear points around the cuts
+ a = filterPoints(a, a.next);
+ c = filterPoints(c, c.next);
+
+ // run earcut on each half
+ earcutLinked(a, triangles, dim, minX, minY, invSize);
+ earcutLinked(c, triangles, dim, minX, minY, invSize);
+ return;
+ }
+ b = b.next;
+ }
+ a = a.next;
+ } while (a !== start);
+}
+
+// link every hole into the outer loop, producing a single-ring polygon without holes
+function eliminateHoles(data, holeIndices, outerNode, dim) {
+ var queue = [],
+ i, len, start, end, list;
+
+ for (i = 0, len = holeIndices.length; i < len; i++) {
+ start = holeIndices[i] * dim;
+ end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
+ list = linkedList(data, start, end, dim, false);
+ if (list === list.next) list.steiner = true;
+ queue.push(getLeftmost(list));
+ }
+
+ queue.sort(compareX);
+
+ // process holes from left to right
+ for (i = 0; i < queue.length; i++) {
+ eliminateHole(queue[i], outerNode);
+ outerNode = filterPoints(outerNode, outerNode.next);
+ }
+
+ return outerNode;
+}
+
+function compareX(a, b) {
+ return a.x - b.x;
+}
+
+// find a bridge between vertices that connects hole with an outer ring and and link it
+function eliminateHole(hole, outerNode) {
+ outerNode = findHoleBridge(hole, outerNode);
+ if (outerNode) {
+ var b = splitPolygon(outerNode, hole);
+
+ // filter collinear points around the cuts
+ filterPoints(outerNode, outerNode.next);
+ filterPoints(b, b.next);
+ }
+}
+
+// David Eberly's algorithm for finding a bridge between hole and outer polygon
+function findHoleBridge(hole, outerNode) {
+ var p = outerNode,
+ hx = hole.x,
+ hy = hole.y,
+ qx = -Infinity,
+ m;
+
+ // find a segment intersected by a ray from the hole's leftmost point to the left;
+ // segment's endpoint with lesser x will be potential connection point
+ do {
+ if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
+ var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
+ if (x <= hx && x > qx) {
+ qx = x;
+ if (x === hx) {
+ if (hy === p.y) return p;
+ if (hy === p.next.y) return p.next;
+ }
+ m = p.x < p.next.x ? p : p.next;
+ }
+ }
+ p = p.next;
+ } while (p !== outerNode);
+
+ if (!m) return null;
+
+ if (hx === qx) return m; // hole touches outer segment; pick leftmost endpoint
+
+ // look for points inside the triangle of hole point, segment intersection and endpoint;
+ // if there are no points found, we have a valid connection;
+ // otherwise choose the point of the minimum angle with the ray as connection point
+
+ var stop = m,
+ mx = m.x,
+ my = m.y,
+ tanMin = Infinity,
+ tan;
+
+ p = m;
+
+ do {
+ if (hx >= p.x && p.x >= mx && hx !== p.x &&
+ pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
+
+ tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
+
+ if (locallyInside(p, hole) &&
+ (tan < tanMin || (tan === tanMin && (p.x > m.x || (p.x === m.x && sectorContainsSector(m, p)))))) {
+ m = p;
+ tanMin = tan;
+ }
+ }
+
+ p = p.next;
+ } while (p !== stop);
+
+ return m;
+}
+
+// whether sector in vertex m contains sector in vertex p in the same coordinates
+function sectorContainsSector(m, p) {
+ return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0;
+}
+
+// interlink polygon nodes in z-order
+function indexCurve(start, minX, minY, invSize) {
+ var p = start;
+ do {
+ if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, invSize);
+ p.prevZ = p.prev;
+ p.nextZ = p.next;
+ p = p.next;
+ } while (p !== start);
+
+ p.prevZ.nextZ = null;
+ p.prevZ = null;
+
+ sortLinked(p);
+}
+
+// Simon Tatham's linked list merge sort algorithm
+// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
+function sortLinked(list) {
+ var i, p, q, e, tail, numMerges, pSize, qSize,
+ inSize = 1;
+
+ do {
+ p = list;
+ list = null;
+ tail = null;
+ numMerges = 0;
+
+ while (p) {
+ numMerges++;
+ q = p;
+ pSize = 0;
+ for (i = 0; i < inSize; i++) {
+ pSize++;
+ q = q.nextZ;
+ if (!q) break;
+ }
+ qSize = inSize;
+
+ while (pSize > 0 || (qSize > 0 && q)) {
+
+ if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
+ e = p;
+ p = p.nextZ;
+ pSize--;
+ } else {
+ e = q;
+ q = q.nextZ;
+ qSize--;
+ }
+
+ if (tail) tail.nextZ = e;
+ else list = e;
+
+ e.prevZ = tail;
+ tail = e;
+ }
+
+ p = q;
+ }
+
+ tail.nextZ = null;
+ inSize *= 2;
+
+ } while (numMerges > 1);
+
+ return list;
+}
+
+// z-order of a point given coords and inverse of the longer side of data bbox
+function zOrder(x, y, minX, minY, invSize) {
+ // coords are transformed into non-negative 15-bit integer range
+ x = 32767 * (x - minX) * invSize;
+ y = 32767 * (y - minY) * invSize;
+
+ x = (x | (x << 8)) & 0x00FF00FF;
+ x = (x | (x << 4)) & 0x0F0F0F0F;
+ x = (x | (x << 2)) & 0x33333333;
+ x = (x | (x << 1)) & 0x55555555;
+
+ y = (y | (y << 8)) & 0x00FF00FF;
+ y = (y | (y << 4)) & 0x0F0F0F0F;
+ y = (y | (y << 2)) & 0x33333333;
+ y = (y | (y << 1)) & 0x55555555;
+
+ return x | (y << 1);
+}
+
+// find the leftmost node of a polygon ring
+function getLeftmost(start) {
+ var p = start,
+ leftmost = start;
+ do {
+ if (p.x < leftmost.x || (p.x === leftmost.x && p.y < leftmost.y)) leftmost = p;
+ p = p.next;
+ } while (p !== start);
+
+ return leftmost;
+}
+
+// check if a point lies within a convex triangle
+function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
+ return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
+ (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
+ (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
+}
+
+// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
+function isValidDiagonal(a, b) {
+ return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges
+ (locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
+ (area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
+ equals$1(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0); // special zero-length case
+}
+
+// signed area of a triangle
+function area(p, q, r) {
+ return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
+}
+
+// check if two points are equal
+function equals$1(p1, p2) {
+ return p1.x === p2.x && p1.y === p2.y;
+}
+
+// check if two segments intersect
+function intersects(p1, q1, p2, q2) {
+ var o1 = sign(area(p1, q1, p2));
+ var o2 = sign(area(p1, q1, q2));
+ var o3 = sign(area(p2, q2, p1));
+ var o4 = sign(area(p2, q2, q1));
+
+ if (o1 !== o2 && o3 !== o4) return true; // general case
+
+ if (o1 === 0 && onSegment(p1, p2, q1)) return true; // p1, q1 and p2 are collinear and p2 lies on p1q1
+ if (o2 === 0 && onSegment(p1, q2, q1)) return true; // p1, q1 and q2 are collinear and q2 lies on p1q1
+ if (o3 === 0 && onSegment(p2, p1, q2)) return true; // p2, q2 and p1 are collinear and p1 lies on p2q2
+ if (o4 === 0 && onSegment(p2, q1, q2)) return true; // p2, q2 and q1 are collinear and q1 lies on p2q2
+
+ return false;
+}
+
+// for collinear points p, q, r, check if point q lies on segment pr
+function onSegment(p, q, r) {
+ return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
+}
+
+function sign(num) {
+ return num > 0 ? 1 : num < 0 ? -1 : 0;
+}
+
+// check if a polygon diagonal intersects any polygon segments
+function intersectsPolygon(a, b) {
+ var p = a;
+ do {
+ if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
+ intersects(p, p.next, a, b)) return true;
+ p = p.next;
+ } while (p !== a);
+
+ return false;
+}
+
+// check if a polygon diagonal is locally inside the polygon
+function locallyInside(a, b) {
+ return area(a.prev, a, a.next) < 0 ?
+ area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
+ area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
+}
+
+// check if the middle point of a polygon diagonal is inside the polygon
+function middleInside(a, b) {
+ var p = a,
+ inside = false,
+ px = (a.x + b.x) / 2,
+ py = (a.y + b.y) / 2;
+ do {
+ if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
+ (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
+ inside = !inside;
+ p = p.next;
+ } while (p !== a);
+
+ return inside;
+}
+
+// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
+// if one belongs to the outer ring and another to a hole, it merges it into a single ring
+function splitPolygon(a, b) {
+ var a2 = new Node(a.i, a.x, a.y),
+ b2 = new Node(b.i, b.x, b.y),
+ an = a.next,
+ bp = b.prev;
+
+ a.next = b;
+ b.prev = a;
+
+ a2.next = an;
+ an.prev = a2;
+
+ b2.next = a2;
+ a2.prev = b2;
+
+ bp.next = b2;
+ b2.prev = bp;
+
+ return b2;
+}
+
+// create a node and optionally link it with previous one (in a circular doubly linked list)
+function insertNode(i, x, y, last) {
+ var p = new Node(i, x, y);
+
+ if (!last) {
+ p.prev = p;
+ p.next = p;
+
+ } else {
+ p.next = last.next;
+ p.prev = last;
+ last.next.prev = p;
+ last.next = p;
+ }
+ return p;
+}
+
+function removeNode(p) {
+ p.next.prev = p.prev;
+ p.prev.next = p.next;
+
+ if (p.prevZ) p.prevZ.nextZ = p.nextZ;
+ if (p.nextZ) p.nextZ.prevZ = p.prevZ;
+}
+
+function Node(i, x, y) {
+ // vertex index in coordinates array
+ this.i = i;
+
+ // vertex coordinates
+ this.x = x;
+ this.y = y;
+
+ // previous and next vertex nodes in a polygon ring
+ this.prev = null;
+ this.next = null;
+
+ // z-order curve value
+ this.z = null;
+
+ // previous and next nodes in z-order
+ this.prevZ = null;
+ this.nextZ = null;
+
+ // indicates whether this is a steiner point
+ this.steiner = false;
+}
+
+// return a percentage difference between the polygon area and its triangulation area;
+// used to verify correctness of triangulation
+earcut.deviation = function (data, holeIndices, dim, triangles) {
+ var hasHoles = holeIndices && holeIndices.length;
+ var outerLen = hasHoles ? holeIndices[0] * dim : data.length;
+
+ var polygonArea = Math.abs(signedArea$1(data, 0, outerLen, dim));
+ if (hasHoles) {
+ for (var i = 0, len = holeIndices.length; i < len; i++) {
+ var start = holeIndices[i] * dim;
+ var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
+ polygonArea -= Math.abs(signedArea$1(data, start, end, dim));
+ }
+ }
+
+ var trianglesArea = 0;
+ for (i = 0; i < triangles.length; i += 3) {
+ var a = triangles[i] * dim;
+ var b = triangles[i + 1] * dim;
+ var c = triangles[i + 2] * dim;
+ trianglesArea += Math.abs(
+ (data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
+ (data[a] - data[b]) * (data[c + 1] - data[a + 1]));
+ }
+
+ return polygonArea === 0 && trianglesArea === 0 ? 0 :
+ Math.abs((trianglesArea - polygonArea) / polygonArea);
+};
+
+function signedArea$1(data, start, end, dim) {
+ var sum = 0;
+ for (var i = start, j = end - dim; i < end; i += dim) {
+ sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
+ j = i;
+ }
+ return sum;
+}
+
+// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
+earcut.flatten = function (data) {
+ var dim = data[0][0].length,
+ result = {vertices: [], holes: [], dimensions: dim},
+ holeIndex = 0;
+
+ for (var i = 0; i < data.length; i++) {
+ for (var j = 0; j < data[i].length; j++) {
+ for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]);
+ }
+ if (i > 0) {
+ holeIndex += data[i - 1].length;
+ result.holes.push(holeIndex);
+ }
+ }
+ return result;
+};
+earcut_1.default = _default$2;
+
+class TinyQueue$1 {
+ constructor(data = [], compare = defaultCompare$1) {
+ this.data = data;
+ this.length = this.data.length;
+ this.compare = compare;
+
+ if (this.length > 0) {
+ for (let i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
+ }
+ }
+
+ push(item) {
+ this.data.push(item);
+ this.length++;
+ this._up(this.length - 1);
+ }
+
+ pop() {
+ if (this.length === 0) return undefined;
+
+ const top = this.data[0];
+ const bottom = this.data.pop();
+ this.length--;
+
+ if (this.length > 0) {
+ this.data[0] = bottom;
+ this._down(0);
+ }
+
+ return top;
+ }
+
+ peek() {
+ return this.data[0];
+ }
+
+ _up(pos) {
+ const {data, compare} = this;
+ const item = data[pos];
+
+ while (pos > 0) {
+ const parent = (pos - 1) >> 1;
+ const current = data[parent];
+ if (compare(item, current) >= 0) break;
+ data[pos] = current;
+ pos = parent;
+ }
+
+ data[pos] = item;
+ }
+
+ _down(pos) {
+ const {data, compare} = this;
+ const halfLength = this.length >> 1;
+ const item = data[pos];
+
+ while (pos < halfLength) {
+ let left = (pos << 1) + 1;
+ let best = data[left];
+ const right = left + 1;
+
+ if (right < this.length && compare(data[right], best) < 0) {
+ left = right;
+ best = data[right];
+ }
+ if (compare(best, item) >= 0) break;
+
+ data[pos] = best;
+ pos = left;
+ }
+
+ data[pos] = item;
+ }
+}
+
+function defaultCompare$1(a, b) {
+ return a < b ? -1 : a > b ? 1 : 0;
+}
+
+var tinyqueue$1 = /*#__PURE__*/Object.freeze({
+ __proto__: null,
+ 'default': TinyQueue$1
+});
+
+var require$$0 = /*@__PURE__*/getAugmentedNamespace(tinyqueue$1);
+
+var Queue = require$$0;
+
+if (Queue.default) Queue = Queue.default; // temporary webpack fix
+
+var polylabel_1 = polylabel;
+var _default$1 = polylabel;
+
+function polylabel(polygon, precision, debug) {
+ precision = precision || 1.0;
+
+ // find the bounding box of the outer ring
+ var minX, minY, maxX, maxY;
+ for (var i = 0; i < polygon[0].length; i++) {
+ var p = polygon[0][i];
+ if (!i || p[0] < minX) minX = p[0];
+ if (!i || p[1] < minY) minY = p[1];
+ if (!i || p[0] > maxX) maxX = p[0];
+ if (!i || p[1] > maxY) maxY = p[1];
+ }
+
+ var width = maxX - minX;
+ var height = maxY - minY;
+ var cellSize = Math.min(width, height);
+ var h = cellSize / 2;
+
+ if (cellSize === 0) {
+ var degeneratePoleOfInaccessibility = [minX, minY];
+ degeneratePoleOfInaccessibility.distance = 0;
+ return degeneratePoleOfInaccessibility;
+ }
+
+ // a priority queue of cells in order of their "potential" (max distance to polygon)
+ var cellQueue = new Queue(undefined, compareMax);
+
+ // cover polygon with initial cells
+ for (var x = minX; x < maxX; x += cellSize) {
+ for (var y = minY; y < maxY; y += cellSize) {
+ cellQueue.push(new Cell(x + h, y + h, h, polygon));
+ }
+ }
+
+ // take centroid as the first best guess
+ var bestCell = getCentroidCell(polygon);
+
+ // special case for rectangular polygons
+ var bboxCell = new Cell(minX + width / 2, minY + height / 2, 0, polygon);
+ if (bboxCell.d > bestCell.d) bestCell = bboxCell;
+
+ var numProbes = cellQueue.length;
+
+ while (cellQueue.length) {
+ // pick the most promising cell from the queue
+ var cell = cellQueue.pop();
+
+ // update the best cell if we found a better one
+ if (cell.d > bestCell.d) {
+ bestCell = cell;
+ if (debug) console.log('found best %d after %d probes', Math.round(1e4 * cell.d) / 1e4, numProbes);
+ }
+
+ // do not drill down further if there's no chance of a better solution
+ if (cell.max - bestCell.d <= precision) continue;
+
+ // split the cell into four cells
+ h = cell.h / 2;
+ cellQueue.push(new Cell(cell.x - h, cell.y - h, h, polygon));
+ cellQueue.push(new Cell(cell.x + h, cell.y - h, h, polygon));
+ cellQueue.push(new Cell(cell.x - h, cell.y + h, h, polygon));
+ cellQueue.push(new Cell(cell.x + h, cell.y + h, h, polygon));
+ numProbes += 4;
+ }
+
+ if (debug) {
+ console.log('num probes: ' + numProbes);
+ console.log('best distance: ' + bestCell.d);
+ }
+
+ var poleOfInaccessibility = [bestCell.x, bestCell.y];
+ poleOfInaccessibility.distance = bestCell.d;
+ return poleOfInaccessibility;
+}
+
+function compareMax(a, b) {
+ return b.max - a.max;
+}
+
+function Cell(x, y, h, polygon) {
+ this.x = x; // cell center x
+ this.y = y; // cell center y
+ this.h = h; // half the cell size
+ this.d = pointToPolygonDist(x, y, polygon); // distance from cell center to polygon
+ this.max = this.d + this.h * Math.SQRT2; // max distance to polygon within a cell
+}
+
+// signed distance from point to polygon outline (negative if point is outside)
+function pointToPolygonDist(x, y, polygon) {
+ var inside = false;
+ var minDistSq = Infinity;
+
+ for (var k = 0; k < polygon.length; k++) {
+ var ring = polygon[k];
+
+ for (var i = 0, len = ring.length, j = len - 1; i < len; j = i++) {
+ var a = ring[i];
+ var b = ring[j];
+
+ if ((a[1] > y !== b[1] > y) &&
+ (x < (b[0] - a[0]) * (y - a[1]) / (b[1] - a[1]) + a[0])) inside = !inside;
+
+ minDistSq = Math.min(minDistSq, getSegDistSq(x, y, a, b));
+ }
+ }
+
+ return minDistSq === 0 ? 0 : (inside ? 1 : -1) * Math.sqrt(minDistSq);
+}
+
+// get polygon centroid
+function getCentroidCell(polygon) {
+ var area = 0;
+ var x = 0;
+ var y = 0;
+ var points = polygon[0];
+
+ for (var i = 0, len = points.length, j = len - 1; i < len; j = i++) {
+ var a = points[i];
+ var b = points[j];
+ var f = a[0] * b[1] - b[0] * a[1];
+ x += (a[0] + b[0]) * f;
+ y += (a[1] + b[1]) * f;
+ area += f * 3;
+ }
+ if (area === 0) return new Cell(points[0][0], points[0][1], 0, polygon);
+ return new Cell(x / area, y / area, 0, polygon);
+}
+
+// get squared distance from a point to a segment
+function getSegDistSq(px, py, a, b) {
+
+ var x = a[0];
+ var y = a[1];
+ var dx = b[0] - x;
+ var dy = b[1] - y;
+
+ if (dx !== 0 || dy !== 0) {
+
+ var t = ((px - x) * dx + (py - y) * dy) / (dx * dx + dy * dy);
+
+ if (t > 1) {
+ x = b[0];
+ y = b[1];
+
+ } else if (t > 0) {
+ x += dx * t;
+ y += dy * t;
+ }
+ }
+
+ dx = px - x;
+ dy = py - y;
+
+ return dx * dx + dy * dy;
+}
+polylabel_1.default = _default$1;
+
+function DEFAULT_COMPARE (a, b) { return a > b ? 1 : a < b ? -1 : 0; }
+
+class SplayTree {
+
+ constructor(compare = DEFAULT_COMPARE, noDuplicates = false) {
+ this._compare = compare;
+ this._root = null;
+ this._size = 0;
+ this._noDuplicates = !!noDuplicates;
+ }
+
+
+ rotateLeft(x) {
+ var y = x.right;
+ if (y) {
+ x.right = y.left;
+ if (y.left) y.left.parent = x;
+ y.parent = x.parent;
+ }
+
+ if (!x.parent) this._root = y;
+ else if (x === x.parent.left) x.parent.left = y;
+ else x.parent.right = y;
+ if (y) y.left = x;
+ x.parent = y;
+ }
+
+
+ rotateRight(x) {
+ var y = x.left;
+ if (y) {
+ x.left = y.right;
+ if (y.right) y.right.parent = x;
+ y.parent = x.parent;
+ }
+
+ if (!x.parent) this._root = y;
+ else if(x === x.parent.left) x.parent.left = y;
+ else x.parent.right = y;
+ if (y) y.right = x;
+ x.parent = y;
+ }
+
+
+ _splay(x) {
+ while (x.parent) {
+ var p = x.parent;
+ if (!p.parent) {
+ if (p.left === x) this.rotateRight(p);
+ else this.rotateLeft(p);
+ } else if (p.left === x && p.parent.left === p) {
+ this.rotateRight(p.parent);
+ this.rotateRight(p);
+ } else if (p.right === x && p.parent.right === p) {
+ this.rotateLeft(p.parent);
+ this.rotateLeft(p);
+ } else if (p.left === x && p.parent.right === p) {
+ this.rotateRight(p);
+ this.rotateLeft(p);
+ } else {
+ this.rotateLeft(p);
+ this.rotateRight(p);
+ }
+ }
+ }
+
+
+ splay(x) {
+ var p, gp, ggp, l, r;
+
+ while (x.parent) {
+ p = x.parent;
+ gp = p.parent;
+
+ if (gp && gp.parent) {
+ ggp = gp.parent;
+ if (ggp.left === gp) ggp.left = x;
+ else ggp.right = x;
+ x.parent = ggp;
+ } else {
+ x.parent = null;
+ this._root = x;
+ }
+
+ l = x.left; r = x.right;
+
+ if (x === p.left) { // left
+ if (gp) {
+ if (gp.left === p) {
+ /* zig-zig */
+ if (p.right) {
+ gp.left = p.right;
+ gp.left.parent = gp;
+ } else gp.left = null;
+
+ p.right = gp;
+ gp.parent = p;
+ } else {
+ /* zig-zag */
+ if (l) {
+ gp.right = l;
+ l.parent = gp;
+ } else gp.right = null;
+
+ x.left = gp;
+ gp.parent = x;
+ }
+ }
+ if (r) {
+ p.left = r;
+ r.parent = p;
+ } else p.left = null;
+
+ x.right = p;
+ p.parent = x;
+ } else { // right
+ if (gp) {
+ if (gp.right === p) {
+ /* zig-zig */
+ if (p.left) {
+ gp.right = p.left;
+ gp.right.parent = gp;
+ } else gp.right = null;
+
+ p.left = gp;
+ gp.parent = p;
+ } else {
+ /* zig-zag */
+ if (r) {
+ gp.left = r;
+ r.parent = gp;
+ } else gp.left = null;
+
+ x.right = gp;
+ gp.parent = x;
+ }
+ }
+ if (l) {
+ p.right = l;
+ l.parent = p;
+ } else p.right = null;
+
+ x.left = p;
+ p.parent = x;
+ }
+ }
+ }
+
+
+ replace(u, v) {
+ if (!u.parent) this._root = v;
+ else if (u === u.parent.left) u.parent.left = v;
+ else u.parent.right = v;
+ if (v) v.parent = u.parent;
+ }
+
+
+ minNode(u = this._root) {
+ if (u) while (u.left) u = u.left;
+ return u;
+ }
+
+
+ maxNode(u = this._root) {
+ if (u) while (u.right) u = u.right;
+ return u;
+ }
+
+
+ insert(key, data) {
+ var z = this._root;
+ var p = null;
+ var comp = this._compare;
+ var cmp;
+
+ if (this._noDuplicates) {
+ while (z) {
+ p = z;
+ cmp = comp(z.key, key);
+ if (cmp === 0) return;
+ else if (comp(z.key, key) < 0) z = z.right;
+ else z = z.left;
+ }
+ } else {
+ while (z) {
+ p = z;
+ if (comp(z.key, key) < 0) z = z.right;
+ else z = z.left;
+ }
+ }
+
+ z = { key, data, left: null, right: null, parent: p };
+
+ if (!p) this._root = z;
+ else if (comp(p.key, z.key) < 0) p.right = z;
+ else p.left = z;
+
+ this.splay(z);
+ this._size++;
+ return z;
+ }
+
+
+ find (key) {
+ var z = this._root;
+ var comp = this._compare;
+ while (z) {
+ var cmp = comp(z.key, key);
+ if (cmp < 0) z = z.right;
+ else if (cmp > 0) z = z.left;
+ else return z;
+ }
+ return null;
+ }
+
+ /**
+ * Whether the tree contains a node with the given key
+ * @param {Key} key
+ * @return {boolean} true/false
+ */
+ contains (key) {
+ var node = this._root;
+ var comparator = this._compare;
+ while (node) {
+ var cmp = comparator(key, node.key);
+ if (cmp === 0) return true;
+ else if (cmp < 0) node = node.left;
+ else node = node.right;
+ }
+
+ return false;
+ }
+
+
+ remove (key) {
+ var z = this.find(key);
+
+ if (!z) return false;
+
+ this.splay(z);
+
+ if (!z.left) this.replace(z, z.right);
+ else if (!z.right) this.replace(z, z.left);
+ else {
+ var y = this.minNode(z.right);
+ if (y.parent !== z) {
+ this.replace(y, y.right);
+ y.right = z.right;
+ y.right.parent = y;
+ }
+ this.replace(z, y);
+ y.left = z.left;
+ y.left.parent = y;
+ }
+
+ this._size--;
+ return true;
+ }
+
+
+ removeNode(z) {
+ if (!z) return false;
+
+ this.splay(z);
+
+ if (!z.left) this.replace(z, z.right);
+ else if (!z.right) this.replace(z, z.left);
+ else {
+ var y = this.minNode(z.right);
+ if (y.parent !== z) {
+ this.replace(y, y.right);
+ y.right = z.right;
+ y.right.parent = y;
+ }
+ this.replace(z, y);
+ y.left = z.left;
+ y.left.parent = y;
+ }
+
+ this._size--;
+ return true;
+ }
+
+
+ erase (key) {
+ var z = this.find(key);
+ if (!z) return;
+
+ this.splay(z);
+
+ var s = z.left;
+ var t = z.right;
+
+ var sMax = null;
+ if (s) {
+ s.parent = null;
+ sMax = this.maxNode(s);
+ this.splay(sMax);
+ this._root = sMax;
+ }
+ if (t) {
+ if (s) sMax.right = t;
+ else this._root = t;
+ t.parent = sMax;
+ }
+
+ this._size--;
+ }
+
+ /**
+ * Removes and returns the node with smallest key
+ * @return {?Node}
+ */
+ pop () {
+ var node = this._root, returnValue = null;
+ if (node) {
+ while (node.left) node = node.left;
+ returnValue = { key: node.key, data: node.data };
+ this.remove(node.key);
+ }
+ return returnValue;
+ }
+
+
+ /* eslint-disable class-methods-use-this */
+
+ /**
+ * Successor node
+ * @param {Node} node
+ * @return {?Node}
+ */
+ next (node) {
+ var successor = node;
+ if (successor) {
+ if (successor.right) {
+ successor = successor.right;
+ while (successor && successor.left) successor = successor.left;
+ } else {
+ successor = node.parent;
+ while (successor && successor.right === node) {
+ node = successor; successor = successor.parent;
+ }
+ }
+ }
+ return successor;
+ }
+
+
+ /**
+ * Predecessor node
+ * @param {Node} node
+ * @return {?Node}
+ */
+ prev (node) {
+ var predecessor = node;
+ if (predecessor) {
+ if (predecessor.left) {
+ predecessor = predecessor.left;
+ while (predecessor && predecessor.right) predecessor = predecessor.right;
+ } else {
+ predecessor = node.parent;
+ while (predecessor && predecessor.left === node) {
+ node = predecessor;
+ predecessor = predecessor.parent;
+ }
+ }
+ }
+ return predecessor;
+ }
+ /* eslint-enable class-methods-use-this */
+
+
+ /**
+ * @param {forEachCallback} callback
+ * @return {SplayTree}
+ */
+ forEach(callback) {
+ var current = this._root;
+ var s = [], done = false, i = 0;
+
+ while (!done) {
+ // Reach the left most Node of the current Node
+ if (current) {
+ // Place pointer to a tree node on the stack
+ // before traversing the node's left subtree
+ s.push(current);
+ current = current.left;
+ } else {
+ // BackTrack from the empty subtree and visit the Node
+ // at the top of the stack; however, if the stack is
+ // empty you are done
+ if (s.length > 0) {
+ current = s.pop();
+ callback(current, i++);
+
+ // We have visited the node and its left
+ // subtree. Now, it's right subtree's turn
+ current = current.right;
+ } else done = true;
+ }
+ }
+ return this;
+ }
+
+
+ /**
+ * Walk key range from `low` to `high`. Stops if `fn` returns a value.
+ * @param {Key} low
+ * @param {Key} high
+ * @param {Function} fn
+ * @param {*?} ctx
+ * @return {SplayTree}
+ */
+ range(low, high, fn, ctx) {
+ const Q = [];
+ const compare = this._compare;
+ let node = this._root, cmp;
+
+ while (Q.length !== 0 || node) {
+ if (node) {
+ Q.push(node);
+ node = node.left;
+ } else {
+ node = Q.pop();
+ cmp = compare(node.key, high);
+ if (cmp > 0) {
+ break;
+ } else if (compare(node.key, low) >= 0) {
+ if (fn.call(ctx, node)) return this; // stop if smth is returned
+ }
+ node = node.right;
+ }
+ }
+ return this;
+ }
+
+ /**
+ * Returns all keys in order
+ * @return {Array<Key>}
+ */
+ keys () {
+ var current = this._root;
+ var s = [], r = [], done = false;
+
+ while (!done) {
+ if (current) {
+ s.push(current);
+ current = current.left;
+ } else {
+ if (s.length > 0) {
+ current = s.pop();
+ r.push(current.key);
+ current = current.right;
+ } else done = true;
+ }
+ }
+ return r;
+ }
+
+
+ /**
+ * Returns `data` fields of all nodes in order.
+ * @return {Array<Value>}
+ */
+ values () {
+ var current = this._root;
+ var s = [], r = [], done = false;
+
+ while (!done) {
+ if (current) {
+ s.push(current);
+ current = current.left;
+ } else {
+ if (s.length > 0) {
+ current = s.pop();
+ r.push(current.data);
+ current = current.right;
+ } else done = true;
+ }
+ }
+ return r;
+ }
+
+
+ /**
+ * Returns node at given index
+ * @param {number} index
+ * @return {?Node}
+ */
+ at (index) {
+ // removed after a consideration, more misleading than useful
+ // index = index % this.size;
+ // if (index < 0) index = this.size - index;
+
+ var current = this._root;
+ var s = [], done = false, i = 0;
+
+ while (!done) {
+ if (current) {
+ s.push(current);
+ current = current.left;
+ } else {
+ if (s.length > 0) {
+ current = s.pop();
+ if (i === index) return current;
+ i++;
+ current = current.right;
+ } else done = true;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Bulk-load items. Both array have to be same size
+ * @param {Array<Key>} keys
+ * @param {Array<Value>} [values]
+ * @param {Boolean} [presort=false] Pre-sort keys and values, using
+ * tree's comparator. Sorting is done
+ * in-place
+ * @return {AVLTree}
+ */
+ load(keys = [], values = [], presort = false) {
+ if (this._size !== 0) throw new Error('bulk-load: tree is not empty');
+ const size = keys.length;
+ if (presort) sort(keys, values, 0, size - 1, this._compare);
+ this._root = loadRecursive(null, keys, values, 0, size);
+ this._size = size;
+ return this;
+ }
+
+
+ min() {
+ var node = this.minNode(this._root);
+ if (node) return node.key;
+ else return null;
+ }
+
+
+ max() {
+ var node = this.maxNode(this._root);
+ if (node) return node.key;
+ else return null;
+ }
+
+ isEmpty() { return this._root === null; }
+ get size() { return this._size; }
+
+
+ /**
+ * Create a tree and load it with items
+ * @param {Array<Key>} keys
+ * @param {Array<Value>?} [values]
+
+ * @param {Function?} [comparator]
+ * @param {Boolean?} [presort=false] Pre-sort keys and values, using
+ * tree's comparator. Sorting is done
+ * in-place
+ * @param {Boolean?} [noDuplicates=false] Allow duplicates
+ * @return {SplayTree}
+ */
+ static createTree(keys, values, comparator, presort, noDuplicates) {
+ return new SplayTree(comparator, noDuplicates).load(keys, values, presort);
+ }
+}
+
+
+function loadRecursive (parent, keys, values, start, end) {
+ const size = end - start;
+ if (size > 0) {
+ const middle = start + Math.floor(size / 2);
+ const key = keys[middle];
+ const data = values[middle];
+ const node = { key, data, parent };
+ node.left = loadRecursive(node, keys, values, start, middle);
+ node.right = loadRecursive(node, keys, values, middle + 1, end);
+ return node;
+ }
+ return null;
+}
+
+
+function sort(keys, values, left, right, compare) {
+ if (left >= right) return;
+
+ const pivot = keys[(left + right) >> 1];
+ let i = left - 1;
+ let j = right + 1;
+
+ while (true) {
+ do i++; while (compare(keys[i], pivot) < 0);
+ do j--; while (compare(keys[j], pivot) > 0);
+ if (i >= j) break;
+
+ let tmp = keys[i];
+ keys[i] = keys[j];
+ keys[j] = tmp;
+
+ tmp = values[i];
+ values[i] = values[j];
+ values[j] = tmp;
+ }
+
+ sort(keys, values, left, j, compare);
+ sort(keys, values, j + 1, right, compare);
+}
+
+const NORMAL = 0;
+const NON_CONTRIBUTING = 1;
+const SAME_TRANSITION = 2;
+const DIFFERENT_TRANSITION = 3;
+
+const INTERSECTION = 0;
+const UNION = 1;
+const DIFFERENCE = 2;
+const XOR = 3;
+
+/**
+ * @param {SweepEvent} event
+ * @param {SweepEvent} prev
+ * @param {Operation} operation
+ */
+function computeFields (event, prev, operation) {
+ // compute inOut and otherInOut fields
+ if (prev === null) {
+ event.inOut = false;
+ event.otherInOut = true;
+
+ // previous line segment in sweepline belongs to the same polygon
+ } else {
+ if (event.isSubject === prev.isSubject) {
+ event.inOut = !prev.inOut;
+ event.otherInOut = prev.otherInOut;
+
+ // previous line segment in sweepline belongs to the clipping polygon
+ } else {
+ event.inOut = !prev.otherInOut;
+ event.otherInOut = prev.isVertical() ? !prev.inOut : prev.inOut;
+ }
+
+ // compute prevInResult field
+ if (prev) {
+ event.prevInResult = (!inResult(prev, operation) || prev.isVertical())
+ ? prev.prevInResult : prev;
+ }
+ }
+
+ // check if the line segment belongs to the Boolean operation
+ let isInResult = inResult(event, operation);
+ if (isInResult) {
+ event.resultTransition = determineResultTransition(event, operation);
+ } else {
+ event.resultTransition = 0;
+ }
+}
+
+
+/* eslint-disable indent */
+function inResult(event, operation) {
+ switch (event.type) {
+ case NORMAL:
+ switch (operation) {
+ case INTERSECTION:
+ return !event.otherInOut;
+ case UNION:
+ return event.otherInOut;
+ case DIFFERENCE:
+ // return (event.isSubject && !event.otherInOut) ||
+ // (!event.isSubject && event.otherInOut);
+ return (event.isSubject && event.otherInOut) ||
+ (!event.isSubject && !event.otherInOut);
+ case XOR:
+ return true;
+ }
+ break;
+ case SAME_TRANSITION:
+ return operation === INTERSECTION || operation === UNION;
+ case DIFFERENT_TRANSITION:
+ return operation === DIFFERENCE;
+ case NON_CONTRIBUTING:
+ return false;
+ }
+ return false;
+}
+/* eslint-enable indent */
+
+
+function determineResultTransition(event, operation) {
+ let thisIn = !event.inOut;
+ let thatIn = !event.otherInOut;
+
+ let isIn;
+ switch (operation) {
+ case INTERSECTION:
+ isIn = thisIn && thatIn; break;
+ case UNION:
+ isIn = thisIn || thatIn; break;
+ case XOR:
+ isIn = thisIn ^ thatIn; break;
+ case DIFFERENCE:
+ if (event.isSubject) {
+ isIn = thisIn && !thatIn;
+ } else {
+ isIn = thatIn && !thisIn;
+ }
+ break;
+ }
+ return isIn ? +1 : -1;
+}
+
+class SweepEvent {
+
+
+ /**
+ * Sweepline event
+ *
+ * @class {SweepEvent}
+ * @param {Array.<Number>} point
+ * @param {Boolean} left
+ * @param {SweepEvent=} otherEvent
+ * @param {Boolean} isSubject
+ * @param {Number} edgeType
+ */
+ constructor (point, left, otherEvent, isSubject, edgeType) {
+
+ /**
+ * Is left endpoint?
+ * @type {Boolean}
+ */
+ this.left = left;
+
+ /**
+ * @type {Array.<Number>}
+ */
+ this.point = point;
+
+ /**
+ * Other edge reference
+ * @type {SweepEvent}
+ */
+ this.otherEvent = otherEvent;
+
+ /**
+ * Belongs to source or clipping polygon
+ * @type {Boolean}
+ */
+ this.isSubject = isSubject;
+
+ /**
+ * Edge contribution type
+ * @type {Number}
+ */
+ this.type = edgeType || NORMAL;
+
+
+ /**
+ * In-out transition for the sweepline crossing polygon
+ * @type {Boolean}
+ */
+ this.inOut = false;
+
+
+ /**
+ * @type {Boolean}
+ */
+ this.otherInOut = false;
+
+ /**
+ * Previous event in result?
+ * @type {SweepEvent}
+ */
+ this.prevInResult = null;
+
+ /**
+ * Type of result transition (0 = not in result, +1 = out-in, -1, in-out)
+ * @type {Number}
+ */
+ this.resultTransition = 0;
+
+ // connection step
+
+ /**
+ * @type {Number}
+ */
+ this.otherPos = -1;
+
+ /**
+ * @type {Number}
+ */
+ this.outputContourId = -1;
+
+ this.isExteriorRing = true; // TODO: Looks unused, remove?
+ }
+
+
+ /**
+ * @param {Array.<Number>} p
+ * @return {Boolean}
+ */
+ isBelow (p) {
+ const p0 = this.point, p1 = this.otherEvent.point;
+ return this.left
+ ? (p0[0] - p[0]) * (p1[1] - p[1]) - (p1[0] - p[0]) * (p0[1] - p[1]) > 0
+ // signedArea(this.point, this.otherEvent.point, p) > 0 :
+ : (p1[0] - p[0]) * (p0[1] - p[1]) - (p0[0] - p[0]) * (p1[1] - p[1]) > 0;
+ //signedArea(this.otherEvent.point, this.point, p) > 0;
+ }
+
+
+ /**
+ * @param {Array.<Number>} p
+ * @return {Boolean}
+ */
+ isAbove (p) {
+ return !this.isBelow(p);
+ }
+
+
+ /**
+ * @return {Boolean}
+ */
+ isVertical () {
+ return this.point[0] === this.otherEvent.point[0];
+ }
+
+
+ /**
+ * Does event belong to result?
+ * @return {Boolean}
+ */
+ get inResult() {
+ return this.resultTransition !== 0;
+ }
+
+
+ clone () {
+ const copy = new SweepEvent(
+ this.point, this.left, this.otherEvent, this.isSubject, this.type);
+
+ copy.contourId = this.contourId;
+ copy.resultTransition = this.resultTransition;
+ copy.prevInResult = this.prevInResult;
+ copy.isExteriorRing = this.isExteriorRing;
+ copy.inOut = this.inOut;
+ copy.otherInOut = this.otherInOut;
+
+ return copy;
+ }
+}
+
+function equals(p1, p2) {
+ if (p1[0] === p2[0]) {
+ if (p1[1] === p2[1]) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+ return false;
+}
+
+// const EPSILON = 1e-9;
+// const abs = Math.abs;
+// TODO https://github.com/w8r/martinez/issues/6#issuecomment-262847164
+// Precision problem.
+//
+// module.exports = function equals(p1, p2) {
+// return abs(p1[0] - p2[0]) <= EPSILON && abs(p1[1] - p2[1]) <= EPSILON;
+// };
+
+const epsilon = 1.1102230246251565e-16;
+const splitter = 134217729;
+const resulterrbound = (3 + 8 * epsilon) * epsilon;
+
+// fast_expansion_sum_zeroelim routine from oritinal code
+function sum(elen, e, flen, f, h) {
+ let Q, Qnew, hh, bvirt;
+ let enow = e[0];
+ let fnow = f[0];
+ let eindex = 0;
+ let findex = 0;
+ if ((fnow > enow) === (fnow > -enow)) {
+ Q = enow;
+ enow = e[++eindex];
+ } else {
+ Q = fnow;
+ fnow = f[++findex];
+ }
+ let hindex = 0;
+ if (eindex < elen && findex < flen) {
+ if ((fnow > enow) === (fnow > -enow)) {
+ Qnew = enow + Q;
+ hh = Q - (Qnew - enow);
+ enow = e[++eindex];
+ } else {
+ Qnew = fnow + Q;
+ hh = Q - (Qnew - fnow);
+ fnow = f[++findex];
+ }
+ Q = Qnew;
+ if (hh !== 0) {
+ h[hindex++] = hh;
+ }
+ while (eindex < elen && findex < flen) {
+ if ((fnow > enow) === (fnow > -enow)) {
+ Qnew = Q + enow;
+ bvirt = Qnew - Q;
+ hh = Q - (Qnew - bvirt) + (enow - bvirt);
+ enow = e[++eindex];
+ } else {
+ Qnew = Q + fnow;
+ bvirt = Qnew - Q;
+ hh = Q - (Qnew - bvirt) + (fnow - bvirt);
+ fnow = f[++findex];
+ }
+ Q = Qnew;
+ if (hh !== 0) {
+ h[hindex++] = hh;
+ }
+ }
+ }
+ while (eindex < elen) {
+ Qnew = Q + enow;
+ bvirt = Qnew - Q;
+ hh = Q - (Qnew - bvirt) + (enow - bvirt);
+ enow = e[++eindex];
+ Q = Qnew;
+ if (hh !== 0) {
+ h[hindex++] = hh;
+ }
+ }
+ while (findex < flen) {
+ Qnew = Q + fnow;
+ bvirt = Qnew - Q;
+ hh = Q - (Qnew - bvirt) + (fnow - bvirt);
+ fnow = f[++findex];
+ Q = Qnew;
+ if (hh !== 0) {
+ h[hindex++] = hh;
+ }
+ }
+ if (Q !== 0 || hindex === 0) {
+ h[hindex++] = Q;
+ }
+ return hindex;
+}
+
+function estimate(elen, e) {
+ let Q = e[0];
+ for (let i = 1; i < elen; i++) Q += e[i];
+ return Q;
+}
+
+function vec(n) {
+ return new Float64Array(n);
+}
+
+const ccwerrboundA = (3 + 16 * epsilon) * epsilon;
+const ccwerrboundB = (2 + 12 * epsilon) * epsilon;
+const ccwerrboundC = (9 + 64 * epsilon) * epsilon * epsilon;
+
+const B = vec(4);
+const C1 = vec(8);
+const C2 = vec(12);
+const D = vec(16);
+const u = vec(4);
+
+function orient2dadapt(ax, ay, bx, by, cx, cy, detsum) {
+ let acxtail, acytail, bcxtail, bcytail;
+ let bvirt, c, ahi, alo, bhi, blo, _i, _j, _0, s1, s0, t1, t0, u3;
+
+ const acx = ax - cx;
+ const bcx = bx - cx;
+ const acy = ay - cy;
+ const bcy = by - cy;
+
+ s1 = acx * bcy;
+ c = splitter * acx;
+ ahi = c - (c - acx);
+ alo = acx - ahi;
+ c = splitter * bcy;
+ bhi = c - (c - bcy);
+ blo = bcy - bhi;
+ s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
+ t1 = acy * bcx;
+ c = splitter * acy;
+ ahi = c - (c - acy);
+ alo = acy - ahi;
+ c = splitter * bcx;
+ bhi = c - (c - bcx);
+ blo = bcx - bhi;
+ t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
+ _i = s0 - t0;
+ bvirt = s0 - _i;
+ B[0] = s0 - (_i + bvirt) + (bvirt - t0);
+ _j = s1 + _i;
+ bvirt = _j - s1;
+ _0 = s1 - (_j - bvirt) + (_i - bvirt);
+ _i = _0 - t1;
+ bvirt = _0 - _i;
+ B[1] = _0 - (_i + bvirt) + (bvirt - t1);
+ u3 = _j + _i;
+ bvirt = u3 - _j;
+ B[2] = _j - (u3 - bvirt) + (_i - bvirt);
+ B[3] = u3;
+
+ let det = estimate(4, B);
+ let errbound = ccwerrboundB * detsum;
+ if (det >= errbound || -det >= errbound) {
+ return det;
+ }
+
+ bvirt = ax - acx;
+ acxtail = ax - (acx + bvirt) + (bvirt - cx);
+ bvirt = bx - bcx;
+ bcxtail = bx - (bcx + bvirt) + (bvirt - cx);
+ bvirt = ay - acy;
+ acytail = ay - (acy + bvirt) + (bvirt - cy);
+ bvirt = by - bcy;
+ bcytail = by - (bcy + bvirt) + (bvirt - cy);
+
+ if (acxtail === 0 && acytail === 0 && bcxtail === 0 && bcytail === 0) {
+ return det;
+ }
+
+ errbound = ccwerrboundC * detsum + resulterrbound * Math.abs(det);
+ det += (acx * bcytail + bcy * acxtail) - (acy * bcxtail + bcx * acytail);
+ if (det >= errbound || -det >= errbound) return det;
+
+ s1 = acxtail * bcy;
+ c = splitter * acxtail;
+ ahi = c - (c - acxtail);
+ alo = acxtail - ahi;
+ c = splitter * bcy;
+ bhi = c - (c - bcy);
+ blo = bcy - bhi;
+ s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
+ t1 = acytail * bcx;
+ c = splitter * acytail;
+ ahi = c - (c - acytail);
+ alo = acytail - ahi;
+ c = splitter * bcx;
+ bhi = c - (c - bcx);
+ blo = bcx - bhi;
+ t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
+ _i = s0 - t0;
+ bvirt = s0 - _i;
+ u[0] = s0 - (_i + bvirt) + (bvirt - t0);
+ _j = s1 + _i;
+ bvirt = _j - s1;
+ _0 = s1 - (_j - bvirt) + (_i - bvirt);
+ _i = _0 - t1;
+ bvirt = _0 - _i;
+ u[1] = _0 - (_i + bvirt) + (bvirt - t1);
+ u3 = _j + _i;
+ bvirt = u3 - _j;
+ u[2] = _j - (u3 - bvirt) + (_i - bvirt);
+ u[3] = u3;
+ const C1len = sum(4, B, 4, u, C1);
+
+ s1 = acx * bcytail;
+ c = splitter * acx;
+ ahi = c - (c - acx);
+ alo = acx - ahi;
+ c = splitter * bcytail;
+ bhi = c - (c - bcytail);
+ blo = bcytail - bhi;
+ s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
+ t1 = acy * bcxtail;
+ c = splitter * acy;
+ ahi = c - (c - acy);
+ alo = acy - ahi;
+ c = splitter * bcxtail;
+ bhi = c - (c - bcxtail);
+ blo = bcxtail - bhi;
+ t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
+ _i = s0 - t0;
+ bvirt = s0 - _i;
+ u[0] = s0 - (_i + bvirt) + (bvirt - t0);
+ _j = s1 + _i;
+ bvirt = _j - s1;
+ _0 = s1 - (_j - bvirt) + (_i - bvirt);
+ _i = _0 - t1;
+ bvirt = _0 - _i;
+ u[1] = _0 - (_i + bvirt) + (bvirt - t1);
+ u3 = _j + _i;
+ bvirt = u3 - _j;
+ u[2] = _j - (u3 - bvirt) + (_i - bvirt);
+ u[3] = u3;
+ const C2len = sum(C1len, C1, 4, u, C2);
+
+ s1 = acxtail * bcytail;
+ c = splitter * acxtail;
+ ahi = c - (c - acxtail);
+ alo = acxtail - ahi;
+ c = splitter * bcytail;
+ bhi = c - (c - bcytail);
+ blo = bcytail - bhi;
+ s0 = alo * blo - (s1 - ahi * bhi - alo * bhi - ahi * blo);
+ t1 = acytail * bcxtail;
+ c = splitter * acytail;
+ ahi = c - (c - acytail);
+ alo = acytail - ahi;
+ c = splitter * bcxtail;
+ bhi = c - (c - bcxtail);
+ blo = bcxtail - bhi;
+ t0 = alo * blo - (t1 - ahi * bhi - alo * bhi - ahi * blo);
+ _i = s0 - t0;
+ bvirt = s0 - _i;
+ u[0] = s0 - (_i + bvirt) + (bvirt - t0);
+ _j = s1 + _i;
+ bvirt = _j - s1;
+ _0 = s1 - (_j - bvirt) + (_i - bvirt);
+ _i = _0 - t1;
+ bvirt = _0 - _i;
+ u[1] = _0 - (_i + bvirt) + (bvirt - t1);
+ u3 = _j + _i;
+ bvirt = u3 - _j;
+ u[2] = _j - (u3 - bvirt) + (_i - bvirt);
+ u[3] = u3;
+ const Dlen = sum(C2len, C2, 4, u, D);
+
+ return D[Dlen - 1];
+}
+
+function orient2d(ax, ay, bx, by, cx, cy) {
+ const detleft = (ay - cy) * (bx - cx);
+ const detright = (ax - cx) * (by - cy);
+ const det = detleft - detright;
+
+ if (detleft === 0 || detright === 0 || (detleft > 0) !== (detright > 0)) return det;
+
+ const detsum = Math.abs(detleft + detright);
+ if (Math.abs(det) >= ccwerrboundA * detsum) return det;
+
+ return -orient2dadapt(ax, ay, bx, by, cx, cy, detsum);
+}
+
+/**
+ * Signed area of the triangle (p0, p1, p2)
+ * @param {Array.<Number>} p0
+ * @param {Array.<Number>} p1
+ * @param {Array.<Number>} p2
+ * @return {Number}
+ */
+function signedArea(p0, p1, p2) {
+ const res = orient2d(p0[0], p0[1], p1[0], p1[1], p2[0], p2[1]);
+ if (res > 0) return -1;
+ if (res < 0) return 1;
+ return 0;
+}
+
+/**
+ * @param {SweepEvent} e1
+ * @param {SweepEvent} e2
+ * @return {Number}
+ */
+function compareEvents(e1, e2) {
+ const p1 = e1.point;
+ const p2 = e2.point;
+
+ // Different x-coordinate
+ if (p1[0] > p2[0]) return 1;
+ if (p1[0] < p2[0]) return -1;
+
+ // Different points, but same x-coordinate
+ // Event with lower y-coordinate is processed first
+ if (p1[1] !== p2[1]) return p1[1] > p2[1] ? 1 : -1;
+
+ return specialCases(e1, e2, p1);
+}
+
+
+/* eslint-disable no-unused-vars */
+function specialCases(e1, e2, p1, p2) {
+ // Same coordinates, but one is a left endpoint and the other is
+ // a right endpoint. The right endpoint is processed first
+ if (e1.left !== e2.left)
+ return e1.left ? 1 : -1;
+
+ // const p2 = e1.otherEvent.point, p3 = e2.otherEvent.point;
+ // const sa = (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1])
+ // Same coordinates, both events
+ // are left endpoints or right endpoints.
+ // not collinear
+ if (signedArea(p1, e1.otherEvent.point, e2.otherEvent.point) !== 0) {
+ // the event associate to the bottom segment is processed first
+ return (!e1.isBelow(e2.otherEvent.point)) ? 1 : -1;
+ }
+
+ return (!e1.isSubject && e2.isSubject) ? 1 : -1;
+}
+/* eslint-enable no-unused-vars */
+
+/**
+ * @param {SweepEvent} se
+ * @param {Array.<Number>} p
+ * @param {Queue} queue
+ * @return {Queue}
+ */
+function divideSegment(se, p, queue) {
+ const r = new SweepEvent(p, false, se, se.isSubject);
+ const l = new SweepEvent(p, true, se.otherEvent, se.isSubject);
+
+ /* eslint-disable no-console */
+ if (equals(se.point, se.otherEvent.point)) {
+ console.warn('what is that, a collapsed segment?', se);
+ }
+ /* eslint-enable no-console */
+
+ r.contourId = l.contourId = se.contourId;
+
+ // avoid a rounding error. The left event would be processed after the right event
+ if (compareEvents(l, se.otherEvent) > 0) {
+ se.otherEvent.left = true;
+ l.left = false;
+ }
+
+ // avoid a rounding error. The left event would be processed after the right event
+ // if (compareEvents(se, r) > 0) {}
+
+ se.otherEvent.otherEvent = l;
+ se.otherEvent = r;
+
+ queue.push(l);
+ queue.push(r);
+
+ return queue;
+}
+
+//const EPS = 1e-9;
+
+/**
+ * Finds the magnitude of the cross product of two vectors (if we pretend
+ * they're in three dimensions)
+ *
+ * @param {Object} a First vector
+ * @param {Object} b Second vector
+ * @private
+ * @returns {Number} The magnitude of the cross product
+ */
+function crossProduct(a, b) {
+ return (a[0] * b[1]) - (a[1] * b[0]);
+}
+
+/**
+ * Finds the dot product of two vectors.
+ *
+ * @param {Object} a First vector
+ * @param {Object} b Second vector
+ * @private
+ * @returns {Number} The dot product
+ */
+function dotProduct(a, b) {
+ return (a[0] * b[0]) + (a[1] * b[1]);
+}
+
+/**
+ * Finds the intersection (if any) between two line segments a and b, given the
+ * line segments' end points a1, a2 and b1, b2.
+ *
+ * This algorithm is based on Schneider and Eberly.
+ * http://www.cimec.org.ar/~ncalvo/Schneider_Eberly.pdf
+ * Page 244.
+ *
+ * @param {Array.<Number>} a1 point of first line
+ * @param {Array.<Number>} a2 point of first line
+ * @param {Array.<Number>} b1 point of second line
+ * @param {Array.<Number>} b2 point of second line
+ * @param {Boolean=} noEndpointTouch whether to skip single touchpoints
+ * (meaning connected segments) as
+ * intersections
+ * @returns {Array.<Array.<Number>>|Null} If the lines intersect, the point of
+ * intersection. If they overlap, the two end points of the overlapping segment.
+ * Otherwise, null.
+ */
+function intersection$1 (a1, a2, b1, b2, noEndpointTouch) {
+ // The algorithm expects our lines in the form P + sd, where P is a point,
+ // s is on the interval [0, 1], and d is a vector.
+ // We are passed two points. P can be the first point of each pair. The
+ // vector, then, could be thought of as the distance (in x and y components)
+ // from the first point to the second point.
+ // So first, let's make our vectors:
+ const va = [a2[0] - a1[0], a2[1] - a1[1]];
+ const vb = [b2[0] - b1[0], b2[1] - b1[1]];
+ // We also define a function to convert back to regular point form:
+
+ /* eslint-disable arrow-body-style */
+
+ function toPoint(p, s, d) {
+ return [
+ p[0] + s * d[0],
+ p[1] + s * d[1]
+ ];
+ }
+
+ /* eslint-enable arrow-body-style */
+
+ // The rest is pretty much a straight port of the algorithm.
+ const e = [b1[0] - a1[0], b1[1] - a1[1]];
+ let kross = crossProduct(va, vb);
+ let sqrKross = kross * kross;
+ const sqrLenA = dotProduct(va, va);
+ //const sqrLenB = dotProduct(vb, vb);
+
+ // Check for line intersection. This works because of the properties of the
+ // cross product -- specifically, two vectors are parallel if and only if the
+ // cross product is the 0 vector. The full calculation involves relative error
+ // to account for possible very small line segments. See Schneider & Eberly
+ // for details.
+ if (sqrKross > 0/* EPS * sqrLenB * sqLenA */) {
+ // If they're not parallel, then (because these are line segments) they
+ // still might not actually intersect. This code checks that the
+ // intersection point of the lines is actually on both line segments.
+ const s = crossProduct(e, vb) / kross;
+ if (s < 0 || s > 1) {
+ // not on line segment a
+ return null;
+ }
+ const t = crossProduct(e, va) / kross;
+ if (t < 0 || t > 1) {
+ // not on line segment b
+ return null;
+ }
+ if (s === 0 || s === 1) {
+ // on an endpoint of line segment a
+ return noEndpointTouch ? null : [toPoint(a1, s, va)];
+ }
+ if (t === 0 || t === 1) {
+ // on an endpoint of line segment b
+ return noEndpointTouch ? null : [toPoint(b1, t, vb)];
+ }
+ return [toPoint(a1, s, va)];
+ }
+
+ // If we've reached this point, then the lines are either parallel or the
+ // same, but the segments could overlap partially or fully, or not at all.
+ // So we need to find the overlap, if any. To do that, we can use e, which is
+ // the (vector) difference between the two initial points. If this is parallel
+ // with the line itself, then the two lines are the same line, and there will
+ // be overlap.
+ //const sqrLenE = dotProduct(e, e);
+ kross = crossProduct(e, va);
+ sqrKross = kross * kross;
+
+ if (sqrKross > 0 /* EPS * sqLenB * sqLenE */) {
+ // Lines are just parallel, not the same. No overlap.
+ return null;
+ }
+
+ const sa = dotProduct(va, e) / sqrLenA;
+ const sb = sa + dotProduct(va, vb) / sqrLenA;
+ const smin = Math.min(sa, sb);
+ const smax = Math.max(sa, sb);
+
+ // this is, essentially, the FindIntersection acting on floats from
+ // Schneider & Eberly, just inlined into this function.
+ if (smin <= 1 && smax >= 0) {
+
+ // overlap on an end point
+ if (smin === 1) {
+ return noEndpointTouch ? null : [toPoint(a1, smin > 0 ? smin : 0, va)];
+ }
+
+ if (smax === 0) {
+ return noEndpointTouch ? null : [toPoint(a1, smax < 1 ? smax : 1, va)];
+ }
+
+ if (noEndpointTouch && smin === 0 && smax === 1) return null;
+
+ // There's overlap on a segment -- two points of intersection. Return both.
+ return [
+ toPoint(a1, smin > 0 ? smin : 0, va),
+ toPoint(a1, smax < 1 ? smax : 1, va)
+ ];
+ }
+
+ return null;
+}
+
+/**
+ * @param {SweepEvent} se1
+ * @param {SweepEvent} se2
+ * @param {Queue} queue
+ * @return {Number}
+ */
+function possibleIntersection (se1, se2, queue) {
+ // that disallows self-intersecting polygons,
+ // did cost us half a day, so I'll leave it
+ // out of respect
+ // if (se1.isSubject === se2.isSubject) return;
+ const inter = intersection$1(
+ se1.point, se1.otherEvent.point,
+ se2.point, se2.otherEvent.point
+ );
+
+ const nintersections = inter ? inter.length : 0;
+ if (nintersections === 0) return 0; // no intersection
+
+ // the line segments intersect at an endpoint of both line segments
+ if ((nintersections === 1) &&
+ (equals(se1.point, se2.point) ||
+ equals(se1.otherEvent.point, se2.otherEvent.point))) {
+ return 0;
+ }
+
+ if (nintersections === 2 && se1.isSubject === se2.isSubject) {
+ // if(se1.contourId === se2.contourId){
+ // console.warn('Edges of the same polygon overlap',
+ // se1.point, se1.otherEvent.point, se2.point, se2.otherEvent.point);
+ // }
+ //throw new Error('Edges of the same polygon overlap');
+ return 0;
+ }
+
+ // The line segments associated to se1 and se2 intersect
+ if (nintersections === 1) {
+
+ // if the intersection point is not an endpoint of se1
+ if (!equals(se1.point, inter[0]) && !equals(se1.otherEvent.point, inter[0])) {
+ divideSegment(se1, inter[0], queue);
+ }
+
+ // if the intersection point is not an endpoint of se2
+ if (!equals(se2.point, inter[0]) && !equals(se2.otherEvent.point, inter[0])) {
+ divideSegment(se2, inter[0], queue);
+ }
+ return 1;
+ }
+
+ // The line segments associated to se1 and se2 overlap
+ const events = [];
+ let leftCoincide = false;
+ let rightCoincide = false;
+
+ if (equals(se1.point, se2.point)) {
+ leftCoincide = true; // linked
+ } else if (compareEvents(se1, se2) === 1) {
+ events.push(se2, se1);
+ } else {
+ events.push(se1, se2);
+ }
+
+ if (equals(se1.otherEvent.point, se2.otherEvent.point)) {
+ rightCoincide = true;
+ } else if (compareEvents(se1.otherEvent, se2.otherEvent) === 1) {
+ events.push(se2.otherEvent, se1.otherEvent);
+ } else {
+ events.push(se1.otherEvent, se2.otherEvent);
+ }
+
+ if ((leftCoincide && rightCoincide) || leftCoincide) {
+ // both line segments are equal or share the left endpoint
+ se2.type = NON_CONTRIBUTING;
+ se1.type = (se2.inOut === se1.inOut)
+ ? SAME_TRANSITION : DIFFERENT_TRANSITION;
+
+ if (leftCoincide && !rightCoincide) {
+ // honestly no idea, but changing events selection from [2, 1]
+ // to [0, 1] fixes the overlapping self-intersecting polygons issue
+ divideSegment(events[1].otherEvent, events[0].point, queue);
+ }
+ return 2;
+ }
+
+ // the line segments share the right endpoint
+ if (rightCoincide) {
+ divideSegment(events[0], events[1].point, queue);
+ return 3;
+ }
+
+ // no line segment includes totally the other one
+ if (events[0] !== events[3].otherEvent) {
+ divideSegment(events[0], events[1].point, queue);
+ divideSegment(events[1], events[2].point, queue);
+ return 3;
+ }
+
+ // one line segment includes the other one
+ divideSegment(events[0], events[1].point, queue);
+ divideSegment(events[3].otherEvent, events[2].point, queue);
+
+ return 3;
+}
+
+/**
+ * @param {SweepEvent} le1
+ * @param {SweepEvent} le2
+ * @return {Number}
+ */
+function compareSegments(le1, le2) {
+ if (le1 === le2) return 0;
+
+ // Segments are not collinear
+ if (signedArea(le1.point, le1.otherEvent.point, le2.point) !== 0 ||
+ signedArea(le1.point, le1.otherEvent.point, le2.otherEvent.point) !== 0) {
+
+ // If they share their left endpoint use the right endpoint to sort
+ if (equals(le1.point, le2.point)) return le1.isBelow(le2.otherEvent.point) ? -1 : 1;
+
+ // Different left endpoint: use the left endpoint to sort
+ if (le1.point[0] === le2.point[0]) return le1.point[1] < le2.point[1] ? -1 : 1;
+
+ // has the line segment associated to e1 been inserted
+ // into S after the line segment associated to e2 ?
+ if (compareEvents(le1, le2) === 1) return le2.isAbove(le1.point) ? -1 : 1;
+
+ // The line segment associated to e2 has been inserted
+ // into S after the line segment associated to e1
+ return le1.isBelow(le2.point) ? -1 : 1;
+ }
+
+ if (le1.isSubject === le2.isSubject) { // same polygon
+ let p1 = le1.point, p2 = le2.point;
+ if (p1[0] === p2[0] && p1[1] === p2[1]/*equals(le1.point, le2.point)*/) {
+ p1 = le1.otherEvent.point; p2 = le2.otherEvent.point;
+ if (p1[0] === p2[0] && p1[1] === p2[1]) return 0;
+ else return le1.contourId > le2.contourId ? 1 : -1;
+ }
+ } else { // Segments are collinear, but belong to separate polygons
+ return le1.isSubject ? -1 : 1;
+ }
+
+ return compareEvents(le1, le2) === 1 ? 1 : -1;
+}
+
+function subdivide(eventQueue, subject, clipping, sbbox, cbbox, operation) {
+ const sweepLine = new SplayTree(compareSegments);
+ const sortedEvents = [];
+
+ const rightbound = Math.min(sbbox[2], cbbox[2]);
+
+ let prev, next, begin;
+
+ while (eventQueue.length !== 0) {
+ let event = eventQueue.pop();
+ sortedEvents.push(event);
+
+ // optimization by bboxes for intersection and difference goes here
+ if ((operation === INTERSECTION && event.point[0] > rightbound) ||
+ (operation === DIFFERENCE && event.point[0] > sbbox[2])) {
+ break;
+ }
+
+ if (event.left) {
+ next = prev = sweepLine.insert(event);
+ begin = sweepLine.minNode();
+
+ if (prev !== begin) prev = sweepLine.prev(prev);
+ else prev = null;
+
+ next = sweepLine.next(next);
+
+ const prevEvent = prev ? prev.key : null;
+ let prevprevEvent;
+ computeFields(event, prevEvent, operation);
+ if (next) {
+ if (possibleIntersection(event, next.key, eventQueue) === 2) {
+ computeFields(event, prevEvent, operation);
+ computeFields(event, next.key, operation);
+ }
+ }
+
+ if (prev) {
+ if (possibleIntersection(prev.key, event, eventQueue) === 2) {
+ let prevprev = prev;
+ if (prevprev !== begin) prevprev = sweepLine.prev(prevprev);
+ else prevprev = null;
+
+ prevprevEvent = prevprev ? prevprev.key : null;
+ computeFields(prevEvent, prevprevEvent, operation);
+ computeFields(event, prevEvent, operation);
+ }
+ }
+ } else {
+ event = event.otherEvent;
+ next = prev = sweepLine.find(event);
+
+ if (prev && next) {
+
+ if (prev !== begin) prev = sweepLine.prev(prev);
+ else prev = null;
+
+ next = sweepLine.next(next);
+ sweepLine.remove(event);
+
+ if (next && prev) {
+ possibleIntersection(prev.key, next.key, eventQueue);
+ }
+ }
+ }
+ }
+ return sortedEvents;
+}
+
+class Contour {
+
+ /**
+ * Contour
+ *
+ * @class {Contour}
+ */
+ constructor() {
+ this.points = [];
+ this.holeIds = [];
+ this.holeOf = null;
+ this.depth = null;
+ }
+
+ isExterior() {
+ return this.holeOf == null;
+ }
+
+}
+
+/**
+ * @param {Array.<SweepEvent>} sortedEvents
+ * @return {Array.<SweepEvent>}
+ */
+function orderEvents(sortedEvents) {
+ let event, i, len, tmp;
+ const resultEvents = [];
+ for (i = 0, len = sortedEvents.length; i < len; i++) {
+ event = sortedEvents[i];
+ if ((event.left && event.inResult) ||
+ (!event.left && event.otherEvent.inResult)) {
+ resultEvents.push(event);
+ }
+ }
+ // Due to overlapping edges the resultEvents array can be not wholly sorted
+ let sorted = false;
+ while (!sorted) {
+ sorted = true;
+ for (i = 0, len = resultEvents.length; i < len; i++) {
+ if ((i + 1) < len &&
+ compareEvents(resultEvents[i], resultEvents[i + 1]) === 1) {
+ tmp = resultEvents[i];
+ resultEvents[i] = resultEvents[i + 1];
+ resultEvents[i + 1] = tmp;
+ sorted = false;
+ }
+ }
+ }
+
+
+ for (i = 0, len = resultEvents.length; i < len; i++) {
+ event = resultEvents[i];
+ event.otherPos = i;
+ }
+
+ // imagine, the right event is found in the beginning of the queue,
+ // when his left counterpart is not marked yet
+ for (i = 0, len = resultEvents.length; i < len; i++) {
+ event = resultEvents[i];
+ if (!event.left) {
+ tmp = event.otherPos;
+ event.otherPos = event.otherEvent.otherPos;
+ event.otherEvent.otherPos = tmp;
+ }
+ }
+
+ return resultEvents;
+}
+
+
+/**
+ * @param {Number} pos
+ * @param {Array.<SweepEvent>} resultEvents
+ * @param {Object>} processed
+ * @return {Number}
+ */
+function nextPos(pos, resultEvents, processed, origPos) {
+ let newPos = pos + 1,
+ p = resultEvents[pos].point,
+ p1;
+ const length = resultEvents.length;
+
+ if (newPos < length)
+ p1 = resultEvents[newPos].point;
+
+ while (newPos < length && p1[0] === p[0] && p1[1] === p[1]) {
+ if (!processed[newPos]) {
+ return newPos;
+ } else {
+ newPos++;
+ }
+ p1 = resultEvents[newPos].point;
+ }
+
+ newPos = pos - 1;
+
+ while (processed[newPos] && newPos > origPos) {
+ newPos--;
+ }
+
+ return newPos;
+}
+
+
+function initializeContourFromContext(event, contours, contourId) {
+ const contour = new Contour();
+ if (event.prevInResult != null) {
+ const prevInResult = event.prevInResult;
+ // Note that it is valid to query the "previous in result" for its output contour id,
+ // because we must have already processed it (i.e., assigned an output contour id)
+ // in an earlier iteration, otherwise it wouldn't be possible that it is "previous in
+ // result".
+ const lowerContourId = prevInResult.outputContourId;
+ const lowerResultTransition = prevInResult.resultTransition;
+ if (lowerResultTransition > 0) {
+ // We are inside. Now we have to check if the thing below us is another hole or
+ // an exterior contour.
+ const lowerContour = contours[lowerContourId];
+ if (lowerContour.holeOf != null) {
+ // The lower contour is a hole => Connect the new contour as a hole to its parent,
+ // and use same depth.
+ const parentContourId = lowerContour.holeOf;
+ contours[parentContourId].holeIds.push(contourId);
+ contour.holeOf = parentContourId;
+ contour.depth = contours[lowerContourId].depth;
+ } else {
+ // The lower contour is an exterior contour => Connect the new contour as a hole,
+ // and increment depth.
+ contours[lowerContourId].holeIds.push(contourId);
+ contour.holeOf = lowerContourId;
+ contour.depth = contours[lowerContourId].depth + 1;
+ }
+ } else {
+ // We are outside => this contour is an exterior contour of same depth.
+ contour.holeOf = null;
+ contour.depth = contours[lowerContourId].depth;
+ }
+ } else {
+ // There is no lower/previous contour => this contour is an exterior contour of depth 0.
+ contour.holeOf = null;
+ contour.depth = 0;
+ }
+ return contour;
+}
+
+/**
+ * @param {Array.<SweepEvent>} sortedEvents
+ * @return {Array.<*>} polygons
+ */
+function connectEdges(sortedEvents) {
+ let i, len;
+ const resultEvents = orderEvents(sortedEvents);
+
+ // "false"-filled array
+ const processed = {};
+ const contours = [];
+
+ for (i = 0, len = resultEvents.length; i < len; i++) {
+
+ if (processed[i]) {
+ continue;
+ }
+
+ const contourId = contours.length;
+ const contour = initializeContourFromContext(resultEvents[i], contours, contourId);
+
+ // Helper function that combines marking an event as processed with assigning its output contour ID
+ const markAsProcessed = (pos) => {
+ processed[pos] = true;
+ resultEvents[pos].outputContourId = contourId;
+ };
+
+ let pos = i;
+ let origPos = i;
+
+ const initial = resultEvents[i].point;
+ contour.points.push(initial);
+
+ /* eslint no-constant-condition: "off" */
+ while (true) {
+ markAsProcessed(pos);
+
+ pos = resultEvents[pos].otherPos;
+
+ markAsProcessed(pos);
+ contour.points.push(resultEvents[pos].point);
+
+ pos = nextPos(pos, resultEvents, processed, origPos);
+
+ if (pos == origPos) {
+ break;
+ }
+ }
+
+ contours.push(contour);
+ }
+
+ return contours;
+}
+
+var tinyqueue = TinyQueue;
+var _default = TinyQueue;
+
+function TinyQueue(data, compare) {
+ if (!(this instanceof TinyQueue)) return new TinyQueue(data, compare);
+
+ this.data = data || [];
+ this.length = this.data.length;
+ this.compare = compare || defaultCompare;
+
+ if (this.length > 0) {
+ for (var i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
+ }
+}
+
+function defaultCompare(a, b) {
+ return a < b ? -1 : a > b ? 1 : 0;
+}
+
+TinyQueue.prototype = {
+
+ push: function (item) {
+ this.data.push(item);
+ this.length++;
+ this._up(this.length - 1);
+ },
+
+ pop: function () {
+ if (this.length === 0) return undefined;
+
+ var top = this.data[0];
+ this.length--;
+
+ if (this.length > 0) {
+ this.data[0] = this.data[this.length];
+ this._down(0);
+ }
+ this.data.pop();
+
+ return top;
+ },
+
+ peek: function () {
+ return this.data[0];
+ },
+
+ _up: function (pos) {
+ var data = this.data;
+ var compare = this.compare;
+ var item = data[pos];
+
+ while (pos > 0) {
+ var parent = (pos - 1) >> 1;
+ var current = data[parent];
+ if (compare(item, current) >= 0) break;
+ data[pos] = current;
+ pos = parent;
+ }
+
+ data[pos] = item;
+ },
+
+ _down: function (pos) {
+ var data = this.data;
+ var compare = this.compare;
+ var halfLength = this.length >> 1;
+ var item = data[pos];
+
+ while (pos < halfLength) {
+ var left = (pos << 1) + 1;
+ var right = left + 1;
+ var best = data[left];
+
+ if (right < this.length && compare(data[right], best) < 0) {
+ left = right;
+ best = data[right];
+ }
+ if (compare(best, item) >= 0) break;
+
+ data[pos] = best;
+ pos = left;
+ }
+
+ data[pos] = item;
+ }
+};
+tinyqueue.default = _default;
+
+const max = Math.max;
+const min = Math.min;
+
+let contourId = 0;
+
+
+function processPolygon(contourOrHole, isSubject, depth, Q, bbox, isExteriorRing) {
+ let i, len, s1, s2, e1, e2;
+ for (i = 0, len = contourOrHole.length - 1; i < len; i++) {
+ s1 = contourOrHole[i];
+ s2 = contourOrHole[i + 1];
+ e1 = new SweepEvent(s1, false, undefined, isSubject);
+ e2 = new SweepEvent(s2, false, e1, isSubject);
+ e1.otherEvent = e2;
+
+ if (s1[0] === s2[0] && s1[1] === s2[1]) {
+ continue; // skip collapsed edges, or it breaks
+ }
+
+ e1.contourId = e2.contourId = depth;
+ if (!isExteriorRing) {
+ e1.isExteriorRing = false;
+ e2.isExteriorRing = false;
+ }
+ if (compareEvents(e1, e2) > 0) {
+ e2.left = true;
+ } else {
+ e1.left = true;
+ }
+
+ const x = s1[0], y = s1[1];
+ bbox[0] = min(bbox[0], x);
+ bbox[1] = min(bbox[1], y);
+ bbox[2] = max(bbox[2], x);
+ bbox[3] = max(bbox[3], y);
+
+ // Pushing it so the queue is sorted from left to right,
+ // with object on the left having the highest priority.
+ Q.push(e1);
+ Q.push(e2);
+ }
+}
+
+
+function fillQueue(subject, clipping, sbbox, cbbox, operation) {
+ const eventQueue = new tinyqueue(null, compareEvents);
+ let polygonSet, isExteriorRing, i, ii, j, jj; //, k, kk;
+
+ for (i = 0, ii = subject.length; i < ii; i++) {
+ polygonSet = subject[i];
+ for (j = 0, jj = polygonSet.length; j < jj; j++) {
+ isExteriorRing = j === 0;
+ if (isExteriorRing) contourId++;
+ processPolygon(polygonSet[j], true, contourId, eventQueue, sbbox, isExteriorRing);
+ }
+ }
+
+ for (i = 0, ii = clipping.length; i < ii; i++) {
+ polygonSet = clipping[i];
+ for (j = 0, jj = polygonSet.length; j < jj; j++) {
+ isExteriorRing = j === 0;
+ if (operation === DIFFERENCE) isExteriorRing = false;
+ if (isExteriorRing) contourId++;
+ processPolygon(polygonSet[j], false, contourId, eventQueue, cbbox, isExteriorRing);
+ }
+ }
+
+ return eventQueue;
+}
+
+const EMPTY = [];
+
+
+function trivialOperation(subject, clipping, operation) {
+ let result = null;
+ if (subject.length * clipping.length === 0) {
+ if (operation === INTERSECTION) {
+ result = EMPTY;
+ } else if (operation === DIFFERENCE) {
+ result = subject;
+ } else if (operation === UNION ||
+ operation === XOR) {
+ result = (subject.length === 0) ? clipping : subject;
+ }
+ }
+ return result;
+}
+
+
+function compareBBoxes(subject, clipping, sbbox, cbbox, operation) {
+ let result = null;
+ if (sbbox[0] > cbbox[2] ||
+ cbbox[0] > sbbox[2] ||
+ sbbox[1] > cbbox[3] ||
+ cbbox[1] > sbbox[3]) {
+ if (operation === INTERSECTION) {
+ result = EMPTY;
+ } else if (operation === DIFFERENCE) {
+ result = subject;
+ } else if (operation === UNION ||
+ operation === XOR) {
+ result = subject.concat(clipping);
+ }
+ }
+ return result;
+}
+
+
+function boolean(subject, clipping, operation) {
+ if (typeof subject[0][0][0] === 'number') {
+ subject = [subject];
+ }
+ if (typeof clipping[0][0][0] === 'number') {
+ clipping = [clipping];
+ }
+ let trivial = trivialOperation(subject, clipping, operation);
+ if (trivial) {
+ return trivial === EMPTY ? null : trivial;
+ }
+ const sbbox = [Infinity, Infinity, -Infinity, -Infinity];
+ const cbbox = [Infinity, Infinity, -Infinity, -Infinity];
+
+ // console.time('fill queue');
+ const eventQueue = fillQueue(subject, clipping, sbbox, cbbox, operation);
+ //console.timeEnd('fill queue');
+
+ trivial = compareBBoxes(subject, clipping, sbbox, cbbox, operation);
+ if (trivial) {
+ return trivial === EMPTY ? null : trivial;
+ }
+ // console.time('subdivide edges');
+ const sortedEvents = subdivide(eventQueue, subject, clipping, sbbox, cbbox, operation);
+ //console.timeEnd('subdivide edges');
+
+ // console.time('connect vertices');
+ const contours = connectEdges(sortedEvents);
+ //console.timeEnd('connect vertices');
+
+ // Convert contours to polygons
+ const polygons = [];
+ for (let i = 0; i < contours.length; i++) {
+ let contour = contours[i];
+ if (contour.isExterior()) {
+ // The exterior ring goes first
+ let rings = [contour.points];
+ // Followed by holes if any
+ for (let j = 0; j < contour.holeIds.length; j++) {
+ let holeId = contour.holeIds[j];
+ rings.push(contours[holeId].points);
+ }
+ polygons.push(rings);
+ }
+ }
+
+ return polygons;
+}
+
+function intersection (subject, clipping) {
+ return boolean(subject, clipping, INTERSECTION);
+}
+
+/**
+ * @class VertexGeometry
+ * @abstract
+ * @classdesc Represents a vertex geometry.
+ */
+class VertexGeometry extends Geometry {
+ /**
+ * Create a vertex geometry.
+ *
+ * @constructor
+ * @ignore
+ */
+ constructor() {
+ super();
+ this._subsampleThreshold = 0.005;
+ }
+ /**
+ * Finds the polygon pole of inaccessibility, the most distant internal
+ * point from the polygon outline.
+ *
+ * @param {Array<Array<number>>} points2d - 2d points of outline to triangulate.
+ * @returns {Array<number>} Point of inaccessibility.
+ * @ignore
+ */
+ _getPoleOfInaccessibility2d(points2d) {
+ let pole2d = polylabel_1([points2d], 3e-2);
+ return pole2d;
+ }
+ _project(points2d, transform) {
+ const camera = this._createCamera(transform.upVector().toArray(), transform.unprojectSfM([0, 0], 0), transform.unprojectSfM([0, 0], 10));
+ return this._deunproject(points2d, transform, camera);
+ }
+ _subsample(points2d, threshold = this._subsampleThreshold) {
+ const subsampled = [];
+ const length = points2d.length;
+ for (let index = 0; index < length; index++) {
+ const p1 = points2d[index];
+ const p2 = points2d[(index + 1) % length];
+ subsampled.push(p1);
+ const dist = Math.sqrt(Math.pow((p2[0] - p1[0]), 2) + Math.pow((p2[1] - p1[1]), 2));
+ const subsamples = Math.floor(dist / threshold);
+ const coeff = 1 / (subsamples + 1);
+ for (let i = 1; i <= subsamples; i++) {
+ const alpha = i * coeff;
+ const subsample = [
+ (1 - alpha) * p1[0] + alpha * p2[0],
+ (1 - alpha) * p1[1] + alpha * p2[1],
+ ];
+ subsampled.push(subsample);
+ }
+ }
+ return subsampled;
+ }
+ /**
+ * Triangulates a 2d polygon and returns the triangle
+ * representation as a flattened array of 3d points.
+ *
+ * @param {Array<Array<number>>} points2d - 2d points of outline to triangulate.
+ * @param {Array<Array<number>>} points3d - 3d points of outline corresponding to the 2d points.
+ * @param {Array<Array<Array<number>>>} [holes2d] - 2d points of holes to triangulate.
+ * @param {Array<Array<Array<number>>>} [holes3d] - 3d points of holes corresponding to the 2d points.
+ * @returns {Array<number>} Flattened array of 3d points ordered based on the triangles.
+ * @ignore
+ */
+ _triangulate(points2d, points3d, holes2d, holes3d) {
+ let data = [points2d.slice(0, -1)];
+ for (let hole2d of holes2d != null ? holes2d : []) {
+ data.push(hole2d.slice(0, -1));
+ }
+ let points = points3d.slice(0, -1);
+ for (let hole3d of holes3d != null ? holes3d : []) {
+ points = points.concat(hole3d.slice(0, -1));
+ }
+ let flattened = earcut_1.flatten(data);
+ let indices = earcut_1(flattened.vertices, flattened.holes, flattened.dimensions);
+ let triangles = [];
+ for (let i = 0; i < indices.length; ++i) {
+ let point = points[indices[i]];
+ triangles.push(point[0]);
+ triangles.push(point[1]);
+ triangles.push(point[2]);
+ }
+ return triangles;
+ }
+ _triangulateSpherical(points2d, holes2d, transform) {
+ const triangles = [];
+ const epsilon = 1e-9;
+ const subareasX = 3;
+ const subareasY = 3;
+ for (let x = 0; x < subareasX; x++) {
+ for (let y = 0; y < subareasY; y++) {
+ const epsilonX0 = x === 0 ? -epsilon : epsilon;
+ const epsilonY0 = y === 0 ? -epsilon : epsilon;
+ const x0 = x / subareasX + epsilonX0;
+ const y0 = y / subareasY + epsilonY0;
+ const x1 = (x + 1) / subareasX + epsilon;
+ const y1 = (y + 1) / subareasY + epsilon;
+ const bbox2d = [
+ [x0, y0],
+ [x0, y1],
+ [x1, y1],
+ [x1, y0],
+ [x0, y0],
+ ];
+ const lookat2d = [
+ (2 * x + 1) / (2 * subareasX),
+ (2 * y + 1) / (2 * subareasY),
+ ];
+ triangles.push(...this._triangulateSubarea(points2d, holes2d, bbox2d, lookat2d, transform));
+ }
+ }
+ return triangles;
+ }
+ _unproject(points2d, transform, distance = 200) {
+ return points2d
+ .map((point) => {
+ return transform.unprojectBasic(point, distance);
+ });
+ }
+ _createCamera(upVector, position, lookAt) {
+ const camera = new Camera$1();
+ camera.up.copy(new Vector3().fromArray(upVector));
+ camera.position.copy(new Vector3().fromArray(position));
+ camera.lookAt(new Vector3().fromArray(lookAt));
+ camera.updateMatrix();
+ camera.updateMatrixWorld(true);
+ return camera;
+ }
+ _deunproject(points2d, transform, camera) {
+ return points2d
+ .map((point2d) => {
+ const pointWorld = transform.unprojectBasic(point2d, 10000);
+ const pointCamera = new Vector3(pointWorld[0], pointWorld[1], pointWorld[2])
+ .applyMatrix4(camera.matrixWorldInverse);
+ return [pointCamera.x / pointCamera.z, pointCamera.y / pointCamera.z];
+ });
+ }
+ _triangulateSubarea(points2d, holes2d, bbox2d, lookat2d, transform) {
+ const intersections = intersection([points2d, ...holes2d], [bbox2d]);
+ if (!intersections) {
+ return [];
+ }
+ const triangles = [];
+ const threshold = this._subsampleThreshold;
+ const camera = this._createCamera(transform.upVector().toArray(), transform.unprojectSfM([0, 0], 0), transform.unprojectBasic(lookat2d, 10));
+ for (const intersection of intersections) {
+ const subsampledPolygon2d = this._subsample(intersection[0], threshold);
+ const polygon2d = this._deunproject(subsampledPolygon2d, transform, camera);
+ const polygon3d = this._unproject(subsampledPolygon2d, transform);
+ const polygonHoles2d = [];
+ const polygonHoles3d = [];
+ for (let i = 1; i < intersection.length; i++) {
+ let subsampledHole2d = this._subsample(intersection[i], threshold);
+ const hole2d = this._deunproject(subsampledHole2d, transform, camera);
+ const hole3d = this._unproject(subsampledHole2d, transform);
+ polygonHoles2d.push(hole2d);
+ polygonHoles3d.push(hole3d);
+ }
+ triangles.push(...this._triangulate(polygon2d, polygon3d, polygonHoles2d, polygonHoles3d));
+ }
+ return triangles;
+ }
+}
+
+/**
+ * @class RectGeometry
+ *
+ * @classdesc Represents a rectangle geometry in the 2D basic image coordinate system.
+ *
+ * @example
+ * ```js
+ * var basicRect = [0.5, 0.3, 0.7, 0.4];
+ * var rectGeometry = new RectGeometry(basicRect);
+ * ```
+ */
+class RectGeometry extends VertexGeometry {
+ /**
+ * Create a rectangle geometry.
+ *
+ * @constructor
+ * @param {Array<number>} rect - An array representing the top-left and bottom-right
+ * corners of the rectangle in basic coordinates. Ordered according to [x0, y0, x1, y1].
+ *
+ * @throws {GeometryTagError} Rectangle coordinates must be valid basic coordinates.
+ */
+ constructor(rect) {
+ super();
+ if (rect.length !== 4) {
+ throw new GeometryTagError("Rectangle needs to have four values.");
+ }
+ if (rect[1] > rect[3]) {
+ throw new GeometryTagError("Basic Y coordinates values can not be inverted.");
+ }
+ for (let coord of rect) {
+ if (coord < 0 || coord > 1) {
+ throw new GeometryTagError("Basic coordinates must be on the interval [0, 1].");
+ }
+ }
+ this._anchorIndex = undefined;
+ this._rect = rect.slice(0, 4);
+ this._inverted = this._rect[0] > this._rect[2];
+ }
+ /**
+ * Get anchor index property.
+ *
+ * @returns {number} Index representing the current anchor property if
+ * achoring indexing has been initialized. If anchor indexing has not been
+ * initialized or has been terminated undefined will be returned.
+ * @ignore
+ */
+ get anchorIndex() {
+ return this._anchorIndex;
+ }
+ /**
+ * Get inverted property.
+ *
+ * @returns {boolean} Boolean determining whether the rect geometry is
+ * inverted. For spherical the rect geometrye may be inverted.
+ * @ignore
+ */
+ get inverted() {
+ return this._inverted;
+ }
+ /**
+ * Get rect property.
+ *
+ * @returns {Array<number>} Array representing the top-left and bottom-right
+ * corners of the rectangle in basic coordinates.
+ */
+ get rect() {
+ return this._rect;
+ }
+ /**
+ * Initialize anchor indexing to enable setting opposite vertex.
+ *
+ * @param {number} [index] - The index of the vertex to use as anchor.
+ *
+ * @throws {GeometryTagError} If anchor indexing has already been initialized.
+ * @throws {GeometryTagError} If index is not valid (0 to 3).
+ * @ignore
+ */
+ initializeAnchorIndexing(index) {
+ if (this._anchorIndex !== undefined) {
+ throw new GeometryTagError("Anchor indexing is already initialized.");
+ }
+ if (index < 0 || index > 3) {
+ throw new GeometryTagError(`Invalid anchor index: ${index}.`);
+ }
+ this._anchorIndex = index === undefined ? 0 : index;
+ }
+ /**
+ * Terminate anchor indexing to disable setting pposite vertex.
+ * @ignore
+ */
+ terminateAnchorIndexing() {
+ this._anchorIndex = undefined;
+ }
+ /**
+ * Set the value of the vertex opposite to the anchor in the polygon
+ * representation of the rectangle.
+ *
+ * @description Setting the opposite vertex may change the anchor index.
+ *
+ * @param {Array<number>} opposite - The new value of the vertex opposite to the anchor.
+ * @param {Transform} transform - The transform of the image related to the rectangle.
+ *
+ * @throws {GeometryTagError} When anchor indexing has not been initialized.
+ * @ignore
+ */
+ setOppositeVertex2d(opposite, transform) {
+ if (this._anchorIndex === undefined) {
+ throw new GeometryTagError("Anchor indexing needs to be initialized.");
+ }
+ const changed = [
+ Math.max(0, Math.min(1, opposite[0])),
+ Math.max(0, Math.min(1, opposite[1])),
+ ];
+ const original = this._rect.slice();
+ const anchor = this._anchorIndex === 0 ? [original[0], original[3]] :
+ this._anchorIndex === 1 ? [original[0], original[1]] :
+ this._anchorIndex === 2 ? [original[2], original[1]] :
+ [original[2], original[3]];
+ if (isSpherical(transform.cameraType)) {
+ const deltaX = this._anchorIndex < 2 ?
+ changed[0] - original[2] :
+ changed[0] - original[0];
+ if (!this._inverted && this._anchorIndex < 2 && changed[0] < 0.25 && original[2] > 0.75 && deltaX < -0.5) {
+ // right side passes boundary rightward
+ this._inverted = true;
+ this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+ }
+ else if (!this._inverted && this._anchorIndex >= 2 && changed[0] < 0.25 && original[2] > 0.75 && deltaX < -0.5) {
+ // left side passes right side and boundary rightward
+ this._inverted = true;
+ this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+ }
+ else if (this._inverted && this._anchorIndex >= 2 && changed[0] < 0.25 && original[0] > 0.75 && deltaX < -0.5) {
+ this._inverted = false;
+ if (anchor[0] > changed[0]) {
+ // left side passes boundary rightward
+ this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+ }
+ else {
+ // left side passes right side and boundary rightward
+ this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+ }
+ }
+ else if (!this._inverted && this._anchorIndex >= 2 && changed[0] > 0.75 && original[0] < 0.25 && deltaX > 0.5) {
+ // left side passes boundary leftward
+ this._inverted = true;
+ this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+ }
+ else if (!this._inverted && this._anchorIndex < 2 && changed[0] > 0.75 && original[0] < 0.25 && deltaX > 0.5) {
+ // right side passes left side and boundary leftward
+ this._inverted = true;
+ this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+ }
+ else if (this._inverted && this._anchorIndex < 2 && changed[0] > 0.75 && original[2] < 0.25 && deltaX > 0.5) {
+ this._inverted = false;
+ if (anchor[0] > changed[0]) {
+ // right side passes boundary leftward
+ this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+ }
+ else {
+ // right side passes left side and boundary leftward
+ this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+ }
+ }
+ else if (this._inverted && this._anchorIndex < 2 && changed[0] > original[0]) {
+ // inverted and right side passes left side completing a loop
+ this._inverted = false;
+ this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+ }
+ else if (this._inverted && this._anchorIndex >= 2 && changed[0] < original[2]) {
+ // inverted and left side passes right side completing a loop
+ this._inverted = false;
+ this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+ }
+ else if (this._inverted) {
+ // if still inverted only top and bottom can switch
+ if (this._anchorIndex < 2) {
+ this._anchorIndex = anchor[1] > changed[1] ? 0 : 1;
+ }
+ else {
+ this._anchorIndex = anchor[1] > changed[1] ? 3 : 2;
+ }
+ }
+ else {
+ // if still not inverted treat as non spherical
+ if (anchor[0] <= changed[0] && anchor[1] > changed[1]) {
+ this._anchorIndex = 0;
+ }
+ else if (anchor[0] <= changed[0] && anchor[1] <= changed[1]) {
+ this._anchorIndex = 1;
+ }
+ else if (anchor[0] > changed[0] && anchor[1] <= changed[1]) {
+ this._anchorIndex = 2;
+ }
+ else {
+ this._anchorIndex = 3;
+ }
+ }
+ const rect = [];
+ if (this._anchorIndex === 0) {
+ rect[0] = anchor[0];
+ rect[1] = changed[1];
+ rect[2] = changed[0];
+ rect[3] = anchor[1];
+ }
+ else if (this._anchorIndex === 1) {
+ rect[0] = anchor[0];
+ rect[1] = anchor[1];
+ rect[2] = changed[0];
+ rect[3] = changed[1];
+ }
+ else if (this._anchorIndex === 2) {
+ rect[0] = changed[0];
+ rect[1] = anchor[1];
+ rect[2] = anchor[0];
+ rect[3] = changed[1];
+ }
+ else {
+ rect[0] = changed[0];
+ rect[1] = changed[1];
+ rect[2] = anchor[0];
+ rect[3] = anchor[1];
+ }
+ if (!this._inverted && rect[0] > rect[2] ||
+ this._inverted && rect[0] < rect[2]) {
+ rect[0] = original[0];
+ rect[2] = original[2];
+ }
+ if (rect[1] > rect[3]) {
+ rect[1] = original[1];
+ rect[3] = original[3];
+ }
+ this._rect[0] = rect[0];
+ this._rect[1] = rect[1];
+ this._rect[2] = rect[2];
+ this._rect[3] = rect[3];
+ }
+ else {
+ if (anchor[0] <= changed[0] && anchor[1] > changed[1]) {
+ this._anchorIndex = 0;
+ }
+ else if (anchor[0] <= changed[0] && anchor[1] <= changed[1]) {
+ this._anchorIndex = 1;
+ }
+ else if (anchor[0] > changed[0] && anchor[1] <= changed[1]) {
+ this._anchorIndex = 2;
+ }
+ else {
+ this._anchorIndex = 3;
+ }
+ const rect = [];
+ if (this._anchorIndex === 0) {
+ rect[0] = anchor[0];
+ rect[1] = changed[1];
+ rect[2] = changed[0];
+ rect[3] = anchor[1];
+ }
+ else if (this._anchorIndex === 1) {
+ rect[0] = anchor[0];
+ rect[1] = anchor[1];
+ rect[2] = changed[0];
+ rect[3] = changed[1];
+ }
+ else if (this._anchorIndex === 2) {
+ rect[0] = changed[0];
+ rect[1] = anchor[1];
+ rect[2] = anchor[0];
+ rect[3] = changed[1];
+ }
+ else {
+ rect[0] = changed[0];
+ rect[1] = changed[1];
+ rect[2] = anchor[0];
+ rect[3] = anchor[1];
+ }
+ if (rect[0] > rect[2]) {
+ rect[0] = original[0];
+ rect[2] = original[2];
+ }
+ if (rect[1] > rect[3]) {
+ rect[1] = original[1];
+ rect[3] = original[3];
+ }
+ this._rect[0] = rect[0];
+ this._rect[1] = rect[1];
+ this._rect[2] = rect[2];
+ this._rect[3] = rect[3];
+ }
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Set the value of a vertex in the polygon representation of the rectangle.
+ *
+ * @description The polygon is defined to have the first vertex at the
+ * bottom-left corner with the rest of the vertices following in clockwise order.
+ *
+ * @param {number} index - The index of the vertex to be set.
+ * @param {Array<number>} value - The new value of the vertex.
+ * @param {Transform} transform - The transform of the image related to the rectangle.
+ * @ignore
+ */
+ setVertex2d(index, value, transform) {
+ let original = this._rect.slice();
+ let changed = [
+ Math.max(0, Math.min(1, value[0])),
+ Math.max(0, Math.min(1, value[1])),
+ ];
+ let rect = [];
+ if (index === 0) {
+ rect[0] = changed[0];
+ rect[1] = original[1];
+ rect[2] = original[2];
+ rect[3] = changed[1];
+ }
+ else if (index === 1) {
+ rect[0] = changed[0];
+ rect[1] = changed[1];
+ rect[2] = original[2];
+ rect[3] = original[3];
+ }
+ else if (index === 2) {
+ rect[0] = original[0];
+ rect[1] = changed[1];
+ rect[2] = changed[0];
+ rect[3] = original[3];
+ }
+ else if (index === 3) {
+ rect[0] = original[0];
+ rect[1] = original[1];
+ rect[2] = changed[0];
+ rect[3] = changed[1];
+ }
+ if (isSpherical(transform.cameraType)) {
+ let passingBoundaryLeftward = index < 2 && changed[0] > 0.75 && original[0] < 0.25 ||
+ index >= 2 && this._inverted && changed[0] > 0.75 && original[2] < 0.25;
+ let passingBoundaryRightward = index < 2 && this._inverted && changed[0] < 0.25 && original[0] > 0.75 ||
+ index >= 2 && changed[0] < 0.25 && original[2] > 0.75;
+ if (passingBoundaryLeftward || passingBoundaryRightward) {
+ this._inverted = !this._inverted;
+ }
+ else {
+ if (rect[0] - original[0] < -0.25) {
+ rect[0] = original[0];
+ }
+ if (rect[2] - original[2] > 0.25) {
+ rect[2] = original[2];
+ }
+ }
+ if (!this._inverted && rect[0] > rect[2] ||
+ this._inverted && rect[0] < rect[2]) {
+ rect[0] = original[0];
+ rect[2] = original[2];
+ }
+ }
+ else {
+ if (rect[0] > rect[2]) {
+ rect[0] = original[0];
+ rect[2] = original[2];
+ }
+ }
+ if (rect[1] > rect[3]) {
+ rect[1] = original[1];
+ rect[3] = original[3];
+ }
+ this._rect[0] = rect[0];
+ this._rect[1] = rect[1];
+ this._rect[2] = rect[2];
+ this._rect[3] = rect[3];
+ this._notifyChanged$.next(this);
+ }
+ /** @ignore */
+ setCentroid2d(value, transform) {
+ let original = this._rect.slice();
+ let x0 = original[0];
+ let x1 = this._inverted ? original[2] + 1 : original[2];
+ let y0 = original[1];
+ let y1 = original[3];
+ let centerX = x0 + (x1 - x0) / 2;
+ let centerY = y0 + (y1 - y0) / 2;
+ let translationX = 0;
+ if (isSpherical(transform.cameraType)) {
+ translationX = this._inverted ? value[0] + 1 - centerX : value[0] - centerX;
+ }
+ else {
+ let minTranslationX = -x0;
+ let maxTranslationX = 1 - x1;
+ translationX = Math.max(minTranslationX, Math.min(maxTranslationX, value[0] - centerX));
+ }
+ let minTranslationY = -y0;
+ let maxTranslationY = 1 - y1;
+ let translationY = Math.max(minTranslationY, Math.min(maxTranslationY, value[1] - centerY));
+ this._rect[0] = original[0] + translationX;
+ this._rect[1] = original[1] + translationY;
+ this._rect[2] = original[2] + translationX;
+ this._rect[3] = original[3] + translationY;
+ if (this._rect[0] < 0) {
+ this._rect[0] += 1;
+ this._inverted = !this._inverted;
+ }
+ else if (this._rect[0] > 1) {
+ this._rect[0] -= 1;
+ this._inverted = !this._inverted;
+ }
+ if (this._rect[2] < 0) {
+ this._rect[2] += 1;
+ this._inverted = !this._inverted;
+ }
+ else if (this._rect[2] > 1) {
+ this._rect[2] -= 1;
+ this._inverted = !this._inverted;
+ }
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get the 3D coordinates for the vertices of the rectangle with
+ * interpolated points along the lines.
+ *
+ * @param {Transform} transform - The transform of the image related to
+ * the rectangle.
+ * @returns {Array<Array<number>>} Polygon array of 3D world coordinates
+ * representing the rectangle.
+ * @ignore
+ */
+ getPoints3d(transform) {
+ return this._getPoints2d()
+ .map((point) => {
+ return transform.unprojectBasic(point, 200);
+ });
+ }
+ /**
+ * Get the coordinates of a vertex from the polygon representation of the geometry.
+ *
+ * @description The first vertex represents the bottom-left corner with the rest of
+ * the vertices following in clockwise order. The method shifts the right side
+ * coordinates of the rectangle by one unit to ensure that the vertices are ordered
+ * clockwise.
+ *
+ * @param {number} index - Vertex index.
+ * @returns {Array<number>} Array representing the 2D basic coordinates of the vertex.
+ * @ignore
+ */
+ getVertex2d(index) {
+ return this._rectToVertices2d(this._rect)[index];
+ }
+ /**
+ * Get the coordinates of a vertex from the polygon representation of the geometry.
+ *
+ * @description The first vertex represents the bottom-left corner with the rest of
+ * the vertices following in clockwise order. The coordinates will not be shifted
+ * so they may not appear in clockwise order when layed out on the plane.
+ *
+ * @param {number} index - Vertex index.
+ * @returns {Array<number>} Array representing the 2D basic coordinates of the vertex.
+ * @ignore
+ */
+ getNonAdjustedVertex2d(index) {
+ return this._rectToNonAdjustedVertices2d(this._rect)[index];
+ }
+ /**
+ * Get a vertex from the polygon representation of the 3D coordinates for the
+ * vertices of the geometry.
+ *
+ * @description The first vertex represents the bottom-left corner with the rest of
+ * the vertices following in clockwise order.
+ *
+ * @param {number} index - Vertex index.
+ * @param {Transform} transform - The transform of the image related to the geometry.
+ * @returns {Array<Array<number>>} Polygon array of 3D world coordinates representing
+ * the vertices of the geometry.
+ * @ignore
+ */
+ getVertex3d(index, transform) {
+ return transform.unprojectBasic(this._rectToVertices2d(this._rect)[index], 200);
+ }
+ /**
+ * Get a polygon representation of the 2D basic coordinates for the vertices of the rectangle.
+ *
+ * @description The first vertex represents the bottom-left corner with the rest of
+ * the vertices following in clockwise order.
+ *
+ * @returns {Array<Array<number>>} Polygon array of 2D basic coordinates representing
+ * the rectangle vertices.
+ * @ignore
+ */
+ getVertices2d() {
+ return this._rectToVertices2d(this._rect);
+ }
+ /**
+ * Get a polygon representation of the 3D coordinates for the vertices of the rectangle.
+ *
+ * @description The first vertex represents the bottom-left corner with the rest of
+ * the vertices following in clockwise order.
+ *
+ * @param {Transform} transform - The transform of the image related to the rectangle.
+ * @returns {Array<Array<number>>} Polygon array of 3D world coordinates representing
+ * the rectangle vertices.
+ * @ignore
+ */
+ getVertices3d(transform) {
+ return this._rectToVertices2d(this._rect)
+ .map((vertex) => {
+ return transform.unprojectBasic(vertex, 200);
+ });
+ }
+ /** @ignore */
+ getCentroid2d() {
+ const rect = this._rect;
+ const x0 = rect[0];
+ const x1 = this._inverted ? rect[2] + 1 : rect[2];
+ const y0 = rect[1];
+ const y1 = rect[3];
+ const centroidX = (x0 + x1) / 2;
+ const centroidY = (y0 + y1) / 2;
+ return [centroidX, centroidY];
+ }
+ /** @ignore */
+ getCentroid3d(transform) {
+ const centroid2d = this.getCentroid2d();
+ return transform.unprojectBasic(centroid2d, 200);
+ }
+ /**
+ * @ignore
+ */
+ getPoleOfInaccessibility2d() {
+ return this._getPoleOfInaccessibility2d(this._rectToVertices2d(this._rect));
+ }
+ /** @ignore */
+ getPoleOfInaccessibility3d(transform) {
+ let pole2d = this._getPoleOfInaccessibility2d(this._rectToVertices2d(this._rect));
+ return transform.unprojectBasic(pole2d, 200);
+ }
+ /** @ignore */
+ getTriangles3d(transform) {
+ return isSpherical(transform.cameraType) ?
+ [] :
+ this._triangulate(this._project(this._getPoints2d(), transform), this.getPoints3d(transform));
+ }
+ /**
+ * Check if a particular bottom-right value is valid according to the current
+ * rectangle coordinates.
+ *
+ * @param {Array<number>} bottomRight - The bottom-right coordinates to validate
+ * @returns {boolean} Value indicating whether the provided bottom-right coordinates
+ * are valid.
+ * @ignore
+ */
+ validate(bottomRight) {
+ let rect = this._rect;
+ if (!this._inverted && bottomRight[0] < rect[0] ||
+ bottomRight[0] - rect[2] > 0.25 ||
+ bottomRight[1] < rect[1]) {
+ return false;
+ }
+ return true;
+ }
+ /**
+ * Get the 2D coordinates for the vertices of the rectangle with
+ * interpolated points along the lines.
+ *
+ * @returns {Array<Array<number>>} Polygon array of 2D basic coordinates
+ * representing the rectangle.
+ */
+ _getPoints2d() {
+ let vertices2d = this._rectToVertices2d(this._rect);
+ let sides = vertices2d.length - 1;
+ let sections = 10;
+ let points2d = [];
+ for (let i = 0; i < sides; ++i) {
+ let startX = vertices2d[i][0];
+ let startY = vertices2d[i][1];
+ let endX = vertices2d[i + 1][0];
+ let endY = vertices2d[i + 1][1];
+ let intervalX = (endX - startX) / (sections - 1);
+ let intervalY = (endY - startY) / (sections - 1);
+ for (let j = 0; j < sections; ++j) {
+ let point = [
+ startX + j * intervalX,
+ startY + j * intervalY,
+ ];
+ points2d.push(point);
+ }
+ }
+ return points2d;
+ }
+ /**
+ * Convert the top-left, bottom-right representation of a rectangle to a polygon
+ * representation of the vertices starting at the bottom-left corner going
+ * clockwise.
+ *
+ * @description The method shifts the right side coordinates of the rectangle
+ * by one unit to ensure that the vertices are ordered clockwise.
+ *
+ * @param {Array<number>} rect - Top-left, bottom-right representation of a
+ * rectangle.
+ * @returns {Array<Array<number>>} Polygon representation of the vertices of the
+ * rectangle.
+ */
+ _rectToVertices2d(rect) {
+ return [
+ [rect[0], rect[3]],
+ [rect[0], rect[1]],
+ [this._inverted ? rect[2] + 1 : rect[2], rect[1]],
+ [this._inverted ? rect[2] + 1 : rect[2], rect[3]],
+ [rect[0], rect[3]],
+ ];
+ }
+ /**
+ * Convert the top-left, bottom-right representation of a rectangle to a polygon
+ * representation of the vertices starting at the bottom-left corner going
+ * clockwise.
+ *
+ * @description The first vertex represents the bottom-left corner with the rest of
+ * the vertices following in clockwise order. The coordinates will not be shifted
+ * to ensure that the vertices are ordered clockwise when layed out on the plane.
+ *
+ * @param {Array<number>} rect - Top-left, bottom-right representation of a
+ * rectangle.
+ * @returns {Array<Array<number>>} Polygon representation of the vertices of the
+ * rectangle.
+ */
+ _rectToNonAdjustedVertices2d(rect) {
+ return [
+ [rect[0], rect[3]],
+ [rect[0], rect[1]],
+ [rect[2], rect[1]],
+ [rect[2], rect[3]],
+ [rect[0], rect[3]],
+ ];
+ }
+}
+
+class ExtremePointCreateTag extends CreateTag {
+ constructor(geometry, options, transform, viewportCoords) {
+ super(geometry, transform, viewportCoords);
+ this._options = {
+ color: options.color == null ? 0xFFFFFF : options.color,
+ indicateCompleter: options.indicateCompleter == null ? true : options.indicateCompleter,
+ };
+ this._rectGeometry = new RectGeometry(this._geometry.getRect2d(transform));
+ this._createGlObjects();
+ }
+ create() {
+ if (this._geometry.points.length < 3) {
+ return;
+ }
+ this._geometry.removePoint2d(this._geometry.points.length - 1);
+ this._created$.next(this);
+ }
+ dispose() {
+ super.dispose();
+ this._disposeObjects();
+ }
+ getDOMObjects(camera, size) {
+ const container = {
+ offsetHeight: size.height, offsetWidth: size.width,
+ };
+ const vNodes = [];
+ const points2d = this._geometry.getPoints2d();
+ const length = points2d.length;
+ for (let index = 0; index < length - 1; index++) {
+ const nonModifiedIndex = index;
+ const [pointX, pointY] = points2d[index];
+ const pointCanvas = this._viewportCoords.basicToCanvasSafe(pointX, pointY, container, this._transform, camera);
+ if (!pointCanvas) {
+ continue;
+ }
+ const abort = (e) => {
+ e.stopPropagation();
+ this._aborted$.next(this);
+ };
+ const remove = (e) => {
+ e.stopPropagation();
+ this._geometry.removePoint2d(nonModifiedIndex);
+ };
+ const transform = this._canvasToTransform(pointCanvas);
+ const completerProperties = {
+ onclick: index === 0 && length < 3 ? abort : remove,
+ style: { transform: transform },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-interactor", completerProperties, []));
+ const background = this._colorToBackground(this._options.color);
+ const pointProperties = {
+ style: {
+ background: background,
+ transform: transform,
+ },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+ }
+ if (length > 2 && this._options.indicateCompleter === true) {
+ const [centroidX, centroidY] = this._geometry.getCentroid2d(this._transform);
+ const centroidCanvas = this._viewportCoords.basicToCanvasSafe(centroidX, centroidY, container, this._transform, camera);
+ if (!!centroidCanvas) {
+ const complete = (e) => {
+ e.stopPropagation();
+ this._geometry.removePoint2d(this._geometry.points.length - 1);
+ this._created$.next(this);
+ };
+ const transform = this._canvasToTransform(centroidCanvas);
+ const completerProperties = {
+ onclick: complete,
+ style: { transform: transform },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-completer.mapillary-tag-larger", completerProperties, []));
+ const pointProperties = {
+ style: {
+ background: this._colorToBackground(this._options.color),
+ transform: transform,
+ },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-vertex.mapillary-tag-larger", pointProperties, []));
+ const dotProperties = {
+ style: {
+ transform: transform,
+ },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-dot", dotProperties, []));
+ }
+ }
+ return vNodes;
+ }
+ _onGeometryChanged() {
+ this._disposeObjects();
+ this._rectGeometry = new RectGeometry(this._geometry.getRect2d(this._transform));
+ this._createGlObjects();
+ }
+ _createGlObjects() {
+ this._glObjects = [];
+ const polygon3d = this._rectGeometry.getPoints3d(this._transform);
+ this._outline = this._createOutine(polygon3d, this._options.color);
+ this._glObjects.push(this._outline);
+ }
+ _disposeObjects() {
+ this._disposeLine(this._outline);
+ this._outline = null;
+ this._glObjects = null;
+ }
+}
+
+/**
+ * @class PolygonGeometry
+ *
+ * @classdesc Represents a polygon geometry in the 2D basic image coordinate system.
+ * All polygons and holes provided to the constructor needs to be closed.
+ *
+ * @example
+ * ```js
+ * var basicPolygon = [[0.5, 0.3], [0.7, 0.3], [0.6, 0.5], [0.5, 0.3]];
+ * var polygonGeometry = new PolygonGeometry(basicPolygon);
+ * ```
+ */
+class PolygonGeometry extends VertexGeometry {
+ /**
+ * Create a polygon geometry.
+ *
+ * @constructor
+ * @param {Array<Array<number>>} polygon - Array of polygon vertices. Must be closed.
+ * @param {Array<Array<Array<number>>>} [holes] - Array of arrays of hole vertices.
+ * Each array of holes vertices must be closed.
+ *
+ * @throws {GeometryTagError} Polygon coordinates must be valid basic coordinates.
+ */
+ constructor(polygon, holes) {
+ super();
+ let polygonLength = polygon.length;
+ if (polygonLength < 3) {
+ throw new GeometryTagError("A polygon must have three or more positions.");
+ }
+ if (polygon[0][0] !== polygon[polygonLength - 1][0] ||
+ polygon[0][1] !== polygon[polygonLength - 1][1]) {
+ throw new GeometryTagError("First and last positions must be equivalent.");
+ }
+ this._polygon = [];
+ for (let vertex of polygon) {
+ if (vertex[0] < 0 || vertex[0] > 1 ||
+ vertex[1] < 0 || vertex[1] > 1) {
+ throw new GeometryTagError("Basic coordinates of polygon must be on the interval [0, 1].");
+ }
+ this._polygon.push(vertex.slice());
+ }
+ this._holes = [];
+ if (holes == null) {
+ return;
+ }
+ for (let i = 0; i < holes.length; i++) {
+ let hole = holes[i];
+ let holeLength = hole.length;
+ if (holeLength < 3) {
+ throw new GeometryTagError("A polygon hole must have three or more positions.");
+ }
+ if (hole[0][0] !== hole[holeLength - 1][0] ||
+ hole[0][1] !== hole[holeLength - 1][1]) {
+ throw new GeometryTagError("First and last positions of hole must be equivalent.");
+ }
+ this._holes.push([]);
+ for (let vertex of hole) {
+ if (vertex[0] < 0 || vertex[0] > 1 ||
+ vertex[1] < 0 || vertex[1] > 1) {
+ throw new GeometryTagError("Basic coordinates of hole must be on the interval [0, 1].");
+ }
+ this._holes[i].push(vertex.slice());
+ }
+ }
+ }
+ /**
+ * Get polygon property.
+ * @returns {Array<Array<number>>} Closed 2d polygon.
+ */
+ get polygon() {
+ return this._polygon;
+ }
+ /**
+ * Get holes property.
+ * @returns {Array<Array<Array<number>>>} Holes of 2d polygon.
+ */
+ get holes() {
+ return this._holes;
+ }
+ /**
+ * Add a vertex to the polygon by appending it after the last vertex.
+ *
+ * @param {Array<number>} vertex - Vertex to add.
+ * @ignore
+ */
+ addVertex2d(vertex) {
+ let clamped = [
+ Math.max(0, Math.min(1, vertex[0])),
+ Math.max(0, Math.min(1, vertex[1])),
+ ];
+ this._polygon.splice(this._polygon.length - 1, 0, clamped);
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get the coordinates of a vertex from the polygon representation of the geometry.
+ *
+ * @param {number} index - Vertex index.
+ * @returns {Array<number>} Array representing the 2D basic coordinates of the vertex.
+ * @ignore
+ */
+ getVertex2d(index) {
+ return this._polygon[index].slice();
+ }
+ /**
+ * Remove a vertex from the polygon.
+ *
+ * @param {number} index - The index of the vertex to remove.
+ * @ignore
+ */
+ removeVertex2d(index) {
+ if (index < 0 ||
+ index >= this._polygon.length ||
+ this._polygon.length < 4) {
+ throw new GeometryTagError("Index for removed vertex must be valid.");
+ }
+ if (index > 0 && index < this._polygon.length - 1) {
+ this._polygon.splice(index, 1);
+ }
+ else {
+ this._polygon.splice(0, 1);
+ this._polygon.pop();
+ let closing = this._polygon[0].slice();
+ this._polygon.push(closing);
+ }
+ this._notifyChanged$.next(this);
+ }
+ /** @ignore */
+ setVertex2d(index, value, transform) {
+ let changed = [
+ Math.max(0, Math.min(1, value[0])),
+ Math.max(0, Math.min(1, value[1])),
+ ];
+ if (index === 0 || index === this._polygon.length - 1) {
+ this._polygon[0] = changed.slice();
+ this._polygon[this._polygon.length - 1] = changed.slice();
+ }
+ else {
+ this._polygon[index] = changed.slice();
+ }
+ this._notifyChanged$.next(this);
+ }
+ /** @ignore */
+ setCentroid2d(value, transform) {
+ let xs = this._polygon.map((point) => { return point[0]; });
+ let ys = this._polygon.map((point) => { return point[1]; });
+ let minX = Math.min.apply(Math, xs);
+ let maxX = Math.max.apply(Math, xs);
+ let minY = Math.min.apply(Math, ys);
+ let maxY = Math.max.apply(Math, ys);
+ let centroid = this.getCentroid2d();
+ let minTranslationX = -minX;
+ let maxTranslationX = 1 - maxX;
+ let minTranslationY = -minY;
+ let maxTranslationY = 1 - maxY;
+ let translationX = Math.max(minTranslationX, Math.min(maxTranslationX, value[0] - centroid[0]));
+ let translationY = Math.max(minTranslationY, Math.min(maxTranslationY, value[1] - centroid[1]));
+ for (let point of this._polygon) {
+ point[0] += translationX;
+ point[1] += translationY;
+ }
+ this._notifyChanged$.next(this);
+ }
+ /** @ignore */
+ getPoints3d(transform) {
+ return this._getPoints3d(this._subsample(this._polygon), transform);
+ }
+ /** @ignore */
+ getVertex3d(index, transform) {
+ return transform.unprojectBasic(this._polygon[index], 200);
+ }
+ /** @ignore */
+ getVertices2d() {
+ return this._polygon.slice();
+ }
+ /** @ignore */
+ getVertices3d(transform) {
+ return this._getPoints3d(this._polygon, transform);
+ }
+ /**
+ * Get a polygon representation of the 3D coordinates for the vertices of each hole
+ * of the geometry. Line segments between vertices will possibly be subsampled
+ * resulting in a larger number of points than the total number of vertices.
+ *
+ * @param {Transform} transform - The transform of the image related to the geometry.
+ * @returns {Array<Array<Array<number>>>} Array of hole polygons in 3D world coordinates
+ * representing the vertices of each hole of the geometry.
+ * @ignore
+ */
+ getHolePoints3d(transform) {
+ return this._holes
+ .map((hole2d) => {
+ return this._getPoints3d(this._subsample(hole2d), transform);
+ });
+ }
+ /**
+ * Get a polygon representation of the 3D coordinates for the vertices of each hole
+ * of the geometry.
+ *
+ * @param {Transform} transform - The transform of the image related to the geometry.
+ * @returns {Array<Array<Array<number>>>} Array of hole polygons in 3D world coordinates
+ * representing the vertices of each hole of the geometry.
+ * @ignore
+ */
+ getHoleVertices3d(transform) {
+ return this._holes
+ .map((hole2d) => {
+ return this._getPoints3d(hole2d, transform);
+ });
+ }
+ /** @ignore */
+ getCentroid2d() {
+ let polygon = this._polygon;
+ let area = 0;
+ let centroidX = 0;
+ let centroidY = 0;
+ for (let i = 0; i < polygon.length - 1; i++) {
+ let xi = polygon[i][0];
+ let yi = polygon[i][1];
+ let xi1 = polygon[i + 1][0];
+ let yi1 = polygon[i + 1][1];
+ let a = xi * yi1 - xi1 * yi;
+ area += a;
+ centroidX += (xi + xi1) * a;
+ centroidY += (yi + yi1) * a;
+ }
+ area /= 2;
+ centroidX /= 6 * area;
+ centroidY /= 6 * area;
+ return [centroidX, centroidY];
+ }
+ /** @ignore */
+ getCentroid3d(transform) {
+ let centroid2d = this.getCentroid2d();
+ return transform.unprojectBasic(centroid2d, 200);
+ }
+ /** @ignore */
+ get3dDomainTriangles3d(transform) {
+ return this._triangulate(this._project(this._polygon, transform), this.getVertices3d(transform), this._holes
+ .map((hole2d) => {
+ return this._project(hole2d, transform);
+ }), this.getHoleVertices3d(transform));
+ }
+ /** @ignore */
+ getTriangles3d(transform) {
+ if (isSpherical(transform.cameraType)) {
+ return this._triangulateSpherical(this._polygon.slice(), this.holes.slice(), transform);
+ }
+ const points2d = this._project(this._subsample(this._polygon), transform);
+ const points3d = this.getPoints3d(transform);
+ const holes2d = this._holes
+ .map((hole) => {
+ return this._project(this._subsample(hole), transform);
+ });
+ const holes3d = this.getHolePoints3d(transform);
+ return this._triangulate(points2d, points3d, holes2d, holes3d);
+ }
+ /** @ignore */
+ getPoleOfInaccessibility2d() {
+ return this._getPoleOfInaccessibility2d(this._polygon.slice());
+ }
+ /** @ignore */
+ getPoleOfInaccessibility3d(transform) {
+ let pole2d = this._getPoleOfInaccessibility2d(this._polygon.slice());
+ return transform.unprojectBasic(pole2d, 200);
+ }
+ _getPoints3d(points2d, transform) {
+ return points2d
+ .map((point) => {
+ return transform.unprojectBasic(point, 200);
+ });
+ }
+}
+
+class OutlineCreateTag extends CreateTag {
+ constructor(geometry, options, transform, viewportCoords) {
+ super(geometry, transform, viewportCoords);
+ this._options = { color: options.color == null ? 0xFFFFFF : options.color };
+ this._createGlObjects();
+ }
+ create() {
+ if (this._geometry instanceof RectGeometry) {
+ this._created$.next(this);
+ }
+ else if (this._geometry instanceof PolygonGeometry) {
+ const polygonGeometry = this._geometry;
+ polygonGeometry.removeVertex2d(polygonGeometry.polygon.length - 2);
+ this._created$.next(this);
+ }
+ }
+ dispose() {
+ super.dispose();
+ this._disposeLine(this._outline);
+ this._disposeObjects();
+ }
+ getDOMObjects(camera, size) {
+ const vNodes = [];
+ const container = {
+ offsetHeight: size.height, offsetWidth: size.width,
+ };
+ const abort = (e) => {
+ e.stopPropagation();
+ this._aborted$.next(this);
+ };
+ if (this._geometry instanceof RectGeometry) {
+ const anchorIndex = this._geometry.anchorIndex;
+ const vertexIndex = anchorIndex === undefined ? 1 : anchorIndex;
+ const [basicX, basicY] = this._geometry.getVertex2d(vertexIndex);
+ const canvasPoint = this._viewportCoords.basicToCanvasSafe(basicX, basicY, container, this._transform, camera);
+ if (canvasPoint != null) {
+ const background = this._colorToBackground(this._options.color);
+ const transform = this._canvasToTransform(canvasPoint);
+ const pointProperties = {
+ style: { background: background, transform: transform },
+ };
+ const completerProperties = {
+ onclick: abort,
+ style: { transform: transform },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-interactor", completerProperties, []));
+ vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+ }
+ }
+ else if (this._geometry instanceof PolygonGeometry) {
+ const polygonGeometry = this._geometry;
+ const [firstVertexBasicX, firstVertexBasicY] = polygonGeometry.getVertex2d(0);
+ const firstVertexCanvas = this._viewportCoords.basicToCanvasSafe(firstVertexBasicX, firstVertexBasicY, container, this._transform, camera);
+ if (firstVertexCanvas != null) {
+ const firstOnclick = polygonGeometry.polygon.length > 4 ?
+ (e) => {
+ e.stopPropagation();
+ polygonGeometry.removeVertex2d(polygonGeometry.polygon.length - 2);
+ this._created$.next(this);
+ } :
+ abort;
+ const transform = this._canvasToTransform(firstVertexCanvas);
+ const completerProperties = {
+ onclick: firstOnclick,
+ style: { transform: transform },
+ };
+ const firstClass = polygonGeometry.polygon.length > 4 ?
+ "mapillary-tag-completer" :
+ "mapillary-tag-interactor";
+ vNodes.push(virtualDom.h("div." + firstClass, completerProperties, []));
+ }
+ if (polygonGeometry.polygon.length > 3) {
+ const [lastVertexBasicX, lastVertexBasicY] = polygonGeometry.getVertex2d(polygonGeometry.polygon.length - 3);
+ const lastVertexCanvas = this._viewportCoords.basicToCanvasSafe(lastVertexBasicX, lastVertexBasicY, container, this._transform, camera);
+ if (lastVertexCanvas != null) {
+ const remove = (e) => {
+ e.stopPropagation();
+ polygonGeometry.removeVertex2d(polygonGeometry.polygon.length - 3);
+ };
+ const transform = this._canvasToTransform(lastVertexCanvas);
+ const completerProperties = {
+ onclick: remove,
+ style: { transform: transform },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-interactor", completerProperties, []));
+ }
+ }
+ const verticesBasic = polygonGeometry.polygon.slice();
+ verticesBasic.splice(-2, 2);
+ for (const vertexBasic of verticesBasic) {
+ const vertexCanvas = this._viewportCoords.basicToCanvasSafe(vertexBasic[0], vertexBasic[1], container, this._transform, camera);
+ if (vertexCanvas != null) {
+ const background = this._colorToBackground(this._options.color);
+ const transform = this._canvasToTransform(vertexCanvas);
+ const pointProperties = {
+ style: {
+ background: background,
+ transform: transform,
+ },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+ }
+ }
+ }
+ return vNodes;
+ }
+ addPoint(point) {
+ if (this._geometry instanceof RectGeometry) {
+ const rectGeometry = this._geometry;
+ if (!rectGeometry.validate(point)) {
+ return;
+ }
+ this._created$.next(this);
+ }
+ else if (this._geometry instanceof PolygonGeometry) {
+ const polygonGeometry = this._geometry;
+ polygonGeometry.addVertex2d(point);
+ }
+ }
+ _onGeometryChanged() {
+ this._disposeLine(this._outline);
+ this._disposeObjects();
+ this._createGlObjects();
+ }
+ _disposeObjects() {
+ this._outline = null;
+ this._glObjects = [];
+ }
+ _createGlObjects() {
+ const polygon3d = this._geometry instanceof RectGeometry ?
+ this._geometry.getPoints3d(this._transform) :
+ this._geometry.getVertices3d(this._transform);
+ this._outline = this._createOutine(polygon3d, this._options.color);
+ this._glObjects = [this._outline];
+ }
+}
+
+class TagCreator {
+ constructor(component, navigator) {
+ this._component = component;
+ this._navigator = navigator;
+ this._tagOperation$ = new Subject();
+ this._createPoints$ = new Subject();
+ this._createPolygon$ = new Subject();
+ this._createRect$ = new Subject();
+ this._delete$ = new Subject();
+ this._tag$ = this._tagOperation$.pipe(scan((tag, operation) => {
+ return operation(tag);
+ }, null), share());
+ this._replayedTag$ = this._tag$.pipe(publishReplay(1), refCount());
+ this._replayedTag$.subscribe();
+ this._createPoints$.pipe(withLatestFrom(this._component.configuration$, this._navigator.stateService.currentTransform$), map(([coord, conf, transform]) => {
+ return () => {
+ const geometry = new PointsGeometry([
+ [coord[0], coord[1]],
+ [coord[0], coord[1]],
+ ]);
+ return new ExtremePointCreateTag(geometry, {
+ color: conf.createColor,
+ indicateCompleter: conf.indicatePointsCompleter,
+ }, transform);
+ };
+ }))
+ .subscribe(this._tagOperation$);
+ this._createRect$.pipe(withLatestFrom(this._component.configuration$, this._navigator.stateService.currentTransform$), map(([coord, conf, transform]) => {
+ return () => {
+ const geometry = new RectGeometry([
+ coord[0],
+ coord[1],
+ coord[0],
+ coord[1],
+ ]);
+ return new OutlineCreateTag(geometry, { color: conf.createColor }, transform);
+ };
+ }))
+ .subscribe(this._tagOperation$);
+ this._createPolygon$.pipe(withLatestFrom(this._component.configuration$, this._navigator.stateService.currentTransform$), map(([coord, conf, transform]) => {
+ return () => {
+ const geometry = new PolygonGeometry([
+ [coord[0], coord[1]],
+ [coord[0], coord[1]],
+ [coord[0], coord[1]],
+ ]);
+ return new OutlineCreateTag(geometry, { color: conf.createColor }, transform);
+ };
+ }))
+ .subscribe(this._tagOperation$);
+ this._delete$.pipe(map(() => {
+ return () => {
+ return null;
+ };
+ }))
+ .subscribe(this._tagOperation$);
+ }
+ get createRect$() {
+ return this._createRect$;
+ }
+ get createPolygon$() {
+ return this._createPolygon$;
+ }
+ get createPoints$() {
+ return this._createPoints$;
+ }
+ get delete$() {
+ return this._delete$;
+ }
+ get tag$() {
+ return this._tag$;
+ }
+ get replayedTag$() {
+ return this._replayedTag$;
+ }
+}
+
+class TagDOMRenderer {
+ render(tags, createTag, atlas, camera, size) {
+ let vNodes = [];
+ for (const tag of tags) {
+ vNodes = vNodes.concat(tag.getDOMObjects(atlas, camera, size));
+ }
+ if (createTag != null) {
+ vNodes = vNodes.concat(createTag.getDOMObjects(camera, size));
+ }
+ return virtualDom.h("div.mapillary-tag-container", {}, vNodes);
+ }
+ clear() {
+ return virtualDom.h("div", {}, []);
+ }
+}
+
+class TagScene {
+ constructor(scene, raycaster) {
+ this._createTag = null;
+ this._needsRender = false;
+ this._raycaster = !!raycaster ? raycaster : new Raycaster();
+ this._scene = !!scene ? scene : new Scene();
+ this._objectTags = {};
+ this._retrievableObjects = [];
+ this._tags = {};
+ }
+ get needsRender() {
+ return this._needsRender;
+ }
+ add(tags) {
+ for (let tag of tags) {
+ if (tag.tag.id in this._tags) {
+ this._remove(tag.tag.id);
+ }
+ this._add(tag);
+ }
+ this._needsRender = true;
+ }
+ addCreateTag(tag) {
+ for (const object of tag.glObjects) {
+ this._scene.add(object);
+ }
+ this._createTag = { tag: tag, objects: tag.glObjects };
+ this._needsRender = true;
+ }
+ clear() {
+ for (const id of Object.keys(this._tags)) {
+ this._remove(id);
+ }
+ this._needsRender = false;
+ }
+ get(id) {
+ return this.has(id) ? this._tags[id].tag : undefined;
+ }
+ has(id) {
+ return id in this._tags;
+ }
+ hasCreateTag() {
+ return this._createTag != null;
+ }
+ intersectObjects([viewportX, viewportY], camera) {
+ this._raycaster.setFromCamera(new Vector2(viewportX, viewportY), camera);
+ const intersects = this._raycaster.intersectObjects(this._retrievableObjects);
+ const intersectedIds = [];
+ for (const intersect of intersects) {
+ if (intersect.object.uuid in this._objectTags) {
+ intersectedIds.push(this._objectTags[intersect.object.uuid]);
+ }
+ }
+ return intersectedIds;
+ }
+ remove(ids) {
+ for (const id of ids) {
+ this._remove(id);
+ }
+ this._needsRender = true;
+ }
+ removeAll() {
+ for (const id of Object.keys(this._tags)) {
+ this._remove(id);
+ }
+ this._needsRender = true;
+ }
+ removeCreateTag() {
+ if (this._createTag == null) {
+ return;
+ }
+ for (const object of this._createTag.objects) {
+ this._scene.remove(object);
+ }
+ this._createTag.tag.dispose();
+ this._createTag = null;
+ this._needsRender = true;
+ }
+ render(perspectiveCamera, renderer) {
+ renderer.render(this._scene, perspectiveCamera);
+ this._needsRender = false;
+ }
+ update() {
+ this._needsRender = true;
+ }
+ updateCreateTagObjects(tag) {
+ if (this._createTag.tag !== tag) {
+ throw new Error("Create tags do not have the same reference.");
+ }
+ for (let object of this._createTag.objects) {
+ this._scene.remove(object);
+ }
+ for (const object of tag.glObjects) {
+ this._scene.add(object);
+ }
+ this._createTag.objects = tag.glObjects;
+ this._needsRender = true;
+ }
+ updateObjects(tag) {
+ const id = tag.tag.id;
+ if (this._tags[id].tag !== tag) {
+ throw new Error("Tags do not have the same reference.");
+ }
+ const tagObjects = this._tags[id];
+ this._removeObjects(tagObjects);
+ delete this._tags[id];
+ this._add(tag);
+ this._needsRender = true;
+ }
+ _add(tag) {
+ const id = tag.tag.id;
+ const tagObjects = { tag: tag, objects: [], retrievableObjects: [] };
+ this._tags[id] = tagObjects;
+ for (const object of tag.getGLObjects()) {
+ tagObjects.objects.push(object);
+ this._scene.add(object);
+ }
+ for (const retrievableObject of tag.getRetrievableObjects()) {
+ tagObjects.retrievableObjects.push(retrievableObject);
+ this._retrievableObjects.push(retrievableObject);
+ this._objectTags[retrievableObject.uuid] = tag.tag.id;
+ }
+ }
+ _remove(id) {
+ const tagObjects = this._tags[id];
+ this._removeObjects(tagObjects);
+ tagObjects.tag.dispose();
+ delete this._tags[id];
+ }
+ _removeObjects(tagObjects) {
+ for (const object of tagObjects.objects) {
+ this._scene.remove(object);
+ }
+ for (const retrievableObject of tagObjects.retrievableObjects) {
+ const index = this._retrievableObjects.indexOf(retrievableObject);
+ if (index !== -1) {
+ this._retrievableObjects.splice(index, 1);
+ }
+ }
+ }
+}
+
+/**
+ * Enumeration for tag modes
+ * @enum {number}
+ * @readonly
+ * @description Modes for the interaction in the tag component.
+ */
+var TagMode;
+(function (TagMode) {
+ /**
+ * Disables creating tags.
+ */
+ TagMode[TagMode["Default"] = 0] = "Default";
+ /**
+ * Create a point geometry through a click.
+ */
+ TagMode[TagMode["CreatePoint"] = 1] = "CreatePoint";
+ /**
+ * Create a points geometry through clicks.
+ */
+ TagMode[TagMode["CreatePoints"] = 2] = "CreatePoints";
+ /**
+ * Create a polygon geometry through clicks.
+ */
+ TagMode[TagMode["CreatePolygon"] = 3] = "CreatePolygon";
+ /**
+ * Create a rect geometry through clicks.
+ */
+ TagMode[TagMode["CreateRect"] = 4] = "CreateRect";
+ /**
+ * Create a rect geometry through drag.
+ *
+ * @description Claims the mouse which results in mouse handlers like
+ * drag pan and scroll zoom becoming inactive.
+ */
+ TagMode[TagMode["CreateRectDrag"] = 5] = "CreateRectDrag";
+})(TagMode || (TagMode = {}));
+
+var TagOperation;
+(function (TagOperation) {
+ TagOperation[TagOperation["None"] = 0] = "None";
+ TagOperation[TagOperation["Centroid"] = 1] = "Centroid";
+ TagOperation[TagOperation["Vertex"] = 2] = "Vertex";
+})(TagOperation || (TagOperation = {}));
+
+class RenderTag {
+ constructor(tag, transform, viewportCoords) {
+ this._tag = tag;
+ this._transform = transform;
+ this._viewportCoords = !!viewportCoords ? viewportCoords : new ViewportCoords();
+ this._glObjectsChanged$ = new Subject();
+ this._interact$ = new Subject();
+ }
+ get glObjectsChanged$() {
+ return this._glObjectsChanged$;
+ }
+ get interact$() {
+ return this._interact$;
+ }
+ get tag() {
+ return this._tag;
+ }
+}
+
+class OutlineRenderTagBase extends RenderTag {
+ constructor(tag, transform) {
+ super(tag, transform);
+ this._geometryChangedSubscription = this._tag.geometry.changed$
+ .subscribe(() => {
+ this._onGeometryChanged();
+ });
+ this._changedSubscription = this._tag.changed$
+ .subscribe(() => {
+ const glObjectsChanged = this._onTagChanged();
+ if (glObjectsChanged) {
+ this._glObjectsChanged$.next(this);
+ }
+ });
+ }
+ dispose() {
+ this._changedSubscription.unsubscribe();
+ this._geometryChangedSubscription.unsubscribe();
+ }
+ _colorToCss(color) {
+ return "#" + ("000000" + color.toString(16)).substr(-6);
+ }
+ _createFill() {
+ let triangles = this._getTriangles();
+ let positions = new Float32Array(triangles);
+ let geometry = new BufferGeometry();
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
+ geometry.computeBoundingSphere();
+ let material = new MeshBasicMaterial({ side: DoubleSide, transparent: true });
+ this._updateFillMaterial(material);
+ return new Mesh(geometry, material);
+ }
+ _createLine(points3d) {
+ let positions = this._getLinePositions(points3d);
+ let geometry = new BufferGeometry();
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
+ geometry.computeBoundingSphere();
+ let material = new LineBasicMaterial();
+ this._updateLineBasicMaterial(material);
+ const line = new Line(geometry, material);
+ line.renderOrder = 1;
+ return line;
+ }
+ _createOutline() {
+ return this._createLine(this._getPoints3d());
+ }
+ _disposeFill() {
+ if (this._fill == null) {
+ return;
+ }
+ this._fill.geometry.dispose();
+ this._fill.material.dispose();
+ this._fill = null;
+ }
+ _disposeOutline() {
+ if (this._outline == null) {
+ return;
+ }
+ this._outline.geometry.dispose();
+ this._outline.material.dispose();
+ this._outline = null;
+ }
+ _getLinePositions(points3d) {
+ let length = points3d.length;
+ let positions = new Float32Array(length * 3);
+ for (let i = 0; i < length; ++i) {
+ let index = 3 * i;
+ let position = points3d[i];
+ positions[index + 0] = position[0];
+ positions[index + 1] = position[1];
+ positions[index + 2] = position[2];
+ }
+ return positions;
+ }
+ _interact(operation, cursor, vertexIndex) {
+ return (e) => {
+ let offsetX = e.offsetX - e.target.offsetWidth / 2;
+ let offsetY = e.offsetY - e.target.offsetHeight / 2;
+ this._interact$.next({
+ cursor: cursor,
+ offsetX: offsetX,
+ offsetY: offsetY,
+ operation: operation,
+ tag: this._tag,
+ vertexIndex: vertexIndex,
+ });
+ };
+ }
+ _updateFillGeometry() {
+ let triangles = this._getTriangles();
+ let positions = new Float32Array(triangles);
+ let geometry = this._fill.geometry;
+ let attribute = geometry.getAttribute("position");
+ if (attribute.array.length === positions.length) {
+ attribute.set(positions);
+ attribute.needsUpdate = true;
+ }
+ else {
+ geometry.deleteAttribute("position");
+ geometry.setAttribute("position", new BufferAttribute(positions, 3));
+ }
+ geometry.computeBoundingSphere();
+ }
+ _updateLine(line, points3d) {
+ let positions = this._getLinePositions(points3d);
+ let geometry = line.geometry;
+ let attribute = geometry.getAttribute("position");
+ attribute.set(positions);
+ attribute.needsUpdate = true;
+ geometry.computeBoundingSphere();
+ }
+ _updateOulineGeometry() {
+ this._updateLine(this._outline, this._getPoints3d());
+ }
+}
+
+/**
+ * @class OutlineRenderTag
+ * @classdesc Tag visualizing the properties of an OutlineTag.
+ */
+class ExtremePointRenderTag extends OutlineRenderTagBase {
+ constructor(tag, transform) {
+ super(tag, transform);
+ this._rectGeometry = new RectGeometry(this._tag.geometry.getRect2d(transform));
+ this._fill = !isSpherical(transform.cameraType) ?
+ this._createFill() : null;
+ this._outline = this._tag.lineWidth >= 1 ?
+ this._createOutline() :
+ null;
+ }
+ dispose() {
+ super.dispose();
+ this._disposeFill();
+ this._disposeOutline();
+ }
+ getDOMObjects(atlas, camera, size) {
+ const vNodes = [];
+ const container = {
+ offsetHeight: size.height, offsetWidth: size.width,
+ };
+ if (!this._tag.editable) {
+ return vNodes;
+ }
+ const lineColor = this._colorToCss(this._tag.lineColor);
+ const points2d = this._tag.geometry.getPoints2d();
+ for (let i = 0; i < points2d.length; i++) {
+ const [vertexBasicX, vertexBasicY] = points2d[i];
+ const vertexCanvas = this._viewportCoords.basicToCanvasSafe(vertexBasicX, vertexBasicY, container, this._transform, camera);
+ if (vertexCanvas == null) {
+ continue;
+ }
+ const cursor = "crosshair";
+ const interact = this._interact(TagOperation.Vertex, cursor, i);
+ const vertexCanvasX = Math.round(vertexCanvas[0]);
+ const vertexCanvasY = Math.round(vertexCanvas[1]);
+ const transform = `translate(-50%, -50%) translate(${vertexCanvasX}px,${vertexCanvasY}px)`;
+ const properties = {
+ onpointerdown: interact,
+ style: { background: lineColor, transform: transform, cursor: cursor },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-resizer", properties, []));
+ if (!this._tag.indicateVertices) {
+ continue;
+ }
+ const pointProperties = {
+ style: { background: lineColor, transform: transform },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+ }
+ return vNodes;
+ }
+ getGLObjects() {
+ const glObjects = [];
+ if (this._fill != null) {
+ glObjects.push(this._fill);
+ }
+ if (this._outline != null) {
+ glObjects.push(this._outline);
+ }
+ return glObjects;
+ }
+ getRetrievableObjects() {
+ return this._fill != null ? [this._fill] : [];
+ }
+ _onGeometryChanged() {
+ this._rectGeometry = new RectGeometry(this._tag.geometry.getRect2d(this._transform));
+ if (this._fill != null) {
+ this._updateFillGeometry();
+ }
+ if (this._outline != null) {
+ this._updateOulineGeometry();
+ }
+ }
+ _onTagChanged() {
+ let glObjectsChanged = false;
+ if (this._fill != null) {
+ this._updateFillMaterial(this._fill.material);
+ }
+ if (this._outline == null) {
+ if (this._tag.lineWidth >= 1) {
+ this._outline = this._createOutline();
+ glObjectsChanged = true;
+ }
+ }
+ else {
+ this._updateOutlineMaterial();
+ }
+ return glObjectsChanged;
+ }
+ _getPoints3d() {
+ return this._rectGeometry.getPoints3d(this._transform);
+ }
+ _getTriangles() {
+ return this._rectGeometry.getTriangles3d(this._transform);
+ }
+ _updateFillMaterial(material) {
+ material.color = new Color(this._tag.fillColor);
+ material.opacity = this._tag.fillOpacity;
+ material.needsUpdate = true;
+ }
+ _updateLineBasicMaterial(material) {
+ material.color = new Color(this._tag.lineColor);
+ material.linewidth = Math.max(this._tag.lineWidth, 1);
+ material.visible = this._tag.lineWidth >= 1 && this._tag.lineOpacity > 0;
+ material.opacity = this._tag.lineOpacity;
+ material.transparent = this._tag.lineOpacity < 1;
+ material.needsUpdate = true;
+ }
+ _updateOutlineMaterial() {
+ let material = this._outline.material;
+ this._updateLineBasicMaterial(material);
+ }
+}
+
+/**
+ * @class Tag
+ * @abstract
+ * @classdesc Abstract class representing the basic functionality of for a tag.
+ */
+class Tag extends EventEmitter {
+ /**
+ * Create a tag.
+ *
+ * @constructor
+ * @param {string} id
+ * @param {Geometry} geometry
+ */
+ constructor(id, geometry) {
+ super();
+ this._id = id;
+ this._geometry = geometry;
+ this._notifyChanged$ = new Subject();
+ this._notifyChanged$
+ .subscribe((t) => {
+ const type = "tag";
+ const event = {
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ });
+ this._geometry.changed$
+ .subscribe((g) => {
+ const type = "geometry";
+ const event = {
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ });
+ }
+ /**
+ * Get id property.
+ * @returns {string}
+ */
+ get id() {
+ return this._id;
+ }
+ /**
+ * Get geometry property.
+ * @returns {Geometry} The geometry of the tag.
+ */
+ get geometry() {
+ return this._geometry;
+ }
+ /**
+ * Get changed observable.
+ * @returns {Observable<Tag>}
+ * @ignore
+ */
+ get changed$() {
+ return this._notifyChanged$;
+ }
+ /**
+ * Get geometry changed observable.
+ * @returns {Observable<Tag>}
+ * @ignore
+ */
+ get geometryChanged$() {
+ return this._geometry.changed$.pipe(map(() => {
+ return this;
+ }), share());
+ }
+ fire(type, event) {
+ super.fire(type, event);
+ }
+ off(type, handler) {
+ super.off(type, handler);
+ }
+ on(type, handler) {
+ super.on(type, handler);
+ }
+}
+
+/**
+ * @class ExtremePointTag
+ *
+ * @classdesc Tag holding properties for visualizing a extreme points
+ * and their outline.
+ *
+ * @example
+ * ```js
+ * var geometry = new PointsGeometry([[0.3, 0.3], [0.5, 0.4]]);
+ * var tag = new ExtremePointTag(
+ * "id-1",
+ * geometry
+ * { editable: true, lineColor: 0xff0000 });
+ *
+ * tagComponent.add([tag]);
+ * ```
+ */
+class ExtremePointTag extends Tag {
+ /**
+ * Create an extreme point tag.
+ *
+ * @override
+ * @constructor
+ * @param {string} id - Unique identifier of the tag.
+ * @param {PointsGeometry} geometry - Geometry defining points of tag.
+ * @param {ExtremePointTagOptions} options - Options defining the visual appearance and
+ * behavior of the extreme point tag.
+ */
+ constructor(id, geometry, options) {
+ super(id, geometry);
+ options = !!options ? options : {};
+ this._editable = options.editable == null ? false : options.editable;
+ this._fillColor = options.fillColor == null ? 0xFFFFFF : options.fillColor;
+ this._fillOpacity = options.fillOpacity == null ? 0.0 : options.fillOpacity;
+ this._indicateVertices = options.indicateVertices == null ? true : options.indicateVertices;
+ this._lineColor = options.lineColor == null ? 0xFFFFFF : options.lineColor;
+ this._lineOpacity = options.lineOpacity == null ? 1 : options.lineOpacity;
+ this._lineWidth = options.lineWidth == null ? 1 : options.lineWidth;
+ }
+ /**
+ * Get editable property.
+ * @returns {boolean} Value indicating if tag is editable.
+ */
+ get editable() {
+ return this._editable;
+ }
+ /**
+ * Set editable property.
+ * @param {boolean}
+ *
+ * @fires changed
+ */
+ set editable(value) {
+ this._editable = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get fill color property.
+ * @returns {number}
+ */
+ get fillColor() {
+ return this._fillColor;
+ }
+ /**
+ * Set fill color property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set fillColor(value) {
+ this._fillColor = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get fill opacity property.
+ * @returns {number}
+ */
+ get fillOpacity() {
+ return this._fillOpacity;
+ }
+ /**
+ * Set fill opacity property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set fillOpacity(value) {
+ this._fillOpacity = value;
+ this._notifyChanged$.next(this);
+ }
+ /** @inheritdoc */
+ get geometry() {
+ return this._geometry;
+ }
+ /**
+ * Get indicate vertices property.
+ * @returns {boolean} Value indicating if vertices should be indicated
+ * when tag is editable.
+ */
+ get indicateVertices() {
+ return this._indicateVertices;
+ }
+ /**
+ * Set indicate vertices property.
+ * @param {boolean}
+ *
+ * @fires changed
+ */
+ set indicateVertices(value) {
+ this._indicateVertices = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get line color property.
+ * @returns {number}
+ */
+ get lineColor() {
+ return this._lineColor;
+ }
+ /**
+ * Set line color property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set lineColor(value) {
+ this._lineColor = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get line opacity property.
+ * @returns {number}
+ */
+ get lineOpacity() {
+ return this._lineOpacity;
+ }
+ /**
+ * Set line opacity property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set lineOpacity(value) {
+ this._lineOpacity = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get line width property.
+ * @returns {number}
+ */
+ get lineWidth() {
+ return this._lineWidth;
+ }
+ /**
+ * Set line width property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set lineWidth(value) {
+ this._lineWidth = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Set options for tag.
+ *
+ * @description Sets all the option properties provided and keeps
+ * the rest of the values as is.
+ *
+ * @param {ExtremePointTagOptions} options - Extreme point tag options
+ *
+ * @fires changed
+ */
+ setOptions(options) {
+ this._editable = options.editable == null ? this._editable : options.editable;
+ this._indicateVertices = options.indicateVertices == null ? this._indicateVertices : options.indicateVertices;
+ this._lineColor = options.lineColor == null ? this._lineColor : options.lineColor;
+ this._lineWidth = options.lineWidth == null ? this._lineWidth : options.lineWidth;
+ this._fillColor = options.fillColor == null ? this._fillColor : options.fillColor;
+ this._fillOpacity = options.fillOpacity == null ? this._fillOpacity : options.fillOpacity;
+ this._notifyChanged$.next(this);
+ }
+}
+
+/**
+ * Enumeration for tag domains.
+ * @enum {number}
+ * @readonly
+ * @description Defines where lines between two vertices are treated
+ * as straight.
+ *
+ * Only applicable for polygons. For rectangles lines between
+ * vertices are always treated as straight in the distorted 2D
+ * projection and bended in the undistorted 3D space.
+ */
+var TagDomain;
+(function (TagDomain) {
+ /**
+ * Treats lines between two vertices as straight in the
+ * distorted 2D projection, i.e. on the image. If the image
+ * is distorted this will result in bended lines when rendered
+ * in the undistorted 3D space.
+ */
+ TagDomain[TagDomain["TwoDimensional"] = 0] = "TwoDimensional";
+ /**
+ * Treats lines as straight in the undistorted 3D space. If the
+ * image is distorted this will result in bended lines when rendered
+ * on the distorted 2D projection of the image.
+ */
+ TagDomain[TagDomain["ThreeDimensional"] = 1] = "ThreeDimensional";
+})(TagDomain || (TagDomain = {}));
+
+/**
+ * @class OutlineRenderTag
+ * @classdesc Tag visualizing the properties of an OutlineTag.
+ */
+class OutlineRenderTag extends OutlineRenderTagBase {
+ constructor(tag, transform) {
+ super(tag, transform);
+ this._fill = !isSpherical(transform.cameraType) ?
+ this._createFill() :
+ tag.domain === TagDomain.TwoDimensional &&
+ tag.geometry instanceof PolygonGeometry ?
+ this._createFill() :
+ null;
+ this._holes = this._tag.lineWidth >= 1 ?
+ this._createHoles() :
+ [];
+ this._outline = this._tag.lineWidth >= 1 ?
+ this._createOutline() :
+ null;
+ }
+ dispose() {
+ super.dispose();
+ this._disposeFill();
+ this._disposeHoles();
+ this._disposeOutline();
+ }
+ getDOMObjects(atlas, camera, size) {
+ const vNodes = [];
+ const isRect = this._tag.geometry instanceof RectGeometry;
+ const isPerspective = !isSpherical(this._transform.cameraType);
+ const container = {
+ offsetHeight: size.height, offsetWidth: size.width,
+ };
+ if (this._tag.icon != null && (isRect || isPerspective)) {
+ const [iconBasicX, iconBasicY] = this._tag.geometry instanceof RectGeometry ?
+ this._tag.geometry.getVertex2d(this._tag.iconIndex) :
+ this._tag.geometry.getPoleOfInaccessibility2d();
+ const iconCanvas = this._viewportCoords.basicToCanvasSafe(iconBasicX, iconBasicY, container, this._transform, camera);
+ if (iconCanvas != null) {
+ const interact = () => {
+ this._interact$.next({ offsetX: 0, offsetY: 0, operation: TagOperation.None, tag: this._tag });
+ };
+ if (atlas.loaded) {
+ const sprite = atlas.getDOMSprite(this._tag.icon, this._tag.iconFloat);
+ const iconCanvasX = Math.round(iconCanvas[0]);
+ const iconCanvasY = Math.round(iconCanvas[1]);
+ const transform = `translate(${iconCanvasX}px,${iconCanvasY}px)`;
+ const click = (e) => {
+ e.stopPropagation();
+ this._tag.click$.next(this._tag);
+ };
+ const properties = {
+ onclick: click,
+ onpointerdown: interact,
+ style: { transform: transform },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-symbol", properties, [sprite]));
+ }
+ }
+ }
+ else if (this._tag.text != null && (isRect || isPerspective)) {
+ const [textBasicX, textBasicY] = this._tag.geometry instanceof RectGeometry ?
+ this._tag.geometry.getVertex2d(3) :
+ this._tag.geometry.getPoleOfInaccessibility2d();
+ const textCanvas = this._viewportCoords.basicToCanvasSafe(textBasicX, textBasicY, container, this._transform, camera);
+ if (textCanvas != null) {
+ const textCanvasX = Math.round(textCanvas[0]);
+ const textCanvasY = Math.round(textCanvas[1]);
+ const transform = this._tag.geometry instanceof RectGeometry ?
+ `translate(${textCanvasX}px,${textCanvasY}px)` :
+ `translate(-50%, -50%) translate(${textCanvasX}px,${textCanvasY}px)`;
+ const interact = () => {
+ this._interact$.next({ offsetX: 0, offsetY: 0, operation: TagOperation.None, tag: this._tag });
+ };
+ const properties = {
+ onpointerdown: interact,
+ style: {
+ color: this._colorToCss(this._tag.textColor),
+ transform: transform,
+ },
+ textContent: this._tag.text,
+ };
+ vNodes.push(virtualDom.h("span.mapillary-tag-symbol", properties, []));
+ }
+ }
+ if (!this._tag.editable) {
+ return vNodes;
+ }
+ const lineColor = this._colorToCss(this._tag.lineColor);
+ if (this._tag.geometry instanceof RectGeometry) {
+ const [centroidBasicX, centroidBasicY] = this._tag.geometry.getCentroid2d();
+ const centroidCanvas = this._viewportCoords.basicToCanvasSafe(centroidBasicX, centroidBasicY, container, this._transform, camera);
+ if (centroidCanvas != null) {
+ const interact = this._interact(TagOperation.Centroid, "move");
+ const centroidCanvasX = Math.round(centroidCanvas[0]);
+ const centroidCanvasY = Math.round(centroidCanvas[1]);
+ const transform = `translate(-50%, -50%) translate(${centroidCanvasX}px,${centroidCanvasY}px)`;
+ const properties = {
+ onpointerdown: interact,
+ style: { background: lineColor, transform: transform },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-mover", properties, []));
+ }
+ }
+ const vertices2d = this._tag.geometry.getVertices2d();
+ for (let i = 0; i < vertices2d.length - 1; i++) {
+ if (isRect &&
+ ((this._tag.icon != null && i === this._tag.iconIndex) ||
+ (this._tag.icon == null && this._tag.text != null && i === 3))) {
+ continue;
+ }
+ const [vertexBasicX, vertexBasicY] = vertices2d[i];
+ const vertexCanvas = this._viewportCoords.basicToCanvasSafe(vertexBasicX, vertexBasicY, container, this._transform, camera);
+ if (vertexCanvas == null) {
+ continue;
+ }
+ const cursor = isRect ?
+ i % 2 === 0 ? "nesw-resize" : "nwse-resize" :
+ "crosshair";
+ const interact = this._interact(TagOperation.Vertex, cursor, i);
+ const vertexCanvasX = Math.round(vertexCanvas[0]);
+ const vertexCanvasY = Math.round(vertexCanvas[1]);
+ const transform = `translate(-50%, -50%) translate(${vertexCanvasX}px,${vertexCanvasY}px)`;
+ const properties = {
+ onpointerdown: interact,
+ style: { background: lineColor, transform: transform, cursor: cursor },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-resizer", properties, []));
+ if (!this._tag.indicateVertices) {
+ continue;
+ }
+ const pointProperties = {
+ style: { background: lineColor, transform: transform },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+ }
+ return vNodes;
+ }
+ getGLObjects() {
+ const glObjects = [];
+ if (this._fill != null) {
+ glObjects.push(this._fill);
+ }
+ for (const hole of this._holes) {
+ glObjects.push(hole);
+ }
+ if (this._outline != null) {
+ glObjects.push(this._outline);
+ }
+ return glObjects;
+ }
+ getRetrievableObjects() {
+ return this._fill != null ? [this._fill] : [];
+ }
+ _onGeometryChanged() {
+ if (this._fill != null) {
+ this._updateFillGeometry();
+ }
+ if (this._holes.length > 0) {
+ this._updateHoleGeometries();
+ }
+ if (this._outline != null) {
+ this._updateOulineGeometry();
+ }
+ }
+ _onTagChanged() {
+ let glObjectsChanged = false;
+ if (this._fill != null) {
+ this._updateFillMaterial(this._fill.material);
+ }
+ if (this._outline == null) {
+ if (this._tag.lineWidth >= 1) {
+ this._holes = this._createHoles();
+ this._outline = this._createOutline();
+ glObjectsChanged = true;
+ }
+ }
+ else {
+ this._updateHoleMaterials();
+ this._updateOutlineMaterial();
+ }
+ return glObjectsChanged;
+ }
+ _getPoints3d() {
+ return this._in3dDomain() ?
+ this._tag.geometry.getVertices3d(this._transform) :
+ this._tag.geometry.getPoints3d(this._transform);
+ }
+ _getTriangles() {
+ return this._in3dDomain() ?
+ this._tag.geometry.get3dDomainTriangles3d(this._transform) :
+ this._tag.geometry.getTriangles3d(this._transform);
+ }
+ _updateFillMaterial(material) {
+ material.color = new Color(this._tag.fillColor);
+ material.opacity = this._tag.fillOpacity;
+ material.needsUpdate = true;
+ }
+ _updateLineBasicMaterial(material) {
+ material.color = new Color(this._tag.lineColor);
+ material.linewidth = Math.max(this._tag.lineWidth, 1);
+ material.visible = this._tag.lineWidth >= 1 && this._tag.lineOpacity > 0;
+ material.opacity = this._tag.lineOpacity;
+ material.transparent = this._tag.lineOpacity < 1;
+ material.needsUpdate = true;
+ }
+ _createHoles() {
+ let holes = [];
+ if (this._tag.geometry instanceof PolygonGeometry) {
+ let holes3d = this._getHoles3d();
+ for (let holePoints3d of holes3d) {
+ let hole = this._createLine(holePoints3d);
+ holes.push(hole);
+ }
+ }
+ return holes;
+ }
+ _disposeHoles() {
+ for (let hole of this._holes) {
+ hole.geometry.dispose();
+ hole.material.dispose();
+ }
+ this._holes = [];
+ }
+ _getHoles3d() {
+ const polygonGeometry = this._tag.geometry;
+ return this._in3dDomain() ?
+ polygonGeometry.getHoleVertices3d(this._transform) :
+ polygonGeometry.getHolePoints3d(this._transform);
+ }
+ _in3dDomain() {
+ return this._tag.geometry instanceof PolygonGeometry && this._tag.domain === TagDomain.ThreeDimensional;
+ }
+ _updateHoleGeometries() {
+ let holes3d = this._getHoles3d();
+ if (holes3d.length !== this._holes.length) {
+ throw new Error("Changing the number of holes is not supported.");
+ }
+ for (let i = 0; i < this._holes.length; i++) {
+ let holePoints3d = holes3d[i];
+ let hole = this._holes[i];
+ this._updateLine(hole, holePoints3d);
+ }
+ }
+ _updateHoleMaterials() {
+ for (const hole of this._holes) {
+ this._updateLineBasicMaterial(hole.material);
+ }
+ }
+ _updateOutlineMaterial() {
+ this._updateLineBasicMaterial(this._outline.material);
+ }
+}
+
+/**
+ * Enumeration for alignments
+ * @enum {number}
+ * @readonly
+ */
+var Alignment;
+(function (Alignment) {
+ /**
+ * Align to bottom
+ */
+ Alignment[Alignment["Bottom"] = 0] = "Bottom";
+ /**
+ * Align to bottom left
+ */
+ Alignment[Alignment["BottomLeft"] = 1] = "BottomLeft";
+ /**
+ * Align to bottom right
+ */
+ Alignment[Alignment["BottomRight"] = 2] = "BottomRight";
+ /**
+ * Align to center
+ */
+ Alignment[Alignment["Center"] = 3] = "Center";
+ /**
+ * Align to left
+ */
+ Alignment[Alignment["Left"] = 4] = "Left";
+ /**
+ * Align to right
+ */
+ Alignment[Alignment["Right"] = 5] = "Right";
+ /**
+ * Align to top
+ */
+ Alignment[Alignment["Top"] = 6] = "Top";
+ /**
+ * Align to top left
+ */
+ Alignment[Alignment["TopLeft"] = 7] = "TopLeft";
+ /**
+ * Align to top right
+ */
+ Alignment[Alignment["TopRight"] = 8] = "TopRight";
+})(Alignment || (Alignment = {}));
+
+/**
+ * @class OutlineTag
+ *
+ * @classdesc Tag holding properties for visualizing a geometry outline.
+ *
+ * @example
+ * ```js
+ * var geometry = new RectGeometry([0.3, 0.3, 0.5, 0.4]);
+ * var tag = new OutlineTag(
+ * "id-1",
+ * geometry
+ * { editable: true, lineColor: 0xff0000 });
+ *
+ * tagComponent.add([tag]);
+ * ```
+ */
+class OutlineTag extends Tag {
+ /**
+ * Create an outline tag.
+ *
+ * @override
+ * @constructor
+ * @param {string} id - Unique identifier of the tag.
+ * @param {VertexGeometry} geometry - Geometry defining vertices of tag.
+ * @param {OutlineTagOptions} options - Options defining the visual appearance and
+ * behavior of the outline tag.
+ */
+ constructor(id, geometry, options) {
+ super(id, geometry);
+ options = !!options ? options : {};
+ const domain = options.domain != null && geometry instanceof PolygonGeometry ?
+ options.domain : TagDomain.TwoDimensional;
+ const twoDimensionalPolygon = this._twoDimensionalPolygon(domain, geometry);
+ this._domain = domain;
+ this._editable = options.editable == null || twoDimensionalPolygon ? false : options.editable;
+ this._fillColor = options.fillColor == null ? 0xFFFFFF : options.fillColor;
+ this._fillOpacity = options.fillOpacity == null ? 0.0 : options.fillOpacity;
+ this._icon = options.icon === undefined ? null : options.icon;
+ this._iconFloat = options.iconFloat == null ? Alignment.Center : options.iconFloat;
+ this._iconIndex = options.iconIndex == null ? 3 : options.iconIndex;
+ this._indicateVertices = options.indicateVertices == null ? true : options.indicateVertices;
+ this._lineColor = options.lineColor == null ? 0xFFFFFF : options.lineColor;
+ this._lineOpacity = options.lineOpacity == null ? 1 : options.lineOpacity;
+ this._lineWidth = options.lineWidth == null ? 1 : options.lineWidth;
+ this._text = options.text === undefined ? null : options.text;
+ this._textColor = options.textColor == null ? 0xFFFFFF : options.textColor;
+ this._click$ = new Subject();
+ this._click$
+ .subscribe(() => {
+ const type = "click";
+ const event = {
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ });
+ }
+ /**
+ * Click observable.
+ *
+ * @description An observable emitting the tag when the icon of the
+ * tag has been clicked.
+ *
+ * @returns {Observable<Tag>}
+ */
+ get click$() {
+ return this._click$;
+ }
+ /**
+ * Get domain property.
+ *
+ * @description Readonly property that can only be set in constructor.
+ *
+ * @returns Value indicating the domain of the tag.
+ */
+ get domain() {
+ return this._domain;
+ }
+ /**
+ * Get editable property.
+ * @returns {boolean} Value indicating if tag is editable.
+ */
+ get editable() {
+ return this._editable;
+ }
+ /**
+ * Set editable property.
+ * @param {boolean}
+ *
+ * @fires changed
+ */
+ set editable(value) {
+ if (this._twoDimensionalPolygon(this._domain, this._geometry)) {
+ return;
+ }
+ this._editable = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get fill color property.
+ * @returns {number}
+ */
+ get fillColor() {
+ return this._fillColor;
+ }
+ /**
+ * Set fill color property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set fillColor(value) {
+ this._fillColor = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get fill opacity property.
+ * @returns {number}
+ */
+ get fillOpacity() {
+ return this._fillOpacity;
+ }
+ /**
+ * Set fill opacity property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set fillOpacity(value) {
+ this._fillOpacity = value;
+ this._notifyChanged$.next(this);
+ }
+ /** @inheritdoc */
+ get geometry() {
+ return this._geometry;
+ }
+ /**
+ * Get icon property.
+ * @returns {string}
+ */
+ get icon() {
+ return this._icon;
+ }
+ /**
+ * Set icon property.
+ * @param {string}
+ *
+ * @fires changed
+ */
+ set icon(value) {
+ this._icon = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get icon float property.
+ * @returns {Alignment}
+ */
+ get iconFloat() {
+ return this._iconFloat;
+ }
+ /**
+ * Set icon float property.
+ * @param {Alignment}
+ *
+ * @fires changed
+ */
+ set iconFloat(value) {
+ this._iconFloat = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get icon index property.
+ * @returns {number}
+ */
+ get iconIndex() {
+ return this._iconIndex;
+ }
+ /**
+ * Set icon index property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set iconIndex(value) {
+ this._iconIndex = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get indicate vertices property.
+ * @returns {boolean} Value indicating if vertices should be indicated
+ * when tag is editable.
+ */
+ get indicateVertices() {
+ return this._indicateVertices;
+ }
+ /**
+ * Set indicate vertices property.
+ * @param {boolean}
+ *
+ * @fires changed
+ */
+ set indicateVertices(value) {
+ this._indicateVertices = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get line color property.
+ * @returns {number}
+ */
+ get lineColor() {
+ return this._lineColor;
+ }
+ /**
+ * Set line color property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set lineColor(value) {
+ this._lineColor = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get line opacity property.
+ * @returns {number}
+ */
+ get lineOpacity() {
+ return this._lineOpacity;
+ }
+ /**
+ * Set line opacity property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set lineOpacity(value) {
+ this._lineOpacity = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get line width property.
+ * @returns {number}
+ */
+ get lineWidth() {
+ return this._lineWidth;
+ }
+ /**
+ * Set line width property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set lineWidth(value) {
+ this._lineWidth = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get text property.
+ * @returns {string}
+ */
+ get text() {
+ return this._text;
+ }
+ /**
+ * Set text property.
+ * @param {string}
+ *
+ * @fires changed
+ */
+ set text(value) {
+ this._text = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get text color property.
+ * @returns {number}
+ */
+ get textColor() {
+ return this._textColor;
+ }
+ /**
+ * Set text color property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set textColor(value) {
+ this._textColor = value;
+ this._notifyChanged$.next(this);
+ }
+ fire(type, event) {
+ super.fire(type, event);
+ }
+ off(type, handler) {
+ super.off(type, handler);
+ }
+ on(type, handler) {
+ super.on(type, handler);
+ }
+ /**
+ * Set options for tag.
+ *
+ * @description Sets all the option properties provided and keeps
+ * the rest of the values as is.
+ *
+ * @param {OutlineTagOptions} options - Outline tag options
+ *
+ * @fires changed
+ */
+ setOptions(options) {
+ const twoDimensionalPolygon = this._twoDimensionalPolygon(this._domain, this._geometry);
+ this._editable = twoDimensionalPolygon || options.editable == null ? this._editable : options.editable;
+ this._icon = options.icon === undefined ? this._icon : options.icon;
+ this._iconFloat = options.iconFloat == null ? this._iconFloat : options.iconFloat;
+ this._iconIndex = options.iconIndex == null ? this._iconIndex : options.iconIndex;
+ this._indicateVertices = options.indicateVertices == null ? this._indicateVertices : options.indicateVertices;
+ this._lineColor = options.lineColor == null ? this._lineColor : options.lineColor;
+ this._lineWidth = options.lineWidth == null ? this._lineWidth : options.lineWidth;
+ this._fillColor = options.fillColor == null ? this._fillColor : options.fillColor;
+ this._fillOpacity = options.fillOpacity == null ? this._fillOpacity : options.fillOpacity;
+ this._text = options.text === undefined ? this._text : options.text;
+ this._textColor = options.textColor == null ? this._textColor : options.textColor;
+ this._notifyChanged$.next(this);
+ }
+ _twoDimensionalPolygon(domain, geometry) {
+ return domain !== TagDomain.ThreeDimensional && geometry instanceof PolygonGeometry;
+ }
+}
+
+/**
+ * @class SpotRenderTag
+ * @classdesc Tag visualizing the properties of a SpotTag.
+ */
+class SpotRenderTag extends RenderTag {
+ dispose() { }
+ getDOMObjects(atlas, camera, size) {
+ const tag = this._tag;
+ const container = {
+ offsetHeight: size.height, offsetWidth: size.width,
+ };
+ const vNodes = [];
+ const [centroidBasicX, centroidBasicY] = tag.geometry.getCentroid2d();
+ const centroidCanvas = this._viewportCoords.basicToCanvasSafe(centroidBasicX, centroidBasicY, container, this._transform, camera);
+ if (centroidCanvas != null) {
+ const interactNone = (e) => {
+ this._interact$.next({ offsetX: 0, offsetY: 0, operation: TagOperation.None, tag: tag });
+ };
+ const canvasX = Math.round(centroidCanvas[0]);
+ const canvasY = Math.round(centroidCanvas[1]);
+ if (tag.icon != null) {
+ if (atlas.loaded) {
+ const sprite = atlas.getDOMSprite(tag.icon, Alignment.Bottom);
+ const iconTransform = `translate(${canvasX}px,${canvasY + 8}px)`;
+ const properties = {
+ onpointerdown: interactNone,
+ style: {
+ pointerEvents: "all",
+ transform: iconTransform,
+ },
+ };
+ vNodes.push(virtualDom.h("div", properties, [sprite]));
+ }
+ }
+ else if (tag.text != null) {
+ const textTransform = `translate(-50%,0%) translate(${canvasX}px,${canvasY + 8}px)`;
+ const properties = {
+ onpointerdown: interactNone,
+ style: {
+ color: this._colorToCss(tag.textColor),
+ transform: textTransform,
+ },
+ textContent: tag.text,
+ };
+ vNodes.push(virtualDom.h("span.mapillary-tag-symbol", properties, []));
+ }
+ const interact = this._interact(TagOperation.Centroid, tag, "move");
+ const background = this._colorToCss(tag.color);
+ const transform = `translate(-50%,-50%) translate(${canvasX}px,${canvasY}px)`;
+ if (tag.editable) {
+ let interactorProperties = {
+ onpointerdown: interact,
+ style: {
+ background: background,
+ transform: transform,
+ },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-spot-interactor", interactorProperties, []));
+ }
+ const pointProperties = {
+ style: {
+ background: background,
+ transform: transform,
+ },
+ };
+ vNodes.push(virtualDom.h("div.mapillary-tag-vertex", pointProperties, []));
+ }
+ return vNodes;
+ }
+ getGLObjects() { return []; }
+ getRetrievableObjects() { return []; }
+ _colorToCss(color) {
+ return "#" + ("000000" + color.toString(16)).substr(-6);
+ }
+ _interact(operation, tag, cursor, vertexIndex) {
+ return (e) => {
+ const offsetX = e.offsetX - e.target.offsetWidth / 2;
+ const offsetY = e.offsetY - e.target.offsetHeight / 2;
+ this._interact$.next({
+ cursor: cursor,
+ offsetX: offsetX,
+ offsetY: offsetY,
+ operation: operation,
+ tag: tag,
+ vertexIndex: vertexIndex,
+ });
+ };
+ }
+}
+
+/**
+ * @class SpotTag
+ *
+ * @classdesc Tag holding properties for visualizing the centroid of a geometry.
+ *
+ * @example
+ * ```js
+ * var geometry = new PointGeometry([0.3, 0.3]);
+ * var tag = new SpotTag(
+ * "id-1",
+ * geometry
+ * { editable: true, color: 0xff0000 });
+ *
+ * tagComponent.add([tag]);
+ * ```
+ */
+class SpotTag extends Tag {
+ /**
+ * Create a spot tag.
+ *
+ * @override
+ * @constructor
+ * @param {string} id
+ * @param {Geometry} geometry
+ * @param {IOutlineTagOptions} options - Options defining the visual appearance and
+ * behavior of the spot tag.
+ */
+ constructor(id, geometry, options) {
+ super(id, geometry);
+ options = !!options ? options : {};
+ this._color = options.color == null ? 0xFFFFFF : options.color;
+ this._editable = options.editable == null ? false : options.editable;
+ this._icon = options.icon === undefined ? null : options.icon;
+ this._text = options.text === undefined ? null : options.text;
+ this._textColor = options.textColor == null ? 0xFFFFFF : options.textColor;
+ }
+ /**
+ * Get color property.
+ * @returns {number} The color of the spot as a hexagonal number;
+ */
+ get color() {
+ return this._color;
+ }
+ /**
+ * Set color property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set color(value) {
+ this._color = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get editable property.
+ * @returns {boolean} Value indicating if tag is editable.
+ */
+ get editable() {
+ return this._editable;
+ }
+ /**
+ * Set editable property.
+ * @param {boolean}
+ *
+ * @fires changed
+ */
+ set editable(value) {
+ this._editable = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get icon property.
+ * @returns {string}
+ */
+ get icon() {
+ return this._icon;
+ }
+ /**
+ * Set icon property.
+ * @param {string}
+ *
+ * @fires changed
+ */
+ set icon(value) {
+ this._icon = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get text property.
+ * @returns {string}
+ */
+ get text() {
+ return this._text;
+ }
+ /**
+ * Set text property.
+ * @param {string}
+ *
+ * @fires changed
+ */
+ set text(value) {
+ this._text = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Get text color property.
+ * @returns {number}
+ */
+ get textColor() {
+ return this._textColor;
+ }
+ /**
+ * Set text color property.
+ * @param {number}
+ *
+ * @fires changed
+ */
+ set textColor(value) {
+ this._textColor = value;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Set options for tag.
+ *
+ * @description Sets all the option properties provided and keps
+ * the rest of the values as is.
+ *
+ * @param {SpotTagOptions} options - Spot tag options
+ *
+ * @fires changed
+ */
+ setOptions(options) {
+ this._color = options.color == null ? this._color : options.color;
+ this._editable = options.editable == null ? this._editable : options.editable;
+ this._icon = options.icon === undefined ? this._icon : options.icon;
+ this._text = options.text === undefined ? this._text : options.text;
+ this._textColor = options.textColor == null ? this._textColor : options.textColor;
+ this._notifyChanged$.next(this);
+ }
+}
+
+class TagSet {
+ constructor() {
+ this._active = false;
+ this._hash = {};
+ this._hashDeactivated = {};
+ this._notifyChanged$ = new Subject();
+ }
+ get active() {
+ return this._active;
+ }
+ get changed$() {
+ return this._notifyChanged$;
+ }
+ activate(transform) {
+ if (this._active) {
+ return;
+ }
+ for (const id in this._hashDeactivated) {
+ if (!this._hashDeactivated.hasOwnProperty(id)) {
+ continue;
+ }
+ const tag = this._hashDeactivated[id];
+ this._add(tag, transform);
+ }
+ this._hashDeactivated = {};
+ this._active = true;
+ this._notifyChanged$.next(this);
+ }
+ deactivate() {
+ if (!this._active) {
+ return;
+ }
+ for (const id in this._hash) {
+ if (!this._hash.hasOwnProperty(id)) {
+ continue;
+ }
+ this._hashDeactivated[id] = this._hash[id].tag;
+ }
+ this._hash = {};
+ this._active = false;
+ }
+ add(tags, transform) {
+ this._assertActivationState(true);
+ for (const tag of tags) {
+ this._add(tag, transform);
+ }
+ this._notifyChanged$.next(this);
+ }
+ addDeactivated(tags) {
+ this._assertActivationState(false);
+ for (const tag of tags) {
+ if (!(tag instanceof OutlineTag ||
+ tag instanceof SpotTag ||
+ tag instanceof ExtremePointTag)) {
+ throw new Error("Tag type not supported");
+ }
+ this._hashDeactivated[tag.id] = tag;
+ }
+ }
+ get(id) {
+ return this.has(id) ? this._hash[id] : undefined;
+ }
+ getAll() {
+ const hash = this._hash;
+ return Object.keys(hash)
+ .map((id) => {
+ return hash[id];
+ });
+ }
+ getAllDeactivated() {
+ const hashDeactivated = this._hashDeactivated;
+ return Object.keys(hashDeactivated)
+ .map((id) => {
+ return hashDeactivated[id];
+ });
+ }
+ getDeactivated(id) {
+ return this.hasDeactivated(id) ? this._hashDeactivated[id] : undefined;
+ }
+ has(id) {
+ return id in this._hash;
+ }
+ hasDeactivated(id) {
+ return id in this._hashDeactivated;
+ }
+ remove(ids) {
+ this._assertActivationState(true);
+ const hash = this._hash;
+ for (const id of ids) {
+ if (!(id in hash)) {
+ continue;
+ }
+ delete hash[id];
+ }
+ this._notifyChanged$.next(this);
+ }
+ removeAll() {
+ this._assertActivationState(true);
+ this._hash = {};
+ this._notifyChanged$.next(this);
+ }
+ removeAllDeactivated() {
+ this._assertActivationState(false);
+ this._hashDeactivated = {};
+ }
+ removeDeactivated(ids) {
+ this._assertActivationState(false);
+ const hashDeactivated = this._hashDeactivated;
+ for (const id of ids) {
+ if (!(id in hashDeactivated)) {
+ continue;
+ }
+ delete hashDeactivated[id];
+ }
+ }
+ _add(tag, transform) {
+ if (tag instanceof OutlineTag) {
+ this._hash[tag.id] = new OutlineRenderTag(tag, transform);
+ }
+ else if (tag instanceof SpotTag) {
+ this._hash[tag.id] = new SpotRenderTag(tag, transform);
+ }
+ else if (tag instanceof ExtremePointTag) {
+ this._hash[tag.id] = new ExtremePointRenderTag(tag, transform);
+ }
+ else {
+ throw new Error("Tag type not supported");
+ }
+ }
+ _assertActivationState(should) {
+ if (should !== this._active) {
+ throw new Error("Tag set not in correct state for operation.");
+ }
+ }
+}
+
+/**
+ * @class PointGeometry
+ *
+ * @classdesc Represents a point geometry in the 2D basic image coordinate system.
+ *
+ * @example
+ * ```js
+ * var basicPoint = [0.5, 0.7];
+ * var pointGeometry = new PointGeometry(basicPoint);
+ * ```
+ */
+class PointGeometry extends Geometry {
+ /**
+ * Create a point geometry.
+ *
+ * @constructor
+ * @param {Array<number>} point - An array representing the basic coordinates of
+ * the point.
+ *
+ * @throws {GeometryTagError} Point coordinates must be valid basic coordinates.
+ */
+ constructor(point) {
+ super();
+ let x = point[0];
+ let y = point[1];
+ if (x < 0 || x > 1 || y < 0 || y > 1) {
+ throw new GeometryTagError("Basic coordinates must be on the interval [0, 1].");
+ }
+ this._point = point.slice();
+ }
+ /**
+ * Get point property.
+ * @returns {Array<number>} Array representing the basic coordinates of the point.
+ */
+ get point() {
+ return this._point;
+ }
+ /**
+ * Get the 2D basic coordinates for the centroid of the point, i.e. the 2D
+ * basic coordinates of the point itself.
+ *
+ * @returns {Array<number>} 2D basic coordinates representing the centroid.
+ * @ignore
+ */
+ getCentroid2d() {
+ return this._point.slice();
+ }
+ /**
+ * Get the 3D world coordinates for the centroid of the point, i.e. the 3D
+ * world coordinates of the point itself.
+ *
+ * @param {Transform} transform - The transform of the image related to the point.
+ * @returns {Array<number>} 3D world coordinates representing the centroid.
+ * @ignore
+ */
+ getCentroid3d(transform) {
+ return transform.unprojectBasic(this._point, 200);
+ }
+ /**
+ * Set the centroid of the point, i.e. the point coordinates.
+ *
+ * @param {Array<number>} value - The new value of the centroid.
+ * @param {Transform} transform - The transform of the image related to the point.
+ * @ignore
+ */
+ setCentroid2d(value, transform) {
+ let changed = [
+ Math.max(0, Math.min(1, value[0])),
+ Math.max(0, Math.min(1, value[1])),
+ ];
+ this._point[0] = changed[0];
+ this._point[1] = changed[1];
+ this._notifyChanged$.next(this);
+ }
+}
+
+class TagHandlerBase extends HandlerBase {
+ constructor(component, container, navigator, viewportCoords) {
+ super(component, container, navigator);
+ this._name = `${this._component.name}-${this._getNameExtension()}`;
+ this._viewportCoords = viewportCoords;
+ }
+ _getConfiguration(enable) {
+ return {};
+ }
+ _mouseEventToBasic(event, element, camera, transform, offsetX, offsetY) {
+ offsetX = offsetX != null ? offsetX : 0;
+ offsetY = offsetY != null ? offsetY : 0;
+ const [canvasX, canvasY] = this._viewportCoords.canvasPosition(event, element);
+ const basic = this._viewportCoords.canvasToBasic(canvasX - offsetX, canvasY - offsetY, element, transform, camera.perspective);
+ return basic;
+ }
+}
+
+class CreateHandlerBase extends TagHandlerBase {
+ constructor(component, container, navigator, viewportCoords, tagCreator) {
+ super(component, container, navigator, viewportCoords);
+ this._tagCreator = tagCreator;
+ this._geometryCreated$ = new Subject();
+ }
+ get geometryCreated$() {
+ return this._geometryCreated$;
+ }
+ _enable() {
+ this._enableCreate();
+ this._container.container.classList.add("component-tag-create");
+ }
+ _disable() {
+ this._container.container.classList.remove("component-tag-create");
+ this._disableCreate();
+ }
+ _validateBasic(basic) {
+ const x = basic[0];
+ const y = basic[1];
+ return 0 <= x && x <= 1 && 0 <= y && y <= 1;
+ }
+ _mouseEventToBasic$(mouseEvent$) {
+ return mouseEvent$.pipe(withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$), map(([event, camera, transform]) => {
+ return this._mouseEventToBasic(event, this._container.container, camera, transform);
+ }));
+ }
+}
+
+class CreatePointHandler extends CreateHandlerBase {
+ _enableCreate() {
+ this._container.mouseService.deferPixels(this._name, 4);
+ this._geometryCreatedSubscription = this._mouseEventToBasic$(this._container.mouseService.proximateClick$).pipe(filter(this._validateBasic), map((basic) => {
+ return new PointGeometry(basic);
+ }))
+ .subscribe(this._geometryCreated$);
+ }
+ _disableCreate() {
+ this._container.mouseService.undeferPixels(this._name);
+ this._geometryCreatedSubscription.unsubscribe();
+ }
+ _getNameExtension() {
+ return "create-point";
+ }
+}
+
+class CreateVertexHandler extends CreateHandlerBase {
+ _enableCreate() {
+ this._container.mouseService.deferPixels(this._name, 4);
+ const transformChanged$ = this._navigator.stateService.currentTransform$.pipe(map(() => { }), publishReplay(1), refCount());
+ this._deleteSubscription = transformChanged$.pipe(skip(1))
+ .subscribe(this._tagCreator.delete$);
+ const basicClick$ = this._mouseEventToBasic$(this._container.mouseService.proximateClick$).pipe(share());
+ this._createSubscription = transformChanged$.pipe(switchMap(() => {
+ return basicClick$.pipe(filter(this._validateBasic), take(1));
+ }))
+ .subscribe(this._create$);
+ this._setVertexSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+ return !!tag ?
+ combineLatest(of(tag), merge(this._container.mouseService.mouseMove$, this._container.mouseService.domMouseMove$), this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$) :
+ empty();
+ }))
+ .subscribe(([tag, event, camera, transform]) => {
+ const basicPoint = this._mouseEventToBasic(event, this._container.container, camera, transform);
+ this._setVertex2d(tag, basicPoint, transform);
+ });
+ this._addPointSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+ return !!tag ?
+ combineLatest(of(tag), basicClick$) :
+ empty();
+ }))
+ .subscribe(([tag, basicPoint]) => {
+ this._addPoint(tag, basicPoint);
+ });
+ this._geometryCreateSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+ return !!tag ?
+ tag.created$.pipe(map((t) => {
+ return t.geometry;
+ })) :
+ empty();
+ }))
+ .subscribe(this._geometryCreated$);
+ }
+ _disableCreate() {
+ this._container.mouseService.undeferPixels(this._name);
+ this._tagCreator.delete$.next(null);
+ this._addPointSubscription.unsubscribe();
+ this._createSubscription.unsubscribe();
+ this._deleteSubscription.unsubscribe();
+ this._geometryCreateSubscription.unsubscribe();
+ this._setVertexSubscription.unsubscribe();
+ }
+}
+
+class CreatePointsHandler extends CreateVertexHandler {
+ get _create$() {
+ return this._tagCreator.createPoints$;
+ }
+ _addPoint(tag, basicPoint) {
+ tag.geometry.addPoint2d(basicPoint);
+ }
+ _getNameExtension() {
+ return "create-points";
+ }
+ _setVertex2d(tag, basicPoint, transform) {
+ tag.geometry.setPoint2d((tag.geometry).points.length - 1, basicPoint, transform);
+ }
+}
+
+class CreatePolygonHandler extends CreateVertexHandler {
+ get _create$() {
+ return this._tagCreator.createPolygon$;
+ }
+ _addPoint(tag, basicPoint) {
+ tag.addPoint(basicPoint);
+ }
+ _getNameExtension() {
+ return "create-polygon";
+ }
+ _setVertex2d(tag, basicPoint, transform) {
+ tag.geometry.setVertex2d(tag.geometry.polygon.length - 2, basicPoint, transform);
+ }
+}
+
+class CreateRectHandler extends CreateVertexHandler {
+ get _create$() {
+ return this._tagCreator.createRect$;
+ }
+ _addPoint(tag, basicPoint) {
+ const rectGeometry = tag.geometry;
+ if (!rectGeometry.validate(basicPoint)) {
+ basicPoint = rectGeometry.getNonAdjustedVertex2d(3);
+ }
+ tag.addPoint(basicPoint);
+ }
+ _enable() {
+ super._enable();
+ this._initializeAnchorIndexingSubscription = this._tagCreator.tag$.pipe(filter((tag) => {
+ return !!tag;
+ }))
+ .subscribe((tag) => {
+ tag.geometry.initializeAnchorIndexing();
+ });
+ }
+ _disable() {
+ super._disable();
+ this._initializeAnchorIndexingSubscription.unsubscribe();
+ }
+ _getNameExtension() {
+ return "create-rect";
+ }
+ _setVertex2d(tag, basicPoint, transform) {
+ tag.geometry.setOppositeVertex2d(basicPoint, transform);
+ }
+}
+
+class CreateRectDragHandler extends CreateHandlerBase {
+ _enableCreate() {
+ this._container.mouseService.claimMouse(this._name, 2);
+ this._deleteSubscription = this._navigator.stateService.currentTransform$.pipe(map((transform) => { return null; }), skip(1))
+ .subscribe(this._tagCreator.delete$);
+ this._createSubscription = this._mouseEventToBasic$(this._container.mouseService.filtered$(this._name, this._container.mouseService.mouseDragStart$)).pipe(filter(this._validateBasic))
+ .subscribe(this._tagCreator.createRect$);
+ this._initializeAnchorIndexingSubscription = this._tagCreator.tag$.pipe(filter((tag) => {
+ return !!tag;
+ }))
+ .subscribe((tag) => {
+ tag.geometry.initializeAnchorIndexing();
+ });
+ const basicMouse$ = combineLatest(merge(this._container.mouseService.filtered$(this._name, this._container.mouseService.mouseMove$), this._container.mouseService.filtered$(this._name, this._container.mouseService.domMouseMove$)), this._container.renderService.renderCamera$).pipe(withLatestFrom(this._navigator.stateService.currentTransform$), map(([[event, camera], transform]) => {
+ return this._mouseEventToBasic(event, this._container.container, camera, transform);
+ }));
+ this._setVertexSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+ return !!tag ?
+ combineLatest(of(tag), basicMouse$, this._navigator.stateService.currentTransform$) :
+ empty();
+ }))
+ .subscribe(([tag, basicPoint, transform]) => {
+ tag.geometry.setOppositeVertex2d(basicPoint, transform);
+ });
+ const basicMouseDragEnd$ = this._container.mouseService.mouseDragEnd$.pipe(withLatestFrom(this._mouseEventToBasic$(this._container.mouseService.filtered$(this._name, this._container.mouseService.mouseDrag$)).pipe(filter(this._validateBasic)), (event, basicPoint) => {
+ return basicPoint;
+ }), share());
+ this._addPointSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+ return !!tag ?
+ combineLatest(of(tag), basicMouseDragEnd$) :
+ empty();
+ }))
+ .subscribe(([tag, basicPoint]) => {
+ const rectGeometry = tag.geometry;
+ if (!rectGeometry.validate(basicPoint)) {
+ basicPoint = rectGeometry.getNonAdjustedVertex2d(3);
+ }
+ tag.addPoint(basicPoint);
+ });
+ this._geometryCreatedSubscription = this._tagCreator.tag$.pipe(switchMap((tag) => {
+ return !!tag ?
+ tag.created$.pipe(map((t) => {
+ return t.geometry;
+ })) :
+ empty();
+ }))
+ .subscribe(this._geometryCreated$);
+ }
+ _disableCreate() {
+ this._container.mouseService.unclaimMouse(this._name);
+ this._tagCreator.delete$.next(null);
+ this._addPointSubscription.unsubscribe();
+ this._createSubscription.unsubscribe();
+ this._deleteSubscription.unsubscribe();
+ this._geometryCreatedSubscription.unsubscribe();
+ this._initializeAnchorIndexingSubscription.unsubscribe();
+ this._setVertexSubscription.unsubscribe();
+ }
+ _getNameExtension() {
+ return "create-rect-drag";
+ }
+}
+
+class EditVertexHandler extends TagHandlerBase {
+ constructor(component, container, navigator, viewportCoords, tagSet) {
+ super(component, container, navigator, viewportCoords);
+ this._tagSet = tagSet;
+ }
+ _enable() {
+ const interaction$ = this._tagSet.changed$.pipe(map((tagSet) => {
+ return tagSet.getAll();
+ }), switchMap((tags) => {
+ return from(tags).pipe(mergeMap((tag) => {
+ return tag.interact$;
+ }));
+ }), switchMap((interaction) => {
+ return concat(of(interaction), this._container.mouseService.documentMouseUp$.pipe(map(() => {
+ return { offsetX: 0, offsetY: 0, operation: TagOperation.None, tag: null };
+ }), first()));
+ }), share());
+ merge(this._container.mouseService.mouseMove$, this._container.mouseService.domMouseMove$).pipe(share());
+ this._claimMouseSubscription = interaction$.pipe(switchMap((interaction) => {
+ return !!interaction.tag ? this._container.mouseService.domMouseDragStart$ : empty();
+ }))
+ .subscribe(() => {
+ this._container.mouseService.claimMouse(this._name, 3);
+ });
+ this._cursorSubscription = interaction$.pipe(map((interaction) => {
+ return interaction.cursor;
+ }), distinctUntilChanged())
+ .subscribe((cursor) => {
+ const interactionCursors = ["crosshair", "move", "nesw-resize", "nwse-resize"];
+ for (const interactionCursor of interactionCursors) {
+ this._container.container.classList.remove(`component-tag-edit-${interactionCursor}`);
+ }
+ if (!!cursor) {
+ this._container.container.classList.add(`component-tag-edit-${cursor}`);
+ }
+ });
+ this._unclaimMouseSubscription = this._container.mouseService
+ .filtered$(this._name, this._container.mouseService.domMouseDragEnd$)
+ .subscribe((e) => {
+ this._container.mouseService.unclaimMouse(this._name);
+ });
+ this._preventDefaultSubscription = interaction$.pipe(switchMap((interaction) => {
+ return !!interaction.tag ?
+ this._container.mouseService.documentMouseMove$ :
+ empty();
+ }))
+ .subscribe((event) => {
+ event.preventDefault(); // prevent selection of content outside the viewer
+ });
+ this._updateGeometrySubscription = interaction$.pipe(switchMap((interaction) => {
+ if (interaction.operation === TagOperation.None || !interaction.tag) {
+ return empty();
+ }
+ const mouseDrag$ = this._container.mouseService
+ .filtered$(this._name, this._container.mouseService.domMouseDrag$).pipe(filter((event) => {
+ return this._viewportCoords.insideElement(event, this._container.container);
+ }));
+ return combineLatest(mouseDrag$, this._container.renderService.renderCamera$).pipe(withLatestFrom(of(interaction), this._navigator.stateService.currentTransform$, ([event, render], i, transform) => {
+ return [event, render, i, transform];
+ }));
+ }))
+ .subscribe(([mouseEvent, renderCamera, interaction, transform]) => {
+ const basic = this._mouseEventToBasic(mouseEvent, this._container.container, renderCamera, transform, interaction.offsetX, interaction.offsetY);
+ const geometry = interaction.tag.geometry;
+ if (interaction.operation === TagOperation.Centroid) {
+ geometry.setCentroid2d(basic, transform);
+ }
+ else if (interaction.operation === TagOperation.Vertex) {
+ geometry.setVertex2d(interaction.vertexIndex, basic, transform);
+ }
+ });
+ }
+ _disable() {
+ this._claimMouseSubscription.unsubscribe();
+ this._cursorSubscription.unsubscribe();
+ this._preventDefaultSubscription.unsubscribe();
+ this._unclaimMouseSubscription.unsubscribe();
+ this._updateGeometrySubscription.unsubscribe();
+ }
+ _getNameExtension() {
+ return "edit-vertex";
+ }
+}
+
+/**
+ * @class TagComponent
+ *
+ * @classdesc Component for showing and editing tags with different
+ * geometries composed from 2D basic image coordinates (see the
+ * {@link Viewer} class documentation for more information about coordinate
+ * systems).
+ *
+ * The `add` method is used for adding new tags or replacing
+ * tags already in the set. Tags are removed by id.
+ *
+ * If a tag already in the set has the same
+ * id as one of the tags added, the old tag will be removed and
+ * the added tag will take its place.
+ *
+ * The tag component mode can be set to either be non interactive or
+ * to be in creating mode of a certain geometry type.
+ *
+ * The tag properties can be updated at any time and the change will
+ * be visibile immediately.
+ *
+ * Tags are only relevant to a single image because they are based on
+ * 2D basic image coordinates. Tags related to a certain image should
+ * be removed when the viewer is moved to another image.
+ *
+ * To retrive and use the tag component
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ component: { tag: true } }, ...);
+ *
+ * var tagComponent = viewer.getComponent("tag");
+ * ```
+ */
+class TagComponent extends Component {
+ /** @ignore */
+ constructor(name, container, navigator) {
+ super(name, container, navigator);
+ this._tagDomRenderer = new TagDOMRenderer();
+ this._tagScene = new TagScene();
+ this._tagSet = new TagSet();
+ this._tagCreator = new TagCreator(this, navigator);
+ this._viewportCoords = new ViewportCoords();
+ this._createHandlers = {
+ "CreatePoint": new CreatePointHandler(this, container, navigator, this._viewportCoords, this._tagCreator),
+ "CreatePoints": new CreatePointsHandler(this, container, navigator, this._viewportCoords, this._tagCreator),
+ "CreatePolygon": new CreatePolygonHandler(this, container, navigator, this._viewportCoords, this._tagCreator),
+ "CreateRect": new CreateRectHandler(this, container, navigator, this._viewportCoords, this._tagCreator),
+ "CreateRectDrag": new CreateRectDragHandler(this, container, navigator, this._viewportCoords, this._tagCreator),
+ "Default": undefined,
+ };
+ this._editVertexHandler =
+ new EditVertexHandler(this, container, navigator, this._viewportCoords, this._tagSet);
+ this._renderTags$ = this._tagSet.changed$.pipe(map((tagSet) => {
+ const tags = tagSet.getAll();
+ // ensure that tags are always rendered in the same order
+ // to avoid hover tracking problems on first resize.
+ tags.sort((t1, t2) => {
+ const id1 = t1.tag.id;
+ const id2 = t2.tag.id;
+ if (id1 < id2) {
+ return -1;
+ }
+ if (id1 > id2) {
+ return 1;
+ }
+ return 0;
+ });
+ return tags;
+ }), share());
+ this._tagChanged$ = this._renderTags$.pipe(switchMap((tags) => {
+ return from(tags).pipe(mergeMap((tag) => {
+ return merge(tag.tag.changed$, tag.tag.geometryChanged$);
+ }));
+ }), share());
+ this._renderTagGLChanged$ = this._renderTags$.pipe(switchMap((tags) => {
+ return from(tags).pipe(mergeMap((tag) => {
+ return tag.glObjectsChanged$;
+ }));
+ }), share());
+ this._createGeometryChanged$ = this._tagCreator.tag$.pipe(switchMap((tag) => {
+ return tag != null ?
+ tag.geometryChanged$ :
+ empty();
+ }), share());
+ this._createGLObjectsChanged$ = this._tagCreator.tag$.pipe(switchMap((tag) => {
+ return tag != null ?
+ tag.glObjectsChanged$ :
+ empty();
+ }), share());
+ this._creatingConfiguration$ = this._configuration$.pipe(distinctUntilChanged((c1, c2) => {
+ return c1.mode === c2.mode;
+ }, (configuration) => {
+ return {
+ createColor: configuration.createColor,
+ mode: configuration.mode,
+ };
+ }), publishReplay(1), refCount());
+ this._creatingConfiguration$
+ .subscribe((configuration) => {
+ const type = "tagmode";
+ const event = {
+ mode: configuration.mode,
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ });
+ }
+ /**
+ * Add tags to the tag set or replace tags in the tag set.
+ *
+ * @description If a tag already in the set has the same
+ * id as one of the tags added, the old tag will be removed
+ * the added tag will take its place.
+ *
+ * @param {Array<Tag>} tags - Tags to add.
+ *
+ * @example
+ * ```js
+ * tagComponent.add([tag1, tag2]);
+ * ```
+ */
+ add(tags) {
+ if (this._activated) {
+ this._navigator.stateService.currentTransform$.pipe(first())
+ .subscribe((transform) => {
+ this._tagSet.add(tags, transform);
+ const renderTags = tags
+ .map((tag) => {
+ return this._tagSet.get(tag.id);
+ });
+ this._tagScene.add(renderTags);
+ });
+ }
+ else {
+ this._tagSet.addDeactivated(tags);
+ }
+ }
+ /**
+ * Calculate the smallest rectangle containing all the points
+ * in the points geometry.
+ *
+ * @description The result may be different depending on if the
+ * current image is an spherical or not. If the
+ * current image is an spherical the rectangle may
+ * wrap the horizontal border of the image.
+ *
+ * @returns {Promise<Array<number>>} Promise to the rectangle
+ * on the format specified for the {@link RectGeometry} in basic
+ * coordinates.
+ */
+ calculateRect(geometry) {
+ return new Promise((resolve, reject) => {
+ this._navigator.stateService.currentTransform$.pipe(first(), map((transform) => {
+ return geometry.getRect2d(transform);
+ }))
+ .subscribe((rect) => {
+ resolve(rect);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Force the creation of a geometry programatically using its
+ * current vertices.
+ *
+ * @description The method only has an effect when the tag
+ * mode is either of the following modes:
+ *
+ * {@link TagMode.CreatePoints}
+ * {@link TagMode.CreatePolygon}
+ * {@link TagMode.CreateRect}
+ * {@link TagMode.CreateRectDrag}
+ *
+ * In the case of points or polygon creation, only the created
+ * vertices are used, i.e. the mouse position is disregarded.
+ *
+ * In the case of rectangle creation the position of the mouse
+ * at the time of the method call is used as one of the vertices
+ * defining the rectangle.
+ *
+ * @fires geometrycreate
+ *
+ * @example
+ * ```js
+ * tagComponent.on("geometrycreate", function(geometry) {
+ * console.log(geometry);
+ * });
+ *
+ * tagComponent.create();
+ * ```
+ */
+ create() {
+ this._tagCreator.replayedTag$.pipe(first(), filter((tag) => {
+ return !!tag;
+ }))
+ .subscribe((tag) => {
+ tag.create();
+ });
+ }
+ /**
+ * Change the current tag mode.
+ *
+ * @description Change the tag mode to one of the create modes for creating new geometries.
+ *
+ * @param {TagMode} mode - New tag mode.
+ *
+ * @fires tagmode
+ *
+ * @example
+ * ```js
+ * tagComponent.changeMode(TagMode.CreateRect);
+ * ```
+ */
+ changeMode(mode) {
+ this.configure({ mode: mode });
+ }
+ fire(type, event) {
+ super.fire(type, event);
+ }
+ /**
+ * Returns the tag in the tag set with the specified id, or
+ * undefined if the id matches no tag.
+ *
+ * @param {string} tagId - Id of the tag.
+ *
+ * @example
+ * ```js
+ * var tag = tagComponent.get("tagId");
+ * ```
+ */
+ get(tagId) {
+ if (this._activated) {
+ const renderTag = this._tagSet.get(tagId);
+ return renderTag !== undefined ? renderTag.tag : undefined;
+ }
+ else {
+ return this._tagSet.getDeactivated(tagId);
+ }
+ }
+ /**
+ * Returns an array of all tags.
+ *
+ * @example
+ * ```js
+ * var tags = tagComponent.getAll();
+ * ```
+ */
+ getAll() {
+ if (this.activated) {
+ return this._tagSet
+ .getAll()
+ .map((renderTag) => {
+ return renderTag.tag;
+ });
+ }
+ else {
+ return this._tagSet.getAllDeactivated();
+ }
+ }
+ /**
+ * Returns an array of tag ids for tags that contain the specified point.
+ *
+ * @description The pixel point must lie inside the polygon or rectangle
+ * of an added tag for the tag id to be returned. Tag ids for
+ * tags that do not have a fill will also be returned if the point is inside
+ * the geometry of the tag. Tags with point geometries can not be retrieved.
+ *
+ * No tag ids will be returned for polygons rendered in cropped spherical or
+ * rectangles rendered in spherical.
+ *
+ * Notice that the pixelPoint argument requires x, y coordinates from pixel space.
+ *
+ * With this function, you can use the coordinates provided by mouse
+ * events to get information out of the tag component.
+ *
+ * If no tag at exist the pixel point, an empty array will be returned.
+ *
+ * @param {Array<number>} pixelPoint - Pixel coordinates on the viewer element.
+ * @returns {Promise<Array<string>>} Promise to the ids of the tags that
+ * contain the specified pixel point.
+ *
+ * @example
+ * ```js
+ * tagComponent.getTagIdsAt([100, 100])
+ * .then((tagIds) => { console.log(tagIds); });
+ * ```
+ */
+ getTagIdsAt(pixelPoint) {
+ return new Promise((resolve, reject) => {
+ this._container.renderService.renderCamera$.pipe(first(), map((render) => {
+ const viewport = this._viewportCoords
+ .canvasToViewport(pixelPoint[0], pixelPoint[1], this._container.container);
+ const ids = this._tagScene.intersectObjects(viewport, render.perspective);
+ return ids;
+ }))
+ .subscribe((ids) => {
+ resolve(ids);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Check if a tag exist in the tag set.
+ *
+ * @param {string} tagId - Id of the tag.
+ *
+ * @example
+ * ```js
+ * var tagExists = tagComponent.has("tagId");
+ * ```
+ */
+ has(tagId) {
+ return this._activated ? this._tagSet.has(tagId) : this._tagSet.hasDeactivated(tagId);
+ }
+ off(type, handler) {
+ super.off(type, handler);
+ }
+ on(type, handler) {
+ super.on(type, handler);
+ }
+ /**
+ * Remove tags with the specified ids from the tag set.
+ *
+ * @param {Array<string>} tagIds - Ids for tags to remove.
+ *
+ * @example
+ * ```js
+ * tagComponent.remove(["id-1", "id-2"]);
+ * ```
+ */
+ remove(tagIds) {
+ if (this._activated) {
+ this._tagSet.remove(tagIds);
+ this._tagScene.remove(tagIds);
+ }
+ else {
+ this._tagSet.removeDeactivated(tagIds);
+ }
+ }
+ /**
+ * Remove all tags from the tag set.
+ *
+ * @example
+ * ```js
+ * tagComponent.removeAll();
+ * ```
+ */
+ removeAll() {
+ if (this._activated) {
+ this._tagSet.removeAll();
+ this._tagScene.removeAll();
+ }
+ else {
+ this._tagSet.removeAllDeactivated();
+ }
+ }
+ _activate() {
+ this._editVertexHandler.enable();
+ const handlerGeometryCreated$ = from(Object.keys(this._createHandlers)).pipe(map((key) => {
+ return this._createHandlers[key];
+ }), filter((handler) => {
+ return !!handler;
+ }), mergeMap((handler) => {
+ return handler.geometryCreated$;
+ }), share());
+ const subs = this._subscriptions;
+ subs.push(handlerGeometryCreated$
+ .subscribe((geometry) => {
+ const type = "geometrycreate";
+ const event = {
+ geometry,
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ }));
+ subs.push(this._tagCreator.tag$.pipe(skipWhile((tag) => {
+ return tag == null;
+ }), distinctUntilChanged())
+ .subscribe((tag) => {
+ const type = tag != null ?
+ "tagcreatestart" :
+ "tagcreateend";
+ const event = {
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ }));
+ subs.push(handlerGeometryCreated$
+ .subscribe(() => {
+ this.changeMode(TagMode.Default);
+ }));
+ subs.push(this._creatingConfiguration$
+ .subscribe((configuration) => {
+ this._disableCreateHandlers();
+ const mode = TagMode[configuration.mode];
+ const handler = this._createHandlers[mode];
+ if (!!handler) {
+ handler.enable();
+ }
+ }));
+ subs.push(this._renderTags$
+ .subscribe(() => {
+ const type = "tags";
+ const event = {
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ }));
+ subs.push(this._tagCreator.tag$.pipe(switchMap((tag) => {
+ return tag != null ?
+ tag.aborted$.pipe(map(() => { return null; })) :
+ empty();
+ }))
+ .subscribe(() => { this.changeMode(TagMode.Default); }));
+ subs.push(this._tagCreator.tag$
+ .subscribe((tag) => {
+ if (this._tagScene.hasCreateTag()) {
+ this._tagScene.removeCreateTag();
+ }
+ if (tag != null) {
+ this._tagScene.addCreateTag(tag);
+ }
+ }));
+ subs.push(this._createGLObjectsChanged$
+ .subscribe((tag) => {
+ this._tagScene.updateCreateTagObjects(tag);
+ }));
+ subs.push(this._renderTagGLChanged$
+ .subscribe((tag) => {
+ this._tagScene.updateObjects(tag);
+ }));
+ subs.push(this._tagChanged$
+ .subscribe(() => {
+ this._tagScene.update();
+ }));
+ subs.push(combineLatest(this._renderTags$.pipe(startWith([]), tap(() => {
+ this._container.domRenderer.render$.next({
+ name: this._name,
+ vNode: this._tagDomRenderer.clear(),
+ });
+ })), this._container.renderService.renderCamera$, this._container.spriteService.spriteAtlas$, this._container.renderService.size$, this._tagChanged$.pipe(startWith(null)), merge(this._tagCreator.tag$, this._createGeometryChanged$).pipe(startWith(null))).pipe(map(([renderTags, rc, atlas, size, , ct]) => {
+ return {
+ name: this._name,
+ vNode: this._tagDomRenderer.render(renderTags, ct, atlas, rc.perspective, size),
+ };
+ }))
+ .subscribe(this._container.domRenderer.render$));
+ subs.push(this._navigator.stateService.currentState$.pipe(map((frame) => {
+ const tagScene = this._tagScene;
+ return {
+ name: this._name,
+ renderer: {
+ frameId: frame.id,
+ needsRender: tagScene.needsRender,
+ render: tagScene.render.bind(tagScene),
+ pass: RenderPass$1.Opaque,
+ },
+ };
+ }))
+ .subscribe(this._container.glRenderer.render$));
+ this._navigator.stateService.currentTransform$.pipe(first())
+ .subscribe((transform) => {
+ this._tagSet.activate(transform);
+ this._tagScene.add(this._tagSet.getAll());
+ });
+ }
+ _deactivate() {
+ this._editVertexHandler.disable();
+ this._disableCreateHandlers();
+ this._tagScene.clear();
+ this._tagSet.deactivate();
+ this._tagCreator.delete$.next(null);
+ this._subscriptions.unsubscribe();
+ this._container.container.classList.remove("component-tag-create");
+ }
+ _getDefaultConfiguration() {
+ return {
+ createColor: 0xFFFFFF,
+ indicatePointsCompleter: true,
+ mode: TagMode.Default,
+ };
+ }
+ _disableCreateHandlers() {
+ const createHandlers = this._createHandlers;
+ for (const key in createHandlers) {
+ if (!createHandlers.hasOwnProperty(key)) {
+ continue;
+ }
+ const handler = createHandlers[key];
+ if (!!handler) {
+ handler.disable();
+ }
+ }
+ }
+}
+/** @inheritdoc */
+TagComponent.componentName = "tag";
+
+/**
+ * @class ZoomComponent
+ *
+ * @classdesc Component rendering UI elements used for zooming.
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({ ... });
+ *
+ * var zoomComponent = viewer.getComponent("zoom");
+ * zoomComponent.configure({ size: ComponentSize.Small });
+ * ```
+ */
+class ZoomComponent extends Component {
+ constructor(name, container, navigator) {
+ super(name, container, navigator);
+ this._viewportCoords = new ViewportCoords();
+ this._zoomDelta$ = new Subject();
+ }
+ _activate() {
+ const subs = this._subscriptions;
+ subs.push(combineLatest(this._navigator.stateService.currentState$, this._navigator.stateService.state$, this._configuration$, this._container.renderService.size$).pipe(map(([frame, state, configuration, size]) => {
+ const zoom = frame.state.zoom;
+ const zoomInIcon = virtualDom.h("div.mapillary-zoom-in-icon", []);
+ const zoomInButton = zoom >= 3 || state === State.Waiting ?
+ virtualDom.h("div.mapillary-zoom-in-button-inactive", [zoomInIcon]) :
+ virtualDom.h("div.mapillary-zoom-in-button", { onclick: () => { this._zoomDelta$.next(1); } }, [zoomInIcon]);
+ const zoomOutIcon = virtualDom.h("div.mapillary-zoom-out-icon", []);
+ const zoomOutButton = zoom <= 0 || state === State.Waiting ?
+ virtualDom.h("div.mapillary-zoom-out-button-inactive", [zoomOutIcon]) :
+ virtualDom.h("div.mapillary-zoom-out-button", { onclick: () => { this._zoomDelta$.next(-1); } }, [zoomOutIcon]);
+ const compact = configuration.size === ComponentSize.Small ||
+ configuration.size === ComponentSize.Automatic && size.width < 640 ?
+ ".mapillary-zoom-compact" : "";
+ return {
+ name: this._name,
+ vNode: virtualDom.h("div.mapillary-zoom-container" + compact, { oncontextmenu: (event) => { event.preventDefault(); } }, [zoomInButton, zoomOutButton]),
+ };
+ }))
+ .subscribe(this._container.domRenderer.render$));
+ subs.push(this._zoomDelta$.pipe(withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$))
+ .subscribe(([zoomDelta, render, transform]) => {
+ const unprojected = this._viewportCoords.unprojectFromViewport(0, 0, render.perspective);
+ const reference = transform.projectBasic(unprojected.toArray());
+ this._navigator.stateService.zoomIn(zoomDelta, reference);
+ }));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ }
+ _getDefaultConfiguration() {
+ return { size: ComponentSize.Automatic };
+ }
+}
+ZoomComponent.componentName = "zoom";
+
+class ImageFallbackComponent extends Component {
+ constructor(name, container, navigator, dom) {
+ super(name, container, navigator);
+ this._canvasId = `${container.id}-${this._name}`;
+ this._dom = !!dom ? dom : new DOM();
+ }
+ _activate() {
+ const canvasSize$ = this._container.domRenderer.element$.pipe(map(() => {
+ return this._dom.document.getElementById(this._canvasId);
+ }), filter((canvas) => {
+ return !!canvas;
+ }), map((canvas) => {
+ const adaptableDomRenderer = canvas.parentElement;
+ const width = adaptableDomRenderer.offsetWidth;
+ const height = adaptableDomRenderer.offsetHeight;
+ return [canvas, { height: height, width: width }];
+ }), distinctUntilChanged((s1, s2) => {
+ return s1.height === s2.height && s1.width === s2.width;
+ }, ([, size]) => {
+ return size;
+ }));
+ this._subscriptions.push(combineLatest(canvasSize$, this._navigator.stateService.currentImage$)
+ .subscribe(([[canvas, size], image]) => {
+ canvas.width = size.width;
+ canvas.height = size.height;
+ canvas
+ .getContext("2d")
+ .drawImage(image.image, 0, 0, size.width, size.height);
+ }));
+ this._container.domRenderer.renderAdaptive$.next({ name: this._name, vNode: virtualDom.h(`canvas#${this._canvasId}`, []) });
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ }
+ _getDefaultConfiguration() {
+ return {};
+ }
+}
+ImageFallbackComponent.componentName = "imagefallback";
+
+/**
+ * @class NavigationFallbackComponent
+ *
+ * @classdesc Fallback navigation component for environments without WebGL support.
+ *
+ * Replaces the functionality in the Direction and Sequence components.
+ */
+class NavigationFallbackComponent extends Component {
+ /** @ignore */
+ constructor(name, container, navigator) {
+ super(name, container, navigator);
+ this._seqNames = {};
+ this._seqNames[NavigationDirection[NavigationDirection.Prev]] = "-prev";
+ this._seqNames[NavigationDirection[NavigationDirection.Next]] = "-next";
+ this._spaTopNames = {};
+ this._spaTopNames[NavigationDirection[NavigationDirection.TurnLeft]] = "-turn-left";
+ this._spaTopNames[NavigationDirection[NavigationDirection.StepLeft]] = "-left";
+ this._spaTopNames[NavigationDirection[NavigationDirection.StepForward]] = "-forward";
+ this._spaTopNames[NavigationDirection[NavigationDirection.StepRight]] = "-right";
+ this._spaTopNames[NavigationDirection[NavigationDirection.TurnRight]] = "-turn-right";
+ this._spaBottomNames = {};
+ this._spaBottomNames[NavigationDirection[NavigationDirection.TurnU]] = "-turn-around";
+ this._spaBottomNames[NavigationDirection[NavigationDirection.StepBackward]] = "-backward";
+ }
+ _activate() {
+ this._subscriptions.push(combineLatest(this._navigator.stateService.currentImage$, this._configuration$).pipe(switchMap(([image, configuration]) => {
+ const sequenceEdges$ = configuration.sequence ?
+ image.sequenceEdges$.pipe(map((status) => {
+ return status.edges
+ .map((edge) => {
+ return edge.data.direction;
+ });
+ })) :
+ of([]);
+ const spatialEdges$ = !isSpherical(image.cameraType) &&
+ configuration.spatial ?
+ image.spatialEdges$.pipe(map((status) => {
+ return status.edges
+ .map((edge) => {
+ return edge.data.direction;
+ });
+ })) :
+ of([]);
+ return combineLatest(sequenceEdges$, spatialEdges$).pipe(map(([seq, spa]) => {
+ return seq.concat(spa);
+ }));
+ }), map((edgeDirections) => {
+ const seqs = this._createArrowRow(this._seqNames, edgeDirections);
+ const spaTops = this._createArrowRow(this._spaTopNames, edgeDirections);
+ const spaBottoms = this._createArrowRow(this._spaBottomNames, edgeDirections);
+ const seqContainer = virtualDom.h(`div.mapillary-navigation-sequence`, seqs);
+ const spaTopContainer = virtualDom.h(`div.NavigationSpatialTop`, spaTops);
+ const spaBottomContainer = virtualDom.h(`div.mapillary-navigation-spatial-bottom`, spaBottoms);
+ const spaContainer = virtualDom.h(`div.mapillary-navigation-spatial`, [spaTopContainer, spaBottomContainer]);
+ return { name: this._name, vNode: virtualDom.h(`div.NavigationContainer`, [seqContainer, spaContainer]) };
+ }))
+ .subscribe(this._container.domRenderer.render$));
+ }
+ _deactivate() {
+ this._subscriptions.unsubscribe();
+ }
+ _getDefaultConfiguration() {
+ return { sequence: true, spatial: true };
+ }
+ _createArrowRow(arrowNames, edgeDirections) {
+ const arrows = [];
+ for (const arrowName in arrowNames) {
+ if (!(arrowNames.hasOwnProperty(arrowName))) {
+ continue;
+ }
+ const direction = NavigationDirection[arrowName];
+ if (edgeDirections.indexOf(direction) !== -1) {
+ arrows.push(this._createVNode(direction, arrowNames[arrowName], "visible"));
+ }
+ else {
+ arrows.push(this._createVNode(direction, arrowNames[arrowName], "hidden"));
+ }
+ }
+ return arrows;
+ }
+ _createVNode(direction, name, visibility) {
+ return virtualDom.h(`span.mapillary-navigation-button.mapillary-navigation${name}`, {
+ onclick: () => {
+ this._navigator.moveDir$(direction)
+ .subscribe(undefined, (error) => {
+ if (!(error instanceof CancelMapillaryError)) {
+ console.error(error);
+ }
+ });
+ },
+ style: {
+ visibility: visibility,
+ },
+ }, []);
+ }
+}
+NavigationFallbackComponent.componentName = "navigationfallback";
+
+/*! pako 2.0.3 https://github.com/nodeca/pako @license (MIT AND Zlib) */
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+/* eslint-disable space-unary-ops */
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+
+//const Z_FILTERED = 1;
+//const Z_HUFFMAN_ONLY = 2;
+//const Z_RLE = 3;
+const Z_FIXED = 4;
+//const Z_DEFAULT_STRATEGY = 0;
+
+/* Possible values of the data_type field (though see inflate()) */
+const Z_BINARY = 0;
+const Z_TEXT = 1;
+//const Z_ASCII = 1; // = Z_TEXT
+const Z_UNKNOWN = 2;
+
+/*============================================================================*/
+
+
+function zero(buf) { let len = buf.length; while (--len >= 0) { buf[len] = 0; } }
+
+// From zutil.h
+
+const STORED_BLOCK = 0;
+const STATIC_TREES = 1;
+const DYN_TREES = 2;
+/* The three kinds of block type */
+
+const MIN_MATCH = 3;
+const MAX_MATCH = 258;
+/* The minimum and maximum match lengths */
+
+// From deflate.h
+/* ===========================================================================
+ * Internal compression state.
+ */
+
+const LENGTH_CODES = 29;
+/* number of length codes, not counting the special END_BLOCK code */
+
+const LITERALS = 256;
+/* number of literal bytes 0..255 */
+
+const L_CODES = LITERALS + 1 + LENGTH_CODES;
+/* number of Literal or Length codes, including the END_BLOCK code */
+
+const D_CODES = 30;
+/* number of distance codes */
+
+const BL_CODES = 19;
+/* number of codes used to transfer the bit lengths */
+
+const HEAP_SIZE = 2 * L_CODES + 1;
+/* maximum heap size */
+
+const MAX_BITS = 15;
+/* All codes must not exceed MAX_BITS bits */
+
+const Buf_size = 16;
+/* size of bit buffer in bi_buf */
+
+
+/* ===========================================================================
+ * Constants
+ */
+
+const MAX_BL_BITS = 7;
+/* Bit length codes must not exceed MAX_BL_BITS bits */
+
+const END_BLOCK = 256;
+/* end of block literal code */
+
+const REP_3_6 = 16;
+/* repeat previous bit length 3-6 times (2 bits of repeat count) */
+
+const REPZ_3_10 = 17;
+/* repeat a zero length 3-10 times (3 bits of repeat count) */
+
+const REPZ_11_138 = 18;
+/* repeat a zero length 11-138 times (7 bits of repeat count) */
+
+/* eslint-disable comma-spacing,array-bracket-spacing */
+const extra_lbits = /* extra bits for each length code */
+ new Uint8Array([0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0]);
+
+const extra_dbits = /* extra bits for each distance code */
+ new Uint8Array([0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13]);
+
+const extra_blbits = /* extra bits for each bit length code */
+ new Uint8Array([0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,3,7]);
+
+const bl_order =
+ new Uint8Array([16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15]);
+/* eslint-enable comma-spacing,array-bracket-spacing */
+
+/* The lengths of the bit length codes are sent in order of decreasing
+ * probability, to avoid transmitting the lengths for unused bit length codes.
+ */
+
+/* ===========================================================================
+ * Local data. These are initialized only once.
+ */
+
+// We pre-fill arrays with 0 to avoid uninitialized gaps
+
+const DIST_CODE_LEN = 512; /* see definition of array dist_code below */
+
+// !!!! Use flat array instead of structure, Freq = i*2, Len = i*2+1
+const static_ltree = new Array((L_CODES + 2) * 2);
+zero(static_ltree);
+/* The static literal tree. Since the bit lengths are imposed, there is no
+ * need for the L_CODES extra codes used during heap construction. However
+ * The codes 286 and 287 are needed to build a canonical tree (see _tr_init
+ * below).
+ */
+
+const static_dtree = new Array(D_CODES * 2);
+zero(static_dtree);
+/* The static distance tree. (Actually a trivial tree since all codes use
+ * 5 bits.)
+ */
+
+const _dist_code = new Array(DIST_CODE_LEN);
+zero(_dist_code);
+/* Distance codes. The first 256 values correspond to the distances
+ * 3 .. 258, the last 256 values correspond to the top 8 bits of
+ * the 15 bit distances.
+ */
+
+const _length_code = new Array(MAX_MATCH - MIN_MATCH + 1);
+zero(_length_code);
+/* length code for each normalized match length (0 == MIN_MATCH) */
+
+const base_length = new Array(LENGTH_CODES);
+zero(base_length);
+/* First normalized length for each code (0 = MIN_MATCH) */
+
+const base_dist = new Array(D_CODES);
+zero(base_dist);
+/* First normalized distance for each code (0 = distance of 1) */
+
+
+function StaticTreeDesc(static_tree, extra_bits, extra_base, elems, max_length) {
+
+ this.static_tree = static_tree; /* static tree or NULL */
+ this.extra_bits = extra_bits; /* extra bits for each code or NULL */
+ this.extra_base = extra_base; /* base index for extra_bits */
+ this.elems = elems; /* max number of elements in the tree */
+ this.max_length = max_length; /* max bit length for the codes */
+
+ // show if `static_tree` has data or dummy - needed for monomorphic objects
+ this.has_stree = static_tree && static_tree.length;
+}
+
+
+let static_l_desc;
+let static_d_desc;
+let static_bl_desc;
+
+
+function TreeDesc(dyn_tree, stat_desc) {
+ this.dyn_tree = dyn_tree; /* the dynamic tree */
+ this.max_code = 0; /* largest code with non zero frequency */
+ this.stat_desc = stat_desc; /* the corresponding static tree */
+}
+
+
+
+const d_code = (dist) => {
+
+ return dist < 256 ? _dist_code[dist] : _dist_code[256 + (dist >>> 7)];
+};
+
+
+/* ===========================================================================
+ * Output a short LSB first on the stream.
+ * IN assertion: there is enough room in pendingBuf.
+ */
+const put_short = (s, w) => {
+// put_byte(s, (uch)((w) & 0xff));
+// put_byte(s, (uch)((ush)(w) >> 8));
+ s.pending_buf[s.pending++] = (w) & 0xff;
+ s.pending_buf[s.pending++] = (w >>> 8) & 0xff;
+};
+
+
+/* ===========================================================================
+ * Send a value on a given number of bits.
+ * IN assertion: length <= 16 and value fits in length bits.
+ */
+const send_bits = (s, value, length) => {
+
+ if (s.bi_valid > (Buf_size - length)) {
+ s.bi_buf |= (value << s.bi_valid) & 0xffff;
+ put_short(s, s.bi_buf);
+ s.bi_buf = value >> (Buf_size - s.bi_valid);
+ s.bi_valid += length - Buf_size;
+ } else {
+ s.bi_buf |= (value << s.bi_valid) & 0xffff;
+ s.bi_valid += length;
+ }
+};
+
+
+const send_code = (s, c, tree) => {
+
+ send_bits(s, tree[c * 2]/*.Code*/, tree[c * 2 + 1]/*.Len*/);
+};
+
+
+/* ===========================================================================
+ * Reverse the first len bits of a code, using straightforward code (a faster
+ * method would use a table)
+ * IN assertion: 1 <= len <= 15
+ */
+const bi_reverse = (code, len) => {
+
+ let res = 0;
+ do {
+ res |= code & 1;
+ code >>>= 1;
+ res <<= 1;
+ } while (--len > 0);
+ return res >>> 1;
+};
+
+
+/* ===========================================================================
+ * Flush the bit buffer, keeping at most 7 bits in it.
+ */
+const bi_flush = (s) => {
+
+ if (s.bi_valid === 16) {
+ put_short(s, s.bi_buf);
+ s.bi_buf = 0;
+ s.bi_valid = 0;
+
+ } else if (s.bi_valid >= 8) {
+ s.pending_buf[s.pending++] = s.bi_buf & 0xff;
+ s.bi_buf >>= 8;
+ s.bi_valid -= 8;
+ }
+};
+
+
+/* ===========================================================================
+ * Compute the optimal bit lengths for a tree and update the total bit length
+ * for the current block.
+ * IN assertion: the fields freq and dad are set, heap[heap_max] and
+ * above are the tree nodes sorted by increasing frequency.
+ * OUT assertions: the field len is set to the optimal bit length, the
+ * array bl_count contains the frequencies for each bit length.
+ * The length opt_len is updated; static_len is also updated if stree is
+ * not null.
+ */
+const gen_bitlen = (s, desc) =>
+// deflate_state *s;
+// tree_desc *desc; /* the tree descriptor */
+{
+ const tree = desc.dyn_tree;
+ const max_code = desc.max_code;
+ const stree = desc.stat_desc.static_tree;
+ const has_stree = desc.stat_desc.has_stree;
+ const extra = desc.stat_desc.extra_bits;
+ const base = desc.stat_desc.extra_base;
+ const max_length = desc.stat_desc.max_length;
+ let h; /* heap index */
+ let n, m; /* iterate over the tree elements */
+ let bits; /* bit length */
+ let xbits; /* extra bits */
+ let f; /* frequency */
+ let overflow = 0; /* number of elements with bit length too large */
+
+ for (bits = 0; bits <= MAX_BITS; bits++) {
+ s.bl_count[bits] = 0;
+ }
+
+ /* In a first pass, compute the optimal bit lengths (which may
+ * overflow in the case of the bit length tree).
+ */
+ tree[s.heap[s.heap_max] * 2 + 1]/*.Len*/ = 0; /* root of the heap */
+
+ for (h = s.heap_max + 1; h < HEAP_SIZE; h++) {
+ n = s.heap[h];
+ bits = tree[tree[n * 2 + 1]/*.Dad*/ * 2 + 1]/*.Len*/ + 1;
+ if (bits > max_length) {
+ bits = max_length;
+ overflow++;
+ }
+ tree[n * 2 + 1]/*.Len*/ = bits;
+ /* We overwrite tree[n].Dad which is no longer needed */
+
+ if (n > max_code) { continue; } /* not a leaf node */
+
+ s.bl_count[bits]++;
+ xbits = 0;
+ if (n >= base) {
+ xbits = extra[n - base];
+ }
+ f = tree[n * 2]/*.Freq*/;
+ s.opt_len += f * (bits + xbits);
+ if (has_stree) {
+ s.static_len += f * (stree[n * 2 + 1]/*.Len*/ + xbits);
+ }
+ }
+ if (overflow === 0) { return; }
+
+ // Trace((stderr,"\nbit length overflow\n"));
+ /* This happens for example on obj2 and pic of the Calgary corpus */
+
+ /* Find the first bit length which could increase: */
+ do {
+ bits = max_length - 1;
+ while (s.bl_count[bits] === 0) { bits--; }
+ s.bl_count[bits]--; /* move one leaf down the tree */
+ s.bl_count[bits + 1] += 2; /* move one overflow item as its brother */
+ s.bl_count[max_length]--;
+ /* The brother of the overflow item also moves one step up,
+ * but this does not affect bl_count[max_length]
+ */
+ overflow -= 2;
+ } while (overflow > 0);
+
+ /* Now recompute all bit lengths, scanning in increasing frequency.
+ * h is still equal to HEAP_SIZE. (It is simpler to reconstruct all
+ * lengths instead of fixing only the wrong ones. This idea is taken
+ * from 'ar' written by Haruhiko Okumura.)
+ */
+ for (bits = max_length; bits !== 0; bits--) {
+ n = s.bl_count[bits];
+ while (n !== 0) {
+ m = s.heap[--h];
+ if (m > max_code) { continue; }
+ if (tree[m * 2 + 1]/*.Len*/ !== bits) {
+ // Trace((stderr,"code %d bits %d->%d\n", m, tree[m].Len, bits));
+ s.opt_len += (bits - tree[m * 2 + 1]/*.Len*/) * tree[m * 2]/*.Freq*/;
+ tree[m * 2 + 1]/*.Len*/ = bits;
+ }
+ n--;
+ }
+ }
+};
+
+
+/* ===========================================================================
+ * Generate the codes for a given tree and bit counts (which need not be
+ * optimal).
+ * IN assertion: the array bl_count contains the bit length statistics for
+ * the given tree and the field len is set for all tree elements.
+ * OUT assertion: the field code is set for all tree elements of non
+ * zero code length.
+ */
+const gen_codes = (tree, max_code, bl_count) =>
+// ct_data *tree; /* the tree to decorate */
+// int max_code; /* largest code with non zero frequency */
+// ushf *bl_count; /* number of codes at each bit length */
+{
+ const next_code = new Array(MAX_BITS + 1); /* next code value for each bit length */
+ let code = 0; /* running code value */
+ let bits; /* bit index */
+ let n; /* code index */
+
+ /* The distribution counts are first used to generate the code values
+ * without bit reversal.
+ */
+ for (bits = 1; bits <= MAX_BITS; bits++) {
+ next_code[bits] = code = (code + bl_count[bits - 1]) << 1;
+ }
+ /* Check that the bit counts in bl_count are consistent. The last code
+ * must be all ones.
+ */
+ //Assert (code + bl_count[MAX_BITS]-1 == (1<<MAX_BITS)-1,
+ // "inconsistent bit counts");
+ //Tracev((stderr,"\ngen_codes: max_code %d ", max_code));
+
+ for (n = 0; n <= max_code; n++) {
+ let len = tree[n * 2 + 1]/*.Len*/;
+ if (len === 0) { continue; }
+ /* Now reverse the bits */
+ tree[n * 2]/*.Code*/ = bi_reverse(next_code[len]++, len);
+
+ //Tracecv(tree != static_ltree, (stderr,"\nn %3d %c l %2d c %4x (%x) ",
+ // n, (isgraph(n) ? n : ' '), len, tree[n].Code, next_code[len]-1));
+ }
+};
+
+
+/* ===========================================================================
+ * Initialize the various 'constant' tables.
+ */
+const tr_static_init = () => {
+
+ let n; /* iterates over tree elements */
+ let bits; /* bit counter */
+ let length; /* length value */
+ let code; /* code value */
+ let dist; /* distance index */
+ const bl_count = new Array(MAX_BITS + 1);
+ /* number of codes at each bit length for an optimal tree */
+
+ // do check in _tr_init()
+ //if (static_init_done) return;
+
+ /* For some embedded targets, global variables are not initialized: */
+/*#ifdef NO_INIT_GLOBAL_POINTERS
+ static_l_desc.static_tree = static_ltree;
+ static_l_desc.extra_bits = extra_lbits;
+ static_d_desc.static_tree = static_dtree;
+ static_d_desc.extra_bits = extra_dbits;
+ static_bl_desc.extra_bits = extra_blbits;
+#endif*/
+
+ /* Initialize the mapping length (0..255) -> length code (0..28) */
+ length = 0;
+ for (code = 0; code < LENGTH_CODES - 1; code++) {
+ base_length[code] = length;
+ for (n = 0; n < (1 << extra_lbits[code]); n++) {
+ _length_code[length++] = code;
+ }
+ }
+ //Assert (length == 256, "tr_static_init: length != 256");
+ /* Note that the length 255 (match length 258) can be represented
+ * in two different ways: code 284 + 5 bits or code 285, so we
+ * overwrite length_code[255] to use the best encoding:
+ */
+ _length_code[length - 1] = code;
+
+ /* Initialize the mapping dist (0..32K) -> dist code (0..29) */
+ dist = 0;
+ for (code = 0; code < 16; code++) {
+ base_dist[code] = dist;
+ for (n = 0; n < (1 << extra_dbits[code]); n++) {
+ _dist_code[dist++] = code;
+ }
+ }
+ //Assert (dist == 256, "tr_static_init: dist != 256");
+ dist >>= 7; /* from now on, all distances are divided by 128 */
+ for (; code < D_CODES; code++) {
+ base_dist[code] = dist << 7;
+ for (n = 0; n < (1 << (extra_dbits[code] - 7)); n++) {
+ _dist_code[256 + dist++] = code;
+ }
+ }
+ //Assert (dist == 256, "tr_static_init: 256+dist != 512");
+
+ /* Construct the codes of the static literal tree */
+ for (bits = 0; bits <= MAX_BITS; bits++) {
+ bl_count[bits] = 0;
+ }
+
+ n = 0;
+ while (n <= 143) {
+ static_ltree[n * 2 + 1]/*.Len*/ = 8;
+ n++;
+ bl_count[8]++;
+ }
+ while (n <= 255) {
+ static_ltree[n * 2 + 1]/*.Len*/ = 9;
+ n++;
+ bl_count[9]++;
+ }
+ while (n <= 279) {
+ static_ltree[n * 2 + 1]/*.Len*/ = 7;
+ n++;
+ bl_count[7]++;
+ }
+ while (n <= 287) {
+ static_ltree[n * 2 + 1]/*.Len*/ = 8;
+ n++;
+ bl_count[8]++;
+ }
+ /* Codes 286 and 287 do not exist, but we must include them in the
+ * tree construction to get a canonical Huffman tree (longest code
+ * all ones)
+ */
+ gen_codes(static_ltree, L_CODES + 1, bl_count);
+
+ /* The static distance tree is trivial: */
+ for (n = 0; n < D_CODES; n++) {
+ static_dtree[n * 2 + 1]/*.Len*/ = 5;
+ static_dtree[n * 2]/*.Code*/ = bi_reverse(n, 5);
+ }
+
+ // Now data ready and we can init static trees
+ static_l_desc = new StaticTreeDesc(static_ltree, extra_lbits, LITERALS + 1, L_CODES, MAX_BITS);
+ static_d_desc = new StaticTreeDesc(static_dtree, extra_dbits, 0, D_CODES, MAX_BITS);
+ static_bl_desc = new StaticTreeDesc(new Array(0), extra_blbits, 0, BL_CODES, MAX_BL_BITS);
+
+ //static_init_done = true;
+};
+
+
+/* ===========================================================================
+ * Initialize a new block.
+ */
+const init_block = (s) => {
+
+ let n; /* iterates over tree elements */
+
+ /* Initialize the trees. */
+ for (n = 0; n < L_CODES; n++) { s.dyn_ltree[n * 2]/*.Freq*/ = 0; }
+ for (n = 0; n < D_CODES; n++) { s.dyn_dtree[n * 2]/*.Freq*/ = 0; }
+ for (n = 0; n < BL_CODES; n++) { s.bl_tree[n * 2]/*.Freq*/ = 0; }
+
+ s.dyn_ltree[END_BLOCK * 2]/*.Freq*/ = 1;
+ s.opt_len = s.static_len = 0;
+ s.last_lit = s.matches = 0;
+};
+
+
+/* ===========================================================================
+ * Flush the bit buffer and align the output on a byte boundary
+ */
+const bi_windup = (s) =>
+{
+ if (s.bi_valid > 8) {
+ put_short(s, s.bi_buf);
+ } else if (s.bi_valid > 0) {
+ //put_byte(s, (Byte)s->bi_buf);
+ s.pending_buf[s.pending++] = s.bi_buf;
+ }
+ s.bi_buf = 0;
+ s.bi_valid = 0;
+};
+
+/* ===========================================================================
+ * Copy a stored block, storing first the length and its
+ * one's complement if requested.
+ */
+const copy_block = (s, buf, len, header) =>
+//DeflateState *s;
+//charf *buf; /* the input data */
+//unsigned len; /* its length */
+//int header; /* true if block header must be written */
+{
+ bi_windup(s); /* align on byte boundary */
+
+ if (header) {
+ put_short(s, len);
+ put_short(s, ~len);
+ }
+// while (len--) {
+// put_byte(s, *buf++);
+// }
+ s.pending_buf.set(s.window.subarray(buf, buf + len), s.pending);
+ s.pending += len;
+};
+
+/* ===========================================================================
+ * Compares to subtrees, using the tree depth as tie breaker when
+ * the subtrees have equal frequency. This minimizes the worst case length.
+ */
+const smaller = (tree, n, m, depth) => {
+
+ const _n2 = n * 2;
+ const _m2 = m * 2;
+ return (tree[_n2]/*.Freq*/ < tree[_m2]/*.Freq*/ ||
+ (tree[_n2]/*.Freq*/ === tree[_m2]/*.Freq*/ && depth[n] <= depth[m]));
+};
+
+/* ===========================================================================
+ * Restore the heap property by moving down the tree starting at node k,
+ * exchanging a node with the smallest of its two sons if necessary, stopping
+ * when the heap property is re-established (each father smaller than its
+ * two sons).
+ */
+const pqdownheap = (s, tree, k) =>
+// deflate_state *s;
+// ct_data *tree; /* the tree to restore */
+// int k; /* node to move down */
+{
+ const v = s.heap[k];
+ let j = k << 1; /* left son of k */
+ while (j <= s.heap_len) {
+ /* Set j to the smallest of the two sons: */
+ if (j < s.heap_len &&
+ smaller(tree, s.heap[j + 1], s.heap[j], s.depth)) {
+ j++;
+ }
+ /* Exit if v is smaller than both sons */
+ if (smaller(tree, v, s.heap[j], s.depth)) { break; }
+
+ /* Exchange v with the smallest son */
+ s.heap[k] = s.heap[j];
+ k = j;
+
+ /* And continue down the tree, setting j to the left son of k */
+ j <<= 1;
+ }
+ s.heap[k] = v;
+};
+
+
+// inlined manually
+// const SMALLEST = 1;
+
+/* ===========================================================================
+ * Send the block data compressed using the given Huffman trees
+ */
+const compress_block = (s, ltree, dtree) =>
+// deflate_state *s;
+// const ct_data *ltree; /* literal tree */
+// const ct_data *dtree; /* distance tree */
+{
+ let dist; /* distance of matched string */
+ let lc; /* match length or unmatched char (if dist == 0) */
+ let lx = 0; /* running index in l_buf */
+ let code; /* the code to send */
+ let extra; /* number of extra bits to send */
+
+ if (s.last_lit !== 0) {
+ do {
+ dist = (s.pending_buf[s.d_buf + lx * 2] << 8) | (s.pending_buf[s.d_buf + lx * 2 + 1]);
+ lc = s.pending_buf[s.l_buf + lx];
+ lx++;
+
+ if (dist === 0) {
+ send_code(s, lc, ltree); /* send a literal byte */
+ //Tracecv(isgraph(lc), (stderr," '%c' ", lc));
+ } else {
+ /* Here, lc is the match length - MIN_MATCH */
+ code = _length_code[lc];
+ send_code(s, code + LITERALS + 1, ltree); /* send the length code */
+ extra = extra_lbits[code];
+ if (extra !== 0) {
+ lc -= base_length[code];
+ send_bits(s, lc, extra); /* send the extra length bits */
+ }
+ dist--; /* dist is now the match distance - 1 */
+ code = d_code(dist);
+ //Assert (code < D_CODES, "bad d_code");
+
+ send_code(s, code, dtree); /* send the distance code */
+ extra = extra_dbits[code];
+ if (extra !== 0) {
+ dist -= base_dist[code];
+ send_bits(s, dist, extra); /* send the extra distance bits */
+ }
+ } /* literal or match pair ? */
+
+ /* Check that the overlay between pending_buf and d_buf+l_buf is ok: */
+ //Assert((uInt)(s->pending) < s->lit_bufsize + 2*lx,
+ // "pendingBuf overflow");
+
+ } while (lx < s.last_lit);
+ }
+
+ send_code(s, END_BLOCK, ltree);
+};
+
+
+/* ===========================================================================
+ * Construct one Huffman tree and assigns the code bit strings and lengths.
+ * Update the total bit length for the current block.
+ * IN assertion: the field freq is set for all tree elements.
+ * OUT assertions: the fields len and code are set to the optimal bit length
+ * and corresponding code. The length opt_len is updated; static_len is
+ * also updated if stree is not null. The field max_code is set.
+ */
+const build_tree = (s, desc) =>
+// deflate_state *s;
+// tree_desc *desc; /* the tree descriptor */
+{
+ const tree = desc.dyn_tree;
+ const stree = desc.stat_desc.static_tree;
+ const has_stree = desc.stat_desc.has_stree;
+ const elems = desc.stat_desc.elems;
+ let n, m; /* iterate over heap elements */
+ let max_code = -1; /* largest code with non zero frequency */
+ let node; /* new node being created */
+
+ /* Construct the initial heap, with least frequent element in
+ * heap[SMALLEST]. The sons of heap[n] are heap[2*n] and heap[2*n+1].
+ * heap[0] is not used.
+ */
+ s.heap_len = 0;
+ s.heap_max = HEAP_SIZE;
+
+ for (n = 0; n < elems; n++) {
+ if (tree[n * 2]/*.Freq*/ !== 0) {
+ s.heap[++s.heap_len] = max_code = n;
+ s.depth[n] = 0;
+
+ } else {
+ tree[n * 2 + 1]/*.Len*/ = 0;
+ }
+ }
+
+ /* The pkzip format requires that at least one distance code exists,
+ * and that at least one bit should be sent even if there is only one
+ * possible code. So to avoid special checks later on we force at least
+ * two codes of non zero frequency.
+ */
+ while (s.heap_len < 2) {
+ node = s.heap[++s.heap_len] = (max_code < 2 ? ++max_code : 0);
+ tree[node * 2]/*.Freq*/ = 1;
+ s.depth[node] = 0;
+ s.opt_len--;
+
+ if (has_stree) {
+ s.static_len -= stree[node * 2 + 1]/*.Len*/;
+ }
+ /* node is 0 or 1 so it does not have extra bits */
+ }
+ desc.max_code = max_code;
+
+ /* The elements heap[heap_len/2+1 .. heap_len] are leaves of the tree,
+ * establish sub-heaps of increasing lengths:
+ */
+ for (n = (s.heap_len >> 1/*int /2*/); n >= 1; n--) { pqdownheap(s, tree, n); }
+
+ /* Construct the Huffman tree by repeatedly combining the least two
+ * frequent nodes.
+ */
+ node = elems; /* next internal node of the tree */
+ do {
+ //pqremove(s, tree, n); /* n = node of least frequency */
+ /*** pqremove ***/
+ n = s.heap[1/*SMALLEST*/];
+ s.heap[1/*SMALLEST*/] = s.heap[s.heap_len--];
+ pqdownheap(s, tree, 1/*SMALLEST*/);
+ /***/
+
+ m = s.heap[1/*SMALLEST*/]; /* m = node of next least frequency */
+
+ s.heap[--s.heap_max] = n; /* keep the nodes sorted by frequency */
+ s.heap[--s.heap_max] = m;
+
+ /* Create a new node father of n and m */
+ tree[node * 2]/*.Freq*/ = tree[n * 2]/*.Freq*/ + tree[m * 2]/*.Freq*/;
+ s.depth[node] = (s.depth[n] >= s.depth[m] ? s.depth[n] : s.depth[m]) + 1;
+ tree[n * 2 + 1]/*.Dad*/ = tree[m * 2 + 1]/*.Dad*/ = node;
+
+ /* and insert the new node in the heap */
+ s.heap[1/*SMALLEST*/] = node++;
+ pqdownheap(s, tree, 1/*SMALLEST*/);
+
+ } while (s.heap_len >= 2);
+
+ s.heap[--s.heap_max] = s.heap[1/*SMALLEST*/];
+
+ /* At this point, the fields freq and dad are set. We can now
+ * generate the bit lengths.
+ */
+ gen_bitlen(s, desc);
+
+ /* The field len is now set, we can generate the bit codes */
+ gen_codes(tree, max_code, s.bl_count);
+};
+
+
+/* ===========================================================================
+ * Scan a literal or distance tree to determine the frequencies of the codes
+ * in the bit length tree.
+ */
+const scan_tree = (s, tree, max_code) =>
+// deflate_state *s;
+// ct_data *tree; /* the tree to be scanned */
+// int max_code; /* and its largest code of non zero frequency */
+{
+ let n; /* iterates over all tree elements */
+ let prevlen = -1; /* last emitted length */
+ let curlen; /* length of current code */
+
+ let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */
+
+ let count = 0; /* repeat count of the current code */
+ let max_count = 7; /* max repeat count */
+ let min_count = 4; /* min repeat count */
+
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
+ }
+ tree[(max_code + 1) * 2 + 1]/*.Len*/ = 0xffff; /* guard */
+
+ for (n = 0; n <= max_code; n++) {
+ curlen = nextlen;
+ nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;
+
+ if (++count < max_count && curlen === nextlen) {
+ continue;
+
+ } else if (count < min_count) {
+ s.bl_tree[curlen * 2]/*.Freq*/ += count;
+
+ } else if (curlen !== 0) {
+
+ if (curlen !== prevlen) { s.bl_tree[curlen * 2]/*.Freq*/++; }
+ s.bl_tree[REP_3_6 * 2]/*.Freq*/++;
+
+ } else if (count <= 10) {
+ s.bl_tree[REPZ_3_10 * 2]/*.Freq*/++;
+
+ } else {
+ s.bl_tree[REPZ_11_138 * 2]/*.Freq*/++;
+ }
+
+ count = 0;
+ prevlen = curlen;
+
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
+
+ } else if (curlen === nextlen) {
+ max_count = 6;
+ min_count = 3;
+
+ } else {
+ max_count = 7;
+ min_count = 4;
+ }
+ }
+};
+
+
+/* ===========================================================================
+ * Send a literal or distance tree in compressed form, using the codes in
+ * bl_tree.
+ */
+const send_tree = (s, tree, max_code) =>
+// deflate_state *s;
+// ct_data *tree; /* the tree to be scanned */
+// int max_code; /* and its largest code of non zero frequency */
+{
+ let n; /* iterates over all tree elements */
+ let prevlen = -1; /* last emitted length */
+ let curlen; /* length of current code */
+
+ let nextlen = tree[0 * 2 + 1]/*.Len*/; /* length of next code */
+
+ let count = 0; /* repeat count of the current code */
+ let max_count = 7; /* max repeat count */
+ let min_count = 4; /* min repeat count */
+
+ /* tree[max_code+1].Len = -1; */ /* guard already set */
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
+ }
+
+ for (n = 0; n <= max_code; n++) {
+ curlen = nextlen;
+ nextlen = tree[(n + 1) * 2 + 1]/*.Len*/;
+
+ if (++count < max_count && curlen === nextlen) {
+ continue;
+
+ } else if (count < min_count) {
+ do { send_code(s, curlen, s.bl_tree); } while (--count !== 0);
+
+ } else if (curlen !== 0) {
+ if (curlen !== prevlen) {
+ send_code(s, curlen, s.bl_tree);
+ count--;
+ }
+ //Assert(count >= 3 && count <= 6, " 3_6?");
+ send_code(s, REP_3_6, s.bl_tree);
+ send_bits(s, count - 3, 2);
+
+ } else if (count <= 10) {
+ send_code(s, REPZ_3_10, s.bl_tree);
+ send_bits(s, count - 3, 3);
+
+ } else {
+ send_code(s, REPZ_11_138, s.bl_tree);
+ send_bits(s, count - 11, 7);
+ }
+
+ count = 0;
+ prevlen = curlen;
+ if (nextlen === 0) {
+ max_count = 138;
+ min_count = 3;
+
+ } else if (curlen === nextlen) {
+ max_count = 6;
+ min_count = 3;
+
+ } else {
+ max_count = 7;
+ min_count = 4;
+ }
+ }
+};
+
+
+/* ===========================================================================
+ * Construct the Huffman tree for the bit lengths and return the index in
+ * bl_order of the last bit length code to send.
+ */
+const build_bl_tree = (s) => {
+
+ let max_blindex; /* index of last bit length code of non zero freq */
+
+ /* Determine the bit length frequencies for literal and distance trees */
+ scan_tree(s, s.dyn_ltree, s.l_desc.max_code);
+ scan_tree(s, s.dyn_dtree, s.d_desc.max_code);
+
+ /* Build the bit length tree: */
+ build_tree(s, s.bl_desc);
+ /* opt_len now includes the length of the tree representations, except
+ * the lengths of the bit lengths codes and the 5+5+4 bits for the counts.
+ */
+
+ /* Determine the number of bit length codes to send. The pkzip format
+ * requires that at least 4 bit length codes be sent. (appnote.txt says
+ * 3 but the actual value used is 4.)
+ */
+ for (max_blindex = BL_CODES - 1; max_blindex >= 3; max_blindex--) {
+ if (s.bl_tree[bl_order[max_blindex] * 2 + 1]/*.Len*/ !== 0) {
+ break;
+ }
+ }
+ /* Update opt_len to include the bit length tree and counts */
+ s.opt_len += 3 * (max_blindex + 1) + 5 + 5 + 4;
+ //Tracev((stderr, "\ndyn trees: dyn %ld, stat %ld",
+ // s->opt_len, s->static_len));
+
+ return max_blindex;
+};
+
+
+/* ===========================================================================
+ * Send the header for a block using dynamic Huffman trees: the counts, the
+ * lengths of the bit length codes, the literal tree and the distance tree.
+ * IN assertion: lcodes >= 257, dcodes >= 1, blcodes >= 4.
+ */
+const send_all_trees = (s, lcodes, dcodes, blcodes) =>
+// deflate_state *s;
+// int lcodes, dcodes, blcodes; /* number of codes for each tree */
+{
+ let rank; /* index in bl_order */
+
+ //Assert (lcodes >= 257 && dcodes >= 1 && blcodes >= 4, "not enough codes");
+ //Assert (lcodes <= L_CODES && dcodes <= D_CODES && blcodes <= BL_CODES,
+ // "too many codes");
+ //Tracev((stderr, "\nbl counts: "));
+ send_bits(s, lcodes - 257, 5); /* not +255 as stated in appnote.txt */
+ send_bits(s, dcodes - 1, 5);
+ send_bits(s, blcodes - 4, 4); /* not -3 as stated in appnote.txt */
+ for (rank = 0; rank < blcodes; rank++) {
+ //Tracev((stderr, "\nbl code %2d ", bl_order[rank]));
+ send_bits(s, s.bl_tree[bl_order[rank] * 2 + 1]/*.Len*/, 3);
+ }
+ //Tracev((stderr, "\nbl tree: sent %ld", s->bits_sent));
+
+ send_tree(s, s.dyn_ltree, lcodes - 1); /* literal tree */
+ //Tracev((stderr, "\nlit tree: sent %ld", s->bits_sent));
+
+ send_tree(s, s.dyn_dtree, dcodes - 1); /* distance tree */
+ //Tracev((stderr, "\ndist tree: sent %ld", s->bits_sent));
+};
+
+
+/* ===========================================================================
+ * Check if the data type is TEXT or BINARY, using the following algorithm:
+ * - TEXT if the two conditions below are satisfied:
+ * a) There are no non-portable control characters belonging to the
+ * "black list" (0..6, 14..25, 28..31).
+ * b) There is at least one printable character belonging to the
+ * "white list" (9 {TAB}, 10 {LF}, 13 {CR}, 32..255).
+ * - BINARY otherwise.
+ * - The following partially-portable control characters form a
+ * "gray list" that is ignored in this detection algorithm:
+ * (7 {BEL}, 8 {BS}, 11 {VT}, 12 {FF}, 26 {SUB}, 27 {ESC}).
+ * IN assertion: the fields Freq of dyn_ltree are set.
+ */
+const detect_data_type = (s) => {
+ /* black_mask is the bit mask of black-listed bytes
+ * set bits 0..6, 14..25, and 28..31
+ * 0xf3ffc07f = binary 11110011111111111100000001111111
+ */
+ let black_mask = 0xf3ffc07f;
+ let n;
+
+ /* Check for non-textual ("black-listed") bytes. */
+ for (n = 0; n <= 31; n++, black_mask >>>= 1) {
+ if ((black_mask & 1) && (s.dyn_ltree[n * 2]/*.Freq*/ !== 0)) {
+ return Z_BINARY;
+ }
+ }
+
+ /* Check for textual ("white-listed") bytes. */
+ if (s.dyn_ltree[9 * 2]/*.Freq*/ !== 0 || s.dyn_ltree[10 * 2]/*.Freq*/ !== 0 ||
+ s.dyn_ltree[13 * 2]/*.Freq*/ !== 0) {
+ return Z_TEXT;
+ }
+ for (n = 32; n < LITERALS; n++) {
+ if (s.dyn_ltree[n * 2]/*.Freq*/ !== 0) {
+ return Z_TEXT;
+ }
+ }
+
+ /* There are no "black-listed" or "white-listed" bytes:
+ * this stream either is empty or has tolerated ("gray-listed") bytes only.
+ */
+ return Z_BINARY;
+};
+
+
+let static_init_done = false;
+
+/* ===========================================================================
+ * Initialize the tree data structures for a new zlib stream.
+ */
+const _tr_init = (s) =>
+{
+
+ if (!static_init_done) {
+ tr_static_init();
+ static_init_done = true;
+ }
+
+ s.l_desc = new TreeDesc(s.dyn_ltree, static_l_desc);
+ s.d_desc = new TreeDesc(s.dyn_dtree, static_d_desc);
+ s.bl_desc = new TreeDesc(s.bl_tree, static_bl_desc);
+
+ s.bi_buf = 0;
+ s.bi_valid = 0;
+
+ /* Initialize the first block of the first file: */
+ init_block(s);
+};
+
+
+/* ===========================================================================
+ * Send a stored block
+ */
+const _tr_stored_block = (s, buf, stored_len, last) =>
+//DeflateState *s;
+//charf *buf; /* input block */
+//ulg stored_len; /* length of input block */
+//int last; /* one if this is the last block for a file */
+{
+ send_bits(s, (STORED_BLOCK << 1) + (last ? 1 : 0), 3); /* send block type */
+ copy_block(s, buf, stored_len, true); /* with header */
+};
+
+
+/* ===========================================================================
+ * Send one empty static block to give enough lookahead for inflate.
+ * This takes 10 bits, of which 7 may remain in the bit buffer.
+ */
+const _tr_align = (s) => {
+ send_bits(s, STATIC_TREES << 1, 3);
+ send_code(s, END_BLOCK, static_ltree);
+ bi_flush(s);
+};
+
+
+/* ===========================================================================
+ * Determine the best encoding for the current block: dynamic trees, static
+ * trees or store, and output the encoded block to the zip file.
+ */
+const _tr_flush_block = (s, buf, stored_len, last) =>
+//DeflateState *s;
+//charf *buf; /* input block, or NULL if too old */
+//ulg stored_len; /* length of input block */
+//int last; /* one if this is the last block for a file */
+{
+ let opt_lenb, static_lenb; /* opt_len and static_len in bytes */
+ let max_blindex = 0; /* index of last bit length code of non zero freq */
+
+ /* Build the Huffman trees unless a stored block is forced */
+ if (s.level > 0) {
+
+ /* Check if the file is binary or text */
+ if (s.strm.data_type === Z_UNKNOWN) {
+ s.strm.data_type = detect_data_type(s);
+ }
+
+ /* Construct the literal and distance trees */
+ build_tree(s, s.l_desc);
+ // Tracev((stderr, "\nlit data: dyn %ld, stat %ld", s->opt_len,
+ // s->static_len));
+
+ build_tree(s, s.d_desc);
+ // Tracev((stderr, "\ndist data: dyn %ld, stat %ld", s->opt_len,
+ // s->static_len));
+ /* At this point, opt_len and static_len are the total bit lengths of
+ * the compressed block data, excluding the tree representations.
+ */
+
+ /* Build the bit length tree for the above two trees, and get the index
+ * in bl_order of the last bit length code to send.
+ */
+ max_blindex = build_bl_tree(s);
+
+ /* Determine the best encoding. Compute the block lengths in bytes. */
+ opt_lenb = (s.opt_len + 3 + 7) >>> 3;
+ static_lenb = (s.static_len + 3 + 7) >>> 3;
+
+ // Tracev((stderr, "\nopt %lu(%lu) stat %lu(%lu) stored %lu lit %u ",
+ // opt_lenb, s->opt_len, static_lenb, s->static_len, stored_len,
+ // s->last_lit));
+
+ if (static_lenb <= opt_lenb) { opt_lenb = static_lenb; }
+
+ } else {
+ // Assert(buf != (char*)0, "lost buf");
+ opt_lenb = static_lenb = stored_len + 5; /* force a stored block */
+ }
+
+ if ((stored_len + 4 <= opt_lenb) && (buf !== -1)) {
+ /* 4: two words for the lengths */
+
+ /* The test buf != NULL is only necessary if LIT_BUFSIZE > WSIZE.
+ * Otherwise we can't have processed more than WSIZE input bytes since
+ * the last block flush, because compression would have been
+ * successful. If LIT_BUFSIZE <= WSIZE, it is never too late to
+ * transform a block into a stored block.
+ */
+ _tr_stored_block(s, buf, stored_len, last);
+
+ } else if (s.strategy === Z_FIXED || static_lenb === opt_lenb) {
+
+ send_bits(s, (STATIC_TREES << 1) + (last ? 1 : 0), 3);
+ compress_block(s, static_ltree, static_dtree);
+
+ } else {
+ send_bits(s, (DYN_TREES << 1) + (last ? 1 : 0), 3);
+ send_all_trees(s, s.l_desc.max_code + 1, s.d_desc.max_code + 1, max_blindex + 1);
+ compress_block(s, s.dyn_ltree, s.dyn_dtree);
+ }
+ // Assert (s->compressed_len == s->bits_sent, "bad compressed size");
+ /* The above check is made mod 2^32, for files larger than 512 MB
+ * and uLong implemented on 32 bits.
+ */
+ init_block(s);
+
+ if (last) {
+ bi_windup(s);
+ }
+ // Tracev((stderr,"\ncomprlen %lu(%lu) ", s->compressed_len>>3,
+ // s->compressed_len-7*last));
+};
+
+/* ===========================================================================
+ * Save the match info and tally the frequency counts. Return true if
+ * the current block must be flushed.
+ */
+const _tr_tally = (s, dist, lc) =>
+// deflate_state *s;
+// unsigned dist; /* distance of matched string */
+// unsigned lc; /* match length-MIN_MATCH or unmatched char (if dist==0) */
+{
+ //let out_length, in_length, dcode;
+
+ s.pending_buf[s.d_buf + s.last_lit * 2] = (dist >>> 8) & 0xff;
+ s.pending_buf[s.d_buf + s.last_lit * 2 + 1] = dist & 0xff;
+
+ s.pending_buf[s.l_buf + s.last_lit] = lc & 0xff;
+ s.last_lit++;
+
+ if (dist === 0) {
+ /* lc is the unmatched char */
+ s.dyn_ltree[lc * 2]/*.Freq*/++;
+ } else {
+ s.matches++;
+ /* Here, lc is the match length - MIN_MATCH */
+ dist--; /* dist = match distance - 1 */
+ //Assert((ush)dist < (ush)MAX_DIST(s) &&
+ // (ush)lc <= (ush)(MAX_MATCH-MIN_MATCH) &&
+ // (ush)d_code(dist) < (ush)D_CODES, "_tr_tally: bad match");
+
+ s.dyn_ltree[(_length_code[lc] + LITERALS + 1) * 2]/*.Freq*/++;
+ s.dyn_dtree[d_code(dist) * 2]/*.Freq*/++;
+ }
+
+// (!) This block is disabled in zlib defaults,
+// don't enable it for binary compatibility
+
+//#ifdef TRUNCATE_BLOCK
+// /* Try to guess if it is profitable to stop the current block here */
+// if ((s.last_lit & 0x1fff) === 0 && s.level > 2) {
+// /* Compute an upper bound for the compressed length */
+// out_length = s.last_lit*8;
+// in_length = s.strstart - s.block_start;
+//
+// for (dcode = 0; dcode < D_CODES; dcode++) {
+// out_length += s.dyn_dtree[dcode*2]/*.Freq*/ * (5 + extra_dbits[dcode]);
+// }
+// out_length >>>= 3;
+// //Tracev((stderr,"\nlast_lit %u, in %ld, out ~%ld(%ld%%) ",
+// // s->last_lit, in_length, out_length,
+// // 100L - out_length*100L/in_length));
+// if (s.matches < (s.last_lit>>1)/*int /2*/ && out_length < (in_length>>1)/*int /2*/) {
+// return true;
+// }
+// }
+//#endif
+
+ return (s.last_lit === s.lit_bufsize - 1);
+ /* We avoid equality with lit_bufsize because of wraparound at 64K
+ * on 16 bit machines and because stored blocks are restricted to
+ * 64K-1 bytes.
+ */
+};
+
+var _tr_init_1 = _tr_init;
+var _tr_stored_block_1 = _tr_stored_block;
+var _tr_flush_block_1 = _tr_flush_block;
+var _tr_tally_1 = _tr_tally;
+var _tr_align_1 = _tr_align;
+
+var trees = {
+ _tr_init: _tr_init_1,
+ _tr_stored_block: _tr_stored_block_1,
+ _tr_flush_block: _tr_flush_block_1,
+ _tr_tally: _tr_tally_1,
+ _tr_align: _tr_align_1
+};
+
+// Note: adler32 takes 12% for level 0 and 2% for level 6.
+// It isn't worth it to make additional optimizations as in original.
+// Small size is preferable.
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+const adler32 = (adler, buf, len, pos) => {
+ let s1 = (adler & 0xffff) |0,
+ s2 = ((adler >>> 16) & 0xffff) |0,
+ n = 0;
+
+ while (len !== 0) {
+ // Set limit ~ twice less than 5552, to keep
+ // s2 in 31-bits, because we force signed ints.
+ // in other case %= will fail.
+ n = len > 2000 ? 2000 : len;
+ len -= n;
+
+ do {
+ s1 = (s1 + buf[pos++]) |0;
+ s2 = (s2 + s1) |0;
+ } while (--n);
+
+ s1 %= 65521;
+ s2 %= 65521;
+ }
+
+ return (s1 | (s2 << 16)) |0;
+};
+
+
+var adler32_1 = adler32;
+
+// Note: we can't get significant speed boost here.
+// So write code to minimize size - no pregenerated tables
+// and array tools dependencies.
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+// Use ordinary array, since untyped makes no boost here
+const makeTable = () => {
+ let c, table = [];
+
+ for (var n = 0; n < 256; n++) {
+ c = n;
+ for (var k = 0; k < 8; k++) {
+ c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
+ }
+ table[n] = c;
+ }
+
+ return table;
+};
+
+// Create table on load. Just 255 signed longs. Not a problem.
+const crcTable = new Uint32Array(makeTable());
+
+
+const crc32 = (crc, buf, len, pos) => {
+ const t = crcTable;
+ const end = pos + len;
+
+ crc ^= -1;
+
+ for (let i = pos; i < end; i++) {
+ crc = (crc >>> 8) ^ t[(crc ^ buf[i]) & 0xFF];
+ }
+
+ return (crc ^ (-1)); // >>> 0;
+};
+
+
+var crc32_1 = crc32;
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+var messages = {
+ 2: 'need dictionary', /* Z_NEED_DICT 2 */
+ 1: 'stream end', /* Z_STREAM_END 1 */
+ 0: '', /* Z_OK 0 */
+ '-1': 'file error', /* Z_ERRNO (-1) */
+ '-2': 'stream error', /* Z_STREAM_ERROR (-2) */
+ '-3': 'data error', /* Z_DATA_ERROR (-3) */
+ '-4': 'insufficient memory', /* Z_MEM_ERROR (-4) */
+ '-5': 'buffer error', /* Z_BUF_ERROR (-5) */
+ '-6': 'incompatible version' /* Z_VERSION_ERROR (-6) */
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+var constants = {
+
+ /* Allowed flush values; see deflate() and inflate() below for details */
+ Z_NO_FLUSH: 0,
+ Z_PARTIAL_FLUSH: 1,
+ Z_SYNC_FLUSH: 2,
+ Z_FULL_FLUSH: 3,
+ Z_FINISH: 4,
+ Z_BLOCK: 5,
+ Z_TREES: 6,
+
+ /* Return codes for the compression/decompression functions. Negative values
+ * are errors, positive values are used for special but normal events.
+ */
+ Z_OK: 0,
+ Z_STREAM_END: 1,
+ Z_NEED_DICT: 2,
+ Z_ERRNO: -1,
+ Z_STREAM_ERROR: -2,
+ Z_DATA_ERROR: -3,
+ Z_MEM_ERROR: -4,
+ Z_BUF_ERROR: -5,
+ //Z_VERSION_ERROR: -6,
+
+ /* compression levels */
+ Z_NO_COMPRESSION: 0,
+ Z_BEST_SPEED: 1,
+ Z_BEST_COMPRESSION: 9,
+ Z_DEFAULT_COMPRESSION: -1,
+
+
+ Z_FILTERED: 1,
+ Z_HUFFMAN_ONLY: 2,
+ Z_RLE: 3,
+ Z_FIXED: 4,
+ Z_DEFAULT_STRATEGY: 0,
+
+ /* Possible values of the data_type field (though see inflate()) */
+ Z_BINARY: 0,
+ Z_TEXT: 1,
+ //Z_ASCII: 1, // = Z_TEXT (deprecated)
+ Z_UNKNOWN: 2,
+
+ /* The deflate compression method */
+ Z_DEFLATED: 8
+ //Z_NULL: null // Use -1 or null inline, depending on var type
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+const { _tr_init: _tr_init$1, _tr_stored_block: _tr_stored_block$1, _tr_flush_block: _tr_flush_block$1, _tr_tally: _tr_tally$1, _tr_align: _tr_align$1 } = trees;
+
+
+
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+const {
+ Z_NO_FLUSH, Z_PARTIAL_FLUSH, Z_FULL_FLUSH, Z_FINISH, Z_BLOCK,
+ Z_OK, Z_STREAM_END, Z_STREAM_ERROR, Z_DATA_ERROR, Z_BUF_ERROR,
+ Z_DEFAULT_COMPRESSION,
+ Z_FILTERED, Z_HUFFMAN_ONLY, Z_RLE, Z_FIXED: Z_FIXED$1, Z_DEFAULT_STRATEGY,
+ Z_UNKNOWN: Z_UNKNOWN$1,
+ Z_DEFLATED
+} = constants;
+
+/*============================================================================*/
+
+
+const MAX_MEM_LEVEL = 9;
+/* Maximum value for memLevel in deflateInit2 */
+const MAX_WBITS = 15;
+/* 32K LZ77 window */
+const DEF_MEM_LEVEL = 8;
+
+
+const LENGTH_CODES$1 = 29;
+/* number of length codes, not counting the special END_BLOCK code */
+const LITERALS$1 = 256;
+/* number of literal bytes 0..255 */
+const L_CODES$1 = LITERALS$1 + 1 + LENGTH_CODES$1;
+/* number of Literal or Length codes, including the END_BLOCK code */
+const D_CODES$1 = 30;
+/* number of distance codes */
+const BL_CODES$1 = 19;
+/* number of codes used to transfer the bit lengths */
+const HEAP_SIZE$1 = 2 * L_CODES$1 + 1;
+/* maximum heap size */
+const MAX_BITS$1 = 15;
+/* All codes must not exceed MAX_BITS bits */
+
+const MIN_MATCH$1 = 3;
+const MAX_MATCH$1 = 258;
+const MIN_LOOKAHEAD = (MAX_MATCH$1 + MIN_MATCH$1 + 1);
+
+const PRESET_DICT = 0x20;
+
+const INIT_STATE = 42;
+const EXTRA_STATE = 69;
+const NAME_STATE = 73;
+const COMMENT_STATE = 91;
+const HCRC_STATE = 103;
+const BUSY_STATE = 113;
+const FINISH_STATE = 666;
+
+const BS_NEED_MORE = 1; /* block not completed, need more input or more output */
+const BS_BLOCK_DONE = 2; /* block flush performed */
+const BS_FINISH_STARTED = 3; /* finish started, need only more output at next deflate */
+const BS_FINISH_DONE = 4; /* finish done, accept no more input or output */
+
+const OS_CODE = 0x03; // Unix :) . Don't detect, use this default.
+
+const err = (strm, errorCode) => {
+ strm.msg = messages[errorCode];
+ return errorCode;
+};
+
+const rank = (f) => {
+ return ((f) << 1) - ((f) > 4 ? 9 : 0);
+};
+
+const zero$1 = (buf) => {
+ let len = buf.length; while (--len >= 0) { buf[len] = 0; }
+};
+
+
+/* eslint-disable new-cap */
+let HASH_ZLIB = (s, prev, data) => ((prev << s.hash_shift) ^ data) & s.hash_mask;
+// This hash causes less collisions, https://github.com/nodeca/pako/issues/135
+// But breaks binary compatibility
+//let HASH_FAST = (s, prev, data) => ((prev << 8) + (prev >> 8) + (data << 4)) & s.hash_mask;
+let HASH = HASH_ZLIB;
+
+/* =========================================================================
+ * Flush as much pending output as possible. All deflate() output goes
+ * through this function so some applications may wish to modify it
+ * to avoid allocating a large strm->output buffer and copying into it.
+ * (See also read_buf()).
+ */
+const flush_pending = (strm) => {
+ const s = strm.state;
+
+ //_tr_flush_bits(s);
+ let len = s.pending;
+ if (len > strm.avail_out) {
+ len = strm.avail_out;
+ }
+ if (len === 0) { return; }
+
+ strm.output.set(s.pending_buf.subarray(s.pending_out, s.pending_out + len), strm.next_out);
+ strm.next_out += len;
+ s.pending_out += len;
+ strm.total_out += len;
+ strm.avail_out -= len;
+ s.pending -= len;
+ if (s.pending === 0) {
+ s.pending_out = 0;
+ }
+};
+
+
+const flush_block_only = (s, last) => {
+ _tr_flush_block$1(s, (s.block_start >= 0 ? s.block_start : -1), s.strstart - s.block_start, last);
+ s.block_start = s.strstart;
+ flush_pending(s.strm);
+};
+
+
+const put_byte = (s, b) => {
+ s.pending_buf[s.pending++] = b;
+};
+
+
+/* =========================================================================
+ * Put a short in the pending buffer. The 16-bit value is put in MSB order.
+ * IN assertion: the stream state is correct and there is enough room in
+ * pending_buf.
+ */
+const putShortMSB = (s, b) => {
+
+ // put_byte(s, (Byte)(b >> 8));
+// put_byte(s, (Byte)(b & 0xff));
+ s.pending_buf[s.pending++] = (b >>> 8) & 0xff;
+ s.pending_buf[s.pending++] = b & 0xff;
+};
+
+
+/* ===========================================================================
+ * Read a new buffer from the current input stream, update the adler32
+ * and total number of bytes read. All deflate() input goes through
+ * this function so some applications may wish to modify it to avoid
+ * allocating a large strm->input buffer and copying from it.
+ * (See also flush_pending()).
+ */
+const read_buf = (strm, buf, start, size) => {
+
+ let len = strm.avail_in;
+
+ if (len > size) { len = size; }
+ if (len === 0) { return 0; }
+
+ strm.avail_in -= len;
+
+ // zmemcpy(buf, strm->next_in, len);
+ buf.set(strm.input.subarray(strm.next_in, strm.next_in + len), start);
+ if (strm.state.wrap === 1) {
+ strm.adler = adler32_1(strm.adler, buf, len, start);
+ }
+
+ else if (strm.state.wrap === 2) {
+ strm.adler = crc32_1(strm.adler, buf, len, start);
+ }
+
+ strm.next_in += len;
+ strm.total_in += len;
+
+ return len;
+};
+
+
+/* ===========================================================================
+ * Set match_start to the longest match starting at the given string and
+ * return its length. Matches shorter or equal to prev_length are discarded,
+ * in which case the result is equal to prev_length and match_start is
+ * garbage.
+ * IN assertions: cur_match is the head of the hash chain for the current
+ * string (strstart) and its distance is <= MAX_DIST, and prev_length >= 1
+ * OUT assertion: the match length is not greater than s->lookahead.
+ */
+const longest_match = (s, cur_match) => {
+
+ let chain_length = s.max_chain_length; /* max hash chain length */
+ let scan = s.strstart; /* current string */
+ let match; /* matched string */
+ let len; /* length of current match */
+ let best_len = s.prev_length; /* best match length so far */
+ let nice_match = s.nice_match; /* stop if match long enough */
+ const limit = (s.strstart > (s.w_size - MIN_LOOKAHEAD)) ?
+ s.strstart - (s.w_size - MIN_LOOKAHEAD) : 0/*NIL*/;
+
+ const _win = s.window; // shortcut
+
+ const wmask = s.w_mask;
+ const prev = s.prev;
+
+ /* Stop when cur_match becomes <= limit. To simplify the code,
+ * we prevent matches with the string of window index 0.
+ */
+
+ const strend = s.strstart + MAX_MATCH$1;
+ let scan_end1 = _win[scan + best_len - 1];
+ let scan_end = _win[scan + best_len];
+
+ /* The code is optimized for HASH_BITS >= 8 and MAX_MATCH-2 multiple of 16.
+ * It is easy to get rid of this optimization if necessary.
+ */
+ // Assert(s->hash_bits >= 8 && MAX_MATCH == 258, "Code too clever");
+
+ /* Do not waste too much time if we already have a good match: */
+ if (s.prev_length >= s.good_match) {
+ chain_length >>= 2;
+ }
+ /* Do not look for matches beyond the end of the input. This is necessary
+ * to make deflate deterministic.
+ */
+ if (nice_match > s.lookahead) { nice_match = s.lookahead; }
+
+ // Assert((ulg)s->strstart <= s->window_size-MIN_LOOKAHEAD, "need lookahead");
+
+ do {
+ // Assert(cur_match < s->strstart, "no future");
+ match = cur_match;
+
+ /* Skip to next match if the match length cannot increase
+ * or if the match length is less than 2. Note that the checks below
+ * for insufficient lookahead only occur occasionally for performance
+ * reasons. Therefore uninitialized memory will be accessed, and
+ * conditional jumps will be made that depend on those values.
+ * However the length of the match is limited to the lookahead, so
+ * the output of deflate is not affected by the uninitialized values.
+ */
+
+ if (_win[match + best_len] !== scan_end ||
+ _win[match + best_len - 1] !== scan_end1 ||
+ _win[match] !== _win[scan] ||
+ _win[++match] !== _win[scan + 1]) {
+ continue;
+ }
+
+ /* The check at best_len-1 can be removed because it will be made
+ * again later. (This heuristic is not always a win.)
+ * It is not necessary to compare scan[2] and match[2] since they
+ * are always equal when the other bytes match, given that
+ * the hash keys are equal and that HASH_BITS >= 8.
+ */
+ scan += 2;
+ match++;
+ // Assert(*scan == *match, "match[2]?");
+
+ /* We check for insufficient lookahead only every 8th comparison;
+ * the 256th check will be made at strstart+258.
+ */
+ do {
+ /*jshint noempty:false*/
+ } while (_win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+ _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+ _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+ _win[++scan] === _win[++match] && _win[++scan] === _win[++match] &&
+ scan < strend);
+
+ // Assert(scan <= s->window+(unsigned)(s->window_size-1), "wild scan");
+
+ len = MAX_MATCH$1 - (strend - scan);
+ scan = strend - MAX_MATCH$1;
+
+ if (len > best_len) {
+ s.match_start = cur_match;
+ best_len = len;
+ if (len >= nice_match) {
+ break;
+ }
+ scan_end1 = _win[scan + best_len - 1];
+ scan_end = _win[scan + best_len];
+ }
+ } while ((cur_match = prev[cur_match & wmask]) > limit && --chain_length !== 0);
+
+ if (best_len <= s.lookahead) {
+ return best_len;
+ }
+ return s.lookahead;
+};
+
+
+/* ===========================================================================
+ * Fill the window when the lookahead becomes insufficient.
+ * Updates strstart and lookahead.
+ *
+ * IN assertion: lookahead < MIN_LOOKAHEAD
+ * OUT assertions: strstart <= window_size-MIN_LOOKAHEAD
+ * At least one byte has been read, or avail_in == 0; reads are
+ * performed for at least two bytes (required for the zip translate_eol
+ * option -- not supported here).
+ */
+const fill_window = (s) => {
+
+ const _w_size = s.w_size;
+ let p, n, m, more, str;
+
+ //Assert(s->lookahead < MIN_LOOKAHEAD, "already enough lookahead");
+
+ do {
+ more = s.window_size - s.lookahead - s.strstart;
+
+ // JS ints have 32 bit, block below not needed
+ /* Deal with !@#$% 64K limit: */
+ //if (sizeof(int) <= 2) {
+ // if (more == 0 && s->strstart == 0 && s->lookahead == 0) {
+ // more = wsize;
+ //
+ // } else if (more == (unsigned)(-1)) {
+ // /* Very unlikely, but possible on 16 bit machine if
+ // * strstart == 0 && lookahead == 1 (input done a byte at time)
+ // */
+ // more--;
+ // }
+ //}
+
+
+ /* If the window is almost full and there is insufficient lookahead,
+ * move the upper half to the lower one to make room in the upper half.
+ */
+ if (s.strstart >= _w_size + (_w_size - MIN_LOOKAHEAD)) {
+
+ s.window.set(s.window.subarray(_w_size, _w_size + _w_size), 0);
+ s.match_start -= _w_size;
+ s.strstart -= _w_size;
+ /* we now have strstart >= MAX_DIST */
+ s.block_start -= _w_size;
+
+ /* Slide the hash table (could be avoided with 32 bit values
+ at the expense of memory usage). We slide even when level == 0
+ to keep the hash table consistent if we switch back to level > 0
+ later. (Using level 0 permanently is not an optimal usage of
+ zlib, so we don't care about this pathological case.)
+ */
+
+ n = s.hash_size;
+ p = n;
+
+ do {
+ m = s.head[--p];
+ s.head[p] = (m >= _w_size ? m - _w_size : 0);
+ } while (--n);
+
+ n = _w_size;
+ p = n;
+
+ do {
+ m = s.prev[--p];
+ s.prev[p] = (m >= _w_size ? m - _w_size : 0);
+ /* If n is not on any hash chain, prev[n] is garbage but
+ * its value will never be used.
+ */
+ } while (--n);
+
+ more += _w_size;
+ }
+ if (s.strm.avail_in === 0) {
+ break;
+ }
+
+ /* If there was no sliding:
+ * strstart <= WSIZE+MAX_DIST-1 && lookahead <= MIN_LOOKAHEAD - 1 &&
+ * more == window_size - lookahead - strstart
+ * => more >= window_size - (MIN_LOOKAHEAD-1 + WSIZE + MAX_DIST-1)
+ * => more >= window_size - 2*WSIZE + 2
+ * In the BIG_MEM or MMAP case (not yet supported),
+ * window_size == input_size + MIN_LOOKAHEAD &&
+ * strstart + s->lookahead <= input_size => more >= MIN_LOOKAHEAD.
+ * Otherwise, window_size == 2*WSIZE so more >= 2.
+ * If there was sliding, more >= WSIZE. So in all cases, more >= 2.
+ */
+ //Assert(more >= 2, "more < 2");
+ n = read_buf(s.strm, s.window, s.strstart + s.lookahead, more);
+ s.lookahead += n;
+
+ /* Initialize the hash value now that we have some input: */
+ if (s.lookahead + s.insert >= MIN_MATCH$1) {
+ str = s.strstart - s.insert;
+ s.ins_h = s.window[str];
+
+ /* UPDATE_HASH(s, s->ins_h, s->window[str + 1]); */
+ s.ins_h = HASH(s, s.ins_h, s.window[str + 1]);
+//#if MIN_MATCH != 3
+// Call update_hash() MIN_MATCH-3 more times
+//#endif
+ while (s.insert) {
+ /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
+ s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH$1 - 1]);
+
+ s.prev[str & s.w_mask] = s.head[s.ins_h];
+ s.head[s.ins_h] = str;
+ str++;
+ s.insert--;
+ if (s.lookahead + s.insert < MIN_MATCH$1) {
+ break;
+ }
+ }
+ }
+ /* If the whole input has less than MIN_MATCH bytes, ins_h is garbage,
+ * but this is not important since only literal bytes will be emitted.
+ */
+
+ } while (s.lookahead < MIN_LOOKAHEAD && s.strm.avail_in !== 0);
+
+ /* If the WIN_INIT bytes after the end of the current data have never been
+ * written, then zero those bytes in order to avoid memory check reports of
+ * the use of uninitialized (or uninitialised as Julian writes) bytes by
+ * the longest match routines. Update the high water mark for the next
+ * time through here. WIN_INIT is set to MAX_MATCH since the longest match
+ * routines allow scanning to strstart + MAX_MATCH, ignoring lookahead.
+ */
+// if (s.high_water < s.window_size) {
+// const curr = s.strstart + s.lookahead;
+// let init = 0;
+//
+// if (s.high_water < curr) {
+// /* Previous high water mark below current data -- zero WIN_INIT
+// * bytes or up to end of window, whichever is less.
+// */
+// init = s.window_size - curr;
+// if (init > WIN_INIT)
+// init = WIN_INIT;
+// zmemzero(s->window + curr, (unsigned)init);
+// s->high_water = curr + init;
+// }
+// else if (s->high_water < (ulg)curr + WIN_INIT) {
+// /* High water mark at or above current data, but below current data
+// * plus WIN_INIT -- zero out to current data plus WIN_INIT, or up
+// * to end of window, whichever is less.
+// */
+// init = (ulg)curr + WIN_INIT - s->high_water;
+// if (init > s->window_size - s->high_water)
+// init = s->window_size - s->high_water;
+// zmemzero(s->window + s->high_water, (unsigned)init);
+// s->high_water += init;
+// }
+// }
+//
+// Assert((ulg)s->strstart <= s->window_size - MIN_LOOKAHEAD,
+// "not enough room for search");
+};
+
+/* ===========================================================================
+ * Copy without compression as much as possible from the input stream, return
+ * the current block state.
+ * This function does not insert new strings in the dictionary since
+ * uncompressible data is probably not useful. This function is used
+ * only for the level=0 compression option.
+ * NOTE: this function should be optimized to avoid extra copying from
+ * window to pending_buf.
+ */
+const deflate_stored = (s, flush) => {
+
+ /* Stored blocks are limited to 0xffff bytes, pending_buf is limited
+ * to pending_buf_size, and each stored block has a 5 byte header:
+ */
+ let max_block_size = 0xffff;
+
+ if (max_block_size > s.pending_buf_size - 5) {
+ max_block_size = s.pending_buf_size - 5;
+ }
+
+ /* Copy as much as possible from input to output: */
+ for (;;) {
+ /* Fill the window as much as possible: */
+ if (s.lookahead <= 1) {
+
+ //Assert(s->strstart < s->w_size+MAX_DIST(s) ||
+ // s->block_start >= (long)s->w_size, "slide too late");
+// if (!(s.strstart < s.w_size + (s.w_size - MIN_LOOKAHEAD) ||
+// s.block_start >= s.w_size)) {
+// throw new Error("slide too late");
+// }
+
+ fill_window(s);
+ if (s.lookahead === 0 && flush === Z_NO_FLUSH) {
+ return BS_NEED_MORE;
+ }
+
+ if (s.lookahead === 0) {
+ break;
+ }
+ /* flush the current block */
+ }
+ //Assert(s->block_start >= 0L, "block gone");
+// if (s.block_start < 0) throw new Error("block gone");
+
+ s.strstart += s.lookahead;
+ s.lookahead = 0;
+
+ /* Emit a stored block if pending_buf will be full: */
+ const max_start = s.block_start + max_block_size;
+
+ if (s.strstart === 0 || s.strstart >= max_start) {
+ /* strstart == 0 is possible when wraparound on 16-bit machine */
+ s.lookahead = s.strstart - max_start;
+ s.strstart = max_start;
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+
+
+ }
+ /* Flush if we may have to slide, otherwise block_start may become
+ * negative and the data will be gone:
+ */
+ if (s.strstart - s.block_start >= (s.w_size - MIN_LOOKAHEAD)) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ }
+
+ s.insert = 0;
+
+ if (flush === Z_FINISH) {
+ /*** FLUSH_BLOCK(s, 1); ***/
+ flush_block_only(s, true);
+ if (s.strm.avail_out === 0) {
+ return BS_FINISH_STARTED;
+ }
+ /***/
+ return BS_FINISH_DONE;
+ }
+
+ if (s.strstart > s.block_start) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+
+ return BS_NEED_MORE;
+};
+
+/* ===========================================================================
+ * Compress as much as possible from the input stream, return the current
+ * block state.
+ * This function does not perform lazy evaluation of matches and inserts
+ * new strings in the dictionary only for unmatched strings or for short
+ * matches. It is used only for the fast compression options.
+ */
+const deflate_fast = (s, flush) => {
+
+ let hash_head; /* head of the hash chain */
+ let bflush; /* set if current block must be flushed */
+
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the next match, plus MIN_MATCH bytes to insert the
+ * string following the next match.
+ */
+ if (s.lookahead < MIN_LOOKAHEAD) {
+ fill_window(s);
+ if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+ return BS_NEED_MORE;
+ }
+ if (s.lookahead === 0) {
+ break; /* flush the current block */
+ }
+ }
+
+ /* Insert the string window[strstart .. strstart+2] in the
+ * dictionary, and set hash_head to the head of the hash chain:
+ */
+ hash_head = 0/*NIL*/;
+ if (s.lookahead >= MIN_MATCH$1) {
+ /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+ s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH$1 - 1]);
+ hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+ s.head[s.ins_h] = s.strstart;
+ /***/
+ }
+
+ /* Find the longest match, discarding those <= prev_length.
+ * At this point we have always match_length < MIN_MATCH
+ */
+ if (hash_head !== 0/*NIL*/ && ((s.strstart - hash_head) <= (s.w_size - MIN_LOOKAHEAD))) {
+ /* To simplify the code, we prevent matches with the string
+ * of window index 0 (in particular we have to avoid a match
+ * of the string with itself at the start of the input file).
+ */
+ s.match_length = longest_match(s, hash_head);
+ /* longest_match() sets match_start */
+ }
+ if (s.match_length >= MIN_MATCH$1) {
+ // check_match(s, s.strstart, s.match_start, s.match_length); // for debug only
+
+ /*** _tr_tally_dist(s, s.strstart - s.match_start,
+ s.match_length - MIN_MATCH, bflush); ***/
+ bflush = _tr_tally$1(s, s.strstart - s.match_start, s.match_length - MIN_MATCH$1);
+
+ s.lookahead -= s.match_length;
+
+ /* Insert new strings in the hash table only if the match length
+ * is not too large. This saves time but degrades compression.
+ */
+ if (s.match_length <= s.max_lazy_match/*max_insert_length*/ && s.lookahead >= MIN_MATCH$1) {
+ s.match_length--; /* string at strstart already in table */
+ do {
+ s.strstart++;
+ /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+ s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH$1 - 1]);
+ hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+ s.head[s.ins_h] = s.strstart;
+ /***/
+ /* strstart never exceeds WSIZE-MAX_MATCH, so there are
+ * always MIN_MATCH bytes ahead.
+ */
+ } while (--s.match_length !== 0);
+ s.strstart++;
+ } else
+ {
+ s.strstart += s.match_length;
+ s.match_length = 0;
+ s.ins_h = s.window[s.strstart];
+ /* UPDATE_HASH(s, s.ins_h, s.window[s.strstart+1]); */
+ s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + 1]);
+
+//#if MIN_MATCH != 3
+// Call UPDATE_HASH() MIN_MATCH-3 more times
+//#endif
+ /* If lookahead < MIN_MATCH, ins_h is garbage, but it does not
+ * matter since it will be recomputed at next deflate call.
+ */
+ }
+ } else {
+ /* No match, output a literal byte */
+ //Tracevv((stderr,"%c", s.window[s.strstart]));
+ /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+ bflush = _tr_tally$1(s, 0, s.window[s.strstart]);
+
+ s.lookahead--;
+ s.strstart++;
+ }
+ if (bflush) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ }
+ s.insert = ((s.strstart < (MIN_MATCH$1 - 1)) ? s.strstart : MIN_MATCH$1 - 1);
+ if (flush === Z_FINISH) {
+ /*** FLUSH_BLOCK(s, 1); ***/
+ flush_block_only(s, true);
+ if (s.strm.avail_out === 0) {
+ return BS_FINISH_STARTED;
+ }
+ /***/
+ return BS_FINISH_DONE;
+ }
+ if (s.last_lit) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ return BS_BLOCK_DONE;
+};
+
+/* ===========================================================================
+ * Same as above, but achieves better compression. We use a lazy
+ * evaluation for matches: a match is finally adopted only if there is
+ * no better match at the next window position.
+ */
+const deflate_slow = (s, flush) => {
+
+ let hash_head; /* head of hash chain */
+ let bflush; /* set if current block must be flushed */
+
+ let max_insert;
+
+ /* Process the input block. */
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the next match, plus MIN_MATCH bytes to insert the
+ * string following the next match.
+ */
+ if (s.lookahead < MIN_LOOKAHEAD) {
+ fill_window(s);
+ if (s.lookahead < MIN_LOOKAHEAD && flush === Z_NO_FLUSH) {
+ return BS_NEED_MORE;
+ }
+ if (s.lookahead === 0) { break; } /* flush the current block */
+ }
+
+ /* Insert the string window[strstart .. strstart+2] in the
+ * dictionary, and set hash_head to the head of the hash chain:
+ */
+ hash_head = 0/*NIL*/;
+ if (s.lookahead >= MIN_MATCH$1) {
+ /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+ s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH$1 - 1]);
+ hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+ s.head[s.ins_h] = s.strstart;
+ /***/
+ }
+
+ /* Find the longest match, discarding those <= prev_length.
+ */
+ s.prev_length = s.match_length;
+ s.prev_match = s.match_start;
+ s.match_length = MIN_MATCH$1 - 1;
+
+ if (hash_head !== 0/*NIL*/ && s.prev_length < s.max_lazy_match &&
+ s.strstart - hash_head <= (s.w_size - MIN_LOOKAHEAD)/*MAX_DIST(s)*/) {
+ /* To simplify the code, we prevent matches with the string
+ * of window index 0 (in particular we have to avoid a match
+ * of the string with itself at the start of the input file).
+ */
+ s.match_length = longest_match(s, hash_head);
+ /* longest_match() sets match_start */
+
+ if (s.match_length <= 5 &&
+ (s.strategy === Z_FILTERED || (s.match_length === MIN_MATCH$1 && s.strstart - s.match_start > 4096/*TOO_FAR*/))) {
+
+ /* If prev_match is also MIN_MATCH, match_start is garbage
+ * but we will ignore the current match anyway.
+ */
+ s.match_length = MIN_MATCH$1 - 1;
+ }
+ }
+ /* If there was a match at the previous step and the current
+ * match is not better, output the previous match:
+ */
+ if (s.prev_length >= MIN_MATCH$1 && s.match_length <= s.prev_length) {
+ max_insert = s.strstart + s.lookahead - MIN_MATCH$1;
+ /* Do not insert strings in hash table beyond this. */
+
+ //check_match(s, s.strstart-1, s.prev_match, s.prev_length);
+
+ /***_tr_tally_dist(s, s.strstart - 1 - s.prev_match,
+ s.prev_length - MIN_MATCH, bflush);***/
+ bflush = _tr_tally$1(s, s.strstart - 1 - s.prev_match, s.prev_length - MIN_MATCH$1);
+ /* Insert in hash table all strings up to the end of the match.
+ * strstart-1 and strstart are already inserted. If there is not
+ * enough lookahead, the last two strings are not inserted in
+ * the hash table.
+ */
+ s.lookahead -= s.prev_length - 1;
+ s.prev_length -= 2;
+ do {
+ if (++s.strstart <= max_insert) {
+ /*** INSERT_STRING(s, s.strstart, hash_head); ***/
+ s.ins_h = HASH(s, s.ins_h, s.window[s.strstart + MIN_MATCH$1 - 1]);
+ hash_head = s.prev[s.strstart & s.w_mask] = s.head[s.ins_h];
+ s.head[s.ins_h] = s.strstart;
+ /***/
+ }
+ } while (--s.prev_length !== 0);
+ s.match_available = 0;
+ s.match_length = MIN_MATCH$1 - 1;
+ s.strstart++;
+
+ if (bflush) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+
+ } else if (s.match_available) {
+ /* If there was no match at the previous position, output a
+ * single literal. If there was a match but the current match
+ * is longer, truncate the previous match to a single literal.
+ */
+ //Tracevv((stderr,"%c", s->window[s->strstart-1]));
+ /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
+ bflush = _tr_tally$1(s, 0, s.window[s.strstart - 1]);
+
+ if (bflush) {
+ /*** FLUSH_BLOCK_ONLY(s, 0) ***/
+ flush_block_only(s, false);
+ /***/
+ }
+ s.strstart++;
+ s.lookahead--;
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ } else {
+ /* There is no previous match to compare with, wait for
+ * the next step to decide.
+ */
+ s.match_available = 1;
+ s.strstart++;
+ s.lookahead--;
+ }
+ }
+ //Assert (flush != Z_NO_FLUSH, "no flush?");
+ if (s.match_available) {
+ //Tracevv((stderr,"%c", s->window[s->strstart-1]));
+ /*** _tr_tally_lit(s, s.window[s.strstart-1], bflush); ***/
+ bflush = _tr_tally$1(s, 0, s.window[s.strstart - 1]);
+
+ s.match_available = 0;
+ }
+ s.insert = s.strstart < MIN_MATCH$1 - 1 ? s.strstart : MIN_MATCH$1 - 1;
+ if (flush === Z_FINISH) {
+ /*** FLUSH_BLOCK(s, 1); ***/
+ flush_block_only(s, true);
+ if (s.strm.avail_out === 0) {
+ return BS_FINISH_STARTED;
+ }
+ /***/
+ return BS_FINISH_DONE;
+ }
+ if (s.last_lit) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+
+ return BS_BLOCK_DONE;
+};
+
+
+/* ===========================================================================
+ * For Z_RLE, simply look for runs of bytes, generate matches only of distance
+ * one. Do not maintain a hash table. (It will be regenerated if this run of
+ * deflate switches away from Z_RLE.)
+ */
+const deflate_rle = (s, flush) => {
+
+ let bflush; /* set if current block must be flushed */
+ let prev; /* byte at distance one to match */
+ let scan, strend; /* scan goes up to strend for length of run */
+
+ const _win = s.window;
+
+ for (;;) {
+ /* Make sure that we always have enough lookahead, except
+ * at the end of the input file. We need MAX_MATCH bytes
+ * for the longest run, plus one for the unrolled loop.
+ */
+ if (s.lookahead <= MAX_MATCH$1) {
+ fill_window(s);
+ if (s.lookahead <= MAX_MATCH$1 && flush === Z_NO_FLUSH) {
+ return BS_NEED_MORE;
+ }
+ if (s.lookahead === 0) { break; } /* flush the current block */
+ }
+
+ /* See how many times the previous byte repeats */
+ s.match_length = 0;
+ if (s.lookahead >= MIN_MATCH$1 && s.strstart > 0) {
+ scan = s.strstart - 1;
+ prev = _win[scan];
+ if (prev === _win[++scan] && prev === _win[++scan] && prev === _win[++scan]) {
+ strend = s.strstart + MAX_MATCH$1;
+ do {
+ /*jshint noempty:false*/
+ } while (prev === _win[++scan] && prev === _win[++scan] &&
+ prev === _win[++scan] && prev === _win[++scan] &&
+ prev === _win[++scan] && prev === _win[++scan] &&
+ prev === _win[++scan] && prev === _win[++scan] &&
+ scan < strend);
+ s.match_length = MAX_MATCH$1 - (strend - scan);
+ if (s.match_length > s.lookahead) {
+ s.match_length = s.lookahead;
+ }
+ }
+ //Assert(scan <= s->window+(uInt)(s->window_size-1), "wild scan");
+ }
+
+ /* Emit match if have run of MIN_MATCH or longer, else emit literal */
+ if (s.match_length >= MIN_MATCH$1) {
+ //check_match(s, s.strstart, s.strstart - 1, s.match_length);
+
+ /*** _tr_tally_dist(s, 1, s.match_length - MIN_MATCH, bflush); ***/
+ bflush = _tr_tally$1(s, 1, s.match_length - MIN_MATCH$1);
+
+ s.lookahead -= s.match_length;
+ s.strstart += s.match_length;
+ s.match_length = 0;
+ } else {
+ /* No match, output a literal byte */
+ //Tracevv((stderr,"%c", s->window[s->strstart]));
+ /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+ bflush = _tr_tally$1(s, 0, s.window[s.strstart]);
+
+ s.lookahead--;
+ s.strstart++;
+ }
+ if (bflush) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ }
+ s.insert = 0;
+ if (flush === Z_FINISH) {
+ /*** FLUSH_BLOCK(s, 1); ***/
+ flush_block_only(s, true);
+ if (s.strm.avail_out === 0) {
+ return BS_FINISH_STARTED;
+ }
+ /***/
+ return BS_FINISH_DONE;
+ }
+ if (s.last_lit) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ return BS_BLOCK_DONE;
+};
+
+/* ===========================================================================
+ * For Z_HUFFMAN_ONLY, do not look for matches. Do not maintain a hash table.
+ * (It will be regenerated if this run of deflate switches away from Huffman.)
+ */
+const deflate_huff = (s, flush) => {
+
+ let bflush; /* set if current block must be flushed */
+
+ for (;;) {
+ /* Make sure that we have a literal to write. */
+ if (s.lookahead === 0) {
+ fill_window(s);
+ if (s.lookahead === 0) {
+ if (flush === Z_NO_FLUSH) {
+ return BS_NEED_MORE;
+ }
+ break; /* flush the current block */
+ }
+ }
+
+ /* Output a literal byte */
+ s.match_length = 0;
+ //Tracevv((stderr,"%c", s->window[s->strstart]));
+ /*** _tr_tally_lit(s, s.window[s.strstart], bflush); ***/
+ bflush = _tr_tally$1(s, 0, s.window[s.strstart]);
+ s.lookahead--;
+ s.strstart++;
+ if (bflush) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ }
+ s.insert = 0;
+ if (flush === Z_FINISH) {
+ /*** FLUSH_BLOCK(s, 1); ***/
+ flush_block_only(s, true);
+ if (s.strm.avail_out === 0) {
+ return BS_FINISH_STARTED;
+ }
+ /***/
+ return BS_FINISH_DONE;
+ }
+ if (s.last_lit) {
+ /*** FLUSH_BLOCK(s, 0); ***/
+ flush_block_only(s, false);
+ if (s.strm.avail_out === 0) {
+ return BS_NEED_MORE;
+ }
+ /***/
+ }
+ return BS_BLOCK_DONE;
+};
+
+/* Values for max_lazy_match, good_match and max_chain_length, depending on
+ * the desired pack level (0..9). The values given below have been tuned to
+ * exclude worst case performance for pathological files. Better values may be
+ * found for specific files.
+ */
+function Config(good_length, max_lazy, nice_length, max_chain, func) {
+
+ this.good_length = good_length;
+ this.max_lazy = max_lazy;
+ this.nice_length = nice_length;
+ this.max_chain = max_chain;
+ this.func = func;
+}
+
+const configuration_table = [
+ /* good lazy nice chain */
+ new Config(0, 0, 0, 0, deflate_stored), /* 0 store only */
+ new Config(4, 4, 8, 4, deflate_fast), /* 1 max speed, no lazy matches */
+ new Config(4, 5, 16, 8, deflate_fast), /* 2 */
+ new Config(4, 6, 32, 32, deflate_fast), /* 3 */
+
+ new Config(4, 4, 16, 16, deflate_slow), /* 4 lazy matches */
+ new Config(8, 16, 32, 32, deflate_slow), /* 5 */
+ new Config(8, 16, 128, 128, deflate_slow), /* 6 */
+ new Config(8, 32, 128, 256, deflate_slow), /* 7 */
+ new Config(32, 128, 258, 1024, deflate_slow), /* 8 */
+ new Config(32, 258, 258, 4096, deflate_slow) /* 9 max compression */
+];
+
+
+/* ===========================================================================
+ * Initialize the "longest match" routines for a new zlib stream
+ */
+const lm_init = (s) => {
+
+ s.window_size = 2 * s.w_size;
+
+ /*** CLEAR_HASH(s); ***/
+ zero$1(s.head); // Fill with NIL (= 0);
+
+ /* Set the default configuration parameters:
+ */
+ s.max_lazy_match = configuration_table[s.level].max_lazy;
+ s.good_match = configuration_table[s.level].good_length;
+ s.nice_match = configuration_table[s.level].nice_length;
+ s.max_chain_length = configuration_table[s.level].max_chain;
+
+ s.strstart = 0;
+ s.block_start = 0;
+ s.lookahead = 0;
+ s.insert = 0;
+ s.match_length = s.prev_length = MIN_MATCH$1 - 1;
+ s.match_available = 0;
+ s.ins_h = 0;
+};
+
+
+function DeflateState() {
+ this.strm = null; /* pointer back to this zlib stream */
+ this.status = 0; /* as the name implies */
+ this.pending_buf = null; /* output still pending */
+ this.pending_buf_size = 0; /* size of pending_buf */
+ this.pending_out = 0; /* next pending byte to output to the stream */
+ this.pending = 0; /* nb of bytes in the pending buffer */
+ this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */
+ this.gzhead = null; /* gzip header information to write */
+ this.gzindex = 0; /* where in extra, name, or comment */
+ this.method = Z_DEFLATED; /* can only be DEFLATED */
+ this.last_flush = -1; /* value of flush param for previous deflate call */
+
+ this.w_size = 0; /* LZ77 window size (32K by default) */
+ this.w_bits = 0; /* log2(w_size) (8..16) */
+ this.w_mask = 0; /* w_size - 1 */
+
+ this.window = null;
+ /* Sliding window. Input bytes are read into the second half of the window,
+ * and move to the first half later to keep a dictionary of at least wSize
+ * bytes. With this organization, matches are limited to a distance of
+ * wSize-MAX_MATCH bytes, but this ensures that IO is always
+ * performed with a length multiple of the block size.
+ */
+
+ this.window_size = 0;
+ /* Actual size of window: 2*wSize, except when the user input buffer
+ * is directly used as sliding window.
+ */
+
+ this.prev = null;
+ /* Link to older string with same hash index. To limit the size of this
+ * array to 64K, this link is maintained only for the last 32K strings.
+ * An index in this array is thus a window index modulo 32K.
+ */
+
+ this.head = null; /* Heads of the hash chains or NIL. */
+
+ this.ins_h = 0; /* hash index of string to be inserted */
+ this.hash_size = 0; /* number of elements in hash table */
+ this.hash_bits = 0; /* log2(hash_size) */
+ this.hash_mask = 0; /* hash_size-1 */
+
+ this.hash_shift = 0;
+ /* Number of bits by which ins_h must be shifted at each input
+ * step. It must be such that after MIN_MATCH steps, the oldest
+ * byte no longer takes part in the hash key, that is:
+ * hash_shift * MIN_MATCH >= hash_bits
+ */
+
+ this.block_start = 0;
+ /* Window position at the beginning of the current output block. Gets
+ * negative when the window is moved backwards.
+ */
+
+ this.match_length = 0; /* length of best match */
+ this.prev_match = 0; /* previous match */
+ this.match_available = 0; /* set if previous match exists */
+ this.strstart = 0; /* start of string to insert */
+ this.match_start = 0; /* start of matching string */
+ this.lookahead = 0; /* number of valid bytes ahead in window */
+
+ this.prev_length = 0;
+ /* Length of the best match at previous step. Matches not greater than this
+ * are discarded. This is used in the lazy match evaluation.
+ */
+
+ this.max_chain_length = 0;
+ /* To speed up deflation, hash chains are never searched beyond this
+ * length. A higher limit improves compression ratio but degrades the
+ * speed.
+ */
+
+ this.max_lazy_match = 0;
+ /* Attempt to find a better match only when the current match is strictly
+ * smaller than this value. This mechanism is used only for compression
+ * levels >= 4.
+ */
+ // That's alias to max_lazy_match, don't use directly
+ //this.max_insert_length = 0;
+ /* Insert new strings in the hash table only if the match length is not
+ * greater than this length. This saves time but degrades compression.
+ * max_insert_length is used only for compression levels <= 3.
+ */
+
+ this.level = 0; /* compression level (1..9) */
+ this.strategy = 0; /* favor or force Huffman coding*/
+
+ this.good_match = 0;
+ /* Use a faster search when the previous match is longer than this */
+
+ this.nice_match = 0; /* Stop searching when current match exceeds this */
+
+ /* used by trees.c: */
+
+ /* Didn't use ct_data typedef below to suppress compiler warning */
+
+ // struct ct_data_s dyn_ltree[HEAP_SIZE]; /* literal and length tree */
+ // struct ct_data_s dyn_dtree[2*D_CODES+1]; /* distance tree */
+ // struct ct_data_s bl_tree[2*BL_CODES+1]; /* Huffman tree for bit lengths */
+
+ // Use flat array of DOUBLE size, with interleaved fata,
+ // because JS does not support effective
+ this.dyn_ltree = new Uint16Array(HEAP_SIZE$1 * 2);
+ this.dyn_dtree = new Uint16Array((2 * D_CODES$1 + 1) * 2);
+ this.bl_tree = new Uint16Array((2 * BL_CODES$1 + 1) * 2);
+ zero$1(this.dyn_ltree);
+ zero$1(this.dyn_dtree);
+ zero$1(this.bl_tree);
+
+ this.l_desc = null; /* desc. for literal tree */
+ this.d_desc = null; /* desc. for distance tree */
+ this.bl_desc = null; /* desc. for bit length tree */
+
+ //ush bl_count[MAX_BITS+1];
+ this.bl_count = new Uint16Array(MAX_BITS$1 + 1);
+ /* number of codes at each bit length for an optimal tree */
+
+ //int heap[2*L_CODES+1]; /* heap used to build the Huffman trees */
+ this.heap = new Uint16Array(2 * L_CODES$1 + 1); /* heap used to build the Huffman trees */
+ zero$1(this.heap);
+
+ this.heap_len = 0; /* number of elements in the heap */
+ this.heap_max = 0; /* element of largest frequency */
+ /* The sons of heap[n] are heap[2*n] and heap[2*n+1]. heap[0] is not used.
+ * The same heap array is used to build all trees.
+ */
+
+ this.depth = new Uint16Array(2 * L_CODES$1 + 1); //uch depth[2*L_CODES+1];
+ zero$1(this.depth);
+ /* Depth of each subtree used as tie breaker for trees of equal frequency
+ */
+
+ this.l_buf = 0; /* buffer index for literals or lengths */
+
+ this.lit_bufsize = 0;
+ /* Size of match buffer for literals/lengths. There are 4 reasons for
+ * limiting lit_bufsize to 64K:
+ * - frequencies can be kept in 16 bit counters
+ * - if compression is not successful for the first block, all input
+ * data is still in the window so we can still emit a stored block even
+ * when input comes from standard input. (This can also be done for
+ * all blocks if lit_bufsize is not greater than 32K.)
+ * - if compression is not successful for a file smaller than 64K, we can
+ * even emit a stored file instead of a stored block (saving 5 bytes).
+ * This is applicable only for zip (not gzip or zlib).
+ * - creating new Huffman trees less frequently may not provide fast
+ * adaptation to changes in the input data statistics. (Take for
+ * example a binary file with poorly compressible code followed by
+ * a highly compressible string table.) Smaller buffer sizes give
+ * fast adaptation but have of course the overhead of transmitting
+ * trees more frequently.
+ * - I can't count above 4
+ */
+
+ this.last_lit = 0; /* running index in l_buf */
+
+ this.d_buf = 0;
+ /* Buffer index for distances. To simplify the code, d_buf and l_buf have
+ * the same number of elements. To use different lengths, an extra flag
+ * array would be necessary.
+ */
+
+ this.opt_len = 0; /* bit length of current block with optimal trees */
+ this.static_len = 0; /* bit length of current block with static trees */
+ this.matches = 0; /* number of string matches in current block */
+ this.insert = 0; /* bytes at end of window left to insert */
+
+
+ this.bi_buf = 0;
+ /* Output buffer. bits are inserted starting at the bottom (least
+ * significant bits).
+ */
+ this.bi_valid = 0;
+ /* Number of valid bits in bi_buf. All bits above the last valid bit
+ * are always zero.
+ */
+
+ // Used for window memory init. We safely ignore it for JS. That makes
+ // sense only for pointers and memory check tools.
+ //this.high_water = 0;
+ /* High water mark offset in window for initialized bytes -- bytes above
+ * this are set to zero in order to avoid memory check warnings when
+ * longest match routines access bytes past the input. This is then
+ * updated to the new high water mark.
+ */
+}
+
+
+const deflateResetKeep = (strm) => {
+
+ if (!strm || !strm.state) {
+ return err(strm, Z_STREAM_ERROR);
+ }
+
+ strm.total_in = strm.total_out = 0;
+ strm.data_type = Z_UNKNOWN$1;
+
+ const s = strm.state;
+ s.pending = 0;
+ s.pending_out = 0;
+
+ if (s.wrap < 0) {
+ s.wrap = -s.wrap;
+ /* was made negative by deflate(..., Z_FINISH); */
+ }
+ s.status = (s.wrap ? INIT_STATE : BUSY_STATE);
+ strm.adler = (s.wrap === 2) ?
+ 0 // crc32(0, Z_NULL, 0)
+ :
+ 1; // adler32(0, Z_NULL, 0)
+ s.last_flush = Z_NO_FLUSH;
+ _tr_init$1(s);
+ return Z_OK;
+};
+
+
+const deflateReset = (strm) => {
+
+ const ret = deflateResetKeep(strm);
+ if (ret === Z_OK) {
+ lm_init(strm.state);
+ }
+ return ret;
+};
+
+
+const deflateSetHeader = (strm, head) => {
+
+ if (!strm || !strm.state) { return Z_STREAM_ERROR; }
+ if (strm.state.wrap !== 2) { return Z_STREAM_ERROR; }
+ strm.state.gzhead = head;
+ return Z_OK;
+};
+
+
+const deflateInit2 = (strm, level, method, windowBits, memLevel, strategy) => {
+
+ if (!strm) { // === Z_NULL
+ return Z_STREAM_ERROR;
+ }
+ let wrap = 1;
+
+ if (level === Z_DEFAULT_COMPRESSION) {
+ level = 6;
+ }
+
+ if (windowBits < 0) { /* suppress zlib wrapper */
+ wrap = 0;
+ windowBits = -windowBits;
+ }
+
+ else if (windowBits > 15) {
+ wrap = 2; /* write gzip wrapper instead */
+ windowBits -= 16;
+ }
+
+
+ if (memLevel < 1 || memLevel > MAX_MEM_LEVEL || method !== Z_DEFLATED ||
+ windowBits < 8 || windowBits > 15 || level < 0 || level > 9 ||
+ strategy < 0 || strategy > Z_FIXED$1) {
+ return err(strm, Z_STREAM_ERROR);
+ }
+
+
+ if (windowBits === 8) {
+ windowBits = 9;
+ }
+ /* until 256-byte window bug fixed */
+
+ const s = new DeflateState();
+
+ strm.state = s;
+ s.strm = strm;
+
+ s.wrap = wrap;
+ s.gzhead = null;
+ s.w_bits = windowBits;
+ s.w_size = 1 << s.w_bits;
+ s.w_mask = s.w_size - 1;
+
+ s.hash_bits = memLevel + 7;
+ s.hash_size = 1 << s.hash_bits;
+ s.hash_mask = s.hash_size - 1;
+ s.hash_shift = ~~((s.hash_bits + MIN_MATCH$1 - 1) / MIN_MATCH$1);
+
+ s.window = new Uint8Array(s.w_size * 2);
+ s.head = new Uint16Array(s.hash_size);
+ s.prev = new Uint16Array(s.w_size);
+
+ // Don't need mem init magic for JS.
+ //s.high_water = 0; /* nothing written to s->window yet */
+
+ s.lit_bufsize = 1 << (memLevel + 6); /* 16K elements by default */
+
+ s.pending_buf_size = s.lit_bufsize * 4;
+
+ //overlay = (ushf *) ZALLOC(strm, s->lit_bufsize, sizeof(ush)+2);
+ //s->pending_buf = (uchf *) overlay;
+ s.pending_buf = new Uint8Array(s.pending_buf_size);
+
+ // It is offset from `s.pending_buf` (size is `s.lit_bufsize * 2`)
+ //s->d_buf = overlay + s->lit_bufsize/sizeof(ush);
+ s.d_buf = 1 * s.lit_bufsize;
+
+ //s->l_buf = s->pending_buf + (1+sizeof(ush))*s->lit_bufsize;
+ s.l_buf = (1 + 2) * s.lit_bufsize;
+
+ s.level = level;
+ s.strategy = strategy;
+ s.method = method;
+
+ return deflateReset(strm);
+};
+
+const deflateInit = (strm, level) => {
+
+ return deflateInit2(strm, level, Z_DEFLATED, MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY);
+};
+
+
+const deflate = (strm, flush) => {
+
+ let beg, val; // for gzip header write only
+
+ if (!strm || !strm.state ||
+ flush > Z_BLOCK || flush < 0) {
+ return strm ? err(strm, Z_STREAM_ERROR) : Z_STREAM_ERROR;
+ }
+
+ const s = strm.state;
+
+ if (!strm.output ||
+ (!strm.input && strm.avail_in !== 0) ||
+ (s.status === FINISH_STATE && flush !== Z_FINISH)) {
+ return err(strm, (strm.avail_out === 0) ? Z_BUF_ERROR : Z_STREAM_ERROR);
+ }
+
+ s.strm = strm; /* just in case */
+ const old_flush = s.last_flush;
+ s.last_flush = flush;
+
+ /* Write the header */
+ if (s.status === INIT_STATE) {
+
+ if (s.wrap === 2) { // GZIP header
+ strm.adler = 0; //crc32(0L, Z_NULL, 0);
+ put_byte(s, 31);
+ put_byte(s, 139);
+ put_byte(s, 8);
+ if (!s.gzhead) { // s->gzhead == Z_NULL
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, 0);
+ put_byte(s, s.level === 9 ? 2 :
+ (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
+ 4 : 0));
+ put_byte(s, OS_CODE);
+ s.status = BUSY_STATE;
+ }
+ else {
+ put_byte(s, (s.gzhead.text ? 1 : 0) +
+ (s.gzhead.hcrc ? 2 : 0) +
+ (!s.gzhead.extra ? 0 : 4) +
+ (!s.gzhead.name ? 0 : 8) +
+ (!s.gzhead.comment ? 0 : 16)
+ );
+ put_byte(s, s.gzhead.time & 0xff);
+ put_byte(s, (s.gzhead.time >> 8) & 0xff);
+ put_byte(s, (s.gzhead.time >> 16) & 0xff);
+ put_byte(s, (s.gzhead.time >> 24) & 0xff);
+ put_byte(s, s.level === 9 ? 2 :
+ (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2 ?
+ 4 : 0));
+ put_byte(s, s.gzhead.os & 0xff);
+ if (s.gzhead.extra && s.gzhead.extra.length) {
+ put_byte(s, s.gzhead.extra.length & 0xff);
+ put_byte(s, (s.gzhead.extra.length >> 8) & 0xff);
+ }
+ if (s.gzhead.hcrc) {
+ strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending, 0);
+ }
+ s.gzindex = 0;
+ s.status = EXTRA_STATE;
+ }
+ }
+ else // DEFLATE header
+ {
+ let header = (Z_DEFLATED + ((s.w_bits - 8) << 4)) << 8;
+ let level_flags = -1;
+
+ if (s.strategy >= Z_HUFFMAN_ONLY || s.level < 2) {
+ level_flags = 0;
+ } else if (s.level < 6) {
+ level_flags = 1;
+ } else if (s.level === 6) {
+ level_flags = 2;
+ } else {
+ level_flags = 3;
+ }
+ header |= (level_flags << 6);
+ if (s.strstart !== 0) { header |= PRESET_DICT; }
+ header += 31 - (header % 31);
+
+ s.status = BUSY_STATE;
+ putShortMSB(s, header);
+
+ /* Save the adler32 of the preset dictionary: */
+ if (s.strstart !== 0) {
+ putShortMSB(s, strm.adler >>> 16);
+ putShortMSB(s, strm.adler & 0xffff);
+ }
+ strm.adler = 1; // adler32(0L, Z_NULL, 0);
+ }
+ }
+
+//#ifdef GZIP
+ if (s.status === EXTRA_STATE) {
+ if (s.gzhead.extra/* != Z_NULL*/) {
+ beg = s.pending; /* start of bytes to update crc */
+
+ while (s.gzindex < (s.gzhead.extra.length & 0xffff)) {
+ if (s.pending === s.pending_buf_size) {
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ flush_pending(strm);
+ beg = s.pending;
+ if (s.pending === s.pending_buf_size) {
+ break;
+ }
+ }
+ put_byte(s, s.gzhead.extra[s.gzindex] & 0xff);
+ s.gzindex++;
+ }
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ if (s.gzindex === s.gzhead.extra.length) {
+ s.gzindex = 0;
+ s.status = NAME_STATE;
+ }
+ }
+ else {
+ s.status = NAME_STATE;
+ }
+ }
+ if (s.status === NAME_STATE) {
+ if (s.gzhead.name/* != Z_NULL*/) {
+ beg = s.pending; /* start of bytes to update crc */
+ //int val;
+
+ do {
+ if (s.pending === s.pending_buf_size) {
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ flush_pending(strm);
+ beg = s.pending;
+ if (s.pending === s.pending_buf_size) {
+ val = 1;
+ break;
+ }
+ }
+ // JS specific: little magic to add zero terminator to end of string
+ if (s.gzindex < s.gzhead.name.length) {
+ val = s.gzhead.name.charCodeAt(s.gzindex++) & 0xff;
+ } else {
+ val = 0;
+ }
+ put_byte(s, val);
+ } while (val !== 0);
+
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ if (val === 0) {
+ s.gzindex = 0;
+ s.status = COMMENT_STATE;
+ }
+ }
+ else {
+ s.status = COMMENT_STATE;
+ }
+ }
+ if (s.status === COMMENT_STATE) {
+ if (s.gzhead.comment/* != Z_NULL*/) {
+ beg = s.pending; /* start of bytes to update crc */
+ //int val;
+
+ do {
+ if (s.pending === s.pending_buf_size) {
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ flush_pending(strm);
+ beg = s.pending;
+ if (s.pending === s.pending_buf_size) {
+ val = 1;
+ break;
+ }
+ }
+ // JS specific: little magic to add zero terminator to end of string
+ if (s.gzindex < s.gzhead.comment.length) {
+ val = s.gzhead.comment.charCodeAt(s.gzindex++) & 0xff;
+ } else {
+ val = 0;
+ }
+ put_byte(s, val);
+ } while (val !== 0);
+
+ if (s.gzhead.hcrc && s.pending > beg) {
+ strm.adler = crc32_1(strm.adler, s.pending_buf, s.pending - beg, beg);
+ }
+ if (val === 0) {
+ s.status = HCRC_STATE;
+ }
+ }
+ else {
+ s.status = HCRC_STATE;
+ }
+ }
+ if (s.status === HCRC_STATE) {
+ if (s.gzhead.hcrc) {
+ if (s.pending + 2 > s.pending_buf_size) {
+ flush_pending(strm);
+ }
+ if (s.pending + 2 <= s.pending_buf_size) {
+ put_byte(s, strm.adler & 0xff);
+ put_byte(s, (strm.adler >> 8) & 0xff);
+ strm.adler = 0; //crc32(0L, Z_NULL, 0);
+ s.status = BUSY_STATE;
+ }
+ }
+ else {
+ s.status = BUSY_STATE;
+ }
+ }
+//#endif
+
+ /* Flush as much pending output as possible */
+ if (s.pending !== 0) {
+ flush_pending(strm);
+ if (strm.avail_out === 0) {
+ /* Since avail_out is 0, deflate will be called again with
+ * more output space, but possibly with both pending and
+ * avail_in equal to zero. There won't be anything to do,
+ * but this is not an error situation so make sure we
+ * return OK instead of BUF_ERROR at next call of deflate:
+ */
+ s.last_flush = -1;
+ return Z_OK;
+ }
+
+ /* Make sure there is something to do and avoid duplicate consecutive
+ * flushes. For repeated and useless calls with Z_FINISH, we keep
+ * returning Z_STREAM_END instead of Z_BUF_ERROR.
+ */
+ } else if (strm.avail_in === 0 && rank(flush) <= rank(old_flush) &&
+ flush !== Z_FINISH) {
+ return err(strm, Z_BUF_ERROR);
+ }
+
+ /* User must not provide more input after the first FINISH: */
+ if (s.status === FINISH_STATE && strm.avail_in !== 0) {
+ return err(strm, Z_BUF_ERROR);
+ }
+
+ /* Start a new block or continue the current one.
+ */
+ if (strm.avail_in !== 0 || s.lookahead !== 0 ||
+ (flush !== Z_NO_FLUSH && s.status !== FINISH_STATE)) {
+ let bstate = (s.strategy === Z_HUFFMAN_ONLY) ? deflate_huff(s, flush) :
+ (s.strategy === Z_RLE ? deflate_rle(s, flush) :
+ configuration_table[s.level].func(s, flush));
+
+ if (bstate === BS_FINISH_STARTED || bstate === BS_FINISH_DONE) {
+ s.status = FINISH_STATE;
+ }
+ if (bstate === BS_NEED_MORE || bstate === BS_FINISH_STARTED) {
+ if (strm.avail_out === 0) {
+ s.last_flush = -1;
+ /* avoid BUF_ERROR next call, see above */
+ }
+ return Z_OK;
+ /* If flush != Z_NO_FLUSH && avail_out == 0, the next call
+ * of deflate should use the same flush parameter to make sure
+ * that the flush is complete. So we don't have to output an
+ * empty block here, this will be done at next call. This also
+ * ensures that for a very small output buffer, we emit at most
+ * one empty block.
+ */
+ }
+ if (bstate === BS_BLOCK_DONE) {
+ if (flush === Z_PARTIAL_FLUSH) {
+ _tr_align$1(s);
+ }
+ else if (flush !== Z_BLOCK) { /* FULL_FLUSH or SYNC_FLUSH */
+
+ _tr_stored_block$1(s, 0, 0, false);
+ /* For a full flush, this empty block will be recognized
+ * as a special marker by inflate_sync().
+ */
+ if (flush === Z_FULL_FLUSH) {
+ /*** CLEAR_HASH(s); ***/ /* forget history */
+ zero$1(s.head); // Fill with NIL (= 0);
+
+ if (s.lookahead === 0) {
+ s.strstart = 0;
+ s.block_start = 0;
+ s.insert = 0;
+ }
+ }
+ }
+ flush_pending(strm);
+ if (strm.avail_out === 0) {
+ s.last_flush = -1; /* avoid BUF_ERROR at next call, see above */
+ return Z_OK;
+ }
+ }
+ }
+ //Assert(strm->avail_out > 0, "bug2");
+ //if (strm.avail_out <= 0) { throw new Error("bug2");}
+
+ if (flush !== Z_FINISH) { return Z_OK; }
+ if (s.wrap <= 0) { return Z_STREAM_END; }
+
+ /* Write the trailer */
+ if (s.wrap === 2) {
+ put_byte(s, strm.adler & 0xff);
+ put_byte(s, (strm.adler >> 8) & 0xff);
+ put_byte(s, (strm.adler >> 16) & 0xff);
+ put_byte(s, (strm.adler >> 24) & 0xff);
+ put_byte(s, strm.total_in & 0xff);
+ put_byte(s, (strm.total_in >> 8) & 0xff);
+ put_byte(s, (strm.total_in >> 16) & 0xff);
+ put_byte(s, (strm.total_in >> 24) & 0xff);
+ }
+ else
+ {
+ putShortMSB(s, strm.adler >>> 16);
+ putShortMSB(s, strm.adler & 0xffff);
+ }
+
+ flush_pending(strm);
+ /* If avail_out is zero, the application will call deflate again
+ * to flush the rest.
+ */
+ if (s.wrap > 0) { s.wrap = -s.wrap; }
+ /* write the trailer only once! */
+ return s.pending !== 0 ? Z_OK : Z_STREAM_END;
+};
+
+
+const deflateEnd = (strm) => {
+
+ if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
+ return Z_STREAM_ERROR;
+ }
+
+ const status = strm.state.status;
+ if (status !== INIT_STATE &&
+ status !== EXTRA_STATE &&
+ status !== NAME_STATE &&
+ status !== COMMENT_STATE &&
+ status !== HCRC_STATE &&
+ status !== BUSY_STATE &&
+ status !== FINISH_STATE
+ ) {
+ return err(strm, Z_STREAM_ERROR);
+ }
+
+ strm.state = null;
+
+ return status === BUSY_STATE ? err(strm, Z_DATA_ERROR) : Z_OK;
+};
+
+
+/* =========================================================================
+ * Initializes the compression dictionary from the given byte
+ * sequence without producing any compressed output.
+ */
+const deflateSetDictionary = (strm, dictionary) => {
+
+ let dictLength = dictionary.length;
+
+ if (!strm/*== Z_NULL*/ || !strm.state/*== Z_NULL*/) {
+ return Z_STREAM_ERROR;
+ }
+
+ const s = strm.state;
+ const wrap = s.wrap;
+
+ if (wrap === 2 || (wrap === 1 && s.status !== INIT_STATE) || s.lookahead) {
+ return Z_STREAM_ERROR;
+ }
+
+ /* when using zlib wrappers, compute Adler-32 for provided dictionary */
+ if (wrap === 1) {
+ /* adler32(strm->adler, dictionary, dictLength); */
+ strm.adler = adler32_1(strm.adler, dictionary, dictLength, 0);
+ }
+
+ s.wrap = 0; /* avoid computing Adler-32 in read_buf */
+
+ /* if dictionary would fill window, just replace the history */
+ if (dictLength >= s.w_size) {
+ if (wrap === 0) { /* already empty otherwise */
+ /*** CLEAR_HASH(s); ***/
+ zero$1(s.head); // Fill with NIL (= 0);
+ s.strstart = 0;
+ s.block_start = 0;
+ s.insert = 0;
+ }
+ /* use the tail */
+ // dictionary = dictionary.slice(dictLength - s.w_size);
+ let tmpDict = new Uint8Array(s.w_size);
+ tmpDict.set(dictionary.subarray(dictLength - s.w_size, dictLength), 0);
+ dictionary = tmpDict;
+ dictLength = s.w_size;
+ }
+ /* insert dictionary into window and hash */
+ const avail = strm.avail_in;
+ const next = strm.next_in;
+ const input = strm.input;
+ strm.avail_in = dictLength;
+ strm.next_in = 0;
+ strm.input = dictionary;
+ fill_window(s);
+ while (s.lookahead >= MIN_MATCH$1) {
+ let str = s.strstart;
+ let n = s.lookahead - (MIN_MATCH$1 - 1);
+ do {
+ /* UPDATE_HASH(s, s->ins_h, s->window[str + MIN_MATCH-1]); */
+ s.ins_h = HASH(s, s.ins_h, s.window[str + MIN_MATCH$1 - 1]);
+
+ s.prev[str & s.w_mask] = s.head[s.ins_h];
+
+ s.head[s.ins_h] = str;
+ str++;
+ } while (--n);
+ s.strstart = str;
+ s.lookahead = MIN_MATCH$1 - 1;
+ fill_window(s);
+ }
+ s.strstart += s.lookahead;
+ s.block_start = s.strstart;
+ s.insert = s.lookahead;
+ s.lookahead = 0;
+ s.match_length = s.prev_length = MIN_MATCH$1 - 1;
+ s.match_available = 0;
+ strm.next_in = next;
+ strm.input = input;
+ strm.avail_in = avail;
+ s.wrap = wrap;
+ return Z_OK;
+};
+
+
+var deflateInit_1 = deflateInit;
+var deflateInit2_1 = deflateInit2;
+var deflateReset_1 = deflateReset;
+var deflateResetKeep_1 = deflateResetKeep;
+var deflateSetHeader_1 = deflateSetHeader;
+var deflate_2 = deflate;
+var deflateEnd_1 = deflateEnd;
+var deflateSetDictionary_1 = deflateSetDictionary;
+var deflateInfo = 'pako deflate (from Nodeca project)';
+
+/* Not implemented
+module.exports.deflateBound = deflateBound;
+module.exports.deflateCopy = deflateCopy;
+module.exports.deflateParams = deflateParams;
+module.exports.deflatePending = deflatePending;
+module.exports.deflatePrime = deflatePrime;
+module.exports.deflateTune = deflateTune;
+*/
+
+var deflate_1 = {
+ deflateInit: deflateInit_1,
+ deflateInit2: deflateInit2_1,
+ deflateReset: deflateReset_1,
+ deflateResetKeep: deflateResetKeep_1,
+ deflateSetHeader: deflateSetHeader_1,
+ deflate: deflate_2,
+ deflateEnd: deflateEnd_1,
+ deflateSetDictionary: deflateSetDictionary_1,
+ deflateInfo: deflateInfo
+};
+
+const _has = (obj, key) => {
+ return Object.prototype.hasOwnProperty.call(obj, key);
+};
+
+var assign = function (obj /*from1, from2, from3, ...*/) {
+ const sources = Array.prototype.slice.call(arguments, 1);
+ while (sources.length) {
+ const source = sources.shift();
+ if (!source) { continue; }
+
+ if (typeof source !== 'object') {
+ throw new TypeError(source + 'must be non-object');
+ }
+
+ for (const p in source) {
+ if (_has(source, p)) {
+ obj[p] = source[p];
+ }
+ }
+ }
+
+ return obj;
+};
+
+
+// Join array of chunks to single array.
+var flattenChunks = (chunks) => {
+ // calculate data length
+ let len = 0;
+
+ for (let i = 0, l = chunks.length; i < l; i++) {
+ len += chunks[i].length;
+ }
+
+ // join chunks
+ const result = new Uint8Array(len);
+
+ for (let i = 0, pos = 0, l = chunks.length; i < l; i++) {
+ let chunk = chunks[i];
+ result.set(chunk, pos);
+ pos += chunk.length;
+ }
+
+ return result;
+};
+
+var common = {
+ assign: assign,
+ flattenChunks: flattenChunks
+};
+
+// String encode/decode helpers
+
+
+// Quick check if we can use fast array to bin string conversion
+//
+// - apply(Array) can fail on Android 2.2
+// - apply(Uint8Array) can fail on iOS 5.1 Safari
+//
+let STR_APPLY_UIA_OK = true;
+
+try { String.fromCharCode.apply(null, new Uint8Array(1)); } catch (__) { STR_APPLY_UIA_OK = false; }
+
+
+// Table with utf8 lengths (calculated by first byte of sequence)
+// Note, that 5 & 6-byte values and some 4-byte values can not be represented in JS,
+// because max possible codepoint is 0x10ffff
+const _utf8len = new Uint8Array(256);
+for (let q = 0; q < 256; q++) {
+ _utf8len[q] = (q >= 252 ? 6 : q >= 248 ? 5 : q >= 240 ? 4 : q >= 224 ? 3 : q >= 192 ? 2 : 1);
+}
+_utf8len[254] = _utf8len[254] = 1; // Invalid sequence start
+
+
+// convert string to array (typed, when possible)
+var string2buf = (str) => {
+ let buf, c, c2, m_pos, i, str_len = str.length, buf_len = 0;
+
+ // count binary size
+ for (m_pos = 0; m_pos < str_len; m_pos++) {
+ c = str.charCodeAt(m_pos);
+ if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
+ c2 = str.charCodeAt(m_pos + 1);
+ if ((c2 & 0xfc00) === 0xdc00) {
+ c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
+ m_pos++;
+ }
+ }
+ buf_len += c < 0x80 ? 1 : c < 0x800 ? 2 : c < 0x10000 ? 3 : 4;
+ }
+
+ // allocate buffer
+ buf = new Uint8Array(buf_len);
+
+ // convert
+ for (i = 0, m_pos = 0; i < buf_len; m_pos++) {
+ c = str.charCodeAt(m_pos);
+ if ((c & 0xfc00) === 0xd800 && (m_pos + 1 < str_len)) {
+ c2 = str.charCodeAt(m_pos + 1);
+ if ((c2 & 0xfc00) === 0xdc00) {
+ c = 0x10000 + ((c - 0xd800) << 10) + (c2 - 0xdc00);
+ m_pos++;
+ }
+ }
+ if (c < 0x80) {
+ /* one byte */
+ buf[i++] = c;
+ } else if (c < 0x800) {
+ /* two bytes */
+ buf[i++] = 0xC0 | (c >>> 6);
+ buf[i++] = 0x80 | (c & 0x3f);
+ } else if (c < 0x10000) {
+ /* three bytes */
+ buf[i++] = 0xE0 | (c >>> 12);
+ buf[i++] = 0x80 | (c >>> 6 & 0x3f);
+ buf[i++] = 0x80 | (c & 0x3f);
+ } else {
+ /* four bytes */
+ buf[i++] = 0xf0 | (c >>> 18);
+ buf[i++] = 0x80 | (c >>> 12 & 0x3f);
+ buf[i++] = 0x80 | (c >>> 6 & 0x3f);
+ buf[i++] = 0x80 | (c & 0x3f);
+ }
+ }
+
+ return buf;
+};
+
+// Helper
+const buf2binstring = (buf, len) => {
+ // On Chrome, the arguments in a function call that are allowed is `65534`.
+ // If the length of the buffer is smaller than that, we can use this optimization,
+ // otherwise we will take a slower path.
+ if (len < 65534) {
+ if (buf.subarray && STR_APPLY_UIA_OK) {
+ return String.fromCharCode.apply(null, buf.length === len ? buf : buf.subarray(0, len));
+ }
+ }
+
+ let result = '';
+ for (let i = 0; i < len; i++) {
+ result += String.fromCharCode(buf[i]);
+ }
+ return result;
+};
+
+
+// convert array to string
+var buf2string = (buf, max) => {
+ let i, out;
+ const len = max || buf.length;
+
+ // Reserve max possible length (2 words per char)
+ // NB: by unknown reasons, Array is significantly faster for
+ // String.fromCharCode.apply than Uint16Array.
+ const utf16buf = new Array(len * 2);
+
+ for (out = 0, i = 0; i < len;) {
+ let c = buf[i++];
+ // quick process ascii
+ if (c < 0x80) { utf16buf[out++] = c; continue; }
+
+ let c_len = _utf8len[c];
+ // skip 5 & 6 byte codes
+ if (c_len > 4) { utf16buf[out++] = 0xfffd; i += c_len - 1; continue; }
+
+ // apply mask on first byte
+ c &= c_len === 2 ? 0x1f : c_len === 3 ? 0x0f : 0x07;
+ // join the rest
+ while (c_len > 1 && i < len) {
+ c = (c << 6) | (buf[i++] & 0x3f);
+ c_len--;
+ }
+
+ // terminated by end of string?
+ if (c_len > 1) { utf16buf[out++] = 0xfffd; continue; }
+
+ if (c < 0x10000) {
+ utf16buf[out++] = c;
+ } else {
+ c -= 0x10000;
+ utf16buf[out++] = 0xd800 | ((c >> 10) & 0x3ff);
+ utf16buf[out++] = 0xdc00 | (c & 0x3ff);
+ }
+ }
+
+ return buf2binstring(utf16buf, out);
+};
+
+
+// Calculate max possible position in utf8 buffer,
+// that will not break sequence. If that's not possible
+// - (very small limits) return max size as is.
+//
+// buf[] - utf8 bytes array
+// max - length limit (mandatory);
+var utf8border = (buf, max) => {
+
+ max = max || buf.length;
+ if (max > buf.length) { max = buf.length; }
+
+ // go back from last position, until start of sequence found
+ let pos = max - 1;
+ while (pos >= 0 && (buf[pos] & 0xC0) === 0x80) { pos--; }
+
+ // Very small and broken sequence,
+ // return max, because we should return something anyway.
+ if (pos < 0) { return max; }
+
+ // If we came to start of buffer - that means buffer is too small,
+ // return max too.
+ if (pos === 0) { return max; }
+
+ return (pos + _utf8len[buf[pos]] > max) ? pos : max;
+};
+
+var strings = {
+ string2buf: string2buf,
+ buf2string: buf2string,
+ utf8border: utf8border
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+function ZStream() {
+ /* next input byte */
+ this.input = null; // JS specific, because we have no pointers
+ this.next_in = 0;
+ /* number of bytes available at input */
+ this.avail_in = 0;
+ /* total number of input bytes read so far */
+ this.total_in = 0;
+ /* next output byte should be put there */
+ this.output = null; // JS specific, because we have no pointers
+ this.next_out = 0;
+ /* remaining free space at output */
+ this.avail_out = 0;
+ /* total number of bytes output so far */
+ this.total_out = 0;
+ /* last error message, NULL if no error */
+ this.msg = ''/*Z_NULL*/;
+ /* not visible by applications */
+ this.state = null;
+ /* best guess about the data type: binary or text */
+ this.data_type = 2/*Z_UNKNOWN*/;
+ /* adler32 value of the uncompressed data */
+ this.adler = 0;
+}
+
+var zstream = ZStream;
+
+const toString = Object.prototype.toString;
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+const {
+ Z_NO_FLUSH: Z_NO_FLUSH$1, Z_SYNC_FLUSH, Z_FULL_FLUSH: Z_FULL_FLUSH$1, Z_FINISH: Z_FINISH$1,
+ Z_OK: Z_OK$1, Z_STREAM_END: Z_STREAM_END$1,
+ Z_DEFAULT_COMPRESSION: Z_DEFAULT_COMPRESSION$1,
+ Z_DEFAULT_STRATEGY: Z_DEFAULT_STRATEGY$1,
+ Z_DEFLATED: Z_DEFLATED$1
+} = constants;
+
+/* ===========================================================================*/
+
+
+/**
+ * class Deflate
+ *
+ * Generic JS-style wrapper for zlib calls. If you don't need
+ * streaming behaviour - use more simple functions: [[deflate]],
+ * [[deflateRaw]] and [[gzip]].
+ **/
+
+/* internal
+ * Deflate.chunks -> Array
+ *
+ * Chunks of output data, if [[Deflate#onData]] not overridden.
+ **/
+
+/**
+ * Deflate.result -> Uint8Array
+ *
+ * Compressed result, generated by default [[Deflate#onData]]
+ * and [[Deflate#onEnd]] handlers. Filled after you push last chunk
+ * (call [[Deflate#push]] with `Z_FINISH` / `true` param).
+ **/
+
+/**
+ * Deflate.err -> Number
+ *
+ * Error code after deflate finished. 0 (Z_OK) on success.
+ * You will not need it in real life, because deflate errors
+ * are possible only on wrong options or bad `onData` / `onEnd`
+ * custom handlers.
+ **/
+
+/**
+ * Deflate.msg -> String
+ *
+ * Error message, if [[Deflate.err]] != 0
+ **/
+
+
+/**
+ * new Deflate(options)
+ * - options (Object): zlib deflate options.
+ *
+ * Creates new deflator instance with specified params. Throws exception
+ * on bad params. Supported options:
+ *
+ * - `level`
+ * - `windowBits`
+ * - `memLevel`
+ * - `strategy`
+ * - `dictionary`
+ *
+ * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
+ * for more information on these.
+ *
+ * Additional options, for internal needs:
+ *
+ * - `chunkSize` - size of generated data chunks (16K by default)
+ * - `raw` (Boolean) - do raw deflate
+ * - `gzip` (Boolean) - create gzip wrapper
+ * - `header` (Object) - custom header for gzip
+ * - `text` (Boolean) - true if compressed data believed to be text
+ * - `time` (Number) - modification time, unix timestamp
+ * - `os` (Number) - operation system code
+ * - `extra` (Array) - array of bytes with extra data (max 65536)
+ * - `name` (String) - file name (binary string)
+ * - `comment` (String) - comment (binary string)
+ * - `hcrc` (Boolean) - true if header crc should be added
+ *
+ * ##### Example:
+ *
+ * ```javascript
+ * const pako = require('pako')
+ * , chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9])
+ * , chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]);
+ *
+ * const deflate = new pako.Deflate({ level: 3});
+ *
+ * deflate.push(chunk1, false);
+ * deflate.push(chunk2, true); // true -> last chunk
+ *
+ * if (deflate.err) { throw new Error(deflate.err); }
+ *
+ * console.log(deflate.result);
+ * ```
+ **/
+function Deflate(options) {
+ this.options = common.assign({
+ level: Z_DEFAULT_COMPRESSION$1,
+ method: Z_DEFLATED$1,
+ chunkSize: 16384,
+ windowBits: 15,
+ memLevel: 8,
+ strategy: Z_DEFAULT_STRATEGY$1
+ }, options || {});
+
+ let opt = this.options;
+
+ if (opt.raw && (opt.windowBits > 0)) {
+ opt.windowBits = -opt.windowBits;
+ }
+
+ else if (opt.gzip && (opt.windowBits > 0) && (opt.windowBits < 16)) {
+ opt.windowBits += 16;
+ }
+
+ this.err = 0; // error code, if happens (0 = Z_OK)
+ this.msg = ''; // error message
+ this.ended = false; // used to avoid multiple onEnd() calls
+ this.chunks = []; // chunks of compressed data
+
+ this.strm = new zstream();
+ this.strm.avail_out = 0;
+
+ let status = deflate_1.deflateInit2(
+ this.strm,
+ opt.level,
+ opt.method,
+ opt.windowBits,
+ opt.memLevel,
+ opt.strategy
+ );
+
+ if (status !== Z_OK$1) {
+ throw new Error(messages[status]);
+ }
+
+ if (opt.header) {
+ deflate_1.deflateSetHeader(this.strm, opt.header);
+ }
+
+ if (opt.dictionary) {
+ let dict;
+ // Convert data if needed
+ if (typeof opt.dictionary === 'string') {
+ // If we need to compress text, change encoding to utf8.
+ dict = strings.string2buf(opt.dictionary);
+ } else if (toString.call(opt.dictionary) === '[object ArrayBuffer]') {
+ dict = new Uint8Array(opt.dictionary);
+ } else {
+ dict = opt.dictionary;
+ }
+
+ status = deflate_1.deflateSetDictionary(this.strm, dict);
+
+ if (status !== Z_OK$1) {
+ throw new Error(messages[status]);
+ }
+
+ this._dict_set = true;
+ }
+}
+
+/**
+ * Deflate#push(data[, flush_mode]) -> Boolean
+ * - data (Uint8Array|ArrayBuffer|String): input data. Strings will be
+ * converted to utf8 byte sequence.
+ * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE modes.
+ * See constants. Skipped or `false` means Z_NO_FLUSH, `true` means Z_FINISH.
+ *
+ * Sends input data to deflate pipe, generating [[Deflate#onData]] calls with
+ * new compressed chunks. Returns `true` on success. The last data block must
+ * have `flush_mode` Z_FINISH (or `true`). That will flush internal pending
+ * buffers and call [[Deflate#onEnd]].
+ *
+ * On fail call [[Deflate#onEnd]] with error code and return false.
+ *
+ * ##### Example
+ *
+ * ```javascript
+ * push(chunk, false); // push one of data chunks
+ * ...
+ * push(chunk, true); // push last chunk
+ * ```
+ **/
+Deflate.prototype.push = function (data, flush_mode) {
+ const strm = this.strm;
+ const chunkSize = this.options.chunkSize;
+ let status, _flush_mode;
+
+ if (this.ended) { return false; }
+
+ if (flush_mode === ~~flush_mode) _flush_mode = flush_mode;
+ else _flush_mode = flush_mode === true ? Z_FINISH$1 : Z_NO_FLUSH$1;
+
+ // Convert data if needed
+ if (typeof data === 'string') {
+ // If we need to compress text, change encoding to utf8.
+ strm.input = strings.string2buf(data);
+ } else if (toString.call(data) === '[object ArrayBuffer]') {
+ strm.input = new Uint8Array(data);
+ } else {
+ strm.input = data;
+ }
+
+ strm.next_in = 0;
+ strm.avail_in = strm.input.length;
+
+ for (;;) {
+ if (strm.avail_out === 0) {
+ strm.output = new Uint8Array(chunkSize);
+ strm.next_out = 0;
+ strm.avail_out = chunkSize;
+ }
+
+ // Make sure avail_out > 6 to avoid repeating markers
+ if ((_flush_mode === Z_SYNC_FLUSH || _flush_mode === Z_FULL_FLUSH$1) && strm.avail_out <= 6) {
+ this.onData(strm.output.subarray(0, strm.next_out));
+ strm.avail_out = 0;
+ continue;
+ }
+
+ status = deflate_1.deflate(strm, _flush_mode);
+
+ // Ended => flush and finish
+ if (status === Z_STREAM_END$1) {
+ if (strm.next_out > 0) {
+ this.onData(strm.output.subarray(0, strm.next_out));
+ }
+ status = deflate_1.deflateEnd(this.strm);
+ this.onEnd(status);
+ this.ended = true;
+ return status === Z_OK$1;
+ }
+
+ // Flush if out buffer full
+ if (strm.avail_out === 0) {
+ this.onData(strm.output);
+ continue;
+ }
+
+ // Flush if requested and has data
+ if (_flush_mode > 0 && strm.next_out > 0) {
+ this.onData(strm.output.subarray(0, strm.next_out));
+ strm.avail_out = 0;
+ continue;
+ }
+
+ if (strm.avail_in === 0) break;
+ }
+
+ return true;
+};
+
+
+/**
+ * Deflate#onData(chunk) -> Void
+ * - chunk (Uint8Array): output data.
+ *
+ * By default, stores data blocks in `chunks[]` property and glue
+ * those in `onEnd`. Override this handler, if you need another behaviour.
+ **/
+Deflate.prototype.onData = function (chunk) {
+ this.chunks.push(chunk);
+};
+
+
+/**
+ * Deflate#onEnd(status) -> Void
+ * - status (Number): deflate status. 0 (Z_OK) on success,
+ * other if not.
+ *
+ * Called once after you tell deflate that the input stream is
+ * complete (Z_FINISH). By default - join collected chunks,
+ * free memory and fill `results` / `err` properties.
+ **/
+Deflate.prototype.onEnd = function (status) {
+ // On success - join
+ if (status === Z_OK$1) {
+ this.result = common.flattenChunks(this.chunks);
+ }
+ this.chunks = [];
+ this.err = status;
+ this.msg = this.strm.msg;
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+// See state defs from inflate.js
+const BAD = 30; /* got a data error -- remain here until reset */
+const TYPE = 12; /* i: waiting for type bits, including last-flag bit */
+
+/*
+ Decode literal, length, and distance codes and write out the resulting
+ literal and match bytes until either not enough input or output is
+ available, an end-of-block is encountered, or a data error is encountered.
+ When large enough input and output buffers are supplied to inflate(), for
+ example, a 16K input buffer and a 64K output buffer, more than 95% of the
+ inflate execution time is spent in this routine.
+
+ Entry assumptions:
+
+ state.mode === LEN
+ strm.avail_in >= 6
+ strm.avail_out >= 258
+ start >= strm.avail_out
+ state.bits < 8
+
+ On return, state.mode is one of:
+
+ LEN -- ran out of enough output space or enough available input
+ TYPE -- reached end of block code, inflate() to interpret next block
+ BAD -- error in block data
+
+ Notes:
+
+ - The maximum input bits used by a length/distance pair is 15 bits for the
+ length code, 5 bits for the length extra, 15 bits for the distance code,
+ and 13 bits for the distance extra. This totals 48 bits, or six bytes.
+ Therefore if strm.avail_in >= 6, then there is enough input to avoid
+ checking for available input while decoding.
+
+ - The maximum bytes that a single length/distance pair can output is 258
+ bytes, which is the maximum length that can be coded. inflate_fast()
+ requires strm.avail_out >= 258 for each loop to avoid checking for
+ output space.
+ */
+var inffast = function inflate_fast(strm, start) {
+ let _in; /* local strm.input */
+ let last; /* have enough input while in < last */
+ let _out; /* local strm.output */
+ let beg; /* inflate()'s initial strm.output */
+ let end; /* while out < end, enough space available */
+//#ifdef INFLATE_STRICT
+ let dmax; /* maximum distance from zlib header */
+//#endif
+ let wsize; /* window size or zero if not using window */
+ let whave; /* valid bytes in the window */
+ let wnext; /* window write index */
+ // Use `s_window` instead `window`, avoid conflict with instrumentation tools
+ let s_window; /* allocated sliding window, if wsize != 0 */
+ let hold; /* local strm.hold */
+ let bits; /* local strm.bits */
+ let lcode; /* local strm.lencode */
+ let dcode; /* local strm.distcode */
+ let lmask; /* mask for first level of length codes */
+ let dmask; /* mask for first level of distance codes */
+ let here; /* retrieved table entry */
+ let op; /* code bits, operation, extra bits, or */
+ /* window position, window bytes to copy */
+ let len; /* match length, unused bytes */
+ let dist; /* match distance */
+ let from; /* where to copy match from */
+ let from_source;
+
+
+ let input, output; // JS specific, because we have no pointers
+
+ /* copy state to local variables */
+ const state = strm.state;
+ //here = state.here;
+ _in = strm.next_in;
+ input = strm.input;
+ last = _in + (strm.avail_in - 5);
+ _out = strm.next_out;
+ output = strm.output;
+ beg = _out - (start - strm.avail_out);
+ end = _out + (strm.avail_out - 257);
+//#ifdef INFLATE_STRICT
+ dmax = state.dmax;
+//#endif
+ wsize = state.wsize;
+ whave = state.whave;
+ wnext = state.wnext;
+ s_window = state.window;
+ hold = state.hold;
+ bits = state.bits;
+ lcode = state.lencode;
+ dcode = state.distcode;
+ lmask = (1 << state.lenbits) - 1;
+ dmask = (1 << state.distbits) - 1;
+
+
+ /* decode literals and length/distances until end-of-block or not enough
+ input data or output space */
+
+ top:
+ do {
+ if (bits < 15) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+
+ here = lcode[hold & lmask];
+
+ dolen:
+ for (;;) { // Goto emulation
+ op = here >>> 24/*here.bits*/;
+ hold >>>= op;
+ bits -= op;
+ op = (here >>> 16) & 0xff/*here.op*/;
+ if (op === 0) { /* literal */
+ //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ // "inflate: literal '%c'\n" :
+ // "inflate: literal 0x%02x\n", here.val));
+ output[_out++] = here & 0xffff/*here.val*/;
+ }
+ else if (op & 16) { /* length base */
+ len = here & 0xffff/*here.val*/;
+ op &= 15; /* number of extra bits */
+ if (op) {
+ if (bits < op) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+ len += hold & ((1 << op) - 1);
+ hold >>>= op;
+ bits -= op;
+ }
+ //Tracevv((stderr, "inflate: length %u\n", len));
+ if (bits < 15) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+ here = dcode[hold & dmask];
+
+ dodist:
+ for (;;) { // goto emulation
+ op = here >>> 24/*here.bits*/;
+ hold >>>= op;
+ bits -= op;
+ op = (here >>> 16) & 0xff/*here.op*/;
+
+ if (op & 16) { /* distance base */
+ dist = here & 0xffff/*here.val*/;
+ op &= 15; /* number of extra bits */
+ if (bits < op) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ if (bits < op) {
+ hold += input[_in++] << bits;
+ bits += 8;
+ }
+ }
+ dist += hold & ((1 << op) - 1);
+//#ifdef INFLATE_STRICT
+ if (dist > dmax) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD;
+ break top;
+ }
+//#endif
+ hold >>>= op;
+ bits -= op;
+ //Tracevv((stderr, "inflate: distance %u\n", dist));
+ op = _out - beg; /* max distance in output */
+ if (dist > op) { /* see if copy from window */
+ op = dist - op; /* distance back in window */
+ if (op > whave) {
+ if (state.sane) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD;
+ break top;
+ }
+
+// (!) This block is disabled in zlib defaults,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+// if (len <= op - whave) {
+// do {
+// output[_out++] = 0;
+// } while (--len);
+// continue top;
+// }
+// len -= op - whave;
+// do {
+// output[_out++] = 0;
+// } while (--op > whave);
+// if (op === 0) {
+// from = _out - dist;
+// do {
+// output[_out++] = output[from++];
+// } while (--len);
+// continue top;
+// }
+//#endif
+ }
+ from = 0; // window index
+ from_source = s_window;
+ if (wnext === 0) { /* very common case */
+ from += wsize - op;
+ if (op < len) { /* some from window */
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = _out - dist; /* rest from output */
+ from_source = output;
+ }
+ }
+ else if (wnext < op) { /* wrap around window */
+ from += wsize + wnext - op;
+ op -= wnext;
+ if (op < len) { /* some from end of window */
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = 0;
+ if (wnext < len) { /* some from start of window */
+ op = wnext;
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = _out - dist; /* rest from output */
+ from_source = output;
+ }
+ }
+ }
+ else { /* contiguous in window */
+ from += wnext - op;
+ if (op < len) { /* some from window */
+ len -= op;
+ do {
+ output[_out++] = s_window[from++];
+ } while (--op);
+ from = _out - dist; /* rest from output */
+ from_source = output;
+ }
+ }
+ while (len > 2) {
+ output[_out++] = from_source[from++];
+ output[_out++] = from_source[from++];
+ output[_out++] = from_source[from++];
+ len -= 3;
+ }
+ if (len) {
+ output[_out++] = from_source[from++];
+ if (len > 1) {
+ output[_out++] = from_source[from++];
+ }
+ }
+ }
+ else {
+ from = _out - dist; /* copy direct from output */
+ do { /* minimum length is three */
+ output[_out++] = output[from++];
+ output[_out++] = output[from++];
+ output[_out++] = output[from++];
+ len -= 3;
+ } while (len > 2);
+ if (len) {
+ output[_out++] = output[from++];
+ if (len > 1) {
+ output[_out++] = output[from++];
+ }
+ }
+ }
+ }
+ else if ((op & 64) === 0) { /* 2nd level distance code */
+ here = dcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+ continue dodist;
+ }
+ else {
+ strm.msg = 'invalid distance code';
+ state.mode = BAD;
+ break top;
+ }
+
+ break; // need to emulate goto via "continue"
+ }
+ }
+ else if ((op & 64) === 0) { /* 2nd level length code */
+ here = lcode[(here & 0xffff)/*here.val*/ + (hold & ((1 << op) - 1))];
+ continue dolen;
+ }
+ else if (op & 32) { /* end-of-block */
+ //Tracevv((stderr, "inflate: end of block\n"));
+ state.mode = TYPE;
+ break top;
+ }
+ else {
+ strm.msg = 'invalid literal/length code';
+ state.mode = BAD;
+ break top;
+ }
+
+ break; // need to emulate goto via "continue"
+ }
+ } while (_in < last && _out < end);
+
+ /* return unused bytes (on entry, bits < 8, so in won't go too far back) */
+ len = bits >> 3;
+ _in -= len;
+ bits -= len << 3;
+ hold &= (1 << bits) - 1;
+
+ /* update state and return */
+ strm.next_in = _in;
+ strm.next_out = _out;
+ strm.avail_in = (_in < last ? 5 + (last - _in) : 5 - (_in - last));
+ strm.avail_out = (_out < end ? 257 + (end - _out) : 257 - (_out - end));
+ state.hold = hold;
+ state.bits = bits;
+ return;
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+const MAXBITS = 15;
+const ENOUGH_LENS = 852;
+const ENOUGH_DISTS = 592;
+//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);
+
+const CODES = 0;
+const LENS = 1;
+const DISTS = 2;
+
+const lbase = new Uint16Array([ /* Length codes 257..285 base */
+ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
+ 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
+]);
+
+const lext = new Uint8Array([ /* Length codes 257..285 extra */
+ 16, 16, 16, 16, 16, 16, 16, 16, 17, 17, 17, 17, 18, 18, 18, 18,
+ 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 16, 72, 78
+]);
+
+const dbase = new Uint16Array([ /* Distance codes 0..29 base */
+ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193,
+ 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145,
+ 8193, 12289, 16385, 24577, 0, 0
+]);
+
+const dext = new Uint8Array([ /* Distance codes 0..29 extra */
+ 16, 16, 16, 16, 17, 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 22,
+ 23, 23, 24, 24, 25, 25, 26, 26, 27, 27,
+ 28, 28, 29, 29, 64, 64
+]);
+
+const inflate_table = (type, lens, lens_index, codes, table, table_index, work, opts) =>
+{
+ const bits = opts.bits;
+ //here = opts.here; /* table entry for duplication */
+
+ let len = 0; /* a code's length in bits */
+ let sym = 0; /* index of code symbols */
+ let min = 0, max = 0; /* minimum and maximum code lengths */
+ let root = 0; /* number of index bits for root table */
+ let curr = 0; /* number of index bits for current table */
+ let drop = 0; /* code bits to drop for sub-table */
+ let left = 0; /* number of prefix codes available */
+ let used = 0; /* code entries in table used */
+ let huff = 0; /* Huffman code */
+ let incr; /* for incrementing code, index */
+ let fill; /* index for replicating entries */
+ let low; /* low bits for current root entry */
+ let mask; /* mask for low root bits */
+ let next; /* next available space in table */
+ let base = null; /* base value table to use */
+ let base_index = 0;
+// let shoextra; /* extra bits table to use */
+ let end; /* use base and extra for symbol > end */
+ const count = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* number of codes of each length */
+ const offs = new Uint16Array(MAXBITS + 1); //[MAXBITS+1]; /* offsets in table for each length */
+ let extra = null;
+ let extra_index = 0;
+
+ let here_bits, here_op, here_val;
+
+ /*
+ Process a set of code lengths to create a canonical Huffman code. The
+ code lengths are lens[0..codes-1]. Each length corresponds to the
+ symbols 0..codes-1. The Huffman code is generated by first sorting the
+ symbols by length from short to long, and retaining the symbol order
+ for codes with equal lengths. Then the code starts with all zero bits
+ for the first code of the shortest length, and the codes are integer
+ increments for the same length, and zeros are appended as the length
+ increases. For the deflate format, these bits are stored backwards
+ from their more natural integer increment ordering, and so when the
+ decoding tables are built in the large loop below, the integer codes
+ are incremented backwards.
+
+ This routine assumes, but does not check, that all of the entries in
+ lens[] are in the range 0..MAXBITS. The caller must assure this.
+ 1..MAXBITS is interpreted as that code length. zero means that that
+ symbol does not occur in this code.
+
+ The codes are sorted by computing a count of codes for each length,
+ creating from that a table of starting indices for each length in the
+ sorted table, and then entering the symbols in order in the sorted
+ table. The sorted table is work[], with that space being provided by
+ the caller.
+
+ The length counts are used for other purposes as well, i.e. finding
+ the minimum and maximum length codes, determining if there are any
+ codes at all, checking for a valid set of lengths, and looking ahead
+ at length counts to determine sub-table sizes when building the
+ decoding tables.
+ */
+
+ /* accumulate lengths for codes (assumes lens[] all in 0..MAXBITS) */
+ for (len = 0; len <= MAXBITS; len++) {
+ count[len] = 0;
+ }
+ for (sym = 0; sym < codes; sym++) {
+ count[lens[lens_index + sym]]++;
+ }
+
+ /* bound code lengths, force root to be within code lengths */
+ root = bits;
+ for (max = MAXBITS; max >= 1; max--) {
+ if (count[max] !== 0) { break; }
+ }
+ if (root > max) {
+ root = max;
+ }
+ if (max === 0) { /* no symbols to code at all */
+ //table.op[opts.table_index] = 64; //here.op = (var char)64; /* invalid code marker */
+ //table.bits[opts.table_index] = 1; //here.bits = (var char)1;
+ //table.val[opts.table_index++] = 0; //here.val = (var short)0;
+ table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+
+ //table.op[opts.table_index] = 64;
+ //table.bits[opts.table_index] = 1;
+ //table.val[opts.table_index++] = 0;
+ table[table_index++] = (1 << 24) | (64 << 16) | 0;
+
+ opts.bits = 1;
+ return 0; /* no symbols, but wait for decoding to report error */
+ }
+ for (min = 1; min < max; min++) {
+ if (count[min] !== 0) { break; }
+ }
+ if (root < min) {
+ root = min;
+ }
+
+ /* check for an over-subscribed or incomplete set of lengths */
+ left = 1;
+ for (len = 1; len <= MAXBITS; len++) {
+ left <<= 1;
+ left -= count[len];
+ if (left < 0) {
+ return -1;
+ } /* over-subscribed */
+ }
+ if (left > 0 && (type === CODES || max !== 1)) {
+ return -1; /* incomplete set */
+ }
+
+ /* generate offsets into symbol table for each length for sorting */
+ offs[1] = 0;
+ for (len = 1; len < MAXBITS; len++) {
+ offs[len + 1] = offs[len] + count[len];
+ }
+
+ /* sort symbols by length, by symbol order within each length */
+ for (sym = 0; sym < codes; sym++) {
+ if (lens[lens_index + sym] !== 0) {
+ work[offs[lens[lens_index + sym]]++] = sym;
+ }
+ }
+
+ /*
+ Create and fill in decoding tables. In this loop, the table being
+ filled is at next and has curr index bits. The code being used is huff
+ with length len. That code is converted to an index by dropping drop
+ bits off of the bottom. For codes where len is less than drop + curr,
+ those top drop + curr - len bits are incremented through all values to
+ fill the table with replicated entries.
+
+ root is the number of index bits for the root table. When len exceeds
+ root, sub-tables are created pointed to by the root entry with an index
+ of the low root bits of huff. This is saved in low to check for when a
+ new sub-table should be started. drop is zero when the root table is
+ being filled, and drop is root when sub-tables are being filled.
+
+ When a new sub-table is needed, it is necessary to look ahead in the
+ code lengths to determine what size sub-table is needed. The length
+ counts are used for this, and so count[] is decremented as codes are
+ entered in the tables.
+
+ used keeps track of how many table entries have been allocated from the
+ provided *table space. It is checked for LENS and DIST tables against
+ the constants ENOUGH_LENS and ENOUGH_DISTS to guard against changes in
+ the initial root table size constants. See the comments in inftrees.h
+ for more information.
+
+ sym increments through all symbols, and the loop terminates when
+ all codes of length max, i.e. all codes, have been processed. This
+ routine permits incomplete codes, so another loop after this one fills
+ in the rest of the decoding tables with invalid code markers.
+ */
+
+ /* set up for code type */
+ // poor man optimization - use if-else instead of switch,
+ // to avoid deopts in old v8
+ if (type === CODES) {
+ base = extra = work; /* dummy value--not used */
+ end = 19;
+
+ } else if (type === LENS) {
+ base = lbase;
+ base_index -= 257;
+ extra = lext;
+ extra_index -= 257;
+ end = 256;
+
+ } else { /* DISTS */
+ base = dbase;
+ extra = dext;
+ end = -1;
+ }
+
+ /* initialize opts for loop */
+ huff = 0; /* starting code */
+ sym = 0; /* starting code symbol */
+ len = min; /* starting code length */
+ next = table_index; /* current table to fill in */
+ curr = root; /* current table index bits */
+ drop = 0; /* current bits to drop from code for index */
+ low = -1; /* trigger new sub-table when len > root */
+ used = 1 << root; /* use root table entries */
+ mask = used - 1; /* mask for comparing low */
+
+ /* check available table space */
+ if ((type === LENS && used > ENOUGH_LENS) ||
+ (type === DISTS && used > ENOUGH_DISTS)) {
+ return 1;
+ }
+
+ /* process all codes and make table entries */
+ for (;;) {
+ /* create table entry */
+ here_bits = len - drop;
+ if (work[sym] < end) {
+ here_op = 0;
+ here_val = work[sym];
+ }
+ else if (work[sym] > end) {
+ here_op = extra[extra_index + work[sym]];
+ here_val = base[base_index + work[sym]];
+ }
+ else {
+ here_op = 32 + 64; /* end of block */
+ here_val = 0;
+ }
+
+ /* replicate for those indices with low len bits equal to huff */
+ incr = 1 << (len - drop);
+ fill = 1 << curr;
+ min = fill; /* save offset to next table */
+ do {
+ fill -= incr;
+ table[next + (huff >> drop) + fill] = (here_bits << 24) | (here_op << 16) | here_val |0;
+ } while (fill !== 0);
+
+ /* backwards increment the len-bit code huff */
+ incr = 1 << (len - 1);
+ while (huff & incr) {
+ incr >>= 1;
+ }
+ if (incr !== 0) {
+ huff &= incr - 1;
+ huff += incr;
+ } else {
+ huff = 0;
+ }
+
+ /* go to next symbol, update count, len */
+ sym++;
+ if (--count[len] === 0) {
+ if (len === max) { break; }
+ len = lens[lens_index + work[sym]];
+ }
+
+ /* create new sub-table if needed */
+ if (len > root && (huff & mask) !== low) {
+ /* if first time, transition to sub-tables */
+ if (drop === 0) {
+ drop = root;
+ }
+
+ /* increment past last table */
+ next += min; /* here min is 1 << curr */
+
+ /* determine length of next table */
+ curr = len - drop;
+ left = 1 << curr;
+ while (curr + drop < max) {
+ left -= count[curr + drop];
+ if (left <= 0) { break; }
+ curr++;
+ left <<= 1;
+ }
+
+ /* check for enough space */
+ used += 1 << curr;
+ if ((type === LENS && used > ENOUGH_LENS) ||
+ (type === DISTS && used > ENOUGH_DISTS)) {
+ return 1;
+ }
+
+ /* point entry in root table to sub-table */
+ low = huff & mask;
+ /*table.op[low] = curr;
+ table.bits[low] = root;
+ table.val[low] = next - opts.table_index;*/
+ table[low] = (root << 24) | (curr << 16) | (next - table_index) |0;
+ }
+ }
+
+ /* fill in remaining table entry if code is incomplete (guaranteed to have
+ at most one remaining entry, since if the code is incomplete, the
+ maximum code length that was allowed to get this far is one bit) */
+ if (huff !== 0) {
+ //table.op[next + huff] = 64; /* invalid code marker */
+ //table.bits[next + huff] = len - drop;
+ //table.val[next + huff] = 0;
+ table[next + huff] = ((len - drop) << 24) | (64 << 16) |0;
+ }
+
+ /* set return parameters */
+ //opts.table_index += used;
+ opts.bits = root;
+ return 0;
+};
+
+
+var inftrees = inflate_table;
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+
+
+
+
+
+const CODES$1 = 0;
+const LENS$1 = 1;
+const DISTS$1 = 2;
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+const {
+ Z_FINISH: Z_FINISH$2, Z_BLOCK: Z_BLOCK$1, Z_TREES,
+ Z_OK: Z_OK$2, Z_STREAM_END: Z_STREAM_END$2, Z_NEED_DICT, Z_STREAM_ERROR: Z_STREAM_ERROR$1, Z_DATA_ERROR: Z_DATA_ERROR$1, Z_MEM_ERROR, Z_BUF_ERROR: Z_BUF_ERROR$1,
+ Z_DEFLATED: Z_DEFLATED$2
+} = constants;
+
+
+/* STATES ====================================================================*/
+/* ===========================================================================*/
+
+
+const HEAD = 1; /* i: waiting for magic header */
+const FLAGS = 2; /* i: waiting for method and flags (gzip) */
+const TIME = 3; /* i: waiting for modification time (gzip) */
+const OS = 4; /* i: waiting for extra flags and operating system (gzip) */
+const EXLEN = 5; /* i: waiting for extra length (gzip) */
+const EXTRA = 6; /* i: waiting for extra bytes (gzip) */
+const NAME = 7; /* i: waiting for end of file name (gzip) */
+const COMMENT = 8; /* i: waiting for end of comment (gzip) */
+const HCRC = 9; /* i: waiting for header crc (gzip) */
+const DICTID = 10; /* i: waiting for dictionary check value */
+const DICT = 11; /* waiting for inflateSetDictionary() call */
+const TYPE$1 = 12; /* i: waiting for type bits, including last-flag bit */
+const TYPEDO = 13; /* i: same, but skip check to exit inflate on new block */
+const STORED = 14; /* i: waiting for stored size (length and complement) */
+const COPY_ = 15; /* i/o: same as COPY below, but only first time in */
+const COPY = 16; /* i/o: waiting for input or output to copy stored block */
+const TABLE = 17; /* i: waiting for dynamic block table lengths */
+const LENLENS = 18; /* i: waiting for code length code lengths */
+const CODELENS = 19; /* i: waiting for length/lit and distance code lengths */
+const LEN_ = 20; /* i: same as LEN below, but only first time in */
+const LEN = 21; /* i: waiting for length/lit/eob code */
+const LENEXT = 22; /* i: waiting for length extra bits */
+const DIST = 23; /* i: waiting for distance code */
+const DISTEXT = 24; /* i: waiting for distance extra bits */
+const MATCH = 25; /* o: waiting for output space to copy string */
+const LIT = 26; /* o: waiting for output space to write literal */
+const CHECK = 27; /* i: waiting for 32-bit check value */
+const LENGTH = 28; /* i: waiting for 32-bit length (gzip) */
+const DONE = 29; /* finished check, done -- remain here until reset */
+const BAD$1 = 30; /* got a data error -- remain here until reset */
+const MEM = 31; /* got an inflate() memory error -- remain here until reset */
+const SYNC = 32; /* looking for synchronization bytes to restart inflate() */
+
+/* ===========================================================================*/
+
+
+
+const ENOUGH_LENS$1 = 852;
+const ENOUGH_DISTS$1 = 592;
+//const ENOUGH = (ENOUGH_LENS+ENOUGH_DISTS);
+
+const MAX_WBITS$1 = 15;
+/* 32K LZ77 window */
+const DEF_WBITS = MAX_WBITS$1;
+
+
+const zswap32 = (q) => {
+
+ return (((q >>> 24) & 0xff) +
+ ((q >>> 8) & 0xff00) +
+ ((q & 0xff00) << 8) +
+ ((q & 0xff) << 24));
+};
+
+
+function InflateState() {
+ this.mode = 0; /* current inflate mode */
+ this.last = false; /* true if processing last block */
+ this.wrap = 0; /* bit 0 true for zlib, bit 1 true for gzip */
+ this.havedict = false; /* true if dictionary provided */
+ this.flags = 0; /* gzip header method and flags (0 if zlib) */
+ this.dmax = 0; /* zlib header max distance (INFLATE_STRICT) */
+ this.check = 0; /* protected copy of check value */
+ this.total = 0; /* protected copy of output count */
+ // TODO: may be {}
+ this.head = null; /* where to save gzip header information */
+
+ /* sliding window */
+ this.wbits = 0; /* log base 2 of requested window size */
+ this.wsize = 0; /* window size or zero if not using window */
+ this.whave = 0; /* valid bytes in the window */
+ this.wnext = 0; /* window write index */
+ this.window = null; /* allocated sliding window, if needed */
+
+ /* bit accumulator */
+ this.hold = 0; /* input bit accumulator */
+ this.bits = 0; /* number of bits in "in" */
+
+ /* for string and stored block copying */
+ this.length = 0; /* literal or length of data to copy */
+ this.offset = 0; /* distance back to copy string from */
+
+ /* for table and code decoding */
+ this.extra = 0; /* extra bits needed */
+
+ /* fixed and dynamic code tables */
+ this.lencode = null; /* starting table for length/literal codes */
+ this.distcode = null; /* starting table for distance codes */
+ this.lenbits = 0; /* index bits for lencode */
+ this.distbits = 0; /* index bits for distcode */
+
+ /* dynamic table building */
+ this.ncode = 0; /* number of code length code lengths */
+ this.nlen = 0; /* number of length code lengths */
+ this.ndist = 0; /* number of distance code lengths */
+ this.have = 0; /* number of code lengths in lens[] */
+ this.next = null; /* next available space in codes[] */
+
+ this.lens = new Uint16Array(320); /* temporary storage for code lengths */
+ this.work = new Uint16Array(288); /* work area for code table building */
+
+ /*
+ because we don't have pointers in js, we use lencode and distcode directly
+ as buffers so we don't need codes
+ */
+ //this.codes = new Int32Array(ENOUGH); /* space for code tables */
+ this.lendyn = null; /* dynamic table for length/literal codes (JS specific) */
+ this.distdyn = null; /* dynamic table for distance codes (JS specific) */
+ this.sane = 0; /* if false, allow invalid distance too far */
+ this.back = 0; /* bits back of last unprocessed length/lit */
+ this.was = 0; /* initial length of match */
+}
+
+
+const inflateResetKeep = (strm) => {
+
+ if (!strm || !strm.state) { return Z_STREAM_ERROR$1; }
+ const state = strm.state;
+ strm.total_in = strm.total_out = state.total = 0;
+ strm.msg = ''; /*Z_NULL*/
+ if (state.wrap) { /* to support ill-conceived Java test suite */
+ strm.adler = state.wrap & 1;
+ }
+ state.mode = HEAD;
+ state.last = 0;
+ state.havedict = 0;
+ state.dmax = 32768;
+ state.head = null/*Z_NULL*/;
+ state.hold = 0;
+ state.bits = 0;
+ //state.lencode = state.distcode = state.next = state.codes;
+ state.lencode = state.lendyn = new Int32Array(ENOUGH_LENS$1);
+ state.distcode = state.distdyn = new Int32Array(ENOUGH_DISTS$1);
+
+ state.sane = 1;
+ state.back = -1;
+ //Tracev((stderr, "inflate: reset\n"));
+ return Z_OK$2;
+};
+
+
+const inflateReset = (strm) => {
+
+ if (!strm || !strm.state) { return Z_STREAM_ERROR$1; }
+ const state = strm.state;
+ state.wsize = 0;
+ state.whave = 0;
+ state.wnext = 0;
+ return inflateResetKeep(strm);
+
+};
+
+
+const inflateReset2 = (strm, windowBits) => {
+ let wrap;
+
+ /* get the state */
+ if (!strm || !strm.state) { return Z_STREAM_ERROR$1; }
+ const state = strm.state;
+
+ /* extract wrap request from windowBits parameter */
+ if (windowBits < 0) {
+ wrap = 0;
+ windowBits = -windowBits;
+ }
+ else {
+ wrap = (windowBits >> 4) + 1;
+ if (windowBits < 48) {
+ windowBits &= 15;
+ }
+ }
+
+ /* set number of window bits, free window if different */
+ if (windowBits && (windowBits < 8 || windowBits > 15)) {
+ return Z_STREAM_ERROR$1;
+ }
+ if (state.window !== null && state.wbits !== windowBits) {
+ state.window = null;
+ }
+
+ /* update state and reset the rest of it */
+ state.wrap = wrap;
+ state.wbits = windowBits;
+ return inflateReset(strm);
+};
+
+
+const inflateInit2 = (strm, windowBits) => {
+
+ if (!strm) { return Z_STREAM_ERROR$1; }
+ //strm.msg = Z_NULL; /* in case we return an error */
+
+ const state = new InflateState();
+
+ //if (state === Z_NULL) return Z_MEM_ERROR;
+ //Tracev((stderr, "inflate: allocated\n"));
+ strm.state = state;
+ state.window = null/*Z_NULL*/;
+ const ret = inflateReset2(strm, windowBits);
+ if (ret !== Z_OK$2) {
+ strm.state = null/*Z_NULL*/;
+ }
+ return ret;
+};
+
+
+const inflateInit = (strm) => {
+
+ return inflateInit2(strm, DEF_WBITS);
+};
+
+
+/*
+ Return state with length and distance decoding tables and index sizes set to
+ fixed code decoding. Normally this returns fixed tables from inffixed.h.
+ If BUILDFIXED is defined, then instead this routine builds the tables the
+ first time it's called, and returns those tables the first time and
+ thereafter. This reduces the size of the code by about 2K bytes, in
+ exchange for a little execution time. However, BUILDFIXED should not be
+ used for threaded applications, since the rewriting of the tables and virgin
+ may not be thread-safe.
+ */
+let virgin = true;
+
+let lenfix, distfix; // We have no pointers in JS, so keep tables separate
+
+
+const fixedtables = (state) => {
+
+ /* build fixed huffman tables if first call (may not be thread safe) */
+ if (virgin) {
+ lenfix = new Int32Array(512);
+ distfix = new Int32Array(32);
+
+ /* literal/length table */
+ let sym = 0;
+ while (sym < 144) { state.lens[sym++] = 8; }
+ while (sym < 256) { state.lens[sym++] = 9; }
+ while (sym < 280) { state.lens[sym++] = 7; }
+ while (sym < 288) { state.lens[sym++] = 8; }
+
+ inftrees(LENS$1, state.lens, 0, 288, lenfix, 0, state.work, { bits: 9 });
+
+ /* distance table */
+ sym = 0;
+ while (sym < 32) { state.lens[sym++] = 5; }
+
+ inftrees(DISTS$1, state.lens, 0, 32, distfix, 0, state.work, { bits: 5 });
+
+ /* do this just once */
+ virgin = false;
+ }
+
+ state.lencode = lenfix;
+ state.lenbits = 9;
+ state.distcode = distfix;
+ state.distbits = 5;
+};
+
+
+/*
+ Update the window with the last wsize (normally 32K) bytes written before
+ returning. If window does not exist yet, create it. This is only called
+ when a window is already in use, or when output has been written during this
+ inflate call, but the end of the deflate stream has not been reached yet.
+ It is also called to create a window for dictionary data when a dictionary
+ is loaded.
+
+ Providing output buffers larger than 32K to inflate() should provide a speed
+ advantage, since only the last 32K of output is copied to the sliding window
+ upon return from inflate(), and since all distances after the first 32K of
+ output will fall in the output data, making match copies simpler and faster.
+ The advantage may be dependent on the size of the processor's data caches.
+ */
+const updatewindow = (strm, src, end, copy) => {
+
+ let dist;
+ const state = strm.state;
+
+ /* if it hasn't been done already, allocate space for the window */
+ if (state.window === null) {
+ state.wsize = 1 << state.wbits;
+ state.wnext = 0;
+ state.whave = 0;
+
+ state.window = new Uint8Array(state.wsize);
+ }
+
+ /* copy state->wsize or less output bytes into the circular window */
+ if (copy >= state.wsize) {
+ state.window.set(src.subarray(end - state.wsize, end), 0);
+ state.wnext = 0;
+ state.whave = state.wsize;
+ }
+ else {
+ dist = state.wsize - state.wnext;
+ if (dist > copy) {
+ dist = copy;
+ }
+ //zmemcpy(state->window + state->wnext, end - copy, dist);
+ state.window.set(src.subarray(end - copy, end - copy + dist), state.wnext);
+ copy -= dist;
+ if (copy) {
+ //zmemcpy(state->window, end - copy, copy);
+ state.window.set(src.subarray(end - copy, end), 0);
+ state.wnext = copy;
+ state.whave = state.wsize;
+ }
+ else {
+ state.wnext += dist;
+ if (state.wnext === state.wsize) { state.wnext = 0; }
+ if (state.whave < state.wsize) { state.whave += dist; }
+ }
+ }
+ return 0;
+};
+
+
+const inflate = (strm, flush) => {
+
+ let state;
+ let input, output; // input/output buffers
+ let next; /* next input INDEX */
+ let put; /* next output INDEX */
+ let have, left; /* available input and output */
+ let hold; /* bit buffer */
+ let bits; /* bits in bit buffer */
+ let _in, _out; /* save starting available input and output */
+ let copy; /* number of stored or match bytes to copy */
+ let from; /* where to copy match bytes from */
+ let from_source;
+ let here = 0; /* current decoding table entry */
+ let here_bits, here_op, here_val; // paked "here" denormalized (JS specific)
+ //let last; /* parent table entry */
+ let last_bits, last_op, last_val; // paked "last" denormalized (JS specific)
+ let len; /* length to copy for repeats, bits to drop */
+ let ret; /* return code */
+ const hbuf = new Uint8Array(4); /* buffer for gzip header crc calculation */
+ let opts;
+
+ let n; // temporary variable for NEED_BITS
+
+ const order = /* permutation of code lengths */
+ new Uint8Array([ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ]);
+
+
+ if (!strm || !strm.state || !strm.output ||
+ (!strm.input && strm.avail_in !== 0)) {
+ return Z_STREAM_ERROR$1;
+ }
+
+ state = strm.state;
+ if (state.mode === TYPE$1) { state.mode = TYPEDO; } /* skip check */
+
+
+ //--- LOAD() ---
+ put = strm.next_out;
+ output = strm.output;
+ left = strm.avail_out;
+ next = strm.next_in;
+ input = strm.input;
+ have = strm.avail_in;
+ hold = state.hold;
+ bits = state.bits;
+ //---
+
+ _in = have;
+ _out = left;
+ ret = Z_OK$2;
+
+ inf_leave: // goto emulation
+ for (;;) {
+ switch (state.mode) {
+ case HEAD:
+ if (state.wrap === 0) {
+ state.mode = TYPEDO;
+ break;
+ }
+ //=== NEEDBITS(16);
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if ((state.wrap & 2) && hold === 0x8b1f) { /* gzip header */
+ state.check = 0/*crc32(0L, Z_NULL, 0)*/;
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32_1(state.check, hbuf, 2, 0);
+ //===//
+
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = FLAGS;
+ break;
+ }
+ state.flags = 0; /* expect zlib header */
+ if (state.head) {
+ state.head.done = false;
+ }
+ if (!(state.wrap & 1) || /* check if zlib header allowed */
+ (((hold & 0xff)/*BITS(8)*/ << 8) + (hold >> 8)) % 31) {
+ strm.msg = 'incorrect header check';
+ state.mode = BAD$1;
+ break;
+ }
+ if ((hold & 0x0f)/*BITS(4)*/ !== Z_DEFLATED$2) {
+ strm.msg = 'unknown compression method';
+ state.mode = BAD$1;
+ break;
+ }
+ //--- DROPBITS(4) ---//
+ hold >>>= 4;
+ bits -= 4;
+ //---//
+ len = (hold & 0x0f)/*BITS(4)*/ + 8;
+ if (state.wbits === 0) {
+ state.wbits = len;
+ }
+ else if (len > state.wbits) {
+ strm.msg = 'invalid window size';
+ state.mode = BAD$1;
+ break;
+ }
+
+ // !!! pako patch. Force use `options.windowBits` if passed.
+ // Required to always use max window size by default.
+ state.dmax = 1 << state.wbits;
+ //state.dmax = 1 << len;
+
+ //Tracev((stderr, "inflate: zlib header ok\n"));
+ strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+ state.mode = hold & 0x200 ? DICTID : TYPE$1;
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ break;
+ case FLAGS:
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.flags = hold;
+ if ((state.flags & 0xff) !== Z_DEFLATED$2) {
+ strm.msg = 'unknown compression method';
+ state.mode = BAD$1;
+ break;
+ }
+ if (state.flags & 0xe000) {
+ strm.msg = 'unknown header flags set';
+ state.mode = BAD$1;
+ break;
+ }
+ if (state.head) {
+ state.head.text = ((hold >> 8) & 1);
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32_1(state.check, hbuf, 2, 0);
+ //===//
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = TIME;
+ /* falls through */
+ case TIME:
+ //=== NEEDBITS(32); */
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (state.head) {
+ state.head.time = hold;
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC4(state.check, hold)
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ hbuf[2] = (hold >>> 16) & 0xff;
+ hbuf[3] = (hold >>> 24) & 0xff;
+ state.check = crc32_1(state.check, hbuf, 4, 0);
+ //===
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = OS;
+ /* falls through */
+ case OS:
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (state.head) {
+ state.head.xflags = (hold & 0xff);
+ state.head.os = (hold >> 8);
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32_1(state.check, hbuf, 2, 0);
+ //===//
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = EXLEN;
+ /* falls through */
+ case EXLEN:
+ if (state.flags & 0x0400) {
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.length = hold;
+ if (state.head) {
+ state.head.extra_len = hold;
+ }
+ if (state.flags & 0x0200) {
+ //=== CRC2(state.check, hold);
+ hbuf[0] = hold & 0xff;
+ hbuf[1] = (hold >>> 8) & 0xff;
+ state.check = crc32_1(state.check, hbuf, 2, 0);
+ //===//
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ }
+ else if (state.head) {
+ state.head.extra = null/*Z_NULL*/;
+ }
+ state.mode = EXTRA;
+ /* falls through */
+ case EXTRA:
+ if (state.flags & 0x0400) {
+ copy = state.length;
+ if (copy > have) { copy = have; }
+ if (copy) {
+ if (state.head) {
+ len = state.head.extra_len - state.length;
+ if (!state.head.extra) {
+ // Use untyped array for more convenient processing later
+ state.head.extra = new Uint8Array(state.head.extra_len);
+ }
+ state.head.extra.set(
+ input.subarray(
+ next,
+ // extra field is limited to 65536 bytes
+ // - no need for additional size check
+ next + copy
+ ),
+ /*len + copy > state.head.extra_max - len ? state.head.extra_max : copy,*/
+ len
+ );
+ //zmemcpy(state.head.extra + len, next,
+ // len + copy > state.head.extra_max ?
+ // state.head.extra_max - len : copy);
+ }
+ if (state.flags & 0x0200) {
+ state.check = crc32_1(state.check, input, copy, next);
+ }
+ have -= copy;
+ next += copy;
+ state.length -= copy;
+ }
+ if (state.length) { break inf_leave; }
+ }
+ state.length = 0;
+ state.mode = NAME;
+ /* falls through */
+ case NAME:
+ if (state.flags & 0x0800) {
+ if (have === 0) { break inf_leave; }
+ copy = 0;
+ do {
+ // TODO: 2 or 1 bytes?
+ len = input[next + copy++];
+ /* use constant limit because in js we should not preallocate memory */
+ if (state.head && len &&
+ (state.length < 65536 /*state.head.name_max*/)) {
+ state.head.name += String.fromCharCode(len);
+ }
+ } while (len && copy < have);
+
+ if (state.flags & 0x0200) {
+ state.check = crc32_1(state.check, input, copy, next);
+ }
+ have -= copy;
+ next += copy;
+ if (len) { break inf_leave; }
+ }
+ else if (state.head) {
+ state.head.name = null;
+ }
+ state.length = 0;
+ state.mode = COMMENT;
+ /* falls through */
+ case COMMENT:
+ if (state.flags & 0x1000) {
+ if (have === 0) { break inf_leave; }
+ copy = 0;
+ do {
+ len = input[next + copy++];
+ /* use constant limit because in js we should not preallocate memory */
+ if (state.head && len &&
+ (state.length < 65536 /*state.head.comm_max*/)) {
+ state.head.comment += String.fromCharCode(len);
+ }
+ } while (len && copy < have);
+ if (state.flags & 0x0200) {
+ state.check = crc32_1(state.check, input, copy, next);
+ }
+ have -= copy;
+ next += copy;
+ if (len) { break inf_leave; }
+ }
+ else if (state.head) {
+ state.head.comment = null;
+ }
+ state.mode = HCRC;
+ /* falls through */
+ case HCRC:
+ if (state.flags & 0x0200) {
+ //=== NEEDBITS(16); */
+ while (bits < 16) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (hold !== (state.check & 0xffff)) {
+ strm.msg = 'header crc mismatch';
+ state.mode = BAD$1;
+ break;
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ }
+ if (state.head) {
+ state.head.hcrc = ((state.flags >> 9) & 1);
+ state.head.done = true;
+ }
+ strm.adler = state.check = 0;
+ state.mode = TYPE$1;
+ break;
+ case DICTID:
+ //=== NEEDBITS(32); */
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ strm.adler = state.check = zswap32(hold);
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = DICT;
+ /* falls through */
+ case DICT:
+ if (state.havedict === 0) {
+ //--- RESTORE() ---
+ strm.next_out = put;
+ strm.avail_out = left;
+ strm.next_in = next;
+ strm.avail_in = have;
+ state.hold = hold;
+ state.bits = bits;
+ //---
+ return Z_NEED_DICT;
+ }
+ strm.adler = state.check = 1/*adler32(0L, Z_NULL, 0)*/;
+ state.mode = TYPE$1;
+ /* falls through */
+ case TYPE$1:
+ if (flush === Z_BLOCK$1 || flush === Z_TREES) { break inf_leave; }
+ /* falls through */
+ case TYPEDO:
+ if (state.last) {
+ //--- BYTEBITS() ---//
+ hold >>>= bits & 7;
+ bits -= bits & 7;
+ //---//
+ state.mode = CHECK;
+ break;
+ }
+ //=== NEEDBITS(3); */
+ while (bits < 3) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.last = (hold & 0x01)/*BITS(1)*/;
+ //--- DROPBITS(1) ---//
+ hold >>>= 1;
+ bits -= 1;
+ //---//
+
+ switch ((hold & 0x03)/*BITS(2)*/) {
+ case 0: /* stored block */
+ //Tracev((stderr, "inflate: stored block%s\n",
+ // state.last ? " (last)" : ""));
+ state.mode = STORED;
+ break;
+ case 1: /* fixed block */
+ fixedtables(state);
+ //Tracev((stderr, "inflate: fixed codes block%s\n",
+ // state.last ? " (last)" : ""));
+ state.mode = LEN_; /* decode codes */
+ if (flush === Z_TREES) {
+ //--- DROPBITS(2) ---//
+ hold >>>= 2;
+ bits -= 2;
+ //---//
+ break inf_leave;
+ }
+ break;
+ case 2: /* dynamic block */
+ //Tracev((stderr, "inflate: dynamic codes block%s\n",
+ // state.last ? " (last)" : ""));
+ state.mode = TABLE;
+ break;
+ case 3:
+ strm.msg = 'invalid block type';
+ state.mode = BAD$1;
+ }
+ //--- DROPBITS(2) ---//
+ hold >>>= 2;
+ bits -= 2;
+ //---//
+ break;
+ case STORED:
+ //--- BYTEBITS() ---// /* go to byte boundary */
+ hold >>>= bits & 7;
+ bits -= bits & 7;
+ //---//
+ //=== NEEDBITS(32); */
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if ((hold & 0xffff) !== ((hold >>> 16) ^ 0xffff)) {
+ strm.msg = 'invalid stored block lengths';
+ state.mode = BAD$1;
+ break;
+ }
+ state.length = hold & 0xffff;
+ //Tracev((stderr, "inflate: stored length %u\n",
+ // state.length));
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ state.mode = COPY_;
+ if (flush === Z_TREES) { break inf_leave; }
+ /* falls through */
+ case COPY_:
+ state.mode = COPY;
+ /* falls through */
+ case COPY:
+ copy = state.length;
+ if (copy) {
+ if (copy > have) { copy = have; }
+ if (copy > left) { copy = left; }
+ if (copy === 0) { break inf_leave; }
+ //--- zmemcpy(put, next, copy); ---
+ output.set(input.subarray(next, next + copy), put);
+ //---//
+ have -= copy;
+ next += copy;
+ left -= copy;
+ put += copy;
+ state.length -= copy;
+ break;
+ }
+ //Tracev((stderr, "inflate: stored end\n"));
+ state.mode = TYPE$1;
+ break;
+ case TABLE:
+ //=== NEEDBITS(14); */
+ while (bits < 14) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.nlen = (hold & 0x1f)/*BITS(5)*/ + 257;
+ //--- DROPBITS(5) ---//
+ hold >>>= 5;
+ bits -= 5;
+ //---//
+ state.ndist = (hold & 0x1f)/*BITS(5)*/ + 1;
+ //--- DROPBITS(5) ---//
+ hold >>>= 5;
+ bits -= 5;
+ //---//
+ state.ncode = (hold & 0x0f)/*BITS(4)*/ + 4;
+ //--- DROPBITS(4) ---//
+ hold >>>= 4;
+ bits -= 4;
+ //---//
+//#ifndef PKZIP_BUG_WORKAROUND
+ if (state.nlen > 286 || state.ndist > 30) {
+ strm.msg = 'too many length or distance symbols';
+ state.mode = BAD$1;
+ break;
+ }
+//#endif
+ //Tracev((stderr, "inflate: table sizes ok\n"));
+ state.have = 0;
+ state.mode = LENLENS;
+ /* falls through */
+ case LENLENS:
+ while (state.have < state.ncode) {
+ //=== NEEDBITS(3);
+ while (bits < 3) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.lens[order[state.have++]] = (hold & 0x07);//BITS(3);
+ //--- DROPBITS(3) ---//
+ hold >>>= 3;
+ bits -= 3;
+ //---//
+ }
+ while (state.have < 19) {
+ state.lens[order[state.have++]] = 0;
+ }
+ // We have separate tables & no pointers. 2 commented lines below not needed.
+ //state.next = state.codes;
+ //state.lencode = state.next;
+ // Switch to use dynamic table
+ state.lencode = state.lendyn;
+ state.lenbits = 7;
+
+ opts = { bits: state.lenbits };
+ ret = inftrees(CODES$1, state.lens, 0, 19, state.lencode, 0, state.work, opts);
+ state.lenbits = opts.bits;
+
+ if (ret) {
+ strm.msg = 'invalid code lengths set';
+ state.mode = BAD$1;
+ break;
+ }
+ //Tracev((stderr, "inflate: code lengths ok\n"));
+ state.have = 0;
+ state.mode = CODELENS;
+ /* falls through */
+ case CODELENS:
+ while (state.have < state.nlen + state.ndist) {
+ for (;;) {
+ here = state.lencode[hold & ((1 << state.lenbits) - 1)];/*BITS(state.lenbits)*/
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ if (here_val < 16) {
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ state.lens[state.have++] = here_val;
+ }
+ else {
+ if (here_val === 16) {
+ //=== NEEDBITS(here.bits + 2);
+ n = here_bits + 2;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ if (state.have === 0) {
+ strm.msg = 'invalid bit length repeat';
+ state.mode = BAD$1;
+ break;
+ }
+ len = state.lens[state.have - 1];
+ copy = 3 + (hold & 0x03);//BITS(2);
+ //--- DROPBITS(2) ---//
+ hold >>>= 2;
+ bits -= 2;
+ //---//
+ }
+ else if (here_val === 17) {
+ //=== NEEDBITS(here.bits + 3);
+ n = here_bits + 3;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ len = 0;
+ copy = 3 + (hold & 0x07);//BITS(3);
+ //--- DROPBITS(3) ---//
+ hold >>>= 3;
+ bits -= 3;
+ //---//
+ }
+ else {
+ //=== NEEDBITS(here.bits + 7);
+ n = here_bits + 7;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ len = 0;
+ copy = 11 + (hold & 0x7f);//BITS(7);
+ //--- DROPBITS(7) ---//
+ hold >>>= 7;
+ bits -= 7;
+ //---//
+ }
+ if (state.have + copy > state.nlen + state.ndist) {
+ strm.msg = 'invalid bit length repeat';
+ state.mode = BAD$1;
+ break;
+ }
+ while (copy--) {
+ state.lens[state.have++] = len;
+ }
+ }
+ }
+
+ /* handle error breaks in while */
+ if (state.mode === BAD$1) { break; }
+
+ /* check for end-of-block code (better have one) */
+ if (state.lens[256] === 0) {
+ strm.msg = 'invalid code -- missing end-of-block';
+ state.mode = BAD$1;
+ break;
+ }
+
+ /* build code tables -- note: do not change the lenbits or distbits
+ values here (9 and 6) without reading the comments in inftrees.h
+ concerning the ENOUGH constants, which depend on those values */
+ state.lenbits = 9;
+
+ opts = { bits: state.lenbits };
+ ret = inftrees(LENS$1, state.lens, 0, state.nlen, state.lencode, 0, state.work, opts);
+ // We have separate tables & no pointers. 2 commented lines below not needed.
+ // state.next_index = opts.table_index;
+ state.lenbits = opts.bits;
+ // state.lencode = state.next;
+
+ if (ret) {
+ strm.msg = 'invalid literal/lengths set';
+ state.mode = BAD$1;
+ break;
+ }
+
+ state.distbits = 6;
+ //state.distcode.copy(state.codes);
+ // Switch to use dynamic table
+ state.distcode = state.distdyn;
+ opts = { bits: state.distbits };
+ ret = inftrees(DISTS$1, state.lens, state.nlen, state.ndist, state.distcode, 0, state.work, opts);
+ // We have separate tables & no pointers. 2 commented lines below not needed.
+ // state.next_index = opts.table_index;
+ state.distbits = opts.bits;
+ // state.distcode = state.next;
+
+ if (ret) {
+ strm.msg = 'invalid distances set';
+ state.mode = BAD$1;
+ break;
+ }
+ //Tracev((stderr, 'inflate: codes ok\n'));
+ state.mode = LEN_;
+ if (flush === Z_TREES) { break inf_leave; }
+ /* falls through */
+ case LEN_:
+ state.mode = LEN;
+ /* falls through */
+ case LEN:
+ if (have >= 6 && left >= 258) {
+ //--- RESTORE() ---
+ strm.next_out = put;
+ strm.avail_out = left;
+ strm.next_in = next;
+ strm.avail_in = have;
+ state.hold = hold;
+ state.bits = bits;
+ //---
+ inffast(strm, _out);
+ //--- LOAD() ---
+ put = strm.next_out;
+ output = strm.output;
+ left = strm.avail_out;
+ next = strm.next_in;
+ input = strm.input;
+ have = strm.avail_in;
+ hold = state.hold;
+ bits = state.bits;
+ //---
+
+ if (state.mode === TYPE$1) {
+ state.back = -1;
+ }
+ break;
+ }
+ state.back = 0;
+ for (;;) {
+ here = state.lencode[hold & ((1 << state.lenbits) - 1)]; /*BITS(state.lenbits)*/
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if (here_bits <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ if (here_op && (here_op & 0xf0) === 0) {
+ last_bits = here_bits;
+ last_op = here_op;
+ last_val = here_val;
+ for (;;) {
+ here = state.lencode[last_val +
+ ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((last_bits + here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ //--- DROPBITS(last.bits) ---//
+ hold >>>= last_bits;
+ bits -= last_bits;
+ //---//
+ state.back += last_bits;
+ }
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ state.back += here_bits;
+ state.length = here_val;
+ if (here_op === 0) {
+ //Tracevv((stderr, here.val >= 0x20 && here.val < 0x7f ?
+ // "inflate: literal '%c'\n" :
+ // "inflate: literal 0x%02x\n", here.val));
+ state.mode = LIT;
+ break;
+ }
+ if (here_op & 32) {
+ //Tracevv((stderr, "inflate: end of block\n"));
+ state.back = -1;
+ state.mode = TYPE$1;
+ break;
+ }
+ if (here_op & 64) {
+ strm.msg = 'invalid literal/length code';
+ state.mode = BAD$1;
+ break;
+ }
+ state.extra = here_op & 15;
+ state.mode = LENEXT;
+ /* falls through */
+ case LENEXT:
+ if (state.extra) {
+ //=== NEEDBITS(state.extra);
+ n = state.extra;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.length += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
+ //--- DROPBITS(state.extra) ---//
+ hold >>>= state.extra;
+ bits -= state.extra;
+ //---//
+ state.back += state.extra;
+ }
+ //Tracevv((stderr, "inflate: length %u\n", state.length));
+ state.was = state.length;
+ state.mode = DIST;
+ /* falls through */
+ case DIST:
+ for (;;) {
+ here = state.distcode[hold & ((1 << state.distbits) - 1)];/*BITS(state.distbits)*/
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ if ((here_op & 0xf0) === 0) {
+ last_bits = here_bits;
+ last_op = here_op;
+ last_val = here_val;
+ for (;;) {
+ here = state.distcode[last_val +
+ ((hold & ((1 << (last_bits + last_op)) - 1))/*BITS(last.bits + last.op)*/ >> last_bits)];
+ here_bits = here >>> 24;
+ here_op = (here >>> 16) & 0xff;
+ here_val = here & 0xffff;
+
+ if ((last_bits + here_bits) <= bits) { break; }
+ //--- PULLBYTE() ---//
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ //---//
+ }
+ //--- DROPBITS(last.bits) ---//
+ hold >>>= last_bits;
+ bits -= last_bits;
+ //---//
+ state.back += last_bits;
+ }
+ //--- DROPBITS(here.bits) ---//
+ hold >>>= here_bits;
+ bits -= here_bits;
+ //---//
+ state.back += here_bits;
+ if (here_op & 64) {
+ strm.msg = 'invalid distance code';
+ state.mode = BAD$1;
+ break;
+ }
+ state.offset = here_val;
+ state.extra = (here_op) & 15;
+ state.mode = DISTEXT;
+ /* falls through */
+ case DISTEXT:
+ if (state.extra) {
+ //=== NEEDBITS(state.extra);
+ n = state.extra;
+ while (bits < n) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ state.offset += hold & ((1 << state.extra) - 1)/*BITS(state.extra)*/;
+ //--- DROPBITS(state.extra) ---//
+ hold >>>= state.extra;
+ bits -= state.extra;
+ //---//
+ state.back += state.extra;
+ }
+//#ifdef INFLATE_STRICT
+ if (state.offset > state.dmax) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD$1;
+ break;
+ }
+//#endif
+ //Tracevv((stderr, "inflate: distance %u\n", state.offset));
+ state.mode = MATCH;
+ /* falls through */
+ case MATCH:
+ if (left === 0) { break inf_leave; }
+ copy = _out - left;
+ if (state.offset > copy) { /* copy from window */
+ copy = state.offset - copy;
+ if (copy > state.whave) {
+ if (state.sane) {
+ strm.msg = 'invalid distance too far back';
+ state.mode = BAD$1;
+ break;
+ }
+// (!) This block is disabled in zlib defaults,
+// don't enable it for binary compatibility
+//#ifdef INFLATE_ALLOW_INVALID_DISTANCE_TOOFAR_ARRR
+// Trace((stderr, "inflate.c too far\n"));
+// copy -= state.whave;
+// if (copy > state.length) { copy = state.length; }
+// if (copy > left) { copy = left; }
+// left -= copy;
+// state.length -= copy;
+// do {
+// output[put++] = 0;
+// } while (--copy);
+// if (state.length === 0) { state.mode = LEN; }
+// break;
+//#endif
+ }
+ if (copy > state.wnext) {
+ copy -= state.wnext;
+ from = state.wsize - copy;
+ }
+ else {
+ from = state.wnext - copy;
+ }
+ if (copy > state.length) { copy = state.length; }
+ from_source = state.window;
+ }
+ else { /* copy from output */
+ from_source = output;
+ from = put - state.offset;
+ copy = state.length;
+ }
+ if (copy > left) { copy = left; }
+ left -= copy;
+ state.length -= copy;
+ do {
+ output[put++] = from_source[from++];
+ } while (--copy);
+ if (state.length === 0) { state.mode = LEN; }
+ break;
+ case LIT:
+ if (left === 0) { break inf_leave; }
+ output[put++] = state.length;
+ left--;
+ state.mode = LEN;
+ break;
+ case CHECK:
+ if (state.wrap) {
+ //=== NEEDBITS(32);
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ // Use '|' instead of '+' to make sure that result is signed
+ hold |= input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ _out -= left;
+ strm.total_out += _out;
+ state.total += _out;
+ if (_out) {
+ strm.adler = state.check =
+ /*UPDATE(state.check, put - _out, _out);*/
+ (state.flags ? crc32_1(state.check, output, _out, put - _out) : adler32_1(state.check, output, _out, put - _out));
+
+ }
+ _out = left;
+ // NB: crc32 stored as signed 32-bit int, zswap32 returns signed too
+ if ((state.flags ? hold : zswap32(hold)) !== state.check) {
+ strm.msg = 'incorrect data check';
+ state.mode = BAD$1;
+ break;
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ //Tracev((stderr, "inflate: check matches trailer\n"));
+ }
+ state.mode = LENGTH;
+ /* falls through */
+ case LENGTH:
+ if (state.wrap && state.flags) {
+ //=== NEEDBITS(32);
+ while (bits < 32) {
+ if (have === 0) { break inf_leave; }
+ have--;
+ hold += input[next++] << bits;
+ bits += 8;
+ }
+ //===//
+ if (hold !== (state.total & 0xffffffff)) {
+ strm.msg = 'incorrect length check';
+ state.mode = BAD$1;
+ break;
+ }
+ //=== INITBITS();
+ hold = 0;
+ bits = 0;
+ //===//
+ //Tracev((stderr, "inflate: length matches trailer\n"));
+ }
+ state.mode = DONE;
+ /* falls through */
+ case DONE:
+ ret = Z_STREAM_END$2;
+ break inf_leave;
+ case BAD$1:
+ ret = Z_DATA_ERROR$1;
+ break inf_leave;
+ case MEM:
+ return Z_MEM_ERROR;
+ case SYNC:
+ /* falls through */
+ default:
+ return Z_STREAM_ERROR$1;
+ }
+ }
+
+ // inf_leave <- here is real place for "goto inf_leave", emulated via "break inf_leave"
+
+ /*
+ Return from inflate(), updating the total counts and the check value.
+ If there was no progress during the inflate() call, return a buffer
+ error. Call updatewindow() to create and/or update the window state.
+ Note: a memory error from inflate() is non-recoverable.
+ */
+
+ //--- RESTORE() ---
+ strm.next_out = put;
+ strm.avail_out = left;
+ strm.next_in = next;
+ strm.avail_in = have;
+ state.hold = hold;
+ state.bits = bits;
+ //---
+
+ if (state.wsize || (_out !== strm.avail_out && state.mode < BAD$1 &&
+ (state.mode < CHECK || flush !== Z_FINISH$2))) {
+ if (updatewindow(strm, strm.output, strm.next_out, _out - strm.avail_out)) ;
+ }
+ _in -= strm.avail_in;
+ _out -= strm.avail_out;
+ strm.total_in += _in;
+ strm.total_out += _out;
+ state.total += _out;
+ if (state.wrap && _out) {
+ strm.adler = state.check = /*UPDATE(state.check, strm.next_out - _out, _out);*/
+ (state.flags ? crc32_1(state.check, output, _out, strm.next_out - _out) : adler32_1(state.check, output, _out, strm.next_out - _out));
+ }
+ strm.data_type = state.bits + (state.last ? 64 : 0) +
+ (state.mode === TYPE$1 ? 128 : 0) +
+ (state.mode === LEN_ || state.mode === COPY_ ? 256 : 0);
+ if (((_in === 0 && _out === 0) || flush === Z_FINISH$2) && ret === Z_OK$2) {
+ ret = Z_BUF_ERROR$1;
+ }
+ return ret;
+};
+
+
+const inflateEnd = (strm) => {
+
+ if (!strm || !strm.state /*|| strm->zfree == (free_func)0*/) {
+ return Z_STREAM_ERROR$1;
+ }
+
+ let state = strm.state;
+ if (state.window) {
+ state.window = null;
+ }
+ strm.state = null;
+ return Z_OK$2;
+};
+
+
+const inflateGetHeader = (strm, head) => {
+
+ /* check state */
+ if (!strm || !strm.state) { return Z_STREAM_ERROR$1; }
+ const state = strm.state;
+ if ((state.wrap & 2) === 0) { return Z_STREAM_ERROR$1; }
+
+ /* save header structure */
+ state.head = head;
+ head.done = false;
+ return Z_OK$2;
+};
+
+
+const inflateSetDictionary = (strm, dictionary) => {
+ const dictLength = dictionary.length;
+
+ let state;
+ let dictid;
+ let ret;
+
+ /* check state */
+ if (!strm /* == Z_NULL */ || !strm.state /* == Z_NULL */) { return Z_STREAM_ERROR$1; }
+ state = strm.state;
+
+ if (state.wrap !== 0 && state.mode !== DICT) {
+ return Z_STREAM_ERROR$1;
+ }
+
+ /* check for correct dictionary identifier */
+ if (state.mode === DICT) {
+ dictid = 1; /* adler32(0, null, 0)*/
+ /* dictid = adler32(dictid, dictionary, dictLength); */
+ dictid = adler32_1(dictid, dictionary, dictLength, 0);
+ if (dictid !== state.check) {
+ return Z_DATA_ERROR$1;
+ }
+ }
+ /* copy dictionary to window using updatewindow(), which will amend the
+ existing dictionary if appropriate */
+ ret = updatewindow(strm, dictionary, dictLength, dictLength);
+ if (ret) {
+ state.mode = MEM;
+ return Z_MEM_ERROR;
+ }
+ state.havedict = 1;
+ // Tracev((stderr, "inflate: dictionary set\n"));
+ return Z_OK$2;
+};
+
+
+var inflateReset_1 = inflateReset;
+var inflateReset2_1 = inflateReset2;
+var inflateResetKeep_1 = inflateResetKeep;
+var inflateInit_1 = inflateInit;
+var inflateInit2_1 = inflateInit2;
+var inflate_2 = inflate;
+var inflateEnd_1 = inflateEnd;
+var inflateGetHeader_1 = inflateGetHeader;
+var inflateSetDictionary_1 = inflateSetDictionary;
+var inflateInfo = 'pako inflate (from Nodeca project)';
+
+/* Not implemented
+module.exports.inflateCopy = inflateCopy;
+module.exports.inflateGetDictionary = inflateGetDictionary;
+module.exports.inflateMark = inflateMark;
+module.exports.inflatePrime = inflatePrime;
+module.exports.inflateSync = inflateSync;
+module.exports.inflateSyncPoint = inflateSyncPoint;
+module.exports.inflateUndermine = inflateUndermine;
+*/
+
+var inflate_1 = {
+ inflateReset: inflateReset_1,
+ inflateReset2: inflateReset2_1,
+ inflateResetKeep: inflateResetKeep_1,
+ inflateInit: inflateInit_1,
+ inflateInit2: inflateInit2_1,
+ inflate: inflate_2,
+ inflateEnd: inflateEnd_1,
+ inflateGetHeader: inflateGetHeader_1,
+ inflateSetDictionary: inflateSetDictionary_1,
+ inflateInfo: inflateInfo
+};
+
+// (C) 1995-2013 Jean-loup Gailly and Mark Adler
+// (C) 2014-2017 Vitaly Puzrin and Andrey Tupitsin
+//
+// This software is provided 'as-is', without any express or implied
+// warranty. In no event will the authors be held liable for any damages
+// arising from the use of this software.
+//
+// Permission is granted to anyone to use this software for any purpose,
+// including commercial applications, and to alter it and redistribute it
+// freely, subject to the following restrictions:
+//
+// 1. The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software. If you use this software
+// in a product, an acknowledgment in the product documentation would be
+// appreciated but is not required.
+// 2. Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+// 3. This notice may not be removed or altered from any source distribution.
+
+function GZheader() {
+ /* true if compressed data believed to be text */
+ this.text = 0;
+ /* modification time */
+ this.time = 0;
+ /* extra flags (not used when writing a gzip file) */
+ this.xflags = 0;
+ /* operating system */
+ this.os = 0;
+ /* pointer to extra field or Z_NULL if none */
+ this.extra = null;
+ /* extra field length (valid if extra != Z_NULL) */
+ this.extra_len = 0; // Actually, we don't need it in JS,
+ // but leave for few code modifications
+
+ //
+ // Setup limits is not necessary because in js we should not preallocate memory
+ // for inflate use constant limit in 65536 bytes
+ //
+
+ /* space at extra (only when reading header) */
+ // this.extra_max = 0;
+ /* pointer to zero-terminated file name or Z_NULL */
+ this.name = '';
+ /* space at name (only when reading header) */
+ // this.name_max = 0;
+ /* pointer to zero-terminated comment or Z_NULL */
+ this.comment = '';
+ /* space at comment (only when reading header) */
+ // this.comm_max = 0;
+ /* true if there was or will be a header crc */
+ this.hcrc = 0;
+ /* true when done reading gzip header (not used when writing a gzip file) */
+ this.done = false;
+}
+
+var gzheader = GZheader;
+
+const toString$1 = Object.prototype.toString;
+
+/* Public constants ==========================================================*/
+/* ===========================================================================*/
+
+const {
+ Z_NO_FLUSH: Z_NO_FLUSH$2, Z_FINISH: Z_FINISH$3,
+ Z_OK: Z_OK$3, Z_STREAM_END: Z_STREAM_END$3, Z_NEED_DICT: Z_NEED_DICT$1, Z_STREAM_ERROR: Z_STREAM_ERROR$2, Z_DATA_ERROR: Z_DATA_ERROR$2, Z_MEM_ERROR: Z_MEM_ERROR$1
+} = constants;
+
+/* ===========================================================================*/
+
+
+/**
+ * class Inflate
+ *
+ * Generic JS-style wrapper for zlib calls. If you don't need
+ * streaming behaviour - use more simple functions: [[inflate]]
+ * and [[inflateRaw]].
+ **/
+
+/* internal
+ * inflate.chunks -> Array
+ *
+ * Chunks of output data, if [[Inflate#onData]] not overridden.
+ **/
+
+/**
+ * Inflate.result -> Uint8Array|String
+ *
+ * Uncompressed result, generated by default [[Inflate#onData]]
+ * and [[Inflate#onEnd]] handlers. Filled after you push last chunk
+ * (call [[Inflate#push]] with `Z_FINISH` / `true` param).
+ **/
+
+/**
+ * Inflate.err -> Number
+ *
+ * Error code after inflate finished. 0 (Z_OK) on success.
+ * Should be checked if broken data possible.
+ **/
+
+/**
+ * Inflate.msg -> String
+ *
+ * Error message, if [[Inflate.err]] != 0
+ **/
+
+
+/**
+ * new Inflate(options)
+ * - options (Object): zlib inflate options.
+ *
+ * Creates new inflator instance with specified params. Throws exception
+ * on bad params. Supported options:
+ *
+ * - `windowBits`
+ * - `dictionary`
+ *
+ * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
+ * for more information on these.
+ *
+ * Additional options, for internal needs:
+ *
+ * - `chunkSize` - size of generated data chunks (16K by default)
+ * - `raw` (Boolean) - do raw inflate
+ * - `to` (String) - if equal to 'string', then result will be converted
+ * from utf8 to utf16 (javascript) string. When string output requested,
+ * chunk length can differ from `chunkSize`, depending on content.
+ *
+ * By default, when no options set, autodetect deflate/gzip data format via
+ * wrapper header.
+ *
+ * ##### Example:
+ *
+ * ```javascript
+ * const pako = require('pako')
+ * const chunk1 = new Uint8Array([1,2,3,4,5,6,7,8,9])
+ * const chunk2 = new Uint8Array([10,11,12,13,14,15,16,17,18,19]);
+ *
+ * const inflate = new pako.Inflate({ level: 3});
+ *
+ * inflate.push(chunk1, false);
+ * inflate.push(chunk2, true); // true -> last chunk
+ *
+ * if (inflate.err) { throw new Error(inflate.err); }
+ *
+ * console.log(inflate.result);
+ * ```
+ **/
+function Inflate(options) {
+ this.options = common.assign({
+ chunkSize: 1024 * 64,
+ windowBits: 15,
+ to: ''
+ }, options || {});
+
+ const opt = this.options;
+
+ // Force window size for `raw` data, if not set directly,
+ // because we have no header for autodetect.
+ if (opt.raw && (opt.windowBits >= 0) && (opt.windowBits < 16)) {
+ opt.windowBits = -opt.windowBits;
+ if (opt.windowBits === 0) { opt.windowBits = -15; }
+ }
+
+ // If `windowBits` not defined (and mode not raw) - set autodetect flag for gzip/deflate
+ if ((opt.windowBits >= 0) && (opt.windowBits < 16) &&
+ !(options && options.windowBits)) {
+ opt.windowBits += 32;
+ }
+
+ // Gzip header has no info about windows size, we can do autodetect only
+ // for deflate. So, if window size not set, force it to max when gzip possible
+ if ((opt.windowBits > 15) && (opt.windowBits < 48)) {
+ // bit 3 (16) -> gzipped data
+ // bit 4 (32) -> autodetect gzip/deflate
+ if ((opt.windowBits & 15) === 0) {
+ opt.windowBits |= 15;
+ }
+ }
+
+ this.err = 0; // error code, if happens (0 = Z_OK)
+ this.msg = ''; // error message
+ this.ended = false; // used to avoid multiple onEnd() calls
+ this.chunks = []; // chunks of compressed data
+
+ this.strm = new zstream();
+ this.strm.avail_out = 0;
+
+ let status = inflate_1.inflateInit2(
+ this.strm,
+ opt.windowBits
+ );
+
+ if (status !== Z_OK$3) {
+ throw new Error(messages[status]);
+ }
+
+ this.header = new gzheader();
+
+ inflate_1.inflateGetHeader(this.strm, this.header);
+
+ // Setup dictionary
+ if (opt.dictionary) {
+ // Convert data if needed
+ if (typeof opt.dictionary === 'string') {
+ opt.dictionary = strings.string2buf(opt.dictionary);
+ } else if (toString$1.call(opt.dictionary) === '[object ArrayBuffer]') {
+ opt.dictionary = new Uint8Array(opt.dictionary);
+ }
+ if (opt.raw) { //In raw mode we need to set the dictionary early
+ status = inflate_1.inflateSetDictionary(this.strm, opt.dictionary);
+ if (status !== Z_OK$3) {
+ throw new Error(messages[status]);
+ }
+ }
+ }
+}
+
+/**
+ * Inflate#push(data[, flush_mode]) -> Boolean
+ * - data (Uint8Array|ArrayBuffer): input data
+ * - flush_mode (Number|Boolean): 0..6 for corresponding Z_NO_FLUSH..Z_TREE
+ * flush modes. See constants. Skipped or `false` means Z_NO_FLUSH,
+ * `true` means Z_FINISH.
+ *
+ * Sends input data to inflate pipe, generating [[Inflate#onData]] calls with
+ * new output chunks. Returns `true` on success. If end of stream detected,
+ * [[Inflate#onEnd]] will be called.
+ *
+ * `flush_mode` is not needed for normal operation, because end of stream
+ * detected automatically. You may try to use it for advanced things, but
+ * this functionality was not tested.
+ *
+ * On fail call [[Inflate#onEnd]] with error code and return false.
+ *
+ * ##### Example
+ *
+ * ```javascript
+ * push(chunk, false); // push one of data chunks
+ * ...
+ * push(chunk, true); // push last chunk
+ * ```
+ **/
+Inflate.prototype.push = function (data, flush_mode) {
+ const strm = this.strm;
+ const chunkSize = this.options.chunkSize;
+ const dictionary = this.options.dictionary;
+ let status, _flush_mode, last_avail_out;
+
+ if (this.ended) return false;
+
+ if (flush_mode === ~~flush_mode) _flush_mode = flush_mode;
+ else _flush_mode = flush_mode === true ? Z_FINISH$3 : Z_NO_FLUSH$2;
+
+ // Convert data if needed
+ if (toString$1.call(data) === '[object ArrayBuffer]') {
+ strm.input = new Uint8Array(data);
+ } else {
+ strm.input = data;
+ }
+
+ strm.next_in = 0;
+ strm.avail_in = strm.input.length;
+
+ for (;;) {
+ if (strm.avail_out === 0) {
+ strm.output = new Uint8Array(chunkSize);
+ strm.next_out = 0;
+ strm.avail_out = chunkSize;
+ }
+
+ status = inflate_1.inflate(strm, _flush_mode);
+
+ if (status === Z_NEED_DICT$1 && dictionary) {
+ status = inflate_1.inflateSetDictionary(strm, dictionary);
+
+ if (status === Z_OK$3) {
+ status = inflate_1.inflate(strm, _flush_mode);
+ } else if (status === Z_DATA_ERROR$2) {
+ // Replace code with more verbose
+ status = Z_NEED_DICT$1;
+ }
+ }
+
+ // Skip snyc markers if more data follows and not raw mode
+ while (strm.avail_in > 0 &&
+ status === Z_STREAM_END$3 &&
+ strm.state.wrap > 0 &&
+ data[strm.next_in] !== 0)
+ {
+ inflate_1.inflateReset(strm);
+ status = inflate_1.inflate(strm, _flush_mode);
+ }
+
+ switch (status) {
+ case Z_STREAM_ERROR$2:
+ case Z_DATA_ERROR$2:
+ case Z_NEED_DICT$1:
+ case Z_MEM_ERROR$1:
+ this.onEnd(status);
+ this.ended = true;
+ return false;
+ }
+
+ // Remember real `avail_out` value, because we may patch out buffer content
+ // to align utf8 strings boundaries.
+ last_avail_out = strm.avail_out;
+
+ if (strm.next_out) {
+ if (strm.avail_out === 0 || status === Z_STREAM_END$3) {
+
+ if (this.options.to === 'string') {
+
+ let next_out_utf8 = strings.utf8border(strm.output, strm.next_out);
+
+ let tail = strm.next_out - next_out_utf8;
+ let utf8str = strings.buf2string(strm.output, next_out_utf8);
+
+ // move tail & realign counters
+ strm.next_out = tail;
+ strm.avail_out = chunkSize - tail;
+ if (tail) strm.output.set(strm.output.subarray(next_out_utf8, next_out_utf8 + tail), 0);
+
+ this.onData(utf8str);
+
+ } else {
+ this.onData(strm.output.length === strm.next_out ? strm.output : strm.output.subarray(0, strm.next_out));
+ }
+ }
+ }
+
+ // Must repeat iteration if out buffer is full
+ if (status === Z_OK$3 && last_avail_out === 0) continue;
+
+ // Finalize if end of stream reached.
+ if (status === Z_STREAM_END$3) {
+ status = inflate_1.inflateEnd(this.strm);
+ this.onEnd(status);
+ this.ended = true;
+ return true;
+ }
+
+ if (strm.avail_in === 0) break;
+ }
+
+ return true;
+};
+
+
+/**
+ * Inflate#onData(chunk) -> Void
+ * - chunk (Uint8Array|String): output data. When string output requested,
+ * each chunk will be string.
+ *
+ * By default, stores data blocks in `chunks[]` property and glue
+ * those in `onEnd`. Override this handler, if you need another behaviour.
+ **/
+Inflate.prototype.onData = function (chunk) {
+ this.chunks.push(chunk);
+};
+
+
+/**
+ * Inflate#onEnd(status) -> Void
+ * - status (Number): inflate status. 0 (Z_OK) on success,
+ * other if not.
+ *
+ * Called either after you tell inflate that the input stream is
+ * complete (Z_FINISH). By default - join collected chunks,
+ * free memory and fill `results` / `err` properties.
+ **/
+Inflate.prototype.onEnd = function (status) {
+ // On success - join
+ if (status === Z_OK$3) {
+ if (this.options.to === 'string') {
+ this.result = this.chunks.join('');
+ } else {
+ this.result = common.flattenChunks(this.chunks);
+ }
+ }
+ this.chunks = [];
+ this.err = status;
+ this.msg = this.strm.msg;
+};
+
+
+/**
+ * inflate(data[, options]) -> Uint8Array|String
+ * - data (Uint8Array): input data to decompress.
+ * - options (Object): zlib inflate options.
+ *
+ * Decompress `data` with inflate/ungzip and `options`. Autodetect
+ * format via wrapper header by default. That's why we don't provide
+ * separate `ungzip` method.
+ *
+ * Supported options are:
+ *
+ * - windowBits
+ *
+ * [http://zlib.net/manual.html#Advanced](http://zlib.net/manual.html#Advanced)
+ * for more information.
+ *
+ * Sugar (options):
+ *
+ * - `raw` (Boolean) - say that we work with raw stream, if you don't wish to specify
+ * negative windowBits implicitly.
+ * - `to` (String) - if equal to 'string', then result will be converted
+ * from utf8 to utf16 (javascript) string. When string output requested,
+ * chunk length can differ from `chunkSize`, depending on content.
+ *
+ *
+ * ##### Example:
+ *
+ * ```javascript
+ * const pako = require('pako');
+ * const input = pako.deflate(new Uint8Array([1,2,3,4,5,6,7,8,9]));
+ * let output;
+ *
+ * try {
+ * output = pako.inflate(input);
+ * } catch (err)
+ * console.log(err);
+ * }
+ * ```
+ **/
+function inflate$1(input, options) {
+ const inflator = new Inflate(options);
+
+ inflator.push(input);
+
+ // That will never happens, if you don't cheat with options :)
+ if (inflator.err) throw inflator.msg || messages[inflator.err];
+
+ return inflator.result;
+}
+
+
+/**
+ * inflateRaw(data[, options]) -> Uint8Array|String
+ * - data (Uint8Array): input data to decompress.
+ * - options (Object): zlib inflate options.
+ *
+ * The same as [[inflate]], but creates raw data, without wrapper
+ * (header and adler32 crc).
+ **/
+function inflateRaw(input, options) {
+ options = options || {};
+ options.raw = true;
+ return inflate$1(input, options);
+}
+
+
+/**
+ * ungzip(data[, options]) -> Uint8Array|String
+ * - data (Uint8Array): input data to decompress.
+ * - options (Object): zlib inflate options.
+ *
+ * Just shortcut to [[inflate]], because it autodetects format
+ * by header.content. Done for convenience.
+ **/
+
+
+var Inflate_1 = Inflate;
+var inflate_2$1 = inflate$1;
+var inflateRaw_1 = inflateRaw;
+var ungzip = inflate$1;
+var constants$2 = constants;
+
+var inflate_1$1 = {
+ Inflate: Inflate_1,
+ inflate: inflate_2$1,
+ inflateRaw: inflateRaw_1,
+ ungzip: ungzip,
+ constants: constants$2
+};
+
+const { Inflate: Inflate$1, inflate: inflate$2, inflateRaw: inflateRaw$1, ungzip: ungzip$1 } = inflate_1$1;
+var inflate_1$2 = inflate$2;
+
+/*! ieee754. BSD-3-Clause License. Feross Aboukhadijeh <https://feross.org/opensource> */
+var read = function (buffer, offset, isLE, mLen, nBytes) {
+ var e, m;
+ var eLen = (nBytes * 8) - mLen - 1;
+ var eMax = (1 << eLen) - 1;
+ var eBias = eMax >> 1;
+ var nBits = -7;
+ var i = isLE ? (nBytes - 1) : 0;
+ var d = isLE ? -1 : 1;
+ var s = buffer[offset + i];
+
+ i += d;
+
+ e = s & ((1 << (-nBits)) - 1);
+ s >>= (-nBits);
+ nBits += eLen;
+ for (; nBits > 0; e = (e * 256) + buffer[offset + i], i += d, nBits -= 8) {}
+
+ m = e & ((1 << (-nBits)) - 1);
+ e >>= (-nBits);
+ nBits += mLen;
+ for (; nBits > 0; m = (m * 256) + buffer[offset + i], i += d, nBits -= 8) {}
+
+ if (e === 0) {
+ e = 1 - eBias;
+ } else if (e === eMax) {
+ return m ? NaN : ((s ? -1 : 1) * Infinity)
+ } else {
+ m = m + Math.pow(2, mLen);
+ e = e - eBias;
+ }
+ return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
+};
+
+var write = function (buffer, value, offset, isLE, mLen, nBytes) {
+ var e, m, c;
+ var eLen = (nBytes * 8) - mLen - 1;
+ var eMax = (1 << eLen) - 1;
+ var eBias = eMax >> 1;
+ var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0);
+ var i = isLE ? 0 : (nBytes - 1);
+ var d = isLE ? 1 : -1;
+ var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0;
+
+ value = Math.abs(value);
+
+ if (isNaN(value) || value === Infinity) {
+ m = isNaN(value) ? 1 : 0;
+ e = eMax;
+ } else {
+ e = Math.floor(Math.log(value) / Math.LN2);
+ if (value * (c = Math.pow(2, -e)) < 1) {
+ e--;
+ c *= 2;
+ }
+ if (e + eBias >= 1) {
+ value += rt / c;
+ } else {
+ value += rt * Math.pow(2, 1 - eBias);
+ }
+ if (value * c >= 2) {
+ e++;
+ c /= 2;
+ }
+
+ if (e + eBias >= eMax) {
+ m = 0;
+ e = eMax;
+ } else if (e + eBias >= 1) {
+ m = ((value * c) - 1) * Math.pow(2, mLen);
+ e = e + eBias;
+ } else {
+ m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen);
+ e = 0;
+ }
+ }
+
+ for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
+
+ e = (e << mLen) | m;
+ eLen += mLen;
+ for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
+
+ buffer[offset + i - d] |= s * 128;
+};
+
+var ieee754 = {
+ read: read,
+ write: write
+};
+
+var pbf = Pbf;
+
+
+
+function Pbf(buf) {
+ this.buf = ArrayBuffer.isView && ArrayBuffer.isView(buf) ? buf : new Uint8Array(buf || 0);
+ this.pos = 0;
+ this.type = 0;
+ this.length = this.buf.length;
+}
+
+Pbf.Varint = 0; // varint: int32, int64, uint32, uint64, sint32, sint64, bool, enum
+Pbf.Fixed64 = 1; // 64-bit: double, fixed64, sfixed64
+Pbf.Bytes = 2; // length-delimited: string, bytes, embedded messages, packed repeated fields
+Pbf.Fixed32 = 5; // 32-bit: float, fixed32, sfixed32
+
+var SHIFT_LEFT_32 = (1 << 16) * (1 << 16),
+ SHIFT_RIGHT_32 = 1 / SHIFT_LEFT_32;
+
+// Threshold chosen based on both benchmarking and knowledge about browser string
+// data structures (which currently switch structure types at 12 bytes or more)
+var TEXT_DECODER_MIN_LENGTH = 12;
+var utf8TextDecoder = typeof TextDecoder === 'undefined' ? null : new TextDecoder('utf8');
+
+Pbf.prototype = {
+
+ destroy: function() {
+ this.buf = null;
+ },
+
+ // === READING =================================================================
+
+ readFields: function(readField, result, end) {
+ end = end || this.length;
+
+ while (this.pos < end) {
+ var val = this.readVarint(),
+ tag = val >> 3,
+ startPos = this.pos;
+
+ this.type = val & 0x7;
+ readField(tag, result, this);
+
+ if (this.pos === startPos) this.skip(val);
+ }
+ return result;
+ },
+
+ readMessage: function(readField, result) {
+ return this.readFields(readField, result, this.readVarint() + this.pos);
+ },
+
+ readFixed32: function() {
+ var val = readUInt32(this.buf, this.pos);
+ this.pos += 4;
+ return val;
+ },
+
+ readSFixed32: function() {
+ var val = readInt32(this.buf, this.pos);
+ this.pos += 4;
+ return val;
+ },
+
+ // 64-bit int handling is based on github.com/dpw/node-buffer-more-ints (MIT-licensed)
+
+ readFixed64: function() {
+ var val = readUInt32(this.buf, this.pos) + readUInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
+ this.pos += 8;
+ return val;
+ },
+
+ readSFixed64: function() {
+ var val = readUInt32(this.buf, this.pos) + readInt32(this.buf, this.pos + 4) * SHIFT_LEFT_32;
+ this.pos += 8;
+ return val;
+ },
+
+ readFloat: function() {
+ var val = ieee754.read(this.buf, this.pos, true, 23, 4);
+ this.pos += 4;
+ return val;
+ },
+
+ readDouble: function() {
+ var val = ieee754.read(this.buf, this.pos, true, 52, 8);
+ this.pos += 8;
+ return val;
+ },
+
+ readVarint: function(isSigned) {
+ var buf = this.buf,
+ val, b;
+
+ b = buf[this.pos++]; val = b & 0x7f; if (b < 0x80) return val;
+ b = buf[this.pos++]; val |= (b & 0x7f) << 7; if (b < 0x80) return val;
+ b = buf[this.pos++]; val |= (b & 0x7f) << 14; if (b < 0x80) return val;
+ b = buf[this.pos++]; val |= (b & 0x7f) << 21; if (b < 0x80) return val;
+ b = buf[this.pos]; val |= (b & 0x0f) << 28;
+
+ return readVarintRemainder(val, isSigned, this);
+ },
+
+ readVarint64: function() { // for compatibility with v2.0.1
+ return this.readVarint(true);
+ },
+
+ readSVarint: function() {
+ var num = this.readVarint();
+ return num % 2 === 1 ? (num + 1) / -2 : num / 2; // zigzag encoding
+ },
+
+ readBoolean: function() {
+ return Boolean(this.readVarint());
+ },
+
+ readString: function() {
+ var end = this.readVarint() + this.pos;
+ var pos = this.pos;
+ this.pos = end;
+
+ if (end - pos >= TEXT_DECODER_MIN_LENGTH && utf8TextDecoder) {
+ // longer strings are fast with the built-in browser TextDecoder API
+ return readUtf8TextDecoder(this.buf, pos, end);
+ }
+ // short strings are fast with our custom implementation
+ return readUtf8(this.buf, pos, end);
+ },
+
+ readBytes: function() {
+ var end = this.readVarint() + this.pos,
+ buffer = this.buf.subarray(this.pos, end);
+ this.pos = end;
+ return buffer;
+ },
+
+ // verbose for performance reasons; doesn't affect gzipped size
+
+ readPackedVarint: function(arr, isSigned) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readVarint(isSigned));
+ var end = readPackedEnd(this);
+ arr = arr || [];
+ while (this.pos < end) arr.push(this.readVarint(isSigned));
+ return arr;
+ },
+ readPackedSVarint: function(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readSVarint());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+ while (this.pos < end) arr.push(this.readSVarint());
+ return arr;
+ },
+ readPackedBoolean: function(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readBoolean());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+ while (this.pos < end) arr.push(this.readBoolean());
+ return arr;
+ },
+ readPackedFloat: function(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readFloat());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+ while (this.pos < end) arr.push(this.readFloat());
+ return arr;
+ },
+ readPackedDouble: function(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readDouble());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+ while (this.pos < end) arr.push(this.readDouble());
+ return arr;
+ },
+ readPackedFixed32: function(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readFixed32());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+ while (this.pos < end) arr.push(this.readFixed32());
+ return arr;
+ },
+ readPackedSFixed32: function(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed32());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+ while (this.pos < end) arr.push(this.readSFixed32());
+ return arr;
+ },
+ readPackedFixed64: function(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readFixed64());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+ while (this.pos < end) arr.push(this.readFixed64());
+ return arr;
+ },
+ readPackedSFixed64: function(arr) {
+ if (this.type !== Pbf.Bytes) return arr.push(this.readSFixed64());
+ var end = readPackedEnd(this);
+ arr = arr || [];
+ while (this.pos < end) arr.push(this.readSFixed64());
+ return arr;
+ },
+
+ skip: function(val) {
+ var type = val & 0x7;
+ if (type === Pbf.Varint) while (this.buf[this.pos++] > 0x7f) {}
+ else if (type === Pbf.Bytes) this.pos = this.readVarint() + this.pos;
+ else if (type === Pbf.Fixed32) this.pos += 4;
+ else if (type === Pbf.Fixed64) this.pos += 8;
+ else throw new Error('Unimplemented type: ' + type);
+ },
+
+ // === WRITING =================================================================
+
+ writeTag: function(tag, type) {
+ this.writeVarint((tag << 3) | type);
+ },
+
+ realloc: function(min) {
+ var length = this.length || 16;
+
+ while (length < this.pos + min) length *= 2;
+
+ if (length !== this.length) {
+ var buf = new Uint8Array(length);
+ buf.set(this.buf);
+ this.buf = buf;
+ this.length = length;
+ }
+ },
+
+ finish: function() {
+ this.length = this.pos;
+ this.pos = 0;
+ return this.buf.subarray(0, this.length);
+ },
+
+ writeFixed32: function(val) {
+ this.realloc(4);
+ writeInt32(this.buf, val, this.pos);
+ this.pos += 4;
+ },
+
+ writeSFixed32: function(val) {
+ this.realloc(4);
+ writeInt32(this.buf, val, this.pos);
+ this.pos += 4;
+ },
+
+ writeFixed64: function(val) {
+ this.realloc(8);
+ writeInt32(this.buf, val & -1, this.pos);
+ writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
+ this.pos += 8;
+ },
+
+ writeSFixed64: function(val) {
+ this.realloc(8);
+ writeInt32(this.buf, val & -1, this.pos);
+ writeInt32(this.buf, Math.floor(val * SHIFT_RIGHT_32), this.pos + 4);
+ this.pos += 8;
+ },
+
+ writeVarint: function(val) {
+ val = +val || 0;
+
+ if (val > 0xfffffff || val < 0) {
+ writeBigVarint(val, this);
+ return;
+ }
+
+ this.realloc(4);
+
+ this.buf[this.pos++] = val & 0x7f | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
+ this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
+ this.buf[this.pos++] = ((val >>>= 7) & 0x7f) | (val > 0x7f ? 0x80 : 0); if (val <= 0x7f) return;
+ this.buf[this.pos++] = (val >>> 7) & 0x7f;
+ },
+
+ writeSVarint: function(val) {
+ this.writeVarint(val < 0 ? -val * 2 - 1 : val * 2);
+ },
+
+ writeBoolean: function(val) {
+ this.writeVarint(Boolean(val));
+ },
+
+ writeString: function(str) {
+ str = String(str);
+ this.realloc(str.length * 4);
+
+ this.pos++; // reserve 1 byte for short string length
+
+ var startPos = this.pos;
+ // write the string directly to the buffer and see how much was written
+ this.pos = writeUtf8(this.buf, str, this.pos);
+ var len = this.pos - startPos;
+
+ if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);
+
+ // finally, write the message length in the reserved place and restore the position
+ this.pos = startPos - 1;
+ this.writeVarint(len);
+ this.pos += len;
+ },
+
+ writeFloat: function(val) {
+ this.realloc(4);
+ ieee754.write(this.buf, val, this.pos, true, 23, 4);
+ this.pos += 4;
+ },
+
+ writeDouble: function(val) {
+ this.realloc(8);
+ ieee754.write(this.buf, val, this.pos, true, 52, 8);
+ this.pos += 8;
+ },
+
+ writeBytes: function(buffer) {
+ var len = buffer.length;
+ this.writeVarint(len);
+ this.realloc(len);
+ for (var i = 0; i < len; i++) this.buf[this.pos++] = buffer[i];
+ },
+
+ writeRawMessage: function(fn, obj) {
+ this.pos++; // reserve 1 byte for short message length
+
+ // write the message directly to the buffer and see how much was written
+ var startPos = this.pos;
+ fn(obj, this);
+ var len = this.pos - startPos;
+
+ if (len >= 0x80) makeRoomForExtraLength(startPos, len, this);
+
+ // finally, write the message length in the reserved place and restore the position
+ this.pos = startPos - 1;
+ this.writeVarint(len);
+ this.pos += len;
+ },
+
+ writeMessage: function(tag, fn, obj) {
+ this.writeTag(tag, Pbf.Bytes);
+ this.writeRawMessage(fn, obj);
+ },
+
+ writePackedVarint: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedVarint, arr); },
+ writePackedSVarint: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSVarint, arr); },
+ writePackedBoolean: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedBoolean, arr); },
+ writePackedFloat: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFloat, arr); },
+ writePackedDouble: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedDouble, arr); },
+ writePackedFixed32: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed32, arr); },
+ writePackedSFixed32: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed32, arr); },
+ writePackedFixed64: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedFixed64, arr); },
+ writePackedSFixed64: function(tag, arr) { if (arr.length) this.writeMessage(tag, writePackedSFixed64, arr); },
+
+ writeBytesField: function(tag, buffer) {
+ this.writeTag(tag, Pbf.Bytes);
+ this.writeBytes(buffer);
+ },
+ writeFixed32Field: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed32);
+ this.writeFixed32(val);
+ },
+ writeSFixed32Field: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed32);
+ this.writeSFixed32(val);
+ },
+ writeFixed64Field: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed64);
+ this.writeFixed64(val);
+ },
+ writeSFixed64Field: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed64);
+ this.writeSFixed64(val);
+ },
+ writeVarintField: function(tag, val) {
+ this.writeTag(tag, Pbf.Varint);
+ this.writeVarint(val);
+ },
+ writeSVarintField: function(tag, val) {
+ this.writeTag(tag, Pbf.Varint);
+ this.writeSVarint(val);
+ },
+ writeStringField: function(tag, str) {
+ this.writeTag(tag, Pbf.Bytes);
+ this.writeString(str);
+ },
+ writeFloatField: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed32);
+ this.writeFloat(val);
+ },
+ writeDoubleField: function(tag, val) {
+ this.writeTag(tag, Pbf.Fixed64);
+ this.writeDouble(val);
+ },
+ writeBooleanField: function(tag, val) {
+ this.writeVarintField(tag, Boolean(val));
+ }
+};
+
+function readVarintRemainder(l, s, p) {
+ var buf = p.buf,
+ h, b;
+
+ b = buf[p.pos++]; h = (b & 0x70) >> 4; if (b < 0x80) return toNum(l, h, s);
+ b = buf[p.pos++]; h |= (b & 0x7f) << 3; if (b < 0x80) return toNum(l, h, s);
+ b = buf[p.pos++]; h |= (b & 0x7f) << 10; if (b < 0x80) return toNum(l, h, s);
+ b = buf[p.pos++]; h |= (b & 0x7f) << 17; if (b < 0x80) return toNum(l, h, s);
+ b = buf[p.pos++]; h |= (b & 0x7f) << 24; if (b < 0x80) return toNum(l, h, s);
+ b = buf[p.pos++]; h |= (b & 0x01) << 31; if (b < 0x80) return toNum(l, h, s);
+
+ throw new Error('Expected varint not more than 10 bytes');
+}
+
+function readPackedEnd(pbf) {
+ return pbf.type === Pbf.Bytes ?
+ pbf.readVarint() + pbf.pos : pbf.pos + 1;
+}
+
+function toNum(low, high, isSigned) {
+ if (isSigned) {
+ return high * 0x100000000 + (low >>> 0);
+ }
+
+ return ((high >>> 0) * 0x100000000) + (low >>> 0);
+}
+
+function writeBigVarint(val, pbf) {
+ var low, high;
+
+ if (val >= 0) {
+ low = (val % 0x100000000) | 0;
+ high = (val / 0x100000000) | 0;
+ } else {
+ low = ~(-val % 0x100000000);
+ high = ~(-val / 0x100000000);
+
+ if (low ^ 0xffffffff) {
+ low = (low + 1) | 0;
+ } else {
+ low = 0;
+ high = (high + 1) | 0;
+ }
+ }
+
+ if (val >= 0x10000000000000000 || val < -0x10000000000000000) {
+ throw new Error('Given varint doesn\'t fit into 10 bytes');
+ }
+
+ pbf.realloc(10);
+
+ writeBigVarintLow(low, high, pbf);
+ writeBigVarintHigh(high, pbf);
+}
+
+function writeBigVarintLow(low, high, pbf) {
+ pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+ pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+ pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+ pbf.buf[pbf.pos++] = low & 0x7f | 0x80; low >>>= 7;
+ pbf.buf[pbf.pos] = low & 0x7f;
+}
+
+function writeBigVarintHigh(high, pbf) {
+ var lsb = (high & 0x07) << 4;
+
+ pbf.buf[pbf.pos++] |= lsb | ((high >>>= 3) ? 0x80 : 0); if (!high) return;
+ pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+ pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+ pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+ pbf.buf[pbf.pos++] = high & 0x7f | ((high >>>= 7) ? 0x80 : 0); if (!high) return;
+ pbf.buf[pbf.pos++] = high & 0x7f;
+}
+
+function makeRoomForExtraLength(startPos, len, pbf) {
+ var extraLen =
+ len <= 0x3fff ? 1 :
+ len <= 0x1fffff ? 2 :
+ len <= 0xfffffff ? 3 : Math.floor(Math.log(len) / (Math.LN2 * 7));
+
+ // if 1 byte isn't enough for encoding message length, shift the data to the right
+ pbf.realloc(extraLen);
+ for (var i = pbf.pos - 1; i >= startPos; i--) pbf.buf[i + extraLen] = pbf.buf[i];
+}
+
+function writePackedVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeVarint(arr[i]); }
+function writePackedSVarint(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSVarint(arr[i]); }
+function writePackedFloat(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFloat(arr[i]); }
+function writePackedDouble(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeDouble(arr[i]); }
+function writePackedBoolean(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeBoolean(arr[i]); }
+function writePackedFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFixed32(arr[i]); }
+function writePackedSFixed32(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed32(arr[i]); }
+function writePackedFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeFixed64(arr[i]); }
+function writePackedSFixed64(arr, pbf) { for (var i = 0; i < arr.length; i++) pbf.writeSFixed64(arr[i]); }
+
+// Buffer code below from https://github.com/feross/buffer, MIT-licensed
+
+function readUInt32(buf, pos) {
+ return ((buf[pos]) |
+ (buf[pos + 1] << 8) |
+ (buf[pos + 2] << 16)) +
+ (buf[pos + 3] * 0x1000000);
+}
+
+function writeInt32(buf, val, pos) {
+ buf[pos] = val;
+ buf[pos + 1] = (val >>> 8);
+ buf[pos + 2] = (val >>> 16);
+ buf[pos + 3] = (val >>> 24);
+}
+
+function readInt32(buf, pos) {
+ return ((buf[pos]) |
+ (buf[pos + 1] << 8) |
+ (buf[pos + 2] << 16)) +
+ (buf[pos + 3] << 24);
+}
+
+function readUtf8(buf, pos, end) {
+ var str = '';
+ var i = pos;
+
+ while (i < end) {
+ var b0 = buf[i];
+ var c = null; // codepoint
+ var bytesPerSequence =
+ b0 > 0xEF ? 4 :
+ b0 > 0xDF ? 3 :
+ b0 > 0xBF ? 2 : 1;
+
+ if (i + bytesPerSequence > end) break;
+
+ var b1, b2, b3;
+
+ if (bytesPerSequence === 1) {
+ if (b0 < 0x80) {
+ c = b0;
+ }
+ } else if (bytesPerSequence === 2) {
+ b1 = buf[i + 1];
+ if ((b1 & 0xC0) === 0x80) {
+ c = (b0 & 0x1F) << 0x6 | (b1 & 0x3F);
+ if (c <= 0x7F) {
+ c = null;
+ }
+ }
+ } else if (bytesPerSequence === 3) {
+ b1 = buf[i + 1];
+ b2 = buf[i + 2];
+ if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80) {
+ c = (b0 & 0xF) << 0xC | (b1 & 0x3F) << 0x6 | (b2 & 0x3F);
+ if (c <= 0x7FF || (c >= 0xD800 && c <= 0xDFFF)) {
+ c = null;
+ }
+ }
+ } else if (bytesPerSequence === 4) {
+ b1 = buf[i + 1];
+ b2 = buf[i + 2];
+ b3 = buf[i + 3];
+ if ((b1 & 0xC0) === 0x80 && (b2 & 0xC0) === 0x80 && (b3 & 0xC0) === 0x80) {
+ c = (b0 & 0xF) << 0x12 | (b1 & 0x3F) << 0xC | (b2 & 0x3F) << 0x6 | (b3 & 0x3F);
+ if (c <= 0xFFFF || c >= 0x110000) {
+ c = null;
+ }
+ }
+ }
+
+ if (c === null) {
+ c = 0xFFFD;
+ bytesPerSequence = 1;
+
+ } else if (c > 0xFFFF) {
+ c -= 0x10000;
+ str += String.fromCharCode(c >>> 10 & 0x3FF | 0xD800);
+ c = 0xDC00 | c & 0x3FF;
+ }
+
+ str += String.fromCharCode(c);
+ i += bytesPerSequence;
+ }
+
+ return str;
+}
+
+function readUtf8TextDecoder(buf, pos, end) {
+ return utf8TextDecoder.decode(buf.subarray(pos, end));
+}
+
+function writeUtf8(buf, str, pos) {
+ for (var i = 0, c, lead; i < str.length; i++) {
+ c = str.charCodeAt(i); // code point
+
+ if (c > 0xD7FF && c < 0xE000) {
+ if (lead) {
+ if (c < 0xDC00) {
+ buf[pos++] = 0xEF;
+ buf[pos++] = 0xBF;
+ buf[pos++] = 0xBD;
+ lead = c;
+ continue;
+ } else {
+ c = lead - 0xD800 << 10 | c - 0xDC00 | 0x10000;
+ lead = null;
+ }
+ } else {
+ if (c > 0xDBFF || (i + 1 === str.length)) {
+ buf[pos++] = 0xEF;
+ buf[pos++] = 0xBF;
+ buf[pos++] = 0xBD;
+ } else {
+ lead = c;
+ }
+ continue;
+ }
+ } else if (lead) {
+ buf[pos++] = 0xEF;
+ buf[pos++] = 0xBF;
+ buf[pos++] = 0xBD;
+ lead = null;
+ }
+
+ if (c < 0x80) {
+ buf[pos++] = c;
+ } else {
+ if (c < 0x800) {
+ buf[pos++] = c >> 0x6 | 0xC0;
+ } else {
+ if (c < 0x10000) {
+ buf[pos++] = c >> 0xC | 0xE0;
+ } else {
+ buf[pos++] = c >> 0x12 | 0xF0;
+ buf[pos++] = c >> 0xC & 0x3F | 0x80;
+ }
+ buf[pos++] = c >> 0x6 & 0x3F | 0x80;
+ }
+ buf[pos++] = c & 0x3F | 0x80;
+ }
+ }
+ return pos;
+}
+
+/**
+ * Decompress and parse an array buffer containing zipped
+ * json data and return as a json object.
+ *
+ * @description Handles array buffers continaing zipped json
+ * data.
+ *
+ * @param {ArrayBuffer} buffer - Array buffer to decompress.
+ * @returns {Object} Parsed object.
+ */
+function decompress(buffer) {
+ const inflated = inflate_1$2(buffer, { to: "string" });
+ return JSON.parse(inflated);
+}
+/**
+ * Retrieves a resource as an array buffer and returns a promise
+ * to the buffer.
+ *
+ * @description Rejects the promise on request failure.
+ *
+ * @param {string} url - URL for resource to retrieve.
+ * @param {Promise} [abort] - Optional promise for aborting
+ * the request through rejection.
+ * @returns {Promise<ArrayBuffer>} Promise to the array buffer
+ * resource.
+ */
+function fetchArrayBuffer(url, abort) {
+ const method = "GET";
+ const responseType = "arraybuffer";
+ return xhrFetch(url, method, responseType, [], null, abort);
+}
+function xhrFetch(url, method, responseType, headers, body, abort) {
+ const xhr = new XMLHttpRequest();
+ const promise = new Promise((resolve, reject) => {
+ xhr.open(method, url, true);
+ for (const header of headers) {
+ xhr.setRequestHeader(header.name, header.value);
+ }
+ xhr.responseType = responseType;
+ xhr.timeout = 15000;
+ xhr.onload = () => {
+ var _a;
+ if (xhr.status !== 200) {
+ const error = (_a = xhr.response) !== null && _a !== void 0 ? _a : new MapillaryError(`Response status error: ${url}`);
+ reject(error);
+ }
+ if (!xhr.response) {
+ reject(new MapillaryError(`Response empty: ${url}`));
+ }
+ resolve(xhr.response);
+ };
+ xhr.onerror = () => {
+ reject(new MapillaryError(`Request error: ${url}`));
+ };
+ xhr.ontimeout = () => {
+ reject(new MapillaryError(`Request timeout: ${url}`));
+ };
+ xhr.onabort = () => {
+ reject(new MapillaryError(`Request aborted: ${url}`));
+ };
+ xhr.send(method === "POST" ? body : null);
+ });
+ if (!!abort) {
+ abort.catch(() => { xhr.abort(); });
+ }
+ return promise;
+}
+/**
+ * Read the fields of a protobuf array buffer into a mesh
+ * object.
+ *
+ * @param {ArrayBuffer} buffer - Protobuf array buffer
+ * to read from.
+ * @returns {MeshContract} Mesh object.
+ */
+function readMeshPbf(buffer) {
+ const pbf$1 = new pbf(buffer);
+ const mesh = { faces: [], vertices: [] };
+ return pbf$1.readFields(readMeshPbfField, mesh);
+}
+function readMeshPbfField(tag, mesh, pbf) {
+ if (tag === 1) {
+ mesh.vertices.push(pbf.readFloat());
+ }
+ else if (tag === 2) {
+ mesh.faces.push(pbf.readVarint());
+ }
+ else {
+ console.warn(`Unsupported pbf tag (${tag})`);
+ }
+}
+
+/**
+ * @class GeometryProviderBase
+ *
+ * @classdesc Base class to extend if implementing a geometry
+ * provider class.
+ *
+ * @example
+ * ```js
+ * class MyGeometryProvider extends GeometryProviderBase {
+ * ...
+ * }
+ * ```
+ */
+class GeometryProviderBase {
+ /**
+ * Create a new geometry provider base instance.
+ */
+ constructor() { }
+ /**
+ * Convert a geodetic bounding box to the the minimum set
+ * of cell ids containing the bounding box.
+ *
+ * @description The bounding box needs
+ * to be sufficiently small to be contained in an area with the size
+ * of maximally four tiles. Up to nine adjacent tiles may be returned.
+ *
+ * @param {LngLat} sw - South west corner of bounding box.
+ * @param {LngLat} ne - North east corner of bounding box.
+ *
+ * @returns {Array<string>} Array of cell ids.
+ */
+ bboxToCellIds(sw, ne) {
+ throw new MapillaryError("Not implemented");
+ }
+ /**
+ * Get the cell ids of all adjacent cells.
+ *
+ * @description In the case of approximately rectangular cells
+ * this is typically the eight orthogonally and diagonally adjacent
+ * cells.
+ *
+ * @param {string} cellId - Id of cell.
+ * @returns {Array<string>} Array of cell ids. No specific
+ * order is guaranteed.
+ */
+ getAdjacent(cellId) {
+ throw new MapillaryError("Not implemented");
+ }
+ /**
+ * Get the vertices of a cell.
+ *
+ * @description The vertices form an unclosed
+ * clockwise polygon in the 2D longitude, latitude
+ * space. No assumption on the position of the first
+ * vertex relative to the others can be made.
+ *
+ * @param {string} cellId - Id of cell.
+ * @returns {Array<LngLat>} Unclosed clockwise polygon.
+ */
+ getVertices(cellId) {
+ throw new MapillaryError("Not implemented");
+ }
+ /**
+ * Convert geodetic coordinates to a cell id.
+ *
+ * @param {LngLat} lngLat - Longitude, latitude to convert.
+ * @returns {string} Cell id for the longitude, latitude.
+ */
+ lngLatToCellId(lngLat) {
+ throw new MapillaryError("Not implemented");
+ }
+ /** @ignore */
+ _approxBboxToCellIds(sw, ne) {
+ if (ne.lat <= sw.lat || ne.lng <= sw.lng) {
+ throw new MapillaryError("North east needs to be top right of south west");
+ }
+ const centerLat = (sw.lat + ne.lat) / 2;
+ const centerLng = (sw.lng + ne.lng) / 2;
+ const enu = geodeticToEnu(ne.lng, ne.lat, 0, centerLng, centerLat, 0);
+ const threshold = Math.max(enu[0], enu[1]);
+ return this._lngLatToCellIds({ lat: centerLat, lng: centerLng }, threshold);
+ }
+ /** @ignore */
+ _enuToGeodetic(point, reference) {
+ const [lng, lat] = enuToGeodetic(point[0], point[1], point[2], reference.lng, reference.lat, 0);
+ return { lat, lng };
+ }
+ /** @ignore */
+ _getLngLatBoundingBoxCorners(lngLat, threshold) {
+ return [
+ [-threshold, threshold, 0],
+ [threshold, threshold, 0],
+ [threshold, -threshold, 0],
+ [-threshold, -threshold, 0],
+ ].map((point) => {
+ return this._enuToGeodetic(point, lngLat);
+ });
+ }
+ /**
+ * Convert a geodetic square to cell ids.
+ *
+ * The square is specified as a longitude, latitude
+ * and a threshold from the position using Manhattan distance.
+ *
+ * @param {LngLat} lngLat - Longitude, latitude.
+ * @param {number} threshold - Threshold of the conversion in meters.
+ *
+ * @returns {Array<string>} Array of cell ids reachable within
+ * the threshold.
+ *
+ * @ignore
+ */
+ _lngLatToCellIds(lngLat, threshold) {
+ const cellId = this.lngLatToCellId(lngLat);
+ const bboxCorners = this._getLngLatBoundingBoxCorners(lngLat, threshold);
+ for (const corner of bboxCorners) {
+ const cid = this.lngLatToCellId(corner);
+ if (cid !== cellId) {
+ return [cellId, ...this.getAdjacent(cellId)];
+ }
+ }
+ return [cellId];
+ }
+}
+
+/**
+ * @class DataProviderBase
+ *
+ * @classdesc Base class to extend if implementing a data provider
+ * class.
+ *
+ * @fires datacreate
+ *
+ * @example
+ * ```js
+ * class MyDataProvider extends DataProviderBase {
+ * constructor() {
+ * super(new S2GeometryProvider());
+ * }
+ * ...
+ * }
+ * ```
+ */
+class DataProviderBase extends EventEmitter {
+ /**
+ * Create a new data provider base instance.
+ *
+ * @param {GeometryProviderBase} geometry - Geometry
+ * provider instance.
+ */
+ constructor(_geometry) {
+ super();
+ this._geometry = _geometry;
+ if (!(this._geometry instanceof GeometryProviderBase)) {
+ throw new MapillaryError("The data provider requires a geometry provider base instance.");
+ }
+ }
+ /**
+ * Get geometry property.
+ *
+ * @returns {GeometryProviderBase} Geometry provider instance.
+ */
+ get geometry() {
+ return this._geometry;
+ }
+ fire(type, event) {
+ super.fire(type, event);
+ }
+ /**
+ * Get core images in a geometry cell.
+ *
+ * @param {string} cellId - The id of the geometry cell.
+ * @returns {Promise<CoreImagesContract>} Promise to
+ * the core images of the requested geometry cell id.
+ * @throws Rejects the promise on errors.
+ */
+ getCoreImages(cellId) {
+ return Promise.reject(new MapillaryError("Not implemented"));
+ }
+ /**
+ * Get a cluster reconstruction.
+ *
+ * @param {string} url - URL for the cluster reconstruction
+ * to retrieve.
+ * @param {Promise} [abort] - Optional promise for aborting
+ * the request through rejection.
+ * @returns {Promise<ClusterContract>} Promise to the
+ * cluster reconstruction.
+ * @throws Rejects the promise on errors.
+ */
+ getCluster(url, abort) {
+ return Promise.reject(new MapillaryError("Not implemented"));
+ }
+ /**
+ * Get spatial images.
+ *
+ * @param {Array<string>} imageIds - The ids for the
+ * images to retrieve.
+ * @returns {Promise<SpatialImagesContract>} Promise to
+ * the spatial images of the requested image ids.
+ * @throws Rejects the promise on errors.
+ */
+ getSpatialImages(imageIds) {
+ return Promise.reject(new MapillaryError("Not implemented"));
+ }
+ /**
+ * Get complete images.
+ *
+ * @param {Array<string>} imageIds - The ids for the
+ * images to retrieve.
+ * @returns {Promise<ImagesContract>} Promise to the images of the
+ * requested image ids.
+ * @throws Rejects the promise on errors.
+ */
+ getImages(imageIds) {
+ return Promise.reject(new MapillaryError("Not implemented"));
+ }
+ /**
+ * Get an image as an array buffer.
+ *
+ * @param {string} url - URL for image to retrieve.
+ * @param {Promise<void>} [abort] - Optional promise for aborting
+ * the request through rejection.
+ * @returns {Promise<ArrayBuffer>} Promise to the array
+ * buffer containing the image.
+ * @throws Rejects the promise on errors.
+ */
+ getImageBuffer(url, abort) {
+ return Promise.reject(new MapillaryError("Not implemented"));
+ }
+ /**
+ * Get image tiles urls for a tile level.
+ *
+ * @param {ImageTilesRequestContract} tiles - Tiles to request
+ * @returns {Promise<ImageTilesContract>} Promise to the
+ * image tiles response contract
+ *
+ * @throws Rejects the promise on errors.
+ *
+ * @example
+ * ```js
+ * var tileRequest = { imageId: 'image-id', z: 12 };
+ * provider.getImageTiles(tileRequest)
+ * .then((response) => console.log(response));
+ * ```
+ */
+ getImageTiles(tiles) {
+ return Promise.reject(new MapillaryError("Not implemented"));
+ }
+ /**
+ * Get a mesh.
+ *
+ * @param {string} url - URL for mesh to retrieve.
+ * @param {Promise<void>} [abort] - Optional promise for aborting
+ * the request through rejection.
+ * @returns {Promise<MeshContract>} Promise to the mesh.
+ * @throws Rejects the promise on errors.
+ */
+ getMesh(url, abort) {
+ return Promise.reject(new MapillaryError("Not implemented"));
+ }
+ /**
+ * Get sequence.
+ *
+ * @param {Array<string>} sequenceId - The id for the
+ * sequence to retrieve.
+ * @returns {Promise} Promise to the sequences of the
+ * requested image ids.
+ * @throws Rejects the promise on errors.
+ */
+ getSequence(sequenceId) {
+ return Promise.reject(new MapillaryError("Not implemented"));
+ }
+ off(type, handler) {
+ super.off(type, handler);
+ }
+ on(type, handler) {
+ super.on(type, handler);
+ }
+ /**
+ * Set an access token for authenticated API requests of
+ * protected resources.
+ *
+ * @param {string} [accessToken] accessToken - User access
+ * token or client access token.
+ */
+ setAccessToken(accessToken) {
+ throw new MapillaryError("Not implemented");
+ }
+}
+
+/*
+ Copyright 2013 Daniel Wirtz <dcode@dcode.io>
+ Copyright 2009 The Closure Library Authors. All Rights Reserved.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS-IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ */
+
+var long = createCommonjsModule(function (module) {
+/**
+ * @license long.js (c) 2013 Daniel Wirtz <dcode@dcode.io>
+ * Released under the Apache License, Version 2.0
+ * see: https://github.com/dcodeIO/long.js for details
+ */
+(function(global, factory) {
+
+ /* AMD */ if (typeof commonjsRequire === 'function' && 'object' === "object" && module && module["exports"])
+ module["exports"] = factory();
+ /* Global */ else
+ (global["dcodeIO"] = global["dcodeIO"] || {})["Long"] = factory();
+
+})(commonjsGlobal, function() {
+
+ /**
+ * Constructs a 64 bit two's-complement integer, given its low and high 32 bit values as *signed* integers.
+ * See the from* functions below for more convenient ways of constructing Longs.
+ * @exports Long
+ * @class A Long class for representing a 64 bit two's-complement integer value.
+ * @param {number} low The low (signed) 32 bits of the long
+ * @param {number} high The high (signed) 32 bits of the long
+ * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+ * @constructor
+ */
+ function Long(low, high, unsigned) {
+
+ /**
+ * The low 32 bits as a signed value.
+ * @type {number}
+ */
+ this.low = low | 0;
+
+ /**
+ * The high 32 bits as a signed value.
+ * @type {number}
+ */
+ this.high = high | 0;
+
+ /**
+ * Whether unsigned or not.
+ * @type {boolean}
+ */
+ this.unsigned = !!unsigned;
+ }
+
+ // The internal representation of a long is the two given signed, 32-bit values.
+ // We use 32-bit pieces because these are the size of integers on which
+ // Javascript performs bit-operations. For operations like addition and
+ // multiplication, we split each number into 16 bit pieces, which can easily be
+ // multiplied within Javascript's floating-point representation without overflow
+ // or change in sign.
+ //
+ // In the algorithms below, we frequently reduce the negative case to the
+ // positive case by negating the input(s) and then post-processing the result.
+ // Note that we must ALWAYS check specially whether those values are MIN_VALUE
+ // (-2^63) because -MIN_VALUE == MIN_VALUE (since 2^63 cannot be represented as
+ // a positive number, it overflows back into a negative). Not handling this
+ // case would often result in infinite recursion.
+ //
+ // Common constant values ZERO, ONE, NEG_ONE, etc. are defined below the from*
+ // methods on which they depend.
+
+ /**
+ * An indicator used to reliably determine if an object is a Long or not.
+ * @type {boolean}
+ * @const
+ * @private
+ */
+ Long.prototype.__isLong__;
+
+ Object.defineProperty(Long.prototype, "__isLong__", {
+ value: true,
+ enumerable: false,
+ configurable: false
+ });
+
+ /**
+ * @function
+ * @param {*} obj Object
+ * @returns {boolean}
+ * @inner
+ */
+ function isLong(obj) {
+ return (obj && obj["__isLong__"]) === true;
+ }
+
+ /**
+ * Tests if the specified object is a Long.
+ * @function
+ * @param {*} obj Object
+ * @returns {boolean}
+ */
+ Long.isLong = isLong;
+
+ /**
+ * A cache of the Long representations of small integer values.
+ * @type {!Object}
+ * @inner
+ */
+ var INT_CACHE = {};
+
+ /**
+ * A cache of the Long representations of small unsigned integer values.
+ * @type {!Object}
+ * @inner
+ */
+ var UINT_CACHE = {};
+
+ /**
+ * @param {number} value
+ * @param {boolean=} unsigned
+ * @returns {!Long}
+ * @inner
+ */
+ function fromInt(value, unsigned) {
+ var obj, cachedObj, cache;
+ if (unsigned) {
+ value >>>= 0;
+ if (cache = (0 <= value && value < 256)) {
+ cachedObj = UINT_CACHE[value];
+ if (cachedObj)
+ return cachedObj;
+ }
+ obj = fromBits(value, (value | 0) < 0 ? -1 : 0, true);
+ if (cache)
+ UINT_CACHE[value] = obj;
+ return obj;
+ } else {
+ value |= 0;
+ if (cache = (-128 <= value && value < 128)) {
+ cachedObj = INT_CACHE[value];
+ if (cachedObj)
+ return cachedObj;
+ }
+ obj = fromBits(value, value < 0 ? -1 : 0, false);
+ if (cache)
+ INT_CACHE[value] = obj;
+ return obj;
+ }
+ }
+
+ /**
+ * Returns a Long representing the given 32 bit integer value.
+ * @function
+ * @param {number} value The 32 bit integer in question
+ * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+ * @returns {!Long} The corresponding Long value
+ */
+ Long.fromInt = fromInt;
+
+ /**
+ * @param {number} value
+ * @param {boolean=} unsigned
+ * @returns {!Long}
+ * @inner
+ */
+ function fromNumber(value, unsigned) {
+ if (isNaN(value) || !isFinite(value))
+ return unsigned ? UZERO : ZERO;
+ if (unsigned) {
+ if (value < 0)
+ return UZERO;
+ if (value >= TWO_PWR_64_DBL)
+ return MAX_UNSIGNED_VALUE;
+ } else {
+ if (value <= -TWO_PWR_63_DBL)
+ return MIN_VALUE;
+ if (value + 1 >= TWO_PWR_63_DBL)
+ return MAX_VALUE;
+ }
+ if (value < 0)
+ return fromNumber(-value, unsigned).neg();
+ return fromBits((value % TWO_PWR_32_DBL) | 0, (value / TWO_PWR_32_DBL) | 0, unsigned);
+ }
+
+ /**
+ * Returns a Long representing the given value, provided that it is a finite number. Otherwise, zero is returned.
+ * @function
+ * @param {number} value The number in question
+ * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+ * @returns {!Long} The corresponding Long value
+ */
+ Long.fromNumber = fromNumber;
+
+ /**
+ * @param {number} lowBits
+ * @param {number} highBits
+ * @param {boolean=} unsigned
+ * @returns {!Long}
+ * @inner
+ */
+ function fromBits(lowBits, highBits, unsigned) {
+ return new Long(lowBits, highBits, unsigned);
+ }
+
+ /**
+ * Returns a Long representing the 64 bit integer that comes by concatenating the given low and high bits. Each is
+ * assumed to use 32 bits.
+ * @function
+ * @param {number} lowBits The low 32 bits
+ * @param {number} highBits The high 32 bits
+ * @param {boolean=} unsigned Whether unsigned or not, defaults to `false` for signed
+ * @returns {!Long} The corresponding Long value
+ */
+ Long.fromBits = fromBits;
+
+ /**
+ * @function
+ * @param {number} base
+ * @param {number} exponent
+ * @returns {number}
+ * @inner
+ */
+ var pow_dbl = Math.pow; // Used 4 times (4*8 to 15+4)
+
+ /**
+ * @param {string} str
+ * @param {(boolean|number)=} unsigned
+ * @param {number=} radix
+ * @returns {!Long}
+ * @inner
+ */
+ function fromString(str, unsigned, radix) {
+ if (str.length === 0)
+ throw Error('empty string');
+ if (str === "NaN" || str === "Infinity" || str === "+Infinity" || str === "-Infinity")
+ return ZERO;
+ if (typeof unsigned === 'number') {
+ // For goog.math.long compatibility
+ radix = unsigned,
+ unsigned = false;
+ } else {
+ unsigned = !! unsigned;
+ }
+ radix = radix || 10;
+ if (radix < 2 || 36 < radix)
+ throw RangeError('radix');
+
+ var p;
+ if ((p = str.indexOf('-')) > 0)
+ throw Error('interior hyphen');
+ else if (p === 0) {
+ return fromString(str.substring(1), unsigned, radix).neg();
+ }
+
+ // Do several (8) digits each time through the loop, so as to
+ // minimize the calls to the very expensive emulated div.
+ var radixToPower = fromNumber(pow_dbl(radix, 8));
+
+ var result = ZERO;
+ for (var i = 0; i < str.length; i += 8) {
+ var size = Math.min(8, str.length - i),
+ value = parseInt(str.substring(i, i + size), radix);
+ if (size < 8) {
+ var power = fromNumber(pow_dbl(radix, size));
+ result = result.mul(power).add(fromNumber(value));
+ } else {
+ result = result.mul(radixToPower);
+ result = result.add(fromNumber(value));
+ }
+ }
+ result.unsigned = unsigned;
+ return result;
+ }
+
+ /**
+ * Returns a Long representation of the given string, written using the specified radix.
+ * @function
+ * @param {string} str The textual representation of the Long
+ * @param {(boolean|number)=} unsigned Whether unsigned or not, defaults to `false` for signed
+ * @param {number=} radix The radix in which the text is written (2-36), defaults to 10
+ * @returns {!Long} The corresponding Long value
+ */
+ Long.fromString = fromString;
+
+ /**
+ * @function
+ * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val
+ * @returns {!Long}
+ * @inner
+ */
+ function fromValue(val) {
+ if (val /* is compatible */ instanceof Long)
+ return val;
+ if (typeof val === 'number')
+ return fromNumber(val);
+ if (typeof val === 'string')
+ return fromString(val);
+ // Throws for non-objects, converts non-instanceof Long:
+ return fromBits(val.low, val.high, val.unsigned);
+ }
+
+ /**
+ * Converts the specified value to a Long.
+ * @function
+ * @param {!Long|number|string|!{low: number, high: number, unsigned: boolean}} val Value
+ * @returns {!Long}
+ */
+ Long.fromValue = fromValue;
+
+ // NOTE: the compiler should inline these constant values below and then remove these variables, so there should be
+ // no runtime penalty for these.
+
+ /**
+ * @type {number}
+ * @const
+ * @inner
+ */
+ var TWO_PWR_16_DBL = 1 << 16;
+
+ /**
+ * @type {number}
+ * @const
+ * @inner
+ */
+ var TWO_PWR_24_DBL = 1 << 24;
+
+ /**
+ * @type {number}
+ * @const
+ * @inner
+ */
+ var TWO_PWR_32_DBL = TWO_PWR_16_DBL * TWO_PWR_16_DBL;
+
+ /**
+ * @type {number}
+ * @const
+ * @inner
+ */
+ var TWO_PWR_64_DBL = TWO_PWR_32_DBL * TWO_PWR_32_DBL;
+
+ /**
+ * @type {number}
+ * @const
+ * @inner
+ */
+ var TWO_PWR_63_DBL = TWO_PWR_64_DBL / 2;
+
+ /**
+ * @type {!Long}
+ * @const
+ * @inner
+ */
+ var TWO_PWR_24 = fromInt(TWO_PWR_24_DBL);
+
+ /**
+ * @type {!Long}
+ * @inner
+ */
+ var ZERO = fromInt(0);
+
+ /**
+ * Signed zero.
+ * @type {!Long}
+ */
+ Long.ZERO = ZERO;
+
+ /**
+ * @type {!Long}
+ * @inner
+ */
+ var UZERO = fromInt(0, true);
+
+ /**
+ * Unsigned zero.
+ * @type {!Long}
+ */
+ Long.UZERO = UZERO;
+
+ /**
+ * @type {!Long}
+ * @inner
+ */
+ var ONE = fromInt(1);
+
+ /**
+ * Signed one.
+ * @type {!Long}
+ */
+ Long.ONE = ONE;
+
+ /**
+ * @type {!Long}
+ * @inner
+ */
+ var UONE = fromInt(1, true);
+
+ /**
+ * Unsigned one.
+ * @type {!Long}
+ */
+ Long.UONE = UONE;
+
+ /**
+ * @type {!Long}
+ * @inner
+ */
+ var NEG_ONE = fromInt(-1);
+
+ /**
+ * Signed negative one.
+ * @type {!Long}
+ */
+ Long.NEG_ONE = NEG_ONE;
+
+ /**
+ * @type {!Long}
+ * @inner
+ */
+ var MAX_VALUE = fromBits(0xFFFFFFFF|0, 0x7FFFFFFF|0, false);
+
+ /**
+ * Maximum signed value.
+ * @type {!Long}
+ */
+ Long.MAX_VALUE = MAX_VALUE;
+
+ /**
+ * @type {!Long}
+ * @inner
+ */
+ var MAX_UNSIGNED_VALUE = fromBits(0xFFFFFFFF|0, 0xFFFFFFFF|0, true);
+
+ /**
+ * Maximum unsigned value.
+ * @type {!Long}
+ */
+ Long.MAX_UNSIGNED_VALUE = MAX_UNSIGNED_VALUE;
+
+ /**
+ * @type {!Long}
+ * @inner
+ */
+ var MIN_VALUE = fromBits(0, 0x80000000|0, false);
+
+ /**
+ * Minimum signed value.
+ * @type {!Long}
+ */
+ Long.MIN_VALUE = MIN_VALUE;
+
+ /**
+ * @alias Long.prototype
+ * @inner
+ */
+ var LongPrototype = Long.prototype;
+
+ /**
+ * Converts the Long to a 32 bit integer, assuming it is a 32 bit integer.
+ * @returns {number}
+ */
+ LongPrototype.toInt = function toInt() {
+ return this.unsigned ? this.low >>> 0 : this.low;
+ };
+
+ /**
+ * Converts the Long to a the nearest floating-point representation of this value (double, 53 bit mantissa).
+ * @returns {number}
+ */
+ LongPrototype.toNumber = function toNumber() {
+ if (this.unsigned)
+ return ((this.high >>> 0) * TWO_PWR_32_DBL) + (this.low >>> 0);
+ return this.high * TWO_PWR_32_DBL + (this.low >>> 0);
+ };
+
+ /**
+ * Converts the Long to a string written in the specified radix.
+ * @param {number=} radix Radix (2-36), defaults to 10
+ * @returns {string}
+ * @override
+ * @throws {RangeError} If `radix` is out of range
+ */
+ LongPrototype.toString = function toString(radix) {
+ radix = radix || 10;
+ if (radix < 2 || 36 < radix)
+ throw RangeError('radix');
+ if (this.isZero())
+ return '0';
+ if (this.isNegative()) { // Unsigned Longs are never negative
+ if (this.eq(MIN_VALUE)) {
+ // We need to change the Long value before it can be negated, so we remove
+ // the bottom-most digit in this base and then recurse to do the rest.
+ var radixLong = fromNumber(radix),
+ div = this.div(radixLong),
+ rem1 = div.mul(radixLong).sub(this);
+ return div.toString(radix) + rem1.toInt().toString(radix);
+ } else
+ return '-' + this.neg().toString(radix);
+ }
+
+ // Do several (6) digits each time through the loop, so as to
+ // minimize the calls to the very expensive emulated div.
+ var radixToPower = fromNumber(pow_dbl(radix, 6), this.unsigned),
+ rem = this;
+ var result = '';
+ while (true) {
+ var remDiv = rem.div(radixToPower),
+ intval = rem.sub(remDiv.mul(radixToPower)).toInt() >>> 0,
+ digits = intval.toString(radix);
+ rem = remDiv;
+ if (rem.isZero())
+ return digits + result;
+ else {
+ while (digits.length < 6)
+ digits = '0' + digits;
+ result = '' + digits + result;
+ }
+ }
+ };
+
+ /**
+ * Gets the high 32 bits as a signed integer.
+ * @returns {number} Signed high bits
+ */
+ LongPrototype.getHighBits = function getHighBits() {
+ return this.high;
+ };
+
+ /**
+ * Gets the high 32 bits as an unsigned integer.
+ * @returns {number} Unsigned high bits
+ */
+ LongPrototype.getHighBitsUnsigned = function getHighBitsUnsigned() {
+ return this.high >>> 0;
+ };
+
+ /**
+ * Gets the low 32 bits as a signed integer.
+ * @returns {number} Signed low bits
+ */
+ LongPrototype.getLowBits = function getLowBits() {
+ return this.low;
+ };
+
+ /**
+ * Gets the low 32 bits as an unsigned integer.
+ * @returns {number} Unsigned low bits
+ */
+ LongPrototype.getLowBitsUnsigned = function getLowBitsUnsigned() {
+ return this.low >>> 0;
+ };
+
+ /**
+ * Gets the number of bits needed to represent the absolute value of this Long.
+ * @returns {number}
+ */
+ LongPrototype.getNumBitsAbs = function getNumBitsAbs() {
+ if (this.isNegative()) // Unsigned Longs are never negative
+ return this.eq(MIN_VALUE) ? 64 : this.neg().getNumBitsAbs();
+ var val = this.high != 0 ? this.high : this.low;
+ for (var bit = 31; bit > 0; bit--)
+ if ((val & (1 << bit)) != 0)
+ break;
+ return this.high != 0 ? bit + 33 : bit + 1;
+ };
+
+ /**
+ * Tests if this Long's value equals zero.
+ * @returns {boolean}
+ */
+ LongPrototype.isZero = function isZero() {
+ return this.high === 0 && this.low === 0;
+ };
+
+ /**
+ * Tests if this Long's value is negative.
+ * @returns {boolean}
+ */
+ LongPrototype.isNegative = function isNegative() {
+ return !this.unsigned && this.high < 0;
+ };
+
+ /**
+ * Tests if this Long's value is positive.
+ * @returns {boolean}
+ */
+ LongPrototype.isPositive = function isPositive() {
+ return this.unsigned || this.high >= 0;
+ };
+
+ /**
+ * Tests if this Long's value is odd.
+ * @returns {boolean}
+ */
+ LongPrototype.isOdd = function isOdd() {
+ return (this.low & 1) === 1;
+ };
+
+ /**
+ * Tests if this Long's value is even.
+ * @returns {boolean}
+ */
+ LongPrototype.isEven = function isEven() {
+ return (this.low & 1) === 0;
+ };
+
+ /**
+ * Tests if this Long's value equals the specified's.
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.equals = function equals(other) {
+ if (!isLong(other))
+ other = fromValue(other);
+ if (this.unsigned !== other.unsigned && (this.high >>> 31) === 1 && (other.high >>> 31) === 1)
+ return false;
+ return this.high === other.high && this.low === other.low;
+ };
+
+ /**
+ * Tests if this Long's value equals the specified's. This is an alias of {@link Long#equals}.
+ * @function
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.eq = LongPrototype.equals;
+
+ /**
+ * Tests if this Long's value differs from the specified's.
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.notEquals = function notEquals(other) {
+ return !this.eq(/* validates */ other);
+ };
+
+ /**
+ * Tests if this Long's value differs from the specified's. This is an alias of {@link Long#notEquals}.
+ * @function
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.neq = LongPrototype.notEquals;
+
+ /**
+ * Tests if this Long's value is less than the specified's.
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.lessThan = function lessThan(other) {
+ return this.comp(/* validates */ other) < 0;
+ };
+
+ /**
+ * Tests if this Long's value is less than the specified's. This is an alias of {@link Long#lessThan}.
+ * @function
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.lt = LongPrototype.lessThan;
+
+ /**
+ * Tests if this Long's value is less than or equal the specified's.
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.lessThanOrEqual = function lessThanOrEqual(other) {
+ return this.comp(/* validates */ other) <= 0;
+ };
+
+ /**
+ * Tests if this Long's value is less than or equal the specified's. This is an alias of {@link Long#lessThanOrEqual}.
+ * @function
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.lte = LongPrototype.lessThanOrEqual;
+
+ /**
+ * Tests if this Long's value is greater than the specified's.
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.greaterThan = function greaterThan(other) {
+ return this.comp(/* validates */ other) > 0;
+ };
+
+ /**
+ * Tests if this Long's value is greater than the specified's. This is an alias of {@link Long#greaterThan}.
+ * @function
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.gt = LongPrototype.greaterThan;
+
+ /**
+ * Tests if this Long's value is greater than or equal the specified's.
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.greaterThanOrEqual = function greaterThanOrEqual(other) {
+ return this.comp(/* validates */ other) >= 0;
+ };
+
+ /**
+ * Tests if this Long's value is greater than or equal the specified's. This is an alias of {@link Long#greaterThanOrEqual}.
+ * @function
+ * @param {!Long|number|string} other Other value
+ * @returns {boolean}
+ */
+ LongPrototype.gte = LongPrototype.greaterThanOrEqual;
+
+ /**
+ * Compares this Long's value with the specified's.
+ * @param {!Long|number|string} other Other value
+ * @returns {number} 0 if they are the same, 1 if the this is greater and -1
+ * if the given one is greater
+ */
+ LongPrototype.compare = function compare(other) {
+ if (!isLong(other))
+ other = fromValue(other);
+ if (this.eq(other))
+ return 0;
+ var thisNeg = this.isNegative(),
+ otherNeg = other.isNegative();
+ if (thisNeg && !otherNeg)
+ return -1;
+ if (!thisNeg && otherNeg)
+ return 1;
+ // At this point the sign bits are the same
+ if (!this.unsigned)
+ return this.sub(other).isNegative() ? -1 : 1;
+ // Both are positive if at least one is unsigned
+ return (other.high >>> 0) > (this.high >>> 0) || (other.high === this.high && (other.low >>> 0) > (this.low >>> 0)) ? -1 : 1;
+ };
+
+ /**
+ * Compares this Long's value with the specified's. This is an alias of {@link Long#compare}.
+ * @function
+ * @param {!Long|number|string} other Other value
+ * @returns {number} 0 if they are the same, 1 if the this is greater and -1
+ * if the given one is greater
+ */
+ LongPrototype.comp = LongPrototype.compare;
+
+ /**
+ * Negates this Long's value.
+ * @returns {!Long} Negated Long
+ */
+ LongPrototype.negate = function negate() {
+ if (!this.unsigned && this.eq(MIN_VALUE))
+ return MIN_VALUE;
+ return this.not().add(ONE);
+ };
+
+ /**
+ * Negates this Long's value. This is an alias of {@link Long#negate}.
+ * @function
+ * @returns {!Long} Negated Long
+ */
+ LongPrototype.neg = LongPrototype.negate;
+
+ /**
+ * Returns the sum of this and the specified Long.
+ * @param {!Long|number|string} addend Addend
+ * @returns {!Long} Sum
+ */
+ LongPrototype.add = function add(addend) {
+ if (!isLong(addend))
+ addend = fromValue(addend);
+
+ // Divide each number into 4 chunks of 16 bits, and then sum the chunks.
+
+ var a48 = this.high >>> 16;
+ var a32 = this.high & 0xFFFF;
+ var a16 = this.low >>> 16;
+ var a00 = this.low & 0xFFFF;
+
+ var b48 = addend.high >>> 16;
+ var b32 = addend.high & 0xFFFF;
+ var b16 = addend.low >>> 16;
+ var b00 = addend.low & 0xFFFF;
+
+ var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
+ c00 += a00 + b00;
+ c16 += c00 >>> 16;
+ c00 &= 0xFFFF;
+ c16 += a16 + b16;
+ c32 += c16 >>> 16;
+ c16 &= 0xFFFF;
+ c32 += a32 + b32;
+ c48 += c32 >>> 16;
+ c32 &= 0xFFFF;
+ c48 += a48 + b48;
+ c48 &= 0xFFFF;
+ return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
+ };
+
+ /**
+ * Returns the difference of this and the specified Long.
+ * @param {!Long|number|string} subtrahend Subtrahend
+ * @returns {!Long} Difference
+ */
+ LongPrototype.subtract = function subtract(subtrahend) {
+ if (!isLong(subtrahend))
+ subtrahend = fromValue(subtrahend);
+ return this.add(subtrahend.neg());
+ };
+
+ /**
+ * Returns the difference of this and the specified Long. This is an alias of {@link Long#subtract}.
+ * @function
+ * @param {!Long|number|string} subtrahend Subtrahend
+ * @returns {!Long} Difference
+ */
+ LongPrototype.sub = LongPrototype.subtract;
+
+ /**
+ * Returns the product of this and the specified Long.
+ * @param {!Long|number|string} multiplier Multiplier
+ * @returns {!Long} Product
+ */
+ LongPrototype.multiply = function multiply(multiplier) {
+ if (this.isZero())
+ return ZERO;
+ if (!isLong(multiplier))
+ multiplier = fromValue(multiplier);
+ if (multiplier.isZero())
+ return ZERO;
+ if (this.eq(MIN_VALUE))
+ return multiplier.isOdd() ? MIN_VALUE : ZERO;
+ if (multiplier.eq(MIN_VALUE))
+ return this.isOdd() ? MIN_VALUE : ZERO;
+
+ if (this.isNegative()) {
+ if (multiplier.isNegative())
+ return this.neg().mul(multiplier.neg());
+ else
+ return this.neg().mul(multiplier).neg();
+ } else if (multiplier.isNegative())
+ return this.mul(multiplier.neg()).neg();
+
+ // If both longs are small, use float multiplication
+ if (this.lt(TWO_PWR_24) && multiplier.lt(TWO_PWR_24))
+ return fromNumber(this.toNumber() * multiplier.toNumber(), this.unsigned);
+
+ // Divide each long into 4 chunks of 16 bits, and then add up 4x4 products.
+ // We can skip products that would overflow.
+
+ var a48 = this.high >>> 16;
+ var a32 = this.high & 0xFFFF;
+ var a16 = this.low >>> 16;
+ var a00 = this.low & 0xFFFF;
+
+ var b48 = multiplier.high >>> 16;
+ var b32 = multiplier.high & 0xFFFF;
+ var b16 = multiplier.low >>> 16;
+ var b00 = multiplier.low & 0xFFFF;
+
+ var c48 = 0, c32 = 0, c16 = 0, c00 = 0;
+ c00 += a00 * b00;
+ c16 += c00 >>> 16;
+ c00 &= 0xFFFF;
+ c16 += a16 * b00;
+ c32 += c16 >>> 16;
+ c16 &= 0xFFFF;
+ c16 += a00 * b16;
+ c32 += c16 >>> 16;
+ c16 &= 0xFFFF;
+ c32 += a32 * b00;
+ c48 += c32 >>> 16;
+ c32 &= 0xFFFF;
+ c32 += a16 * b16;
+ c48 += c32 >>> 16;
+ c32 &= 0xFFFF;
+ c32 += a00 * b32;
+ c48 += c32 >>> 16;
+ c32 &= 0xFFFF;
+ c48 += a48 * b00 + a32 * b16 + a16 * b32 + a00 * b48;
+ c48 &= 0xFFFF;
+ return fromBits((c16 << 16) | c00, (c48 << 16) | c32, this.unsigned);
+ };
+
+ /**
+ * Returns the product of this and the specified Long. This is an alias of {@link Long#multiply}.
+ * @function
+ * @param {!Long|number|string} multiplier Multiplier
+ * @returns {!Long} Product
+ */
+ LongPrototype.mul = LongPrototype.multiply;
+
+ /**
+ * Returns this Long divided by the specified. The result is signed if this Long is signed or
+ * unsigned if this Long is unsigned.
+ * @param {!Long|number|string} divisor Divisor
+ * @returns {!Long} Quotient
+ */
+ LongPrototype.divide = function divide(divisor) {
+ if (!isLong(divisor))
+ divisor = fromValue(divisor);
+ if (divisor.isZero())
+ throw Error('division by zero');
+ if (this.isZero())
+ return this.unsigned ? UZERO : ZERO;
+ var approx, rem, res;
+ if (!this.unsigned) {
+ // This section is only relevant for signed longs and is derived from the
+ // closure library as a whole.
+ if (this.eq(MIN_VALUE)) {
+ if (divisor.eq(ONE) || divisor.eq(NEG_ONE))
+ return MIN_VALUE; // recall that -MIN_VALUE == MIN_VALUE
+ else if (divisor.eq(MIN_VALUE))
+ return ONE;
+ else {
+ // At this point, we have |other| >= 2, so |this/other| < |MIN_VALUE|.
+ var halfThis = this.shr(1);
+ approx = halfThis.div(divisor).shl(1);
+ if (approx.eq(ZERO)) {
+ return divisor.isNegative() ? ONE : NEG_ONE;
+ } else {
+ rem = this.sub(divisor.mul(approx));
+ res = approx.add(rem.div(divisor));
+ return res;
+ }
+ }
+ } else if (divisor.eq(MIN_VALUE))
+ return this.unsigned ? UZERO : ZERO;
+ if (this.isNegative()) {
+ if (divisor.isNegative())
+ return this.neg().div(divisor.neg());
+ return this.neg().div(divisor).neg();
+ } else if (divisor.isNegative())
+ return this.div(divisor.neg()).neg();
+ res = ZERO;
+ } else {
+ // The algorithm below has not been made for unsigned longs. It's therefore
+ // required to take special care of the MSB prior to running it.
+ if (!divisor.unsigned)
+ divisor = divisor.toUnsigned();
+ if (divisor.gt(this))
+ return UZERO;
+ if (divisor.gt(this.shru(1))) // 15 >>> 1 = 7 ; with divisor = 8 ; true
+ return UONE;
+ res = UZERO;
+ }
+
+ // Repeat the following until the remainder is less than other: find a
+ // floating-point that approximates remainder / other *from below*, add this
+ // into the result, and subtract it from the remainder. It is critical that
+ // the approximate value is less than or equal to the real value so that the
+ // remainder never becomes negative.
+ rem = this;
+ while (rem.gte(divisor)) {
+ // Approximate the result of division. This may be a little greater or
+ // smaller than the actual value.
+ approx = Math.max(1, Math.floor(rem.toNumber() / divisor.toNumber()));
+
+ // We will tweak the approximate result by changing it in the 48-th digit or
+ // the smallest non-fractional digit, whichever is larger.
+ var log2 = Math.ceil(Math.log(approx) / Math.LN2),
+ delta = (log2 <= 48) ? 1 : pow_dbl(2, log2 - 48),
+
+ // Decrease the approximation until it is smaller than the remainder. Note
+ // that if it is too large, the product overflows and is negative.
+ approxRes = fromNumber(approx),
+ approxRem = approxRes.mul(divisor);
+ while (approxRem.isNegative() || approxRem.gt(rem)) {
+ approx -= delta;
+ approxRes = fromNumber(approx, this.unsigned);
+ approxRem = approxRes.mul(divisor);
+ }
+
+ // We know the answer can't be zero... and actually, zero would cause
+ // infinite recursion since we would make no progress.
+ if (approxRes.isZero())
+ approxRes = ONE;
+
+ res = res.add(approxRes);
+ rem = rem.sub(approxRem);
+ }
+ return res;
+ };
+
+ /**
+ * Returns this Long divided by the specified. This is an alias of {@link Long#divide}.
+ * @function
+ * @param {!Long|number|string} divisor Divisor
+ * @returns {!Long} Quotient
+ */
+ LongPrototype.div = LongPrototype.divide;
+
+ /**
+ * Returns this Long modulo the specified.
+ * @param {!Long|number|string} divisor Divisor
+ * @returns {!Long} Remainder
+ */
+ LongPrototype.modulo = function modulo(divisor) {
+ if (!isLong(divisor))
+ divisor = fromValue(divisor);
+ return this.sub(this.div(divisor).mul(divisor));
+ };
+
+ /**
+ * Returns this Long modulo the specified. This is an alias of {@link Long#modulo}.
+ * @function
+ * @param {!Long|number|string} divisor Divisor
+ * @returns {!Long} Remainder
+ */
+ LongPrototype.mod = LongPrototype.modulo;
+
+ /**
+ * Returns the bitwise NOT of this Long.
+ * @returns {!Long}
+ */
+ LongPrototype.not = function not() {
+ return fromBits(~this.low, ~this.high, this.unsigned);
+ };
+
+ /**
+ * Returns the bitwise AND of this Long and the specified.
+ * @param {!Long|number|string} other Other Long
+ * @returns {!Long}
+ */
+ LongPrototype.and = function and(other) {
+ if (!isLong(other))
+ other = fromValue(other);
+ return fromBits(this.low & other.low, this.high & other.high, this.unsigned);
+ };
+
+ /**
+ * Returns the bitwise OR of this Long and the specified.
+ * @param {!Long|number|string} other Other Long
+ * @returns {!Long}
+ */
+ LongPrototype.or = function or(other) {
+ if (!isLong(other))
+ other = fromValue(other);
+ return fromBits(this.low | other.low, this.high | other.high, this.unsigned);
+ };
+
+ /**
+ * Returns the bitwise XOR of this Long and the given one.
+ * @param {!Long|number|string} other Other Long
+ * @returns {!Long}
+ */
+ LongPrototype.xor = function xor(other) {
+ if (!isLong(other))
+ other = fromValue(other);
+ return fromBits(this.low ^ other.low, this.high ^ other.high, this.unsigned);
+ };
+
+ /**
+ * Returns this Long with bits shifted to the left by the given amount.
+ * @param {number|!Long} numBits Number of bits
+ * @returns {!Long} Shifted Long
+ */
+ LongPrototype.shiftLeft = function shiftLeft(numBits) {
+ if (isLong(numBits))
+ numBits = numBits.toInt();
+ if ((numBits &= 63) === 0)
+ return this;
+ else if (numBits < 32)
+ return fromBits(this.low << numBits, (this.high << numBits) | (this.low >>> (32 - numBits)), this.unsigned);
+ else
+ return fromBits(0, this.low << (numBits - 32), this.unsigned);
+ };
+
+ /**
+ * Returns this Long with bits shifted to the left by the given amount. This is an alias of {@link Long#shiftLeft}.
+ * @function
+ * @param {number|!Long} numBits Number of bits
+ * @returns {!Long} Shifted Long
+ */
+ LongPrototype.shl = LongPrototype.shiftLeft;
+
+ /**
+ * Returns this Long with bits arithmetically shifted to the right by the given amount.
+ * @param {number|!Long} numBits Number of bits
+ * @returns {!Long} Shifted Long
+ */
+ LongPrototype.shiftRight = function shiftRight(numBits) {
+ if (isLong(numBits))
+ numBits = numBits.toInt();
+ if ((numBits &= 63) === 0)
+ return this;
+ else if (numBits < 32)
+ return fromBits((this.low >>> numBits) | (this.high << (32 - numBits)), this.high >> numBits, this.unsigned);
+ else
+ return fromBits(this.high >> (numBits - 32), this.high >= 0 ? 0 : -1, this.unsigned);
+ };
+
+ /**
+ * Returns this Long with bits arithmetically shifted to the right by the given amount. This is an alias of {@link Long#shiftRight}.
+ * @function
+ * @param {number|!Long} numBits Number of bits
+ * @returns {!Long} Shifted Long
+ */
+ LongPrototype.shr = LongPrototype.shiftRight;
+
+ /**
+ * Returns this Long with bits logically shifted to the right by the given amount.
+ * @param {number|!Long} numBits Number of bits
+ * @returns {!Long} Shifted Long
+ */
+ LongPrototype.shiftRightUnsigned = function shiftRightUnsigned(numBits) {
+ if (isLong(numBits))
+ numBits = numBits.toInt();
+ numBits &= 63;
+ if (numBits === 0)
+ return this;
+ else {
+ var high = this.high;
+ if (numBits < 32) {
+ var low = this.low;
+ return fromBits((low >>> numBits) | (high << (32 - numBits)), high >>> numBits, this.unsigned);
+ } else if (numBits === 32)
+ return fromBits(high, 0, this.unsigned);
+ else
+ return fromBits(high >>> (numBits - 32), 0, this.unsigned);
+ }
+ };
+
+ /**
+ * Returns this Long with bits logically shifted to the right by the given amount. This is an alias of {@link Long#shiftRightUnsigned}.
+ * @function
+ * @param {number|!Long} numBits Number of bits
+ * @returns {!Long} Shifted Long
+ */
+ LongPrototype.shru = LongPrototype.shiftRightUnsigned;
+
+ /**
+ * Converts this Long to signed.
+ * @returns {!Long} Signed long
+ */
+ LongPrototype.toSigned = function toSigned() {
+ if (!this.unsigned)
+ return this;
+ return fromBits(this.low, this.high, false);
+ };
+
+ /**
+ * Converts this Long to unsigned.
+ * @returns {!Long} Unsigned long
+ */
+ LongPrototype.toUnsigned = function toUnsigned() {
+ if (this.unsigned)
+ return this;
+ return fromBits(this.low, this.high, true);
+ };
+
+ /**
+ * Converts this Long to its byte representation.
+ * @param {boolean=} le Whether little or big endian, defaults to big endian
+ * @returns {!Array.<number>} Byte representation
+ */
+ LongPrototype.toBytes = function(le) {
+ return le ? this.toBytesLE() : this.toBytesBE();
+ };
+
+ /**
+ * Converts this Long to its little endian byte representation.
+ * @returns {!Array.<number>} Little endian byte representation
+ */
+ LongPrototype.toBytesLE = function() {
+ var hi = this.high,
+ lo = this.low;
+ return [
+ lo & 0xff,
+ (lo >>> 8) & 0xff,
+ (lo >>> 16) & 0xff,
+ (lo >>> 24) & 0xff,
+ hi & 0xff,
+ (hi >>> 8) & 0xff,
+ (hi >>> 16) & 0xff,
+ (hi >>> 24) & 0xff
+ ];
+ };
+
+ /**
+ * Converts this Long to its big endian byte representation.
+ * @returns {!Array.<number>} Big endian byte representation
+ */
+ LongPrototype.toBytesBE = function() {
+ var hi = this.high,
+ lo = this.low;
+ return [
+ (hi >>> 24) & 0xff,
+ (hi >>> 16) & 0xff,
+ (hi >>> 8) & 0xff,
+ hi & 0xff,
+ (lo >>> 24) & 0xff,
+ (lo >>> 16) & 0xff,
+ (lo >>> 8) & 0xff,
+ lo & 0xff
+ ];
+ };
+
+ return Long;
+});
+});
+
+var s2geometry = createCommonjsModule(function (module) {
+/// S2 Geometry functions
+// the regional scoreboard is based on a level 6 S2 Cell
+// - https://docs.google.com/presentation/d/1Hl4KapfAENAOf4gv-pSngKwvS_jwNVHRPZTTDzXXn6Q/view?pli=1#slide=id.i22
+// at the time of writing there's no actual API for the intel map to retrieve scoreboard data,
+// but it's still useful to plot the score cells on the intel map
+
+
+// the S2 geometry is based on projecting the earth sphere onto a cube, with some scaling of face coordinates to
+// keep things close to approximate equal area for adjacent cells
+// to convert a lat,lng into a cell id:
+// - convert lat,lng to x,y,z
+// - convert x,y,z into face,u,v
+// - u,v scaled to s,t with quadratic formula
+// - s,t converted to integer i,j offsets
+// - i,j converted to a position along a Hubbert space-filling curve
+// - combine face,position to get the cell id
+
+//NOTE: compared to the google S2 geometry library, we vary from their code in the following ways
+// - cell IDs: they combine face and the hilbert curve position into a single 64 bit number. this gives efficient space
+// and speed. javascript doesn't have appropriate data types, and speed is not cricical, so we use
+// as [face,[bitpair,bitpair,...]] instead
+// - i,j: they always use 30 bits, adjusting as needed. we use 0 to (1<<level)-1 instead
+// (so GetSizeIJ for a cell is always 1)
+
+(function (exports) {
+
+var S2 = exports.S2 = { L: {} };
+
+S2.L.LatLng = function (/*Number*/ rawLat, /*Number*/ rawLng, /*Boolean*/ noWrap) {
+ var lat = parseFloat(rawLat, 10);
+ var lng = parseFloat(rawLng, 10);
+
+ if (isNaN(lat) || isNaN(lng)) {
+ throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
+ }
+
+ if (noWrap !== true) {
+ lat = Math.max(Math.min(lat, 90), -90); // clamp latitude into -90..90
+ lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180); // wrap longtitude into -180..180
+ }
+
+ return { lat: lat, lng: lng };
+};
+
+S2.L.LatLng.DEG_TO_RAD = Math.PI / 180;
+S2.L.LatLng.RAD_TO_DEG = 180 / Math.PI;
+
+/*
+S2.LatLngToXYZ = function(latLng) {
+ // http://stackoverflow.com/questions/8981943/lat-long-to-x-y-z-position-in-js-not-working
+ var lat = latLng.lat;
+ var lon = latLng.lng;
+ var DEG_TO_RAD = Math.PI / 180.0;
+
+ var phi = lat * DEG_TO_RAD;
+ var theta = lon * DEG_TO_RAD;
+
+ var cosLat = Math.cos(phi);
+ var sinLat = Math.sin(phi);
+ var cosLon = Math.cos(theta);
+ var sinLon = Math.sin(theta);
+ var rad = 500.0;
+
+ return [
+ rad * cosLat * cosLon
+ , rad * cosLat * sinLon
+ , rad * sinLat
+ ];
+};
+*/
+S2.LatLngToXYZ = function(latLng) {
+ var d2r = S2.L.LatLng.DEG_TO_RAD;
+
+ var phi = latLng.lat*d2r;
+ var theta = latLng.lng*d2r;
+
+ var cosphi = Math.cos(phi);
+
+ return [Math.cos(theta)*cosphi, Math.sin(theta)*cosphi, Math.sin(phi)];
+};
+
+S2.XYZToLatLng = function(xyz) {
+ var r2d = S2.L.LatLng.RAD_TO_DEG;
+
+ var lat = Math.atan2(xyz[2], Math.sqrt(xyz[0]*xyz[0]+xyz[1]*xyz[1]));
+ var lng = Math.atan2(xyz[1], xyz[0]);
+
+ return S2.L.LatLng(lat*r2d, lng*r2d);
+};
+
+var largestAbsComponent = function(xyz) {
+ var temp = [Math.abs(xyz[0]), Math.abs(xyz[1]), Math.abs(xyz[2])];
+
+ if (temp[0] > temp[1]) {
+ if (temp[0] > temp[2]) {
+ return 0;
+ } else {
+ return 2;
+ }
+ } else {
+ if (temp[1] > temp[2]) {
+ return 1;
+ } else {
+ return 2;
+ }
+ }
+
+};
+
+var faceXYZToUV = function(face,xyz) {
+ var u,v;
+
+ switch (face) {
+ case 0: u = xyz[1]/xyz[0]; v = xyz[2]/xyz[0]; break;
+ case 1: u = -xyz[0]/xyz[1]; v = xyz[2]/xyz[1]; break;
+ case 2: u = -xyz[0]/xyz[2]; v = -xyz[1]/xyz[2]; break;
+ case 3: u = xyz[2]/xyz[0]; v = xyz[1]/xyz[0]; break;
+ case 4: u = xyz[2]/xyz[1]; v = -xyz[0]/xyz[1]; break;
+ case 5: u = -xyz[1]/xyz[2]; v = -xyz[0]/xyz[2]; break;
+ default: throw {error: 'Invalid face'};
+ }
+
+ return [u,v];
+};
+
+
+
+
+S2.XYZToFaceUV = function(xyz) {
+ var face = largestAbsComponent(xyz);
+
+ if (xyz[face] < 0) {
+ face += 3;
+ }
+
+ var uv = faceXYZToUV (face,xyz);
+
+ return [face, uv];
+};
+
+S2.FaceUVToXYZ = function(face,uv) {
+ var u = uv[0];
+ var v = uv[1];
+
+ switch (face) {
+ case 0: return [ 1, u, v];
+ case 1: return [-u, 1, v];
+ case 2: return [-u,-v, 1];
+ case 3: return [-1,-v,-u];
+ case 4: return [ v,-1,-u];
+ case 5: return [ v, u,-1];
+ default: throw {error: 'Invalid face'};
+ }
+};
+
+var singleSTtoUV = function(st) {
+ if (st >= 0.5) {
+ return (1/3.0) * (4*st*st - 1);
+ } else {
+ return (1/3.0) * (1 - (4*(1-st)*(1-st)));
+ }
+};
+
+S2.STToUV = function(st) {
+ return [singleSTtoUV(st[0]), singleSTtoUV(st[1])];
+};
+
+
+var singleUVtoST = function(uv) {
+ if (uv >= 0) {
+ return 0.5 * Math.sqrt (1 + 3*uv);
+ } else {
+ return 1 - 0.5 * Math.sqrt (1 - 3*uv);
+ }
+};
+S2.UVToST = function(uv) {
+ return [singleUVtoST(uv[0]), singleUVtoST(uv[1])];
+};
+
+
+S2.STToIJ = function(st,order) {
+ var maxSize = (1<<order);
+
+ var singleSTtoIJ = function(st) {
+ var ij = Math.floor(st * maxSize);
+ return Math.max(0, Math.min(maxSize-1, ij));
+ };
+
+ return [singleSTtoIJ(st[0]), singleSTtoIJ(st[1])];
+};
+
+
+S2.IJToST = function(ij,order,offsets) {
+ var maxSize = (1<<order);
+
+ return [
+ (ij[0]+offsets[0])/maxSize,
+ (ij[1]+offsets[1])/maxSize
+ ];
+};
+
+
+
+var rotateAndFlipQuadrant = function(n, point, rx, ry)
+{
+ if(ry == 0)
+ {
+ if(rx == 1){
+ point.x = n - 1 - point.x;
+ point.y = n - 1 - point.y;
+
+ }
+
+ var x = point.x;
+ point.x = point.y;
+ point.y = x;
+ }
+
+};
+
+
+
+
+
+// hilbert space-filling curve
+// based on http://blog.notdot.net/2009/11/Damn-Cool-Algorithms-Spatial-indexing-with-Quadtrees-and-Hilbert-Curves
+// note: rather then calculating the final integer hilbert position, we just return the list of quads
+// this ensures no precision issues whth large orders (S3 cell IDs use up to 30), and is more
+// convenient for pulling out the individual bits as needed later
+var pointToHilbertQuadList = function(x,y,order,face) {
+ var hilbertMap = {
+ 'a': [ [0,'d'], [1,'a'], [3,'b'], [2,'a'] ],
+ 'b': [ [2,'b'], [1,'b'], [3,'a'], [0,'c'] ],
+ 'c': [ [2,'c'], [3,'d'], [1,'c'], [0,'b'] ],
+ 'd': [ [0,'a'], [3,'c'], [1,'d'], [2,'d'] ]
+ };
+
+ if ('number' !== typeof face) {
+ console.warn(new Error("called pointToHilbertQuadList without face value, defaulting to '0'").stack);
+ }
+ var currentSquare = (face % 2) ? 'd' : 'a';
+ var positions = [];
+
+ for (var i=order-1; i>=0; i--) {
+
+ var mask = 1<<i;
+
+ var quad_x = x&mask ? 1 : 0;
+ var quad_y = y&mask ? 1 : 0;
+
+ var t = hilbertMap[currentSquare][quad_x*2+quad_y];
+
+ positions.push(t[0]);
+
+ currentSquare = t[1];
+ }
+
+ return positions;
+};
+
+// S2Cell class
+
+S2.S2Cell = function(){};
+
+S2.S2Cell.FromHilbertQuadKey = function(hilbertQuadkey) {
+ var parts = hilbertQuadkey.split('/');
+ var face = parseInt(parts[0]);
+ var position = parts[1];
+ var maxLevel = position.length;
+ var point = {
+ x : 0,
+ y: 0
+ };
+ var i;
+ var level;
+ var bit;
+ var rx, ry;
+ var val;
+
+ for(i = maxLevel - 1; i >= 0; i--) {
+
+ level = maxLevel - i;
+ bit = position[i];
+ rx = 0;
+ ry = 0;
+ if (bit === '1') {
+ ry = 1;
+ }
+ else if (bit === '2') {
+ rx = 1;
+ ry = 1;
+ }
+ else if (bit === '3') {
+ rx = 1;
+ }
+
+ val = Math.pow(2, level - 1);
+ rotateAndFlipQuadrant(val, point, rx, ry);
+
+ point.x += val * rx;
+ point.y += val * ry;
+
+ }
+
+ if (face % 2 === 1) {
+ var t = point.x;
+ point.x = point.y;
+ point.y = t;
+ }
+
+
+ return S2.S2Cell.FromFaceIJ(parseInt(face), [point.x, point.y], level);
+};
+
+//static method to construct
+S2.S2Cell.FromLatLng = function(latLng, level) {
+ if ((!latLng.lat && latLng.lat !== 0) || (!latLng.lng && latLng.lng !== 0)) {
+ throw new Error("Pass { lat: lat, lng: lng } to S2.S2Cell.FromLatLng");
+ }
+ var xyz = S2.LatLngToXYZ(latLng);
+
+ var faceuv = S2.XYZToFaceUV(xyz);
+ var st = S2.UVToST(faceuv[1]);
+
+ var ij = S2.STToIJ(st,level);
+
+ return S2.S2Cell.FromFaceIJ (faceuv[0], ij, level);
+};
+
+/*
+S2.faceIjLevelToXyz = function (face, ij, level) {
+ var st = S2.IJToST(ij, level, [0.5, 0.5]);
+ var uv = S2.STToUV(st);
+ var xyz = S2.FaceUVToXYZ(face, uv);
+
+ return S2.XYZToLatLng(xyz);
+ return xyz;
+};
+*/
+
+S2.S2Cell.FromFaceIJ = function(face,ij,level) {
+ var cell = new S2.S2Cell();
+ cell.face = face;
+ cell.ij = ij;
+ cell.level = level;
+
+ return cell;
+};
+
+
+S2.S2Cell.prototype.toString = function() {
+ return 'F'+this.face+'ij['+this.ij[0]+','+this.ij[1]+']@'+this.level;
+};
+
+S2.S2Cell.prototype.getLatLng = function() {
+ var st = S2.IJToST(this.ij,this.level, [0.5,0.5]);
+ var uv = S2.STToUV(st);
+ var xyz = S2.FaceUVToXYZ(this.face, uv);
+
+ return S2.XYZToLatLng(xyz);
+};
+
+S2.S2Cell.prototype.getCornerLatLngs = function() {
+ var result = [];
+ var offsets = [
+ [ 0.0, 0.0 ],
+ [ 0.0, 1.0 ],
+ [ 1.0, 1.0 ],
+ [ 1.0, 0.0 ]
+ ];
+
+ for (var i=0; i<4; i++) {
+ var st = S2.IJToST(this.ij, this.level, offsets[i]);
+ var uv = S2.STToUV(st);
+ var xyz = S2.FaceUVToXYZ(this.face, uv);
+
+ result.push ( S2.XYZToLatLng(xyz) );
+ }
+ return result;
+};
+
+
+S2.S2Cell.prototype.getFaceAndQuads = function () {
+ var quads = pointToHilbertQuadList(this.ij[0], this.ij[1], this.level, this.face);
+
+ return [this.face,quads];
+};
+S2.S2Cell.prototype.toHilbertQuadkey = function () {
+ var quads = pointToHilbertQuadList(this.ij[0], this.ij[1], this.level, this.face);
+
+ return this.face.toString(10) + '/' + quads.join('');
+};
+
+S2.latLngToNeighborKeys = S2.S2Cell.latLngToNeighborKeys = function (lat, lng, level) {
+ return S2.S2Cell.FromLatLng({ lat: lat, lng: lng }, level).getNeighbors().map(function (cell) {
+ return cell.toHilbertQuadkey();
+ });
+};
+S2.S2Cell.prototype.getNeighbors = function() {
+
+ var fromFaceIJWrap = function(face,ij,level) {
+ var maxSize = (1<<level);
+ if (ij[0]>=0 && ij[1]>=0 && ij[0]<maxSize && ij[1]<maxSize) {
+ // no wrapping out of bounds
+ return S2.S2Cell.FromFaceIJ(face,ij,level);
+ } else {
+ // the new i,j are out of range.
+ // with the assumption that they're only a little past the borders we can just take the points as
+ // just beyond the cube face, project to XYZ, then re-create FaceUV from the XYZ vector
+
+ var st = S2.IJToST(ij,level,[0.5,0.5]);
+ var uv = S2.STToUV(st);
+ var xyz = S2.FaceUVToXYZ(face,uv);
+ var faceuv = S2.XYZToFaceUV(xyz);
+ face = faceuv[0];
+ uv = faceuv[1];
+ st = S2.UVToST(uv);
+ ij = S2.STToIJ(st,level);
+ return S2.S2Cell.FromFaceIJ (face, ij, level);
+ }
+ };
+
+ var face = this.face;
+ var i = this.ij[0];
+ var j = this.ij[1];
+ var level = this.level;
+
+
+ return [
+ fromFaceIJWrap(face, [i-1,j], level),
+ fromFaceIJWrap(face, [i,j-1], level),
+ fromFaceIJWrap(face, [i+1,j], level),
+ fromFaceIJWrap(face, [i,j+1], level)
+ ];
+
+};
+
+//
+// Functional Style
+//
+S2.FACE_BITS = 3;
+S2.MAX_LEVEL = 30;
+S2.POS_BITS = (2 * S2.MAX_LEVEL) + 1; // 61 (60 bits of data, 1 bit lsb marker)
+
+S2.facePosLevelToId = S2.S2Cell.facePosLevelToId = S2.fromFacePosLevel = function (faceN, posS, levelN) {
+ var Long = exports.dcodeIO && exports.dcodeIO.Long || long;
+ var faceB;
+ var posB;
+ var bin;
+
+ if (!levelN) {
+ levelN = posS.length;
+ }
+ if (posS.length > levelN) {
+ posS = posS.substr(0, levelN);
+ }
+
+ // 3-bit face value
+ faceB = Long.fromString(faceN.toString(10), true, 10).toString(2);
+ while (faceB.length < S2.FACE_BITS) {
+ faceB = '0' + faceB;
+ }
+
+ // 60-bit position value
+ posB = Long.fromString(posS, true, 4).toString(2);
+ while (posB.length < (2 * levelN)) {
+ posB = '0' + posB;
+ }
+
+ bin = faceB + posB;
+ // 1-bit lsb marker
+ bin += '1';
+ // n-bit padding to 64-bits
+ while (bin.length < (S2.FACE_BITS + S2.POS_BITS)) {
+ bin += '0';
+ }
+
+ return Long.fromString(bin, true, 2).toString(10);
+};
+
+S2.keyToId = S2.S2Cell.keyToId
+= S2.toId = S2.toCellId = S2.fromKey
+= function (key) {
+ var parts = key.split('/');
+
+ return S2.fromFacePosLevel(parts[0], parts[1], parts[1].length);
+};
+
+S2.idToKey = S2.S2Cell.idToKey
+= S2.S2Cell.toKey = S2.toKey
+= S2.fromId = S2.fromCellId
+= S2.S2Cell.toHilbertQuadkey = S2.toHilbertQuadkey
+= function (idS) {
+ var Long = exports.dcodeIO && exports.dcodeIO.Long || long;
+ var bin = Long.fromString(idS, true, 10).toString(2);
+
+ while (bin.length < (S2.FACE_BITS + S2.POS_BITS)) {
+ bin = '0' + bin;
+ }
+
+ // MUST come AFTER binstr has been left-padded with '0's
+ var lsbIndex = bin.lastIndexOf('1');
+ // substr(start, len)
+ // substring(start, end) // includes start, does not include end
+ var faceB = bin.substring(0, 3);
+ // posB will always be a multiple of 2 (or it's invalid)
+ var posB = bin.substring(3, lsbIndex);
+ var levelN = posB.length / 2;
+
+ var faceS = Long.fromString(faceB, true, 2).toString(10);
+ var posS = Long.fromString(posB, true, 2).toString(4);
+
+ while (posS.length < levelN) {
+ posS = '0' + posS;
+ }
+
+ return faceS + '/' + posS;
+};
+
+S2.keyToLatLng = S2.S2Cell.keyToLatLng = function (key) {
+ var cell2 = S2.S2Cell.FromHilbertQuadKey(key);
+ return cell2.getLatLng();
+};
+
+S2.idToLatLng = S2.S2Cell.idToLatLng = function (id) {
+ var key = S2.idToKey(id);
+ return S2.keyToLatLng(key);
+};
+
+S2.S2Cell.latLngToKey = S2.latLngToKey
+= S2.latLngToQuadkey = function (lat, lng, level) {
+ if (isNaN(level) || level < 1 || level > 30) {
+ throw new Error("'level' is not a number between 1 and 30 (but it should be)");
+ }
+ // TODO
+ //
+ // S2.idToLatLng(id)
+ // S2.keyToLatLng(key)
+ // S2.nextFace(key) // prevent wrapping on nextKey
+ // S2.prevFace(key) // prevent wrapping on prevKey
+ //
+ // .toKeyArray(id) // face,quadtree
+ // .toKey(id) // hilbert
+ // .toPoint(id) // ij
+ // .toId(key) // uint64 (as string)
+ // .toLong(key) // long.js
+ // .toLatLng(id) // object? or array?, or string (with comma)?
+ //
+ // maybe S2.HQ.x, S2.GPS.x, S2.CI.x?
+ return S2.S2Cell.FromLatLng({ lat: lat, lng: lng }, level).toHilbertQuadkey();
+};
+
+S2.stepKey = function (key, num) {
+ var Long = exports.dcodeIO && exports.dcodeIO.Long || long;
+ var parts = key.split('/');
+
+ var faceS = parts[0];
+ var posS = parts[1];
+ var level = parts[1].length;
+
+ var posL = Long.fromString(posS, true, 4);
+ // TODO handle wrapping (0 === pos + 1)
+ // (only on the 12 edges of the globe)
+ var otherL;
+ if (num > 0) {
+ otherL = posL.add(Math.abs(num));
+ }
+ else if (num < 0) {
+ otherL = posL.subtract(Math.abs(num));
+ }
+ var otherS = otherL.toString(4);
+
+ if ('0' === otherS) {
+ console.warning(new Error("face/position wrapping is not yet supported"));
+ }
+
+ while (otherS.length < level) {
+ otherS = '0' + otherS;
+ }
+
+ return faceS + '/' + otherS;
+};
+
+S2.S2Cell.prevKey = S2.prevKey = function (key) {
+ return S2.stepKey(key, -1);
+};
+
+S2.S2Cell.nextKey = S2.nextKey = function (key) {
+ return S2.stepKey(key, 1);
+};
+
+})(module.exports );
+});
+
+/**
+ * @class S2GeometryProvider
+ *
+ * @classdesc Geometry provider based on S2 cells.
+ *
+ * @example
+ * ```js
+ * class MyDataProvider extends DataProviderBase {
+ * ...
+ * }
+ *
+ * const geometryProvider = new S2GeometryProvider();
+ * const dataProvider = new MyDataProvider(geometryProvider);
+ * ```
+ */
+class S2GeometryProvider extends GeometryProviderBase {
+ /**
+ * Create a new S2 geometry provider instance.
+ */
+ constructor(_level = 17) {
+ super();
+ this._level = _level;
+ }
+ /** @inheritdoc */
+ bboxToCellIds(sw, ne) {
+ return this._approxBboxToCellIds(sw, ne);
+ }
+ /** @inheritdoc */
+ getAdjacent(cellId) {
+ const k = s2geometry.S2.idToKey(cellId);
+ const position = k.split('/')[1];
+ const level = position.length;
+ const [a0, a1, a2, a3] = this._getNeighbors(k, level);
+ const existing = [k, a0, a1, a2, a3];
+ const others = Array
+ .from(new Set([
+ ...this._getNeighbors(a0, level),
+ ...this._getNeighbors(a1, level),
+ ...this._getNeighbors(a2, level),
+ ...this._getNeighbors(a3, level),
+ ].filter((o) => {
+ return !existing.includes(o);
+ })));
+ const adjacent = [a0, a1, a2, a3];
+ for (const other of others) {
+ let count = 0;
+ for (const n of this._getNeighbors(other, level)) {
+ if (existing.includes(n)) {
+ count++;
+ }
+ }
+ if (count === 2) {
+ adjacent.push(other);
+ }
+ }
+ return adjacent.map((a) => s2geometry.S2.keyToId(a));
+ }
+ /** @inheritdoc */
+ getVertices(cellId) {
+ const key = s2geometry.S2.idToKey(cellId);
+ const cell = s2geometry.S2.S2Cell.FromHilbertQuadKey(key);
+ return cell
+ .getCornerLatLngs()
+ .map((c) => {
+ return { lat: c.lat, lng: c.lng };
+ });
+ }
+ /** @inheritdoc */
+ lngLatToCellId(lngLat) {
+ return this._lngLatToId(lngLat, this._level);
+ }
+ _getNeighbors(s2key, level) {
+ const latlng = s2geometry.S2.keyToLatLng(s2key);
+ const neighbors = s2geometry.S2.latLngToNeighborKeys(latlng.lat, latlng.lng, level);
+ return neighbors;
+ }
+ _lngLatToId(lngLat, level) {
+ const s2key = s2geometry.S2.latLngToKey(lngLat.lat, lngLat.lng, level);
+ return s2geometry.S2.keyToId(s2key);
+ }
+}
+
+function convertCameraType(graphCameraType) {
+ switch (graphCameraType) {
+ case "equirectangular":
+ case "spherical":
+ return "spherical";
+ case "fisheye":
+ return "fisheye";
+ default:
+ return "perspective";
+ }
+}
+class GraphConverter {
+ clusterReconstruction(source) {
+ const id = null;
+ const points = source.points;
+ const normalize = 1 / 255;
+ for (const pointId in points) {
+ if (!points.hasOwnProperty(pointId)) {
+ continue;
+ }
+ const color = points[pointId].color;
+ color[0] *= normalize;
+ color[1] *= normalize;
+ color[2] *= normalize;
+ }
+ const lla = source.reference_lla;
+ const reference = {
+ alt: lla.altitude,
+ lat: lla.latitude,
+ lng: lla.longitude,
+ };
+ return {
+ id,
+ points,
+ reference,
+ };
+ }
+ coreImage(source) {
+ const geometry = this._geometry(source.geometry);
+ const computedGeometry = this._geometry(source.computed_geometry);
+ const sequence = { id: source.sequence };
+ const id = source.id;
+ return {
+ computed_geometry: computedGeometry,
+ geometry,
+ id,
+ sequence,
+ };
+ }
+ spatialImage(source) {
+ var _a, _b, _c, _d;
+ source.camera_type = convertCameraType(source.camera_type);
+ source.merge_id = source.merge_cc ? source.merge_cc.toString() : null;
+ source.private = null;
+ const thumbUrl = source.camera_type === 'spherical' ?
+ source.thumb_2048_url : source.thumb_1024_url;
+ source.thumb = (_a = source.thumb) !== null && _a !== void 0 ? _a : { id: null, url: thumbUrl };
+ source.cluster = (_b = source.sfm_cluster) !== null && _b !== void 0 ? _b : { id: null, url: null };
+ source.creator = { id: null, username: null };
+ source.owner = (_c = source.owner) !== null && _c !== void 0 ? _c : { id: null };
+ source.mesh = (_d = source.mesh) !== null && _d !== void 0 ? _d : { id: null, url: null };
+ return source;
+ }
+ _geometry(geometry) {
+ const coords = geometry === null || geometry === void 0 ? void 0 : geometry.coordinates;
+ const lngLat = coords ?
+ {
+ lat: coords[1],
+ lng: coords[0],
+ } : null;
+ return lngLat;
+ }
+}
+
+class GraphQueryCreator {
+ constructor() {
+ this.imagesPath = 'images';
+ this.sequencePath = 'image_ids';
+ this._imageTilesPath = 'tiles';
+ this.coreFields = ['computed_geometry', 'geometry', 'sequence'];
+ this.idFields = ['id'];
+ this.spatialFields = [
+ 'altitude',
+ 'atomic_scale',
+ 'camera_parameters',
+ 'camera_type',
+ 'captured_at',
+ 'compass_angle',
+ 'computed_altitude',
+ 'computed_compass_angle',
+ 'computed_rotation',
+ 'exif_orientation',
+ 'height',
+ 'merge_cc',
+ 'mesh',
+ 'quality_score',
+ 'sfm_cluster',
+ 'thumb_1024_url',
+ 'thumb_2048_url',
+ 'width',
+ ];
+ this.imageTileFields = ['url', 'z', 'x', 'y'];
+ }
+ images(imageIds, fields) {
+ return `image_ids=${imageIds.join(',')}&fields=${fields.join(',')}`;
+ }
+ imagesS2(cellId, fields) {
+ return `s2=${cellId}&fields=${fields.join(',')}`;
+ }
+ imageTiles(z, fields) {
+ return `z=${z}&fields=${fields.join(',')}`;
+ }
+ imageTilesPath(imageId) {
+ return `${imageId}/${this._imageTilesPath}`;
+ }
+ sequence(sequenceId) {
+ return `sequence_id=${sequenceId}`;
+ }
+}
+
+class GraphDataProvider extends DataProviderBase {
+ constructor(options, geometry, converter, queryCreator) {
+ var _a;
+ super(geometry !== null && geometry !== void 0 ? geometry : new S2GeometryProvider());
+ this._convert = converter !== null && converter !== void 0 ? converter : new GraphConverter();
+ this._query = queryCreator !== null && queryCreator !== void 0 ? queryCreator : new GraphQueryCreator();
+ this._method = 'GET';
+ const opts = options !== null && options !== void 0 ? options : {};
+ this._endpoint = (_a = opts.endpoint) !== null && _a !== void 0 ? _a : "https://graph.mapillary.com";
+ this._accessToken = opts.accessToken;
+ }
+ getCluster(url, abort) {
+ return fetchArrayBuffer(url, abort)
+ .then((buffer) => {
+ const reconstructions = decompress(buffer);
+ if (reconstructions.length < 1) {
+ throw new Error('Cluster reconstruction empty');
+ }
+ return this._convert
+ .clusterReconstruction(reconstructions[0]);
+ });
+ }
+ getCoreImages(cellId) {
+ const fields = [
+ ...this._query.idFields,
+ ...this._query.coreFields,
+ ];
+ const query = this._query.imagesS2(cellId, fields);
+ const url = new URL(this._query.imagesPath, this._endpoint).href;
+ return this
+ ._fetchGraphContract(query, url)
+ .then(r => {
+ const result = {
+ cell_id: cellId,
+ images: [],
+ };
+ const items = r.data;
+ for (const item of items) {
+ const coreImage = this._convert.coreImage(item);
+ result.images.push(coreImage);
+ }
+ return result;
+ });
+ }
+ getImageBuffer(url, abort) {
+ return fetchArrayBuffer(url, abort);
+ }
+ getImages(imageIds) {
+ const fields = [
+ ...this._query.idFields,
+ ...this._query.coreFields,
+ ...this._query.spatialFields,
+ ];
+ const query = this._query.images(imageIds, fields);
+ const url = new URL(this._query.imagesPath, this._endpoint).href;
+ return this
+ ._fetchGraphContract(query, url)
+ .then(r => {
+ const result = [];
+ const items = r.data;
+ for (const item of items) {
+ const coreImage = this._convert.coreImage(item);
+ const spatialImage = this._convert.spatialImage(item);
+ const image = Object.assign({}, spatialImage, coreImage);
+ const contract = {
+ node: image,
+ node_id: item.id,
+ };
+ result.push(contract);
+ }
+ return result;
+ });
+ }
+ getImageTiles(request) {
+ const fields = [
+ ...this._query.imageTileFields,
+ ];
+ const query = this._query.imageTiles(request.z, fields);
+ const url = new URL(this._query.imageTilesPath(request.imageId), this._endpoint).href;
+ return this
+ ._fetchGraphContract(query, url)
+ .then(r => {
+ const result = {
+ node: r.data,
+ node_id: request.imageId,
+ };
+ return result;
+ });
+ }
+ getMesh(url, abort) {
+ return fetchArrayBuffer(url, abort)
+ .then((buffer) => {
+ return readMeshPbf(buffer);
+ });
+ }
+ getSequence(sequenceId) {
+ const query = this._query.sequence(sequenceId);
+ const url = new URL(this._query.sequencePath, this._endpoint).href;
+ return this
+ ._fetchGraphContract(query, url)
+ .then(r => {
+ const result = {
+ id: sequenceId,
+ image_ids: r.data.map(item => item.id),
+ };
+ return result;
+ });
+ }
+ getSpatialImages(imageIds) {
+ const fields = [
+ ...this._query.idFields,
+ ...this._query.coreFields,
+ ...this._query.spatialFields,
+ ];
+ const query = this._query.images(imageIds, fields);
+ const url = new URL(this._query.imagesPath, this._endpoint).href;
+ return this
+ ._fetchGraphContract(query, url)
+ .then(r => {
+ const result = [];
+ const items = r.data;
+ for (const item of items) {
+ const spatialImage = this._convert.spatialImage(item);
+ const contract = {
+ node: spatialImage,
+ node_id: item.id,
+ };
+ result.push(contract);
+ }
+ return result;
+ });
+ }
+ setAccessToken(accessToken) {
+ this._accessToken = accessToken;
+ }
+ _createHeaders() {
+ const headers = [
+ { name: 'Accept', value: 'application/json' },
+ {
+ name: 'Content-Type',
+ value: 'application/x-www-form-urlencoded',
+ },
+ ];
+ if (this._accessToken) {
+ headers.push({
+ name: 'Authorization',
+ value: `OAuth ${this._accessToken}`,
+ });
+ }
+ return headers;
+ }
+ _fetchGraphContract(body, url) {
+ const method = this._method;
+ const headers = this._createHeaders();
+ const query = `${url}?${body}`;
+ return xhrFetch(query, method, "json", headers, null, null)
+ .catch((error) => {
+ const message = this._makeErrorMessage(error);
+ throw new MapillaryError(message);
+ });
+ }
+ _makeErrorMessage(graphError) {
+ const error = graphError.error;
+ const message = error ?
+ `${error.code} (${error.type}, ${error.fbtrace_id}): ${error.message}` :
+ "Failed to fetch data";
+ return message;
+ }
+}
+
+/**
+ * @class Marker
+ *
+ * @classdesc Represents an abstract marker class that should be extended
+ * by marker implementations used in the marker component.
+ */
+class Marker {
+ constructor(id, lngLat) {
+ this._id = id;
+ this._lngLat = lngLat;
+ }
+ /**
+ * Get id.
+ * @returns {string} The id of the marker.
+ */
+ get id() {
+ return this._id;
+ }
+ /**
+ * Get geometry.
+ *
+ * @ignore
+ */
+ get geometry() {
+ return this._geometry;
+ }
+ /**
+ * Get lngLat.
+ * @returns {LngLat} The geographic coordinates of the marker.
+ */
+ get lngLat() {
+ return this._lngLat;
+ }
+ /** @ignore */
+ createGeometry(position) {
+ if (!!this._geometry) {
+ return;
+ }
+ this._createGeometry(position);
+ // update matrix world if raycasting occurs before first render
+ this._geometry.updateMatrixWorld(true);
+ }
+ /** @ignore */
+ disposeGeometry() {
+ if (!this._geometry) {
+ return;
+ }
+ this._disposeGeometry();
+ this._geometry = undefined;
+ }
+ /** @ignore */
+ getInteractiveObjects() {
+ if (!this._geometry) {
+ return [];
+ }
+ return this._getInteractiveObjects();
+ }
+ /** @ignore */
+ lerpAltitude(alt, alpha) {
+ if (!this._geometry) {
+ return;
+ }
+ this._geometry.position.z =
+ (1 - alpha) * this._geometry.position.z + alpha * alt;
+ }
+ /** @ignore */
+ updatePosition(position, lngLat) {
+ if (!!lngLat) {
+ this._lngLat.lat = lngLat.lat;
+ this._lngLat.lng = lngLat.lng;
+ }
+ if (!this._geometry) {
+ return;
+ }
+ this._geometry.position.fromArray(position);
+ this._geometry.updateMatrixWorld(true);
+ }
+}
+
+/**
+ * @class CircleMarker
+ *
+ * @classdesc Non-interactive marker with a flat circle shape. The circle
+ * marker can not be configured to be interactive.
+ *
+ * Circle marker properties can not be updated after creation.
+ *
+ * To create and add one `CircleMarker` with default configuration
+ * and one with configuration use
+ *
+ * @example
+ * ```js
+ * var defaultMarker = new CircleMarker(
+ * "id-1",
+ * { lat: 0, lng: 0, });
+ *
+ * var configuredMarker = new CircleMarker(
+ * "id-2",
+ * { lat: 0, lng: 0, },
+ * {
+ * color: "#0ff",
+ * opacity: 0.3,
+ * radius: 0.7,
+ * });
+ *
+ * markerComponent.add([defaultMarker, configuredMarker]);
+ * ```
+ */
+class CircleMarker extends Marker {
+ constructor(id, lngLat, options) {
+ super(id, lngLat);
+ options = !!options ? options : {};
+ this._color = options.color != null ? options.color : 0xffffff;
+ this._opacity = options.opacity != null ? options.opacity : 0.4;
+ this._radius = options.radius != null ? options.radius : 1;
+ }
+ _createGeometry(position) {
+ const circle = new Mesh(new CircleGeometry(this._radius, 16), new MeshBasicMaterial({
+ color: this._color,
+ opacity: this._opacity,
+ transparent: true,
+ }));
+ circle.up.fromArray([0, 0, 1]);
+ circle.renderOrder = -1;
+ const group = new Object3D();
+ group.add(circle);
+ group.position.fromArray(position);
+ this._geometry = group;
+ }
+ _disposeGeometry() {
+ for (let mesh of this._geometry.children) {
+ mesh.geometry.dispose();
+ mesh.material.dispose();
+ }
+ }
+ _getInteractiveObjects() {
+ return [];
+ }
+}
+
+/**
+ * @class SimpleMarker
+ *
+ * @classdesc Interactive marker with ice cream shape. The sphere
+ * inside the ice cream can be configured to be interactive.
+ *
+ * Simple marker properties can not be updated after creation.
+ *
+ * To create and add one `SimpleMarker` with default configuration
+ * (non-interactive) and one interactive with configuration use
+ *
+ * @example
+ * ```js
+ * var defaultMarker = new SimpleMarker(
+ * "id-1",
+ * { lat: 0, lng: 0, });
+ *
+ * var interactiveMarker = new SimpleMarker(
+ * "id-2",
+ * { lat: 0, lng: 0, },
+ * {
+ * ballColor: "#00f",
+ * ballOpacity: 0.5,
+ * color: "#00f",
+ * interactive: true,
+ * opacity: 0.3,
+ * radius: 0.7,
+ * });
+ *
+ * markerComponent.add([defaultMarker, interactiveMarker]);
+ * ```
+ */
+class SimpleMarker extends Marker {
+ constructor(id, lngLat, options) {
+ super(id, lngLat);
+ options = !!options ? options : {};
+ this._ballColor = options.ballColor != null ? options.ballColor : 0xff0000;
+ this._ballOpacity = options.ballOpacity != null ? options.ballOpacity : 0.8;
+ this._circleToRayAngle = 2;
+ this._color = options.color != null ? options.color : 0xff0000;
+ this._interactive = !!options.interactive;
+ this._opacity = options.opacity != null ? options.opacity : 0.4;
+ this._radius = options.radius != null ? options.radius : 1;
+ }
+ _createGeometry(position) {
+ const radius = this._radius;
+ const height = this._markerHeight(radius);
+ const markerMaterial = new MeshBasicMaterial({
+ color: this._color,
+ opacity: this._opacity,
+ transparent: true,
+ depthWrite: false,
+ });
+ const marker = new Mesh(this._createMarkerGeometry(radius, 8, 8), markerMaterial);
+ const interactive = new Mesh(new SphereGeometry(radius / 2, 8, 8), new MeshBasicMaterial({
+ color: this._ballColor,
+ opacity: this._ballOpacity,
+ transparent: true,
+ }));
+ interactive.position.z = height;
+ interactive.renderOrder = 1;
+ const group = new Object3D();
+ group.add(interactive);
+ group.add(marker);
+ group.position.fromArray(position);
+ this._geometry = group;
+ }
+ _disposeGeometry() {
+ for (const mesh of this._geometry.children) {
+ mesh.geometry.dispose();
+ mesh.material.dispose();
+ }
+ }
+ _getInteractiveObjects() {
+ return this._interactive ? [this._geometry.children[0]] : [];
+ }
+ _markerHeight(radius) {
+ const t = Math.tan(Math.PI - this._circleToRayAngle);
+ return radius * Math.sqrt(1 + t * t);
+ }
+ _createMarkerGeometry(radius, widthSegments, heightSegments) {
+ const height = this._markerHeight(radius);
+ const circleToRayAngle = this._circleToRayAngle;
+ const indexRows = [];
+ const positions = new Float32Array(3 * (widthSegments + 1) * (heightSegments + 1));
+ let positionIndex = 0;
+ for (let y = 0; y <= heightSegments; ++y) {
+ const indexRow = [];
+ for (let x = 0; x <= widthSegments; ++x) {
+ const u = x / widthSegments * Math.PI * 2;
+ const v = y / heightSegments * Math.PI;
+ let r = radius;
+ if (v > circleToRayAngle) {
+ const t = Math.tan(v - circleToRayAngle);
+ r = radius * Math.sqrt(1 + t * t);
+ }
+ const arrayIndex = 3 * positionIndex;
+ const sinv = Math.sin(v);
+ positions[arrayIndex + 0] = r * Math.cos(u) * sinv;
+ positions[arrayIndex + 1] = r * Math.sin(u) * sinv;
+ positions[arrayIndex + 2] = r * Math.cos(v) + height;
+ indexRow.push(positionIndex++);
+ }
+ indexRows.push(indexRow);
+ }
+ const indices = new Uint16Array(6 * widthSegments * heightSegments);
+ let index = 0;
+ for (let y = 0; y < heightSegments; ++y) {
+ for (let x = 0; x < widthSegments; ++x) {
+ const pi1 = indexRows[y][x + 1];
+ const pi2 = indexRows[y][x];
+ const pi3 = indexRows[y + 1][x];
+ const pi4 = indexRows[y + 1][x + 1];
+ indices[index++] = pi1;
+ indices[index++] = pi2;
+ indices[index++] = pi4;
+ indices[index++] = pi2;
+ indices[index++] = pi3;
+ indices[index++] = pi4;
+ }
+ }
+ const geometry = new BufferGeometry();
+ const positionAttribute = new BufferAttribute(positions, 3);
+ geometry.setAttribute("position", positionAttribute);
+ geometry.setIndex(new BufferAttribute(indices, 1));
+ return geometry;
+ }
+}
+
+/**
+ * @class Popup
+ *
+ * @classdesc Popup instance for rendering custom HTML content
+ * on top of images. Popups are based on 2D basic image coordinates
+ * (see the {@link Viewer} class documentation for more information about coordinate
+ * systems) and a certain popup is therefore only relevant to a single image.
+ * Popups related to a certain image should be removed when moving
+ * to another image.
+ *
+ * A popup must have both its content and its point or rect set to be
+ * rendered. Popup options can not be updated after creation but the
+ * basic point or rect as well as its content can be changed by calling
+ * the appropriate methods.
+ *
+ * To create and add one `Popup` with default configuration
+ * (tooltip visuals and automatic float) and one with specific options
+ * use
+ *
+ * @example
+ * ```js
+ * var defaultSpan = document.createElement('span');
+ * defaultSpan.innerHTML = 'hello default';
+ *
+ * var defaultPopup = new Popup();
+ * defaultPopup.setDOMContent(defaultSpan);
+ * defaultPopup.setBasicPoint([0.3, 0.3]);
+ *
+ * var cleanSpan = document.createElement('span');
+ * cleanSpan.innerHTML = 'hello clean';
+ *
+ * var cleanPopup = new Popup({
+ * clean: true,
+ * float: Alignment.Top,
+ * offset: 10,
+ * opacity: 0.7,
+ * });
+ *
+ * cleanPopup.setDOMContent(cleanSpan);
+ * cleanPopup.setBasicPoint([0.6, 0.6]);
+ *
+ * popupComponent.add([defaultPopup, cleanPopup]);
+ * ```
+ *
+ * @description Implementation of API methods and API documentation inspired
+ * by/used from https://github.com/mapbox/mapbox-gl-js/blob/v0.38.0/src/ui/popup.js
+ */
+class Popup {
+ constructor(options, viewportCoords, dom) {
+ this._options = {};
+ options = !!options ? options : {};
+ this._options.capturePointer = options.capturePointer === false ?
+ options.capturePointer : true;
+ this._options.clean = options.clean;
+ this._options.float = options.float;
+ this._options.offset = options.offset;
+ this._options.opacity = options.opacity;
+ this._options.position = options.position;
+ this._dom = !!dom ? dom : new DOM();
+ this._viewportCoords = !!viewportCoords ? viewportCoords : new ViewportCoords();
+ this._notifyChanged$ = new Subject();
+ }
+ /**
+ * @description Internal observable used by the component to
+ * render the popup when its position or content has changed.
+ * @ignore
+ */
+ get changed$() {
+ return this._notifyChanged$;
+ }
+ /**
+ * @description Internal method used by the component to
+ * remove all references to the popup.
+ * @ignore
+ */
+ remove() {
+ if (this._content && this._content.parentNode) {
+ this._content.parentNode.removeChild(this._content);
+ }
+ if (this._container) {
+ this._container.parentNode.removeChild(this._container);
+ delete this._container;
+ }
+ if (this._parentContainer) {
+ delete this._parentContainer;
+ }
+ }
+ /**
+ * Sets a 2D basic image coordinates point to the popup's anchor, and
+ * moves the popup to it.
+ *
+ * @description Overwrites any previously set point or rect.
+ *
+ * @param {Array<number>} basicPoint - Point in 2D basic image coordinates.
+ *
+ * @example
+ * ```js
+ * var popup = new Popup();
+ * popup.setText('hello image');
+ * popup.setBasicPoint([0.3, 0.3]);
+ *
+ * popupComponent.add([popup]);
+ * ```
+ */
+ setBasicPoint(basicPoint) {
+ this._point = basicPoint.slice();
+ this._rect = null;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Sets a 2D basic image coordinates rect to the popup's anchor, and
+ * moves the popup to it.
+ *
+ * @description Overwrites any previously set point or rect.
+ *
+ * @param {Array<number>} basicRect - Rect in 2D basic image
+ * coordinates ([topLeftX, topLeftY, bottomRightX, bottomRightY]) .
+ *
+ * @example
+ * ```js
+ * var popup = new Popup();
+ * popup.setText('hello image');
+ * popup.setBasicRect([0.3, 0.3, 0.5, 0.6]);
+ *
+ * popupComponent.add([popup]);
+ * ```
+ */
+ setBasicRect(basicRect) {
+ this._rect = basicRect.slice();
+ this._point = null;
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Sets the popup's content to the element provided as a DOM node.
+ *
+ * @param {Node} htmlNode - A DOM node to be used as content for the popup.
+ *
+ * @example
+ * ```js
+ * var div = document.createElement('div');
+ * div.innerHTML = 'hello image';
+ *
+ * var popup = new Popup();
+ * popup.setDOMContent(div);
+ * popup.setBasicPoint([0.3, 0.3]);
+ *
+ * popupComponent.add([popup]);
+ * ```
+ */
+ setDOMContent(htmlNode) {
+ if (this._content && this._content.parentNode) {
+ this._content.parentNode.removeChild(this._content);
+ }
+ const className = "mapillary-popup-content" +
+ (this._options.clean === true ? "-clean" : "") +
+ (this._options.capturePointer === true ? " mapillary-popup-capture-pointer" : "");
+ this._content = this._dom.createElement("div", className, this._container);
+ this._content.appendChild(htmlNode);
+ this._notifyChanged$.next(this);
+ }
+ /**
+ * Sets the popup's content to the HTML provided as a string.
+ *
+ * @description This method does not perform HTML filtering or sanitization,
+ * and must be used only with trusted content. Consider
+ * {@link Popup.setText} if the
+ * content is an untrusted text string.
+ *
+ * @param {string} html - A string representing HTML content for the popup.
+ *
+ * @example
+ * ```js
+ * var popup = new Popup();
+ * popup.setHTML('<div>hello image</div>');
+ * popup.setBasicPoint([0.3, 0.3]);
+ *
+ * popupComponent.add([popup]);
+ * ```
+ */
+ setHTML(html) {
+ const frag = this._dom.document.createDocumentFragment();
+ const temp = this._dom.createElement("body");
+ let child;
+ temp.innerHTML = html;
+ while (true) {
+ child = temp.firstChild;
+ if (!child) {
+ break;
+ }
+ frag.appendChild(child);
+ }
+ this.setDOMContent(frag);
+ }
+ /**
+ * Sets the popup's content to a string of text.
+ *
+ * @description This function creates a Text node in the DOM, so it cannot insert raw HTML.
+ * Use this method for security against XSS if the popup content is user-provided.
+ *
+ * @param {string} text - Textual content for the popup.
+ *
+ * @example
+ * ```js
+ * var popup = new Popup();
+ * popup.setText('hello image');
+ * popup.setBasicPoint([0.3, 0.3]);
+ *
+ * popupComponent.add([popup]);
+ * ```
+ */
+ setText(text) {
+ this.setDOMContent(this._dom.document.createTextNode(text));
+ }
+ /**
+ * @description Internal method for attaching the popup to
+ * its parent container so that it is rendered in the DOM tree.
+ * @ignore
+ */
+ setParentContainer(parentContainer) {
+ this._parentContainer = parentContainer;
+ }
+ /**
+ * @description Internal method for updating the rendered
+ * position of the popup called by the popup component.
+ * @ignore
+ */
+ update(renderCamera, size, transform) {
+ if (!this._parentContainer || !this._content) {
+ return;
+ }
+ if (!this._point && !this._rect) {
+ return;
+ }
+ if (!this._container) {
+ this._container = this._dom.createElement("div", "mapillary-popup", this._parentContainer);
+ const showTip = this._options.clean !== true &&
+ this._options.float !== Alignment.Center;
+ if (showTip) {
+ const tipClassName = "mapillary-popup-tip" +
+ (this._options.capturePointer === true ? " mapillary-popup-capture-pointer" : "");
+ this._tip = this._dom.createElement("div", tipClassName, this._container);
+ this._dom.createElement("div", "mapillary-popup-tip-inner", this._tip);
+ }
+ this._container.appendChild(this._content);
+ this._parentContainer.appendChild(this._container);
+ if (this._options.opacity != null) {
+ this._container.style.opacity = this._options.opacity.toString();
+ }
+ }
+ let pointPixel = null;
+ let position = this._alignmentToPopupAligment(this._options.position);
+ let float = this._alignmentToPopupAligment(this._options.float);
+ const classList = this._container.classList;
+ if (this._point != null) {
+ pointPixel =
+ this._viewportCoords.basicToCanvasSafe(this._point[0], this._point[1], { offsetHeight: size.height, offsetWidth: size.width }, transform, renderCamera.perspective);
+ }
+ else {
+ const alignments = ["center", "top", "bottom", "left", "right", "top-left", "top-right", "bottom-left", "bottom-right"];
+ let appliedPosition = null;
+ for (const alignment of alignments) {
+ if (classList.contains(`mapillary-popup-float-${alignment}`)) {
+ appliedPosition = alignment;
+ break;
+ }
+ }
+ [pointPixel, position] = this._rectToPixel(this._rect, position, appliedPosition, renderCamera, size, transform);
+ if (!float) {
+ float = position;
+ }
+ }
+ if (pointPixel == null) {
+ this._container.style.display = "none";
+ return;
+ }
+ this._container.style.display = "";
+ if (!float) {
+ const width = this._container.offsetWidth;
+ const height = this._container.offsetHeight;
+ const floats = this._pixelToFloats(pointPixel, size, width, height);
+ float = floats.length === 0 ? "top" : floats.join("-");
+ }
+ const offset = this._normalizeOffset(this._options.offset);
+ pointPixel = [pointPixel[0] + offset[float][0], pointPixel[1] + offset[float][1]];
+ pointPixel = [Math.round(pointPixel[0]), Math.round(pointPixel[1])];
+ const floatTranslate = {
+ "bottom": "translate(-50%,0)",
+ "bottom-left": "translate(-100%,0)",
+ "bottom-right": "translate(0,0)",
+ "center": "translate(-50%,-50%)",
+ "left": "translate(-100%,-50%)",
+ "right": "translate(0,-50%)",
+ "top": "translate(-50%,-100%)",
+ "top-left": "translate(-100%,-100%)",
+ "top-right": "translate(0,-100%)",
+ };
+ for (const key in floatTranslate) {
+ if (!floatTranslate.hasOwnProperty(key)) {
+ continue;
+ }
+ classList.remove(`mapillary-popup-float-${key}`);
+ }
+ classList.add(`mapillary-popup-float-${float}`);
+ this._container.style.transform = `${floatTranslate[float]} translate(${pointPixel[0]}px,${pointPixel[1]}px)`;
+ }
+ _rectToPixel(rect, position, appliedPosition, renderCamera, size, transform) {
+ if (!position) {
+ const width = this._container.offsetWidth;
+ const height = this._container.offsetHeight;
+ const floatOffsets = {
+ "bottom": [0, height / 2],
+ "bottom-left": [-width / 2, height / 2],
+ "bottom-right": [width / 2, height / 2],
+ "left": [-width / 2, 0],
+ "right": [width / 2, 0],
+ "top": [0, -height / 2],
+ "top-left": [-width / 2, -height / 2],
+ "top-right": [width / 2, -height / 2],
+ };
+ const automaticPositions = ["top", "bottom", "left", "right"];
+ let largestVisibleArea = [0, null, null];
+ for (const automaticPosition of automaticPositions) {
+ const autoPointBasic = this._pointFromRectPosition(rect, automaticPosition);
+ const autoPointPixel = this._viewportCoords.basicToCanvasSafe(autoPointBasic[0], autoPointBasic[1], { offsetHeight: size.height, offsetWidth: size.width }, transform, renderCamera.perspective);
+ if (autoPointPixel == null) {
+ continue;
+ }
+ const floatOffset = floatOffsets[automaticPosition];
+ const offsetedPosition = [autoPointPixel[0] + floatOffset[0], autoPointPixel[1] + floatOffset[1]];
+ const staticCoeff = appliedPosition != null && appliedPosition === automaticPosition ? 1 : 0.7;
+ const floats = this._pixelToFloats(offsetedPosition, size, width / staticCoeff, height / (2 * staticCoeff));
+ if (floats.length === 0 &&
+ autoPointPixel[0] > 0 &&
+ autoPointPixel[0] < size.width &&
+ autoPointPixel[1] > 0 &&
+ autoPointPixel[1] < size.height) {
+ return [autoPointPixel, automaticPosition];
+ }
+ const minX = Math.max(offsetedPosition[0] - width / 2, 0);
+ const maxX = Math.min(offsetedPosition[0] + width / 2, size.width);
+ const minY = Math.max(offsetedPosition[1] - height / 2, 0);
+ const maxY = Math.min(offsetedPosition[1] + height / 2, size.height);
+ const visibleX = Math.max(0, maxX - minX);
+ const visibleY = Math.max(0, maxY - minY);
+ const visibleArea = staticCoeff * visibleX * visibleY;
+ if (visibleArea > largestVisibleArea[0]) {
+ largestVisibleArea[0] = visibleArea;
+ largestVisibleArea[1] = autoPointPixel;
+ largestVisibleArea[2] = automaticPosition;
+ }
+ }
+ if (largestVisibleArea[0] > 0) {
+ return [largestVisibleArea[1], largestVisibleArea[2]];
+ }
+ }
+ const pointBasic = this._pointFromRectPosition(rect, position);
+ const pointPixel = this._viewportCoords.basicToCanvasSafe(pointBasic[0], pointBasic[1], { offsetHeight: size.height, offsetWidth: size.width }, transform, renderCamera.perspective);
+ return [pointPixel, position != null ? position : "top"];
+ }
+ _alignmentToPopupAligment(float) {
+ switch (float) {
+ case Alignment.Bottom:
+ return "bottom";
+ case Alignment.BottomLeft:
+ return "bottom-left";
+ case Alignment.BottomRight:
+ return "bottom-right";
+ case Alignment.Center:
+ return "center";
+ case Alignment.Left:
+ return "left";
+ case Alignment.Right:
+ return "right";
+ case Alignment.Top:
+ return "top";
+ case Alignment.TopLeft:
+ return "top-left";
+ case Alignment.TopRight:
+ return "top-right";
+ default:
+ return null;
+ }
+ }
+ _normalizeOffset(offset) {
+ if (offset == null) {
+ return this._normalizeOffset(0);
+ }
+ if (typeof offset === "number") {
+ // input specifies a radius
+ const sideOffset = offset;
+ const sign = sideOffset >= 0 ? 1 : -1;
+ const cornerOffset = sign * Math.round(Math.sqrt(0.5 * Math.pow(sideOffset, 2)));
+ return {
+ "bottom": [0, sideOffset],
+ "bottom-left": [-cornerOffset, cornerOffset],
+ "bottom-right": [cornerOffset, cornerOffset],
+ "center": [0, 0],
+ "left": [-sideOffset, 0],
+ "right": [sideOffset, 0],
+ "top": [0, -sideOffset],
+ "top-left": [-cornerOffset, -cornerOffset],
+ "top-right": [cornerOffset, -cornerOffset],
+ };
+ }
+ else {
+ // input specifes a value for each position
+ return {
+ "bottom": offset.bottom || [0, 0],
+ "bottom-left": offset.bottomLeft || [0, 0],
+ "bottom-right": offset.bottomRight || [0, 0],
+ "center": offset.center || [0, 0],
+ "left": offset.left || [0, 0],
+ "right": offset.right || [0, 0],
+ "top": offset.top || [0, 0],
+ "top-left": offset.topLeft || [0, 0],
+ "top-right": offset.topRight || [0, 0],
+ };
+ }
+ }
+ _pixelToFloats(pointPixel, size, width, height) {
+ const floats = [];
+ if (pointPixel[1] < height) {
+ floats.push("bottom");
+ }
+ else if (pointPixel[1] > size.height - height) {
+ floats.push("top");
+ }
+ if (pointPixel[0] < width / 2) {
+ floats.push("right");
+ }
+ else if (pointPixel[0] > size.width - width / 2) {
+ floats.push("left");
+ }
+ return floats;
+ }
+ _pointFromRectPosition(rect, position) {
+ const x0 = rect[0];
+ const x1 = rect[0] < rect[2] ? rect[2] : rect[2] + 1;
+ const y0 = rect[1];
+ const y1 = rect[3];
+ switch (position) {
+ case "bottom":
+ return [(x0 + x1) / 2, y1];
+ case "bottom-left":
+ return [x0, y1];
+ case "bottom-right":
+ return [x1, y1];
+ case "center":
+ return [(x0 + x1) / 2, (y0 + y1) / 2];
+ case "left":
+ return [x0, (y0 + y1) / 2];
+ case "right":
+ return [x1, (y0 + y1) / 2];
+ case "top":
+ return [(x0 + x1) / 2, y0];
+ case "top-left":
+ return [x0, y0];
+ case "top-right":
+ return [x1, y0];
+ default:
+ return [(x0 + x1) / 2, y1];
+ }
+ }
+}
+
+function isBrowser() {
+ return (typeof window !== "undefined" &&
+ typeof document !== "undefined");
+}
+function isArraySupported() {
+ return !!(Array.prototype &&
+ Array.prototype.concat &&
+ Array.prototype.filter &&
+ Array.prototype.includes &&
+ Array.prototype.indexOf &&
+ Array.prototype.join &&
+ Array.prototype.map &&
+ Array.prototype.push &&
+ Array.prototype.pop &&
+ Array.prototype.reverse &&
+ Array.prototype.shift &&
+ Array.prototype.slice &&
+ Array.prototype.splice &&
+ Array.prototype.sort &&
+ Array.prototype.unshift);
+}
+function isBlobSupported() {
+ return ("Blob" in window &&
+ "URL" in window);
+}
+function isFunctionSupported() {
+ return !!(Function.prototype &&
+ Function.prototype.apply &&
+ Function.prototype.bind);
+}
+function isJSONSupported() {
+ return ("JSON" in window &&
+ "parse" in JSON &&
+ "stringify" in JSON);
+}
+function isMapSupported() {
+ return "Map" in window;
+}
+function isObjectSupported() {
+ return !!(Object.assign &&
+ Object.keys &&
+ Object.values);
+}
+function isPromiseSupported() {
+ return !!("Promise" in window &&
+ Promise.resolve &&
+ Promise.reject &&
+ Promise.prototype &&
+ Promise.prototype.catch &&
+ Promise.prototype.then);
+}
+function isSetSupported() {
+ return "Set" in window;
+}
+let isWebGLSupportedCache = undefined;
+function isWebGLSupportedCached() {
+ if (isWebGLSupportedCache === undefined) {
+ isWebGLSupportedCache = isWebGLSupported();
+ }
+ return isWebGLSupportedCache;
+}
+function isWebGLSupported() {
+ const attributes = {
+ alpha: false,
+ antialias: false,
+ depth: true,
+ failIfMajorPerformanceCaveat: false,
+ premultipliedAlpha: true,
+ preserveDrawingBuffer: false,
+ stencil: true,
+ };
+ const canvas = document.createElement("canvas");
+ const webGL2Context = canvas.getContext("webgl2", attributes);
+ if (!!webGL2Context) {
+ return true;
+ }
+ const context = canvas.getContext("webgl", attributes) ||
+ canvas
+ .getContext("experimental-webgl", attributes);
+ if (!context) {
+ return false;
+ }
+ const requiredExtensions = ["OES_standard_derivatives"];
+ const supportedExtensions = context.getSupportedExtensions();
+ for (const requiredExtension of requiredExtensions) {
+ if (supportedExtensions.indexOf(requiredExtension) === -1) {
+ return false;
+ }
+ }
+ return true;
+}
+/**
+ * Test whether the current browser supports the full
+ * functionality of MapillaryJS.
+ *
+ * @description The full functionality includes WebGL rendering.
+ *
+ * @return {boolean}
+ *
+ * @example `var supported = isSupported();`
+ */
+function isSupported() {
+ return isFallbackSupported() &&
+ isWebGLSupportedCached();
+}
+/**
+ * Test whether the current browser supports the fallback
+ * functionality of MapillaryJS.
+ *
+ * @description The fallback functionality does not include WebGL
+ * rendering, only 2D canvas rendering.
+ *
+ * @return {boolean}
+ *
+ * @example `var fallbackSupported = isFallbackSupported();`
+ */
+function isFallbackSupported() {
+ return isBrowser() &&
+ isArraySupported() &&
+ isBlobSupported() &&
+ isFunctionSupported() &&
+ isJSONSupported() &&
+ isMapSupported() &&
+ isObjectSupported() &&
+ isPromiseSupported() &&
+ isSetSupported();
+}
+
+/**
+ * Enumeration for camera controls.
+ *
+ * @description Specifies different modes for how the
+ * camera is controlled through pointer, keyboard or
+ * other modes of input.
+ *
+ * @enum {number}
+ * @readonly
+ */
+var CameraControls;
+(function (CameraControls) {
+ /**
+ * Control the camera with custom logic by
+ * attaching a custom camera controls
+ * instance to the {@link Viewer}.
+ */
+ CameraControls[CameraControls["Custom"] = 0] = "Custom";
+ /**
+ * Control the camera from a birds perspective
+ * to get an overview.
+ */
+ CameraControls[CameraControls["Earth"] = 1] = "Earth";
+ /**
+ * Control the camera in a first person view
+ * from the street level perspective.
+ */
+ CameraControls[CameraControls["Street"] = 2] = "Street";
+})(CameraControls || (CameraControls = {}));
+
+/**
+ * Enumeration for render mode
+ * @enum {number}
+ * @readonly
+ * @description Modes for specifying how rendering is done
+ * in the viewer. All modes preserves the original aspect
+ * ratio of the images.
+ */
+var RenderMode;
+(function (RenderMode) {
+ /**
+ * Displays all content within the viewer.
+ *
+ * @description Black bars shown on both
+ * sides of the content. Bars are shown
+ * either below and above or to the left
+ * and right of the content depending on
+ * the aspect ratio relation between the
+ * image and the viewer.
+ */
+ RenderMode[RenderMode["Letterbox"] = 0] = "Letterbox";
+ /**
+ * Fills the viewer by cropping content.
+ *
+ * @description Cropping is done either
+ * in horizontal or vertical direction
+ * depending on the aspect ratio relation
+ * between the image and the viewer.
+ */
+ RenderMode[RenderMode["Fill"] = 1] = "Fill";
+})(RenderMode || (RenderMode = {}));
+
+var RenderPass;
+(function (RenderPass) {
+ /**
+ * Occurs after the background render pass.
+ */
+ RenderPass[RenderPass["Opaque"] = 0] = "Opaque";
+})(RenderPass || (RenderPass = {}));
+
+class ComponentController {
+ constructor(container, navigator, observer, key, options, componentService) {
+ this._container = container;
+ this._observer = observer;
+ this._navigator = navigator;
+ this._options = options != null ? options : {};
+ this._key = key;
+ this._navigable = key == null;
+ this._componentService = !!componentService ?
+ componentService :
+ new ComponentService(this._container, this._navigator);
+ this._coverComponent = this._componentService.getCover();
+ this._initializeComponents();
+ if (key) {
+ this._initilizeCoverComponent();
+ this._subscribeCoverComponent();
+ }
+ else {
+ this._navigator.movedToId$.pipe(first((k) => {
+ return k != null;
+ }))
+ .subscribe((k) => {
+ this._key = k;
+ this._componentService.deactivateCover();
+ this._coverComponent.configure({
+ id: this._key,
+ state: CoverState.Hidden,
+ });
+ this._subscribeCoverComponent();
+ this._navigator.stateService.start();
+ this._navigator.cacheService.start();
+ this._navigator.panService.start();
+ this._observer.startEmit();
+ });
+ }
+ }
+ get navigable() {
+ return this._navigable;
+ }
+ get(name) {
+ return this._componentService.get(name);
+ }
+ activate(name) {
+ this._componentService.activate(name);
+ }
+ activateCover() {
+ this._coverComponent.configure({ state: CoverState.Visible });
+ }
+ deactivate(name) {
+ this._componentService.deactivate(name);
+ }
+ deactivateCover() {
+ this._coverComponent.configure({ state: CoverState.Loading });
+ }
+ remove() {
+ this._componentService.remove();
+ if (this._configurationSubscription != null) {
+ this._configurationSubscription.unsubscribe();
+ }
+ }
+ _initializeComponents() {
+ var _a, _b;
+ const options = this._options;
+ this._uFalse((_a = options.fallback) === null || _a === void 0 ? void 0 : _a.image, "imagefallback");
+ this._uFalse((_b = options.fallback) === null || _b === void 0 ? void 0 : _b.navigation, "navigationfallback");
+ this._uFalse(options.marker, "marker");
+ this._uFalse(options.popup, "popup");
+ this._uFalse(options.slider, "slider");
+ this._uFalse(options.spatial, "spatial");
+ this._uFalse(options.tag, "tag");
+ this._uTrue(options.attribution, "attribution");
+ this._uTrue(options.bearing, "bearing");
+ this._uTrue(options.cache, "cache");
+ this._uTrue(options.direction, "direction");
+ this._uTrue(options.image, "image");
+ this._uTrue(options.keyboard, "keyboard");
+ this._uTrue(options.pointer, "pointer");
+ this._uTrue(options.sequence, "sequence");
+ this._uTrue(options.zoom, "zoom");
+ }
+ _initilizeCoverComponent() {
+ let options = this._options;
+ this._coverComponent.configure({ id: this._key });
+ if (options.cover === undefined || options.cover) {
+ this.activateCover();
+ }
+ else {
+ this.deactivateCover();
+ }
+ }
+ _setNavigable(navigable) {
+ if (this._navigable === navigable) {
+ return;
+ }
+ this._navigable = navigable;
+ this._observer.navigable$.next(navigable);
+ }
+ _subscribeCoverComponent() {
+ this._configurationSubscription =
+ this._coverComponent.configuration$.pipe(distinctUntilChanged(undefined, (c) => {
+ return c.state;
+ }))
+ .subscribe((conf) => {
+ if (conf.state === CoverState.Loading) {
+ this._navigator.stateService.currentId$.pipe(first(), switchMap((key) => {
+ const keyChanged = key == null || key !== conf.id;
+ if (keyChanged) {
+ this._setNavigable(false);
+ }
+ return keyChanged ?
+ this._navigator.moveTo$(conf.id) :
+ this._navigator.stateService.currentImage$.pipe(first());
+ }))
+ .subscribe(() => {
+ this._navigator.stateService.start();
+ this._navigator.cacheService.start();
+ this._navigator.panService.start();
+ this._observer.startEmit();
+ this._coverComponent.configure({ state: CoverState.Hidden });
+ this._componentService.deactivateCover();
+ this._setNavigable(true);
+ }, (error) => {
+ console.error("Failed to deactivate cover.", error);
+ this._coverComponent.configure({ state: CoverState.Visible });
+ });
+ }
+ else if (conf.state === CoverState.Visible) {
+ this._observer.stopEmit();
+ this._navigator.stateService.stop();
+ this._navigator.cacheService.stop();
+ this._navigator.playService.stop();
+ this._navigator.panService.stop();
+ this._componentService.activateCover();
+ this._setNavigable(conf.id == null);
+ }
+ });
+ }
+ _uFalse(option, name) {
+ if (option === undefined) {
+ this._componentService.deactivate(name);
+ return;
+ }
+ if (typeof option === "boolean") {
+ if (option) {
+ this._componentService.activate(name);
+ }
+ else {
+ this._componentService.deactivate(name);
+ }
+ return;
+ }
+ this._componentService.configure(name, option);
+ this._componentService.activate(name);
+ }
+ _uTrue(option, name) {
+ if (option === undefined) {
+ this._componentService.activate(name);
+ return;
+ }
+ if (typeof option === "boolean") {
+ if (option) {
+ this._componentService.activate(name);
+ }
+ else {
+ this._componentService.deactivate(name);
+ }
+ return;
+ }
+ this._componentService.configure(name, option);
+ this._componentService.activate(name);
+ }
+}
+
+class DOMRenderer {
+ constructor(element, renderService, currentFrame$) {
+ this._adaptiveOperation$ = new Subject();
+ this._render$ = new Subject();
+ this._renderAdaptive$ = new Subject();
+ this._subscriptions = new SubscriptionHolder();
+ this._renderService = renderService;
+ this._currentFrame$ = currentFrame$;
+ const subs = this._subscriptions;
+ const rootNode = virtualDom.create(virtualDom.h("div.mapillary-dom-renderer", []));
+ element.appendChild(rootNode);
+ this._offset$ = this._adaptiveOperation$.pipe(scan((adaptive, operation) => {
+ return operation(adaptive);
+ }, {
+ elementHeight: element.offsetHeight,
+ elementWidth: element.offsetWidth,
+ imageAspect: 0,
+ renderMode: RenderMode.Fill,
+ }), filter((adaptive) => {
+ return adaptive.imageAspect > 0 && adaptive.elementWidth > 0 && adaptive.elementHeight > 0;
+ }), map((adaptive) => {
+ const elementAspect = adaptive.elementWidth / adaptive.elementHeight;
+ const ratio = adaptive.imageAspect / elementAspect;
+ let verticalOffset = 0;
+ let horizontalOffset = 0;
+ if (adaptive.renderMode === RenderMode.Letterbox) {
+ if (adaptive.imageAspect > elementAspect) {
+ verticalOffset = adaptive.elementHeight * (1 - 1 / ratio) / 2;
+ }
+ else {
+ horizontalOffset = adaptive.elementWidth * (1 - ratio) / 2;
+ }
+ }
+ else {
+ if (adaptive.imageAspect > elementAspect) {
+ horizontalOffset = -adaptive.elementWidth * (ratio - 1) / 2;
+ }
+ else {
+ verticalOffset = -adaptive.elementHeight * (1 / ratio - 1) / 2;
+ }
+ }
+ return {
+ bottom: verticalOffset,
+ left: horizontalOffset,
+ right: horizontalOffset,
+ top: verticalOffset,
+ };
+ }));
+ const imageAspectSubscription = this._currentFrame$.pipe(filter((frame) => {
+ return frame.state.currentImage != null;
+ }), distinctUntilChanged((k1, k2) => {
+ return k1 === k2;
+ }, (frame) => {
+ return frame.state.currentImage.id;
+ }), map((frame) => {
+ return frame.state.currentTransform.basicAspect;
+ }), map((aspect) => {
+ return (adaptive) => {
+ adaptive.imageAspect = aspect;
+ return adaptive;
+ };
+ }))
+ .subscribe(this._adaptiveOperation$);
+ const renderAdaptiveSubscription = combineLatest(this._renderAdaptive$.pipe(scan((vNodeHashes, vNodeHash) => {
+ if (vNodeHash.vNode == null) {
+ delete vNodeHashes[vNodeHash.name];
+ }
+ else {
+ vNodeHashes[vNodeHash.name] = vNodeHash.vNode;
+ }
+ return vNodeHashes;
+ }, {})), this._offset$).pipe(map((vo) => {
+ const vNodes = [];
+ const hashes = vo[0];
+ for (const name in hashes) {
+ if (!hashes.hasOwnProperty(name)) {
+ continue;
+ }
+ vNodes.push(hashes[name]);
+ }
+ const offset = vo[1];
+ const properties = {
+ style: {
+ bottom: offset.bottom + "px",
+ left: offset.left + "px",
+ "pointer-events": "none",
+ position: "absolute",
+ right: offset.right + "px",
+ top: offset.top + "px",
+ },
+ };
+ return {
+ name: "mapillary-dom-adaptive-renderer",
+ vNode: virtualDom.h("div.mapillary-dom-adaptive-renderer", properties, vNodes),
+ };
+ }))
+ .subscribe(this._render$);
+ this._vNode$ = this._render$.pipe(scan((vNodeHashes, vNodeHash) => {
+ if (vNodeHash.vNode == null) {
+ delete vNodeHashes[vNodeHash.name];
+ }
+ else {
+ vNodeHashes[vNodeHash.name] = vNodeHash.vNode;
+ }
+ return vNodeHashes;
+ }, {}), map((hashes) => {
+ const vNodes = [];
+ for (const name in hashes) {
+ if (!hashes.hasOwnProperty(name)) {
+ continue;
+ }
+ vNodes.push(hashes[name]);
+ }
+ return virtualDom.h("div.mapillary-dom-renderer", vNodes);
+ }));
+ this._vPatch$ = this._vNode$.pipe(scan((nodePatch, vNode) => {
+ nodePatch.vpatch = virtualDom.diff(nodePatch.vNode, vNode);
+ nodePatch.vNode = vNode;
+ return nodePatch;
+ }, { vNode: virtualDom.h("div.mapillary-dom-renderer", []), vpatch: null }), pluck("vpatch"));
+ this._element$ = this._vPatch$.pipe(scan((oldElement, vPatch) => {
+ return virtualDom.patch(oldElement, vPatch);
+ }, rootNode), publishReplay(1), refCount());
+ subs.push(imageAspectSubscription);
+ subs.push(renderAdaptiveSubscription);
+ subs.push(this._element$.subscribe(() => { }));
+ subs.push(this._renderService.size$.pipe(map((size) => {
+ return (adaptive) => {
+ adaptive.elementWidth = size.width;
+ adaptive.elementHeight = size.height;
+ return adaptive;
+ };
+ }))
+ .subscribe(this._adaptiveOperation$));
+ subs.push(this._renderService.renderMode$.pipe(map((renderMode) => {
+ return (adaptive) => {
+ adaptive.renderMode = renderMode;
+ return adaptive;
+ };
+ }))
+ .subscribe(this._adaptiveOperation$));
+ }
+ get element$() {
+ return this._element$;
+ }
+ get render$() {
+ return this._render$;
+ }
+ get renderAdaptive$() {
+ return this._renderAdaptive$;
+ }
+ clear(name) {
+ this._renderAdaptive$.next({ name: name, vNode: null });
+ this._render$.next({ name: name, vNode: null });
+ }
+ remove() {
+ this._subscriptions.unsubscribe();
+ }
+}
+
+class GLRenderer {
+ constructor(canvas, canvasContainer, renderService) {
+ this._renderFrame$ = new Subject();
+ this._renderCameraOperation$ = new Subject();
+ this._render$ = new Subject();
+ this._clear$ = new Subject();
+ this._renderOperation$ = new Subject();
+ this._rendererOperation$ = new Subject();
+ this._eraserOperation$ = new Subject();
+ this._triggerOperation$ = new Subject();
+ this._subscriptions = new SubscriptionHolder();
+ this._opaqueRender$ = new Subject();
+ this._renderService = renderService;
+ const subs = this._subscriptions;
+ this._renderer$ = this._rendererOperation$.pipe(scan((renderer, operation) => {
+ return operation(renderer);
+ }, { needsRender: false, renderer: null }), filter((renderer) => {
+ return !!renderer.renderer;
+ }));
+ this._renderCollection$ = this._renderOperation$.pipe(scan((hashes, operation) => {
+ return operation(hashes);
+ }, {}), share());
+ this._renderCamera$ = this._renderCameraOperation$.pipe(scan((rc, operation) => {
+ return operation(rc);
+ }, { frameId: -1, needsRender: false, perspective: null }));
+ this._eraser$ = this._eraserOperation$.pipe(startWith((eraser) => {
+ return eraser;
+ }), scan((eraser, operation) => {
+ return operation(eraser);
+ }, { needsRender: false }));
+ const trigger$ = this._triggerOperation$.pipe(startWith((trigger) => {
+ return trigger;
+ }), scan((trigger, operation) => {
+ return operation(trigger);
+ }, { needsRender: false }));
+ const clearColor = new Color(0x0F0F0F);
+ const renderSubscription = combineLatest(this._renderer$, this._renderCollection$, this._renderCamera$, this._eraser$, trigger$).pipe(map(([renderer, hashes, rc, eraser, trigger]) => {
+ const renders = Object.keys(hashes)
+ .map((key) => {
+ return hashes[key];
+ });
+ return { camera: rc, eraser: eraser, trigger: trigger, renderer: renderer, renders: renders };
+ }), filter((co) => {
+ let needsRender = co.renderer.needsRender ||
+ co.camera.needsRender ||
+ co.eraser.needsRender ||
+ co.trigger.needsRender;
+ const frameId = co.camera.frameId;
+ for (const render of co.renders) {
+ if (render.frameId !== frameId) {
+ return false;
+ }
+ needsRender = needsRender || render.needsRender;
+ }
+ return needsRender;
+ }), distinctUntilChanged((n1, n2) => {
+ return n1 === n2;
+ }, (co) => {
+ return co.eraser.needsRender ||
+ co.trigger.needsRender ? -co.camera.frameId : co.camera.frameId;
+ }))
+ .subscribe((co) => {
+ co.renderer.needsRender = false;
+ co.camera.needsRender = false;
+ co.eraser.needsRender = false;
+ co.trigger.needsRender = false;
+ const perspectiveCamera = co.camera.perspective;
+ const backgroundRenders = [];
+ const opaqueRenders = [];
+ for (const render of co.renders) {
+ if (render.pass === RenderPass$1.Background) {
+ backgroundRenders.push(render.render);
+ }
+ else if (render.pass === RenderPass$1.Opaque) {
+ opaqueRenders.push(render.render);
+ }
+ }
+ const renderer = co.renderer.renderer;
+ renderer.resetState();
+ renderer.setClearColor(clearColor, 1.0);
+ renderer.clear();
+ for (const renderBackground of backgroundRenders) {
+ renderBackground(perspectiveCamera, renderer);
+ }
+ renderer.clearDepth();
+ for (const renderOpaque of opaqueRenders) {
+ renderOpaque(perspectiveCamera, renderer);
+ }
+ renderer.resetState();
+ this._opaqueRender$.next();
+ });
+ subs.push(renderSubscription);
+ subs.push(this._renderFrame$.pipe(map((rc) => {
+ return (irc) => {
+ irc.frameId = rc.frameId;
+ irc.perspective = rc.perspective;
+ if (rc.changed === true) {
+ irc.needsRender = true;
+ }
+ return irc;
+ };
+ }))
+ .subscribe(this._renderCameraOperation$));
+ this._renderFrameSubscribe();
+ const renderHash$ = this._render$.pipe(map((hash) => {
+ return (hashes) => {
+ hashes[hash.name] = hash.renderer;
+ return hashes;
+ };
+ }));
+ const clearHash$ = this._clear$.pipe(map((name) => {
+ return (hashes) => {
+ delete hashes[name];
+ return hashes;
+ };
+ }));
+ subs.push(merge(renderHash$, clearHash$)
+ .subscribe(this._renderOperation$));
+ this._webGLRenderer$ = this._render$.pipe(first(), map(() => {
+ canvasContainer.appendChild(canvas);
+ const element = renderService.element;
+ const webGLRenderer = new WebGLRenderer({ canvas: canvas });
+ webGLRenderer.setPixelRatio(window.devicePixelRatio);
+ webGLRenderer.setSize(element.offsetWidth, element.offsetHeight);
+ webGLRenderer.autoClear = false;
+ return webGLRenderer;
+ }), publishReplay(1), refCount());
+ subs.push(this._webGLRenderer$
+ .subscribe(() => { }));
+ const createRenderer$ = this._webGLRenderer$.pipe(first(), map((webGLRenderer) => {
+ return (renderer) => {
+ renderer.needsRender = true;
+ renderer.renderer = webGLRenderer;
+ return renderer;
+ };
+ }));
+ const resizeRenderer$ = this._renderService.size$.pipe(map((size) => {
+ return (renderer) => {
+ if (renderer.renderer == null) {
+ return renderer;
+ }
+ renderer.renderer.setSize(size.width, size.height);
+ renderer.needsRender = true;
+ return renderer;
+ };
+ }));
+ const clearRenderer$ = this._clear$.pipe(map(() => {
+ return (renderer) => {
+ if (renderer.renderer == null) {
+ return renderer;
+ }
+ renderer.needsRender = true;
+ return renderer;
+ };
+ }));
+ subs.push(merge(createRenderer$, resizeRenderer$, clearRenderer$)
+ .subscribe(this._rendererOperation$));
+ const renderCollectionEmpty$ = this._renderCollection$.pipe(filter((hashes) => {
+ return Object.keys(hashes).length === 0;
+ }), share());
+ subs.push(renderCollectionEmpty$
+ .subscribe(() => {
+ if (this._renderFrameSubscription == null) {
+ return;
+ }
+ this._renderFrameSubscription.unsubscribe();
+ this._renderFrameSubscription = null;
+ this._renderFrameSubscribe();
+ }));
+ subs.push(renderCollectionEmpty$.pipe(map(() => {
+ return (eraser) => {
+ eraser.needsRender = true;
+ return eraser;
+ };
+ }))
+ .subscribe(this._eraserOperation$));
+ }
+ get render$() {
+ return this._render$;
+ }
+ get opaqueRender$() {
+ return this._opaqueRender$;
+ }
+ get webGLRenderer$() {
+ return this._webGLRenderer$;
+ }
+ clear(name) {
+ this._clear$.next(name);
+ }
+ remove() {
+ this._rendererOperation$.next((renderer) => {
+ if (renderer.renderer != null) {
+ const extension = renderer.renderer
+ .getContext()
+ .getExtension('WEBGL_lose_context');
+ if (!!extension) {
+ extension.loseContext();
+ }
+ renderer.renderer = null;
+ }
+ return renderer;
+ });
+ if (this._renderFrameSubscription != null) {
+ this._renderFrameSubscription.unsubscribe();
+ }
+ this._subscriptions.unsubscribe();
+ }
+ triggerRerender() {
+ this._renderService.renderCameraFrame$
+ .pipe(skip(1), first())
+ .subscribe(() => {
+ this._triggerOperation$.next((trigger) => {
+ trigger.needsRender = true;
+ return trigger;
+ });
+ });
+ }
+ _renderFrameSubscribe() {
+ this._render$.pipe(first(), map(() => {
+ return (irc) => {
+ irc.needsRender = true;
+ return irc;
+ };
+ }))
+ .subscribe((operation) => {
+ this._renderCameraOperation$.next(operation);
+ });
+ this._renderFrameSubscription = this._render$.pipe(first(), mergeMap(() => {
+ return this._renderService.renderCameraFrame$;
+ }))
+ .subscribe(this._renderFrame$);
+ }
+}
+
+class RenderCamera {
+ constructor(elementWidth, elementHeight, renderMode) {
+ this._spatial = new Spatial();
+ this._viewportCoords = new ViewportCoords();
+ this._size = { width: elementWidth, height: elementHeight };
+ this._initialFov = 60;
+ this._alpha = -1;
+ this._renderMode = renderMode;
+ this._zoom = 0;
+ this._frameId = -1;
+ this._changed = false;
+ this._changedForFrame = -1;
+ this._currentImageId = null;
+ this._previousImageId = null;
+ this._currentSpherical = false;
+ this._previousSpherical = false;
+ this._state = null;
+ this._currentProjectedPoints = [];
+ this._previousProjectedPoints = [];
+ this._currentFov = this._initialFov;
+ this._previousFov = this._initialFov;
+ this._camera = new Camera();
+ this._perspective = new PerspectiveCamera(this._initialFov, this._computeAspect(elementWidth, elementHeight), 0.16, 10000);
+ this._perspective.position.copy(this._camera.position);
+ this._perspective.up.copy(this._camera.up);
+ this._perspective.lookAt(this._camera.lookat);
+ this._perspective.updateMatrixWorld(true);
+ this._perspective.matrixAutoUpdate = false;
+ this._rotation = { phi: 0, theta: 0 };
+ }
+ get alpha() {
+ return this._alpha;
+ }
+ get camera() {
+ return this._camera;
+ }
+ get changed() {
+ return this._frameId === this._changedForFrame;
+ }
+ get frameId() {
+ return this._frameId;
+ }
+ get perspective() {
+ return this._perspective;
+ }
+ get renderMode() {
+ return this._renderMode;
+ }
+ get rotation() {
+ return this._rotation;
+ }
+ get zoom() {
+ return this._zoom;
+ }
+ get size() {
+ return this._size;
+ }
+ getTilt() {
+ return 90 - this._spatial.radToDeg(this._rotation.theta);
+ }
+ fovToZoom(fov) {
+ fov = Math.min(90, Math.max(0, fov));
+ const currentFov = this._computeCurrentFov(0);
+ const actualFov = this._alpha === 1 ?
+ currentFov :
+ this._interpolateFov(currentFov, this._computePreviousFov(0), this._alpha);
+ const y0 = Math.tan(actualFov / 2 * Math.PI / 180);
+ const y1 = Math.tan(fov / 2 * Math.PI / 180);
+ const zoom = Math.log(y0 / y1) / Math.log(2);
+ return zoom;
+ }
+ setFrame(frame) {
+ const state = frame.state;
+ if (state.state !== this._state) {
+ this._state = state.state;
+ if (this._state !== State.Custom) {
+ this.setRenderMode(this._renderMode);
+ this.setSize(this._size);
+ }
+ this._changed = true;
+ }
+ const currentImageId = state.currentImage.id;
+ const previousImageId = !!state.previousImage ? state.previousImage.id : null;
+ if (currentImageId !== this._currentImageId) {
+ this._currentImageId = currentImageId;
+ this._currentSpherical = isSpherical(state.currentTransform.cameraType);
+ this._currentProjectedPoints = this._computeProjectedPoints(state.currentTransform);
+ this._changed = true;
+ }
+ if (previousImageId !== this._previousImageId) {
+ this._previousImageId = previousImageId;
+ this._previousSpherical =
+ isSpherical(state.previousTransform.cameraType);
+ this._previousProjectedPoints = this._computeProjectedPoints(state.previousTransform);
+ this._changed = true;
+ }
+ const zoom = state.zoom;
+ if (zoom !== this._zoom) {
+ this._zoom = zoom;
+ this._changed = true;
+ }
+ if (this._changed) {
+ this._currentFov = this._computeCurrentFov(this.zoom);
+ this._previousFov = this._computePreviousFov(this._zoom);
+ }
+ const alpha = state.alpha;
+ if (this._changed || alpha !== this._alpha) {
+ this._alpha = alpha;
+ switch (this._state) {
+ case State.Earth:
+ this._perspective.fov = 60;
+ this._changed = true;
+ break;
+ case State.Custom:
+ break;
+ default:
+ this._perspective.fov =
+ this._interpolateFov(this._currentFov, this._previousFov, this._alpha);
+ this._changed = true;
+ break;
+ }
+ if (this._state !== State.Custom) {
+ this._perspective.updateProjectionMatrix();
+ }
+ }
+ const camera = state.camera;
+ if (this._camera.diff(camera) > 1e-9) {
+ this._camera.copy(camera);
+ this._rotation = this._computeRotation(camera);
+ this._perspective.up.copy(camera.up);
+ this._perspective.position.copy(camera.position);
+ // Workaround for shaking camera
+ this._perspective.matrixAutoUpdate = true;
+ this._perspective.lookAt(camera.lookat);
+ this._perspective.matrixAutoUpdate = false;
+ this._perspective.updateMatrix();
+ this._perspective.updateMatrixWorld(false);
+ this._changed = true;
+ }
+ this._setFrameId(frame.id);
+ }
+ setProjectionMatrix(matrix) {
+ this._perspective.projectionMatrix.fromArray(matrix);
+ this._perspective.projectionMatrixInverse
+ .copy(this._perspective.projectionMatrix)
+ .invert();
+ this._changed = true;
+ }
+ setRenderMode(renderMode) {
+ this._renderMode = renderMode;
+ if (this._state === State.Custom) {
+ return;
+ }
+ this._perspective.fov = this._computeFov();
+ this._perspective.updateProjectionMatrix();
+ this._changed = true;
+ }
+ setSize(size) {
+ this._size = size;
+ if (this._state === State.Custom) {
+ return;
+ }
+ this._perspective.aspect = this._computeAspect(size.width, size.height);
+ this._perspective.fov = this._computeFov();
+ this._perspective.updateProjectionMatrix();
+ this._changed = true;
+ }
+ _computeAspect(elementWidth, elementHeight) {
+ return elementWidth === 0 ? 0 : elementWidth / elementHeight;
+ }
+ _computeCurrentFov(zoom) {
+ if (this._perspective.aspect === 0) {
+ return 0;
+ }
+ if (!this._currentImageId) {
+ return this._initialFov;
+ }
+ return this._currentSpherical ?
+ this._yToFov(1, zoom) :
+ this._computeVerticalFov(this._currentProjectedPoints, this._renderMode, zoom, this.perspective.aspect);
+ }
+ _computeFov() {
+ this._currentFov = this._computeCurrentFov(this._zoom);
+ this._previousFov = this._computePreviousFov(this._zoom);
+ return this._interpolateFov(this._currentFov, this._previousFov, this._alpha);
+ }
+ _computePreviousFov(zoom) {
+ if (this._perspective.aspect === 0) {
+ return 0;
+ }
+ if (!this._currentImageId) {
+ return this._initialFov;
+ }
+ return !this._previousImageId ?
+ this._currentFov :
+ this._previousSpherical ?
+ this._yToFov(1, zoom) :
+ this._computeVerticalFov(this._previousProjectedPoints, this._renderMode, zoom, this.perspective.aspect);
+ }
+ _computeProjectedPoints(transform) {
+ const vertices = [[0.5, 0], [1, 0]];
+ const directions = [[0.5, 0], [0, 0.5]];
+ const pointsPerLine = 100;
+ return computeProjectedPoints(transform, vertices, directions, pointsPerLine, this._viewportCoords);
+ }
+ _computeRequiredVerticalFov(projectedPoint, zoom, aspect) {
+ const maxY = Math.max(projectedPoint[0] / aspect, projectedPoint[1]);
+ return this._yToFov(maxY, zoom);
+ }
+ _computeRotation(camera) {
+ let direction = camera.lookat.clone().sub(camera.position);
+ let up = camera.up.clone();
+ let phi = this._spatial.azimuthal(direction.toArray(), up.toArray());
+ let theta = Math.PI / 2 - this._spatial.angleToPlane(direction.toArray(), [0, 0, 1]);
+ return { phi: phi, theta: theta };
+ }
+ _computeVerticalFov(projectedPoints, renderMode, zoom, aspect) {
+ const fovs = projectedPoints
+ .map((projectedPoint) => {
+ return this._computeRequiredVerticalFov(projectedPoint, zoom, aspect);
+ });
+ const fov = renderMode === RenderMode.Fill ?
+ Math.min(...fovs) * 0.995 :
+ Math.max(...fovs);
+ return fov;
+ }
+ _yToFov(y, zoom) {
+ return 2 * Math.atan(y / Math.pow(2, zoom)) * 180 / Math.PI;
+ }
+ _interpolateFov(v1, v2, alpha) {
+ return alpha * v1 + (1 - alpha) * v2;
+ }
+ _setFrameId(frameId) {
+ this._frameId = frameId;
+ if (this._changed) {
+ this._changed = false;
+ this._changedForFrame = frameId;
+ }
+ }
+}
+
+class RenderService {
+ constructor(element, currentFrame$, renderMode, renderCamera) {
+ this._subscriptions = new SubscriptionHolder();
+ this._element = element;
+ this._currentFrame$ = currentFrame$;
+ this._spatial = new Spatial();
+ renderMode = renderMode != null ? renderMode : RenderMode.Fill;
+ this._resize$ = new Subject();
+ this._projectionMatrix$ = new Subject();
+ this._renderCameraOperation$ =
+ new Subject();
+ this._size$ =
+ new BehaviorSubject({
+ height: this._element.offsetHeight,
+ width: this._element.offsetWidth,
+ });
+ const subs = this._subscriptions;
+ subs.push(this._resize$.pipe(map(() => {
+ return {
+ height: this._element.offsetHeight,
+ width: this._element.offsetWidth,
+ };
+ }))
+ .subscribe(this._size$));
+ this._renderMode$ = new BehaviorSubject(renderMode);
+ this._renderCameraHolder$ = this._renderCameraOperation$.pipe(startWith((rc) => {
+ return rc;
+ }), scan((rc, operation) => {
+ return operation(rc);
+ }, renderCamera !== null && renderCamera !== void 0 ? renderCamera : new RenderCamera(this._element.offsetWidth, this._element.offsetHeight, renderMode)), publishReplay(1), refCount());
+ this._renderCameraFrame$ = this._currentFrame$.pipe(withLatestFrom(this._renderCameraHolder$), tap(([frame, rc]) => {
+ rc.setFrame(frame);
+ }), map((args) => {
+ return args[1];
+ }), publishReplay(1), refCount());
+ this._renderCamera$ = this._renderCameraFrame$.pipe(filter((rc) => {
+ return rc.changed;
+ }), publishReplay(1), refCount());
+ this._bearing$ = this._renderCamera$.pipe(map((rc) => {
+ let bearing = this._spatial.radToDeg(this._spatial.azimuthalToBearing(rc.rotation.phi));
+ return this._spatial.wrap(bearing, 0, 360);
+ }), publishReplay(1), refCount());
+ subs.push(this._size$.pipe(skip(1), map((size) => {
+ return (rc) => {
+ rc.setSize(size);
+ return rc;
+ };
+ }))
+ .subscribe(this._renderCameraOperation$));
+ subs.push(this._renderMode$.pipe(skip(1), map((rm) => {
+ return (rc) => {
+ rc.setRenderMode(rm);
+ return rc;
+ };
+ }))
+ .subscribe(this._renderCameraOperation$));
+ subs.push(this._projectionMatrix$.pipe(map((projectionMatrix) => {
+ return (rc) => {
+ rc.setProjectionMatrix(projectionMatrix);
+ return rc;
+ };
+ }))
+ .subscribe(this._renderCameraOperation$));
+ subs.push(this._bearing$.subscribe(() => { }));
+ subs.push(this._renderCameraHolder$.subscribe(() => { }));
+ subs.push(this._size$.subscribe(() => { }));
+ subs.push(this._renderMode$.subscribe(() => { }));
+ subs.push(this._renderCamera$.subscribe(() => { }));
+ subs.push(this._renderCameraFrame$.subscribe(() => { }));
+ }
+ get bearing$() {
+ return this._bearing$;
+ }
+ get element() {
+ return this._element;
+ }
+ get projectionMatrix$() {
+ return this._projectionMatrix$;
+ }
+ get renderCamera$() {
+ return this._renderCamera$;
+ }
+ get renderCameraFrame$() {
+ return this._renderCameraFrame$;
+ }
+ get renderMode$() {
+ return this._renderMode$;
+ }
+ get resize$() {
+ return this._resize$;
+ }
+ get size$() {
+ return this._size$;
+ }
+ dispose() {
+ this._subscriptions.unsubscribe();
+ }
+}
+
+class KeyboardService {
+ constructor(canvasContainer) {
+ this._keyDown$ = fromEvent(canvasContainer, "keydown");
+ this._keyUp$ = fromEvent(canvasContainer, "keyup");
+ }
+ get keyDown$() {
+ return this._keyDown$;
+ }
+ get keyUp$() {
+ return this._keyUp$;
+ }
+}
+
+// MouseEvent.button
+const LEFT_BUTTON = 0;
+const RIGHT_BUTTON = 2;
+// MouseEvent.buttons
+const BUTTONS_MAP = {
+ [LEFT_BUTTON]: 1,
+ [RIGHT_BUTTON]: 2
+};
+class MouseService {
+ constructor(container, canvasContainer, domContainer, doc) {
+ this._subscriptions = new SubscriptionHolder();
+ const subs = this._subscriptions;
+ this._activeSubject$ = new BehaviorSubject(false);
+ this._active$ = this._activeSubject$
+ .pipe(distinctUntilChanged(), publishReplay(1), refCount());
+ this._claimMouse$ = new Subject();
+ this._claimWheel$ = new Subject();
+ this._deferPixelClaims$ = new Subject();
+ this._deferPixels$ = this._deferPixelClaims$
+ .pipe(scan((claims, claim) => {
+ if (claim.deferPixels == null) {
+ delete claims[claim.name];
+ }
+ else {
+ claims[claim.name] = claim.deferPixels;
+ }
+ return claims;
+ }, {}), map((claims) => {
+ let deferPixelMax = -1;
+ for (const key in claims) {
+ if (!claims.hasOwnProperty(key)) {
+ continue;
+ }
+ const deferPixels = claims[key];
+ if (deferPixels > deferPixelMax) {
+ deferPixelMax = deferPixels;
+ }
+ }
+ return deferPixelMax;
+ }), startWith(-1), publishReplay(1), refCount());
+ subs.push(this._deferPixels$.subscribe(() => { }));
+ this._documentMouseMove$ =
+ fromEvent(doc, "pointermove")
+ .pipe(filter(this._isMousePen));
+ this._documentMouseUp$ =
+ fromEvent(doc, "pointerup")
+ .pipe(filter(this._isMousePen));
+ this._mouseDown$ =
+ fromEvent(canvasContainer, "pointerdown")
+ .pipe(filter(this._isMousePen));
+ this._mouseEnter$ =
+ fromEvent(canvasContainer, "pointerenter")
+ .pipe(filter(this._isMousePen));
+ this._mouseLeave$ =
+ fromEvent(canvasContainer, "pointerleave")
+ .pipe(filter(this._isMousePen));
+ this._mouseMove$ =
+ fromEvent(canvasContainer, "pointermove")
+ .pipe(filter(this._isMousePen));
+ this._mouseUp$ =
+ fromEvent(canvasContainer, "pointerup")
+ .pipe(filter(this._isMousePen));
+ this._mouseOut$ =
+ fromEvent(canvasContainer, "pointerout")
+ .pipe(filter(this._isMousePen));
+ this._mouseOver$ =
+ fromEvent(canvasContainer, "pointerover")
+ .pipe(filter(this._isMousePen));
+ this._domMouseDown$ =
+ fromEvent(domContainer, "pointerdown")
+ .pipe(filter(this._isMousePen));
+ this._domMouseMove$ =
+ fromEvent(domContainer, "pointermove")
+ .pipe(filter(this._isMousePen));
+ this._click$ =
+ fromEvent(canvasContainer, "click");
+ this._contextMenu$ =
+ fromEvent(canvasContainer, "contextmenu");
+ this._windowBlur$ =
+ fromEvent(window, "blur");
+ this._dblClick$ = merge(fromEvent(container, "click"), fromEvent(canvasContainer, "dblclick"))
+ .pipe(bufferCount(3, 1), filter((events) => {
+ const event1 = events[0];
+ const event2 = events[1];
+ const event3 = events[2];
+ return event1.type === "click" &&
+ event2.type === "click" &&
+ event3.type === "dblclick" &&
+ event1.target.parentNode === canvasContainer &&
+ event2.target.parentNode === canvasContainer;
+ }), map((events) => {
+ return events[2];
+ }), share());
+ subs.push(merge(this._domMouseDown$, this._domMouseMove$, this._dblClick$, this._contextMenu$)
+ .subscribe((event) => {
+ event.preventDefault();
+ }));
+ this._mouseWheel$ = merge(fromEvent(canvasContainer, "wheel"), fromEvent(domContainer, "wheel"))
+ .pipe(share());
+ this._consistentContextMenu$ =
+ merge(this._mouseDown$, this._mouseMove$, this._mouseOut$, this._mouseUp$, this._contextMenu$)
+ .pipe(bufferCount(3, 1), filter((events) => {
+ // fire context menu on mouse up both on mac and windows
+ return events[0].type === "pointerdown" &&
+ events[1].type === "contextmenu" &&
+ events[2].type === "pointerup";
+ }), map((events) => {
+ return events[1];
+ }), share());
+ const dragStop$ = merge(this._windowBlur$, this._documentMouseMove$
+ .pipe(filter((e) => {
+ return this._buttonReleased(e, LEFT_BUTTON);
+ })), this._documentMouseUp$
+ .pipe(filter((e) => {
+ return this._mouseButton(e) === LEFT_BUTTON;
+ })))
+ .pipe(share());
+ const mouseDragInitiate$ = this._createMouseDragInitiate$(LEFT_BUTTON, this._mouseDown$, dragStop$, true)
+ .pipe(share());
+ this._mouseDragStart$ =
+ this._createMouseDragStart$(mouseDragInitiate$)
+ .pipe(share());
+ this._mouseDrag$ =
+ this._createMouseDrag$(mouseDragInitiate$, dragStop$)
+ .pipe(share());
+ this._mouseDragEnd$ =
+ this._createMouseDragEnd$(this._mouseDragStart$, dragStop$)
+ .pipe(share());
+ const domMouseDragInitiate$ = this._createMouseDragInitiate$(LEFT_BUTTON, this._domMouseDown$, dragStop$, false)
+ .pipe(share());
+ this._domMouseDragStart$ =
+ this._createMouseDragStart$(domMouseDragInitiate$)
+ .pipe(share());
+ this._domMouseDrag$ =
+ this._createMouseDrag$(domMouseDragInitiate$, dragStop$)
+ .pipe(share());
+ this._domMouseDragEnd$ =
+ this._createMouseDragEnd$(this._domMouseDragStart$, dragStop$)
+ .pipe(share());
+ const rightDragStop$ = merge(this._windowBlur$, this._documentMouseMove$.pipe(filter((e) => {
+ return this._buttonReleased(e, RIGHT_BUTTON);
+ })), this._documentMouseUp$.pipe(filter((e) => {
+ return this._mouseButton(e) === RIGHT_BUTTON;
+ })))
+ .pipe(share());
+ const mouseRightDragInitiate$ = this._createMouseDragInitiate$(RIGHT_BUTTON, this._mouseDown$, rightDragStop$, true)
+ .pipe(share());
+ this._mouseRightDragStart$ =
+ this._createMouseDragStart$(mouseRightDragInitiate$)
+ .pipe(share());
+ this._mouseRightDrag$ =
+ this._createMouseDrag$(mouseRightDragInitiate$, rightDragStop$)
+ .pipe(share());
+ this._mouseRightDragEnd$ =
+ this._createMouseDragEnd$(this._mouseRightDragStart$, rightDragStop$)
+ .pipe(share());
+ this._proximateClick$ = this._mouseDown$
+ .pipe(switchMap((mouseDown) => {
+ return this._click$.pipe(takeUntil(this._createDeferredMouseMove$(mouseDown, this._documentMouseMove$)), take(1));
+ }), share());
+ this._staticClick$ = this._mouseDown$
+ .pipe(switchMap(() => {
+ return this._click$.pipe(takeUntil(this._documentMouseMove$), take(1));
+ }), share());
+ subs.push(this._mouseDragStart$.subscribe());
+ subs.push(this._mouseDrag$.subscribe());
+ subs.push(this._mouseDragEnd$.subscribe());
+ subs.push(this._domMouseDragStart$.subscribe());
+ subs.push(this._domMouseDrag$.subscribe());
+ subs.push(this._domMouseDragEnd$.subscribe());
+ subs.push(this._mouseRightDragStart$.subscribe());
+ subs.push(this._mouseRightDrag$.subscribe());
+ subs.push(this._mouseRightDragEnd$.subscribe());
+ subs.push(this._staticClick$.subscribe());
+ this._mouseOwner$ = this._createOwner$(this._claimMouse$)
+ .pipe(publishReplay(1), refCount());
+ this._wheelOwner$ = this._createOwner$(this._claimWheel$)
+ .pipe(publishReplay(1), refCount());
+ subs.push(this._mouseOwner$.subscribe(() => { }));
+ subs.push(this._wheelOwner$.subscribe(() => { }));
+ }
+ get active$() {
+ return this._active$;
+ }
+ get activate$() {
+ return this._activeSubject$;
+ }
+ get documentMouseMove$() {
+ return this._documentMouseMove$;
+ }
+ get documentMouseUp$() {
+ return this._documentMouseUp$;
+ }
+ get domMouseDragStart$() {
+ return this._domMouseDragStart$;
+ }
+ get domMouseDrag$() {
+ return this._domMouseDrag$;
+ }
+ get domMouseDragEnd$() {
+ return this._domMouseDragEnd$;
+ }
+ get domMouseDown$() {
+ return this._domMouseDown$;
+ }
+ get domMouseMove$() {
+ return this._domMouseMove$;
+ }
+ get mouseOwner$() {
+ return this._mouseOwner$;
+ }
+ get mouseDown$() {
+ return this._mouseDown$;
+ }
+ get mouseEnter$() {
+ return this._mouseEnter$;
+ }
+ get mouseMove$() {
+ return this._mouseMove$;
+ }
+ get mouseLeave$() {
+ return this._mouseLeave$;
+ }
+ get mouseOut$() {
+ return this._mouseOut$;
+ }
+ get mouseOver$() {
+ return this._mouseOver$;
+ }
+ get mouseUp$() {
+ return this._mouseUp$;
+ }
+ get click$() {
+ return this._click$;
+ }
+ get dblClick$() {
+ return this._dblClick$;
+ }
+ get contextMenu$() {
+ return this._consistentContextMenu$;
+ }
+ get mouseWheel$() {
+ return this._mouseWheel$;
+ }
+ get mouseDragStart$() {
+ return this._mouseDragStart$;
+ }
+ get mouseDrag$() {
+ return this._mouseDrag$;
+ }
+ get mouseDragEnd$() {
+ return this._mouseDragEnd$;
+ }
+ get mouseRightDragStart$() {
+ return this._mouseRightDragStart$;
+ }
+ get mouseRightDrag$() {
+ return this._mouseRightDrag$;
+ }
+ get mouseRightDragEnd$() {
+ return this._mouseRightDragEnd$;
+ }
+ get proximateClick$() {
+ return this._proximateClick$;
+ }
+ get staticClick$() {
+ return this._staticClick$;
+ }
+ get windowBlur$() {
+ return this._windowBlur$;
+ }
+ dispose() {
+ this._subscriptions.unsubscribe();
+ }
+ claimMouse(name, zindex) {
+ this._claimMouse$.next({ name: name, zindex: zindex });
+ }
+ unclaimMouse(name) {
+ this._claimMouse$.next({ name: name, zindex: null });
+ }
+ deferPixels(name, deferPixels) {
+ this._deferPixelClaims$.next({ name: name, deferPixels: deferPixels });
+ }
+ undeferPixels(name) {
+ this._deferPixelClaims$.next({ name: name, deferPixels: null });
+ }
+ claimWheel(name, zindex) {
+ this._claimWheel$.next({ name: name, zindex: zindex });
+ }
+ unclaimWheel(name) {
+ this._claimWheel$.next({ name: name, zindex: null });
+ }
+ filtered$(name, observable$) {
+ return this._filtered(name, observable$, this._mouseOwner$);
+ }
+ filteredWheel$(name, observable$) {
+ return this._filtered(name, observable$, this._wheelOwner$);
+ }
+ _createDeferredMouseMove$(origin, mouseMove$) {
+ return mouseMove$.pipe(map((mouseMove) => {
+ const deltaX = mouseMove.clientX - origin.clientX;
+ const deltaY = mouseMove.clientY - origin.clientY;
+ return [mouseMove, Math.sqrt(deltaX * deltaX + deltaY * deltaY)];
+ }), withLatestFrom(this._deferPixels$), filter(([[, delta], deferPixels]) => {
+ return delta > deferPixels;
+ }), map(([[mouseMove]]) => {
+ return mouseMove;
+ }));
+ }
+ _createMouseDrag$(mouseDragStartInitiate$, stop$) {
+ return mouseDragStartInitiate$.pipe(map(([, mouseMove]) => {
+ return mouseMove;
+ }), switchMap((mouseMove) => {
+ return concat(of(mouseMove), this._documentMouseMove$).pipe(takeUntil(stop$));
+ }));
+ }
+ _createMouseDragEnd$(mouseDragStart$, stop$) {
+ return mouseDragStart$.pipe(switchMap(() => {
+ return stop$.pipe(first());
+ }));
+ }
+ _createMouseDragStart$(mouseDragStartInitiate$) {
+ return mouseDragStartInitiate$.pipe(map(([mouseDown]) => {
+ return mouseDown;
+ }));
+ }
+ _createMouseDragInitiate$(button, mouseDown$, stop$, defer) {
+ return mouseDown$.pipe(filter((mouseDown) => {
+ return this._mouseButton(mouseDown) === button;
+ }), switchMap((mouseDown) => {
+ return combineLatest(of(mouseDown), defer ?
+ this._createDeferredMouseMove$(mouseDown, this._documentMouseMove$) :
+ this._documentMouseMove$).pipe(takeUntil(stop$), take(1));
+ }));
+ }
+ _createOwner$(claim$) {
+ return claim$.pipe(scan((claims, claim) => {
+ if (claim.zindex == null) {
+ delete claims[claim.name];
+ }
+ else {
+ claims[claim.name] = claim.zindex;
+ }
+ return claims;
+ }, {}), map((claims) => {
+ let owner = null;
+ let zIndexMax = -1;
+ for (const name in claims) {
+ if (!claims.hasOwnProperty(name)) {
+ continue;
+ }
+ if (claims[name] > zIndexMax) {
+ zIndexMax = claims[name];
+ owner = name;
+ }
+ }
+ return owner;
+ }), startWith(null));
+ }
+ _filtered(name, observable$, owner$) {
+ return observable$.pipe(withLatestFrom(owner$), filter(([, owner]) => {
+ return owner === name;
+ }), map(([item]) => {
+ return item;
+ }));
+ }
+ _mouseButton(event) {
+ const upOrDown = event.type === "pointerdown" || event.type === "pointerup";
+ const InstallTrigger = window.InstallTrigger;
+ if (upOrDown &&
+ typeof InstallTrigger !== 'undefined' &&
+ event.button === RIGHT_BUTTON && event.ctrlKey &&
+ window.navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
+ // Fix for the fact that Firefox (detected by InstallTrigger)
+ // on Mac determines e.button = 2 when using Control + left click.
+ return LEFT_BUTTON;
+ }
+ return event.button;
+ }
+ _buttonReleased(event, button) {
+ // Right button `mouseup` is not fired in
+ // Chrome on Mac outside the window or iframe. If
+ // the button is no longer pressed during move
+ // it may have been released and drag stop
+ // should be emitted.
+ const flag = BUTTONS_MAP[button];
+ return event.buttons === undefined || (event.buttons & flag) !== flag;
+ }
+ _isMousePen(event) {
+ const type = event.pointerType;
+ return type === "mouse" || type === "pen";
+ }
+}
+
+class SpriteAtlas {
+ set json(value) {
+ this._json = value;
+ }
+ set image(value) {
+ this._image = value;
+ this._texture = new Texture(this._image);
+ this._texture.minFilter = NearestFilter;
+ }
+ get loaded() {
+ return !!(this._image && this._json);
+ }
+ getGLSprite(name) {
+ if (!this.loaded) {
+ throw new Error("Sprites cannot be retrieved before the atlas is loaded.");
+ }
+ let definition = this._json[name];
+ if (!definition) {
+ console.warn("Sprite with key" + name + "does not exist in sprite definition.");
+ return new Object3D();
+ }
+ let texture = this._texture.clone();
+ texture.needsUpdate = true;
+ let width = this._image.width;
+ let height = this._image.height;
+ texture.offset.x = definition.x / width;
+ texture.offset.y = (height - definition.y - definition.height) / height;
+ texture.repeat.x = definition.width / width;
+ texture.repeat.y = definition.height / height;
+ let material = new SpriteMaterial({ map: texture });
+ return new Sprite(material);
+ }
+ getDOMSprite(name, float) {
+ if (!this.loaded) {
+ throw new Error("Sprites cannot be retrieved before the atlas is loaded.");
+ }
+ if (float == null) {
+ float = Alignment.Center;
+ }
+ let definition = this._json[name];
+ if (!definition) {
+ console.warn("Sprite with key" + name + "does not exist in sprite definition.");
+ return virtualDom.h("div", {}, []);
+ }
+ let clipTop = definition.y;
+ let clipRigth = definition.x + definition.width;
+ let clipBottom = definition.y + definition.height;
+ let clipLeft = definition.x;
+ let left = -definition.x;
+ let top = -definition.y;
+ let height = this._image.height;
+ let width = this._image.width;
+ switch (float) {
+ case Alignment.Bottom:
+ case Alignment.Center:
+ case Alignment.Top:
+ left -= definition.width / 2;
+ break;
+ case Alignment.BottomLeft:
+ case Alignment.Left:
+ case Alignment.TopLeft:
+ left -= definition.width;
+ break;
+ case Alignment.BottomRight:
+ case Alignment.Right:
+ case Alignment.TopRight:
+ }
+ switch (float) {
+ case Alignment.Center:
+ case Alignment.Left:
+ case Alignment.Right:
+ top -= definition.height / 2;
+ break;
+ case Alignment.Top:
+ case Alignment.TopLeft:
+ case Alignment.TopRight:
+ top -= definition.height;
+ break;
+ case Alignment.Bottom:
+ case Alignment.BottomLeft:
+ case Alignment.BottomRight:
+ }
+ let pixelRatioInverse = 1 / definition.pixelRatio;
+ clipTop *= pixelRatioInverse;
+ clipRigth *= pixelRatioInverse;
+ clipBottom *= pixelRatioInverse;
+ clipLeft *= pixelRatioInverse;
+ left *= pixelRatioInverse;
+ top *= pixelRatioInverse;
+ height *= pixelRatioInverse;
+ width *= pixelRatioInverse;
+ let properties = {
+ src: this._image.src,
+ style: {
+ clip: `rect(${clipTop}px, ${clipRigth}px, ${clipBottom}px, ${clipLeft}px)`,
+ height: `${height}px`,
+ left: `${left}px`,
+ position: "absolute",
+ top: `${top}px`,
+ width: `${width}px`,
+ },
+ };
+ return virtualDom.h("img", properties, []);
+ }
+}
+class SpriteService {
+ constructor(sprite) {
+ this._retina = window.devicePixelRatio > 1;
+ this._spriteAtlasOperation$ = new Subject();
+ this._spriteAtlas$ = this._spriteAtlasOperation$.pipe(startWith((atlas) => {
+ return atlas;
+ }), scan((atlas, operation) => {
+ return operation(atlas);
+ }, new SpriteAtlas()), publishReplay(1), refCount());
+ this._atlasSubscription = this._spriteAtlas$
+ .subscribe(() => { });
+ if (sprite == null) {
+ return;
+ }
+ let format = this._retina ? "@2x" : "";
+ let imageXmlHTTP = new XMLHttpRequest();
+ imageXmlHTTP.open("GET", sprite + format + ".png", true);
+ imageXmlHTTP.responseType = "arraybuffer";
+ imageXmlHTTP.onload = () => {
+ let image = new Image();
+ image.onload = () => {
+ this._spriteAtlasOperation$.next((atlas) => {
+ atlas.image = image;
+ return atlas;
+ });
+ };
+ let blob = new Blob([imageXmlHTTP.response]);
+ image.src = window.URL.createObjectURL(blob);
+ };
+ imageXmlHTTP.onerror = (error) => {
+ console.error(new Error(`Failed to fetch sprite sheet (${sprite}${format}.png)`));
+ };
+ imageXmlHTTP.send();
+ let jsonXmlHTTP = new XMLHttpRequest();
+ jsonXmlHTTP.open("GET", sprite + format + ".json", true);
+ jsonXmlHTTP.responseType = "text";
+ jsonXmlHTTP.onload = () => {
+ let json = JSON.parse(jsonXmlHTTP.response);
+ this._spriteAtlasOperation$.next((atlas) => {
+ atlas.json = json;
+ return atlas;
+ });
+ };
+ jsonXmlHTTP.onerror = (error) => {
+ console.error(new Error(`Failed to fetch sheet (${sprite}${format}.json)`));
+ };
+ jsonXmlHTTP.send();
+ }
+ get spriteAtlas$() {
+ return this._spriteAtlas$;
+ }
+ dispose() {
+ this._atlasSubscription.unsubscribe();
+ }
+}
+
+class TouchService {
+ constructor(canvasContainer, domContainer) {
+ this._subscriptions = new SubscriptionHolder();
+ const subs = this._subscriptions;
+ this._activeSubject$ = new BehaviorSubject(false);
+ this._active$ = this._activeSubject$.pipe(distinctUntilChanged(), publishReplay(1), refCount());
+ subs.push(fromEvent(domContainer, "touchmove")
+ .subscribe((event) => {
+ event.preventDefault();
+ }));
+ this._touchStart$ = fromEvent(canvasContainer, "touchstart");
+ this._touchMove$ = fromEvent(canvasContainer, "touchmove");
+ this._touchEnd$ = fromEvent(canvasContainer, "touchend");
+ this._touchCancel$ = fromEvent(canvasContainer, "touchcancel");
+ const tapStart$ = this._touchStart$.pipe(filter((te) => {
+ return te.touches.length === 1 && te.targetTouches.length === 1;
+ }), share());
+ this._doubleTap$ = tapStart$.pipe(bufferWhen(() => {
+ return tapStart$.pipe(first(), switchMap(() => {
+ return merge(timer(300), tapStart$).pipe(take(1));
+ }));
+ }), filter((events) => {
+ return events.length === 2;
+ }), map((events) => {
+ return events[events.length - 1];
+ }), share());
+ subs.push(this._doubleTap$
+ .subscribe((event) => {
+ event.preventDefault();
+ }));
+ this._singleTouchMove$ = this._touchMove$.pipe(filter((te) => {
+ return te.touches.length === 1 && te.targetTouches.length === 1;
+ }), share());
+ let singleTouchStart$ = merge(this._touchStart$, this._touchEnd$, this._touchCancel$).pipe(filter((te) => {
+ return te.touches.length === 1 && te.targetTouches.length === 1;
+ }));
+ let multipleTouchStart$ = merge(this._touchStart$, this._touchEnd$, this._touchCancel$).pipe(filter((te) => {
+ return te.touches.length >= 1;
+ }));
+ let touchStop$ = merge(this._touchEnd$, this._touchCancel$).pipe(filter((te) => {
+ return te.touches.length === 0;
+ }));
+ this._singleTouchDragStart$ = singleTouchStart$.pipe(mergeMap(() => {
+ return this._singleTouchMove$.pipe(takeUntil(merge(touchStop$, multipleTouchStart$)), take(1));
+ }));
+ this._singleTouchDragEnd$ = singleTouchStart$.pipe(mergeMap(() => {
+ return merge(touchStop$, multipleTouchStart$).pipe(first());
+ }));
+ this._singleTouchDrag$ = singleTouchStart$.pipe(switchMap(() => {
+ return this._singleTouchMove$.pipe(skip(1), takeUntil(merge(multipleTouchStart$, touchStop$)));
+ }));
+ let touchesChanged$ = merge(this._touchStart$, this._touchEnd$, this._touchCancel$);
+ this._pinchStart$ = touchesChanged$.pipe(filter((te) => {
+ return te.touches.length === 2 && te.targetTouches.length === 2;
+ }));
+ this._pinchEnd$ = touchesChanged$.pipe(filter((te) => {
+ return te.touches.length !== 2 || te.targetTouches.length !== 2;
+ }));
+ this._pinchOperation$ = new Subject();
+ this._pinch$ = this._pinchOperation$.pipe(scan((pinch, operation) => {
+ return operation(pinch);
+ }, {
+ changeX: 0,
+ changeY: 0,
+ clientX: 0,
+ clientY: 0,
+ distance: 0,
+ distanceChange: 0,
+ distanceX: 0,
+ distanceY: 0,
+ originalEvent: null,
+ pageX: 0,
+ pageY: 0,
+ screenX: 0,
+ screenY: 0,
+ touch1: null,
+ touch2: null,
+ }));
+ const pinchSubscription = this._touchMove$.pipe(filter((te) => {
+ return te.touches.length === 2 && te.targetTouches.length === 2;
+ }), map((te) => {
+ return (previous) => {
+ let touch1 = te.touches[0];
+ let touch2 = te.touches[1];
+ let minX = Math.min(touch1.clientX, touch2.clientX);
+ let maxX = Math.max(touch1.clientX, touch2.clientX);
+ let minY = Math.min(touch1.clientY, touch2.clientY);
+ let maxY = Math.max(touch1.clientY, touch2.clientY);
+ let centerClientX = minX + (maxX - minX) / 2;
+ let centerClientY = minY + (maxY - minY) / 2;
+ let centerPageX = centerClientX + touch1.pageX - touch1.clientX;
+ let centerPageY = centerClientY + touch1.pageY - touch1.clientY;
+ let centerScreenX = centerClientX + touch1.screenX - touch1.clientX;
+ let centerScreenY = centerClientY + touch1.screenY - touch1.clientY;
+ let distanceX = Math.abs(touch1.clientX - touch2.clientX);
+ let distanceY = Math.abs(touch1.clientY - touch2.clientY);
+ let distance = Math.sqrt(distanceX * distanceX + distanceY * distanceY);
+ let distanceChange = distance - previous.distance;
+ let changeX = distanceX - previous.distanceX;
+ let changeY = distanceY - previous.distanceY;
+ let current = {
+ changeX: changeX,
+ changeY: changeY,
+ clientX: centerClientX,
+ clientY: centerClientY,
+ distance: distance,
+ distanceChange: distanceChange,
+ distanceX: distanceX,
+ distanceY: distanceY,
+ originalEvent: te,
+ pageX: centerPageX,
+ pageY: centerPageY,
+ screenX: centerScreenX,
+ screenY: centerScreenY,
+ touch1: touch1,
+ touch2: touch2,
+ };
+ return current;
+ };
+ }))
+ .subscribe(this._pinchOperation$);
+ subs.push(pinchSubscription);
+ this._pinchChange$ = this._pinchStart$.pipe(switchMap(() => {
+ return this._pinch$.pipe(skip(1), takeUntil(this._pinchEnd$));
+ }));
+ }
+ get active$() {
+ return this._active$;
+ }
+ get activate$() {
+ return this._activeSubject$;
+ }
+ get doubleTap$() {
+ return this._doubleTap$;
+ }
+ get touchStart$() {
+ return this._touchStart$;
+ }
+ get touchMove$() {
+ return this._touchMove$;
+ }
+ get touchEnd$() {
+ return this._touchEnd$;
+ }
+ get touchCancel$() {
+ return this._touchCancel$;
+ }
+ get singleTouchDragStart$() {
+ return this._singleTouchDragStart$;
+ }
+ get singleTouchDrag$() {
+ return this._singleTouchDrag$;
+ }
+ get singleTouchDragEnd$() {
+ return this._singleTouchDragEnd$;
+ }
+ get pinch$() {
+ return this._pinchChange$;
+ }
+ get pinchStart$() {
+ return this._pinchStart$;
+ }
+ get pinchEnd$() {
+ return this._pinchEnd$;
+ }
+ dispose() {
+ this._subscriptions.unsubscribe();
+ }
+}
+
+class ConfigurationService {
+ constructor(options) {
+ var _a, _b, _c, _d;
+ const host = (_b = (_a = options === null || options === void 0 ? void 0 : options.url) === null || _a === void 0 ? void 0 : _a.exploreHost) !== null && _b !== void 0 ? _b : "www.mapillary.com";
+ const scheme = (_d = (_c = options === null || options === void 0 ? void 0 : options.url) === null || _c === void 0 ? void 0 : _c.scheme) !== null && _d !== void 0 ? _d : "https";
+ const exploreUrl = `${scheme}://${host}`;
+ this._exploreUrl$ = of(exploreUrl);
+ const imageTiling = (options === null || options === void 0 ? void 0 : options.imageTiling) === false ? false : true;
+ this._imageTiling$ = of(imageTiling);
+ }
+ get exploreUrl$() {
+ return this._exploreUrl$;
+ }
+ get imageTiling$() {
+ return this._imageTiling$;
+ }
+}
+
+class Container {
+ constructor(options, stateService, dom) {
+ var _a;
+ this._onWindowResize = () => {
+ if (this._trackResize) {
+ this.renderService.resize$.next();
+ }
+ };
+ this._dom = dom !== null && dom !== void 0 ? dom : new DOM();
+ if (typeof options.container === "string") {
+ this._container = this._dom.document
+ .getElementById(options.container);
+ if (!this._container) {
+ throw new Error(`Container "${options.container}" not found.`);
+ }
+ }
+ else if (options.container instanceof HTMLElement) {
+ this._container = options.container;
+ }
+ else {
+ throw new Error(`Invalid type: "container" must be ` +
+ `a String or HTMLElement.`);
+ }
+ this._trackResize =
+ options.trackResize === false ?
+ false : true;
+ this.id = (_a = this._container.id) !== null && _a !== void 0 ? _a : "mapillary-fallback-container-id";
+ this._container.classList
+ .add("mapillary-viewer");
+ this._canvasContainer = this._dom
+ .createElement("div", "mapillary-interactive", this._container);
+ this._canvas = this._dom
+ .createElement("canvas", "mapillary-canvas");
+ this._canvas.style.position = "absolute";
+ this._canvas.setAttribute("tabindex", "0");
+ // Add DOM container after canvas container to
+ // render DOM elements on top of the interactive
+ // canvas.
+ this._domContainer = this._dom
+ .createElement("div", "mapillary-dom", this._container);
+ this.configurationService = new ConfigurationService(options);
+ this.renderService =
+ new RenderService(this._container, stateService.currentState$, options.renderMode);
+ this.glRenderer =
+ new GLRenderer(this._canvas, this._canvasContainer, this.renderService);
+ this.domRenderer =
+ new DOMRenderer(this._domContainer, this.renderService, stateService.currentState$);
+ this.keyboardService =
+ new KeyboardService(this._canvasContainer);
+ this.mouseService =
+ new MouseService(this._container, this._canvasContainer, this._domContainer, document);
+ this.touchService =
+ new TouchService(this._canvasContainer, this._domContainer);
+ this.spriteService =
+ new SpriteService(options.sprite);
+ window.addEventListener('resize', this._onWindowResize, false);
+ }
+ get canvas() {
+ return !!this._canvas.parentNode ?
+ this._canvas : null;
+ }
+ get canvasContainer() {
+ return this._canvasContainer;
+ }
+ get container() {
+ return this._container;
+ }
+ get domContainer() {
+ return this._domContainer;
+ }
+ remove() {
+ window.removeEventListener('resize', this._onWindowResize, false);
+ this.spriteService.dispose();
+ this.touchService.dispose();
+ this.mouseService.dispose();
+ this.glRenderer.remove();
+ this.domRenderer.remove();
+ this.renderService.dispose();
+ this._removeNode(this._canvasContainer);
+ this._removeNode(this._domContainer);
+ this._container.classList
+ .remove("mapillary-viewer");
+ }
+ _removeNode(node) {
+ if (node.parentNode) {
+ node.parentNode.removeChild(node);
+ }
+ }
+}
+
+class CacheService {
+ constructor(_graphService, _stateService, _api) {
+ this._graphService = _graphService;
+ this._stateService = _stateService;
+ this._api = _api;
+ this._subscriptions = new SubscriptionHolder();
+ this._started = false;
+ this._cellDepth = 1;
+ }
+ get started() {
+ return this._started;
+ }
+ configure(configuration) {
+ if (!configuration) {
+ this._cellDepth = 1;
+ return;
+ }
+ this._cellDepth = Math.max(1, Math.min(3, configuration.cellDepth));
+ }
+ start() {
+ if (this._started) {
+ return;
+ }
+ const subs = this._subscriptions;
+ subs.push(this._stateService.currentState$
+ .pipe(distinctUntilChanged(undefined, (frame) => {
+ return frame.state.currentImage.id;
+ }), map((frame) => {
+ const state = frame.state;
+ const trajectory = state.trajectory;
+ const trajectoryKeys = trajectory
+ .map((n) => {
+ return n.id;
+ });
+ const sequenceKey = trajectory[trajectory.length - 1].sequenceId;
+ return [
+ trajectoryKeys,
+ state.currentImage.originalLngLat,
+ sequenceKey,
+ ];
+ }), bufferCount(1, 5), withLatestFrom(this._graphService.graphMode$), switchMap(([keepBuffer, graphMode]) => {
+ const keepKeys = keepBuffer[0][0];
+ const lngLat = keepBuffer[0][1];
+ const geometry = this._api.data.geometry;
+ const cellId = geometry.lngLatToCellId(lngLat);
+ const keepCellIds = connectedComponent(cellId, this._cellDepth, geometry);
+ const keepSequenceKey = graphMode === GraphMode.Sequence ?
+ keepBuffer[0][2] :
+ undefined;
+ return this._graphService
+ .uncache$(keepKeys, keepCellIds, keepSequenceKey);
+ }))
+ .subscribe(() => { }));
+ subs.push(this._graphService.graphMode$
+ .pipe(skip(1), withLatestFrom(this._stateService.currentState$), switchMap(([mode, frame]) => {
+ return mode === GraphMode.Sequence ?
+ this._keyToEdges(frame.state.currentImage.id, (image) => {
+ return image.sequenceEdges$;
+ }) :
+ from(frame.state.trajectory
+ .map((image) => {
+ return image.id;
+ })
+ .slice(frame.state.currentIndex)).pipe(mergeMap((key) => {
+ return this._keyToEdges(key, (image) => {
+ return image.spatialEdges$;
+ });
+ }, 6));
+ }))
+ .subscribe(() => { }));
+ subs.push(this._graphService.dataAdded$
+ .pipe(withLatestFrom(this._stateService.currentId$), switchMap(([_, imageId]) => {
+ return this._graphService.cacheImage$(imageId);
+ }))
+ .subscribe(() => { }));
+ this._started = true;
+ }
+ stop() {
+ if (!this._started) {
+ return;
+ }
+ this._subscriptions.unsubscribe();
+ this._started = false;
+ }
+ _keyToEdges(key, imageToEdgeMap) {
+ return this._graphService.cacheImage$(key).pipe(switchMap(imageToEdgeMap), first((status) => {
+ return status.cached;
+ }), timeout(15000), catchError((error) => {
+ console.error(`Failed to cache edges (${key}).`, error);
+ return empty();
+ }));
+ }
+}
+
+class LoadingService {
+ constructor() {
+ this._loadersSubject$ = new Subject();
+ this._loaders$ = this._loadersSubject$.pipe(scan((loaders, loader) => {
+ if (loader.task !== undefined) {
+ loaders[loader.task] = loader.loading;
+ }
+ return loaders;
+ }, {}), startWith({}), publishReplay(1), refCount());
+ }
+ get loading$() {
+ return this._loaders$.pipe(map((loaders) => {
+ for (const key in loaders) {
+ if (!loaders.hasOwnProperty(key)) {
+ continue;
+ }
+ if (loaders[key]) {
+ return true;
+ }
+ }
+ return false;
+ }), debounceTime(100), distinctUntilChanged());
+ }
+ taskLoading$(task) {
+ return this._loaders$.pipe(map((loaders) => {
+ return !!loaders[task];
+ }), debounceTime(100), distinctUntilChanged());
+ }
+ startLoading(task) {
+ this._loadersSubject$.next({ loading: true, task: task });
+ }
+ stopLoading(task) {
+ this._loadersSubject$.next({ loading: false, task: task });
+ }
+}
+
+var PanMode;
+(function (PanMode) {
+ PanMode[PanMode["Disabled"] = 0] = "Disabled";
+ PanMode[PanMode["Enabled"] = 1] = "Enabled";
+ PanMode[PanMode["Started"] = 2] = "Started";
+})(PanMode || (PanMode = {}));
+class PanService {
+ constructor(graphService, stateService, enabled, graphCalculator, spatial, viewportCoords) {
+ this._subscriptions = new SubscriptionHolder();
+ this._graphService = graphService;
+ this._stateService = stateService;
+ this._graphCalculator = graphCalculator !== null && graphCalculator !== void 0 ? graphCalculator : new GraphCalculator();
+ this._spatial = spatial !== null && spatial !== void 0 ? spatial : new Spatial();
+ this._viewportCoords = viewportCoords !== null && viewportCoords !== void 0 ? viewportCoords : new ViewportCoords();
+ this._mode = enabled !== false ?
+ PanMode.Enabled : PanMode.Disabled;
+ this._panImagesSubject$ = new Subject();
+ this._panImages$ = this._panImagesSubject$.pipe(startWith([]), publishReplay(1), refCount());
+ this._subscriptions.push(this._panImages$.subscribe());
+ }
+ get panImages$() {
+ return this._panImages$;
+ }
+ dispose() {
+ this.stop();
+ if (this._panImagesSubscription != null) {
+ this._panImagesSubscription.unsubscribe();
+ }
+ this._subscriptions.unsubscribe();
+ }
+ enable() {
+ if (this._mode !== PanMode.Disabled) {
+ return;
+ }
+ this._mode = PanMode.Enabled;
+ this.start();
+ }
+ disable() {
+ if (this._mode === PanMode.Disabled) {
+ return;
+ }
+ this.stop();
+ this._mode = PanMode.Disabled;
+ }
+ start() {
+ if (this._mode !== PanMode.Enabled) {
+ return;
+ }
+ const panImages$ = this._stateService.currentImage$.pipe(switchMap((current) => {
+ if (!current.merged || isSpherical(current.cameraType)) {
+ return of([]);
+ }
+ const current$ = of(current);
+ const bounds = this._graphCalculator.boundingBoxCorners(current.lngLat, 20);
+ const adjacent$ = this._graphService
+ .cacheBoundingBox$(bounds[0], bounds[1]).pipe(catchError((error) => {
+ console.error(`Failed to cache periphery bounding box (${current.id})`, error);
+ return empty();
+ }), map((images) => {
+ if (isSpherical(current.cameraType)) {
+ return [];
+ }
+ const potential = [];
+ for (const image of images) {
+ if (image.id === current.id) {
+ continue;
+ }
+ if (image.mergeId !== current.mergeId) {
+ continue;
+ }
+ if (isSpherical(image.cameraType)) {
+ continue;
+ }
+ if (this._distance(image, current) > 4) {
+ continue;
+ }
+ potential.push(image);
+ }
+ return potential;
+ }));
+ return combineLatest(current$, adjacent$).pipe(withLatestFrom(this._stateService.reference$), map(([[cn, adjacent], reference]) => {
+ const currentDirection = this._spatial.viewingDirection(cn.rotation);
+ const currentTranslation = computeTranslation({ lat: cn.lngLat.lat, lng: cn.lngLat.lng, alt: cn.computedAltitude }, cn.rotation, reference);
+ const currentTransform = this._createTransform(cn, currentTranslation);
+ const currentAzimuthal = this._spatial.wrap(this._spatial.azimuthal(currentDirection.toArray(), currentTransform.upVector().toArray()), 0, 2 * Math.PI);
+ const currentProjectedPoints = this._computeProjectedPoints(currentTransform);
+ const currentHFov = this._computeHorizontalFov(currentProjectedPoints) / 180 * Math.PI;
+ const preferredOverlap = Math.PI / 8;
+ let left = undefined;
+ let right = undefined;
+ for (const a of adjacent) {
+ const translation = computeTranslation({ lat: a.lngLat.lat, lng: a.lngLat.lng, alt: a.computedAltitude }, a.rotation, reference);
+ const transform = this._createTransform(a, translation);
+ const projectedPoints = this._computeProjectedPoints(transform);
+ const hFov = this._computeHorizontalFov(projectedPoints) / 180 * Math.PI;
+ const direction = this._spatial.viewingDirection(a.rotation);
+ const azimuthal = this._spatial.wrap(this._spatial.azimuthal(direction.toArray(), transform.upVector().toArray()), 0, 2 * Math.PI);
+ const directionChange = this._spatial.angleBetweenVector2(currentDirection.x, currentDirection.y, direction.x, direction.y);
+ let overlap = Number.NEGATIVE_INFINITY;
+ if (directionChange > 0) {
+ if (currentAzimuthal > azimuthal) {
+ overlap = currentAzimuthal - 2 * Math.PI + currentHFov / 2 - (azimuthal - hFov / 2);
+ }
+ else {
+ overlap = currentAzimuthal + currentHFov / 2 - (azimuthal - hFov / 2);
+ }
+ }
+ else {
+ if (currentAzimuthal < azimuthal) {
+ overlap = azimuthal + hFov / 2 - (currentAzimuthal + 2 * Math.PI - currentHFov / 2);
+ }
+ else {
+ overlap = azimuthal + hFov / 2 - (currentAzimuthal - currentHFov / 2);
+ }
+ }
+ const nonOverlap = Math.abs(hFov - overlap);
+ const distanceCost = this._distance(a, cn);
+ const timeCost = Math.min(this._timeDifference(a, cn), 4);
+ const overlapCost = 20 * Math.abs(overlap - preferredOverlap);
+ const fovCost = Math.min(5, 1 / Math.min(hFov / currentHFov, 1));
+ const nonOverlapCost = overlap > 0 ? -2 * nonOverlap : 0;
+ const cost = distanceCost + timeCost + overlapCost + fovCost + nonOverlapCost;
+ if (overlap > 0 &&
+ overlap < 0.5 * currentHFov &&
+ overlap < 0.5 * hFov &&
+ nonOverlap > 0.5 * currentHFov) {
+ if (directionChange > 0) {
+ if (!left) {
+ left = [cost, a, transform, hFov];
+ }
+ else {
+ if (cost < left[0]) {
+ left = [cost, a, transform, hFov];
+ }
+ }
+ }
+ else {
+ if (!right) {
+ right = [cost, a, transform, hFov];
+ }
+ else {
+ if (cost < right[0]) {
+ right = [cost, a, transform, hFov];
+ }
+ }
+ }
+ }
+ }
+ const panImagess = [];
+ if (!!left) {
+ panImagess.push([left[1], left[2], left[3]]);
+ }
+ if (!!right) {
+ panImagess.push([right[1], right[2], right[3]]);
+ }
+ return panImagess;
+ }), startWith([]));
+ }));
+ this._panImagesSubscription = this._stateService.currentState$.pipe(map((frame) => {
+ return frame.state.imagesAhead > 0;
+ }), distinctUntilChanged(), switchMap((traversing) => {
+ return traversing ? of([]) : panImages$;
+ }))
+ .subscribe((panImages) => {
+ this._panImagesSubject$.next(panImages);
+ });
+ this._mode = PanMode.Started;
+ }
+ stop() {
+ if (this._mode !== PanMode.Started) {
+ return;
+ }
+ this._panImagesSubscription.unsubscribe();
+ this._panImagesSubject$.next([]);
+ this._mode = PanMode.Enabled;
+ }
+ _distance(image, reference) {
+ const [x, y, z] = geodeticToEnu(image.lngLat.lng, image.lngLat.lat, image.computedAltitude, reference.lngLat.lng, reference.lngLat.lat, reference.computedAltitude);
+ return Math.sqrt(x * x + y * y + z * z);
+ }
+ _timeDifference(image, reference) {
+ const milliSecond = (1000 * 60 * 60 * 24 * 30);
+ return Math.abs(image.capturedAt - reference.capturedAt) / milliSecond;
+ }
+ _createTransform(image, translation) {
+ return new Transform(image.exifOrientation, image.width, image.height, image.scale, image.rotation, translation, image.assetsCached ? image.image : undefined, undefined, image.cameraParameters, image.cameraType);
+ }
+ _computeProjectedPoints(transform) {
+ const vertices = [[1, 0]];
+ const directions = [[0, 0.5]];
+ const pointsPerLine = 20;
+ return computeProjectedPoints(transform, vertices, directions, pointsPerLine, this._viewportCoords);
+ }
+ _computeHorizontalFov(projectedPoints) {
+ const fovs = projectedPoints
+ .map((projectedPoint) => {
+ return this._coordToFov(projectedPoint[0]);
+ });
+ const fov = Math.min(...fovs);
+ return fov;
+ }
+ _coordToFov(x) {
+ return 2 * Math.atan(x) * 180 / Math.PI;
+ }
+}
+
+/**
+ * @class API
+ *
+ * @classdesc Provides methods for access to the API.
+ */
+class APIWrapper {
+ constructor(_data) {
+ this._data = _data;
+ }
+ get data() {
+ return this._data;
+ }
+ getCoreImages$(cellId) {
+ return this._wrap$(this._data.getCoreImages(cellId));
+ }
+ getImages$(imageIds) {
+ return this._wrap$(this._data.getImages(imageIds));
+ }
+ getImageTiles$(tiles) {
+ return this._wrap$(this._data.getImageTiles(tiles));
+ }
+ getSequence$(sequenceId) {
+ return this._wrap$(this._data.getSequence(sequenceId));
+ }
+ getSpatialImages$(imageIds) {
+ return this._wrap$(this._data.getSpatialImages(imageIds));
+ }
+ setAccessToken(accessToken) {
+ this._data.setAccessToken(accessToken);
+ }
+ _wrap$(promise) {
+ return Observable.create((subscriber) => {
+ promise.then((value) => {
+ subscriber.next(value);
+ subscriber.complete();
+ }, (error) => {
+ subscriber.error(error);
+ });
+ });
+ }
+}
+
+/**
+ * @class GraphService
+ *
+ * @classdesc Represents a service for graph operations.
+ */
+class GraphService {
+ /**
+ * Create a new graph service instance.
+ *
+ * @param {Graph} graph - Graph instance to be operated on.
+ */
+ constructor(graph) {
+ this._dataAdded$ = new Subject();
+ this._subscriptions = new SubscriptionHolder();
+ this._onDataAdded = (event) => {
+ this._graph$
+ .pipe(first(), mergeMap(graph => {
+ return graph.updateCells$(event.cellIds).pipe(tap(() => { graph.resetSpatialEdges(); }));
+ }))
+ .subscribe(cellId => { this._dataAdded$.next(cellId); });
+ };
+ const subs = this._subscriptions;
+ this._graph$ = concat(of(graph), graph.changed$).pipe(publishReplay(1), refCount());
+ subs.push(this._graph$.subscribe(() => { }));
+ this._graphMode = GraphMode.Spatial;
+ this._graphModeSubject$ = new Subject();
+ this._graphMode$ = this._graphModeSubject$.pipe(startWith(this._graphMode), publishReplay(1), refCount());
+ subs.push(this._graphMode$.subscribe(() => { }));
+ this._firstGraphSubjects$ = [];
+ this._initializeCacheSubscriptions = [];
+ this._sequenceSubscriptions = [];
+ this._spatialSubscriptions = [];
+ graph.api.data.on("datacreate", this._onDataAdded);
+ }
+ /**
+ * Get dataAdded$.
+ *
+ * @returns {Observable<string>} Observable emitting
+ * a cell id every time data has been added to a cell.
+ */
+ get dataAdded$() {
+ return this._dataAdded$;
+ }
+ /**
+ * Get filter observable.
+ *
+ * @desciption Emits the filter every time it has changed.
+ *
+ * @returns {Observable<FilterFunction>} Observable
+ * emitting the filter function every time it is set.
+ */
+ get filter$() {
+ return this._graph$.pipe(first(), mergeMap((graph) => {
+ return graph.filter$;
+ }));
+ }
+ /**
+ * Get graph mode observable.
+ *
+ * @description Emits the current graph mode.
+ *
+ * @returns {Observable<GraphMode>} Observable
+ * emitting the current graph mode when it changes.
+ */
+ get graphMode$() {
+ return this._graphMode$;
+ }
+ /**
+ * Cache full images in a bounding box.
+ *
+ * @description When called, the full properties of
+ * the image are retrieved. The image cache is not initialized
+ * for any new images retrieved and the image assets are not
+ * retrieved, {@link cacheImage$} needs to be called for caching
+ * assets.
+ *
+ * @param {LngLat} sw - South west corner of bounding box.
+ * @param {LngLat} ne - North east corner of bounding box.
+ * @return {Observable<Array<Image>>} Observable emitting a single item,
+ * the images of the bounding box, when they have all been retrieved.
+ * @throws {Error} Propagates any IO image caching errors to the caller.
+ */
+ cacheBoundingBox$(sw, ne) {
+ return this._graph$.pipe(first(), mergeMap((graph) => {
+ return graph.cacheBoundingBox$(sw, ne);
+ }));
+ }
+ /**
+ * Cache full images in a cell.
+ *
+ * @description When called, the full properties of
+ * the image are retrieved. The image cache is not initialized
+ * for any new images retrieved and the image assets are not
+ * retrieved, {@link cacheImage$} needs to be called for caching
+ * assets.
+ *
+ * @param {string} cellId - Id of the cell.
+ * @return {Observable<Array<Image>>} Observable emitting a single item,
+ * the images of the cell, when they have all been retrieved.
+ * @throws {Error} Propagates any IO image caching errors to the caller.
+ */
+ cacheCell$(cellId) {
+ return this._graph$.pipe(first(), mergeMap((graph) => {
+ return graph.cacheCell$(cellId);
+ }));
+ }
+ /**
+ * Cache a image in the graph and retrieve it.
+ *
+ * @description When called, the full properties of
+ * the image are retrieved and the image cache is initialized.
+ * After that the image assets are cached and the image
+ * is emitted to the observable when.
+ * In parallel to caching the image assets, the sequence and
+ * spatial edges of the image are cached. For this, the sequence
+ * of the image and the required tiles and spatial images are
+ * retrieved. The sequence and spatial edges may be set before
+ * or after the image is returned.
+ *
+ * @param {string} id - Id of the image to cache.
+ * @return {Observable<Image>} Observable emitting a single item,
+ * the image, when it has been retrieved and its assets are cached.
+ * @throws {Error} Propagates any IO image caching errors to the caller.
+ */
+ cacheImage$(id) {
+ const firstGraphSubject$ = new Subject();
+ this._firstGraphSubjects$.push(firstGraphSubject$);
+ const firstGraph$ = firstGraphSubject$.pipe(publishReplay(1), refCount());
+ const image$ = firstGraph$.pipe(map((graph) => {
+ return graph.getNode(id);
+ }), mergeMap((image) => {
+ return image.assetsCached ?
+ of(image) :
+ image.cacheAssets$();
+ }), publishReplay(1), refCount());
+ image$.subscribe(undefined, (error) => {
+ console.error(`Failed to cache image (${id}).`, error);
+ });
+ let initializeCacheSubscription;
+ initializeCacheSubscription = this._graph$.pipe(first(), mergeMap((graph) => {
+ if (graph.isCachingFull(id) || !graph.hasNode(id)) {
+ return graph.cacheFull$(id);
+ }
+ if (graph.isCachingFill(id) || !graph.getNode(id).complete) {
+ return graph.cacheFill$(id);
+ }
+ return of(graph);
+ }), tap((graph) => {
+ if (!graph.hasNode(id)) {
+ throw new GraphMapillaryError(`Failed to cache image (${id})`);
+ }
+ if (!graph.hasInitializedCache(id)) {
+ graph.initializeCache(id);
+ }
+ }), finalize(() => {
+ if (initializeCacheSubscription == null) {
+ return;
+ }
+ this._removeFromArray(initializeCacheSubscription, this._initializeCacheSubscriptions);
+ this._removeFromArray(firstGraphSubject$, this._firstGraphSubjects$);
+ }))
+ .subscribe((graph) => {
+ firstGraphSubject$.next(graph);
+ firstGraphSubject$.complete();
+ }, (error) => {
+ firstGraphSubject$.error(error);
+ });
+ if (!initializeCacheSubscription.closed) {
+ this._initializeCacheSubscriptions.push(initializeCacheSubscription);
+ }
+ const graphSequence$ = firstGraph$.pipe(catchError(() => {
+ return empty();
+ }), mergeMap((graph) => {
+ if (graph.isCachingNodeSequence(id) || !graph.hasNodeSequence(id)) {
+ return graph.cacheNodeSequence$(id);
+ }
+ return of(graph);
+ }), publishReplay(1), refCount());
+ let sequenceSubscription;
+ sequenceSubscription = graphSequence$.pipe(tap((graph) => {
+ if (!graph.getNode(id).sequenceEdges.cached) {
+ graph.cacheSequenceEdges(id);
+ }
+ }), finalize(() => {
+ if (sequenceSubscription == null) {
+ return;
+ }
+ this._removeFromArray(sequenceSubscription, this._sequenceSubscriptions);
+ }))
+ .subscribe(() => { return; }, (error) => {
+ console.error(`Failed to cache sequence edges (${id}).`, error);
+ });
+ if (!sequenceSubscription.closed) {
+ this._sequenceSubscriptions.push(sequenceSubscription);
+ }
+ if (this._graphMode === GraphMode.Spatial) {
+ let spatialSubscription;
+ spatialSubscription = firstGraph$.pipe(catchError(() => {
+ return empty();
+ }), expand((graph) => {
+ if (graph.hasTiles(id)) {
+ return empty();
+ }
+ return from(graph.cacheTiles$(id)).pipe(mergeMap((graph$) => {
+ return graph$.pipe(mergeMap((g) => {
+ if (g.isCachingTiles(id)) {
+ return empty();
+ }
+ return of(g);
+ }), catchError((error) => {
+ console.error(`Failed to cache tile data (${id}).`, error);
+ return empty();
+ }));
+ }));
+ }), takeLast(1), mergeMap((graph) => {
+ if (graph.hasSpatialArea(id)) {
+ return of(graph);
+ }
+ return from(graph.cacheSpatialArea$(id)).pipe(mergeMap((graph$) => {
+ return graph$.pipe(catchError((error) => {
+ console.error(`Failed to cache spatial images (${id}).`, error);
+ return empty();
+ }));
+ }));
+ }), takeLast(1), mergeMap((graph) => {
+ return graph.hasNodeSequence(id) ?
+ of(graph) :
+ graph.cacheNodeSequence$(id);
+ }), tap((graph) => {
+ if (!graph.getNode(id).spatialEdges.cached) {
+ graph.cacheSpatialEdges(id);
+ }
+ }), finalize(() => {
+ if (spatialSubscription == null) {
+ return;
+ }
+ this._removeFromArray(spatialSubscription, this._spatialSubscriptions);
+ }))
+ .subscribe(() => { return; }, (error) => {
+ const message = `Failed to cache spatial edges (${id}).`;
+ console.error(message, error);
+ });
+ if (!spatialSubscription.closed) {
+ this._spatialSubscriptions.push(spatialSubscription);
+ }
+ }
+ return image$.pipe(first((image) => {
+ return image.assetsCached;
+ }));
+ }
+ /**
+ * Cache a sequence in the graph and retrieve it.
+ *
+ * @param {string} sequenceId - Sequence id.
+ * @returns {Observable<Sequence>} Observable emitting a single item,
+ * the sequence, when it has been retrieved and its assets are cached.
+ * @throws {Error} Propagates any IO image caching errors to the caller.
+ */
+ cacheSequence$(sequenceId) {
+ return this._graph$.pipe(first(), mergeMap((graph) => {
+ if (graph.isCachingSequence(sequenceId) || !graph.hasSequence(sequenceId)) {
+ return graph.cacheSequence$(sequenceId);
+ }
+ return of(graph);
+ }), map((graph) => {
+ return graph.getSequence(sequenceId);
+ }));
+ }
+ /**
+ * Cache a sequence and its images in the graph and retrieve the sequence.
+ *
+ * @description Caches a sequence and its assets are cached and
+ * retrieves all images belonging to the sequence. The image assets
+ * or edges will not be cached.
+ *
+ * @param {string} sequenceId - Sequence id.
+ * @param {string} referenceImageId - Id of image to use as reference
+ * for optimized caching.
+ * @returns {Observable<Sequence>} Observable emitting a single item,
+ * the sequence, when it has been retrieved, its assets are cached and
+ * all images belonging to the sequence has been retrieved.
+ * @throws {Error} Propagates any IO image caching errors to the caller.
+ */
+ cacheSequenceImages$(sequenceId, referenceImageId) {
+ return this._graph$.pipe(first(), mergeMap((graph) => {
+ if (graph.isCachingSequence(sequenceId) || !graph.hasSequence(sequenceId)) {
+ return graph.cacheSequence$(sequenceId);
+ }
+ return of(graph);
+ }), mergeMap((graph) => {
+ if (graph.isCachingSequenceNodes(sequenceId) || !graph.hasSequenceNodes(sequenceId)) {
+ return graph.cacheSequenceNodes$(sequenceId, referenceImageId);
+ }
+ return of(graph);
+ }), map((graph) => {
+ return graph.getSequence(sequenceId);
+ }));
+ }
+ /**
+ * Dispose the graph service and its children.
+ */
+ dispose() {
+ this._graph$
+ .pipe(first())
+ .subscribe((graph) => { graph.unsubscribe(); });
+ this._subscriptions.unsubscribe();
+ }
+ /**
+ * Set a spatial edge filter on the graph.
+ *
+ * @description Resets the spatial edges of all cached images.
+ *
+ * @param {FilterExpression} filter - Filter expression to be applied.
+ * @return {Observable<Graph>} Observable emitting a single item,
+ * the graph, when the spatial edges have been reset.
+ */
+ setFilter$(filter) {
+ this._resetSubscriptions(this._spatialSubscriptions);
+ return this._graph$.pipe(first(), tap((graph) => {
+ graph.resetSpatialEdges();
+ graph.setFilter(filter);
+ }), map(() => {
+ return undefined;
+ }));
+ }
+ /**
+ * Set the graph mode.
+ *
+ * @description If graph mode is set to spatial, caching
+ * is performed with emphasis on spatial edges. If graph
+ * mode is set to sequence no tile data is requested and
+ * no spatial edges are computed.
+ *
+ * When setting graph mode to sequence all spatial
+ * subscriptions are aborted.
+ *
+ * @param {GraphMode} mode - Graph mode to set.
+ */
+ setGraphMode(mode) {
+ if (this._graphMode === mode) {
+ return;
+ }
+ if (mode === GraphMode.Sequence) {
+ this._resetSubscriptions(this._spatialSubscriptions);
+ }
+ this._graphMode = mode;
+ this._graphModeSubject$.next(this._graphMode);
+ }
+ /**
+ * Reset the graph.
+ *
+ * @description Resets the graph but keeps the images of the
+ * supplied ids.
+ *
+ * @param {Array<string>} keepIds - Ids of images to keep in graph.
+ * @return {Observable<Image>} Observable emitting a single item,
+ * the graph, when it has been reset.
+ */
+ reset$(keepIds) {
+ this._abortSubjects(this._firstGraphSubjects$);
+ this._resetSubscriptions(this._initializeCacheSubscriptions);
+ this._resetSubscriptions(this._sequenceSubscriptions);
+ this._resetSubscriptions(this._spatialSubscriptions);
+ return this._graph$.pipe(first(), tap((graph) => {
+ graph.reset(keepIds);
+ }), map(() => {
+ return undefined;
+ }));
+ }
+ /**
+ * Uncache the graph.
+ *
+ * @description Uncaches the graph by removing tiles, images and
+ * sequences. Keeps the images of the supplied ids and the tiles
+ * related to those images.
+ *
+ * @param {Array<string>} keepIds - Ids of images to keep in graph.
+ * @param {Array<string>} keepCellIds - Ids of cells to keep in graph.
+ * @param {string} keepSequenceId - Optional id of sequence
+ * for which the belonging images should not be disposed or
+ * removed from the graph. These images may still be uncached if
+ * not specified in keep ids param.
+ * @return {Observable<Graph>} Observable emitting a single item,
+ * the graph, when the graph has been uncached.
+ */
+ uncache$(keepIds, keepCellIds, keepSequenceId) {
+ return this._graph$.pipe(first(), tap((graph) => {
+ graph.uncache(keepIds, keepCellIds, keepSequenceId);
+ }), map(() => {
+ return undefined;
+ }));
+ }
+ _abortSubjects(subjects) {
+ for (const subject of subjects.slice()) {
+ this._removeFromArray(subject, subjects);
+ subject.error(new Error("Cache image request was aborted."));
+ }
+ }
+ _removeFromArray(object, objects) {
+ const index = objects.indexOf(object);
+ if (index !== -1) {
+ objects.splice(index, 1);
+ }
+ }
+ _resetSubscriptions(subscriptions) {
+ for (const subscription of subscriptions.slice()) {
+ this._removeFromArray(subscription, subscriptions);
+ if (!subscription.closed) {
+ subscription.unsubscribe();
+ }
+ }
+ }
+}
+
+class FrameGenerator {
+ constructor(root) {
+ if (root.requestAnimationFrame) {
+ this._cancelAnimationFrame = root.cancelAnimationFrame.bind(root);
+ this._requestAnimationFrame = root.requestAnimationFrame.bind(root);
+ }
+ else if (root.mozRequestAnimationFrame) {
+ this._cancelAnimationFrame = root.mozCancelAnimationFrame.bind(root);
+ this._requestAnimationFrame = root.mozRequestAnimationFrame.bind(root);
+ }
+ else if (root.webkitRequestAnimationFrame) {
+ this._cancelAnimationFrame = root.webkitCancelAnimationFrame.bind(root);
+ this._requestAnimationFrame = root.webkitRequestAnimationFrame.bind(root);
+ }
+ else if (root.msRequestAnimationFrame) {
+ this._cancelAnimationFrame = root.msCancelAnimationFrame.bind(root);
+ this._requestAnimationFrame = root.msRequestAnimationFrame.bind(root);
+ }
+ else if (root.oRequestAnimationFrame) {
+ this._cancelAnimationFrame = root.oCancelAnimationFrame.bind(root);
+ this._requestAnimationFrame = root.oRequestAnimationFrame.bind(root);
+ }
+ else {
+ this._cancelAnimationFrame = root.clearTimeout.bind(root);
+ this._requestAnimationFrame = (cb) => { return root.setTimeout(cb, 1000 / 60); };
+ }
+ }
+ get cancelAnimationFrame() {
+ return this._cancelAnimationFrame;
+ }
+ get requestAnimationFrame() {
+ return this._requestAnimationFrame;
+ }
+}
+
+class CustomState extends StateBase {
+ constructor(state) {
+ super(state);
+ }
+ setViewMatrix(viewMatrix) {
+ const viewMatrixInverse = new Matrix4()
+ .fromArray(viewMatrix)
+ .invert();
+ const me = viewMatrixInverse.elements;
+ const eye = new Vector3(me[12], me[13], me[14]);
+ const forward = new Vector3(-me[8], -me[9], -me[10]);
+ const up = new Vector3(me[4], me[5], me[6]);
+ const camera = this._camera;
+ camera.position.copy(eye);
+ camera.lookat.copy(eye
+ .clone()
+ .add(forward));
+ camera.up.copy(up);
+ const focal = 0.5 / Math.tan(Math.PI / 3);
+ camera.focal = focal;
+ }
+}
+
+class EarthState extends StateBase {
+ constructor(state) {
+ super(state);
+ const eye = this._camera.position.clone();
+ const forward = this._camera.lookat
+ .clone()
+ .sub(eye)
+ .normalize();
+ const xy = Math.sqrt(forward.x * forward.x + forward.y * forward.y);
+ const angle = Math.atan2(forward.z, xy);
+ const lookat = new Vector3();
+ if (angle > -Math.PI / 45) {
+ lookat.copy(eye);
+ eye.add(new Vector3(forward.x, forward.y, 0)
+ .multiplyScalar(-50));
+ eye.z = 30;
+ }
+ else {
+ // Target a point on invented ground and keep forward direction
+ const l0 = eye.clone();
+ const n = new Vector3(0, 0, 1);
+ const p0 = new Vector3(0, 0, -2);
+ const d = new Vector3().subVectors(p0, l0).dot(n) / forward.dot(n);
+ const maxDistance = 10000;
+ const intersection = l0
+ .clone()
+ .add(forward.
+ clone()
+ .multiplyScalar(Math.min(maxDistance, d)));
+ lookat.copy(intersection);
+ const t = eye
+ .clone()
+ .sub(intersection)
+ .normalize();
+ eye.copy(intersection.add(t.multiplyScalar(Math.max(50, t.length()))));
+ }
+ this._camera.position.copy(eye);
+ this._camera.lookat.copy(lookat);
+ this._camera.up.set(0, 0, 1);
+ }
+ dolly(delta) {
+ const camera = this._camera;
+ const offset = camera.position
+ .clone()
+ .sub(camera.lookat);
+ const length = offset.length();
+ const scaled = length * Math.pow(2, -delta);
+ const clipped = Math.max(1, Math.min(scaled, 4000));
+ offset.normalize();
+ offset.multiplyScalar(clipped);
+ camera.position
+ .copy(camera.lookat)
+ .add(offset);
+ }
+ orbit(rotation) {
+ const camera = this._camera;
+ const q = new Quaternion()
+ .setFromUnitVectors(camera.up, new Vector3(0, 0, 1));
+ const qInverse = q
+ .clone()
+ .invert();
+ const offset = camera.position
+ .clone()
+ .sub(camera.lookat);
+ offset.applyQuaternion(q);
+ const length = offset.length();
+ let phi = Math.atan2(offset.y, offset.x);
+ phi += rotation.phi;
+ let theta = Math.atan2(Math.sqrt(offset.x * offset.x + offset.y * offset.y), offset.z);
+ theta += rotation.theta;
+ const threshold = Math.PI / 36;
+ theta = Math.max(threshold, Math.min(Math.PI / 2 - threshold, theta));
+ offset.x = Math.sin(theta) * Math.cos(phi);
+ offset.y = Math.sin(theta) * Math.sin(phi);
+ offset.z = Math.cos(theta);
+ offset.applyQuaternion(qInverse);
+ camera.position
+ .copy(camera.lookat)
+ .add(offset.multiplyScalar(length));
+ }
+ truck(direction) {
+ const camera = this._camera;
+ camera.position
+ .add(new Vector3().fromArray(direction));
+ camera.lookat
+ .add(new Vector3().fromArray(direction));
+ }
+ update() { }
+}
+
+class InteractiveWaitingState extends InteractiveStateBase {
+ constructor(state) {
+ super(state);
+ this._adjustCameras();
+ this._motionless = this._motionlessTransition();
+ }
+ prepend(images) {
+ super.prepend(images);
+ this._motionless = this._motionlessTransition();
+ }
+ set(images) {
+ super.set(images);
+ this._motionless = this._motionlessTransition();
+ }
+ move(delta) {
+ this._alpha = Math.max(0, Math.min(1, this._alpha + delta));
+ }
+ moveTo(position) {
+ this._alpha = Math.max(0, Math.min(1, position));
+ }
+ update(fps) {
+ this._updateRotation();
+ if (!this._rotationDelta.isZero) {
+ this._applyRotation(this._rotationDelta, this._previousCamera);
+ this._applyRotation(this._rotationDelta, this._currentCamera);
+ }
+ this._updateRotationBasic();
+ if (this._basicRotation[0] !== 0 || this._basicRotation[1] !== 0) {
+ this._applyRotationBasic(this._basicRotation);
+ }
+ let animationSpeed = this._animationSpeed * (60 / fps);
+ this._updateZoom(animationSpeed);
+ this._updateLookat(animationSpeed);
+ this._camera.lerpCameras(this._previousCamera, this._currentCamera, this.alpha);
+ }
+ _getAlpha() {
+ return this._motionless ? Math.round(this._alpha) : this._alpha;
+ }
+ _setCurrentCamera() {
+ super._setCurrentCamera();
+ this._adjustCameras();
+ }
+ _adjustCameras() {
+ if (this._previousImage == null) {
+ return;
+ }
+ if (isSpherical(this._currentImage.cameraType)) {
+ let lookat = this._camera.lookat.clone().sub(this._camera.position);
+ this._currentCamera.lookat.copy(lookat.clone().add(this._currentCamera.position));
+ }
+ if (isSpherical(this._previousImage.cameraType)) {
+ let lookat = this._currentCamera.lookat.clone().sub(this._currentCamera.position);
+ this._previousCamera.lookat.copy(lookat.clone().add(this._previousCamera.position));
+ }
+ }
+}
+
+class WaitingState extends StateBase {
+ constructor(state) {
+ super(state);
+ this._zoom = 0;
+ this._adjustCameras();
+ this._motionless = this._motionlessTransition();
+ }
+ prepend(images) {
+ super.prepend(images);
+ this._motionless = this._motionlessTransition();
+ }
+ set(images) {
+ super.set(images);
+ this._motionless = this._motionlessTransition();
+ }
+ move(delta) {
+ this._alpha = Math.max(0, Math.min(1, this._alpha + delta));
+ }
+ moveTo(position) {
+ this._alpha = Math.max(0, Math.min(1, position));
+ }
+ update(fps) {
+ this._camera.lerpCameras(this._previousCamera, this._currentCamera, this.alpha);
+ }
+ _getAlpha() {
+ return this._motionless ? Math.round(this._alpha) : this._alpha;
+ }
+ _setCurrentCamera() {
+ super._setCurrentCamera();
+ this._adjustCameras();
+ }
+ _adjustCameras() {
+ if (this._previousImage == null) {
+ return;
+ }
+ if (isSpherical(this._currentImage.cameraType)) {
+ let lookat = this._camera.lookat.clone().sub(this._camera.position);
+ this._currentCamera.lookat.copy(lookat.clone().add(this._currentCamera.position));
+ }
+ if (isSpherical(this._previousImage.cameraType)) {
+ let lookat = this._currentCamera.lookat.clone().sub(this._currentCamera.position);
+ this._previousCamera.lookat.copy(lookat.clone().add(this._previousCamera.position));
+ }
+ }
+}
+
+class StateTransitionMatrix {
+ constructor() {
+ const custom = State[State.Custom];
+ const earth = State[State.Earth];
+ const traverse = State[State.Traversing];
+ const wait = State[State.Waiting];
+ const waitInteractively = State[State.WaitingInteractively];
+ this._creators = new Map();
+ const creator = this._creators;
+ creator.set(custom, CustomState);
+ creator.set(earth, EarthState);
+ creator.set(traverse, TraversingState);
+ creator.set(wait, WaitingState);
+ creator.set(waitInteractively, InteractiveWaitingState);
+ this._transitions = new Map();
+ const transitions = this._transitions;
+ transitions.set(custom, [earth, traverse]);
+ transitions.set(earth, [custom, traverse]);
+ transitions.set(traverse, [custom, earth, wait, waitInteractively]);
+ transitions.set(wait, [traverse, waitInteractively]);
+ transitions.set(waitInteractively, [traverse, wait]);
+ }
+ getState(state) {
+ if (state instanceof CustomState) {
+ return State.Custom;
+ }
+ else if (state instanceof EarthState) {
+ return State.Earth;
+ }
+ else if (state instanceof TraversingState) {
+ return State.Traversing;
+ }
+ else if (state instanceof WaitingState) {
+ return State.Waiting;
+ }
+ else if (state instanceof InteractiveWaitingState) {
+ return State.WaitingInteractively;
+ }
+ throw new Error("Invalid state instance");
+ }
+ generate(state, options) {
+ const concreteState = this._creators.get(State[state]);
+ return new concreteState(options);
+ }
+ transition(state, to) {
+ if (!this.validate(state, to)) {
+ throw new Error("Invalid transition");
+ }
+ return this.generate(to, state);
+ }
+ validate(state, to) {
+ const source = State[this.getState(state)];
+ const target = State[to];
+ const transitions = this._transitions;
+ return transitions.has(source) &&
+ transitions.get(source).includes(target);
+ }
+}
+
+class StateContext {
+ constructor(state, transitionMode) {
+ this._transitions = new StateTransitionMatrix();
+ this._state = this._transitions.generate(state, {
+ alpha: 1,
+ camera: new Camera(),
+ currentIndex: -1,
+ reference: { alt: 0, lat: 0, lng: 0 },
+ trajectory: [],
+ transitionMode: transitionMode == null ? TransitionMode.Default : transitionMode,
+ zoom: 0,
+ });
+ }
+ get state() {
+ return this._transitions.getState(this._state);
+ }
+ get reference() {
+ return this._state.reference;
+ }
+ get alpha() {
+ return this._state.alpha;
+ }
+ get camera() {
+ return this._state.camera;
+ }
+ get zoom() {
+ return this._state.zoom;
+ }
+ get currentImage() {
+ return this._state.currentImage;
+ }
+ get previousImage() {
+ return this._state.previousImage;
+ }
+ get currentCamera() {
+ return this._state.currentCamera;
+ }
+ get currentTransform() {
+ return this._state.currentTransform;
+ }
+ get previousTransform() {
+ return this._state.previousTransform;
+ }
+ get trajectory() {
+ return this._state.trajectory;
+ }
+ get currentIndex() {
+ return this._state.currentIndex;
+ }
+ get lastImage() {
+ return this._state.trajectory[this._state.trajectory.length - 1];
+ }
+ get imagesAhead() {
+ return this._state.trajectory.length - 1 - this._state.currentIndex;
+ }
+ get motionless() {
+ return this._state.motionless;
+ }
+ custom() {
+ this._transition(State.Custom);
+ }
+ earth() {
+ this._transition(State.Earth);
+ }
+ traverse() {
+ this._transition(State.Traversing);
+ }
+ wait() {
+ this._transition(State.Waiting);
+ }
+ waitInteractively() {
+ this._transition(State.WaitingInteractively);
+ }
+ getCenter() {
+ return this._state.getCenter();
+ }
+ setCenter(center) {
+ this._state.setCenter(center);
+ }
+ setZoom(zoom) {
+ this._state.setZoom(zoom);
+ }
+ update(fps) {
+ this._state.update(fps);
+ }
+ append(images) {
+ this._state.append(images);
+ }
+ prepend(images) {
+ this._state.prepend(images);
+ }
+ remove(n) {
+ this._state.remove(n);
+ }
+ clear() {
+ this._state.clear();
+ }
+ clearPrior() {
+ this._state.clearPrior();
+ }
+ cut() {
+ this._state.cut();
+ }
+ set(images) {
+ this._state.set(images);
+ }
+ setViewMatrix(matrix) {
+ this._state.setViewMatrix(matrix);
+ }
+ rotate(delta) {
+ this._state.rotate(delta);
+ }
+ rotateUnbounded(delta) {
+ this._state.rotateUnbounded(delta);
+ }
+ rotateWithoutInertia(delta) {
+ this._state.rotateWithoutInertia(delta);
+ }
+ rotateBasic(basicRotation) {
+ this._state.rotateBasic(basicRotation);
+ }
+ rotateBasicUnbounded(basicRotation) {
+ this._state.rotateBasicUnbounded(basicRotation);
+ }
+ rotateBasicWithoutInertia(basicRotation) {
+ this._state.rotateBasicWithoutInertia(basicRotation);
+ }
+ rotateToBasic(basic) {
+ this._state.rotateToBasic(basic);
+ }
+ move(delta) {
+ this._state.move(delta);
+ }
+ moveTo(delta) {
+ this._state.moveTo(delta);
+ }
+ zoomIn(delta, reference) {
+ this._state.zoomIn(delta, reference);
+ }
+ setSpeed(speed) {
+ this._state.setSpeed(speed);
+ }
+ setTransitionMode(mode) {
+ this._state.setTransitionMode(mode);
+ }
+ dolly(delta) {
+ this._state.dolly(delta);
+ }
+ orbit(rotation) {
+ this._state.orbit(rotation);
+ }
+ truck(direction) {
+ this._state.truck(direction);
+ }
+ _transition(to) {
+ if (!this._transitions.validate(this._state, to)) {
+ const from = this._transitions.getState(this._state);
+ console.warn(`Transition not valid (${State[from]} - ${State[to]})`);
+ return;
+ }
+ const state = this._transitions.transition(this._state, to);
+ this._state = state;
+ }
+}
+
+class StateService {
+ constructor(initialState, transitionMode) {
+ this._appendImage$ = new Subject();
+ this._subscriptions = new SubscriptionHolder();
+ const subs = this._subscriptions;
+ this._start$ = new Subject();
+ this._frame$ = new Subject();
+ this._fpsSampleRate = 30;
+ this._contextOperation$ = new BehaviorSubject((context) => {
+ return context;
+ });
+ this._context$ = this._contextOperation$.pipe(scan((context, operation) => {
+ return operation(context);
+ }, new StateContext(initialState, transitionMode)), publishReplay(1), refCount());
+ this._state$ = this._context$.pipe(map((context) => {
+ return context.state;
+ }), distinctUntilChanged(), publishReplay(1), refCount());
+ this._fps$ = this._start$.pipe(switchMap(() => {
+ return this._frame$.pipe(bufferCount(1, this._fpsSampleRate), map(() => {
+ return new Date().getTime();
+ }), pairwise(), map((times) => {
+ return Math.max(20, 1000 * this._fpsSampleRate / (times[1] - times[0]));
+ }), startWith(60));
+ }), share());
+ this._currentState$ = this._frame$.pipe(withLatestFrom(this._fps$, this._context$, (frameId, fps, context) => {
+ return [frameId, fps, context];
+ }), filter((fc) => {
+ return fc[2].currentImage != null;
+ }), tap((fc) => {
+ fc[2].update(fc[1]);
+ }), map((fc) => {
+ return { fps: fc[1], id: fc[0], state: fc[2] };
+ }), share());
+ this._lastState$ = this._currentState$.pipe(publishReplay(1), refCount());
+ let imageChanged$ = this._currentState$.pipe(distinctUntilChanged(undefined, (f) => {
+ return f.state.currentImage.id;
+ }), publishReplay(1), refCount());
+ let imageChangedSubject$ = new Subject();
+ subs.push(imageChanged$
+ .subscribe(imageChangedSubject$));
+ this._currentId$ = new BehaviorSubject(null);
+ subs.push(imageChangedSubject$.pipe(map((f) => {
+ return f.state.currentImage.id;
+ }))
+ .subscribe(this._currentId$));
+ this._currentImage$ = imageChangedSubject$.pipe(map((f) => {
+ return f.state.currentImage;
+ }), publishReplay(1), refCount());
+ this._currentCamera$ = imageChangedSubject$.pipe(map((f) => {
+ return f.state.currentCamera;
+ }), publishReplay(1), refCount());
+ this._currentTransform$ = imageChangedSubject$.pipe(map((f) => {
+ return f.state.currentTransform;
+ }), publishReplay(1), refCount());
+ this._reference$ = imageChangedSubject$.pipe(map((f) => {
+ return f.state.reference;
+ }), distinctUntilChanged((r1, r2) => {
+ return r1.lat === r2.lat && r1.lng === r2.lng;
+ }, (reference) => {
+ return { lat: reference.lat, lng: reference.lng };
+ }), publishReplay(1), refCount());
+ this._currentImageExternal$ = imageChanged$.pipe(map((f) => {
+ return f.state.currentImage;
+ }), publishReplay(1), refCount());
+ subs.push(this._appendImage$.pipe(map((image) => {
+ return (context) => {
+ context.append([image]);
+ return context;
+ };
+ }))
+ .subscribe(this._contextOperation$));
+ this._inMotionOperation$ = new Subject();
+ subs.push(imageChanged$.pipe(map(() => {
+ return true;
+ }))
+ .subscribe(this._inMotionOperation$));
+ subs.push(this._inMotionOperation$.pipe(distinctUntilChanged(), filter((moving) => {
+ return moving;
+ }), switchMap(() => {
+ return this._currentState$.pipe(filter((frame) => {
+ return frame.state.imagesAhead === 0;
+ }), map((frame) => {
+ return [frame.state.camera.clone(), frame.state.zoom];
+ }), pairwise(), map((pair) => {
+ let c1 = pair[0][0];
+ let c2 = pair[1][0];
+ let z1 = pair[0][1];
+ let z2 = pair[1][1];
+ return c1.diff(c2) > 1e-5 || Math.abs(z1 - z2) > 1e-5;
+ }), first((changed) => {
+ return !changed;
+ }));
+ }))
+ .subscribe(this._inMotionOperation$));
+ this._inMotion$ = this._inMotionOperation$.pipe(distinctUntilChanged(), publishReplay(1), refCount());
+ this._inTranslationOperation$ = new Subject();
+ subs.push(imageChanged$.pipe(map(() => {
+ return true;
+ }))
+ .subscribe(this._inTranslationOperation$));
+ subs.push(this._inTranslationOperation$.pipe(distinctUntilChanged(), filter((inTranslation) => {
+ return inTranslation;
+ }), switchMap(() => {
+ return this._currentState$.pipe(filter((frame) => {
+ return frame.state.imagesAhead === 0;
+ }), map((frame) => {
+ return frame.state.camera.position.clone();
+ }), pairwise(), map((pair) => {
+ return pair[0].distanceToSquared(pair[1]) !== 0;
+ }), first((changed) => {
+ return !changed;
+ }));
+ }))
+ .subscribe(this._inTranslationOperation$));
+ this._inTranslation$ = this._inTranslationOperation$.pipe(distinctUntilChanged(), publishReplay(1), refCount());
+ subs.push(this._state$.subscribe(() => { }));
+ subs.push(this._currentImage$.subscribe(() => { }));
+ subs.push(this._currentCamera$.subscribe(() => { }));
+ subs.push(this._currentTransform$.subscribe(() => { }));
+ subs.push(this._reference$.subscribe(() => { }));
+ subs.push(this._currentImageExternal$.subscribe(() => { }));
+ subs.push(this._lastState$.subscribe(() => { }));
+ subs.push(this._inMotion$.subscribe(() => { }));
+ subs.push(this._inTranslation$.subscribe(() => { }));
+ this._frameId = null;
+ this._frameGenerator = new FrameGenerator(window);
+ }
+ get currentState$() {
+ return this._currentState$;
+ }
+ get currentImage$() {
+ return this._currentImage$;
+ }
+ get currentId$() {
+ return this._currentId$;
+ }
+ get currentImageExternal$() {
+ return this._currentImageExternal$;
+ }
+ get currentCamera$() {
+ return this._currentCamera$;
+ }
+ get currentTransform$() {
+ return this._currentTransform$;
+ }
+ get state$() {
+ return this._state$;
+ }
+ get reference$() {
+ return this._reference$;
+ }
+ get inMotion$() {
+ return this._inMotion$;
+ }
+ get inTranslation$() {
+ return this._inTranslation$;
+ }
+ get appendImage$() {
+ return this._appendImage$;
+ }
+ dispose() {
+ this.stop();
+ this._subscriptions.unsubscribe();
+ }
+ custom() {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => {
+ context.custom();
+ });
+ }
+ earth() {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.earth(); });
+ }
+ traverse() {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.traverse(); });
+ }
+ wait() {
+ this._invokeContextOperation((context) => { context.wait(); });
+ }
+ waitInteractively() {
+ this._invokeContextOperation((context) => { context.waitInteractively(); });
+ }
+ appendImagess(images) {
+ this._invokeContextOperation((context) => { context.append(images); });
+ }
+ prependImages(images) {
+ this._invokeContextOperation((context) => { context.prepend(images); });
+ }
+ removeImages(n) {
+ this._invokeContextOperation((context) => { context.remove(n); });
+ }
+ clearImages() {
+ this._invokeContextOperation((context) => { context.clear(); });
+ }
+ clearPriorImages() {
+ this._invokeContextOperation((context) => { context.clearPrior(); });
+ }
+ cutImages() {
+ this._invokeContextOperation((context) => { context.cut(); });
+ }
+ setImages(images) {
+ this._invokeContextOperation((context) => { context.set(images); });
+ }
+ setViewMatrix(matrix) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.setViewMatrix(matrix); });
+ }
+ rotate(delta) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.rotate(delta); });
+ }
+ rotateUnbounded(delta) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.rotateUnbounded(delta); });
+ }
+ rotateWithoutInertia(delta) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.rotateWithoutInertia(delta); });
+ }
+ rotateBasic(basicRotation) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.rotateBasic(basicRotation); });
+ }
+ rotateBasicUnbounded(basicRotation) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.rotateBasicUnbounded(basicRotation); });
+ }
+ rotateBasicWithoutInertia(basicRotation) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.rotateBasicWithoutInertia(basicRotation); });
+ }
+ rotateToBasic(basic) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.rotateToBasic(basic); });
+ }
+ move(delta) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.move(delta); });
+ }
+ moveTo(position) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.moveTo(position); });
+ }
+ dolly(delta) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.dolly(delta); });
+ }
+ orbit(rotation) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.orbit(rotation); });
+ }
+ truck(direction) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.truck(direction); });
+ }
+ /**
+ * Change zoom level while keeping the reference point position approximately static.
+ *
+ * @parameter {number} delta - Change in zoom level.
+ * @parameter {Array<number>} reference - Reference point in basic coordinates.
+ */
+ zoomIn(delta, reference) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.zoomIn(delta, reference); });
+ }
+ getCenter() {
+ return this._lastState$.pipe(first(), map((frame) => {
+ return frame.state.getCenter();
+ }));
+ }
+ getZoom() {
+ return this._lastState$.pipe(first(), map((frame) => {
+ return frame.state.zoom;
+ }));
+ }
+ setCenter(center) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.setCenter(center); });
+ }
+ setSpeed(speed) {
+ this._invokeContextOperation((context) => { context.setSpeed(speed); });
+ }
+ setTransitionMode(mode) {
+ this._invokeContextOperation((context) => { context.setTransitionMode(mode); });
+ }
+ setZoom(zoom) {
+ this._inMotionOperation$.next(true);
+ this._invokeContextOperation((context) => { context.setZoom(zoom); });
+ }
+ start() {
+ if (this._frameId == null) {
+ this._start$.next(null);
+ this._frameId = this._frameGenerator.requestAnimationFrame(this._frame.bind(this));
+ this._frame$.next(this._frameId);
+ }
+ }
+ stop() {
+ if (this._frameId != null) {
+ this._frameGenerator.cancelAnimationFrame(this._frameId);
+ this._frameId = null;
+ }
+ }
+ _invokeContextOperation(action) {
+ this._contextOperation$
+ .next((context) => {
+ action(context);
+ return context;
+ });
+ }
+ _frame() {
+ this._frameId = this._frameGenerator.requestAnimationFrame(this._frame.bind(this));
+ this._frame$.next(this._frameId);
+ }
+}
+
+function cameraControlsToState(cameraControls) {
+ switch (cameraControls) {
+ case CameraControls.Custom:
+ return State.Custom;
+ case CameraControls.Earth:
+ return State.Earth;
+ case CameraControls.Street:
+ return State.Traversing;
+ default:
+ return null;
+ }
+}
+
+class Navigator {
+ constructor(options, api, graphService, loadingService, stateService, cacheService, playService, panService) {
+ var _a;
+ if (api) {
+ this._api = api;
+ }
+ else if (options.dataProvider) {
+ if (options.dataProvider instanceof DataProviderBase) {
+ this._api = new APIWrapper(options.dataProvider);
+ }
+ else {
+ throw new Error("Incorrect type: 'dataProvider' must extend the DataProviderBase class.");
+ }
+ }
+ else {
+ this._api = new APIWrapper(new GraphDataProvider({
+ accessToken: options.accessToken,
+ }));
+ }
+ this._graphService = graphService !== null && graphService !== void 0 ? graphService : new GraphService(new Graph(this.api));
+ this._loadingName = "navigator";
+ this._loadingService = loadingService !== null && loadingService !== void 0 ? loadingService : new LoadingService();
+ const cameraControls = (_a = options.cameraControls) !== null && _a !== void 0 ? _a : CameraControls.Street;
+ this._stateService = stateService !== null && stateService !== void 0 ? stateService : new StateService(cameraControlsToState(cameraControls), options.transitionMode);
+ this._cacheService = cacheService !== null && cacheService !== void 0 ? cacheService : new CacheService(this._graphService, this._stateService, this._api);
+ this._playService = playService !== null && playService !== void 0 ? playService : new PlayService(this._graphService, this._stateService);
+ this._panService = panService !== null && panService !== void 0 ? panService : new PanService(this._graphService, this._stateService, options.combinedPanning);
+ this._idRequested$ = new BehaviorSubject(null);
+ this._movedToId$ = new BehaviorSubject(null);
+ this._request$ = null;
+ this._requestSubscription = null;
+ this._imageRequestSubscription = null;
+ }
+ get api() {
+ return this._api;
+ }
+ get cacheService() {
+ return this._cacheService;
+ }
+ get graphService() {
+ return this._graphService;
+ }
+ get loadingService() {
+ return this._loadingService;
+ }
+ get movedToId$() {
+ return this._movedToId$;
+ }
+ get panService() {
+ return this._panService;
+ }
+ get playService() {
+ return this._playService;
+ }
+ get stateService() {
+ return this._stateService;
+ }
+ dispose() {
+ this._abortRequest("viewer removed");
+ this._cacheService.stop();
+ this._graphService.dispose();
+ this._panService.dispose();
+ this._playService.dispose();
+ this._stateService.dispose();
+ }
+ moveTo$(id) {
+ this._abortRequest(`to id ${id}`);
+ this._loadingService.startLoading(this._loadingName);
+ const image$ = this._moveTo$(id);
+ return this._makeRequest$(image$);
+ }
+ moveDir$(direction) {
+ this._abortRequest(`in dir ${NavigationDirection[direction]}`);
+ this._loadingService.startLoading(this._loadingName);
+ const image$ = this.stateService.currentImage$.pipe(first(), mergeMap((image) => {
+ return ([NavigationDirection.Next, NavigationDirection.Prev].indexOf(direction) > -1 ?
+ image.sequenceEdges$ :
+ image.spatialEdges$).pipe(first(), map((status) => {
+ for (let edge of status.edges) {
+ if (edge.data.direction === direction) {
+ return edge.target;
+ }
+ }
+ return null;
+ }));
+ }), mergeMap((directionId) => {
+ if (directionId == null) {
+ this._loadingService.stopLoading(this._loadingName);
+ return throwError(new Error(`Direction (${direction}) does not exist for current image.`));
+ }
+ return this._moveTo$(directionId);
+ }));
+ return this._makeRequest$(image$);
+ }
+ setFilter$(filter) {
+ this._stateService.clearImages();
+ return this._movedToId$.pipe(first(), mergeMap((id) => {
+ if (id != null) {
+ return this._trajectoryIds$().pipe(mergeMap((ids) => {
+ return this._graphService.setFilter$(filter).pipe(mergeMap(() => {
+ return this._cacheIds$(ids);
+ }));
+ }), last());
+ }
+ return this._idRequested$.pipe(first(), mergeMap((requestedId) => {
+ if (requestedId != null) {
+ return this._graphService.setFilter$(filter).pipe(mergeMap(() => {
+ return this._graphService.cacheImage$(requestedId);
+ }));
+ }
+ return this._graphService.setFilter$(filter).pipe(map(() => {
+ return undefined;
+ }));
+ }));
+ }), map(() => {
+ return undefined;
+ }));
+ }
+ setAccessToken$(accessToken) {
+ this._abortRequest("to set user token");
+ this._stateService.clearImages();
+ return this._movedToId$.pipe(first(), tap(() => {
+ this._api.setAccessToken(accessToken);
+ }), mergeMap((id) => {
+ return id == null ?
+ this._graphService.reset$([]) :
+ this._trajectoryIds$().pipe(mergeMap((ids) => {
+ return this._graphService.reset$(ids).pipe(mergeMap(() => {
+ return this._cacheIds$(ids);
+ }));
+ }), last(), map(() => {
+ return undefined;
+ }));
+ }));
+ }
+ _cacheIds$(ids) {
+ const cacheImages$ = ids
+ .map((id) => {
+ return this._graphService.cacheImage$(id);
+ });
+ return from(cacheImages$).pipe(mergeAll());
+ }
+ _abortRequest(reason) {
+ if (this._requestSubscription != null) {
+ this._requestSubscription.unsubscribe();
+ this._requestSubscription = null;
+ }
+ if (this._imageRequestSubscription != null) {
+ this._imageRequestSubscription.unsubscribe();
+ this._imageRequestSubscription = null;
+ }
+ if (this._request$ != null) {
+ if (!(this._request$.isStopped || this._request$.hasError)) {
+ this._request$.error(new CancelMapillaryError(`Request aborted by a subsequent request ${reason}.`));
+ }
+ this._request$ = null;
+ }
+ }
+ _makeRequest$(image$) {
+ const request$ = new ReplaySubject(1);
+ this._requestSubscription = request$
+ .subscribe(undefined, () => { });
+ this._request$ = request$;
+ this._imageRequestSubscription = image$
+ .subscribe((image) => {
+ this._request$ = null;
+ request$.next(image);
+ request$.complete();
+ }, (error) => {
+ this._request$ = null;
+ request$.error(error);
+ });
+ return request$;
+ }
+ _moveTo$(id) {
+ this._idRequested$.next(id);
+ return this._graphService.cacheImage$(id).pipe(tap((image) => {
+ this._stateService.setImages([image]);
+ this._movedToId$.next(image.id);
+ }), finalize(() => {
+ this._loadingService.stopLoading(this._loadingName);
+ }));
+ }
+ _trajectoryIds$() {
+ return this._stateService.currentState$.pipe(first(), map((frame) => {
+ return frame.state.trajectory
+ .map((image) => {
+ return image.id;
+ });
+ }));
+ }
+}
+
+class Projection {
+ constructor(viewportCoords, spatial) {
+ this._spatial = spatial !== null && spatial !== void 0 ? spatial : new Spatial();
+ this._viewportCoords = viewportCoords !== null && viewportCoords !== void 0 ? viewportCoords : new ViewportCoords();
+ }
+ basicToCanvas(basicPoint, container, render, transform) {
+ return this._viewportCoords
+ .basicToCanvasSafe(basicPoint[0], basicPoint[1], container, transform, render.perspective);
+ }
+ canvasToBasic(canvasPoint, container, render, transform) {
+ let basicPoint = this._viewportCoords
+ .canvasToBasic(canvasPoint[0], canvasPoint[1], container, transform, render.perspective);
+ if (basicPoint[0] < 0 ||
+ basicPoint[0] > 1 ||
+ basicPoint[1] < 0 ||
+ basicPoint[1] > 1) {
+ basicPoint = null;
+ }
+ return basicPoint;
+ }
+ eventToUnprojection(event, container, render, reference, transform) {
+ const pixelPoint = this._viewportCoords
+ .canvasPosition(event, container);
+ return this.canvasToUnprojection(pixelPoint, container, render, reference, transform);
+ }
+ canvasToUnprojection(canvasPoint, container, render, reference, transform) {
+ const canvasX = canvasPoint[0];
+ const canvasY = canvasPoint[1];
+ const [viewportX, viewportY] = this._viewportCoords
+ .canvasToViewport(canvasX, canvasY, container);
+ const point3d = new Vector3(viewportX, viewportY, 1)
+ .unproject(render.perspective);
+ let basicPoint = transform
+ .projectBasic(point3d.toArray());
+ if (basicPoint[0] < 0 ||
+ basicPoint[0] > 1 ||
+ basicPoint[1] < 0 ||
+ basicPoint[1] > 1) {
+ basicPoint = null;
+ }
+ const direction3d = point3d
+ .clone()
+ .sub(render.camera.position)
+ .normalize();
+ const dist = -2 / direction3d.z;
+ let lngLat = null;
+ if (dist > 0 && dist < 100 && !!basicPoint) {
+ const point = direction3d
+ .clone()
+ .multiplyScalar(dist)
+ .add(render.camera.position);
+ const [lng, lat] = enuToGeodetic(point.x, point.y, point.z, reference.lng, reference.lat, reference.alt);
+ lngLat = { lat, lng };
+ }
+ const unprojection = {
+ basicPoint: basicPoint,
+ lngLat: lngLat,
+ pixelPoint: [canvasX, canvasY],
+ };
+ return unprojection;
+ }
+ cameraToLngLat(render, reference) {
+ const position = render.camera.position;
+ const [lng, lat] = enuToGeodetic(position.x, position.y, position.z, reference.lng, reference.lat, reference.alt);
+ return { lat, lng };
+ }
+ lngLatToCanvas(lngLat, container, render, reference) {
+ const point3d = geodeticToEnu(lngLat.lng, lngLat.lat, 0, reference.lng, reference.lat, reference.alt);
+ const canvas = this._viewportCoords
+ .projectToCanvasSafe(point3d, container, render.perspective);
+ return canvas;
+ }
+ distanceBetweenLngLats(lngLat1, lngLat2) {
+ return this._spatial
+ .distanceFromLngLat(lngLat1.lng, lngLat1.lat, lngLat2.lng, lngLat2.lat);
+ }
+}
+
+class Observer {
+ constructor(viewer, navigator, container) {
+ this._subscriptions = new SubscriptionHolder();
+ this._emitSubscriptions = new SubscriptionHolder();
+ this._container = container;
+ this._viewer = viewer;
+ this._navigator = navigator;
+ this._projection = new Projection();
+ this._started = false;
+ this._navigable$ = new Subject();
+ const subs = this._subscriptions;
+ // load, navigable, dataloading should always emit,
+ // also when cover is activated.
+ subs.push(this._navigable$
+ .subscribe((navigable) => {
+ const type = "navigable";
+ const event = {
+ navigable,
+ target: this._viewer,
+ type,
+ };
+ this._viewer.fire(type, event);
+ }));
+ subs.push(this._navigator.loadingService.loading$
+ .subscribe((loading) => {
+ const type = "dataloading";
+ const event = {
+ loading,
+ target: this._viewer,
+ type,
+ };
+ this._viewer.fire(type, event);
+ }));
+ subs.push(this._container.glRenderer.opaqueRender$
+ .pipe(first())
+ .subscribe(() => {
+ const type = "load";
+ const event = {
+ target: this._viewer,
+ type,
+ };
+ this._viewer.fire(type, event);
+ }));
+ }
+ get started() {
+ return this._started;
+ }
+ get navigable$() {
+ return this._navigable$;
+ }
+ get projection() {
+ return this._projection;
+ }
+ dispose() {
+ this.stopEmit();
+ this._subscriptions.unsubscribe();
+ }
+ project$(lngLat) {
+ return combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.currentImage$, this._navigator.stateService.reference$).pipe(first(), map(([render, image, reference]) => {
+ if (this._projection
+ .distanceBetweenLngLats(lngLat, image.lngLat) > 1000) {
+ return null;
+ }
+ const canvasPoint = this._projection.lngLatToCanvas(lngLat, this._container.container, render, reference);
+ return !!canvasPoint ?
+ [Math.round(canvasPoint[0]), Math.round(canvasPoint[1])] :
+ null;
+ }));
+ }
+ projectBasic$(basicPoint) {
+ return combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$).pipe(first(), map(([render, transform]) => {
+ const canvasPoint = this._projection.basicToCanvas(basicPoint, this._container.container, render, transform);
+ return !!canvasPoint ?
+ [Math.round(canvasPoint[0]), Math.round(canvasPoint[1])] :
+ null;
+ }));
+ }
+ startEmit() {
+ if (this._started) {
+ return;
+ }
+ this._started = true;
+ const subs = this._emitSubscriptions;
+ subs.push(this._navigator.stateService.currentImageExternal$
+ .subscribe((image) => {
+ const type = "image";
+ const event = {
+ image: image,
+ target: this._viewer,
+ type,
+ };
+ this._viewer.fire(type, event);
+ }));
+ subs.push(this._navigator.stateService.currentImageExternal$.pipe(switchMap((image) => {
+ return image.sequenceEdges$;
+ }))
+ .subscribe((status) => {
+ const type = "sequenceedges";
+ const event = {
+ status,
+ target: this._viewer,
+ type,
+ };
+ this._viewer.fire(type, event);
+ }));
+ subs.push(this._navigator.stateService.currentImageExternal$.pipe(switchMap((image) => {
+ return image.spatialEdges$;
+ }))
+ .subscribe((status) => {
+ const type = "spatialedges";
+ const event = {
+ status,
+ target: this._viewer,
+ type,
+ };
+ this._viewer.fire(type, event);
+ }));
+ subs.push(combineLatest(this._navigator.stateService.inMotion$, this._container.mouseService.active$, this._container.touchService.active$).pipe(map((values) => {
+ return values[0] || values[1] || values[2];
+ }), distinctUntilChanged())
+ .subscribe((started) => {
+ const type = started ? "movestart" : "moveend";
+ const event = {
+ target: this._viewer,
+ type,
+ };
+ this._viewer.fire(type, event);
+ }));
+ subs.push(this._container.renderService.bearing$.pipe(auditTime(100), distinctUntilChanged((b1, b2) => {
+ return Math.abs(b2 - b1) < 1;
+ }))
+ .subscribe((bearing) => {
+ const type = "bearing";
+ const event = {
+ bearing,
+ target: this._viewer,
+ type,
+ };
+ this._viewer.fire(type, event);
+ }));
+ const mouseMove$ = this._container.mouseService.active$.pipe(switchMap((active) => {
+ return active ?
+ empty() :
+ this._container.mouseService.mouseMove$;
+ }));
+ subs.push(merge(this._mapMouseEvent$("click", this._container.mouseService.staticClick$), this._mapMouseEvent$("contextmenu", this._container.mouseService.contextMenu$), this._mapMouseEvent$("dblclick", this._container.mouseService.dblClick$), this._mapMouseEvent$("mousedown", this._container.mouseService.mouseDown$), this._mapMouseEvent$("mousemove", mouseMove$), this._mapMouseEvent$("mouseout", this._container.mouseService.mouseOut$), this._mapMouseEvent$("mouseover", this._container.mouseService.mouseOver$), this._mapMouseEvent$("mouseup", this._container.mouseService.mouseUp$))
+ .pipe(withLatestFrom(this._container.renderService.renderCamera$, this._navigator.stateService.reference$, this._navigator.stateService.currentTransform$, this._navigator.stateService.state$), map(([[type, event], render, reference, transform, state]) => {
+ const unprojection = this._projection.eventToUnprojection(event, this._container.container, render, reference, transform);
+ const basicPoint = state === State.Traversing ?
+ unprojection.basicPoint : null;
+ return {
+ basicPoint,
+ lngLat: unprojection.lngLat,
+ originalEvent: event,
+ pixelPoint: unprojection.pixelPoint,
+ target: this._viewer,
+ type: type,
+ };
+ }))
+ .subscribe((event) => {
+ this._viewer.fire(event.type, event);
+ }));
+ subs.push(this._container.renderService.renderCamera$.pipe(distinctUntilChanged(([x1, y1], [x2, y2]) => {
+ return this._closeTo(x1, x2, 1e-2) &&
+ this._closeTo(y1, y2, 1e-2);
+ }, (rc) => {
+ return rc.camera.position.toArray();
+ }))
+ .subscribe(() => {
+ const type = "position";
+ const event = {
+ target: this._viewer,
+ type,
+ };
+ this._viewer.fire(type, event);
+ }));
+ subs.push(this._container.renderService.renderCamera$.pipe(distinctUntilChanged(([phi1, theta1], [phi2, theta2]) => {
+ return this._closeTo(phi1, phi2, 1e-3) &&
+ this._closeTo(theta1, theta2, 1e-3);
+ }, (rc) => {
+ return [rc.rotation.phi, rc.rotation.theta];
+ }))
+ .subscribe(() => {
+ const type = "pov";
+ const event = {
+ target: this._viewer,
+ type,
+ };
+ this._viewer.fire(type, event);
+ }));
+ subs.push(this._container.renderService.renderCamera$.pipe(distinctUntilChanged((fov1, fov2) => {
+ return this._closeTo(fov1, fov2, 1e-2);
+ }, (rc) => {
+ return rc.perspective.fov;
+ }))
+ .subscribe(() => {
+ const type = "fov";
+ const event = {
+ target: this._viewer,
+ type,
+ };
+ this._viewer.fire(type, event);
+ }));
+ }
+ stopEmit() {
+ if (!this.started) {
+ return;
+ }
+ this._emitSubscriptions.unsubscribe();
+ this._started = false;
+ }
+ unproject$(canvasPoint) {
+ return combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.reference$, this._navigator.stateService.currentTransform$).pipe(first(), map(([render, reference, transform]) => {
+ const unprojection = this._projection.canvasToUnprojection(canvasPoint, this._container.container, render, reference, transform);
+ return unprojection.lngLat;
+ }));
+ }
+ unprojectBasic$(canvasPoint) {
+ return combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.currentTransform$).pipe(first(), map(([render, transform]) => {
+ return this._projection.canvasToBasic(canvasPoint, this._container.container, render, transform);
+ }));
+ }
+ _closeTo(v1, v2, absoluteTolerance) {
+ return Math.abs(v1 - v2) <= absoluteTolerance;
+ }
+ _mapMouseEvent$(type, mouseEvent$) {
+ return mouseEvent$.pipe(map((event) => {
+ return [type, event];
+ }));
+ }
+}
+
+class CustomRenderer {
+ constructor(_container, _navigator) {
+ this._container = _container;
+ this._navigator = _navigator;
+ this._renderers = {};
+ }
+ add(renderer, viewer) {
+ const subs = new SubscriptionHolder();
+ this._renderers[renderer.id] = { subs, renderer };
+ subs.push(combineLatest([
+ this._container.glRenderer.webGLRenderer$,
+ this._navigator.stateService.reference$,
+ ])
+ .pipe(take(1))
+ .subscribe(([gl, reference]) => {
+ renderer.onAdd(viewer, reference, gl.getContext());
+ }));
+ subs.push(this._container.glRenderer.opaqueRender$
+ .pipe(withLatestFrom(this._container.renderService.renderCamera$, this._container.glRenderer.webGLRenderer$))
+ .subscribe(([, renderCamera, glRenderer]) => {
+ const context = glRenderer.getContext();
+ const viewMatrix = renderCamera.perspective.matrixWorldInverse;
+ const projectionMatrix = renderCamera.perspective.projectionMatrix;
+ renderer.render(context, viewMatrix.toArray(), projectionMatrix.toArray());
+ }));
+ subs.push(this._navigator.stateService.reference$
+ .pipe(skip(1))
+ .subscribe((reference) => {
+ renderer.onReference(viewer, reference);
+ }));
+ }
+ dispose(viewer) {
+ for (const id of Object.keys(this._renderers)) {
+ this.remove(id, viewer);
+ }
+ }
+ has(id) {
+ return id in this._renderers;
+ }
+ remove(id, viewer) {
+ this._renderers[id].subs.unsubscribe();
+ const renderer = this._renderers[id].renderer;
+ delete this._renderers[id];
+ this._container.glRenderer.webGLRenderer$
+ .subscribe((gl) => {
+ renderer.onRemove(viewer, gl.getContext());
+ });
+ }
+}
+
+class CustomCameraControls {
+ constructor(_container, _navigator) {
+ this._container = _container;
+ this._navigator = _navigator;
+ this._controls = null;
+ this._subscriptions = new SubscriptionHolder();
+ }
+ attach(controls, viewer) {
+ if (this._controls) {
+ throw new MapillaryError('Custom camera controls already attached');
+ }
+ const attach$ = new Subject();
+ const active$ = attach$
+ .pipe(switchMap(() => {
+ return this._navigator.stateService.state$;
+ }), map((state) => {
+ return state === State.Custom;
+ }), distinctUntilChanged());
+ const subs = this._subscriptions;
+ subs.push(active$
+ .pipe(startWith(false), pairwise(), withLatestFrom(this._navigator.stateService.reference$, this._container.renderService.renderCamera$))
+ .subscribe(([[deactivate, activate], ref, cam]) => {
+ if (activate) {
+ controls.onActivate(viewer, cam.perspective.matrixWorldInverse.toArray(), cam.perspective.projectionMatrix.toArray(), ref);
+ }
+ else if (deactivate) {
+ controls.onDeactivate(viewer);
+ }
+ }));
+ subs.push(active$
+ .pipe(switchMap(active => {
+ return active ?
+ this._navigator.stateService.currentState$
+ .pipe(skip(1)) :
+ empty();
+ }))
+ .subscribe(frame => {
+ controls.onAnimationFrame(viewer, frame.id);
+ }));
+ subs.push(active$
+ .pipe(switchMap(active => {
+ return active ?
+ this._navigator.stateService.reference$
+ .pipe(skip(1)) :
+ empty();
+ }))
+ .subscribe(ref => controls.onReference(viewer, ref)));
+ subs.push(active$
+ .pipe(switchMap(active => {
+ return active ?
+ this._container.renderService.size$
+ .pipe(skip(1)) :
+ empty();
+ }))
+ .subscribe(() => controls.onResize(viewer)));
+ subs.push(combineLatest([
+ // Include to ensure GL renderer has been initialized
+ this._container.glRenderer.webGLRenderer$,
+ this._container.renderService.renderCamera$,
+ this._navigator.stateService.reference$,
+ this._navigator.stateService.state$,
+ ])
+ .pipe(first())
+ .subscribe(() => {
+ const projectionMatrixCallback = (projectionMatrix) => {
+ if (!this._controls ||
+ controls !== this._controls) {
+ return;
+ }
+ this._updateProjectionMatrix(projectionMatrix);
+ };
+ const viewMatrixCallback = (viewMatrix) => {
+ if (!this._controls ||
+ controls !== this._controls) {
+ return;
+ }
+ this._updateViewMatrix(viewMatrix);
+ };
+ controls.onAttach(viewer, viewMatrixCallback, projectionMatrixCallback);
+ attach$.next();
+ attach$.complete();
+ }));
+ this._controls = controls;
+ }
+ dispose(viewer) {
+ this.detach(viewer);
+ }
+ detach(viewer) {
+ if (!this._controls) {
+ return;
+ }
+ this._subscriptions.unsubscribe();
+ this._navigator.stateService.state$
+ .subscribe(state => {
+ if (state === State.Custom) {
+ this._controls.onDeactivate(viewer);
+ }
+ this._controls.onDetach(viewer);
+ this._controls = null;
+ });
+ }
+ _updateProjectionMatrix(projectionMatrix) {
+ this._navigator.stateService.state$
+ .pipe(first())
+ .subscribe(state => {
+ if (state !== State.Custom) {
+ const message = "Incorrect camera control mode for " +
+ "projection matrix update";
+ console.warn(message);
+ return;
+ }
+ this._container.renderService.projectionMatrix$
+ .next(projectionMatrix);
+ });
+ }
+ _updateViewMatrix(viewMatrix) {
+ this._navigator.stateService.state$
+ .pipe(first())
+ .subscribe(state => {
+ if (state !== State.Custom) {
+ const message = "Incorrect camera control mode for " +
+ "view matrix update";
+ console.warn(message);
+ return;
+ }
+ this._navigator.stateService.setViewMatrix(viewMatrix);
+ });
+ }
+}
+
+/**
+ * @class Viewer
+ *
+ * @classdesc The Viewer object represents the navigable image viewer.
+ * Create a Viewer by specifying a container, client ID, image id and
+ * other options. The viewer exposes methods and events for programmatic
+ * interaction.
+ *
+ * In the case of asynchronous methods, MapillaryJS returns promises to
+ * the results. Notifications are always emitted through JavaScript events.
+ */
+class Viewer extends EventEmitter {
+ /**
+ * Create a new viewer instance.
+ *
+ * @description It is possible to initialize the viewer with or
+ * without a id.
+ *
+ * When you want to show a specific image in the viewer from
+ * the start you should initialize it with a id.
+ *
+ * When you do not know the first image id at implementation
+ * time, e.g. in a map-viewer application you should initialize
+ * the viewer without a id and call `moveTo` instead.
+ *
+ * When initializing with a id the viewer is bound to that id
+ * until the image for that id has been successfully loaded.
+ * Also, a cover with the image of the id will be shown.
+ * If the data for that id can not be loaded because the id is
+ * faulty or other errors occur it is not possible to navigate
+ * to another id because the viewer is not navigable. The viewer
+ * becomes navigable when the data for the id has been loaded and
+ * the image is shown in the viewer. This way of initializing
+ * the viewer is mostly for embedding in blog posts and similar
+ * where one wants to show a specific image initially.
+ *
+ * If the viewer is initialized without a id (with null or
+ * undefined) it is not bound to any particular id and it is
+ * possible to move to any id with `viewer.moveTo("<my-image-id>")`.
+ * If the first move to a id fails it is possible to move to another
+ * id. The viewer will show a black background until a move
+ * succeeds. This way of intitializing is suited for a map-viewer
+ * application when the initial id is not known at implementation
+ * time.
+ *
+ * @param {ViewerOptions} options - Optional configuration object
+ * specifing Viewer's and the components' initial setup.
+ *
+ * @example
+ * ```js
+ * var viewer = new Viewer({
+ * accessToken: "<my-access-token>",
+ * container: "<my-container-id>",
+ * });
+ * ```
+ */
+ constructor(options) {
+ super();
+ this._navigator =
+ new Navigator(options);
+ this._container =
+ new Container(options, this._navigator.stateService);
+ this._observer =
+ new Observer(this, this._navigator, this._container);
+ this._componentController =
+ new ComponentController(this._container, this._navigator, this._observer, options.imageId, options.component);
+ this._customRenderer =
+ new CustomRenderer(this._container, this._navigator);
+ this._customCameraControls =
+ new CustomCameraControls(this._container, this._navigator);
+ }
+ /**
+ * Return a boolean indicating if the viewer is in a navigable state.
+ *
+ * @description The navigable state indicates if the viewer supports
+ * moving, i.e. calling the {@link moveTo} and {@link moveDir}
+ * methods or changing the authentication state,
+ * i.e. calling {@link setAccessToken}. The viewer will not be in a navigable
+ * state if the cover is activated and the viewer has been supplied a id.
+ * When the cover is deactivated or the viewer is activated without being
+ * supplied a id it will be navigable.
+ *
+ * @returns {boolean} Boolean indicating whether the viewer is navigable.
+ */
+ get isNavigable() {
+ return this._componentController.navigable;
+ }
+ /**
+ * Activate the combined panning functionality.
+ *
+ * @description The combined panning functionality is active by default.
+ */
+ activateCombinedPanning() {
+ this._navigator.panService.enable();
+ }
+ /**
+ * Activate a component.
+ *
+ * @param {ComponentName | FallbackComponentName} name - Name of
+ * the component which will become active.
+ *
+ * @example
+ * ```js
+ * viewer.activateComponent("marker");
+ * ```
+ */
+ activateComponent(name) {
+ this._componentController.activate(name);
+ }
+ /**
+ * Activate the cover (deactivates all other components).
+ */
+ activateCover() {
+ this._componentController.activateCover();
+ }
+ /**
+ * Add a custom renderer to the viewer's rendering pipeline.
+ *
+ * @description During a render pass, custom renderers
+ * are called in the order they were added.
+ *
+ * @param renderer - The custom renderer implementation.
+ */
+ addCustomRenderer(renderer) {
+ this._customRenderer.add(renderer, this);
+ }
+ /**
+ * Attach custom camera controls to control the viewer's
+ * camera pose and projection.
+ *
+ * @description Custom camera controls allow the API user
+ * to move the viewer's camera freely and define the camera
+ * projection. These camera properties are used
+ * to render the viewer 3D scene directly into the
+ * viewer's GL context.
+ *
+ * Only a single custom camera control instance can be
+ * attached to the viewer. A new custom camera control
+ * instance can be attached after detaching a previous
+ * one.
+ *
+ * Set the viewer's camera controls to
+ * {@link CameraControls.Custom} to activate attached
+ * camera controls. If {@link CameraControls.Custom}
+ * has already been set when a custom camera control
+ * instance is attached, it will be activated immediately.
+ *
+ * Set the viewer's camera controls to any other
+ * {@link CameraControls} mode to deactivate the
+ * custom camera controls.
+ *
+ * @param controls - The custom camera controls implementation.
+ *
+ * @throws {MapillaryError} When camera controls attached
+ * are already attached to the viewer.
+ */
+ attachCustomCameraControls(controls) {
+ this._customCameraControls.attach(controls, this);
+ }
+ /**
+ * Deactivate the combined panning functionality.
+ *
+ * @description Deactivating the combined panning functionality
+ * could be needed in scenarios involving sequence only navigation.
+ */
+ deactivateCombinedPanning() {
+ this._navigator.panService.disable();
+ }
+ /**
+ * Deactivate a component.
+ *
+ * @param {ComponentName | FallbackComponentName} name - Name
+ * of component which become inactive.
+ *
+ * @example
+ * ```js
+ * viewer.deactivateComponent("pointer");
+ * ```
+ */
+ deactivateComponent(name) {
+ this._componentController.deactivate(name);
+ }
+ /**
+ * Deactivate the cover (activates all components marked as active).
+ */
+ deactivateCover() {
+ this._componentController.deactivateCover();
+ }
+ /**
+ * Detach a previously attached custom camera control
+ * instance from the viewer.
+ *
+ * @description If no custom camera control instance
+ * has previously been attached, calling this method
+ * has no effect.
+ *
+ * Already attached custom camera controls need to
+ * be detached before attaching another custom camera
+ * control instance.
+ */
+ detachCustomCameraControls() {
+ this._customCameraControls.detach(this);
+ }
+ fire(type, event) {
+ super.fire(type, event);
+ }
+ /**
+ * Get the bearing of the current viewer camera.
+ *
+ * @description The bearing depends on how the camera
+ * is currently rotated and does not correspond
+ * to the compass angle of the current image if the view
+ * has been panned.
+ *
+ * Bearing is measured in degrees clockwise with respect to
+ * north.
+ *
+ * @returns {Promise<number>} Promise to the bearing
+ * of the current viewer camera.
+ *
+ * @example
+ * ```js
+ * viewer.getBearing().then(b => { console.log(b); });
+ * ```
+ */
+ getBearing() {
+ return new Promise((resolve, reject) => {
+ this._container.renderService.bearing$.pipe(first())
+ .subscribe((bearing) => {
+ resolve(bearing);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Get the viewer's camera control mode.
+ *
+ * @description The camera control mode determines
+ * how the camera is controlled when the viewer
+ * recieves pointer and keyboard input.
+ *
+ * @returns {CameraControls} controls - Camera control mode.
+ *
+ * @example
+ * ```js
+ * viewer.getCameraControls().then(c => { console.log(c); });
+ * ```
+ */
+ getCameraControls() {
+ return new Promise((resolve, reject) => {
+ this._navigator.stateService.state$.pipe(first())
+ .subscribe((state) => {
+ switch (state) {
+ case State.Custom:
+ resolve(CameraControls.Custom);
+ break;
+ case State.Earth:
+ resolve(CameraControls.Earth);
+ break;
+ default:
+ resolve(CameraControls.Street);
+ break;
+ }
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Returns the viewer's canvas element.
+ *
+ * @description This is the element onto which the viewer renders
+ * the WebGL content.
+ *
+ * @returns {HTMLCanvasElement} The viewer's canvas element, or
+ * null or not initialized.
+ */
+ getCanvas() {
+ return this._container.canvas;
+ }
+ /**
+ * Returns the HTML element containing the viewer's canvas element.
+ *
+ * @description This is the element to which event bindings for viewer
+ * interactivity (such as panning and zooming) are attached.
+ *
+ * @returns {HTMLDivElement} The container for the viewer's
+ * canvas element.
+ */
+ getCanvasContainer() {
+ return this._container.canvasContainer;
+ }
+ /**
+ * Get the basic coordinates of the current image that is
+ * at the center of the viewport.
+ *
+ * @description Basic coordinates are 2D coordinates on the [0, 1] interval
+ * and have the origin point, (0, 0), at the top left corner and the
+ * maximum value, (1, 1), at the bottom right corner of the original
+ * image.
+ *
+ * @returns {Promise<number[]>} Promise to the basic coordinates
+ * of the current image at the center for the viewport.
+ *
+ * @example
+ * ```js
+ * viewer.getCenter().then(c => { console.log(c); });
+ * ```
+ */
+ getCenter() {
+ return new Promise((resolve, reject) => {
+ this._navigator.stateService.getCenter()
+ .subscribe((center) => {
+ resolve(center);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Get a component.
+ *
+ * @param {string} name - Name of component.
+ * @returns {Component} The requested component.
+ *
+ * @example
+ * ```js
+ * var pointerComponent = viewer.getComponent("pointer");
+ * ```
+ */
+ getComponent(name) {
+ return this._componentController.get(name);
+ }
+ /**
+ * Returns the viewer's containing HTML element.
+ *
+ * @returns {HTMLElement} The viewer's container.
+ */
+ getContainer() {
+ return this._container.container;
+ }
+ /**
+ * Get the viewer's current vertical field of view.
+ *
+ * @description The vertical field of view rendered on the viewer canvas
+ * measured in degrees.
+ *
+ * @returns {Promise<number>} Promise to the current field of view
+ * of the viewer camera.
+ *
+ * @example
+ * ```js
+ * viewer.getFieldOfView().then(fov => { console.log(fov); });
+ * ```
+ */
+ getFieldOfView() {
+ return new Promise((resolve, reject) => {
+ this._container.renderService.renderCamera$.pipe(first())
+ .subscribe((rc) => {
+ resolve(rc.perspective.fov);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Get the viewer's current image.
+ *
+ * @returns {Promise<Image>} Promise to the current image.
+ *
+ * @example
+ * ```js
+ * viewer.getImage().then(image => { console.log(image.id); });
+ * ```
+ */
+ getImage() {
+ return new Promise((resolve, reject) => {
+ this._navigator.stateService.currentImage$.pipe(first())
+ .subscribe((image) => { resolve(image); }, (error) => { reject(error); });
+ });
+ }
+ /**
+ * Get the viewer's current point of view.
+ *
+ * @returns {Promise<PointOfView>} Promise to the current point of view
+ * of the viewer camera.
+ *
+ * @example
+ * ```js
+ * viewer.getPointOfView().then(pov => { console.log(pov); });
+ * ```
+ */
+ getPointOfView() {
+ return new Promise((resolve, reject) => {
+ combineLatest(this._container.renderService.renderCamera$, this._container.renderService.bearing$).pipe(first())
+ .subscribe(([rc, bearing]) => {
+ resolve({
+ bearing: bearing,
+ tilt: rc.getTilt(),
+ });
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Get the viewer's current position
+ *
+ * @returns {Promise<LngLat>} Promise to the viewers's current
+ * position.
+ *
+ * @example
+ * ```js
+ * viewer.getPosition().then(pos => { console.log(pos); });
+ * ```
+ */
+ getPosition() {
+ return new Promise((resolve, reject) => {
+ combineLatest(this._container.renderService.renderCamera$, this._navigator.stateService.reference$).pipe(first())
+ .subscribe(([render, reference]) => {
+ resolve(this._observer.projection.cameraToLngLat(render, reference));
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Get the image's current zoom level.
+ *
+ * @returns {Promise<number>} Promise to the viewers's current
+ * zoom level.
+ *
+ * @example
+ * ```js
+ * viewer.getZoom().then(z => { console.log(z); });
+ * ```
+ */
+ getZoom() {
+ return new Promise((resolve, reject) => {
+ this._navigator.stateService.getZoom()
+ .subscribe((zoom) => {
+ resolve(zoom);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Check if a custom renderer has been added to the viewer's
+ * rendering pipeline.
+ *
+ * @param {string} id - Unique id of the custom renderer.
+ * @returns {boolean} Value indicating whether the customer
+ * renderer has been added.
+ */
+ hasCustomRenderer(rendererId) {
+ return this._customRenderer.has(rendererId);
+ }
+ /**
+ * Navigate in a given direction.
+ *
+ * @param {NavigationDirection} direction - Direction in which which to move.
+ * @returns {Promise<Image>} Promise to the image that was navigated to.
+ * @throws If the current image does not have the edge direction
+ * or the edges has not yet been cached.
+ * @throws Propagates any IO errors to the caller.
+ * @throws When viewer is not navigable.
+ * @throws {@link CancelMapillaryError} When a subsequent move request
+ * is made before the move dir call has completed.
+ *
+ * @example
+ * ```js
+ * viewer.moveDir(NavigationDirection.Next).then(
+ * image => { console.log(image); },
+ * error => { console.error(error); });
+ * ```
+ */
+ moveDir(direction) {
+ const moveDir$ = this.isNavigable ?
+ this._navigator.moveDir$(direction) :
+ throwError(new Error("Calling moveDir is not supported when viewer is not navigable."));
+ return new Promise((resolve, reject) => {
+ moveDir$.subscribe((image) => {
+ resolve(image);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Navigate to a given image id.
+ *
+ * @param {string} imageId - Id of the image to move to.
+ * @returns {Promise<Image>} Promise to the image that was navigated to.
+ * @throws Propagates any IO errors to the caller.
+ * @throws When viewer is not navigable.
+ * @throws {@link CancelMapillaryError} When a subsequent
+ * move request is made before the move to id call has completed.
+ *
+ * @example
+ * ```js
+ * viewer.moveTo("<my-image-id>").then(
+ * image => { console.log(image); },
+ * error => { console.error(error); });
+ * ```
+ */
+ moveTo(imageId) {
+ const moveTo$ = this.isNavigable ?
+ this._navigator.moveTo$(imageId) :
+ throwError(new Error("Calling moveTo is not supported when viewer is not navigable."));
+ return new Promise((resolve, reject) => {
+ moveTo$.subscribe((image) => {
+ resolve(image);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ off(type, handler) {
+ super.off(type, handler);
+ }
+ on(type, handler) {
+ super.on(type, handler);
+ }
+ /**
+ * Project geodetic coordinates to canvas pixel coordinates.
+ *
+ * @description The geodetic coordinates may not always correspond to pixel
+ * coordinates, e.g. if the geodetic coordinates have a position behind the
+ * viewer camera. In the case of no correspondence the returned value will
+ * be `null`.
+ *
+ * If the distance from the viewer camera position to the provided
+ * longitude-latitude is more than 1000 meters `null` will be returned.
+ *
+ * The projection is performed from the ground plane, i.e.
+ * the altitude with respect to the ground plane for the geodetic
+ * point is zero.
+ *
+ * Note that whenever the camera moves, the result of the method will be
+ * different.
+ *
+ * @param {LngLat} lngLat - Geographical coordinates to project.
+ * @returns {Promise<Array<number>>} Promise to the pixel coordinates corresponding
+ * to the lngLat.
+ *
+ * @example
+ * ```js
+ * viewer.project({ lat: 0, lng: 0 })
+ * .then(pixelPoint => {
+ * if (!pixelPoint) {
+ * console.log("no correspondence");
+ * }
+ *
+ * console.log(pixelPoint);
+ * });
+ * ```
+ */
+ project(lngLat) {
+ return new Promise((resolve, reject) => {
+ this._observer.project$(lngLat)
+ .subscribe((pixelPoint) => {
+ resolve(pixelPoint);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Project basic image coordinates for the current image to canvas pixel
+ * coordinates.
+ *
+ * @description The basic image coordinates may not always correspond to a
+ * pixel point that lies in the visible area of the viewer container. In the
+ * case of no correspondence the returned value can be `null`.
+ *
+ *
+ * @param {Array<number>} basicPoint - Basic images coordinates to project.
+ * @returns {Promise<Array<number>>} Promise to the pixel coordinates corresponding
+ * to the basic image point.
+ *
+ * @example
+ * ```js
+ * viewer.projectFromBasic([0.3, 0.7])
+ * .then(pixelPoint => { console.log(pixelPoint); });
+ * ```
+ */
+ projectFromBasic(basicPoint) {
+ return new Promise((resolve, reject) => {
+ this._observer.projectBasic$(basicPoint)
+ .subscribe((pixelPoint) => {
+ resolve(pixelPoint);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Clean up and release all internal resources associated with
+ * this viewer.
+ *
+ * @description This includes DOM elements, event bindings, and
+ * WebGL resources.
+ *
+ * Use this method when you are done using the viewer and wish to
+ * ensure that it no longer consumes browser resources. Afterwards,
+ * you must not call any other methods on the viewer.
+ *
+ * @fires remove
+ *
+ * @example
+ * ```js
+ * viewer.remove();
+ * ```
+ */
+ remove() {
+ this._customRenderer.dispose(this);
+ this._customCameraControls.dispose(this);
+ this._observer.dispose();
+ this._componentController.remove();
+ this._navigator.dispose();
+ this._container.remove();
+ const type = "remove";
+ const event = {
+ target: this,
+ type,
+ };
+ this.fire(type, event);
+ }
+ /**
+ * Remove a custom renderer from the viewer's rendering pipeline.
+ *
+ * @param id - Unique id of the custom renderer.
+ */
+ removeCustomRenderer(rendererId) {
+ this._customRenderer.remove(rendererId, this);
+ }
+ /**
+ * Detect the viewer's new width and height and resize it
+ * manually.
+ *
+ * @description The components will also detect the viewer's
+ * new size and resize their rendered elements if needed.
+ *
+ * When the {@link ViewerOptions.trackResize} option is
+ * set to true, the viewer will automatically resize
+ * when the browser window is resized. If any other
+ * custom behavior is preferred, the option should be set
+ * to false and the {@link Viewer.resize} method should
+ * be called on demand.
+ *
+ * @example
+ * ```js
+ * viewer.resize();
+ * ```
+ */
+ resize() {
+ this._container.renderService.resize$.next();
+ }
+ /**
+ * Set the viewer's camera control mode.
+ *
+ * @description The camera control mode determines
+ * how the camera is controlled when the viewer
+ * recieves pointer and keyboard input.
+ *
+ * Changing the camera control mode is not possible
+ * when the slider component is active and attempts
+ * to do so will be ignored.
+ *
+ * @param {CameraControls} controls - Camera control mode.
+ *
+ * @example
+ * ```js
+ * viewer.setCameraControls(CameraControls.Street);
+ * ```
+ */
+ setCameraControls(controls) {
+ const state = cameraControlsToState(controls);
+ if (state === State.Traversing) {
+ this._navigator.stateService.traverse();
+ }
+ else if (state === State.Earth) {
+ this._navigator.stateService.earth();
+ }
+ else if (state === State.Custom) {
+ this._navigator.stateService.custom();
+ }
+ else {
+ console.warn(`Unsupported camera control transition (${controls})`);
+ }
+ }
+ /**
+ * Set the basic coordinates of the current image to be in the
+ * center of the viewport.
+ *
+ * @description Basic coordinates are 2D coordinates on the [0, 1] interval
+ * and has the origin point, (0, 0), at the top left corner and the
+ * maximum value, (1, 1), at the bottom right corner of the original
+ * image.
+ *
+ * @param {number[]} The basic coordinates of the current
+ * image to be at the center for the viewport.
+ *
+ * @example
+ * ```js
+ * viewer.setCenter([0.5, 0.5]);
+ * ```
+ */
+ setCenter(center) {
+ this._navigator.stateService.setCenter(center);
+ }
+ /**
+ * Set the viewer's current vertical field of view.
+ *
+ * @description Sets the vertical field of view rendered
+ * on the viewer canvas measured in degrees. The value
+ * will be clamped to be able to set a valid zoom level
+ * based on the projection model of the current image and
+ * the viewer's current render mode.
+ *
+ * @param {number} fov - Vertical field of view in degrees.
+ *
+ * @example
+ * ```js
+ * viewer.setFieldOfView(45);
+ * ```
+ */
+ setFieldOfView(fov) {
+ this._container.renderService.renderCamera$.pipe(first())
+ .subscribe((rc) => {
+ const zoom = rc.fovToZoom(fov);
+ this._navigator.stateService.setZoom(zoom);
+ });
+ }
+ /**
+ * Set the filter selecting images to use when calculating
+ * the spatial edges.
+ *
+ * @description The following filter types are supported:
+ *
+ * Comparison
+ *
+ * `["==", key, value]` equality: `image[key] = value`
+ *
+ * `["!=", key, value]` inequality: `image[key] ≠ value`
+ *
+ * `["<", key, value]` less than: `image[key] < value`
+ *
+ * `["<=", key, value]` less than or equal: `image[key] ≤ value`
+ *
+ * `[">", key, value]` greater than: `image[key] > value`
+ *
+ * `[">=", key, value]` greater than or equal: `image[key] ≥ value`
+ *
+ * Set membership
+ *
+ * `["in", key, v0, ..., vn]` set inclusion: `image[key] ∈ {v0, ..., vn}`
+ *
+ * `["!in", key, v0, ..., vn]` set exclusion: `image[key] ∉ {v0, ..., vn}`
+ *
+ * Combining
+ *
+ * `["all", f0, ..., fn]` logical `AND`: `f0 ∧ ... ∧ fn`
+ *
+ * A key must be a string that identifies a property name of a
+ * simple {@link Image} property, i.e. a key of the {@link FilterKey}
+ * type. A value must be a string, number, or
+ * boolean. Strictly-typed comparisons are used. The values
+ * `f0, ..., fn` of the combining filter must be filter expressions.
+ *
+ * Clear the filter by setting it to null or empty array.
+ *
+ * Commonly used filter properties (see the {@link Image} class
+ * documentation for a full list of properties that can be used
+ * in a filter) are shown the the example code.
+ *
+ * @param {FilterExpression} filter - The filter expression.
+ * @returns {Promise<void>} Promise that resolves after filter is applied.
+ *
+ * @example
+ * ```js
+ * // Examples
+ * viewer.setFilter(["==", "cameraType", "spherical"]);
+ * viewer.setFilter([">=", "capturedAt", <my-time-stamp>]);
+ * viewer.setFilter(["in", "sequenceId", "<sequence-id-1>", "<sequence-id-2>"]);
+ * ```
+ */
+ setFilter(filter) {
+ return new Promise((resolve, reject) => {
+ this._navigator.setFilter$(filter)
+ .subscribe(() => {
+ resolve(undefined);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Set the viewer's render mode.
+ *
+ * @param {RenderMode} renderMode - Render mode.
+ *
+ * @example
+ * ```js
+ * viewer.setRenderMode(RenderMode.Letterbox);
+ * ```
+ */
+ setRenderMode(renderMode) {
+ this._container.renderService.renderMode$.next(renderMode);
+ }
+ /**
+ * Set the viewer's transition mode.
+ *
+ * @param {TransitionMode} transitionMode - Transition mode.
+ *
+ * @example
+ * ```js
+ * viewer.setTransitionMode(TransitionMode.Instantaneous);
+ * ```
+ */
+ setTransitionMode(transitionMode) {
+ this._navigator.stateService.setTransitionMode(transitionMode);
+ }
+ /**
+ * Set an access token for authenticated API requests of protected
+ * resources.
+ *
+ * The token may be a user access token or a client access token.
+ *
+ * @description When the supplied user token is null or undefined,
+ * any previously set user bearer token will be cleared and the
+ * viewer will make unauthenticated requests.
+ *
+ * Calling setAccessToken aborts all outstanding move requests.
+ * The promises of those move requests will be rejected with a
+ * {@link CancelMapillaryError} the rejections need to be caught.
+ *
+ * Calling setAccessToken also resets the complete viewer cache
+ * so it should not be called repeatedly.
+ *
+ * @param {string} [accessToken] accessToken - Optional user
+ * access token or client access token.
+ * @returns {Promise<void>} Promise that resolves after token
+ * is set.
+ *
+ * @throws When viewer is not navigable.
+ *
+ * @example
+ * ```js
+ * viewer.setAccessToken("<my access token>")
+ * .then(() => { console.log("user token set"); });
+ * ```
+ */
+ setAccessToken(accessToken) {
+ const setAccessToken$ = this.isNavigable ?
+ this._navigator.setAccessToken$(accessToken) :
+ throwError(new Error("Calling setAccessToken is not supported when viewer is not navigable."));
+ return new Promise((resolve, reject) => {
+ setAccessToken$
+ .subscribe(() => {
+ resolve(undefined);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Set the image's current zoom level.
+ *
+ * @description Possible zoom level values are on the [0, 3] interval.
+ * Zero means zooming out to fit the image to the view whereas three
+ * shows the highest level of detail.
+ *
+ * @param {number} The image's current zoom level.
+ *
+ * @example
+ * ```js
+ * viewer.setZoom(2);
+ * ```
+ */
+ setZoom(zoom) {
+ this._navigator.stateService.setZoom(zoom);
+ }
+ /**
+ * Trigger the rendering of a single frame.
+ *
+ * @description Use this method with custom renderers to
+ * force the viewer to rerender when the custom content
+ * changes. Calling this multiple times before the next
+ * frame is rendered will still result in only a single
+ * frame being rendered.
+ */
+ triggerRerender() {
+ this._container.glRenderer.triggerRerender();
+ }
+ /**
+ * Unproject canvas pixel coordinates to geodetic
+ * coordinates.
+ *
+ * @description The pixel point may not always correspond to geodetic
+ * coordinates. In the case of no correspondence the returned value will
+ * be `null`.
+ *
+ * The unprojection to a lngLat will be performed towards the ground plane, i.e.
+ * the altitude with respect to the ground plane for the returned lngLat is zero.
+ *
+ * @param {Array<number>} pixelPoint - Pixel coordinates to unproject.
+ * @returns {Promise<LngLat>} Promise to the lngLat corresponding to the pixel point.
+ *
+ * @example
+ * ```js
+ * viewer.unproject([100, 100])
+ * .then(lngLat => { console.log(lngLat); });
+ * ```
+ */
+ unproject(pixelPoint) {
+ return new Promise((resolve, reject) => {
+ this._observer.unproject$(pixelPoint)
+ .subscribe((lngLat) => {
+ resolve(lngLat);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+ /**
+ * Unproject canvas pixel coordinates to basic image coordinates for the
+ * current image.
+ *
+ * @description The pixel point may not always correspond to basic image
+ * coordinates. In the case of no correspondence the returned value will
+ * be `null`.
+ *
+ * @param {Array<number>} pixelPoint - Pixel coordinates to unproject.
+ * @returns {Promise<LngLat>} Promise to the basic coordinates corresponding
+ * to the pixel point.
+ *
+ * @example
+ * ```js
+ * viewer.unprojectToBasic([100, 100])
+ * .then(basicPoint => { console.log(basicPoint); });
+ * ```
+ */
+ unprojectToBasic(pixelPoint) {
+ return new Promise((resolve, reject) => {
+ this._observer.unprojectBasic$(pixelPoint)
+ .subscribe((basicPoint) => {
+ resolve(basicPoint);
+ }, (error) => {
+ reject(error);
+ });
+ });
+ }
+}
+
+/**
+ * Internal bootstrap
+ *
+ * This is a workaround to make the CommonJS unit testing
+ * work with Jest. Once Jest/Node supports ES6 modules
+ * fully this should be removed. GeoRBush and UnitBezier
+ * are registered here only to avoid loading them during
+ * unit tests.
+ */
+Graph.register(GeoRBush);
+MarkerSet.register(GeoRBush);
+TraversingState.register(unitbezier);
+ComponentService.registerCover(CoverComponent);
+ComponentService.register(AttributionComponent);
+ComponentService.register(BearingComponent);
+ComponentService.register(CacheComponent);
+ComponentService.register(DirectionComponent);
+ComponentService.register(ImageComponent);
+ComponentService.register(KeyboardComponent);
+ComponentService.register(MarkerComponent);
+ComponentService.register(PointerComponent);
+ComponentService.register(PopupComponent);
+ComponentService.register(SequenceComponent);
+ComponentService.register(SliderComponent);
+ComponentService.register(SpatialComponent);
+ComponentService.register(TagComponent);
+ComponentService.register(ZoomComponent);
+ComponentService.register(ImageFallbackComponent);
+ComponentService.register(NavigationFallbackComponent);
+
+export { Alignment, ArgumentMapillaryError, BearingComponent, CacheComponent, CameraControls, CameraVisualizationMode, CancelMapillaryError, CircleMarker, Component, ComponentSize, DataProviderBase, DirectionComponent, DragPanHandler, ExtremePointTag, Geometry, GeometryProviderBase, GeometryTagError, GraphDataProvider, GraphMapillaryError, Image$1 as Image, KeyPlayHandler, KeySequenceNavigationHandler, KeySpatialNavigationHandler, KeyZoomHandler, KeyboardComponent, MapillaryError, Marker, MarkerComponent, NavigationDirection, OriginalPositionMode, OutlineTag, PointGeometry, PointerComponent, PointsGeometry, PolygonGeometry, Popup, PopupComponent, RectGeometry, RenderMode, RenderPass, S2GeometryProvider, ScrollZoomHandler, SequenceComponent, SimpleMarker, SliderComponent, SliderConfigurationMode, SpatialComponent, SpotTag, Tag, TagComponent, TagDomain, TagMode, TouchZoomHandler, TransitionMode, VertexGeometry, Viewer, ZoomComponent, decompress, enuToGeodetic, fetchArrayBuffer, geodeticToEnu, isFallbackSupported, isSupported, readMeshPbf };
+//# sourceMappingURL=mapillary.module.js.map