Three.js setFromRotationMatrix strange behavior when rotation is over 90 degrees
Asked Answered
H

1

9

I have objects which each have a separate parent for each rotation axis (1 for X-rotation, 1 for Y-rotation, and 1 for Z-rotation. They are all related to each other in that order as well: X-rotation object is a child of the Y-rotation object. Y-rotation object is a child of the Z-rotation object).

I'm trying to make a feature which allows users to rotate all objects in the scene together (they are all contained in a single Object3D). When that Object3D is rotated, the program must find all of the objects' absolute positions and rotations relative to the world so that the program can output the new values for each object.

To do this, I currently have it setup to move the object so that its position inside the "scene-rotator", which is an Object3D, is set to its absolute position relative to the world. Now, I'm trying to make the rotation of the object become the absolute rotation of the object relative to the world, so that it changes accordingly when the "scene-rotator"'s rotation is changed. Also, the setFromRotationMatrix method was not working correctly when I tried just running it once on the child object, so instead, I had to run it again for each parent object and get each separate rotation from them accordingly

This is the code that I currently have which is supposed to get the absolute rotation of the object relative to the world:

var beforeRotForX = new THREE.Euler();
beforeRotForX.setFromRotationMatrix(objects[i].parent.matrixWorld, "ZYX");

var beforeRotForY = new THREE.Euler(); // Had to be a separate one for some reason...
beforeRotForY.setFromRotationMatrix(objects[i].parent.parent.matrixWorld, "ZYX");

var beforeRotForZ = new THREE.Euler(); // And apparently this one has to be separate too
beforeRotForZ.setFromRotationMatrix(objects[i].parent.parent.parent.matrixWorld, "ZYX");

// Absolute before rotation
objects[i].userData.sceneBeforeRotAbs = {
    x: beforeRotForX.x,
    y: beforeRotForY.y,
    z: beforeRotForZ.z
};

Then, it must apply that absolute rotation to the relative rotation of the object

objects[i].parent.rotation.x = objects[i].userData.sceneBeforeRotAbs.x;
objects[i].parent.parent.rotation.y = objects[i].userData.sceneBeforeRotAbs.y;
objects[i].parent.parent.parent.rotation.z = objects[i].userData.sceneBeforeRotAbs.z;

This all works fine when the Y-rotation of the second parent is within -90 through 90

// Results of absolute world rotation when the Y-rotation of the
// second parent is set to 90 degrees (1.5707... as euler)
objects[i].userData.sceneBeforeRotAbs.x === 0
objects[i].userData.sceneBeforeRotAbs.y === 1.5707963267948966
objects[i].userData.sceneBeforeRotAbs.z === 0

but when the Y-rotation of the second parent is below -90 or greater than 90, then it gives the wrong value for the absolute world X-rotation and Y-rotation as a result

// Results of absolute world rotation when the Y-rotation of the
// second parent is set to 91 degrees (1.5882... as euler)
objects[i].userData.sceneBeforeRotAbs.x === 3.141592653589793
objects[i].userData.sceneBeforeRotAbs.y === 1.5533438924131038
objects[i].userData.sceneBeforeRotAbs.z === 0
Haunt answered 19/3, 2016 at 22:15 Comment(2)
i've read through your problem dscription 2 times and cannot really wrap my head around what you are trying to achieve. But one thing that hits me is the problem in rotating things around all axis with euler angles. When you experience strange behaviour from rotating with euler angles it could be because of a Gimbal Lock. I would recomend trying this using quaternions instead. Three.js supports them aswellLozier
I can see what you mean as to why it might seem redundant to be doing any of this, but for reference, this is the page that it is for: mrgarretto.com/model and it is a tool for people to use which must be able to generate various things after the user makes a project with it. Here is an album I put together which might help to show the problem in action: imgur.com/a/jkTeo I will try using quaternions and see if that fixes the problemHaunt
E
5

You're running into gimbal lock. When using euler angles you'll always run into gimbal lock issues, and you'll encounter unexpected behavior when applying multiple rotations.

For example, in 2D space, a 30° rotation is the same as a -330° rotation. In 3D space, you can get the same problem: rotating an object 180° in the X-axis is the same as giving it a 180° Y-axis + 180° Z-axis rotation.

You should declare your rotations using quaternions, and then multiply them together to get the desired result without gimbal lock issues.

// Declare angles
var angleX = 45;
var angleY = 120;
var angleZ = 78;

// Declare X and Y axes
var axisX = new THREE.Vector3(1, 0, 0);
var axisY = new THREE.Vector3(0, 1, 0);
var axisZ = new THREE.Vector3(0, 0, 1);

// Init quaternions that will rotate along each axis
var quatX = new THREE.Quaternion();
var quatY = new THREE.Quaternion();
var quatZ = new THREE.Quaternion();

// Set quaternions from each axis (in radians)...
quatX.setFromAxisAngle(axisX, THREE.Math.degToRad(angleX));
quatY.setFromAxisAngle(axisY, THREE.Math.degToRad(angleY));
quatZ.setFromAxisAngle(axisZ, THREE.Math.degToRad(angleZ));

// ...then multiply them to get final rotation
quatY.multiply(quatX);
quatZ.multiply(quatY);

// Apply multiplied rotation to your mesh
mesh.quaternion.copy(quatZ);
Einhorn answered 18/5, 2017 at 19:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.