Three.js四元数轮换未正确应用

时间:2018-06-08 00:09:08

标签: three.js rotation trigonometry quaternions euler-angles

我正在构建一个包含旋转工具的应用。我几乎让它工作但不完全。为了给出一个视觉,这里有一个截图:

Rotation Tool Active

在图像中,红点是中心点,绿点形成角度的第一行,蓝点跟随鼠标。用户放置中心点(红色),放置第一行(红点),然后放置旋转的项目(图像中的三个蓝色球体),跟随蓝点,以相同的角度旋转。当用户单击上次时,将放置旋转的对象,并且工具向导将消失。

问题在于,尽管物体以适当的中心点旋转,但它们根本不会与蓝点对齐旋转。随着角度变宽,旋转似乎加速到这样的程度,当角度看起来是~60时,速度似乎是无限的(旋转物体位于完全相同的位置,卡在那里)。有时旋转会反转方向。

考虑到这种行为,我认为它可能与某些触发某些触发功能的误用有关,但我不确定它会是什么或在哪里找到它。

我试图用四元数旋转来做这件事。我将注意到,在构建UI工具之前,我使用Euler角度从控制台旋转这些对象,并且事情运行良好,但是我认为四元数可能是更好的解决方案。

以下是相关代码:

var clickCounter;
var angleLineMaterial = new THREE.LineBasicMaterial({ color: 0x888888 });

function initRotationTool(){

    rotToolState = {
        points: [],
        angleLines: [],
        quaternion: null,
        eul: {}
    }

    clickCounter = 0;
}

initRotationTool();

function lineToPoint( line, endPosition ){

    var end = new THREE.Vector3( endPosition.x, endPosition.y, endPosition.z );
    line.geometry.vertices[1] = end;
    line.geometry.verticesNeedUpdate = true;
}

var angleLine0ToMouse = function( e ){
    lineToPoint ( rotToolState.angleLines[0], placeAtPlaneIntersectionPoint( activeGuidePlane ) );
}

var angleLine1ToMouse = function( e ){
    lineToPoint ( rotToolState.angleLines[1], placeAtPlaneIntersectionPoint( activeGuidePlane ) );
}

function movePointTo( point, position ){
    point.position = { x: position.x, y: position.y, z: position.z };
    point.displayEntity.position.copy( point.position );
}

var toolPoint2FollowMouse = function( e ){
    movePointTo( rotToolState.points[ 2 ], placeAtPlaneIntersectionPoint( activeGuidePlane )  );
}

var getRotToolQuaternion = function( e ){
    rotToolState.quaternion = getQuaternionBetweenVec3sOriginatingAtPoint( rotToolState.points[1].position, rotToolState.points[2].position, rotToolState.points[0].position );
    console.log( "getRotToolQuaternion", rotToolState.quaternion );
}

var getRotToolEuler = function( e ){
    rotToolState.eul = getEulerBetweenVec3sOriginatingAtPoint( rotToolState.points[1].position, rotToolState.points[2].position, rotToolState.points[0].position );
    console.log( "getRotToolEul", rotToolState.eul );
}

var rotNodesWithTool = function( e ){

    if ( SELECTED.nodes && SELECTED.nodes.length > 0 ){ 
        //rotateNodeArrayOnAxisAroundPoint( SELECTED.nodes, "y", _Math.degToRad ( rotToolState.quaternion._y ) , rotToolState.points[0].position, order = 'XYZ' ); //nodeArr, axis, angle, point, order = 'XYZ' );

        quaternionRotateNodeArrayAroundPoint( SELECTED.nodes, rotToolState.quaternion, rotToolState.points[0].position );       
    }
}



