Back to July Calendar 

Previous Entry Next Entry→

August 1, 2005

 

Wow, it's August, huh?  I'll be packing off to Seattle with the family tomorrow, so there will likely be a gap in this Sablog.

 

Back to OpenGL.  It's a bit less fun in the web venue since it doesn't seem to have an obvious way of internet delivery like java.  3DML  is software for doing web-based 3D modeling in XML.  It competes with  VRML, but we'll save that maybe for another day.

 

How about bringing in some interactive devices, like keyboard and mouse?  GLUT has a keyboard callback, glutKeyboardFunc() that is invoked when a key is pressed.  The pressed key is returned as an unsigned char which is typically processed with an if or a switch logic.  For example,

     glutKeyboardFunc(keyPress);

with the defined function

void keyPress(unsigned char key, int x, int y) {

   if(key == 'q' || key == 'Q' || key = '\27') exit(0);

}

The keyboard callback also gives the mouse coordinates, x and y are given in screen coordinates with the origin in the upper left corner.  Certain special keys are handled differently--which we'll get to later.

 

The mouse callback is similar, using glutMouseFunc() with, for example glutMouseFunc(mouseButton) to use, say

void mouseButton(int button, int state, int x, int y) {

   if(state == GLUT_DOWN&&button == GLUT_LEFT_BUTTON) exit(0);

}

Where this gets more interesting is when you can use the mouse to draw.  What follows is a code snippet that will draw a rectangle with diagonal corners corresponding to two clicks of the right mouse button and whose sides are horizontal and vertical (left button exits):

int hh; //global variable for viewport height
void
mouseButton(int button, int state, int x, int y) {

   static bool first = true;

   static int xx, yy;

   if(state == GLUT_DOWN&&button == GLUT_LEFT_BUTTON) exit(0);
   if(state == GLUT_DOWN&&button == GLUT_RIGHT_BUTTON) {
      if(first) {
         xx = x;
         yy = hh - y;
         first = !first;
      }
      else {
         first = !first;
         glClear(GL_COLOR_BUFFER_BIT);
         glBegin(GL_POLYGON);
           glVertex2i(x1,y1);
           glVertex2i(x1,hh - y2);
           glVertex2i(x2,hh - y2);
           glVertex2i(x2,y1);
         glEnd();
      }
   }

}

When the mouse is moved without any button pressed, that's a "passive motion event;" with a button pressed is called a "move event."  A mouse entering or leaving a window is an "entry event."  Who thinks up these crazy names? The callback functions corresponding to these take function pointers which return the mouse position:

     void glutMotionFunc(void (*f) (int x, int y));

     void glutPassiveMotionFunc(void (*f) (int x, int y));
     void glutEntryFunc(void (*f) (int state)); //returns GLUT_ENTERED or GLUT_LEFT

 

Apparently, a "widget" in OpenGL is a special kind of window with which a user can interact.  These include check boxes, text fields, menus, etc.  GLUT menus are typically pop-up type menus.  To specify such a menu you must

  • provide strings to describe each menu option

  • bind specific actions to each menu item

  • bind each menu item to a mouse button

Menus are usually created in main() or init().  Top level menus (you can have submenus) are created by the following function, which takes the name of the callback function for the menu and returns an integer identifier for the menu:

     int glutCreateMenu(void (*f) (int value))

The menu thus created will then be the current menu.  The current menu can be changed using

     void glutSetMenu(int id)

You add entries to the current menu with

     void glutAddMenuEntry(char *name, int value)

and the menu is then attached to a mouse button with

     void glutAttachMenu(int button)

All very sensible, no?  For example, the following snippet (part of main()) will use the right mouse button to pull down a menu with two enteries: clear the screen & end the program. 

glutCreateMenu(menu01);

glutAddMenuEntry("wipe screen", 1);

glutAddMenuEntry("exit program", 2);

glutAttachMenu(GLUT_RIGHT_BUTTON);

 

Since there is only one menu, we don't have to name it.  The menu01 callback function can then be defined by something like

void menu01(int value) {

  if(value == 1) {

    glClear(GL_COLOR_BUFFER_BIT);

    glutSwapBuffers(); //or glFlush()

  }

  if(value == 2) exit(0);

}

The submenu must be named according to it's entry in the its parent menu.  When the user moves the mouse to this entry, the submenu pops up.  The function is

 

