Robotics C++ Physics II AP Physics B Electronics Java Astronomy Other Courses Summer Session  

GLUT Part 2

4. Reshape to Prevent Window Distortions

5. Animation 

6. Keyboard

7. Moving Camera with Keyboard

8. Mouse

9. Popup Menus 

 

 

 

Reshape to Prevent Distortions

In the above graphic you see two windows a console window and the OpenGL window. Now resize the window so that the height no longer matches the width.

The triangle gets distorted. This occurs because you're not setting the perspective correctly. By default the perspective assumes that the ratio width/height is 1 and draws accordingly. So when the ratio is changed the perspective gets distorted. Therefore, every time the ratio changes the perspective needs to be recomputed.

GLUT provides a way to define which function should be called when the window is resized, i.e. to register a callback for recomputing the perspective. Furthermore, this function will also be called when the window is initially created so that even if you're initial window is not square things will look OK. GLUT achieves this using the function glutReshapeFunc.

void glutReshapeFunc(void (*func)(int width, int height));

Parameters:
func - The name of the function that will be responsible for setting the correct perspective when the window changes size.

So the first thing that we must do is to go back to the main function we defined in the previous section and add a call to glutReshapeFunc. Lets call our own function to take care of window resizes changeSize. The code for the main function with the call to glutReshapeFunc added in is:

void main(int argc, char **argv)

{
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_DEPTH | GLUT_SINGLE | GLUT_RGBA);
glutInitWindowPosition(100,100);
glutInitWindowSize(320,320);
glutCreateWindow("3D Tech- GLUT Tutorial");
glutDisplayFunc(renderScene);
// Here is our new entry in the main function
glutReshapeFunc(changeSize);
glutMainLoop();
}

The next thing we need to do is to define the function that we'll take care of the perspective. As seen by the syntax of glutReshapeFunc, the changeSize function has two arguments, these are the new width and height, respectively, of the client area of the window, i.e. without the window decorations.

void changeSize(int w, int h)

{
// Prevent a divide by zero, when window is too short
// (you cannot make a window of zero width).
if(h == 0)
h = 1;
float ratio = 1.0* w / h;
// Reset the coordinate system before modifying
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
// Set the viewport to be the entire window
glViewport(0, 0, w, h);
// Set the correct perspective.
gluPerspective(45,ratio,1,1000);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
gluLookAt(0.0,0.0,5.0,
      0.0,0.0,-1.0,
  0.0f,1.0f,0.0f);
}

A  few functions were introduced in this piece of code so let's go into a bit of detail in here before we all get lost.

The first step was to compute the ratio between the width and the height. Note that in order for this to be done correctly we must take care of the case when the height of a window is actually zero to prevent a division by zero. We then set the current matrix to be the projection matrix. This is the matrix that defines the viewing volume.

We then load the identity matrix to initialize it. Afterwards we set the viewport to be the whole window with the function glViewport. You can try with different values to see what you come up with, the first two parameters are the top right corner, and the last two are the bottom left. Note that these coordinates are relative to the client area of the window, not the screen. If you do try with different values then don't forget that the ratio computed above should also use the new width and height values.

The gluPerspective function is part of another library for OpenGL, the OpenGL Utility Library, or GLU. GLU is a standard component of the implementation of OpenGL. The gluPerspective function establishes the perspective parameters. The first one defines the field of view angle in the yz plane, the ratio defines the relation between the width and height of the viewport. The last two parameters define the near and far clipping planes. Anything closer than the near value, or further away than the far value will be clipped away from the scene. Beware with these settings or you may end up not seeing anything at all.

Finally, setting the camera. First we set the GL_MODELVIEW as our current matrix. the modelview matrix is where we'll define both the camera settings and the modeling transformations. Before setting the camera it is always healthy to load the identity matrix. this avoids previous transformations to affect the camera settings. The gluLookAt function provides an easy and intuitive way to set the camera position and orientation. Basically it has three groups of parameters, each one is composed of 3 floating point values. The first three values indicate the camera position. The second set of values defines the point we're looking at. Actually it can be any point in our line of sight.The last group indicates the up vector, this is usually set to (0.0, 1.0, 0.0), meaning that the camera's is not tilted. If you want to tilt the camera just play with these values. For example, to see everything upside down try (0.0, -1.0, 0.0).

Modified code to prevent distortions

 

#include<GL/glut.h>

void changeSize(int w, int h)

{

      // Prevent a divide by zero, when window is too short

      // (you cant make a window of zero width).

      if(h == 0)

            h = 1;

 

      float ratio = 1.0* w / h;

 

      // Reset the coordinate system before modifying

      glMatrixMode(GL_PROJECTION);

      glLoadIdentity();

     

      // Set the viewport to be the entire window

    glViewport(0, 0, w, h);

 

      // Set the correct perspective.

      gluPerspective(45,ratio,1,1000);

      glMatrixMode(GL_MODELVIEW);

      glLoadIdentity();

      gluLookAt(0.0,0.0,5.0,

                  0.0,0.0,-1.0,

                    0.0f,1.0f,0.0f);

}

