Source: view/default_view.js

import { View } from './view.js';
import { Marker } from './marker.js';
import { Project } from '../project.js';
import { Timer } from "../../src/timer.js";

import { Scene, sRGBEncoding, LinearEncoding, LinearFilter, Vector3, WebGLRenderer, PCFSoftShadowMap, Euler, VSMShadowMap, LinearToneMapping, CineonToneMapping, RGBAFormat, FloatType, Raycaster, Vector2, Plane, UnsignedByteType, PMREMGenerator, Mesh, Quaternion, DirectionalLight, BoxGeometry, MeshBasicMaterial } from '../../node_modules/three/build/three.module.js';

import { Pass } from '../../node_modules/three/examples/jsm/postprocessing/Pass.js';
import { Reporter } from '../reporter/reporter.js';
import { checkPropTypes, UUIDRegex } from '../lib.js';
import { ContactShadow } from '../scene/contact_shadow.js'
import { ShaderPass } from '../../node_modules/three/examples/jsm/postprocessing/ShaderPass.js';
//import { GammaCorrectionShader } from '../../node_modules/three/examples/jsm/shaders/GammaCorrectionShader.js';
import { GammaCorrectionShader } from '../../test/resources/js/GammaCorrectionShader.js';
import { FXAAShader } from '../../node_modules/three/examples/jsm/shaders/FXAAShader.js';
import { CSS2DRenderer } from '../../node_modules/three/examples/jsm/renderers/CSS2DRenderer.js';
import { EXRLoader } from '../../node_modules/three/examples/jsm/loaders/EXRLoader.js';
import { KeyboardShortcuts } from '../../src/utilities/keyboardShortcuts.js';
import { EXR } from '../package/image/exr.js';

import { Component } from '../component/component.js';
import { Block } from '../package/block/block.js';
import { WrappedMaterial } from '../package/material/wrapped_material.js';
import { BlockInstance } from '../configurator/block_instance.js';
import { Configuration } from '../configurator/configuration.js';
import { Configurator } from '../configurator/configurator.js';
import { Actor } from '../actor/actor.js';


const fxaaShader = new ShaderPass(FXAAShader);
const gammaCorrectionShader = new ShaderPass(GammaCorrectionShader);


/** 
 * Default View 
 * @extends View
 */
class DefaultView extends View {

    /**
     * @param {Reporter} reporter
     * @param {Object} settings
     * @param {Scene} settings.scene
     * @param {WebGLRenderer} settings.renderer
     * @param {WebGLRenderer} settings.rendererOrtho
     * @param {Timer} settings.timer
     * @param {Object<string,any>} [settings.cameraSettings]
     * @param {Array<Pass>} [settings.passes]
     * @param {Function} [settings.findConfigurator]
     * @param {Function} [settings.addConfigurator]
     * @param {Function} [settings.findComponentById]
     */

