A Demonstration of my Project thus far

My project is a C++ program that reads in data describing 3D models, in .obj file format, and renders and lights the models with OpenGL. Since my last post, I have implemented some user interactions and use of the CGAL data structure. The models I use to test my program came from the UC Berkeley Computer Graphics website :)

Latest Changes

  • Automatically find and fit model.
  • Maintain aspect ratio on window resize.
  • Select Color.
  • Shading based on vertex normal vectors.
  • Rotation by mouse drag.
  • Zoom by right click mouse drag.
  • Option to move model by mouse drag.

Find and Fit

cam.findModel(objModel);
cam.viewModel();
cam.moveToCenter();
zoomF = cam.fitModel(maxCoords.at(0), minCoords.at(0),
                     maxCoords.at(1), minCoords.at(1),
                     maxCoords.at(2), minCoords.at(2));

Now, whenever a new .obj file is selected, the “camera” automatically centers the model and makes it fit on the screen. I say “camera” because there is no actual camera in OpenGL. Instead, you manipulate the view by applying matrix transformations to the modelview and projection matrices. More on that later. I wrote a “camera” class, which handles these transformations.

Aspect Ratio

I think this is probably the least noticeable new feature, but it was one of the bigger challenges. If you watch closely, you can see that when I resize the window in the video, the cube retains its shape. In otherwords, it doesn’t turn into a rectangle if the viewer isn’t perfectly square. You might also notice that the angel is more slender than before.

Angel Aspect Before

Squished angel
Angel Aspect After
Angel with corrected aspect ratio

void camera::adjustAspect(float window.width, float window.height) {
    /* Modify the Projection Matrix */
    glMatrixMode(GL_PROJECTION);
    /* Start from a 'clean slate' */ 
    glLoadIdentity();
    /* Calculate Aspect Ratio */
    float newAspect = window.width/window.height;
    /* Adjust Accordingly */
    leftAdjust = newAspect * left;
    rightAdjust = newAspect * right;
    glOrtho(zoomF * leftAdjust, zoomF * rightAdjust, zoomF * bottom, zoomF * top, near, far);
}

Color Picking

humanoid color pick

To me this feature seems more impressive than the constant aspect ratio, but it took about five minutes to implement. Thanks to QT, color picking boiled down to two lines of code.

void MainWindow::on_toolButton_clicked()
{
    QColor color = QColorDialog::getColor();
    ui->widget->grabColor(color.red(), color.green(), color.blue());
}

ui is the User Interface, widget is the part of the ui that displays the model

Ok, I had to write grabColor too, so maybe 5 lines. Still. I actually threw this feature in because I needed a break from some other issue I was stuck on.

###Vertex Shading

In my last post, I mentioned that every vertex needs a normal vector so openGL knows how to shade the model. At that time, I was just computing the normal for each face and lighting all of the vertices in that face uniformly.

Before and after cube picture

Before and After per-vertex shading

Now, the normal for each vertex is assigned to be the average of the normals of all of the faces it belongs to. So the vertices on the corners of the cube are assigned a normal equal to the average of the normal vectors of the three faces that meet at that corner. The vertices on the edge of two faces get the average of the two normal vectors of those faces. The vertices in the middle of the face just get the normal that belongs to that face. OpenGL then interpolates in between the vertices to produce the smooth gradient you see.

User Controls

Making it “feel” natural

I expected understanding the openGL calls to be challenging, but I didn’t anticipate the challenge of making the controls intuitive. I guess this speaks to the idea that the best computer graphics go unnoticed.

The user controls are simulated by a series of repaints. Actually the model is being redrawn constantly, but without adjustments to the modelview or projection matrix, it is just drawn exactly the same. So my goal is to animate, or redraw, the model so the user feels like they are actually touching and manipulating the model with their mouse.

Rotation

Getting the model to rotate wasn’t hard. I could just use glRotatef to do that.

    glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);

Getting the model to rotate according to mouse drag wasn’t a big deal either. All I had to do was set the parameters in glRotatef based on the change in position of the mouse. The hard part was getting the model to rotate in a way that felt natural.

The first problem is glRotatef rotates the model around the origin, the point (0,0,0). This gives the appearance that the model was orbiting about an arbitrary point in space. Not what I want. I want the model to rotate about it’s center. That way it stays where it is and just spins around.

There is a clever trick to accomplish this. Since glRotatef rotates about the origin, you can just move whatever point you want to rotate about to the origin, apply the rotation, and then move the object back to where it was. Imagine I pick up the model, move it’s center to the origin, rotate it, and move it back to it’s original point in space.

/* Move center to origin */
glTranslatef(model.center().x, model.center().y, model.center().z);
/* Rotate */ 
glRotatef(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
/* Apply reverse translation to move center back to where it was */
glTranslatef(- model.center().x, - model.center().y, - model.center().z);

Pretty neat trick! But still, I am left with a model that is rotating in crazy unpredictable patterns.

?????????

The rotations seem accurate at the beginning of the demo, but soon deviate from what is expected. You can see that the effect of dragging the mouse in one direction (like left to right) is inconsistent as time goes on.

This is because the rotations are accumulating.

Desired Effect:
0.) Start Position.
1.) Apply rotation A to position at 0.
2.) Apply rotation B to position at 1.

We want each mouse drag to produce a new, independent rotation. So when I drag the mouse from left to right, the model spins right, regardless of the rotations I performed previously.

Actual Effect:
0.) Start Position.
1.) Apply rotation A to position at 0.
2.) Apply rotation (A + B) to position at 1.

As is, the rotations are adding on top of eachother. So when I drag the mouse left to right, the model spins according to the sum of the prior rotation and the desired left to right rotation. Confusing right?!?!?!?!

OpenGL Matrix Stack

Here, the openGL matrix stack comes in. Here is the deal: In order to rotate my model, I am manipulating the Model View Matrix. But openGL actually maintains a “stack” of Model View Matrices for me to work with. If I want to save my current matrix, I can “push” it to the stack. Then, I can change the matrix however I want. When I decide that I want to go back to that matrix I pushed earlier, I can “pop” the matrix and I will get the next one on the stack. More confusion, no?

Let’s pretend that coloring is a matrix operation. So I start out with a regular white model view matrix. I call glPushMatrix(). Then I paint the matrix yellow.

/* My matrix is white */
glPushMatrix(); // Saving white matrix for later. 
applyPaint(Yellow);
drawMyMatrix();

displays a yellow matrix

Next, I decide I want a red matrix. If I just start using red paint to my yellow matrix, I am going to end up with an orange matrix. So, first I call glPopMatrix(). Now I get the white matrix back, paint it red, and I have a true red matrix.

/* My matrix is yellow */
glPopMatrix(); // Getting the white matrix back. 
applyPaint(red);
drawMyMatrix();
glPushMatrix(); // Saving red matrix for later.

displays a red matrix

Now, I call glPushMatrix() again. I want to turn the matrix purple this time. So I paint this matrix blue, and get a purple matrix. Now I want a green matrix! But I don’t want to slather green paint on a purple thing.

applyPaint(blue);
/* Now my matrix is purple! */
drawMyMatrix();

displays a purple matrix

glPopMatrix(); // Got my red matrix back. 
glPopMatrix(); // Got my white matrix back. 
applyPaint(green);
drawMyMatrix();

displays a green matrix

Back to Rotation

QQuaternion GLWidget::drag2Rotate(float dx, float dy)
{
    /* Define Axis of Rotation */
    axisOfRotation.setX(-dy);
    axisOfRotation.setY(-dx);
    axisOfRotation.setZ(0);
    magnitude = sqrt(dx*dx + dy*dy);
    /* Update Rotation Quaternion */
    QQuaternion newQ = QQuaternion::fromAxisAndAngle(axisOfRotation, magnitude);
    currQ = newQ * currQ;
    return currQ;
}

I keep track of the current Quaternion (represents the current rotation) by multiplying the new, applied rotation by the former rotation

        glPushMatrix(); /* Saves state before rotation */
        QMatrix4x4 rotationMatrix; / Begins as Identity Matrix, Like a "white" matrix from my example
        /* Here I rotate by the Quaternion, which holds the current rotation */
        rotationMatrix.rotate(currQ);
        glMatrixMode(GL_MODELVIEW);
        /* Translate so rotation occurs about model center */
        glTranslatef(m.center().at(0), m.center().at(1), m.center().at(2));
        glMultMatrixf(mat.constData());
        glTranslatef(-m.center().at(0), -m.center().at(1), -m.center().at(2));
        m.drawMe();
        glPopMatrix(); /* Reverts to state before rotation */ 

This way, the next time the model is drawn, the rotation is applied to the original, "white", matrix.

Zoom

My first idea was to use glScalef on the Model View Matrix to zoom.

void MainWindow::on_toolButton_clicked() {
    glMatrixMode(GL_MODELVIEW); // Specifies which matrix will be scaled
    glScalef(xScale, yScale, zScale);
}

Calling glScalef(0.5,0.5,0.5) would uniformly scale the Model View Matrix down 50%

That isn’t quite right. Here I am actually changing the size of the model, rather than getting closer or further. This is kind of like the desired effect, but if I get too close holes start appearing in my models. By scaling the Model View Matrix, the model can get so big that parts of it would lie outside the clipping planes.

Instead of adjusting the Model View Matrix, I need to adjust the Projection Matrix. Doing so changes the way I view the “world”, as opposed to changing the model itself.

void camera::setZoom(float factor) {
    if (factor > 0.01)
        zoomF = factor;
    else
        zoomF = 0.01;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity(); // Make sure zoom isn't applied to the previous state
    glOrtho(leftAdjust*zoomF,rightAdjust*zoomF,bottom*zoomF,top*zoomF,-near,-far);
}

That’s it for now :) My next post will show my final project!