void renderScene(void)

{

      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

      glBegin(GL_TRIANGLES);

            glVertex3f(-0.5,-0.5,0.0);

            glVertex3f(0.5,0.0,0.0);

            glVertex3f(0.0,0.5,0.0);

      glEnd();

      glFlush();

}

void main(int argc, char **argv)

{

      glutInit(&argc, argv);

      glutInitDisplayMode(GLUT_DEPTH | GLUT_SINGLE | GLUT_RGBA);

      glutInitWindowPosition(100,100);

      glutInitWindowSize(320,320);

      glutCreateWindow("Lighthouse 3D - GLUT Tutorial");

      glutDisplayFunc(renderScene);

      glutReshapeFunc(changeSize);

      glutMainLoop();

}

Animation

 

We now have an OpenGL window with a white triangle. Now to complete this part of the GLUT tutorial lets add code to spin the triangle.

Lets go back to the main function and add some extra items. First lets tell GLUT that we want a double buffer. Double buffering allows for smooth animation by keeping the drawing in a back buffer and swapping the back with the front buffer (the visible one) when the rendering is complete. Using double buffering prevents flickering.


The second thing we must do is to tell GLUT that when the application is idle the render function should be called. This causes GLUT to keep calling our rendering function therefore enabling animation. GLUT provides a function, glutIdleFunc, that lets you register a callback function to be called when the application is idle.

void glutIdleFunc(void (*func)(void));

Parameters:

func - The name of the function that will be called whenever the application is idle.
In our case, when the application is idle we want to call the previously defined function that does the actual rendering: renderScene. We also need to enable depth testing. By default OpenGL doesn't do this, so it doesn't know what objects are in the front and which are on the back. Unless we did draw everything in back to front order, the objects would be incorrectly drawn.

Fortunately all that is required is to enable depth testing as shown in the main function below. So the main function now looks like this:

void main(int argc, char **argv)

{

               glutInit(&argc, argv);

              

               // This is where we say that we want a double buffer

               glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

              

               glutInitWindowPosition(100,100);

               glutInitWindowSize(320,320);

               glutCreateWindow("3D Tech- GLUT Tutorial");

               glutDisplayFunc(renderScene);

              

               // here is the setting of the idle function

               glutIdleFunc(renderScene);

              

               glutReshapeFunc(changeSize);

              

               // enable depth testing

               glEnable(GL_DEPTH_TEST);

               glutMainLoop();

}

 

Afterwards, we change the rendering itself. First lets declare a floating point variable angle, and initialize it to 0.0 . TNow add to the renderScene function.

 

float angle=0.0;

 

void renderScene(void)

{

 

               // notice that we're now clearing the depth buffer as well. This this is required, otherwise the depth buffer gets filled and nothing gets rendered.

               // Try it out, remove the depth buffer part.

 

               glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

              

               // we're refering to the camera settings.

               glPushMatrix();

              

               // Perform a rotation around the y axis (0,1,0)

               // by the amount of degrees defined in the variable angle

               glRotatef(angle,0.0,1.0,0.0);

               glBegin(GL_TRIANGLES);

                               glVertex3f(-0.5,-0.5,0.0);

                               glVertex3f(0.5,0.0,0.0);

                               glVertex3f(0.0,0.5,0.0);

               glEnd();

              

               // discard the modelling transformations ater this the matrix will have only the camera settings.

               glPopMatrix();

              

               // swapping the buffers causes the rendering above to be shown

               glutSwapBuffers();

              

               // finally increase the angle for the next frame

               angle++;

}


	
The glutSwapBuffers function cause the front and back buffers to switch thereby showing what was previously drawn in the back buffer. The syntax is as follows:

void glutSwapBuffers();

Code and output are shown below

#include<GL/glut.h>

float angle = 0.0;

void changeSize(int w, int h)

{

            // Prevent a divide by zero, when window is too short - you can not make a window of zero width

            if(h == 0)

                        h = 1;

 

            float ratio = 1.0* w / h;

 

            // Reset the coordinate system before modifying

            glMatrixMode(GL_PROJECTION);

            glLoadIdentity();

           

            // Set the viewport to be the entire window

    glViewport(0, 0, w, h);

 

            // Set the correct perspective.

            gluPerspective(45,ratio,1,1000);

            glMatrixMode(GL_MODELVIEW);

            glLoadIdentity();

            gluLookAt(0.0,0.0,5.0,

                              0.0,0.0,-1.0,

                                      0.0f,1.0f,0.0f);

}

 

 

