简体   繁体   中英

Use CTRL-click to select multiple three.js objects?

I'm trying to enable the user to select multiple three.js object using standard CTRL-click. Single-select is working perfectly. When doing CTRL-click, I'm getting behaviors I can't understand. I'm getting duplicate entries in my SELECTED array, and when I click a second object, sometimes it gets added to the array, sometimes not. Not every item in the SELECTED array has the transformation applied... I can't even really make sense of the results I'm getting. I think the issue is somehow with the logic of my statement inside the if ( event.type === 'click' && event.ctrlKey ) statement in the mouseEventHandler() function, but I just can't figure out what I'm doing wrong.

Here's my relevant code:

var ray = new THREE.Raycaster();
var mouse = new THREE.Vector2();
var INTERSECTED;  // Object closest to the camera
var SELECTED = [];    // Object selected via dblclick

function onMouse( event ) {

    event.preventDefault();

    // calculate mouse position in normalized device coordinates
    // (-1 to +1) for both components

    mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
    mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

    mouseEventHandler( event );

}

function mouseEventHandler( event /* , fn, revFn */ ){

    // update the picking ray with the camera and mouse position
    ray.setFromCamera( mouse, entities.cameras.perspCamera );

    // calculate objects intersecting the picking ray
    var intersects = ray.intersectObjects( scene.children );

    // if there's at least one intersected object...
    if ( intersects && intersects[0] && intersects[0].object ){

        // Check if the event is a mouse move, INTERSECTED exists and we're sitting on the same INTERSECTED object as the last time this function ran...        
        if ( event.type === 'mousemove' ){
            // Check if the current top-level intersected object is the previous INTERSECTED        
            if ( intersects[ 0 ].object != INTERSECTED ){
                // ... if there is a previous INTERSECTED
                if ( INTERSECTED ) {    
                    // restore the previous INTERSECTED to it's previous state.
                    unTransformGraphElementOnMouseOut( INTERSECTED );                                   
                }                       
                // set the currently intersected object to INTERSECTED  
                INTERSECTED = intersects[ 0 ].object;       
                // and transform it accordingly.
                transformGraphElementOnMouseOver( INTERSECTED );                            
                }   
        }       

        if ( event.type === 'click' && event.ctrlKey ){

            // If there's an INTERSECTED object that's a GraphElement 
            var intersectedIsGraphElement = INTERSECTED && INTERSECTED.isGraphElement;
            var selectedIncludesIntersected = SELECTED && SELECTED.includes( INTERSECTED );
            var selectedDoesntIncludeIntersected = SELECTED && !SELECTED.includes( INTERSECTED );

            if ( !INTERSECTED || !INTERSECTED.isGraphElement ){             
                return;
            }

            if ( intersectedIsGraphElement ) { 

                // If SELECTED includes INTERSECTED, leave it alone.
                if ( selectedIncludesIntersected ) { 
                    return; 
                    }

                // If SELECTED doesn't include INTERSECTED, transform it and add it.
                else if ( selectedDoesntIncludeIntersected ) { 
                    SELECTED.push( INTERSECTED );
                    transformGraphElementOnSelect( INTERSECTED );
                    console.log( SELECTED );
                    }
            }
        }

        if ( event.type === 'click' ){  // If CTRL isn't clicked

            // If there's no INTERSECTED or the INTERSECTED isn't a GraphElement
            if ( !INTERSECTED || !INTERSECTED.isGraphElement ){             
                // If there's a SELECTED Array
                if ( SELECTED ){
                    // for all elements in the SELECTED array
                    for ( var s = 0; s < SELECTED.length; s++ ){
                        // restore them to their unselected state.
                        unTransformGraphElementOnUnselect( SELECTED[ s ] );

                    }
                    // and purge the SELECTED array.
                    SELECTED = [];
                }
            }

            // IF there's an INTERSECTED and it's a GraphElement
            if ( INTERSECTED && INTERSECTED.isGraphElement ){

                // If there's a SELECTED array
                if ( SELECTED ){

                    // If that SELECTED array includes the currently INTERSECTED object
                    if ( SELECTED.includes ( INTERSECTED ) ) { 
                        // Negative for loop -- untransform and remove from SELECTED everything but the INTERSECTED object
                        for ( var s = 0; s < SELECTED.length; s++ ){

                            if ( SELECTED[ s ] !== INTERSECTED ){

                                unTransformGraphElementOnUnselect( SELECTED[ s ] );
                                SELECTED.splice( s, 1 ); 

                            }
                        }
                    }

                    else { 

                        for ( var s = 0; s < SELECTED.length; s++ ){ 
                            unTransformGraphElementOnUnselect( SELECTED[ s ] ); 
                            }
                        SELECTED = [];

                    }
                }

                transformGraphElementOnSelect( INTERSECTED );
                SELECTED.push( INTERSECTED );
            }
        }

        // Check if the mouse event is a wheel event (This is temporary, just to see if we can save a file with the change. We're also going to make it so that the change happens at the level of the graphElement itself, and not just the displayObject )
        if ( event.type === 'wheel' ){
            if ( intersects[ 0 ].object.isGraphElement && intersects[ 0 ].object === INTERSECTED ){
                // transform on wheel.
                transformGraphElementOnWheel( INTERSECTED );                            
            }           
        }

    INTERSECTED && console.log( 'INTERSECTED.isGraphElement: ', INTERSECTED.isGraphElement, 'MouseEvent: ', event.type );           
    }
}

