are you ready for a mindfuck ? are you ready for a mindfuck?

....::::Menu::::....
...::About::...
...::Articles::...
...::Contact::...
...::Home & News::...
...::Links & Credits::... OpenGL logo
Valid XHTML 1.0!

Polygon

by Elie De Brauwer

Goal of this file

This file ends our basic knowledge about 2 dimensional drawings, we'll create a color palette and create a colored polygon that is created by the user. The transformation basics will also be explained here. So a base is available that can be used when we switch to 3d graphics.

History of this file

  • 24 august 2003: created
  • 28 august 2003: continued
  • 3 september 2003: finished

What will be done

In this article I will create an application with a color palette from which the user can select some colors and draw a polygon by clicking a number of points in the window. In order to follow you need to have some knowledge about dynamical memory allocation.

Drawing

In a previous article about the creation of a circle I enumerated all possible parameters glBegin() could take. GL_LINE_LOOP and GL_LINES were already used in previous articles now we'll use GL_POLYGON and GL_QUADS. We will also see a new command (glColor3f()) which we can use between glBegin() and glEnd().
We will start our program by drawing six colored squares in the upper left corner. We used glRect*() before to draw a square but now we'll acomplish the same with the use of GL_QUADS. For example we can draw a red square by using issuing the following commands

glBegin(GL_QUADS);
  glColor3f(1,0,0);
  glVertex2f( 1.0, 1.0);
  glVertex2f(-1.0, 1.0);
  glVertex2f(-1.0,-1.0);
  glVertex2f( 1.0,-1.0);
glEnd();

glColor3f() takes three arguments, a red, green and blue value in the interval [0,1]. The other functions we called we already know from previous articles.
Now a more tricky part, a polygon. A polygon is a shape whose number of corners has no limit but the form of the shape has some limits. The glBegin() manpages defines it as drawing a single convex polygon. But in my opinion a little image says more than the last sentence:"

examples of valid polygons

