Three.js Rotate camera around object (which may move) - javascript

I have a camera that moves in a few different ways in the scene. The camera should rotate around a target position. In my case, this is a point on a mesh that the user has targeted. Because the camera usually doesn't require moving relative to this point, I was not able to use the pivot idea here: https://github.com/mrdoob/three.js/issues/1830. My current solution uses the following code:
var rotationY = new THREE.Matrix4();
var rotationX = new THREE.Matrix4();
var translation = new THREE.Matrix4();
var translationInverse = new THREE.Matrix4();
var matrix = new THREE.Matrix4();
function rotateCameraAroundObject(dx, dy, target) {
// collect up and right vectors from camera perspective
camComponents.up = rotateVectorForObject(new THREE.Vector3(0,1,0), camera.matrixWorld);
camComponents.right = rotateVectorForObject(new THREE.Vector3(1,0,0), camera.matrixWorld);
matrix.identity();
rotationX.makeRotationAxis(camComponents.right, -dx);
rotationY.makeRotationAxis(camComponents.up, -dy);
translation.makeTranslation(
target.position.x - camera.position.x,
target.position.y - camera.position.y,
target.position.z - camera.position.z);
translationInverse.getInverse(translation);
matrix.multiply(translation).multiply(rotationY).multiply(rotationX).multiply(translationInverse);
camera.applyMatrix(matrix);
camera.lookAt(target.position);
}
The issue is that we do not want to use lookAt, because of the reorientation. We want to be able to remove that line.
If we use the code above without lookAt, we rotate around the point but we do not look at the point. My understanding is that my method should rotate the camera's view as much as the camera itself is rotate, but instead the camera is rotated only a small amount. Could anyone help me understand what's wrong?
EDIT: Cleaned up the original post and code to hopefully clarify my question.
My thinking is that I can translate to the origin (my target position), rotate my desired amount, and then translate back to the beginning position. Because of the rotation, I expect to be in a new position looking at the origin.
In fact, I'm testing it now without the translation matrices being used, so the matrix multiplication line is:
matrix.multiply(rotationY).multiply(rotationX);
and it seems to be behaving the same. Thanks for all the help so far!
ONE MORE THING! A part of the problem is that when the camera behaves badly close to the north or south poles. I am looking for a 'free roaming' sort of feel.

Put the following in your render loop:
camera.position.x = target.position.x + radius * Math.cos( constant * elapsedTime );
camera.position.z = target.position.z + radius * Math.sin( constant * elapsedTime );
camera.lookAt( target.position );
renderer.render( scene, camera );
Alternatively, you can use THREE.OrbitControls or THREE.TrackballControls. See the three.js examples.

The Gimbal lock that you are referring to (reorientation) is because of the use of Euler angles in the default implementation of the camera lookat. If you set
camera.useQuaternion = true;
before your call to lookat, then euler angles will not be used. Would this solve your problem ?

Related

Attaching position to camera but rotating in world axis

