Hello everyone. I have been having trouble getting my code to work perfectly. I am using a first person space sim setup where I eventually want it to be possible to fly around like in Xwing or Tie Fighter without getting gimbal lock after a few minutes of rotating around.

The main frustration right now is with my wire sphere/spaceship that I want rotating and translating with my world view in first person when I fly around is not following me perfectly. I did a rotate matrix multiply to rotate the world view first and then I do the translation. Did I set up the world view in the right order, or do I have to translate first and then rotate for the world view?

I used an inverted matrix of the final world view after doing the rotation and the translation and I loaded that inverted matrix to position my sphere/spaceship that I'm trying to fly around in. I placed a couple of bigger spheres that don't rotate or move just to have them there as a frame of reference while I'm flying around.

I included the code in it's entirety and the comments have a lot of details and instructions. At the beginning, I included instructions for compiling on a console on the Mac OS X or Linux. It's setup currently for compiling on a Mac. If you are a Windows user, I trust you will be able to figure out what to do with it if you know OpenGL and programming.

Here is the code:

Code:// // SpaceTrip.cpp // #include <OpenGL/gl.h>// For Mac OS X. #include <GLUT/glut.h>// For Mac OS X. // // For Mac OS X, compile using: g++ -o SpaceTrip SpaceTrip.cpp -framework OpenGL - framework GLUT // // // For Linux: #include <GL/gl.h> // " #include <GL/glu.h> // " #include <GL/glut.h> // // Compile using: g++ -o SpaceTrip SpaceTrip.cpp -lglut -lGLU -lGL #include <cmath> #include <cstring> #include <cstdlib> #include <sstream> #include <iostream> usingnamespace std; // update rate (60 fps) int interval = 1000 / 60; GLfloat DegreesToRadians(GLfloat angle_in_degrees); void MultiplyTheMatrices(); bool InvertTheMatrix(); GLfloat move_dist_x = 0.0; GLfloat move_dist_y = 0.0; GLfloat move_dist_z = 0.0; // spaceship (user's view) coordinates GLfloat spaceship_x = 0.0; GLfloat spaceship_y = 0.0; GLfloat spaceship_z = 0.0; // rotation of user's view GLfloat yaw_angle = 0.0; GLfloat pitch_angle = 0.0; GLfloat roll_angle = 0.0; GLfloat user_speed = 0.0; GLfloat cam_right_vector_x = 0.0; GLfloat cam_right_vector_y = 0.0; GLfloat cam_right_vector_z = 0.0; GLfloat cam_up_vector_x = 0.0; GLfloat cam_up_vector_y = 0.0; GLfloat cam_up_vector_z = 0.0; GLfloat cam_look_vector_x = 0.0; GLfloat cam_look_vector_y = 0.0; GLfloat cam_look_vector_z = 0.0; GLfloat rot_x_mat[16]; // rotation x matrix with values set in MultiplyTheMatrices() function GLfloat rot_y_mat[16]; // rotation y matrix with values set in MultiplyTheMatrices() function GLfloat rot_z_mat[16]; // rotation z matrix with values set in MultiplyTheMatrices() function GLfloat temp_mat[16]; // first step in rotation matrix calculation by multiplying (rot_x_mat * rot_y_mat) GLfloat rot[16]; // final rotation matrix created by multiplying (temp_mat * rot_z_mat) GLfloat view_mat[16]; // array used to grab matrix values from GL_MODELVIEW_MATRIX after rotations for the world view are performed but before the translation of the world view is performed GLfloat m[16]; // array used to grab matrix values from GL_MODELVIEW_MATRIX after both the rotations for the world view and the translation of the world view are performed GLfloat inv[16]; // array used by the InvertTheMatrix() function GLfloat inv_mat[16]; // array used to store the inverted form of the world view matrix stored in m[16] so that the spaceship/wiresphere can be positioned at the world camera coordinate facing in the same direction as the camera view void init(void) { glClearColor(0.0, 0.0, 0.0, 0.0); glShadeModel(GL_FLAT); } void display() { // clear (has to be done at the beginning) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glColor3ub(255, 255, 255); // save the matrix before updating the position of the entire universe glPushMatrix(); MultiplyTheMatrices(); // take my pitch, yaw, and roll angles and create a rotation matrix: rot glMultMatrixf(rot); // take my rotation matrix and multiply it against the MODELVIEW matrix // copy the current values located in the MODELVIEW_MATRIX which includes the rotations glGetFloatv(GL_MODELVIEW_MATRIX, view_mat); cam_right_vector_x = view_mat[0]; cam_right_vector_y = view_mat[4]; cam_right_vector_z = view_mat[8]; cam_up_vector_x = view_mat[1]; cam_up_vector_y = view_mat[5]; cam_up_vector_z = view_mat[9]; cam_look_vector_x = view_mat[2]; cam_look_vector_y = view_mat[6]; cam_look_vector_z = view_mat[10]; // view coordinate update 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 the view at the coordinates I want by "moving the universe" glGetFloatv(GL_MODELVIEW_MATRIX, m); InvertTheMatrix(); // make an inverted matrix of the current MODELVIEW_MATRIX and store it in inv_mat. // place the spaceship that I'm sitting in at the exact center of my view glPushMatrix(); glLoadMatrixf(inv_mat); // apply the inverted matrix in order to position my spaceship/sphere at the correct view position and rotation as the world view - NOT WORKING PROPERLY, NEED HELP WITH THIS. glColor3ub(220, 220, 220); glutWireSphere(20.0f, 20, 20); glPopMatrix(); // 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(500.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(); } void keyboard(unsigned char key, int x, int y) { switch (key) { case 'l': // increase speed if(user_speed < 20.0) user_speed = user_speed + 1.0; break; case 'k': // decrease speed if(user_speed >= 1.0) user_speed = user_speed - 1.0; break; case '2': // pitch up pitch_angle = pitch_angle - 4.5; if (pitch_angle <= -360.0) pitch_angle = pitch_angle + 360.0; break; case '8': // pitch down pitch_angle = pitch_angle + 4.5; if (pitch_angle >= 360.0) pitch_angle = pitch_angle - 360.0; break; case '4': // yaw to the left yaw_angle = yaw_angle - 4.5; if (yaw_angle <= -360.0) yaw_angle = yaw_angle + 360.0; break; case '6': // yaw to the right yaw_angle = yaw_angle + 4.5; if (yaw_angle >= 360.0) yaw_angle = yaw_angle - 360.0; break; case '1': // pitch up and yaw to the left pitch_angle = pitch_angle - 4.5; if (pitch_angle <= -360.0) pitch_angle = pitch_angle + 360.0; yaw_angle = yaw_angle - 4.5; if (yaw_angle <= -360.0) yaw_angle = yaw_angle + 360.0; break; case '3': // pitch up and yaw to the right pitch_angle = pitch_angle - 4.5; if (pitch_angle <= -360.0) pitch_angle = pitch_angle + 360.0; yaw_angle = yaw_angle + 4.5; if (yaw_angle >= 360.0) yaw_angle = yaw_angle - 360.0; break; case '7': // pitch down and yaw to the left pitch_angle = pitch_angle + 4.5; if (pitch_angle >= 360.0) pitch_angle = pitch_angle - 360.0; yaw_angle = yaw_angle - 4.5; if (yaw_angle <= -360.0) yaw_angle = yaw_angle + 360.0; break; case '9': // pitch down and yaw to the right pitch_angle = pitch_angle + 4.5; if (pitch_angle >= 360.0) pitch_angle = pitch_angle - 360.0; yaw_angle = yaw_angle + 4.5; if (yaw_angle >= 360.0) yaw_angle = yaw_angle - 360.0; break; case ',': // roll to the left roll_angle = roll_angle - 4.5; if (roll_angle <= -360.0) roll_angle = roll_angle + 360.0; break; case '.': // roll to the right roll_angle = roll_angle + 4.5; if (roll_angle >= 360.0) roll_angle = roll_angle - 360.0; break; case 'b': exit(0); break; default: break; } } void update(int value) { glutTimerFunc(interval, update, 0); glutPostRedisplay(); } void reshape(int width, int height) { glViewport(0, 0, (GLsizei) width, (GLsizei) height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(60.0f, (GLfloat) width/(GLfloat) height, 1.0f, 1000000.0f); glMatrixMode (GL_MODELVIEW); glLoadIdentity(); } GLfloat DegreesToRadians(GLfloat angle_in_degrees) { GLfloat radians = 0.0; radians = ((angle_in_degrees * 3.141592653589793238462643383279) / 180.0); return radians; } void MultiplyTheMatrices() { rot_x_mat[0] = 1.0; rot_x_mat[1] = 0.0; rot_x_mat[2] = 0.0; rot_x_mat[3] = 0.0; rot_x_mat[4] = 0.0; rot_x_mat[5] = cosf(DegreesToRadians(pitch_angle)); rot_x_mat[6] = sinf(DegreesToRadians(pitch_angle)); rot_x_mat[7] = 0.0; rot_x_mat[8] = 0.0; rot_x_mat[9] = -(sinf(DegreesToRadians(pitch_angle))); rot_x_mat[10] = cosf(DegreesToRadians(pitch_angle)); rot_x_mat[11] = 0.0; rot_x_mat[12] = 0.0; rot_x_mat[13] = 0.0; rot_x_mat[14] = 0.0; rot_x_mat[15] = 1.0; rot_y_mat[0] = cosf(DegreesToRadians(yaw_angle)); rot_y_mat[1] = 0.0; rot_y_mat[2] = -(sinf(DegreesToRadians(yaw_angle))); rot_y_mat[3] = 0.0; rot_y_mat[4] = 0.0; rot_y_mat[5] = 1.0; rot_y_mat[6] = 0.0; rot_y_mat[7] = 0.0; rot_y_mat[8] = sinf(DegreesToRadians(yaw_angle)); rot_y_mat[9] = 0.0; rot_y_mat[10] = cosf(DegreesToRadians(yaw_angle)); rot_y_mat[11] = 0.0; rot_y_mat[12] = 0.0; rot_y_mat[13] = 0.0; rot_y_mat[14] = 0.0; rot_y_mat[15] = 1.0; rot_z_mat[0] = cosf(DegreesToRadians(roll_angle)); rot_z_mat[1] = -(sinf(DegreesToRadians(roll_angle))); rot_z_mat[2] = 0.0; rot_z_mat[3] = 0.0; rot_z_mat[4] = sinf(DegreesToRadians(roll_angle)); rot_z_mat[5] = cosf(DegreesToRadians(roll_angle)); rot_z_mat[6] = 0.0; rot_z_mat[7] = 0.0; rot_z_mat[8] = 0.0; rot_z_mat[9] = 0.0; rot_z_mat[10] = 1.0; rot_z_mat[11] = 0.0; rot_z_mat[12] = 0.0; rot_z_mat[13] = 0.0; rot_z_mat[14] = 0.0; rot_z_mat[15] = 1.0; temp_mat[0] = (rot_x_mat[0] * rot_y_mat[0]) + (rot_x_mat[4] * rot_y_mat[1]) + (rot_x_mat[8] * rot_y_mat[2]) + (rot_x_mat[12] * rot_y_mat[3]); temp_mat[1] = (rot_x_mat[0] * rot_y_mat[4]) + (rot_x_mat[4] * rot_y_mat[5]) + (rot_x_mat[8] * rot_y_mat[6]) + (rot_x_mat[12] * rot_y_mat[7]); temp_mat[2] = (rot_x_mat[0] * rot_y_mat[8]) + (rot_x_mat[4] * rot_y_mat[9]) + (rot_x_mat[8] * rot_y_mat[10]) + (rot_x_mat[12] * rot_y_mat[11]); temp_mat[3] = (rot_x_mat[0] * rot_y_mat[12]) + (rot_x_mat[4] * rot_y_mat[13]) + (rot_x_mat[8] * rot_y_mat[14]) + (rot_x_mat[12] * rot_y_mat[15]); temp_mat[4] = (rot_x_mat[1] * rot_y_mat[0]) + (rot_x_mat[5] * rot_y_mat[1]) + (rot_x_mat[9] * rot_y_mat[2]) + (rot_x_mat[13] * rot_y_mat[3]); temp_mat[5] = (rot_x_mat[1] * rot_y_mat[4]) + (rot_x_mat[5] * rot_y_mat[5]) + (rot_x_mat[9] * rot_y_mat[6]) + (rot_x_mat[13] * rot_y_mat[7]); temp_mat[6] = (rot_x_mat[1] * rot_y_mat[8]) + (rot_x_mat[5] * rot_y_mat[9]) + (rot_x_mat[9] * rot_y_mat[10]) + (rot_x_mat[13] * rot_y_mat[11]); temp_mat[7] = (rot_x_mat[1] * rot_y_mat[12]) + (rot_x_mat[5] * rot_y_mat[13]) + (rot_x_mat[9] * rot_y_mat[14]) + (rot_x_mat[13] * rot_y_mat[15]); temp_mat[8] = (rot_x_mat[2] * rot_y_mat[0]) + (rot_x_mat[6] * rot_y_mat[1]) + (rot_x_mat[10] * rot_y_mat[2]) + (rot_x_mat[14] * rot_y_mat[3]); temp_mat[9] = (rot_x_mat[2] * rot_y_mat[4]) + (rot_x_mat[6] * rot_y_mat[5]) + (rot_x_mat[10] * rot_y_mat[6]) + (rot_x_mat[14] * rot_y_mat[7]); temp_mat[10] = (rot_x_mat[2] * rot_y_mat[8]) + (rot_x_mat[6] * rot_y_mat[9]) + (rot_x_mat[10] * rot_y_mat[10]) + (rot_x_mat[14] * rot_y_mat[11]); temp_mat[11] = (rot_x_mat[2] * rot_y_mat[12]) + (rot_x_mat[6] * rot_y_mat[13]) + (rot_x_mat[10] * rot_y_mat[14]) + (rot_x_mat[14] * rot_y_mat[15]); temp_mat[12] = (rot_x_mat[3] * rot_y_mat[0]) + (rot_x_mat[7] * rot_y_mat[1]) + (rot_x_mat[11] * rot_y_mat[2]) + (rot_x_mat[15] * rot_y_mat[3]); temp_mat[13] = (rot_x_mat[3] * rot_y_mat[4]) + (rot_x_mat[7] * rot_y_mat[5]) + (rot_x_mat[11] * rot_y_mat[6]) + (rot_x_mat[15] * rot_y_mat[7]); temp_mat[14] = (rot_x_mat[3] * rot_y_mat[8]) + (rot_x_mat[7] * rot_y_mat[9]) + (rot_x_mat[11] * rot_y_mat[10]) + (rot_x_mat[15] * rot_y_mat[11]); temp_mat[15] = (rot_x_mat[3] * rot_y_mat[12]) + (rot_x_mat[7] * rot_y_mat[13]) + (rot_x_mat[11] * rot_y_mat[14]) + (rot_x_mat[15] * rot_y_mat[15]); rot[0] = (temp_mat[0] * rot_z_mat[0]) + (temp_mat[4] * rot_z_mat[1]) + (temp_mat[8] * rot_z_mat[2]) + (temp_mat[12] * rot_z_mat[3]); rot[1] = (temp_mat[0] * rot_z_mat[4]) + (temp_mat[4] * rot_z_mat[5]) + (temp_mat[8] * rot_z_mat[6]) + (temp_mat[12] * rot_z_mat[7]); rot[2] = (temp_mat[0] * rot_z_mat[8]) + (temp_mat[4] * rot_z_mat[9]) + (temp_mat[8] * rot_z_mat[10]) + (temp_mat[12] * rot_z_mat[11]); rot[3] = (temp_mat[0] * rot_z_mat[12]) + (temp_mat[4] * rot_z_mat[13]) + (temp_mat[8] * rot_z_mat[14]) + (temp_mat[12] * rot_z_mat[15]); rot[4] = (temp_mat[1] * rot_z_mat[0]) + (temp_mat[5] * rot_z_mat[1]) + (temp_mat[9] * rot_z_mat[2]) + (temp_mat[13] * rot_z_mat[3]); rot[5] = (temp_mat[1] * rot_z_mat[4]) + (temp_mat[5] * rot_z_mat[5]) + (temp_mat[9] * rot_z_mat[6]) + (temp_mat[13] * rot_z_mat[7]); rot[6] = (temp_mat[1] * rot_z_mat[8]) + (temp_mat[5] * rot_z_mat[9]) + (temp_mat[9] * rot_z_mat[10]) + (temp_mat[13] * rot_z_mat[11]); rot[7] = (temp_mat[1] * rot_z_mat[12]) + (temp_mat[5] * rot_z_mat[13]) + (temp_mat[9] * rot_z_mat[14]) + (temp_mat[13] * rot_z_mat[15]); rot[8] = (temp_mat[2] * rot_z_mat[0]) + (temp_mat[6] * rot_z_mat[1]) + (temp_mat[10] * rot_z_mat[2]) + (temp_mat[14] * rot_z_mat[3]); rot[9] = (temp_mat[2] * rot_z_mat[4]) + (temp_mat[6] * rot_z_mat[5]) + (temp_mat[10] * rot_z_mat[6]) + (temp_mat[14] * rot_z_mat[7]); rot[10] = (temp_mat[2] * rot_z_mat[8]) + (temp_mat[6] * rot_z_mat[9]) + (temp_mat[10] * rot_z_mat[10]) + (temp_mat[14] * rot_z_mat[11]); rot[11] = (temp_mat[2] * rot_z_mat[12]) + (temp_mat[6] * rot_z_mat[13]) + (temp_mat[10] * rot_z_mat[14]) + (temp_mat[14] * rot_z_mat[15]); rot[12] = (temp_mat[3] * rot_z_mat[0]) + (temp_mat[7] * rot_z_mat[1]) + (temp_mat[11] * rot_z_mat[2]) + (temp_mat[15] * rot_z_mat[3]); rot[13] = (temp_mat[3] * rot_z_mat[4]) + (temp_mat[7] * rot_z_mat[5]) + (temp_mat[11] * rot_z_mat[6]) + (temp_mat[15] * rot_z_mat[7]); rot[14] = (temp_mat[3] * rot_z_mat[8]) + (temp_mat[7] * rot_z_mat[9]) + (temp_mat[11] * rot_z_mat[10]) + (temp_mat[15] * rot_z_mat[11]); rot[15] = (temp_mat[3] * rot_z_mat[12]) + (temp_mat[7] * rot_z_mat[13]) + (temp_mat[11] * rot_z_mat[14]) + (temp_mat[15] * rot_z_mat[15]); } bool InvertTheMatrix() { GLfloat det; int i; inv[0] = m[5] * m[10] * m[15] - m[5] * m[11] * m[14] - m[9] * m[6] * m[15] + m[9] * m[7] * m[14] + m[13] * m[6] * m[11] - m[13] * m[7] * m[10]; inv[4] = -m[4] * m[10] * m[15] + m[4] * m[11] * m[14] + m[8] * m[6] * m[15] - m[8] * m[7] * m[14] - m[12] * m[6] * m[11] + m[12] * m[7] * m[10]; inv[8] = m[4] * m[9] * m[15] - m[4] * m[11] * m[13] - m[8] * m[5] * m[15] + m[8] * m[7] * m[13] + m[12] * m[5] * m[11] - m[12] * m[7] * m[9]; inv[12] = -m[4] * m[9] * m[14] + m[4] * m[10] * m[13] + m[8] * m[5] * m[14] - m[8] * m[6] * m[13] - m[12] * m[5] * m[10] + m[12] * m[6] * m[9]; inv[1] = -m[1] * m[10] * m[15] + m[1] * m[11] * m[14] + m[9] * m[2] * m[15] - m[9] * m[3] * m[14] - m[13] * m[2] * m[11] + m[13] * m[3] * m[10]; inv[5] = m[0] * m[10] * m[15] - m[0] * m[11] * m[14] - m[8] * m[2] * m[15] + m[8] * m[3] * m[14] + m[12] * m[2] * m[11] - m[12] * m[3] * m[10]; inv[9] = -m[0] * m[9] * m[15] + m[0] * m[11] * m[13] + m[8] * m[1] * m[15] - m[8] * m[3] * m[13] - m[12] * m[1] * m[11] + m[12] * m[3] * m[9]; inv[13] = m[0] * m[9] * m[14] - m[0] * m[10] * m[13] - m[8] * m[1] * m[14] + m[8] * m[2] * m[13] + m[12] * m[1] * m[10] - m[12] * m[2] * m[9]; inv[2] = m[1] * m[6] * m[15] - m[1] * m[7] * m[14] - m[5] * m[2] * m[15] + m[5] * m[3] * m[14] + m[13] * m[2] * m[7] - m[13] * m[3] * m[6]; inv[6] = -m[0] * m[6] * m[15] + m[0] * m[7] * m[14] + m[4] * m[2] * m[15] - m[4] * m[3] * m[14] - m[12] * m[2] * m[7] + m[12] * m[3] * m[6]; inv[10] = m[0] * m[5] * m[15] - m[0] * m[7] * m[13] - m[4] * m[1] * m[15] + m[4] * m[3] * m[13] + m[12] * m[1] * m[7] - m[12] * m[3] * m[5]; inv[14] = -m[0] * m[5] * m[14] + m[0] * m[6] * m[13] + m[4] * m[1] * m[14] - m[4] * m[2] * m[13] - m[12] * m[1] * m[6] + m[12] * m[2] * m[5]; inv[3] = -m[1] * m[6] * m[11] + m[1] * m[7] * m[10] + m[5] * m[2] * m[11] - m[5] * m[3] * m[10] - m[9] * m[2] * m[7] + m[9] * m[3] * m[6]; inv[7] = m[0] * m[6] * m[11] - m[0] * m[7] * m[10] - m[4] * m[2] * m[11] + m[4] * m[3] * m[10] + m[8] * m[2] * m[7] - m[8] * m[3] * m[6]; inv[11] = -m[0] * m[5] * m[11] + m[0] * m[7] * m[9] + m[4] * m[1] * m[11] - m[4] * m[3] * m[9] - m[8] * m[1] * m[7] + m[8] * m[3] * m[5]; inv[15] = m[0] * m[5] * m[10] - m[0] * m[6] * m[9] - m[4] * m[1] * m[10] + m[4] * m[2] * m[9] + m[8] * m[1] * m[6] - m[8] * m[2] * m[5]; det = m[0] * inv[0] + m[1] * inv[4] + m[2] * inv[8] + m[3] * inv[12]; if (det == 0) returnfalse; det = 1.0 / det; for (i = 0; i < 16; i++) inv_mat[i] = inv[i] * det; returntrue; } int main(int argc, char** argv) { glutInit(&argc, argv); glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH); glutInitWindowSize(1024, 768); glutInitWindowPosition(0, 0); glutCreateWindow("Space Trip"); init(); glutDisplayFunc(display); glutReshapeFunc(reshape); glutTimerFunc(interval, update, 0); glutKeyboardFunc(keyboard); glutMainLoop(); return 0; }

1) Am I multiplying the rotation matrices in the right order? I multiplied (X * Y) and placed it in temp_mat[16]. Then I multiplied (temp_mat * Z) to get the final rotation matrix: rot. I get the look vector and then use that to translate in the direction of the view produced from the rotation. Then I do the translation. I save that matrix, produce the inverted matrix inv_mat and load it into the MODELVIEW MATRIX to try to position my small sphere/ship. It over-rotates in the direction I try to rotate on an axis, and it translates quicker than the world camera view in first person does.

2) For producing the world view, was it correct for me to do the rotations first and then do the translation to set up the world view first? Or was I supposed to do a translation and then the rotations for setting up the view?

3) Is the inverted matrix for positioning my spaceship/sphere correct?

4) The axis-angle rotations I'm using aren't perfect and eventually gimbal lock. I heard that there is a better way to do it without having to learn Quaternions to avoid gimbal lock. Is there a better method that anyone can suggest than the one I am using? I probably will decide to go with Quaternions anyway. I imagine LucasArts would have used them back "in the day" when Xwing came out and then Tie Fighter, and then Xwing vs TieFighter.

Any help anyone can give me would be very much appreciated. Thank you.