import { ImagePanorama } from './ImagePanorama';
import { Infospot } from '../infospot/Infospot';
import { CONTROLS, EVENTS } from '../Constants';
import { StereographicShader } from '../shaders/StereographicShader';
import * as THREE from 'three';
/**
* @classdesc Little Planet
* @constructor
* @param {string} type - Type of little planet basic class
* @param {string} source - URL for the image source
*/
function LittlePlanet ( type = 'image', source ) {
if ( type === 'image' ) {
ImagePanorama.call( this, source );
}
this.EPS = 0.000001;
this.frameId = null;
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.type = 'little_planet';
this.addEventListener( EVENTS.WIDNOW_RESIZE, this.onWindowResize );
}
LittlePlanet.prototype = Object.assign( Object.create( ImagePanorama.prototype ), {
constructor: LittlePlanet,
add: function ( object ) {
if ( arguments.length > 1 ) {
for ( let i = 0; i < arguments.length; i ++ ) {
this.add( arguments[ i ] );
}
return this;
}
if ( object instanceof Infospot ) {
object.material.depthTest = false;
}
ImagePanorama.prototype.add.call( this, object );
},
/**
* Create a skybox geometry
* @memberOf LittlePlanet
* @instance
*/
createGeometry: function ( edgeLength ) {
const ratio = 0.5;
return new THREE.PlaneBufferGeometry( edgeLength, ratio * edgeLength );
},
/**
* Create material
* @memberOf LittlePlanet
* @instance
*/
createMaterial: function ( size = this.edgeLength ) {
const { fragmentShader, vertexShader, uniforms: _uniforms } = StereographicShader;
const uniforms = THREE.UniformsUtils.clone( _uniforms );
uniforms.zoom.value = size;
uniforms.opacity.value = 0.0;
return new THREE.ShaderMaterial( {
vertexShader,
fragmentShader,
uniforms,
transparent: true,
opacity: 0
} );
},
registerMouseEvents: function () {
this.container.addEventListener( 'mousedown', this.onMouseDown.bind( this ), { passive: true } );
this.container.addEventListener( 'mousemove', this.onMouseMove.bind( this ), { passive: true } );
this.container.addEventListener( 'mouseup', this.onMouseUp.bind( this ), { passive: true } );
this.container.addEventListener( 'touchstart', this.onMouseDown.bind( this ), { passive: true } );
this.container.addEventListener( 'touchmove', this.onMouseMove.bind( this ), { passive: true } );
this.container.addEventListener( 'touchend', this.onMouseUp.bind( this ), { passive: true } );
this.container.addEventListener( 'mousewheel', this.onMouseWheel.bind( this ), { passive: false } );
this.container.addEventListener( 'DOMMouseScroll', this.onMouseWheel.bind( this ), { passive: false } );
this.container.addEventListener( 'contextmenu', this.onContextMenu.bind( this ), { passive: true } );
},
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 );
},
onMouseDown: function ( event ) {
const inputCount = ( event.touches && event.touches.length ) || 1 ;
switch ( inputCount ) {
case 1:
const x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX;
const y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY;
this.dragging = true;
this.userMouse.set( x, y );
break;
case 2:
const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
const distance = Math.sqrt( dx * dx + dy * dy );
this.userMouse.pinchDistance = distance;
break;
default:
break;
}
this.onUpdateCallback();
},
onMouseMove: function ( event ) {
const inputCount = ( event.touches && event.touches.length ) || 1 ;
switch ( inputCount ) {
case 1:
const x = ( event.clientX >= 0 ) ? event.clientX : event.touches[ 0 ].clientX;
const y = ( event.clientY >= 0 ) ? event.clientY : event.touches[ 0 ].clientY;
const angleX = THREE.Math.degToRad( x - this.userMouse.x ) * 0.4;
const 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:
const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
const distance = Math.sqrt( dx * dx + dy * dy );
this.addZoomDelta( this.userMouse.pinchDistance - distance );
break;
default:
break;
}
},
onMouseUp: function () {
this.dragging = false;
},
onMouseWheel: function ( event ) {
event.preventDefault();
event.stopPropagation();
let 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();
},
addZoomDelta: function ( delta ) {
const uniforms = this.material.uniforms;
const lowerBound = this.size * 0.1;
const 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;
}
},
onUpdateCallback: function () {
this.frameId = window.requestAnimationFrame( this.onUpdateCallback.bind( this ) );
this.quatSlerp.slerp( this.quatCur, 0.1 );
if ( this.material ) {
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 );
}
},
reset: function () {
this.quatCur.set( 0, 0, 0, 1 );
this.quatSlerp.set( 0, 0, 0, 1 );
this.onUpdateCallback();
},
updateTexture: function ( texture ) {
this.material.uniforms.tDiffuse.value = texture;
},
getTexture: function () {
return this.material.uniforms.tDiffuse.value;
},
onLoad: function ( texture ) {
this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight;
this.registerMouseEvents();
this.onUpdateCallback();
this.dispatchEvent( { type: EVENTS.VIEWER_HANDLER, method: 'disableControl' } );
ImagePanorama.prototype.onLoad.call( this, texture );
},
onLeave: function () {
this.unregisterMouseEvents();
this.dispatchEvent( { type: EVENTS.VIEWER_HANDLER, method: 'enableControl', data: CONTROLS.ORBIT } );
window.cancelAnimationFrame( this.frameId );
ImagePanorama.prototype.onLeave.call( this );
},
onWindowResize: function () {
this.material.uniforms.resolution.value = this.container.clientWidth / this.container.clientHeight;
},
onContextMenu: function () {
this.dragging = false;
},
dispose: function () {
this.unregisterMouseEvents();
ImagePanorama.prototype.dispose.call( this );
}
});
export { LittlePlanet };