Quick abstract: I don't know how to give to an object, attached to the camera, a local rotation so that it is rotated as if it is attached to the scene.
Background: I am the creator of this website which offers to players of the related game the ability to build levels on a 3D Builder, in a web browser. Everything work fine with it but some parts are not well optimized, specially the toolbar (or dashboard), where objects can be selected to be added to the main scene.
Those toolbar objects need to be sticked at the bottom of the screen and always above all layers but their rotation has to be relative to the main axis of the scene. That's where I face some weird behaviors on browsers other than Firefox and Chrome.
If first thought this other thread was saving my life on this matter by implementing the Object3D.onBeforeRender function, but I faced other issues.
Currently those objects are attached to the scene and their position is updated relatively to the camera with the onBeforeRender function of each object. On such way I noticed some flickering results with Edge and iPhone browser, despite everything ends up fine in the rendering. Not nice on the scene rotation...
So I tried something else by attaching each object to the camera and giving them their supposed world rotation. Now when testing on Edge there is no flickering and the results is much better but I can't get how to give them the same rotation than when they are attached to the scene.
Here is a visual:
and an animated gif (you can see a little how the red background and cube at the right part are moving/flickering, but it is awfull if done quicklier):
For the #1 (at the right) there's a good rendering but a bad experience on some browsers and for the #2 (at the left) the rotation, based on the camera, is not good but the experience is fine.
Here are some code abstracts I've implemented:
For #1:
rightCube.relPos = new THREE.Vector3(500, 0, 0); // a new property to be used
rightCube.onBeforeRender = function( renderer, scene, camera, geometry, material, group ) {
camReference.position.copy(this.relPos);
var pos = camReference.getWorldPosition();
this.position.set( pos.x, pos.y, pos.z );
};
scene.add(rightCube);
And camReference is a simple Object3D representing the camera origin
For the #2:
leftCube.onBeforeRender = function( renderer, scene, camera, geometry, material, group ) {
this.rotation.copy(camera.getWorldRotation());
};
camera.add(leftCube);
I admit on this last part, that getting the camera world rotation is not sufficient but that's where I'm stuck and need some help: I don't know how to give to an object, attached to the camera, a local rotation so that it is rotated as if it is attached to the scene.
This post is pretty long but I hope my problem can be well understood... and that someone will give me the key to make it work.
Three.js r82
I've found out how to deal with that problem. I had to go into some calculus and I am surprised that it is not yet implemented in Three.js. If I have missed something #WestLangley please let me know!
Basically my request is to have a rotateToWorld functionnality from the camera coordinate. Getting the rotation of the camera is not enought because of the orbit control position offset which could be different than the Scene Axis. From the camera coordinate, rotating to the World can be seen as a rotation around x then y (Euler rotations), z being directed to the viewer. Here is a figure I tried to draw as a better explanation:
Here is the function I have coded to do that:
var rotateToWorld = function (camera, controls) {
var p = camera.position.clone();
var o = controls.target.clone();
// position relative to the control
p.sub(o);
var Dxyz = Math.sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
var Dxz = Math.sqrt(p.x*p.x + p.z*p.z);
return {
x: Math.acos(Dxz/Dxyz) * (p.y>0?1:-1), // Angle Rotation on x axis camera
y: Math.acos(p.z/Dxz) * (p.x>0?-1:1), // Angle Rotation on y axis camera
z: 0 // No rotation around z axis
};
};
Then from a cube object attached to the camera (not to the scene) it can be rendered like that:
cube.onBeforeRender = function( renderer, scene, camera, geometry, material, group ) {
var R = rotateToWorld(camera, controls); // controls defined elsewhere in the code
this.rotation.set(R.x, R.y, 0);
};
As a result this is what I got with a perspective camera:
And now it works with Microsoft Edge, Safari and other browsers despite when attaching to the scene and updating the position of the object (to keep it in front of the camera) it was only workable with Firefox and Chrome (as for today).
I wonder now if this is a functionnality that could be added to Three.js...
EDIT:
#WestLangley pointed out in comments that the clone() function of Vector3 instantiate a new object each time it is called, so better is to use a closure function to initialize once p and o as Vector3 objects then update them with the copy() function.
So here is a better version:
var rotateToWorld = function() {
var p = new THREE.Vector3();
var o = new THREE.Vector3();
return function rotateToWorld(camera, controls) {
p.copy(camera.position);
o.copy(controls.target);
// position relative to the control
p.sub(o);
var Dxyz = Math.sqrt(p.x*p.x + p.y*p.y + p.z*p.z);
var Dxz = Math.sqrt(p.x*p.x + p.z*p.z);
return {
x: Math.acos(Dxz/Dxyz) * (p.y>0?1:-1), // Angle Rotation on X axis camera
y: Math.acos(p.z/Dxz) * (p.x>0?-1:1), // Angle Rotation on Y axis camera
z: 0 // No rotation around z axis
};
}
}();
Again, thanks #WestLangley! :)

Three.js - Convert mesh velocity to mesh rotation

