Tween camera position while rotation with slerp -- THREE.js - 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.

Related

Move Threejs mesh to the center of another mesh

I have the following structure in my scene (imported from glb):
I try to move the mesh svgGroupsvg_test to the center of mesh named M_Top (red cross is the expected location).
Here is my code:
function engraveSVG(object, value) {
//object is the name of the target mesh
var svgMeshName = 'svgGroup' + value
loadSVGandFit(svgMeshName, object, value).then(res => {
var svgMesh = scene.getObjectByName(svgMeshName);
svgMesh.scale.set(0.1, 0.1, 1)
const axesHelper = new THREE.AxesHelper( 20 );
svgMesh.parent.add(axesHelper)
moveCenterMeshToOtherMeshCenter(svgMesh, scene.getObjectByName(object))
})
}
I tried the following functions:
function moveCenterMeshToOtherMeshCenter(centerMesh, otherMesh) {
// get the center positions of both meshes in the local world
const centerMeshPosition = new THREE.Vector3();
const otherMeshPosition = new THREE.Vector3();
centerMesh.getWorldPosition(centerMeshPosition);
otherMesh.getWorldPosition(otherMeshPosition);
// calculate the difference between the center positions of both meshes
const difference = otherMeshPosition.sub(centerMeshPosition);
// translate the center mesh by the difference
centerMesh.translateX(difference.x);
centerMesh.translateY(difference.y);
centerMesh.translateZ(difference.z);
}
function moveCenterToOther(centerMesh, otherMesh) {
const centerBox = new THREE.Box3().setFromObject(centerMesh);
const otherBox = new THREE.Box3().setFromObject(otherMesh);
const centerPosition = centerBox.getCenter(new THREE.Vector3());
const otherPosition = otherBox.getCenter(new THREE.Vector3());
const offset = new THREE.Vector3().subVectors(otherPosition, centerPosition);
centerMesh.position.add(offset);
}
Is there something wrong ? Get center return a value around 0.
I chose to add the svg mesh in the same group of my target mesh. But it changes nothing.
The axes is also in the local coordinate.
Any help would be very appreciate.
I found a solution. I add my SVG in the root of the scene. I am now able to get the correct bounding box size and center in the world coordinates.

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

How to properly tween mesh using quaternions?

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

Camera transition with rotation

I got a question about camera object. I'm trying to make a transition between cameras and I got it partially working. Camera is moving well but it does not rotate correct. I suppose my code does not calculate lookAtVector right but I cannot find information how to do it correct.
Here is the code I'm using:
var new_position = new_camera.position.clone();
var new_rotation = new_camera.rotation.clone();
var new_quaternion = new_camera.quaternion.clone();
camera.rotation.clone(new_rotation);
camera.quaternion.clone(new_quaternion);
newlookAtVector = new THREE.Vector3(0, 0, -1);
newlookAtVector.applyEuler(new_rotation, new_camera.eulerOrder);
new TWEEN.Tween( camera.position ).to( {
x: new_position.x,
y: new_position.y,
z: new_position.z}, 600 ).onUpdate(function () {
camera.lookAt(newlookAtVector);
}).onComplete(function () {
camera.lookAt(newlookAtVector);
}).easing( TWEEN.Easing.Sinusoidal.Out).start();
Thank you!

ThreeJS - how to pick just one type of objects?