void renderScene(void)

 {

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 

            glPushMatrix();

            glRotatef(angle,0.0,1.0,0.0);

            glBegin(GL_TRIANGLES);

                        glVertex3f(-0.5,-0.5,0.0);

                        glVertex3f(0.5,0.0,0.0);

                        glVertex3f(0.0,0.5,0.0);

            glEnd();

            glPopMatrix();

            angle++;

            glutSwapBuffers();

}

 

void main(int argc, char **argv)

]{

            glutInit(&argc, argv);

            glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

            glutInitWindowPosition(100,100);

            glutInitWindowSize(320,320);

            glutCreateWindow("Spinning Triangle");

            glutDisplayFunc(renderScene);

            glutIdleFunc(renderScene);

            glutReshapeFunc(changeSize);

            glutMainLoop();

}

Keyboard

GLUT allows us to build applications that detect keyboard input using either the "normal" keys, or the special keys like F1 and Up. In this section we'll see how to detect which key was pressed, what further information we get from GLUT, and how to deal with that.

 

As you have probably noticed by now, whenever you want to take control of the processing of an event you have to tell GLUT in advance which function is going to perform such task. Up until now we used GLUT to tell the windows system which functions we wanted to do the rendering when the window needed to be repainted, which function to call when the system was idle, and which function to call when the window was resized.

 

Similarly we must do the same thing for keyboard events. We must notify the windows system, using GLUT, which function(s) will perform the required processing when a key is pressed. This procedure of notifying that when an event occurs we want to execute a particular function is also called "register a callback function".

 

GLUT provides two functions to register callbacks for keyboard events that occur when you press a key. The first one, glutKeyboardFunc, is used to tell the windows system which function we want to process the "normal" key events. By "normal" keys, we mean letters, numbers, anything that has an ASCII code. The syntax for this function is as follows:

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

Parameters:

func - The name of the function that will process the "normal" keyboard events. Passing NULL as an argument causes GLUT to ignore "normal" keys.


The function used as an argument to glutKeyboardFunc needs to have three arguments. The first indicates the ASCII code of the key pressed, the remaining two arguments provide the mouse position when the key is pressed. The mouse position is relative to the top left corner of the client area of the window.

A possible implementation for this function is to provide a way out of the application when the user presses the ESCAPE key. Note that when the glutMainLoop function was presented we mentioned that it was an infinite loop, i.e. it never returns. The only way out of this loop is to call the system exit function. So that's exactly what our function will do, when the user presses escape it calls the system exit function causing the application to terminate (remember to include stdlib.h in the source code). Next we present the function code:

   

void processNormalKeys(unsigned char key, int x, int y)

{

 

               if (key == 27)

                               exit(0);

}

 


Note that we are using exactly the same signature as the one specified in the syntax of glutKeyboardFunc. If you don't do this you'll get an error when compiling this in VC.

Next, we tackle the special keys. GLUT provides the function glutSpecialFunc so that you can register your function for special key events processing. The syntax for this function is as follows:

void glutSpecialFunc(void (*func) (int key, int x, int y));

Parameters:

func - The name of the function that will process the special keyboard events. Passing NULL as an argument causes GLUT to ignore the special keys.


We're going to write a function that changes the color of our triangle when some of the special keys are pressed. This function will paint the triangle using red if F1 is pressed, green if F2 is pressed, and blue if F3 is pressed.

   

void processSpecialKeys(int key, int x, int y)

{

                switch(key)

                 {

                               case GLUT_KEY_F1 :

                                                             red = 1.0;

                                                             green = 0.0;

                                                             blue = 0.0; break;

                               case GLUT_KEY_F2 :

                                                             red = 0.0;

                                                             green = 1.0;

                                                             blue = 0.0; break;

                               case GLUT_KEY_F3 :

                                                             red = 0.0;

                                                             green = 0.0;

                                                             blue = 1.0; break;

               }

}

 


The GLUT_KEY_* are predefined constants in glut.h. The full set of constants is presented next:

   

GLUT_KEY_F1                        F1 function key

GLUT_KEY_F2                        F2 function key

GLUT_KEY_F3                        F3 function key

GLUT_KEY_F4                        F4 function key

GLUT_KEY_F5                        F5 function key

GLUT_KEY_F6                        F6 function key

GLUT_KEY_F7                        F7 function key

GLUT_KEY_F8                        F8 function key

GLUT_KEY_F9                        F9 function key

GLUT_KEY_F10                      F10 function key

GLUT_KEY_F11                      F11 function key

GLUT_KEY_F12                      F12 function key

GLUT_KEY_LEFT                    Left function key

GLUT_KEY_RIGHT                  Up function key

GLUT_KEY_UP                       Right function key

GLUT_KEY_DOWN                 Down function key

GLUT_KEY_PAGE_UP            Page Up function key

GLUT_KEY_PAGE_DOWN      Page Down function key