Suppose I have a three.js mesh, and a velocity vector3.
The velocity is being altered elsewhere, and then added to the mesh.position every frame.
What I want is for the mesh.rotation to correspond to the velocity, i.e, the mesh is an arrow that always points in the direction it's going in.
Here are two approaches I've tried - which get the mesh rotating yeah, but just at the wrong angles and backwards and everything.
//this doesn't work
mesh.rot.x = Math.atan2( vel.y, vel.z );
mesh.rot.y = -Math.atan2( vel.x, vel.z );
mesh.rot.z = Math.atan2( vel.x, vel.y );
//got this from a semi-related stackoverflow question; also doesn't work
mesh.rot.y = Math.asin( vel.z );
mesh.rot.z = Math.atan2( vel.y, vel.x );
var bankVec = new Vector3( -vel.y, vel.x, 0 );
var upVec = new Vector3( dot( bankVec, vel ) );
mesh.rot.x = Math.atan2(dot(bankVec, vel) / dot(upVec, vel),abs(bankVec)*abs(upVec));
I don't really get vector maths.
I'm now at the stage where I'm just arbitrarily changing signs and swapping arguments around, hoping for the best. So any kind of explanation as to how I should be doing this would be much appreciated.
Not really looking for code, just looking for the relevant equation of dot products, cross products, atan2's, whatever.
You can construct a rotation matrix from the normalized velocity vector. The vector, normalized, is 1 axis. Then you pick a 2nd axis somehow. Then you do a cross product to find the 3rd axis. Finally do a cross product between the first and 3rd to get the final 2nd axis.
Then you construct a 3x3 matrix from the 3 vectors (i can't remember their order at the moment, but something like [x1,y1,z1,x2,y2,z2,x3,y3,z3]).
Another method is the Quaternion class:
.setFromUnitVectors ( vFrom, vTo )
Sets this quaternion to the rotation required to rotate direction
vector vFrom to direction vector vTo. Adapted from
http://lolengine.net/blog/2013/09/18/beautiful-maths-quaternion-from-vectors.
vFrom and vTo are assumed to be normalized.
http://threejs.org/docs/#Reference/Math/Quaternion
Since internally three.js likes to use quaternions, this may be a good method. If your arrow is an upward pointing arrow, you probably want vFrom=[0,1,0], vTo=velocityVector.normalize();

three.js rotate facing object

I did vector stuff for already 13 years now, but am still struggling with the way three.js handles things. So what I like to do is best described with a fan that always faces the camera and, of course, rotates.
This is how I achieved this in another language:
// Create a vector facing in camera direction.
temp.x = 0;
temp.y = 1;
temp.z = 0;
vec_rotate(temp, camera.pan);
// Apply vector direction to scene object.
vec_to_angle(entity.pan, temp);
// Rotate scene object's angle around another angle / imaginary line from camera to object.
ang_rotate(
entity.pan,
vector(random(360), 0, 0)
);
Thus, after applying entity.lookAt(camera.position) I am missing an angle rotation based on the current angle (last function call of the example).
One way to model a fan is like so:
var fan = new THREE.Mesh( ... );
var pivot = new THREE.Object3D();
scene.add( pivot );
pivot.lookAt( camera.position );
pivot.add( fan );
Then in the animation loop (assuming the fan mesh by default faces the positive-z axis),
fan.rotation.z += 0.01;
three.js r.68

How to strech(scale) mesh on the world axis

There is an online 3d editor where you can edit individual meshes (move, scale, rotate). Ability to edit meshes implemented using custom transform controls which based on threejs's TransformControls code. This is fragment from mousemove event:
var intersect = intersectObjects(pointer, [xzPlane]); // intersect mouse's pointer with horizontal plane
var point = new THREE.Vector3();
point.copy(intersect.point);
point.sub(offset); // coords from mousedown event (from start stretching)
// some code for 'scale' value calculating base on 'point' variable
// var scale = ...;
//
mesh.scale.x = scale;
This code works well if the mesh does not rotate.
Requires scaling always happened to the world coordinate system. This is programming question
For example, from this:
To this:
P.S. I think that custom mesh matrix must be created, but I have very little experience with matrices
Thanks!
Instead of setting the rotation, like so:
mesh.rotation.set( Math.PI/4, 0, 0 );
apply the identical rotation to the geometry, instead:
var euler = new THREE.Euler( Math.PI / 4, 0, 0 );
mesh.geometry.applyMatrix( new THREE.Matrix4().makeRotationFromEuler( euler ) );
Now, you can set the scale and get the result you want.
mesh.scale.z = 2;
fiddle: http://jsfiddle.net/Tm7Ab/5/
three.js r.67

How to get Orientation of Camera in THREE.js

I am creating a 3d game using THREE.JS and the Web Audio API. One of the problems I am having is that I want to use the web audio Listener orientation, and define the listener to be the camera, whose position and direction are constantly being updated
My question, is there anyway to easily get the vector direction of a THREE camera?
I was trying to calculate it by using the old camera position, and using the velocity vectors to calculate which way it is facing, but this won't work when the camera is standing still...
Would it be feasible to create a unit vector by getting using camera.rotation.x, camera.rotation.y, camera.rotation.z ?
or is there an even easier way?
Thanks so much for your time!
You want to know the direction in world space in which the camera is looking.
In camera space, the camera is located at the origin and is looking down it's negative z-axis.
Pick a point in front of the camera in camera space:
var pLocal = new THREE.Vector3( 0, 0, -1 );
Now transform that point into world space:
var pWorld = pLocal.applyMatrix4( camera.matrixWorld );
You can now construct the desired direction vector:
var dir = pWorld.sub( camera.position ).normalize();
EDIT: Updated for three.js r.57
EDIT: Also see: three.js set and read camera look vector
In revision 69 (not sure in what revision it was introduced), you can call
camera.getWorldDirection()
to get the camera orientation vector.
if your camera is a child of the player object .e.g. in FPS game. Do the same calculation on the player (parent of camera) , and use this (the Bullit gets the right direction, in this case from obj)
Bullit.prototype.getDirection=function(obj){
this.position.add(obj.position);
var pLocal = new THREE.Vector3( 0, 0, -1 );
var pWorld = pLocal.applyMatrix4( obj.matrixWorld );
var dir = pWorld.sub( obj.position ).normalize();
this.direction=dir;
}

Categories

Resources