Source: panorama/LittlePlanet.js

( function () {

	/**
	 * Little Planet
	 * @constructor
	 * @param {string} type 		- Type of little planet basic class
	 * @param {string} source 		- URL for the image source
	 * @param {number} [size=10000] - Size of plane geometry
	 * @param {number} [ratio=0.5]  - Ratio of plane geometry's height against width
	 */
	PANOLENS.LittlePlanet = function ( type, source, size, ratio ) {

		type = type || 'image';

		type === 'image' && PANOLENS.ImagePanorama.call( this, source, size );

		this.size = size || 10000;
		this.ratio = ratio || 0.5;
		this.EPS = 0.000001;
		this.frameId;

		this.geometry = this.createGeometry();
		this.material = this.createMaterial( this.size );

		this.dragging = false;
		this.userMouse = new THREE.Vector2();

		this.quatA = new THREE.Quaternion();
		this.quatB = new THREE.Quaternion();
		this.quatCur = new THREE.Quaternion();
		this.quatSlerp = new THREE.Quaternion();

		this.vectorX = new THREE.Vector3( 1, 0, 0 );
		this.vectorY = new THREE.Vector3( 0, 1, 0 );

		this.addEventListener( 'window-resize', this.onWindowResize );

	};

	PANOLENS.LittlePlanet.prototype = Object.create( PANOLENS.ImagePanorama.prototype );

	PANOLENS.LittlePlanet.prototype.constructor = PANOLENS.LittlePlanet;

	PANOLENS.LittlePlanet.prototype.createGeometry = function () {

		return new THREE.PlaneGeometry( this.size, this.size * this.ratio );

	};

	PANOLENS.LittlePlanet.prototype.createMaterial = function ( size ) {

		var uniforms = PANOLENS.StereographicShader.uniforms;
		uniforms.zoom.value = size;

		return new THREE.ShaderMaterial( {

			uniforms: uniforms,
			vertexShader: PANOLENS.StereographicShader.vertexShader,
			fragmentShader: PANOLENS.StereographicShader.fragmentShader

		} );
		
	};

	PANOLENS.LittlePlanet.prototype.registerMouseEvents = function () {

		this.container.addEventListener( 'mousedown', this.onMouseDown.bind( this ), false );
		this.container.addEventListener( 'mousemove', this.onMouseMove.bind( this ), false );
		this.container.addEventListener( 'mouseup', this.onMouseUp.bind( this ), false );
		this.container.addEventListener( 'touchstart', this.onMouseDown.bind( this ), false );
		this.container.addEventListener( 'touchmove', this.onMouseMove.bind( this ), false );
		this.container.addEventListener( 'touchend', this.onMouseUp.bind( this ), false );
		this.container.addEventListener( 'mousewheel', this.onMouseWheel.bind( this ), false );
		this.container.addEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), false );
		this.container.addEventListener( 'contextmenu', this.onContextMenu.bind( this ), false );
		
	};

	PANOLENS.LittlePlanet.prototype.unregisterMouseEvents = function () {

		this.container.removeEventListener( 'mousedown', this.onMouseDown.bind( this ), false );
		this.container.removeEventListener( 'mousemove', this.onMouseMove.bind( this ), false );
		this.container.removeEventListener( 'mouseup', this.onMouseUp.bind( this ), false );
		this.container.removeEventListener( 'touchstart', this.onMouseDown.bind( this ), false );
		this.container.removeEventListener( 'touchmove', this.onMouseMove.bind( this ), false );
		this.container.removeEventListener( 'touchend', this.onMouseUp.bind( this ), false );
		this.container.removeEventListener( 'mousewheel', this.onMouseWheel.bind( this ), false );
		this.container.removeEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), false );
		this.container.removeEventListener( 'contextmenu', this.onContextMenu.bind( this ), false );
		
	};

	PANOLENS.LittlePlanet.prototype.onMouseDown = function ( event ) {

		var x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX;
		var y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY;

		var inputCount = ( event.touches && event.touches.length ) || 1 ;

		switch ( inputCount ) {

			case 1:

				this.dragging = true;
				this.userMouse.set( x, y );

				break;

			case 2:

				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
				var distance = Math.sqrt( dx * dx + dy * dy );
				this.userMouse.pinchDistance = distance;

				break;

			default:

				break;

		}

		this.onUpdateCallback();

	};

	PANOLENS.LittlePlanet.prototype.onMouseMove = function ( event ) {

		var x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX;
		var y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY;

		var inputCount = ( event.touches && event.touches.length ) || 1 ;

		switch ( inputCount ) {

			case 1:

				var angleX = THREE.Math.degToRad( x - this.userMouse.x ) * 0.4;
				var angleY = THREE.Math.degToRad( y - this.userMouse.y ) * 0.4;

				if ( this.dragging ) {
					this.quatA.setFromAxisAngle( this.vectorY, angleX );
					this.quatB.setFromAxisAngle( this.vectorX, angleY );
					this.quatCur.multiply( this.quatA ).multiply( this.quatB );
					this.userMouse.set( x, y );
				}

				break;

			case 2:

				var uniforms = this.material.uniforms;
				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
				var distance = Math.sqrt( dx * dx + dy * dy );

				this.addZoomDelta( this.userMouse.pinchDistance - distance );

				break;

			default:

				break;

		}

	};

	PANOLENS.LittlePlanet.prototype.onMouseUp = function ( event ) {

		this.dragging = false;

	};

	PANOLENS.LittlePlanet.prototype.onMouseWheel = function ( event ) {

		event.preventDefault();
		event.stopPropagation();

		var delta = 0;

		if ( event.wheelDelta !== undefined ) { // WebKit / Opera / Explorer 9

			delta = event.wheelDelta;

		} else if ( event.detail !== undefined ) { // Firefox

			delta = - event.detail;

		}

		this.addZoomDelta( delta );
		this.onUpdateCallback();

	};

	PANOLENS.LittlePlanet.prototype.addZoomDelta = function ( delta ) {

		var uniforms = this.material.uniforms;
		var lowerBound = this.size * 0.1;
		var upperBound = this.size * 10;

		uniforms.zoom.value += delta;

		if ( uniforms.zoom.value <= lowerBound ) {

			uniforms.zoom.value = lowerBound;

		} else if ( uniforms.zoom.value >= upperBound ) {

			uniforms.zoom.value = upperBound;

		}

	};

	PANOLENS.LittlePlanet.prototype.onUpdateCallback = function () {

		this.frameId = window.requestAnimationFrame( this.onUpdateCallback.bind( this ) );
		
		this.quatSlerp.slerp( this.quatCur, 0.1 );
		this.material.uniforms.transform.value.makeRotationFromQuaternion( this.quatSlerp );
		
		if ( !this.dragging && 1.0 - this.quatSlerp.clone().dot( this.quatCur ) < this.EPS ) {
			
			window.cancelAnimationFrame( this.frameId );

		}

	};

	PANOLENS.LittlePlanet.prototype.reset = function () {

		this.quatCur.set( 0, 0, 0, 1 );
		this.quatSlerp.set( 0, 0, 0, 1 );
		this.onUpdateCallback();

	};

	PANOLENS.LittlePlanet.prototype.onLoad = function () {

		this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight;

		this.registerMouseEvents();
		this.onUpdateCallback();
		
		this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'disableControl' } );
		
	};

	PANOLENS.LittlePlanet.prototype.onLeave = function () {

		this.unregisterMouseEvents();

		this.dispatchEvent( { type: 'panolens-viewer-handler', method: 'enableControl', data: PANOLENS.Controls.ORBIT } );

		window.cancelAnimationFrame( this.frameId );

		PANOLENS.Panorama.prototype.onLeave.call( this );
		
	};

	PANOLENS.LittlePlanet.prototype.onWindowResize = function () {

		this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight;

	};

	PANOLENS.LittlePlanet.prototype.onContextMenu = function () {

		this.dragging = false;

	};

})();