Three.js setFromRotationMatrix strange behavior when rotation is over 90 degrees - javascript

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

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);

Related

Quaterion-rotate bone untill it alignes with a Vector3, three js

I want to rotate a bone so it is aligned with a Vector3 (directionToTarget), I have:
const directionToTarget = new THREE.Vector3(random, random, random); //pseudocode randoms
directionToTarget.normalize();
var Hand2worldQ = new THREE.Quaternion();
this._anchor['LeftHandIndex1'].getWorldQuaternion(Hand2worldQ); // gets Lefthand bone quaternion
this._mesh.skeleton.bones[ 0 ].quaternion.set( SomeFunctionThatMakesVecintoQuarternion(directionToTarget );
// this._mesh.skeleton.bones inherits Hand2worldQ/LeftHand rotation
SomeFunctionThatMakesVec3intoQuarternion(directionToTarget ) is what i need
Object3D starts by looking down the 0, 0, 1 axis. You can use the Object3D.lookAt() method to make this object point towards your target vector. Then you can extract that rotation and use it for whatever else you need:
// Initialize an abstract object looking towards 0, 0, 1
const object3D = new THREE.Object3D();
// Rotate it so it looks towards the target xyz coordinates
object3D.lookAt(randomX, randomY, randomZ);
// This will now have the final rotation
const myQuaternion = object3D.quaternion;
console.log(myQuaternion);

PaperJS - How to move along path and rotate along the path

The examples shown in here show how to move an object along the path in Paperjs but how do I rotate them correctly along the path?
In the examples shown on the link above, people suggested by using a circle as an example. But once changed to a rectangle new Path.Rectangle(new Point(20,20), new Size(20,20)); you can see that it moves along the path but does not actually rotate in the direction of the path.
How do I calculate the rotation and set it to my object?
In order to calculate the rotation, you need to know the tangent vector to the path at the position of your rectangle.
This can be retrieved with path.getTangentAt(offset) method.
Then, an easy way to animate the rotation of an item is to set item.applyMatrix to false and then animate the item.rotation property on each frame.
Here is a sketch demonstrating the solution.
// Create the rectangle to animate along the path.
// Note that matrix is not applied, this will allow us to easily animate its
// rotation.
var rectangle = new Path.Rectangle({
point: view.center,
size: new Size(100, 200),
strokeColor: 'orange',
applyMatrix: false
});
// Create the path along which the rectangle will be animated.
var path = new Path.Circle({
center: view.center,
radius: 250,
strokeColor: 'blue'
});
// On each frame...
function onFrame(event) {
// ...calculate the time of the animation between 0 and 1...
var slowness = 400;
var time = event.count % slowness / slowness;
// ...and move the rectangle.
updateRectangle(time);
}
function updateRectangle(time) {
// Calculate the offset relatively to the path length.
var offset = time * path.length;
// Get point to position the rectangle.
var point = path.getPointAt(offset);
// Get tangent vector at this point.
var tangent = path.getTangentAt(offset);
// Move rectangle.
rectangle.position = point;
// Rotate rectangle.
rectangle.rotation = tangent.angle;
}

How to determine the width/height/depth of an Object3D (as if it wasn't rotated)?

I am computing the height of an Object3D like so:
let obj = ... ; // An Object3D instance. Could be a Mesh, Group, etc.
let boundingBox = new THREE.Box3().setFromObject(obj);
let height = Math.abs(boundingBox.min.y - boundingBox.max.y);
When obj is rotated (on the X and/or Z axis), the difference between boundingBox.min.y and boundingBox.max.y increases/decreases, resulting in a height that is different to when it isn't rotated.
But I want to calculate the height of obj as if it wasn't rotated at all. How can I do this?
I'm guessing I need to transform boundingBox's dimensions based on the angle(s) of rotation, but I'm not sure how to do that.
Before rotation:
After rotation:
(red = obj, blue = boundingBox)
THREE.Box3().setFromObject(obj) will give you the "world-axis-aligned bounding box" of the object. It will explicitly compute the world-coordinates (read: including rotation, position and scale of the object and all of its parents) for all of your vertices.
If you just want the bounding-box of the geometry (without position, rotation, scale of the object), you can just use obj.geometry.boundingBox after calling computeBoundingBox():
obj.geometry.computeBoundingBox();
let boundingBox = obj.geometry.boundingBox;
For object-hierarchies, you can do something like this to get an aggregated bounding-box:
function getCombinedBoundingBox(object) {
const result = new THREE.Box();
object.traverse(child => {
// skip everything that doesn't have a geometry
if (!child.geometry) { return; }
child.geometry.computeBoundingBox();
result.union(child.geometry.boundingBox);
});
return result;
}
Note that this will only work if the child-objects are not transformed in any way.

THREE.js lookAt - How to pan smoothly between old and new target positions?

Example JSfiddle
I can get my cone to point at each target sphere in turn (red,green,yellow,blue) using the THREE.js "lookAt" function.
// Initialisation
c_geometry = new THREE.CylinderGeometry(3, 40, 120, 40, 10, false);
c_geometry.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );
c_material = new THREE.MeshNormalMaterial()
myCone = new THREE.Mesh(c_geometry, c_material);
scene.add(myCone);
// Application (within the Animation loop)
myCone.lookAt(target.position);
But now I want the cone to pan smoothly and slowly from the old target to the new target. I guess that I can do it by computing intermediate points on the circular arc which is centred at the cone centre Cxyz and which passes from the previous target position Pxyz to the new target position Nxyz.
Please can someone point me to suitable: (a) utilities or (b) trigonometry algorithms or (c) code examples for calculating the xyz coordinates of the intermediate points on such an arc? (I will supply the angular increment between points based on desired sweep rate and time interval between frames).
You want to smoothly transition from one orientation to another.
In your case, you would pre-calculate the target quaternions:
myCone.lookAt( s1.position );
q1 = new THREE.Quaternion().copy( myCone.quaternion );
myCone.lookAt( s2.position );
q2 = new THREE.Quaternion().copy( myCone.quaternion );
Then, in your render loop:
myCone.quaternion.slerpQuaternions( q1, q2, time ); // 0 < time < 1
three.js r.141
For those of you looking to lerp position and lookAt, you can create a initial lookAt target from the current look direction and lerp that towards the final target:
function MoveWhileLookingAt(object: Object3D, destination: Vector3, lookAt: Vector3){
const fromPosition = object.position.clone();
const fromLookAt = new Vector3(
0,
.1, // To avoid initial camera flip on certain starting points (like top down view)
-object.position.distanceTo(lookAt) // THREE.Camera looks down negative Z. Remove the minus if working with a regular object.
);
object.localToWorld(fromLookAt);
const tempTarget = fromLookAt.clone();
function LookAtLerp(alpha: number){
// This goes in your render loop
object.position.lerpVectors(fromPosition, destination, alpha);
tempTarget.lerpVectors(fromLookAt, lookAt, alpha);
object.lookAt(tempTarget);
}
}

Bounding box of models and center of rotation of the camera in xtk

Trying out http://www.goxtk.com, great stuff!
Is there a quick way to get the bounding box for a model or some other point that could be used as the center of rotation of the camera? Worst case, what's the best way to loop over the points? Thanks for any replies!
It is possible to query each X.object() for its centroid, like this:
...
r = new X.renderer('r');
r.init();
o = new X.object();
o.load('test.vtk');
r.add(o);
r.render();
r.onShowtime = function() {
// print the centroid
console.log(o.points().centroid());
};
...
You have to overload the onShowtime function of the X.renderer to be sure that the X.object was properly setup (.vtk file loaded etc.).
To configure the camera, you can do f.e. the following:
...
r.camera().setPosition(-400,0,0); // set the position
r.camera().setFocus(-10,-10,-10); // set the focus point
r.camera().setUp(1,0,0); // set the (normalized) up vector
r.render();
...
Anyway, to loop over the points:
...
// o is an X.object
var numberOfPoints = o.points().count();
var pointArrayLength = o.points().length(); // equals numberOfPoints * 3
var allPoints = o.points().all(); // as a flat 1D array optimized for WebGL
// just loop it :)
...

Categories