void glutAddSubMenu(char *name, int menu)

 

This is pretty deadly boring, so I'll get to the example and call it a day.

 

Wow, this required more tedium the a barrel uncut cuticles.  Here's a snapshot of the animation which shows a rotating square in each window.  The complet code follows.  As you can see, there are two windows created: a single buffered window and a double buffered window (the difference is hardly discernable on my machine.)  The more obvious difference is that the application can be closed by pressing 'q' or 'Q' is the mouse if over the first window, or by right clicking to bring up the "quit" menu and then selecting it on the right window:

 

// single_double from OpenGl by Edward Angel
#include <GL/glut.h>
#include <math.h>

#define DEG_TO_RAD 0.017453
#define NULL 0

int singleb, doubleb; //window ids
GLfloat theta = 0.0;

void displays();
void reshapeIt(int, int);
void spinDisplay(void);
void mouse(int, int, int, int);
void keyIt(unsigned char, int, int);
void displays();
void displayd();
void quit_menu(int);

int main(int argc, char** argv) {
  glutInit(&argc, argv);
  //create a single buffered window
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);
  singleb = glutCreateWindow("single buffered");
  //initIt();
  glutDisplayFunc(displays);
  glutReshapeFunc(reshapeIt);
  glutIdleFunc(spinDisplay);
  glutMouseFunc(mouse);
  glutKeyboardFunc(keyIt);

  //create double buffered window to right
  glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
  glutInitWindowPosition(310,0);
  doubleb = glutCreateWindow("double buffered");
  //initIt();
  glutDisplayFunc(displayd);
  glutReshapeFunc(reshapeIt);
  glutIdleFunc(spinDisplay);
  glutMouseFunc(mouse);
  glutCreateMenu(quit_menu);
  glutAddMenuEntry("quit",1);
  glutAttachMenu(GLUT_RIGHT_BUTTON);

  //Enter even loop
  glutMainLoop();
}

void displays() {
  glClear(GL_COLOR_BUFFER_BIT);
  glBegin(GL_POLYGON);
    glVertex2f(cos(DEG_TO_RAD*theta),sin(DEG_TO_RAD*theta));
    glVertex2f(-sin(DEG_TO_RAD*theta),cos(DEG_TO_RAD*theta));
    glVertex2f(-cos(DEG_TO_RAD*theta),-sin(DEG_TO_RAD*theta));
    glVertex2f(sin(DEG_TO_RAD*theta),-cos(DEG_TO_RAD*theta));
  glEnd();
  glFlush();
}

void displayd() {
  glClear(GL_COLOR_BUFFER_BIT);
  glBegin(GL_POLYGON);
    glVertex2f(cos(DEG_TO_RAD*theta),sin(DEG_TO_RAD*theta));
    glVertex2f(-sin(DEG_TO_RAD*theta),cos(DEG_TO_RAD*theta));
    glVertex2f(-cos(DEG_TO_RAD*theta),-sin(DEG_TO_RAD*theta));
    glVertex2f(sin(DEG_TO_RAD*theta),-cos(DEG_TO_RAD*theta));
  glEnd();
  glutSwapBuffers();
}

void spinDisplay (void) {
  //increment angle
  theta += 2;
  if (theta > 360.0) theta -= 360.0;
  //draw single buffer window
  glutSetWindow(singleb);
  glutPostWindowRedisplay(singleb);
  //draw double buffer window
  glutSetWindow(doubleb);
  glutPostWindowRedisplay(doubleb);
}

/*The two windows share the mouse callback
function. This funcion allows the user to stop
the roation in both windows by setting the idle
function to NULL (and to resotre the spinning later.)
Although the two windows use the same callback, each has
its own state. Stopping rotation in one window doesn't
stop the stat of the other window, which would require
clicking the mouse in that window.*/

void mouse(int btn, int state, int x, int y) {
  if(btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    glutIdleFunc(spinDisplay);
  if(btn == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN)
    glutIdleFunc(NULL);
}

// this is a standard reshape callback to set up clipping window

void reshapeIt(int w, int h) {
  glViewport(0,0,w,h);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D(-2.0,2.0,-2.0,2.0);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
}

// Both a keyboard and a mouse callback are illustrated here

void keyIt(unsigned char key, int x, int y) {
  if(key == 'Q' || key == 'q') exit(0);
}

void quit_menu(int id) {
  if(id == 1) exit(0);
}