5

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.

framauro13
  • 797
  • 2
  • 8
  • 18

2 Answers2

3

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.

gsimard
  • 643
  • 8
  • 24
  • Thanks for the update. I made this change and it is close, however I'm still getting some odd behavior. I made this change with the one suggested by RiverC, and explained the new problems in a comment there. I will update when I get home tonight with more details. Thanks again, I appreciate the help. – framauro13 Jun 26 '13 at 17:13
3

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.

Jon Clements
  • 138,671
  • 33
  • 247
  • 280
  • Thanks for the update. Haven't touched this code in a while, so I'm getting back up to speed with LWJGl. I think I am close. After making your changes and changes suggested by gsimard, I am getting closer. However, when yaw (bearing in the question) is at 0, pitch is fine. When yaw is at 180, pitch is reversed (pitch up moves the camera down instead). Also, rotation becomes sporadic after rotating about 20 degrees. If I keep rotating it levels out. So something is still not quite right. I'll put the code on github and update this question later tonight. I think it's almost there. Thanks again. – framauro13 Jun 26 '13 at 17:12
  • Why do you do stuff to the rotation matrix after creating it? You should be able to send a Mat4 directly to the Shader and use it to multiply the scene's vertices without modifying it. What I mean is, the '//set the direction' part of the code. It seems like you're trying to use that to offset the camera via the multiplication, but in my experience this has been hit and miss. Try dropping that. –  Jun 27 '13 at 15:53
  • I *think* the reason I did that initially was so that I could sum the direction vector with the eye vector to generate a vector to interact with objects in the world. A 'picking' vector more or less. Also, I fixed the issue with pitch flipping. I swapped the order of bearing and pitch, so the line reads `Quaternion.mul(Quaternion.mul(roll, bearing, change), pitch, change);` Still trying to figure out why roll gets wonky after rolling a certain amount. – framauro13 Jun 27 '13 at 15:58
  • Here is the full class on gist after doing some refactoring and making the changes suggested by you and gsimard: https://gist.github.com/bsjohnson/26cbd6d01583c4cbc871 – framauro13 Jun 27 '13 at 16:06
  • Also, the setting of m30 - m33 is to translate the position of the camera if the player has moved the character (forwards, backwards, strafing, etc...) If there is a better way of applying this to the rotation matrix I'm more than open to changing it. – framauro13 Jun 27 '13 at 16:28
  • I used to do this, but after my most recent code I couldn't get the offset to work and had to add it to the position before rotating the vertex (instead of having it happen automatically in the multiplication in the shader.) I think it's probably just that I'm doing the rotation matrix wrong somehow. In other words, in the shader it was `(modelProjectionMatrix*rotationMatrix*(vertex+offsets))` –  Jun 27 '13 at 20:43
  • I am marking this answer accepted as it ultimately solved the rolling problem (along with gsimard's answer, give him some upvotes as well). The "jittering" problem I'm seeing resembles gimble lock, but I wouldn't expect that using quaternions. I'm going to make a separate post with the source code to hopefully resolve that issue and have a functioning camera class. – framauro13 Aug 02 '13 at 13:31
  • I think the Gimbal lock has all to do with how you are translating movement of mouse and keyboard into rotations/movements. If you work through the math you'll discover that with the gimbal order (yaw,pitch,roll) you will need to do some trigonometry on either keyboard or mouse movements to get flight working right. –  Aug 22 '13 at 21:04