    constructor(reporter, settings) {

        

        super(
            reporter,
            {
                // scene: settings.scene,
                // renderer: settings.renderer,
                // rendererOrtho: settings.rendererOrtho,
                // timer: settings.timer,
                //findConfigurator: settings.findConfigurator,
                //addConfigurator: settings.addConfigurator

                ...settings,

                rendererSettings: {
                    'antialias': false,
                    'alpha': true,
                    'preserveDrawingBuffer': true,
                    'precision': "highp",
                    'premultipliedAlpha': true,
                    'stencil': false,
                    'logarithmicDepthBuffer': false,
                    'toneMapping': CineonToneMapping, //LinearToneMapping, //ReinhardToneMapping, //THREE.NoToneMapping //; // Met NoToneMapping werkt de exposure niet! //CineonToneMapping, //THREE.ACESFilmicToneMapping //THREE.
                    'toneMappingExposure': 1,
                    'toneMappingWhitePoint': 1,
                    'gammaFactor': 2.2,
                    'outputEncoding': CineonToneMapping, //sRGBEncoding, // LinearEncoding, //   //When using post-processing or in general when rendering to a render target, WebGLRenderer.outputEncoding is not evaluated.
                    'physicallyCorrectLights': true,
                    'minFilter': LinearFilter,
                    'magFilter': LinearFilter,
                    'format': RGBAFormat,
                    'stencilBuffer': false,
                    'type': FloatType, // line not present in original code
                    'shadowMap.enabled': true
                },

                cameraSettings: {
                    fov: 30,
                    aspect: window.innerWidth / window.innerHeight,
                    near: 0.5,
                    far: 100
                },


                passes: [

                    fxaaShader, //deze pass moet altijd als laatste komen (met uitzondering van de gamma correction shader) zodat de anti aliasing het beste werkt
                    gammaCorrectionShader

                ],





            }
        );

        checkPropTypes(
            settings,
            {
                scene: Scene,
                renderer: WebGLRenderer,
                timer: Timer
            },
            {
                rendererSettings: Object,
                cameraSettings: Object,
                passes: val => {
                    if (!Array.isArray(val)) {
                        return 'Not an array';
                    }

                    for (let i = 0; i < val.length; i += 1) {
                        const entry = val[i];
                        if (!entry instanceof Pass) {
                            return `Entry ${i} is not a Pass but a ${entry.constructor.name}`;
                        }
                    }

                    return true;
                }
            }
        );

        //Settings / Variables
        this.dragObject = null;
        this.dragging = false;
        this.allMeshes = [];
        this.layerMap = Project.layerMap
        this.onMouseDownActive = false;
        this.shift = new Vector3();
        this.intersects = new Vector3();
        this.exrBackground = null;


        //Composer Passes
        this.pixelRatio = this.renderer.getPixelRatio();

        //set the dimensions where needed
        this.on(
            'dimensionsupdate',
            newDimensions => {

                fxaaShader.uniforms.resolution.value.x = 1 / (newDimensions.width * this.pixelRatio);
                fxaaShader.uniforms.resolution.value.y = 1 / (newDimensions.height * this.pixelRatio);

            }
        );

        fxaaShader.name = "FXAA Pass";
        gammaCorrectionShader.name = "Gamma Correction Pass";

        //renderer
        this.renderer.setClearColor(0xFFFFFF, 1); //deze is nodig om de FXAA goed te laten werken
        this.renderer.shadowMap.enabled = true;
        this.renderer.shadowMap.type = PCFSoftShadowMap; //VSMShadowMap;

        this.renderer.antialias = false,
        this.renderer.gammaFactor = 1.8,
        this.renderer.physicallyCorrectLights =  true,
        this.renderer.toneMapping = 4,
        this.renderer.toneMappingExposure = 0.5,
        this.renderer.logarithmicDepthBuffer = true


        //Camera
        this.perspectiveCamera.position.set(-2, 1, 5);
        //this.perspectiveCamera.layers.disable(2);
        //this.perspectiveCamera.layers.disable(3);

        //Raycaster
        //this.raycaster = new Raycaster(); //er zit al een raycaster in view


        //Directional Light
        this.directionalLight = new DirectionalLight(0xffffff, 3);
        this.directionalLight.position.set(-30, 65, 50);
        //this.directionalLight.shadow.bias = 0.001

        //Set up shadow properties for the light
        this.directionalLight.shadow.mapSize.width = 512; // default
        this.directionalLight.shadow.mapSize.height = 512; // default
        this.directionalLight.shadow.camera.near = 0.5; // default
        this.directionalLight.shadow.camera.far = 500; // default

        

        //shadow intensity workaround -> https://github.com/mrdoob/three.js/pull/14087
        const shadowIntensity = 2.7; // between 0 and 1

        this.directionalLight2 = this.directionalLight.clone();
        this.directionalLight.castShadow = true;
        this.directionalLight2.castShadow = false;
        this.directionalLight.intensity = shadowIntensity;
        this.directionalLight2.intensity = 6 - shadowIntensity;
        //this.directionalLight2.shadow.bias = 0.001

        //fill light
        this.directionalLightLeft = this.directionalLight.clone();
        this.directionalLightLeft.position.set(50, 50, 40);
        this.directionalLightLeft.castShadow = true;
        this.directionalLightLeft.intensity = 1.8;
        //this.directionalLightLeft.shadow.bias = 0.001

        //back light
        this.directionalLightBack = this.directionalLight.clone();
        this.directionalLightBack.position.set(0, 50, -50);
        this.directionalLightBack.castShadow = false;
        this.directionalLightBack.intensity = 1.5;
        //this.directionalLightBack.shadow.bias = 0.001

        this.scene.add(this.directionalLight)
        this.scene.add(this.directionalLight2)
        this.scene.add(this.directionalLightLeft)
        this.scene.add(this.directionalLightBack)
        


        //Mouse
        this.mouse = new Vector2();

        //Mouse Intersection Plane 
        this.intersectionPlane = new Plane();
        this.intersectionPlane.setFromCoplanarPoints(new Vector3(), new Vector3(1, 0, 0), new Vector3(0, 0, 1))
        this.intersectionPlaneNormal = new Vector3(0, 1, 0);
        this.pointOfIntersection = new Vector3();

        //Drag Variables
        this.dragPlane = new Plane(); // een plane om een intersectie mee te kunnen maken voor de drag functionaliteit in de scene
        this.dragPlane.setFromCoplanarPoints(new Vector3(), new Vector3(1, 0, 0), new Vector3(0, 0, 1))
        this.dragPlaneNormal = new Vector3(0, 1, 0);
        this.shift = new Vector3();
        this.dragging = false;
        this.dragObject = null;
        this.pointOfIntersection = new Vector3();
        this.onPointerDownActive = false;
        this.onTouchStartActive = false;

        

        //Keyboard Shortcuts
        // KeyboardShortcuts(
        //     this.scene, 
        //     this.perspectiveCamera, 
        //     this.renderer, 
        //     this.shadow, 
        //     this.dragObject, 
        //     this.allMeshes, 
        //     this.onSceneUpdate() 
        // ) 

        //const marker = new Marker( reporter, { scene:this.scene } );
        //marker.addMarker();



    }

