I've spent a couple weeks on this issue and can't seem to find a proper solution and need some advice.
I'm working on creating a Camera class using LWJGL/Java, and am using Quaternions to handle bearing (yaw), pitch and roll rotations. I'd like this camera to handle all 6 degrees of movement in 3D space, and roll. Bearing, Pitch and Roll are all quaternions. I multiply them into a 'change' quaternion, and create a translation matrix from that. I put that in a float buffer, and multiply the modelview matrix by my buffer containing the rotation matrix.
I can get the bearing and pitch rotations to work properly, but when I implement roll, I'm running into issues. Mainly, rotating around the Z-axis (rolling) doesn't seem to work. When ever I "roll" the camera, it seems to roll around the global Z axis instead of the local camera direction axis. I can usually get 2 of the 3 to work depending on the order I multiply the quaternions, but I can't get them working together.
Since they all work independently, I'm assuming there's something wrong with my orientation method where I combine them and build a rotation matrix. I'm having problems pasting the whole class in, so here are the methods and declarations relating to rotation:
private final static float DEGTORAD = (float)(Math.PI/180);
//Eye - position of the camera in the 3D world.
private Vector3f eye;
//Camera axis vectors, calculated each time reorient() is called.
//Initialized to global x, y, and z axis initially.
private Vector3f up;
private Vector3f right;
private Vector3f direction;
//Angles of rotation (in degrees)
private float pitchAngle;
private float bearingAngle;
private float rollAngle;
private Quaternion pitch;
private Quaternion bearing;
private Quaternion roll;
private FloatBuffer viewMatrixBuffer = BufferUtils.createFloatBuffer(16);
private Quaternion currentOrientation;
...
/**
* Change the bearing (yaw)
* @param bearing delta in degrees
*/
public void bearing(float bearingDelta){
bearingAngle += bearingDelta;
if(bearingAngle > 360){
bearingAngle -= 360;
}else if(bearingAngle < 0){
bearingAngle += 360;
}
bearing.setFromAxisAngle(new Vector4f(0f, 1f, 0f, bearingAngle * DEGTORAD));
bearing.normalise();
}
/**
* Change the pitch
* @param pitch delta in degrees
*/
public void pitch(float pitchDelta){
pitchAngle += pitchDelta;
if(pitchAngle > 360){
pitchAngle -= 360;
}else if(pitchAngle < 0){
pitchAngle += 360;
}
pitch.setFromAxisAngle(new Vector4f(1f, 0f, 0f, pitchAngle * DEGTORAD));
pitch.normalise();
}
/**
* @param initialRoll
*/
public void roll(float initialRoll) {
rollAngle += initialRoll;
if(rollAngle > 360){
rollAngle -= 360;
}else if(rollAngle < 0){
rollAngle += 360;
}
roll.setFromAxisAngle(new Vector4f(0, 0, 1, rollAngle * DEGTORAD));
roll.normalise();
}
/**
* Change direction to focus on a certain point in the world
* @param eye
*/
public void lookThrough(){
reorient();
GL11.glMultMatrix(viewMatrixBuffer);
}
public void reorient(){
//Multiply in order: bearing, pitch, roll. Non-commutative!
Quaternion change = new Quaternion();
Quaternion.mul(bearing, pitch, change);
Quaternion.mul(roll, change, change);
// orient the camera...
Matrix4f rotationMatrix = getRotationMatrix(change);
//Get the looking direction
direction.x = rotationMatrix.m20;
direction.y = rotationMatrix.m21;
direction.z = rotationMatrix.m22;
//Set the position
rotationMatrix.m30 = eye.x;
rotationMatrix.m31 = eye.y;
rotationMatrix.m32 = eye.z;
rotationMatrix.m33 = 1;
rotationMatrix.invert();
rotationMatrix.store(viewMatrixBuffer);
viewMatrixBuffer.rewind();
Vector3f.cross(new Vector3f(0,1,0), direction, null).normalise(right);
Vector3f.cross(right, direction, null).normalise(up);
}
Vector3f, Quaternion, and Matrix4f are all LWJGL classes, not custom made.
So my question is, given 3 Quaternions representing Bearing, Pitch and Roll, how do I modify the ModelView matrix to accurately represent these rotations?
EDIT: I feel that this is very close. See the Gist link in RiverC's comment. After rotating so many degrees, the view jumps around a lot before coming back to normal when rolling. The gist of it is there, but it's still slightly off.
You're doing the multiplications in the wrong order.
For two rotations q1 and q2, if q2 is to follow q1 (since rotations are generally non-communitive) you multiply q2*q1.
In a gimbal style system such as controls for a FPS, the priority order is always yaw, pitch, roll. This would suggest the following math:
roll * pitch * yaw
As a java point, I would suggest not creating new Quaternions for every update but anyway
change = Quaternion.mul(Quaternion.mul(roll, pitch, change), yaw, change);
If you look at the code, the third Quaternion will just be overwritten with the result, so no need to reset it or recreate it each frame / update.
This rotational order thing is confusing, but if you look at the Wiki page on Quaternions, it's the general rule.
I know this is old, but allow me a guess anyway. The problem is exactly what you said yourself:
When ever I "roll" the camera, it seems to roll around the global Z axis instead of the local camera direction axis.
It does so because you asked it to roll around vector (0,0,1), that is, the global Z axis.
This is what unit quaternions do: they rotate a vector (here a set of vectors, your rotation matrix) around an axis specified by their imaginary vectorial part (x,y,z) by some angle function of the w scalar (w = cos(angle/2)).
If I understand what you are trying to do, that is rolling your camera as when tilting your head from left to right, then you should build a roll quaternion around your direction vector:
roll.setFromAxisAngle(new Vector4f(direction.x, direction.y, direction.z, rollAngle * DEGTORAD));
I'm assuming your direction vector is normalized, or that LWJGL knows what to do with a non-unitary axis vector when calling setFromAxisAngle.
来源:https://stackoverflow.com/questions/7772160/lwjgl-problems-implementing-roll-in-a-6dof-camera-using-quaternions-and-a-tr