How to properly tween mesh using quaternions? - three.js

I have cobbled together a tween function (with enormous help from SO) to tween a mesh...and it's working fine.
My question is that - I feel that the way I am doing it is inefficient and I'm am looking for a better solution.
For example I am cloning my mesh each time I call the function...and that is so I can call rotateOnAxis(). I know what I want is a target quaternion that is my mesh rotation incremented on the axis...but I just haven't been able to find a better way to get it.
As usual any help much appreciated.
PS. really loving threejs!!!
function tweenRot(obj, axis, angle){
var actp = obj.clone(); // seems wasteful
actp.rotateOnAxis( axis, angle );
var targq = actp.quaternion;
var qm = new THREE.Quaternion();
var curQuaternion = obj.quaternion;
var tween = new TWEEN.Tween({t:0}).to({t:1}, 500)
.easing( TWEEN.Easing.Sinusoidal.InOut )
.onUpdate(function(){
THREE.Quaternion.slerp(curQuaternion, targq, qm, this.t);
qm.normalize();
obj.rotation.setFromQuaternion(qm)
});
tween.start();
}

You want to tween an object's rotation by specifying an axis of rotation and an angle. You can to that like so:
var tweenRotateOnAxis = function() {
// axis is assumed to be normalized
// angle is in radians
var qStart = new THREE.Quaternion();
var o = new THREE.Object3D();
return function tweenRotateOnAxis( object, axis, angle ) {
var qEnd, time = { t: 0 };
// start quaternion
qStart.copy( object.quaternion );
// end quaternion
o.quaternion.copy( qStart );
o.rotateOnAxis( axis, angle );
qEnd = o.quaternion;
// tween
new TWEEN.Tween( time )
.to( { t : 1 }, 1000 )
.easing( TWEEN.Easing.Linear.EaseNone )
.onUpdate( function() {
THREE.Quaternion.slerp( qStart, qEnd, object.quaternion, time.t );
} )
.onComplete( function() {
object.quaternion.copy( qEnd ); // to be exact
} )
.start();
};
}();
This should be reasonably efficient, even if you call it repeatedly. The rotation will be in object space.
Don't forget to call TWEEN.update() in your render loop.
three.js r.75

Related

Tween camera target in three.js

I have this code that works well:
function onMouseMove( event ) {
window.onmousedown = function() {
var canvasPosition = renderer.domElement.getBoundingClientRect();
var mouseX = event.clientX - canvasPosition.left;
var mouseY = event.clientY - canvasPosition.top;
var mouseVector = new THREE.Vector3 (
2 * (mouseX / window.innerWidth) - 1,
1 - 2 * (mouseY / window.innerHeight), 1);
mouseVector.unproject( camera );
var dir = mouseVector.sub( camera.position ).normalize();
var distance = - camera.position.z / dir.z;
var pos = camera.position.clone().add( dir.multiplyScalar( distance ) );
camera.getWorldDirection();
camera.lookAt( pos );
// camera.updateMatrixWorld(true);
console.log(mouseVector);
console.log(mouseX);
console.log(mouseY);
// render();
}
}
But I would like to smooth the movement. So I found the following code from the tween example, but not sure how to use it. In the above code, I get current camera lookat from one place, one format, and put the new camera look at in camera.lookat in a different format - neither of which seem to be standard x,y,z.
In the below code, the tween would have me change an properties (x,y,z) on a single item. which the unprojecting and normalizing of the camera do not accommodate:
new TWEEN.Tween( intersects[ 0 ].object.position )
.to( {
x: Math.random() * 800 - 400,
y: Math.random() * 800 - 400,
z: Math.random() * 800 - 400
}, 2000 )
.easing( TWEEN.Easing.Elastic.Out)
.start();
If there is a breakdown or something I can read, or actually work out problems to understand, I'd be grateful. I've read camera tutorials and matrix tutorials over and over for years, but my brain just can't comprehend it.
I've been digging around here quite a bit, but nothing addresses a camera tween - at least for a valid version of threejs
Thank you!
I recommend you get acquainted with linear interpolation, or more commonly known as "lerp". The THREE.Vector3 class has a lerp function that you could use to interpolate between a starting point and an ending point:
var camPos = new THREE.Vector3(0, 0, 0); // Holds current camera position
var targetPos = new THREE.Vector3(10, 10, -10);// Target position
var origin = new THREE.Vector3(0, 0, 0); // Optional origin
function animate(){
// Interpolate camPos toward targetPos
camPos.lerp(targetPos, 0.05);
// Apply new camPos to your camera
camera.position.copy(camPos);
// (Optional) have camera look at the origin after it's been moved
camera.lookAt(origin);
// render();
}
In the above example, your animate() function is called once per frame, and the camera will travel 5% towards targetPos per frame.
If you change targetPos, the camera will animate towards its new target value.
I recommend you first get acquainted with lerping before you start bringing in third-party libraries like TWEEN.js or others.
just for smoothing the movement, this might already help you:
// keep this outside of the event-handler
var lookAtPosition = new THREE.Vector3();
var lookAtTween = new TWEEN.Tween(lookAtPosition);
// as lookAt is not a property we can assign to we need to
// call it every time the tween was updated:
lookAtTween.onUpdate(function() {
camera.lookAt(lookAtPosition);
});
window.onmousedown = function() {
// do your thing to compute pos
// instead of `camera.lookAt(pos)`, do this:
lookAtTween
.stop() // just in case it's still animating
.to(pos, 500) // set destination and duration
.start(); // start the tween
};

