How to make flying around in 3d Space work with Quaternions in OpenGL

Hello everyone!

I’ve been experimenting with rotation angles to make my 3d space “flying around” simulation work and everything I tried before ends up gimbal locking. I decided to tackle Quaternions and I supposedly finished coding my program in the way I inferred from learning about Quaternions online on a couple of sites, and I’m still getting frustrated with the results. I haven’t been able to find any “step by step” instructions on rotating with Quaternions that ended up working perfectly, or complete working sample code. If there is something wrong with the concept of what I’m doing, it would probably be easier for someone who got a Quaternion camera working to see what I’m missing. I would appreciate any help anyone can give me. All my float variables and arrays are all declared as GLfloat.

I start off with my first quaternion (qph1[4]: [0] is w, [1] is x, [2] is y, [3] is z) being w = 1.0, x = 0.0, y = 0.0, z = 0.0 and I reset the first quaternion to that for each iteration of my OpenGL rendering function. I have the three functions listed below which take the global variable angles calculated from keystroke input, convert it to radians, and then convert the values to a quaternion. Before creating the final quaternion rotation matrix (QUAT[16]), I multiply the quaternions (GLfloat[4] arrays) by taking the new one and multiplying it by the original one in the X rotation function. Then the Y rotation function gets called and continues multiplying the previous multiplication with the new one and same with the Z rotation function. I use the qph1, qph2 and qph3 arrays to juggle with until I end up with what I want back in qph1 to create the final matrix before starting all over again. I always take what was calculated and multiply that by the previous calculation to get the new result. Is it correct the way I’m doing it now? (i.e. B * A = BA; C * BA = CBA) Or am I supposed to go in order from left to right with the multiplication? (i.e. A * B = AB; AB * C = ABC)? Any help you guys can give me would be very much appreciated.


void Axis_Angle_X_Rotation_Determination(GLfloat pitch_in_degrees)
{
    qph2[0] = cosf(DegToRad(pitch_in_degrees / 2.0));
    qph2[1] = cam_right_vector_x * sinf(DegToRad(pitch_in_degrees / 2.0));
    qph2[2] = cam_right_vector_y * sinf(DegToRad(pitch_in_degrees / 2.0));
    qph2[3] = cam_right_vector_z * sinf(DegToRad(pitch_in_degrees / 2.0));
    
    MultiplyQuaternions(qph2, qph1, qph3);
}

void Axis_Angle_Y_Rotation_Determination(GLfloat yaw_in_degrees)
{
    qph1[0] = cosf(DegToRad(yaw_in_degrees / 2.0));
    qph1[1] = cam_up_vector_x * sinf(DegToRad(yaw_in_degrees / 2.0));
    qph1[2] = cam_up_vector_y * sinf(DegToRad(yaw_in_degrees / 2.0));
    qph1[3] = cam_up_vector_z * sinf(DegToRad(yaw_in_degrees / 2.0));
    
    MultiplyQuaternions(qph1, qph3, qph2);
}

void Axis_Angle_Z_Rotation_Determination(GLfloat roll_in_degrees)
{
    qph3[0] = cosf(DegToRad(roll_in_degrees / 2.0));
    qph3[1] = -cam_look_vector_x * sinf(DegToRad(roll_in_degrees / 2.0));
    qph3[2] = -cam_look_vector_y * sinf(DegToRad(roll_in_degrees / 2.0));
    qph3[3] = -cam_look_vector_z * sinf(DegToRad(roll_in_degrees / 2.0));

    MultiplyQuaternions(qph3, qph2, qph1);
}

void MultiplyQuaternions(GLfloat LQ[4], GLfloat OTQ[4], GLfloat NTQ[4])
{
    GLfloat magnitude = 0.0;

//  This is what my array code below is doing so that you don't have to analyze the mess too much.
//
//  new quaternion.w = (w1w2 - x1x2 - y1y2 - z1z2)
//  new quaternion.x = (w1x2 + x1w2 + y1z2 - z1y2)
//  new quaternion.y = (w1y2 - x1z2 + y1w2 + z1x2)
//  new quaternion.z = (w1z2 + x1y2 - y1x2 + z1w2)
    
    NTQ[0] = (LQ[0] * OTQ[0]) - (LQ[1] * OTQ[1]) - (LQ[2] * OTQ[2]) - (LQ[3] * OTQ[3]);
    NTQ[1] = (LQ[0] * OTQ[1]) + (LQ[1] * OTQ[0]) + (LQ[2] * OTQ[3]) - (LQ[3] * OTQ[2]);
    NTQ[2] = (LQ[0] * OTQ[2]) - (LQ[1] * OTQ[3]) + (LQ[2] * OTQ[0]) + (LQ[3] * OTQ[1]);
    NTQ[3] = (LQ[0] * OTQ[3]) + (LQ[1] * OTQ[2]) - (LQ[2] * OTQ[1]) + (LQ[3] * OTQ[0]);
    
    magnitude = sqrt((NTQ[0]*NTQ[0])+(NTQ[1]*NTQ[1])+(NTQ[2]*NTQ[2])+(NTQ[3]*NTQ[3]));
    
    NTQ[0] = NTQ[0] / magnitude;
    NTQ[1] = NTQ[1] / magnitude;
    NTQ[2] = NTQ[2] / magnitude;
    NTQ[3] = NTQ[3] / magnitude;
}