function rotationTool( position ){

    if ( clickCounter === 0 ){

        //create the startPoint
        rotToolState.points.push ( new Point( position, 1.0, 0xff0000 ) ); 

        // initiate a line of zero length....       
        var lineStart = rotToolState.points[0].position;

        var lineEnd = position;

        var geometry = new THREE.Geometry();
        geometry.vertices.push(
            new THREE.Vector3( lineStart.x, lineStart.y, lineStart.z ),
            new THREE.Vector3( lineStart.x, lineStart.y, lineStart.z )
        );

        rotToolState.angleLines.push( new THREE.Line( geometry, angleLineMaterial ) );
        scene.add( rotToolState.angleLines[0] );            

        // And now add an event listener that moves the first line's second vertex with the mouse.
        document.getElementById('visualizationContainer').addEventListener( 'mousemove', angleLine0ToMouse, false );

        clickCounter++;
        return;
    }

    else if ( clickCounter === 1 ){

        // remove the eventlistener that moves the first line's second vertex with the mouse.   
        document.getElementById('visualizationContainer').removeEventListener( 'mousemove', angleLine0ToMouse, false );

        // drop the line-end and the endpoint ( rotToolState.points[1] ).       
        lineToPoint( rotToolState.angleLines[0], position );
        rotToolState.points.push ( new Point( position, 1.0, 0x00ff00 ) );

        // initiate a line of zero length....       
        var lineStart = rotToolState.points[0].position;
        var lineEnd = position;

        var geometry = new THREE.Geometry();
        geometry.vertices.push(
            new THREE.Vector3( lineStart.x, lineStart.y, lineStart.z ),
            new THREE.Vector3( lineStart.x, lineStart.y, lineStart.z )
        );

        rotToolState.angleLines.push( new THREE.Line( geometry, angleLineMaterial ) );
        scene.add( rotToolState.angleLines[1] );            

        // add a third point ( rotToolState.points[2] ) and line that both moves with the mouse 
        rotToolState.points.push ( new Point( position, 1.0, 0x0000ff ) );      

        document.getElementById('visualizationContainer').addEventListener( 'mousemove', toolPoint2FollowMouse, false );
        document.getElementById('visualizationContainer').addEventListener( 'mousemove', angleLine1ToMouse, false );
        document.getElementById('visualizationContainer').addEventListener( 'mousemove', getRotToolQuaternion, false ); 
        document.getElementById('visualizationContainer').addEventListener( 'mousemove', getRotToolEuler, false );  
        document.getElementById('visualizationContainer').addEventListener( 'mousemove', rotNodesWithTool, false );

        clickCounter++;
        return;

    }

    else if ( clickCounter === 2 ){

        // draw a second line to wherever the mouse is now. 
        document.getElementById('visualizationContainer').removeEventListener( 'mousemove', toolPoint2FollowMouse, false ); 
        document.getElementById('visualizationContainer').removeEventListener( 'mousemove', angleLine1ToMouse, false );         
        document.getElementById('visualizationContainer').removeEventListener( 'mousemove', getRotToolQuaternion, false );
        document.getElementById('visualizationContainer').removeEventListener( 'mousemove', getRotToolEuler, false );   
        document.getElementById('visualizationContainer').removeEventListener( 'mousemove', rotNodesWithTool, false );

        // drop the triangulating third point ( temporary )
        // rotToolState.points.push ( new Point( position, 1.0, 0x0000ff ) );       

        clickCounter++;
        return;
    }

    else if ( clickCounter === 3 ){



        // remove the eventlistener that moves second line's second vertex with the mouse & rotates everything.
/*      document.getElementById('visualizationContainer').removeEventListener( 'mousemove', function(e){   
            rotToolState.angleLines[1].vertex[1].position.set( ... );
            rotateEverythingSelected....
            } ); */

        // Drop everything in the new position.
//      rotateEverythingSelected...

        // remove the lines and points
        scene.remove( rotToolState.angleLines[0] );
        scene.remove( rotToolState.angleLines[1] );
        scene.remove( rotToolState.points[0].displayEntity );
        scene.remove( rotToolState.points[1].displayEntity ); 
        scene.remove( rotToolState.points[2].displayEntity ); 

        initRotationTool();
        return;
    }

    console.log( "I shouldn't execute. clickCounter = " , clickCounter );
}


function getQuaternionBetweenVec3s( v1, v2 ){

    return new THREE.Quaternion().setFromUnitVectors( v1, v2 );

}

function getQuaternionBetweenVec3sOriginatingAtPoint( v1, v2, point ){

    var vSub1 = new THREE.Vector3();
    var vSub2 = new THREE.Vector3();

    vSub1.subVectors( v1, point );
    vSub2.subVectors( v2, point );

    return getQuaternionBetweenVec3s( vSub1, vSub2 );

}

function getEulerBetweenVec3s( v1, v2 ){

    var vec1 = { z: { a: v1.x, b: v1.y },
                 y: { a: v1.x, b: v1.z },
                 x: { a: v1.y, b: v1.z }
                };

    var vec2 = { z: { a: v2.x, b: v2.y },
                 y: { a: v2.x, b: v2.z },
                 x: { a: v2.y, b: v2.z }
                };          

    var eul = {
        x: getAngleBetween2DVectors( vec1.x, vec2.x ),
        y: getAngleBetween2DVectors( vec1.y, vec2.y ),
        z: getAngleBetween2DVectors( vec1.z, vec2.z )
    };

    return eul;
}

function getEulerBetweenVec3sOriginatingAtPoint( v1, v2, point ){

    var vSub1 = new THREE.Vector3();
    var vSub2 = new THREE.Vector3();

    vSub1.subVectors( v1, point );
    vSub2.subVectors( v2, point );

    return getEulerBetweenVec3s( vSub1, vSub2 );

}

function getAngleBetween2DVectors( v1, v2 ){

    return Math.atan2( v2.b - v1.b, v2.a - v1.a ); 
}

