Вращение Quaternion Three.js не применяется должным образом - PullRequest
0 голосов
/ 08 июня 2018

Я создаю приложение, которое включает в себя инструмент вращения.У меня почти все работает, но не совсем.Для наглядности вот скриншот:

Rotation Tool Active

На изображении красная точка является центральной точкой, зеленая точка образует первую линиюугол, а синяя точка следует за мышью.Пользователь помещает центральную точку (красная), помещает первую линию (красная точка), а затем поворачиваемые элементы (три синие сферы на изображении) следуют за синей точкой, вращаясь под тем же углом.Когда пользователь щелкает последний раз, повернутые объекты размещаются, а направляющие инструментов исчезают.

Проблема заключается в том, что, хотя объекты вращаются с соответствующей центральной точкой, они не вращаются в соответствии с синимточка на всех.Кажется, что вращение ускоряется при увеличении угла до точки, где, когда угол оказывается равным ~ 60, скорость кажется бесконечной (вращающиеся объекты находятся в одном и том же месте, застряв там).Иногда вращение меняет направление.

Учитывая поведение, я думаю, что это может иметь отношение к неправильному назначению какой-либо функции триггера где-то, но я не уверен, что это будет и где его найти.

Я пытаюсь сделать это с вращением кватерниона.Я отмечу, что перед созданием инструмента пользовательского интерфейса я вращал эти объекты из консоли, используя углы Эйлера, и все работало хорошо, однако я думаю, что кватернионы, вероятно, являются лучшим решением.

Вот соответствующий код:

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 */

Как вы можете видеть, у меня также настроены функции Эйлера, хотя в настоящее время я выполняю все через кватернионы.

Любая помощь будет принята с благодарностью.Спасибо!


ОБНОВЛЕНИЕ:

Кажется, есть две отдельные проблемы, и я думаю, что я решил первый из них сегодня утром.Вращение кватерниона, которое применялось к сферам, применялось к их параллельным (уже повернутым) позициям, а не к их исходным позициям.Я позаботился об этом, скопировав их исходные векторы позиций в объект и применив постоянно обновляющееся значение кватерниона к значениям в этом объекте, чтобы получить новые позиции.

Однако вращение по-прежнему работает неправильно.Вот небольшое видео для объяснения:

(Прямая ссылка здесь, так как вышеупомянутый iframe может не работать: Видео-демонстрация 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 голосов
/ 18 июня 2018

Что ж, с другом, оглядывающимся через плечо, я смог решить проблему довольно просто.Оказывается, мне нужно нормализовать векторы, которые я передаю .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

}

Эта функция получает две точки, которые определяют угол,после того, как расстояние от источника было вычтено из каждого из них.

Вот и все.Работает красиво.Я надеюсь, что это поможет кому-то в дальнейшем.

...