GLUT_KEY_HOME                  Home function key

GLUT_KEY_END                    End function key

GLUT_KEY_INSERT                Insert function key

 

 

In order for the code defined above on processSpecialKeys to compile we must add the declaration of the red, green, and blue variables to the beginning of our code. Furthermore, for the code to have the desired effect we must change the function responsible for the rendering, renderScene.

   

...

// all variables initialized to 1.0, meaning

// the triangle will initially be white

 

float red=1.0, blue=1.0, green=1.0;

 

void renderScene(void)

{

               glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

               glPushMatrix();

               glRotatef(angle,0.0,1.0,0.0);

 

               // this is where we set the actual color

               // glColor specifies the color of all further drawings

               glColor3f(red,green,blue);

 

               glBegin(GL_TRIANGLES);

                               glVertex3f(-0.5,-0.5,0.0);

                               glVertex3f(0.5,0.0,0.0);

                               glVertex3f(0.0,0.5,0.0);

               glEnd();

               glPopMatrix();

               angle++;

               glutSwapBuffers();

}

 

 

OK, now we're ready to tell GLUT that the functions we just defined are the ones that will process keyboard events. In other words it is time to call GLUT's glutKeyboardFunc and glutSpecialFunc. The call to these functions can be made anywhere, meaning that we may change the processing function for keyboard event processing at any time. However this is not an usual feature, so we'll place it on the main function. Next we present the new main function, with keyboard processing is presented (note that this function is based on the previous sections of this tutorial):

   

void main(int argc, char **argv)

{

               glutInit(&argc, argv);

              

               // This is where we say that we want a double buffer

               glutInitDisplayMode(GLUT_DEPTH|GLUT_DOUBLE|GLUT_RGBA);

              

               glutInitWindowPosition(100,100);

               glutInitWindowSize(320,320);

               glutCreateWindow("3D Tech - GLUT Tutorial");

               glutDisplayFunc(renderScene);

               glutIdleFunc(renderScene);

               glutReshapeFunc(changeSize);

              

               // here are the new entries

               glutKeyboardFunc(processNormalKeys);

               glutSpecialFunc(processSpecialKeys);   

 

               // enable depth testing

               glEnable(GL_DEPTH_TEST);

               glutMainLoop();

}

 

 

Output

 

 

Code

 

#include<GL/glut.h>

#include<stdlib.h>

float angle = 0.0;

float red=1.0, blue=1.0, green=1.0;

 

void changeSize(int w, int h)

{

            // Prevent a divide by zero, when window is too short – no window of zero width).

            if(h == 0)

                        h = 1;

 

            float ratio = 1.0* w / h;

 

            // Reset the coordinate system before modifying

            glMatrixMode(GL_PROJECTION);

            glLoadIdentity();

           

            // Set the viewport to be the entire window

    glViewport(0, 0, w, h);

 

            // Set the correct perspective.

            gluPerspective(45,ratio,1,1000);

            glMatrixMode(GL_MODELVIEW);

            glLoadIdentity();

            gluLookAt(0.0,0.0,5.0,

                              0.0,0.0,-1.0,

                                      0.0f,1.0f,0.0f);

}

 

void renderScene(void)

{

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            glPushMatrix();

            glRotatef(angle,0.0,1.0,0.0);

 

            glColor3f(red,green,blue);

 

            glBegin(GL_TRIANGLES);

                        glVertex3f(-0.5,-0.5,0.0);

                        glVertex3f(0.5,0.0,0.0);

                        glVertex3f(0.0,0.5,0.0);

            glEnd();

            glPopMatrix();

            angle++;

            glutSwapBuffers();

}

void processNormalKeys(unsigned char key, int x, int y)

{

 

            if (key == 27)

                        exit(0);

}

 

void processSpecialKeys(int key, int x, int y)

{

 

            switch(key)

              {

                        case GLUT_KEY_F1 : red = 1.0; green = 0.0; blue = 0.0; break;

                        case GLUT_KEY_F2 : red = 0.0; green = 1.0; blue = 0.0; break;

                        case GLUT_KEY_F3 : red = 0.0; green = 0.0; blue = 1.0; break;

            }

}

void main(int argc, char **argv)

{

            glutInit(&argc, argv);

            glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

            glutInitWindowPosition(100,100);

            glutInitWindowSize(320,320);

            glutCreateWindow("GLUT Keyboard");

            glutDisplayFunc(renderScene);

            glutIdleFunc(renderScene);

            glutReshapeFunc(changeSize);

 

            //adding here the setting of keyboard processing

            glutKeyboardFunc(processNormalKeys);

            glutSpecialFunc(processSpecialKeys);

            glutMainLoop();

}

 


CTRL, ALT and SHIFT - Not Covered Here

Moving Camera with Keyboard