和....

/* 3D ROTATION OF NODES AND NODE ARRAYS USING EULERS */

function rotateNodeOnAxisAroundPoint( node, axis, angle, point, order = 'XYZ' ){

    if ( !point ){ point = new THREE.Vector3( 0, 0, 0 ); }

    moveNodeTo( node, rotateVec3AroundAxisOnPoint( new THREE.Vector3( node.position.x, node.position.y, node.position.z ), axis, angle, point, order ) ) ;
}

function rotateNodeArrayOnAxisAroundPoint( nodeArr, axis, angle, point, order = 'XYZ' ){

    if ( !point ){ point = new THREE.Vector3( 0, 0, 0 ); }

    for ( var n = 0; n < nodeArr.length; n++ ){ 
        rotateNodeOnAxisAroundPoint( nodeArr[ n ], axis, angle, point, order );
    }
}

/* 3D VECTOR3D ROTATION EULER HELPER FUNCTIONS */

function rotateVec3AroundAxisOnPoint( v, axis, angle, point, order = 'XYZ' ){

    var angles = {};

    if ( !point ){ point = new THREE.Vector3( 0, 0, 0 ); }

    if ( axis === "x" ){
        angles = { x: angle, y: 0, z: 0 };  
    }

    if ( axis === "y" ){
        angles = { x: 0, y: angle, z: 0 };          
    }

    if ( axis === "z" ){
        angles = { x: 0, y: 0, z: angle };      
    }

    v = rotateVec3AroundPoint( v, point, angles, order );

    return v;
}

function rotateVec3AroundPoint( v, point, angles, order = 'XYZ' ){

    var vecSub = new THREE.Vector3();
    var vecSubRotated = new THREE.Vector3();
    var vecAdd = new THREE.Vector3();

    vecSub.subVectors( v, point ); 
    vecSubRotated = rotateVec3AroundOrigin( vecSub, angles, order );

    vecAdd.addVectors( vecSubRotated, point ); 

    return vecAdd;
}

function rotateVec3AroundOrigin( v, angles, order = 'XYZ' ){

    var euler = new THREE.Euler( angles.x, angles.y, angles.z, order );
    v.applyEuler( euler );
    return v;
}


/* 3D ROTATION OF NODES AND NODE ARRAYS USING QUATERNIONS */

function quaternionRotateNodeAroundPoint( node, quaternion, point ){

    if ( !point ){ point = new THREE.Vector3( 0, 0, 0 ); }

    moveNodeTo( node, quaternionRotateVec3AroundPoint( new THREE.Vector3( node.position.x, node.position.y, node.position.z ), quaternion, point ) );   

}

function quaternionRotateNodeArrayAroundPoint( nodeArr, quaternion, point ){

    if ( !point ){ point = new THREE.Vector3( 0, 0, 0 ); }

    for ( var n = 0; n < nodeArr.length; n++ ){ 
        quaternionRotateNodeAroundPoint( nodeArr[ n ], quaternion, point );
    }   
}

function quaternionRotateNodeOnAxisAroundPoint( node, axis, angle, point ){

    if ( !point ){ point = new THREE.Vector3( 0, 0, 0 ); }

    moveNodeTo( node, quaternionRotateVec3AroundAxisOnPoint( new THREE.Vector3( node.position.x, node.position.y, node.position.z ), axis, angle, point ) ) ;
}

function quaternionRotateNodeArrayOnAxisAroundPoint( nodeArr, axis, angle, point ){

    if ( !point ){ point = new THREE.Vector3( 0, 0, 0 ); }

    for ( var n = 0; n < nodeArr.length; n++ ){ 
        quaternionRotateNodeOnAxisAroundPoint( nodeArr[ n ], axis, angle, point );
    }
}


/* 3D VECTOR3D ROTATION QUATERNION HELPER FUNCTIONS */

function quaternionRotateVec3AroundAxisOnPoint( v, axis, angle, point ){

    var quaternion = new THREE.Quaternion();
    var axisAngle = new THREE.Vector3();

    if ( !point ){ point = new THREE.Vector3( 0, 0, 0 ); }

    if ( axis === "x" ){
        axisAngle = { x: 1, y: 0, z: 0 };
    }

    if ( axis === "y" ){        
        axisAngle = { x: 0, y: 1, z: 0 };
    }

    if ( axis === "z" ){
        axisAngle = { x: 0, y: 0, z: 1 };
    }

    quaternion.setFromAxisAngle( axisAngle, angle );
    v = quaternionRotateVec3AroundPoint( v, quaternion, point );

    return v;
}