How to rotate Object3d around X,Y & Z axis without having the axes to flip or cause gimbal lock?

Probably threejs's rotation question no 9000 , i simply want to have 3 UI buttons to incrementally rotate an object 90 degrees in either x,y & z direction with every click on each, how hard could it be ?
Using regular rotation methods which uses euler will cause gimbal lock, and using quanternions the axes will flip randomly in which z-axis will be y-axis at one point or another.
please have a look at my working demo : http://grutex.com/webgl/demos/help/
Edit: here is the rotation functions part :
function z_rotate(){
var startAngle = 0;
var start = {angle: startAngle};
var end = {angle: startAngle + 90};
var lastAngle=0;
var tween = new TWEEN.Tween(start)
.to(end, 400)
.easing( TWEEN.Easing.Quadratic.Out )
.onUpdate(function(){
startAngle=this.angle;
my_object.rotateOnAxis(new THREE.Vector3(0,0,1),degreeToRadians(startAngle-lastAngle));
lastAngle=startAngle;
})
.start();
}
function x_rotate(){
var startAngle = 0;
var start = {angle: startAngle};
var end = {angle: startAngle + 90};
var lastAngle=0;
var tween = new TWEEN.Tween(start)
.to(end, 400)
.easing( TWEEN.Easing.Quadratic.Out )
.onUpdate(function(){
startAngle=this.angle;
my_object.rotateOnAxis(new THREE.Vector3(1,0,0),degreeToRadians(startAngle-lastAngle));
lastAngle=startAngle;
})
.start();
}
function y_rotate(){
var startAngle = 0;
var start = {angle: startAngle};
var end = {angle: startAngle + 90};
var lastAngle=0;
var tween = new TWEEN.Tween(start)
.to(end, 400)
.easing( TWEEN.Easing.Quadratic.Out )
.onUpdate(function(){
startAngle=this.angle;
my_object.rotateOnAxis(new THREE.Vector3(0,1,0),degreeToRadians(startAngle-lastAngle));
lastAngle=startAngle;
})
.start();
}
As #WestLangley pointed out, i should attach and detach the 3d object to a parent pivot point (THREE.Object3D), attaching the 3d object to the pivot point before the tween start and detaching the pivot point after tween completion and resetting its rotation to (0,0,0)
#WestLangley answer : Three.js: Adding and Removing Children of Rotated Objects
New modified rotation function :
var axis_x = new THREE.Vector3( 1, 0, 0 ).normalize();
function x_rotate(){
pivot_attach();
var startAngle = 0;
var start = {angle: degreeToRadians(startAngle)};
var end = {angle: degreeToRadians(startAngle + 90)};
var lastAngle=0;
var tween = new TWEEN.Tween(start)
.to(end, 400)
.easing( TWEEN.Easing.Quadratic.Out )
.onUpdate(function(){
startAngle=this.angle;
pivot.rotateOnAxis(axis_x,startAngle-lastAngle);
lastAngle=startAngle;
})
.start().onComplete(pivot_detach);
}
function pivot_attach() {
pivot.updateMatrixWorld();
THREE.SceneUtils.attach( my_object, scene, pivot );
}
function pivot_detach() {
pivot.updateMatrixWorld();
my_object.updateMatrixWorld(); // if not done by the renderer
THREE.SceneUtils.detach( my_object, pivot, scene );
pivot.rotation.set( 0, 0, 0 );
}
and don't forget to add the pivot to the scene "scene.add(pivot);"

how do i know the direction (unit vector) from an axis of THREE.AxisHelper?

