Trackball using quaternion

Hi,

I’m trying to implement trackball interface described in this page: Object Mouse Trackball - OpenGL Wiki

The following code was produced according to the same mathematical principle in “Sit and Spin” section of that page.


// Window size
int window_width = 800, window_height = 600;
float window_aspect = (float)window_width / (float)window_height;

Quat lastQuat;
Quat rotQuat;
Quat curQuat;

Vec3 center(0.0,0.0,0.0); 

float xi, yi;
bool leftButtonDown = false;
bool middleButtonDown = false;
bool rightButtonDown = false;

float z(float x, float y){
    float x2 = x*x;
    float y2 = y*y;
    float r2 = 1.0;
    if(x2 + y2 <= r2 * 0.5){
        return sqrt( r2 - (x2 + y2) );
    }
    else{
        return r2 * 0.5 / sqrt(x2 + y2);
    }
}

Vec3 trackballProject(float x, float y){
    x = x - window_width * 0.5 - center[0];
    y = y - window_height * 0.5 - center[1];
    return Vec3(x, y, z(x,y)).normalize();
}

void trackballRotate(float x1, float y1, float x2, float y2){
    Vec3 v1 = trackballProject(x1, y1);
    Vec3 v2 = trackballProject(x2, y2);
    Vec3 normal = v1 ^ v2; // cross product of v1 and v2
    float theta = acos(v1*v2); // dot product of v1 and v2
    rotQuat.fromAxis(normal, theta);
    curQuat = lastQuat * rotQuat; // quaternion multiplication
    lastQuat = curQuat;
}

void initGL(){ // lighting & depth settings here    
    //...
}

void drawScene(){
    glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
    
    glBegin(GL_TRIANGLES);
    {
        // draw whatever I want
    }
    glEnd();

    glFlush();
}

void drawRotatedScene(){
    glMatrixMode(GL_MODELVIEW);
    glPushMatrix(); { // prevents auto-rotation after releasing mouse button
        glMultMatrixf((GLfloat*)curQuat.getMatrix().mat);
        drawScene();
    } glPopMatrix();
}

void display(){
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    drawRotatedScene();
    glutSwapBuffers();
}

void resize(int w, int h){
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0, (float)w / (float)h, 0.01, 100.0);
    
    // Place the camera down the Z axis looking at the origin.
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    
    gluLookAt(0, 0, 2.4 + 1.0,
              0, 0, 0,
              0, 1, 0);
}

void handleKeyPress(unsigned char key, int x, int y){
    switch (key) {
        case 27: //Escape key
            exit(0);
    }
    glutPostRedisplay();
}

void mouseButton(int button, int state, int x, int y){ 
    y = window_height - y; 
     
    if(button == GLUT_LEFT_BUTTON){ // orbitCam 
        if(state == GLUT_UP){ 
            leftButtonDown = false;  
            xi = -1; 
            yi = -1; 
        } 
        else{ 
            leftButtonDown = true;  
            xi = x; 
            yi = y; 
        } 
    } 
    if(button == GLUT_MIDDLE_BUTTON){ // panObj 
        if(state == GLUT_UP){ 
            middleButtonDown = false; 
            xi = -1; 
            yi = -1; 
        }  
        else{ 
            middleButtonDown = true; 
            xi = x; 
            yi = y; 
        } 
    } 
    if(button == GLUT_RIGHT_BUTTON){ // zoom 
        if(state == GLUT_UP){ 
            rightButtonDown = false; 
            xi = -1; 
            yi = -1; 
        }  
        else{ 
            rightButtonDown = true; 
            xi = x; 
            yi = y; 
        } 
    } 
     
    glutPostRedisplay(); // let glut know to redraw the screen 
} 
 
void mouseMotion(int x, int y){ 
    y = window_height - y; 
     
    if(leftButtonDown){ 
        trackballRotate(xi, yi, x, y);
        
        float deltaX = x - xi; 
        float deltaY = y - yi; 
        rotY += (deltaX * 0.1); 
        rotX += (deltaY * 0.1); 
        xi = x; 
        yi = y; 
    } 
     
    if(middleButtonDown){ 
        float deltaX = x - xi; 
        float deltaY = y - yi; 
        shiftX += (deltaX * 0.1); 
        shiftY += (deltaY * 0.1); 
        xi = x; 
        yi = y; 
    } 
     
    if(rightButtonDown){ 
        float deltaY = y - yi; 
        zoom += (deltaY * 0.1); 
        yi = y; 
    } 
     
 
    glutPostRedisplay(); // let glut know to redraw the screen 
}

void idle(){
    glutPostRedisplay();
}

int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowPosition(0, 0);
    glutInitWindowSize(window_width, window_height);
    
    glutCreateWindow("Global Thermonuclear War");
    //scene_spin_context.setWindow(scene_window, scene_animate);
    
    initGL();

    glutDisplayFunc(display);
    glutKeyboardFunc(handleKeyPress);
    glutReshapeFunc(resize); 
    glutMouseFunc(mouseButton); 
    glutMotionFunc(mouseMotion);
    glutIdleFunc(idle);
    
    glutMainLoop();
    return 0;
}


// Convert from Axis Angle
void Quat::fromAxis(const Vec3 &v, float angle){
    float sinAngle;
    angle *= 0.5f;
    Vec3 vn(v);
    vn.normalize();
 
    sinAngle = sin(angle);
 
    x = (vn.x * sinAngle);
    y = (vn.y * sinAngle);
    z = (vn.z * sinAngle);
    w = cos(angle);
}

When I click & drag on the viewport, however, the object appears only to rotate about the z-axis, which is more like a carousel than a trackball. I never get to see the other side of the object.
I believe my calculations are exactly the same as the formulae in the Wiki. I feintly suspect that the axis of rotation (called “normal” in my code) is incorrect, but I don’t know where to begin.
Any suggestion will be much appreciated.

I’m not sure if this will help but you could try this page: http://nehe.gamedev.net/tutorial/arcball_rotation/19003/