PageSourceSearch

https://bamff.land/wp-content/plugins/360-image-panorama-for-elementor/assets/js/panolens.js?ver=6.4.12

js bamff.land collected 2026-10-07 01:45:23 UTC 332,275 bytes, 12,640 lines download raw bytes

1/**
2 * Panolens.js
3 * @author pchen66
4 * @namespace PANOLENS
5 */
6
7var PANOLENS = { REVISION: '9' };
8;/*! npm.im/iphone-inline-video 2.2.2 */
9var enableInlineVideo=function(){"use strict";/*! npm.im/intervalometer */
10function e(e,i,n,r){function t(n){d=i(t,r),e(n-(a||n)),a=n}var d,a;return{start:function(){d||t(0)},stop:function(){n(d),d=null,a=0}}}function i(i){return e(i,requestAnimationFrame,cancelAnimationFrame)}function n(e,i,n){function r(r){n&&!n(e,i)||r.stopImmediatePropagation()}return e.addEventListener(i,r),r}function r(e,i,n,r){function t(){return n[i]}function d(e){n[i]=e}r&&d(e[i]),Object.defineProperty(e,i,{get:t,set:d})}function t(e,i,n){n.addEventListener(i,function(){return e.dispatchEvent(new Event(i))})}function d(e,i){Promise.resolve().then(function(){e.dispatchEvent(new Event(i))})}function a(e){var i=new Audio;return t(e,"play",i),t(e,"playing",i),t(e,"pause",i),i.crossOrigin=e.crossOrigin,i.src=e.src||e.currentSrc||"data:",i}function u(e,i,n){(m||0)+200<Date.now()&&(e[h]=!0,m=Date.now()),n||(e.currentTime=i),k[++T%3]=100*i|0}function o(e){return e.driver.currentTime>=e.video.duration}function s(e){var i=this;i.video.readyState>=i.video.HAVE_FUTURE_DATA?(i.hasAudio||(i.driver.currentTime=i.video.currentTime+e*i.video.playbackRate/1e3,i.video.loop&&o(i)&&(i.driver.currentTime=0)),u(i.video,i.driver.currentTime)):i.video.networkState===i.video.NETWORK_IDLE&&0===i.video.buffered.length&&i.video.load(),i.video.ended&&(delete i.video[h],i.video.pause(!0))}function c(){var e=this,i=e[g];if(e.webkitDisplayingFullscreen)return void e[E]();"data:"!==i.driver.src&&i.driver.src!==e.src&&(u(e,0,!0),i.driver.src=e.src),e.paused&&(i.paused=!1,0===e.buffered.length&&e.load(),i.driver.play(),i.updater.start(),i.hasAudio||(d(e,"play"),i.video.readyState>=i.video.HAVE_ENOUGH_DATA&&d(e,"playing")))}function v(e){var i=this,n=i[g];n.driver.pause(),n.updater.stop(),i.webkitDisplayingFullscreen&&i[w](),n.paused&&!e||(n.paused=!0,n.hasAudio||d(i,"pause"),i.ended&&!i.webkitDisplayingFullscreen&&(i[h]=!0,d(i,"ended")))}function p(e,n){var r={};e[g]=r,r.paused=!0,r.hasAudio=n,r.video=e,r.updater=i(s.bind(r)),n?r.driver=a(e):(e.addEventListener("canplay",function(){e.paused||d(e,"playing")}),r.driver={src:e.src||e.currentSrc||"data:",muted:!0,paused:!0,pause:function(){r.driver.paused=!0},play:function(){r.driver.paused=!1,o(r)&&u(e,0)},get ended(){return o(r)}}),e.addEventListener("emptied",function(){var i=!r.driver.src||"data:"===r.driver.src;r.driver.src&&r.driver.src!==e.src&&(u(e,0,!0),r.driver.src=e.src,i||!n&&e.autoplay?r.driver.play():r.updater.stop())},!1),e.addEventListener("webkitbeginfullscreen",function(){e.paused?n&&0===r.driver.buffered.length&&r.driver.load():(e.pause(),e[E]())}),n&&(e.addEventListener("webkitendfullscreen",function(){r.driver.currentTime=e.currentTime}),e.addEventListener("seeking",function(){k.indexOf(100*e.currentTime|0)<0&&(r.driver.currentTime=e.currentTime)}))}function l(e){var i=e[h];return delete e[h],!e.webkitDisplayingFullscreen&&!i}function f(e){var i=e[g];e[E]=e.play,e[w]=e.pause,e.play=c,e.pause=v,r(e,"paused",i.driver),r(e,"muted",i.driver,!0),r(e,"playbackRate",i.driver,!0),r(e,"ended",i.driver),r(e,"loop",i.driver,!0),n(e,"seeking",function(e){return!e.webkitDisplayingFullscreen}),n(e,"seeked",function(e){return!e.webkitDisplayingFullscreen}),n(e,"timeupdate",l),n(e,"ended",l)}function y(e,i){if(void 0===i&&(i={}),!e[g]){if(!i.everywhere){if(!b)return;if(!(i.iPad||i.ipad?/iPhone|iPod|iPad/:/iPhone|iPod/).test(navigator.userAgent))return}e.pause();var n=e.autoplay;e.autoplay=!1,p(e,!e.muted),f(e),e.classList.add("IIV"),e.muted&&n&&(e.play(),e.addEventListener("playing",function i(){e.autoplay=!0,e.removeEventListener("playing",i)})),/iPhone|iPod|iPad/.test(navigator.platform)||console.warn("iphone-inline-video is not guaranteed to work in emulated environments")}}var m,b="object"==typeof document&&"object-fit"in document.head.style&&!matchMedia("(-webkit-video-playable-inline)").matches,g="bfred-it:iphone-inline-video",h="bfred-it:iphone-inline-video:event",E="bfred-it:iphone-inline-video:nativeplay",w="bfred-it:iphone-inline-video:nativepause",k=[],T=0;return y}();
11;/**
12 * Tween.js - Licensed under the MIT license
13 * https://github.com/tweenjs/tween.js
14 * ----------------------------------------------
15 *
16 * See https://github.com/tweenjs/tween.js/graphs/contributors for the full list of contributors.
17 * Thank you all, you're awesome!
18 */
19
20var TWEEN = TWEEN || (function () {
21
22	var _tweens = [];
23
24	return {
25
26		getAll: function () {
27
28			return _tweens;
29
30		},
31
32		removeAll: function () {
33
34			_tweens = [];
35
36		},
37
38		add: function (tween) {
39
40			_tweens.push(tween);
41
42		},
43
44		remove: function (tween) {
45
46			var i = _tweens.indexOf(tween);
47
48			if (i !== -1) {
49				_tweens.splice(i, 1);
50			}
51
52		},
53
54		update: function (time, preserve) {
55
56			if (_tweens.length === 0) {
57				return false;
58			}
59
60			var i = 0;
61
62			time = time !== undefined ? time : TWEEN.now();
63
64			while (i < _tweens.length) {
65
66				if (_tweens[i].update(time) || preserve) {
67					i++;
68				} else {
69					_tweens.splice(i, 1);
70				}
71
72			}
73
74			return true;
75
76		}
77	};
78
79})();
80
81
82// Include a performance.now polyfill.
83// In node.js, use process.hrtime.
84if (typeof (window) === 'undefined' && typeof (process) !== 'undefined') {
85	TWEEN.now = function () {
86		var time = process.hrtime();
87
88		// Convert [seconds, nanoseconds] to milliseconds.
89		return time[0] * 1000 + time[1] / 1000000;
90	};
91}
92// In a browser, use window.performance.now if it is available.
93else if (typeof (window) !== 'undefined' &&
94         window.performance !== undefined &&
95		 window.performance.now !== undefined) {
96	// This must be bound, because directly assigning this function
97	// leads to an invocation exception in Chrome.
98	TWEEN.now = window.performance.now.bind(window.performance);
99}
100// Use Date.now if it is available.
101else if (Date.now !== undefined) {
102	TWEEN.now = Date.now;
103}
104// Otherwise, use 'new Date().getTime()'.
105else {
106	TWEEN.now = function () {
107		return new Date().getTime();
108	};
109}
110
111
112TWEEN.Tween = function (object) {
113
114	var _object = object;
115	var _valuesStart = {};
116	var _valuesEnd = {};
117	var _valuesStartRepeat = {};
118	var _duration = 1000;
119	var _repeat = 0;
120	var _repeatDelayTime;
121	var _yoyo = false;
122	var _isPlaying = false;
123	var _reversed = false;
124	var _delayTime = 0;
125	var _startTime = null;
126	var _easingFunction = TWEEN.Easing.Linear.None;
127	var _interpolationFunction = TWEEN.Interpolation.Linear;
128	var _chainedTweens = [];
129	var _onStartCallback = null;
130	var _onStartCallbackFired = false;
131	var _onUpdateCallback = null;
132	var _onCompleteCallback = null;
133	var _onStopCallback = null;
134
135	this.to = function (properties, duration) {
136
137		_valuesEnd = properties;
138
139		if (duration !== undefined) {
140			_duration = duration;
141		}
142
143		return this;
144
145	};
146
147	this.start = function (time) {
148
149		TWEEN.add(this);
150
151		_isPlaying = true;
152
153		_onStartCallbackFired = false;
154
155		_startTime = time !== undefined ? time : TWEEN.now();
156		_startTime += _delayTime;
157
158		for (var property in _valuesEnd) {
159
160			// Check if an Array was provided as property value
161			if (_valuesEnd[property] instanceof Array) {
162
163				if (_valuesEnd[property].length === 0) {
164					continue;
165				}
166
167				// Create a local copy of the Array with the start value at the front
168				_valuesEnd[property] = [_object[property]].concat(_valuesEnd[property]);
169
170			}
171
172			// If `to()` specifies a property that doesn't exist in the source object,
173			// we should not set that property in the object
174			if (_object[property] === undefined) {
175				continue;
176			}
177
178			// Save the starting value.
179			_valuesStart[property] = _object[property];
180
181			if ((_valuesStart[property] instanceof Array) === false) {
182				_valuesStart[property] *= 1.0; // Ensures we're using numbers, not strings
183			}
184
185			_valuesStartRepeat[property] = _valuesStart[property] || 0;
186
187		}
188
189		return this;
190
191	};
192
193	this.stop = function () {
194
195		if (!_isPlaying) {
196			return this;
197		}
198
199		TWEEN.remove(this);
200		_isPlaying = false;
201
202		if (_onStopCallback !== null) {
203			_onStopCallback.call(_object, _object);
204		}
205
206		this.stopChainedTweens();
207		return this;
208
209	};
210
211	this.end = function () {
212
213		this.update(_startTime + _duration);
214		return this;
215
216	};
217
218	this.stopChainedTweens = function () {
219
220		for (var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++) {
221			_chainedTweens[i].stop();
222		}
223
224	};
225
226	this.delay = function (amount) {
227
228		_delayTime = amount;
229		return this;
230
231	};
232
233	this.repeat = function (times) {
234
235		_repeat = times;
236		return this;
237
238	};
239
240	this.repeatDelay = function (amount) {
241
242		_repeatDelayTime = amount;
243		return this;
244
245	};
246
247	this.yoyo = function (yoyo) {
248
249		_yoyo = yoyo;
250		return this;
251
252	};
253
254
255	this.easing = function (easing) {
256
257		_easingFunction = easing;
258		return this;
259
260	};
261
262	this.interpolation = function (interpolation) {
263
264		_interpolationFunction = interpolation;
265		return this;
266
267	};
268
269	this.chain = function () {
270
271		_chainedTweens = arguments;
272		return this;
273
274	};
275
276	this.onStart = function (callback) {
277
278		_onStartCallback = callback;
279		return this;
280
281	};
282
283	this.onUpdate = function (callback) {
284
285		_onUpdateCallback = callback;
286		return this;
287
288	};
289
290	this.onComplete = function (callback) {
291
292		_onCompleteCallback = callback;
293		return this;
294
295	};
296
297	this.onStop = function (callback) {
298
299		_onStopCallback = callback;
300		return this;
301
302	};
303
304	this.update = function (time) {
305
306		var property;
307		var elapsed;
308		var value;
309
310		if (time < _startTime) {
311			return true;
312		}
313
314		if (_onStartCallbackFired === false) {
315
316			if (_onStartCallback !== null) {
317				_onStartCallback.call(_object, _object);
318			}
319
320			_onStartCallbackFired = true;
321		}
322
323		elapsed = (time - _startTime) / _duration;
324		elapsed = elapsed > 1 ? 1 : elapsed;
325
326		value = _easingFunction(elapsed);
327
328		for (property in _valuesEnd) {
329
330			// Don't update properties that do not exist in the source object
331			if (_valuesStart[property] === undefined) {
332				continue;
333			}
334
335			var start = _valuesStart[property] || 0;
336			var end = _valuesEnd[property];
337
338			if (end instanceof Array) {
339
340				_object[property] = _interpolationFunction(end, value);
341
342			} else {
343
344				// Parses relative end values with start as base (e.g.: +10, -3)
345				if (typeof (end) === 'string') {
346
347					if (end.charAt(0) === '+' || end.charAt(0) === '-') {
348						end = start + parseFloat(end);
349					} else {
350						end = parseFloat(end);
351					}
352				}
353
354				// Protect against non numeric properties.
355				if (typeof (end) === 'number') {
356					_object[property] = start + (end - start) * value;
357				}
358
359			}
360
361		}
362
363		if (_onUpdateCallback !== null) {
364			_onUpdateCallback.call(_object, value);
365		}
366
367		if (elapsed === 1) {
368
369			if (_repeat > 0) {
370
371				if (isFinite(_repeat)) {
372					_repeat--;
373				}
374
375				// Reassign starting values, restart by making startTime = now
376				for (property in _valuesStartRepeat) {
377
378					if (typeof (_valuesEnd[property]) === 'string') {
379						_valuesStartRepeat[property] = _valuesStartRepeat[property] + parseFloat(_valuesEnd[property]);
380					}
381
382					if (_yoyo) {
383						var tmp = _valuesStartRepeat[property];
384
385						_valuesStartRepeat[property] = _valuesEnd[property];
386						_valuesEnd[property] = tmp;
387					}
388
389					_valuesStart[property] = _valuesStartRepeat[property];
390
391				}
392
393				if (_yoyo) {
394					_reversed = !_reversed;
395				}
396
397				if (_repeatDelayTime !== undefined) {
398					_startTime = time + _repeatDelayTime;
399				} else {
400					_startTime = time + _delayTime;
401				}
402
403				return true;
404
405			} else {
406
407				if (_onCompleteCallback !== null) {
408
409					_onCompleteCallback.call(_object, _object);
410				}
411
412				for (var i = 0, numChainedTweens = _chainedTweens.length; i < numChainedTweens; i++) {
413					// Make the chained tweens start exactly at the time they should,
414					// even if the `update()` method was called way past the duration of the tween
415					_chainedTweens[i].start(_startTime + _duration);
416				}
417
418				return false;
419
420			}
421
422		}
423
424		return true;
425
426	};
427
428};
429
430
431TWEEN.Easing = {
432
433	Linear: {
434
435		None: function (k) {
436
437			return k;
438
439		}
440
441	},
442
443	Quadratic: {
444
445		In: function (k) {
446
447			return k * k;
448
449		},
450
451		Out: function (k) {
452
453			return k * (2 - k);
454
455		},
456
457		InOut: function (k) {
458
459			if ((k *= 2) < 1) {
460				return 0.5 * k * k;
461			}
462
463			return - 0.5 * (--k * (k - 2) - 1);
464
465		}
466
467	},
468
469	Cubic: {
470
471		In: function (k) {
472
473			return k * k * k;
474
475		},
476
477		Out: function (k) {
478
479			return --k * k * k + 1;
480
481		},
482
483		InOut: function (k) {
484
485			if ((k *= 2) < 1) {
486				return 0.5 * k * k * k;
487			}
488
489			return 0.5 * ((k -= 2) * k * k + 2);
490
491		}
492
493	},
494
495	Quartic: {
496
497		In: function (k) {
498
499			return k * k * k * k;
500
501		},
502
503		Out: function (k) {
504
505			return 1 - (--k * k * k * k);
506
507		},
508
509		InOut: function (k) {
510
511			if ((k *= 2) < 1) {
512				return 0.5 * k * k * k * k;
513			}
514
515			return - 0.5 * ((k -= 2) * k * k * k - 2);
516
517		}
518
519	},
520
521	Quintic: {
522
523		In: function (k) {
524
525			return k * k * k * k * k;
526
527		},
528
529		Out: function (k) {
530
531			return --k * k * k * k * k + 1;
532
533		},
534
535		InOut: function (k) {
536
537			if ((k *= 2) < 1) {
538				return 0.5 * k * k * k * k * k;
539			}
540
541			return 0.5 * ((k -= 2) * k * k * k * k + 2);
542
543		}
544
545	},
546
547	Sinusoidal: {
548
549		In: function (k) {
550
551			return 1 - Math.cos(k * Math.PI / 2);
552
553		},
554
555		Out: function (k) {
556
557			return Math.sin(k * Math.PI / 2);
558
559		},
560
561		InOut: function (k) {
562
563			return 0.5 * (1 - Math.cos(Math.PI * k));
564
565		}
566
567	},
568
569	Exponential: {
570
571		In: function (k) {
572
573			return k === 0 ? 0 : Math.pow(1024, k - 1);
574
575		},
576
577		Out: function (k) {
578
579			return k === 1 ? 1 : 1 - Math.pow(2, - 10 * k);
580
581		},
582
583		InOut: function (k) {
584
585			if (k === 0) {
586				return 0;
587			}
588
589			if (k === 1) {
590				return 1;
591			}
592
593			if ((k *= 2) < 1) {
vendor: 26,488 bytes, lines 594-1778
594				return 0.5 * Math.pow(1024, k - 1);
595			}
596
597			return 0.5 * (- Math.pow(2, - 10 * (k - 1)) + 2);
598
599		}
600
601	},
602
603	Circular: {
604
605		In: function (k) {
606
607			return 1 - Math.sqrt(1 - k * k);
608
609		},
610
611		Out: function (k) {
612
613			return Math.sqrt(1 - (--k * k));
614
615		},
616
617		InOut: function (k) {
618
619			if ((k *= 2) < 1) {
620				return - 0.5 * (Math.sqrt(1 - k * k) - 1);
621			}
622
623			return 0.5 * (Math.sqrt(1 - (k -= 2) * k) + 1);
624
625		}
626
627	},
628
629	Elastic: {
630
631		In: function (k) {
632
633			if (k === 0) {
634				return 0;
635			}
636
637			if (k === 1) {
638				return 1;
639			}
640
641			return -Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI);
642
643		},
644
645		Out: function (k) {
646
647			if (k === 0) {
648				return 0;
649			}
650
651			if (k === 1) {
652				return 1;
653			}
654
655			return Math.pow(2, -10 * k) * Math.sin((k - 0.1) * 5 * Math.PI) + 1;
656
657		},
658
659		InOut: function (k) {
660
661			if (k === 0) {
662				return 0;
663			}
664
665			if (k === 1) {
666				return 1;
667			}
668
669			k *= 2;
670
671			if (k < 1) {
672				return -0.5 * Math.pow(2, 10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI);
673			}
674
675			return 0.5 * Math.pow(2, -10 * (k - 1)) * Math.sin((k - 1.1) * 5 * Math.PI) + 1;
676
677		}
678
679	},
680
681	Back: {
682
683		In: function (k) {
684
685			var s = 1.70158;
686
687			return k * k * ((s + 1) * k - s);
688
689		},
690
691		Out: function (k) {
692
693			var s = 1.70158;
694
695			return --k * k * ((s + 1) * k + s) + 1;
696
697		},
698
699		InOut: function (k) {
700
701			var s = 1.70158 * 1.525;
702
703			if ((k *= 2) < 1) {
704				return 0.5 * (k * k * ((s + 1) * k - s));
705			}
706
707			return 0.5 * ((k -= 2) * k * ((s + 1) * k + s) + 2);
708
709		}
710
711	},
712
713	Bounce: {
714
715		In: function (k) {
716
717			return 1 - TWEEN.Easing.Bounce.Out(1 - k);
718
719		},
720
721		Out: function (k) {
722
723			if (k < (1 / 2.75)) {
724				return 7.5625 * k * k;
725			} else if (k < (2 / 2.75)) {
726				return 7.5625 * (k -= (1.5 / 2.75)) * k + 0.75;
727			} else if (k < (2.5 / 2.75)) {
728				return 7.5625 * (k -= (2.25 / 2.75)) * k + 0.9375;
729			} else {
730				return 7.5625 * (k -= (2.625 / 2.75)) * k + 0.984375;
731			}
732
733		},
734
735		InOut: function (k) {
736
737			if (k < 0.5) {
738				return TWEEN.Easing.Bounce.In(k * 2) * 0.5;
739			}
740
741			return TWEEN.Easing.Bounce.Out(k * 2 - 1) * 0.5 + 0.5;
742
743		}
744
745	}
746
747};
748
749TWEEN.Interpolation = {
750
751	Linear: function (v, k) {
752
753		var m = v.length - 1;
754		var f = m * k;
755		var i = Math.floor(f);
756		var fn = TWEEN.Interpolation.Utils.Linear;
757
758		if (k < 0) {
759			return fn(v[0], v[1], f);
760		}
761
762		if (k > 1) {
763			return fn(v[m], v[m - 1], m - f);
764		}
765
766		return fn(v[i], v[i + 1 > m ? m : i + 1], f - i);
767
768	},
769
770	Bezier: function (v, k) {
771
772		var b = 0;
773		var n = v.length - 1;
774		var pw = Math.pow;
775		var bn = TWEEN.Interpolation.Utils.Bernstein;
776
777		for (var i = 0; i <= n; i++) {
778			b += pw(1 - k, n - i) * pw(k, i) * v[i] * bn(n, i);
779		}
780
781		return b;
782
783	},
784
785	CatmullRom: function (v, k) {
786
787		var m = v.length - 1;
788		var f = m * k;
789		var i = Math.floor(f);
790		var fn = TWEEN.Interpolation.Utils.CatmullRom;
791
792		if (v[0] === v[m]) {
793
794			if (k < 0) {
795				i = Math.floor(f = m * (1 + k));
796			}
797
798			return fn(v[(i - 1 + m) % m], v[i], v[(i + 1) % m], v[(i + 2) % m], f - i);
799
800		} else {
801
802			if (k < 0) {
803				return v[0] - (fn(v[0], v[0], v[1], v[1], -f) - v[0]);
804			}
805
806			if (k > 1) {
807				return v[m] - (fn(v[m], v[m], v[m - 1], v[m - 1], f - m) - v[m]);
808			}
809
810			return fn(v[i ? i - 1 : 0], v[i], v[m < i + 1 ? m : i + 1], v[m < i + 2 ? m : i + 2], f - i);
811
812		}
813
814	},
815
816	Utils: {
817
818		Linear: function (p0, p1, t) {
819
820			return (p1 - p0) * t + p0;
821
822		},
823
824		Bernstein: function (n, i) {
825
826			var fc = TWEEN.Interpolation.Utils.Factorial;
827
828			return fc(n) / fc(i) / fc(n - i);
829
830		},
831
832		Factorial: (function () {
833
834			var a = [1];
835
836			return function (n) {
837
838				var s = 1;
839
840				if (a[n]) {
841					return a[n];
842				}
843
844				for (var i = n; i > 1; i--) {
845					s *= i;
846				}
847
848				a[n] = s;
849				return s;
850
851			};
852
853		})(),
854
855		CatmullRom: function (p0, p1, p2, p3, t) {
856
857			var v0 = (p2 - p0) * 0.5;
858			var v1 = (p3 - p1) * 0.5;
859			var t2 = t * t;
860			var t3 = t * t2;
861
862			return (2 * p1 - 2 * p2 + v0 + v1) * t3 + (- 3 * p1 + 3 * p2 - 2 * v0 - v1) * t2 + v0 * t + p1;
863
864		}
865
866	}
867
868};
869
870// UMD (Universal Module Definition)
871(function (root) {
872
873	if (typeof define === 'function' && define.amd) {
874
875		// AMD
876		define([], function () {
877			return TWEEN;
878		});
879
880	} else if (typeof module !== 'undefined' && typeof exports === 'object') {
881
882		// Node.js
883		module.exports = TWEEN;
884
885	} else if (root !== undefined) {
886
887		// Global variable
888		root.TWEEN = TWEEN;
889
890	}
891
892})(this);
893;/**
894 * @author qiao / https://github.com/qiao
895 * @author mrdoob / http://mrdoob.com
896 * @author alteredq / http://alteredqualia.com/
897 * @author WestLangley / http://github.com/WestLangley
898 * @author erich666 / http://erichaines.com
899 */
900/*global THREE, console */
901
902// This set of controls performs orbiting, dollying (zooming), and panning. It maintains
903// the "up" direction as +Y, unlike the TrackballControls. Touch on tablet and phones is
904// supported.
905//
906//    Orbit - left mouse / touch: one finger move
907//    Zoom - middle mouse, or mousewheel / touch: two finger spread or squish
908//    Pan - right mouse, or arrow keys / touch: three finter swipe
909
910THREE.OrbitControls = function ( object, domElement ) {
911
912	this.object = object;
913	this.domElement = ( domElement !== undefined ) ? domElement : document;
914	this.frameId;
915
916	// API
917
918	// Set to false to disable this control
919	this.enabled = true;
920
921	// "target" sets the location of focus, where the control orbits around
922	// and where it pans with respect to.
923	this.target = new THREE.Vector3();
924
925	// center is old, deprecated; use "target" instead
926	this.center = this.target;
927
928	// This option actually enables dollying in and out; left as "zoom" for
929	// backwards compatibility
930	this.noZoom = false;
931	this.zoomSpeed = 1.0;
932
933	// Limits to how far you can dolly in and out ( PerspectiveCamera only )
934	this.minDistance = 0;
935	this.maxDistance = Infinity;
936
937	// Limits to how far you can zoom in and out ( OrthographicCamera only )
938	this.minZoom = 0;
939	this.maxZoom = Infinity;
940
941	// Set to true to disable this control
942	this.noRotate = false;
943	this.rotateSpeed = -0.15;
944
945	// Set to true to disable this control
946	this.noPan = true;
947	this.keyPanSpeed = 7.0;	// pixels moved per arrow key push
948
949	// Set to true to automatically rotate around the target
950	this.autoRotate = false;
951	this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
952
953	// How far you can orbit vertically, upper and lower limits.
954	// Range is 0 to Math.PI radians.
955	this.minPolarAngle = 0; // radians
956	this.maxPolarAngle = Math.PI; // radians
957
958	// Momentum
959  	this.momentumDampingFactor = 0.90;
960  	this.momentumScalingFactor = -0.005;
961  	this.momentumKeydownFactor = 20;
962
963  	// Fov
964  	this.minFov = 30;
965  	this.maxFov = 120;
966
967	// How far you can orbit horizontally, upper and lower limits.
968	// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
969	this.minAzimuthAngle = - Infinity; // radians
970	this.maxAzimuthAngle = Infinity; // radians
971
972	// Set to true to disable use of the keys
973	this.noKeys = false;
974
975	// The four arrow keys
976	this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40 };
977
978	// Mouse buttons
979	this.mouseButtons = { ORBIT: THREE.MOUSE.LEFT, ZOOM: THREE.MOUSE.MIDDLE, PAN: THREE.MOUSE.RIGHT };
980
981	////////////
982	// internals
983
984	var scope = this;
985
986	var EPS = 10e-8;
987	var MEPS = 10e-5;
988
989	var rotateStart = new THREE.Vector2();
990	var rotateEnd = new THREE.Vector2();
991	var rotateDelta = new THREE.Vector2();
992
993	var panStart = new THREE.Vector2();
994	var panEnd = new THREE.Vector2();
995	var panDelta = new THREE.Vector2();
996	var panOffset = new THREE.Vector3();
997
998	var offset = new THREE.Vector3();
999
1000	var dollyStart = new THREE.Vector2();
1001	var dollyEnd = new THREE.Vector2();
1002	var dollyDelta = new THREE.Vector2();
1003
1004	var theta;
1005	var phi;
1006	var phiDelta = 0;
1007	var thetaDelta = 0;
1008	var scale = 1;
1009	var pan = new THREE.Vector3();
1010
1011	var lastPosition = new THREE.Vector3();
1012	var lastQuaternion = new THREE.Quaternion();
1013
1014	var momentumLeft = 0, momentumUp = 0;
1015	var eventCurrent, eventPrevious;
1016	var momentumOn = false;
1017
1018	var keyUp, keyBottom, keyLeft, keyRight;
1019
1020	var STATE = { NONE : -1, ROTATE : 0, DOLLY : 1, PAN : 2, TOUCH_ROTATE : 3, TOUCH_DOLLY : 4, TOUCH_PAN : 5 };
1021
1022	var state = STATE.NONE;
1023
1024	// for reset
1025
1026	this.target0 = this.target.clone();
1027	this.position0 = this.object.position.clone();
1028	this.zoom0 = this.object.zoom;
1029
1030	// so camera.up is the orbit axis
1031
1032	var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
1033	var quatInverse = quat.clone().inverse();
1034
1035	// events
1036
1037	var changeEvent = { type: 'change' };
1038	var startEvent = { type: 'start' };
1039	var endEvent = { type: 'end' };
1040
1041	this.setLastQuaternion = function ( quaternion ) {
1042		lastQuaternion.copy( quaternion );
1043		scope.object.quaternion.copy( quaternion );
1044	};
1045
1046	this.getLastPosition = function () {
1047		return lastPosition;
1048	}
1049
1050	this.rotateLeft = function ( angle ) {
1051
1052		if ( angle === undefined ) {
1053
1054			angle = getAutoRotationAngle();
1055
1056		}
1057
1058		thetaDelta -= angle;
1059
1060
1061	};
1062
1063	this.rotateUp = function ( angle ) {
1064
1065		if ( angle === undefined ) {
1066
1067			angle = getAutoRotationAngle();
1068
1069		}
1070
1071		phiDelta -= angle;
1072
1073	};
1074
1075	// pass in distance in world space to move left
1076	this.panLeft = function ( distance ) {
1077
1078		var te = this.object.matrix.elements;
1079
1080		// get X column of matrix
1081		panOffset.set( te[ 0 ], te[ 1 ], te[ 2 ] );
1082		panOffset.multiplyScalar( - distance );
1083
1084		pan.add( panOffset );
1085
1086	};
1087
1088	// pass in distance in world space to move up
1089	this.panUp = function ( distance ) {
1090
1091		var te = this.object.matrix.elements;
1092
1093		// get Y column of matrix
1094		panOffset.set( te[ 4 ], te[ 5 ], te[ 6 ] );
1095		panOffset.multiplyScalar( distance );
1096
1097		pan.add( panOffset );
1098
1099	};
1100
1101	// pass in x,y of change desired in pixel space,
1102	// right and down are positive
1103	this.pan = function ( deltaX, deltaY ) {
1104
1105		var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
1106
1107		if ( scope.object instanceof THREE.PerspectiveCamera ) {
1108
1109			// perspective
1110			var position = scope.object.position;
1111			var offset = position.clone().sub( scope.target );
1112			var targetDistance = offset.length();
1113
1114			// half of the fov is center to top of screen
1115			targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
1116
1117			// we actually don't use screenWidth, since perspective camera is fixed to screen height
1118			scope.panLeft( 2 * deltaX * targetDistance / element.clientHeight );
1119			scope.panUp( 2 * deltaY * targetDistance / element.clientHeight );
1120
1121		} else if ( scope.object instanceof THREE.OrthographicCamera ) {
1122
1123			// orthographic
1124			scope.panLeft( deltaX * (scope.object.right - scope.object.left) / element.clientWidth );
1125			scope.panUp( deltaY * (scope.object.top - scope.object.bottom) / element.clientHeight );
1126
1127		} else {
1128
1129			// camera neither orthographic or perspective
1130			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
1131
1132		}
1133
1134	};
1135
1136	this.momentum = function(){
1137		
1138		if ( !momentumOn ) return;
1139
1140		if ( Math.abs( momentumLeft ) < MEPS && Math.abs( momentumUp ) < MEPS ) { 
1141
1142			momentumOn = false; 
1143			return;
1144		}
1145
1146		momentumUp   *= this.momentumDampingFactor;
1147		momentumLeft *= this.momentumDampingFactor;
1148
1149		thetaDelta -= this.momentumScalingFactor * momentumLeft;
1150		phiDelta   -= this.momentumScalingFactor * momentumUp;
1151
1152	};
1153
1154	this.dollyIn = function ( dollyScale ) {
1155
1156		if ( dollyScale === undefined ) {
1157
1158			dollyScale = getZoomScale();
1159
1160		}
1161
1162		if ( scope.object instanceof THREE.PerspectiveCamera ) {
1163
1164			scale /= dollyScale;
1165
1166		} else if ( scope.object instanceof THREE.OrthographicCamera ) {
1167
1168			scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom * dollyScale ) );
1169			scope.object.updateProjectionMatrix();
1170			scope.dispatchEvent( changeEvent );
1171
1172		} else {
1173
1174			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
1175
1176		}
1177
1178	};
1179
1180	this.dollyOut = function ( dollyScale ) {
1181
1182		if ( dollyScale === undefined ) {
1183
1184			dollyScale = getZoomScale();
1185
1186		}
1187
1188		if ( scope.object instanceof THREE.PerspectiveCamera ) {
1189
1190			scale *= dollyScale;
1191
1192		} else if ( scope.object instanceof THREE.OrthographicCamera ) {
1193
1194			scope.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / dollyScale ) );
1195			scope.object.updateProjectionMatrix();
1196			scope.dispatchEvent( changeEvent );
1197
1198		} else {
1199
1200			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
1201
1202		}
1203
1204	};
1205
1206	this.update = function ( ignoreUpdate ) {
1207
1208		var position = this.object.position;
1209
1210		offset.copy( position ).sub( this.target );
1211
1212		// rotate offset to "y-axis-is-up" space
1213		offset.applyQuaternion( quat );
1214
1215		// angle from z-axis around y-axis
1216
1217		theta = Math.atan2( offset.x, offset.z );
1218
1219		// angle from y-axis
1220
1221		phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );
1222
1223		if ( this.autoRotate && state === STATE.NONE ) {
1224
1225			this.rotateLeft( getAutoRotationAngle() );
1226
1227		}
1228
1229		this.momentum();
1230
1231		theta += thetaDelta;
1232		phi += phiDelta;
1233
1234		// restrict theta to be between desired limits
1235		theta = Math.max( this.minAzimuthAngle, Math.min( this.maxAzimuthAngle, theta ) );
1236
1237		// restrict phi to be between desired limits
1238		phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );
1239
1240		// restrict phi to be betwee EPS and PI-EPS
1241		phi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );
1242
1243		var radius = offset.length() * scale;
1244
1245		// restrict radius to be between desired limits
1246		radius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );
1247
1248		// move target to panned location
1249		this.target.add( pan );
1250
1251		offset.x = radius * Math.sin( phi ) * Math.sin( theta );
1252		offset.y = radius * Math.cos( phi );
1253		offset.z = radius * Math.sin( phi ) * Math.cos( theta );
1254
1255		// rotate offset back to "camera-up-vector-is-up" space
1256		offset.applyQuaternion( quatInverse );
1257
1258		position.copy( this.target ).add( offset );
1259
1260		this.object.lookAt( this.target );
1261
1262		thetaDelta = 0;
1263		phiDelta = 0;
1264		scale = 1;
1265		pan.set( 0, 0, 0 );
1266
1267		// update condition is:
1268		// min(camera displacement, camera rotation in radians)^2 > EPS
1269		// using small-angle approximation cos(x/2) = 1 - x^2 / 8
1270		if ( lastPosition.distanceToSquared( this.object.position ) > EPS
1271		    || 8 * (1 - lastQuaternion.dot(this.object.quaternion)) > EPS ) {
1272
1273			ignoreUpdate !== true && this.dispatchEvent( changeEvent );
1274
1275			lastPosition.copy( this.object.position );
1276			lastQuaternion.copy (this.object.quaternion );
1277
1278		}
1279
1280	};
1281
1282
1283	this.reset = function () {
1284
1285		state = STATE.NONE;
1286
1287		this.target.copy( this.target0 );
1288		this.object.position.copy( this.position0 );
1289		this.object.zoom = this.zoom0;
1290
1291		this.object.updateProjectionMatrix();
1292		this.dispatchEvent( changeEvent );
1293
1294		this.update();
1295
1296	};
1297
1298	this.getPolarAngle = function () {
1299
1300		return phi;
1301
1302	};
1303
1304	this.getAzimuthalAngle = function () {
1305
1306		return theta
1307
1308	};
1309
1310	function getAutoRotationAngle() {
1311
1312		return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
1313
1314	}
1315
1316	function getZoomScale() {
1317
1318		return Math.pow( 0.95, scope.zoomSpeed );
1319
1320	}
1321
1322	function onMouseDown( event ) {
1323
1324		momentumOn = false;
1325
1326   		momentumLeft = momentumUp = 0;
1327
1328		if ( scope.enabled === false ) return;
1329		event.preventDefault();
1330
1331		if ( event.button === scope.mouseButtons.ORBIT ) {
1332			if ( scope.noRotate === true ) return;
1333
1334			state = STATE.ROTATE;
1335
1336			rotateStart.set( event.clientX, event.clientY );
1337
1338		} else if ( event.button === scope.mouseButtons.ZOOM ) {
1339			if ( scope.noZoom === true ) return;
1340
1341			state = STATE.DOLLY;
1342
1343			dollyStart.set( event.clientX, event.clientY );
1344
1345		} else if ( event.button === scope.mouseButtons.PAN ) {
1346			if ( scope.noPan === true ) return;
1347
1348			state = STATE.PAN;
1349
1350			panStart.set( event.clientX, event.clientY );
1351
1352		}
1353
1354		if ( state !== STATE.NONE ) {
1355			document.addEventListener( 'mousemove', onMouseMove, false );
1356			document.addEventListener( 'mouseup', onMouseUp, false );
1357			scope.dispatchEvent( startEvent );
1358		}
1359
1360		scope.update();
1361
1362	}
1363
1364	function onMouseMove( event ) {
1365
1366		if ( scope.enabled === false ) return;
1367
1368		event.preventDefault();
1369
1370		var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
1371
1372		if ( state === STATE.ROTATE ) {
1373
1374			if ( scope.noRotate === true ) return;
1375
1376			rotateEnd.set( event.clientX, event.clientY );
1377			rotateDelta.subVectors( rotateEnd, rotateStart );
1378
1379			// rotating across whole screen goes 360 degrees around
1380			scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
1381
1382			// rotating up and down along whole screen attempts to go 360, but limited to 180
1383			scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
1384
1385			rotateStart.copy( rotateEnd );
1386
1387			if( eventPrevious ){
1388				momentumLeft = event.clientX - eventPrevious.clientX;
1389				momentumUp = event.clientY - eventPrevious.clientY;
1390			}
1391
1392			eventPrevious = event;
1393
1394		} else if ( state === STATE.DOLLY ) {
1395
1396			if ( scope.noZoom === true ) return;
1397
1398			dollyEnd.set( event.clientX, event.clientY );
1399			dollyDelta.subVectors( dollyEnd, dollyStart );
1400
1401			if ( dollyDelta.y > 0 ) {
1402
1403				scope.dollyIn();
1404
1405			} else if ( dollyDelta.y < 0 ) {
1406
1407				scope.dollyOut();
1408
1409			}
1410
1411			dollyStart.copy( dollyEnd );
1412
1413		} else if ( state === STATE.PAN ) {
1414
1415			if ( scope.noPan === true ) return;
1416
1417			panEnd.set( event.clientX, event.clientY );
1418			panDelta.subVectors( panEnd, panStart );
1419
1420			scope.pan( panDelta.x, panDelta.y );
1421
1422			panStart.copy( panEnd );
1423
1424		}
1425
1426		if ( state !== STATE.NONE ) scope.update();
1427
1428	}
1429
1430	function onMouseUp( /* event */ ) {
1431
1432		momentumOn = true;
1433
1434		eventPrevious = undefined;
1435
1436		if ( scope.enabled === false ) return;
1437
1438		document.removeEventListener( 'mousemove', onMouseMove, false );
1439		document.removeEventListener( 'mouseup', onMouseUp, false );
1440		scope.dispatchEvent( endEvent );
1441		state = STATE.NONE;
1442
1443	}
1444
1445	function onMouseWheel( event ) {
1446
1447		if ( scope.enabled === false || scope.noZoom === true || state !== STATE.NONE ) return;
1448
1449		event.preventDefault();
1450		event.stopPropagation();
1451
1452		var delta = 0;
1453
1454		if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9
1455
1456			delta = event.wheelDelta;
1457
1458		} else if ( event.detail !== undefined ) { // Firefox
1459
1460			delta = - event.detail;
1461
1462		}
1463
1464		if ( delta > 0 ) {
1465
1466			//scope.dollyOut();
1467			scope.object.fov = ( scope.object.fov < scope.maxFov ) 
1468				? scope.object.fov + 1
1469				: scope.maxFov;
1470			scope.object.updateProjectionMatrix();
1471
1472		} else if ( delta < 0 ) {
1473
1474			//scope.dollyIn();
1475			scope.object.fov = ( scope.object.fov > scope.minFov ) 
1476				? scope.object.fov - 1
1477				: scope.minFov;
1478			scope.object.updateProjectionMatrix();
1479
1480		}
1481
1482		scope.update();
1483		scope.dispatchEvent( changeEvent );
1484		scope.dispatchEvent( startEvent );
1485		scope.dispatchEvent( endEvent );
1486
1487	}
1488
1489	function onKeyUp ( event ) {
1490
1491		switch ( event.keyCode ) {
1492
1493			case scope.keys.UP:
1494				keyUp = false;
1495				break;
1496
1497			case scope.keys.BOTTOM:
1498				keyBottom = false;
1499				break;
1500
1501			case scope.keys.LEFT:
1502				keyLeft = false;
1503				break;
1504
1505			case scope.keys.RIGHT:
1506				keyRight = false;
1507				break;
1508
1509		}
1510
1511	}
1512
1513	function onKeyDown( event ) {
1514
1515		if ( scope.enabled === false || scope.noKeys === true || scope.noRotate === true ) return;
1516
1517		switch ( event.keyCode ) {
1518
1519			case scope.keys.UP:
1520				keyUp = true;
1521				break;
1522
1523			case scope.keys.BOTTOM:
1524				keyBottom = true;
1525				break;
1526
1527			case scope.keys.LEFT:
1528				keyLeft = true;
1529				break;
1530
1531			case scope.keys.RIGHT:
1532				keyRight = true;
1533				break;
1534
1535		}
1536
1537		if (keyUp || keyBottom || keyLeft || keyRight) {
1538
1539			momentumOn = true;
1540
1541			if (keyUp) momentumUp = - scope.rotateSpeed * scope.momentumKeydownFactor;
1542			if (keyBottom) momentumUp = scope.rotateSpeed * scope.momentumKeydownFactor;
1543			if (keyLeft) momentumLeft = - scope.rotateSpeed * scope.momentumKeydownFactor;
1544			if (keyRight) momentumLeft = scope.rotateSpeed * scope.momentumKeydownFactor;
1545
1546		}
1547
1548	}
1549
1550	function touchstart( event ) {
1551
1552		momentumOn = false;
1553
1554		momentumLeft = momentumUp = 0;
1555
1556		if ( scope.enabled === false ) return;
1557
1558		switch ( event.touches.length ) {
1559
1560			case 1:	// one-fingered touch: rotate
1561
1562				if ( scope.noRotate === true ) return;
1563
1564				state = STATE.TOUCH_ROTATE;
1565
1566				rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
1567				break;
1568
1569			case 2:	// two-fingered touch: dolly
1570
1571				if ( scope.noZoom === true ) return;
1572
1573				state = STATE.TOUCH_DOLLY;
1574
1575				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
1576				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
1577				var distance = Math.sqrt( dx * dx + dy * dy );
1578
1579				break;
1580
1581			case 3: // three-fingered touch: pan
1582
1583				if ( scope.noPan === true ) return;
1584
1585				state = STATE.TOUCH_PAN;
1586
1587				panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
1588				break;
1589
1590			default:
1591
1592				state = STATE.NONE;
1593
1594		}
1595
1596		if ( state !== STATE.NONE ) scope.dispatchEvent( startEvent );
1597
1598	}
1599
1600	function touchmove( event ) {
1601
1602		if ( scope.enabled === false ) return;
1603
1604		event.preventDefault();
1605		event.stopPropagation();
1606
1607		var element = scope.domElement === document ? scope.domElement.body : scope.domElement;
1608
1609		switch ( event.touches.length ) {
1610
1611			case 1: // one-fingered touch: rotate
1612
1613				if ( scope.noRotate === true ) return;
1614				if ( state !== STATE.TOUCH_ROTATE ) return;
1615
1616				rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
1617				rotateDelta.subVectors( rotateEnd, rotateStart );
1618
1619				// rotating across whole screen goes 360 degrees around
1620				scope.rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientWidth * scope.rotateSpeed );
1621				// rotating up and down along whole screen attempts to go 360, but limited to 180
1622				scope.rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight * scope.rotateSpeed );
1623
1624				rotateStart.copy( rotateEnd );
1625
1626				if( eventPrevious ){
1627					momentumLeft = event.touches[ 0 ].pageX - eventPrevious.pageX;
1628					momentumUp = event.touches[ 0 ].pageY - eventPrevious.pageY;
1629				}
1630
1631				eventPrevious = {
1632					pageX: event.touches[ 0 ].pageX,
1633					pageY: event.touches[ 0 ].pageY,
1634				};
1635
1636				scope.update();
1637				break;
1638
1639			case 2: // two-fingered touch: dolly
1640
1641				if ( scope.noZoom === true ) return;
1642				if ( state !== STATE.TOUCH_DOLLY ) return;
1643
1644				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
1645				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
1646				var distance = Math.sqrt( dx * dx + dy * dy );
1647
1648				if ( event.scale < 1 ) {
1649
1650					scope.object.fov = ( scope.object.fov < scope.maxFov ) 
1651						? scope.object.fov + 1
1652						: scope.maxFov;
1653					scope.object.updateProjectionMatrix();
1654
1655				} else if ( event.scale > 1 ) {
1656
1657					scope.object.fov = ( scope.object.fov > scope.minFov ) 
1658						? scope.object.fov - 1
1659						: scope.minFov;
1660					scope.object.updateProjectionMatrix();
1661
1662				}
1663
1664				scope.update();
1665				scope.dispatchEvent( changeEvent );
1666				break;
1667
1668			case 3: // three-fingered touch: pan
1669
1670				if ( scope.noPan === true ) return;
1671				if ( state !== STATE.TOUCH_PAN ) return;
1672
1673				panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
1674				panDelta.subVectors( panEnd, panStart );
1675
1676				scope.pan( panDelta.x, panDelta.y );
1677
1678				panStart.copy( panEnd );
1679
1680				scope.update();
1681				break;
1682
1683			default:
1684
1685				state = STATE.NONE;
1686
1687		}
1688
1689	}
1690
1691	function touchend( /* event */ ) {
1692
1693		momentumOn = true;
1694
1695		eventPrevious = undefined;
1696
1697		if ( scope.enabled === false ) return;
1698
1699		scope.dispatchEvent( endEvent );
1700		state = STATE.NONE;
1701
1702	}
1703
1704	//this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );
1705	this.domElement.addEventListener( 'mousedown', onMouseDown, false );
1706	this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );
1707	this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
1708
1709	this.domElement.addEventListener( 'touchstart', touchstart, false );
1710	this.domElement.addEventListener( 'touchend', touchend, false );
1711	this.domElement.addEventListener( 'touchmove', touchmove, false );
1712
1713	window.addEventListener( 'keyup', onKeyUp, false );
1714	window.addEventListener( 'keydown', onKeyDown, false );
1715
1716	// force an update at start
1717	this.update();
1718
1719};
1720
1721THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
1722THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;;/**
1723 * @author richt / http://richt.me
1724 * @author WestLangley / http://github.com/WestLangley
1725 *
1726 * W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html)
1727 */
1728
1729THREE.DeviceOrientationControls = function( camera, domElement ) {
1730
1731	var scope = this;
1732	var changeEvent = { type: 'change' };
1733
1734	var rotY = 0;
1735	var rotX = 0;
1736	var tempX = 0;
1737	var tempY = 0;
1738
1739	this.camera = camera;
1740	this.camera.rotation.reorder( "YXZ" );
1741	this.domElement = ( domElement !== undefined ) ? domElement : document;
1742
1743	this.enabled = true;
1744
1745	this.deviceOrientation = {};
1746	this.screenOrientation = 0;
1747
1748	this.alpha = 0;
1749	this.alphaOffsetAngle = 0;
1750
1751
1752	var onDeviceOrientationChangeEvent = function( event ) {
1753
1754		scope.deviceOrientation = event;
1755
1756	};
1757
1758	var onScreenOrientationChangeEvent = function() {
1759
1760		scope.screenOrientation = window.orientation || 0;
1761
1762	};
1763
1764	var onTouchStartEvent = function (event) {
1765
1766		event.preventDefault();
1767		event.stopPropagation();
1768
1769		tempX = event.touches[ 0 ].pageX;
1770		tempY = event.touches[ 0 ].pageY;
1771
1772	};
1773
1774	var onTouchMoveEvent = function (event) {
1775
1776		event.preventDefault();
1777		event.stopPropagation();
1778
1779		rotY += THREE.Math.degToRad( ( event.touches[ 0 ].pageX - tempX ) / 4 );
1780		rotX += THREE.Math.degToRad( ( tempY - event.touches[ 0 ].pageY ) / 4 );
1781
1782		scope.updateAlphaOffsetAngle( rotY );
1783
1784		tempX = event.touches[ 0 ].pageX;
1785		tempY = event.touches[ 0 ].pageY;
1786
1787	};
1788
1789	// The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
1790
1791	var setCameraQuaternion = function( quaternion, alpha, beta, gamma, orient ) {
1792
1793		var zee = new THREE.Vector3( 0, 0, 1 );
1794
1795		var euler = new THREE.Euler();
1796
1797		var q0 = new THREE.Quaternion();
1798
1799		var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis
1800
1801		var vectorFingerY;
1802		var fingerQY = new THREE.Quaternion();
1803		var fingerQX = new THREE.Quaternion();
1804
1805		if ( scope.screenOrientation == 0 ) {
1806
1807			vectorFingerY = new THREE.Vector3( 1, 0, 0 );
1808			fingerQY.setFromAxisAngle( vectorFingerY, -rotX );
1809
1810		} else if ( scope.screenOrientation == 180 ) {
1811
1812			vectorFingerY = new THREE.Vector3( 1, 0, 0 );
1813			fingerQY.setFromAxisAngle( vectorFingerY, rotX );
1814
1815		} else if ( scope.screenOrientation == 90 ) {
1816
1817			vectorFingerY = new THREE.Vector3( 0, 1, 0 );
1818			fingerQY.setFromAxisAngle( vectorFingerY, rotX );
1819
1820		} else if ( scope.screenOrientation == - 90) {
1821
1822			vectorFingerY = new THREE.Vector3( 0, 1, 0 );
1823			fingerQY.setFromAxisAngle( vectorFingerY, -rotX );
1824
1825		}
1826
1827		q1.multiply( fingerQY );
1828		q1.multiply( fingerQX );
1829
1830		euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us
1831
1832		quaternion.setFromEuler( euler ); // orient the device
1833
1834		quaternion.multiply( q1 ); // camera looks out the back of the device, not the top
1835
1836		quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation
1837
1838	};
1839
1840	this.connect = function() {
1841
1842		onScreenOrientationChangeEvent(); // run once on load
1843
1844		window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
1845		window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
1846		window.addEventListener( 'deviceorientation', this.update.bind( this ), false );
1847
1848		scope.domElement.addEventListener( "touchstart", onTouchStartEvent, false );
1849		scope.domElement.addEventListener( "touchmove", onTouchMoveEvent, false );
1850
1851		scope.enabled = true;
1852
1853	};
1854
1855	this.disconnect = function() {
1856
1857		window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
1858		window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
1859		window.removeEventListener( 'deviceorientation', this.update.bind( this ), false );
1860
1861		scope.domElement.removeEventListener( "touchstart", onTouchStartEvent, false );
1862		scope.domElement.removeEventListener( "touchmove", onTouchMoveEvent, false );
1863
1864		scope.enabled = false;
1865
1866	};
1867
1868	this.update = function( ignoreUpdate ) {
1869
1870		if ( scope.enabled === false ) return;
1871
1872		var alpha = scope.deviceOrientation.alpha ? THREE.Math.degToRad( scope.deviceOrientation.alpha ) + this.alphaOffsetAngle : 0; // Z
1873		var beta = scope.deviceOrientation.beta ? THREE.Math.degToRad( scope.deviceOrientation.beta ) : 0; // X'
1874		var gamma = scope.deviceOrientation.gamma ? THREE.Math.degToRad( scope.deviceOrientation.gamma ) : 0; // Y''
1875		var orient = scope.screenOrientation ? THREE.Math.degToRad( scope.screenOrientation ) : 0; // O
1876
1877		setCameraQuaternion( scope.camera.quaternion, alpha, beta, gamma, orient );
1878		this.alpha = alpha;
1879
1880		ignoreUpdate !== true && this.dispatchEvent( changeEvent );
1881
1882	};
1883
1884	this.updateAlphaOffsetAngle = function( angle ) {
1885
1886		this.alphaOffsetAngle = angle;
1887		this.update();
1888
1889	};
1890
1891	this.dispose = function() {
1892
1893		this.disconnect();
1894
1895	};
1896
1897	this.connect();
1898
1899};
1900
1901THREE.DeviceOrientationControls.prototype = Object.create( THREE.EventDispatcher.prototype );
1902THREE.DeviceOrientationControls.prototype.constructor = THREE.DeviceOrientationControls;; /** The Bend modifier lets you bend the current selection up to 90 degrees about a single axis,
1903 * producing a uniform bend in an object's geometry.
1904 * You can control the angle and direction of the bend on any of three axes.
1905 * The geometry has to have rather large number of polygons!
1906 * options:
1907 * 	 direction - deformation direction (in local coordinates!). 
1908 * 	 axis - deformation axis (in local coordinates!). Vector of direction and axis are perpendicular.
1909 * 	 angle - deformation angle.
1910 * @author Vildanov Almaz / [email protected]
1911 * The algorithm of a bend is based on the chain line cosh: y = 1/b * cosh(b*x) - 1/b. It can be used only in three.js.
1912 */
1913
1914THREE.BendModifier = function () {
1915
1916};
1917
1918THREE.BendModifier.prototype = {
1919
1920    constructor: THREE.BendModifier,
1921
1922    set: function ( direction, axis, angle ) {
1923        this.direction = new THREE.Vector3(); this.direction.copy( direction );
1924		this.axis = new THREE.Vector3(); this.axis.copy( axis );
1925        this.angle = angle;
1926        return this
1927    },
1928
1929	_sign: function (a) {
1930        return 0 > a ? -1 : 0 < a ? 1 : 0
1931    },
1932
1933	_cosh: function( x )  {
1934		return ( Math.exp( x ) + Math.exp( -x ) ) / 2;
1935	},
1936
1937	_sinhInverse: function( x )  {
1938			return  Math.log( Math.abs( x ) + Math.sqrt( x * x + 1 ) );
1939	},
1940
1941    modify: function ( geometry ) {
1942
1943		var thirdAxis = new THREE.Vector3();  thirdAxis.crossVectors( this.direction, this.axis );
1944
1945		// P - matrices of the change-of-coordinates
1946		var P = new THREE.Matrix4();
1947		P.set ( thirdAxis.x, thirdAxis.y, thirdAxis.z, 0, 
1948			this.direction.x, this.direction.y, this.direction.z, 0, 
1949			this.axis.x, this.axis.y, this.axis.z, 0, 
1950			0, 0, 0, 1 ).transpose();
1951
1952		var InverseP =  new THREE.Matrix4().getInverse( P );
1953		var newVertices = []; var oldVertices = []; var anglesBetweenOldandNewVertices = [];
1954
1955		var meshGeometryBoundingBoxMaxx = 0; var meshGeometryBoundingBoxMinx = 0;
1956		var meshGeometryBoundingBoxMaxy = 0; var meshGeometryBoundingBoxMiny = 0;
1957
1958		for (var i = 0; i < geometry.vertices.length; i++)  {
1959
1960			newVertices[i] = new THREE.Vector3(); newVertices[i].copy( geometry.vertices[i] ).applyMatrix4( InverseP );
1961			if ( newVertices[i].x > meshGeometryBoundingBoxMaxx ) { meshGeometryBoundingBoxMaxx = newVertices[i].x; }
1962			if ( newVertices[i].x < meshGeometryBoundingBoxMinx ) { meshGeometryBoundingBoxMinx = newVertices[i].x; }
1963			if ( newVertices[i].y > meshGeometryBoundingBoxMaxy ) { meshGeometryBoundingBoxMaxy = newVertices[i].y; }
1964			if ( newVertices[i].y < meshGeometryBoundingBoxMiny ) { meshGeometryBoundingBoxMiny = newVertices[i].y; }
1965
1966		}
1967
1968		var meshWidthold =  meshGeometryBoundingBoxMaxx - meshGeometryBoundingBoxMinx;
1969		var meshDepth =  meshGeometryBoundingBoxMaxy - meshGeometryBoundingBoxMiny;
1970		var ParamB = 2 * this._sinhInverse( Math.tan( this.angle ) ) / meshWidthold;
1971		var oldMiddlex = (meshGeometryBoundingBoxMaxx + meshGeometryBoundingBoxMinx) / 2;
1972		var oldMiddley = (meshGeometryBoundingBoxMaxy + meshGeometryBoundingBoxMiny) / 2;
1973
1974		for (var i = 0; i < geometry.vertices.length; i++ )  {
1975
1976			oldVertices[i] = new THREE.Vector3(); oldVertices[i].copy( newVertices[i] );
1977			newVertices[i].x = this._sign( newVertices[i].x - oldMiddlex ) * 1 / ParamB * this._sinhInverse( ( newVertices[i].x - oldMiddlex ) * ParamB );
1978
1979		}
1980
1981		var meshWidth = 2 / ParamB * this._sinhInverse( meshWidthold / 2 * ParamB );
1982
1983		var NewParamB = 2 * this._sinhInverse( Math.tan( this.angle ) ) / meshWidth;
1984
1985		var rightEdgePos = new THREE.Vector3( meshWidth / 2, -meshDepth / 2, 0 );
1986		rightEdgePos.y = 1 / NewParamB * this._cosh( NewParamB * rightEdgePos.x ) - 1 / NewParamB - meshDepth / 2;
1987
1988		var bendCenter = new THREE.Vector3( 0, rightEdgePos.y  + rightEdgePos.x / Math.tan( this.angle ), 0 );
1989
1990		for ( var i = 0; i < geometry.vertices.length; i++ )  {
1991
1992			var x0 = this._sign( oldVertices[i].x - oldMiddlex ) * 1 / ParamB * this._sinhInverse( ( oldVertices[i].x - oldMiddlex ) * ParamB );
1993			var y0 = 1 / NewParamB * this._cosh( NewParamB * x0 ) - 1 / NewParamB;
1994
1995			var k = new THREE.Vector3( bendCenter.x - x0, bendCenter.y - ( y0 - meshDepth / 2 ), bendCenter.z ).normalize();
1996
1997			var Q = new THREE.Vector3();
1998			Q.addVectors( new THREE.Vector3( x0, y0 - meshDepth / 2, oldVertices[i].z ), k.multiplyScalar( oldVertices[i].y + meshDepth / 2 ) );
1999			newVertices[i].x = Q.x;  newVertices[i].y = Q.y;
2000
2001		}	
2002
2003		var middle = oldMiddlex * meshWidth / meshWidthold;
2004
2005		for ( var i = 0; i < geometry.vertices.length; i++ )  {
2006
2007			var O = new THREE.Vector3( oldMiddlex, oldMiddley, oldVertices[i].z );
2008			var p = new THREE.Vector3(); p.subVectors( oldVertices[i], O );
2009			var q = new THREE.Vector3(); q.subVectors( newVertices[i], O );
2010
2011			anglesBetweenOldandNewVertices[i] = Math.acos( 1 / this._cosh( ParamB * newVertices[i].x ) )  * this._sign( newVertices[i].x );
2012
2013			newVertices[i].x = newVertices[i].x + middle;
2014			geometry.vertices[i].copy( newVertices[i].applyMatrix4( P ) );
2015
2016		}
2017
2018		geometry.computeFaceNormals();
2019		geometry.verticesNeedUpdate = true;
2020		geometry.normalsNeedUpdate = true;
2021
2022		// compute Vertex Normals
2023		var fvNames = [ 'a', 'b', 'c', 'd' ];
2024
2025		for ( var f = 0, fl = geometry.faces.length; f < fl; f ++ ) {
2026
2027			var face = geometry.faces[ f ];
2028			if ( face.vertexNormals === undefined ) {
2029				continue;
2030			}
2031			for ( var v = 0, vl = face.vertexNormals.length; v < vl; v ++ ) {
2032
2033				var angle = anglesBetweenOldandNewVertices[ face[ fvNames[ v ] ] ];
2034				var x = this.axis.x,
2035					y = this.axis.y,
2036					z = this.axis.z;
2037
2038				var rotateMatrix = new THREE.Matrix3();
2039				rotateMatrix.set ( Math.cos(angle) + (1-Math.cos(angle))*x*x, (1-Math.cos(angle))*x*y - Math.sin(angle)*z, (1-Math.cos(angle))*x*z + Math.sin(angle)*y,
2040								(1-Math.cos(angle))*y*x + Math.sin(angle)*z, Math.cos(angle) + (1-Math.cos(angle))*y*y, (1-Math.cos(angle))*y*z - Math.sin(angle)*x,
2041								(1-Math.cos(angle))*z*x - Math.sin(angle)*y, (1-Math.cos(angle))*z*y + Math.sin(angle)*x, Math.cos(angle) + (1-Math.cos(angle))*z*z );
2042
2043				face.vertexNormals[ v ].applyMatrix3( rotateMatrix );
2044
2045				}
2046
2047			}
2048		// end compute Vertex Normals			
2049
2050		return this			
2051    }	
2052};/**
2053 * @author mrdoob / http://mrdoob.com/
2054 */
2055
2056THREE.CardboardEffect = function ( renderer ) {
2057
2058	var _camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
2059
2060	var _scene = new THREE.Scene();
2061
2062	var _stereo = new THREE.StereoCamera();
2063	_stereo.aspect = 0.5;
2064
2065	var _params = { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat };
2066
2067	var _renderTarget = new THREE.WebGLRenderTarget( 512, 512, _params );
2068	_renderTarget.scissorTest = true;
2069	_renderTarget.texture.generateMipmaps = false;
2070
2071	// Distortion Mesh ported from:
2072	// https://github.com/borismus/webvr-boilerplate/blob/master/src/distortion/barrel-distortion-fragment.js
2073
2074	var distortion = new THREE.Vector2( 0.441, 0.156 );
2075
2076	var geometry = new THREE.PlaneBufferGeometry( 1, 1, 10, 20 ).removeAttribute( 'normal' ).toNonIndexed();
2077
2078	var positions = geometry.attributes.position.array;
2079	var uvs = geometry.attributes.uv.array;
2080
2081	// duplicate
2082	geometry.attributes.position.count *= 2;
2083	geometry.attributes.uv.count *= 2;
2084
2085	var positions2 = new Float32Array( positions.length * 2 );
2086	positions2.set( positions );
2087	positions2.set( positions, positions.length );
2088
2089	var uvs2 = new Float32Array( uvs.length * 2 );
2090	uvs2.set( uvs );
2091	uvs2.set( uvs, uvs.length );
2092
2093	var vector = new THREE.Vector2();
2094	var length = positions.length / 3;
2095
2096	for ( var i = 0, l = positions2.length / 3; i < l; i ++ ) {
2097
2098		vector.x = positions2[ i * 3 + 0 ];
2099		vector.y = positions2[ i * 3 + 1 ];
2100
2101		var dot = vector.dot( vector );
2102		var scalar = 1.5 + ( distortion.x + distortion.y * dot ) * dot;
2103
2104		var offset = i < length ? 0 : 1;
2105
2106		positions2[ i * 3 + 0 ] = ( vector.x / scalar ) * 1.5 - 0.5 + offset;
2107		positions2[ i * 3 + 1 ] = ( vector.y / scalar ) * 3.0;
2108
2109		uvs2[ i * 2 ] = ( uvs2[ i * 2 ] + offset ) * 0.5;
2110
2111	}
2112
2113	geometry.attributes.position.array = positions2;
2114	geometry.attributes.uv.array = uvs2;
2115
2116	//
2117
2118	// var material = new THREE.MeshBasicMaterial( { wireframe: true } );
2119	var material = new THREE.MeshBasicMaterial( { map: _renderTarget.texture } );
2120	var mesh = new THREE.Mesh( geometry, material );
2121	_scene.add( mesh );
2122
2123	//
2124
2125	this.setSize = function ( width, height ) {
2126
2127		renderer.setSize( width, height );
2128
2129		var pixelRatio = renderer.getPixelRatio();
2130
2131		_renderTarget.setSize( width * pixelRatio, height * pixelRatio );
2132
2133	};
2134
2135	this.render = function ( scene, camera ) {
2136
2137		scene.updateMatrixWorld();
2138
2139		if ( camera.parent === null ) camera.updateMatrixWorld();
2140
2141		_stereo.update( camera );
2142
2143		var width = _renderTarget.width / 2;
2144		var height = _renderTarget.height;
2145
2146		_renderTarget.scissor.set( 0, 0, width, height );
2147		_renderTarget.viewport.set( 0, 0, width, height );
2148		renderer.render( scene, _stereo.cameraL, _renderTarget );
2149
2150		_renderTarget.scissor.set( width, 0, width, height );
2151		_renderTarget.viewport.set( width, 0, width, height );
2152		renderer.render( scene, _stereo.cameraR, _renderTarget );
2153
2154		renderer.render( _scene, _camera );
2155
2156	};
2157
2158};;/**
2159 * @author alteredq / http://alteredqualia.com/
2160 * @authod mrdoob / http://mrdoob.com/
2161 * @authod arodic / http://aleksandarrodic.com/
2162 * @authod fonserbc / http://fonserbc.github.io/
2163*/
2164
2165THREE.StereoEffect = function ( renderer ) {
2166
2167	var _stereo = new THREE.StereoCamera();
2168	_stereo.aspect = 0.5;
2169
2170	this.setEyeSeparation = function ( eyeSep ) {
2171
2172		_stereo.eyeSep = eyeSep;
2173
2174	};
2175
2176	this.setSize = function ( width, height ) {
2177
2178		renderer.setSize( width, height );
2179
2180	};
2181
2182	this.render = function ( scene, camera ) {
2183
2184		scene.updateMatrixWorld();
2185
2186		if ( camera.parent === null ) camera.updateMatrixWorld();
2187
2188		_stereo.update( camera );
2189
2190		var size = renderer.getSize();
2191
2192		if ( renderer.autoClear ) renderer.clear();
2193		renderer.setScissorTest( true );
2194
2195		renderer.setScissor( 0, 0, size.width / 2, size.height );
2196		renderer.setViewport( 0, 0, size.width / 2, size.height );
2197		renderer.render( scene, _stereo.cameraL );
2198
2199		renderer.setScissor( size.width / 2, 0, size.width / 2, size.height );
2200		renderer.setViewport( size.width / 2, 0, size.width / 2, size.height );
2201		renderer.render( scene, _stereo.cameraR );
2202
2203		renderer.setScissorTest( false );
2204
2205	};
2206
2207};
2208;var GSVPANO = GSVPANO || {};
2209GSVPANO.PanoLoader = function (parameters) {
2210
2211	'use strict';
2212
2213	var _parameters = parameters || {},
2214		_location,
2215		_zoom,
2216		_panoId,
2217		_panoClient = new google.maps.StreetViewService(),
2218		_count = 0,
2219		_total = 0,
2220		_canvas = [],
2221		_ctx = [],
2222		_wc = 0,
2223		_hc = 0,
2224		result = null,
2225		rotation = 0,
2226		copyright = '',
2227		onSizeChange = null,
2228		onPanoramaLoad = null;
2229
2230	var levelsW = [ 1, 2, 4, 7, 13, 26 ],
2231		levelsH = [ 1, 1, 2, 4, 7, 13 ];
2232
2233	var widths = [ 416, 832, 1664, 3328, 6656, 13312 ],
2234		heights = [ 416, 416, 832, 1664, 3328, 6656 ];
2235
2236	var gl = null;
2237	try{
2238		var canvas = document.createElement( 'canvas' );
2239	    gl = canvas.getContext('experimental-webgl');
2240	    if(gl == null){
2241	        gl = canvas.getContext('webgl');
2242	    }
2243	}
2244	catch(error){}
2245
2246	var maxW = 1024,
2247		maxH = 1024;
2248
2249	if( gl ) {
2250		var maxTexSize = Math.max( gl.getParameter(gl.MAX_TEXTURE_SIZE), 6656 );
2251		//alert( 'MAX_TEXTURE_SIZE ' + maxTexSize );
2252		maxW = maxH = maxTexSize;
2253	}
2254		
2255	this.setProgress = function (loaded, total) {
2256	
2257		if (this.onProgress) {
2258			this.onProgress({loaded: loaded, total: total});
2259		}
2260		
2261	};
2262
2263	this.throwError = function (message) {
2264	
2265		if (this.onError) {
2266			this.onError(message);
2267		} else {
2268			console.error(message);
2269		}
2270		
2271	};
2272
2273	this.adaptTextureToZoom = function () {
2274	
2275		var w = levelsW[ _zoom ] * 416,
2276			h = levelsH[ _zoom ] * 416;
2277
2278		w = widths [ _zoom ];
2279		h = heights[ _zoom ];
2280
2281		_wc = Math.ceil( w / maxW );
2282		_hc = Math.ceil( h / maxH );
2283
2284		_canvas = []; _ctx = [];
2285
2286		var ptr = 0;
2287		for( var y = 0; y < _hc; y++ ) {
2288			for( var x = 0; x < _wc; x++ ) {
2289				var c = document.createElement('canvas');
2290				if( x < ( _wc - 1 ) ) c.width = maxW; else c.width = w - ( maxW * x );
2291				if( y < ( _hc - 1 ) ) c.height = maxH; else c.height = h - ( maxH * y );
2292				_canvas.push( c );
2293				_ctx.push( c.getContext('2d') );
2294				ptr++;
2295			}
2296		}
2297
2298	};
2299
2300	this.composeFromTile = function (x, y, texture) {
2301	
2302		x *= 512;
2303		y *= 512;
2304		var px = Math.floor( x / maxW ), py = Math.floor( y / maxH );
2305
2306		x -= px * maxW;
2307		y -= py * maxH;
2308
2309		_ctx[ py * _wc + px ].drawImage(texture, 0, 0, texture.width, texture.height, x, y, 512, 512 );
2310		this.progress();
2311		
2312	}; 
2313
2314	this.progress = function() {
2315
2316		_count++;
2317		
2318		var p = Math.round(_count * 100 / _total);
2319		this.setProgress(_count, _total);
2320		
2321		if (_count === _total) {
2322			this.canvas = _canvas;
2323			this.panoId = _panoId;
2324			this.zoom = _zoom;
2325			if (this.onPanoramaLoad) {
2326				this.onPanoramaLoad(_canvas[0]);
2327			}
2328		}
2329	}
2330
2331	this.loadFromId = function( id ) {
2332
2333		_panoId = id;
2334		this.composePanorama();
2335
2336	};
2337
2338	this.composePanorama = function () {
2339	
2340		this.setProgress(0, 1);
2341		
2342		var w = levelsW[ _zoom ],
2343			h = levelsH[ _zoom ],
2344			self = this,
2345			url,
2346			x,
2347			y;
2348			
2349		_count = 0;
2350		_total = w * h;
2351
2352		var self = this;
2353		for( var y = 0; y < h; y++ ) {
2354			for( var x = 0; x < w; x++ ) {
2355				var url = 'https://geo0.ggpht.com/cbk?cb_client=maps_sv.tactile&authuser=0&hl=en&output=tile&zoom=' + _zoom + '&x=' + x + '&y=' + y + '&panoid=' + _panoId + '&nbt&fover=2';
2356				( function( x, y ) { 
2357					if( _parameters.useWebGL ) {
2358						var texture = THREE.ImageUtils.loadTexture( url, null, function() {
2359							self.composeFromTile( x, y, texture );
2360						} );
2361					} else {
2362						var img = new Image();
2363						img.addEventListener( 'load', function() {
2364							self.composeFromTile( x, y, this );			
2365						} );
2366						img.crossOrigin = '';
2367						img.src = url;
2368					}
2369				} )( x, y );
2370			}
2371		}
2372		
2373	};
2374	
2375	this.load = function ( panoid ) {
2376	
2377		this.loadPano( panoid );
2378
2379	};
2380
2381	this.loadPano = function( id ) {
2382
2383		var self = this;
2384		_panoClient.getPanoramaById( id, function (result, status) {
2385			if (status === google.maps.StreetViewStatus.OK) {
2386				self.result = result;
2387				if( self.onPanoramaData ) self.onPanoramaData( result );
2388				//var h = google.maps.geometry.spherical.computeHeading(location, result.location.latLng);
2389				//rotation = (result.tiles.centerHeading - h) * Math.PI / 180.0;
2390				copyright = result.copyright;
2391				self.copyright = result.copyright;
2392				_panoId = result.location.pano;
2393				self.location = location;
2394				self.composePanorama();
2395			} else {
2396				if( self.onNoPanoramaData ) self.onNoPanoramaData( status );
2397				self.throwError('Could not retrieve panorama for the following reason: ' + status);
2398			}
2399		});
2400		
2401	};
2402	
2403	this.setZoom = function( z ) {
2404		_zoom = z;
2405		this.adaptTextureToZoom();
2406	};
2407
2408	this.setZoom( _parameters.zoom || 1 );
2409
2410};;(function(){
2411
2412    'use strict';
2413
2414    /**
2415     * Data Image Source
2416     * @type {String}
2417     */
2418    PANOLENS.DataImageSource = 'https://pchen66.github.io/Panolens/asset/icon/';
2419
2420    /**
2421     * Data Image
2422     * @memberOf PANOLENS
2423     * @enum {string}
2424     */
2425    PANOLENS.DataImage = {
2426        Info:               PANOLENS.DataImageSource + 'information.png',
2427        Arrow:              PANOLENS.DataImageSource + 'arrow-up.png',
2428        FullscreenEnter:    PANOLENS.DataImageSource + 'fullscreen.png',
2429        FullscreenLeave:    PANOLENS.DataImageSource + 'fullscreen-exit.png',
2430        VideoPlay:          PANOLENS.DataImageSource + 'video-play.png',
2431        VideoPause:         PANOLENS.DataImageSource + 'pause.png',
2432        WhiteTile:          PANOLENS.DataImageSource + 'tiles.png',
2433        ReticleIdle:        PANOLENS.DataImageSource + 'reticle-idle.png',
2434        Setting:            PANOLENS.DataImageSource + 'setting.png',
2435        ChevronRight:       PANOLENS.DataImageSource + 'chevron-right.png',
2436        Check:              PANOLENS.DataImageSource + 'check.png',
2437        ViewIndicator:      PANOLENS.DataImageSource + 'view-indicator.svg',
2438        ReticleDwell:       PANOLENS.DataImageSource + 'reticle-animation.png'
2439    };
2440
2441})();;(function(){
2442
2443	'use strict';
2444	
2445	/**
2446	 * Control Index Enum
2447	 * @memberOf PANOLENS
2448	 * @enum {number}
2449	 */
2450	
2451	PANOLENS.Controls = {
2452
2453		ORBIT: 0,
2454
2455		DEVICEORIENTATION: 1
2456
2457	};
2458
2459	/**
2460	 * Effect Mode Enum
2461	 * @memberOf PANOLENS
2462	 * @enum {number}
2463	 */
2464	PANOLENS.Modes = {
2465
2466		/** Unknown */
2467		UNKNOWN: 0,
2468
2469		/** Normal */
2470		NORMAL: 1,
2471
2472		/** Google Cardboard*/
2473		CARDBOARD: 2,
2474
2475		/** Stereoscopic **/
2476		STEREO: 3
2477
2478	};
2479
2480})();;(function(){
2481	
2482	'use strict';
2483
2484	/**
2485	 * Utility
2486	 * @namespace PANOLENS.Utils
2487	 * @memberOf PANOLENS
2488	 * @type {object}
2489	 */
2490	PANOLENS.Utils = {};
2491
2492	PANOLENS.Utils.checkTouchSupported = function () {
2493
2494		return window ? 'ontouchstart' in window || window.navigator.msMaxTouchPoints : false;
2495
2496	};
2497
2498})();;(function(){
2499	
2500	'use strict';
2501
2502	/**
2503	 * Image loader with progress based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/ImageLoader.js}
2504	 * @memberOf PANOLENS.Utils
2505	 * @namespace
2506	 */
2507	PANOLENS.Utils.ImageLoader = {};
2508
2509	/**
2510	 * Load an image with XMLHttpRequest to provide progress checking
2511	 * @param  {string}   url        - An image url
2512	 * @param  {function} onLoad     - On load callback
2513	 * @param  {function} onProgress - In progress callback
2514	 * @param  {function} onError    - On error callback
2515	 * @return {HTMLImageElement}    - DOM image element
2516	 */
2517
2518	PANOLENS.Utils.ImageLoader.load = function ( url, onLoad, onProgress, onError ) {
2519
2520		var cached, request, arrayBufferView, blob, urlCreator, image, reference;
2521
2522		// Reference key
2523		for ( var iconName in PANOLENS.DataImage ) {
2524
2525			if ( PANOLENS.DataImage.hasOwnProperty( iconName ) 
2526				&& url === PANOLENS.DataImage[ iconName ] ) {
2527
2528				reference = iconName;
2529
2530			}
2531
2532		}
2533
2534		// Cached
2535		cached = THREE.Cache.get( reference ? reference : url );
2536
2537		if ( cached !== undefined ) {
2538
2539			if ( onLoad ) {
2540
2541				setTimeout( function () {
2542
2543					if ( onProgress ) {
2544
2545						onProgress( { loaded: 1, total: 1 } );
2546
2547					} 
2548					
2549					onLoad( cached );
2550
2551				}, 0 );
2552
2553			}
2554
2555			return cached;
2556
2557		}
2558		
2559		// Construct a new XMLHttpRequest
2560		urlCreator = window.URL || window.webkitURL;
2561		image = document.createElementNS( 'http://www.w3.org/1999/xhtml', 'img' );
2562
2563		// Add to cache
2564		THREE.Cache.add( reference ? reference : url, image );
2565
2566		function onImageLoaded () {
2567
2568			urlCreator.revokeObjectURL( image.src );
2569			onLoad && onLoad( image );
2570
2571		}
2572
2573		if ( url.indexOf( 'data:' ) === 0 ) {
2574
2575			image.addEventListener( 'load', onImageLoaded, false );
2576			image.src = url;
2577			return image;
2578		}
2579
2580		image.crossOrigin = this.crossOrigin !== undefined ? this.crossOrigin : '';
2581
2582		request = new XMLHttpRequest();
2583		request.open( 'GET', url, true );
2584		request.responseType = 'arraybuffer';
2585		request.onprogress = function ( event ) {
2586
2587		    if ( event.lengthComputable ) {
2588
2589		      onProgress && onProgress( { loaded: event.loaded, total: event.total } );
2590
2591		    }
2592
2593		};
2594		request.onloadend = function( event ) {
2595
2596		    arrayBufferView = new Uint8Array( this.response );
2597		    blob = new Blob( [ arrayBufferView ] );
2598		    
2599		    image.addEventListener( 'load', onImageLoaded, false );
2600			image.src = urlCreator.createObjectURL( blob );
2601
2602		};
2603
2604		request.send(null);
2605
2606	};
2607
2608	// Enable cache
2609	THREE.Cache.enabled = true;
2610
2611})();;(function(){
2612	
2613	'use strict';
2614
2615	/**
2616	 * Texture loader based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/TextureLoader.js}
2617	 * @memberOf PANOLENS.Utils
2618	 * @namespace
2619	 */
2620	PANOLENS.Utils.TextureLoader = {};
2621
2622	/**
2623	 * Load image texture
2624	 * @param  {string}   url        - An image url
2625	 * @param  {function} onLoad     - On load callback
2626	 * @param  {function} onProgress - In progress callback
2627	 * @param  {function} onError    - On error callback
2628	 * @return {THREE.Texture}   	 - Image texture
2629	 */
2630	PANOLENS.Utils.TextureLoader.load = function ( url, onLoad, onProgress, onError ) {
2631
2632		var texture = new THREE.Texture(); 
2633
2634		PANOLENS.Utils.ImageLoader.load( url, function ( image ) {
2635
2636			texture.image = image;
2637			texture.needsUpdate = true;
2638
2639			onLoad && onLoad( texture );
2640
2641		}, onProgress, onError );
2642
2643		return texture;
2644
2645	};
2646
2647})();;(function(){
2648	
2649	'use strict';
2650
2651	/**
2652	 * Cube Texture Loader based on {@link https://github.com/mrdoob/three.js/blob/master/src/loaders/CubeTextureLoader.js}
2653	 * @memberOf PANOLENS.Utils
2654	 * @namespace
2655	 */
2656	PANOLENS.Utils.CubeTextureLoader = {};
2657
2658	/**
2659	 * Load 6 images as a cube texture
2660	 * @param  {array}   urls        - Array with 6 image urls
2661	 * @param  {function} onLoad     - On load callback
2662	 * @param  {function} onProgress - In progress callback
2663	 * @param  {function} onError    - On error callback
2664	 * @return {THREE.CubeTexture}   - Cube texture
2665	 */
2666	PANOLENS.Utils.CubeTextureLoader.load = function ( urls, onLoad, onProgress, onError ) {
2667
2668		var texture, loaded, progress, all, loadings;
2669
2670		texture = new THREE.CubeTexture( [] );
2671
2672		loaded = 0;
2673		progress = {};
2674		all = {};
2675
2676		urls.map( function ( url, index ) {
2677
2678			PANOLENS.Utils.ImageLoader.load( url, function ( image ) {
2679
2680				texture.images[ index ] = image;
2681
2682				loaded++;
2683
2684				if ( loaded === 6 ) {
2685
2686					texture.needsUpdate = true;
2687
2688					onLoad && onLoad( texture );
2689
2690				}
2691
2692			}, function ( event ) {
2693
2694				progress[ index ] = { loaded: event.loaded, total: event.total };
2695
2696				all.loaded = 0;
2697				all.total = 0;
2698				loadings = 0;
2699
2700				for ( var i in progress ) {
2701
2702					loadings++;
2703					all.loaded += progress[ i ].loaded;
2704					all.total += progress[ i ].total;
2705
2706				}
2707
2708				if ( loadings < 6 ) {
2709
2710					all.total = all.total / loadings * 6;
2711
2712				}
2713
2714				onProgress && onProgress( all );
2715
2716			}, onError );
2717
2718		} );
2719
2720		return texture;
2721
2722	};
2723
2724})();;/**
2725 * Stereographic projection shader
2726 * based on http://notlion.github.io/streetview-stereographic
2727 * @author pchen66
2728 */
2729
2730PANOLENS.StereographicShader = {
2731
2732	uniforms: {
2733
2734		"tDiffuse":   { value: new THREE.Texture() },
2735		"resolution": { value: 1.0 },
2736		"transform":  { value: new THREE.Matrix4() },
2737		"zoom": 	  { value: 1.0 }
2738
2739	},
2740
2741	vertexShader: [
2742
2743		"varying vec2 vUv;",
2744
2745		"void main() {",
2746
2747			"vUv = uv;",
2748			"gl_Position = vec4( position, 1.0 );",
2749
2750		"}" 
2751
2752	].join( "\n" ),
2753
2754	fragmentShader: [
2755
2756		"uniform sampler2D tDiffuse;",
2757		"uniform float resolution;",
2758		"uniform mat4 transform;",
2759		"uniform float zoom;",
2760
2761		"varying vec2 vUv;",
2762
2763		"const float PI = 3.141592653589793;",
2764
2765		"void main(){",
2766
2767			"vec2 position = -1.0 +  2.0 * vUv;",
2768
2769			"position *= vec2( zoom * resolution, zoom * 0.5 );",
2770
2771			"float x2y2 = position.x * position.x + position.y * position.y;",
2772			"vec3 sphere_pnt = vec3( 2. * position, x2y2 - 1. ) / ( x2y2 + 1. );",
2773
2774			"sphere_pnt = vec3( transform * vec4( sphere_pnt, 1.0 ) );",
2775
2776			"vec2 sampleUV = vec2(",
2777				"(atan(sphere_pnt.y, sphere_pnt.x) / PI + 1.0) * 0.5,",
2778				"(asin(sphere_pnt.z) / PI + 0.5)",
2779			");",
2780
2781			"gl_FragColor = texture2D( tDiffuse, sampleUV );",
2782
2783		"}"
2784
2785	].join( "\n" )
2786
2787};;( function () {
2788
2789	'use strict';
2790
2791	/**
2792	 * Skeleton panorama derived from THREE.Mesh
2793	 * @constructor
2794	 * @param {THREE.Geometry} geometry - The geometry for this panorama
2795	 * @param {THREE.Material} material - The material for this panorama
2796	 */
2797	PANOLENS.Panorama = function ( geometry, material ) {
2798
2799		THREE.Mesh.call( this );
2800
2801		this.type = 'panorama';
2802
2803		this.ImageQualityLow = 1;
2804		this.ImageQualityFair = 2;
2805		this.ImageQualityMedium = 3;
2806		this.ImageQualityHigh = 4;
2807		this.ImageQualitySuperHigh = 5;
2808
2809		this.animationDuration = 1000;
2810
2811		this.defaultInfospotSize = 350;
2812
2813		this.container = undefined;
2814
2815		this.loaded = false;
2816
2817		this.linkedSpots = [];
2818
2819		this.isInfospotVisible = false;
2820		
2821		this.linkingImageURL = undefined;
2822		this.linkingImageScale = undefined;
2823
2824		this.geometry = geometry;
2825
2826		this.material = material;
2827		this.material.side = THREE.DoubleSide;
2828		this.material.visible = false;
2829
2830		this.scale.x *= -1;
2831
2832		this.infospotAnimation = new TWEEN.Tween( this ).to( {}, this.animationDuration / 2 );
2833
2834		this.addEventListener( 'load', this.fadeIn.bind( this ) );
2835		this.addEventListener( 'panolens-container', this.setContainer.bind( this ) );
2836		this.addEventListener( 'click', this.onClick.bind( this ) );
2837
2838		this.setupTransitions();
2839
2840	}
2841
2842	PANOLENS.Panorama.prototype = Object.create( THREE.Mesh.prototype );
2843
2844	PANOLENS.Panorama.prototype.constructor = PANOLENS.Panorama;
2845
2846	/**
2847	 * Adding an object
2848	 * To counter the scale.x = -1, it will automatically add an 
2849	 * empty object with inverted scale on x
2850	 * @param {THREE.Object3D} object - The object to be added
2851	 */
2852	PANOLENS.Panorama.prototype.add = function ( object ) {
2853
2854		var scope, invertedObject;
2855
2856		scope = this;
2857
2858		if ( arguments.length > 1 ) {
2859
2860			for ( var i = 0; i < arguments.length; i ++ ) {
2861
2862				this.add( arguments[ i ] );
2863
2864			}
2865
2866			return this;
2867
2868		}
2869
2870		// In case of infospots
2871		if ( object instanceof PANOLENS.Infospot ) {
2872
2873			invertedObject = object;
2874
2875			if ( object.dispatchEvent ) {
2876
2877				this.container && object.dispatchEvent( { type: 'panolens-container', container: this.container } );
2878				
2879				object.dispatchEvent( { type: 'panolens-infospot-focus', method: function ( vector, duration, easing ) {
2880
2881					/**
2882		        	 * Infospot focus handler event
2883		        	 * @type {object}
2884		        	 * @event PANOLENS.Panorama#panolens-viewer-handler
2885		        	 * @property {string} method - Viewer function name
2886		        	 * @property {*} data - The argument to be passed into the method
2887		        	 */
2888		        	scope.dispatchEvent( { type : 'panolens-viewer-handler', method: 'tweenControlCenter', data: [ vector, duration, easing ] } );
2889
2890
2891				} } );
2892			}
2893
2894		} else {
2895
2896			// Counter scale.x = -1 effect
2897			invertedObject = new THREE.Object3D();
2898			invertedObject.scale.x = -1;
2899			invertedObject.scalePlaceHolder = true;
2900			invertedObject.add( object );
2901
2902		}
2903
2904		THREE.Object3D.prototype.add.call( this, invertedObject );
2905
2906	};
2907
2908	PANOLENS.Panorama.prototype.load = function () {
2909
2910		this.onLoad();
2911		
2912	};
2913
2914	/**
2915	 * Click event handler
2916	 * @param  {object} event - Click event
2917	 * @fires PANOLENS.Infospot#dismiss
2918	 */
2919	PANOLENS.Panorama.prototype.onClick = function ( event ) {
2920
2921		if ( event.intersects && event.intersects.length === 0 ) {
2922
2923			this.traverse( function ( object ) {
2924
2925				/**
2926				 * Dimiss event
2927				 * @type {object}
2928				 * @event PANOLENS.Infospot#dismiss
2929				 */
2930				object.dispatchEvent( { type: 'dismiss' } );
2931
2932			} );
2933
2934		}
2935
2936	};
2937
2938	/**
2939	 * Set container of this panorama 
2940	 * @param {HTMLElement|object} data - Data with container information
2941	 * @fires PANOLENS.Infospot#panolens-container
2942	 */
2943	PANOLENS.Panorama.prototype.setContainer = function ( data ) {
2944
2945		var container;
2946
2947		if ( data instanceof HTMLElement ) {
2948
2949			container = data;
2950
2951		} else if ( data && data.container ) {
2952
2953			container = data.container;
2954
2955		}
2956
2957		if ( container ) {
2958
2959			this.children.forEach( function ( child ) {
2960
2961				if ( child instanceof PANOLENS.Infospot && child.dispatchEvent ) {
2962
2963					/**
2964					 * Set container event
2965					 * @type {object}
2966					 * @event PANOLENS.Infospot#panolens-container
2967					 * @property {HTMLElement} container - The container of this panorama
2968					 */
2969					child.dispatchEvent( { type: 'panolens-container', container: container } );
2970
2971				}
2972
2973			} );
2974
2975			this.container = container;
2976
2977		}
2978		
2979
2980	};
2981
2982	/**
2983	 * This will be called when panorama is loaded
2984	 * @fires PANOLENS.Panorama#load
2985	 */
2986	PANOLENS.Panorama.prototype.onLoad = function () {
2987
2988		this.loaded = true;
2989
2990		/**
2991		 * Load panorama event
2992		 * @type {object}
2993		 * @event PANOLENS.Panorama#load
2994		 */
2995		this.dispatchEvent( { type: 'load' } );
2996
2997	};
2998
2999	/**
3000	 * This will be called when panorama is in progress
3001	 * @fires PANOLENS.Panorama#progress
3002	 */
3003	PANOLENS.Panorama.prototype.onProgress = function ( progress ) {
3004
3005		/**
3006		 * Loading panorama progress event
3007		 * @type {object}
3008		 * @event PANOLENS.Panorama#progress
3009	 	 * @property {object} progress - The progress object containing loaded and total amount
3010		 */
3011		this.dispatchEvent( { type: 'progress', progress: progress } );
3012
3013	};
3014
3015	/**
3016	 * This will be called when panorama loading has error
3017	 * @fires PANOLENS.Panorama#error
3018	 */
3019	PANOLENS.Panorama.prototype.onError = function () {
3020
3021		/**
3022		 * Loading panorama error event
3023		 * @type {object}
3024		 * @event PANOLENS.Panorama#error
3025		 */
3026		this.dispatchEvent( { type: 'error' } );
3027
3028	};
3029
3030	/**
3031	 * Get zoom level based on window width
3032	 * @return {number} zoom level indicating image quality
3033	 */
3034	PANOLENS.Panorama.prototype.getZoomLevel = function () {
3035
3036		var zoomLevel;
3037
3038		if ( window.innerWidth <= 800 ) {
3039
3040			zoomLevel = this.ImageQualityFair;
3041
3042		} else if ( window.innerWidth > 800 &&  window.innerWidth <= 1280 ) {
3043
3044			zoomLevel = this.ImageQualityMedium;
3045
3046		} else if ( window.innerWidth > 1280 && window.innerWidth <= 1920 ) {
3047
3048			zoomLevel = this.ImageQualityHigh;
3049
3050		} else if ( window.innerWidth > 1920 ) {
3051
3052			zoomLevel = this.ImageQualitySuperHigh;
3053
3054		} else {
3055
3056			zoomLevel = this.ImageQualityLow;
3057
3058		}
3059
3060		return zoomLevel;
3061
3062	};
3063
3064	/**
3065	 * Update texture of a panorama
3066	 * @param {THREE.Texture} texture - Texture to be updated
3067	 */
3068	PANOLENS.Panorama.prototype.updateTexture = function ( texture ) {
3069
3070		this.material.map = texture;
3071
3072		this.material.needsUpdate = true;
3073
3074	};
3075
3076	/**
3077	 * Toggle visibility of infospots in this panorama
3078	 * @param  {boolean} isVisible - Visibility of infospots
3079	 * @param  {number} delay - Delay in milliseconds to change visibility
3080	 * @fires PANOLENS.Panorama#infospot-animation-complete
3081	 */
3082	PANOLENS.Panorama.prototype.toggleInfospotVisibility = function ( isVisible, delay ) {
3083
3084		delay = ( delay !== undefined ) ? delay : 0;
3085
3086		var scope, visible;
3087
3088		scope = this;
3089		visible = ( isVisible !== undefined ) ? isVisible : ( this.isInfospotVisible ? false : true );
3090
3091		this.traverse( function ( object ) {
3092
3093			if ( object instanceof PANOLENS.Infospot ) {
3094
3095				visible ? object.show( delay ) : object.hide( delay );
3096
3097			}
3098
3099		} );
3100
3101		this.isInfospotVisible = visible;
3102
3103		// Animation complete event
3104		this.infospotAnimation.onComplete( function () {
3105
3106			/**
3107			 * Complete toggling infospot visibility
3108			 * @event PANOLENS.Panorama#infospot-animation-complete
3109			 * @type {object} 
3110			 */
3111			scope.dispatchEvent( { type : 'infospot-animation-complete', visible: visible } );
3112
3113		} ).delay( delay ).start();
3114
3115	};
3116
3117	/**
3118	 * Set image of this panorama's linking infospot
3119	 * @param {string} url   - Url to the image asset
3120	 * @param {number} scale - Scale factor of the infospot
3121	 */
3122	PANOLENS.Panorama.prototype.setLinkingImage = function ( url, scale ) {
3123
3124		this.linkingImageURL = url;
3125		this.linkingImageScale = scale;
3126
3127	};
3128
3129	/**
3130	 * Link one-way panorama
3131	 * @param  {PANOLENS.Panorama} pano  - The panorama to be linked to
3132	 * @param  {THREE.Vector3} position - The position of infospot which navigates to the pano
3133	 * @param  {number} [imageScale=300] - Image scale of linked infospot
3134	 * @param  {string} [imageSrc=PANOLENS.DataImage.Arrow] - The image source of linked infospot
3135	 */
3136	PANOLENS.Panorama.prototype.link = function ( pano, position, imageScale, imageSrc ) {
3137
3138		var scope = this, spot, scale, img;
3139
3140		this.visible = true;
3141
3142		if ( !position ) {
3143
3144			console.warn( 'Please specify infospot position for linking' );
3145
3146			return;
3147
3148		}
3149
3150		// Infospot scale
3151		if ( imageScale !== undefined ) {
3152
3153			scale = imageScale;
3154
3155		} else if ( pano.linkingImageScale !== undefined ) {
3156
3157			scale = pano.linkingImageScale;
3158
3159		} else {
3160
3161			scale = 300;
3162
3163		}
3164
3165
3166		// Infospot image
3167		if ( imageSrc ) {
3168
3169			img = imageSrc
3170
3171		} else if ( pano.linkingImageURL ) {
3172
3173			img = pano.linkingImageURL;
3174
3175		} else {
3176
3177			img = PANOLENS.DataImage.Arrow;
3178
3179		}
3180
3181		// Creates a new infospot
3182		spot = new PANOLENS.Infospot( scale, img );
3183        spot.position.copy( position );
3184        spot.toPanorama = pano;
3185        spot.addEventListener( 'click', function () {
3186
3187        	/**
3188        	 * Viewer handler event
3189        	 * @type {object}
3190        	 * @event PANOLENS.Panorama#panolens-viewer-handler
3191        	 * @property {string} method - Viewer function name
3192        	 * @property {*} data - The argument to be passed into the method
3193        	 */
3194        	scope.dispatchEvent( { type : 'panolens-viewer-handler', method: 'setPanorama', data: pano } );
3195
3196        } );
3197
3198        this.linkedSpots.push( spot );
3199
3200        this.add( spot );
3201
3202        this.visible = false;
3203
3204	};
3205
3206	PANOLENS.Panorama.prototype.reset = function () {
3207
3208		this.children.length = 0;	
3209
3210	};
3211
3212	PANOLENS.Panorama.prototype.setupTransitions = function () {
3213
3214		this.fadeInAnimation = new TWEEN.Tween( this.material )
3215			.easing( TWEEN.Easing.Quartic.Out )
3216			.onStart( function () {
3217
3218				this.visible = true;
3219				this.material.visible = true;
3220
3221				/**
3222				 * Enter panorama fade in start event
3223				 * @event PANOLENS.Panorama#enter-fade-start
3224				 * @type {object} 
3225				 */
3226				this.dispatchEvent( { type: 'enter-fade-start' } );
3227
3228			}.bind( this ) );
3229
3230		this.fadeOutAnimation = new TWEEN.Tween( this.material )
3231			.easing( TWEEN.Easing.Quartic.Out )
3232			.onComplete( function () {
3233
3234				this.visible = false;
3235				this.material.visible = true;
3236
3237				/**
3238				 * Leave panorama complete event
3239				 * @event PANOLENS.Panorama#leave-complete
3240				 * @type {object} 
3241				 */
3242				this.dispatchEvent( { type: 'leave-complete' } );
3243
3244			}.bind( this ) );
3245
3246		this.enterTransition = new TWEEN.Tween( this )
3247			.easing( TWEEN.Easing.Quartic.Out )
3248			.onComplete( function () {
3249
3250				/**
3251				 * Enter panorama and animation complete event
3252				 * @event PANOLENS.Panorama#enter-animation-complete
3253				 * @type {object} 
3254				 */
3255				this.dispatchEvent( { type: 'enter-animation-complete' } );
3256
3257			}.bind ( this ) )
3258			.start();
3259
3260		this.leaveTransition = new TWEEN.Tween( this )
3261			.easing( TWEEN.Easing.Quartic.Out );
3262
3263	};
3264
3265	/**
3266	 * Start fading in animation
3267	 * @fires PANOLENS.Panorama#enter-fade-complete
3268	 */
3269	PANOLENS.Panorama.prototype.fadeIn = function ( duration ) {
3270
3271		duration = duration >= 0 ? duration : this.animationDuration;
3272
3273		this.fadeOutAnimation.stop();
3274		this.fadeInAnimation
3275		.to( { opacity: 1 }, duration )
3276		.onComplete( function () {
3277
3278				this.toggleInfospotVisibility( true, duration / 2 );
3279
3280				/**
3281				 * Enter panorama fade complete event
3282				 * @event PANOLENS.Panorama#enter-fade-complete
3283				 * @type {object} 
3284				 */
3285				this.dispatchEvent( { type: 'enter-fade-complete' } );			
3286
3287			}.bind( this ) )
3288		.start();
3289
3290	};
3291
3292	/**
3293	 * Start fading out animation
3294	 */
3295	PANOLENS.Panorama.prototype.fadeOut = function ( duration ) {
3296
3297		duration = duration >= 0 ? duration : this.animationDuration;
3298
3299		this.fadeInAnimation.stop();
3300		this.fadeOutAnimation.to( { opacity: 0 }, duration ).start();
3301
3302	};
3303
3304	/**
3305	 * This will be called when entering a panorama 
3306	 * @fires PANOLENS.Panorama#enter
3307	 * @fires PANOLENS.Panorama#enter-animation-start
3308	 */
3309	PANOLENS.Panorama.prototype.onEnter = function () {
3310
3311		var duration = this.animationDuration;
3312
3313		this.leaveTransition.stop();
3314		this.enterTransition
3315			.to( {}, duration )
3316			.onStart( function () {
3317
3318				/**
3319				 * Enter panorama and animation starting event
3320				 * @event PANOLENS.Panorama#enter-animation-start
3321				 * @type {object} 
3322				 */
3323				this.dispatchEvent( { type: 'enter-animation-start' } );
3324				
3325				if ( this.loaded ) {
3326
3327					this.fadeIn( duration );
3328
3329				} else {
3330
3331					this.load();
3332
3333				}
3334				
3335			}.bind( this ) )
3336			.start();
3337
3338		/**
3339		 * Enter panorama event
3340		 * @event PANOLENS.Panorama#enter
3341		 * @type {object} 
3342		 */
3343		this.dispatchEvent( { type: 'enter' } );
3344
3345	};
3346
3347	/**
3348	 * This will be called when leaving a panorama
3349	 * @fires PANOLENS.Panorama#leave
3350	 */
3351	PANOLENS.Panorama.prototype.onLeave = function () {
3352
3353		var duration = this.animationDuration;
3354
3355		this.enterTransition.stop();
3356		this.leaveTransition
3357			.to( {}, duration )
3358			.onStart( function () {
3359
3360				/**
3361				 * Leave panorama and animation starting event
3362				 * @event PANOLENS.Panorama#leave-animation-start
3363				 * @type {object} 
3364				 */
3365				this.dispatchEvent( { type: 'leave-animation-start' } );
3366
3367				this.fadeOut( duration );
3368				this.toggleInfospotVisibility( false );
3369
3370			}.bind( this ) )
3371			.start();
3372
3373		/**
3374		 * Leave panorama event
3375		 * @event PANOLENS.Panorama#leave
3376		 * @type {object} 
3377		 */
3378		this.dispatchEvent( { type: 'leave' } );
3379
3380	};
3381
3382	/**
3383	 * Dispose panorama
3384	 */
3385	PANOLENS.Panorama.prototype.dispose = function () {
3386
3387		/**
3388    	 * On panorama dispose handler
3389    	 * @type {object}
3390    	 * @event PANOLENS.Panorama#panolens-viewer-handler
3391    	 * @property {string} method - Viewer function name
3392    	 * @property {*} data - The argument to be passed into the method
3393    	 */
3394    	this.dispatchEvent( { type : 'panolens-viewer-handler', method: 'onPanoramaDispose', data: this } );
3395
3396		// recursive disposal on 3d objects
3397		function recursiveDispose ( object ) {
3398
3399			for ( var i = object.children.length - 1; i >= 0; i-- ) {
3400
3401				recursiveDispose( object.children[i] );
3402				object.remove( object.children[i] );
3403
3404			}
3405
3406			if ( object instanceof PANOLENS.Infospot ) {
3407
3408				object.dispose();
3409
3410			}
3411			
3412			object.geometry && object.geometry.dispose();
3413			object.material && object.material.dispose();
3414		}
3415
3416		recursiveDispose( this );
3417
3418		if ( this.parent ) {
3419
3420			this.parent.remove( this );
3421
3422		}
3423
3424	};
3425
3426} )();;(function(){
3427	
3428	'use strict';
3429	
3430	/**
3431	 * Equirectangular based image panorama
3432	 * @constructor
3433	 * @param {string} image - Image url or HTMLImageElement
3434	 * @param {number} [radius=5000] - Radius of panorama
3435	 */
3436	PANOLENS.ImagePanorama = function ( image, radius ) {
3437
3438		radius = radius || 5000;
3439
3440		var geometry = new THREE.SphereGeometry( radius, 60, 40 ),
3441			material = new THREE.MeshBasicMaterial( { opacity: 0, transparent: true } );
3442
3443		PANOLENS.Panorama.call( this, geometry, material );
3444
3445		this.src = image;
3446
3447	}
3448
3449	PANOLENS.ImagePanorama.prototype = Object.create( PANOLENS.Panorama.prototype );
3450
3451	PANOLENS.ImagePanorama.prototype.constructor = PANOLENS.ImagePanorama;
3452
3453	/**
3454	 * Load image asset
3455	 * @param  {*} src - Url or image element
3456	 */
3457	PANOLENS.ImagePanorama.prototype.load = function ( src ) {
3458
3459		src = src || this.src;
3460
3461		if ( !src ) { 
3462
3463			console.warn( 'Image source undefined' );
3464
3465			return; 
3466
3467		} else if ( typeof src === 'string' ) {
3468
3469			PANOLENS.Utils.TextureLoader.load( src, this.onLoad.bind( this ), this.onProgress.bind( this ), this.onError.bind( this ) );
3470
3471		} else if ( src instanceof HTMLImageElement ) {
3472
3473			this.onLoad( new THREE.Texture( src ) );
3474
3475		}
3476
3477	};
3478
3479	/**
3480	 * This will be called when image is loaded
3481	 * @param  {THREE.Texture} texture - Texture to be updated
3482	 */
3483	PANOLENS.ImagePanorama.prototype.onLoad = function ( texture ) {
3484
3485		texture.minFilter = texture.magFilter = THREE.LinearFilter;
3486
3487		texture.needsUpdate = true;
3488
3489		this.updateTexture( texture );
3490
3491		// Call onLoad after second frame being painted
3492		window.requestAnimationFrame(function(){
3493
3494			window.requestAnimationFrame(function(){
3495
3496				PANOLENS.Panorama.prototype.onLoad.call( this );
3497				
3498
3499			}.bind(this));
3500
3501		}.bind(this));
3502
3503		
3504
3505	};
3506
3507	PANOLENS.ImagePanorama.prototype.reset = function () {
3508
3509		PANOLENS.Panorama.prototype.reset.call( this );
3510
3511	};
3512
3513})();;(function(){
3514
3515	'use strict';
3516	
3517	/**
3518	 * Google streetview panorama
3519	 * 
3520	 * [How to get Panorama ID]{@link http://stackoverflow.com/questions/29916149/google-maps-streetview-how-to-get-panorama-id}
3521	 * @constructor
3522	 * @param {string} panoId - Panorama id from Google Streetview 
3523	 * @param {number} [radius=5000] - The minimum radius for this panoram
3524	 */
3525	PANOLENS.GoogleStreetviewPanorama = function ( panoId, radius ) {
3526
3527		PANOLENS.ImagePanorama.call( this, undefined, radius );
3528
3529		this.panoId = panoId;
3530
3531		this.gsvLoader = undefined;
3532
3533		this.loadRequested = false;
3534
3535		this.setupGoogleMapAPI();
3536
3537	}
3538
3539	PANOLENS.GoogleStreetviewPanorama.prototype = Object.create( PANOLENS.ImagePanorama.prototype );
3540
3541	PANOLENS.GoogleStreetviewPanorama.constructor = PANOLENS.GoogleStreetviewPanorama;
3542
3543	/**
3544	 * Load Google Street View by panorama id
3545	 * @param {string} panoId - Gogogle Street View panorama id
3546	 */
3547	PANOLENS.GoogleStreetviewPanorama.prototype.load = function ( panoId ) {
3548
3549		this.loadRequested = true;
3550
3551		panoId = ( panoId || this.panoId ) || {};
3552
3553		if ( panoId && this.gsvLoader ) {
3554
3555			this.loadGSVLoader( panoId );
3556
3557		} else {
3558
3559			this.gsvLoader = {};
3560
3561		}
3562
3563	};
3564
3565	/**
3566	 * Setup Google Map API
3567	 */
3568	PANOLENS.GoogleStreetviewPanorama.prototype.setupGoogleMapAPI = function () {
3569
3570		var script = document.createElement( 'script' );
3571		script.src = 'https://maps.googleapis.com/maps/api/js';
3572		script.onreadystatechange = this.setGSVLoader.bind( this );
3573    	script.onload = this.setGSVLoader.bind( this );
3574
3575		document.getElementsByTagName('head')[0].appendChild( script );
3576
3577	};
3578
3579	/**
3580	 * Set GSV Loader
3581	 */
3582	PANOLENS.GoogleStreetviewPanorama.prototype.setGSVLoader = function () {
3583
3584		this.gsvLoader = new GSVPANO.PanoLoader();
3585
3586		if ( this.gsvLoader === {} || this.loadRequested ) {
3587
3588			this.load();
3589
3590		}
3591
3592	};
3593
3594	/**
3595	 * Get GSV Loader
3596	 * @return {object} GSV Loader instance
3597	 */
3598	PANOLENS.GoogleStreetviewPanorama.prototype.getGSVLoader = function () {
3599
3600		return this.gsvLoader;
3601
3602	};
3603
3604	/**
3605	 * Load GSV Loader
3606	 * @param  {string} panoId - Gogogle Street View panorama id
3607	 */
3608	PANOLENS.GoogleStreetviewPanorama.prototype.loadGSVLoader = function ( panoId ) {
3609
3610		this.loadRequested = false;
3611
3612		this.gsvLoader.onProgress = this.onProgress.bind( this );
3613
3614		this.gsvLoader.onPanoramaLoad = this.onLoad.bind( this );
3615
3616		this.gsvLoader.setZoom( this.getZoomLevel() );
3617
3618		this.gsvLoader.load( panoId );
3619
3620		this.gsvLoader.loaded = true;
3621	};
3622
3623	/**
3624	 * This will be called when panorama is loaded
3625	 * @param  {HTMLCanvasElement} canvas - Canvas where the tiles have been drawn
3626	 */
3627	PANOLENS.GoogleStreetviewPanorama.prototype.onLoad = function ( canvas ) {
3628
3629		if ( !this.gsvLoader ) { return; }
3630
3631		PANOLENS.ImagePanorama.prototype.onLoad.call( this, new THREE.Texture( canvas ) );
3632
3633	};
3634
3635	PANOLENS.GoogleStreetviewPanorama.prototype.reset = function () {
3636
3637		this.gsvLoader = undefined;
3638
3639		PANOLENS.ImagePanorama.prototype.reset.call( this );
3640
3641	};
3642
3643})();;(function(){
3644	
3645	'use strict';
3646	
3647	/**
3648	 * Cubemap-based panorama
3649	 * @constructor
3650	 * @param {array} images - An array of cubetexture containing six images
3651	 * @param {number} [edgeLength=10000] - The length of cube's edge
3652	 */
3653	PANOLENS.CubePanorama = function ( images, edgeLength ){
3654
3655		var shader, geometry, material;
3656
3657		this.images = images || [];
3658
3659		edgeLength = edgeLength || 10000;
3660		shader = JSON.parse( JSON.stringify( THREE.ShaderLib[ 'cube' ] ) );
3661
3662		geometry = new THREE.BoxGeometry( edgeLength, edgeLength, edgeLength );
3663		material = new THREE.ShaderMaterial( {
3664
3665			fragmentShader: shader.fragmentShader,
3666			vertexShader: shader.vertexShader,
3667			uniforms: shader.uniforms,
3668			side: THREE.BackSide
3669
3670		} );
3671
3672		PANOLENS.Panorama.call( this, geometry, material );
3673
3674	}
3675
3676	PANOLENS.CubePanorama.prototype = Object.create( PANOLENS.Panorama.prototype );
3677
3678	PANOLENS.CubePanorama.prototype.constructor = PANOLENS.CubePanorama;
3679
3680	/**
3681	 * Load 6 images and bind listeners
3682	 */
3683	PANOLENS.CubePanorama.prototype.load = function () {
3684
3685		PANOLENS.Utils.CubeTextureLoader.load( 	
3686
3687			this.images, 
3688
3689			this.onLoad.bind( this ), 
3690			this.onProgress.bind( this ), 
3691			this.onError.bind( this ) 
3692
3693		);
3694
3695	};
3696
3697	/**
3698	 * This will be called when 6 textures are ready
3699	 * @param  {THREE.CubeTexture} texture - Cube texture
3700	 */
3701	PANOLENS.CubePanorama.prototype.onLoad = function ( texture ) {
3702		
3703		this.material.uniforms[ 'tCube' ].value = texture;
3704
3705		PANOLENS.Panorama.prototype.onLoad.call( this );
3706
3707	};
3708
3709})();;(function(){
3710
3711	'use strict';
3712
3713	/**
3714	 * Basic panorama with 6 faces tile images
3715	 * @constructor
3716	 * @param {number} [edgeLength=10000] - The length of cube's edge
3717	 */
3718	PANOLENS.BasicPanorama = function ( edgeLength ) {
3719		
3720		var tile = PANOLENS.DataImage.WhiteTile;
3721
3722		PANOLENS.CubePanorama.call( this, [ tile, tile, tile, tile, tile, tile ], edgeLength );
3723
3724	}
3725
3726	PANOLENS.BasicPanorama.prototype = Object.create( PANOLENS.CubePanorama.prototype );
3727
3728	PANOLENS.BasicPanorama.prototype.constructor = PANOLENS.BasicPanorama;
3729
3730})();;(function(){
3731
3732	'use strict';
3733
3734	/**
3735	 * Video Panorama
3736	 * @constructor
3737	 * @param {string} src - Equirectangular video url
3738	 * @param {object} [options] - Option for video settings
3739	 * @param {HTMLElement} [options.videoElement] - HTML5 video element contains the video
3740	 * @param {boolean} [options.loop=true] - Specify if the video should loop in the end
3741	 * @param {boolean} [options.muted=false] - Mute the video or not
3742	 * @param {boolean} [options.autoplay=false] - Specify if the video should auto play
3743	 * @param {boolean} [options.playsinline=true] - Specify if video should play inline for iOS. If you want it to auto play inline, set both autoplay and muted options to true
3744	 * @param {string} [options.crossOrigin="anonymous"] - Sets the cross-origin attribute for the video, which allows for cross-origin videos in some browsers (Firefox, Chrome). Set to either "anonymous" or "use-credentials".
3745	 * @param {number} [radius=5000] - The minimum radius for this panoram
3746	 */
3747	PANOLENS.VideoPanorama = function ( src, options, radius ) {
3748
3749		radius = radius || 5000;
3750
3751		var geometry = new THREE.SphereGeometry( radius, 60, 40 ),
3752			material = new THREE.MeshBasicMaterial( { opacity: 0, transparent: true } );
3753
3754		PANOLENS.Panorama.call( this, geometry, material );
3755
3756		this.src = src;
3757		this.options = options || {};
3758		this.options.playsinline = this.options.playsinline !== false ? true : false;
3759
3760		this.videoElement = undefined;
3761		this.videoRenderObject = undefined;
3762		this.videoProgress = 0;
3763
3764		this.isIOS = /iPhone|iPad|iPod/i.test( navigator.userAgent );
3765		this.isMobile = this.isIOS || /Android|BlackBerry|Opera Mini|IEMobile/i.test( navigator.userAgent );
3766
3767		this.addEventListener( 'leave', this.pauseVideo.bind( this ) );
3768		this.addEventListener( 'enter-fade-start', this.resumeVideoProgress.bind( this ) );
3769		this.addEventListener( 'video-toggle', this.toggleVideo.bind( this ) );
3770		this.addEventListener( 'video-time', this.setVideoCurrentTime.bind( this ) );
3771
3772	};
3773
3774	PANOLENS.VideoPanorama.prototype = Object.create( PANOLENS.Panorama.prototype );
3775
3776	PANOLENS.VideoPanorama.constructor = PANOLENS.VideoPanorama;
3777
3778	/**
3779	 * Load video panorama
3780	 * @param  {string} src     - The video url
3781	 * @param  {object} options - Option object containing videoElement
3782	 * @fires  PANOLENS.Panorama#panolens-viewer-handler
3783	 */
3784	PANOLENS.VideoPanorama.prototype.load = function ( src, options ) {
3785
3786		var scope = this;
3787
3788		src = ( src || this.src ) || '';
3789		options = ( options || this.options ) || {};
3790
3791		this.videoElement = options.videoElement || document.createElement( 'video' );
3792
3793		this.videoElement.muted = options.muted || false;
3794		this.videoElement.loop = ( options.loop !== undefined ) ? options.loop : true;
3795		this.videoElement.autoplay = ( options.autoplay !== undefined ) ? options.autoplay : false;
3796		this.videoElement.crossOrigin = ( options.crossOrigin !== undefined ) ? options.crossOrigin : "anonymous";
3797		
3798		// iphone inline player
3799		if (options.playsinline) {
3800			this.videoElement.setAttribute( "playsinline", "" );
3801			this.videoElement.setAttribute( "webkit-playsinline", "" );
3802		} 
3803
3804		var onloadeddata = function(){
3805
3806			scope.onProgress( { loaded: 1, total: 1 } );
3807			scope.setVideoTexture( scope.videoElement );
3808
3809			if ( scope.videoElement.autoplay ) {
3810
3811				/**
3812				 * Viewer handler event
3813				 * @type {object}
3814				 * @property {string} method - 'updateVideoPlayButton'
3815				 * @property {boolean} data - Pause video or not
3816				 */
3817				scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } );
3818
3819			}
3820
3821			// For mobile silent autoplay
3822			if ( scope.isMobile ) {
3823
3824				if ( scope.videoElement.autoplay && scope.videoElement.muted ) {
3825
3826					/**
3827					 * Viewer handler event
3828					 * @type {object}
3829					 * @property {string} method - 'updateVideoPlayButton'
3830					 * @property {boolean} data - Pause video or not
3831					 */
3832					scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } );
3833
3834				} else {
3835
3836					/**
3837					 * Viewer handler event
3838					 * @type {object}
3839					 * @property {string} method - 'updateVideoPlayButton'
3840					 * @property {boolean} data - Pause video or not
3841					 */
3842					scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } );
3843
3844				}
3845				
3846			}
3847
3848			scope.onLoad();
3849		};
3850
3851		/**
3852		 * Ready state of the audio/video element
3853		 * 0 = HAVE_NOTHING - no information whether or not the audio/video is ready
3854		 * 1 = HAVE_METADATA - metadata for the audio/video is ready
3855		 * 2 = HAVE_CURRENT_DATA - data for the current playback position is available, but not enough data to play next frame/millisecond
3856		 * 3 = HAVE_FUTURE_DATA - data for the current and at least the next frame is available
3857		 * 4 = HAVE_ENOUGH_DATA - enough data available to start playing
3858		 */
3859		if ( this.videoElement.readyState > 2 ) {
3860
3861			onloadeddata();
3862
3863		} else {
3864
3865			if ( !this.videoElement.querySelectorAll('source').length || !this.videoElement.src ) {
3866
3867				this.videoElement.src =  src;
3868
3869			}
3870
3871			this.videoElement.load();
3872		}
3873
3874		this.videoElement.onloadeddata = onloadeddata;
3875		
3876
3877		this.videoElement.ontimeupdate = function ( event ) {
3878
3879			scope.videoProgress = this.duration >= 0 ? this.currentTime / this.duration : 0;
3880
3881			/**
3882			 * Viewer handler event
3883			 * @type {object}
3884			 * @property {string} method - 'onVideoUpdate'
3885			 * @property {number} data - The percentage of video progress. Range from 0.0 to 1.0
3886			 */
3887			scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onVideoUpdate', data: scope.videoProgress } );
3888
3889		};
3890
3891		this.videoElement.addEventListener( 'ended', function () {
3892			
3893			if ( !scope.options.loop ) {
3894
3895				scope.resetVideo();
3896				scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } );
3897
3898			}
3899
3900		}, false ); 
3901
3902	};
3903
3904	/**
3905	 * Set video texture
3906	 * @param {HTMLVideoElement} video  - The html5 video element
3907	 * @fires PANOLENS.Panorama#panolens-viewer-handler
3908	 */
3909	PANOLENS.VideoPanorama.prototype.setVideoTexture = function ( video ) {
3910
3911		var videoTexture, videoRenderObject, scene;
3912
3913		if ( !video ) return;
3914
3915		videoTexture = new THREE.VideoTexture( video );
3916		videoTexture.minFilter = THREE.LinearFilter;
3917		videoTexture.magFilter = THREE.LinearFilter;
3918		videoTexture.format = THREE.RGBFormat;
3919
3920		videoRenderObject = {
3921
3922			video : video,
3923			videoTexture: videoTexture
3924
3925		};
3926
3927		if ( this.isIOS ){
3928
3929			enableInlineVideo( video );
3930
3931		}
3932
3933		this.updateTexture( videoTexture );
3934
3935		this.videoRenderObject = videoRenderObject;
3936	
3937	};
3938
3939	PANOLENS.VideoPanorama.prototype.reset = function () {
3940
3941		this.videoElement = undefined;	
3942
3943		PANOLENS.Panorama.prototype.reset.call( this );
3944
3945	};
3946
3947	/**
3948	 * Check if video is paused
3949	 * @return {boolean} - is video paused or not
3950	 */
3951	PANOLENS.VideoPanorama.prototype.isVideoPaused = function () {
3952
3953		return this.videoRenderObject.video.paused;
3954
3955	};
3956
3957	/**
3958	 * Toggle video to play or pause
3959	 */
3960	PANOLENS.VideoPanorama.prototype.toggleVideo = function () {
3961
3962		if ( this.videoRenderObject && this.videoRenderObject.video ) {
3963
3964			if ( this.isVideoPaused() ) {
3965
3966				this.videoRenderObject.video.play();
3967
3968
3969			} else {
3970
3971				this.videoRenderObject.video.pause();
3972
3973			}
3974
3975		}
3976
3977	};
3978
3979	/**
3980	 * Set video currentTime
3981	 * @param {object} event - Event contains percentage. Range from 0.0 to 1.0
3982	 */
3983	PANOLENS.VideoPanorama.prototype.setVideoCurrentTime = function ( event ) {
3984
3985		if ( this.videoRenderObject && this.videoRenderObject.video && !Number.isNaN(event.percentage) && event.percentage !== 1 ) {
3986
3987			this.videoRenderObject.video.currentTime = this.videoRenderObject.video.duration * event.percentage;
3988
3989			this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onVideoUpdate', data: event.percentage } );
3990
3991		}
3992
3993	};
3994
3995	/**
3996	 * Play video
3997	 */
3998	PANOLENS.VideoPanorama.prototype.playVideo = function () {
3999
4000		if ( this.videoRenderObject && this.videoRenderObject.video && this.isVideoPaused() ) {
4001
4002			this.videoRenderObject.video.play();
4003
4004		}
4005
4006		/**
4007		 * Play event
4008		 * @type {object}
4009		 * @event 'play'
4010		 * */
4011		this.dispatchEvent( { type: 'play' } );
4012
4013	};
4014
4015	/**
4016	 * Pause video
4017	 */
4018	PANOLENS.VideoPanorama.prototype.pauseVideo = function () {
4019
4020		if ( this.videoRenderObject && this.videoRenderObject.video && !this.isVideoPaused() ) {
4021
4022			this.videoRenderObject.video.pause();
4023
4024		}
4025
4026		/**
4027		 * Pause event
4028		 * @type {object}
4029		 * @event 'pause'
4030		 * */
4031		this.dispatchEvent( { type: 'pause' } );
4032
4033	};
4034
4035	/**
4036	 * Resume video
4037	 */
4038	PANOLENS.VideoPanorama.prototype.resumeVideoProgress = function () {
4039
4040		if ( this.videoElement.autoplay && !this.isMobile ) {
4041
4042			this.playVideo();
4043
4044			/**
4045			 * Viewer handler event
4046			 * @type {object}
4047			 * @property {string} method - 'updateVideoPlayButton'
4048			 * @property {boolean} data - Pause video or not
4049			 */
4050			this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: false } );
4051
4052		} else {
4053
4054			this.pauseVideo();
4055
4056			/**
4057			 * Viewer handler event
4058			 * @type {object}
4059			 * @property {string} method - 'updateVideoPlayButton'
4060			 * @property {boolean} data - Pause video or not
4061			 */
4062			this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'updateVideoPlayButton', data: true } );
4063
4064		}
4065
4066		this.setVideoCurrentTime( { percentage: this.videoProgress } );
4067
4068	};
4069
4070	/**
4071	 * Reset video at stating point
4072	 */
4073	PANOLENS.VideoPanorama.prototype.resetVideo = function () {
4074
4075		if ( this.videoRenderObject && this.videoRenderObject.video ) {
4076
4077			this.setVideoCurrentTime( { percentage: 0 } );
4078
4079		}
4080
4081	};
4082
4083	/**
4084	* Check if video is muted
4085	* @return {boolean} - is video muted or not
4086	*/
4087	PANOLENS.VideoPanorama.prototype.isVideoMuted = function () {
4088
4089		return this.videoRenderObject.video.muted;
4090
4091	};
4092
4093	/**
4094	 * Mute video
4095	 */
4096	PANOLENS.VideoPanorama.prototype.muteVideo = function () {
4097
4098		if ( this.videoRenderObject && this.videoRenderObject.video && !this.isVideoMuted() ) {
4099
4100			this.videoRenderObject.video.muted = true;
4101
4102		}
4103
4104		this.dispatchEvent( { type: 'volumechange' } );
4105
4106	};
4107
4108	/**
4109	 * Unmute video
4110	 */
4111	PANOLENS.VideoPanorama.prototype.unmuteVideo = function () {
4112
4113		if ( this.videoRenderObject && this.videoRenderObject.video && this.isVideoMuted() ) {
4114
4115			this.videoRenderObject.video.muted = false;
4116
4117		}
4118
4119		this.dispatchEvent( { type: 'volumechange' } );
4120
4121	};
4122
4123	/**
4124	 * Returns the video element
4125	 */
4126	PANOLENS.VideoPanorama.prototype.getVideoElement = function () {
4127
4128		return this.videoRenderObject.video;
4129
4130	};
4131
4132	/**
4133	 * Dispose video panorama
4134	 */
4135	PANOLENS.VideoPanorama.prototype.dispose = function () {
4136
4137		this.resetVideo();
4138		this.pauseVideo();
4139		
4140		this.removeEventListener( 'leave', this.pauseVideo.bind( this ) );
4141		this.removeEventListener( 'enter-fade-start', this.resumeVideoProgress.bind( this ) );
4142		this.removeEventListener( 'video-toggle', this.toggleVideo.bind( this ) );
4143		this.removeEventListener( 'video-time', this.setVideoCurrentTime.bind( this ) );
4144
4145		PANOLENS.Panorama.prototype.dispose.call( this );
4146
4147	};
4148
4149})();;(function(){
4150
4151	'use strict';
4152
4153	/**
4154	 * Empty panorama
4155	 * @constructor
4156	 * @param {number} [radius=5000] - Radius of panorama
4157	 */
4158	PANOLENS.EmptyPanorama = function ( radius ) {
4159
4160		radius = radius || 5000;
4161
4162		var geometry = new THREE.Geometry(),
4163			material = new THREE.MeshBasicMaterial( { 
4164				color: 0x000000, opacity: 1, transparent: true 
4165			} );
4166
4167		PANOLENS.Panorama.call( this, geometry, material );
4168
4169	}
4170
4171	PANOLENS.EmptyPanorama.prototype = Object.create( PANOLENS.Panorama.prototype );
4172
4173	PANOLENS.EmptyPanorama.prototype.constructor = PANOLENS.EmptyPanorama;
4174
4175})();;( function () {
4176
4177	/**
4178	 * Little Planet
4179	 * @constructor
4180	 * @param {string} type 		- Type of little planet basic class
4181	 * @param {string} source 		- URL for the image source
4182	 * @param {number} [size=10000] - Size of plane geometry
4183	 * @param {number} [ratio=0.5]  - Ratio of plane geometry's height against width
4184	 */
4185	PANOLENS.LittlePlanet = function ( type, source, size, ratio ) {
4186
4187		type = type || 'image';
4188
4189		type === 'image' && PANOLENS.ImagePanorama.call( this, source, size );
4190
4191		this.size = size || 10000;
4192		this.ratio = ratio || 0.5;
4193		this.EPS = 0.000001;
4194		this.frameId;
4195
4196		this.geometry = this.createGeometry();
4197		this.material = this.createMaterial( this.size );
4198
4199		this.dragging = false;
4200		this.userMouse = new THREE.Vector2();
4201
4202		this.quatA = new THREE.Quaternion();
4203		this.quatB = new THREE.Quaternion();
4204		this.quatCur = new THREE.Quaternion();
4205		this.quatSlerp = new THREE.Quaternion();
4206
4207		this.vectorX = new THREE.Vector3( 1, 0, 0 );
4208		this.vectorY = new THREE.Vector3( 0, 1, 0 );
4209
4210		this.addEventListener( 'window-resize', this.onWindowResize );
4211
4212	};
4213
4214	PANOLENS.LittlePlanet.prototype = Object.create( PANOLENS.ImagePanorama.prototype );
4215
4216	PANOLENS.LittlePlanet.prototype.constructor = PANOLENS.LittlePlanet;
4217
4218	PANOLENS.LittlePlanet.prototype.createGeometry = function () {
4219
4220		return new THREE.PlaneGeometry( this.size, this.size * this.ratio );
4221
4222	};
4223
4224	PANOLENS.LittlePlanet.prototype.createMaterial = function ( size ) {
4225
4226		var uniforms = PANOLENS.StereographicShader.uniforms;
4227		uniforms.zoom.value = size;
4228
4229		return new THREE.ShaderMaterial( {
4230
4231			uniforms: uniforms,
4232			vertexShader: PANOLENS.StereographicShader.vertexShader,
4233			fragmentShader: PANOLENS.StereographicShader.fragmentShader
4234
4235		} );
4236		
4237	};
4238
4239	PANOLENS.LittlePlanet.prototype.registerMouseEvents = function () {
4240
4241		this.container.addEventListener( 'mousedown', this.onMouseDown.bind( this ), false );
4242		this.container.addEventListener( 'mousemove', this.onMouseMove.bind( this ), false );
4243		this.container.addEventListener( 'mouseup', this.onMouseUp.bind( this ), false );
4244		this.container.addEventListener( 'touchstart', this.onMouseDown.bind( this ), false );
4245		this.container.addEventListener( 'touchmove', this.onMouseMove.bind( this ), false );
4246		this.container.addEventListener( 'touchend', this.onMouseUp.bind( this ), false );
4247		this.container.addEventListener( 'mousewheel', this.onMouseWheel.bind( this ), false );
4248		this.container.addEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), false );
4249		this.container.addEventListener( 'contextmenu', this.onContextMenu.bind( this ), false );
4250		
4251	};
4252
4253	PANOLENS.LittlePlanet.prototype.unregisterMouseEvents = function () {
4254
4255		this.container.removeEventListener( 'mousedown', this.onMouseDown.bind( this ), false );
4256		this.container.removeEventListener( 'mousemove', this.onMouseMove.bind( this ), false );
4257		this.container.removeEventListener( 'mouseup', this.onMouseUp.bind( this ), false );
4258		this.container.removeEventListener( 'touchstart', this.onMouseDown.bind( this ), false );
4259		this.container.removeEventListener( 'touchmove', this.onMouseMove.bind( this ), false );
4260		this.container.removeEventListener( 'touchend', this.onMouseUp.bind( this ), false );
4261		this.container.removeEventListener( 'mousewheel', this.onMouseWheel.bind( this ), false );
4262		this.container.removeEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), false );
4263		this.container.removeEventListener( 'contextmenu', this.onContextMenu.bind( this ), false );
4264		
4265	};
4266
4267	PANOLENS.LittlePlanet.prototype.onMouseDown = function ( event ) {
4268
4269		var x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX;
4270		var y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY;
4271
4272		var inputCount = ( event.touches && event.touches.length ) || 1 ;
4273
4274		switch ( inputCount ) {
4275
4276			case 1:
4277
4278				this.dragging = true;
4279				this.userMouse.set( x, y );
4280
4281				break;
4282
4283			case 2:
4284
4285				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
4286				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
4287				var distance = Math.sqrt( dx * dx + dy * dy );
4288				this.userMouse.pinchDistance = distance;
4289
4290				break;
4291
4292			default:
4293
4294				break;
4295
4296		}
4297
4298		this.onUpdateCallback();
4299
4300	};
4301
4302	PANOLENS.LittlePlanet.prototype.onMouseMove = function ( event ) {
4303
4304		var x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX;
4305		var y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY;
4306
4307		var inputCount = ( event.touches && event.touches.length ) || 1 ;
4308
4309		switch ( inputCount ) {
4310
4311			case 1:
4312
4313				var angleX = THREE.Math.degToRad( x - this.userMouse.x ) * 0.4;
4314				var angleY = THREE.Math.degToRad( y - this.userMouse.y ) * 0.4;
4315
4316				if ( this.dragging ) {
4317					this.quatA.setFromAxisAngle( this.vectorY, angleX );
4318					this.quatB.setFromAxisAngle( this.vectorX, angleY );
4319					this.quatCur.multiply( this.quatA ).multiply( this.quatB );
4320					this.userMouse.set( x, y );
4321				}
4322
4323				break;
4324
4325			case 2:
4326
4327				var uniforms = this.material.uniforms;
4328				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
4329				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
4330				var distance = Math.sqrt( dx * dx + dy * dy );
4331
4332				this.addZoomDelta( this.userMouse.pinchDistance - distance );
4333
4334				break;
4335
4336			default:
4337
4338				break;
4339
4340		}
4341
4342	};
4343
4344	PANOLENS.LittlePlanet.prototype.onMouseUp = function ( event ) {
4345
4346		this.dragging = false;
4347
4348	};
4349
4350	PANOLENS.LittlePlanet.prototype.onMouseWheel = function ( event ) {
4351
4352		event.preventDefault();
4353		event.stopPropagation();
4354
4355		var delta = 0;
4356
4357		if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9
4358
4359			delta = event.wheelDelta;
4360
4361		} else if ( event.detail !== undefined ) { // Firefox
4362
4363			delta = - event.detail;
4364
4365		}
4366
4367		this.addZoomDelta( delta );
4368		this.onUpdateCallback();
4369
4370	};
4371
4372	PANOLENS.LittlePlanet.prototype.addZoomDelta = function ( delta ) {
4373
4374		var uniforms = this.material.uniforms;
4375		var lowerBound = this.size * 0.1;
4376		var upperBound = this.size * 10;
4377
4378		uniforms.zoom.value += delta;
4379
4380		if ( uniforms.zoom.value <= lowerBound ) {
4381
4382			uniforms.zoom.value = lowerBound;
4383
4384		} else if ( uniforms.zoom.value >= upperBound ) {
4385
4386			uniforms.zoom.value = upperBound;
4387
4388		}
4389
4390	};
4391
4392	PANOLENS.LittlePlanet.prototype.onUpdateCallback = function () {
4393
4394		this.frameId = window.requestAnimationFrame( this.onUpdateCallback.bind( this ) );
4395		
4396		this.quatSlerp.slerp( this.quatCur, 0.1 );
4397		this.material.uniforms.transform.value.makeRotationFromQuaternion( this.quatSlerp );
4398		
4399		if ( !this.dragging && 1.0 - this.quatSlerp.clone().dot( this.quatCur ) < this.EPS ) {
4400			
4401			window.cancelAnimationFrame( this.frameId );
4402
4403		}
4404
4405	};
4406
4407	PANOLENS.LittlePlanet.prototype.reset = function () {
4408
4409		this.quatCur.set( 0, 0, 0, 1 );
4410		this.quatSlerp.set( 0, 0, 0, 1 );
4411		this.onUpdateCallback();
4412
4413	};
4414
4415	PANOLENS.LittlePlanet.prototype.onLoad = function () {
4416
4417		this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight;
4418
4419		this.registerMouseEvents();
4420		this.onUpdateCallback();
4421		
4422		this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'disableControl' } );
4423		
4424	};
4425
4426	PANOLENS.LittlePlanet.prototype.onLeave = function () {
4427
4428		this.unregisterMouseEvents();
4429
4430		this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'enableControl', data: PANOLENS.Controls.ORBIT } );
4431
4432		window.cancelAnimationFrame( this.frameId );
4433
4434		PANOLENS.Panorama.prototype.onLeave.call( this );
4435		
4436	};
4437
4438	PANOLENS.LittlePlanet.prototype.onWindowResize = function () {
4439
4440		this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight;
4441
4442	};
4443
4444	PANOLENS.LittlePlanet.prototype.onContextMenu = function () {
4445
4446		this.dragging = false;
4447
4448	};
4449
4450})();;( function () {
4451
4452	/**
4453	 * Image Little Planet
4454	 * @constructor
4455	 * @param {string} source 		- URL for the image source
4456	 * @param {number} [size=10000] - Size of plane geometry
4457	 * @param {number} [ratio=0.5]  - Ratio of plane geometry's height against width
4458	 */
4459	PANOLENS.ImageLittlePlanet = function ( source, size, ratio ) {
4460
4461		PANOLENS.LittlePlanet.call( this, 'image', source, size, ratio );
4462
4463	};
4464
4465	PANOLENS.ImageLittlePlanet.prototype = Object.create( PANOLENS.LittlePlanet.prototype );
4466	
4467	PANOLENS.ImageLittlePlanet.prototype.constructor = PANOLENS.ImageLittlePlanet;
4468
4469	PANOLENS.ImageLittlePlanet.prototype.onLoad = function ( texture ) {
4470
4471		this.updateTexture( texture );
4472
4473		PANOLENS.ImagePanorama.prototype.onLoad.call( this, texture );
4474		PANOLENS.LittlePlanet.prototype.onLoad.call( this );
4475
4476	};
4477
4478	PANOLENS.ImageLittlePlanet.prototype.updateTexture = function ( texture ) {
4479
4480		texture.minFilter = texture.magFilter = THREE.LinearFilter;
4481		
4482		this.material.uniforms[ "tDiffuse" ].value = texture;
4483
4484	};
4485
4486} )();;(function(){
4487	
4488	/**
4489	 * Reticle 3D Sprite
4490	 * @constructor
4491	 * @param {THREE.Color} [color=0xfffff] - Color of the reticle sprite
4492	 * @param {boolean} [autoSelect=true] - Auto selection
4493	 * @param {string} [idleImageUrl=PANOLENS.DataImage.ReticleIdle] - Image asset url
4494	 * @param {string} [dwellImageUrl=PANOLENS.DataImage.ReticleDwell] - Image asset url
4495	 * @param {number} [dwellTime=1500] - Duration for dwelling sequence to complete
4496	 * @param {number}
4496 [dwellSpriteAmount=45] - Number of dwelling sprite sequence
4497	 */
4498	PANOLENS.Reticle = function ( color, autoSelect, idleImageUrl, dwellImageUrl, dwellTime, dwellSpriteAmount ) {
4499
4500		color = color || 0xffffff;
4501
4502		this.autoSelect = autoSelect != undefined ? autoSelect : true;
4503
4504		this.dwellTime = dwellTime || 1500;
4505		this.dwellSpriteAmount = dwellSpriteAmount || 45;
4506		this.dwellInterval = this.dwellTime / this.dwellSpriteAmount;
4507
4508		this.IDLE = 0;
4509		this.DWELLING = 1;
4510		this.status;
4511
4512		this.scaleIdle = new THREE.Vector3( 0.2, 0.2, 1 );
4513		this.scaleDwell = new THREE.Vector3( 1, 0.8, 1 );
4514
4515		this.textureLoaded = false;
4516		this.idleImageUrl = idleImageUrl || PANOLENS.DataImage.ReticleIdle;
4517		this.dwellImageUrl = dwellImageUrl || PANOLENS.DataImage.ReticleDwell;
4518		this.idleTexture = new THREE.Texture();
4519		this.dwellTexture = new THREE.Texture();
4520
4521		THREE.Sprite.call( this, new THREE.SpriteMaterial( { color: color, depthTest: false } ) );
4522
4523		this.currentTile = 0;
4524		this.startTime = 0;
4525
4526		this.visible = false;
4527		this.renderOrder = 10;
4528		this.timerId;
4529
4530		// initial update
4531		this.updateStatus( this.IDLE );
4532
4533	};
4534
4535	PANOLENS.Reticle.prototype = Object.create( THREE.Sprite.prototype );
4536
4537	PANOLENS.Reticle.prototype.constructor = PANOLENS.Reticle;
4538
4539	/**
4540	 * Make reticle visible
4541	 */
4542	PANOLENS.Reticle.prototype.show = function () {
4543
4544		this.visible = true;
4545
4546	};
4547
4548	/**
4549	 * Make reticle invisible
4550	 */
4551	PANOLENS.Reticle.prototype.hide = function () {
4552
4553		this.visible = false;
4554
4555	};
4556
4557	/**
4558	 * Load reticle textures
4559	 */
4560	PANOLENS.Reticle.prototype.loadTextures = function () {
4561
4562		this.idleTexture = PANOLENS.Utils.TextureLoader.load( this.idleImageUrl );
4563		this.dwellTexture = PANOLENS.Utils.TextureLoader.load( this.dwellImageUrl );
4564
4565		this.material.map = this.idleTexture;
4566		this.setupDwellSprite( this.dwellTexture );
4567		this.textureLoaded = true;
4568
4569	};
4570
4571	/**
4572	 * Start reticle timer selection
4573	 * @param  {function} completeCallback - Callback after dwell completes
4574	 */
4575	PANOLENS.Reticle.prototype.select = function ( completeCallback ) {
4576
4577		if ( performance.now() - this.startTime >= this.dwellTime ) {
4578
4579			this.completeDwelling();
4580			completeCallback();
4581
4582		} else if ( this.autoSelect ){
4583
4584			this.updateDwelling( performance.now() );
4585			this.timerId = window.requestAnimationFrame( this.select.bind( this, completeCallback ) );
4586
4587		}
4588
4589	};
4590
4591	/**
4592	 * Clear and reset reticle timer
4593	 */
4594	PANOLENS.Reticle.prototype.clearTimer = function () {
4595
4596		window.cancelAnimationFrame( this.timerId );
4597		this.timerId = null;
4598
4599	};
4600
4601	/**
4602	 * Setup dwell sprite animation
4603	 */
4604	PANOLENS.Reticle.prototype.setupDwellSprite = function ( texture ) {
4605
4606		texture.wrapS = THREE.RepeatWrapping;
4607		texture.repeat.set( 1 / this.dwellSpriteAmount, 1 );
4608
4609	}
4610
4611	/**
4612	 * Update reticle status
4613	 * @param {number} status - Reticle status
4614	 */
4615	PANOLENS.Reticle.prototype.updateStatus = function ( status ) {
4616
4617		this.status = status;
4618
4619		if ( status === this.IDLE ) {
4620			this.scale.copy( this.scaleIdle );
4621			this.material.map = this.idleTexture;
4622		} else if ( status === this.DWELLING ) {
4623			this.scale.copy( this.scaleDwell );
4624			this.material.map = this.dwellTexture;
4625		}
4626
4627		this.currentTile = 0;
4628		this.material.map.offset.x = 0;
4629
4630	};
4631
4632	/**
4633	 * Start dwelling sequence
4634	 */
4635	PANOLENS.Reticle.prototype.startDwelling = function ( completeCallback ) {
4636
4637		if ( !this.autoSelect ) {
4638
4639			return;
4640
4641		}
4642
4643		this.startTime = performance.now();
4644		this.updateStatus( this.DWELLING );
4645		this.select( completeCallback );
4646
4647	};
4648
4649	/**
4650	 * Update dwelling sequence
4651	 * @param  {number} time - Timestamp for elasped time
4652	 */
4653	PANOLENS.Reticle.prototype.updateDwelling = function ( time ) {
4654
4655		var elasped = time - this.startTime;
4656
4657		if ( this.currentTile <= this.dwellSpriteAmount ) {
4658			this.currentTile = Math.floor( elasped / this.dwellTime * this.dwellSpriteAmount );
4659			this.material.map.offset.x = this.currentTile / this.dwellSpriteAmount;
4660		} else {
4661			this.updateStatus( this.IDLE );
4662		}
4663
4664	};
4665
4666	/**
4667	 * Cancel dwelling
4668	 */
4669	PANOLENS.Reticle.prototype.cancelDwelling = function () {
4670		this.clearTimer();
4671		this.updateStatus( this.IDLE );
4672
4673	};
4674
4675	/**
4676	 * Complete dwelling
4677	 */
4678	PANOLENS.Reticle.prototype.completeDwelling = function () {
4679		this.clearTimer();	
4680		this.updateStatus( this.IDLE );
4681	};
4682
4683})();;( function () {
4684	
4685	/**
4686	 * Creates a tile with bent capability
4687	 * @constructor
4688	 * @param {number}  [width=10]                      				- Width along the X axis
4689	 * @param {number}  [height=5]                      				- Height along the Y axis
4690	 * @param {number}  [widthSegments=20]              				- Width segments
4691	 * @param {number}  [heightSegments=20]             				- Height segments
4692	 * @param {THREE.Vector3} [forceDirection=THREE.Vector3( 0, 0, 1 )] - Force direction
4693	 * @param {THREE.Vector3} [forceAxis=THREE.Vector3( 0, 1, 0 )] 		- Along this axis
4694	 * @param {number} [forceAngle=Math.PI/12] 							- Angle to bend in radians
4695	 */
4696	PANOLENS.Tile = function ( width, height, widthSegments, heightSegments, forceDirection, forceAxis, forceAngle ) {
4697
4698		var scope = this;
4699
4700		this.parameters = {
4701			width: width,
4702			height: height,
4703			widthSegments: widthSegments,
4704			heightSegments: heightSegments,
4705			forceDirection: forceDirection,
4706			forceAxis: forceAxis,
4707			forceAngle: forceAngle
4708		};
4709
4710		width = width || 10;
4711		height = height || 5;
4712		widthSegments = widthSegments || 1;
4713		heightSegments = heightSegments || 1;
4714		forceDirection = forceDirection || new THREE.Vector3( 0, 0, 1 );
4715		forceAxis = forceAxis || new THREE.Vector3( 0, 1, 0 );
4716		forceAngle = forceAngle !== undefined ? forceAngle : 0;
4717
4718		THREE.Mesh.call( this, 
4719			new THREE.PlaneGeometry( width, height, widthSegments, heightSegments ),
4720			new THREE.MeshBasicMaterial( { color: 0xffffff, transparent: true } )
4721		);
4722
4723		this.bendModifier = new THREE.BendModifier();
4724
4725		this.entity = undefined;
4726
4727		this.animationDuration = 500;
4728		this.animationFadeOut = undefined;
4729		this.animationFadeIn = undefined;
4730		this.animationTranslation = undefined;
4731		this.tweens = {};
4732
4733		if ( forceAngle !== 0 ) {
4734
4735			this.bend( forceDirection, forceAxis, forceAngle );
4736
4737		}
4738		
4739		this.originalGeometry = this.geometry.clone();
4740	}
4741
4742	PANOLENS.Tile.prototype = Object.create( THREE.Mesh.prototype );
4743
4744	PANOLENS.Tile.prototype.constructor = PANOLENS.Tile;
4745
4746	PANOLENS.Tile.prototype.clone = function (){
4747
4748		var parameters = this.parameters, tile;
4749
4750		tile = new PANOLENS.Tile(
4751			parameters.width,
4752			parameters.height,
4753			parameters.widthSegments,
4754			parameters.heightSegments,
4755			parameters.forceDirection,
4756			parameters.forceAxis,
4757			parameters.forceAngle
4758		);
4759
4760		tile.setEntity( this.entity );
4761		tile.material = this.material.clone();
4762
4763		return tile;
4764
4765	};
4766
4767	/**
4768	 * Bend panel with direction, axis, and angle
4769	 * @param  {THREE.Vector3} direction - Force direction
4770	 * @param  {THREE.Vector3} axis - Along this axis
4771	 * @param  {number} angle - Angle to bend in radians
4772	 */
4773	PANOLENS.Tile.prototype.bend = function ( direction, axis, angle ) {
4774
4775		this.bendModifier.set( direction, axis, angle ).modify( this.geometry );
4776
4777	};
4778
4779	/**
4780	 * Restore geometry back to initial state 
4781	 */
4782	PANOLENS.Tile.prototype.unbend = function () {
4783
4784		var geometry = this.geometry;
4785
4786		this.geometry = this.originalGeometry;
4787		this.originalGeometry = this.geometry.clone();
4788
4789		geometry.dispose();
4790		geometry = null;
4791
4792	};
4793
4794	/**
4795	 * Create a tween object for animation
4796	 * based on - {@link https://github.com/tweenjs/tween.js}
4797	 * @param  {string} name       - Name of the tween animation
4798	 * @param  {object} object     - Object to be tweened
4799	 * @param  {object} toState    - Final state of the object's properties
4800	 * @param  {number} duration   - Tweening duration
4801	 * @param  {TWEEN.Easing} easing     - Easing function
4802	 * @param  {number} delay      - Animation delay time
4803	 * @param  {Function} onStart    - On start function
4804	 * @param  {Function} onUpdate   - On update function
4805	 * @param  {Function} onComplete - On complete function
4806	 * @return {TWEEN.Tween}         - Tween object
4807	 */
4808	PANOLENS.Tile.prototype.tween = function ( name, object, toState, duration, easing, delay, onStart, onUpdate, onComplete ) {
4809
4810		object = object || this;
4811    	toState = toState || {};
4812    	duration = duration || this.animationDuration;
4813    	easing = easing || TWEEN.Easing.Exponential.Out;
4814    	delay = delay !== undefined ? delay : 0;
4815    	onStart = onStart ? onStart : null;
4816    	onUpdate = onUpdate ? onUpdate : null;
4817    	onComplete = onComplete ? onComplete : null;
4818
4819    	if ( !this.tweens[name] ) {
4820    		this.tweens[name] = new TWEEN.Tween( object )
4821    			.to( toState, duration )
4822	        	.easing( easing )
4823	        	.delay( delay )
4824	        	.onStart( onStart )
4825	        	.onUpdate( onUpdate )
4826	        	.onComplete( onComplete );
4827    	}
4828
4829    	return this.tweens[name];
4830
4831    };
4832
4833    /**
4834     * Short-hand for displaying a single ripple effect
4835     * by duplicating itself and fadeout
4836     * @param  {number} scale    - The duplicated self fadeout scale
4837     * @param  {number} duration - Effect duration
4838     * @param  {TWEEN.Easing} easing   - Easing function
4839     */
4840    PANOLENS.Tile.prototype.ripple = function ( scale, duration, easing ) {
4841
4842    	scale = scale || 2;
4843    	duration = duration || 200;
4844    	easing = easing || TWEEN.Easing.Cubic.Out;
4845
4846    	var scope = this, ripple = this.clone();
4847
4848        new TWEEN.Tween( ripple.scale )
4849        .to({x: scale, y: scale}, duration )
4850        .easing( easing )
4851        .start();
4852
4853        new TWEEN.Tween( ripple.material )
4854        .to({opacity: 0}, duration )
4855        .easing( easing )
4856        .onComplete(function(){
4857            scope.remove( ripple );
4858            ripple.geometry.dispose();
4859            ripple.material.dispose();
4860        })
4861        .start();
4862
4863        this.add( ripple );
4864
4865    };
4866
4867    /**
4868	 * Set entity if multiple objects are considered as one entity
4869	 * @param {object} entity - Entity represents whole group structure
4870	 */
4871	PANOLENS.Tile.prototype.setEntity = function ( entity ) {
4872
4873		this.entity = entity;
4874
4875	};
4876
4877} )();;(function(){
4878	
4879	'use strict';
4880
4881    /**
4882     * Group consists of tile array
4883     * @constructor
4884     * @param {array}  tileArray         - Tile array of PANOLENS.Tile 
4885     * @param {number} verticalGap       - Vertical gap between each tile
4886     * @param {number} depthGap          - Depth gap between each tile
4887     * @param {number} animationDuration - Animation duration
4888     * @param {number} offset            - Offset index
4889     */
4890	PANOLENS.TileGroup = function ( tileArray, verticalGap, depthGap, animationDuration, offset ) {
4891
4892		var scope = this, textureLoader;
4893
4894		THREE.Object3D.call( this );
4895
4896		this.tileArray = tileArray || [];
4897		this.offset = offset !== undefined ? offset : 0;
4898		this.v_gap = verticalGap !== undefined ? verticalGap : 6;
4899		this.d_gap = depthGap !== undefined ? depthGap : 2;
4900		this.animationDuration = animationDuration !== undefined ? animationDuration : 200;
4901		this.animationEasing = TWEEN.Easing.Exponential.Out;
4902		this.visibleDelta = 2;
4903
4904		this.tileArray.map( function ( tile, index ) {
4905
4906			if ( tile.image ) {
4907
4908				PANOLENS.Utils.TextureLoader.load( tile.image, scope.setTexture.bind( tile ) );
4909
4910			}
4911
4912			tile.position.set( 0, index * -scope.v_gap, index * -scope.d_gap );
4913			tile.originalPosition = tile.position.clone();
4914			tile.setEntity( scope );
4915			scope.add( tile );
4916
4917		} );
4918
4919		this.updateVisbility();
4920
4921	}
4922
4923	PANOLENS.TileGroup.prototype = Object.create( THREE.Object3D.prototype );
4924
4925	PANOLENS.TileGroup.prototype.constructor = PANOLENS.TileGroup;
4926
4927    /**
4928     * Update corresponding tile textures
4929     * @param  {array} imageArray - Image array with index to index image update
4930     */
4931	PANOLENS.TileGroup.prototype.updateTexture = function ( imageArray ) {
4932
4933		var scope = this;
4934
4935		imageArray = imageArray || [];
4936		this.children.map( function ( child, index ) {
4937			if ( child instanceof PANOLENS.Tile && imageArray[index] ) {
4938				PANOLENS.Utils.TextureLoader.load( imageArray[index], scope.setTexture.bind( child ) );
4939	    		if ( child.outline ) {
4940	    			child.outline.material.visible = true;
4941	    		}
4942			}
4943		} );
4944
4945	};
4946
4947    /**
4948     * Update all tile textures and hide the remaining ones
4949     * @param  {array} imageArray - Image array with index to index image update
4950     */
4951	PANOLENS.TileGroup.prototype.updateAllTexture = function ( imageArray ) {
4952
4953		this.updateTexture( imageArray );
4954
4955		if ( imageArray.length < this.children.length ) {
4956			for ( var i = imageArray.length; i < this.children.length; i++ ) {
4957				if ( this.children[i] instanceof PANOLENS.Tile ) {
4958					this.children[i].material.visible = false;
4959					if ( this.children[i].outline ) {
4960						this.children[i].outline.material.visible = false;
4961					}
4962				}
4963			}
4964		}
4965
4966	}
4967
4968    /**
4969     * Set individual texture
4970     * @param {THREE.Texture} texture - Texture to be updated
4971     */
4972	PANOLENS.TileGroup.prototype.setTexture = function ( texture ) {
4973
4974        texture.minFilter = THREE.LinearFilter;
4975        texture.magFilter = THREE.LinearFilter;
4976        this.material.visible = true;
4977        this.material.map = texture;
4978        this.material.needsUpdate = true;
4979
4980    };
4981
4982    /**
4983     * Update visibility
4984     */
4985    PANOLENS.TileGroup.prototype.updateVisbility = function () {
4986
4987    	this.children[this.offset].visible = true;
4988    	new TWEEN.Tween( this.children[this.offset].material )
4989		.to( { opacity: 1 }, this.animationDuration )
4990		.easing( this.animationEasing )
4991		.start();
4992    	
4993    	if ( this.children[this.offset].outline ) {
4994
4995    		this.children[this.offset].outline.visible = true;
4996
4997    	}
4998
4999    	// Backward
5000    	for ( var i = this.offset - 1; i >= 0 ; i-- ) {
5001
5002    		if ( this.tileArray.indexOf(this.children[i]) === -1 ) { continue; }
5003
5004    		if ( this.offset - i <= this.visibleDelta ) {
5005
5006    			this.children[i].visible = true;
5007    			new TWEEN.Tween( this.children[i].material )
5008    			.to( { opacity: 1 / ( this.offset - i ) * 0.5 }, this.animationDuration )
5009    			.easing( this.animationEasing )
5010    			.start();
5011
5012    		} else {
5013
5014    			this.children[i].visible = false;
5015
5016    		}
5017
5018    		this.children[i].outline && (this.children[i].outline.visible = false);
5019
5020    	}
5021
5022    	// Forward
5023    	for ( var i = this.offset + 1; i < this.children.length ; i++ ) {
5024
5025    		if ( this.tileArray.indexOf(this.children[i]) === -1 ) { continue; }
5026
5027    		if ( i - this.offset <= this.visibleDelta ) {
5028
5029    			this.children[i].visible = true;
5030    			new TWEEN.Tween( this.children[i].material )
5031    			.to( { opacity: 1 / ( i - this.offset ) * 0.5 }, this.animationDuration )
5032    			.easing( this.animationEasing )
5033    			.start();
5034
5035    		} else {
5036
5037    			this.children[i].visible = false;
5038
5039    		}
5040
5041    		this.children[i].outline && (this.children[i].outline.visible = false);
5042
5043    	}
5044
5045    };
5046
5047    /**
5048     * Scroll up
5049     * @param  {number} duration - Scroll up duration
5050     */
5051    PANOLENS.TileGroup.prototype.scrollUp = function ( duration ) {
5052
5053    	var tiles = this.tileArray, offset;
5054
5055    	duration = duration !== undefined ? duration : this.animationDuration;
5056
5057    	offset = this.offset + 1;
5058
5059    	if ( this.offset < tiles.length - 1 && tiles[ this.offset + 1 ].material.visible ) {
5060
5061    		for ( var i = tiles.length - 1; i >= 0; i-- ) {
5062	    		
5063    			new TWEEN.Tween( tiles[i].position )
5064    			.to({ y: ( i - offset ) * -this.v_gap,
5065    				  z: Math.abs( i - offset ) * -this.d_gap }, duration )
5066    			.easing( this.animationEasing )
5067    			.start();
5068	    		
5069	    	}
5070
5071	    	this.offset ++;
5072	    	this.updateVisbility();
5073	    	this.dispatchEvent( { type: 'scroll', direction: 'up' } );
5074
5075    	}
5076
5077    };
5078
5079    /**
5080     * Scroll down 
5081     * @param  {number} duration - Scroll up duration
5082     */
5083    PANOLENS.TileGroup.prototype.scrollDown = function ( duration ) {
5084
5085    	var tiles = this.tileArray, offset;
5086
5087    	duration = duration !== undefined ? duration : this.animationDuration;
5088
5089    	offset = this.offset - 1;
5090
5091    	if ( this.offset > 0 && tiles[ this.offset - 1 ].material.visible ) {
5092
5093    		for ( var i = 0; i < tiles.length; i++ ) {
5094
5095	    		new TWEEN.Tween( tiles[i].position )
5096    			.to({ y: ( i - offset ) * -this.v_gap,
5097    				  z: Math.abs( i - offset ) * -this.d_gap }, duration )
5098    			.easing( this.animationEasing )
5099    			.start();
5100	    		
5101	    	}
5102
5103	    	this.offset --;
5104	    	this.updateVisbility();
5105	    	this.dispatchEvent( { type: 'scroll', direction: 'down' } );
5106
5107    	}
5108
5109    };
5110
5111    PANOLENS.TileGroup.prototype.reset = function () {
5112
5113    	this.tileArray.map( function ( child, index ) {
5114
5115    		child.position.copy( child.originalPosition );
5116
5117    	} );
5118
5119    	this.offset = 0;
5120    	this.updateVisbility();
5121
5122    };
5123
5124    /**
5125     * Get current index
5126     * @return {number} Index of the group. Range from 0 to this.tileArray.length
5127     */
5128    PANOLENS.TileGroup.prototype.getIndex = function () {
5129
5130    	return this.offset;
5131
5132    };
5133
5134    /**
5135     * Get visible tile counts
5136     * @return {number} Number of visible tiles
5137     */
5138    PANOLENS.TileGroup.prototype.getTileCount = function () {
5139
5140    	var count = 0;
5141
5142    	this.tileArray.map( function ( tile ) {
5143
5144    		if ( tile.material.visible ) {
5145
5146    			count ++;
5147
5148    		}
5149
5150    	} );
5151
5152    	return count;
5153
5154    };
5155
5156})();;(function(){
5157	
5158	'use strict';
5159
5160	var sharedFont, sharedTexture;
5161	var pendingQueue = [];
5162
5163	/**
5164	 * Sprite text based on {@link https://github.com/Jam3/three-bmfont-text}
5165	 * @constructor
5166	 * @param {string} text     - Text to be displayed
5167	 * @param {number} maxWidth	- Max width
5168	 * @param {number} color    - Color in hexadecimal
5169	 * @param {number} opacity  - Text opacity
5170	 * @param {object} options  - Options to create text geometry
5171	 */
5172	PANOLENS.SpriteText = function ( text, maxWidth, color, opacity, options ) {
5173
5174		THREE.Object3D.call( this );
5175
5176		this.text = text || '';
5177		this.maxWidth = maxWidth || 2000;
5178		this.color = color || 0xffffff;
5179		this.opacity = opacity !== undefined ? opacity : 1;
5180		this.options = options || {};
5181
5182		this.animationDuration = 500;
5183		this.animationFadeOut = undefined;
5184		this.animationFadeIn = undefined;
5185		this.tweens = {};
5186
5187		this.addText( text );
5188
5189	}
5190
5191	PANOLENS.SpriteText.prototype = Object.create( THREE.Object3D.prototype );
5192
5193	PANOLENS.SpriteText.prototype.constructor = PANOLENS.SpriteText;
5194
5195	// Reference function will be overwritten by Bmfont.js
5196	PANOLENS.SpriteText.prototype.generateTextGeometry = function () {};
5197	PANOLENS.SpriteText.prototype.generateSDFShader = function () {};
5198
5199	/**
5200	 * Set BMFont
5201	 * @param {Function} callback - Callback after font is loaded
5202	 * @param {object}   font     - The font to be loaded
5203	 * @param {THREE.Texture}   texture  - Font texture
5204	 */
5205	PANOLENS.SpriteText.prototype.setBMFont = function ( callback, font, texture ) {
5206
5207		texture.needsUpdate = true;
5208	  	texture.minFilter = THREE.LinearMipMapLinearFilter;
5209		texture.magFilter = THREE.LinearFilter;
5210		texture.generateMipmaps = true;
5211		texture.anisotropy = 8;
5212
5213		sharedFont = font;
5214		sharedTexture = texture;
5215
5216		for ( var i = pendingQueue.length - 1; i >= 0; i-- ) {
5217			pendingQueue[ i ].target.addText( pendingQueue[ i ].text );
5218		}
5219
5220		while ( pendingQueue.length > 0 ) {
5221			pendingQueue.pop();
5222		}
5223
5224		callback && callback();
5225
5226	};
5227
5228	/**
5229	 * Add text mesh
5230	 * @param {string} text - Text to be displayed
5231	 */
5232	PANOLENS.SpriteText.prototype.addText = function ( text ) {
5233
5234		if ( !sharedFont || !sharedTexture ) {
5235			pendingQueue.push( { target: this, text: text } );
5236			return;
5237		}
5238
5239		var textAnchor = new THREE.Object3D();
5240
5241		this.options.text = text;
5242		this.options.font = sharedFont;
5243		this.options.width = this.maxWidth;
5244
5245		var geometry = this.generateTextGeometry( this.options );
5246		geometry.computeBoundingBox();
5247		geometry.computeBoundingSphere();
5248
5249		var material = new THREE.RawShaderMaterial(this.generateSDFShader({
5250		    map: sharedTexture,
5251		    side: THREE.DoubleSide,
5252		    transparent: true,
5253		    color: this.color,
5254		    opacity: this.opacity
5255		}));
5256
5257		var layout = geometry.layout;
5258		var textMesh = new THREE.Mesh( geometry, material );
5259
5260		textMesh.entity = this;
5261		textMesh.position.x = -layout.width / 2;
5262		textMesh.position.y = layout.height * 1.035;
5263
5264		textAnchor.scale.x = textAnchor.scale.y = -0.05;
5265		textAnchor.add( textMesh );
5266
5267		this.mesh = textMesh;
5268		this.add( textAnchor );
5269
5270	};
5271
5272	/**
5273	 * Update text geometry
5274	 * @param  {object} options - Geometry options based on
5275	 *  https://github.com/Jam3/three-bmfont-text#geometry--createtextopt
5276	 */
5277	PANOLENS.SpriteText.prototype.update = function ( options ) {
5278
5279		var mesh;
5280
5281		options = options || {};
5282
5283		mesh = this.mesh;
5284
5285		mesh.geometry.update( options );
5286		mesh.position.x = -mesh.geometry.layout.width / 2;
5287		mesh.position.y = mesh.geometry.layout.height * 1.035;
5288
5289	};
5290
5291	/**
5292	 * Create a tween object for animation
5293	 * based on - {@link https://github.com/tweenjs/tween.js}
5294	 * @param  {string} name       - Name of the tween animation
5295	 * @param  {object} object     - Object to be tweened
5296	 * @param  {object} toState    - Final state of the object's properties
5297	 * @param  {number} duration   - Tweening duration
5298	 * @param  {TWEEN.Easing} easing     - Easing function
5299	 * @param  {number} delay      - Animation delay time
5300	 * @param  {Function} onStart    - On start function
5301	 * @param  {Function} onUpdate   - On update function
5302	 * @param  {Function} onComplete - On complete function
5303	 * @return {TWEEN.Tween}         - Tween object
5304	 */
5305	PANOLENS.SpriteText.prototype.tween = function ( name, object, toState, duration, easing, delay, onStart, onUpdate, onComplete ) {
5306
5307		object = object || this;
5308		toState = toState || {};
5309		duration = duration || this.animationDuration;
5310		easing = easing || TWEEN.Easing.Exponential.Out;
5311		delay = delay !== undefined ? delay : 0;
5312		onStart = onStart ? onStart : null;
5313		onUpdate = onUpdate ? onUpdate : null;
5314		onComplete = onComplete ? onComplete : null;
5315
5316		if ( !this.tweens[name] ) {
5317			this.tweens[name] = new TWEEN.Tween( object )
5318				.to( toState, duration )
5319	        	.easing( easing )
5320	        	.delay( delay )
5321	        	.onStart( onStart )
5322	        	.onUpdate( onUpdate )
5323	        	.onComplete( onComplete );
5324		}
5325
5326		return this.tweens[name];
5327
5328	};
5329
5330	/**
5331	 * Get geometry layout
5332	 * @return {object} Text geometry layout 
5333	 */
5334	PANOLENS.SpriteText.prototype.getLayout = function () {
5335
5336		return this.mesh && this.mesh.geometry && this.mesh.geometry.layout || {};
5337
5338	};
5339
5340	/**
5341	 * Set entity if multiple objects are considered as one entity
5342	 * @param {object} entity - Entity represents whole group structure
5343	 */
5344	PANOLENS.SpriteText.prototype.setEntity = function ( entity ) {
5345
5346		this.entity = entity;
5347
5348	};
5349
5350})();;(function () {
5351	
5352	/**
5353	 * Widget for controls
5354	 * @constructor
5355	 * @param {HTMLElement} container - A domElement where default control widget will be attached to
5356	 */
5357	PANOLENS.Widget = function ( container ) {
5358
5359		THREE.EventDispatcher.call( this );
5360
5361		this.DEFAULT_TRANSITION  = 'all 0.27s ease';
5362		this.TOUCH_ENABLED = PANOLENS.Utils.checkTouchSupported();
5363		this.PREVENT_EVENT_HANDLER = function ( event ) {
5364			event.preventDefault();
5365			event.stopPropagation();
5366		};
5367
5368		this.container = container;
5369
5370		this.barElement;
5371		this.fullscreenElement;
5372		this.videoElement;
5373		this.settingElement;
5374
5375		this.mainMenu;
5376
5377		this.activeMainItem;
5378		this.activeSubMenu;
5379		this.mask;
5380
5381	}
5382
5383	PANOLENS.Widget.prototype = Object.create( THREE.EventDispatcher.prototype );
5384
5385	PANOLENS.Widget.prototype.constructor = PANOLENS.Widget;
5386
5387	/**
5388	 * Add control bar
5389	 */
5390	PANOLENS.Widget.prototype.addControlBar = function () {
5391
5392		if ( !this.container ) {
5393
5394			console.warn( 'Widget container not set' ); 
5395			return; 
5396		}
5397
5398		var scope = this, bar, styleTranslate, styleOpacity, gradientStyle;
5399
5400		gradientStyle = 'linear-gradient(bottom, rgba(0,0,0,0.2), rgba(0,0,0,0))';
5401
5402		bar = document.createElement( 'div' );
5403		bar.style.width = '100%';
5404		bar.style.height = '44px';
5405		bar.style.float = 'left';
5406		bar.style.transform = bar.style.webkitTransform = bar.style.msTransform = 'translateY(-100%)';
5407		bar.style.background = '-webkit-' + gradientStyle;
5408		bar.style.background = '-moz-' + gradientStyle;
5409		bar.style.background = '-o-' + gradientStyle;
5410		bar.style.background = '-ms-' + gradientStyle;
5411		bar.style.background = gradientStyle;
5412		bar.style.transition = this.DEFAULT_TRANSITION;
5413		bar.style.pointerEvents = 'none';
5414		bar.isHidden = false;
5415		bar.toggle = function () {
5416			bar.isHidden = !bar.isHidden;
5417			styleTranslate = bar.isHidden ? 'translateY(0)' : 'translateY(-100%)';
5418			styleOpacity = bar.isHidden ? 0 : 1;
5419			bar.style.transform = bar.style.webkitTransform = bar.style.msTransform = styleTranslate;
5420			bar.style.opacity = styleOpacity;
5421		};
5422
5423		// Menu
5424		var menu = this.createDefaultMenu();
5425		this.mainMenu = this.createMainMenu( menu );
5426		bar.appendChild( this.mainMenu );
5427
5428		// Mask
5429		var mask = this.createMask();
5430		this.mask = mask;
5431		this.container.appendChild( mask );
5432
5433		// Dispose
5434		bar.dispose = function () {
5435
5436			if ( scope.fullscreenElement ) {
5437
5438				bar.removeChild( scope.fullscreenElement );
5439				scope.fullscreenElement.dispose();
5440				scope.fullscreenElement = null;
5441
5442			}
5443
5444			if ( scope.settingElement ) {
5445
5446				bar.removeChild( scope.settingElement );
5447				scope.settingElement.dispose();
5448				scope.settingElement = null;
5449
5450			}
5451
5452			if ( scope.videoElement ) {
5453
5454				bar.removeChild( scope.videoElement );
5455				scope.videoElement.dispose();
5456				scope.videoElement = null;
5457
5458			}
5459
5460		};
5461
5462		this.container.appendChild( bar );
5463
5464		// Mask events
5465		this.mask.addEventListener( 'mousemove', this.PREVENT_EVENT_HANDLER, true );
5466		this.mask.addEventListener( 'mouseup', this.PREVENT_EVENT_HANDLER, true );
5467		this.mask.addEventListener( 'mousedown', this.PREVENT_EVENT_HANDLER, true );
5468		this.mask.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', function ( event ) {
5469
5470			event.preventDefault();
5471			event.stopPropagation();
5472
5473			scope.mask.hide();
5474			scope.settingElement.deactivate();
5475
5476		}, false );
5477
5478		// Event listener
5479		this.addEventListener( 'control-bar-toggle', bar.toggle );
5480
5481		this.barElement = bar;
5482
5483	};
5484
5485	/**
5486	 * Create default menu
5487	 */
5488	PANOLENS.Widget.prototype.createDefaultMenu = function () {
5489
5490		var scope = this, handler;
5491
5492		handler = function ( method, data ) {
5493
5494			return function () {
5495
5496				scope.dispatchEvent( { 
5497
5498					type: 'panolens-viewer-handler', 
5499					method: method, 
5500					data: data 
5501
5502				} ); 
5503
5504			}
5505
5506		};
5507
5508		return [
5509
5510			{ 
5511				title: 'Control', 
5512				subMenu: [ 
5513					{ 
5514						title: this.TOUCH_ENABLED ? 'Touch' : 'Mouse', 
5515						handler: handler( 'enableControl', PANOLENS.Controls.ORBIT )
5516					},
5517					{ 
5518						title: 'Sensor', 
5519						handler: handler( 'enableControl', PANOLENS.Controls.DEVICEORIENTATION ) 
5520					} 
5521				]
5522			},
5523
5524			{ 
5525				title: 'Mode', 
5526				subMenu: [ 
5527					{ 
5528						title: 'Normal',
5529						handler: handler( 'disableEffect' )
5530					}, 
5531					{ 
5532						title: 'Cardboard',
5533						handler: handler( 'enableEffect', PANOLENS.Modes.CARDBOARD )
5534					},
5535					{ 
5536						title: 'Stereoscopic',
5537						handler: handler( 'enableEffect', PANOLENS.Modes.STEREO )
5538					}
5539				]
5540			}
5541
5542		];
5543
5544	};
5545
5546	/**
5547	 * Add buttons on top of control bar
5548	 * @param {string} name - The control button name to be created
5549	 */
5550	PANOLENS.Widget.prototype.addControlButton = function ( name ) {
5551
5552		var element;
5553
5554		switch( name ) {
5555
5556			case 'fullscreen':
5557
5558				element = this.createFullscreenButton();
5559				this.fullscreenElement = element; 
5560
5561				break;
5562
5563			case 'setting':
5564
5565				element = this.createSettingButton();
5566				this.settingElement = element;
5567
5568				break;
5569
5570			case 'video':
5571
5572				element = this.createVideoControl();
5573				this.videoElement = element;
5574
5575				break;
5576
5577			default:
5578
5579				return;
5580
5581		}
5582
5583		if ( !element ) {
5584
5585			return;
5586
5587		}
5588
5589		this.barElement.appendChild( element );
5590
5591	};
5592
5593	PANOLENS.Widget.prototype.createMask = function () {
5594
5595		var element = document.createElement( 'div' );
5596		element.style.position = 'absolute';
5597		element.style.top = 0;
5598		element.style.left = 0;
5599		element.style.width = '100%';
5600		element.style.height = '100%';
5601		element.style.background = 'transparent';
5602		element.style.display = 'none';
5603
5604		element.show = function () {
5605
5606			this.style.display = 'block';
5607
5608		};
5609
5610		element.hide = function () {
5611
5612			this.style.display = 'none';
5613
5614		};
5615
5616		return element;
5617
5618	};
5619
5620	/**
5621	 * Create Setting button to toggle menu
5622	 */
5623	PANOLENS.Widget.prototype.createSettingButton = function () {
5624
5625		var scope = this, item;
5626
5627		function onTap ( event ) {
5628
5629			event.preventDefault();
5630			event.stopPropagation();
5631
5632			scope.mainMenu.toggle();
5633
5634			if ( this.activated ) {
5635	
5636				this.deactivate();
5637
5638			} else {
5639
5640				this.activate();
5641
5642			}
5643
5644		}
5645
5646		item = this.createCustomItem( { 
5647
5648			style : { 
5649
5650				backgroundImage : 'url("' + PANOLENS.DataImage.Setting + '")',
5651				webkitTransition : this.DEFAULT_TRANSITION,
5652				transition : this.DEFAULT_TRANSITION
5653
5654			},
5655
5656			onTap: onTap
5657
5658		} );
5659
5660		item.activate = function () {
5661
5662			this.style.transform = 'rotate3d(0,0,1,90deg)';
5663			this.activated = true;
5664			scope.mask.show();
5665
5666		};
5667
5668		item.deactivate = function () {
5669
5670			this.style.transform = 'rotate3d(0,0,0,0)';
5671			this.activated = false;
5672			scope.mask.hide();
5673
5674			if ( scope.mainMenu && scope.mainMenu.visible ) {
5675
5676				scope.mainMenu.hide();
5677				
5678			}
5679
5680			if ( scope.activeSubMenu && scope.activeSubMenu.visible ) {
5681
5682				scope.activeSubMenu.hide();
5683
5684			}
5685
5686			if ( scope.mainMenu && scope.mainMenu._width ) {
5687
5688				scope.mainMenu.changeSize( scope.mainMenu._width );
5689				scope.mainMenu.unslideAll();
5690
5691			}
5692			
5693		};
5694
5695		item.activated = false;
5696
5697		return item;
5698
5699	};
5700
5701	/**
5702	 * Create Fullscreen button
5703	 * @return {HTMLSpanElement} - The dom element icon for fullscreen
5704	 * @fires PANOLENS.Widget#panolens-viewer-handler
5705	 */
5706	PANOLENS.Widget.prototype.createFullscreenButton = function () {
5707
5708		var scope = this, item, isFullscreen = false, tapSkipped = true, stylesheetId;
5709
5710		stylesheetId = 'panolens-style-addon';
5711
5712		// Don't create button if no support
5713		if ( !document.fullscreenEnabled       && 
5714			 !document.webkitFullscreenEnabled &&
5715			 !document.mozFullScreenEnabled    &&
5716			 !document.msFullscreenEnabled ) {
5717			return;
5718		}
5719
5720		function onTap ( event ) {
5721
5722			event.preventDefault();
5723			event.stopPropagation();
5724
5725			tapSkipped = false;
5726
5727			if ( !isFullscreen ) {
5728			    scope.container.requestFullscreen && scope.container.requestFullscreen();
5729			    scope.container.msRequestFullscreen && scope.container.msRequestFullscreen();
5730			    scope.container.mozRequestFullScreen && scope.container.mozRequestFullScreen();
5731			    scope.container.webkitRequestFullscreen && scope.container.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
5732				isFullscreen = true;
5733			} else {
5734			    document.exitFullscreen && document.exitFullscreen();
5735			    document.msExitFullscreen && document.msExitFullscreen();
5736			    document.mozCancelFullScreen && document.mozCancelFullScreen();
5737			    document.webkitExitFullscreen && document.webkitExitFullscreen();
5738				isFullscreen = false;
5739			}
5740
5741			this.style.backgroundImage = ( isFullscreen ) 
5742				? 'url("' + PANOLENS.DataImage.FullscreenLeave + '")' 
5743				: 'url("' + PANOLENS.DataImage.FullscreenEnter + '")';
5744
5745		}
5746
5747		function onFullScreenChange (e) {
5748
5749			if ( tapSkipped ) {
5750
5751				isFullscreen = !isFullscreen; 
5752
5753				item.style.backgroundImage = ( isFullscreen ) 
5754				? 'url("' + PANOLENS.DataImage.FullscreenLeave + '")' 
5755				: 'url("' + PANOLENS.DataImage.FullscreenEnter + '")';
5756
5757			}
5758
5759			/**
5760			 * Viewer handler event
5761			 * @type {object}
5762			 * @property {string} method - 'onWindowResize' function call on PANOLENS.Viewer
5763			 */
5764			scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'onWindowResize', data: false } );
5765
5766			tapSkipped = true;
5767
5768		}
5769
5770		document.addEventListener( 'fullscreenchange', onFullScreenChange, false );
5771		document.addEventListener( 'webkitfullscreenchange', onFullScreenChange, false );
5772		document.addEventListener( 'mozfullscreenchange', onFullScreenChange, false );
5773		document.addEventListener( 'MSFullscreenChange', onFullScreenChange, false );
5774
5775		item = this.createCustomItem( { 
5776
5777			style : { 
5778
5779				backgroundImage : 'url("' + PANOLENS.DataImage.FullscreenEnter + '")' 
5780
5781			},
5782
5783			onTap : onTap
5784
5785		} );
5786
5787		// Add fullscreen stlye if not exists
5788		if ( !document.querySelector( stylesheetId ) ) {
5789			var sheet = document.createElement( 'style' );
5790			sheet.id = stylesheetId;
5791			sheet.innerHTML = ':-webkit-full-screen { width: 100% !important; height: 100% !important }';
5792			document.body.appendChild( sheet );
5793		}
5794		
5795		return item;
5796
5797	};
5798
5799	/**
5800	 * Create video control container
5801	 * @return {HTMLSpanElement} - The dom element icon for video control
5802	 */
5803	PANOLENS.Widget.prototype.createVideoControl = function () {
5804
5805		var item;
5806
5807		item = document.createElement( 'span' );
5808		item.style.display = 'none';
5809		item.show = function () { 
5810
5811			item.style.display = '';
5812
5813		};
5814
5815		item.hide = function () { 
5816
5817			item.style.display = 'none';
5818			item.controlButton.paused = true;
5819			item.controlButton.update();
5820
5821		};
5822
5823		item.controlButton = this.createVideoControlButton();
5824		item.seekBar = this.createVideoControlSeekbar();
5825		
5826		item.appendChild( item.controlButton );
5827		item.appendChild( item.seekBar );
5828
5829		item.dispose = function () {
5830
5831			item.removeChild( item.controlButton );
5832			item.removeChild( item.seekBar );
5833
5834			item.controlButton.dispose();
5835			item.controlButton = null;
5836
5837			item.seekBar.dispose();
5838			item.seekBar = null;
5839
5840		};
5841
5842		this.addEventListener( 'video-control-show', item.show );
5843		this.addEventListener( 'video-control-hide', item.hide );
5844
5845		return item;
5846
5847	};
5848
5849	/**
5850	 * Create video control button
5851	 * @return {HTMLSpanElement} - The dom element icon for video control
5852	 * @fires PANOLENS.Widget#panolens-viewer-handler
5853	 */
5854	PANOLENS.Widget.prototype.createVideoControlButton = function () {
5855
5856		var scope = this, item;
5857
5858		function onTap ( event ) {
5859
5860			event.preventDefault();
5861			event.stopPropagation();
5862
5863			/**
5864			 * Viewer handler event
5865			 * @type {object}
5866			 * @property {string} method - 'toggleVideoPlay' function call on PANOLENS.Viewer
5867			 */
5868			scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'toggleVideoPlay', data: !this.paused } );
5869
5870			this.paused = !this.paused;
5871
5872			item.update();
5873
5874		};
5875
5876		item = this.createCustomItem( { 
5877
5878			style : { 
5879
5880				float : 'left',
5881				backgroundImage : 'url("' + PANOLENS.DataImage.VideoPlay + '")'
5882
5883			},
5884
5885			onTap : onTap
5886
5887		} );
5888
5889		item.paused = true;
5890
5891		item.update = function ( paused ) {
5892
5893			this.paused = paused !== undefined ? paused : this.paused;
5894
5895			this.style.backgroundImage = 'url("' + ( this.paused 
5896				? PANOLENS.DataImage.VideoPlay 
5897				: PANOLENS.DataImage.VideoPause ) + '")';
5898
5899		};
5900
5901		return item;
5902
5903	};
5904
5905	/**
5906	 * Create video seekbar
5907	 * @return {HTMLSpanElement} - The dom element icon for video seekbar
5908	 * @fires PANOLENS.Widget#panolens-viewer-handler
5909	 */
5910	PANOLENS.Widget.prototype.createVideoControlSeekbar = function () {
5911
5912		var scope = this, item, progressElement, progressElementControl,
5913			isDragging = false, mouseX, percentageNow, percentageNext;
5914
5915		progressElement = document.createElement( 'div' );
5916		progressElement.style.width = '0%';
5917		progressElement.style.height = '100%';
5918		progressElement.style.backgroundColor = '#fff';
5919
5920		progressElementControl = document.createElement( 'div' );
5921		progressElementControl.style.float = 'right';
5922		progressElementControl.style.width = '14px';
5923		progressElementControl.style.height = '14px';
5924		progressElementControl.style.transform = 'translate(7px, -5px)';
5925		progressElementControl.style.borderRadius = '50%';
5926		progressElementControl.style.backgroundColor = '#ddd';
5927
5928		progressElementControl.addEventListener( 'mousedown', onMouseDown, false );
5929		progressElementControl.addEventListener( 'touchstart', onMouseDown, false );
5930
5931		function onMouseDown ( event ) {
5932
5933			event.stopPropagation();
5934			
5935			isDragging = true;
5936			
5937			mouseX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX );
5938
5939			percentageNow = parseInt( progressElement.style.width ) / 100;
5940
5941			addControlListeners();
5942		}
5943
5944		function onVideoControlDrag ( event ) {
5945
5946			var clientX;
5947
5948			if( isDragging ){
5949
5950				clientX = event.clientX || ( event.changedTouches && event.changedTouches[0].clientX );
5951				
5952				percentageNext = ( clientX - mouseX ) / item.clientWidth;
5953
5954				percentageNext = percentageNow + percentageNext;
5955
5956				percentageNext = percentageNext > 1 ? 1 : ( ( percentageNext < 0 ) ? 0 : percentageNext );
5957
5958				item.setProgress ( percentageNext );
5959
5960				/**
5961				 * Viewer handler event
5962				 * @type {object}
5963				 * @property {string} method - 'setVideoCurrentTime' function call on PANOLENS.Viewer
5964				 * @property {number} data - Percentage of current video. Range from 0.0 to 1.0
5965				 */
5966				scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setVideoCurrentTime', data: percentageNext } );
5967
5968			}
5969
5970		}
5971
5972		function onVideoControlStop ( event ) {
5973
5974			event.stopPropagation();
5975
5976			isDragging = false;
5977
5978			removeControlListeners();
5979
5980		}
5981
5982		function addControlListeners () {
5983
5984			scope.container.addEventListener( 'mousemove', onVideoControlDrag, false );
5985			scope.container.addEventListener( 'mouseup', onVideoControlStop, false );
5986			scope.container.addEventListener( 'touchmove', onVideoControlDrag, false );
5987			scope.container.addEventListener( 'touchend', onVideoControlStop, false );
5988
5989
5990		}
5991
5992		function removeControlListeners () {
5993
5994			scope.container.removeEventListener( 'mousemove', onVideoControlDrag, false );
5995			scope.container.removeEventListener( 'mouseup', onVideoControlStop, false );
5996			scope.container.removeEventListener( 'touchmove', onVideoControlDrag, false );
5997			scope.container.removeEventListener( 'touchend', onVideoControlStop, false );
5998
5999		}
6000
6001		function onTap ( event ) {
6002
6003			event.preventDefault();
6004			event.stopPropagation();
6005
6006			var percentage;
6007
6008			if ( event.target === progressElementControl ) { return; }
6009
6010			percentage = ( event.changedTouches && event.changedTouches.length > 0 )
6011				? ( event.changedTouches[0].pageX - event.target.getBoundingClientRect().left ) / this.clientWidth
6012				: event.offsetX / this.clientWidth;
6013
6014			/**
6015			 * Viewer handler event
6016			 * @type {object}
6017			 * @property {string} method - 'setVideoCurrentTime' function call on PANOLENS.Viewer
6018			 * @property {number} data - Percentage of current video. Range from 0.0 to 1.0
6019			 */
6020			scope.dispatchEvent( { type: 'panolens-viewer-handler', method: 'setVideoCurrentTime', data: percentage } );
6021
6022			item.setProgress( event.offsetX / this.clientWidth );
6023
6024		};
6025
6026		function onDispose () {
6027
6028			removeControlListeners();
6029			progressElement = null;
6030			progressElementControl = null;
6031
6032		}
6033
6034		progressElement.appendChild( progressElementControl );
6035
6036		item = this.createCustomItem( {
6037
6038			style : { 
6039
6040				float : 'left',
6041				width : '30%',
6042				height : '4px',
6043				marginTop : '20px',
6044				backgroundColor : 'rgba(188,188,188,0.8)'
6045
6046			},
6047
6048			onTap : onTap,
6049			onDispose: onDispose
6050
6051		} );
6052
6053		item.appendChild( progressElement );
6054
6055		item.setProgress = function( percentage ) {
6056
6057			progressElement.style.width = percentage * 100 + '%';
6058
6059		};		
6060
6061		this.addEventListener( 'video-update', function ( event ) { 
6062
6063			item.setProgress( event.percentage ); 
6064
6065		} );
6066
6067		return item;
6068
6069	};
6070
6071	/**
6072	 * Create menu item
6073	 * @param  {string} title - Title to display
6074	 * @return {HTMLDomElement} - An anchor tag element
6075	 */
6076	PANOLENS.Widget.prototype.createMenuItem = function ( title ) {
6077
6078		var scope = this, item = document.createElement( 'a' );
6079		item.textContent = title;
6080		item.style.display = 'block';
6081		item.style.padding = '10px';
6082		item.style.textDecoration = 'none';
6083		item.style.cursor = 'pointer';
6084		item.style.pointerEvents = 'auto';
6085		item.style.transition = this.DEFAULT_TRANSITION;
6086
6087		item.slide = function ( right ) {
6088
6089			this.style.transform = 'translateX(' + ( right ? '' : '-' ) + '100%)';
6090
6091		};
6092
6093		item.unslide = function () {
6094
6095			this.style.transform = 'translateX(0)';
6096
6097		};
6098
6099		item.setIcon = function ( url ) {
6100
6101			if ( this.icon ) {
6102
6103				this.icon.style.backgroundImage = 'url(' + url + ')';
6104
6105			}
6106
6107		};
6108
6109		item.setSelectionTitle = function ( title ) {
6110
6111			if ( this.selection ) {
6112
6113				this.selection.textContent = title;
6114
6115			}
6116
6117		};
6118
6119		item.addSelection = function ( name ) {
6120			
6121			var selection = document.createElement( 'span' );
6122			selection.style.fontSize = '13px';
6123			selection.style.fontWeight = '300';
6124			selection.style.float = 'right';
6125
6126			this.selection = selection;
6127			this.setSelectionTitle( name );
6128			this.appendChild( selection );
6129			
6130			return this;
6131
6132		};
6133
6134		item.addIcon = function ( url, left, flip ) {
6135
6136			url = url || PANOLENS.DataImage.ChevronRight;
6137			left = left || false;
6138			flip = flip || false;
6139			
6140			var element = document.createElement( 'span' );
6141			element.style.float = left ? 'left' : 'right';
6142			element.style.width = '17px';
6143			element.style.height = '17px';
6144			element.style[ 'margin' + ( left ? 'Right' : 'Left' ) ] = '12px';
6145			element.style.backgroundSize = 'cover';
6146
6147			if ( flip ) {
6148
6149				element.style.transform = 'rotateZ(180deg)';
6150
6151			}
6152
6153			this.icon = element;
6154			this.setIcon( url );
6155			this.appendChild( element );
6156
6157			return this;
6158
6159		};
6160
6161		item.addSubMenu = function ( title, items ) {
6162
6163			this.subMenu = scope.createSubMenu( title, items );
6164
6165			return this;
6166
6167		};
6168
6169		item.addEventListener( 'mouseenter', function () {
6170			
6171			this.style.backgroundColor = '#e0e0e0';
6172
6173		}, false );
6174
6175		item.addEventListener( 'mouseleave', function () {
6176			
6177			this.style.backgroundColor = '#fafafa';
6178
6179		}, false );
6180
6181		return item;
6182
6183	};
6184
6185	/**
6186	 * Create menu item header
6187	 * @param  {string} title - Title to display
6188	 * @return {HTMLDomElement} - An anchor tag element
6189	 */
6190	PANOLENS.Widget.prototype.createMenuItemHeader = function ( title ) {
6191
6192		var header = this.createMenuItem( title );
6193
6194		header.style.borderBottom = '1px solid #333';
6195		header.style.paddingBottom = '15px';
6196
6197		return header;
6198
6199	};
6200
6201	/**
6202	 * Create main menu
6203	 * @param  {array} menus - Menu array list
6204	 * @return {HTMLDomElement} - A span element
6205	 */
6206	PANOLENS.Widget.prototype.createMainMenu = function ( menus ) {
6207		
6208		var scope = this, menu = this.createMenu(), subMenu;
6209
6210		menu._width = 200;
6211		menu.changeSize( menu._width );
6212
6213		function onTap ( event ) {
6214
6215			event.preventDefault();
6216			event.stopPropagation();
6217
6218			var mainMenu = scope.mainMenu, subMenu = this.subMenu;
6219
6220			function onNextTick () {
6221
6222				mainMenu.changeSize( subMenu.clientWidth );
6223				subMenu.show();
6224				subMenu.unslideAll();
6225
6226			}
6227
6228			mainMenu.hide();
6229			mainMenu.slideAll();
6230			mainMenu.parentElement.appendChild( subMenu );
6231
6232			scope.activeMainItem = this;
6233			scope.activeSubMenu = subMenu;
6234
6235			window.requestAnimationFrame( onNextTick );
6236
6237		};
6238
6239		for ( var i = 0; i < menus.length; i++ ) {
6240
6241			var item = menu.addItem( menus[ i ].title );
6242
6243			item.style.paddingLeft = '20px';
6244
6245			item.addIcon()
6246				.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false );
6247
6248			if ( menus[ i ].subMenu && menus[ i ].subMenu.length > 0 ) {
6249
6250				var title = menus[ i ].subMenu[ 0 ].title;
6251
6252				item.addSelection( title )
6253					.addSubMenu( menus[ i ].title, menus[ i ].subMenu );
6254
6255			}
6256
6257		}
6258
6259		return menu;
6260
6261	};
6262
6263	/**
6264	 * Create sub menu
6265	 * @param {string} title - Sub menu title
6266	 * @param {array} items - Item array list
6267	 * @return {HTMLDomElement} - A span element
6268	 */
6269	PANOLENS.Widget.prototype.createSubMenu = function ( title, items ) {
6270
6271		var scope = this, menu, subMenu = this.createMenu();
6272
6273		subMenu.items = items;
6274		subMenu.activeItem;
6275
6276		function onTap ( event ) {
6277
6278			event.preventDefault();
6279			event.stopPropagation();
6280
6281			menu = scope.mainMenu;
6282			menu.changeSize( menu._width );
6283			menu.unslideAll();
6284			menu.show();
6285			subMenu.slideAll( true );
6286			subMenu.hide();
6287
6288			if ( this.type !== 'header' ) {
6289
6290				subMenu.setActiveItem( this );
6291				scope.activeMainItem.setSelectionTitle( this.textContent );
6292
6293				this.handler && this.handler();
6294
6295			}
6296
6297		}
6298
6299		subMenu.addHeader( title ).addIcon( undefined, true, true ).addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false );
6300
6301		for ( var i = 0; i < items.length; i++ ) {
6302
6303			var item = subMenu.addItem( items[ i ].title );
6304
6305			item.style.fontWeight = 300;
6306			item.handler = items[ i ].handler;
6307			item.addIcon( ' ', true );
6308			item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', onTap, false );
6309
6310			if ( !subMenu.activeItem ) {
6311
6312				subMenu.setActiveItem( item );
6313
6314			}
6315
6316		}
6317
6318		subMenu.slideAll( true );
6319
6320		return subMenu;
6321		
6322	};
6323
6324	/**
6325	 * Create general menu
6326	 * @return {HTMLDomElement} - A span element
6327	 */
6328	PANOLENS.Widget.prototype.createMenu = function () {
6329
6330		var scope = this, menu = document.createElement( 'span' ), style;
6331
6332		style = menu.style;
6333
6334		style.padding = '5px 0';
6335		style.position = 'fixed';
6336		style.bottom = '100%';
6337		style.right = '14px';
6338		style.backgroundColor = '#fafafa';
6339		style.fontFamily = 'Helvetica Neue';
6340		style.fontSize = '14px';
6341		style.visibility = 'hidden';
6342		style.opacity = 0;
6343		style.boxShadow = '0 0 12pt rgba(0,0,0,0.25)';
6344  		style.borderRadius = '2px';
6345		style.overflow = 'hidden';
6346		style.willChange = 'width, height, opacity';
6347		style.pointerEvents = 'auto';
6348		style.transition = this.DEFAULT_TRANSITION;
6349
6350		menu.visible = false;
6351
6352		menu.changeSize = function ( width, height ) {
6353
6354			if ( width ) {
6355
6356				this.style.width = width + 'px';
6357
6358			}
6359
6360			if ( height ) {
6361
6362				this.style.height = height + 'px';
6363
6364			}
6365
6366		};
6367
6368		menu.show = function () {
6369
6370			this.style.opacity = 1;
6371			this.style.visibility = 'visible';
6372			this.visible = true;
6373
6374		};
6375
6376		menu.hide = function () {
6377
6378			this.style.opacity = 0;
6379			this.style.visibility = 'hidden';
6380			this.visible = false;
6381
6382		};
6383
6384		menu.toggle = function () {
6385
6386			if ( this.visible ) {
6387
6388				this.hide();
6389
6390			} else {
6391
6392				this.show();
6393
6394			}
6395
6396		};
6397
6398		menu.slideAll = function ( right ) {
6399
6400			for ( var i = 0; i < menu.children.length; i++ ){
6401
6402				if ( menu.children[ i ].slide ) {
6403
6404					menu.children[ i ].slide( right );
6405
6406				}
6407
6408			}
6409
6410		};
6411
6412		menu.unslideAll = function () {
6413
6414			for ( var i = 0; i < menu.children.length; i++ ){
6415
6416				if ( menu.children[ i ].unslide ) {
6417
6418					menu.children[ i ].unslide();
6419
6420				}
6421
6422			}
6423
6424		};
6425
6426		menu.addHeader = function ( title ) {
6427
6428			var header = scope.createMenuItemHeader( title );
6429			header.type = 'header';
6430
6431			this.appendChild( header );
6432
6433			return header;
6434
6435		};
6436
6437		menu.addItem = function ( title ) {
6438
6439			var item = scope.createMenuItem( title );
6440			item.type = 'item';
6441
6442			this.appendChild( item );
6443
6444			return item;
6445
6446		};
6447
6448		menu.setActiveItem = function ( item ) {
6449
6450			if ( this.activeItem ) {
6451
6452				this.activeItem.setIcon( ' ' );
6453
6454			}
6455
6456			item.setIcon( PANOLENS.DataImage.Check );
6457
6458			this.activeItem = item;
6459
6460		};
6461
6462		menu.addEventListener( 'mousemove', this.PREVENT_EVENT_HANDLER, true );
6463		menu.addEventListener( 'mouseup', this.PREVENT_EVENT_HANDLER, true );
6464		menu.addEventListener( 'mousedown', this.PREVENT_EVENT_HANDLER, true );
6465
6466		return menu;
6467
6468	};
6469
6470	/**
6471	 * Create custom item element
6472	 * @return {HTMLSpanElement} - The dom element icon
6473	 */
6474	PANOLENS.Widget.prototype.createCustomItem = function ( options ) {
6475
6476		options = options || {};
6477
6478		var scope = this,
6479			item = options.element || document.createElement( 'span' );
6480
6481		item.style.cursor = 'pointer';
6482		item.style.float = 'right';
6483		item.style.width = '44px';
6484		item.style.height = '100%';
6485		item.style.backgroundSize = '60%';
6486		item.style.backgroundRepeat = 'no-repeat';
6487		item.style.backgroundPosition = 'center';
6488		item.style.webkitUserSelect = 
6489		item.style.MozUserSelect = 
6490		item.style.userSelect = 'none';
6491		item.style.position = 'relative';
6492		item.style.pointerEvents = 'auto';
6493
6494		// White glow on icon
6495		item.addEventListener( scope.TOUCH_ENABLED ? 'touchstart' : 'mouseenter', function() {
6496			item.style.filter = 
6497			item.style.webkitFilter = 'drop-shadow(0 0 5px rgba(255,255,255,1))';
6498		});
6499		item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'mouseleave', function() {
6500			item.style.filter = 
6501			item.style.webkitFilter = '';
6502		});
6503
6504		item = this.mergeStyleOptions( item, options.style );
6505
6506		if ( options.onTap ) {
6507
6508			item.addEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', options.onTap, false );
6509
6510		}
6511
6512		item.dispose = function () {
6513
6514			item.removeEventListener( scope.TOUCH_ENABLED ? 'touchend' : 'click', options.onTap, false );
6515
6516			options.onDispose && options.onDispose();
6517
6518		};
6519		
6520		return item;
6521
6522	};
6523
6524	/**
6525	 * Merge item css style
6526	 * @param  {HTMLDOMElement} element - The element to be merged with style
6527	 * @param  {object} options - The style options
6528	 * @return {HTMLDOMElement} - The same element with merged styles
6529	 */
6530	PANOLENS.Widget.prototype.mergeStyleOptions = function ( element, options ) {
6531
6532		options = options || {};
6533
6534		for ( var property in options ){
6535
6536			if ( options.hasOwnProperty( property ) ) {
6537
6538				element.style[ property ] = options[ property ];
6539
6540			}
6541
6542		}
6543
6544		return element;
6545
6546	};
6547
6548	/**
6549	 * Dispose widgets by detaching dom elements from container
6550	 */
6551	PANOLENS.Widget.prototype.dispose = function () {
6552
6553		if ( this.barElement ) {
6554			this.container.removeChild( this.barElement );
6555			this.barElement.dispose();
6556			this.barElement = null;
6557
6558		}
6559
6560	};
6561
6562})();;( function () {
6563
6564	/**
6565	 * Information spot attached to panorama
6566	 * @constructor
6567	 * @param {number} [scale=300] - Infospot scale
6568	 * @param {imageSrc} [imageSrc=PANOLENS.DataImage.Info] - Image overlay info
6569	 * @param {boolean} [animated=true] - Enable default hover animation
6570	 */
6571	PANOLENS.Infospot = function ( scale, imageSrc, animated ) {
6572		
6573		var scope = this, ratio, startScale, endScale, duration;
6574
6575		scale = scale || 300;
6576		imageSrc = imageSrc || PANOLENS.DataImage.Info;
6577		duration = 500;
6578
6579		THREE.Sprite.call( this );
6580
6581		this.type = 'infospot';
6582
6583		this.animated = animated !== undefined ? animated : true;
6584		this.isHovering = false;
6585		this.visible = false;
6586
6587		this.element;
6588		this.toPanorama;
6589		this.cursorStyle;
6590
6591		this.mode = PANOLENS.Modes.UNKNOWN;
6592
6593		this.scale.set( scale, scale, 1 );
6594		this.rotation.y = Math.PI;
6595		this.scaleFactor = 1.3;
6596
6597		this.container;
6598
6599		// Event Handler
6600		this.HANDLER_FOCUS;
6601
6602		PANOLENS.Utils.TextureLoader.load( imageSrc, postLoad );		
6603
6604		function postLoad ( texture ) {
6605
6606			texture.wrapS = THREE.RepeatWrapping;
6607			texture.repeat.x = - 1;
6608
6609			texture.image.width = texture.image.naturalWidth || 64;
6610			texture.image.height = texture.image.naturalHeight || 64;
6611
6612			ratio = texture.image.width / texture.image.height;
6613			scope.scale.set( ratio * scale, scale, 1 );
6614
6615			startScale = scope.scale.clone();
6616
6617			scope.scaleUpAnimation = new TWEEN.Tween( scope.scale )
6618				.to( { x: startScale.x * scope.scaleFactor, y: startScale.y * scope.scaleFactor }, duration )
6619				.easing( TWEEN.Easing.Elastic.Out );
6620
6621			scope.scaleDownAnimation = new TWEEN.Tween( scope.scale )
6622				.to( { x: startScale.x, y: startScale.y }, duration )
6623				.easing( TWEEN.Easing.Elastic.Out );
6624
6625			scope.material.side = THREE.DoubleSide;
6626			scope.material.map = texture;
6627			scope.material.depthTest = false;
6628			scope.material.needsUpdate = true;
6629
6630		}
6631
6632		function show () {
6633
6634			this.visible = true;
6635
6636		}
6637
6638		function hide () {
6639
6640			this.visible = false;
6641
6642		}
6643
6644		// Add show and hide animations
6645		this.showAnimation = new TWEEN.Tween( this.material )
6646			.to( { opacity: 1 }, duration )
6647			.onStart( show.bind( this ) )
6648			.easing( TWEEN.Easing.Quartic.Out );
6649
6650		this.hideAnimation = new TWEEN.Tween( this.material )
6651			.to( { opacity: 0 }, duration )
6652			.onComplete( hide.bind( this ) )
6653			.easing( TWEEN.Easing.Quartic.Out );
6654
6655		// Attach event listeners
6656		this.addEventListener( 'click', this.onClick );
6657		this.addEventListener( 'hover', this.onHover );
6658		this.addEventListener( 'hoverenter', this.onHoverStart );
6659		this.addEventListener( 'hoverleave', this.onHoverEnd );
6660		this.addEventListener( 'panolens-dual-eye-effect', this.onDualEyeEffect );
6661		this.addEventListener( 'panolens-container', this.setContainer.bind( this ) );
6662		this.addEventListener( 'dismiss', this.onDismiss );
6663		this.addEventListener( 'panolens-infospot-focus', this.setFocusMethod );
6664
6665	};
6666
6667	PANOLENS.Infospot.prototype = Object.create( THREE.Sprite.prototype );
6668
6669	/**
6670	 * Set infospot container
6671	 * @param {HTMLElement|object} data - Data with container information
6672	 */
6673	PANOLENS.Infospot.prototype.setContainer = function ( data ) {
6674
6675		var container;
6676
6677		if ( data instanceof HTMLElement ) {
6678
6679			container = data;
6680
6681		} else if ( data && data.container ) {
6682
6683			container = data.container;
6684
6685		}
6686
6687		// Append element if exists
6688		if ( container && this.element ) {
6689
6690			container.appendChild( this.element );
6691
6692		}
6693
6694		this.container = container;
6695
6696	};
6697
6698	/**
6699	 * Get container
6700	 * @return {HTMLElement} - The container of this infospot
6701	 */
6702	PANOLENS.Infospot.prototype.getContainer = function () {
6703
6704		return this.container;
6705
6706	};
6707
6708	/**
6709	 * This will be called by a click event
6710	 * Translate and lock the hovering element if any
6711	 * @param  {object} event - Event containing mouseEvent with clientX and clientY
6712	 */
6713	PANOLENS.Infospot.prototype.onClick = function ( event ) {
6714
6715		if ( this.element && this.getContainer() ) {
6716
6717			this.onHoverStart( event );
6718
6719			// Lock element
6720			this.lockHoverElement();
6721
6722		}
6723
6724	};
6725
6726	/**
6727	 * Dismiss current element if any
6728	 * @param  {object} event - Dismiss event
6729	 */
6730	PANOLENS.Infospot.prototype.onDismiss = function ( event ) {
6731
6732		if ( this.element ) {
6733
6734			this.unlockHoverElement();
6735			this.onHoverEnd();
6736
6737		}
6738
6739	};
6740
6741	/**
6742	 * This will be called by a mouse hover event
6743	 * Translate the hovering element if any
6744	 * @param  {object} event - Event containing mouseEvent with clientX and clientY
6745	 */
6746	PANOLENS.Infospot.prototype.onHover = function ( event ) {
6747
6748	};
6749
6750	/**
6751	 * This will be called on a mouse hover start
6752	 * Sets cursor style to 'pointer', display the element and scale up the infospot
6753	 */
6754	PANOLENS.Infospot.prototype.onHoverStart = function ( event ) {
6755
6756		if ( !this.getContainer() ) { return; }
6757
6758		var cursorStyle = this.cursorStyle || ( this.mode === PANOLENS.Modes.NORMAL ? 'pointer' : 'default' );
6759
6760		this.isHovering = true;
6761		this.container.style.cursor = cursorStyle;
6762		
6763		if ( this.animated ) {
6764
6765			this.scaleDownAnimation && this.scaleDownAnimation.stop();
6766			this.scaleUpAnimation && this.scaleUpAnimation.start();
6767
6768		}
6769		
6770		if ( this.element && event.mouseEvent.clientX >= 0 && event.mouseEvent.clientY >= 0 ) {
6771
6772			if ( this.mode === PANOLENS.Modes.CARDBOARD || this.mode === PANOLENS.Modes.STEREO ) {
6773
6774				this.element.style.display = 'none';
6775				this.element.left && ( this.element.left.style.display = 'block' );
6776				this.element.right && ( this.element.right.style.display = 'block' );
6777
6778				// Store element width for reference
6779				this.element._width = this.element.left.clientWidth;
6780				this.element._height = this.element.left.clientHeight;
6781
6782			} else {
6783
6784				this.element.style.display = 'block';
6785				this.element.left && ( this.element.left.style.display = 'none' );
6786				this.element.right && ( this.element.right.style.display = 'none' );
6787
6788				// Store element width for reference
6789				this.element._width = this.element.clientWidth;
6790				this.element._height = this.element.clientHeight;
6791
6792			}
6793			
6794		}
6795
6796	};
6797
6798	/**
6799	 * This will be called on a mouse hover end
6800	 * Sets cursor style to 'default', hide the element and scale down the infospot
6801	 */
6802	PANOLENS.Infospot.prototype.onHoverEnd = function () {
6803
6804		if ( !this.getContainer() ) { return; }
6805
6806		this.isHovering = false;
6807		this.container.style.cursor = 'default';
6808
6809		if ( this.animated ) {
6810
6811			this.scaleUpAnimation && this.scaleUpAnimation.stop();
6812			this.scaleDownAnimation && this.scaleDownAnimation.start();
6813
6814		}
6815
6816		if ( this.element && !this.element.locked ) {
6817
6818			this.element.style.display = 'none';
6819			this.element.left && ( this.element.left.style.display = 'none' );
6820			this.element.right && ( this.element.right.style.display = 'none' );
6821
6822			this.unlockHoverElement();
6823
6824		}
6825
6826	};
6827
6828	/**
6829	 * On dual eye effect handler
6830	 * Creates duplicate left and right element
6831	 * @param  {object} event - panolens-dual-eye-effect event
6832	 */
6833	PANOLENS.Infospot.prototype.onDualEyeEffect = function ( event ) {
6834		
6835		if ( !this.getContainer() ) { return; }
6836
6837		var element, halfWidth, halfHeight;
6838
6839		this.mode = event.mode;
6840
6841		element = this.element;
6842
6843		halfWidth = this.container.clientWidth / 2;
6844		halfHeight = this.container.clientHeight / 2;
6845
6846		if ( !element ) {
6847
6848			return;
6849
6850		}
6851
6852		if ( !element.left || !element.right ) {
6853
6854			element.left = element.cloneNode( true );
6855			element.right = element.cloneNode( true );
6856
6857		}
6858
6859		if ( this.mode === PANOLENS.Modes.CARDBOARD || this.mode === PANOLENS.Modes.STEREO ) {
6860
6861			element.left.style.display = element.style.display;
6862			element.right.style.display = element.style.display;
6863			element.style.display = 'none';
6864
6865		} else {
6866
6867			element.style.display = element.left.style.display;
6868			element.left.style.display = 'none';
6869			element.right.style.display = 'none';
6870
6871		}
6872
6873		// Update elements translation
6874		this.translateElement( halfWidth, halfHeight );
6875
6876		this.container.appendChild( element.left );
6877		this.container.appendChild( element.right );
6878
6879	};
6880
6881	/**
6882	 * Translate the hovering element by css transform
6883	 * @param  {number} x - X position on the window screen
6884	 * @param  {number} y - Y position on the window screen
6885	 */
6886	PANOLENS.Infospot.prototype.translateElement = function ( x, y ) {
6887
6888		if ( !this.element._width || !this.element._height || !this.getContainer() ) {
6889
6890			return;
6891
6892		}
6893
6894		var left, top, element, width, height, delta, container;
6895
6896		container = this.container;
6897		element = this.element;
6898		width = element._width / 2;
6899		height = element._height / 2;
6900		delta = element.verticalDelta !== undefined ? element.verticalDelta : 40;
6901
6902		left = x - width;
6903		top = y - height - delta;
6904
6905		if ( ( this.mode === PANOLENS.Modes.CARDBOARD || this.mode === PANOLENS.Modes.STEREO ) 
6906				&& element.left && element.right
6907				&& !( x === container.clientWidth / 2 && y === container.clientHeight / 2 ) ) {
6908
6909			left = container.clientWidth / 4 - width + ( x - container.clientWidth / 2 );
6910			top = container.clientHeight / 2 - height - delta + ( y - container.clientHeight / 2 );
6911
6912			this.setElementStyle( 'transform', element.left, 'translate(' + left + 'px, ' + top + 'px)' );
6913
6914			left += container.clientWidth / 2;
6915
6916			this.setElementStyle( 'transform', element.right, 'translate(' + left + 'px, ' + top + 'px)' );
6917
6918		} else {
6919
6920			this.setElementStyle( 'transform', element, 'translate(' + left + 'px, ' + top + 'px)' );
6921
6922		}
6923
6924	};
6925
6926	/**
6927	 * Set vendor specific css
6928	 * @param {string} type - CSS style name
6929	 * @param {HTMLElement} element - The element to be modified
6930	 * @param {string} value - Style value
6931	 */
6932	PANOLENS.Infospot.prototype.setElementStyle = function ( type, element, value ) {
6933
6934		var style = element.style;
6935
6936		if ( type === 'transform' ) {
6937
6938			style.webkitTransform = style.msTransform = style.transform = value;
6939
6940		}
6941
6942	};
6943
6944	/**
6945	 * Set hovering text content
6946	 * @param {string} text - Text to be displayed
6947	 */
6948	PANOLENS.Infospot.prototype.setText = function ( text ) {
6949
6950		if ( this.element ) {
6951
6952			this.element.textContent = text;
6953
6954		}
6955
6956	};
6957
6958	/**
6959	 * Set cursor css style on hover
6960	 */
6961	PANOLENS.Infospot.prototype.setCursorHoverStyle = function ( style ) {
6962
6963		this.cursorStyle = style;
6964
6965	};
6966
6967	/**
6968	 * Add hovering text element
6969	 * @param {string} text - Text to be displayed
6970	 * @param {number} [delta=40] - Vertical delta to the infospot
6971	 */
6972	PANOLENS.Infospot.prototype.addHoverText = function ( text, delta ) {
6973
6974		if ( !this.element ) {
6975
6976			this.element = document.createElement( 'div' );
6977			this.element.style.display = 'none';
6978			this.element.style.color = '#fff';
6979			this.element.style.top = 0;
6980			this.element.style.maxWidth = '50%';
6981			this.element.style.maxHeight = '50%';
6982			this.element.style.textShadow = '0 0 3px #000000';
6983			this.element.style.fontFamily = '"Trebuchet MS", Helvetica, sans-serif';
6984			this.element.style.position = 'absolute';
6985			this.element.classList.add( 'panolens-infospot' );
6986			this.element.verticalDelta = delta !== undefined ? delta : 40;
6987
6988		}
6989
6990		this.setText( text );
6991
6992	};
6993
6994	/**
6995	 * Add hovering element by cloning an element
6996	 * @param {HTMLDOMElement} el - Element to be cloned and displayed
6997	 * @param {number} [delta=40] - Vertical delta to the infospot
6998	 */
6999	PANOLENS.Infospot.prototype.addHoverElement = function ( el, delta ) {
7000
7001		if ( !this.element ) { 
7002
7003			this.element = el.cloneNode( true );
7004			this.element.style.display = 'none';
7005			this.element.style.top = 0;
7006			this.element.style.position = 'absolute';
7007			this.element.classList.add( 'panolens-infospot' );
7008			this.element.verticalDelta = delta !== undefined ? delta : 40;
7009
7010		}
7011
7012	};
7013
7014	/**
7015	 * Remove hovering element
7016	 */
7017	PANOLENS.Infospot.prototype.removeHoverElement = function () {
7018
7019		if ( this.element ) { 
7020
7021			if ( this.element.left ) {
7022
7023				this.container.removeChild( this.element.left );
7024				this.element.left = null;
7025
7026			}
7027
7028			if ( this.element.right ) {
7029
7030				this.container.removeChild( this.element.right );
7031				this.element.right = null;
7032
7033			}
7034
7035			this.container.removeChild( this.element );
7036			this.element = null;
7037
7038		}
7039
7040	};
7041
7042	/**
7043	 * Lock hovering element
7044	 */
7045	PANOLENS.Infospot.prototype.lockHoverElement = function () {
7046
7047		if ( this.element ) { 
7048
7049			this.element.locked = true;
7050
7051		}
7052
7053	};
7054
7055	/**
7056	 * Unlock hovering element
7057	 */
7058	PANOLENS.Infospot.prototype.unlockHoverElement = function () {
7059
7060		if ( this.element ) { 
7061
7062			this.element.locked = false;
7063
7064		}
7065
7066	};
7067
7068	/**
7069	 * Show infospot
7070	 * @param  {number} [delay=0] - Delay time to show
7071	 */
7072	PANOLENS.Infospot.prototype.show = function ( delay ) {
7073
7074		delay = delay || 0;
7075
7076		if ( this.animated ) {
7077
7078			this.hideAnimation && this.hideAnimation.stop();
7079			this.showAnimation && this.showAnimation.delay( delay ).start();
7080
7081		}
7082
7083	};
7084
7085	/**
7086	 * Hide infospot
7087	 * @param  {number} [delay=0] - Delay time to hide
7088	 */
7089	PANOLENS.Infospot.prototype.hide = function ( delay ) {
7090
7091		delay = delay || 0;
7092
7093		if ( this.animated ) {
7094
7095			this.showAnimation && this.showAnimation.stop();
7096			this.hideAnimation && this.hideAnimation.delay( delay ).start();
7097
7098		}
7099		
7100		
7101	};
7102
7103	/**
7104	 * Set focus event handler
7105	 */
7106	PANOLENS.Infospot.prototype.setFocusMethod = function ( event ) {
7107
7108		if ( event ) {
7109
7110			this.HANDLER_FOCUS = event.method;
7111
7112		}
7113
7114	};
7115
7116	/**
7117	 * Focus camera center to this infospot
7118	 * @param {number} [duration=1000] - Duration to tween
7119	 * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function
7120	 */
7121	PANOLENS.Infospot.prototype.focus = function ( duration, easing ) {
7122
7123		if ( this.HANDLER_FOCUS ) {
7124
7125			this.HANDLER_FOCUS( this.position, duration, easing );
7126			this.onDismiss();
7127
7128		}
7129
7130	};
7131
7132	/**
7133	 * Dispose infospot
7134	 */
7135	PANOLENS.Infospot.prototype.dispose = function () {
7136
7137		this.removeHoverElement();
7138		this.material.dispose();
7139
7140		if ( this.parent ) {
7141
7142			this.parent.remove( this );
7143
7144		}
7145
7146	};
7147
7148} )();;( function () {
7149
7150	'use strict';
7151
7152	/**
7153	 * Viewer contains pre-defined scene, camera and renderer
7154	 * @constructor
7155	 * @param {object} [options] - Use custom or default config options
7156	 * @param {HTMLElement} [options.container] - A HTMLElement to host the canvas
7157	 * @param {THREE.Scene} [options.scene=THREE.Scene] - A THREE.Scene which contains panorama and 3D objects
7158	 * @param {THREE.Camera} [options.camera=THREE.PerspectiveCamera] - A THREE.Camera to view the scene
7159	 * @param {THREE.WebGLRenderer} [options.renderer=THREE.WebGLRenderer] - A THREE.WebGLRenderer to render canvas
7160	 * @param {boolean} [options.controlBar=true] - Show/hide control bar on the bottom of the container
7161	 * @param {array}   [options.controlButtons=[]] - Button names to mount on controlBar if controlBar exists, Defaults to ['fullscreen', 'setting', 'video']
7162	 * @param {boolean} [options.autoHideControlBar=false] - Auto hide control bar when click on non-active area
7163	 * @param {boolean} [options.autoHideInfospot=true] - Auto hide infospots when click on non-active area
7164	 * @param {boolean} [options.horizontalView=false] - Allow only horizontal camera control
7165	 * @param {number}  [options.clickTolerance=10] - Distance tolerance to tigger click / tap event
7166	 * @param {number}  [options.cameraFov=60] - Camera field of view value
7167	 * @param {boolean} [options.reverseDragging=false] - Reverse dragging direction
7168	 * @param {boolean} [options.enableReticle=false] - Enable reticle for mouseless interaction other than VR mode
7169	 * @param {number}  [options.dwellTime=1500] - Dwell time for reticle selection
7170	 * @param {boolean} [options.autoReticleSelect=true] - Auto select a clickable target after dwellTime
7171	 * @param {boolean} [options.viewIndicator=false] - Adds an angle view indicator in upper left corner
7172	 * @param {number}  [options.indicatorSize=30] - Size of View Indicator
7173	 * @param {string}  [options.output='none'] - Whether and where to output raycast position. Could be 'console' or 'overlay'
7174	 */
7175	PANOLENS.Viewer = function ( options ) {
7176
7177		THREE.EventDispatcher.call( this );
7178
7179		if ( !THREE ) {
7180
7181			console.error('Three.JS not found');
7182
7183			return;
7184		}
7185
7186		var container;
7187
7188		options = options || {};
7189		options.controlBar = options.controlBar !== undefined ? options.controlBar : true;
7190		options.controlButtons = options.controlButtons || [ 'fullscreen', 'setting', 'video' ];
7191		options.autoHideControlBar = options.autoHideControlBar !== undefined ? options.autoHideControlBar : false;
7192		options.autoHideInfospot = options.autoHideInfospot !== undefined ? options.autoHideInfospot : true;
7193		options.horizontalView = options.horizontalView !== undefined ? options.horizontalView : false;
7194		options.clickTolerance = options.clickTolerance || 10;
7195		options.cameraFov = options.cameraFov || 60;
7196		options.reverseDragging = options.reverseDragging || false;
7197		options.enableReticle = options.enableReticle || false;
7198		options.dwellTime = options.dwellTime || 1500;
7199		options.autoReticleSelect = options.autoReticleSelect !== undefined ? options.autoReticleSelect : true;
7200		options.viewIndicator = options.viewIndicator !== undefined ? options.viewIndicator : false;
7201		options.indicatorSize = options.indicatorSize || 30;
7202		options.output = options.output ? options.output : 'none';
7203
7204		this.options = options;
7205
7206		// Container
7207		if ( options.container ) {
7208
7209			container = options.container;
7210			container._width = container.clientWidth;
7211			container._height = container.clientHeight;
7212
7213		} else {
7214
7215			container = document.createElement( 'div' );
7216			container.classList.add( 'panolens-container' );
7217			container.style.width = '100%';
7218			container.style.height = '100%';
7219			container._width = window.innerWidth;
7220			container._height = window.innerHeight;
7221			document.body.appendChild( container );
7222
7223		}
7224
7225		this.container = container;
7226
7227		this.camera = options.camera || new THREE.PerspectiveCamera( this.options.cameraFov, this.container.clientWidth / this.container.clientHeight, 1, 10000 );
7228		this.scene = options.scene || new THREE.Scene();
7229		this.renderer = options.renderer || new THREE.WebGLRenderer( { alpha: true, antialias: false } );
7230
7231		this.viewIndicatorSize = options.indicatorSize;
7232
7233		this.reticle = {};
7234		this.tempEnableReticle = this.options.enableReticle;
7235
7236		this.mode = PANOLENS.Modes.NORMAL;
7237
7238		this.OrbitControls;
7239		this.DeviceOrientationControls;
7240
7241		this.CardboardEffect;
7242		this.StereoEffect;
7243
7244		this.controls;
7245		this.effect;
7246		this.panorama;
7247		this.widget;
7248
7249		this.hoverObject;
7250		this.infospot;
7251		this.pressEntityObject;
7252		this.pressObject;
7253
7254		this.raycaster = new THREE.Raycaster();
7255		this.raycasterPoint = new THREE.Vector2();
7256		this.userMouse = new THREE.Vector2();
7257		this.updateCallbacks = [];
7258		this.requestAnimationId;
7259
7260		this.cameraFrustum = new THREE.Frustum();
7261		this.cameraViewProjectionMatrix = new THREE.Matrix4();
7262
7263		this.outputDivElement;
7264
7265		// Handler references
7266		this.HANDLER_MOUSE_DOWN = this.onMouseDown.bind( this );
7267		this.HANDLER_MOUSE_UP = this.onMouseUp.bind( this );
7268		this.HANDLER_MOUSE_MOVE = this.onMouseMove.bind( this );
7269		this.HANDLER_WINDOW_RESIZE = this.onWindowResize.bind( this );
7270		this.HANDLER_KEY_DOWN = this.onKeyDown.bind( this );
7271		this.HANDLER_KEY_UP = this.onKeyUp.bind( this );
7272		this.HANDLER_TAP = this.onTap.bind( this, {
7273			clientX: this.container.clientWidth / 2,
7274			clientY: this.container.clientHeight / 2
7275		} );
7276
7277		// Flag for infospot output
7278		this.OUTPUT_INFOSPOT = false;
7279
7280		// Animations
7281		this.tweenLeftAnimation = new TWEEN.Tween();
7282		this.tweenUpAnimation = new TWEEN.Tween();
7283
7284		// Renderer
7285		this.renderer.setPixelRatio( window.devicePixelRatio );
7286		this.renderer.setSize( this.container.clientWidth, this.container.clientHeight );
7287		this.renderer.setClearColor( 0x000000, 1 );
7288		this.renderer.sortObjects = false;
7289
7290		// Append Renderer Element to container
7291		this.renderer.domElement.classList.add( 'panolens-canvas' );
7292		this.renderer.domElement.style.display = 'block';
7293		this.container.style.backgroundColor = '#000';
7294		this.container.appendChild( this.renderer.domElement );
7295
7296		// Camera Controls
7297		this.OrbitControls = new THREE.OrbitControls( this.camera, this.container );
7298		this.OrbitControls.name = 'orbit';
7299		this.OrbitControls.minDistance = 1;
7300		this.OrbitControls.noPan = true;
7301		this.DeviceOrientationControls = new THREE.DeviceOrientationControls( this.camera, this.container );
7302		this.DeviceOrientationControls.name = 'device-orientation';
7303		this.DeviceOrientationControls.enabled = false;
7304		this.camera.position.z = 1;
7305
7306		// Register change event if passiveRenering
7307		if ( this.options.passiveRendering ) {
7308
7309			console.warn( 'passiveRendering is now deprecated' );
7310
7311		}
7312
7313		// Controls
7314		this.controls = [ this.OrbitControls, this.DeviceOrientationControls ];
7315		this.control = this.OrbitControls;
7316
7317		// Cardboard effect
7318		this.CardboardEffect = new THREE.CardboardEffect( this.renderer );
7319		this.CardboardEffect.setSize( this.container.clientWidth, this.container.clientHeight );
7320
7321		// Stereo effect
7322		this.StereoEffect = new THREE.StereoEffect( this.renderer );
7323		this.StereoEffect.setSize( this.container.clientWidth, this.container.clientHeight );
7324
7325		this.effect = this.CardboardEffect;
7326
7327		// Add default hidden reticle
7328		this.addReticle();
7329
7330		// Lock horizontal view
7331		if ( this.options.horizontalView ) {
7332			this.OrbitControls.minPolarAngle = Math.PI / 2;
7333			this.OrbitControls.maxPolarAngle = Math.PI / 2;
7334		}
7335
7336		// Add Control UI
7337		if ( this.options.controlBar !== false ) {
7338			this.addDefaultControlBar( this.options.controlButtons );
7339		}
7340
7341		// Add View Indicator
7342		if ( this.options.viewIndicator ) {
7343			this.addViewIndicator();
7344		}
7345
7346		// Reverse dragging direction
7347		if ( this.options.reverseDragging ) {
7348			this.reverseDraggingDirection();
7349		}
7350
7351		// Register event if reticle is enabled, otherwise defaults to mouse
7352		if ( this.options.enableReticle ) {
7353			this.enableReticleControl();
7354		} else {
7355			this.registerMouseAndTouchEvents();
7356		}
7357
7358		if ( this.options.output === 'overlay' ) {
7359			this.addOutputElement();
7360		}
7361
7362		// Register dom event listeners
7363		this.registerEventListeners();
7364
7365		// Animate
7366		this.animate.call( this );
7367
7368	};
7369
7370	PANOLENS.Viewer.prototype = Object.create( THREE.EventDispatcher.prototype );
7371
7372	PANOLENS.Viewer.prototype.constructor = PANOLENS.Viewer;
7373
7374	/**
7375	 * Add an object to the scene
7376	 * Automatically hookup with panolens-viewer-handler listener
7377	 * to communicate with viewer method
7378	 * @param {THREE.Object3D} object - The object to be added
7379	 */
7380	PANOLENS.Viewer.prototype.add = function ( object ) {
7381
7382		if ( arguments.length > 1 ) {
7383
7384			for ( var i = 0; i < arguments.length; i ++ ) {
7385
7386				this.add( arguments[ i ] );
7387
7388			}
7389
7390			return this;
7391
7392		}
7393
7394		this.scene.add( object );
7395
7396		// All object added to scene has 'panolens-viewer-handler' event to handle viewer communication
7397		if ( object.addEventListener ) {
7398
7399			object.addEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) );
7400
7401		}
7402
7403		// All object added to scene being passed with container
7404		if ( object instanceof PANOLENS.Panorama && object.dispatchEvent ) {
7405
7406			object.dispatchEvent( { type: 'panolens-container', container: this.container } );
7407
7408		}
7409
7410		// Hookup default panorama event listeners
7411		if ( object.type === 'panorama' ) {
7412
7413			this.addPanoramaEventListener( object );
7414
7415			if ( !this.panorama ) {
7416
7417				this.setPanorama( object );
7418
7419			}
7420
7421		}
7422
7423	};
7424
7425	/**
7426	 * Remove an object from the scene
7427	 * @param  {THREE.Object3D} object - Object to be removed
7428	 */
7429	PANOLENS.Viewer.prototype.remove = function ( object ) {
7430
7431		if ( object.removeEventListener ) {
7432
7433			object.removeEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) );
7434
7435		}
7436
7437		this.scene.remove( object );
7438
7439	};
7440
7441	/**
7442	 * Add default control bar
7443	 * @param {array} array - The control buttons array
7444	 */
7445	PANOLENS.Viewer.prototype.addDefaultControlBar = function ( array ) {
7446
7447		var scope = this;
7448
7449		if ( this.widget ) {
7450
7451			console.warn( 'Default control bar exists' );
7452			return;
7453
7454		}
7455
7456		this.widget = new PANOLENS.Widget( this.container );
7457		this.widget.addEventListener( 'panolens-viewer-handler', this.eventHandler.bind( this ) );
7458		this.widget.addControlBar();
7459		array.forEach( function( buttonName ){
7460
7461			scope.widget.addControlButton( buttonName );
7462
7463		} );
7464
7465	};
7466
7467	/**
7468	 * Set a panorama to be the current one
7469	 * @param {PANOLENS.Panorama} pano - Panorama to be set
7470	 */
7471	PANOLENS.Viewer.prototype.setPanorama = function ( pano ) {
7472
7473		var scope = this, leavingPanorama = this.panorama;
7474
7475		if ( pano.type === 'panorama' && leavingPanorama !== pano ) {
7476
7477			// Clear exisiting infospot
7478			this.hideInfospot();
7479
7480			var afterEnterComplete = function () {
7481
7482				leavingPanorama && leavingPanorama.onLeave();
7483				pano.removeEventListener( 'enter-fade-start', afterEnterComplete );
7484
7485			};
7486
7487			pano.addEventListener( 'enter-fade-start', afterEnterComplete );
7488
7489			// Assign and enter panorama
7490			(this.panorama = pano).onEnter();
7491			
7492		}
7493
7494	};
7495
7496	/**
7497	 * Event handler to execute commands from child objects
7498	 * @param {object} event - The dispatched event with method as function name and data as an argument
7499	 */
7500	PANOLENS.Viewer.prototype.eventHandler = function ( event ) {
7501
7502		if ( event.method && this[ event.method ] ) {
7503
7504			this[ event.method ]( event.data );
7505
7506		}
7507
7508	};
7509
7510	/**
7511	 * Dispatch event to all descendants
7512	 * @param  {object} event - Event to be passed along
7513	 */
7514	PANOLENS.Viewer.prototype.dispatchEventToChildren = function ( event ) {
7515
7516		this.scene.traverse( function ( object ) {
7517
7518			if ( object.dispatchEvent ) {
7519
7520				object.dispatchEvent( event );
7521
7522			}
7523
7524		});
7525
7526	};
7527
7528	/**
7529	 * Set widget content
7530	 * @param  {integer} controlIndex - Control index
7531	 * @param  {PANOLENS.Modes} mode - Modes for effects
7532	 */
7533	PANOLENS.Viewer.prototype.activateWidgetItem = function ( controlIndex, mode ) {
7534
7535		var mainMenu = this.widget.mainMenu;
7536		var ControlMenuItem = mainMenu.children[ 0 ];
7537		var ModeMenuItem = mainMenu.children[ 1 ];
7538
7539		var item;
7540
7541		if ( controlIndex !== undefined ) {
7542
7543			switch ( controlIndex ) {
7544
7545				case 0:
7546
7547					item = ControlMenuItem.subMenu.children[ 1 ];
7548
7549					break;
7550
7551				case 1:
7552
7553					item = ControlMenuItem.subMenu.children[ 2 ];
7554
7555					break;
7556					
7557				default:
7558
7559					item = ControlMenuItem.subMenu.children[ 1 ];
7560
7561					break;	
7562
7563			}
7564
7565			ControlMenuItem.subMenu.setActiveItem( item )
7566			ControlMenuItem.setSelectionTitle( item.textContent );
7567
7568		}
7569
7570		if ( mode !== undefined ) {
7571
7572			switch( mode ) {
7573
7574				case PANOLENS.Modes.CARDBOARD:
7575
7576					item = ModeMenuItem.subMenu.children[ 2 ];
7577
7578					break;
7579
7580				case PANOLENS.Modes.STEREO:
7581
7582					item = ModeMenuItem.subMenu.children[ 3 ];
7583					
7584					break;
7585
7586				default:
7587
7588					item = ModeMenuItem.subMenu.children[ 1 ];
7589
7590					break;
7591			}
7592
7593			ModeMenuItem.subMenu.setActiveItem( item )
7594			ModeMenuItem.setSelectionTitle( item.textContent );
7595
7596		}
7597
7598	};
7599
7600	/**
7601	 * Enable rendering effect
7602	 * @param  {PANOLENS.Modes} mode - Modes for effects
7603	 */
7604	PANOLENS.Viewer.prototype.enableEffect = function ( mode ) {
7605
7606		if ( this.mode === mode ) { return; }
7607		if ( mode === PANOLENS.Modes.NORMAL ) { this.disableEffect(); return; }
7608		else { this.mode = mode; }
7609
7610		var fov = this.camera.fov;
7611
7612		switch( mode ) {
7613
7614			case PANOLENS.Modes.CARDBOARD:
7615
7616				this.effect = this.CardboardEffect;
7617				this.enableReticleControl();
7618
7619				break;
7620
7621			case PANOLENS.Modes.STEREO:
7622
7623				this.effect = this.StereoEffect;
7624				this.enableReticleControl();
7625				
7626				break;
7627
7628			default:
7629
7630				this.effect = null;
7631				this.disableReticleControl();
7632
7633				break;
7634
7635		}
7636
7637		this.activateWidgetItem( undefined, this.mode );
7638
7639		/**
7640		 * Dual eye effect event
7641		 * @type {object}
7642		 * @event PANOLENS.Viewer#panolens-dual-eye-effect
7643		 * @event PANOLENS.Infospot#panolens-dual-eye-effect
7644		 * @property {PANOLENS.Modes} mode - Current display mode
7645		 */
7646		this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: this.mode } );
7647
7648		// Force effect stereo camera to update by refreshing fov
7649		this.camera.fov = fov + 10e-3;
7650		this.effect.setSize( this.container.clientWidth, this.container.clientHeight );
7651		this.render();
7652		this.camera.fov = fov;
7653
7654	};
7655
7656	/**
7657	 * Disable additional rendering effect
7658	 */
7659	PANOLENS.Viewer.prototype.disableEffect = function () {
7660
7661		if ( this.mode === PANOLENS.Modes.NORMAL ) { return; }
7662
7663		this.mode = PANOLENS.Modes.NORMAL;
7664		this.disableReticleControl();
7665
7666		this.activateWidgetItem( undefined, this.mode );
7667
7668		/**
7669		 * Dual eye effect event
7670		 * @type {object}
7671		 * @event PANOLENS.Viewer#panolens-dual-eye-effect
7672		 * @event PANOLENS.Infospot#panolens-dual-eye-effect
7673		 * @property {PANOLENS.Modes} mode - Current display mode
7674		 */
7675		this.dispatchEventToChildren( { type: 'panolens-dual-eye-effect', mode: this.mode } );
7676
7677		this.renderer.setSize( this.container.clientWidth, this.container.clientHeight );
7678		this.render();
7679	};
7680
7681	/**
7682	 * Enable reticle control
7683	 */
7684	PANOLENS.Viewer.prototype.enableReticleControl = function () {
7685
7686		if ( this.reticle.visible ) { return; }
7687		if ( !this.reticle.textureLoaded ) { this.reticle.loadTextures(); }
7688
7689		this.tempEnableReticle = true;
7690
7691		// Register reticle event and unregister mouse event
7692		this.unregisterMouseAndTouchEvents();
7693		this.reticle.show();
7694		this.registerReticleEvent();
7695		this.updateReticleEvent();
7696
7697	};
7698
7699	/**
7700	 * Disable reticle control
7701	 */
7702	PANOLENS.Viewer.prototype.disableReticleControl = function () {
7703
7704		this.tempEnableReticle = false;
7705
7706		// Register mouse event and unregister reticle event
7707		if ( !this.options.enableReticle ) {
7708
7709			this.reticle.hide();
7710			this.unregisterReticleEvent();
7711			this.registerMouseAndTouchEvents();
7712
7713		} else {
7714
7715			this.updateReticleEvent();
7716
7717		}
7718
7719	};
7720
7721	/**
7722	 * Toggle video play or stop
7723	 * @fires PANOLENS.Viewer#video-toggle
7724	 */
7725	PANOLENS.Viewer.prototype.toggleVideoPlay = function ( pause ) {
7726
7727		if ( this.panorama instanceof PANOLENS.VideoPanorama ) {
7728
7729			/**
7730			 * Toggle video event
7731			 * @type {object}
7732			 * @event PANOLENS.Viewer#video-toggle
7733			 */
7734			this.panorama.dispatchEvent( { type: 'video-toggle', pause: pause } );
7735
7736		}
7737
7738	};
7739
7740	/**
7741	 * Set currentTime in a video
7742	 * @param {number} percentage - Percentage of a video. Range from 0.0 to 1.0
7743	 * @fires PANOLENS.Viewer#video-time
7744	 */
7745	PANOLENS.Viewer.prototype.setVideoCurrentTime = function ( percentage ) {
7746
7747		if ( this.panorama instanceof PANOLENS.VideoPanorama ) {
7748
7749			/**
7750			 * Setting video time event
7751			 * @type {object}
7752			 * @event PANOLENS.Viewer#video-time
7753			 * @property {number} percentage - Percentage of a video. Range from 0.0 to 1.0
7754			 */
7755			this.panorama.dispatchEvent( { type: 'video-time', percentage: percentage } );
7756
7757		}
7758
7759	};
7760
7761	/**
7762	 * This will be called when video updates if an widget is present
7763	 * @param {number} percentage - Percentage of a video. Range from 0.0 to 1.0
7764	 * @fires PANOLENS.Viewer#video-update
7765	 */
7766	PANOLENS.Viewer.prototype.onVideoUpdate = function ( percentage ) {
7767
7768		/**
7769		 * Video update event
7770		 * @type {object}
7771		 * @event PANOLENS.Viewer#video-update
7772		 * @property {number} percentage - Percentage of a video. Range from 0.0 to 1.0
7773		 */
7774		this.widget && this.widget.dispatchEvent( { type: 'video-update', percentage: percentage } );
7775
7776	};
7777
7778	/**
7779	 * Add update callback to be called every animation frame
7780	 */
7781	PANOLENS.Viewer.prototype.addUpdateCallback = function ( fn ) {
7782
7783		if ( fn ) {
7784
7785			this.updateCallbacks.push( fn );
7786
7787		}
7788
7789	};
7790
7791	/**
7792	 * Remove update callback
7793	 * @param  {Function} fn - The function to be removed
7794	 */
7795	PANOLENS.Viewer.prototype.removeUpdateCallback = function ( fn ) {
7796
7797		var index = this.updateCallbacks.indexOf( fn );
7798
7799		if ( fn && index >= 0 ) {
7800
7801			this.updateCallbacks.splice( index, 1 );
7802
7803		}
7804
7805	};
7806
7807	/**
7808	 * Show video widget
7809	 */
7810	PANOLENS.Viewer.prototype.showVideoWidget = function () {
7811
7812		/**
7813		 * Show video widget event
7814		 * @type {object}
7815		 * @event PANOLENS.Viewer#video-control-show
7816		 */
7817		this.widget && this.widget.dispatchEvent( { type: 'video-control-show' } );
7818
7819	};
7820
7821	/**
7822	 * Hide video widget
7823	 */
7824	PANOLENS.Viewer.prototype.hideVideoWidget = function () {
7825
7826		/**
7827		 * Hide video widget
7828		 * @type {object}
7829		 * @event PANOLENS.Viewer#video-control-hide
7830		 */
7831		this.widget && this.widget.dispatchEvent( { type: 'video-control-hide' } );
7832
7833	};
7834
7835	PANOLENS.Viewer.prototype.updateVideoPlayButton = function ( paused ) {
7836
7837		if ( this.widget && 
7838			 this.widget.videoElement && 
7839			 this.widget.videoElement.controlButton ) {
7840
7841			this.widget.videoElement.controlButton.update( paused );
7842
7843		}
7844
7845	};
7846
7847	/**
7848	 * Add default panorama event listeners
7849	 * @param {PANOLENS.Panorama} pano - The panorama to be added with event listener
7850	 */
7851	PANOLENS.Viewer.prototype.addPanoramaEventListener = function ( pano ) {
7852
7853		var scope = this;
7854
7855		// Set camera control on every panorama
7856		pano.addEventListener( 'enter-fade-start', this.setCameraControl.bind( this ) );
7857
7858		// Show and hide widget event only when it's PANOLENS.VideoPanorama
7859		if ( pano instanceof PANOLENS.VideoPanorama ) {
7860
7861			pano.addEventListener( 'enter-fade-start', this.showVideoWidget.bind( this ) );
7862			pano.addEventListener( 'leave', function () {
7863
7864				if ( !(this.panorama instanceof PANOLENS.VideoPanorama) ) {
7865
7866					this.hideVideoWidget.call( this );
7867
7868				}
7869				
7870			}.bind( this ) );
7871
7872		}
7873
7874	};
7875
7876	/**
7877	 * Set camera control
7878	 */
7879	PANOLENS.Viewer.prototype.setCameraControl = function () {
7880
7881		this.OrbitControls.target.copy( this.panorama.position );
7882
7883	};
7884
7885	/**
7886	 * Get current camera control
7887	 * @return {object} - Current navigation control. THREE.OrbitControls or THREE.DeviceOrientationControls
7888	 */
7889	PANOLENS.Viewer.prototype.getControl = function () {
7890
7891		return this.control;
7892
7893	},
7894
7895	/**
7896	 * Get scene
7897	 * @return {THREE.Scene} - Current scene which the viewer is built on
7898	 */
7899	PANOLENS.Viewer.prototype.getScene = function () {
7900
7901		return this.scene;
7902
7903	};
7904
7905	/**
7906	 * Get camera
7907	 * @return {THREE.Camera} - The scene camera
7908	 */
7909	PANOLENS.Viewer.prototype.getCamera = function () {
7910
7911		return this.camera;
7912
7913	},
7914
7915	/**
7916	 * Get renderer
7917	 * @return {THREE.WebGLRenderer} - The renderer using webgl
7918	 */
7919	PANOLENS.Viewer.prototype.getRenderer = function () {
7920
7921		return this.renderer;
7922
7923	};
7924
7925	/**
7926	 * Get container
7927	 * @return {HTMLDOMElement} - The container holds rendererd canvas
7928	 */
7929	PANOLENS.Viewer.prototype.getContainer = function () {
7930
7931		return this.container;
7932
7933	};
7934
7935	/**
7936	 * Get control name
7937	 * @return {string} - Control name. 'orbit' or 'device-orientation'
7938	 */
7939	PANOLENS.Viewer.prototype.getControlName = function () {
7940
7941		return this.control.name;
7942
7943	};
7944
7945	/**
7946	 * Get next navigation control name
7947	 * @return {string} - Next control name
7948	 */
7949	PANOLENS.Viewer.prototype.getNextControlName = function () {
7950
7951		return this.controls[ this.getNextControlIndex() ].name;
7952
7953	};
7954
7955	/**
7956	 * Get next navigation control index
7957	 * @return {number} - Next control index
7958	 */
7959	PANOLENS.Viewer.prototype.getNextControlIndex = function () {
7960
7961		var controls, control, nextIndex;
7962
7963		controls = this.controls;
7964		control = this.control;
7965		nextIndex = controls.indexOf( control ) + 1;
7966
7967		return ( nextIndex >= controls.length ) ? 0 : nextIndex;
7968
7969	};
7970
7971	/**
7972	 * Set field of view of camera
7973	 */
7974	PANOLENS.Viewer.prototype.setCameraFov = function ( fov ) {
7975
7976		this.camera.fov = fov;
7977		this.camera.updateProjectionMatrix();
7978
7979	};
7980
7981	/**
7982	 * Enable control by index
7983	 * @param  {PANOLENS.Controls} index - Index of camera control
7984	 */
7985	PANOLENS.Viewer.prototype.enableControl = function ( index ) {
7986
7987		index = ( index >= 0 && index < this.controls.length ) ? index : 0;
7988
7989		this.control.enabled = false;
7990
7991		this.control = this.controls[ index ];
7992
7993		this.control.enabled = true;
7994
7995		switch ( index ) {
7996
7997			case PANOLENS.Controls.ORBIT:
7998
7999				this.camera.position.copy( this.panorama.position );
8000				this.camera.position.z += 1;
8001
8002				break;
8003
8004			case PANOLENS.Controls.DEVICEORIENTATION:
8005
8006				this.camera.position.copy( this.panorama.position );
8007
8008				break;
8009
8010			default:
8011
8012				break;
8013		}
8014
8015		this.control.update();
8016
8017		this.activateWidgetItem( index, undefined );
8018
8019	};
8020
8021	/**
8022	 * Disable current control
8023	 */
8024	PANOLENS.Viewer.prototype.disableControl = function () {
8025
8026		this.control.enabled = false;
8027
8028	};
8029
8030	/**
8031	 * Toggle next control
8032	 */
8033	PANOLENS.Viewer.prototype.toggleNextControl = function () {
8034
8035		this.enableControl( this.getNextControlIndex() );
8036
8037	};
8038
8039	/**
8040	 * Screen Space Projection
8041	 */
8042	PANOLENS.Viewer.prototype.getScreenVector = function ( worldVector ) {
8043
8044		var vector = worldVector.clone();
8045		var widthHalf = ( this.container.clientWidth ) / 2;
8046		var heightHalf = this.container.clientHeight / 2;
8047
8048		vector.project( this.camera );
8049
8050		vector.x = ( vector.x * widthHalf ) + widthHalf;
8051		vector.y = - ( vector.y * heightHalf ) + heightHalf;
8052		vector.z = 0;
8053
8054		return vector;
8055
8056	};
8057
8058	/**
8059	 * Check Sprite in Viewport
8060	 */
8061	PANOLENS.Viewer.prototype.checkSpriteInViewport = function ( sprite ) {
8062
8063		this.camera.matrixWorldInverse.getInverse( this.camera.matrixWorld );
8064		this.cameraViewProjectionMatrix.multiplyMatrices( this.camera.projectionMatrix, this.camera.matrixWorldInverse );
8065		this.cameraFrustum.setFromMatrix( this.cameraViewProjectionMatrix );
8066
8067		return sprite.visible && this.cameraFrustum.intersectsSprite( sprite );
8068
8069	};
8070
8071	/**
8072	 * Reverse dragging direction
8073	 */
8074	PANOLENS.Viewer.prototype.reverseDraggingDirection = function () {
8075
8076		this.OrbitControls.rotateSpeed *= -1;
8077		this.OrbitControls.momentumScalingFactor *= -1;
8078
8079	};
8080
8081	/**
8082	 * Add reticle 
8083	 */
8084	PANOLENS.Viewer.prototype.addReticle = function () {
8085
8086		this.reticle = new PANOLENS.Reticle( 0xffffff, 
8087			this.options.autoReticleSelect,
8088			PANOLENS.DataImage.ReticleIdle, 
8089			PANOLENS.DataImage.ReticleDwell, 
8090			this.options.dwellTime,
8091			45 );
8092		this.reticle.position.z = -10;
8093		this.camera.add( this.reticle );
8094		this.scene.add( this.camera );
8095
8096	};
8097
8098	/**
8099	 * Tween control looking center
8100	 * @param {THREE.Vector3} vector - Vector to be looked at the center
8101	 * @param {number} [duration=1000] - Duration to tween
8102	 * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function
8103	 */
8104	PANOLENS.Viewer.prototype.tweenControlCenter = function ( vector, duration, easing ) {
8105
8106		if ( this.control !== this.OrbitControls ) {
8107
8108			return;
8109
8110		}
8111
8112		// Pass in arguments as array
8113		if ( vector instanceof Array ) {
8114
8115			duration = vector[ 1 ];
8116			easing = vector[ 2 ];
8117			vector = vector[ 0 ];
8118
8119		}
8120
8121		duration = duration !== undefined ? duration : 1000;
8122		easing = easing || TWEEN.Easing.Exponential.Out;
8123
8124		var scope, ha, va, chv, cvv, hv, vv, vptc, ov, nv;
8125
8126		scope = this;
8127
8128		chv = this.camera.getWorldDirection();
8129		cvv = chv.clone();
8130
8131		vptc = this.panorama.getWorldPosition().sub( this.camera.getWorldPosition() );
8132
8133		hv = vector.clone();
8134		// Scale effect
8135		hv.x *= -1;
8136		hv.add( vptc ).normalize();
8137		vv = hv.clone();
8138
8139		chv.y = 0;
8140		hv.y = 0;
8141
8142		ha = Math.atan2( hv.z, hv.x ) - Math.atan2( chv.z, chv.x );
8143		ha = ha > Math.PI ? ha - 2 * Math.PI : ha;
8144		ha = ha < -Math.PI ? ha + 2 * Math.PI : ha;
8145		va = Math.abs( cvv.angleTo( chv ) + ( cvv.y * vv.y <= 0 ? vv.angleTo( hv ) : -vv.angleTo( hv ) ) );
8146		va *= vv.y < cvv.y ? 1 : -1;
8147
8148		ov = { left: 0, up: 0 };
8149		nv = { left: 0, up: 0 };
8150
8151		this.tweenLeftAnimation.stop();
8152		this.tweenUpAnimation.stop();
8153
8154		this.tweenLeftAnimation = new TWEEN.Tween( ov )
8155			.to( { left: ha }, duration )
8156			.easing( easing )
8157			.onUpdate(function(){
8158				scope.control.rotateLeft( this.left - nv.left );
8159				nv.left = this.left;
8160			})
8161			.start();
8162
8163		this.tweenUpAnimation = new TWEEN.Tween( ov )
8164			.to( { up: va }, duration )
8165			.easing( easing )
8166			.onUpdate(function(){
8167				scope.control.rotateUp( this.up - nv.up );
8168				nv.up = this.up;
8169			})
8170			.start();
8171
8172	};
8173
8174	/**
8175	 * Tween control looking center by object
8176	 * @param {THREE.Object3D} object - Object to be looked at the center
8177	 * @param {number} [duration=1000] - Duration to tween
8178	 * @param {function} [easing=TWEEN.Easing.Exponential.Out] - Easing function
8179	 */
8180	PANOLENS.Viewer.prototype.tweenControlCenterByObject = function ( object, duration, easing ) {
8181
8182		var isUnderScalePlaceHolder = false;
8183
8184		object.traverseAncestors( function ( ancestor ) {
8185
8186			if ( ancestor.scalePlaceHolder ) {
8187
8188				isUnderScalePlaceHolder = true;
8189
8190			}
8191		} );
8192
8193		if ( isUnderScalePlaceHolder ) {
8194
8195			var invertXVector = new THREE.Vector3( -1, 1, 1 );
8196
8197			this.tweenControlCenter( object.getWorldPosition().multiply( invertXVector ), duration, easing );
8198
8199		} else {
8200
8201			this.tweenControlCenter( object.getWorldPosition(), duration, easing );
8202
8203		}
8204
8205	};
8206
8207	/**
8208	 * This is called when window size is changed
8209	 * @fires PANOLENS.Viewer#window-resize
8210	 * @param {number} [windowWidth] - Specify if custom element has changed width
8211	 * @param {number} [windowHeight] - Specify if custom element has changed height
8212	 */
8213	PANOLENS.Viewer.prototype.onWindowResize = function ( windowWidth, windowHeight ) {
8214
8215		var width, height, expand;
8216
8217		expand = this.container.classList.contains( 'panolens-container' ) || this.container.isFullscreen;
8218
8219		if ( windowWidth !== undefined && windowHeight !== undefined ) {
8220
8221			width = windowWidth;
8222			height = windowHeight;
8223			this.container._width = windowWidth;
8224			this.container._height = windowHeight;
8225
8226		} else {
8227
8228			width = expand ? Math.max(document.documentElement.clientWidth, window.innerWidth || 0) : this.container.clientWidth;
8229			height = expand ? Math.max(document.documentElement.clientHeight, window.innerHeight || 0) : this.container.clientHeight;
8230			this.container._width = width;
8231			this.container._height = height;
8232
8233		}
8234
8235		this.camera.aspect = width / height;
8236		this.camera.updateProjectionMatrix();
8237
8238		this.renderer.setSize( width, height );
8239
8240		// Update reticle
8241		if ( this.options.enableReticle || this.tempEnableReticle ) {
8242
8243			this.updateReticleEvent();
8244
8245		}
8246
8247		/**
8248		 * Window resizing event
8249		 * @type {object}
8250		 * @event PANOLENS.Viewer#window-resize
8251		 * @property {number} width  - Width of the window
8252		 * @property {number} height - Height of the window
8253		 */
8254		this.dispatchEvent( { type: 'window-resize', width: width, height: height });
8255		this.scene.traverse( function ( object ) {
8256
8257			if ( object.dispatchEvent ) {
8258
8259				object.dispatchEvent( { type: 'window-resize', width: width, height: height });
8260
8261			}
8262
8263		} );
8264
8265	};
8266
8267	PANOLENS.Viewer.prototype.addOutputElement = function () {
8268
8269		var element = document.createElement( 'div' );
8270		element.style.position = 'absolute';
8271		element.style.right = '10px';
8272		element.style.top = '10px';
8273		element.style.color = "#fff";
8274		this.container.appendChild( element );
8275		this.outputDivElement = element;
8276
8277	};
8278
8279	/**
8280	 * Output infospot attach position in developer console by holding down Ctrl button
8281	 */
8282	PANOLENS.Viewer.prototype.outputInfospotPosition = function () {
8283
8284		var intersects, point, panoramaWorldPosition, outputPosition;
8285
8286		intersects = this.raycaster.intersectObject( this.panorama, true );
8287
8288		if ( intersects.length > 0 ) {
8289
8290			point = intersects[0].point;
8291			panoramaWorldPosition = this.panorama.getWorldPosition();
8292
8293			// Panorama is scaled -1 on X axis
8294			outputPosition = new THREE.Vector3(
8295				-(point.x - panoramaWorldPosition.x).toFixed(2),
8296				(point.y - panoramaWorldPosition.y).toFixed(2),
8297				(point.z - panoramaWorldPosition.z).toFixed(2)
8298			);
8299
8300			switch ( this.options.output ) {
8301
8302				case 'console':
8303					console.info( outputPosition.x + ', ' + outputPosition.y + ', ' + outputPosition.z );
8304					break;
8305
8306				case 'overlay':
8307					this.outputDivElement.textContent = outputPosition.x + ', ' + outputPosition.y + ', ' + outputPosition.z;
8308					break;
8309
8310				default:
8311					break;
8312
8313			}
8314
8315		}
8316
8317	};
8318
8319	PANOLENS.Viewer.prototype.onMouseDown = function ( event ) {
8320
8321		event.preventDefault();
8322
8323		this.userMouse.x = ( event.clientX >= 0 ) ? event.clientX : event.touches[0].clientX;
8324		this.userMouse.y = ( event.clientY >= 0 ) ? event.clientY : event.touches[0].clientY;
8325		this.userMouse.type = 'mousedown';
8326		this.onTap( event );
8327
8328	};
8329
8330	PANOLENS.Viewer.prototype.onMouseMove = function ( event ) {
8331
8332		event.preventDefault();
8333		this.userMouse.type = 'mousemove';
8334		this.onTap( event );
8335
8336	};
8337
8338	PANOLENS.Viewer.prototype.onMouseUp = function ( event ) {
8339
8340		var onTarget = false, type;
8341
8342		this.userMouse.type = 'mouseup';
8343
8344		type = ( this.userMouse.x >= event.clientX - this.options.clickTolerance 
8345				&& this.userMouse.x <= event.clientX + this.options.clickTolerance
8346				&& this.userMouse.y >= event.clientY - this.options.clickTolerance
8347				&& this.userMouse.y <= event.clientY + this.options.clickTolerance ) 
8348				||  ( event.changedTouches 
8349				&& this.userMouse.x >= event.changedTouches[0].clientX - this.options.clickTolerance
8350				&& this.userMouse.x <= event.changedTouches[0].clientX + this.options.clickTolerance 
8351				&& this.userMouse.y >= event.changedTouches[0].clientY - this.options.clickTolerance
8352				&& this.userMouse.y <= event.changedTouches[0].clientY + this.options.clickTolerance ) 
8353		? 'click' : undefined;
8354
8355		// Event should happen on canvas
8356		if ( event && event.target && !event.target.classList.contains( 'panolens-canvas' ) ) { return; }
8357
8358		event.preventDefault();
8359
8360		if ( event.changedTouches && event.changedTouches.length === 1 ) {
8361
8362			onTarget = this.onTap( { clientX : event.changedTouches[0].clientX, clientY : event.changedTouches[0].clientY }, type );
8363		
8364		} else {
8365
8366			onTarget = this.onTap( event, type );
8367
8368		}
8369
8370		this.userMouse.type = 'none';
8371
8372		if ( onTarget ) { 
8373
8374			return; 
8375
8376		}
8377
8378		if ( type === 'click' ) {
8379
8380			this.options.autoHideInfospot && this.panorama && this.panorama.toggleInfospotVisibility();
8381			this.options.autoHideControlBar && this.toggleControlBar();
8382
8383		}
8384
8385	};
8386
8387	PANOLENS.Viewer.prototype.onTap = function ( event, type ) {
8388
8389		var intersects, intersect_entity, intersect;
8390
8391		this.raycasterPoint.x = ( ( event.clientX - this.container.offsetLeft ) / this.container.clientWidth ) * 2 - 1;
8392		this.raycasterPoint.y = - ( ( event.clientY - this.container.offsetTop ) / this.container.clientHeight ) * 2 + 1;
8393
8394		this.raycaster.setFromCamera( this.raycasterPoint, this.camera );
8395
8396		// Return if no panorama 
8397		if ( !this.panorama ) { 
8398
8399			return; 
8400
8401		}
8402
8403		// output infospot information
8404		if ( event.type !== 'mousedown' && PANOLENS.Utils.checkTouchSupported() || this.OUTPUT_INFOSPOT ) { 
8405
8406			this.outputInfospotPosition(); 
8407
8408		}
8409
8410		intersects = this.raycaster.intersectObjects( this.panorama.children, true );
8411
8412		intersect_entity = this.getConvertedIntersect( intersects );
8413
8414		intersect = ( intersects.length > 0 ) ? intersects[0].object : intersect;
8415
8416		if ( this.userMouse.type === 'mouseup'  ) {
8417
8418			if ( intersect_entity && this.pressEntityObject === intersect_entity && this.pressEntityObject.dispatchEvent ) {
8419
8420				this.pressEntityObject.dispatchEvent( { type: 'pressstop-entity', mouseEvent: event } );
8421
8422			}
8423
8424			this.pressEntityObject = undefined;
8425
8426		}
8427
8428		if ( this.userMouse.type === 'mouseup'  ) {
8429
8430			if ( intersect && this.pressObject === intersect && this.pressObject.dispatchEvent ) {
8431
8432				this.pressObject.dispatchEvent( { type: 'pressstop', mouseEvent: event } );
8433
8434			}
8435
8436			this.pressObject = undefined;
8437
8438		}
8439
8440		if ( type === 'click' ) {
8441
8442			this.panorama.dispatchEvent( { type: 'click', intersects: intersects, mouseEvent: event } );
8443
8444			if ( intersect_entity && intersect_entity.dispatchEvent ) {
8445
8446				intersect_entity.dispatchEvent( { type: 'click-entity', mouseEvent: event } );
8447
8448			}
8449
8450			if ( intersect && intersect.dispatchEvent ) {
8451
8452				intersect.dispatchEvent( { type: 'click', mouseEvent: event } );
8453
8454			}
8455
8456		} else {
8457
8458			this.panorama.dispatchEvent( { type: 'hover', intersects: intersects, mouseEvent: event } );
8459
8460			if ( ( this.hoverObject && intersects.length > 0 && this.hoverObject !== intersect_entity )
8461				|| ( this.hoverObject && intersects.length === 0 ) ){
8462
8463				if ( this.hoverObject.dispatchEvent ) {
8464
8465					this.hoverObject.dispatchEvent( { type: 'hoverleave', mouseEvent: event } );
8466
8467					// Cancel dwelling
8468					this.reticle.cancelDwelling();
8469
8470				}
8471
8472				this.hoverObject = undefined;
8473
8474			}
8475
8476			if ( intersect_entity && intersects.length > 0 ) {
8477
8478				if ( this.hoverObject !== intersect_entity ) {
8479
8480					this.hoverObject = intersect_entity;
8481
8482					if ( this.hoverObject.dispatchEvent ) {
8483
8484						this.hoverObject.dispatchEvent( { type: 'hoverenter', mouseEvent: event } );
8485
8486						// Start reticle timer
8487						if ( this.options.autoReticleSelect && this.options.enableReticle || this.tempEnableReticle ) {
8488							this.reticle.startDwelling( this.onTap.bind( this, event, 'click' ) );
8489						}
8490
8491					}
8492
8493				}
8494
8495				if ( this.userMouse.type === 'mousedown' && this.pressEntityObject != intersect_entity ) {
8496
8497					this.pressEntityObject = intersect_entity;
8498
8499					if ( this.pressEntityObject.dispatchEvent ) {
8500
8501						this.pressEntityObject.dispatchEvent( { type: 'pressstart-entity', mouseEvent: event } );
8502
8503					}
8504
8505				}
8506
8507				if ( this.userMouse.type === 'mousedown' && this.pressObject != intersect ) {
8508
8509					this.pressObject = intersect;
8510
8511					if ( this.pressObject.dispatchEvent ) {
8512
8513						this.pressObject.dispatchEvent( { type: 'pressstart', mouseEvent: event } );
8514
8515					}
8516
8517				}
8518
8519				if ( this.userMouse.type === 'mousemove' || this.options.enableReticle ) {
8520
8521					if ( intersect && intersect.dispatchEvent ) {
8522
8523						intersect.dispatchEvent( { type: 'hover', mouseEvent: event } );
8524
8525					}
8526
8527					if ( this.pressEntityObject && this.pressEntityObject.dispatchEvent ) {
8528
8529						this.pressEntityObject.dispatchEvent( { type: 'pressmove-entity', mouseEvent: event } );
8530
8531					}
8532
8533					if ( this.pressObject && this.pressObject.dispatchEvent ) {
8534
8535						this.pressObject.dispatchEvent( { type: 'pressmove', mouseEvent: event } );
8536
8537					}
8538
8539				}
8540
8541			}
8542
8543			if ( !intersect_entity && this.pressEntityObject && this.pressEntityObject.dispatchEvent ) {
8544
8545				this.pressEntityObject.dispatchEvent( { type: 'pressstop-entity', mouseEvent: event } );
8546
8547				this.pressEntityObject = undefined;
8548
8549			}
8550
8551			if ( !intersect && this.pressObject && this.pressObject.dispatchEvent ) {
8552
8553				this.pressObject.dispatchEvent( { type: 'pressstop', mouseEvent: event } );
8554
8555				this.pressObject = undefined;
8556
8557			}
8558
8559		}
8560
8561		// Infospot handler
8562		if ( intersect && intersect instanceof PANOLENS.Infospot ) {
8563
8564			this.infospot = intersect;
8565			
8566			if ( type === 'click' ) {
8567
8568				return true;
8569
8570			}
8571			
8572
8573		} else if ( this.infospot ) {
8574
8575			this.hideInfospot();
8576
8577		}
8578
8579	};
8580
8581	PANOLENS.Viewer.prototype.getConvertedIntersect = function ( intersects ) {
8582
8583		var intersect;
8584
8585		for ( var i = 0; i < intersects.length; i++ ) {
8586
8587			if ( intersects[i].distance >= 0 && intersects[i].object && !intersects[i].object.passThrough ) {
8588
8589				if ( intersects[i].object.entity && intersects[i].object.entity.passThrough ) {
8590					continue;
8591				} else if ( intersects[i].object.entity && !intersects[i].object.entity.passThrough ) {
8592					intersect = intersects[i].object.entity;
8593					break;
8594				} else {
8595					intersect = intersects[i].object;
8596					break;
8597				}
8598
8599			}
8600
8601		}
8602
8603		return intersect;
8604
8605	};
8606
8607	PANOLENS.Viewer.prototype.hideInfospot = function ( intersects ) {
8608
8609		if ( this.infospot ) {
8610
8611			this.infospot.onHoverEnd();
8612
8613			this.infospot = undefined;
8614
8615		}
8616
8617	};
8618
8619	/**
8620	 * Toggle control bar
8621	 * @fires [PANOLENS.Viewer#control-bar-toggle]
8622	 */
8623	PANOLENS.Viewer.prototype.toggleControlBar = function () {
8624
8625		/**
8626		 * Toggle control bar event
8627		 * @type {object}
8628		 * @event PANOLENS.Viewer#control-bar-toggle
8629		 */
8630		this.widget && this.widget.dispatchEvent( { type: 'control-bar-toggle' } );
8631
8632	};
8633
8634	PANOLENS.Viewer.prototype.onKeyDown = function ( event ) {
8635
8636		if ( this.options.output && this.options.output !== 'none' && event.key === 'Control' ) {
8637
8638			this.OUTPUT_INFOSPOT = true;
8639
8640		}
8641
8642	};
8643
8644	PANOLENS.Viewer.prototype.onKeyUp = function ( event ) {
8645
8646		this.OUTPUT_INFOSPOT = false;
8647
8648	};
8649
8650	/**
8651	 * Update control and callbacks
8652	 */
8653	PANOLENS.Viewer.prototype.update = function () {
8654
8655		TWEEN.update();
8656
8657		this.updateCallbacks.forEach( function( callback ){ callback(); } );
8658
8659		this.control.update();
8660
8661		this.scene.traverse( function( child ){
8662			if ( child instanceof PANOLENS.Infospot 
8663				&& child.element 
8664				&& ( this.hoverObject === child 
8665					|| child.element.style.display !== 'none' 
8666					|| (child.element.left && child.element.left.style.display !== 'none')
8667					|| (child.element.right && child.element.right.style.display !== 'none') ) ) {
8668				if ( this.checkSpriteInViewport( child ) ) {
8669					var vector = this.getScreenVector( child.getWorldPosition() );
8670					child.translateElement( vector.x, vector.y );
8671				} else {
8672					child.onDismiss();
8673				}
8674				
8675			}
8676		}.bind(this) );
8677
8678	};
8679
8680	/**
8681	 * Rendering function to be called on every animation frame
8682	 */
8683	PANOLENS.Viewer.prototype.render = function () {
8684
8685		if ( this.mode === PANOLENS.Modes.CARDBOARD || this.mode === PANOLENS.Modes.STEREO ) {
8686
8687			this.effect.render( this.scene, this.camera );
8688
8689		} else {
8690
8691			this.renderer.render( this.scene, this.camera );
8692
8693		}
8694
8695	};
8696
8697	PANOLENS.Viewer.prototype.animate = function () {
8698
8699		this.requestAnimationId = window.requestAnimationFrame( this.animate.bind( this ) );
8700
8701		this.onChange();
8702
8703	};
8704
8705	PANOLENS.Viewer.prototype.onChange = function () {
8706
8707		this.update();
8708		this.render();
8709
8710	};
8711
8712	/**
8713	 * Register mouse and touch event on container
8714	 */
8715	PANOLENS.Viewer.prototype.registerMouseAndTouchEvents = function () {
8716
8717		this.container.addEventListener( 'mousedown' , 	this.HANDLER_MOUSE_DOWN, false );
8718		this.container.addEventListener( 'mousemove' , 	this.HANDLER_MOUSE_MOVE, false );
8719		this.container.addEventListener( 'mouseup'	 , 	this.HANDLER_MOUSE_UP  , false );
8720		this.container.addEventListener( 'touchstart', 	this.HANDLER_MOUSE_DOWN, false );
8721		this.container.addEventListener( 'touchend'  , 	this.HANDLER_MOUSE_UP  , false );
8722
8723	};
8724
8725	/**
8726	 * Unregister mouse and touch event on container
8727	 */
8728	PANOLENS.Viewer.prototype.unregisterMouseAndTouchEvents = function () {
8729
8730		this.container.removeEventListener( 'mousedown' ,  this.HANDLER_MOUSE_DOWN, false );
8731		this.container.removeEventListener( 'mousemove' ,  this.HANDLER_MOUSE_MOVE, false );
8732		this.container.removeEventListener( 'mouseup'	,  this.HANDLER_MOUSE_UP  , false );
8733		this.container.removeEventListener( 'touchstart',  this.HANDLER_MOUSE_DOWN, false );
8734		this.container.removeEventListener( 'touchend'  ,  this.HANDLER_MOUSE_UP  , false );
8735	};
8736
8737	/**
8738	 * Register reticle event
8739	 */
8740	PANOLENS.Viewer.prototype.registerReticleEvent = function () {
8741
8742		this.addUpdateCallback( this.HANDLER_TAP );
8743
8744	};
8745
8746	/**
8747	 * Unregister reticle event
8748	 */
8749	PANOLENS.Viewer.prototype.unregisterReticleEvent = function () {
8750
8751		this.removeUpdateCallback( this.HANDLER_TAP );
8752
8753	};
8754
8755	/**
8756	 * Update reticle event
8757	 */
8758	PANOLENS.Viewer.prototype.updateReticleEvent = function () {
8759
8760		var centerX, centerY;
8761
8762		centerX = this.container.clientWidth / 2 + this.container.offsetLeft;
8763		centerY = this.container.clientHeight / 2;
8764
8765		this.removeUpdateCallback( this.HANDLER_TAP );
8766		this.HANDLER_TAP = this.onTap.bind( this, { clientX: centerX, clientY: centerY } );
8767		this.addUpdateCallback( this.HANDLER_TAP );
8768
8769	};
8770
8771	/**
8772	 * Register container and window listeners
8773	 */
8774	PANOLENS.Viewer.prototype.registerEventListeners = function () {
8775
8776		// Resize Event
8777		window.addEventListener( 'resize' , this.HANDLER_WINDOW_RESIZE, true );
8778
8779		// Keyboard Event
8780		window.addEventListener( 'keydown', this.HANDLER_KEY_DOWN, true );
8781		window.addEventListener( 'keyup'  , this.HANDLER_KEY_UP	 , true );
8782
8783	};
8784
8785	/**
8786	 * Unregister container and window listeners
8787	 */
8788	PANOLENS.Viewer.prototype.unregisterEventListeners = function () {
8789
8790		// Resize Event
8791		window.removeEventListener( 'resize' , this.HANDLER_WINDOW_RESIZE, true );
8792
8793		// Keyboard Event
8794		window.removeEventListener( 'keydown', this.HANDLER_KEY_DOWN, true );
8795		window.removeEventListener( 'keyup'  , this.HANDLER_KEY_UP  , true );
8796
8797	};
8798
8799	/**
8800	 * Dispose all scene objects and clear cache
8801	 */
8802	PANOLENS.Viewer.prototype.dispose = function () {
8803
8804		// Unregister dom event listeners
8805		this.unregisterEventListeners();
8806
8807		// recursive disposal on 3d objects
8808		function recursiveDispose ( object ) {
8809
8810			for ( var i = object.children.length - 1; i >= 0; i-- ) {
8811
8812				recursiveDispose( object.children[i] );
8813				object.remove( object.children[i] );
8814
8815			}
8816
8817			if ( object instanceof PANOLENS.Infospot ) {
8818
8819				object.dispose();
8820
8821			}
8822
8823			object.geometry && object.geometry.dispose();
8824			object.material && object.material.dispose();
8825		}
8826
8827		recursiveDispose( this.scene );
8828
8829		// dispose widget
8830		if ( this.widget ) {
8831
8832			this.widget.dispose();
8833			this.widget = null;
8834
8835		}
8836
8837		// clear cache
8838		if ( THREE.Cache && THREE.Cache.enabled ) {
8839
8840			THREE.Cache.clear();
8841
8842		}
8843
8844	};
8845
8846	/**
8847	 * Destory viewer by disposing and stopping requestAnimationFrame
8848	 */
8849	PANOLENS.Viewer.prototype.destory = function () {
8850
8851		this.dispose();
8852		this.render();
8853		window.cancelAnimationFrame( this.requestAnimationId );		
8854
8855	};
8856
8857	/**
8858	 * On panorama dispose
8859	 */
8860	PANOLENS.Viewer.prototype.onPanoramaDispose = function ( panorama ) {
8861
8862		if ( panorama instanceof PANOLENS.VideoPanorama ) {
8863
8864			this.hideVideoWidget();
8865
8866		}
8867
8868		if ( panorama === this.panorama ) {
8869
8870			this.panorama = null;
8871
8872		}
8873
8874	};
8875
8876	/**
8877	 * Load ajax call
8878	 * @param {string} url - URL to be requested
8879	 * @param {function} [callback] - Callback after request completes
8880	 */
8881	PANOLENS.Viewer.prototype.loadAsyncRequest = function ( url, callback ) {
8882
8883		var request = new XMLHttpRequest();
8884		request.onloadend = function ( event ) {
8885			callback && callback( event );
8886		};
8887		request.open( "GET", url, true );
8888		request.send( null );
8889
8890	};
8891
8892	/**
8893	 * View indicator in upper left
8894	 * */
8895	PANOLENS.Viewer.prototype.addViewIndicator = function () {
8896
8897		var scope = this;
8898
8899		function loadViewIndicator ( asyncEvent ) {
8900
8901			if ( asyncEvent.loaded === 0 ) return;
8902
8903			var viewIndicatorDiv = asyncEvent.target.responseXML.documentElement;
8904			viewIndicatorDiv.style.width = scope.viewIndicatorSize + "px";
8905			viewIndicatorDiv.style.height = scope.viewIndicatorSize + "px";
8906			viewIndicatorDiv.style.position = "absolute";
8907			viewIndicatorDiv.style.top = "10px";
8908			viewIndicatorDiv.style.left = "10px";
8909			viewIndicatorDiv.style.opacity = "0.5";
8910			viewIndicatorDiv.style.cursor = "pointer";
8911			viewIndicatorDiv.id = "panolens-view-indicator-container";
8912
8913			scope.container.appendChild( viewIndicatorDiv );
8914
8915			var indicator = viewIndicatorDiv.querySelector( "#indicator" );
8916			var setIndicatorD = function () {
8917
8918				scope.radius = scope.viewIndicatorSize * 0.225;
8919				scope.currentPanoAngle = scope.camera.rotation.y - THREE.Math.degToRad( 90 );
8920				scope.fovAngle = THREE.Math.degToRad( scope.camera.fov ) ;
8921				scope.leftAngle = -scope.currentPanoAngle - scope.fovAngle / 2;
8922				scope.rightAngle = -scope.currentPanoAngle + scope.fovAngle / 2;
8923				scope.leftX = scope.radius * Math.cos( scope.leftAngle );
8924				scope.leftY = scope.radius * Math.sin( scope.leftAngle );
8925				scope.rightX = scope.radius * Math.cos( scope.rightAngle );
8926				scope.rightY = scope.radius * Math.sin( scope.rightAngle );
8927				scope.indicatorD = "M " + scope.leftX + " " + scope.leftY + " A " + scope.radius + " " + scope.radius + " 0 0 1 " + scope.rightX + " " + scope.rightY;
8928
8929				if ( scope.leftX && scope.leftY && scope.rightX && scope.rightY && scope.radius ) {
8930
8931					indicator.setAttribute( "d", scope.indicatorD );
8932
8933				}
8934
8935			};
8936
8937			scope.addUpdateCallback( setIndicatorD );
8938
8939			var indicatorOnMouseEnter = function () {
8940
8941				this.style.opacity = "1";
8942
8943			};
8944
8945			var indicatorOnMouseLeave = function () {
8946
8947				this.style.opacity = "0.5";
8948
8949			};
8950
8951			viewIndicatorDiv.addEventListener( "mouseenter", indicatorOnMouseEnter );
8952			viewIndicatorDiv.addEventListener( "mouseleave", indicatorOnMouseLeave );
8953		}
8954
8955		this.loadAsyncRequest( PANOLENS.DataImage.ViewIndicator, loadViewIndicator );
8956
8957	};
8958
8959	/**
8960	 * Append custom control item to existing control bar
8961	 * @param {object} [option={}] - Style object to overwirte default element style. It takes 'style', 'onTap' and 'group' properties.
8962	 */
8963	PANOLENS.Viewer.prototype.appendControlItem = function ( option ) {
8964
8965		var item = this.widget.createCustomItem( option );		
8966
8967		if ( option.group === 'video' ) {
8968
8969			this.widget.videoElement.appendChild( item );
8970
8971		} else {
8972
8973			this.widget.barElement.appendChild( item );
8974
8975		}
8976
8977		return item;
8978
8979	};
8980
8981} )();
8982;(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
8983	var createLayout = require('layout-bmfont-text')
8984	var inherits = require('inherits')
8985	var createIndices = require('quad-indices')
8986	var buffer = require('three-buffer-vertex-data')
8987	var assign = require('object-assign')
8988
8989	var vertices = require('./lib/vertices')
8990	var utils = require('./lib/utils')
8991
8992	var Base = THREE.BufferGeometry
8993
8994	module.exports = function createTextGeometry (opt) {
8995	  return new TextGeometry(opt)
8996	}
8997
8998	function TextGeometry (opt) {
8999	  Base.call(this)
9000
9001	  if (typeof opt === 'string') {
9002	    opt = { text: opt }
9003	  }
9004
9005	  // use these as default values for any subsequent
9006	  // calls to update()
9007	  this._opt = assign({}, opt)
9008
9009	  // also do an initial setup...
9010	  if (opt) this.update(opt)
9011	}
9012
9013	inherits(TextGeometry, Base)
9014
9015	TextGeometry.prototype.update = function (opt) {
9016	  if (typeof opt === 'string') {
9017	    opt = { text: opt }
9018	  }
9019
9020	  // use constructor defaults
9021	  opt = assign({}, this._opt, opt)
9022
9023	  if (!opt.font) {
9024	    throw new TypeError('must specify a { font } in options')
9025	  }
9026
9027	  this.layout = createLayout(opt)
9028
9029	  // get vec2 texcoords
9030	  var flipY = opt.flipY !== false
9031
9032	  // the desired BMFont data
9033	  var font = opt.font
9034
9035	  // determine texture size from font file
9036	  var texWidth = font.common.scaleW
9037	  var texHeight = font.common.scaleH
9038
9039	  // get visible glyphs
9040	  var glyphs = this.layout.glyphs.filter(function (glyph) {
9041	    var bitmap = glyph.data
9042	    return bitmap.width * bitmap.height > 0
9043	  })
9044
9045	  // provide visible glyphs for convenience
9046	  this.visibleGlyphs = glyphs
9047
9048	  // get common vertex data
9049	  var positions = vertices.positions(glyphs)
9050	  var uvs = vertices.uvs(glyphs, texWidth, texHeight, flipY)
9051	  var indices = createIndices({
9052	    clockwise: true,
9053	    type: 'uint16',
9054	    count: glyphs.length
9055	  })
9056
9057	  // update vertex data
9058	  buffer.index(this, indices, 1, 'uint16')
9059	  buffer.attr(this, 'position', positions, 2)
9060	  buffer.attr(this, 'uv', uvs, 2)
9061
9062	  // update multipage data
9063	  if (!opt.multipage && 'page' in this.attributes) {
9064	    // disable multipage rendering
9065	    this.removeAttribute('page')
9066	  } else if (opt.multipage) {
9067	    var pages = vertices.pages(glyphs)
9068	    // enable multipage rendering
9069	    buffer.attr(this, 'page', pages, 1)
9070	  }
9071	}
9072
9073	TextGeometry.prototype.computeBoundingSphere = function () {
9074	  if (this.boundingSphere === null) {
9075	    this.boundingSphere = new THREE.Sphere()
9076	  }
9077
9078	  var positions = this.attributes.position.array
9079	  var itemSize = this.attributes.position.itemSize
9080	  if (!positions || !itemSize || positions.length < 2) {
9081	    this.boundingSphere.radius = 0
9082	    this.boundingSphere.center.set(0, 0, 0)
9083	    return
9084	  }
9085	  utils.computeSphere(positions, this.boundingSphere)
9086	  if (isNaN(this.boundingSphere.radius)) {
9087	    console.error('THREE.BufferGeometry.computeBoundingSphere(): ' +
9088	      'Computed radius is NaN. The ' +
9089	      '"position" attribute is likely to have NaN values.')
9090	  }
9091	}
9092
9093	TextGeometry.prototype.computeBoundingBox = function () {
9094	  if (this.boundingBox === null) {
9095	    this.boundingBox = new THREE.Box3()
9096	  }
9097
9098	  var bbox = this.boundingBox
9099	  var positions = this.attributes.position.array
9100	  var itemSize = this.attributes.position.itemSize
9101	  if (!positions || !itemSize || positions.length < 2) {
9102	    bbox.makeEmpty()
9103	    return
9104	  }
9105	  utils.computeBox(positions, bbox)
9106	}
9107
9108	},{"./lib/utils":2,"./lib/vertices":3,"inherits":4,"layout-bmfont-text":5,"object-assign":26,"quad-indices":27,"three-buffer-vertex-data":31}],2:[function(require,module,exports){
9109	var itemSize = 2
9110	var box = { min: [0, 0], max: [0, 0] }
9111
9112	function bounds (positions) {
9113	  var count = positions.length / itemSize
9114	  box.min[0] = positions[0]
9115	  box.min[1] = positions[1]
9116	  box.max[0] = positions[0]
9117	  box.max[1] = positions[1]
9118
9119	  for (var i = 0; i < count; i++) {
9120	    var x = positions[i * itemSize + 0]
9121	    var y = positions[i * itemSize + 1]
9122	    box.min[0] = Math.min(x, box.min[0])
9123	    box.min[1] = Math.min(y, box.min[1])
9124	    box.max[0] = Math.max(x, box.max[0])
9125	    box.max[1] = Math.max(y, box.max[1])
9126	  }
9127	}
9128
9129	module.exports.computeBox = function (positions, output) {
9130	  bounds(positions)
9131	  output.min.set(box.min[0], box.min[1], 0)
9132	  output.max.set(box.max[0], box.max[1], 0)
9133	}
9134
9135	module.exports.computeSphere = function (positions, output) {
9136	  bounds(positions)
9137	  var minX = box.min[0]
9138	  var minY = box.min[1]
9139	  var maxX = box.max[0]
9140	  var maxY = box.max[1]
9141	  var width = maxX - minX
9142	  var height = maxY - minY
9143	  var length = Math.sqrt(width * width + height * height)
9144	  output.center.set(minX + width / 2, minY + height / 2, 0)
9145	  output.radius = length / 2
9146	}
9147
9148	},{}],3:[function(require,module,exports){
9149	module.exports.pages = function pages (glyphs) {
9150	  var pages = new Float32Array(glyphs.length * 4 * 1)
9151	  var i = 0
9152	  glyphs.forEach(function (glyph) {
9153	    var id = glyph.data.page || 0
9154	    pages[i++] = id
9155	    pages[i++] = id
9156	    pages[i++] = id
9157	    pages[i++] = id
9158	  })
9159	  return pages
9160	}
9161
9162	module.exports.uvs = function uvs (glyphs, texWidth, texHeight, flipY) {
9163	  var uvs = new Float32Array(glyphs.length * 4 * 2)
9164	  var i = 0
9165	  glyphs.forEach(function (glyph) {
9166	    var bitmap = glyph.data
9167	    var bw = (bitmap.x + bitmap.width)
9168	    var bh = (bitmap.y + bitmap.height)
9169
9170	    // top left position
9171	    var u0 = bitmap.x / texWidth
9172	    var v1 = bitmap.y / texHeight
9173	    var u1 = bw / texWidth
9174	    var v0 = bh / texHeight
9175
9176	    if (flipY) {
9177	      v1 = (texHeight - bitmap.y) / texHeight
9178	      v0 = (texHeight - bh) / texHeight
9179	    }
9180
9181	    // BL
9182	    uvs[i++] = u0
9183	    uvs[i++] = v1
9184	    // TL
9185	    uvs[i++] = u0
9186	    uvs[i++] = v0
9187	    // TR
9188	    uvs[i++] = u1
9189	    uvs[i++] = v0
9190	    // BR
9191	    uvs[i++] = u1
9192	    uvs[i++] = v1
9193	  })
9194	  return uvs
9195	}
9196
9197	module.exports.positions = function positions (glyphs) {
9198	  var positions = new Float32Array(glyphs.length * 4 * 2)
9199	  var i = 0
9200	  glyphs.forEach(function (glyph) {
9201	    var bitmap = glyph.data
9202
9203	    // bottom left position
9204	    var x = glyph.position[0] + bitmap.xoffset
9205	    var y = glyph.position[1] + bitmap.yoffset
9206
9207	    // quad size
9208	    var w = bitmap.width
9209	    var h = bitmap.height
9210
9211	    // BL
9212	    positions[i++] = x
9213	    positions[i++] = y
9214	    // TL
9215	    positions[i++] = x
9216	    positions[i++] = y + h
9217	    // TR
9218	    positions[i++] = x + w
9219	    positions[i++] = y + h
9220	    // BR
9221	    positions[i++] = x + w
9222	    positions[i++] = y
9223	  })
9224	  return positions
9225	}
9226
9227	},{}],4:[function(require,module,exports){
9228	if (typeof Object.create === 'function') {
9229	  // implementation from standard node.js 'util' module
9230	  module.exports = function inherits(ctor, superCtor) {
9231	    ctor.super_ = superCtor
9232	    ctor.prototype = Object.create(superCtor.prototype, {
9233	      constructor: {
9234	        value: ctor,
9235	        enumerable: false,
9236	        writable: true,
9237	        configurable: true
9238	      }
9239	    });
9240	  };
9241	} else {
9242	  // old school shim for old browsers
9243	  module.exports = function inherits(ctor, superCtor) {
9244	    ctor.super_ = superCtor
9245	    var TempCtor = function () {}
9246	    TempCtor.prototype = superCtor.prototype
9247	    ctor.prototype = new TempCtor()
9248	    ctor.prototype.constructor = ctor
9249	  }
9250	}
9251
9252	},{}],5:[function(require,module,exports){
9253	var wordWrap = require('word-wrapper')
9254	var xtend = require('xtend')
9255	var findChar = require('indexof-property')('id')
9256	var number = require('as-number')
9257
9258	var X_HEIGHTS = ['x', 'e', 'a', 'o', 'n', 's', 'r', 'c', 'u', 'm', 'v', 'w', 'z']
9259	var M_WIDTHS = ['m', 'w']
9260	var CAP_HEIGHTS = ['H', 'I', 'N', 'E', 'F', 'K', 'L', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
9261
9262
9263	var TAB_ID = '\t'.charCodeAt(0)
9264	var SPACE_ID = ' '.charCodeAt(0)
9265	var ALIGN_LEFT = 0, 
9266	    ALIGN_CENTER = 1, 
9267	    ALIGN_RIGHT = 2
9268
9269	module.exports = function createLayout(opt) {
9270	  return new TextLayout(opt)
9271	}
9272
9273	function TextLayout(opt) {
9274	  this.glyphs = []
9275	  this._measure = this.computeMetrics.bind(this)
9276	  this.update(opt)
9277	}
9278
9279	TextLayout.prototype.update = function(opt) {
9280	  opt = xtend({
9281	    measure: this._measure
9282	  }, opt)
9283	  this._opt = opt
9284	  this._opt.tabSize = number(this._opt.tabSize, 4)
9285
9286	  if (!opt.font)
9287	    throw new Error('must provide a valid bitmap font')
9288
9289	  var glyphs = this.glyphs
9290	  var text = opt.text||'' 
9291	  var font = opt.font
9292	  this._setupSpaceGlyphs(font)
9293	  
9294	  var lines = wordWrap.lines(text, opt)
9295	  var minWidth = opt.width || 0
9296
9297	  //clear glyphs
9298	  glyphs.length = 0
9299
9300	  //get max line width
9301	  var maxLineWidth = lines.reduce(function(prev, line) {
9302	    return Math.max(prev, line.width, minWidth)
9303	  }, 0)
9304
9305	  //the pen position
9306	  var x = 0
9307	  var y = 0
9308	  var lineHeight = number(opt.lineHeight, font.common.lineHeight)
9309	  var baseline = font.common.base
9310	  var descender = lineHeight-baseline
9311	  var letterSpacing = opt.letterSpacing || 0
9312	  var height = lineHeight * lines.length - descender
9313	  var align = getAlignType(this._opt.align)
9314
9315	  //draw text along baseline
9316	  y -= height
9317	  
9318	  //the metrics for this text layout
9319	  this._width = maxLineWidth
9320	  this._height = height
9321	  this._descender = lineHeight - baseline
9322	  this._baseline = baseline
9323	  this._xHeight = getXHeight(font)
9324	  this._capHeight = getCapHeight(font)
9325	  this._lineHeight = lineHeight
9326	  this._ascender = lineHeight - descender - this._xHeight
9327	    
9328	  //layout each glyph
9329	  var self = this
9330	  lines.forEach(function(line, lineIndex) {
9331	    var start = line.start
9332	    var end = line.end
9333	    var lineWidth = line.width
9334	    var lastGlyph
9335	    
9336	    //for each glyph in that line...
9337	    for (var i=start; i<end; i++) {
9338	      var id = text.charCodeAt(i)
9339	      var glyph = self.getGlyph(font, id)
9340	      if (glyph) {
9341	        if (lastGlyph) 
9342	          x += getKerning(font, lastGlyph.id, glyph.id)
9343
9344	        var tx = x
9345	        if (align === ALIGN_CENTER) 
9346	          tx += (maxLineWidth-lineWidth)/2
9347	        else if (align === ALIGN_RIGHT)
9348	          tx += (maxLineWidth-lineWidth)
9349
9350	        glyphs.push({
9351	          position: [tx, y],
9352	          data: glyph,
9353	          index: i,
9354	          line: lineIndex
9355	        })  
9356
9357	        //move pen forward
9358	        x += glyph.xadvance + letterSpacing
9359	        lastGlyph = glyph
9360	      }
9361	    }
9362
9363	    //next line down
9364	    y += lineHeight
9365	    x = 0
9366	  })
9367	  this._linesTotal = lines.length;
9368	}
9369
9370	TextLayout.prototype._setupSpaceGlyphs = function(font) {
9371	  //These are fallbacks, when the font doesn't include
9372	  //' ' or '\t' glyphs
9373	  this._fallbackSpaceGlyph = null
9374	  this._fallbackTabGlyph = null
9375
9376	  if (!font.chars || font.chars.length === 0)
9377	    return
9378
9379	  //try to get space glyph
9380	  //then fall back to the 'm' or 'w' glyphs
9381	  //then fall back to the first glyph available
9382	  var space = getGlyphById(font, SPACE_ID) 
9383	          || getMGlyph(font) 
9384	          || font.chars[0]
9385
9386	  //and create a fallback for tab
9387	  var tabWidth = this._opt.tabSize * space.xadvance
9388	  this._fallbackSpaceGlyph = space
9389	  this._fallbackTabGlyph = xtend(space, {
9390	    x: 0, y: 0, xadvance: tabWidth, id: TAB_ID, 
9391	    xoffset: 0, yoffset: 0, width: 0, height: 0
9392	  })
9393	}
9394
9395	TextLayout.prototype.getGlyph = function(font, id) {
9396	  var glyph = getGlyphById(font, id)
9397	  if (glyph)
9398	    return glyph
9399	  else if (id === TAB_ID) 
9400	    return this._fallbackTabGlyph
9401	  else if (id === SPACE_ID) 
9402	    return this._fallbackSpaceGlyph
9403	  return null
9404	}
9405
9406	TextLayout.prototype.computeMetrics = function(text, start, end, width) {
9407	  var letterSpacing = this._opt.letterSpacing || 0
9408	  var font = this._opt.font
9409	  var curPen = 0
9410	  var curWidth = 0
9411	  var count = 0
9412	  var glyph
9413	  var lastGlyph
9414
9415	  if (!font.chars || font.chars.length === 0) {
9416	    return {
9417	      start: start,
9418	      end: start,
9419	      width: 0
9420	    }
9421	  }
9422
9423	  end = Math.min(text.length, end)
9424	  for (var i=start; i < end; i++) {
9425	    var id = text.charCodeAt(i)
9426	    var glyph = this.getGlyph(font, id)
9427
9428	    if (glyph) {
9429	      //move pen forward
9430	      var xoff = glyph.xoffset
9431	      var kern = lastGlyph ? getKerning(font, lastGlyph.id, glyph.id) : 0
9432	      curPen += kern
9433
9434	      var nextPen = curPen + glyph.xadvance + letterSpacing
9435	      var nextWidth = curPen + glyph.width
9436
9437	      //we've hit our limit; we can't move onto the next glyph
9438	      if (nextWidth >= width || nextPen >= width)
9439	        break
9440
9441	      //otherwise continue along our line
9442	      curPen = nextPen
9443	      curWidth = nextWidth
9444	      lastGlyph = glyph
9445	    }
9446	    count++
9447	  }
9448	  
9449	  //make sure rightmost edge lines up with rendered glyphs
9450	  if (lastGlyph)
9451	    curWidth += lastGlyph.xoffset
9452
9453	  return {
9454	    start: start,
9455	    end: start + count,
9456	    width: curWidth
9457	  }
9458	}
9459
9460	//getters for the private vars
9461	;['width', 'height', 
9462	  'descender', 'ascender',
9463	  'xHeight', 'baseline',
9464	  'capHeight',
9465	  'lineHeight' ].forEach(addGetter)
9466
9467	function addGetter(name) {
9468	  Object.defineProperty(TextLayout.prototype, name, {
9469	    get: wrapper(name),
9470	    configurable: true
9471	  })
9472	}
9473
9474	//create lookups for private vars
9475	function wrapper(name) {
9476	  return (new Function([
9477	    'return function '+name+'() {',
9478	    '  return this._'+name,
9479	    '}'
9480	  ].join('\n')))()
9481	}
9482
9483	function getGlyphById(font, id) {
9484	  if (!font.chars || font.chars.length === 0)
9485	    return null
9486
9487	  var glyphIdx = findChar(font.chars, id)
9488	  if (glyphIdx >= 0)
9489	    return font.chars[glyphIdx]
9490	  return null
9491	}
9492
9493	function getXHeight(font) {
9494	  for (var i=0; i<X_HEIGHTS.length; i++) {
9495	    var id = X_HEIGHTS[i].charCodeAt(0)
9496	    var idx = findChar(font.chars, id)
9497	    if (idx >= 0) 
9498	      return font.chars[idx].height
9499	  }
9500	  return 0
9501	}
9502
9503	function getMGlyph(font) {
9504	  for (var i=0; i<M_WIDTHS.length; i++) {
9505	    var id = M_WIDTHS[i].charCodeAt(0)
9506	    var idx = findChar(font.chars, id)
9507	    if (idx >= 0) 
9508	      return font.chars[idx]
9509	  }
9510	  return 0
9511	}
9512
9513	function getCapHeight(font) {
9514	  for (var i=0; i<CAP_HEIGHTS.length; i++) {
9515	    var id = CAP_HEIGHTS[i].charCodeAt(0)
9516	    var idx = findChar(font.chars, id)
9517	    if (idx >= 0) 
9518	      return font.chars[idx].height
9519	  }
9520	  return 0
9521	}
9522
9523	function getKerning(font, left, right) {
9524	  if (!font.kernings || font.kernings.length === 0)
9525	    return 0
9526
9527	  var table = font.kernings
9528	  for (var i=0; i<table.length; i++) {
9529	    var kern = table[i]
9530	    if (kern.first === left && kern.second === right)
9531	      return kern.amount
9532	  }
9533	  return 0
9534	}
9535
9536	function getAlignType(align) {
9537	  if (align === 'center')
9538	    return ALIGN_CENTER
9539	  else if (align === 'right')
9540	    return ALIGN_RIGHT
9541	  return ALIGN_LEFT
9542	}
9543	},{"as-number":6,"indexof-property":7,"word-wrapper":8,"xtend":9}],6:[function(require,module,exports){
9544	module.exports = function numtype(num, def) {
9545	  return typeof num === 'number'
9546	    ? num 
9547	    : (typeof def === 'number' ? def : 0)
9548	}
9549	},{}],7:[function(require,module,exports){
9550	module.exports = function compile(property) {
9551	  if (!property || typeof property !== 'string')
9552	    throw new Error('must specify property for indexof search')
9553
9554	  return new Function('array', 'value', 'start', [
9555	    'start = start || 0',
9556	    'for (var i=start; i<array.length; i++)',
9557	    '  if (array[i]["' + property +'"] === value)',
9558	    '      return i',
9559	    'return -1'
9560	  ].join('\n'))
9561	}
9562	},{}],8:[function(require,module,exports){
9563	var newline = /\n/
9564	var newlineChar = '\n'
9565	var whitespace = /\s/
9566
9567	module.exports = function(text, opt) {
9568	    var lines = module.exports.lines(text, opt)
9569	    return lines.map(function(line) {
9570	        return text.substring(line.start, line.end)
9571	    }).join('\n')
9572	}
9573
9574	module.exports.lines = function wordwrap(text, opt) {
9575	    opt = opt||{}
9576
9577	    //zero width results in nothing visible
9578	    if (opt.width === 0 && opt.mode !== 'nowrap') 
9579	        return []
9580
9581	    text = text||''
9582	    var width = typeof opt.width === 'number' ? opt.width : Number.MAX_VALUE
9583	    var start = Math.max(0, opt.start||0)
9584	    var end = typeof opt.end === 'number' ? opt.end : text.length
9585	    var mode = opt.mode
9586
9587	    var measure = opt.measure || monospace
9588	    if (mode === 'pre')
9589	        return pre(measure, text, start, end, width)
9590	    else
9591	        return greedy(measure, text, start, end, width, mode)
9592	}
9593
9594	function idxOf(text, chr, start, end) {
9595	    var idx = text.indexOf(chr, start)
9596	    if (idx === -1 || idx > end)
9597	        return end
9598	    return idx
9599	}
9600
9601	function isWhitespace(chr) {
9602	    return whitespace.test(chr)
9603	}
9604
9605	function pre(measure, text, start, end, width) {
9606	    var lines = []
9607	    var lineStart = start
9608	    for (var i=start; i<end && i<text.length; i++) {
9609	        var chr = text.charAt(i)
9610	        var isNewline = newline.test(chr)
9611
9612	        //If we've reached a newline, then step down a line
9613	        //Or if we've reached the EOF
9614	        if (isNewline || i===end-1) {
9615	            var lineEnd = isNewline ? i : i+1
9616	            var measured = measure(text, lineStart, lineEnd, width)
9617	            lines.push(measured)
9618	            
9619	            lineStart = i+1
9620	        }
9621	    }
9622	    return lines
9623	}
9624
9625	function greedy(measure, text, start, end, width, mode) {
9626	    //A greedy word wrapper based on LibGDX algorithm
9627	    //https://github.com/libgdx/libgdx/blob/master/gdx/src/com/badlogic/gdx/graphics/g2d/BitmapFontCache.java
9628	    var lines = []
9629
9630	    var testWidth = width
9631	    //if 'nowrap' is specified, we only wrap on newline chars
9632	    if (mode === 'nowrap')
9633	        testWidth = Number.MAX_VALUE
9634
9635	    while (start < end && start < text.length) {
9636	        //get next newline position
9637	        var newLine = idxOf(text, newlineChar, start, end)
9638
9639	        //eat whitespace at start of line
9640	        while (start < newLine) {
9641	            if (!isWhitespace( text.charAt(start) ))
9642	                break
9643	            start++
9644	        }
9645
9646	        //determine visible # of glyphs for the available width
9647	        var measured = measure(text, start, newLine, testWidth)
9648
9649	        var lineEnd = start + (measured.end-measured.start)
9650	        var nextStart = lineEnd + newlineChar.length
9651
9652	        //if we had to cut the line before the next newline...
9653	        if (lineEnd < newLine) {
9654	            //find char to break on
9655	            while (lineEnd > start) {
9656	                if (isWhitespace(text.charAt(lineEnd)))
9657	                    break
9658	                lineEnd--
9659	            }
9660	            if (lineEnd === start) {
9661	                if (nextStart > start + newlineChar.length) nextStart--
9662	                lineEnd = nextStart // If no characters to break, show all.
9663	            } else {
9664	                nextStart = lineEnd
9665	                //eat whitespace at end of line
9666	                while (lineEnd > start) {
9667	                    if (!isWhitespace(text.charAt(lineEnd - newlineChar.length)))
9668	                        break
9669	                    lineEnd--
9670	                }
9671	            }
9672	        }
9673	        if (lineEnd >= start) {
9674	            var result = measure(text, start, lineEnd, testWidth)
9675	            lines.push(result)
9676	        }
9677	        start = nextStart
9678	    }
9679	    return lines
9680	}
9681
9682	//determines the visible number of glyphs within a given width
9683	function monospace(text, start, end, width) {
9684	    var glyphs = Math.min(width, end-start)
9685	    return {
9686	        start: start,
9687	        end: start+glyphs
9688	    }
9689	}
9690	},{}],9:[function(require,module,exports){
9691	module.exports = extend
9692
9693	var hasOwnProperty = Object.prototype.hasOwnProperty;
9694
9695	function extend() {
9696	    var target = {}
9697
9698	    for (var i = 0; i < arguments.length; i++) {
9699	        var source = arguments[i]
9700
9701	        for (var key in source) {
9702	            if (hasOwnProperty.call(source, key)) {
9703	                target[key] = source[key]
9704	            }
9705	        }
9706	    }
9707
9708	    return target
9709	}
9710
9711	},{}],10:[function(require,module,exports){
9712	(function (Buffer){
9713	var xhr = require('xhr')
9714	var noop = function(){}
9715	var parseASCII = require('parse-bmfont-ascii')
9716	var parseXML = require('parse-bmfont-xml')
9717	var readBinary = require('parse-bmfont-binary')
9718	var isBinaryFormat = require('./lib/is-binary')
9719	var xtend = require('xtend')
9720
9721	var xml2 = (function hasXML2() {
9722	  return window.XMLHttpRequest && "withCredentials" in new XMLHttpRequest
9723	})()
9724
9725	module.exports = function(opt, cb) {
9726	  cb = typeof cb === 'function' ? cb : noop
9727
9728	  if (typeof opt === 'string')
9729	    opt = { uri: opt }
9730	  else if (!opt)
9731	    opt = {}
9732
9733	  var expectBinary = opt.binary
9734	  if (expectBinary)
9735	    opt = getBinaryOpts(opt)
9736
9737	  xhr(opt, function(err, res, body) {
9738	    if (err)
9739	      return cb(err)
9740	    if (!/^2/.test(res.statusCode))
9741	      return cb(new Error('http status code: '+res.statusCode))
9742	    if (!body)
9743	      return cb(new Error('no body result'))
9744
9745	    var binary = false 
9746
9747	    //if the response type is an array buffer,
9748	    //we need to convert it into a regular Buffer object
9749	    if (isArrayBuffer(body)) {
9750	      var array = new Uint8Array(body)
9751	      body = new Buffer(array, 'binary')
9752	    }
9753
9754	    //now check the string/Buffer response
9755	    //and see if it has a binary BMF header
9756	    if (isBinaryFormat(body)) {
9757	      binary = true
9758	      //if we have a string, turn it into a Buffer
9759	      if (typeof body === 'string') 
9760	        body = new Buffer(body, 'binary')
9761	    } 
9762
9763	    //we are not parsing a binary format, just ASCII/XML/etc
9764	    if (!binary) {
9765	      //might still be a buffer if responseType is 'arraybuffer'
9766	      if (Buffer.isBuffer(body))
9767	        body = body.toString(opt.encoding)
9768	      body = body.trim()
9769	    }
9770
9771	    var result
9772	    try {
9773	      var type = res.headers['content-type']
9774	      if (binary)
9775	        result = readBinary(body)
9776	      else if (/json/.test(type) || body.charAt(0) === '{')
9777	        result = JSON.parse(body)
9778	      else if (/xml/.test(type)  || body.charAt(0) === '<')
9779	        result = parseXML(body)
9780	      else
9781	        result = parseASCII(body)
9782	    } catch (e) {
9783	      cb(new Error('error parsing font '+e.message))
9784	      cb = noop
9785	    }
9786	    cb(null, result)
9787	  })
9788	}
9789
9790	function isArrayBuffer(arr) {
9791	  var str = Object.prototype.toString
9792	  return str.call(arr) === '[object ArrayBuffer]'
9793	}
9794
9795	function getBinaryOpts(opt) {
9796	  //IE10+ and other modern browsers support array buffers
9797	  if (xml2)
9798	    return xtend(opt, { responseType: 'arraybuffer' })
9799	  
9800	  if (typeof window.XMLHttpRequest === 'undefined')
9801	    throw new Error('your browser does not support XHR loading')
9802
9803	  //IE9 and XML1 browsers could still use an override
9804	  var req = new window.XMLHttpRequest()
9805	  req.overrideMimeType('text/plain;
9805 charset=x-user-defined')
9806	  return xtend({
9807	    xhr: req
9808	  }, opt)
9809	}
9810	}).call(this,require("buffer").Buffer)
9811	},{"./lib/is-binary":11,"buffer":37,"parse-bmfont-ascii":13,"parse-bmfont-binary":14,"parse-bmfont-xml":15,"xhr":18,"xtend":25}],11:[function(require,module,exports){
9812	(function (Buffer){
9813	var equal = require('buffer-equal')
9814	var HEADER = new Buffer([66, 77, 70, 3])
9815
9816	module.exports = function(buf) {
9817	  if (typeof buf === 'string')
9818	    return buf.substring(0, 3) === 'BMF'
9819	  return buf.length > 4 && equal(buf.slice(0, 4), HEADER)
9820	}
9821	}).call(this,require("buffer").Buffer)
9822	},{"buffer":37,"buffer-equal":12}],12:[function(require,module,exports){
9823	var Buffer = require('buffer').Buffer; // for use with browserify
9824
9825	module.exports = function (a, b) {
9826	    if (!Buffer.isBuffer(a)) return undefined;
9827	    if (!Buffer.isBuffer(b)) return undefined;
9828	    if (typeof a.equals === 'function') return a.equals(b);
9829	    if (a.length !== b.length) return false;
9830	    
9831	    for (var i = 0; i < a.length; i++) {
9832	        if (a[i] !== b[i]) return false;
9833	    }
9834	    
9835	    return true;
9836	};
9837
9838	},{"buffer":37}],13:[function(require,module,exports){
9839	module.exports = function parseBMFontAscii(data) {
9840	  if (!data)
9841	    throw new Error('no data provided')
9842	  data = data.toString().trim()
9843
9844	  var output = {
9845	    pages: [],
9846	    chars: [],
9847	    kernings: []
9848	  }
9849
9850	  var lines = data.split(/\r\n?|\n/g)
9851
9852	  if (lines.length === 0)
9853	    throw new Error('no data in BMFont file')
9854
9855	  for (var i = 0; i < lines.length; i++) {
9856	    var lineData = splitLine(lines[i], i)
9857	    if (!lineData) //skip empty lines
9858	      continue
9859
9860	    if (lineData.key === 'page') {
9861	      if (typeof lineData.data.id !== 'number')
9862	        throw new Error('malformed file at line ' + i + ' -- needs page id=N')
9863	      if (typeof lineData.data.file !== 'string')
9864	        throw new Error('malformed file at line ' + i + ' -- needs page file="path"')
9865	      output.pages[lineData.data.id] = lineData.data.file
9866	    } else if (lineData.key === 'chars' || lineData.key === 'kernings') {
9867	      //... do nothing for these two ...
9868	    } else if (lineData.key === 'char') {
9869	      output.chars.push(lineData.data)
9870	    } else if (lineData.key === 'kerning') {
9871	      output.kernings.push(lineData.data)
9872	    } else {
9873	      output[lineData.key] = lineData.data
9874	    }
9875	  }
9876
9877	  return output
9878	}
9879
9880	function splitLine(line, idx) {
9881	  line = line.replace(/\t+/g, ' ').trim()
9882	  if (!line)
9883	    return null
9884
9885	  var space = line.indexOf(' ')
9886	  if (space === -1) 
9887	    throw new Error("no named row at line " + idx)
9888
9889	  var key = line.substring(0, space)
9890
9891	  line = line.substring(space + 1)
9892	  //clear "letter" field as it is non-standard and
9893	  //requires additional complexity to parse " / = symbols
9894	  line = line.replace(/letter=[\'\"]\S+[\'\"]/gi, '')  
9895	  line = line.split("=")
9896	  line = line.map(function(str) {
9897	    return str.trim().match((/(".*?"|[^"\s]+)+(?=\s*|\s*$)/g))
9898	  })
9899
9900	  var data = []
9901	  for (var i = 0; i < line.length; i++) {
9902	    var dt = line[i]
9903	    if (i === 0) {
9904	      data.push({
9905	        key: dt[0],
9906	        data: ""
9907	      })
9908	    } else if (i === line.length - 1) {
9909	      data[data.length - 1].data = parseData(dt[0])
9910	    } else {
9911	      data[data.length - 1].data = parseData(dt[0])
9912	      data.push({
9913	        key: dt[1],
9914	        data: ""
9915	      })
9916	    }
9917	  }
9918
9919	  var out = {
9920	    key: key,
9921	    data: {}
9922	  }
9923
9924	  data.forEach(function(v) {
9925	    out.data[v.key] = v.data;
9926	  })
9927
9928	  return out
9929	}
9930
9931	function parseData(data) {
9932	  if (!data || data.length === 0)
9933	    return ""
9934
9935	  if (data.indexOf('"') === 0 || data.indexOf("'") === 0)
9936	    return data.substring(1, data.length - 1)
9937	  if (data.indexOf(',') !== -1)
9938	    return parseIntList(data)
9939	  return parseInt(data, 10)
9940	}
9941
9942	function parseIntList(data) {
9943	  return data.split(',').map(function(val) {
9944	    return parseInt(val, 10)
9945	  })
9946	}
9947	},{}],14:[function(require,module,exports){
9948	var HEADER = [66, 77, 70]
9949
9950	module.exports = function readBMFontBinary(buf) {
9951	  if (buf.length < 6)
9952	    throw new Error('invalid buffer length for BMFont')
9953
9954	  var header = HEADER.every(function(byte, i) {
9955	    return buf.readUInt8(i) === byte
9956	  })
9957
9958	  if (!header)
9959	    throw new Error('BMFont missing BMF byte header')
9960
9961	  var i = 3
9962	  var vers = buf.readUInt8(i++)
9963	  if (vers > 3)
9964	    throw new Error('Only supports BMFont Binary v3 (BMFont App v1.10)')
9965	  
9966	  var target = { kernings: [], chars: [] }
9967	  for (var b=0; b<5; b++)
9968	    i += readBlock(target, buf, i)
9969	  return target
9970	}
9971
9972	function readBlock(target, buf, i) {
9973	  if (i > buf.length-1)
9974	    return 0
9975
9976	  var blockID = buf.readUInt8(i++)
9977	  var blockSize = buf.readInt32LE(i)
9978	  i += 4
9979
9980	  switch(blockID) {
9981	    case 1: 
9982	      target.info = readInfo(buf, i)
9983	      break
9984	    case 2:
9985	      target.common = readCommon(buf, i)
9986	      break
9987	    case 3:
9988	      target.pages = readPages(buf, i, blockSize)
9989	      break
9990	    case 4:
9991	      target.chars = readChars(buf, i, blockSize)
9992	      break
9993	    case 5:
9994	      target.kernings = readKernings(buf, i, blockSize)
9995	      break
9996	  }
9997	  return 5 + blockSize
9998	}
9999
10000	function readInfo(buf, i) {
10001	  var info = {}
10002	  info.size = buf.readInt16LE(i)
10003
10004	  var bitField = buf.readUInt8(i+2)
10005	  info.smooth = (bitField >> 7) & 1
10006	  info.unicode = (bitField >> 6) & 1
10007	  info.italic = (bitField >> 5) & 1
10008	  info.bold = (bitField >> 4) & 1
10009	  
10010	  //fixedHeight is only mentioned in binary spec 
10011	  if ((bitField >> 3) & 1)
10012	    info.fixedHeight = 1
10013	  
10014	  info.charset = buf.readUInt8(i+3) || ''
10015	  info.stretchH = buf.readUInt16LE(i+4)
10016	  info.aa = buf.readUInt8(i+6)
10017	  info.padding = [
10018	    buf.readInt8(i+7),
10019	    buf.readInt8(i+8),
10020	    buf.readInt8(i+9),
10021	    buf.readInt8(i+10)
10022	  ]
10023	  info.spacing = [
10024	    buf.readInt8(i+11),
10025	    buf.readInt8(i+12)
10026	  ]
10027	  info.outline = buf.readUInt8(i+13)
10028	  info.face = readStringNT(buf, i+14)
10029	  return info
10030	}
10031
10032	function readCommon(buf, i) {
10033	  var common = {}
10034	  common.lineHeight = buf.readUInt16LE(i)
10035	  common.base = buf.readUInt16LE(i+2)
10036	  common.scaleW = buf.readUInt16LE(i+4)
10037	  common.scaleH = buf.readUInt16LE(i+6)
10038	  common.pages = buf.readUInt16LE(i+8)
10039	  var bitField = buf.readUInt8(i+10)
10040	  common.packed = 0
10041	  common.alphaChnl = buf.readUInt8(i+11)
10042	  common.redChnl = buf.readUInt8(i+12)
10043	  common.greenChnl = buf.readUInt8(i+13)
10044	  common.blueChnl = buf.readUInt8(i+14)
10045	  return common
10046	}
10047
10048	function readPages(buf, i, size) {
10049	  var pages = []
10050	  var text = readNameNT(buf, i)
10051	  var len = text.length+1
10052	  var count = size / len
10053	  for (var c=0; c<count; c++) {
10054	    pages[c] = buf.slice(i, i+text.length).toString('utf8')
10055	    i += len
10056	  }
10057	  return pages
10058	}
10059
10060	function readChars(buf, i, blockSize) {
10061	  var chars = []
10062
10063	  var count = blockSize / 20
10064	  for (var c=0; c<count; c++) {
10065	    var char = {}
10066	    var off = c*20
10067	    char.id = buf.readUInt32LE(i + 0 + off)
10068	    char.x = buf.readUInt16LE(i + 4 + off)
10069	    char.y = buf.readUInt16LE(i + 6 + off)
10070	    char.width = buf.readUInt16LE(i + 8 + off)
10071	    char.height = buf.readUInt16LE(i + 10 + off)
10072	    char.xoffset = buf.readInt16LE(i + 12 + off)
10073	    char.yoffset = buf.readInt16LE(i + 14 + off)
10074	    char.xadvance = buf.readInt16LE(i + 16 + off)
10075	    char.page = buf.readUInt8(i + 18 + off)
10076	    char.chnl = buf.readUInt8(i + 19 + off)
10077	    chars[c] = char
10078	  }
10079	  return chars
10080	}
10081
10082	function readKernings(buf, i, blockSize) {
10083	  var kernings = []
10084	  var count = blockSize / 10
10085	  for (var c=0; c<count; c++) {
10086	    var kern = {}
10087	    var off = c*10
10088	    kern.first = buf.readUInt32LE(i + 0 + off)
10089	    kern.second = buf.readUInt32LE(i + 4 + off)
10090	    kern.amount = buf.readInt16LE(i + 8 + off)
10091	    kernings[c] = kern
10092	  }
10093	  return kernings
10094	}
10095
10096	function readNameNT(buf, offset) {
10097	  var pos=offset
10098	  for (; pos<buf.length; pos++) {
10099	    if (buf[pos] === 0x00) 
10100	      break
10101	  }
10102	  return buf.slice(offset, pos)
10103	}
10104
10105	function readStringNT(buf, offset) {
10106	  return readNameNT(buf, offset).toString('utf8')
10107	}
10108	},{}],15:[function(require,module,exports){
10109	var parseAttributes = require('./parse-attribs')
10110	var parseFromString = require('xml-parse-from-string')
10111
10112	//In some cases element.attribute.nodeName can return
10113	//all lowercase values.. so we need to map them to the correct 
10114	//case
10115	var NAME_MAP = {
10116	  scaleh: 'scaleH',
10117	  scalew: 'scaleW',
10118	  stretchh: 'stretchH',
10119	  lineheight: 'lineHeight',
10120	  alphachnl: 'alphaChnl',
10121	  redchnl: 'redChnl',
10122	  greenchnl: 'greenChnl',
10123	  bluechnl: 'blueChnl'
10124	}
10125
10126	module.exports = function parse(data) {
10127	  data = data.toString()
10128	  
10129	  var xmlRoot = parseFromString(data)
10130	  var output = {
10131	    pages: [],
10132	    chars: [],
10133	    kernings: []
10134	  }
10135
10136	  //get config settings
10137	  ;['info', 'common'].forEach(function(key) {
10138	    var element = xmlRoot.getElementsByTagName(key)[0]
10139	    if (element)
10140	      output[key] = parseAttributes(getAttribs(element))
10141	  })
10142
10143	  //get page info
10144	  var pageRoot = xmlRoot.getElementsByTagName('pages')[0]
10145	  if (!pageRoot)
10146	    throw new Error('malformed file -- no <pages> element')
10147	  var pages = pageRoot.getElementsByTagName('page')
10148	  for (var i=0; i<pages.length; i++) {
10149	    var p = pages[i]
10150	    var id = parseInt(p.getAttribute('id'), 10)
10151	    var file = p.getAttribute('file')
10152	    if (isNaN(id))
10153	      throw new Error('malformed file -- page "id" attribute is NaN')
10154	    if (!file)
10155	      throw new Error('malformed file -- needs page "file" attribute')
10156	    output.pages[parseInt(id, 10)] = file
10157	  }
10158
10159	  //get kernings / chars
10160	  ;['chars', 'kernings'].forEach(function(key) {
10161	    var element = xmlRoot.getElementsByTagName(key)[0]
10162	    if (!element)
10163	      return
10164	    var childTag = key.substring(0, key.length-1)
10165	    var children = element.getElementsByTagName(childTag)
10166	    for (var i=0; i<children.length; i++) {      
10167	      var child = children[i]
10168	      output[key].push(parseAttributes(getAttribs(child)))
10169	    }
10170	  })
10171	  return output
10172	}
10173
10174	function getAttribs(element) {
10175	  var attribs = getAttribList(element)
10176	  return attribs.reduce(function(dict, attrib) {
10177	    var key = mapName(attrib.nodeName)
10178	    dict[key] = attrib.nodeValue
10179	    return dict
10180	  }, {})
10181	}
10182
10183	function getAttribList(element) {
10184	  //IE8+ and modern browsers
10185	  var attribs = []
10186	  for (var i=0; i<element.attributes.length; i++)
10187	    attribs.push(element.attributes[i])
10188	  return attribs
10189	}
10190
10191	function mapName(nodeName) {
10192	  return NAME_MAP[nodeName.toLowerCase()] || nodeName
10193	}
10194	},{"./parse-attribs":16,"xml-parse-from-string":17}],16:[function(require,module,exports){
10195	//Some versions of GlyphDesigner have a typo
10196	//that causes some bugs with parsing. 
10197	//Need to confirm with recent version of the software
10198	//to see whether this is still an issue or not.
10199	var GLYPH_DESIGNER_ERROR = 'chasrset'
10200
10201	module.exports = function parseAttributes(obj) {
10202	  if (GLYPH_DESIGNER_ERROR in obj) {
10203	    obj['charset'] = obj[GLYPH_DESIGNER_ERROR]
10204	    delete obj[GLYPH_DESIGNER_ERROR]
10205	  }
10206
10207	  for (var k in obj) {
10208	    if (k === 'face' || k === 'charset') 
10209	      continue
10210	    else if (k === 'padding' || k === 'spacing')
10211	      obj[k] = parseIntList(obj[k])
10212	    else
10213	      obj[k] = parseInt(obj[k], 10) 
10214	  }
10215	  return obj
10216	}
10217
10218	function parseIntList(data) {
10219	  return data.split(',').map(function(val) {
10220	    return parseInt(val, 10)
10221	  })
10222	}
10223	},{}],17:[function(require,module,exports){
10224	module.exports = (function xmlparser() {
10225	  //common browsers
10226	  if (typeof window.DOMParser !== 'undefined') {
10227	    return function(str) {
10228	      var parser = new window.DOMParser()
10229	      return parser.parseFromString(str, 'application/xml')
10230	    }
10231	  } 
10232
10233	  //IE8 fallback
10234	  if (typeof window.ActiveXObject !== 'undefined'
10235	      && new window.ActiveXObject('Microsoft.XMLDOM')) {
10236	    return function(str) {
10237	      var xmlDoc = new window.ActiveXObject("Microsoft.XMLDOM")
10238	      xmlDoc.async = "false"
10239	      xmlDoc.loadXML(str)
10240	      return xmlDoc
10241	    }
10242	  }
10243
10244	  //last resort fallback
10245	  return function(str) {
10246	    var div = document.createElement('div')
10247	    div.innerHTML = str
10248	    return div
10249	  }
10250	})()
10251	},{}],18:[function(require,module,exports){
10252	"use strict";
10253	var window = require("global/window")
10254	var once = require("once")
10255	var isFunction = require("is-function")
10256	var parseHeaders = require("parse-headers")
10257	var xtend = require("xtend")
10258
10259	module.exports = createXHR
10260	createXHR.XMLHttpRequest = window.XMLHttpRequest || noop
10261	createXHR.XDomainRequest = "withCredentials" in (new createXHR.XMLHttpRequest()) ? createXHR.XMLHttpRequest : window.XDomainRequest
10262
10263	forEachArray(["get", "put", "post", "patch", "head", "delete"], function(method) {
10264	    createXHR[method === "delete" ? "del" : method] = function(uri, options, callback) {
10265	        options = initParams(uri, options, callback)
10266	        options.method = method.toUpperCase()
10267	        return _createXHR(options)
10268	    }
10269	})
10270
10271	function forEachArray(array, iterator) {
10272	    for (var i = 0; i < array.length; i++) {
10273	        iterator(array[i])
10274	    }
10275	}
10276
10277	function isEmpty(obj){
10278	    for(var i in obj){
10279	        if(obj.hasOwnProperty(i)) return false
10280	    }
10281	    return true
10282	}
10283
10284	function initParams(uri, options, callback) {
10285	    var params = uri
10286
10287	    if (isFunction(options)) {
10288	        callback = options
10289	        if (typeof uri === "string") {
10290	            params = {uri:uri}
10291	        }
10292	    } else {
10293	        params = xtend(options, {uri: uri})
10294	    }
10295
10296	    params.callback = callback
10297	    return params
10298	}
10299
10300	function createXHR(uri, options, callback) {
10301	    options = initParams(uri, options, callback)
10302	    return _createXHR(options)
10303	}
10304
10305	function _createXHR(options) {
10306	    var callback = options.callback
10307	    if(typeof callback === "undefined"){
10308	        throw new Error("callback argument missing")
10309	    }
10310	    callback = once(callback)
10311
10312	    function readystatechange() {
10313	        if (xhr.readyState === 4) {
10314	            loadFunc()
10315	        }
10316	    }
10317
10318	    function getBody() {
10319	        // Chrome with requestType=blob throws errors arround when even testing access to responseText
10320	        var body = undefined
10321
10322	        if (xhr.response) {
10323	            body = xhr.response
10324	        } else if (xhr.responseType === "text" || !xhr.responseType) {
10325	            body = xhr.responseText || xhr.responseXML
10326	        }
10327
10328	        if (isJson) {
10329	            try {
10330	                body = JSON.parse(body)
10331	            } catch (e) {}
10332	        }
10333
10334	        return body
10335	    }
10336
10337	    var failureResponse = {
10338	                body: undefined,
10339	                headers: {},
10340	                statusCode: 0,
10341	                method: method,
vendor: 4,097 bytes, lines 10342-10456
10342	                url: uri,
10343	                rawRequest: xhr
10344	            }
10345
10346	    function errorFunc(evt) {
10347	        clearTimeout(timeoutTimer)
10348	        if(!(evt instanceof Error)){
10349	            evt = new Error("" + (evt || "Unknown XMLHttpRequest Error") )
10350	        }
10351	        evt.statusCode = 0
10352	        callback(evt, failureResponse)
10353	    }
10354
10355	    // will load the data & process the response in a special response object
10356	    function loadFunc() {
10357	        if (aborted) return
10358	        var status
10359	        clearTimeout(timeoutTimer)
10360	        if(options.useXDR && xhr.status===undefined) {
10361	            //IE8 CORS GET successful response doesn't have a status field, but body is fine
10362	            status = 200
10363	        } else {
10364	            status = (xhr.status === 1223 ? 204 : xhr.status)
10365	        }
10366	        var response = failureResponse
10367	        var err = null
10368
10369	        if (status !== 0){
10370	            response = {
10371	                body: getBody(),
10372	                statusCode: status,
10373	                method: method,
10374	                headers: {},
10375	                url: uri,
10376	                rawRequest: xhr
10377	            }
10378	            if(xhr.getAllResponseHeaders){ //remember xhr can in fact be XDR for CORS in IE
10379	                response.headers = parseHeaders(xhr.getAllResponseHeaders())
10380	            }
10381	        } else {
10382	            err = new Error("Internal XMLHttpRequest Error")
10383	        }
10384	        callback(err, response, response.body)
10385
10386	    }
10387
10388	    var xhr = options.xhr || null
10389
10390	    if (!xhr) {
10391	        if (options.cors || options.useXDR) {
10392	            xhr = new createXHR.XDomainRequest()
10393	        }else{
10394	            xhr = new createXHR.XMLHttpRequest()
10395	        }
10396	    }
10397
10398	    var key
10399	    var aborted
10400	    var uri = xhr.url = options.uri || options.url
10401	    var method = xhr.method = options.method || "GET"
10402	    var body = options.body || options.data || null
10403	    var headers = xhr.headers = options.headers || {}
10404	    var sync = !!options.sync
10405	    var isJson = false
10406	    var timeoutTimer
10407
10408	    if ("json" in options) {
10409	        isJson = true
10410	        headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json") //Don't override existing accept header declared by user
10411	        if (method !== "GET" && method !== "HEAD") {
10412	            headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json") //Don't override existing accept header declared by user
10413	            body = JSON.stringify(options.json)
10414	        }
10415	    }
10416
10417	    xhr.onreadystatechange = readystatechange
10418	    xhr.onload = loadFunc
10419	    xhr.onerror = errorFunc
10420	    // IE9 must have onprogress be set to a unique function.
10421	    xhr.onprogress = function () {
10422	        // IE must die
10423	    }
10424	    xhr.ontimeout = errorFunc
10425	    xhr.open(method, uri, !sync, options.username, options.password)
10426	    //has to be after open
10427	    if(!sync) {
10428	        xhr.withCredentials = !!options.withCredentials
10429	    }
10430	    // Cannot set timeout with sync request
10431	    // not setting timeout on the xhr object, because of old webkits etc. not handling that correctly
10432	    // both npm's request and jquery 1.x use this kind of timeout, so this is being consistent
10433	    if (!sync && options.timeout > 0 ) {
10434	        timeoutTimer = setTimeout(function(){
10435	            aborted=true//IE9 may still call readystatechange
10436	            xhr.abort("timeout")
10437	            var e = new Error("XMLHttpRequest timeout")
10438	            e.code = "ETIMEDOUT"
10439	            errorFunc(e)
10440	        }, options.timeout )
10441	    }
10442
10443	    if (xhr.setRequestHeader) {
10444	        for(key in headers){
10445	            if(headers.hasOwnProperty(key)){
10446	                xhr.setRequestHeader(key, headers[key])
10447	            }
10448	        }
10449	    } else if (options.headers && !isEmpty(options.headers)) {
10450	        throw new Error("Headers cannot be set on an XDomainRequest object")
10451	    }
10452
10453	    if ("responseType" in options) {
10454	        xhr.responseType = options.responseType
10455	    }
10456
10457	    if ("beforeSend" in options &&
10458	        typeof options.beforeSend === "function"
10459	    ) {
10460	        options.beforeSend(xhr)
10461	    }
10462
10463	    xhr.send(body)
10464
10465	    return xhr
10466
10467
10468	}
10469
10470	function noop() {}
10471
10472	},{"global/window":19,"is-function":20,"once":21,"parse-headers":24,"xtend":25}],19:[function(require,module,exports){
10473	(function (global){
10474	if (typeof window !== "undefined") {
10475	    module.exports = window;
10476	} else if (typeof global !== "undefined") {
10477	    module.exports = global;
10478	} else if (typeof self !== "undefined"){
10479	    module.exports = self;
10480	} else {
10481	    module.exports = {};
10482	}
10483
10484	}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
10485	},{}],20:[function(require,module,exports){
10486	module.exports = isFunction
10487
10488	var toString = Object.prototype.toString
10489
10490	function isFunction (fn) {
10491	  var string = toString.call(fn)
10492	  return string === '[object Function]' ||
10493	    (typeof fn === 'function' && string !== '[object RegExp]') ||
10494	    (typeof window !== 'undefined' &&
10495	     // IE8 and below
10496	     (fn === window.setTimeout ||
10497	      fn === window.alert ||
10498	      fn === window.confirm ||
10499	      fn === window.prompt))
10500	};
10501
10502	},{}],21:[function(require,module,exports){
10503	module.exports = once
10504
10505	once.proto = once(function () {
10506	  Object.defineProperty(Function.prototype, 'once', {
10507	    value: function () {
10508	      return once(this)
10509	    },
10510	    configurable: true
10511	  })
10512	})
10513
10514	function once (fn) {
10515	  var called = false
10516	  return function () {
10517	    if (called) return
10518	    called = true
10519	    return fn.apply(this, arguments)
10520	  }
10521	}
10522
10523	},{}],22:[function(require,module,exports){
10524	var isFunction = require('is-function')
10525
10526	module.exports = forEach
10527
10528	var toString = Object.prototype.toString
10529	var hasOwnProperty = Object.prototype.hasOwnProperty
10530
10531	function forEach(list, iterator, context) {
10532	    if (!isFunction(iterator)) {
10533	        throw new TypeError('iterator must be a function')
10534	    }
10535
10536	    if (arguments.length < 3) {
10537	        context = this
10538	    }
10539	    
10540	    if (toString.call(list) === '[object Array]')
10541	        forEachArray(list, iterator, context)
10542	    else if (typeof list === 'string')
10543	        forEachString(list, iterator, context)
10544	    else
10545	        forEachObject(list, iterator, context)
10546	}
10547
10548	function forEachArray(array, iterator, context) {
10549	    for (var i = 0, len = array.length; i < len; i++) {
10550	        if (hasOwnProperty.call(array, i)) {
10551	            iterator.call(context, array[i], i, array)
10552	        }
10553	    }
10554	}
10555
10556	function forEachString(string, iterator, context) {
10557	    for (var i = 0, len = string.length; i < len; i++) {
10558	        // no such thing as a sparse string.
10559	        iterator.call(context, string.charAt(i), i, string)
10560	    }
10561	}
10562
10563	function forEachObject(object, iterator, context) {
10564	    for (var k in object) {
10565	        if (hasOwnProperty.call(object, k)) {
10566	            iterator.call(context, object[k], k, object)
10567	        }
10568	    }
10569	}
10570
10571	},{"is-function":20}],23:[function(require,module,exports){
10572
10573	exports = module.exports = trim;
10574
10575	function trim(str){
10576	  return str.replace(/^\s*|\s*$/g, '');
10577	}
10578
10579	exports.left = function(str){
10580	  return str.replace(/^\s*/, '');
10581	};
10582
10583	exports.right = function(str){
10584	  return str.replace(/\s*$/, '');
10585	};
10586
10587	},{}],24:[function(require,module,exports){
10588	var trim = require('trim')
10589	  , forEach = require('for-each')
10590	  , isArray = function(arg) {
10591	      return Object.prototype.toString.call(arg) === '[object Array]';
10592	    }
10593
10594	module.exports = function (headers) {
10595	  if (!headers)
10596	    return {}
10597
10598	  var result = {}
10599
10600	  forEach(
10601	      trim(headers).split('\n')
10602	    , function (row) {
10603	        var index = row.indexOf(':')
10604	          , key = trim(row.slice(0, index)).toLowerCase()
10605	          , value = trim(row.slice(index + 1))
10606
10607	        if (typeof(result[key]) === 'undefined') {
10608	          result[key] = value
10609	        } else if (isArray(result[key])) {
10610	          result[key].push(value)
10611	        } else {
10612	          result[key] = [ result[key], value ]
10613	        }
10614	      }
10615	  )
10616
10617	  return result
10618	}
10619	},{"for-each":22,"trim":23}],25:[function(require,module,exports){
10620	arguments[4][9][0].apply(exports,arguments)
10621	},{"dup":9}],26:[function(require,module,exports){
10622	/* eslint-disable no-unused-vars */
10623	'use strict';
10624	var hasOwnProperty = Object.prototype.hasOwnProperty;
10625	var propIsEnumerable = Object.prototype.propertyIsEnumerable;
10626
10627	function toObject(val) {
10628	  if (val === null || val === undefined) {
10629	    throw new TypeError('Object.assign cannot be called with null or undefined');
10630	  }
10631
10632	  return Object(val);
10633	}
10634
10635	module.exports = Object.assign || function (target, source) {
10636	  var from;
10637	  var to = toObject(target);
10638	  var symbols;
10639
10640	  for (var s = 1; s < arguments.length; s++) {
10641	    from = Object(arguments[s]);
10642
10643	    for (var key in from) {
10644	      if (hasOwnProperty.call(from, key)) {
10645	        to[key] = from[key];
10646	      }
10647	    }
10648
10649	    if (Object.getOwnPropertySymbols) {
10650	      symbols = Object.getOwnPropertySymbols(from);
10651	      for (var i = 0; i < symbols.length; i++) {
10652	        if (propIsEnumerable.call(from, symbols[i])) {
10653	          to[symbols[i]] = from[symbols[i]];
10654	        }
10655	      }
10656	    }
10657	  }
10658
10659	  return to;
10660	};
10661
10662	},{}],27:[function(require,module,exports){
10663	var dtype = require('dtype')
10664	var anArray = require('an-array')
10665	var isBuffer = require('is-buffer')
10666
10667	var CW = [0, 2, 3]
10668	var CCW = [2, 1, 3]
10669
10670	module.exports = function createQuadElements(array, opt) {
10671	    //if user didn't specify an output array
10672	    if (!array || !(anArray(array) || isBuffer(array))) {
10673	        opt = array || {}
10674	        array = null
10675	    }
10676
10677	    if (typeof opt === 'number') //backwards-compatible
10678	        opt = { count: opt }
10679	    else
10680	        opt = opt || {}
10681
10682	    var type = typeof opt.type === 'string' ? opt.type : 'uint16'
10683	    var count = typeof opt.count === 'number' ? opt.count : 1
10684	    var start = (opt.start || 0) 
10685
10686	    var dir = opt.clockwise !== false ? CW : CCW,
10687	        a = dir[0], 
10688	        b = dir[1],
10689	        c = dir[2]
10690
10691	    var numIndices = count * 6
10692
10693	    var indices = array || new (dtype(type))(numIndices)
10694	    for (var i = 0, j = 0; i < numIndices; i += 6, j += 4) {
10695	        var x = i + start
10696	        indices[x + 0] = j + 0
10697	        indices[x + 1] = j + 1
10698	        indices[x + 2] = j + 2
10699	        indices[x + 3] = j + a
10700	        indices[x + 4] = j + b
10701	        indices[x + 5] = j + c
10702	    }
10703	    return indices
10704	}
10705	},{"an-array":28,"dtype":29,"is-buffer":30}],28:[function(require,module,exports){
10706	var str = Object.prototype.toString
10707
10708	module.exports = anArray
10709
10710	function anArray(arr) {
10711	  return (
10712	       arr.BYTES_PER_ELEMENT
10713	    && str.call(arr.buffer) === '[object ArrayBuffer]'
10714	    || Array.isArray(arr)
10715	  )
10716	}
10717
10718	},{}],29:[function(require,module,exports){
10719	module.exports = function(dtype) {
10720	  switch (dtype) {
10721	    case 'int8':
10722	      return Int8Array
10723	    case 'int16':
10724	      return Int16Array
10725	    case 'int32':
10726	      return Int32Array
10727	    case 'uint8':
10728	      return Uint8Array
10729	    case 'uint16':
10730	      return Uint16Array
10731	    case 'uint32':
10732	      return Uint32Array
10733	    case 'float32':
10734	      return Float32Array
10735	    case 'float64':
10736	      return Float64Array
10737	    case 'array':
10738	      return Array
10739	    case 'uint8_clamped':
10740	      return Uint8ClampedArray
10741	  }
10742	}
10743
10744	},{}],30:[function(require,module,exports){
10745	/**
10746	 * Determine if an object is Buffer
10747	 *
10748	 * Author:   Feross Aboukhadijeh <[email protected]> <http://feross.org>
10749	 * License:  MIT
10750	 *
10751	 * `npm install is-buffer`
10752	 */
10753
10754	module.exports = function (obj) {
10755	  return !!(obj != null &&
10756	    (obj._isBuffer || // For Safari 5-7 (missing Object.prototype.constructor)
10757	      (obj.constructor &&
10758	      typeof obj.constructor.isBuffer === 'function' &&
10759	      obj.constructor.isBuffer(obj))
10760	    ))
10761	}
10762
10763	},{}],31:[function(require,module,exports){
10764	var flatten = require('flatten-vertex-data')
10765
10766	module.exports.attr = setAttribute
10767	module.exports.index = setIndex
10768
10769	function setIndex (geometry, data, itemSize, dtype) {
10770	  if (typeof itemSize !== 'number') itemSize = 1
10771	  if (typeof dtype !== 'number') dtype = 'uint16'
10772
10773	  var isR69 = !geometry.index && typeof geometry.setIndex !== 'function'
10774	  var attrib = isR69 ? geometry.getAttribute('index') : geometry.index
10775	  var newAttrib = updateAttribute(attrib, data, itemSize, dtype)
10776	  if (newAttrib) {
10777	    if (isR69) geometry.addAttribute('index', newAttrib)
10778	    else geometry.index = newAttrib
10779	  }
10780	}
10781
10782	function setAttribute (geometry, key, data, itemSize, dtype) {
10783	  if (typeof itemSize !== 'number') itemSize = 3
10784	  if (typeof dtype !== 'number') dtype = 'float32'
10785	  if (Array.isArray(data) &&
10786	    Array.isArray(data[0]) &&
10787	    data[0].length !== itemSize) {
10788	    throw new Error('Nested vertex array has unexpected size; expected ' +
10789	      itemSize + ' but found ' + data[0].length)
10790	  }
10791
10792	  var attrib = geometry.getAttribute(key)
10793	  var newAttrib = updateAttribute(attrib, data, itemSize, dtype)
10794	  if (newAttrib) {
10795	    geometry.addAttribute(key, newAttrib)
10796	  }
10797	}
10798
10799	function updateAttribute (attrib, data, itemSize, dtype) {
10800	  data = data || []
10801	  if (!attrib || rebuildAttribute(attrib, data, itemSize)) {
10802	    // create a new array with desired type
10803	    data = flatten(data, dtype)
10804	    attrib = new THREE.BufferAttribute(data, itemSize)
10805	    attrib.needsUpdate = true
10806	    return attrib
10807	  } else {
10808	    // copy data into the existing array
10809	    flatten(data, attrib.array)
10810	    attrib.needsUpdate = true
10811	    return null
10812	  }
10813	}
10814
10815	// Test whether the attribute needs to be re-created,
10816	// returns false if we can re-use it as-is.
10817	function rebuildAttribute (attrib, data, itemSize) {
10818	  if (attrib.itemSize !== itemSize) return true
10819	  if (!attrib.array) return true
10820	  var attribLength = attrib.array.length
10821	  if (Array.isArray(data) && Array.isArray(data[0])) {
10822	    // [ [ x, y, z ] ]
10823	    return attribLength !== data.length * itemSize
10824	  } else {
10825	    // [ x, y, z ]
10826	    return attribLength !== data.length
10827	  }
10828	}
10829
10830	},{"flatten-vertex-data":32}],32:[function(require,module,exports){
10831	/*eslint new-cap:0*/
10832	var dtype = require('dtype')
10833	module.exports = flattenVertexData
10834	function flattenVertexData (data, output, offset) {
10835	  if (!data) throw new TypeError('must specify data as first parameter')
10836	  offset = +(offset || 0) | 0
10837
10838	  if (Array.isArray(data) && Array.isArray(data[0])) {
10839	    var dim = data[0].length
10840	    var length = data.length * dim
10841
10842	    // no output specified, create a new typed array
10843	    if (!output || typeof output === 'string') {
10844	      output = new (dtype(output || 'float32'))(length + offset)
10845	    }
10846
10847	    var dstLength = output.length - offset
10848	    if (length !== dstLength) {
10849	      throw new Error('source length ' + length + ' (' + dim + 'x' + data.length + ')' +
10850	        ' does not match destination length ' + dstLength)
10851	    }
10852
10853	    for (var i = 0, k = offset; i < data.length; i++) {
10854	      for (var j = 0; j < dim; j++) {
10855	        output[k++] = data[i][j]
10856	      }
10857	    }
10858	  } else {
10859	    if (!output || typeof output === 'string') {
10860	      // no output, create a new one
10861	      var Ctor = dtype(output || 'float32')
10862	      if (offset === 0) {
10863	        output = new Ctor(data)
10864	      } else {
10865	        output = new Ctor(data.length + offset)
10866	        output.set(data, offset)
10867	      }
10868	    } else {
10869	      // store output in existing array
10870	      output.set(data, offset)
10871	    }
10872	  }
10873
10874	  return output
10875	}
10876
10877	},{"dtype":33}],33:[function(require,module,exports){
10878	arguments[4][29][0].apply(exports,arguments)
10879	},{"dup":29}],34:[function(require,module,exports){
10880	var assign = require('object-assign')
10881
10882	module.exports = function createSDFShader (opt) {
10883	  opt = opt || {}
10884	  var opacity = typeof opt.opacity === 'number' ? opt.opacity : 1
10885	  var alphaTest = typeof opt.alphaTest === 'number' ? opt.alphaTest : 0.0001
10886	  var precision = opt.precision || 'highp'
10887	  var color = opt.color
10888	  var map = opt.map
10889
10890	  // remove to satisfy r73
10891	  delete opt.map
10892	  delete opt.color
10893	  delete opt.precision
10894	  delete opt.opacity
10895
10896	  return assign({
10897	    uniforms: {
10898	      opacity: { type: 'f', value: opacity },
10899	      map: { type: 't', value: map || new THREE.Texture() },
10900	      color: { type: 'c', value: new THREE.Color(color) }
10901	    },
10902	    vertexShader: [
10903	      'attribute vec2 uv;',
10904	      'attribute vec4 position;',
10905	      'uniform mat4 projectionMatrix;',
10906	      'uniform mat4 modelViewMatrix;',
10907	      'varying vec2 vUv;',
10908	      'void main() {',
10909	      'vUv = uv;',
10910	      'gl_Position = projectionMatrix * modelViewMatrix * position;',
10911	      '}'
10912	    ].join('\n'),
10913	    fragmentShader: [
10914	      '#ifdef GL_OES_standard_derivatives',
10915	      '#extension GL_OES_standard_derivatives : enable',
10916	      '#endif',
10917	      'precision ' + precision + ' float;',
10918	      'uniform float opacity;',
10919	      'uniform vec3 color;',
10920	      'uniform sampler2D map;',
10921	      'varying vec2 vUv;',
10922
10923	      'float aastep(float value) {',
10924	      '  #ifdef GL_OES_standard_derivatives',
10925	      '    float afwidth = length(vec2(dFdx(value), dFdy(value))) * 0.70710678118654757;',
10926	      '  #else',
10927	      '    float afwidth = (1.0 / 32.0) * (1.4142135623730951 / (2.0 * gl_FragCoord.w));',
10928	      '  #endif',
10929	      '  return smoothstep(0.5 - afwidth, 0.5 + afwidth, value);',
10930	      '}',
10931
10932	      'void main() {',
10933	      '  vec4 texColor = texture2D(map, vUv);',
10934	      '  float alpha = aastep(texColor.a);',
10935	      '  gl_FragColor = vec4(color, opacity * alpha);',
10936	      alphaTest === 0
10937	        ? ''
10938	        : '  if (gl_FragColor.a < ' + alphaTest + ') discard;',
10939	      '}'
10940	    ].join('\n')
10941	  }, opt)
10942	}
10943
10944	},{"object-assign":26}],35:[function(require,module,exports){
10945	var loadFont = require('load-bmfont')
10946	//global.THREE = require('three')
10947
10948	// A utility to load a font, then a texture
10949	module.exports = function (opt, cb) {
10950	  loadFont(opt.font, function (err, font) {
10951	    if (err) throw err
10952		PANOLENS.Utils.TextureLoader.load( opt.image, function (tex) {
10953	      cb(font, tex)
10954	    } );
10955	  })
10956	}
10957
10958	},{"load-bmfont":10}],36:[function(require,module,exports){
10959
10960	var createText = require('../')
10961	var SDFShader = require('../shaders/sdf')
10962
10963	if ( PANOLENS && PANOLENS.Utils && PANOLENS.SpriteText ) {
10964		PANOLENS.Utils.loadBMFont = function(fontObject, callback){
10965			require('./load')(fontObject, PANOLENS.SpriteText.prototype.setBMFont.bind(PANOLENS.SpriteText.prototype, callback));
10966		};
10967		PANOLENS.SpriteText.prototype.generateTextGeometry = createText;
10968		PANOLENS.SpriteText.prototype.generateSDFShader = SDFShader;
10969	}
10970
10971	},{"../":1,"../shaders/sdf":34,"./load":35}],37:[function(require,module,exports){
10972	(function (global){
10973	/*!
10974	 * The buffer module from node.js, for the browser.
10975	 *
10976	 * @author   Feross Aboukhadijeh <[email protected]> <http://feross.org>
10977	 * @license  MIT
10978	 */
10979	/* eslint-disable no-proto */
10980
10981	'use strict'
10982
10983	var base64 = require('base64-js')
10984	var ieee754 = require('ieee754')
10985	var isArray = require('isarray')
10986
10987	exports.Buffer = Buffer
10988	exports.SlowBuffer = SlowBuffer
10989	exports.INSPECT_MAX_BYTES = 50
10990	Buffer.poolSize = 8192 // not used by this implementation
10991
10992	var rootParent = {}
10993
10994	/**
10995	 * If `Buffer.TYPED_ARRAY_SUPPORT`:
10996	 *   === true    Use Uint8Array implementation (fastest)
10997	 *   === false   Use Object implementation (most compatible, even IE6)
10998	 *
10999	 * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+,
11000	 * Opera 11.6+, iOS 4.2+.
11001	 *
11002	 * Due to various browser bugs, sometimes the Object implementation will be used even
11003	 * when the browser supports typed arrays.
11004	 *
11005	 * Note:
11006	 *
11007	 *   - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances,
11008	 *     See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438.
11009	 *
11010	 *   - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function.
11011	 *
11012	 *   - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of
11013	 *     incorrect length in some situations.
11014
11015	 * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they
11016	 * get the Object implementation, which is slower but behaves correctly.
11017	 */
11018	Buffer.TYPED_ARRAY_SUPPORT = global.TYPED_ARRAY_SUPPORT !== undefined
11019	  ? global.TYPED_ARRAY_SUPPORT
11020	  : typedArraySupport()
11021
11022	function typedArraySupport () {
11023	  try {
11024	    var arr = new Uint8Array(1)
11025	    arr.foo = function () { return 42 }
11026	    return arr.foo() === 42 && // typed array instances can be augmented
11027	        typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray`
11028	        arr.subarray(1, 1).byteLength === 0 // ie10 has broken `subarray`
11029	  } catch (e) {
11030	    return false
11031	  }
11032	}
11033
11034	function kMaxLength () {
11035	  return Buffer.TYPED_ARRAY_SUPPORT
11036	    ? 0x7fffffff
11037	    : 0x3fffffff
11038	}
11039
11040	/**
11041	 * Class: Buffer
11042	 * =============
11043	 *
11044	 * The Buffer constructor returns instances of `Uint8Array` that are augmented
11045	 * with function properties for all the node `Buffer` API functions. We use
11046	 * `Uint8Array` so that square bracket notation works as expected -- it returns
11047	 * a single octet.
11048	 *
11049	 * By augmenting the instances, we can avoid modifying the `Uint8Array`
11050	 * prototype.
11051	 */
11052	function Buffer (arg) {
11053	  if (!(this instanceof Buffer)) {
11054	    // Avoid going through an ArgumentsAdaptorTrampoline in the common case.
11055	    if (arguments.length > 1) return new Buffer(arg, arguments[1])
11056	    return new Buffer(arg)
11057	  }
11058
11059	  if (!Buffer.TYPED_ARRAY_SUPPORT) {
11060	    this.length = 0
11061	    this.parent = undefined
11062	  }
11063
11064	  // Common case.
11065	  if (typeof arg === 'number') {
11066	    return fromNumber(this, arg)
11067	  }
11068
11069	  // Slightly less common case.
11070	  if (typeof arg === 'string') {
11071	    return fromString(this, arg, arguments.length > 1 ? arguments[1] : 'utf8')
11072	  }
11073
11074	  // Unusual.
11075	  return fromObject(this, arg)
11076	}
11077
11078	function fromNumber (that, length) {
11079	  that = allocate(that, length < 0 ? 0 : checked(length) | 0)
11080	  if (!Buffer.TYPED_ARRAY_SUPPORT) {
11081	    for (var i = 0; i < length; i++) {
11082	      that[i] = 0
11083	    }
11084	  }
11085	  return that
11086	}
11087
11088	function fromString (that, string, encoding) {
11089	  if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8'
11090
11091	  // Assumption: byteLength() return value is always < kMaxLength.
11092	  var length = byteLength(string, encoding) | 0
11093	  that = allocate(that, length)
11094
11095	  that.write(string, encoding)
11096	  return that
11097	}
11098
11099	function fromObject (that, object) {
11100	  if (Buffer.isBuffer(object)) return fromBuffer(that, object)
11101
11102	  if (isArray(object)) return fromArray(that, object)
11103
11104	  if (object == null) {
11105	    throw new TypeError('must start with number, buffer, array or string')
11106	  }
11107
11108	  if (typeof ArrayBuffer !== 'undefined') {
11109	    if (object.buffer instanceof ArrayBuffer) {
11110	      return fromTypedArray(that, object)
11111	    }
11112	    if (object instanceof ArrayBuffer) {
11113	      return fromArrayBuffer(that, object)
11114	    }
11115	  }
11116
11117	  if (object.length) return fromArrayLike(that, object)
11118
11119	  return fromJsonObject(that, object)
11120	}
11121
11122	function fromBuffer (that, buffer) {
11123	  var length = checked(buffer.length) | 0
11124	  that = allocate(that, length)
11125	  buffer.copy(that, 0, 0, length)
11126	  return that
11127	}
11128
11129	function fromArray (that, array) {
11130	  var length = checked(array.length) | 0
11131	  that = allocate(that, length)
11132	  for (var i = 0; i < length; i += 1) {
11133	    that[i] = array[i] & 255
11134	  }
11135	  return that
11136	}
11137
11138	// Duplicate of fromArray() to keep fromArray() monomorphic.
11139	function fromTypedArray (that, array) {
11140	  var length = checked(array.length) | 0
11141	  that = allocate(that, length)
11142	  // Truncating the elements is probably not what people expect from typed
11143	  // arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior
11144	  // of the old Buffer constructor.
11145	  for (var i = 0; i < length; i += 1) {
11146	    that[i] = array[i] & 255
11147	  }
11148	  return that
11149	}
11150
11151	function fromArrayBuffer (that, array) {
11152	  //array.byteLength // this throws if `array` is not a valid ArrayBuffer
11153
11154	  if (Buffer.TYPED_ARRAY_SUPPORT) {
11155	    // Return an augmented `Uint8Array` instance, for best performance
11156	    that = new Uint8Array(array)
11157	    that.__proto__ = Buffer.prototype
11158	  } else {
11159	    // Fallback: Return an object instance of the Buffer class
11160	    that = fromTypedArray(that, new Uint8Array(array))
11161	  }
11162	  return that
11163	}
11164
11165	function fromArrayLike (that, array) {
11166	  var length = checked(array.length) | 0
11167	  that = allocate(that, length)
11168	  for (var i = 0; i < length; i += 1) {
11169	    that[i] = array[i] & 255
11170	  }
11171	  return that
11172	}
11173
11174	// Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object.
11175	// Returns a zero-length buffer for inputs that don't conform to the spec.
11176	function fromJsonObject (that, object) {
11177	  var array
11178	  var length = 0
11179
11180	  if (object.type === 'Buffer' && isArray(object.data)) {
11181	    array = object.data
11182	    length = checked(array.length) | 0
11183	  }
11184	  that = allocate(that, length)
11185
11186	  for (var i = 0; i < length; i += 1) {
11187	    that[i] = array[i] & 255
11188	  }
11189	  return that
11190	}
11191
11192	if (Buffer.TYPED_ARRAY_SUPPORT) {
11193	  Buffer.prototype.__proto__ = Uint8Array.prototype
11194	  Buffer.__proto__ = Uint8Array
11195	} else {
11196	  // pre-set for values that may exist in the future
11197	  Buffer.prototype.length = undefined
11198	  Buffer.prototype.parent = undefined
11199	}
11200
11201	function allocate (that, length) {
11202	  if (Buffer.TYPED_ARRAY_SUPPORT) {
11203	    // Return an augmented `Uint8Array` instance, for best performance
11204	    that = new Uint8Array(length)
11205	    that.__proto__ = Buffer.prototype
11206	  } else {
11207	    // Fallback: Return an object instance of the Buffer class
11208	    that.length = length
11209	  }
11210
11211	  var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1
11212	  if (fromPool) that.parent = rootParent
11213
11214	  return that
11215	}
11216
11217	function checked (length) {
11218	  // Note: cannot use `length < kMaxLength` here because that fails when
11219	  // length is NaN (which is otherwise coerced to zero.)
11220	  if (length >= kMaxLength()) {
11221	    throw new RangeError('Attempt to allocate Buffer larger than maximum ' +
11222	                         'size: 0x' + kMaxLength().toString(16) + ' bytes')
11223	  }
11224	  return length | 0
11225	}
11226
11227	function SlowBuffer (subject, encoding) {
11228	  if (!(this instanceof SlowBuffer)) return new SlowBuffer(subject, encoding)
11229
11230	  var buf = new Buffer(subject, encoding)
11231	  delete buf.parent
11232	  return buf
11233	}
11234
11235	Buffer.isBuffer = function isBuffer (b) {
11236	  return !!(b != null && b._isBuffer)
11237	}
11238
11239	Buffer.compare = function compare (a, b) {
11240	  if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) {
11241	    throw new TypeError('Arguments must be Buffers')
11242	  }
11243
11244	  if (a === b) return 0
11245
11246	  var x = a.length
11247	  var y = b.length
11248
11249	  var i = 0
11250	  var len = Math.min(x, y)
11251	  while (i < len) {
11252	    if (a[i] !== b[i]) break
11253
11254	    ++i
11255	  }
11256
11257	  if (i !== len) {
11258	    x = a[i]
11259	    y = b[i]
11260	  }
11261
11262	  if (x < y) return -1
11263	  if (y < x) return 1
11264	  return 0
11265	}
11266
11267	Buffer.isEncoding = function isEncoding (encoding) {
11268	  switch (String(encoding).toLowerCase()) {
11269	    case 'hex':
11270	    case 'utf8':
11271	    case 'utf-8':
11272	    case 'ascii':
11273	    case 'binary':
11274	    case 'base64':
11275	    case 'raw':
11276	    case 'ucs2':
11277	    case 'ucs-2':
11278	    case 'utf16le':
11279	    case 'utf-16le':
11280	      return true
11281	    default:
11282	      return false
11283	  }
11284	}
11285
11286	Buffer.concat = function concat (list, length) {
11287	  if (!isArray(list)) throw new TypeError('list argument must be an Array of Buffers.')
11288
11289	  if (list.length === 0) {
11290	    return new Buffer(0)
11291	  }
11292
11293	  var i
11294	  if (length === undefined) {
11295	    length = 0
11296	    for (i = 0; i < list.length; i++) {
11297	      length += list[i].length
11298	    }
11299	  }
11300
11301	  var buf = new Buffer(length)
11302	  var pos = 0
11303	  for (i = 0; i < list.length; i++) {
11304	    var item = list[i]
11305	    item.copy(buf, pos)
11306	    pos += item.length
11307	  }
11308	  return buf
11309	}
11310
11311	function byteLength (string, encoding) {
11312	  if (typeof string !== 'string') string = '' + string
11313
11314	  var len = string.length
11315	  if (len === 0) return 0
11316
11317	  // Use a for loop to avoid recursion
11318	  var loweredCase = false
11319	  for (;;) {
11320	    switch (encoding) {
11321	      case 'ascii':
11322	      case 'binary':
11323	      // Deprecated
11324	      case 'raw':
11325	      case 'raws':
11326	        return len
11327	      case 'utf8':
11328	      case 'utf-8':
11329	        return utf8ToBytes(string).length
11330	      case 'ucs2':
11331	      case 'ucs-2':
11332	      case 'utf16le':
11333	      case 'utf-16le':
11334	        return len * 2
11335	      case 'hex':
11336	        return len >>> 1
11337	      case 'base64':
11338	        return base64ToBytes(string).length
11339	      default:
11340	        if (loweredCase) return utf8ToBytes(string).length // assume utf8
11341	        encoding = ('' + encoding).toLowerCase()
11342	        loweredCase = true
11343	    }
11344	  }
11345	}
11346	Buffer.byteLength = byteLength
11347
11348	function slowToString (encoding, start, end) {
11349	  var loweredCase = false
11350
11351	  start = start | 0
11352	  end = end === undefined || end === Infinity ? this.length : end | 0
11353
11354	  if (!encoding) encoding = 'utf8'
11355	  if (start < 0) start = 0
11356	  if (end > this.length) end = this.length
11357	  if (end <= start) return ''
11358
11359	  while (true) {
11360	    switch (encoding) {
11361	      case 'hex':
11362	        return hexSlice(this, start, end)
11363
11364	      case 'utf8':
11365	      case 'utf-8':
11366	        return utf8Slice(this, start, end)
11367
11368	      case 'ascii':
11369	        return asciiSlice(this, start, end)
11370
11371	      case 'binary':
11372	        return binarySlice(this, start, end)
11373
11374	      case 'base64':
11375	        return base64Slice(this, start, end)
11376
11377	      case 'ucs2':
11378	      case 'ucs-2':
11379	      case 'utf16le':
11380	      case 'utf-16le':
11381	        return utf16leSlice(this, start, end)
11382
11383	      default:
11384	        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
11385	        encoding = (encoding + '').toLowerCase()
11386	        loweredCase = true
11387	    }
11388	  }
11389	}
11390
11391	// Even though this property is private, it shouldn't be removed because it is
11392	// used by `is-buffer` to detect buffer instances in Safari 5-7.
11393	Buffer.prototype._isBuffer = true
11394
11395	Buffer.prototype.toString = function toString () {
11396	  var length = this.length | 0
11397	  if (length === 0) return ''
11398	  if (arguments.length === 0) return utf8Slice(this, 0, length)
11399	  return slowToString.apply(this, arguments)
11400	}
11401
11402	Buffer.prototype.equals = function equals (b) {
11403	  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
11404	  if (this === b) return true
11405	  return Buffer.compare(this, b) === 0
11406	}
11407
11408	Buffer.prototype.inspect = function inspect () {
11409	  var str = ''
11410	  var max = exports.INSPECT_MAX_BYTES
11411	  if (this.length > 0) {
11412	    str = this.toString('hex', 0, max).match(/.{2}/g).join(' ')
11413	    if (this.length > max) str += ' ... '
11414	  }
11415	  return '<Buffer ' + str + '>'
11416	}
11417
11418	Buffer.prototype.compare = function compare (b) {
11419	  if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer')
11420	  if (this === b) return 0
11421	  return Buffer.compare(this, b)
11422	}
11423
11424	Buffer.prototype.indexOf = function indexOf (val, byteOffset) {
11425	  if (byteOffset > 0x7fffffff) byteOffset = 0x7fffffff
11426	  else if (byteOffset < -0x80000000) byteOffset = -0x80000000
11427	  byteOffset >>= 0
11428
11429	  if (this.length === 0) return -1
11430	  if (byteOffset >= this.length) return -1
11431
11432	  // Negative offsets start from the end of the buffer
11433	  if (byteOffset < 0) byteOffset = Math.max(this.length + byteOffset, 0)
11434
11435	  if (typeof val === 'string') {
11436	    if (val.length === 0) return -1 // special case: looking for empty string always fail
vendor: 7,009 bytes, lines 11436-11661
11436s
11437	    return String.prototype.indexOf.call(this, val, byteOffset)
11438	  }
11439	  if (Buffer.isBuffer(val)) {
11440	    return arrayIndexOf(this, val, byteOffset)
11441	  }
11442	  if (typeof val === 'number') {
11443	    if (Buffer.TYPED_ARRAY_SUPPORT && Uint8Array.prototype.indexOf === 'function') {
11444	      return Uint8Array.prototype.indexOf.call(this, val, byteOffset)
11445	    }
11446	    return arrayIndexOf(this, [ val ], byteOffset)
11447	  }
11448
11449	  function arrayIndexOf (arr, val, byteOffset) {
11450	    var foundIndex = -1
11451	    for (var i = 0; byteOffset + i < arr.length; i++) {
11452	      if (arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex]) {
11453	        if (foundIndex === -1) foundIndex = i
11454	        if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex
11455	      } else {
11456	        foundIndex = -1
11457	      }
11458	    }
11459	    return -1
11460	  }
11461
11462	  throw new TypeError('val must be string, number or Buffer')
11463	}
11464
11465	function hexWrite (buf, string, offset, length) {
11466	  offset = Number(offset) || 0
11467	  var remaining = buf.length - offset
11468	  if (!length) {
11469	    length = remaining
11470	  } else {
11471	    length = Number(length)
11472	    if (length > remaining) {
11473	      length = remaining
11474	    }
11475	  }
11476
11477	  // must be an even number of digits
11478	  var strLen = string.length
11479	  if (strLen % 2 !== 0) throw new Error('Invalid hex string')
11480
11481	  if (length > strLen / 2) {
11482	    length = strLen / 2
11483	  }
11484	  for (var i = 0; i < length; i++) {
11485	    var parsed = parseInt(string.substr(i * 2, 2), 16)
11486	    if (isNaN(parsed)) throw new Error('Invalid hex string')
11487	    buf[offset + i] = parsed
11488	  }
11489	  return i
11490	}
11491
11492	function utf8Write (buf, string, offset, length) {
11493	  return blitBuffer(utf8ToBytes(string, buf.length - offset), buf, offset, length)
11494	}
11495
11496	function asciiWrite (buf, string, offset, length) {
11497	  return blitBuffer(asciiToBytes(string), buf, offset, length)
11498	}
11499
11500	function binaryWrite (buf, string, offset, length) {
11501	  return asciiWrite(buf, string, offset, length)
11502	}
11503
11504	function base64Write (buf, string, offset, length) {
11505	  return blitBuffer(base64ToBytes(string), buf, offset, length)
11506	}
11507
11508	function ucs2Write (buf, string, offset, length) {
11509	  return blitBuffer(utf16leToBytes(string, buf.length - offset), buf, offset, length)
11510	}
11511
11512	Buffer.prototype.write = function write (string, offset, length, encoding) {
11513	  // Buffer#write(string)
11514	  if (offset === undefined) {
11515	    encoding = 'utf8'
11516	    length = this.length
11517	    offset = 0
11518	  // Buffer#write(string, encoding)
11519	  } else if (length === undefined && typeof offset === 'string') {
11520	    encoding = offset
11521	    length = this.length
11522	    offset = 0
11523	  // Buffer#write(string, offset[, length][, encoding])
11524	  } else if (isFinite(offset)) {
11525	    offset = offset | 0
11526	    if (isFinite(length)) {
11527	      length = length | 0
11528	      if (encoding === undefined) encoding = 'utf8'
11529	    } else {
11530	      encoding = length
11531	      length = undefined
11532	    }
11533	  // legacy write(string, encoding, offset, length) - remove in v0.13
11534	  } else {
11535	    var swap = encoding
11536	    encoding = offset
11537	    offset = length | 0
11538	    length = swap
11539	  }
11540
11541	  var remaining = this.length - offset
11542	  if (length === undefined || length > remaining) length = remaining
11543
11544	  if ((string.length > 0 && (length < 0 || offset < 0)) || offset > this.length) {
11545	    throw new RangeError('attempt to write outside buffer bounds')
11546	  }
11547
11548	  if (!encoding) encoding = 'utf8'
11549
11550	  var loweredCase = false
11551	  for (;;) {
11552	    switch (encoding) {
11553	      case 'hex':
11554	        return hexWrite(this, string, offset, length)
11555
11556	      case 'utf8':
11557	      case 'utf-8':
11558	        return utf8Write(this, string, offset, length)
11559
11560	      case 'ascii':
11561	        return asciiWrite(this, string, offset, length)
11562
11563	      case 'binary':
11564	        return binaryWrite(this, string, offset, length)
11565
11566	      case 'base64':
11567	        // Warning: maxLength not taken into account in base64Write
11568	        return base64Write(this, string, offset, length)
11569
11570	      case 'ucs2':
11571	      case 'ucs-2':
11572	      case 'utf16le':
11573	      case 'utf-16le':
11574	        return ucs2Write(this, string, offset, length)
11575
11576	      default:
11577	        if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding)
11578	        encoding = ('' + encoding).toLowerCase()
11579	        loweredCase = true
11580	    }
11581	  }
11582	}
11583
11584	Buffer.prototype.toJSON = function toJSON () {
11585	  return {
11586	    type: 'Buffer',
11587	    data: Array.prototype.slice.call(this._arr || this, 0)
11588	  }
11589	}
11590
11591	function base64Slice (buf, start, end) {
11592	  if (start === 0 && end === buf.length) {
11593	    return base64.fromByteArray(buf)
11594	  } else {
11595	    return base64.fromByteArray(buf.slice(start, end))
11596	  }
11597	}
11598
11599	function utf8Slice (buf, start, end) {
11600	  end = Math.min(buf.length, end)
11601	  var res = []
11602
11603	  var i = start
11604	  while (i < end) {
11605	    var firstByte = buf[i]
11606	    var codePoint = null
11607	    var bytesPerSequence = (firstByte > 0xEF) ? 4
11608	      : (firstByte > 0xDF) ? 3
11609	      : (firstByte > 0xBF) ? 2
11610	      : 1
11611
11612	    if (i + bytesPerSequence <= end) {
11613	      var secondByte, thirdByte, fourthByte, tempCodePoint
11614
11615	      switch (bytesPerSequence) {
11616	        case 1:
11617	          if (firstByte < 0x80) {
11618	            codePoint = firstByte
11619	          }
11620	          break
11621	        case 2:
11622	          secondByte = buf[i + 1]
11623	          if ((secondByte & 0xC0) === 0x80) {
11624	            tempCodePoint = (firstByte & 0x1F) << 0x6 | (secondByte & 0x3F)
11625	            if (tempCodePoint > 0x7F) {
11626	              codePoint = tempCodePoint
11627	            }
11628	          }
11629	          break
11630	        case 3:
11631	          secondByte = buf[i + 1]
11632	          thirdByte = buf[i + 2]
11633	          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80) {
11634	            tempCodePoint = (firstByte & 0xF) << 0xC | (secondByte & 0x3F) << 0x6 | (thirdByte & 0x3F)
11635	            if (tempCodePoint > 0x7FF && (tempCodePoint < 0xD800 || tempCodePoint > 0xDFFF)) {
11636	              codePoint = tempCodePoint
11637	            }
11638	          }
11639	          break
11640	        case 4:
11641	          secondByte = buf[i + 1]
11642	          thirdByte = buf[i + 2]
11643	          fourthByte = buf[i + 3]
11644	          if ((secondByte & 0xC0) === 0x80 && (thirdByte & 0xC0) === 0x80 && (fourthByte & 0xC0) === 0x80) {
11645	            tempCodePoint = (firstByte & 0xF) << 0x12 | (secondByte & 0x3F) << 0xC | (thirdByte & 0x3F) << 0x6 | (fourthByte & 0x3F)
11646	            if (tempCodePoint > 0xFFFF && tempCodePoint < 0x110000) {
11647	              codePoint = tempCodePoint
11648	            }
11649	          }
11650	      }
11651	    }
11652
11653	    if (codePoint === null) {
11654	      // we did not generate a valid codePoint so insert a
11655	      // replacement char (U+FFFD) and advance only 1 byte
11656	      codePoint = 0xFFFD
11657	      bytesPerSequence = 1
11658	    } else if (codePoint > 0xFFFF) {
11659	      // encode to utf16 (surrogate pair dance)
11660	      codePoint -= 0x10000
11661	      res.push(codePoint >>>
vendor: 2,699 bytes, lines 11661-11777
11661 10 & 0x3FF | 0xD800)
11662	      codePoint = 0xDC00 | codePoint & 0x3FF
11663	    }
11664
11665	    res.push(codePoint)
11666	    i += bytesPerSequence
11667	  }
11668
11669	  return decodeCodePointsArray(res)
11670	}
11671
11672	// Based on http://stackoverflow.com/a/22747272/680742, the browser with
11673	// the lowest limit is Chrome, with 0x10000 args.
11674	// We go 1 magnitude less, for safety
11675	var MAX_ARGUMENTS_LENGTH = 0x1000
11676
11677	function decodeCodePointsArray (codePoints) {
11678	  var len = codePoints.length
11679	  if (len <= MAX_ARGUMENTS_LENGTH) {
11680	    return String.fromCharCode.apply(String, codePoints) // avoid extra slice()
11681	  }
11682
11683	  // Decode in chunks to avoid "call stack size exceeded".
11684	  var res = ''
11685	  var i = 0
11686	  while (i < len) {
11687	    res += String.fromCharCode.apply(
11688	      String,
11689	      codePoints.slice(i, i += MAX_ARGUMENTS_LENGTH)
11690	    )
11691	  }
11692	  return res
11693	}
11694
11695	function asciiSlice (buf, start, end) {
11696	  var ret = ''
11697	  end = Math.min(buf.length, end)
11698
11699	  for (var i = start; i < end; i++) {
11700	    ret += String.fromCharCode(buf[i] & 0x7F)
11701	  }
11702	  return ret
11703	}
11704
11705	function binarySlice (buf, start, end) {
11706	  var ret = ''
11707	  end = Math.min(buf.length, end)
11708
11709	  for (var i = start; i < end; i++) {
11710	    ret += String.fromCharCode(buf[i])
11711	  }
11712	  return ret
11713	}
11714
11715	function hexSlice (buf, start, end) {
11716	  var len = buf.length
11717
11718	  if (!start || start < 0) start = 0
11719	  if (!end || end < 0 || end > len) end = len
11720
11721	  var out = ''
11722	  for (var i = start; i < end; i++) {
11723	    out += toHex(buf[i])
11724	  }
11725	  return out
11726	}
11727
11728	function utf16leSlice (buf, start, end) {
11729	  var bytes = buf.slice(start, end)
11730	  var res = ''
11731	  for (var i = 0; i < bytes.length; i += 2) {
11732	    res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256)
11733	  }
11734	  return res
11735	}
11736
11737	Buffer.prototype.slice = function slice (start, end) {
11738	  var len = this.length
11739	  start = ~~start
11740	  end = end === undefined ? len : ~~end
11741
11742	  if (start < 0) {
11743	    start += len
11744	    if (start < 0) start = 0
11745	  } else if (start > len) {
11746	    start = len
11747	  }
11748
11749	  if (end < 0) {
11750	    end += len
11751	    if (end < 0) end = 0
11752	  } else if (end > len) {
11753	    end = len
11754	  }
11755
11756	  if (end < start) end = start
11757
11758	  var newBuf
11759	  if (Buffer.TYPED_ARRAY_SUPPORT) {
11760	    newBuf = this.subarray(start, end)
11761	    newBuf.__proto__ = Buffer.prototype
11762	  } else {
11763	    var sliceLen = end - start
11764	    newBuf = new Buffer(sliceLen, undefined)
11765	    for (var i = 0; i < sliceLen; i++) {
11766	      newBuf[i] = this[i + start]
11767	    }
11768	  }
11769
11770	  if (newBuf.length) newBuf.parent = this.parent || this
11771
11772	  return newBuf
11773	}
11774
11775	/*
11776	 * Need to make sure that buffer isn't trying to write out of bounds.
11777	 */
vendor: 10,339 bytes, lines 11778-12106
11778	function checkOffset (offset, ext, length) {
11779	  if ((offset % 1) !== 0 || offset < 0) throw new RangeError('offset is not uint')
11780	  if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length')
11781	}
11782
11783	Buffer.prototype.readUIntLE = function readUIntLE (offset, byteLength, noAssert) {
11784	  offset = offset | 0
11785	  byteLength = byteLength | 0
11786	  if (!noAssert) checkOffset(offset, byteLength, this.length)
11787
11788	  var val = this[offset]
11789	  var mul = 1
11790	  var i = 0
11791	  while (++i < byteLength && (mul *= 0x100)) {
11792	    val += this[offset + i] * mul
11793	  }
11794
11795	  return val
11796	}
11797
11798	Buffer.prototype.readUIntBE = function readUIntBE (offset, byteLength, noAssert) {
11799	  offset = offset | 0
11800	  byteLength = byteLength | 0
11801	  if (!noAssert) {
11802	    checkOffset(offset, byteLength, this.length)
11803	  }
11804
11805	  var val = this[offset + --byteLength]
11806	  var mul = 1
11807	  while (byteLength > 0 && (mul *= 0x100)) {
11808	    val += this[offset + --byteLength] * mul
11809	  }
11810
11811	  return val
11812	}
11813
11814	Buffer.prototype.readUInt8 = function readUInt8 (offset, noAssert) {
11815	  if (!noAssert) checkOffset(offset, 1, this.length)
11816	  return this[offset]
11817	}
11818
11819	Buffer.prototype.readUInt16LE = function readUInt16LE (offset, noAssert) {
11820	  if (!noAssert) checkOffset(offset, 2, this.length)
11821	  return this[offset] | (this[offset + 1] << 8)
11822	}
11823
11824	Buffer.prototype.readUInt16BE = function readUInt16BE (offset, noAssert) {
11825	  if (!noAssert) checkOffset(offset, 2, this.length)
11826	  return (this[offset] << 8) | this[offset + 1]
11827	}
11828
11829	Buffer.prototype.readUInt32LE = function readUInt32LE (offset, noAssert) {
11830	  if (!noAssert) checkOffset(offset, 4, this.length)
11831
11832	  return ((this[offset]) |
11833	      (this[offset + 1] << 8) |
11834	      (this[offset + 2] << 16)) +
11835	      (this[offset + 3] * 0x1000000)
11836	}
11837
11838	Buffer.prototype.readUInt32BE = function readUInt32BE (offset, noAssert) {
11839	  if (!noAssert) checkOffset(offset, 4, this.length)
11840
11841	  return (this[offset] * 0x1000000) +
11842	    ((this[offset + 1] << 16) |
11843	    (this[offset + 2] << 8) |
11844	    this[offset + 3])
11845	}
11846
11847	Buffer.prototype.readIntLE = function readIntLE (offset, byteLength, noAssert) {
11848	  offset = offset | 0
11849	  byteLength = byteLength | 0
11850	  if (!noAssert) checkOffset(offset, byteLength, this.length)
11851
11852	  var val = this[offset]
11853	  var mul = 1
11854	  var i = 0
11855	  while (++i < byteLength && (mul *= 0x100)) {
11856	    val += this[offset + i] * mul
11857	  }
11858	  mul *= 0x80
11859
11860	  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
11861
11862	  return val
11863	}
11864
11865	Buffer.prototype.readIntBE = function readIntBE (offset, byteLength, noAssert) {
11866	  offset = offset | 0
11867	  byteLength = byteLength | 0
11868	  if (!noAssert) checkOffset(offset, byteLength, this.length)
11869
11870	  var i = byteLength
11871	  var mul = 1
11872	  var val = this[offset + --i]
11873	  while (i > 0 && (mul *= 0x100)) {
11874	    val += this[offset + --i] * mul
11875	  }
11876	  mul *= 0x80
11877
11878	  if (val >= mul) val -= Math.pow(2, 8 * byteLength)
11879
11880	  return val
11881	}
11882
11883	Buffer.prototype.readInt8 = function readInt8 (offset, noAssert) {
11884	  if (!noAssert) checkOffset(offset, 1, this.length)
11885	  if (!(this[offset] & 0x80)) return (this[offset])
11886	  return ((0xff - this[offset] + 1) * -1)
11887	}
11888
11889	Buffer.prototype.readInt16LE = function readInt16LE (offset, noAssert) {
11890	  if (!noAssert) checkOffset(offset, 2, this.length)
11891	  var val = this[offset] | (this[offset + 1] << 8)
11892	  return (val & 0x8000) ? val | 0xFFFF0000 : val
11893	}
11894
11895	Buffer.prototype.readInt16BE = function readInt16BE (offset, noAssert) {
11896	  if (!noAssert) checkOffset(offset, 2, this.length)
11897	  var val = this[offset + 1] | (this[offset] << 8)
11898	  return (val & 0x8000) ? val | 0xFFFF0000 : val
11899	}
11900
11901	Buffer.prototype.readInt32LE = function readInt32LE (offset, noAssert) {
11902	  if (!noAssert) checkOffset(offset, 4, this.length)
11903
11904	  return (this[offset]) |
11905	    (this[offset + 1] << 8) |
11906	    (this[offset + 2] << 16) |
11907	    (this[offset + 3] << 24)
11908	}
11909
11910	Buffer.prototype.readInt32BE = function readInt32BE (offset, noAssert) {
11911	  if (!noAssert) checkOffset(offset, 4, this.length)
11912
11913	  return (this[offset] << 24) |
11914	    (this[offset + 1] << 16) |
11915	    (this[offset + 2] << 8) |
11916	    (this[offset + 3])
11917	}
11918
11919	Buffer.prototype.readFloatLE = function readFloatLE (offset, noAssert) {
11920	  if (!noAssert) checkOffset(offset, 4, this.length)
11921	  return ieee754.read(this, offset, true, 23, 4)
11922	}
11923
11924	Buffer.prototype.readFloatBE = function readFloatBE (offset, noAssert) {
11925	  if (!noAssert) checkOffset(offset, 4, this.length)
11926	  return ieee754.read(this, offset, false, 23, 4)
11927	}
11928
11929	Buffer.prototype.readDoubleLE = function readDoubleLE (offset, noAssert) {
11930	  if (!noAssert) checkOffset(offset, 8, this.length)
11931	  return ieee754.read(this, offset, true, 52, 8)
11932	}
11933
11934	Buffer.prototype.readDoubleBE = function readDoubleBE (offset, noAssert) {
11935	  if (!noAssert) checkOffset(offset, 8, this.length)
11936	  return ieee754.read(this, offset, false, 52, 8)
11937	}
11938
11939	function checkInt (buf, value, offset, ext, max, min) {
11940	  if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance')
11941	  if (value > max || value < min) throw new RangeError('value is out of bounds')
11942	  if (offset + ext > buf.length) throw new RangeError('index out of range')
11943	}
11944
11945	Buffer.prototype.writeUIntLE = function writeUIntLE (value, offset, byteLength, noAssert) {
11946	  value = +value
11947	  offset = offset | 0
11948	  byteLength = byteLength | 0
11949	  if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)
11950
11951	  var mul = 1
11952	  var i = 0
11953	  this[offset] = value & 0xFF
11954	  while (++i < byteLength && (mul *= 0x100)) {
11955	    this[offset + i] = (value / mul) & 0xFF
11956	  }
11957
11958	  return offset + byteLength
11959	}
11960
11961	Buffer.prototype.writeUIntBE = function writeUIntBE (value, offset, byteLength, noAssert) {
11962	  value = +value
11963	  offset = offset | 0
11964	  byteLength = byteLength | 0
11965	  if (!noAssert) checkInt(this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0)
11966
11967	  var i = byteLength - 1
11968	  var mul = 1
11969	  this[offset + i] = value & 0xFF
11970	  while (--i >= 0 && (mul *= 0x100)) {
11971	    this[offset + i] = (value / mul) & 0xFF
11972	  }
11973
11974	  return offset + byteLength
11975	}
11976
11977	Buffer.prototype.writeUInt8 = function writeUInt8 (value, offset, noAssert) {
11978	  value = +value
11979	  offset = offset | 0
11980	  if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0)
11981	  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
11982	  this[offset] = (value & 0xff)
11983	  return offset + 1
11984	}
11985
11986	function objectWriteUInt16 (buf, value, offset, littleEndian) {
11987	  if (value < 0) value = 0xffff + value + 1
11988	  for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) {
11989	    buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>>
11990	      (littleEndian ? i : 1 - i) * 8
11991	  }
11992	}
11993
11994	Buffer.prototype.writeUInt16LE = function writeUInt16LE (value, offset, noAssert) {
11995	  value = +value
11996	  offset = offset | 0
11997	  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
11998	  if (Buffer.TYPED_ARRAY_SUPPORT) {
11999	    this[offset] = (value & 0xff)
12000	    this[offset + 1] = (value >>> 8)
12001	  } else {
12002	    objectWriteUInt16(this, value, offset, true)
12003	  }
12004	  return offset + 2
12005	}
12006
12007	Buffer.prototype.writeUInt16BE = function writeUInt16BE (value, offset, noAssert) {
12008	  value = +value
12009	  offset = offset | 0
12010	  if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0)
12011	  if (Buffer.TYPED_ARRAY_SUPPORT) {
12012	    this[offset] = (value >>> 8)
12013	    this[offset + 1] = (value & 0xff)
12014	  } else {
12015	    objectWriteUInt16(this, value, offset, false)
12016	  }
12017	  return offset + 2
12018	}
12019
12020	function objectWriteUInt32 (buf, value, offset, littleEndian) {
12021	  if (value < 0) value = 0xffffffff + value + 1
12022	  for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; i++) {
12023	    buf[offset + i] = (value >>> (littleEndian ? i : 3 - i) * 8) & 0xff
12024	  }
12025	}
12026
12027	Buffer.prototype.writeUInt32LE = function writeUInt32LE (value, offset, noAssert) {
12028	  value = +value
12029	  offset = offset | 0
12030	  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
12031	  if (Buffer.TYPED_ARRAY_SUPPORT) {
12032	    this[offset + 3] = (value >>> 24)
12033	    this[offset + 2] = (value >>> 16)
12034	    this[offset + 1] = (value >>> 8)
12035	    this[offset] = (value & 0xff)
12036	  } else {
12037	    objectWriteUInt32(this, value, offset, true)
12038	  }
12039	  return offset + 4
12040	}
12041
12042	Buffer.prototype.writeUInt32BE = function writeUInt32BE (value, offset, noAssert) {
12043	  value = +value
12044	  offset = offset | 0
12045	  if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0)
12046	  if (Buffer.TYPED_ARRAY_SUPPORT) {
12047	    this[offset] = (value >>> 24)
12048	    this[offset + 1] = (value >>> 16)
12049	    this[offset + 2] = (value >>> 8)
12050	    this[offset + 3] = (value & 0xff)
12051	  } else {
12052	    objectWriteUInt32(this, value, offset, false)
12053	  }
12054	  return offset + 4
12055	}
12056
12057	Buffer.prototype.writeIntLE = function writeIntLE (value, offset, byteLength, noAssert) {
12058	  value = +value
12059	  offset = offset | 0
12060	  if (!noAssert) {
12061	    var limit = Math.pow(2, 8 * byteLength - 1)
12062
12063	    checkInt(this, value, offset, byteLength, limit - 1, -limit)
12064	  }
12065
12066	  var i = 0
12067	  var mul = 1
12068	  var sub = value < 0 ? 1 : 0
12069	  this[offset] = value & 0xFF
12070	  while (++i < byteLength && (mul *= 0x100)) {
12071	    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
12072	  }
12073
12074	  return offset + byteLength
12075	}
12076
12077	Buffer.prototype.writeIntBE = function writeIntBE (value, offset, byteLength, noAssert) {
12078	  value = +value
12079	  offset = offset | 0
12080	  if (!noAssert) {
12081	    var limit = Math.pow(2, 8 * byteLength - 1)
12082
12083	    checkInt(this, value, offset, byteLength, limit - 1, -limit)
12084	  }
12085
12086	  var i = byteLength - 1
12087	  var mul = 1
12088	  var sub = value < 0 ? 1 : 0
12089	  this[offset + i] = value & 0xFF
12090	  while (--i >= 0 && (mul *= 0x100)) {
12091	    this[offset + i] = ((value / mul) >> 0) - sub & 0xFF
12092	  }
12093
12094	  return offset + byteLength
12095	}
12096
12097	Buffer.prototype.writeInt8 = function writeInt8 (value, offset, noAssert) {
12098	  value = +value
12099	  offset = offset | 0
12100	  if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80)
12101	  if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value)
12102	  if (value < 0) value = 0xff + value + 1
12103	  this[offset] = (value & 0xff)
12104	  return offset + 1
12105	}
12106
vendor: 4,365 bytes, lines 12107-12230
12107	Buffer.prototype.writeInt16LE = function writeInt16LE (value, offset, noAssert) {
12108	  value = +value
12109	  offset = offset | 0
12110	  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
12111	  if (Buffer.TYPED_ARRAY_SUPPORT) {
12112	    this[offset] = (value & 0xff)
12113	    this[offset + 1] = (value >>> 8)
12114	  } else {
12115	    objectWriteUInt16(this, value, offset, true)
12116	  }
12117	  return offset + 2
12118	}
12119
12120	Buffer.prototype.writeInt16BE = function writeInt16BE (value, offset, noAssert) {
12121	  value = +value
12122	  offset = offset | 0
12123	  if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000)
12124	  if (Buffer.TYPED_ARRAY_SUPPORT) {
12125	    this[offset] = (value >>> 8)
12126	    this[offset + 1] = (value & 0xff)
12127	  } else {
12128	    objectWriteUInt16(this, value, offset, false)
12129	  }
12130	  return offset + 2
12131	}
12132
12133	Buffer.prototype.writeInt32LE = function writeInt32LE (value, offset, noAssert) {
12134	  value = +value
12135	  offset = offset | 0
12136	  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
12137	  if (Buffer.TYPED_ARRAY_SUPPORT) {
12138	    this[offset] = (value & 0xff)
12139	    this[offset + 1] = (value >>> 8)
12140	    this[offset + 2] = (value >>> 16)
12141	    this[offset + 3] = (value >>> 24)
12142	  } else {
12143	    objectWriteUInt32(this, value, offset, true)
12144	  }
12145	  return offset + 4
12146	}
12147
12148	Buffer.prototype.writeInt32BE = function writeInt32BE (value, offset, noAssert) {
12149	  value = +value
12150	  offset = offset | 0
12151	  if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000)
12152	  if (value < 0) value = 0xffffffff + value + 1
12153	  if (Buffer.TYPED_ARRAY_SUPPORT) {
12154	    this[offset] = (value >>> 24)
12155	    this[offset + 1] = (value >>> 16)
12156	    this[offset + 2] = (value >>> 8)
12157	    this[offset + 3] = (value & 0xff)
12158	  } else {
12159	    objectWriteUInt32(this, value, offset, false)
12160	  }
12161	  return offset + 4
12162	}
12163
12164	function checkIEEE754 (buf, value, offset, ext, max, min) {
12165	  if (value > max || value < min) throw new RangeError('value is out of bounds')
12166	  if (offset + ext > buf.length) throw new RangeError('index out of range')
12167	  if (offset < 0) throw new RangeError('index out of range')
12168	}
12169
12170	function writeFloat (buf, value, offset, littleEndian, noAssert) {
12171	  if (!noAssert) {
12172	    checkIEEE754(buf, value, offset, 4, 3.4028234663852886e+38, -3.4028234663852886e+38)
12173	  }
12174	  ieee754.write(buf, value, offset, littleEndian, 23, 4)
12175	  return offset + 4
12176	}
12177
12178	Buffer.prototype.writeFloatLE = function writeFloatLE (value, offset, noAssert) {
12179	  return writeFloat(this, value, offset, true, noAssert)
12180	}
12181
12182	Buffer.prototype.writeFloatBE = function writeFloatBE (value, offset, noAssert) {
12183	  return writeFloat(this, value, offset, false, noAssert)
12184	}
12185
12186	function writeDouble (buf, value, offset, littleEndian, noAssert) {
12187	  if (!noAssert) {
12188	    checkIEEE754(buf, value, offset, 8, 1.7976931348623157E+308, -1.7976931348623157E+308)
12189	  }
12190	  ieee754.write(buf, value, offset, littleEndian, 52, 8)
12191	  return offset + 8
12192	}
12193
12194	Buffer.prototype.writeDoubleLE = function writeDoubleLE (value, offset, noAssert) {
12195	  return writeDouble(this, value, offset, true, noAssert)
12196	}
12197
12198	Buffer.prototype.writeDoubleBE = function writeDoubleBE (value, offset, noAssert) {
12199	  return writeDouble(this, value, offset, false, noAssert)
12200	}
12201
12202	// copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length)
12203	Buffer.prototype.copy = function copy (target, targetStart, start, end) {
12204	  if (!start) start = 0
12205	  if (!end && end !== 0) end = this.length
12206	  if (targetStart >= target.length) targetStart = target.length
12207	  if (!targetStart) targetStart = 0
12208	  if (end > 0 && end < start) end = start
12209
12210	  // Copy 0 bytes; we're done
12211	  if (end === start) return 0
12212	  if (target.length === 0 || this.length === 0) return 0
12213
12214	  // Fatal error conditions
12215	  if (targetStart < 0) {
12216	    throw new RangeError('targetStart out of bounds')
12217	  }
12218	  if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds')
12219	  if (end < 0) throw new RangeError('sourceEnd out of bounds')
12220
12221	  // Are we oob?
12222	  if (end > this.length) end = this.length
12223	  if (target.length - targetStart < end - start) {
12224	    end = target.length - targetStart + start
12225	  }
12226
12227	  var len = end - start
12228	  var i
12229
12230	  if (this === target && start < targetStart && targetStart < end) {
vendor: 5,459 bytes, lines 12231-12431
12231	    // descending copy from end
12232	    for (i = len - 1; i >= 0; i--) {
12233	      target[i + targetStart] = this[i + start]
12234	    }
12235	  } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) {
12236	    // ascending copy from start
12237	    for (i = 0; i < len; i++) {
12238	      target[i + targetStart] = this[i + start]
12239	    }
12240	  } else {
12241	    Uint8Array.prototype.set.call(
12242	      target,
12243	      this.subarray(start, start + len),
12244	      targetStart
12245	    )
12246	  }
12247
12248	  return len
12249	}
12250
12251	// fill(value, start=0, end=buffer.length)
12252	Buffer.prototype.fill = function fill (value, start, end) {
12253	  if (!value) value = 0
12254	  if (!start) start = 0
12255	  if (!end) end = this.length
12256
12257	  if (end < start) throw new RangeError('end < start')
12258
12259	  // Fill 0 bytes; we're done
12260	  if (end === start) return
12261	  if (this.length === 0) return
12262
12263	  if (start < 0 || start >= this.length) throw new RangeError('start out of bounds')
12264	  if (end < 0 || end > this.length) throw new RangeError('end out of bounds')
12265
12266	  var i
12267	  if (typeof value === 'number') {
12268	    for (i = start; i < end; i++) {
12269	      this[i] = value
12270	    }
12271	  } else {
12272	    var bytes = utf8ToBytes(value.toString())
12273	    var len = bytes.length
12274	    for (i = start; i < end; i++) {
12275	      this[i] = bytes[i % len]
12276	    }
12277	  }
12278
12279	  return this
12280	}
12281
12282	// HELPER FUNCTIONS
12283	// ================
12284
12285	var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g
12286
12287	function base64clean (str) {
12288	  // Node strips out invalid characters like \n and \t from the string, base64-js does not
12289	  str = stringtrim(str).replace(INVALID_BASE64_RE, '')
12290	  // Node converts strings with length < 2 to ''
12291	  if (str.length < 2) return ''
12292	  // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not
12293	  while (str.length % 4 !== 0) {
12294	    str = str + '='
12295	  }
12296	  return str
12297	}
12298
12299	function stringtrim (str) {
12300	  if (str.trim) return str.trim()
12301	  return str.replace(/^\s+|\s+$/g, '')
12302	}
12303
12304	function toHex (n) {
12305	  if (n < 16) return '0' + n.toString(16)
12306	  return n.toString(16)
12307	}
12308
12309	function utf8ToBytes (string, units) {
12310	  units = units || Infinity
12311	  var codePoint
12312	  var length = string.length
12313	  var leadSurrogate = null
12314	  var bytes = []
12315
12316	  for (var i = 0; i < length; i++) {
12317	    codePoint = string.charCodeAt(i)
12318
12319	    // is surrogate component
12320	    if (codePoint > 0xD7FF && codePoint < 0xE000) {
12321	      // last char was a lead
12322	      if (!leadSurrogate) {
12323	        // no lead yet
12324	        if (codePoint > 0xDBFF) {
12325	          // unexpected trail
12326	          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
12327	          continue
12328	        } else if (i + 1 === length) {
12329	          // unpaired lead
12330	          if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
12331	          continue
12332	        }
12333
12334	        // valid lead
12335	        leadSurrogate = codePoint
12336
12337	        continue
12338	      }
12339
12340	      // 2 leads in a row
12341	      if (codePoint < 0xDC00) {
12342	        if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
12343	        leadSurrogate = codePoint
12344	        continue
12345	      }
12346
12347	      // valid surrogate pair
12348	      codePoint = (leadSurrogate - 0xD800 << 10 | codePoint - 0xDC00) + 0x10000
12349	    } else if (leadSurrogate) {
12350	      // valid bmp char, but last char was a lead
12351	      if ((units -= 3) > -1) bytes.push(0xEF, 0xBF, 0xBD)
12352	    }
12353
12354	    leadSurrogate = null
12355
12356	    // encode utf8
12357	    if (codePoint < 0x80) {
12358	      if ((units -= 1) < 0) break
12359	      bytes.push(codePoint)
12360	    } else if (codePoint < 0x800) {
12361	      if ((units -= 2) < 0) break
12362	      bytes.push(
12363	        codePoint >> 0x6 | 0xC0,
12364	        codePoint & 0x3F | 0x80
12365	      )
12366	    } else if (codePoint < 0x10000) {
12367	      if ((units -= 3) < 0) break
12368	      bytes.push(
12369	        codePoint >> 0xC | 0xE0,
12370	        codePoint >> 0x6 & 0x3F | 0x80,
12371	        codePoint & 0x3F | 0x80
12372	      )
12373	    } else if (codePoint < 0x110000) {
12374	      if ((units -= 4) < 0) break
12375	      bytes.push(
12376	        codePoint >> 0x12 | 0xF0,
12377	        codePoint >> 0xC & 0x3F | 0x80,
12378	        codePoint >> 0x6 & 0x3F | 0x80,
12379	        codePoint & 0x3F | 0x80
12380	      )
12381	    } else {
12382	      throw new Error('Invalid code point')
12383	    }
12384	  }
12385
12386	  return bytes
12387	}
12388
12389	function asciiToBytes (str) {
12390	  var byteArray = []
12391	  for (var i = 0; i < str.length; i++) {
12392	    // Node's code seems to be doing this and not & 0x7F..
12393	    byteArray.push(str.charCodeAt(i) & 0xFF)
12394	  }
12395	  return byteArray
12396	}
12397
12398	function utf16leToBytes (str, units) {
12399	  var c, hi, lo
12400	  var byteArray = []
12401	  for (var i = 0; i < str.length; i++) {
12402	    if ((units -= 2) < 0) break
12403
12404	    c = str.charCodeAt(i)
12405	    hi = c >> 8
12406	    lo = c % 256
12407	    byteArray.push(lo)
12408	    byteArray.push(hi)
12409	  }
12410
12411	  return byteArray
12412	}
12413
12414	function base64ToBytes (str) {
12415	  return base64.toByteArray(base64clean(str))
12416	}
12417
12418	function blitBuffer (src, dst, offset, length) {
12419	  for (var i = 0; i < length; i++) {
12420	    if ((i + offset >= dst.length) || (i >= src.length)) break
12421	    dst[i + offset] = src[i]
12422	  }
12423	  return i
12424	}
12425
12426	}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
12427	},{"base64-js":38,"ieee754":39,"isarray":40}],38:[function(require,module,exports){
12428	;(function (exports) {
12429	  'use strict'
12430
12431	  var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
vendor: 4,088 bytes, lines 12431-12552
12431+/'
12432
12433	  var Arr = (typeof Uint8Array !== 'undefined')
12434	    ? Uint8Array
12435	    : Array
12436
12437	  var PLUS = '+'.charCodeAt(0)
12438	  var SLASH = '/'.charCodeAt(0)
12439	  var NUMBER = '0'.charCodeAt(0)
12440	  var LOWER = 'a'.charCodeAt(0)
12441	  var UPPER = 'A'.charCodeAt(0)
12442	  var PLUS_URL_SAFE = '-'.charCodeAt(0)
12443	  var SLASH_URL_SAFE = '_'.charCodeAt(0)
12444
12445	  function decode (elt) {
12446	    var code = elt.charCodeAt(0)
12447	    if (code === PLUS || code === PLUS_URL_SAFE) return 62 // '+'
12448	    if (code === SLASH || code === SLASH_URL_SAFE) return 63 // '/'
12449	    if (code < NUMBER) return -1 // no match
12450	    if (code < NUMBER + 10) return code - NUMBER + 26 + 26
12451	    if (code < UPPER + 26) return code - UPPER
12452	    if (code < LOWER + 26) return code - LOWER + 26
12453	  }
12454
12455	  function b64ToByteArray (b64) {
12456	    var i, j, l, tmp, placeHolders, arr
12457
12458	    if (b64.length % 4 > 0) {
12459	      throw new Error('Invalid string. Length must be a multiple of 4')
12460	    }
12461
12462	    // the number of equal signs (place holders)
12463	    // if there are two placeholders, than the two characters before it
12464	    // represent one byte
12465	    // if there is only one, then the three characters before it represent 2 bytes
12466	    // this is just a cheap hack to not do indexOf twice
12467	    var len = b64.length
12468	    placeHolders = b64.charAt(len - 2) === '=' ? 2 : b64.charAt(len - 1) === '=' ? 1 : 0
12469
12470	    // base64 is 4/3 + up to two characters of the original data
12471	    arr = new Arr(b64.length * 3 / 4 - placeHolders)
12472
12473	    // if there are placeholders, only get up to the last complete 4 chars
12474	    l = placeHolders > 0 ? b64.length - 4 : b64.length
12475
12476	    var L = 0
12477
12478	    function push (v) {
12479	      arr[L++] = v
12480	    }
12481
12482	    for (i = 0, j = 0; i < l; i += 4, j += 3) {
12483	      tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3))
12484	      push((tmp & 0xFF0000) >> 16)
12485	      push((tmp & 0xFF00) >> 8)
12486	      push(tmp & 0xFF)
12487	    }
12488
12489	    if (placeHolders === 2) {
12490	      tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4)
12491	      push(tmp & 0xFF)
12492	    } else if (placeHolders === 1) {
12493	      tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2)
12494	      push((tmp >> 8) & 0xFF)
12495	      push(tmp & 0xFF)
12496	    }
12497
12498	    return arr
12499	  }
12500
12501	  function uint8ToBase64 (uint8) {
12502	    var i
12503	    var extraBytes = uint8.length % 3 // if we have 1 byte left, pad 2 bytes
12504	    var output = ''
12505	    var temp, length
12506
12507	    function encode (num) {
12508	      return lookup.charAt(num)
12509	    }
12510
12511	    function tripletToBase64 (num) {
12512	      return encode(num >> 18 & 0x3F) + encode(num >> 12 & 0x3F) + encode(num >> 6 & 0x3F) + encode(num & 0x3F)
12513	    }
12514
12515	    // go through the array every three bytes, we'll deal with trailing stuff later
12516	    for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
12517	      temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2])
12518	      output += tripletToBase64(temp)
12519	    }
12520
12521	    // pad the end with zeros, but make sure to not forget the extra bytes
12522	    switch (extraBytes) {
12523	      case 1:
12524	        temp = uint8[uint8.length - 1]
12525	        output += encode(temp >> 2)
12526	        output += encode((temp << 4) & 0x3F)
12527	        output += '=='
12528	        break
12529	      case 2:
12530	        temp = (uint8[uint8.length - 2] << 8) + (uint8[uint8.length - 1])
12531	        output += encode(temp >> 10)
12532	        output += encode((temp >> 4) & 0x3F)
12533	        output += encode((temp << 2) & 0x3F)
12534	        output += '='
12535	        break
12536	      default:
12537	        break
12538	    }
12539
12540	    return output
12541	  }
12542
12543	  exports.toByteArray = b64ToByteArray
12544	  exports.fromByteArray = uint8ToBase64
12545	}(typeof exports === 'undefined' ? (this.base64js = {}) : exports))
12546
12547	},{}],39:[function(require,module,exports){
12548	exports.read = function (buffer, offset, isLE, mLen, nBytes) {
12549	  var e, m
12550	  var eLen = nBytes * 8 - mLen - 1
12551	  var eMax = (1 << eLen) - 1
12552	  var eBias = eMax >> 1
vendor: 2,246 bytes, lines 12553-12640
12553	  var nBits = -7
12554	  var i = isLE ? (nBytes - 1) : 0
12555	  var d = isLE ? -1 : 1
12556	  var s = buffer[offset + i]
12557
12558	  i += d
12559
12560	  e = s & ((1 << (-nBits)) - 1)
12561	  s >>= (-nBits)
12562	  nBits += eLen
12563	  for (; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8) {}
12564
12565	  m = e & ((1 << (-nBits)) - 1)
12566	  e >>= (-nBits)
12567	  nBits += mLen
12568	  for (; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8) {}
12569
12570	  if (e === 0) {
12571	    e = 1 - eBias
12572	  } else if (e === eMax) {
12573	    return m ? NaN : ((s ? -1 : 1) * Infinity)
12574	  } else {
12575	    m = m + Math.pow(2, mLen)
12576	    e = e - eBias
12577	  }
12578	  return (s ? -1 : 1) * m * Math.pow(2, e - mLen)
12579	}
12580
12581	exports.write = function (buffer, value, offset, isLE, mLen, nBytes) {
12582	  var e, m, c
12583	  var eLen = nBytes * 8 - mLen - 1
12584	  var eMax = (1 << eLen) - 1
12585	  var eBias = eMax >> 1
12586	  var rt = (mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0)
12587	  var i = isLE ? 0 : (nBytes - 1)
12588	  var d = isLE ? 1 : -1
12589	  var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0
12590
12591	  value = Math.abs(value)
12592
12593	  if (isNaN(value) || value === Infinity) {
12594	    m = isNaN(value) ? 1 : 0
12595	    e = eMax
12596	  } else {
12597	    e = Math.floor(Math.log(value) / Math.LN2)
12598	    if (value * (c = Math.pow(2, -e)) < 1) {
12599	      e--
12600	      c *= 2
12601	    }
12602	    if (e + eBias >= 1) {
12603	      value += rt / c
12604	    } else {
12605	      value += rt * Math.pow(2, 1 - eBias)
12606	    }
12607	    if (value * c >= 2) {
12608	      e++
12609	      c /= 2
12610	    }
12611
12612	    if (e + eBias >= eMax) {
12613	      m = 0
12614	      e = eMax
12615	    } else if (e + eBias >= 1) {
12616	      m = (value * c - 1) * Math.pow(2, mLen)
12617	      e = e + eBias
12618	    } else {
12619	      m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen)
12620	      e = 0
12621	    }
12622	  }
12623
12624	  for (; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8) {}
12625
12626	  e = (e << mLen) | m
12627	  eLen += mLen
12628	  for (; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8) {}
12629
12630	  buffer[offset + i - d] |= s * 128
12631	}
12632
12633	},{}],40:[function(require,module,exports){
12634	var toString = {}.toString;
12635
12636	module.exports = Array.isArray || function (arr) {
12637	  return toString.call(arr) == '[object Array]';
12638	};
12639
12640},{}]},{},[36]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.