Thread: How to make a spaceship match rotation and translation with world view in OpenGL

  1. #1
    Registered User
    Join Date
    Dec 2015
    Posts
    11

    How to make a spaceship match rotation and translation with world view in OpenGL

    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.

  2. #2
    and the hat of int overfl Salem's Avatar
    Join Date
    Aug 2001
    Location
    The edge of the known universe
    Posts
    39,659
    > usingnamespace std;
    The first suggestion is when you paste code to the forum, you select the "paste as text" option. Otherwise, you end up with some randomly spaced cruft which other people will find hard to copy/paste to their own environment (so they won't bother).

    > 1) Am I multiplying the rotation matrices in the right order?
    It's hard to say - your matrix code is so awful to read. Where are the loops?

    I suggest you create a matrix class which encapsulates some functionality.
    Having done that, you create a 'main' which exercises your matrix class with some example calculations you know the answer to - to check it actually works.

    Throwing together a whole load of code, then wondering which aspect of it doesn't work - doesn't work.
    A development process

    Breaking the problem up into manageable and testable steps is vital for any non-trivial program.
    If you dance barefoot on the broken glass of undefined behaviour, you've got to expect the occasional cut.
    If at first you don't succeed, try writing your phone number on the exam paper.

  3. #3
    Registered User
    Join Date
    Dec 2015
    Posts
    11
    Quote Originally Posted by Salem View Post
    > usingnamespace std;
    The first suggestion is when you paste code to the forum, you select the "paste as text" option. Otherwise, you end up with some randomly spaced cruft which other people will find hard to copy/paste to their own environment (so they won't bother).
    I apologize for that. In my defense though, I opened up a brand new editor window in Xcode and I was able to copy and paste what I posted on here back into the file no problem. It's meant to be a straight one-file program that can be compiled at the console. If someone is using a different development environment where they have to copy and paste bits of code into different windows to make it work someplace else, I didn't mean to make their job difficult.

    > 1) Am I multiplying the rotation matrices in the right order?
    It's hard to say - your matrix code is so awful to read. Where are the loops?
    I wrote the code the way I did because I wanted it to be more readable then useful to reuse. I figured that while I was testing this stuff, I would just create a bunch of matrix arrays and go row by row explicitly to show what calculations are happening. Nested FOR loops and multiple dimension arrays use less lines of code in the final version of a program but they may be harder to decipher right away what exactly is happening when I'm still in the experimentation phase.
    Throwing together a whole load of code, then wondering which aspect of it doesn't work - doesn't work.
    There was a lot more thought and research involved than just throwing a bunch of code together. The majority of the program works. I was just having trouble figuring out why my avatar/spaceship wasn't matching my first person view perfectly.

    As far as which matrices to multiply in the right order, I figure an expert OpenGL programmer can tell me that without looking at any code. If it's a first-person game meant to fly around on all 3 axes in space and I want the object I'm flying in the centre of to match my view of the compass direction in the world, there shouldn't be too much of a difference in consensus as to which order the rotations and the translations go for the world view, and which order the inverse rotations and inverse translations go for my spaceship that is moving with me. There should be only one real conceptual way to do it. Whether I use glRotatef() and then glTranslate or vice versa for the world compass view of my first person view, and whether or not to use the inverse glRotatef before using the inverse glTranslate or vice versa for my vehicle should be a straightforward recipe to an experienced OpenGL programmer. Whether I use custom matrices to do the rotations or not is just code semantics. Yes I could experiment and find out myself. I'm in the process of doing that and I've been working on it for 3 or 4 days now. I figure I would just ask while I was still trying to figure it out in case an expert could lend me a quick hand.

    I didn't ask anyone to re-write my code for me. I'm just a beginner trying to learn the basic concepts of OpenGL. If you don't know what a first-person view is supposed to do with an avatar in a world view, then don't worry about it. I feel close to figuring it out. If someone else here wants to try out the code I generously provided and wants to take a crack at giving me some insight, I would appreciate it.

    Don't worry about the code. If you know the concept of what I'm asking about and you can explain it based on what I'm basically trying to accomplish, that's good enough.

    Thank you.

  4. #4
    misoturbutc Hodor's Avatar
    Join Date
    Nov 2013
    Posts
    1,787
    If you are looking at the world as if you were in the spaceship for rotating "the world" you'd translate to the origin, rotate, translate back (if what you are trying to do is show that the ship is rotating)

  5. #5
    Registered User
    Join Date
    Dec 2015
    Posts
    11
    Quote Originally Posted by Hodor View Post
    If you are looking at the world as if you were in the spaceship for rotating "the world" you'd translate to the origin, rotate, translate back (if what you are trying to do is show that the ship is rotating)
    It's all good. I figured it out.

    In terms of using the regular glRotatef() and glTranslatef commands, I have to do the following when I'm set-up up a first person view and then putting the object I'm in at the same coordinates and orientation as the first person view:

    Code:
    // set up the first person view
    glRotatef(pitch_angle, 1.0, 0.0, 0.0);
    glRotatef(yaw_angle, 0.0, 1.0, 0.0);
    glRotatef(roll_angle, 0.0, 0.0, 0.1);
    glTranslatef(-spaceship_x, -spaceship_y, -spaceship_z);
    
    // place the spaceship at my view coordinates rotated on the same axes as my view is
    glTranslatef(spaceship_x, spaceship_y, spaceship_z);
    glRotatef(-pitch_angle, 1.0, 0.0, 0.0);
    glRotatef(-yaw_angle, 0.0, 1.0, 0.0);
    glRotatef(-roll_angle, 0.0, 0.0, 0.1);
    Obviously, using three glRotatef commands like that isn't the proper solution as far as avoiding gimbal lock and working the way it's supposed to in every possible orientation. At least the movement and orientation of the spaceship and the user's view is consistent with each other now. Figuring out all the other stuff with better rotation matrices like the ones I've been experimenting with won't be too bad now.

    I was frustrated because I went around two different forums asking about the above order for first person view and orienting and placing a spaceship/avatar and nobody explained it properly to me. I guess if everyone else was confused because of the way I asked my questions, it was probably my fault.

  6. #6
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Don't be afraid of quaternions. They are your friend. They are also much simpler than their reputation; I blame snooty mathematicians.

    Wikipedia article on Quaternions and spatial rotation describes the rotation matrix
    c + x2 (1 - c) x y (1 - c) - z s x z (1 - c) + y s
    y x (1 - c) + z s c + y2 (1 - c) y z (1 - c) - x s
    z x (1 - c) - y s z y (1 - c) + x s c + z2 ( 1 - c)
    where
    c = cos(φ)
    s = sin(φ)
    x2 + y2 + z2 = 1
    for a rotation of angle φ around axis (x,y,z).

    We can always describe that same rotation -- that means, every possible rotation -- using a quaternion (w, i, j, k) using
    w = cos(φ/2)
    i = x sin(φ/2)
    j = y sin(φ/2)
    k = z sin(φ/2)

    These rotation quaternions are always of unit length. That is, its squared norm is
    w2 + i2 + j2 + k2 = 1
    You can always normalize a quaternion by dividing each component by the square root of its squared norm. (The zero quaternion, all components zero, corresponds to the rotation quaternion with w = 1, i=j=k=0, so even the divide-by-zero case is well defined for rotation quaternions. Nice!)

    Two consecutive rotations -- i.e., multiplying two rotation matrices -- is equivalent to the product of the rotation quaternions describing those rotations:
    w = w1 w2 - i1 i2 - j1 j2 - k1 k2
    i = w1 i2 + i1 w2 + j1 k2 - k1 j2
    j = w1 j2 - i1 k2 + j1 w2 + k1 i2
    k = w1 k2 + i1 j2 - j1 i2 + k1 w2

    Because we use floating-point numbers, consecutive operations introduces rounding errors, and the errors compound, so that after a few dozen operations, the rotation matrix is skewed, and the results deformed. Matrix normalization is not easy; it can be done, but as with Euler rotations, there is an order to the normalization and it can become a noticeable effect in some cases. However, normalization of quaternions is trivial, and that makes them so useful.

    Blending between two rotation matrices is quite hard. However, if you blend two quaternions (by multiplying the components of the source one by p = 0..1 , and the components of the target one by (1-p), and sum each component), and normalize the result, it works just fine; you "blend" from one rotation to another just as you'd expect!

    (The start and end of the blend can feel "jerky", if you just use a linear p from 0 to 1. You can make it smooth by using p = 3(t/T)2 - 2(t/T)3, where T is the duration of the blend, and t is the time during the blend; t/T = 0 .. 1.)

    You don't need to convert the quaternion to the axis-angle form to construct the matrix that corresponds to the rotation quaternion. The rotation matrix that corresponds to the rotation quaternion is
    1 - 2 j2 - 2 k2 2 (i j - k w) 2 (i k + j w)
    2 (i j + k w) 1 - 2 i2 - 2 k2 2 (j k - i w)
    2 (i k - j w) 2 (j k + i w) 1 - 2 i2 - 2 j2

    In general, it is more efficient to use the rotation quaternion to construct the rotation matrix, and calculate the rotated coordinates using the matrix, instead of rotating the coordinates using the quaternion directly.

    In your case, you should use one rotation quaternion to describe the current orientation (rotation from original coordinate frame) of your spaceship. Each "vernier" -- attitude adjustment rocket -- you have, applies another quaternion to it (multiplies the current orientation), and you can scale that by a scalar (0 to 1) if your controls are analog, i.e. not just "rotate that way", but "rotate that way really slowly". Just remember to normalize the current orientation rotation quaternion after it is modified.

  7. #7
    Registered User
    Join Date
    Dec 2015
    Posts
    11
    Quote Originally Posted by Nominal Animal View Post
    Don't be afraid of quaternions. They are your friend. They are also much simpler than their reputation; I blame snooty mathematicians.
    I appreciate the info. Thank you for that. I actually watched a lecture by a teacher/professor online on YouTube where he explains the concept to some extent but not to the point of how to code it for a 3D sim. I was impressed to find out that Quaternions are used for the technology to know where to store data on a circular disk. I didn't know I was benefitting from Quaternions in secret all these years.

    Another lecture he gave, he teaches how to do an axis-angle rotation along an arbitrary axis. I watched that one a couple of times and actually took notes. He doesn't draw out all the matrices but I know how to construct a translation and a rotation matrix for X, Y and Z already so I wasn't worried about that.

    The part he teaches that I needed to know was how to take a point (which could be a look, up or right vector even though he doesn't mention them directly) and create a vector from the origin to that point to determine the axis. Then he shows how to use trig to figure out what the angles are that you have to make off of the axes to get to that point to do the rotation that you need to make on that axis/vector.

    It's supposedly the next best thing to using Quaternions but it is still susceptible to gimbal lock, which is why NASA had to end up using Quaternions. I would like to try it out first and if the gimbal locking happens still despite being careful, I will have to turn to Quaternions for sure.

    I'm pretty sure LucasArts used Quaternions for their X-wing game in the 90s (an old DOS game). I put that game to the test that even the programmers weren't probably expecting. One mission was to protect bigger rebel ships (Y-wings and B-wings probably) while flying around in an A-wing to protect them from tie fighters while the bigger rebels ships launched missiles at the star destroyer. The y-wings and b-wings were all killed off, no more tie fighters were launching out from the star destroyer because I mopped them all off, and there was just the star destroyer and my A-wing left. Instead of pulling the plug on the mission, I kept darting in and out for almost an hour firing lasers at the shield towers until the shields were down. Then I kept flying in and out to shoot at the star destroyers hull while evading all the fire coming from the destroyer. Eventually I knocked the hull down to 0% and won the mission. If gimbal lock was going to happen after flying around for that long, it would have happened if they didn't use Quaternions or some other guaranteed rotation mathematics.

  8. #8
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by Progger View Post
    Another lecture he gave, he teaches how to do an axis-angle rotation along an arbitrary axis.
    That would be the first rotation matrix in my post #6 above. It's also shown in the Wikipedia article on Rotation matrix (from axis and angle).

    Quote Originally Posted by Progger View Post
    I know how to construct a translation and a rotation matrix for X, Y and Z already so I wasn't worried about that.
    Oh, you don't actually construct the axis-angle rotation matrix using Euler rotations at all; the derivation is different.

    (The Wikipedia article on trilateration shows why you shouldn't rely on Euler angles for real-world computation. It gets messy, fast. There is often a much simpler solution using vector algebra -- as you can see if you compare an old version and the current one.)

    Quote Originally Posted by Progger View Post
    The part he teaches that I needed to know was how to take a point (which could be a look, up or right vector even though he doesn't mention them directly) and create a vector from the origin to that point to determine the axis.
    Huh? It's actually much simpler than that.

    Let's assume you want a translation and a rotation matrix, which moves the camera/eye (origin) to point o, with Z axis pointing towards point t, and Y axis (camera "up") towards u.

    First, the translation is obviously o.

    Next, we use the relative vectors (t-o) and (u-o) to form the new basis vectors. The key point is to make sure they're perpendicular to each other and normalized to unit length, v below being just a temporary vector:
    z = (t-o) / ||t-o||
    v = (u-o) - zˇ(u-o)
    y = v / ||v||
    x = y×z
    As you can see, this construction does have "order" in the same sense as the Euler rotations do. Here, the "forward" direction (positive z axis) is most important, so it's defined first.

    Because we have an up vector u, we normalize that next. It does not need to be perpendicular (that is, the angle tou does not need to be 90 degrees), as we can make it perpendicular by subtracting the dot product with the already defined Z axis. (In mathematical terms, it suffices that u is on the YZ plane, excluding the Z axis, in the rotated coordinate system. In practice, you should make sure v is not too short.)

    When we have z and y at unit length and perpendicular to each other, we can always get x by getting the cross product of z and y. (The order of those depends on whether you use a left-handed or right-handed coordinate system; that is, if "Z" is in and "Y" is up, whether "X" is to left or to right.)

    Finally, the rotation matrix is then just the transpose of the standard one:
    xx yx zx
    xy yy zy
    xz yz zz
    (Because the vectors are orthonormal -- that is, orthogonal to each other (dot products between any pair is zero) and unit normal (each vector has length 1) --, the transpose is also orthonormal.)

    If you want, you can convert that matrix (or any pure rotation matrix) to a quaternion, although once again you must be careful. (There is a pretty concise description of the issues here.)

    Again, for smooth camera/eye transitions, do not rely on moving the points t and u relative to o -- unless, of course, t is an object you are tracking. In general transitions, you get much better results if you blend the rotation quaternions, and optionally use the smooth "phase" (that makes the rotation start and stop smoothly, changing fastest at the middle) I showed in the middle part of my post #6 above.

  9. #9
    Registered User
    Join Date
    Dec 2015
    Posts
    11
    Quote Originally Posted by Nominal Animal View Post
    That would be the first rotation matrix in my post #6 above. It's also shown in the Wikipedia article on Rotation matrix (from axis and angle).

    I ended up giving up on the axis-angle rotation on an arbitrary axis and decided to go for the gold and try out the Quaternions.

    The bad news is that it isn't rotating properly yet. The good news is that at least it's consistent. When I have to do a roll and rotate on the look vector (i have to negate the look vector in OpenGL to get the forward view), the wire sphere in front of me (one of the planets) pulsates and squishes and unsquishes closer to me and away from me in a predictable and consistent pattern. When I pitch on the right vector, it does the same thing but pitching around a 360 on the vertical. When I yaw on the up vector, it does the same thing except horizontally. I did a cout << sort((w*w) + (x*x) + (y*y) + (z*z)) to make sure the value is 1 which it is and after maybe 30 or 40 cycles, it slowly degrades by like 0.00001 per 3 or 4 dozen cycles without normalizing it.

    I feel really close to getting this right but it's frustrating.

    The general logic I'm doing is this:

    1) Set up the quaternion to w = 1.0, x = 0.0, y = 0.0, z = 0.0;

    2)
    Code:
    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);
    }
    For the multiplication order, I take the new quaternion that is calculated and I multiply that on the left-hand side of the equation with the current total quaternion to get the new total quaternion. So the first quaternion with the w = 1.0 and the x, y, z as 0.0 is treated as Q2 and the newly calculated quaternion (with the current angles calculated) is treated as Q1 and then I do Q1 * Q2 = Q3 in that order. Is that correct?

    3) I assembly the quaternion matrix and multiply it with glMultMatrix(QUAT);
    Code:
        QUAT[0] = 1.0 - (2.0 * qph1[2] * 2.0) - (2.0 * qph1[3] * 2.0);
        QUAT[4] = (2.0 * qph1[1] * qph1[2]) - (2.0 * qph1[0] * qph1[3]);
        QUAT[8] = (2.0 * qph1[1] * qph1[3]) + (2.0 * qph1[0] * qph1[2]);
        QUAT[12] = 0.0;
        QUAT[1] = (2.0 * qph1[1] * qph1[2]) + (2.0 * qph1[0] * qph1[3]);
        QUAT[5] = 1.0 - (2.0 * qph1[1] * 2.0) - (2.0 * qph1[3] * 2.0);
        QUAT[9] = (2.0 * qph1[2] * qph1[3]) + (2.0 * qph1[0] * qph1[1]);
        QUAT[13] = 0.0;
        QUAT[2] = (2.0 * qph1[1] * qph1[3]) - (2.0 * qph1[0] * qph1[2]);
        QUAT[6] = (2.0 * qph1[2] * qph1[3]) - (2.0 * qph1[0] * qph1[1]);
        QUAT[10] = 1.0 - (2.0 * qph1[1] * 2.0) - (2.0 * qph1[2] * 2.0);
        QUAT[14] = 0.0;
        QUAT[3] = 0.0;
        QUAT[7] = 0.0;
        QUAT[11] = 0.0;
        QUAT[15] = 1.0;
    qph1[0] is w. qph1[1] is x. qph1[2] is y. qph1[3] is z.


    I have no idea what is happening. I wish these websites that taught how to do Quaternions for 3d rotations in OpenGL would give a more "step by step" instruction about how to make it work properly.

    I know my code doesn't look pretty but as far as the general concept on getting from quaternion to the final matrix to multiply against the MODELVIEW matrix, is there something I missed?

  10. #10
    Registered User
    Join Date
    Dec 2015
    Posts
    11
    It still doesn't work 100% properly but I corrected some equations that I incorrectly put in the matrix for the quaternion calculations. I had some 2 * (variable) * 2 expressions rather than using 2 * (variable)squared like it should have been. That corrected most of the wonkiness but it still doesn't quite work.

    If I roll, pitch and yaw while in the original orientation, it works but its a little jerky for what I was expecting from Quaternions. As soon as I starting rotating on more than one axis, it slowly degrades to complete wonkiness.

    This is frustrating.

  11. #11
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    I can't find my old rotation code (because I don't remember when I last fiddled with rotations), so I'll have to rewrite and test it from scratch. (It's nontrivial, and because this is not a priority for me, it'll take some time.) I'll also need to go back to the OpenGL specs, to see how the internal coordinate systems and especially the matrices are defined (multiplied on the left or on the right, row- or column-major in memory, and so on).

    In the mean time, keep us posted on your progress.

  12. #12
    Programming Wraith GReaper's Avatar
    Join Date
    Apr 2009
    Location
    Greece
    Posts
    2,738
    If you're doing the matrix manipulations by hand in order to learn, more power to you. But in any other case, consider using a library such as GLM.
    Devoted my life to programming...

  13. #13
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    GReaper, I disagree. (Well, maybe not re GLM, as it is not a "library" per se, just a header file. GLM is useful and a good suggestion; it's the "a library" in general that I disagree with.)

    OpenGL does most of the work anyway; you only really need one function to multiply two quaternions and normalize the result, and one function to convert a rotation quaternion and a translation before rotation to a 4x4 transformation matrix. And optionally another that converts the same rotationto the inverse, with the inverse translation done after the rotation. Fortunately, inverting the non-scalar components of a rotation quaternion inverts the rotation.

    Also, it might be better to talk explicitly about versors rather than general quaternions. Versors are those quaternions that have length one, and represent rotations.

    Here is my extremely crude C example program I wrote from scratch. It is ugly and crude, but hopefully it helps.

    First, here is the Mac/Linux Makefile you can use to make the source on Mac or Linux. It autodetects the OS automatically, but I haven't tested it on Mac. Note that indentation at the start of the line must use tabs, not spaces; this is a Make quirk.
    Code:
    OS              := $(shell uname -s | tr 'a-z' 'A-Z' | tr -cd '0-9A-Z')
    CC              := gcc
    CFLAGS          := -Wall -O2
    LDFLAGS         := -lm
    PROGS           := example
    
    LINUX_CFLAGS    :=
    LINUX_LDFLAGS   := -lGL -lGLU -lglut 
    
    DARWIN_CFLAGS   :=
    DARWIN_LD       := -framework OpenGL -framework GLUT
    
    .PHONY: all
    all: clean $(PROGS)
    
    .PHONY: clean
    clean:
    	rm -f $(PROGS)
    
    example: example.c
    	$(CC) $(CFLAGS) $($(OS)_CFLAGS) $^ $(LDFLAGS) $($(OS)_LDFLAGS) -o $@
    And here is the example.c itself. Again, this too should work without changes on Mac and Linux, but I haven't checked it on Mac.
    Code:
    #define  _POSIX_C_SOURCE 200809L
    #include <stdlib.h>
    #include <stdio.h>
    #include <math.h>
    #include <time.h>
    
    #if defined(__APPLE__) && defined(__MACH__)
    #include <OpenGL/gl.h>
    #include <GLUT/glut.h>
    #elif defined(__linux__)
    #include <GL/gl.h>
    #include <GL/glu.h>
    #include <GL/glut.h>
    #else
    #error Unsupported operating system.
    #endif
    
    #define UNUSED __attribute__((unused))
    
    typedef struct {
        GLdouble    x;  /* i component */
        GLdouble    y;  /* j component */
        GLdouble    z;  /* k component */
        GLdouble    w;  /* Scalar component */
    } rotationq;
    
    typedef struct {
        GLdouble    x;  /* Rotation axis x component */
        GLdouble    y;  /* Rotation axis y component */
        GLdouble    z;  /* Rotation axis z component */
        GLdouble    r;  /* Radians per half a second */
    } vernier;
    
    typedef enum {
        CONTROL_WORLD = 0,
        CONTROL_PITCH_POS,
        CONTROL_PITCH_NEG,
        CONTROL_ROLL_POS,
        CONTROL_ROLL_NEG,
        CONTROL_YAW_POS,
        CONTROL_YAW_NEG,
        CONTROL_THRUST,
        CONTROL_COUNT
    } control;
    
    /* Keyboard controls */
    static unsigned char  control_state[CONTROL_COUNT] = { 0U };
    static unsigned char  control_keyboard[CONTROL_COUNT] = { 0U };
    static int            control_special[CONTROL_COUNT] = { 0U };
    
    /* Ship properties */
    static GLdouble       ship_location[3] = { 100.0, 100.0, 0.0 };
    static GLdouble       ship_matrix[16] = { 0 };
    static GLdouble       ship_inverse[16] = { 0 };
    static rotationq      ship_orientation = { 0.0, 0.0, 0.0, 1.0 };
    static vernier        ship_vernier[CONTROL_COUNT] = {{0}};
    static GLdouble       ship_thrust = 100.0; /* Coordinate units per second */
    
    static void define_matrix(GLdouble *const matrix, const rotationq *const rq, const GLdouble *const center)
    {
        const GLdouble origin[3] = { center[0], center[1], center[2] };
        /* Column-major order:
         *      matrix[0] matrix[4] matrix[8]  matrix[12]
         *      matrix[1] matrix[5] matrix[9]  matrix[13]
         *      matrix[2] matrix[6] matrix[10] matrix[14]
         *      matrix[3] matrix[7] matrix[11] matrix[15]
        */
        matrix[0]  = 1.0 - 2.0 * (rq->y * rq->y + rq->z * rq->z);
        matrix[1]  =       2.0 * (rq->x * rq->y + rq->z * rq->w);
        matrix[2]  =       2.0 * (rq->x * rq->z - rq->y * rq->w);
        matrix[3]  = 0.0;
        matrix[4]  =       2.0 * (rq->x * rq->y - rq->z * rq->w);
        matrix[5]  = 1.0 - 2.0 * (rq->x * rq->x + rq->z * rq->z);
        matrix[6]  =       2.0 * (rq->y * rq->z + rq->x * rq->w);
        matrix[7]  = 0.0;
        matrix[8]  =       2.0 * (rq->x * rq->z + rq->y * rq->w);
        matrix[9]  =       2.0 * (rq->y * rq->z - rq->x * rq->w);
        matrix[10] = 1.0 - 2.0 * (rq->x * rq->x + rq->y * rq->y);
        matrix[11] = 0.0;
        matrix[12] = -(matrix[0] * origin[0] + matrix[4] * origin[1] + matrix[8] * origin[2]);
        matrix[13] = -(matrix[1] * origin[0] + matrix[5] * origin[1] + matrix[9] * origin[2]);
        matrix[14] = -(matrix[2] * origin[0] + matrix[6] * origin[1] + matrix[10]* origin[2]);
        matrix[15] = 1.0;
    }
    
    static void define_inverse(GLdouble *const matrix, const rotationq *const rq, const GLdouble *const center)
    {
        /* Inverting the non-scalar components in the rotation quaternion inverts the rotation.
         * This also moves the origin to the center. Thus, it is the opposite of define_matrix().
        */
        matrix[0]  = 1.0 - 2.0 * (rq->y * rq->y + rq->z * rq->z);
        matrix[1]  =       2.0 * (rq->x * rq->y - rq->z * rq->w);
        matrix[2]  =       2.0 * (rq->x * rq->z + rq->y * rq->w);
        matrix[3]  = 0.0;
        matrix[4]  =       2.0 * (rq->x * rq->y + rq->z * rq->w);
        matrix[5]  = 1.0 - 2.0 * (rq->x * rq->x + rq->z * rq->z);
        matrix[6]  =       2.0 * (rq->y * rq->z - rq->x * rq->w);
        matrix[7]  = 0.0;
        matrix[8]  =       2.0 * (rq->x * rq->z - rq->y * rq->w);
        matrix[9]  =       2.0 * (rq->y * rq->z + rq->x * rq->w);
        matrix[10] = 1.0 - 2.0 * (rq->x * rq->x + rq->y * rq->y);
        matrix[11] = 0.0;
        matrix[12] = center[0];
        matrix[13] = center[1];
        matrix[14] = center[2];
        matrix[15] = 1.0;
    }
    
    static void apply_vernier(rotationq *const rq, const vernier *const v, const GLdouble duration)
    {
        const double vn = sqrt(v->x * v->x + v->y*v->y + v->z*v->z);
        if (vn > 0.0) {
            const GLdouble  s = sin(v->r * duration) / vn;
            const GLdouble  c = cos(v->r * duration);
            const rotationq r1 = { s * v->x, s * v->y, s * v->z, c };
            const rotationq r2 = *rq;
            const rotationq r = { r1.w*r2.x + r1.x*r2.w + r1.y*r2.z - r1.z*r2.y,
                                  r1.w*r2.y - r1.x*r2.z + r1.y*r2.w + r1.z*r2.x,
                                  r1.w*r2.z + r1.x*r2.y - r1.y*r2.x + r1.z*r2.w,
                                  r1.w*r2.w - r1.x*r2.x - r1.y*r2.y - r1.z*r2.z };
            const double rn = sqrt(r.x*r.x + r.y*r.y + r.z*r.z + r.w*r.w);
            if (rn > 0.0) {
                rq->x = r.x / rn;
                rq->y = r.y / rn;
                rq->z = r.z / rn;
                rq->w = r.w / rn;
            } else {
                rq->x = 0.0;
                rq->y = 0.0;
                rq->z = 0.0;
                rq->w = 1.0;
            }
        }
    }
    
    static void display_window(void)
    {
        const int world_mode = control_state[CONTROL_WORLD];
        static struct timespec prev_update = { 0 };
        static struct timespec curr_update = { 0 };
        double                 duration; /* Frame duration in seconds */
    
        prev_update = curr_update;
    #ifdef CLOCK_MONOTONIC
        clock_gettime(CLOCK_MONOTONIC, &curr_update);
    #else
        clock_gettime(CLOCK_REALTIME, &curr_update);
    #endif
        duration = (double)(curr_update.tv_sec - prev_update.tv_sec)
                 + (double)(curr_update.tv_nsec - prev_update.tv_nsec) / 1000000000.0;
        if (duration < 0.0 || duration > 1.0)
            duration = 0.0;
    
        /* Inertialess drive. Eh. */
        if (duration > 0.0) {
            size_t i = CONTROL_COUNT;
            while (i-->0)
                if (control_state[i])
                    apply_vernier(&ship_orientation, ship_vernier + i, duration);
            if (control_state[CONTROL_THRUST]) {
                const double effect = duration * ship_thrust;
                ship_location[0] -= effect * ship_matrix[2];
                ship_location[1] -= effect * ship_matrix[6];
                ship_location[2] -= effect * ship_matrix[10];
            }
        }
    
        define_matrix(ship_matrix, &ship_orientation, ship_location);
        define_inverse(ship_inverse, &ship_orientation, ship_location);
    
        if (world_mode) {
            glClearColor(0.0, 0.0, 0.125, 0.0);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
        } else {
            glClearColor(0.0, 0.0, 0.0, 0.0);
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            glMatrixMode(GL_MODELVIEW);
            glLoadMatrixd(ship_matrix);
        }
    
        /*
         * Draw the space stuff
        */
    
        glPushMatrix();
        glTranslatef(-100.0f, -200.0f, -1000.0f);
        glColor3f(0.5f, 0.5f, 0.5f);
        glutWireSphere(400.0, 16, 16);
        glPopMatrix();
    
        /*
         * Add the ship locating mark if in world mode.
        */
        if (world_mode) {
            glLoadIdentity();
            glLoadMatrixd(ship_inverse);
            glBegin(GL_LINES);
            glColor3f(   1.0f,   0.0f,  0.0f);
            glVertex3f(  0.0f,   0.0f,  0.0f);
            glVertex3f(  0.0f,   0.0f, 50.0f);
            glColor3f(   0.0f,   1.0f,  0.0f);
            glVertex3f(  0.0f, -25.0f,  0.0f);
            glVertex3f(  0.0f,  50.0f,  0.0f);
            glColor3f(   0.0f,   0.0f,  1.0f);
            glVertex3f(-25.0f,   0.0f,  0.0f);
            glVertex3f( 50.0f,   0.0f,  0.0f);
            glColor3f(   1.0f,   1.0f,  1.0f);
            glVertex3f(  0.0f,   0.0f,  0.0f);
            glVertex3f(  0.0f,   0.0f,-50.0f);
            glEnd();
        }        
    
        glFinish();
        glutSwapBuffers();
        glutPostRedisplay();
    }
    
    static void reshape_window(int width, int height)
    {
        glViewport(0, 0, width, height);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        gluPerspective(60.0f, (GLdouble)width / (GLdouble)height, 1.0, 1048576.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        glEnable(GL_DEPTH_TEST);
        glDepthFunc(GL_LESS);
        glShadeModel(GL_FLAT);
    }
    
    static void special_down(int key, int x UNUSED, int y UNUSED)
    {
        if (key) {
            size_t i = CONTROL_COUNT;
            while (i-->0)
                if (key == control_special[i])
                    control_state[i] = 1;
        }            
    }
    
    static void special_up(int key, int x UNUSED, int y UNUSED)
    {
        if (key) {
            size_t i = CONTROL_COUNT;
            while (i-->0)
                if (key == control_special[i])
                    control_state[i] = 0;
        }
    }
    
    static void keyboard_down(unsigned char key, int x UNUSED, int y UNUSED)
    {
        if (key) {
            size_t i = CONTROL_COUNT;
            while (i-->0)
                if (key == control_keyboard[i])
                    control_state[i] = 1;
        }
    }
    
    static void keyboard_up(unsigned char key, int x UNUSED, int y UNUSED)
    {
        if (key) {
            size_t i = CONTROL_COUNT;
            while (i-->0)
                if (key == control_keyboard[i])
                    control_state[i] = 0;
        }
    }
    
    static void define_vernier(vernier *const v,
                               const double x, const double y, const double z,
                               const double degrees_per_second)
    {
        v->x = x;
        v->y = y;
        v->z = z;
        v->r = degrees_per_second * 3.14159265358979323846 / 360.0;
    }
    
    
    int main(int argc, char *argv[])
    {
        glutInit(&argc, argv);
        glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
        glutInitWindowSize(768, 384);
        glutInitWindowPosition(128, 128);
        glutCreateWindow("Example");
    
        glutDisplayFunc(display_window);
        glutReshapeFunc(reshape_window);
    
        glutSpecialFunc(special_down);
        glutSpecialUpFunc(special_up);
        glutKeyboardFunc(keyboard_down);
        glutKeyboardUpFunc(keyboard_up);
        glutIgnoreKeyRepeat(1);
    
        control_special[CONTROL_PITCH_POS] = GLUT_KEY_UP;
        control_special[CONTROL_PITCH_NEG] = GLUT_KEY_DOWN;
        control_special[CONTROL_YAW_POS] = GLUT_KEY_RIGHT;
        control_special[CONTROL_YAW_NEG] = GLUT_KEY_LEFT;
        control_keyboard[CONTROL_ROLL_POS] = 'z';
        control_keyboard[CONTROL_ROLL_NEG] = 'x';
        control_keyboard[CONTROL_THRUST] = ' ';
        control_keyboard[CONTROL_WORLD] = '\r';
    
        fprintf(stdout, "Controls:\n");
        fprintf(stdout, "\tUp, Down    - Pitch\n");
        fprintf(stdout, "\tLeft, Right - Yaw\n");
        fprintf(stdout, "\tZ, X        - Roll\n");
        fprintf(stdout, "\tSpace       - Thrust\n");
        fprintf(stdout, "\tEnter       - World view (blue)\n");
        fflush(stdout);
    
        define_vernier(ship_vernier + CONTROL_PITCH_POS, 1.0, 0.0, 0.0,  +90.0);
        define_vernier(ship_vernier + CONTROL_PITCH_NEG, 1.0, 0.0, 0.0,  -90.0);
        define_vernier(ship_vernier + CONTROL_YAW_POS,   0.0, 1.0, 0.0,  +90.0);
        define_vernier(ship_vernier + CONTROL_YAW_NEG,   0.0, 1.0, 0.0,  -90.0);
        define_vernier(ship_vernier + CONTROL_ROLL_POS,  0.0, 0.0, 1.0,  -90.0);
        define_vernier(ship_vernier + CONTROL_ROLL_NEG,  0.0, 0.0, 1.0,  +90.0);
        define_vernier(ship_vernier + CONTROL_THRUST,    0.0, 0.0, 0.0,    0.0);
        define_vernier(ship_vernier + CONTROL_WORLD,     0.0, 0.0, 0.0,    0.0);
    
        glutMainLoop();
    
        return EXIT_SUCCESS;
    }
    This one uses inertialess drive, and automatic verniers. That is, the ship will only move forward if you press Space, and rotate while you press Up, Down, Left, Right, Z, or X, for pitch, yaw, and roll, respectively.

    If you press Enter, you switch to the fixed World view, where the ship orientation is shown using a small cross. (White line points forward, blue right, and green up).

    I don't use the GLUT timer; I just invalidate the window after every buffer swap, and use a CLOCK_MONOTONIC (or CLOCK_REALTIME if a monotonic clock is not available) to measure the elapsed time, and use that to calculate the next time step.

    The only gotcha I really encountered was that OpenGL assumes column-major order for the transformation matrix, whereas C uses row-major order for 2D arrays. It's almost a transpose, except for the translation part of the transformation matrix.

    As far as I can (quickly) test, this one seems to work fine. There is no gimbal lock, as the "verniers" (rotations) are applied to the quaternion describing the current ship orientation via quaternion multiplication.

    Any questions? Comments (other than how ugly the code is)?

  14. #14
    Programming Wraith GReaper's Avatar
    Join Date
    Apr 2009
    Location
    Greece
    Posts
    2,738
    Quote Originally Posted by Nominal Animal View Post
    GReaper, I disagree. (Well, maybe not re GLM, as it is not a "library" per se, just a header file. GLM is useful and a good suggestion; it's the "a library" in general that I disagree with.)
    Why? I mean, considering that sooner or later they'll have to let go of the static pipeline, GLM will be of great use for matrix manipulations. Are you concerned in terms of efficiency?
    Devoted my life to programming...

  15. #15
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    TL;DR: A dedicated OpenGL math library like GLM = good, but don't bother with generic math libraries or other full-blown vector/quaternion/matrix math libraries.

    Details:

    I meant that a general quaternion, vector, and matrix math library is not of much use with 3D graphics. The main thing with OpenGL is to understand how it defines the various transforms it uses; there isn't that much math involved at all, just a handful of operations. Using a heavy library for it just obfuscates the details and takes you further away from the underlying model, due to all the interface glue you need to add.

    I have something similar to GLM for my own use, except in C99 (and relying on GCC vector support), although I haven't tinkered with it for a year now (and seem to have misplaced the code). The idea is simple: to implement the few operations needed with near-optimal code, so that you don't need to worry about the small details.

    I've been developing some code that generates ball-and-stick molecular models using raycasting techniques (for the sticks/cylinders, see here), but with outlines and intersection lines as parametric curves, with the intention of being able to produce publishing-quality images by overlaying vector details on top of a rendered pixmap image. It's vector-heavy stuff, but like 3D graphics usually tend to be, the actual equations turn out to be pretty simple. (Hardest part is probably finding "optimal" Bézier curves to approximate the actual curves with absolute deviations less than a set limit.) Using a full-blown vector/matrix library like GSL would be complete overkill, and just make things harder.

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. My view on the gaming world
    By swgh in forum A Brief History of Cprogramming.com
    Replies: 16
    Last Post: 06-25-2008, 09:41 AM
  2. What's your world view?
    By nickname_changed in forum A Brief History of Cprogramming.com
    Replies: 21
    Last Post: 05-17-2005, 10:08 PM
  3. OpenGL Spaceship
    By Necrofear in forum Game Programming
    Replies: 11
    Last Post: 03-14-2005, 03:43 PM
  4. Camera rotation/movement in 3D world
    By tegwin in forum Game Programming
    Replies: 11
    Last Post: 01-24-2003, 01:43 PM
  5. Is this REALLY how Americans view the world
    By Fountain in forum A Brief History of Cprogramming.com
    Replies: 74
    Last Post: 11-21-2002, 02:34 PM

Tags for this Thread