Back to July Calendar 

Previous Entry Next Entry→

August 12, 2005

 

In the teen vernacular, "Seattle owns Palm Desert," though one might feel differently come December.  We floated down the Tolte river on inflatably firm matrices and then stopped frequently by the path to pluck on succulent blackberries as we strolled back.

 

All the while, naturally, I was thinking how I might add gravity to the motion of some of the Mondrian shapes.  Here's what I came up with:

 

/////////////////////////////////////////////////////////////////
class GravityShape extends MovingShape {
  int x_velocity = 2;
  int y_velocity = 0;
  int y_acceleration = 2;
  // int delta_t = 1; //Note: for a smoother, more versatile motion, we
  // may want to decrease this and go to casting floats to integer?

  int max_x; // max x value
  int min_x; // min x value
  int max_y; // max y value
  int min_y; // min y value
  /////////////////////////////////////////////////////////////////
 
public GravityShape(int x,int y,int w,int h,byte s, Color c) {
    super(x,y,w,h,s,c); // call superclass constructor
    max_x = 300-width;
    min_x = 0;
    max_y = 300;
    min_y = height;
  }
  // override Movement()
  public void Movement() {
    ulCornx += x_velocity;
    if(ulCornx >= max_x) {
      ulCornx = max_x;
      x_velocity *= -1;
    }
    if(ulCornx < 0) {
      ulCornx = 0;
      x_velocity *= -1;
    }
    y_velocity += y_acceleration;
    if (ulCorny + y_velocity >= max_y) {
      ulCorny = max_y - y_velocity; // don't overshoot the boundary
      y_velocity *= -1;
    }
    else ulCorny += y_velocity;
  }
}

 

 

===*/?/

Back to OpenGL again.  Remember the "pipeline?"  I found this diagram at the hyperlink to left:

where they describe the state-based pipeline.  If OpenGL is in the standard "immediate mode" for rendering, primitives enter the pipeline as soon as they are created in the program.  Having done so, all that remains is their image on the screen.  To redisplay it, you have to recreate and reprocess it, potentially a overly much consumption of time-consuming operations.  Alternatively, OpenGL can be in "retained mode,"  where collections of primitives can be "compiled" as objects on a stack called a "display list" via the function glNewList() which takes an unsigned integer id name and a GLenum which specify either GL_COMPILE or GL_COMPILE_AND_EXECUTE mode.  The display list can then be executed by glCallList(), which takes the unsigned integer id name as its argument.  Once created, a display list cannot be altered, except to delete it with glDeleteLists(GLuint fist, GLsizei number).

 

Typically, one works with a number of display lists simultaneously through functions such as

glListBase()

which sets an offset for

glCallLists(GLsizei num, GLenum type, GLvoid *lists),

which executes num display lists from the array lists of integers of type. 

glGenLists()

will return  the first of n consecutive integer ids available for new display items. 

 

One common application of display lists is the rendering of text via graphics objects.  The Times-Roman 10-point font, for example, will require 256 display lists (one per character) which can be created with

           baseTR10 = glGenLists(256);

and then set up with

for(i=0;i<256;i++) {

  glNewList(baseTR10+i, GL_COMPLETE);

  glBitMapCharacter(GLUT_BITMAP_TIMES_ROMAN_10, i);

  glEndList();

}

Since this is a GLUT bitmap font, the raster position is automatically moved right by the width of one character.

Next, set the list base

    glListBase(baseTR10);

so that we can refer to each display list by its ASCII code.  So, given a character string

    char *text;

we can draw it with one function call:

glCallLists((GLint) strlen(text), GL_UNSIGNED_BYTE, text);

Later we'll maybe see how to use OpenGL transformations to solve the text problem even more efficiently.

 

Another example considered by Angel is the construction of a cartoon face with eyes, ears, mouth and nose.  The code for it might look like

glNewList(FACE, GL_COMPILE);

  glCallList(EYE);  //position the first eye, and so on...

  glCallList(EYE);

  glCallList(EAR);

  glCallList(EAR);

  glCallList(NOSE);

  glCallList(MOUTH);

glEndList();

"Picking" is the operation of located an object (a display list, or other, potentially more complicated choices of elements) on the screen, and is surprising difficult to manage.  The exact location of the interior of an object may be a fuzzy concept if it is drawn according to some rules relative to, say, its upper left corner.  Then there is the problem of objects overlapping.  The pipeline approach in OpenGL does not easily lend itself to solving these sorts of problems, but there are a variety of ways to solve the problem. OpenGL uses a complicated process in "selection mode" to accomplish picking.  Various function are required, such as

glRenderMode()

which chooses between GL_RENDER, GL_SELECTION and GL_FEEDBACK render modes.  In SELECTION mode, each primitive that renders within a clipping window and generates a message called a "hit," which is stored in the "name stack."  The function

void glSelectBuffer(GLsizei n, GLuint *buff)

then identifies buff of size n as an array where selection data can be placed.

void glInitName()

initializes the stack,

void glPushName(GLuint name)

pushes a name on the name stack

void glPopName()

pops it off, and

void glLoadName(GLuint name)

replaces the top of the name stack with name.

 

The information produced via these functions is called the "hit list" and can be queried after rendering to decide how to perform picking.  Generally, an object is a set of primitives associated with the same integer id.  The name is loaded on the name stack and then rendered.  You can't load on an empty stack, so an unused name is put on the stack to initialize it:

glInitNames();

glPushName(0);

Selection mode is usually entered and exited in the span of a mouse callback The function glRenderMode() then returns the number of hits that have been processed and the "hit list" is examined.  The clipping volume can also be altered in the process to obtain hits in the desired region, usually somewhere around the mouse pointer.

 

The clipping volume can be set by simply setting the view volume with gluOrtho2D() (or other view functions) and then, say, saving this on the stack with glPushMatrix().  Any primitive then falling in the clipping volume then generates a hit regardless of the mouse pointer location.  Still, with this option, only objects near the mouse cursor may be of interest.

 

For example, choosing all objects "near the mouse cursor" involves a rectangle centered on the mouse cursor whose dimensions depend on the size of viewport, the location of the cursor, and maybe other stuff.  Intriguingly, Angel says it involves "the inverse of the projection matrix."  OpenGL computes these through the function,

void glPickMatrix(GLdouble x, GLdouble y, GLdouble w, GLdouble h, GLint *vp)

where vp is viewport.

 

Back to the crucible of MSVC++ (Microsoft Visual C++) I've got some code now that experiments with this these ideas.  A screen capture of the following program running after a few clicks of the mouse either inside (hits = 1) or outsides of the two flickering red and blue rectangles.

 

 

// display lists and hits adapted from OpenGl Primer by Edward Angel
#include <GL/glut.h>
#include <math.h>
#include <iostream>

#define NULL 0

int singleb; //window ids //doubleb,

//prototypes
void display();
void reshapeIt(int, int);
void mouse(int, int, int, int);
void keyIt(unsigned char, int, int);
void display();
void quit_menu(int);
void drawObjects(GLenum);
void processHits(GLuint, GLuint[]);

int main(int argc, char** argv) {
  glutInit(&argc, argv);
  //create a single buffered window
  glutInitDisplayMode(GLUT_SINGLE | GLUT_RGB);//making this DOUBLE kills flickker
  singleb = glutCreateWindow("single buffered");
  //display will call drawObjects which draws red and blue rectangles

  //and pushes each on the stack in in SELECT mode.

  glutDisplayFunc(display);

  //Use PROJECTION and MODELVIEW to frame image
  glutReshapeFunc(reshapeIt);

  //Animate by repeated calls
  glutIdleFunc(display);

  //the mouse handling is thrown into the pipeline/MainLoop

  //It uses processHits() to figure out how to interpret mouse action
  glutMouseFunc(mouse);
  //Enter event loop
  glutMainLoop();
}

void display() {
  glClear(GL_COLOR_BUFFER_BIT);
  drawObjects(GL_RENDER);
  glFlush();//switch to swapBuffers()for DOUBLE
}

void drawObjects(GLenum mode) {
  if(mode == GL_SELECT) glLoadName(1);//push this picture on stack, name it 1
  glColor3f(1.0,0.0,0.0);
  glRectf(-0.5,-0.5,1.0,1.0);
  if(mode == GL_SELECT) glLoadName(2);//push this picture on stack, name it 2
  glColor3f(0.0,0.0,1.0);
  glRectf(-1.0,-1.0,0.5,0.5);
}

// HERE COME DA MOUSE!!!!
#define SIZE 500
#define N 3
void mouse(int btn, int state, int x, int y) {
  GLuint nameBuffer[SIZE];
  GLuint hits;
  GLint viewport[4];
  if(btn == GLUT_LEFT_BUTTON && state == GLUT_DOWN){
    //initialize the name stack
    glInitNames();
    glPushName(0);
    glSelectBuffer(SIZE, nameBuffer);
    //set up viewing for selection mode
    glGetIntegerv(GL_VIEWPORT, viewport);
    glMatrixMode(GL_PROJECTION);
    //save original viewing matrix
    glPushMatrix();
    glLoadIdentity();
    //NXN pick area around cursor
    // need to invert mouse y to get world coordinates
    gluPickMatrix((GLdouble) x,
                  (GLdouble) (viewport[3] - y),
                  N, N, viewport);
    gluOrtho2D(-2.0, 2.0, -2.0, 2.0);
    glRenderMode(GL_SELECT);
    drawObjects(GL_SELECT);
    glMatrixMode(GL_PROJECTION);
    //restore viewing matrix
    glPopMatrix();
    //return to normal render mode
    hits = glRenderMode(GL_RENDER);
    //process hits from selection mode rendering
    processHits(hits, nameBuffer);
    //normal render
  }
}


void processHits(GLuint hits, GLuint buffer[]) {
  unsigned int i, j;
  GLuint names, *ptr;
  printf ("hits = %d\n", hits);
  ptr = (GLuint *) buffer;
  //loop over number of hits
  for(i=0; i<hits; i++) {
    names = *ptr;
    //skip over number of names and depths
    ptr += 3;
    // check each name in record
    for (j=0; j<names; j++) {
      if(*ptr == 1) printf("red rectangle\n");
      else printf("blue rectangle\n");
      // go to next hit record
      ptr++;
    }
  }
}
//If you omit this you don't get the nice framing and perspective,

//but the rectangles are there.  The PROJECTION and MODELVIEW matrices

//deserve further study!  Later...
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();
}