    eventHandlerStates = {

        _onPointerDownActive: false,
        get active() {
            return this._active
        },
        set onPointerDownActive(state) {
            // console.log(`Setting dragState.active [${this._active}] => [${state}]`);
            this._onPointerDownActive = state;
        },
        
    }
    



    //Event Handlers

    /** 
     * Touch events
     */


    onTouchStart(event){

        //super.onTouchStart()

        // console.log( 'touch start')
        // console.log( event.touches[0].clientX )
        // console.log( event.touches[0].clientY )

        if (this.dragState.active) {
            //do nothing
        }
        else {

            this.onPointerDownActive = false;
            this.onTouchStartActive = true;

            //?
            this.lastPointerMove = {
                x: event.touches[0].clientX,
                y: event.touches[0].clientY,
                timestamp: new Date().getTime()
            };

            //check of block
            var data = this.findFirstIntersectedBlockInstance() || {};
            //console.log( data )

            data.event = event;
            this.emit('pointerdown', data);

            //get point of intersection
            const intersections = this.intersect(this.lastPointerMove.x, this.lastPointerMove.y);

            //console.log( intersections )

            //if block then
            if (data.blockInstance instanceof BlockInstance) {

                this.selectionActive = true;
                // console.log('selection activated')
                this.cameraControls.enabled = false; //disable cameraControls       

                const selectedConfigurator = this.findConfigurator(data.blockInstance.tree)   //get the configurator from the data
                //this.showBoundingBoxes( selectedConfigurator.configuration )  //show bbox for reference
                //set the draggable object
                this.dragObject = selectedConfigurator  //._content.main.medium

                //set the dragPlane on the first intersection point
                this.dragPlane.setFromNormalAndCoplanarPoint(this.dragPlaneNormal, intersections[0].point)

                //set a shift of the object versus the dragPlane
                this.shift.subVectors(this.dragObject.body.position, intersections[0].point);  //sets this vector to a - b  

            }
            else {
                this.dragObject = null;
                this.hideBoundingBoxes()
                this.selectionActive = false;
                this.cameraControls.enabled = true; //disable cameraControls  
            }

            this.update()

        }

    }



    onTouchMove(event){

        console.log( 'touch move')

        if (this.dragState.active) {
            //do nothing
        }
        else {

            this.lastPointerMove.x = event.touches[0].clientX;
            this.lastPointerMove.y = event.touches[0].clientY;

            if (this.domElementDimensions === undefined) {
                this.updateDOMElementDimensions()  //<- deze hier tijdelijk neergzet, anders heeft this.intersect een error - deze kan waarschijnlijk beter op een andere plaats worden aangeroepen als de app opstart
            }

            //calculate the intersection based on the mouse position
            this.intersect(this.lastPointerMove.x, this.lastPointerMove.y)

            //check if mouse pointer is down
            if (this.onTouchStartActive) {

                //check if dragObject is set
                if (this.dragObject instanceof Configurator) {

                    //console.log( 'dragObject = configuration' )

                    if (this.hideShadowOnDrag === false) {
                        //console.log( ' hide shadow' )
                        //update the shadow without the selected configuration (exclude)
                        this.shadow._update([this.dragObject.body]);
                        this.hideShadowOnDrag = true
                    }

                    //get the point of intersection
                    this.rayCaster.ray.intersectPlane(this.dragPlane, this.pointOfIntersection);

                    //set the position of the dragobject based on the point of intersection
                    this.dragObject.body.position.copy(this.pointOfIntersection).add(this.shift);  //<- de add shift nog ff netjes oplossen. Weet niet of dit de beste plek er voor is.

                    this.dragObject.body.position.setY(0) //force y = 0

                    this.update()
                }

            }
            else {
                this.hideShadowOnDrag = false
            }

        }

    }

    onTouchEnd(event){

        console.log( 'touch end')

    
        if (this.dragState.active) {
            //do nothing
        }
        else {

            this.hideShadowOnDrag = false

            this.onPointerDownActive = false;
            this.onTouchStartActive = false;
            this.cameraControls.enabled = true;  //re-enable cameraControls from down event

            this.lastPointerUp = {
                x: this.lastPointerMove.x,
                y: this.lastPointerMove.y,
                timestamp: new Date().getTime()
            };

            //set the end position of the configrator/configuration
            if (this.dragObject instanceof Configurator) {

                this.selectionActive = true;
                const configurator = this.dragObject
                configurator.position.copy(this.dragObject.body.position)
                configurator.position.setY(0) //force y axis = 0

            }
            else {
                // console.log('selection de-activated')
                this.selectionActive = false;
                this.dragObject = null;
            }

            this.dragObject = null;
            //console.log( this.dragObject )

            this.shadow._update();
            this.update()
        }
        
    }


    //dit event is nog experimental!!
    onTouchCancel(event){

        console.log( 'touch cancel')

        this.onTouchCancelActive = true;
        
    }





