Course list http://www.c-jump.com/bcc/

Lab 8 -- GLUT keyboard and mouse events


  1. FreeGLUT API
  2. Getting ready
  3. GLUT callback functions
  4. Things to do -- add Wavefront OBJ support
  5. Things to do -- main( )
  6. Things to do -- keyboard events
  7. Things to do -- mouse button events
  8. Things to do -- mouse move and drag events
  9. Things to do -- window aspect ratio
  10. How to submit

FreeGLUT API



Getting ready



GLUT callback functions



Things to do -- add Wavefront OBJ support


  1. Since we are about to start moving in 3D space, it is important to add a plane surface for visual guidance while moving in virtual world. Recall that in Lab 7, Diffuse and specular light , we used Wavefront OBJ loader to load and display various models.

  2. Add Wavefront OBJ support to c262_lab08.cpp. (Hint: I would use WinMerge to do the initial editing.) First, comment out the existing #include directive:

    
    //#include "../../../common/Objects/dragon.h"
    
    
  3. To allow to testing of our program with a variety of objects, add code

    
    //------------------------------------------------------------------
    // HOW TO TEST DIFFERENT MODELS:
    //------------------------------------------------------------------
    // Include one of the models defined in a header file,
    // -or-
    // load te model from the OBJ file by defining MODEL_OBJ_FILENAME
    //------------------------------------------------------------------
    
    //#include "../../../common/Objects/dragon.h"
    //#include "../../../common/Objects/phlegm_small.h"    // UPDATE! - look at this file if you haven't!  
                                // It has the vertices[], normals[] and indices[] in it
    //#include "../../../common/Objects/teapot.h" // use scale_amount = 10.0f;
    //#include "../../../common/Objects/cube.h" // scale_amount = 0.5f;
    
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/bunny-1500.obj"     // scale_amount = 100.0f;
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/bunny-5000.obj"    // scale_amount = 100.0f;
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/cow-1500.obj"      //scale_amount = 40.0f;
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/cow-5000.obj"   //scale_amount = 40.0f;
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/cup.obj"          // scale_amount = 0.01f;
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/dragon-1500.obj"  // scale_amount = 40.0f;
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/dragon-5000.obj"    // scale_amount = 2.0f
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/lamp600.obj"        // scale_amount = 0.007f
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/lego.obj"            // scale_amount = 0.001f
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/shammy.obj"          // scale_amount = 0.001f
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/snakie.obj"          // scale_amount = 0.001f
    //#define MODEL_OBJ_FILENAME "../../Models/Spheretree/teapot.obj"      // scale_amount = 0.001f
    
    //#define MODEL_OBJ_FILENAME "../../Models/Misc/tetrahedron.obj.txt" // crashes the driver
    //#define MODEL_OBJ_FILENAME "../../Models/Misc/cube5.obj" // a bigger cube (5 units) -- scale_amount = 0.15f;
    //#define MODEL_OBJ_FILENAME "../../Models/Misc/cube_blender.obj.txt" // works fine
    //#define MODEL_OBJ_FILENAME "../../Models/Misc/dragon.obj" // scale_amount = 40.0f;
    //#define MODEL_OBJ_FILENAME "../../Models/Misc/teapot.obj" // from http://people.sc.fsu.edu/~jburkardt/data/obj/obj.html
    #define MODEL_OBJ_FILENAME "../../Models/Misc/car.obj" // scale_amount = 3.0f;
    #define MODEL_PLANE_FILENAME "../../Models/Misc/plane.obj"
    
    

    directly above the line of code that declares the object_model variable:

    
    Model* object_model;
    
    
  4. Uncomment the line

    
    Model* plane_model;
    
    
  5. At the end of the render() function body, immediately before the glutSwapBuffers() call, add code to draw a wireframe ground:

    
        // Do everything related to the floor plane
        glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); 
        // Note: you can probably *store* these matrices to improve efficiency - since it's not moving.
    
        // The plane is a square with side 0.5 units
        // sitting on the Z-X plane
        glm::mat4 mtx_trans = glm::translate(
            glm::mat4( 1.0f ),
            glm::vec3( 0.0f, -0.5f, 0.0f ) // push it down
            );
    
        plane_model->mM = glm::scale(  // Scale second
            mtx_trans,              // Translate first
            glm::vec3( 100.0f, 100.0f, 100.0f )
            );
    
        camera->set_uniform_view_perspective( plane_model->shader->program_ID );
        plane_model->render();
    
    
  6. We also need a couple of changes in the main() function as follows:

    First, replace the lines

    
        object_model->set_geometry(vertices, num_vertices*3*sizeof(GLfloat));
        object_model->set_normals(normals, num_normals*3*sizeof(GLfloat));
        object_model->set_index_buffer(indices, num_indices*sizeof(GLuint));
    
    

    with the lines

    
    #ifdef MODEL_OBJ_FILENAME
        WavefrontObjLoader loader( MODEL_OBJ_FILENAME );
        if ( !loader.load() ) {
            std::cout << "Failed to load OBJ file, exiting!" << std::endl;
            return 1;
        }
        assert( loader.num_vertices );
        object_model->set_geometry( loader.vertices, loader.num_vertices * 3 * sizeof( GLfloat ) );
        if ( loader.num_normals ) { object_model->set_normals( loader.normals, loader.num_normals * 3 * sizeof( GLfloat ) ); }
        if ( loader.num_uvs ) { object_model->set_texture_coordinates( loader.UVs, loader.num_uvs * 2 * sizeof( GLfloat ) ); }
        if ( loader.num_indices ) { object_model->set_index_buffer( loader.indices, loader.num_indices * sizeof( GLuint ) ); }
    #else
        object_model->set_geometry(vertices, num_vertices*3*sizeof(GLfloat));
        object_model->set_normals(normals, num_normals*3*sizeof(GLfloat));
        object_model->set_index_buffer(indices, num_indices*sizeof(GLuint));
    #endif
    
    

    The above code lets us switch between objects declared in the C++ header files and those loaded from the OBJ files.

    Second, immediately after the line

    
        object_model->upload_2_server();
    
    

    add the following:

    
        plane_model = new Model( solid_color_shader );
    
    #ifdef MODEL_OBJ_FILENAME
        WavefrontObjLoader loader2( MODEL_PLANE_FILENAME );
        if ( !loader2.load() ) {
            std::cout << "Failed to load plane.obj file, exiting!" << std::endl;
            return 1;
        }
        assert( loader2.num_vertices );
        plane_model->set_geometry( loader2.vertices, loader2.num_vertices * 3 * sizeof( GLfloat ) );
        if ( loader2.num_normals ) { plane_model->set_normals( loader2.normals, loader2.num_normals * 3 * sizeof( GLfloat ) ); }
        if ( loader2.num_uvs ) { plane_model->set_texture_coordinates( loader2.UVs, loader2.num_uvs * 2 * sizeof( GLfloat ) ); }
        if ( loader2.num_indices ) { plane_model->set_index_buffer( loader2.indices, loader2.num_indices * sizeof( GLuint ) ); }
    #else
        plane_model->set_geometry(vertices, num_vertices*3*sizeof(GLfloat));
        plane_model->set_normals(normals, num_normals*3*sizeof(GLfloat));
        plane_model->set_index_buffer(indices, num_indices*sizeof(GLuint));
    #endif
        
        plane_model->set_light(light);
        plane_model->upload_2_server();
    
    
  7. Compile and run the program. Make sure everything works as expected. Try different models by uncommenting the corresponding MODEL_OBJ_FILENAME definitions. Remember to change the scale_amount variable if the object appears to be too small or too big on the screen.

     


