CS409/CS809 Tutorial 5: Better Keyboard Input

Setting up an OpenGL Project with the Obj Library of Files

1.      Start Microsoft Visual Studio 2013.  Select File / New / Project.  Under “Project Type”, choose Templates / Visual C++. Then choose a Win32 Console Application.  Change the name to Tutorial5 or some suitable name and change the location to a hard drive located on the computer (typically C or D).  You will need to remember this location for steps 3 and 5.  Make sure the “Create folder for project” checkbox is not checked and that the “Add to source control” is not checked.  Click OK.

·         If the Chose Location window keeps popping up again, you aren’t allowed to save files at the location you chose.  Select a different location, such as the desktop.

·         Do not put your program on one of the external drives like H.  The connection is slow, and your program may not compile correctly.

2.      Click Next> or Application Settings (they go to the same place).  Check the “Empty Project” box.  Make sure the “Security development lifecycle check” is not checked.  Click Finish.

3.      Go to http://www2.cs.uregina.ca/~anima/409/Terms/201810/Tutorials/index.html and download the Tutorial5.zip and ObjLibrary.zip files to your project directory.

4.      Unzip the 2 zip files.  You will have a folder called ObjLibrary and 14 other files.

5.      In Visual C++, go to Project/Add Existing Item… and select main5.cpp (and holding control, continue to select), Sleep.h, Sleep.cpp, and GetGlut.h.

6.      Ensure that Solution Explorer is visible (View / Solution Explorer). In the Solution Explorer, select Tutorial5 (the project name) and right click it.  Choose Add / New Filter.  A new folder icon should appear in the Solution Explorer.  Name it ObjLibrary.  (This creates an imaginary folder in the project, but it would not create a Windows folder on the computer).

7.      In the Solution Explorer, right click on the ObjLibrary folder icon and select Add / Existing item.  Go into the ObjLibrary folder on Windows and select all files.  The files should appear in the Solution Explorer inside the ObjLibrary folder.  You should get 26 files.

8.      Compile and run the original program.  You should see 2 windows: a text console and a graphical window labeled “Better Keyboard Input”.  There is a set of positive X/Y/Z axes and the spiky balls and buckets from Tutorial 4 in the middle of this second window.  Exit the windows.  Closing the text window will close the graphical window, and usually vice versa.  Closing the text window (first) may also cause the program to crash.

9.      For the remainder of the instructions, it is assumed that you compile and run regularly so that you can see what each change does.

Continuous Keyboard Input

10.  Declare a global variable (before the main function) of the float or double type to represent the rotation of the spiky balls.
double spiky_rotation = 0.0;
Change the
display function to display the spiky balls rotated around the X axis by spiky_rotation degrees.
            glRotated(spiky_rotation, 1.0, 0.0, 0.0);

11.  Add a case to the switch statement in the keyboard function for the [T] key (case 't':).  If the [T] key is pressed, the rotation of the spiky balls should be increased by 2.0 degrees.
  case 't':
      
spiky_rotation += 2.0;
       break;

12.  Test you program.  You will observe that when you press [T], the spiky balls immediately rotate by a small amount (2 degrees).  Then, about half a second later (how long depends on how you have set the options for your computer), the spiky ball will begin to rotate smoothly.  This is because the keyboard function is only called when the key is first pressed down, not while it is held down.  Thus, the spiky balls rotate 2 degrees when you first press the [T] key.  Eventually, if you keep holding the key, the operating system will start sending key-pressed messages to your program at a steady rate and the spiky balls will begin to rotate smoothly.  This can be seen when you hold down a key, say [A], on the keyboard.  First, a single 'A' appears.  Then, after about half a second, you get a whole line of 'A's.  Although this is fine for typing, it is undesirable in games, as the player may need to act immediately in order to avoid some danger.  We will fix this by storing the state (pressed or not pressed) of each key in our program and checking those values instead of waiting for keyboard messages.

13.  Declare a global array (before the main function) of 256 elements of the bool type:
bool key_pressed[256];

14.  Initialize the elments of the array to false in the init function.

15.  Add a line to the end of the keyboard function to record the key pressed in the array.  Use the key parameter as the array index:
  key_pressed[key] = true;
We can do this because there are only 256 possible values for an
unsigned char, and we have 256 elements in our key_pressed array.

16.  Add a check to the update function to rotate the spiky balls if the [T] key is pressed.  Use 't' as the array index.
  if(key_pressed['t'])
       spiky_rotation += 2.0;

Remove the check for the
[T] key from the keyboard function.  Do not remove the check for the [ESC] key (case 27:).

17.  Test you program.  The spiky balls should start rotating when you press the [T] key and never stop again.

18.  Add a function named keyboardUp after the keyboard function.  This function should record which key was released.
void keyboardUp (unsigned char key, int x, int y)
{
  key_pressed[key] = false;
}
Add a prototype before the main function:
void keyboardUp (unsigned char key, int x, int y);

19.  Add a line to main directly below the call to glutKeyboardFunc to call glutKeyboardUpFunc with your new function keyboardUp as a parameter.  This will cause the keyboardUp function to be called whenever a key is released.
  glutKeyboardUpFunc(keyboardUp);

20.  Test you program.  The spiky balls should start rotating when you press the [T] key and stop when you release the key.  However, if you hold [SHIFT] while you release [T], the spiky balls will not stop.  This is because GLUT makes a distinction between upprcase and lowercase letters, and thus it belives that the key you pressed ('t') was not the same as the key you released ('T').  This is a problem we will have to fix in our program.