void AssembleRotationMatrix()
{
//  This is the matrix in column-major order
//
//  1-2y2-2z2               2xy-2wz         2xz+2wy         0
//  2xy+2wz                 1-2x2-2z2       2yz-2wx         0
//  2xz-2wy                 2yz+2wx         1-2x2-2y2       0
//  0                       0               0               1
//

    QUAT[0] = 1.0 - (2.0 * qph1[2] * qph1[2]) - (2.0 * qph1[3] * qph1[3]);
    QUAT[1] = (2.0 * qph1[1] * qph1[2]) + (2.0 * qph1[0] * qph1[3]);
    QUAT[2] = (2.0 * qph1[1] * qph1[3]) - (2.0 * qph1[0] * qph1[2]);
    QUAT[3] = 0.0;
    QUAT[4] = (2.0 * qph1[1] * qph1[2]) - (2.0 * qph1[0] * qph1[3]);
    QUAT[5] = 1.0 - (2.0 * qph1[1] * qph1[1]) - (2.0 * qph1[3] * qph1[3]);
    QUAT[6] = (2.0 * qph1[2] * qph1[3]) + (2.0 * qph1[0] * qph1[1]);
    QUAT[7] = 0.0;
    QUAT[8] = (2.0 * qph1[1] * qph1[3]) + (2.0 * qph1[0] * qph1[2]);
    QUAT[9] = (2.0 * qph1[2] * qph1[3]) - (2.0 * qph1[0] * qph1[1]);
    QUAT[10] = 1.0 - (2.0 * qph1[1] * qph1[1]) - (2.0 * qph1[2] * qph1[2]);
    QUAT[11] = 0.0;
    QUAT[12] = 0.0;
    QUAT[13] = 0.0;
    QUAT[14] = 0.0;
    QUAT[15] = 1.0;
}

void display()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glColor3ub(255, 255, 255);
    
    glPushMatrix();

    qph1[0] = 1.0;  qph1[1] = 0.0;  qph1[2] = 0.0;  qph1[3] = 0.0;
    
    Axis_Angle_X_Rotation_Determination(pitch_angle);
    Axis_Angle_Y_Rotation_Determination(yaw_angle);
    Axis_Angle_Z_Rotation_Determination(roll_angle);
    
    AssembleRotationMatrix();
    
    glMultMatrixf(QUAT);
    
    cam_right_vector_x = QUAT[0];
    cam_right_vector_y = QUAT[4];
    cam_right_vector_z = QUAT[8];
    cam_up_vector_x = QUAT[1];
    cam_up_vector_y = QUAT[5];
    cam_up_vector_z = QUAT[9];
    cam_look_vector_x = QUAT[2];
    cam_look_vector_y = QUAT[6];
    cam_look_vector_z = QUAT[10];
    
    move_dist_x = user_speed * (-cam_look_vector_x);
    move_dist_y = user_speed * (-cam_look_vector_y);
    move_dist_z = user_speed * (-cam_look_vector_z);
    
    spaceship_x = spaceship_x + move_dist_x;
    spaceship_y = spaceship_y + move_dist_y;
    spaceship_z = spaceship_z + move_dist_z;
    
    glTranslatef(-spaceship_x, -spaceship_y, -spaceship_z);
    
    // place some extra wire spheres around so that flying through space doesn't look completely empty
    glPushMatrix();
    glTranslatef(0.0, 0.0, -7500.0);
    glColor3ub(80, 150, 255);
    glutWireSphere(800.0f, 20, 20);  // big blue sphere positioned starting 7500 units in front of you
    glPopMatrix();
    
    glPushMatrix();
    glTranslatef(0.0, 0.0, 300.0);
    glColor3ub(255, 0, 0);
    glutWireSphere(200.0f, 20, 20);  // small red sphere positioned starting 300 units behind you
    glPopMatrix();
    
    glPopMatrix();

    glutSwapBuffers();
}