I have a mesh with an added THREE.AxisHelper. The mesh rotate and traslate into the world and i need to know the direction of the mesh x axis at a given time.
Create a new method by copying the source code of Object3D.getWorldDirection().
getWorldDirection: function () {
var quaternion = new Quaternion();
return function getWorldDirection( optionalTarget ) {
var result = optionalTarget || new Vector3();
this.getWorldQuaternion( quaternion );
return result.set( 0, 0, 1 ).applyQuaternion( quaternion );
};
}(),
and replace the z-axis ( 0, 0, 1 ) with the the x-axis ( 1, 0, 0 ).
For efficiency, when you use the method, you can pass in the optionalTarget so a new Vector3 is not allocated every time the method is called.
three.js r.80
based on WestLangley resp., I have written this function:
this.dirAirplane = new THREE.Vector3();
...
this.takeDir = function(){
var quaternion = new THREE.Quaternion();
this.mesh.getWorldQuaternion( quaternion );
this.dirAirplane.set(1,0,0).applyQuaternion(quaternion);
}
Thanks

Tween camera position while rotation with slerp -- THREE.js

I want to tween camera position while rotation.
Here is my function:
function moveAndLookAt(camera, dstpos, dstlookat, options) {
options || (options = {duration: 300});
var origpos = new THREE.Vector3().copy(camera.position); // original position
var origrot = new THREE.Euler().copy(camera.rotation); // original rotation
camera.position.set(dstpos.x, dstpos.y, dstpos.z);
camera.lookAt(dstlookat);
var dstrot = new THREE.Euler().copy(camera.rotation)
// reset original position and rotation
camera.position.set(origpos.x, origpos.y, origpos.z);
camera.rotation.set(origrot.x, origrot.y, origrot.z);
//
// Tweening
//
// position
new TWEEN.Tween(camera.position).to({
x: dstpos.x,
y: dstpos.y,
z: dstpos.z
}, options.duration).start();;
// rotation (using slerp)
(function () {
var qa = camera.quaternion; // src quaternion
var qb = new THREE.Quaternion().setFromEuler(dstrot); // dst quaternion
var qm = new THREE.Quaternion();
var o = {t: 0};
new TWEEN.Tween(o).to({t: 1}, options.duration).onUpdate(function () {
THREE.Quaternion.slerp(qa, qb, qm, o.t);
camera.quaternion.set(qm.x, qm.y, qm.z, qm.w);
}).start();
}).call(this);
}
It works great: http://codepen.io/abernier/pen/zxzObm
The only problem is the tween for rotation does NOT seem to be linear... causing decay with position's tween (which is linear).
How can I turn slerp into a linear tween ?
Thank you
In your code
// rotation (using slerp)
(function () {
var qa = camera.quaternion; // src quaternion
Change it to
qa = new THREE.Quaternion().copy(camera.quaternion); // src quaternion
The way you do it, qa is the same as the camera quaternion, and it feeds back in the slerp calculus. It must be a constant variable.

How to get a point coordinate on a SkinnedMaterial for the human demo of three.js?

For the human demo http://threejs.org/examples/#webgl_morphtargets_human , I wonder how to get a point on the skin of the human. For example when I click some place on the human body, what's the coordinate of that place?
I tried using the raycaster to get that but in vain.The code is like this:
var projector;
init() {
// Others
// ...
projector = new THREE.Projector();
renderer.domElement.addEventListener('mouseup', onMouseUp, false);
}
function onMouseUp(e) {
e.preventDefault();
var vector = new THREE.Vector3(
( event.clientX / window.innerWidth ) * 2 - 1,
- ( event.clientY / window.innerHeight ) * 2 + 1,
0.5
);
projector.unprojectVector( vector, camera );
var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var intersections = raycaster.intersectObjects( character.root.children );
if (intersections.length > 0) {
debugger;
// ...
}
}
But the intersections is always empty.
Three.js is r67
Thanks in advance.
I'm new to three.js, and i've also tried to draw plots on the human. I manage to do it, but it's not on the "visible" body. In fact, you should first use the intersect method with the recursive arg :
var intersections = raycaster.intersectObjects( scene.children, true );
Thus, you 'll be able to interact with the objects composing the body, but they are not positioned under the "skin". It seems to be that they have been "moved", because you can interact with them if you aim in front of the feet of the body. Unfortunately, I don't know for the moment why, and how to interact with their "visible representation".
Well, finally I find that it's just because the human animation. It works if I comment the animation out.

Resources