( function () { 'use strict'; /** * Skeleton panorama derived from THREE.Mesh * @constructor * @param {THREE.Geometry} geometry - The geometry for this panorama * @param {THREE.Material} material - The material for this panorama */ PANOLENS.Panorama = function ( geometry, material ) { THREE.Mesh.call( this ); this.type = 'panorama'; this.ImageQualityLow = 1; this.ImageQualityFair = 2; this.ImageQualityMedium = 3; this.ImageQualityHigh = 4; this.ImageQualitySuperHigh = 5; this.animationDuration = 1000; this.defaultInfospotSize = 350; this.container = undefined; this.loaded = false; this.linkedSpots = []; this.isInfospotVisible = false; this.linkingImageURL = undefined; this.linkingImageScale = undefined; this.geometry = geometry; this.material = material; this.material.side = THREE.DoubleSide; this.material.visible = false; this.scale.x *= -1; this.infospotAnimation = new TWEEN.Tween( this ).to( {}, this.animationDuration / 2 ); this.addEventListener( 'load', this.fadeIn.bind( this ) ); this.addEventListener( 'panolens-container', this.setContainer.bind( this ) ); this.addEventListener( 'click', this.onClick.bind( this ) ); this.setupTransitions(); } PANOLENS.Panorama.prototype = Object.create( THREE.Mesh.prototype ); PANOLENS.Panorama.prototype.constructor = PANOLENS.Panorama; /** * Adding an object * To counter the scale.x = -1, it will automatically add an * empty object with inverted scale on x * @param {THREE.Object3D} object - The object to be added */ PANOLENS.Panorama.prototype.add = function ( object ) { var scope, invertedObject; scope = this; if ( arguments.length > 1 ) { for ( var i = 0; i < arguments.length; i ++ ) { this.add( arguments[ i ] ); } return this; } // In case of infospots if ( object instanceof PANOLENS.Infospot ) { invertedObject = object; if ( object.dispatchEvent ) { this.container && object.dispatchEvent( { type: 'panolens-container', container: this.container } ); object.dispatchEvent( { type: 'panolens-infospot-focus', method: function ( vector, duration, easing ) { /** * Infospot focus handler event * @type {object} * @event PANOLENS.Panorama#panolens-viewer-handler * @property {string} method - Viewer function name * @property {*} data - The argument to be passed into the method */ scope.dispatchEvent( { type : 'panolens-viewer-handler', method: 'tweenControlCenter', data: [ vector, duration, easing ] } ); } } ); } } else { // Counter scale.x = -1 effect invertedObject = new THREE.Object3D(); invertedObject.scale.x = -1; invertedObject.scalePlaceHolder = true; invertedObject.add( object ); } THREE.Object3D.prototype.add.call( this, invertedObject ); }; PANOLENS.Panorama.prototype.load = function () { this.onLoad(); }; /** * Click event handler * @param {object} event - Click event * @fires PANOLENS.Infospot#dismiss */ PANOLENS.Panorama.prototype.onClick = function ( event ) { if ( event.intersects && event.intersects.length === 0 ) { this.traverse( function ( object ) { /** * Dimiss event * @type {object} * @event PANOLENS.Infospot#dismiss */ object.dispatchEvent( { type: 'dismiss' } ); } ); } }; /** * Set container of this panorama * @param {HTMLElement|object} data - Data with container information * @fires PANOLENS.Infospot#panolens-container */ PANOLENS.Panorama.prototype.setContainer = function ( data ) { var container; if ( data instanceof HTMLElement ) { container = data; } else if ( data && data.container ) { container = data.container; } if ( container ) { this.children.forEach( function ( child ) { if ( child instanceof PANOLENS.Infospot && child.dispatchEvent ) { /** * Set container event * @type {object} * @event PANOLENS.Infospot#panolens-container * @property {HTMLElement} container - The container of this panorama */ child.dispatchEvent( { type: 'panolens-container', container: container } ); } } ); this.container = container; } }; /** * This will be called when panorama is loaded * @fires PANOLENS.Panorama#load */ PANOLENS.Panorama.prototype.onLoad = function () { this.loaded = true; /** * Load panorama event * @type {object} * @event PANOLENS.Panorama#load */ this.dispatchEvent( { type: 'load' } ); }; /** * This will be called when panorama is in progress * @fires PANOLENS.Panorama#progress */ PANOLENS.Panorama.prototype.onProgress = function ( progress ) { /** * Loading panorama progress event * @type {object} * @event PANOLENS.Panorama#progress * @property {object} progress - The progress object containing loaded and total amount */ this.dispatchEvent( { type: 'progress', progress: progress } ); }; /** * This will be called when panorama loading has error * @fires PANOLENS.Panorama#error */ PANOLENS.Panorama.prototype.onError = function () { /** * Loading panorama error event * @type {object} * @event PANOLENS.Panorama#error */ this.dispatchEvent( { type: 'error' } ); }; /** * Get zoom level based on window width * @return {number} zoom level indicating image quality */ PANOLENS.Panorama.prototype.getZoomLevel = function () { var zoomLevel; if ( window.innerWidth <= 800 ) { zoomLevel = this.ImageQualityFair; } else if ( window.innerWidth > 800 && window.innerWidth <= 1280 ) { zoomLevel = this.ImageQualityMedium; } else if ( window.innerWidth > 1280 && window.innerWidth <= 1920 ) { zoomLevel = this.ImageQualityHigh; } else if ( window.innerWidth > 1920 ) { zoomLevel = this.ImageQualitySuperHigh; } else { zoomLevel = this.ImageQualityLow; } return zoomLevel; }; /** * Update texture of a panorama * @param {THREE.Texture} texture - Texture to be updated */ PANOLENS.Panorama.prototype.updateTexture = function ( texture ) { this.material.map = texture; this.material.needsUpdate = true; }; /** * Toggle visibility of infospots in this panorama * @param {boolean} isVisible - Visibility of infospots * @param {number} delay - Delay in milliseconds to change visibility * @fires PANOLENS.Panorama#infospot-animation-complete */ PANOLENS.Panorama.prototype.toggleInfospotVisibility = function ( isVisible, delay ) { delay = ( delay !== undefined ) ? delay : 0; var scope, visible; scope = this; visible = ( isVisible !== undefined ) ? isVisible : ( this.isInfospotVisible ? false : true ); this.traverse( function ( object ) { if ( object instanceof PANOLENS.Infospot ) { visible ? object.show( delay ) : object.hide( delay ); } } ); this.isInfospotVisible = visible; // Animation complete event this.infospotAnimation.onComplete( function () { /** * Complete toggling infospot visibility * @event PANOLENS.Panorama#infospot-animation-complete * @type {object} */ scope.dispatchEvent( { type : 'infospot-animation-complete', visible: visible } ); } ).delay( delay ).start(); }; /** * Set image of this panorama's linking infospot * @param {string} url - Url to the image asset * @param {number} scale - Scale factor of the infospot */ PANOLENS.Panorama.prototype.setLinkingImage = function ( url, scale ) { this.linkingImageURL = url; this.linkingImageScale = scale; }; /** * Link one-way panorama * @param {PANOLENS.Panorama} pano - The panorama to be linked to * @param {THREE.Vector3} position - The position of infospot which navigates to the pano * @param {number} [imageScale=300] - Image scale of linked infospot * @param {string} [imageSrc=PANOLENS.DataImage.Arrow] - The image source of linked infospot */ PANOLENS.Panorama.prototype.link = function ( pano, position, imageScale, imageSrc ) { var scope = this, spot, scale, img; this.visible = true; if ( !position ) { console.warn( 'Please specify infospot position for linking' ); return; } // Infospot scale if ( imageScale !== undefined ) { scale = imageScale; } else if ( pano.linkingImageScale !== undefined ) { scale = pano.linkingImageScale; } else { scale = 300; } // Infospot image if ( imageSrc ) { img = imageSrc } else if ( pano.linkingImageURL ) { img = pano.linkingImageURL; } else { img = PANOLENS.DataImage.Arrow; } // Creates a new infospot spot = new PANOLENS.Infospot( scale, img ); spot.position.copy( position ); spot.toPanorama = pano; spot.addEventListener( 'click', function () { /** * Viewer handler event * @type {object} * @event PANOLENS.Panorama#panolens-viewer-handler * @property {string} method - Viewer function name * @property {*} data - The argument to be passed into the method */ scope.dispatchEvent( { type : 'panolens-viewer-handler', method: 'setPanorama', data: pano } ); } ); this.linkedSpots.push( spot ); this.add( spot ); this.visible = false; }; PANOLENS.Panorama.prototype.reset = function () { this.children.length = 0; }; PANOLENS.Panorama.prototype.setupTransitions = function () { this.fadeInAnimation = new TWEEN.Tween( this.material ) .easing( TWEEN.Easing.Quartic.Out ) .onStart( function () { this.visible = true; this.material.visible = true; /** * Enter panorama fade in start event * @event PANOLENS.Panorama#enter-fade-start * @type {object} */ this.dispatchEvent( { type: 'enter-fade-start' } ); }.bind( this ) ); this.fadeOutAnimation = new TWEEN.Tween( this.material ) .easing( TWEEN.Easing.Quartic.Out ) .onComplete( function () { this.visible = false; this.material.visible = true; /** * Leave panorama complete event * @event PANOLENS.Panorama#leave-complete * @type {object} */ this.dispatchEvent( { type: 'leave-complete' } ); }.bind( this ) ); this.enterTransition = new TWEEN.Tween( this ) .easing( TWEEN.Easing.Quartic.Out ) .onComplete( function () { /** * Enter panorama and animation complete event * @event PANOLENS.Panorama#enter-animation-complete * @type {object} */ this.dispatchEvent( { type: 'enter-animation-complete' } ); }.bind ( this ) ) .start(); this.leaveTransition = new TWEEN.Tween( this ) .easing( TWEEN.Easing.Quartic.Out ); }; /** * Start fading in animation * @fires PANOLENS.Panorama#enter-fade-complete */ PANOLENS.Panorama.prototype.fadeIn = function ( duration ) { duration = duration >= 0 ? duration : this.animationDuration; this.fadeOutAnimation.stop(); this.fadeInAnimation .to( { opacity: 1 }, duration ) .onComplete( function () { this.toggleInfospotVisibility( true, duration / 2 ); /** * Enter panorama fade complete event * @event PANOLENS.Panorama#enter-fade-complete * @type {object} */ this.dispatchEvent( { type: 'enter-fade-complete' } ); }.bind( this ) ) .start(); }; /** * Start fading out animation */ PANOLENS.Panorama.prototype.fadeOut = function ( duration ) { duration = duration >= 0 ? duration : this.animationDuration; this.fadeInAnimation.stop(); this.fadeOutAnimation.to( { opacity: 0 }, duration ).start(); }; /** * This will be called when entering a panorama * @fires PANOLENS.Panorama#enter * @fires PANOLENS.Panorama#enter-animation-start */ PANOLENS.Panorama.prototype.onEnter = function () { var duration = this.animationDuration; this.leaveTransition.stop(); this.enterTransition .to( {}, duration ) .onStart( function () { /** * Enter panorama and animation starting event * @event PANOLENS.Panorama#enter-animation-start * @type {object} */ this.dispatchEvent( { type: 'enter-animation-start' } ); if ( this.loaded ) { this.fadeIn( duration ); } else { this.load(); } }.bind( this ) ) .start(); /** * Enter panorama event * @event PANOLENS.Panorama#enter * @type {object} */ this.dispatchEvent( { type: 'enter' } ); }; /** * This will be called when leaving a panorama * @fires PANOLENS.Panorama#leave */ PANOLENS.Panorama.prototype.onLeave = function () { var duration = this.animationDuration; this.enterTransition.stop(); this.leaveTransition .to( {}, duration ) .onStart( function () { /** * Leave panorama and animation starting event * @event PANOLENS.Panorama#leave-animation-start * @type {object} */ this.dispatchEvent( { type: 'leave-animation-start' } ); this.fadeOut( duration ); this.toggleInfospotVisibility( false ); }.bind( this ) ) .start(); /** * Leave panorama event * @event PANOLENS.Panorama#leave * @type {object} */ this.dispatchEvent( { type: 'leave' } ); }; /** * Dispose panorama */ PANOLENS.Panorama.prototype.dispose = function () { /** * On panorama dispose handler * @type {object} * @event PANOLENS.Panorama#panolens-viewer-handler * @property {string} method - Viewer function name * @property {*} data - The argument to be passed into the method */ this.dispatchEvent( { type : 'panolens-viewer-handler', method: 'onPanoramaDispose', data: this } ); // recursive disposal on 3d objects function recursiveDispose ( object ) { for ( var i = object.children.length - 1; i >= 0; i-- ) { recursiveDispose( object.children[i] ); object.remove( object.children[i] ); } if ( object instanceof PANOLENS.Infospot ) { object.dispose(); } object.geometry && object.geometry.dispose(); object.material && object.material.dispose(); } recursiveDispose( this ); if ( this.parent ) { this.parent.remove( this ); } }; } )();