OK, so lets see a more exciting use for the keyboard using GLUT. In this section we're going to go through the code of an application that will draw a small world populated with snowman, and we're going to use the direction keys to move the camera in this world. The left and right keys will rotate the camera around the Y axis, i.e. in the XZ plane, whereas the up and down keys will move the camera forward and backwards in the current direction.

 

The code for this sample application is now presented with comments where appropriate. First lets deal with the initializations:

   

#include <math.h>

#include <GL/glut.h>

 

#include <stdlib.h>

 

static float angle=0.0,ratio;

static float x=0.0f,y=1.75f,z=5.0f;

static float lx=0.0f,ly=0.0f,lz=-1.0f;

static GLint snowman_display_list;

 

Note that we have included math.h, we'll need this for the angle of rotation. The meaning of the variables declared above we'll become clearer as we get along the code but meanwhile where goes a brief description:

  angle: the angle of rotation in the y axis. this variable will allow us to rotate the camera.

  x,y,z: The camera position

  lx,ly,lz: A vector defining our line of sight

  ratio: The window width/heith ratio

  snowman_display_list: the display list index for a single snowman

Next we have a common function to deal with window resizing. The only difference is that the parameters of the gluLookAt function are now variables instead of fixed values. Just in case you haven't gone through the previous sections of the tutorial, and are wondering what is this function for, here goes a brief explanation. The gluLookAt function provides an easy and intuitive way to set the camera position and orientation. Basically it has three groups of parameters, each one is composed of 3 floating point values. The first three values indicate the camera position. The second set of values defines the point we're looking at. Actually it can be any point in our line of sight.The last group indicates the up vector, this is usually set to (0.0, 1.0, 0.0), meaning that the camera's is not tilted. If you want to tilt the camera just play with these values. For example, to see everything upside down try (0.0, -1.0, 0.0).

As mentioned before x, y, and z represent the camera position so these values correspond to the first vector in gluLookAt. The second set of parameters, the look at point, is computed by adding the vector which defines our line of sight to the camera position.

  Look At Point = Line Of Sight + Camera Position

   

void changeSize(int w, int h)

               {

 

               // Prevent a divide by zero, when window is too short

               // (you cant make a window of zero width).

               if(h == 0)

                               h = 1;

 

               ratio = 1.0f * w / h;

               // Reset the coordinate system before modifying

               glMatrixMode(GL_PROJECTION);

               glLoadIdentity();

              

               // Set the viewport to be the entire window

               glViewport(0, 0, w, h);

 

               // Set the clipping volume

               gluPerspective(45,ratio,1,1000);

               glMatrixMode(GL_MODELVIEW);

               glLoadIdentity();

               gluLookAt(x, y, z,

                                              x + lx,y + ly,z + lz,

                                              0.0f,1.0f,0.0f);

               }

 

 


Next we have the initialization code for the rendering including the definition of the display list (discussed separately) and the rendering itself. You can skip this bit if you want to and still follow the tutorial.

void drawSnowMan()

{

 

               glColor3f(1.0f, 1.0f, 1.0f);

 

// Draw Body          

               glTranslatef(0.0f ,0.75f, 0.0f);

               glutSolidSphere(0.75f,20,20);

 

 

// Draw Head

               glTranslatef(0.0f, 1.0f, 0.0f);

               glutSolidSphere(0.25f,20,20);

 

// Draw Eyes

               glPushMatrix();

               glColor3f(0.0f,0.0f,0.0f);

               glTranslatef(0.05f, 0.10f, 0.18f);

               glutSolidSphere(0.05f,10,10);

               glTranslatef(-0.1f, 0.0f, 0.0f);

               glutSolidSphere(0.05f,10,10);

               glPopMatrix();

 

// Draw Nose

               glColor3f(1.0f, 0.5f , 0.5f);

               glRotatef(0.0f,1.0f, 0.0f, 0.0f);

               glutSolidCone(0.08f,0.5f,10,2);

}

 

GLuint createDL() {

               GLuint snowManDL;

 

               // Create the id for the list

               snowManDL = glGenLists(1);

 

               // start list

               glNewList(snowManDL,GL_COMPILE);

 

               // call the function that contains

               // the rendering commands

                               drawSnowMan();

 

               // endList

               glEndList();

 

               return(snowManDL);

}

 

void initScene() {

 

               glEnable(GL_DEPTH_TEST);

               snowman_display_list = createDL();

}

 

 

void renderScene(void) {

               glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

 

               // Draw ground

 

               glColor3f(0.9f, 0.9f, 0.9f);

               glBegin(GL_QUADS);

                               glVertex3f(-100.0f, 0.0f, -100.0f);

                               glVertex3f(-100.0f, 0.0f,  100.0f);

                               glVertex3f( 100.0f, 0.0f,  100.0f);

                               glVertex3f( 100.0f, 0.0f, -100.0f);

               glEnd();

 

               // Draw 36 SnowMen

 

               for(int i = -3; i < 3; i++)

                               for(int j=-3; j < 3; j++) {

                                              glPushMatrix();

                                              glTranslatef(i*10.0,0,j * 10.0);

                                              glCallList(snowman_display_list);;

                                              glPopMatrix();

                               }

               glutSwapBuffers();

}

 

 


