CPSC 453

Lab 2 - GLUT Revisited



In this lab, we'll play around a bit with the functionality offered by OpenGL/GLUT, including:

  1. drawing simple objects,
  2. GLU/GLUT primitives,
  3. grabbing keyboard input,
  4. and grabbing mouse events.

1. Drawing simple objects

First of all, grab the code for the basic GLUT application.

Download these to your chosen directory and make sure you can compile and run the program.

 $> make
 $> ./helloGL

As mentioned in the first lab, there are 3 main primitives offered by OpenGL: points, lines, and polygons. From these primitives, you can render an incredible variety of objects. To remind yourself of the various primitives, consult the figure below (from Chapter 2 of the Red Book):

Show figure

Task

  • Replace the rendering code in helloGL with code that draws a colored square.
    Solution
 // In DrawGL()	
 glColor3f(1, 0, 0);     // Set color to red
 glBegin(GL_QUADS);      // Draw square with quad
 glVertex2f(-0.5, -0.5);
 glVertex2f(-0.5, 0.5);
 glVertex2f(0.5, 0.5);
 glVertex2f(0.5, -0.5);
 glEnd();



2. GLU/GLUT primitives

Besides squares and lines, there are a few primitive graphics objects that are useful to have at hand, to render easily at any time. Thankfully, the GL extensions (GLU and GLUT) offer a variety of 3D primitives. Some examples:

  • Cube
    glutWireCube(float size) (or glutSolidCube(float) for filled faces)
  • Sphere
    glutWireSphere(float, int, int) (equiv. glutSolidSphere(...))
  • Cylinder
    gluCylinder(...)

The GLUT primitives are dead easy to use in an application: just call the function with desired parameters, and optionally set some states (color, texture) beforehand. Try dropping a wireframe cube into helloGL:

 // In DrawGL()
 glutWireCube(1.0);

GLU primitives require a bit more work to use, as you have to instantiate a class called GLUquadric.

 // In DrawGL()
 GLUquadric* q = gluNewQuadric();    // Note that its bad form to 
 gluCylinder(q, 0.5, 0.5, 1, 10, 5); // allocate and free memory 
 gluDeleteQuadric(q);                // every time the display is refreshed!

Task

  • How many other GLU/GLUT primitives can you find?
    Solution
  • GLUT offers: sphere, cube, cone, torus, teapot, and a few X-hedrons.
    linky
  • GLU offers a smaller set: sphere, cylinder, and disk.
    linky
  • Draw a cube and a square at the same time but not overlapping or intersecting.
    Solution

Try putting this code into DrawGL(), then compile and run it.

 // In DrawGL()
 glPushMatrix();
 glTranslatef(-1, 0, 0);
 glutWireCube(0.7);
 glPopMatrix();

 glPushMatrix();
 glTranslatef(1, 0, 0);
 glutWireSphere(0.7, 10, 10);
 glPopMatrix();


  • Draw a wire cube in a way that shows all edges.
    Solution
 // In DrawGL()
 glRotatef(30, 0, 1, 0);
 glRotatef(30, 1, 0, 0);
 glutWireCube(1);



3. Keyboard input

Grabbing keyboard input is very easy in GLUT, and allows you to bind keys to various functions in your program. All you need to do is create a callback function conforming to the GLUT key-event prototype, and then register the callback with GLUT.

The callback has the form:

 void glutKeyboardFunc(void (*func) (unsigned char key, int x, int y));

So let's add a function to helloGL that simply prints the value of the argument:

 // New function
 void KeyPress(unsigned char k, int x, int y)
 {
 	std::cout << "You pressed " << k << std::endl;
 }

Note that this requires

 #include <iostream>

to compile.

The next step is to register the callback with GLUT. This is done with a call to glutKeyboardFunc(...) in main():

 // in main()
 glutKeyboardFunc(KeyPress);

Make these changes, compile your program, and verify that it works.

Task

  • Modify the program so that pressing 't' will toggle the rendering of a wireframe sphere.
    Solution

First you should declare a global variable for showing or hiding the sphere.

 // global
 bool showSphere = true;

Then add conditional rendering in DrawGL():

 // In DrawGL()
 if (showSphere) glutWireSphere(1, 10, 10);

Finally, toggle the value of showSphere in KeyPress():

 // In KeyPress()
 if (c == 't')
 	showSphere = !showSphere;

Compile and run this code. Did it work?? It shouldn't! Why not? There is one final thing to do: tell GLUT that it needs to redraw the scene.

Recall that DrawGL() is a callback function, only called when a redraw event occurs. Left to its own devices, GLUT only sends this event when the window is created, resized, or minimized/maximized. You can, however, fire the event yourself, with a call to glutPostRedisplay(). Add this to the KeyPress() function and you should find that the sphere now toggles correctly.


4. Mouse input

Mouse input is similarly easy to acquire from GLUT. In this case, the callback function looks like

 void glutMouseFunc(void (*func)(int button, int state, int x, int y)); 

where button is one of GLUT_LEFT_BUTTON, GLUT_MIDDLE_BUTTON, or GLUT_RIGHT_BUTTON, and state is one of GLUT_UP or GLUT_DOWN. The x and y parameters give the position of the mouse cursor in window coordinates (we'll talk more about this later).

For now, let's just write a function that prints out the position of the mouse when the left button is clicked.

 // New function
 void MouseEvent(int button, int state, int x, int y)
 {
 	if (button == GLUT_LEFT_BUTTON && 
 		state == GLUT_DOWN)
 		std::cout << "You clicked at <" << x << ", " << y << ">\n";
 }

To register the callback, you pass the function pointer to glutMouseFunc():

 // in main()
 glutMouseFunc(MouseEvent);

Compile and run the code, verifying that you are getting output when clicking in the GL window.

Task

  • What can you determine about the window coordinates from the output?
    Solution

If you click near the top-left corner, you'll get a mouse coordinate near to <0, 0>. Clicking near the bottom gives you coordinates near to <500, 500> or whatever the window size is set to. Therefore the x coordinate increase from left to right (as usual), but the y coordinate increases from top to bottom. This is often a source of confusion when trying to map mouse input into OpenGL coordinates, so keep it in mind.


Further reading