User Input and Animation
Adapted from Stephen J. Chenney's Tutorial
Modified by Yu-Chi Lai at 2005
This tutorial will build on the previous tutorial, adding animation and
user control via the keyboard.
- Rotating
Our Cube
- Key Presses
in FLTK
- Build and
Run the Program
- Idle Callback
for Animation
- Build and
Run the Program Again
Lets get started.
Step 1: Rotating Our Cube
We're going to need to keep track of the orientation of our cube so we'll
need to add some member variables to the MyWindow class. Add these lines to MyWindow.h.
float rotation;
float rotationIncrement;
rotation will keep track of our
cubes horizontal rotation and rotationIncrement
is the rate that we will change that rotation. This will make it easy
for us to change the rotation speed later if we want. We need to initialize
these variables so our cube starts in the same orientation each time we
run our program. We also need to set the amount we're going to rotate
with each key press. The constructor for MyWindow is the obvious place to add this code.
Our modified constructor should look like this:
MyWindow::MyWindow(int width, int height, char* title) : Fl_Gl_Window(width, height, title)
{
mode(FL_RGB | FL_ALPHA | FL_DEPTH | FL_DOUBLE);
rotation = 0.f;
rotationIncrement =10.f;
}
We now need to make an OpenGL call to use our rotation value. We'll
place this after the gluLookAt call
in draw() but before we make the
call to DrawCube().
. . .
gluLookAt(0, 0, 3, 0, 0, 0, 0, 1, 0);
glRotatef(rotation,0, 1, 0);
// draw something
DrawCube();
. . .
glRotatef() takes the angle to rotate
(in degrees) and the axis to rotate around (the positive y axis in our case).
Our cube is all set to rotate. We just need to modify the horizontal
and vertical angles while our program is running. We'll let the user
do this with the arrow keys.
Step 2: Key Presses in FLTK
FLTK uses an event callback scheme to let us process user input and system
events. To get a chance to react to these events we have to define
a callback method. For the window classes in FLTK this method is called
handle. handle takes and integer
that is a constant for the type of event that has occurred and should return
a non-zero value if you handled the event. If you didn't take care
of the event you should call the handle method of the base class (Fl_Gl_Window in this case) to give it a chance
to react to the event. We first need to add a method declaration for
handle to the MyWindow class. We need to add this to
MyWindow.h right after the declaration
for DrawCube().
. . .
virtual void draw();
void DrawCube();
virtual int handle(int event);
float rotation;
float rotationIncrement;
. . .
The definition for handle should be added to bottom of MyWindow.cpp.
int MyWindow::handle(int event)
{
switch (event) {
case FL_FOCUS:
case FL_UNFOCUS:
return 1;
case FL_KEYBOARD:
int key = Fl::event_key();
switch (key) {
case FL_Left:
rotation -= rotationIncrement;
redraw();
return 1;
case FL_Right:
rotation += rotationIncrement;
redraw();
return 1;
case ' ':
animating = !animating;
return 1;
}
}
return Fl_Gl_Window::handle(event);
}
Our handle method first sees if
we're interested in the type of event that has occurred. You'll notice
we respond to the FL_FOCUS and FL_UNFOCUS
events but don't actually do anything. We need to do this to make
sure FLTK sends all the keyboard events to our window. If the event
was a keyboard event we then see if the key was the left or right arrow
keys. If it was we modify the rotation of our cube. We then
call redraw(). This tells
FLTK that our window needs to be updated which will call are draw() method to get called. If the event
was not a key press or was not a key we were interested in we call the handle()
method of our base class so it can react to the event if it wants.
We need to add one more included file to the top of MyWindow.cpp. We need the FLTK header
with the definition of Fl::event_key().
#include
<Fl/Fl.h>
Ok we should be set to rotate our cube with the keyboard.
Step 3: Build and Run the Program
Choose Build -> Build Solution
to compile and link the program and Debug
-> Start Without Debugging to run it.
You should see an OpenGL window just like this. By pressing the
left or right arrow keys the cube should now rotate and you should see
the other sides of the cube rendered in different colors.
Step 4: Idle Callback for Animation
The idle callback functionality has been encapsulated
into a widget that might be easier to use. See the OpenGL
Survival Guide.
Now that we can rotate the cube from the keyboard lets see about getting
the cube to rotate on its own. Basically we need to continuously
update our rotation angle wait for the window to refresh and then update
it again. Luckily FLTK provide a means for us to do such a thing.
FLTK provides what it calls idle callbacks. Idle callbacks work
much like the handle() callback we already used except this callback will
only be called when our window isn't busy doing something else (like handling
a key press or redrawing the contents of the window).
First we're going to add a boolean flag to our window class that will
tell us whether we want to rotate the cube with the idle callback or not.
We'll later type this boolean to the space bar so we can turn the animation
on and off as we wish. Add the boolean declaration to MyWindow.h after the other member declarations.
. . .
float rotation;
float rotationIncrement;
bool animating;
. . .
The next thing to do is to create a function that FLTK will call when it
is idle. This function needs to be a global function and not a member
of our class because FLTK will want to get a pointer to the function.
Here's the definition of our idle callback that we'll add to MyWindow.cpp
just before the constructor.
void IdleCallback(void* pData)
{
if (pData != NULL)
{
MyWindow* pWindow = reinterpret_cast<MyWindow*>(pData);
if (pWindow->animating) {
pWindow->rotation += pWindow->rotationIncrement / 100;
pWindow->redraw();
}
}
}
This function takes a void pointer which will actually be a pointer to our
window. We check if the pointer is not NULL and then cast it to be a MyWindow pointer. We check to see if
our cube is animating and update its rotation if it is. Notice that
I've scaled down the rotation increment. Idle callbacks will happen
much quicker than the arrow keys can be pressed so we need to slow down
the animating rotation speed.
We now need to tell FLTK to call our idle callback when the window is not
otherwise busy. We'll add this call to the MyWindow constructor and why were there we
can initialize the animating boolean
we added to our class. Our revised constructor should look like this.
MyWindow::MyWindow(int width, int height, char* title)
: Fl_Gl_Window(width, height, title)
{
mode(FL_RGB | FL_ALPHA | FL_DEPTH | FL_DOUBLE);
rotation = 0.f;
rotationIncrement = 10.f;
animating = false;
Fl::add_idle(IdleCallback, this);
}
add_idle takes a pointer to the
function to call when the window is idle and a void pointer which will be passed to the idle
callback. We've placed a pointer to our window in the void
pointer so we can update the rotation from the idle callback.
The only thing left to do is tie our animating boolean to the space bar.
We'll need to modify our handle()
method to do this.
int MyWindow::handle(int event)
{
switch (event) {
case FL_FOCUS:
case FL_UNFOCUS:
return 1;
case FL_KEYBOARD:
int key = Fl::event_key();
switch (key) {
case FL_Left:
rotation -= rotationIncrement;
redraw();
return 1;
case FL_Right:
rotation += rotationIncrement;
redraw();
return 1;
case ' ':
animating = !animating;
return 1;
}
}
return Fl_Gl_Window::handle(event);
}
As you can see we just need to add a case for the space character and flop
the animating flag if the key pressed
was the space bar.
Step 5: Build and Run the Program
To test the new version of out program we choose Build ->
Build Solution to compile and link the program and Debug
-> Start Without Debugging to run it.
Source code for this tutorial.
|