Here goes the function that will process the special keys events. We're using the left and right keys to rotate the camera, i.e. to change the vector that defines the line of sight. The up and down keys are used to move along the current line of sight.

 

   

void inputKey(int key, int x, int y) {

 

               switch (key) {

                               case GLUT_KEY_LEFT :

                                              angle -= 0.01f;

                                              orientMe(angle);break;

                               case GLUT_KEY_RIGHT :

                                              angle +=0.01f;

                                              orientMe(angle);break;

                               case GLUT_KEY_UP :

                                              moveMeFlat(1);break;

                               case GLUT_KEY_DOWN :

                                              moveMeFlat(-1);break;

               }

}

 

 


When the user presses the left or right keys the variable angle is changed accordingly and the function orientMe is called with the new value. This function will rotate the camera accordingly. The function moveMeFlat is responsable for moving the camera in the plane XZ along the line of sight The parameter indicates the relative direction of the movement.

Here follows the function responsible for rotating the camera. This function receives an angle and it computes the appropriate values for the new x and z components of the line of sight vector. Note that we're only moving in the XZ plane, therefore we don't need to change the y coordinate of the line of sight vector. The new lx and lz are mapped onto a unitary circle on the XZ plane. Therefore, given a angle ang, the new values for lx and lz are:

  lx = sin(ang)

  lz = cos(ang) Just like if we wanted to convert from Polar coordinates (ang,1) to Euclidean coordinates. Afterwards we set the new camera orientation with gluLookAt. Note that the camera doesn't move, the camera position remains the same, only the look at point is altered.

 

   

void orientMe(float ang) {

 

               lx = sin(ang);

               lz = -cos(ang);

               glLoadIdentity();

               gluLookAt(x, y, z,

                                     x + lx,y + ly,z + lz,

                                                0.0f,1.0f,0.0f);

}

 

 


This next function is responsible for the camera movement. We want to move the camera along the line of sight, i.e. the next camera position must be along the line of sight vector. In order to achieve this we're going to add a fraction of the line of sight vector to our current position, so the new values for x and z are:

  x = x + direction(lx)*fraction

  z = z + direction*(lz)*fraction where direction is either -1 or 1 depending on wheter we want to move forwards, or backwards. The fraction is a possible speed implementation. We know that (lx,lz) is a unitary vector (as mentioned before, its a point in the unit circle), therefore if the fraction is kept constant then the speed will be kept constant as well. By increasing the fraction we're moving faster, i.e. we're moving farther in each frame.

The following steps are the same as in the orientMe function.

 

   

void moveMeFlat(int direction) {

               x = x + direction*(lx)*0.1;

               z = z + direction*(lz)*0.1;

               glLoadIdentity();

               gluLookAt(x, y, z,

                                     x + lx,y + ly,z + lz,

                                                0.0f,1.0f,0.0f);

}

 

 


The standard code for a main function using GLUT.

 

   

int main(int argc, char **argv)

{

               glutInit(&argc, argv);

               glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

               glutInitWindowPosition(100,100);

               glutInitWindowSize(640,360);

               glutCreateWindow("SnowMen from 3D-Tech");

 

               initScene();

 

               glutSpecialFunc(inputKey);

 

               glutDisplayFunc(renderScene);

               glutIdleFunc(renderScene);

 

               glutReshapeFunc(changeSize);

 

               glutMainLoop();

 

               return(0);

}

 

Output

Code

#include<GL/glut.h>

#include<stdlib.h>

float angle = 0.0;

float red=1.0, blue=1.0, green=1.0;

 

void changeSize(int w, int h)

{

            // Prevent a divide by zero, when window is too short – no window of zero width).

            if(h == 0)

                        h = 1;

 

            float ratio = 1.0* w / h;

 

            // Reset the coordinate system before modifying

            glMatrixMode(GL_PROJECTION);

            glLoadIdentity();

           

            // Set the viewport to be the entire window

    glViewport(0, 0, w, h);

 

            // Set the correct perspective.

            gluPerspective(45,ratio,1,1000);

            glMatrixMode(GL_MODELVIEW);

            glLoadIdentity();

            gluLookAt(0.0,0.0,5.0,

                              0.0,0.0,-1.0,

                                      0.0f,1.0f,0.0f);

}

 

 