The image was created using dia. The polygons on the left are allowed the ones on the right are not allowed. Why does OpenGL makes such a fuss about these little differences ? Well the answer is simple, speed. By assuming that all polygons are of the simpler type a big advantage in speed is made but the more complex polygons are displayed in a wrond way. This is something the developer (yes, that's you) has to think about because OpenGL will just do something wrong without giving you a warning. Ok you might perhaps not get the point which polygon is displayed correctly and which one is not. Well this is your lucky day because the application that is described in this article let's the user pick a number of points, even assign each point an other color and after each click OpenGL will do the drawing of the polygon and if you created a wrong polygon you will see OpenGL do something wrong.
Some of you might be wondering what to do if you'd want to draw a complex polygon ? Well that's simple just split it up into more than one simple polygons. Each figure can be split up in a finite number of triangles.

Polygonmodes and shademodel

First let's say something about a function called glPolygonMode(GLenum face, GLenum mode). This function takes two parameters, the face parameter can be GL_BACK, GL_FRONT and GL_FRONT_AND_BACK and the mode paramter can be GL_LINE, GL_POINT or GL_FILL (which is the default). Face defines to which polygons the mode applies. When the face is GL_BACK, the mode only applies to back facing polygons (and the same goes for GL_FRONT and GL_FRONT_AND_BACK).
But how do you know wether a polygon of front facing or backfacing ? Well, polygons that are drawn counterclockwise are front-facing. You can also change this by calling glFrontFace(GLenum mode),the parameter mode can be either GL_CCW of GL_CW, like this you can define wether a countclockwise or a clockwise drawn polygon is fronfacing.
Now we know what the face is, let's take a close look at the mode. The default is GL_FILL which fills the polygon, this makes it appear as a solid polygon, you can change this to GL_LINE to create a wireframe model, only the edges of the polygon are drawn. The last option is GL_POINT which only draws the vertices that are defined. The mode only applies to the face specified.
You can change the pointsize of the points drawn when the mode is GL_POINT by calling glPointSize(GLfloat size). With this function call you specify the diameter of the points, the default is 1.
You can change the width of the linex drawn in GL_LINE mode by calling glLineWidth(GLfloat width) again the initial value is 1.
For more information about linewidth, pointsize, antialiased points or antialiased lines please refer to the manpages and experiment a bit with these features.
You can define the shading that is used by calling the funcion glShadeModel(GLenum mode) where mode can be GL_FLAT for flat shading and GL_SMOOTH for smooth shading. When flat shading is used the entire polygon is filled with the same color which is the mean of the colors in the vertices. When smooth shading is enabled the polygon is filled with gradient alike structures. The shading model used is called the Gouraud shading model (another example of a shading model which isn't available in OpenGL afaik is Phong shading, for a better explanation consult any text on computer graphics). I would suggest taking a look at the screenshots below because a screenshot can say more than a thousand words (in this case anyhow).

Putting it all together

Below you can see the sourcecode, you can see it uses new and delete to dynamically allocate memory to store more s_point data when the users clicks on the screen. When you run the program you will see that a polygon is draw as soon as three or more points are entered. You can use the keys w, p and f to change the glPolygonMode() to wireframe mode, point mode and fill mode. You can use the keys s and b to change the glShadeModel to smooth shading and flat shading.
If you look at the resize callback you can also see we use the gluOrtho2D to make the coordinates equal the dimension of the window. By doing this we can create more drawing space when resizing the screen.
We also use a call to glColor3fv to change the color with a value we got from a color array. This function expects a pointer which points to an array containing three floats. The v stands for vector in this case.

Some screenshots

snapshot1

A not valid filled polygon with smooth shading

snapshot2

The same not valid polygon with smooth shading in wirefrime mode

snapshot3""/

The filled polygon with flat shading

snapshot4

An example of a valid polygon, filled mode and smooth shading.

And the same polygon now in wireframe mode.

The program

#include <cstdlib>
#include <iostream>
#include <cmath>
#include <GL/glut.h>
using namespace std;

// typedefs
typedef unsigned char uchar;

// constants 
const int BOXSIZE = 20;
const int NUMCOLORS = 7;

// struct
struct s_point{
  int x, y, color;
};

// static vars
static s_point *points = NULL;
static int cur_color = 0;
static int num_points = 0;
static int height;

// callbacks 
void keyb(uchar key, int x, int y);
void disp(void);
void resize(int x, int y);
void mouse(int button, int state, int x, int y);

// prototypes
void drawQuad(int x, int y,int delta);

////////////////////////////////
// main
int main(int argc,char  **argv){
  glutInit(&argc, argv);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
  glutInitWindowSize(500,500);
  glutInitWindowPosition(100,100);
  glutCreateWindow("Polygon.cpp");
 
  glClearColor(0.0,0.0,0.0,0.0);
  glutKeyboardFunc(keyb);
  glutDisplayFunc(disp);
  glutReshapeFunc(resize);
  glutMouseFunc(mouse);
  glutMainLoop();
  return 0;

}

////////////////////////////////////////////////
// mouse
void mouse(int button, int state, int x, int y){
  
  // if left mouse down 
  if(button == GLUT_LEFT && state == GLUT_DOWN){

    // are we clicking in the pallette ?
    if((x < BOXSIZE) && (abs(y-height) < (NUMCOLORS * BOXSIZE))){
      // if we are in the pallette, change the current color
      cur_color = abs(y-height)/BOXSIZE;

      // or have we clicked in the screen ?
    }else{
      s_point * temp = new  s_point[num_points+1];

      // copy the old to a temporary
      for(int i=0;i<(num_points);i++){
	temp[i].x = points[i].x;
	temp[i].y = points[i].y;
	temp[i].color = points[i].color;
      }

      // save the new one
      temp[num_points].x = x;
      temp[num_points].y = height - y;
      temp[num_points].color = cur_color;

      // increment the number of points
      num_points++;
      // delete the old 
      delete [] points;
      // make the temporary the current one
      points = temp;
      // ask for a redisplay
      glutPostRedisplay();
    }
  }
}

///////////////////////////////////
// keyb
void keyb(uchar key, int x, int y){
  switch(key){
  case 'q':
    delete[] points;
    exit(0);
    break;
  case 'w':
    cout << "Wireframe mode on " << endl;
    glPolygonMode(GL_FRONT_AND_BACK,GL_LINE);
    glutPostRedisplay();
    break;
  case 'p':
    cout << "Point mode on " << endl;
    glPolygonMode(GL_FRONT_AND_BACK,GL_POINT);
    glutPostRedisplay();
    break;
  case 'f':
    cout << "Fill mode on " << endl;
    glPolygonMode(GL_FRONT_AND_BACK,GL_FILL);
    glutPostRedisplay();
    break;
  case 's':
    cout << "Smooth shading " << endl;
    glShadeModel(GL_SMOOTH);
    glutPostRedisplay();
    break;
  case 'b':
    cout << "Flat shading " << endl;
    glShadeModel(GL_FLAT);
    glutPostRedisplay();
    break;
  }
};

////////////////
// disp
void disp(void){

  // define colors
  static float colorarray[NUMCOLORS][3] =
    {
      {1.0,1.0,1.0},
      {0.0,1.0,1.0},
      {0.0,0.0,1.0},
      {1.0,1.0,0.0},
      {1.0,0.0,0.0},
      {0.0,1.0,0.0},
      {1.0,0.0,1.0}
    };
  
  glClear(GL_COLOR_BUFFER_BIT);
 
  // draw the palette 
  int ypos = 0;
  for(int i=0;i<NUMCOLORS;i++){
    glColor3fv(colorarray[i]);
    drawQuad(0,ypos,BOXSIZE);
    ypos+=BOXSIZE;
  }

  glBegin(GL_POLYGON);
  for(int i=0;i<num_points;i++){
    glColor3fv(colorarray[points[i].color]);
    glVertex2f(points[i].x,points[i].y);
  }
  glEnd();
  glutSwapBuffers();
}

//////////////////////////////////////
// drawQuad
void drawQuad(int x, int y, int delta){
  glBegin(GL_QUADS);
  glVertex2f(x,y);
  glVertex2f(x+delta,y);
  glVertex2f(x+delta,y+delta);
  glVertex2f(x,y+delta);
  glEnd();
}

//////////////////////////
// resize
void resize(int x, int y){
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluOrtho2D(0,x,0,y);
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  height = y;
}

You can always download the sourcecode here

Putting it even more together

It would be a nice exercise to implement the following features:

  • Implement the possibility to draw multiple polygons, an array of an array of points.
  • Implement a clear function
  • Let the user draw a polygon, let him end the polygon by pressing a key. Let him select a point and let the polygon rotate around this point
  • Alter the keyboard callbacks so only back facing or front facing polygons are drawn.