function transformGraphElementOnMouseOver( obj ){
    if ( obj.isGraphElement ) { obj.referent.transformOnMouseOver(); }  
}

function unTransformGraphElementOnMouseOut( obj ){
    if ( obj.isGraphElement ) { obj.referent.transformOnMouseOut(); }
}

function transformGraphElementOnSelect( obj ){
    if ( obj.isGraphElement ) { obj.referent.transformOnDblClick(); }   
}

function unTransformGraphElementOnUnselect( obj ){
    if ( obj.isGraphElement ) { obj.referent.unTransformOnDblClickOutside(); }  
}

function transformGraphElementOnWheel( obj ){
    if ( obj.isGraphElement ) { obj.referent.transformOnWheel(); }  
}

function listenFor(){
    document.getElementById('visualizationContainer').addEventListener( 'click', onMouse, false );
    document.getElementById('visualizationContainer').addEventListener( 'mousemove', onMouse, false );
    document.getElementById('visualizationContainer').addEventListener( 'mousedown', onMouse, false );
    document.getElementById('visualizationContainer').addEventListener( 'dblclick', onMouse, false )
    document.getElementById('visualizationContainer').addEventListener( 'wheel', onMouse, false );
    document.getElementById('visualizationContainer').addEventListener( 'contextmenu', onMouse, false );
}

listenFor();

@marekful thank you. Your tip off was part of the solution and helped me unravel the rest of it. The second part of my issue was that my splice action running inside the for loop was changing the array indexes themselves as I was looping through them, thereby short-circuiting the for loop. Finally, the duplication was happening because I was pushing INTERSECTED back into the SELECTED array after it was done processing, regardless of whether SELECTED included INTERSECTED or not.

There's still another issue with my code - when I click the sky, where no objects are located, no click is registered. But I don't think that's relevant to the scope of my question here, which appears to be resolved. Here's the updated code to my mouseEventHandler():