21.  Add a check to the keyboard and keyboardUp functions to force all letters to be treated as upppercase.  It should be before you update the key_pressed array:
  if(key >= 'a' && key <= 'z')
       key = key - 'a' + 'A';

Also change the test in
update to be for 'T' instead of for 't'.  A more sophisticated program would also check for other symbols on the same keys, such as ',' and '<'.

22.  Test your program.  The spiky balls should start rotating when you press [T] and stop rotating when you release [T], regarless of when you were are were not pressing [SHIFT].

Continuous Special Key Input

23.  The technique above works for most keys.  However, as we saw in Tutorial 1, there are some keys (such as the arrow keys) that have to be handled using different functions.

24.  Declare a global variable (before the main function) of the float or double type to represent the vertical (Y) position of the buckets.
double bucket_y = 0.0;
Change the
display function to display the buckets translated in the Y direction by this value.  Use only the one existing glTranslate* command.

25.  Add functions named special and specialUp after the keyboardUp function.  These functions will detect when special keys are pressed and released.
void special(int special_key, int x, int y)
{
  switch(special_key)
  {
  case GLUT_KEY_UP:
       break;
  }
}

void specialUp (int special_key, int x, int y)
{
  switch(special_key)
  {
  case GLUT_KEY_UP:
       break;
  }
}

Add prototypes for these new functions just after the prototype for
keyboardUp:
void special (int special_key, int x, int y);
void specialUp (int special_key, int x, int y);

26.   In the main function, add a calls to glutSpecialFunc and glutSpecialUpFunc after the call to glutKeyboardUpFunc.  These will set the functions we are using when the special keys (such as the arrow keys) are pressed and realeased:
  glutSpecialFunc(special);
  glutSpecialUpFunc(specialUp);

27.  Add a command to the GLUT_KEY_UP case of the special function.  It should increase the Y position of the buckets by 0.1 units.
       bucket_y += 0.1;

28.  Test your program.  As you might expect, the keyboard input is not handled well.  When you press the up arrow, the buckets should immediately move upwards slightly.  Then, after a pause, they will move upwards at a steady rate.  Fixing this is similar to what we did with the [T] key, but with an added twist.  The special keys do not have a nice indentifiers that we can use as indexes, so we will have to make constants to use instead.

·         There are GLUT constants for the special keys, but their values vary depending on implementation.  This means that you should never use them as array indexes, or you might get a program that works properly on one computer but not on another.

29.  Create 2 global constants, the first named KEY_UP_ARROW and the second named KEY_COUNT.  Change the size of the key_pressed array to KEY_COUNT.
const unsigned int KEY_UP_ARROW = 256;
const unsigned int KEY_COUNT    = 257;
bool key_pressed[KEY_COUNT];
The first 256 elements of the key_pressed array still store the states of the normal keys, but there is now an extra element for the up arrow.

30.  Add a line to the special function that sets element KEY_UP_ARROW of the key_pressed array to true when the up arrow is pressed:
       key_pressed[KEY_UP_ARROW] = true;

31.  Add a check to update to move the bucket upwards while the up arrow is pressed.
  if(key_pressed[KEY_UP_ARROW])
       bucket_y += 0.1;

Remove the command to move the buckets in the
special function.

32.  Test your program.  When you press the up arrow, the bucket should begin moving upward and never stop.

33.  Add a command to the specialUp function to stop moving the bucket when the up arrow is released:
       key_pressed[KEY_UP_ARROW] = false;

34.  Test your program.  The bucket should move upward while you hold down the up arrow and stop when you release it.  So far, there is no way to move the buckets back down.

Responding Only Once to a Key Being Held Down

35.  Add an another constant for the [END] key, named something like KEY_END.  You will also have to increase KEY_COUNT to make room for the new value in the array.
const unsigned int KEY_UP_ARROW = 256;
const unsigned int KEY_END      = 257;
const unsigned int KEY_COUNT    = 258;

36.  Add another case in the special function for GLUT_KEY_END.  It should first check if the [END] key is already pressed and, if not, it should set the Y position for the buckets to 0.0.  Then, whether or not the key was already pressed, it should set they key to pressed.
  case GLUT_KEY_END:
       if(!key_pressed[KEY_END])
            bucket_y = 0.0;
       key_pressed[KEY_END] = true;
       break;

37.  Test the program.  The buckets should move smoothly up while the up arrow is held and jump back down the first time [END] is pressed.  No matter what you do, the buckets will never move down again.

38.  Add a case for the [END] key to the specialUp function that marks the [END] key as not pressed.
  case GLUT_KEY_END:
       key_pressed[KEY_END] = false;
       break;

39.  Test the program.  Now the buckets should jump back down every time the [END] key is pressed.  However, holding [END] will not make them move back down each frame.

Exercises

40.  Add a command to move the bucket downward when the down arrow (GLUT_KEY_DOWN) is pressed.  You will have to add another constant for the key and increase KEY_COUNT to make room for it in the array.  What happens if you hold the up and down arrows at the same time?

41.  Add commands to move the bucket along the Z axis on the left and right arrow keys.  The bucket Z position should also be reset on the [END] key.  What happens if you hold the all of the arrows at the same time?

42.  Add a command to rotate the spiky balls around their Y axis while the [Y] key is held down.  You will need a second glRotated command, which should be after the one to rotate around the X axis.  Try rotating the spiky balls around the Y axis, rotating them around the X axis a bit, and then rotating them around the Y axis again.

43.  Add a command to reset the rotation of the spiky balls when the [R] key is pressed.  The reset should only happen when you press [R], not when you hold it.  Note that you will have to check the value in key_pressed before you change it.