void renderScene(void) {

            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

            glPushMatrix();

            glRotatef(angle,0.0,1.0,0.0);

 

            glColor3f(red,green,blue);

 

            glBegin(GL_TRIANGLES);

                        glVertex3f(-0.5,-0.5,0.0);

                        glVertex3f(0.5,0.0,0.0);

                        glVertex3f(0.0,0.5,0.0);

            glEnd();

            glPopMatrix();

            angle++;

            glutSwapBuffers();

}

 

void processNormalKeys(unsigned char key, int x, int y)

{

 

            if (key == 27)

                        exit(0);

}

 

void processSpecialKeys(int key, int x, int y)

{

 

            switch(key)

              {

                        case GLUT_KEY_F1 : red = 1.0; green = 0.0; blue = 0.0; break;

                        case GLUT_KEY_F2 : red = 0.0; green = 1.0; blue = 0.0; break;

                        case GLUT_KEY_F3 : red = 0.0; green = 0.0; blue = 1.0; break;

            }

}

 

void main(int argc, char **argv)

{

            glutInit(&argc, argv);

            glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

            glutInitWindowPosition(100,100);

            glutInitWindowSize(320,320);

            glutCreateWindow("GLUT Keyboard");

            glutDisplayFunc(renderScene);

            glutIdleFunc(renderScene);

            glutReshapeFunc(changeSize);

 

            //adding here the setting of keyboard processing

            glutKeyboardFunc(processNormalKeys);

            glutSpecialFunc(processSpecialKeys);

            glutMainLoop();

}

Mouse

Detecting Mouse Clicks

As in the keyboard version, GLUT provides a way for you to register the function that will be responsable for processing events generated by mouse clicks. The name of this function is glutMouseFunc,and it is commonly called in the initialization phase of the application. The syntax is as follows:

 

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

Parameters:

func - The name of the function that will handle mouse click events

 


As we can see from the signature of glutMouseFunc, the function that will handle  the mouse click events must have four parameters. The first relates to which button was pressed, or released. This argument can have one of three values:

Ø GLUT_LEFT_BUTTON

Ø GLUT_MIDDLE_BUTTON

Ø GLUT_RIGHT_BUTTON The second argument relates to the state of the button when the callback was generated, i.e. pressed or released. The possible values

     are:

    GLUT_DOWN

    GLUT_UP

When a callback is generated with the state GLUT_DOWN, the application can assume that a GLUT_UP will come afterwards even if the mouse moves outside the window. However if the application calls glutMouseFunc again with NULL as argument then GLUT will stop sending mouse state changes.

The remaining two parameters provide the (x,y) coordinates of the mouse relatively to the upper left corner of the client area of the window.

Detecting Motion

 

GLUT provides mouse motion detection capabilities to an application. There are two types of motion that GLUT handles: active and passive motion. Active motion occurs when the mouse is moved and a button is pressed. Passive motion is when the mouse is moving but no buttons are pressed. If an application is tracking motion, an event will be generated per frame during the period that the mouse is moving.

 

As usual you must register with GLUT the function that will be responsable for handling the motion events. GLUT allows us to specify two different functions: one for tracking passive motion, and another to track active motion.

 

The signatures for the GLUT functions are as follows:

 

void glutMotionFunc(void (*func) (int x,int y));
void glutPassiveMotionFunc(void (*func) (int x, int y));

Parameters:

func - the function that will be responsible for the respective type of motion.


The parameters for the motion processing function are the (x,y) coordinates of the mouse relatively to the upper left corner of the window's client area.

 

Detecting when the mouse enters or leaves the window

GLUT is also able to detect when the mouse leaves or enters the window region. A callback function can be registered to handle these two events. The GLUT function to register this callback is glutEntryFunc and the syntax is as follows:

 

void glutEntryFunc(void (*func)(int state));

Parameters:

func - the function that will handle these events.


The parameter of the function that will handle these events tells us if the mouse has entered of left the window region. GLUT defines two constants that can be used in the application:

 

Ø GLUT_LEFT

Ø GLUT_ENTERED

 

Note: This doesn't work exactly as it says in Microsoft Windows, this is because in Microsoft's OS the focus is changed with a mouse click. Although you can change this is your own system using some tools from Microsoft, others are likely to have the standard setting so its probably better if you don't use this feature in Microsoft Windows to detect when the mouse enters/leaves the window.

 

Putting it all together

 

The first thing we should do is to register with GLUT which function will be responsible for handling mouse events. Therefore we are going to rewrite our main function to include all the necessary callback's registrations. We are going to add all the functionality described above to our application to present a simple example from which you can learn the missing pieces, or just pick up the things that where not that clear in this tutorial.

 

   

void main(int argc, char **argv)