function mouseEventHandler( event /* , fn, revFn */ ){

// update the picking ray with the camera and mouse position
ray.setFromCamera( mouse, entities.cameras.perspCamera );

// calculate objects intersecting the picking ray
var intersects = ray.intersectObjects( scene.children );

// if there's at least one intersected object...
if ( intersects && intersects[0] && intersects[0].object ){

    // Check if the event is a mouse move, INTERSECTED exists and we're sitting on the same INTERSECTED object as the last time this function ran...        
    if ( event.type === 'mousemove' ){
        // Check if the current top-level intersected object is the previous INTERSECTED        
        if ( intersects[ 0 ].object != INTERSECTED ){
            // ... if there is a previous INTERSECTED
            if ( INTERSECTED ) {    
                // restore the previous INTERSECTED to it's previous state.
                unTransformGraphElementOnMouseOut( INTERSECTED );                                   
            }                       
            // set the currently intersected object to INTERSECTED  
            INTERSECTED = intersects[ 0 ].object;       
            // and transform it accordingly.
            transformGraphElementOnMouseOver( INTERSECTED );                            
            }   
    }       

    if ( event.type === 'click' ){

        if ( event.ctrlKey ){

            // If there's an INTERSECTED object that's a GraphElement 
            var intersectedIsGraphElement = INTERSECTED && INTERSECTED.isGraphElement;
            var selectedIncludesIntersected = SELECTED && SELECTED.includes( INTERSECTED );
            var selectedDoesntIncludeIntersected = SELECTED && !SELECTED.includes( INTERSECTED );

            if ( !INTERSECTED || !INTERSECTED.isGraphElement ){             
                return;
            }

            if ( intersectedIsGraphElement ) { 

                // If SELECTED includes INTERSECTED, leave it alone.
                if ( selectedIncludesIntersected ) { 
                    return; 
                    }

                // If SELECTED doesn't include INTERSECTED, transform it and add it.
                else if ( selectedDoesntIncludeIntersected ) { 
                    SELECTED.push( INTERSECTED );
                    transformGraphElementOnSelect( INTERSECTED );
                    console.log( SELECTED );
                    }
            }
        }

        else if ( !event.ctrlKey ){  // If CTRL isn't clicked

            // If there's no INTERSECTED or the INTERSECTED isn't a GraphElement
            if ( !INTERSECTED || !INTERSECTED.isGraphElement ){             
                // If there's a SELECTED Array
                if ( SELECTED ){
                    // for all elements in the SELECTED array
                    for ( var s = 0; s < SELECTED.length; s++ ){
                        // restore them to their unselected state.
                        unTransformGraphElementOnUnselect( SELECTED[ s ] );

                    }
                    // and purge the SELECTED array.
                    SELECTED = [];
                }
            }

            // IF there's an INTERSECTED and it's a GraphElement
            if ( INTERSECTED && INTERSECTED.isGraphElement ){

                // If there's a SELECTED array
                if ( SELECTED ){

                    // If that SELECTED array includes the currently INTERSECTED object
                    if ( SELECTED.includes ( INTERSECTED ) ) { 
                        // Negative for loop -- untransform and remove from SELECTED everything but the INTERSECTED object
                        for ( var s = 0; s < SELECTED.length; s++ ){

                            if ( SELECTED[ s ] !== INTERSECTED ){
                                unTransformGraphElementOnUnselect( SELECTED[ s ] );                             
                            }
                        }
                        SELECTED = [ INTERSECTED ];
                        console.log( SELECTED );
                    }

                    else { 

                        for ( var s = 0; s < SELECTED.length; s++ ){ 
                            unTransformGraphElementOnUnselect( SELECTED[ s ] ); 
                            }
                        SELECTED = [];

                        transformGraphElementOnSelect( INTERSECTED );
                        SELECTED.push( INTERSECTED );                           
                    }
                }
            }
        }
    }


    // Check if the mouse event is a wheel event (This is temporary, just to see if we can save a file with the change. We're also going to make it so that the change happens at the level of the graphElement itself, and not just the displayObject )
    if ( event.type === 'wheel' ){
        if ( intersects[ 0 ].object.isGraphElement && intersects[ 0 ].object === INTERSECTED ){
            // transform on wheel.
            transformGraphElementOnWheel( INTERSECTED );                            
        }           
    }

INTERSECTED && console.log( 'INTERSECTED.isGraphElement: ', INTERSECTED.isGraphElement, 'MouseEvent: ', event.type );           
}

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM