Я создаю приложение, которое включает в себя инструмент вращения.У меня почти все работает, но не совсем.Для наглядности вот скриншот:
На изображении красная точка является центральной точкой, зеленая точка образует первую линиюугол, а синяя точка следует за мышью.Пользователь помещает центральную точку (красная), помещает первую линию (красная точка), а затем поворачиваемые элементы (три синие сферы на изображении) следуют за синей точкой, вращаясь под тем же углом.Когда пользователь щелкает последний раз, повернутые объекты размещаются, а направляющие инструментов исчезают.
Проблема заключается в том, что, хотя объекты вращаются с соответствующей центральной точкой, они не вращаются в соответствии с синимточка на всех.Кажется, что вращение ускоряется при увеличении угла до точки, где, когда угол оказывается равным ~ 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 ) );
}
Я надеюсь, что это обновление упростит и прояснит проблему, которая все еще остается.Спасибо за ваше понимание.