Things to do -- main( )


  1. In main(), find glutInitDisplayMode() and add GLUT_MULTISAMPLE:

    
        glutInitDisplayMode( GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH | GLUT_MULTISAMPLE );
    
    

    Multisampling helps achieving full-screen antialiasing by averaging pixel colors on the edges of polygons.

  2. You need to enable multisampling to activate this feature:

    
        glEnable( GL_MULTISAMPLE );
    
    
  3. Increase window size to 1024 x 1024 (if your screen resolution is permitting):

    
        glutInitWindowSize( 1024, 1024 );
    
    

     


Things to do -- keyboard events


  1. In a typical FPS (First-Person Shooter) game, the WASD keys allow the user to move around. Internally this is achieved by translating the coordinates of the scene along the X or Z axes.

  2. Locate keyboard_down() in c262_lab08.cpp. This function takes in a character (the one that was pressed) as well as the x and y position of the mouse when the key was pressed. To implement some basic key controls, let's start with a switch like this:

    
        switch (key) {
            case 033: // octal equivalent of the Escape key
                glutLeaveMainLoop();
                break;
            case 'w':
                // increase the camera's Z velocity
                break;
            case 's':
                // decrease the camera's Z velocity
                break;
            case 'a':
                // decrease the camera's X velocity
                break;
            case 'd':
                // increase the camera's X velocity
                break;
        }
    
    

    Since we want to control smooth and continuous camera movement, the kyes that we press on the keyboard should not update the camera position directly. So why not? It's because the keyboard sends the "key-press" events at a rate of 10 times per second. If we change the position at this rate, the motion on the screen will be noticeably jerky. Also, this kind of camera movement won't be consistent across multiple OS and hardware platforms.

    Instead, we will have our camera set stationary to begin with, but when it starts moving, the motion is controlled by the velocity. The actual speed will be a combination of the motion velocities along the X and Z axes. For example, if the user pushes down the W key, it is a signal for the camera to start moving forward. This can be done by a single line of code like this:

    
            case 'w':
                // increase the camera's Z velocity
                zVel = max_speed;
                break;
            case 's':
                // decrease the camera's Z velocity
                zVel = -max_speed;
                break;
            case 'a':
                // decrease the camera's X velocity
                xVel = max_speed;
                break;
            case 'd':
                // increase the camera's X velocity
                xVel = -max_speed;
                break;
    
    

    Add code for the remaining keys according to the comments inside the switch statement listed above. Do it!

    
        switch ( key ) {
            case 033:
                glutLeaveMainLoop();
                break;
            case 'w': zVel=0; break;
            case 's': zVel=0; break;
            case 'a': xVel=0; break;
            case 'd': xVel=0; break;
        }
    
    
  3. Next, when one of the WASD keys is released, the camera motion should stop. We capture this event in keyboard_up() function. Go ahead and set the X and Z velocities to zero. Do it!

     


