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!

Circle

by Elie De Brauwer

Goal of this file

The goal of this articles is to give you an introduction to drawing 2 dimensional figures using lines and some mathematics.

History of this file

  • 17 august 2003: created
  • 19 august 2003: continued
  • 20 august 2003: finished

The concept

In the introduction we mentioned that OpenGL works on a low level. Now it even works on such a low level that no function exists for drawing something simple like a circle. So when we need a circle we are on our own. In this article I will draw a circle composed from lines. If you create an approximation of a circle using three lines you get a triangle, if you use four you get a square and if you use an infinite number of lines you get a perfect circle. But when drawing a circle on a computer screen inifinite comes close to thirtyfive (if you window isn't too large).
Now, how does one find the coordinates of the pixels that are on the circles edges and need to be colored. That's rather simple if you know some things about mathematics. Observe the image below (btw this image was created using eukleides with a bit of help from the gimp, the eukleides source is also available):

coordinates

Let's assume we have a circle with radius L, now starting from the centerpoint (in the center of the plane with coordinates 0,0). Now all points have the coordinates (L*cos(alpha),L*sin(alpha)) for alpha between 0 and 360 degrees of between 0 and 2*Pi radians.
This article also introduces double buffering.

The circle in code

C/C++ provides us with the sine and consine functions so we don't need to define them ourself, we only need to include the cmath header.We determine the points that lie on a circle by using the following algorithm

FOR(i=0;i<NUM_LINES;i++){
	xcoord = l * cos(i*2*PI/NUM_LINES);
	ycoord = l * sin(i*2*PI/NUM_LINES);	
}

Suppose NUM_LINES is three then a point will occur at an angle 0,120 and 240 (degrees). Note that the sine and cosine functions take there paramters in radians [0,2*PI] instead of degrees. l is the radius of the circle. If we should draw it like the result would be a symmetrical figure around the the x-axis.
It is also clear that by altering the NUM_LINES variable we can alter the number of lines that are used to describe the figure.

Vertices in OpenGL

This part is of major importance within OpenGL programming. Until now we only used predefined shapes like glutSolidTorus(), glutWireCube() and glRectf(). But these predefined functions won't get your very far. Now we'll learn to create our own volumes by specifying there vertices. First we tell OpenGL we want to begin to define a shape. We do this by calling glBegin(), we end it by calling glEnd(). Only glBegin() takes a parameter, this parameter defines what we want to draw we have the following options:

GL_POINTS
Defines that you want to draw a collection of points, each vertex you draw will be a point on the screen. (Note that a point is a single pixel on the screen) You can change the size of the point by calling glPointSize(GLfloat size)
GL_LINES
Each pair of vertices defines a line. If you define 2n vertices, n lines will be drawn, if you define 2n+1 vertices n lines will be drawn and the last vertex will be ignored.
GL_LINE_STRIP
Draws a shape constructed by line segments. If you define n vertices, n-1 line pieces will be drawn. The shape is not closed at the end.
GL_LINE_LOOP
Same as GL_LINE_STRIP but the shape is closed. So by defining n vertices, you draw a shape that is created by a closed loop of n vertices.
GL_TRIANGLES
Each triplet defines a triangles. 3n+2, 3n+1 or 3n vertices define n triangles, the vertices at location 3n+1 and/or 3n+2 are ignored. Triangles will be widely used for the simple reason that three points (a triangle that is) always defines a plane in space.
GL_TRIANGLE_STRIP
Defines a string of connected triangles it can easily be visualised by the next ascii string : \/\/\/\/ If you define n vertices this results in n-2 triangles.The first three vertices define the first triangle, the next vertex defines the next triangle (with the last two vertices of the first triangles) and so on.
GL_TRIANGLE_FAN
This can be compared with the GL_TRIANGLE_STRIP but all triangles have one vertex (the first one defined) in common. The first three vertices define the first triangles the first vertex defined, the last vertex of the last triangle and the new vertex define the next triangle. n vertices define n-2 triangles. If it is said that n vertices are needed to draw the first one, nothing will be drawing if < n vertices are drawn
GL_QUADS
Each group of four vertices define a square, 4n, 4n+1, 4n+2 and 4n+3 vertices define n squares. All vertices > 4n are ignored.
GL_QUAD_STRIP
>Like a GL_TRIANGLE_STRIP but with squares.
GL_POLYGON
Defines a convex polygon, all vertices defined define this polygon.

The following is a quoted from the glBegin manpage:

       Only  a  subset  of  GL commands can be used between glBegin and glEnd.
       The commands are  glVertex,  glColor,  glIndex,  glNormal,  glTexCoord,
       glEvalCoord,  glEvalPoint,  glArrayElement, glMaterial, and glEdgeFlag.
       Also, it is acceptable to use glCallList or glCallLists to execute dis-
       play  lists  that include only the preceding commands.  If any other GL
       command is executed between glBegin and glEnd, the error  flag  is  set
       and the command is ignored.

The meaning of these other commands and what displaylists are and such will be defined in other articles. We are only at the beginning know and explaining some of these would be overkill. Currently we will only use commands of the following form:

// shape 1
glBegin(GLenum mode);
	// collection of calls to glVertex() and all of it's flavors
	glVertex3f();
glEnd();
...
// shape n
glBegin(GLenum mode);
	// collection of calls to glVertex() and all of it's flavors
	glVertex3f();
glEnd();

Now the glVertex*() (a function called glVertex*() means 'all flavors of the glVertex' like glVertex2d(), glVertex2f(), glVertex3d(),glVertex3dv(),... ) function defines a vertex (a vertex is nothing more that a point in space with the difference you can attache some options to it like a color for example).

Double buffering

This is also the first example where double buffering is used. Double buffering is nothing more than using two virtual screens. One that is visible to the user and one that is used by the computer to draw vertices on. When the computer is finished drawing the two screens are swtiched, and the used views the new drawing and the computer can start with a new drawing. We use double buffering in glut by logically or'ing GLUT_DOUBLE with the glutInitDisplayMode(), call. We should switch the buffers at the end of the display callback function, we do this by calling void glutSwapBuffers(void). To view the difference between double and single buffering, compile the sourcecode, replace glutSwapBuffers() by glFlush() and replace GLUT_DOUBLE by GLUT_SINGLE (these two can't be used together). Now start the programs and keep the '+' pressed. Once you draw enought line segments you will see the circle 'being built'. You should use double buffering everytime when animation is used.

