Three.js - Convert mesh velocity to mesh rotation - javascript

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

Related

3D model in HTML/CSS; Calculate Euler rotation of triangle

TLDR; Given a set of triangle vertices and a normal vector (all in unit space), how do I calculate X, Y, Z Euler rotation angles of the triangle in world space?
I am attemping to display a 3D model in HTML - with actual HTML tags and CSS transforms. I've already loaded an OBJ file into a Javascript class instance.
The model is triangulated. My first aim is just to display the triangles as planes (HTML elements are rectangular) - I'll be 'cutting out' the triangle shapes with CSS clip-path later on.
I am really struggling to understand and get the triangles of the model rotated correctly.
I thought a rotation matrix could help me out, but my only experience with those is where I already have the rotation vector and I need to convert and send that to WebGL. This time there is no WebGL (or tutorials) to make things easier.
The following excerpt shows the face creation/'rendering' of faces. I'm using the face normal as the rotation but I know this is wrong.
for (const face of _obj.faces) {
const vertices = face.vertices.map(_index => _obj.vertices[_index]);
const center = [
(vertices[0][0] + vertices[1][0] + vertices[2][0]) / 3,
(vertices[0][1] + vertices[1][1] + vertices[2][1]) / 3,
(vertices[0][2] + vertices[1][2] + vertices[2][2]) / 3
];
// Each vertex has a normal but I am just picking the first vertex' normal
// to use as the 'face normal'.
const normals = face.normals.map(_index => _obj.normals[_index]);
const normal = normals[0];
// HTML element creation code goes here; reference is 'element'.
// Set face position (unit space)
element.style.setProperty('--posX', center[0]);
element.style.setProperty('--posY', center[1]);
element.style.setProperty('--posZ', center[2]);
// Set face rotation, converting to degrees also.
const rotation = [
normal[0] * toDeg,
normal[1] * toDeg,
normal[2] * toDeg,
];
element.style.setProperty('--rotX', rotation[0]);
element.style.setProperty('--rotY', rotation[1]);
element.style.setProperty('--rotZ', rotation[2]);
}
The CSS first translates the face on X,Y,Z, then rotates it on X,Y,Z in that order.
I think I need to 'decompose' my triangles' rotation into separate axis rotations - i.e rotate on X, then on Y, then on Z to get the correct rotation as per the model face.
I realise that the normal vector gives me an orientation but not a rotation around itself - I need to calculate that. I think I have to determine a vector along one triangle side and cross it with the normal, but this is something I am not clear on.
I have spent hours looking at similar questions on SO but I'm not smart enough to understand or make them work for me.
Is it possible to describe what steps to take without Latex equations? I'm good with pseudo code but my Math skills are severely lacking.
The full code is here: https://whoshotdk.co.uk/cssfps/ (view HTML source)
The mesh building function is at line 422.
The OBJ file is here: https://whoshotdk.co.uk/cssfps/data/model/test.obj
The Blender file is here: https://whoshotdk.co.uk/cssfps/data/model/test.blend
The mesh is just a single plane at an angle, displayed in my example (wrongly) in pink.
The world is setup so that -X is left, -Y is up, -Z is into the screen.
Thank You!
If you have a plane and want to rotate it to be in the same direction as some normal, you need to figure out the angles between that plane's normal vector and the normal vector you want. The Euler angles between two 3D vectors can be complicated, but in this case the initial plane normal should always be the same, so I'll assume the plane normal starts pointing towards positive X to make the maths simpler.
You also probably want to rotate before you translate, so that everything is easier since you'll be rotating around the origin of the coordinate system.
By taking the general 3D rotation matrix (all three 3D rotation matrices multiplied together, you can find it on the Wikipedia page) and applying it to the vector (1,0,0) you can then get the equations for the three angles a, b, and c needed to rotate that initial vector to the vector (x,y,z). This results in:
x = cos(a)*cos(b)
y = sin(a)*cos(b)
z = -sin(b)
Then rearranging these equations to find a, b and c, which will be the three angles you need (the three values of the rotation array, respectively):
a = atan(y/x)
b = asin(-z)
c = 0
So in your code this would look like:
const rotation = [
Math.atan2(normal[1], normal[0]) * toDeg,
Math.asin(-normal[2]) * toDeg,
0
];
It may be that you need to use a different rotation matrix (if the order of the rotations is not what you expected) or a different starting vector (although you can just use this method and then do an extra 90 degree rotation if each plane actually starts in the positive Y direction, for example).

three.js lookAt() : how to point some local axis which *isn't* the positive Z axis towards another object