{

        glutInit(&argc, argv);

        glutInitDisplayMode(GLUT_DEPTH | GLUT_DOUBLE | GLUT_RGBA);

        glutInitWindowPosition(100,100);

        glutInitWindowSize(320,320);

        glutCreateWindow("SnowMen");

        glutDisplayFunc(renderScene);

        glutIdleFunc(renderScene);

        glutReshapeFunc(changeSize);

 

 

        //adding here the mouse processing callbacks

        glutMouseFunc(processMouse);

        glutMotionFunc(processMouseActiveMotion);

        glutPassiveMotionFunc(processMousePassiveMotion);

        glutEntryFunc(processMouseEntry);

       

        glutMainLoop();

}

 


We're going to define the callback functions we registered to do some strange stuff. When a mouse button is pressed, and the ALT key is also pressed, we're going to change the triangle's color. So the left button makes the triangle red, the middle turns the triangle's color to green, whereas the right button makes a blue triangle. Next we present a function to do just this (note that the name of the function must be processMouse as this is the name registered in the main function:

 

void processMouse(int button, int state, int x, int y)

{

 

        specialKey = glutGetModifiers();

        // if both a mouse button, and the ALT key, are pressed  then

        if ((state == GLUT_DOWN) &&

                       (specialKey == GLUT_ACTIVE_ALT))

          {

 

               // set the color to pure red for the left button

               if (button == GLUT_LEFT_BUTTON)

               {

                       red = 1.0; green = 0.0; blue = 0.0;

               }

               // set the color to pure green for the middle button

               else if (button == GLUT_MIDDLE_BUTTON)

               {

                       red = 0.0; green = 1.0; blue = 0.0;

               }

               // set the color to pure blue for the right button

               else

                {

                       red = 0.0; green = 0.0; blue = 1.0;

               }

        }

}

 

 
Now lets have a more subtle color picking method. When a button is pressed, but no ALT key, we're going to set blue to 0.0, and let the red and green components be dependent upon the mouse position on the window's client area. The function bellow does just this:

   

void processMouseActiveMotion(int x, int y)

{

 

        // the ALT key was used in the previous function

        if (specialKey != GLUT_ACTIVE_ALT)

       {

               // setting red to be relative to the mouse

               // position inside the window

               if (x < 0)

                       red = 0.0;

               else if (x > width)

                       red = 1.0;

               else

                       red = ((float) x)/height;

               // setting green to be relative to the mouse

               // position inside the window

               if (y < 0)

                       green = 0.0;

               else if (y > width)

                       green = 1.0;

               else

                       green = ((float) y)/height;

               // removing the blue component.

               blue = 0.0;

        }

}

 

It's time to add some action to passive motion. When the SHIFT key is pressed, the mouse will have a rotation in the X axis relative to the mouse position in the x window coordinate. We had to change slightly the renderScene function to do this, so here goes the new renderScene:

 

float angleX = 0.0;

...

void renderScene(void)

{

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glPushMatrix();

        glRotatef(angle,0.0,1.0,0.0);

       

        // This is the line we added for the

        // rotation on the X axis;

        glRotatef(angleX,1.0,0.0,0.0);

       

        glColor3f(red,green,blue);

 

        glBegin(GL_TRIANGLES);

               glVertex3f(-0.5,-0.5,0.0);

               glVertex3f(0.5,0.0,0.0);

               glVertex3f(0.0,0.5,0.0);

        glEnd();

        glPopMatrix();

        angle++;

        glutSwapBuffers();

}

 

Now for the function that will process passive motion events. This function will change the value of angleX relatively to the mouse x coordinate value.

 

   

void processMousePassiveMotion(int x, int y)

{

 

        // User must press the SHIFT key to change the

        // rotation in the X axis

        if (specialKey != GLUT_ACTIVE_SHIFT)

{

 

               // setting the angle to be relative to the mouse

               // position inside the window

               if (x < 0)

                       angleX = 0.0;

               else if (x > width)

                       angleX = 180.0;

               else

                       angleX = 180.0 * ((float) x)/height;

        }

}

 

Finally, when the mouse leaves the window we will stop the animation. In order to do this we have to change the renderScene function again. It is a small change but here goes the renderScene function again.

 

   

// initially define the increase of the angle by 1.0;

float deltaAngle = 1.0;

...

void renderScene(void) {

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glPushMatrix();

        glRotatef(angle,0.0,1.0,0.0);

        glRotatef(angleX,1.0,0.0,0.0);

        glColor3f(red,green,blue);

 

        glBegin(GL_TRIANGLES);

               glVertex3f(-0.5,-0.5,0.0);

               glVertex3f(0.5,0.0,0.0);

               glVertex3f(0.0,0.5,0.0);

        glEnd();

        glPopMatrix();

        // this is the new line

        // previously it was: angle++;

        angle+=deltaAngle;

        glutSwapBuffers();

}

 

The processMouseEntry is the last function.  

   

void processMouseEntry(int state)

{

        if (state == GLUT_LEFT)

               deltaAngle = 0.0;

        else

               deltaAngle = 1.0;

}

Output

Popup Menus