it seems that there is a problem with my custom Quaternion implementation.
Download My quaternion implementation in Java.
When I lauch this snippet, in order to see quaternion angle evolutions, in a Main() method :
Quaternion currentRotation = Quaternion.buildIdentityQuaternion();
Quaternion constantRotation = Quaternion.buildFromAxisAngle(10, 0,1,0);
while(true){
float angle = currentRotation.toAxisAngle()[0];
System.out.println(angle);
currentRotation = constantRotation.mulInLeftOf(currentRotation);
}
I've got the following results :
0.0
9.99997
19.999975
29.999979
39.99998
49.999977
59.999977
69.99998
79.99997
89.99997
99.99997
109.99997
119.99997
129.99997
139.99997
149.99997
159.99997
169.99997
179.99997
189.99998
199.99998
209.99998
219.99998
230.0
240.0
250.00002
260.00003
270.00003
280.00003
290.00006
300.0001
310.00012
320.00015
330.00024
340.00037
350.00082
360.0
350.00012
340.0001
330.0001
320.0001
310.00006
300.00006
290.00006
So why does the angle value first goes to 360 degrees then decreases toward 0 ? Though I've computed the angle with the formula 2*Acos(Quaternion.w) in Quaternion#toAxisAngle() method ? Maybe the implementation is not bad, so how can I compute the angle so that it returns 0, 10, ...,350, 360, 0, 10, ..., 350, 360, 0, 10 and so on ?
And finally, is there a way to compute the real angle ? SO that angle goes across values : 0,10,20,30,...,360,0,10,20... ?
However I managed to use it in a JOGL program to make a Simple 6-color cube rotate regularly, simply by computing a quaternion multiplication each time and by calling toMatrix() method on the result quaternion. What worked (do not pay attention to JOGL specific implementation details) :
// The OpenGL functionnalities of my program
MyJOGLEventListener implements GLEventListener {
Quaternion myCurrentRotation = Quaternion.buildIdentityQuaternion() // w=1, x=0, y=0, z=0
Quaternion constantRotation = Quaternion.buildFromAxisAngle(0.02f, 0,1,0) // angle = 0.02 degrees, x=0, y=1, z=0
GLUgl2 glu = new GLUgl2();
public void display(GLAutoDrawable drawable) {
GL2 gl = drawable.getGL().getGL2();
gl.glClear(/* OMITTED : color buffer and depth buffer*/);
gl.glLoadIdentiy();
glu.gluLookAt(/* OMITTED */);
/// the three most relevent lines ///////////////////////////////////
gl.glMultMatrix(myCurrentRotation.toMatrix(), 0);
MyCubeDrawer.drawCube(gl);
myCurrentRotation = constantRotation.mulInLeftOf(myCurrentRotation);
//////////////////////////////////////////////////////////////////////
}
// OMITED : in init(GLAutoDrawable) method, I set up depth buffer
// OMITED : the reshape(GLAutoDrawable drawable, int x, int y, int width, int height) sets the viewport and projection
}
Regards
I suspect that your quaternion implementation is fine. When you pass 360 degrees with a quaternion, it inverts the axis of rotation. So at first, your quaternion represents positive rotations around the positive y axis; however, once you cross 360 degrees, it begins to represent a positive rotation around the negative y axis.
So the angles you are getting are still correct. A rotation of zero is usually represented as [1 0 0 0]. A rotation of 180 degrees around the y axis will be represented as [0 0 1 0]. And then a rotation of 360 degrees (obviously equivalent to a rotation of 0) comes out to be [-1 0 0 0]. Rotating by another 180 degrees will give you [0 0 -1 0] This is a general property of quaternions. They're 2x redundant. If you negate all the components of the quaternion, it's equivalent to rotating the opposite direction around a flipped axis to get the same result.
To get what you wanted, you could just check the y component of your quaternion. If it's less-than-zero, then subtract your computed rotation from 360 in order to flip the axis back to positive.
Related
I am trying to use quaternion rotation using the quaternion from JOML: https://github.com/JOML-CI/JOML/blob/main/src/org/joml/Quaternionf.java.
I can get objects to rotate but they get stuck and are unable to complete a full rotation. The object is being updated every frame.
Edit:
Removed all euler related code and am simply trying to get the object to rotate on a single axis based on a certain angle.
Edit 2:
Am trying to use the conjugate and multiplying the quaternions together like I have seen in some videos. I'm not quite there though as the model spins itself off the screen for some reason.
Edit 3:
Normalizing the quaternion fixed the disappearing behaviour. The issue seems to be that there's no simple way to rotate a certain amount without either having a timer to lerp over which will not work in my case as I am trying to rotate an object an arbitrary amount with no set beginning and end.
Rotation function
public void rotate(float angle, float x, float y, float z) {
Quaternionf rot = new Quaternionf();
rot.rotateAxis((float) Math.toRadians(angle), x, y, z);
Quaternionf conjugate = rot.conjugate();
rotation = rot.mul(rotation).mul(conjugate);
}
Calling the rotation function
entity.transform.rotate( 1,0,1, 0);
It does not matter whether you transform your Euler angles into a matrix or a quaternion or whatever representation: As long as you use Euler angles to represent an orientation, Gimbal Lock is unavoidable.
So, in order to avoid Gimbal Lock from happening, you must discard using Euler Angles and stay with one representation for an orientation (like a 3x3 matrix or a quaternion) and apply delta changes to them, instead of trying to represent a full orientation as three Euler angles. So, whenever you - let's say - rotate the object a few degrees around a certain axis, you apply that delta change or orientation to the matrix/quaternion.
I believe I have figured it out.
You need to create a quaternion and rotate it to your delta values, do not manipulate quaternion values directly (e.g. use rotationX function instead).
To add quaternions together you multiply them.
Finally you need to use the equation:
delta quaternion * quaternion to rotate * inverse of delta quaternion
Code:
public void rotate(float x, float y, float z) {
//Use modulus to fix values to below 360 then convert values to radians
float newX = (float) Math.toRadians(x % 360);
float newY = (float) Math.toRadians(y % 360);
float newZ = (float) Math.toRadians(z % 360);
//Create a quaternion with the delta rotation values
Quaternionf rotationDelta = new Quaternionf();
rotationDelta.rotationXYZ(newX, newY, newZ);
//Calculate the inverse of the delta quaternion
Quaternionf conjugate = rotationDelta.conjugate();
//Multiply this transform by the rotation delta quaternion and its inverse
rotation.mul(rotationDelta).mul(conjugate);
}
After a long time, I try to use JAVA again. I'm using the vectmath package, with which I'd like to rotate a 3d vector with a rotation matrix. So I wrote that:
double x=2, y=0.12;
Matrix3d rotMat = new Matrix3d(1,0,0, 0,1,0, 0,0,1); //identity matrix
rotMat.rotX(x); //rotation on X axis
rotMat.rotY(y); // rotation on Y axis
System.out.println("rot :\n" + rotMat); // diagonal shouldn't have 1 value on it
result:
rot :
0.9928086358538663, 0.0, 0.11971220728891936
0.0, 1.0, 0.0
-0.11971220728891936, 0.0, 0.9928086358538663
Unfortunately, it doesn't give me what I expected. It is like he ignored the first rotation (around X) and only take the second one (around Y).
If I comment the rotMat.rotX(x); it gives me the same result.
I suspect either a mistake with the print, or with variable management.
Thanks
Methods rotX and rotY set/overwrite the matrix elements, so your subsequent call to rotY(y) cancels the call to rotX(x). Something like this should work:
Vector3d vector = ... //a vector to be transformed
double x=2, y=0.12;
Matrix3d rotMat = new Matrix3d(1,0,0, 0,1,0, 0,0,1); //identity matrix
rotMat.rotX(x); //rotation on X axis
rotMat.transform(vector);
rotMat.rotY(y); // rotation on Y axis
rotMat.transform(vector);
// the vector should now have both x and y rotation
// transformations applied
System.out.println("Rotated vector :\n" + vector);
I located my camera within 3D cube -
public void display(GLAutoDrawable drawable) {
...
glu.gluLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
...
}
It's look like that - (each color - side-lock ) -
Now I trying to rotate and camera looking (the three centerX, centerY, centerZ ) around axis X - according to this rotation formula -
It works well until it reach to the state which the Z's value become to negative - and after that the camera's looking start to rotate to the other side of X's axis .
I show you the (centerX,centerY,centerZ) values in the range of this bug -
1.0 -1.2817129 0.59767246 //ok
1.0 -1.3289262 0.4836895 // ok
1.0 -1.3660256 0.36602536 //ok
1.0 -1.3927287 0.24557555 // ok
1.0 -1.4088323 0.12325676 //ok
1.0 -1.4142138 -9.784749E-8 // ok for last time
1.0 -1.4088323 -0.12325695 // rotate to other side of X
1.0 -1.3927287 -0.24557574 // so on ...
How you recommends for me to fix that and make it rotate on all over 360 degrees ?
Edit:
Does (upX,upY,upZ) should by rotate in this case too ?
You need to rotate the direction vector about the X-axis, which is center - eye, not center. The overall computation should be center = eye + Rot(center - eye).
I have matrix. This matrix represents array x and y coordinates. For example
float[] src = {7,1,7,2,7,3,7,4};
I need to rotate this coordinates to 90 degrees.
I use android.graphics.Matrix like this:
float[] src = {7,1,7,2,7,3,7,4};
float[] dist = new float[8];
Matrix matrix = new Matrix();
matrix.preRotate(90.0f);
matrix.mapPoints(dist,src);
after operation rotate I have array with next values
-1.0 7.0 -2.0 7.0 -3.0 7.0 -4.0 7.0
Its is good for area with 360 degrees.
And how do rotate in area from 0 to 90? I need set up center of circle in this area but how ?
Thanks.
Use setRotate, not preRotate:
setRotate initializes the matrix as a rotation matrix.
preRotate multiplies the current matrix by a rotation matrix M' =
M x R
Since you called the default constructor your starting with the identity matrix.
Remember matrix multiplication is not commutative.
I'm not familiar with android, but if you translate after you rotate you can get a rotation around a specific point. Find where your center point would be rotated to, then translate it back to it's original position.
Use the Matrix preRotate(float degrees, float px, float py) method (preRotate documenation)
This preRoate(degrees) is equivalent to preRotate(degrees, 0, 0).
I have a Cone I drew in Java 3D with the following code:
Cone cone = new Cone(2f, 3f);
Transform3D t3d = new Transform3D();
TransformGroup coneTransform = new TransformGroup(t3d);
coneTransform.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
t3d.setTranslation(new Vector3f(0f,0f,0f);
coneTransform.setTransform(t3d);
coneTransform.addChild(cone);
this.addChild(coneTransform);
Suppose I have the cone sitting at point (1,1,1) and I want the tip of the cone to point down an imaginary line running through (0,0,0) and (1,1,1)... how can I do this?
Here's an example of what I've been trying:
Transform3D t3d = new Transform3D();
Vector3f direction = new Vector3f(1,2,1);
final double angleX = direction.angle(new Vector3f(1,0,0));
final double angleY = direction.angle(new Vector3f(0,1,0));
final double angleZ = direction.angle(new Vector3f(0,0,1));
t3d.rotX(angleX);
t3d.rotY(angleY);
t3d.rotZ(angleZ);
t3d.setTranslation(direction);
coneTransform.setTransform(t3d);
Thanks in advance for all help!
I'm just learning Java 3D myself at the moment, and from my current knowledge, the rotation methods set the transform to a rotation about that axis only.
Therefore, if you wish to perform rotations about multiple axes, then you will need to use a second Transform3D.
ie:
Transform3D rotation = new Transform3D();
Transform3D temp = new Transform3D();
rotation.rotX(Math.PI/2);
temp.rotZ(Math.PI/2);
rotation.mul(temp); // multiply the 2 transformation matrices together.
As for the reason for Math.PI, this is because it uses radians instead of degrees, where Math.PI is equivalent to 180 degrees.
Finding the angle between your current orientation and your intended orientation isn't too hard - you could use Vector3fs, with the angle() method. A Vector would be set up with the initial orientation, and another in the intended.
However, this doesn't tell you in which axes the angle lies. Doing so would require examination of the vectors to see which segments are set. [of course, there may be something that I am currently unaware of in the API]
This is not a java3D specific answer.
In general a matrix can be built such that there are 4 vectors that describe it.
1) A side (or lateral) vector
2) An up vector
3) A direction vector
4) A position
Each row of a 4x4 matrix.
Thus for a simple identity matrix we have the following matrix (I'll define a column major matrix, for a row major matrix all you need to do is swap the matrix indices around such that row 2 col 3 becomes row 3 col 2 throughout the matrix).
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1
in this the first column is the side vector. The second column the up vector. The third the direction and the fourth the position.
Logically we can see that the vector (1, 0, 0, 0) points along the x axis (and thus is the side vector). The vector (0, 1, 0, 0) points along the y axis (and thus is the up vector). The third (0, 0, 1, 0) points along the Z-axis (and thus is the direction vector). The fourth (0, 0, 0, 1) indicates that the objects does not move at all.
Now lets say we wanted to face along the X-axis.
Obviously that would mean we have a vector of (1, 0, 0, 0 ) for our direction vector. Up would still be (0, 1, 0, 0) and position still 0, 0, 0 1. So what would our side vector be? Well, logically it would point along the z-axis. But which way? Well hold your fingers such that one finger points forward, one to the side and one up. Now rotate so that the forward finger is facing the same direction as the side pointing finger. Which way is the side pointing finger pointing now? The opposite direction to the original direction pointing finger. Thus the matrix is
0 0 1 0
0 1 0 0
-1 0 0 0
0 0 0 1
At this point things seemingly get a little more complicated. It is simple enough to take an arbitrary position and an arbitrary point to look at (I'll call them vPos and vFocus). It is easy enough to form a vector from vPos to vFocus by subtracting vPos from vFocus (vFocus.x - vPos.x, vFocus.y - vPos.y, vFocus.z - vPos.z, vFocus.w - vPos.w ). Bear in mind all positions should be defined with a '1' in the w position where all directions should have a '0'. This is automatically taken care of when you do the subtraction above as the 1 in both ws will cancel out and leave 0. Anyway, we now have a vector pointing from the position towards vFocus we'll call it vDir. Unfortunately it has the length of the difference between vPos and vFocus. However if we divide the vDir vector by its length (vDir.x / length, vDir.y / length, vDir.z / length, vDir.w / length) then we normalise it and we have a direction with a total length of 1.
At this ponit we now have our 3rd and 4th columns of our matrix. Now, lets assuem up is still (0, 1, 0, 0) or vUp. We can assume that the crossproduct of the direction and vUp will produce a vector that is perpendicular (and also of unit length) to the plane formed by vDir and vUp. This gives us our side vector or vLat. Now .. we did kind of assume the up vector so its not strictly correct. We can now calculate it exactly by taking the cross product of vLat and vDir and we have all 4 vectors.
The final matrix is thus defined as follows
vLat.x vUp.x vDir.x vPos.x
vLat.y vUp.y vDir.y vPos.y
vLat.z vUp.z vDir.z vPos.z
vLat.w vUp.w vDir.w vPos.w
This isn't strictly the full answer as you will get problems as you look towards a point near to your (0, 1, 0, 0) vector but that should work for most cases.
I finally figured out what I wanted to do by using Quaternions, which I learned about here: http://www.cs.uic.edu/~jbell/Courses/Eng591_F1999/outline_2.html Here's my solution.
Creating the cone:
private void attachCone(float size) {
Cone cone = new Cone(size, size* 2);
// The group for rotation
arrowheadRotationGroup = new TransformGroup();
arrowheadRotationGroup.
setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
arrowheadRotationGroup.addChild(cone);
// The group for positioning the cone
arrowheadPositionGroup = new TransformGroup();
arrowheadPositionGroup.
setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
arrowheadPositionGroup.addChild(arrowheadRotationGroup);
super.addChild(arrowheadPositionGroup);
}
Now, when I want to rotate the cone to point in a certain direction specified as the vector from the point (0,0,0) to (direction.x, direction.y, direction.z), I use:
private final Vector3f yAxis = new Vector3f(0f, 1f, 0f);
private Vector3f direction;
private void rotateCone() {
// Get the normalized axis perpendicular to the direction
Vector3f axis = new Vector3f();
axis.cross(yAxis, direction);
axis.normalize();
// When the intended direction is a point on the yAxis, rotate on x
if (Float.isNaN(axis.x) && Float.isNaN(axis.y) && Float.isNaN(axis.z))
{
axis.x = 1f;
axis.y = 0f;
axis.z = 0f;
}
// Compute the quaternion transformations
final float angleX = yAxis.angle(direction);
final float a = axis.x * (float) Math.sin(angleX / 2f);
final float b = axis.y * (float) Math.sin(angleX / 2f);
final float c = axis.z * (float) Math.sin(angleX / 2f);
final float d = (float) Math.cos(angleX / 2f);
Transform3D t3d = new Transform3D();
Quat4f quat = new Quat4f(a, b, c, d);
t3d.set(quat);
arrowheadRotationGroup.setTransform(t3d);
Transform3D translateToTarget = new Transform3D();
translateToTarget.setTranslation(this.direction);
arrowheadPositionGroup.setTransform(translateToTarget);
}
I think this should do it:
coneTransform.rotX(Math.PI / 4);
coneTransform.rotY(Math.PI / 4);
you can give your Transform3D a rotation matrix. you can get a rotation matrix using Rotation matrix calculator online: http://toolserver.org/~dschwen/tools/rotationmatrix.html here's my example:
Matrix3f mat = new Matrix3f(0.492403876506104f, 0.586824088833465f,
-0.642787609686539f, 0.413175911166535f, 0.492403876506104f,
0.766044443118978f, 0.766044443118978f, -0.642787609686539f, 0f);
Transform3D trans = new Transform3D();
trans.set(mat);