    /**
     * Finds the block 'under' the current (last known) mouse coordinates
     * and emits an event with that data, if anyone is listening
     * @fires View#click
     */

    onClick(event) {

        // console.log('click')

        this.dragState.active = false;
        this.onPointerDownActive = false;
        //this.selectionActive = false;

        super.onClick()

    }

    onDoubleClick(event) {

        console.log('double click')

        //control the state
        this.dragState.active = false;
        this.onPointerDownActive = false;
        //this.selectionActive = false;

        super.onDoubleClick()

    }

    //selectionActive = false; //deze wordt gebruikt om te bepalen of de selectie actief is of niet

    /**
     * Stores the pointer down coordinates and timestamp.
     * Finds the block 'under' the current (last known) mouse coordinates
     * and emits an event with that data, if anyone is listening
     * @fires View#pointerdown
     */

    onPointerDown(event) {

        console.log( 'viewport pointer down')

        if (this.dragState.active) {
            //do nothing
        }
        else {

            

            this.onPointerDownActive = true;

            this.lastPointerDown = {
                x: this.lastPointerMove.x,
                y: this.lastPointerMove.y,
                timestamp: new Date().getTime()
            };

            //check of block
            var data = this.findFirstIntersectedBlockInstance() || {};
            data.event = event;
            this.emit('pointerdown', data);

            //get point of intersection
            const intersections = this.intersect(this.lastPointerDown.x, this.lastPointerDown.y);

            //if block then
            if (data.blockInstance instanceof BlockInstance) {

                this.selectionActive = true;
                // console.log('selection activated')
                this.cameraControls.enabled = false; //disable cameraControls       

                const selectedConfigurator = this.findConfigurator(data.blockInstance.tree)   //get the configurator from the data
                //this.showBoundingBoxes( selectedConfigurator.configuration )  //show bbox for reference
                //set the draggable object
                this.dragObject = selectedConfigurator  //._content.main.medium

                //set the dragPlane on the first intersection point
                this.dragPlane.setFromNormalAndCoplanarPoint(this.dragPlaneNormal, intersections[0].point)

                //set a shift of the object versus the dragPlane
                if( intersections[0] !== undefined && this.dragObject !== undefined  ){
                    this.shift.subVectors(this.dragObject.body.position, intersections[0].point);  //sets this vector to a - b
                }
                  

            }
            else {
                this.dragObject = null;
                this.hideBoundingBoxes()
                this.selectionActive = false;
                this.cameraControls.enabled = true; //disable cameraControls  
            }

            this.update()

        }

    }



    //hideShadowOnDrag = false; //deze wordt bij pointer move 1x op tur gezet zodat de shaduw uitgezet kan wordeen tijdens het onPointerMove event


    onPointerMove(event) {

        if (this.dragState.active) {
            //do nothing
        }
        else {

            this.lastPointerMove.x = event.clientX;
            this.lastPointerMove.y = event.clientY;

            if (this.domElementDimensions === undefined) {
                this.updateDOMElementDimensions()  //<- deze hier tijdelijk neergzet, anders heeft this.intersect een error - deze kan waarschijnlijk beter op een andere plaats worden aangeroepen als de app opstart
            }

            //calculate the intersection based on the mouse position
            this.intersect(this.lastPointerMove.x, this.lastPointerMove.y)

            //check if mouse pointer is down
            if (this.onPointerDownActive) {

                //check if dragObject is set
                if (this.dragObject instanceof Configurator) {

                    //console.log( 'dragObject = configuration' )

                    if (this.hideShadowOnDrag === false) {
                        //console.log( ' hide shadow' )
                        //update the shadow without the selected configuration (exclude)
                        this.shadow._update([this.dragObject.body]);
                        this.hideShadowOnDrag = true
                    }

                    //get the point of intersection
                    this.rayCaster.ray.intersectPlane(this.dragPlane, this.pointOfIntersection);

                    //set the position of the dragobject based on the point of intersection
                    this.shift.y = 0;
                    this.pointOfIntersection.y = 0;
                    this.dragObject.body.position.copy(this.pointOfIntersection).add(this.shift);  //<- de add shift nog ff netjes oplossen. Weet niet of dit de beste plek er voor is.

                    this.dragObject.body.position.setY(0) //force y = 0

                    this.update()
                }

            }
            else {
                this.hideShadowOnDrag = false
            }

        }

    }



    /**
     * Stores the pointer up coordinates and timestamp.
     * If the mouse hasnt move (a lot) since mouse down, it find the block
     * 'under' the last known mouse coordinates and emits an event
     * with that data, if anyone is listening
     * @fires View#fastclick
     */

