panorama/PanoMomentPanorama.js

import * as THREE from 'three';
import { PanoMoment } from './PanoMoment';

/**
 * PanoMoment Panorama
 * @param {object} identifier PanoMoment identifier
 */
function PanoMomentPanorama ( identifier ) {

    PanoMoment.call( this, identifier );

    // Event Bindings
    this.viewerResetControlLimits = () => this.resetControlLimits( false );

}

PanoMomentPanorama.prototype = Object.assign( Object.create( PanoMoment.prototype ), {

    constructor: PanoMomentPanorama,

    /**
     * When window is resized
     */
    onWindowResize: function() {

        this.resetControlLimits( false );

    },

    /**
     * Attch UI Event Listener to Container
     * @param {boolean} attach 
     */
    attachFOVListener: function( attach = true ) {

        const [ OrbitControls ] = this.controls;

        if ( attach ) {

            OrbitControls.addEventListener( 'fov', this.viewerResetControlLimits );

        } else {

            OrbitControls.removeEventListener( 'fov', this.viewerResetControlLimits );

        }
        
    },

    /**
     * Update intial heading with texture offset
     */
    updateHeading: function() {

        if ( !this.momentData ) return;

        const { momentData: { max_horizontal_fov } } = this;

        this.material.uniforms.offset.value.x = ( max_horizontal_fov / 360 + .25 ) % 1;

        // control update
        this.resetControlLimits( false );

        PanoMoment.prototype.updateHeading.call( this );

    },

    /**
     * Reset Polar Angle Limit by momentData or default
     * @param {boolean} reset 
     */
    resetAzimuthAngleLimits: function( reset = false ) {

        const { 
            controls: [ OrbitControls ], 
            momentData: { contains_parallax, min_vertical_fov }, 
            defaults: { minPolarAngle, maxPolarAngle }, 
            camera 
        } = this;

        if ( !contains_parallax && !reset ) return;

        const delta = THREE.Math.degToRad( ( 0.95 * min_vertical_fov - camera.fov ) / 2 );
        const angles = {
            minPolarAngle: Math.PI / 2 - delta,
            maxPolarAngle: Math.PI / 2 + delta
        };

        Object.assign( OrbitControls, reset ? { minPolarAngle, maxPolarAngle } : angles );

    },

    /**
     * Calculate FOV limit
     * @param {number} fov 
     * @param {boolean} horizontal 
     */
    calculateFOV: function( fov, horizontal ) {

        const { camera: { aspect } } = this;
        const factor = horizontal ? aspect : ( 1 / aspect );

        return 2 * Math.atan( Math.tan( fov * Math.PI / 360 ) * factor ) / Math.PI * 180;

    },

    /**
     * Set FOV Limit by momentData or default
     * @param {boolean} reset 
     */
    resetFOVLimits: function ( reset = false ) {

        const { momentData, camera, controls: [ OrbitControls ], defaults: { fov } } = this;
        const fovH = this.calculateFOV( camera.fov, true ) ;

        if ( fovH > ( momentData.min_horizontal_fov * .95 ) ) {

            camera.fov = this.calculateFOV( momentData.min_horizontal_fov * .95, false );
        
        } else if ( fovH < OrbitControls.minFov ) {

            camera.fov = this.calculateFOV( OrbitControls.minFov, false );

        }

        camera.fov = reset ? fov : camera.fov;
        camera.updateProjectionMatrix();

    },

    /**
     * Reset Polar Angle and FOV Limits
     * @param {boolean} reset
     */
    resetControlLimits: function( reset = false ) {

        if ( !this.momentData ) return;

        this.resetFOVLimits( reset );
        this.resetAzimuthAngleLimits( reset );

    },

    /**
     * Enter Panorama
     */
    enter: function() {

        this.attachFOVListener( true );
        this.resetControlLimits( false );

        PanoMoment.prototype.enter.call( this );

    },

    /**
     * Leave Panorama
     */
    leave: function() {

        this.attachFOVListener( false );
        this.resetControlLimits( true );

        PanoMoment.prototype.leave.call( this );

    }

} );

export { PanoMomentPanorama };