Things to do -- mouse button events


  1. As in a typical FPS game, we implement zooming by a mouse button click. When the mouse button is pressed, the view zooms in. As soon as the mouse button is released, the zoom level goes back to normal.

  2. In OpenGL, the visual effect of zooming is set by the projection matrix transformation. Originally, projection is set in FPSCamera class constructor:

    
        mP = glm::perspective( fov, aspect, nearPlane, farPlane );
    
    
  3. To control the projection in support of different zooming levels, we add new member function to the FPSCamera class. Make sure to add function declaration in FPSCamera.h header file:

    
        void set_perspective( GLfloat fov, GLfloat aspect, GLfloat nearPlane, GLfloat farPlane );
    
    

    and then add the function implementation to the FPSCamera.cpp:

    
    void FPSCamera::set_perspective( GLfloat fov, GLfloat aspect, GLfloat nearPlane, GLfloat farPlane )
    {
        mP = glm::perspective( fov, aspect, nearPlane, farPlane );
    }
    
    
  4. Save all changes and open c262_lab08.cpp. Take a look at the callback_mouse_button() function. Here, we simply set the zooming flag to true or false. The flag is actually used by the render() function. If zooming mode is on, the FOV (field of view of the camera) is reduced. If zoom is off, the FOV is restored. We maintain the FOV range between 5.0f and 60.0f degrees.

  5. The render() function invokes zoom_in() to reduce the FOV. Find zoom_in() and add this code:

    
        // FOV == field of view of the camera
        // zooming is set by the callback_mouse_button()
        fov -= 10.0f;
        if ( fov < 5.0f ) fov = 5.0f; // don't allow fov less than 5
        camera->set_perspective( fov, 1.0f, 1.0f, 1000.0f );
    
    
  6. Similarly, locate zoom_out() function and add code to increase the FOV by 10.0f degrees at a time. Make sure to keep the max FOV at 60.0f degrees. Do it!

     


Things to do -- mouse move and drag events


  1. A common use of the mouse is to allow the viewer to look around, i.e. turn head left/right and also look up and down. In FPS game the behavior is the same in both zooming mode (when the mouse button is pressed down), and during a "passive" mouse movement within the window. Since GLUT generates two different events to distinguish mouse drags from the regular motion, we must specify the handler function twice to connect it with both mouse drag and mouse move events. Our handler function is named look():

    
        glutPassiveMotionFunc( look );           // when mouse moves
        glutMotionFunc( look );                  // when mouse drags around
    
    
  2. To look around, the program must determine if the mouse is moving left/right, up/down, and by how much. To do all this, the program needs to

    
    void look( int x, int y )
    {
        if ( is_first_time ) {
            // Check for zooming mode, but do not allow the model
            // jump off the screen when the mouse enters the window from an outside
            // region of screen. Thus, to activate the mouse, you need to click the
            // mouse to zoom the view AND move it a little:
            if ( zooming ) is_first_time = false;
            prev_mouse_X = GLfloat( x );
            prev_mouse_Y = GLfloat( y );
            return;
        }
        //...
    
    
  3. By subtracting the current mouse position from the old one, we determine how much of a difference there is in the mouse movement (do it!):

    
        // 3) ...
        GLfloat deltaX = prev_mouse_X - x;
        GLfloat deltaY = prev_mouse_Y - y;
        if ( deltaX < -6.0f ) { deltaX = -1.0f; }
        if ( deltaX >  6.0f ) { deltaX =  1.0f; }
        if ( deltaY < -6.0f ) { deltaY = -1.0f; }
        if ( deltaY >  6.0f ) { deltaY =  1.0f; }
    
    
  4. Follow the instructions provided inside the look() function comments and finish its implementation. Do it! Here's a sample:

    
    void look (int x, int y) {
        if (is_first_time) {
            //is_first_time = false;
            // Check for zooming mode, but do not allow the model
            // jump off the screen when the mouse enters the window from an outside
            // region of screen. Thus, to activate the mouse, you need to click the
            // mouse to zoom the view AND move it a little:
            if ( zooming ) is_first_time = false;
            prev_mouse_X = x;
            prev_mouse_Y = y;
            return;
        }
        GLfloat deltaX = prev_mouse_X - x;
        GLfloat deltaY = prev_mouse_Y - y;
     
        if ( deltaX < -6.0f ) { deltaX = -1.0f; }
        if ( deltaX >  6.0f ) { deltaX =  1.0f; }
        if ( deltaY < -6.0f ) { deltaY = -1.0f; }
        if ( deltaY >  6.0f ) { deltaY =  1.0f; }
     
        if (zooming) {
            deltaX /= 10.0f;
            deltaY /= 10.0f;
        }
     
        if ( camera->is_flying ) {
            camera->rotateX((-deltaY)/( 50 * 5 ));
            camera->rotateY((-deltaX)/( 20 * 5 ));
            camera->rotateZ((-deltaX)/( 70 * 5 ));
        }
        else {
            camera->rotateX( deltaY / 150.0f );
            camera->rotateY( deltaX / 150.0f );
        }
       
        prev_mouse_X = x;
        prev_mouse_Y = y;
    }
    
    

     


Things to do -- window aspect ratio



How to submit