    onPointerUp(event) {

        if (this.dragState.active) {
            //do nothing
        }
        else {

            this.hideShadowOnDrag = false

            this.onPointerDownActive = false;
            this.cameraControls.enabled = true;  //re-enable cameraControls from down event

            this.lastPointerUp = {
                x: this.lastPointerMove.x,
                y: this.lastPointerMove.y,
                timestamp: new Date().getTime()
            };

            // if (Object.keys(this._events['fastclick']).length > 0) {

            //     // check whether the distance between pointer down and up
            //     // is sufficiently small to be seen as a 'click' event

            //     if (
            //         Math.pow(
            //             (this.lastPointerDown.x - this.lastPointerUp.x) * (this.lastPointerDown.x - this.lastPointerUp.x) + (this.lastPointerDown.y - this.lastPointerUp.y) * (this.lastPointerDown.y - this.lastPointerUp.y),
            //             0.5
            //         ) < 2
            //     ) {
            //         const data = this.findFirstIntersectedBlockInstance() || {};
            //         data.event = event;
            //         this.emit('fastclick', data);
            //     }
            // }

            //set the end position of the configrator/configuration
            if (this.dragObject instanceof Configurator) {

                this.selectionActive = true;
                const configurator = this.dragObject
                configurator.position.copy(this.dragObject.body.position)
                configurator.position.setY(0) //force y axis = 0

            }
            else {
                // console.log('selection de-activated')
                this.selectionActive = false;
                this.dragObject = null;
            }

            this.shadow._update();
            this.update()
        }

    }



    /**
    * All the different states to track and control the status of the drag events.
    * These are needed to prevent conflicts with click / dblclick events (maybe )
    * @type {Object} 
    */

    dragState = {
        _active: false,
        get active() {
            return this._active
        },
        set active(state) {
            // console.log(`Setting dragState.active [${this._active}] => [${state}]`);
            this._active = state;
        },

        _enter: false,
        get enter() {
            return this._enter
        },
        set enter(state) {
            // console.log(`Setting dragState.enter [${this._enter}] => [${state}]`);
            this._enter = state;
        },

        _leave: false,
        get leave() {
            return this._leave
        },
        set leave(state) {
            // console.log(`Setting dragState.leave [${this._leave}] => [${state}]`);
            this._leave = state;
        },

        _over: false,
        get over() {
            return this._over
        },
        set over(state) {
            // console.log(`Setting dragState.over [${this._over}] => [${state}]`);
            this._over = state;
        },
    }


    /** @type {Object} */
    expectedComponentDrag = null;


    /**
     * @param {Component} component
     * @returns {Promise<Configuration>}
     */

    expectComponentDrag(component) {

        this.dragState.active = true
        this.dragState.leave = false
        this.dragState.over = false;


        console.assert(component instanceof Component, 'expectComponentDrag -> entered component needs to be an instance of the class Component!')

        //check if true then reject current expectation
        if (this.expectedComponentDrag) {
            console.warn('Another component drag is still being expected... cancelling that one');
            this.expectedComponentDrag.reject('A newer component expectation has made this one obsolete');
        }

        let resolve, reject = null;

        const promise = new Promise((res, rej) => { resolve = res; reject = rej; });

        // clean up expectedComponentDrag when done
        promise
            .catch(err => {
                console.error('Error in expected component drag chain:', err)
            })
            .finally(() => {
                // console.log('Drag promise fulfilled');
                this.expectedComponentDrag = null;
                //this.dragObject = null;
            });


        // set expectation
        this.expectedComponentDrag = { component, promise, resolve, reject };

        // console.log(this.expectedComponentDrag)

        const view = this;

        //our patience is limited
        if (this.expectedComponentDrag) {
            this.expectedComponentDrag.timeout = setTimeout(
                function () {
                    // console.log('Reject expected drag after timeout');
                    view.expectedComponentDrag.reject('Timeout');
                },
                2000
            );
        };

        return promise;
    }


    checkDrag(event) {
        //return event.dataTransfer.types.contains("application/x-moz-file");
    }

    /** @type {Array} */
    matOptions = []



    /**
     * Can receive data in the form of a coomponent.
     * Evaluates the data and makes distinction between a wrapped material or a block.
     * @fires View#dragenter
     */

