GLUT Part 2
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();
}
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:
void mai
n(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:
#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();
}
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)
{
{
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
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();
}
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