Putting it all together

In the next code segment we use the reshape function discussed in the hello article. We don't need to change the coordinate system (gluOrtho2d(), glOrtho()) since the default of an OpenGL window is that it's coordinates are mapped to [-1,1] for x and y. This means that a circle with radius one first perfectly into a default window.
We use a GL_LINE_LOOP here because a circle is a closed shape indeed. Please not that piece of code shouldn't be used to draw circles in real life programs because it isn't that performant, this is just and example to illustrate the use of glBegin(), glEnd() and glVertex*().

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

typedef unsigned char uchar;

// number of line segments
static int num_lines = 3;

// callback prototypes
void disp(void);
void keyb(uchar k, int x, int y);
void reshape(int x, int y);


////////////////////////////////
// main
int main(int argc, char **argv){
  glutInit(&argc,argv);
  glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE);
  glutInitWindowSize(400,400);
  glutInitWindowPosition(100,100);
  glutCreateWindow("circle.cpp");

  glClearColor(0.0,0.0,0.0,0.0);
  glutDisplayFunc(disp);
  glutKeyboardFunc(keyb);
  glutReshapeFunc(reshape);
  glutMainLoop();
  return 0;
}

////////////////
// disp
void disp(void){
  double angle;
  glClear(GL_COLOR_BUFFER_BIT);

  // identation like this is nice for glBegin() and glEnd()
  // but emacs doesn't support it.
  glBegin(GL_LINE_LOOP);
    for(int i =0;i<num_lines;i++){
      // M_PI defined in cmath.h
      angle = i*2*M_PI/num_lines;
      // we use vertex2f since we are currently in working
      // in 2d. 
      glVertex2f(cos(angle),sin(angle));
      // we don't need to multiply  by the length since the 
      // radius is 1.
    }
  glEnd();

  glutSwapBuffers();
}

///////////////////////////////////
// keyb
void keyb(uchar k, int x, int y){
  switch (k){
  case 'q':
    exit(0);
    break;
  case '+':
    if(num_lines < 99){
      num_lines++;
      cout << "Circle consists of " << num_lines << " lines " << endl;
      glutPostRedisplay();
    }
    break;
  case '-':
    if(num_lines >3){
      num_lines--;
      cout << "Circle consists of " << num_lines << " lines " << endl;
      glutPostRedisplay();
    }
    break;
  }
}

//////////////////////////
// reshape
void reshape(int x,int y){
  /* 
     we use the  reshape2 function out of the 
     hello article but we skip the glOrtho2D call 
     because when we don't define it the defaults are 
     used, this is a window within the following ranges
     [-1,1] for x and for y (0,0) in the center of the
     screen and this is exactly what we want
  */
  if(x<y)
    glViewport(0,(y-x)/2,x,x);
  else
    glViewport((x-y)/2,0,y,y);
}

You can also download the sourcode here

Snapshots

Circle snapshot 1

Circle drawn using 4 line segments (a square that is)

Circle snapshot 2

Circle drawn using 14 line segments

Circle snapshot 3

Circle drawn using 35 line segments, this makes in this resolution a rather nice circle.