    async onDragEnter(event) {

        event.preventDefault();

        //reset the shift
        this.shift = new Vector3();

        this.hideDimensions()

        if (this.dragState.active) {

            if (this.dragState.leave) {

                //console.log('drag re-enter');

                this.lastPointerMove.x = event.clientX;
                this.lastPointerMove.y = event.clientY;

                this.dragState.leave = false;
                this.dragState.over = false;
            }
            else {
                this.dragState.enter = true;
                this.dragState.leave = false;
                this.dragState.over = false;

                //console.log('drag enter')

                this.onPointerDownActive === true;
                this.cameraControls.enabled = false;  //disable cameraControls

                this.lastPointerMove.x = event.clientX;
                this.lastPointerMove.y = event.clientY;

                const component = this.expectedComponentDrag.component

                if (component) {

                    component.build()

                    clearTimeout(this.expectedComponentDrag.timeout);

                    switch (component.constructor) {

                        case WrappedMaterial:

                            // console.log("drag enter wrapped material")
                            const material = component

                            this.dragObject = material

                            break;


                        case Block:

                            // console.log("drag enter block")

                            //Make new configuration/configurator
                            const configuration = new Configuration(new Reporter(), { pkg: component.tree, blockInstances: [new BlockInstance(new Reporter(), { block: component })] })
                            await configuration.build()
                            const newConfigurator = await this.addConfigurator({ pkg: component.tree, configuration });

                            this.dragObject = newConfigurator //set the draggable object

                            this.expectedComponentDrag.createdConfigurator = newConfigurator;

                            this.expectedComponentDrag.promise.catch(err => {

                                // deze fn bestaat nog niet
                                //Project.removeConfigurator(newConfigurator);

                                throw new Error(err);

                            });


                            //show bbox for reference
                            //this.showBoundingBoxes( configuration )

                            //get the inetrsection
                            const intersections = this.intersect(this.lastPointerMove.x, this.lastPointerMove.y); //<-als er in de lucht wordt gesleept dan kan die geen intersection maken met de drag plane!
                            //this.intersect(this.lastPointerMove.x, this.lastPointerMove.y);

                            if( intersections[0] !== undefined ){
                                //console.log( intersections[0].point )
                                this.dragPlane.setFromNormalAndCoplanarPoint(this.dragPlaneNormal, intersections[0].point) //set the dragPlane on the first intersection point
                            
                                //this.shift.subVectors( this.dragObject.content.main.medium.position, intersections[0].point );  //set a shift of the object versus the dragPlane

                                //set a shift of the object versus the dragPlane
                                //this.shift.subVectors(this.dragObject.body.position, intersections[0].point);  //sets this vector to a - b 
                            
                            
                            }else{
                                
                            }
                            
                            
                             

                            //get the point of intersection
                            this.rayCaster.ray.intersectPlane(this.dragPlane, this.pointOfIntersection);
                            // console.log(this.pointOfIntersection)

                            //set the position of the dragobject based on the point of intersection
                            this.dragObject.body.position.copy(this.pointOfIntersection)
                            this.dragObject.body.position.setY(0)

                            // console.log(this.dragObject.body)
                            this.scene.add(this.dragObject.body)

                            //update the scene with the new position
                            this.shadow._update([this.dragObject.body]);
                            this.update()

                            break;

                        //deze case en de bovenstaande case beter uitwerken! Daar zit nog een bug in op regel 695 -> (TypeError: Cannot read property 'point' of undefined) -> deze bug vind plaats zodra er iets op de lucht wordt gesleept en die geen intersection kan geven!
                        case Configuration:

                            // console.log("drag enter configuration")
                            // console.log( component )
                            // console.log( component.tree.pkg )

                            //hier eerst een nieuwe configuration aanmaken op basis van de bestaande! anders kun je geen twee dezelfde configurations in de scene zetten!
                            await component.build()
                            const newConfiguratorConfig = await this.addConfigurator({ pkg: component.tree.pkg, configuration: component });

                            // console.log( newConfiguratorConfig )

                            this.dragObject = newConfiguratorConfig //set the draggable object

                            this.expectedComponentDrag.createdConfigurator = newConfiguratorConfig;

                            this.expectedComponentDrag.promise.catch(err => {

                                // deze fn bestaat nog niet
                                //Project.removeConfigurator(newConfigurator);

                                throw new Error(err);

                            });


                            //show bbox for reference
                            //this.showBoundingBoxes( configuration )

                            //get the inetrsection
                            const intersectionsConfig = this.intersect(this.lastPointerMove.x, this.lastPointerMove.y);
                            // console.log( intersectionsConfig )
                            //this.intersect(this.lastPointerMove.x, this.lastPointerMove.y);

                            this.dragPlane.setFromNormalAndCoplanarPoint(this.dragPlaneNormal, intersectionsConfig[0].point) //set the dragPlane on the first intersection point
                            
                            //set a shift of the object versus the dragPlane
                            //this.shift.subVectors(this.dragObject.body.position, intersectionsConfig[0].point);  //sets this vector to a - b  

                            //get the point of intersection
                            this.rayCaster.ray.intersectPlane(this.dragPlane, this.pointOfIntersection);

                            //set the position of the dragobject based on the point of intersection
                            this.dragObject.body.position.copy(this.pointOfIntersection)
                            this.dragObject.body.position.setY(0)

                            // console.log(this.dragObject.body)
                            this.scene.add(this.dragObject.body)

                            //update the scene with the new position
                            this.shadow._update([this.dragObject.body]);
                            this.update()

                            break;


                        default:
                            this.expectedComponentDrag.reject(`Unknown onDragEnter expected component constructor ${this.expectedComponentDrag.component.constructor.name}`);

                    }
                }
            }

        }
        else {
            console.warn(`DragEnter event received, but dragState = `, this.dragState)
        }

    }


    /**
     * Finds the block 'under' the current (last known) mouse coordinates
     * when a drag is happening and emits an event with that data and the
     * original dragover event
     * @fires View#dragover
     */


    async onDragOver(event) {

        if (this.dragState.active) {

            // console.log('drag over')
            event.preventDefault();

            this.lastPointerMove.x = event.clientX;
            this.lastPointerMove.y = event.clientY;

            //set the drag state
            if (this.dragState.over === false) {

                //await this.dragObject.configuration.build()

                if (this.dragState.enter) { //<- hier moet nog een betere oplossing voor komen
                    // console.log('disable shadow')
                    // console.log(this.dragObject.body)
                    this.shadow._update([this.dragObject.body]);
                }
                this.update()
                this.dragState.over = true;
                //this.dragState.enter = false;
            }

            //get the point of intersection
            this.intersect(this.lastPointerMove.x, this.lastPointerMove.y);
            this.rayCaster.ray.intersectPlane(this.dragPlane, this.pointOfIntersection);

            //set the position of the dragobject based on the point of intersection
            if (this.dragObject instanceof Configurator) {
                this.dragObject.body.position.copy(this.pointOfIntersection)
                this.dragObject.body.position.setY(0)
            }

            this.update()
        }

    }



    /**
     * Finds the block 'under' the drop location
     * and emits an event with that data and the drop event 
     * @fires View#drop
     */

    async onDrop(event) {

        if (this.dragState.active) {

            // console.log('drop')
            event.preventDefault();
            const dataTransfer = event.dataTransfer.getData("text"); //hiermee nog een check uitvoeren op de ID van het object?

            this.onPointerDownActive = false;
            this.cameraControls.enabled = true; //re-enable cameraControls from down event

            if (this.dragObject) {

                // console.log( "position", this.dragObject.position )

                switch (this.dragObject.constructor) {

                    case WrappedMaterial:
                        // console.log("drop wrapped material")

                        const data = this.findFirstIntersectedBlockInstance() || {};
                        data.event = event;
                        const configurator = this.findConfigurator(data.blockInstance.tree)

                        this.matOptions = configurator.configuration.options.material(this.dragObject); // check the possible material options

                        // console.log(this.matOptions)

                        const assignable = data.intersection?.object?.userData?.PB?.assignable;

                        let selectedMatOption = null;

                        if (assignable) {
                            selectedMatOption = this.matOptions.find(opt => opt.assignable === assignable);
                            if (selectedMatOption) {
                                this.domElement.style.cursor = "cell";
                            }
                            else {
                                this.domElement.style.cursor = "no-drop";
                            }
                        }
                        else {
                            this.domElement.style.cursor = "no-drop";
                        }

                        const newConfiguration = configurator.configuration.assignMaterial(selectedMatOption);

                        await configurator.update(newConfiguration);

                        this.expectedComponentDrag.resolve(newConfiguration);

                        break;

                    case Configurator:

                        // console.log('drop configurator')
                        //get the point of intersection and update
                        this.intersect(this.lastPointerMove.x, this.lastPointerMove.y);
                        this.rayCaster.ray.intersectPlane(this.dragPlane, this.pointOfIntersection);
                        this.pointOfIntersection.y = 0; //force y = 0!!
                        this.dragObject.body.position.copy(this.pointOfIntersection)

                        //get & update the configurator position
                        this.dragObject.position.copy(this.dragObject.body.position) // in this case the dragObject is the configurator
                        
                        this.expectedComponentDrag.resolve(this.expectedComponentDrag.createdConfigurator.configuration);

                        break;

                    default:
                        this.expectedComponentDrag.reject(`Unknown onDrop expected component constructor ${this.expectedComponentDrag.component.constructor.name}`);
                        break;


                }
            }

            //update the scene
            this.shadow._update()
            this.update()

            this.dragState.active = false;
            this.dragState.enter = false;
            this.dragState.leave = false;
            this.dragState.over = false;

            this.selectionActive = true;
        }

    }





    onDragLeave(event) {

        if (this.dragState.active) {

            event.preventDefault();
            // console.log('drag leave')

            if (this.dragState.enter) {

                this.dragState.enter = false;
                this.dragState.leave = true;
                this.dragState.over = false;

            }

            this.update()
        }

    }

    onDragEnd() {

        if (this.dragState.active) {

            // console.log('drag end')
            this.cameraControls.enabled = true; //re-enable cameraControls from down event

            if (this.dragObject) {
                this.selectionActive = true;
            }
            else {
                this.selectionActive = false;
                this.dragObject = null;

            }
            if (this.dragState.leave) {
                this.expectedComponentDrag.reject('The drag event has left the window with a drop event');
            }

            //reset drag states
            this.dragState.active = false;
            this.dragState.enter = false;
            this.dragState.over = false;
            this.dragState.leave = false;

            //update scene
            this.shadow._update();
            this.update()

        }
    }

    // onComponentDragEnter(){

    //     console.log( 'component drag enter' )

    //     // https://developer.mozilla.org/en-US/docs/Web/CSS/cursor
    //     this.domElement.style.cursor = "crosshair";

    //     const data = this.findFirstIntersectedBlockInstance() || {};        
    //     data.event = event;
    //     const assignable = data.intersection?.object?.userData?.PB?.assignable;

    //     if ( assignable ) {
    //         const selectedMatOption = matOptions.find(opt => opt.assignable === assignable);
    //         if ( selectedMatOption) {
    //             this.domElement.style.cursor = "cell";
    //         }
    //         else {
    //             this.domElement.style.cursor = "no-drop";
    //         }
    //     }
    //     else {
    //         this.domElement.style.cursor = "no-drop";
    //     }

    // }


    onComponentDragLeave(event) {

        if (Object.keys(listenerObject).length > 0) {

            //voorlopig uitgezetomdat dit conflicteert met de drag functies

            // this.domElement.addEventListener(
            //     'dragover',
            //     viewBoundDragEmitter
            // );
        }
        else {
            // this.domElement.removeEventListener(
            //     'dragover',
            //     viewBoundDragEmitter
            // );
        }

    }


    onKeyUp(event) {

        let key = event.which || event.keyCode;		// both these properties are deprecated...

        // //Rotate selectin -> Shift C
        if (event.shiftKey && key === 67) {
            // console.log('rotate selection')
            if (this.dragObject) {
                // console.log('rotate selection 2')
                this.rotateSelection(this.dragObject)
            }
        }

        if ( key === 67 ) {
            // console.log('connectors')
            this.toggleConnectorHelpers()
        }

    }

    rotateSelection( selection ) {

        if (selection instanceof Configurator) {

            const configurator = selection
            //console.log( configurator.body.position )
            //console.log( configurator.quaternion )
            configurator.move(configurator.body.position, configurator.quaternion.multiply(new Quaternion().setFromEuler(new Euler(0, Math.PI / 2, 0))));
            this.shadow._update()
            this.update()

        }
        else if (selection instanceof Block ) {

            const configurator = this.findConfigurator( selection.tree) 
            configurator.move(configurator.body.position, configurator.quaternion.multiply(new Quaternion().setFromEuler(new Euler(0, Math.PI / 2, 0))));
            this.shadow._update()
            this.update()

        }

    }



    //Update Functions

    update() {
        
        super.update();
    }

    //This function is called after a static update of the scene
    onSceneUpdate(...args) {

        if (this.dragState.active) {
            //do nothing
        }
        else {
            this.shadow._update();
        }

        super.onSceneUpdate(...args);
    }


    toggleConnectorHelpers(){

        // console.log('connector helpers')
        
        // console.log( this )

        this.scene.traverse( function(child)  {

            if( child.name === 'Actor' ){
                // console.log( child.userData.origin.configuration.connectors )
                for( let connector of child.userData.origin.configuration.connectors ){
                    const position = connector.connector._settings.position 
                    // console.log( position )
                    const box = new BoxGeometry(0.1, 0.1, 0.1)
                    const mat = new MeshBasicMaterial( )
                    const mesh = new Mesh( box, mat )
                    mesh.position.copy(position)
                    scene.add(mesh)
                }
            }
        })

         

        // const connectorGroup = new Group()
        // connectorGroup.name = "connectors"

        // const dir = new Vector3(0, 0, 0.1);
        // dir.normalize();                                                                //normalize the direction vector (convert to vector of length 1)
        // const origin = new Vector3(0, 0, 0);                                          // origin of the arrowHelper (not able to set oafter creation -> use position instead)
        // const length = 0.15;                                                             // length of the arrowHelper
        // const hex = 0xff0000;                                                           // color of the arrowHelper
        // const headLength = 0.05                                                         // The length of the head of the arrow. Default is 0.2 * length.                                               
        // const headWidth = 0.05                                                          // The width of the head of the arrow. Default is 0.2 * headLength.
        // const arrowHelper = new ArrowHelper(dir, origin, length, hex, 0.05, 0.05);   // arrowhelper template

        // //HELPER - ARROW -> place helpers in block group
        // for (var i = 0; i < this.settings.connectors.length; i++) {

        //     //get the connector data
        //     //console.log( this.settings) 
        //     const conn = this.settings.connectors[i].content.main[quality]  //get the connector   
        //     //console.log( conn )                         
        //     const newDir = new Vector3(0, 0, 2);                            //create new vector, deze moet groter zijn dan 1! zit anders een bug in dat die de vector niet negatief kan roteren!
        //     newDir.applyQuaternion(conn.quaternion)                         //apply connector rotation to vector  
        //     //console.log( newDir )
        //     const newArrowHelper = arrowHelper.clone()                      //create copy of the arrow helper template                        
        //     newArrowHelper.position.copy(conn.position);                    //copy the position of the connector to the arrowhelper
        //     newArrowHelper.setDirection(newDir)
        //     //newArrowHelper.layers.set( layerMap.connectorHelpers )

        //     connectorGroup.add(newArrowHelper)
        //     //connectorGroup.layers.set( layerMap.connectorHelpers )

        //     //block.add(connectorGroup)

        // }




    }



};

export { DefaultView };