I'm creating an app where a person (right now I'm using a cone-shape) is standing on some surface (right now I'm using a cylinder laid lengthwise) and I'd like their feet to orient toward some point (right now it's the center of the cylinder).
(edit: I just realized that my Z axis in this photo is pointing in the wrong direction; it should be pointing towards the camera, but the question remains unchanged.)
Here is a version of the code similar to what I'm trying to accomplish. https://codepen.io/liamcorbett/pen/YMWayJ (Use arrow keys to move the cone)
//...
person = CreatePerson();
person.mesh.up = new THREE.Vector3(0, 0, 1);
//
// ...
//
function updateObj(obj, aboutObj=false){
let mesh = obj.mesh;
if (aboutObj) {
mesh.lookAt(
aboutObj.mesh.position.x,
aboutObj.mesh.position.y,
mesh.position.z)
};
}
//
// ...
//
function animate() {
// ...
updateObj(person);
// ...
}
The code above gives me something similar to what I'm looking for, but the issue is that lookAt() seems to always point the local Positive Z-axis in some direction, and I'd much prefer that it point the local Negative Y-axis instead.
I'd prefer to not change the x,y,z axes of the model itself, as I feel that's going to be a pain to deal with when I'm applying other logic to the person object.
Is there a way to change which axis lookAt() uses? Or am I going to have to roll my own lookAt() function? Thanks ~
Is there a way to change which axis lookAt() uses?
No, the default local forward vector for 3D objects (excluding cameras) is (0, 0, 1). Unlike other engines, three.js does not allow to configure the forward vector, only the up vector. But this is not really helpful in your case.
You can try to transform the geometry in order to achieve a similar effect.
If you don't want to do this for some reasons and you still want to use Object3D.lookAt(), you have to compute a different target vector (so not the cylinder's center).
Even if the forward vector of the lookAt method can't be changed (as #Mugen87 said), you can still adjust the local rotation afterwards by knowing in advance the difference between the forward Z axis used, and the axis you consider your mesh to be "upward" (ex: a person standing up on the Y axis).
Basically, in your case, just add this line after the lookAt method :
mesh.rotateOnAxis( new THREE.Vector3(1,0,0), Math.PI * -0.5 );
And the cone will look up :)

Rotating a vector around a sphere (simulating sun) in three.js

I am trying to adapt this code (which itself is a implementation of this). I have gotten the general visualization and rendering to work, but I'm now trying to animate some realistic movement.
The point of the light source is determined by a normalized vector (for example THREE.Vector3(1, 0.75, 0). The light will appear to be coming from the top right). Now what I would like to do is to have the vector rotate around the sphere in such a way that it seems like the sphere is orbiting the light source. But I can't figure out how to do this. I've tried updating/changing the position of the vector. But I'm not sure how to calculate the proper next x,y,z values. I've tried applying euler angles and rotational matrix, like so:
euler = new THREE.Euler(f,g,h, 'XYZ');
matrix = new THREE.Matrix4().makeRotationFromEuler(euler);
light = vector.applyMatrix4(matrix);
But here, I'm not sure how to get the correct values of f,g,h such that the light doesn't just wobble around the sphere.
Am I even on the right track?
Working example: http://jsfiddle.net/VsWb9/3890/
You are increasing linearly two of 3 coordinates in euler angles, that is the issue.
For other rotations than around x/y/z axis, the best for understanding/avoiding issues/coding/computational cost is to trust quaternions.
They are rather intuitive, in threejs too. They are made of 4 coordinates : 3 for a rotation axis, and the fourth for the rotation value.
var quat=new THREE.Quaternion();
//we set the axis around which the rotation will occur. It needs to be normalized
var axis=new THREE.Vector3(0,1,0).normalize();
//and the angle value (radians)
var angle=0;
//this is your light vector (=original position of your light)
var light=new THREE.Vector3(1,0,0).normalize();
Then in your render loop you change the angle value and tell the quaternion to use the axis above, and the updating angle :
angle+=.001//(or angle -= if your axis points in the +y direction like here)
quat.setFromAxisAngle(axis,angle);
And finally apply it :
light.applyQuaternion(quat);
Updated fiddle : http://jsfiddle.net/Atrahasis/0v93p2xy/
Note about normalized vectors :
a normalized vector is 1-unit length.
( 1 , 0 , 0 ),( 0 , 1 , 0 ),( 0 , 0 , 1 ) are native-normalized vectors (and unit vectors).
( 1 , .75 , 0 ) is not a normalized vector, its length is √(1²+.75²)=1.5625 (pythagoras)
for example ( 0.6614... , .75 , 0 ) is a normalized vector

version 70 about the TrackballControls.js rotation

My question in github--
https://github.com/mrdoob/three.js/issues/6043#issuecomment-73478885
I know TrackballControls.js be used for controlling a camera, not a model.
But I want rotation model with this TrackballControls.js.When I control the model with mouse,I can pan,zoom,rotate.When I rotate the model, I won't feel in a rotating camera, but seemed to rotate the model itself. I need a mouse event.
method:
controls.target = model.position;
or controls.target.copy( model.position )
they no use;
As seen in the lao3d.com Viewer you want to rotate the object around the worlds Y and X axis independent of its translation.
So I made this little example http://jsfiddle.net/nk3977jb/1/ .
It takes into consideration that the camera might be rotated.
And therefore creates its current Up and Right vector using the quaternion of the camera to get the up and view vector to then calculate the cross product of it.
var view = new THREE.Vector3(0, 0, -1);
view.applyQuaternion(camera.quaternion);
var cross = new THREE.Vector3();
cross.crossVectors(view, camera.up);
You can think of view being -Z going into the Screen, +Y being the cameras up vector pointing upwards from whichever rotation the camera has and cross being the crossproduct of -Z and +Y therefore pointing right.
With these axis setup you can rotate the object around them.

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

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 ?

Categories

Resources