I'm new to ThreeJS and I have an issue with picking objects by raycasting. I have created some spheres and some lines but only want to change the spheres on mouseover. I think I need to add some condition in the raycast code but I have no idea what...
Here's my code, hope anyone can help:
This creates the objects:
var numSpheres = 10;
var angRand = [numSpheres];
var spread = 10;
var radius = windowY/5;
var radiusControl = 20;
//sphere
var sphereGeometry = new THREE.SphereGeometry(0.35, 100, 100);
//line
var lineGeometry = new THREE.Geometry();
var lineMaterial = new THREE.LineBasicMaterial({
color: 0xCCCCCC
});
//create dynamically
for (var i = 0; i < numSpheres; i++) {
var sphereMaterial = new THREE.MeshBasicMaterial({color: 0x334455});
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
var line = new THREE.Line(lineGeometry, lineMaterial);
angRand[i] = Math.floor((Math.random() * 360) + 1);//random angle for each sphere/line
var radiusIncr = spread * (angRand[i]+200)/180;
var xPos = Math.cos((360/numSpheres * (i) + angRand[i]/2 )) * (radius - radiusIncr);
var yPos = Math.sin((360/numSpheres * (i) + angRand[i]/2 )) * (radius - radiusIncr);
var offsetY = Math.floor((Math.random()*5)+1);
sphere.position.x = xPos/radiusControl;
sphere.position.y = yPos/radiusControl + offsetY;
lineGeometry.vertices.push(
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(sphere.position.x, sphere.position.y, 0)
);
scene.add(sphere);
scene.add(line);
}
And this is my raycast:
var mouse = {
x: 0,
y: 0
},
INTERSECTED;
window.addEventListener('mousemove', onMouseMove, false);
window.requestAnimationFrame(render);
function onMouseMove(event) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
//event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
//console.log(mouse.x + " | " + mouse.y);
}
function mousePos() {
// find intersections
// create a Ray with origin at the mouse position
// and direction into the scene (camera direction)
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
vector.unproject(camera);
var ray = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
ray.linePrecision = 1;
// create an array containing all objects in the scene with which the ray intersects
var intersects = ray.intersectObjects(scene.children, true);
//console.log(intersects.length);
// INTERSECTED = the object in the scene currently closest to the camera
// and intersected by the Ray projected from the mouse position
// if there is one (or more) intersections
if (intersects.length > 0) {
// if the closest object intersected is not the currently stored intersection object
if (intersects[0].object != INTERSECTED) {
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED)
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
// store reference to closest object as current intersection object
INTERSECTED = intersects[0].object;
// store color of closest object (for later restoration)
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
// set a new color for closest object
INTERSECTED.material.color.setHex(0xEE7F00);
//INTERSECTED.radius.set( 1, 2, 2 );
}
} else // there are no intersections
{
// restore previous intersection object (if it exists) to its original color
if (INTERSECTED)
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
//INTERSECTED.scale.set( 1, 1, 1 );
// remove previous intersection object reference
// by setting current intersection object to "nothing"
INTERSECTED = null;
}
}
The raycast returns an intersect array of objects which itself contains information about what the ray hit.
Since you only have spheres and lines you can branch on the geometry type intersects[0].object.geometry.type which would be either 'LineGeometry' or 'SphereGeometry'.
Edit: Obligatory jsfiddle, see console for hit output.
http://jsfiddle.net/z43hjqm9/1/
To simplify working with the mouse, you can use the class EventsControls. Try to make through this example.
<script src="js/controls/EventsControls.js"></script>
EventsControls = new EventsControls( camera, renderer.domElement );
EventsControls.attachEvent('mouseOver', function() {
this.container.style.cursor = 'pointer';
this.mouseOvered.material = selMaterial;
...
});
EventsControls.attachEvent('mouseOut', function() {
this.container.style.cursor = 'auto';
this.mouseOvered.material = autoMaterial;
...
});
//
function render() {
EventsControls.update();
controls.update();
renderer.render(scene, camera);
}
In your code,
var intersects = ray.intersectObjects(scene.children, true);
the first parameter to the call is an object that will be evaluated to see if it, or any of its descendants (recursive is true) intersect the ray.
So, simply create an object target and add the spheres to it (but not the lines).
This will make your call also more effective
1.use different arrays to place different objects
a.for all objectType1,after scene.add(objectType1)-> do array1.push(objectType1)
b.for all objectType 2,after scene.add(objectType2)-> do array2.push(objectType2)
now whichever type of objects you want to interact, pass that array in intersect as-
var intersects = raycaster.intersectObjects( arrayType1,true);
now only the arrayType1 objects will interact.

Resources