Three.JS Orbit Controls - enabling and disabling without position jumping


I am creating a geometry manipulation prototype with Three.JS. I am using OrbitControls.JS to manipulate the camera and am having trouble enabling and disabling the controls.

Here is my demo: http://moczys.com/webGL/Prototype_V02-05.html

The idea is that when you hover over a vertex of the Tetrahedron, a grey sphere appears. By clicking on the sphere, you bring up a vertex manipulation handle. Then by clicking an dragging on an arrow, you can move the vertex in that direction. You should then be able to click away from the geometry to get out of this mode.

The problem occurs when you click away. If you click & drag after moving the vertex, the camera goes a little crazy. Depending on how far away from the original point you are, the OrbitControls will spin the camera a distance in that direction. This is just a really jarring/confusing action that really impacts the usability, so I'd like to fix it, but can't seem to locate the problem.

I think that it is recording the initial position in the OrbitControls.js, and then keeping that until they are re-enabled... however I haven't been able to figure out where. This occurs somewhere in the MouseUp, MouseDown, and MouseMove event handlers. I was hoping this might be interesting to someone who might know more about the operation of OrbitControls than I do.

And here is my clicking/dragging event handler code:

    function onDocumentMouseMove( event ) 
{
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
//event.preventDefault();

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

if(MOUSEDOWN&&editMode==2)
{
    var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
    projector.unprojectVector( vector, camera );

    var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
    //lastPoint = vertexIntersects[0].object;
    var instance = vertexTargets.indexOf(lastPoint);
    if(vertexEdit==1){
        var intersects = raycaster.intersectObject(XYplane);

        vertexTargets[instance].position.x=intersects[0].point.x;
        targetList[0].geometry.vertices[instance].x=intersects[0].point.x;
        //console.log("x = "+intersects[0].point.x);
    }
    else if(vertexEdit==2){
        var intersects = raycaster.intersectObject(XYplane);
        vertexTargets[instance].position.y=intersects[0].point.y;
        targetList[0].geometry.vertices[instance].y=intersects[0].point.y;
        //console.log("y = "+intersects[0].point.y);
    }
    else if(vertexEdit==3){
        var intersects = raycaster.intersectObject(YZplane);
        vertexTargets[instance].position.z=intersects[0].point.z;
        targetList[0].geometry.vertices[instance].z=intersects[0].point.z;
        //console.log("z = "+intersects[0].point.z);
    }
    setAxisPosition(vertexTargets[instance].position.clone());
    var geom = targetList[0].geometry;
    geom.computeCentroids();
    geom.computeFaceNormals();
    geom.computeVertexNormals();
    geom.verticesNeedUpdate = true;
    geom.normalsNeedUpdate = true;
    updatePanels(targetList[0]);

    }
}


function onDocumentMouseDown( event ) 
{
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
// event.preventDefault();

//console.log("Click.");
MOUSEDOWN = true;
// update the mouse variable
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

checkSelection();   
if(editMode==2){
    controls.enabled = false;
    controls.rotate = false;
}
else{
    controls.enabled = true;
    controls.rotate = true;
}

}

function onDocumentMouseUp( event ) 
{
//event.preventDefault();
if (editMode!=2){
   controls.enabled = true;
   controls.rotate = true;
}
MOUSEDOWN = false;
if(editMode==2){
    //editMode=1;
    //updateVertexTargets(targetList[0].geometry);
}

}

I'd love to hear any suggestions that people might have, thanks!

UPDATE Q1 2019

noRotate is now deprecated, use enableRotate instead.


I figured it out! After looking closer at OrbitControls.JS, there is a "noRotate" flag that can be set that returns out of the rotate function, completely eliminating the creation of a start vector like I was talking about above.

Here is the working demo: http://moczys.com/webGL/Prototype_V02-05-2.html

And here is the code with changes commented:

function onDocumentMouseMove( event ) 
{
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
//event.preventDefault();

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


if(MOUSEDOWN&&editMode==2)
{
// Added to stop rotation while moving a vertex with the arrow handles
    controls.noRotate = true;

    var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
    projector.unprojectVector( vector, camera );

    var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
    //lastPoint = vertexIntersects[0].object;
    var instance = vertexTargets.indexOf(lastPoint);
    if(vertexEdit==1){
        var intersects = raycaster.intersectObject(XYplane);

        vertexTargets[instance].position.x=intersects[0].point.x;
        targetList[0].geometry.vertices[instance].x=intersects[0].point.x;
        //console.log("x = "+intersects[0].point.x);
    }
    else if(vertexEdit==2){
        var intersects = raycaster.intersectObject(XYplane);
        vertexTargets[instance].position.y=intersects[0].point.y;
        targetList[0].geometry.vertices[instance].y=intersects[0].point.y;
        //console.log("y = "+intersects[0].point.y);
    }
    else if(vertexEdit==3){
        var intersects = raycaster.intersectObject(YZplane);
        vertexTargets[instance].position.z=intersects[0].point.z;
        targetList[0].geometry.vertices[instance].z=intersects[0].point.z;
        //console.log("z = "+intersects[0].point.z);
    }
    setAxisPosition(vertexTargets[instance].position.clone());
    var geom = targetList[0].geometry;
    geom.computeCentroids();
    geom.computeFaceNormals();
    geom.computeVertexNormals();
    geom.verticesNeedUpdate = true;
    geom.normalsNeedUpdate = true;
    updatePanels(targetList[0]);

}
}


function onDocumentMouseDown( event ) 
{
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
// event.preventDefault();

//console.log("Click.");
MOUSEDOWN = true;
// update the mouse variable
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

checkSelection();   
if(editMode==2){
    //controls.enabled = false;
    //controls.rotate = false;

// Added here to disable rotation when the arrow handles are active
    controls.noRotate = true;
}
else{
    //controls.enabled = true;
    //controls.rotate = true;

// Added here to enable rotation all other times
    controls.noRotate = false;
}

}

function onDocumentMouseUp( event ) 
{
//event.preventDefault();
if (editMode!=2){
    //controls.enabled = true;
    //controls.rotate = true;
}
MOUSEDOWN = false;

// add here to enable rotation whenever the mouse button is lifted
controls.noRotate = false;

}

Hope somebody finds this useful!


I haven't been able to test it, but I think that you code should be

function onDocumentMouseDown( event ) 
{
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
// event.preventDefault();

//console.log("Click.");
MOUSEDOWN = true;
// update the mouse variable
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;

checkSelection();   
    if(editMode==2){
        controls.enabled = false;
        controls.rotate = false;
    }else{
        controls.enabled = true;
        controls.rotate = true;
        controls.onMouseDown (event);    // added this line to set the correct state
    }
}