CPSC 589/689
Lab 2 - Spinning Globe
In this exercise, we will create an application that:
To begin, you will need to download and extract barebones.tar.gz to your own directory. To extract the archive, type
gunzip barebones.tar.gz tar -xf barebones.tar
There is no directory structure in the archive, so if you extract it in
~/589/lab1/, for instance, then all the files will end up in that same directory.
Next, download the earth texture map to the same directory as the code.
To make sure that everything is working, compile the code and run the application by typing
make ./myapp
(You can change the name of the executable in the Makefile.) You should see a window that looks like this:

Now to modify the barebones application!
First you should declare and initialize the sphere object. Since we're going to apply an earth texture map, call it earthObj.
To declare it, add the following to the private data in renderer.h:
// in renderer.h -> private members
GLuint earthObj;
To initialize it, add
// in renderer.cpp -> Renderer() earthObj = 0;
to the constructor in renderer.cpp.
We don't want to really initialize the object until after OpenGL has been initialized. In the QGLWidget
class that we're using, there is a virtual function initializeGL() that is implemented in renderer.cpp. This is where we'll initialize the earth object.
// in renderer.cpp -> initializeGL()
GLUquadricObj *q = gluNewQuadric();
earthObj = glGenLists(1);
glNewList(earthObj, GL_COMPILE);
gluSphere(q, 1.8, 24, 24);
glEndList();
gluDeleteQuadric(q);
Finally, we need to render our sphere. Since earthObj is actually a display list with the appropriate rendering calls, we simply need to call the display list in our paint function Renderer::paintGL().
// in renderer.cpp -> paintGL() glRotatef(-90, 1, 0, 0); glCallList(earthObj);
At this point, if you compile and run it, you will see a plain white sphere (because no color was specified).

Next we want to apply a texture map to the sphere. The first stage is reading the image file and converting it to OpenGL's texture format. The code needed to do this is already in the barebones code, in Renderer::loadTexture(), which returns a texture "name" as a GLuint.
We need to store the texture name to refer to it later, so first we should declare and initialize a variable.
// in renderer.h -> private members
GLuint earthTex;
// in renderer.cpp -> Renderer()
earthTex = 0;
To load the texture, we simply add the following line to initializeGL():
// in renderer.cpp -> initializeGL()
earthTex = loadTexture("earthtexmap.jpg");
We also need to tell the sphere to provide texture coordinates, which is done by adding the following line to our earlier code:
GLUquadricObj *q = gluNewQuadric(); gluQuadricTexture(q, GL_TRUE); earthObj = glGenLists(1);
Finally, we need to bind the texture before rendering the sphere:
// in renderer.cpp -> paintGL()
glBindTexture(GL_TEXTURE_2D, earthTex);
glCallList(earthObj);
(Because OpenGL is state-based, the texture will still be bound from the loadTexture() call. Since we only have one texture in this example, we don't need to continually bind it. However, it is good practice to always bind the appropriate texture before rendering.)
Now your program should produce something like this.

To animate the globe, we need to use a timer. Qt provides the QTimer class, which calls a function at regular intervals.
Qt uses an event system based on the concept of signals and slots. Signals and slots are connected together, so that whenever a signal is emitted, all connected slot functions are called. The QTimer class emits a signal at a specifed interval, so we need to create a slot function that handles animation duties. This is done by adding a function prototype to the slots section of renderer.h:
// renderer.h
slots:
void animate();
We want to use the animate() function to rotate the sphere, so let's also define a rotation value:
// renderer.h -> private members float earthRot;
and initialize it in the constructor:
// renderer.cpp -> Renderer() earthRot = 0;
Now let's implement the animation function:
// renderer.cpp -> animate() void animate() { earthRot += 0.5; // Rotate by 0.5 degrees if (earthRot > 360.0) earthRot -= 360.0; updateGL(); }
Next, we need to set the timer to call our function. The timer variable is already declared, so add the following lines to Renderer() and initializeGL():
// renderer.cpp -> Renderer() connect(timer, SIGNAL(timeout()), this, SLOT(animate())); // renderer.cpp -> initializeGL(); timer->start(20, 0); // Call animate() every 20ms
Finally, to have the sphere actually rotate, we need a call to glRotatef() in paintGL():
// renderer.cpp -> paintGL()
glRotatef(earthRot, 0, 1, 0);
glRotatef(-90, 1, 0, 0);
(What would be the effect of swapping these two calls to glRotatef()?)
Compile and run the program, and you should see the globe is now spinning.
Now we'll see how to add QUI elements such as sliders, and connect them to variables in our program. First, let's declare add the GUI elements to our window by editing window.cpp.
// window.cpp // Put theuser controls here QGroupBox *zoomBox = new QGroupBox(3, Qt::Horizontal, "Zoom control", leftBox, "zoomBox"); QLabel *zoomLabel = new QLabel("Zoom", zoomBox, "zoomLabel"); QLCDNumber *zoomLCD = new QLCDNumber(3, zoomBox, "zoomLCD"); zoomLCD->display(5); QSlider *zoom = new QSlider(0, 100, 5, 5, Qt::Horizontal, zoomBox, "zoom"); connect(zoom, SIGNAL(valueChanged(int)), glwidget, SLOT(setZoom(int))); connect(glwidget, SIGNAL(zoomChanged(int)), zoomLCD, SLOT(display(int)));
This will produce some GUI controls on the bottom of the window.

The last two lines refer to a couple of functions that don't exist yet: Renderer::setZoom(int) and Renderer::zoomChanged(int), which are slots and signals respectively. For the GUI controls to work, we need to implement these functions.
First they should be declared in renderer.h
// renderer.h signals: void zoomChanged(int); slots: void setZoom(int);
Signals don't need to be implemented, as all they need to do is fire, which is handled by the Qt pre-compiler. Therefore we only need to implement setZoom():
// renderer.cpp
void Renderer::setZoom(int z)
{
cameraZ = float(z);
emit(zoomChanged(z));
updateGL();
}
The paintGL() function already uses the cameraZ value to position the camera, so this is all we need to do. Compile and run the program, and you should be able to move the camera in and out via the slider. As the slider moves, the value displayed on the LCD also changes.
That's it, we're done!

