Source: sources/vue/displays/medias/TViewport3D.js

/**
 * @author [Tristan Valcke]{@link https://github.com/Itee}
 * @license [BSD-3-Clause]{@link https://opensource.org/licenses/BSD-3-Clause}
 *
 * @file Todo
 *
 * @example Todo
 *
 */

import { Clock }                                   from 'three-full'
import Vue                          from 'vue'
import { default as Stats }         from '../../../../node_modules/stats.js/src/Stats'

export default Vue.component( 'TViewport3D', {

    template: `<div class="tViewport3D" @click.left="_select" @click.right="_deselect" tabindex="-1"><slot></slot></div>`,

    props: [
        'width',
        'height',
        'scene',
        'camera',
        'control',
        'effect',
        'renderer',
        'showStats',
        'autoUpdate',
        'backgroundColor',
        'enableShadow',
        'isRaycastable',
        'allowDecimate',
        'needResize',
        'needUpdate',
        'needCacheUpdate'
    ],

    data () {

        return {
            frameId:         null,
            resizeTimeoutId: null,
            timer:           new Clock( true ),
            stats:           new Stats()
        }

    },

//    watch: {
//
////        control ( newControl, oldControl ) {
////
////            this._setControl( newControl, oldControl )
////
////        },
////
////        effect ( newEffect, oldEffect ) {
////
////            this._setEffect( newEffect, oldEffect )
////
////        },
////
////        renderer ( newRenderer, oldRenderer ) {
////
////            this._setRenderer( newRenderer, oldRenderer )
////
////        },
////
////        autoUpdate ( autoUpdate/*, oldAutoUpdate*/ ) {
////
////            if ( autoUpdate ) {
////                this._startLoop()
////            } else {
////                this._stopLoop()
////            }
////
////        },
////
////        needUpdate ( /*newValue, oldValue*/ ) {
////
////            this._updateViewport()
////
////        },
//
////        backgroundColor ( newBg/*, oldBg*/ ) {
////
////            this.renderer.setClearColor( newBg || 0x000000 )
////
////        },
//
////        showStats ( newValue/*, oldValue*/ ) {
////
////            this.stats.domElement.style.display = ( newValue ) ? 'block' : 'none'
////
////        },
////
////        needResize ( newValue/*, oldValue*/ ) {
////
////            if ( newValue === true ) {
////                this._resize( this.$el )
////            }
////
////        },
////
////        allowDecimate ( newValue/*, oldValue*/ ) {
////
////            if ( newValue ) {
////                this._decimateVisibleMeshes()
////            } else {
////                this._populateVisibleMeshes()
////            }
////
////        }
//
//    },
//
//    methods: {
//
//        // Setters
//
////        _setEffect ( newEffect, oldEffect ) {
////
////            if ( oldEffect && newEffect === oldEffect ) { return }
////
////            this.effect = newEffect
////
////            this._resize()
////
////        },
//
////        _setRenderer ( newRenderer, oldRenderer ) {
////
////            if ( oldRenderer && newRenderer === oldRenderer ) { return }
////
////            // Add renderer canvas
////            this.renderer.domElement.style.display = 'block'
////            this.$el.appendChild( this.renderer.domElement )
////
////        },
//
//        // Resize
//
////        _resize () {
////
////            this._resizeCamera( 1, 1 )
////            this._resizeControl( 1, 1 )
////            this._resizeEffect( 1, 1 )
////            this._resizeRenderer( 1, 1 )
////
////            if ( this.resizeTimeoutId ) {
////                clearTimeout( this.resizeTimeoutId )
////            }
////
////            this.resizeTimeoutId = setTimeout( () => {
////                const containerWidth  = this.$el.offsetWidth
////                const containerHeight = this.$el.offsetHeight
////
////                this._resizeCamera( containerWidth, containerHeight )
////                this._resizeControl( containerWidth, containerHeight )
////                this._resizeEffect( containerWidth, containerHeight )
////                this._resizeRenderer( containerWidth, containerHeight )
////            }, 0 )
////
////            this.$emit( 'resized', null )
////
////        },
////        _resizeCamera ( width, height ) {
////
////            if ( !this.camera ) { return }
////
////            const aspectRatio = ( width / height )
////
////            if ( this.camera.isPerspectiveCamera ) {
////
////                this.camera.aspect = aspectRatio
////
////                this.camera.updateProjectionMatrix()
////
////            } else if ( this.camera.isOrthographicCamera ) {
////
////                const frustumSize  = 20
////                this.camera.left   = -frustumSize * aspectRatio / 2
////                this.camera.right  = frustumSize * aspectRatio / 2
////                this.camera.top    = frustumSize / 2
////                this.camera.bottom = -frustumSize / 2
////
////                this.camera.updateProjectionMatrix()
////
////            } else {
////
////                TLogger.error( `TViewport3D: Unable to resize unknown camera of type ${typeof this.camera}` )
////
////            }
////
////        },
////        _resizeControl ( width, height ) {
////
////            if ( !this.control ) { return }
////            if ( !this.control.resize ) { return }
////
////            this.control.resize( width, height )
////
////        },
////        _resizeEffect ( width, height ) {
////
////            if ( !this.effect ) { return }
////            if ( !this.effect.setSize ) { return }
////
////            this.effect.setSize( width, height )
////
////        },
////        _resizeRenderer ( width, height ) {
////
////            if ( this.effect ) { return }
////            if ( !this.renderer ) { return }
////            if ( !this.renderer.setSize ) { return }
////
////            this.renderer.setSize( width, height )
////
////        },
//
////        // Render loop
////
////        _startLoop () {
////
////            if ( this.frameId ) {
////                return
////            }
////
////            this.frameId = window.requestAnimationFrame( this._loop.bind( this ) )
////
////        },
////        _loop () {
////
////            if ( this.stats && this.showStats ) {
////                this.stats.begin()
////            }
////
////            this.frameId = window.requestAnimationFrame( this._loop.bind( this ) )
////
////            this._updateViewport()
////
////            if ( this.stats && this.showStats ) {
////                this.stats.end()
////            }
////
////        },
////        _stopLoop () {
////
////            window.cancelAnimationFrame( this.frameId )
////            this.frameId = null
////
////        },
////
////        _updateViewport () {
////
////            this._updateCamera()
////            this._updateControl()
////            this._updateEffect()
////            this._updateRenderer()
////
////        },
////        _updateCamera () {
////
////            if ( !this.camera ) { return }
////
////            const cameraType = this.camera.type
////
////            if (
////                this.camera.isPerspectiveCamera ||
////                this.camera.isOrthographicCamera
////            ) {
////
////                // Do nothings here...
////
////            } else if ( cameraType === 'CinematicCamera' ) {
////
////                this.camera.renderCinematic( this.scene, this.renderer )
////
////            } else if ( cameraType === 'CubeCamera' ) {
////
////                this.camera.update( this.renderer, this.scene )
////
////            } else if ( cameraType === 'StereoCamera' ) {
////
////                this.camera.update( this.camera )
////
////            } else {
////
////                throw new Error( `Unmanaged camera type: ${this.camera}` )
////
////            }
////
////        },
////        _updateControl () {
////
////            if ( !this.control ) { return }
////            if ( !this.control.update ) { return }
////            if ( !this.$data ) { return }
////            if ( !this.$data.timer ) { return }
////
////            this.control.update( this.$data.timer.getDelta() )
////
////        },
////        _updateEffect () {
////
////            if ( !this.effect ) { return }
////            if ( !this.effect.render ) { return }
////
////            this.effect.render( this.scene, this.camera )
////
////        },
////        _updateRenderer () {
////
////            if ( this.effect ) { return }
////            if ( !this.renderer ) { return }
////            if ( !this.renderer.render ) { return }
////
////            const scene    = this.scene
////            const camera   = this.camera
////            const renderer = this.renderer
////
////            renderer.render( scene, camera )
////
////        },
//
//        // Utils
//
////        // Todo: dispatch mouse/keyboard events in differents methods to be handler with intersected object
////        // Todo: drag/drop altClick keymodifier
////        // Todo :allow to change keymodifier signification !!!
////
////        // Raycast if(raycastOnClick) onClick(raycast)
////        _raycast ( mouseEvent ) {
////
////            if ( !this.isRaycastable ) {
////                return
////            }
////
////            mouseEvent.preventDefault()
////
////            // calculate mouse position in normalized device coordinates
////            // (-1 to +1) for both components
////            const mousePositionX             = mouseEvent.layerX || mouseEvent.offsetX || 1
////            const mousePositionY             = mouseEvent.layerY || mouseEvent.offsetY || 1
////            const containerWidth             = this.$el.offsetWidth
////            const containerHeight            = this.$el.offsetHeight
////            const normalizedMouseCoordinates = {
////                x: ( mousePositionX / containerWidth ) * 2 - 1,
////                y: -( mousePositionY / containerHeight ) * 2 + 1
////            }
////
////            // update the picking ray with the camera and mouse position
////            this._raycaster.setFromCamera( normalizedMouseCoordinates, this.camera )
////
////            // Debug ray
////            const debugRay = false
////            if ( debugRay ) {
////
////                const origin      = this._raycaster.ray.origin
////                const direction   = this._raycaster.ray.direction
////                const arrowHelper = new ArrowHelper( direction, origin, 10, 0x123456 )
////                this.scene.add( arrowHelper )
////
////            }
////
////            // calculate objects intersecting the picking ray
////            const raycastables = this._getRaycastableCache()
////            const intersects   = this._raycaster.intersectObjects( raycastables, false )
////            if ( intersects && intersects.length > 0 ) {
////                const nearestIntersect = intersects[ 0 ]
////                this.$emit( 'intersect', nearestIntersect )
////            } else {
////                this.$emit( 'intersect', null )
////            }
////
////        },
////
////        _select ( mouseEvent ) {
////
////            if ( !this.isRaycastable ) {
////                return
////            }
////
////            mouseEvent.preventDefault()
////
////            // calculate mouse position in normalized device coordinates
////            // (-1 to +1) for both components
////            const mousePositionX             = mouseEvent.layerX || mouseEvent.offsetX || 1
////            const mousePositionY             = mouseEvent.layerY || mouseEvent.offsetY || 1
////            const containerWidth             = this.$el.offsetWidth
////            const containerHeight            = this.$el.offsetHeight
////            const normalizedMouseCoordinates = {
////                x: ( mousePositionX / containerWidth ) * 2 - 1,
////                y: -( mousePositionY / containerHeight ) * 2 + 1
////            }
////
////            // update the picking ray with the camera and mouse position
////            this._raycaster.setFromCamera( normalizedMouseCoordinates, this.camera )
////
////            // calculate objects intersecting the picking ray
////            const raycastables = this._getRaycastableCache()
////            const intersects   = this._raycaster.intersectObjects( raycastables, false )
////            if ( intersects && intersects.length > 0 ) {
////                this.$emit( 'select', intersects[ 0 ] )
////            }
////        },
////
////        _deselect ( mouseEvent ) {
////
////            if ( !this.isRaycastable ) {
////                return
////            }
////
////            mouseEvent.preventDefault()
////
////            this.$emit( 'deselect' )
////
////        },
////
////        /// Helpers
////
////        _decimateVisibleMeshes () {
////
////            if ( !this.allowDecimate ) {
////                return
////            }
////
////            // Decimate scene
////            const cache = this._getDecimateCache()
////            for ( let meshIndex = 0, numberOfMeshesToDecimate = cache.length ; meshIndex < numberOfMeshesToDecimate ; meshIndex++ ) {
////
////                cache[ meshIndex ].layers.set( 10 )
////
////            }
////
////            this.isDecimate = true
////
////        },
////
////        _populateVisibleMeshes () {
////
////            if ( this.allowDecimate ) {
////                return
////            }
////
////            const decimables = this._cache.decimables
////            for ( let meshIndex = 0, numberOfMeshesToDecimate = decimables.length ; meshIndex < numberOfMeshesToDecimate ; meshIndex++ ) {
////
////                decimables[ meshIndex ].layers.set( 0 )
////
////            }
////
////            this.isDecimate = false
////
////            this._cache.raycastables = []
////
////        },
////
////        _getRaycastableCache () {
////
////            if ( this.needCacheUpdate || this._cache.raycastables.length === 0 ) {
////
////                this._cache.raycastables = []
////
////                let raycastables = this.scene.getObjectByName( 'Données' )
////                if ( !raycastables ) {
////                    raycastables = this.scene.getObjectByName( 'Sites' )
////                }
////                if ( !raycastables ) {
////                    raycastables = this.scene
////                }
////
////                const frustum                    = new Frustum()
////                const cameraViewProjectionMatrix = new Matrix4()
////
////                // every time the camera or objects change position (or every frame)
////                this.camera.updateMatrixWorld() // make sure the camera matrix is updated
////                this.camera.matrixWorldInverse.getInverse( this.camera.matrixWorld )
////                cameraViewProjectionMatrix.multiplyMatrices( this.camera.projectionMatrix, this.camera.matrixWorldInverse )
////                frustum.setFromMatrix( cameraViewProjectionMatrix )
////
////                this._updateRaycastableChildren.call( this, raycastables.children, frustum )
////
////                this.$emit( 'cacheUpdated', 'raycastables' )
////
////            }
////
////            return this._cache.raycastables
////
////        },
////
////        _updateRaycastableChildren ( children, frustum ) {
////
////            for ( let i = 0, n = children.length ; i < n ; i++ ) {
////
////                let child = children[ i ]
////
////                if ( !child.visible ) {
////                    continue
////                }
////
////                if ( child.isGroup || child.type === 'Scene' ) {
////                    this._updateRaycastableChildren.call( this, child.children, frustum )
////                }
////
////                if ( !child.isRaycastable ) {
////                    continue
////                }
////
////                if ( !frustum.intersectsObject( child ) ) {
////                    continue
////                }
////
////                this._cache.raycastables.push( child )
////                this._updateRaycastableChildren.call( this, child.children, frustum )
////
////            }
////
////        },
////
////        _getDecimateCache () {
////
////            // TODO: should be params
////            const decimateValue = 0.9
////
////            if ( !this.isDecimate && ( this.needCacheUpdate || this._cache.decimables.length === 0 ) ) {
////
////                this._cache.decimables = []
////                const meshes           = []
////
////                //Todo: Should be able to specify the Group/Layers/whatever where decimate
////                let decimables = this.scene.getObjectByName( 'Données' )
////                if ( !decimables ) {
////                    decimables = this.scene.getObjectByName( 'Sites' )
////                }
////                if ( !decimables ) {
////                    decimables = this.scene
////                }
////                this._updateDecimateCache.call( this, decimables.children, meshes )
////
////                // Store random meshes to decimate
////                const numberOfMeshes       = meshes.length
////                const numberOfMeshesToHide = Math.round( numberOfMeshes * decimateValue )
////                while ( this._cache.decimables.length < numberOfMeshesToHide ) {
////                    this._cache.decimables.push( meshes[ Math.floor( Math.random() * numberOfMeshes ) ] )
////                }
////
////                this.$emit( 'cacheUpdated', 'decimables' )
////
////            }
////
////            return this._cache.decimables
////
////        },
////
////        _updateDecimateCache ( children, meshes ) {
////
////            let child = undefined
////            for ( let i = 0, n = children.length ; i < n ; i++ ) {
////
////                child = children[ i ]
////
////                if ( !child.visible ) {
////                    continue
////                }
////
////                if ( child.isGroup ) {
////                    this._updateDecimateCache.call( this, child.children, meshes )
////                }
////
////                if ( child.type === 'Scene' ) {
////                    this._updateDecimateCache.call( this, child.children, meshes )
////                }
////
////                if ( child.isMesh ) {
////                    meshes.push( child )
////                }
////
////            }
////
////        }
//
//    },
//
//    created () {
//
////        window.addEventListener( 'resize', this._resize, false )
////
////        // Untracked private data
////        this._cache = {
////            decimables:   [],
////            isDecimate:   false,
////            raycastables: []
////        }
////
////        this._repopulateTimeoutId = undefined
//
//    },
//
//    mounted () {
//
////        // Set renderer
////        this._setRenderer( this.renderer )
////
////        // Init effects
////        this._setEffect( this.effect )
////
////        // Init raycaster
////        this._raycaster = new Raycaster()
////
////        // Init stats
////        this.stats                           = new Stats()
////        this.stats.domElement.style.display  = ( this.showStats ) ? 'block' : 'none'
////        this.stats.domElement.style.position = 'absolute'
////        this.stats.domElement.style.top      = null
////        this.stats.domElement.style.left     = null
////        this.stats.domElement.style.right    = '0px'
////        this.stats.domElement.style.bottom   = '0px'
////        this.$el.appendChild( this.stats.domElement )
////
////        // Fill parent
////        this._resize()
////
////        // Listen ( should bind in template ???)
////        this.$el.addEventListener( 'mousemove', this._raycast.bind( this ), true )
////        //        this.$el.addEventListener( 'mousedown', this._select.bind( this ), true )
////
////        // Start rendering if autoupdate
////        if ( this.autoUpdate ) {
////            this._startLoop()
////        }
//
//    },
//
//    beforeDestroy () {
//
//        this._stopLoop()
//
//        window.removeEventListener( 'resize', this._resize, false )
//
//    }

} )