function quaternionRotateVec3AroundPoint( v, quaternion, point ){

    var vecSub = new THREE.Vector3();
    var vecSubRotated = new THREE.Vector3();
    var vecAdd = new THREE.Vector3();

    vecSub.subVectors( v, point ); 
    vecSubRotated = applyQuaternionToVec3( vecSub, quaternion );

    vecAdd.addVectors( vecSubRotated, point ); 

    return vecAdd;
}

function applyQuaternionToVec3( v, quaternion ){

    v.applyQuaternion( quaternion );
    return v;
}

/* END 3D VECTOR3D ROTATION QUATERNION HELPER FUNCTIONS */

正如您所看到的,我也设置了Euler功能,但我目前正在通过四元数运行所有内容。

非常感谢任何帮助。谢谢!

更新:

似乎有两个不同的问题,我相信我今天早上解决了第一个问题。正在应用于球体的四元数旋转被应用于它们同时(已经旋转)的位置而不是它们的原始位置。我通过将原始位置向量复制到对象中并将连续更新的四元数值应用于该对象中的值来获取新位置来处理此问题。

然而,旋转仍然无法正常工作。这是一个小视频来解释:

(由于上面的iframe可能无效,因此直接链接:Video demo of isssue

视频说明: 1.当我点击调试点时我注意到的修复不是我应用的修复,但碰巧是我忘记的调试点。与此问题无关。 2.我正在使用setFromUnitVectors()。我在视频中猜到了这一点,不记得了。

相关代码从上面改变:

var clickCounter;
var rotToolState;
var origNodePositions = []; // THIS LINE WAS ADDED
var angleLineMaterial = new THREE.LineBasicMaterial({ color: 0x888888 });

function initRotationTool(){

    rotToolState = {
        points: [],
        angleLines: [],
        quaternion: {
            last: null,
            current: null
        },
        eul: {}
    }

    origNodePositions = [];  // THIS LINE WAS ADDED

    clickCounter = 0;
}

initRotationTool();

// Node Operations: Get Original Positions when the tool is initialized. THESE FUNCTIONS WERE ADDED

function getOrigNodePosition( node ){

    if ( node && node.isNode ){
        var origPos = new THREE.Vector3();
        origPos.copy( node.position );
        origNodePositions.push( origPos );
    }
}

function getOrigNodeArrayPositions( nodeArr ){

    if ( nodeArr.length > 0 ){ 
        for ( var n = 0; n < nodeArr.length; n++ ){ 
            getOrigNodePosition( nodeArr[ n ] );
        }
    }   
}

和...

else if ( clickCounter === 1 ){

    // remove the eventlistener that moves the first line's second vertex with the mouse.   
    document.getElementById('visualizationContainer').removeEventListener( 'mousemove', angleLine0ToMouse, false );

    // drop the line-end and the endpoint ( rotToolState.points[1] ).       
    lineToPoint( rotToolState.angleLines[0], position );
    rotToolState.points.push ( new Point( position, 1.0, 0x00ff00 ) );

    // initiate a line of zero length....       
    var lineStart = rotToolState.points[0].position;
    var lineEnd = position;

    var geometry = new THREE.Geometry();
    geometry.vertices.push(
        new THREE.Vector3( lineStart.x, lineStart.y, lineStart.z ),
        new THREE.Vector3( lineStart.x, lineStart.y, lineStart.z )
    );

    rotToolState.angleLines.push( new THREE.Line( geometry, angleLineMaterial ) );
    scene.add( rotToolState.angleLines[1] );        

    getOrigNodeArrayPositions( SELECTED.nodes ); // THIS LINE WAS ADDED....

此功能已被修改....

function quaternionRotateNodeAroundPoint( node, quaternion, point ){

    if ( !point ){ point = new THREE.Vector3( 0, 0, 0 ); }

    //var startPos = node.position;

    var nodeIndex = SELECTED.nodes.indexOf( node );
    var startPos2 = origNodePositions[ nodeIndex ];

    //moveNodeTo( node, quaternionRotateVec3AroundPoint( startPos, quaternion, point ) );
    moveNodeTo( node, quaternionRotateVec3AroundPoint( startPos2, quaternion, point ) );    

}

我希望此更新可以简化并澄清仍然存在的问题。感谢您的见解。

1 个答案:

答案 0 :(得分:0)

好友看着我的肩膀,我能够很简单地解决问题。事实证明,我需要规范化传递给.setFromUnitVectors的向量。我通过在我用来捕获旋转工具生成的角点之间的四元数的函数中添加两行来处理这个问题:

function getQuaternionBetweenVec3s( v1, v2 ){

    var v1n = v1.normalize();  // Line was added
    var v2n = v2.normalize();  // Line was added

    return new THREE.Quaternion().setFromUnitVectors( v1n, v2n );  // params formerly v1, v2

}

此功能接收定义角度的两个点,在从每个点中减去距离原点的距离之后。

那就是它。工作得很漂亮。我希望这可以帮助某人下线。