If I got it right: an Object3D has on one side the set of properties (position, scale, rotation) [*] and on the other side the matrix - this later contains esentially the same information, it's computed from the others in Object3D.updateMatrix() and is the one really used internally by the renderer computations. Instead, the set (position, scale, rotation) is the one that is usually manipulated by the programmer (translations, rotations...).
Sometimes, however, one manipulates directly the matrix, and, further, instead of setting matrixAutoUpdate=false and forgetting about (position, scale, rotation), one wants to keep the properties in sync, i.e. do what Object3D.updateMatrix() does but in the other direction. Is there some implemented/efficient/recommended procedure to accomplish this?
[*] Caveats:
1) if useQuaternion=true, the quaternion is used instead of rotation
2) rotation is complemented by eulerOrder
3) I'm not sure what role play up in this picture, I guess it's not important here.
The routine
Matrix4.decompose( position, quaternion, scale ) // revision 57dev
will compute the position, quaternion, and scale vectors from a matrix.
(In r.56 the arg names were unfortunately misnamed, and this has been fixed in r.57dev.)
If you want the rotation vector instead, you will have to compute it from the just-computed quaternion like so:
rotation.setEulerFromQuaternion( quaternion, eulerOrder );
The up vector is not relevant to your question.
You can call the applyMatrix(matrix) method on a 3D object. To see what it does, see here.
Either:
1) you can feed it an identity matrix, or
2) you could copy what the function does:
this.scale.getScaleFromMatrix( this.matrix );
var mat = new THREE.Matrix4().extractRotation( this.matrix );
this.rotation.setEulerFromRotationMatrix( mat, this.eulerOrder );
this.position.getPositionFromMatrix( this.matrix );
I'm not sure how quaternions are relevant if you do the above, I don't know the circumstances under which the useQuaternion flag is set to true. But in Object3D objects by default it's false. I have a feeling it uses quaternions only when calculating certain rotations so to avoid gimbal lock.
I think the following code taken from the lookAt() method would update the quaternion:
this.quaternion.copy( this.matrix.decompose()[ 1 ] );
Related
I am learning ThreeJS and trying to understand some fundamental concepts.
Suppose I have a camera in 3D space, looking at some target (defined in the camera.target property). The camera is located at x1, y1, z1.
I want to add a feature that when activated, moves the camera directly above the target, i.e. it should be looking down at the XY plane, as though the camera is in the sky looking STRAIGHT down.
My question is, how do I do this in ThreeJS, and also how do you think of this conceptually/mathematically?
While your question seems simple, here is a nuanced take with some tips that you might find helpful.
Position
Yes, the simplest answer is to assign the camera's position where the z value is the distance from the target.
camera.position.set( 0, 0, distance )
But this only works if the target is positioned at the origin. What if you target is at position ( 10, 20, 30 )?
You can use vector math to fix this, and three has this baked in for you.
Create a Vector3 with the position assigned as if the target was at the origin.
let offset = new THREE.Vector3( 0, 0, distance )
Add this vector to the target's position, and assign it to the camera's position.
camera.position.addVectors( target.position, offset )
The camera is now positioned above the target.
Rotation
In either case, simply repositioning you camera may not be enough to keep your target in view. After moving your camera, you will need to force it to look at the target.
camera.lookAt( target.position )
Now, lookAt is a fairly simple function, and may not result in the camera roll that you expect. You will need to figure out how best to compensate for this, by adjusting its up, quaternion, or other factors. (Compensating for this is outside the scope of this question.)
Global vs. Local
Another nuance is whether you want the camera to be "above" the part in a global sense, or in a local sense.
If your camera and target exist in a global space (directly in your scene), then the directions above will suit your use-case.
But if your target is rotated on its side within the global space (i.e. its +z axis points along the global +x axis), yet you want the camera's new orientation to be "above" the target in the sense that it is looking down the target's -z axis, then you will need to compensate for the target's rotation as well. Luckily, three also provides math functions that can accomplish this.
camera.position.copy( offset )
camera.position.applyMatrix4( target.matrixWorld )
This first line sets the camera's position to that of the "target at the origin" position. The second line updates that vector using the target's world transformation matrix, effectively translating it into the target's space.
Animation
Reading between the lines, it sounds like you might want to animate this process. There are a variety of animation libraries available, and you'll need to find one that suits your needs and purpose. That said, there are also many questions about animation on Stack Overflow, and I'm sure you can find someone to answer your questions on that topic, should you hit any resistance.
I'm making a 3D monster maker. I recently added a feature to flip parts along the x and y axes, this works perfectly fine on its own, however, I also have a feature that allows users to combine parts (sets flags, doesn't combine mesh), this means that simply flipping the individual objects won't flip the "shape" of the combined object. I have had two ideas of how to do this which didn't work and I'll list them below. I have access to the origin of the objects and the centre of mass of all instances that are combined - the 0, 0, 0 point on a theoretical number plane
In these examples we're flipping across the y axis, the axis plane is X = width, Y = height, Z = depth
Attempt #1 - Simply flipping the individual object's X scale, getting the X distance from the centreMass and taking that from the centreMass for position, this works when the direction of the object is (0, 0, 1) and the right (1, 0, 0) or (-1, 0, 0), in any other direction X isn't the exact "left/right" of the object. Here's a video to clarify: https://youtu.be/QXdEF4ScP10
code:
modelInstance[i].scale.x *= -1;
modelInstance[i].basePosition.set(centre.x - modelInstance[i].distFromCentre.x, modelInstance[I].basePosition.y, modelInstance[I].basePosition.z);
modelInstance[i].transform.set(modelInstance[i].basePosition, modelInstance[i].baseRotation, modelInstance[i].scale);
Attempt #2 - Rotate the objects Y180° around the centreMass and then flip their z value. As far as I understand, this is a solution, but I don't think I can do this. The way to rotate an object around a point AFAIK involves transforming the matrix to the point, rotating it, and then translating it back which I can't use. Due to the ability to rotate, join, flip, and scale objects I keep the rotation, position, and scale completely separate because issues with scaling/rotating and movement occur. I have a Vector3 for the position, a matrix for the rotation, and a Vector3 for the scale, whenever I change any of these I use object.transform.set(position, matrix.getRotation(), scale); So when I attempt to do this method (translating rotation matrix to point etc) the objects individually flip but remain in the same place, translating the objects transform matrix has weird results and doesn't work. Video of both variations: https://youtu.be/5xzTAHA1vCU
code:
modelInstance[i].scale.z *= -1;
modelInstance[i].baseRotationMatrix.translate(modelInstance[i].distFromCentre).rotate(Vector3.Y, 180).translate( modelInstance[i].distFromCentre.scl(-1));
modelInstance[i].transform.set(modelInstance[i].basePosition, modelInstance[i].baseRotation, modelInstance[i].scale);
Ok, since no one else has helped I'll give you some code that you can either use directly or use to help you alter your code so that it is done in a similar way.
First of all, I tend to just deal with matrices and pass them to shaders as projection matrices, ie. I don't really know what modelInstance[i] is, is it an actor (I never use them), or some other libgdx class? Whatever it is, if you do use this code to generate your matrices, you should be able to overwrite your modelInstance[i] matrix at the end of it. If not, maybe it'll give you pointers on how to alter your code.
First, rotate or flip your object with out any translation. Don't translate or scale first, because when you rotate you'll also rotate the translation you've performed. I use this function to generate a rotation matrix, it rotates around the y axis first, which I think is way better then other rotation orders. Alternatively you could create an identity matrix and use the libgdx rotation functions on it to create a similar matrix.
public static void setYxzRotationMatrix(double xRotation, double yRotation, double zRotation, Matrix4 matrix)
{
// yxz - y rotation performed first
float c1=(float)Math.cos(yRotation);
float c2=(float)Math.cos(xRotation);
float c3=(float)Math.cos(zRotation);
float s1=(float)Math.sin(yRotation);
float s2=(float)Math.sin(xRotation);
float s3=(float)Math.sin(zRotation);
matrix.val[0]= -c1*c3 - s1*s2*s3; matrix.val[1]=c2*s3; matrix.val[2]=c1*s2*s3-c3*s1; matrix.val[3]=0;
matrix.val[4]= -c3*s1*s2 + c1*s3; matrix.val[5]=c2*c3; matrix.val[6]=c1*c3*s2+s1*s3; matrix.val[7]=0;
matrix.val[8]= -c2*s1; matrix.val[9]=-s2; matrix.val[10]=c1*c2; matrix.val[11]=0;
matrix.val[12]=0; matrix.val[13]=0; matrix.val[14]=0; matrix.val[15]=1.0f;
}
I use the above function to rotate my object to the correct orientation, I then translate it to the correct location, then multiply it by the cameras matrix and scale as the final operation. This will definitely work if you can do it that way, but I just pass my final matrix to the shader. I'm not sure how you use your matrices. If you want to flip the model using the scale, you should try it immediately after the rotation matrix has been created. I'd recommend getting it working without flipping with scale first, so you can test both matrix.scl() and matrix.scale() as the final step. Off hand, I'm not sure which scale function you'll need.
Matrix4 matrix1;
setYxzRotationMatrix(xRotationInRadians, yRotationInRadians, zRotationInRadians,matrix1);
//matrix1 will rotate your model to the correct orientation, around the origin.
//here is where you may wish to use matrix1.scl(-1,1,1) or matrix1.scale(-1,1,1).
//get anchor position here if required - see notes later
//now translate to the correct location, I alter the matrix directly so I know exactly
what is going on. I think matrix1.trn(x, y, z) would do the same.
matrix1.val[12]=x;
matrix1.val[13]=y;
matrix1.val[14]=z;
//Combine with your camera, this may be part of your stage or scene, but I don't use
//these, so can't help.
Matrix4 matrix2;
//set matrix2 to an identity matrix, multiply it by the cameras projection matrix, then
//finally with your rotation/flip/transform matrix1 you've created.
matrix2.idt().mul(yourCamera.combined).mul(matrix1);
matrix2.scale(-1,1,1); //flipping like this will work, but may screw up any anchor
//position if you calculated one earlier.
//matrix2 is the final projection matrix for your model. ie. you just pass that matrix
to a shader and it should be used to multiply with each vertex position vector to create
the fragment positions.
Hopefully you'll be able to adapt the above to your needs. I suggest trying one operation at a time and making sure your next operation doesn't screw up what you've already done.
The above code assumes you know where you want to translate the model to, that is you know where the center is going to be. If you have an anchor point, lets say -3 units in the x direction, you need to find out where that anchor point has been moved to after the rotation and maybe flip. You can do that by multiplying a vector with matrix1, I'd suggest before any translation to the correct location.
Vector3 anchor=new vector3(-3,0,0);
anchor.mul(matrix1); //after this operation anchor is now set to the correct location
//for the new rotation and flipping of the model. This offset should
//be applied to your translation if your anchor point is not at 0,0,0
//of the model.
This can all be a bit of a pain, particularly if you don't like matrices. It doesn't help that everything is done in a different way to what you've tried so far, but this is the method I use to display all the 3D models in my game and will work if you can adapt it to your code. Hopefully it'll help someone anyway.
I have a Plane, and a Group of objects, which has a rotation. I need to set the plane's surface normal to always be rotated in accordance with the Group's rotation (this rotation changes based on user input).
I've tried things like this, which sort of work:
onGroupRotation() {
const vec = orbitGroup.rotation.toVector3();
orbitPlane.set(vec, 0);
}
The problem is that the rotation's magnitude seems too small: the plane's rotation doesn't quite match that of the orbitGroup.
I don't really know linear algebra and I'm still learning about things like vector dot and cross products. I'm sure there's a simple solution someone more knowledgeable can help me fix.
The default forward vector for 3D objects like groups or meshes is (0, 0, 1). If you set the surface normal to this value and apply the quaternion of the group, you should get the desired result.
plane.normal.set( 0, 0, 1 ).applyQuaternion( group.quaternion );
I use a pivot group to rotate my planegeometries instead of each one individually. After rotation of the pivot group-object, I want to find the new positions for each child/planegeometry-mesh to correspond to the positions that is relative to the actual world position.
How do I go about doing this?
The easy way
Like Craig mentioned, getWorldPosition is a function on Object3D (the base class of pretty much everything in the scene), which returns a new Vector3 of the object's world position.
var childPlaneWorldPosition = childPlane.getWorldPosition();
The harder way:
There are two methods for converting between local and world positions: localToWorld and worldToLocal.
These are also functions on Object3D, and take a Vector3. The vector is then (destructively) converted to the desired coordinate system. Just know that it's not smart enough to know if the vector you're giving it is already in the right coordinate system--you'll need to keep track of that.
So, to convert a child plane's position from local to world coordinates, you would do this:
// clone because localToWorld changes the vector passed to it
var childPlanePosition = childPlane.position.clone();
childPlane.parent.localToWorld(childPlanePosition);
Notice that localToWorld is called on childPlane's parent. This is because the childPlane is local to its parent, and therefore its position is local to its parent's coordinate system.
The hard(er to understand) way:
Each childPlane stores not only its local transformation matrix (childPlane.matrix), but also its world transformation matrix (childPlane.matrixWorld). You can, of course, get the world position directly from the matrixWorld property in one step.
var childWorldPosition = new THREE.Vector3(
childPlane.matrixWorld.elements[12],
childPlane.matrixWorld.elements[13],
childPlane.matrixWorld.elements[14]
);
Edit to answer some questions
"If I understand correctly, can I find the "real" position of the meshes in the pivot-group children-array?"
Yes. If you called:
pivotGroup.add(childPlane);
Then that childPlane will be listed in the pivotGroup.children array, which you could use to iterate over all of the childPlane objects
"And clone these to the position object for each meshes?"
If you want the planes to be in world coordinates (in the scene), but you used the above code to add them to the group, then they are no longer direct children of the scene. You would need to re-add them to the scene:
scene.add(childPlane);
And then apply their calculated world positions. That said, why not just leave them in the group?
(You didn't ask this one) "How would you leave the planes as direct children of the scene, but rotate them as a group?"
Well, you wouldn't. But three.js does this group rotation by multiplying matrices to come up with finalized world matrices for each plane. So you could do the same thing manually, by creating a rotation matrix, and applying it to all of your planes.
var rotMat = new THREE.Matrix4().makeRotationMatrix(x, y, z);
for(var i = 0; i < planesArray.length; ++i){ // I guess this would loop over your 3D array
planesArray.applyMatrix(rotMat);
}
Use plane_mesh.getWorldPosition()
I'm trying to make a Plane to always face the camera or another moving object but I want the Plane to only rotate on 1 axis. How can I use the lookAt function to make it only rotate side ways without tilting to look up or down at the moving object?
thanks, I managed to solve it easily by just keeping the y position of the rotating object constant.
if(planex){
var yaw_control = controls.getYawObject();
pos = new THREE.Vector3( yaw_control.position.x, planex.position.y, yaw_control.position.z );
planex.lookAt(pos);
}
http://www.lighthouse3d.com/opengl/billboarding/index.php?billCyl
maybe this article of any help for you. You are looking for those cylindrical billboards i think but read up from the first page ;) You can modify the specific mesh matrix yourself, although i am not sure if this is the most efficient way. I also did this myself once.
Get the camera look vec:
three.js set and read camera look vector
Then get the camera upVec and afterwards get the cross prodcut of those = rightVec according to the article above.
using those vectors, you can fill in a new Three.Matrix4() like explained in the article and then replace the meshes matrix with the newly created one. As I said, i am not quite into the matrix stuff in three.js but this works but it is probably not that efficient.
For this to work you will have to deactive the meshes auto matrix update with
mesh.matrixAutoUpdate = false;