Click here to Skip to main content
15,867,453 members
Articles / Multimedia / OpenGL

OpenGL Geometric Primitives

Rate me:
Please Sign up or sign in to vote.
4.73/5 (35 votes)
27 Feb 2008CPOL16 min read 190.5K   8.8K   69   18
Learn OpenGL geometric primitives through this interactive program.

Visual C++ 2015 Files

Visual C++ 6 Files (Old)

OpenGL_Geometric/opengl_geometric_primitives_1.gif

Contents

Introduction

The purpose of this interactive program is to make it easier to learn OpenGL Geometric Primitives. There are ten geometric primitives in OpenGL, from a point to a polygon, and all are represented by vertices. This program will give you the flexibility to add and remove vertices in a certain order, and shows you how the choice of the primitive determines how the vertices are to be combined. It can be used in the following ways:

  • Learn OpenGL Geometric Primitives through
    • Interactive Program
    • Source Code
    • Documentation
  • Learn some OpenGL functions designed to work with geometric primitives
  • Draw primitives manually and generate their corresponding OpenGL C code

Usage

Compiling & Running the Program

For information on how to compile and run the program, check the Usage section in the GLUT Window Template article.

Program Usage

Menu

 

 

Primitive Options to change the shape, color, and other properties of the geometric primitive.
Vertex Options to generate vertices, increase\decrease generation rate, delete vertices, show\hide vertices, show\hide vertex number, increase\decrease vertex size, and change vertex color.
Grid Options to show\hide grid, turn on\off the option to automatically align vertices to grid, add\remove row to grid, add\remove column to grid, increase\decrease the line width of the lines forming the grid, and change the grid color.
Generate Code Generate OpenGL C code from your drawing. By default, the code is generated in a file called program.c.
Back Color Select the background color

 

 

 

 

Keyboard

 

 

 

 

Left\Right Change geometric primitive
Up\Down Change Primitive Color
a Turn On\Off auto-plotting on grid
C\c Add\Remove column to Grid
d Delete all vertices
g Show\Hide Grid
h Display help message
L\l Increase\Decrease Line Width
n Show\Hide Numbers
p Show\Hide vertices
R\r Add\Remove Row to Grid
S\s Increase\Decrease point size
T\t Increase\Decrease Generation Rate for vertices
v Generate Vertices
W\w Increase\Decrease Grid Line Width
X\X Increase\Decrease Vertex size
z Remove the last inserted vertex

 

 

 

 

Mouse

 

 

 

 

Left Button Add a vertex
Middle Button Remove a vertex
Right Button Show menu for more options

 

 

Explaining the Program

The Vertex

All geometric primitives are described in terms of vertices, which are coordinates that define the points themselves, endpoints of line segments, or corners of polygons.

In order to draw a vertex in OpenGL, we use the OpenGL function glVertex. This function has the following variations:

void glVertex2d( GLdouble x, GLdouble y )
void glVertex2f( GLfloat x, GLfloat y )
void glVertex2i( GLint x, GLint y )
void glVertex2s( GLshort x, GLshort y )
void glVertex3d( GLdouble x, GLdouble y, GLdouble z )
void glVertex3f( GLfloat x, GLfloat y, GLfloat z )
void glVertex3i( GLint x, GLint y, GLint z )
void glVertex3s( GLshort x, GLshort y, GLshort z )
void glVertex4d( GLdouble x, GLdouble y, GLdouble z, GLdouble w )
void glVertex4f( GLfloat x, GLfloat y, GLfloat z, GLfloat w )
void glVertex4i( GLint x, GLint y, GLint z, GLint w )
void glVertex4s( GLshort x, GLshort y, GLshort z, GLshort w )

We will be using glVertex2f since we are drawing vertices with floating point coordinates x and y in 2D space.

This interactive program supports the following operations on vertices:

  • Add a vertex using the mouse left button. The vertex coordinates are calculated from our mouse coordinates.
  • Remove the last vertex we added with the mouse middle button or by hitting the 'z' key on the keyboard.
  • Delete all vertices we have added by hitting the 'd' key on the keyboard or selecting the 'Vertex\Delete Vertices' menu item of the right click menu.
  • Generate a random number of vertices with random coordinates by hitting the 'v' key on the keyboard or selecting the 'Vertex\Generate Vertices' menu item of the right click menu.

The Primitive

A primitive is an interpretation of a list of vertices into a certain shape. In OpenGL, we specify the list of vertices for a certain primitive by placing them in a glBegin()\glEnd() block as follows:

glBegin (mode);
    glVertex* (...);
    .....
    glVertex* (...);
glEnd ();

where mode is a symbolic constant representing the desired primitive. The mode can be one of ten symbolic constants: GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP, GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_QUADS, GL_QUAD_STRIP, and GL_POLYGON.

This interactive program supports primitives by:

  • Maintaining a list of vertices.
  • Giving the user the option to select the desired geometric primitive using the 'Primitive\Type' submenu of the right click menu or by pressing the Left and Right keys.

Points

A point in OpenGL is simply represented by a single vertex. All internal calculations are done as if vertices are three-dimensional. Vertices specified as two-dimensional are assigned a z coordinate equal to zero.

GL_POINTS

Description

Treats each vertex as a single point. Vertex n defines a point n. N points are drawn.

Sample
glBegin(GL_POINTS);
    glVertex2f(x1, y1);
glEnd();
Note

Note that in all the samples for the primitives, the minimum number of vertices is used to demonstrate the usage of the primitive. For example, we specify a single vertex for a point, two vertices for a line segment, 3 vertices for a line strip, 6 vertices for a quad strip, and so on...

Preview
GL_POINTS GL_POINTS
Note

There will be two previews for every geometric primitive.

  • The first preview shows a single instance of the geometric primitive. The purpose behind this is to describe what the primitive really is.
  • The second preview shows the primitive using common points across all primitives. The purpose behind this is to show how using the same points results in a different shape when a different primitive is selected.

Useful Point Functions

void glPointSize(GLfloat size)Lines

Use this function in order to set the size of a point in pixels. For example, to set the point size to 4 pixels, do the following:

glPointSize(4.0f);

This function is used in the interactive program to allow the user to change the vertex size or the primitive point size in case the primitive is a point.

In OpenGL, a line represents a line segment rather than the mathematical concept of unlimited lines. OpenGL makes it easy to interconnect lines together through their endpoints.

GL_LINES

Description

Treats each pair of vertices as an independent line segment. Vertices 2n-1 and 2n define a line n. N/2 lines are drawn.

Sample
glBegin(GL_LINES);
    glVertex2f(x1, y1);
    glVertex2f(x2, y2);
glEnd();
Preview
GL_LINES GL_LINES

GL_LINE_STRIP

Description

Draws a connected group of line segments from the first vertex to the last. Vertices n and n+1 define line n. N-1 lines are drawn.

Sample
glBegin(GL_LINE_STRIP);
    glVertex2f(x1, y1);
    glVertex2f(x2, y2);
    glVertex2f(x3, y3);
glEnd();
Preview
GL_LINE_STRIP GL_LINE_STRIP

GL_LINE_LOOP

Description

Draws a connected group of line segments from the first vertex to the last, then back to the first. Vertices n and n+1 define line n. The last line however, is defined by vertices N and 1. N lines are drawn.

Sample
glBegin(GL_LINE_LOOP);
    glVertex2f(x1, y1);
    glVertex2f(x2, y2);
    glVertex2f(x3, y3);
glEnd();
Preview
GL_LINE_LOOP GL_LINE_LOOP

Useful Line Functions

 

 

void glLineWidth(GLfloat width)

 

 

Use this function to set the line width in pixels. For example, to set the line width to 2 pixels, do the following:

glLineWidth(2.0f);

This function is used in the program to allow the user to change the width of the lines that form the grid or the primitive line width in case the primitive is a line.

void glLineStipple(GLint factor, GLushort pattern)

Use this function to create lines with a dotted or dashed pattern, called stippling. To use line stippling, we must first enable it by calling the function below

glEnable(GL_LINE_STIPPLE);

The "pattern" is a 16-bit value that represents the pattern of the line. Each bit in the 16 bits represents 1 pixel * factor on the line. If the factor is 1, then a bit represents 1 pixel. If it is 2, then a bit represents 2 pixels... If the value of the bit is 1, then the corresponding pixel(s) are on, otherwise (bit's value is 0) they're off.

Notice that the bit pattern used for stippling is used in reverse when drawing the line for performance reasons. Here is an example:

short pattern = 0x0BAD; //  0000 1011 1010 1101
int factor = 1;

glLineStipple(factor, pattern);

glBegin(GL_LINES);
    ...
glEnd();

Here is how this pattern affects the line: the first pixel is on, second is off, third is on, forth is on, fifth is off, and so on...

HEX\BINARY Table
HEX 0 1 2 3 4 5 6 7 8 9 A B C D E F
BINARY 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111

The line stipple functionality is used to apply an alternating pattern on the grid lines, and thus display less of the grid on the window in order to draw the attention of the user more to the primitive being drawn.

Polygons

With the primitives mentioned above, we can draw any shape in 3D space. However, the shape can only be drawn in wireframe form (i.e. not filled with a color). In order to draw a solid surface, we need more than points and lines; we need polygons. A polygon is a closed shape that may or may not be filled with a color.

GL_TRIANGLES

Description

Treats each triplet of vertices as an independent triangle. Vertices 3n-2, 3n-1, and 3n define triangle n. N/3 triangles are drawn.

The simplest polygon possible is the triangle, with only three sides.

Sample
glBegin(GL_TRIANGLES);
    glVertex2f(x1, y1);
    glVertex2f(x2, y2);
    glVertex2f(x3, y3);
glEnd();
Preview
GL_TRIANGLES GL_TRIANGLES

GL_TRAINGLE_STRIP

Description

Draws a connected group of triangles. One triangle is defined for each vertex presented after the first two vertices. For odd n, vertices n, n+1, and n+2 define triangle n. For even n, vertices n+1, n, and n+2 define triangle n. N-2 triangles are drawn.

The advantage behind using the triangle strip is that after specifying the first three vertices for the initial triangle, you only need to specify a single point for each additional triangle. This would save a lot of data and time when drawing too many interconnected triangles to represent a more complex structure.

Sample
glBegin(GL_TRIANGLE_STRIP);
    glVertex2f(x1, y1);
    glVertex2f(x2, y2);
    glVertex2f(x3, y3);
    glVertex2f(x4, y4);
glEnd();
Preview
GL_TRAINGLE_STRIP GL_TRAINGLE_STRIP

GL_TRIANGLE_FAN

Description

Draws a connected group of triangles that fan around a central point. One triangle is defined for each vertex presented after the first two vertices. Vertices 1, n+1, and n+2 define triangle n. N-2 triangles are drawn.

Sample
glBegin(GL_TRIANGLE_FAN);
    glVertex2f(x1, y1);
    glVertex2f(x2, y2);
    glVertex2f(x3, y3);
    glVertex2f(x4, y4);
glEnd();
Preview
GL_TRIANGLE_FAN GL_TRIANGLE_FAN

GL_QUADS

Description

Treats each group of four vertices as an independent quadrilateral. Vertices 4n-3, 4n-2, 4n-1, and 4n define quadrilateral n. N/4 quadrilaterals are drawn.

Sample
glBegin(GL_QUADS);
    glVertex2f(x1, y1);
    glVertex2f(x2, y2);
    glVertex2f(x3, y3);
    glVertex2f(x4, y4);
glEnd();
Preview
GL_QUADS GL_QUADS

GL_QUAD_STRIP

Description

Draws a connected group of quadrilaterals. One quadrilateral is defined for each pair of vertices presented after the first pair. Vertices 2n-1, 2n, 2n+2, and 2n+1 define quadrilateral n. N/2-1 quadrilaterals are drawn.

Sample
glBegin(GL_QUAD_STRIP);
    glVertex2f(x1, y1);
    glVertex2f(x2, y2);
    glVertex2f(x3, y3);
    glVertex2f(x4, y4);
    glVertex2f(x5, y5);
    glVertex2f(x6, y6);
glEnd();
Preview
GL_QUAD_STRIP GL_QUAD_STRIP

GL_POLYGON

Description

Draws a single, convex polygon. Vertices 1 through N define this polygon. A polygon is convex if all points on the line segment between any two points in the polygon or at the boundary of the polygon lie inside the polygon.

Sample
glBegin(GL_POLYGON);
    glVertex2f(x1, y1);
    glVertex2f(x2, y2);
    glVertex2f(x3, y3);
    glVertex2f(x4, y4);
    glVertex2f(x5, y5);
glEnd();
Preview
GL_POLYGON GL_POLYGON

The key feature about this interactive program is that it allows us to test for certain concepts and validate that they are true. For example, let's validate that the polygon is convex.

  Shape 1 Shape 2 Shape 3
Lines Non-Convex Non-Convex Non-Convex
Polygon Convex Convex Convex

Although shape 1 and shape 3 seem to follow the rule of convex polygons, shape 2 seems to me non-convex since I can connect points 2 and 4 into a line segment that doesn't fall in the polygon. Can we say that the OpenGL Architecture Review board where wrong about what they described in the redbook? It might be something I'm missing... Please prove me wrong!

Useful Polygon Functions

There are many useful functions related to polygons, but to keep things simple, we are here only concerned with the glPolygonMode function.

void glPolygonMode(GLenum face, GLenum mode);

Use this function in order to change how the polygon will be rendered. By default, the polygon is filled with the current color. However, we can disable filling the polygon and display its outline or the vertices forming it.

Parameter Description Values
face Specifies which face of polygons is affected by the mode change. GL_FRONT
GL_BACK GL_FRONT_AND_BACK
mode Specifies the new drawing mode. GL_FILL is the default, producing filled polygons. GL_LINE produces polygon outlines, and GL_POINT only plots the points of the
vertices.
GL_FILL
GL_LINEGL_POINT

In our interactive program, we use this function to help draw our polygons and their borders. To draw the polygon, we say:

glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);

glBegin(GL_POLYGON);
    glVertex2f(x1, y1);
    ...
glEnd();

To draw its border, we say:

glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);

glBegin(GL_POLYGON);
    glVertex2f(x1, y1);
    ...
glEnd();

Program View

The program basically consists of 4 visible components: Vertices, Primitives, Grid, and Background. Here is the functionality supported by the program that allows you to manipulate the view of these components.

  1. Vertices
    • Increase\Decrease Vertex Size. Click 'X' to increase size and 'x' to decrease size. You can also select the +\- menu items under Vertex\Vertex size.
    • Change the vertex Color. You can do this by selecting the Vertex\Vertex Color menu items.
    • Show\Hide the vertices. Click the 'p' key or select the Show/Hide Points menu item under the Vertex submenu.
    • Show\Hide the numbers associated with vertices. click the 'n' key or select the Show/Hide Numbers menu item under the Vertex submenu.
  2. Primitives
    • Change the primitive Color. You can do this by selecting the Primitive\Color menu items or by pressing the Up and Down keys.
    • Change point size if the primitive is a point. Click 'S' to increase size and 's' to decrease size. You can also select the +\- menu items under Primitive\Point size.
    • Change line width in case the primitive is a line. Click 'L' to increase width and 'l' to decrease width. You can also select the +\- menu items under Primitive\Line width.
    • Show\Hide the primitive border and change the border color if the primitive is a polygon. You can change border color and show\hide border using the menu items under Primitive\Polygon.
  3. Grid
    • Increase\Decrease Grid Line Width. Click 'W' to increase width and 'w' to decrease width. You can also select the +\- menu items under Grid\Line width.
    • Change the grid lines color. You can do this by selecting the Back Color menu items.
    • Show\Hide the grid. Click the 'g' key or select the Show/Hide Grid menu item under the Grid submenu.
  4. Background
    • Change the background color. You can do this by selecting the Back Color menu items.

Check the sample demo attached with this article to see a video of how the program works.

Generating Vertices

You can generate a random number of vertices with random colors. You can do so by selecting the Vertex\Generate Vertices menu item, or by pressing the 'v' key.

The number of vertices generated is random, but it is dependent on a maximum, which is the generation rate. To increase the probability of having more vertices, you can increase the generation rate. this can be done by selecting the +\- menu items under the Vertex\Generation Rate submenu.

One interesting way of using the vertices generation functionality is to create for example all the possible lines between points on the grid. For example, I can set the current primitive to GL_LINES and keep on pressing the 'v' key until all the lines between all points are generated.

All lines between all points

Grid

Auto Plotting

When auto-plotting is turned off, a vertex is placed in the exact position at which the user clicks the mouse button.

Auto Plotting Off

Auto-plotting Turned Off

 

However, when auto-plotting is turned on, the vertices are automatically aligned to the grid by placing them on the nearest line interesection on the grid. The below figure shows the same vertices drawn above, but with auto-plotting enabled.

Auto Plotting On

Auto-plotting Turned On

 

The implementation of this functionality is very simple as it only revolves around the logic of rounding a floating point value to the nearest integer. Knowing that there is no round function in standard C, I have implemented my own as shown below.

int round(double number)
{
    return (number >= 0) ? (int)(number + 0.5) : (int)(number - 0.5);
}

Here is the code of the mouse callback function responsible of handling mouse clicks. The x and y coordinates are the mouse coordinates with respect to the window, where the unit is in pixels. Since in OpenGL we draw in world coordinates rather than mouse coordinates, we should convert x and y window coordinates to their corresponding world coordinates pointX and pointY. Notice how the pointX and pointY coordinates are rounded when autoPlot is true.

C++
//-------------------------------------------------------------------------
// This function is passed to the glutMouseFunc and is called 
// whenever the mouse is clicked.
//-------------------------------------------------------------------------
void mouse(int button, int state, int x, int y)
{
    if(button == GLUT_LEFT_BUTTON && state == GLUT_DOWN)
    {
        float pointX, pointY;

        pointX = (float)x/window_width * world_width;
        pointY = (float)(window_height - y)/window_height * world_height;

        if (autoPlot)
        {
            pointX = round(pointX);
            pointY = round(pointY);
        }
    
        // Add a vertex to the list of vertices...
        addVertex(&head, &tail, pointX, pointY, 0.0f);
    
        // automatically calls the display function
        glutPostRedisplay();
    }
    else if(button == GLUT_MIDDLE_BUTTON && state == GLUT_DOWN)
    {
        deleteVertex(&head, &tail);
        glutPostRedisplay();
    }
}

Auto-plotting also applies to vertices generated randomly. So when auto-plotting is off and vertices are generated, they are placed anywhere inside the window. However, when auto-plotting is on, the points are laid out on the grid.

C++
//-------------------------------------------------------------------------
// Generate a random number of vertices at random positions.
//-------------------------------------------------------------------------
void generateVertices()
{
    int i;
    int numberOfVertices = rand() % generationRate;
    
    for(i = 0; i < numberOfVertices; i++)
    {
        float x, y;
    
        if (autoPlot)
        {
            x = rand() % (int)world_width;
            y = rand() % (int)world_height;
        }
        else
        {
            x = rand() % window_width;
            y = rand() % window_height;

            x = (float)x/window_width * world_width;
            y = (float)(window_height - y)/window_height * world_height;
        }

        // Add a vertex to the list of vertices...
        addVertex( &head, &tail, x, y, 0.0f);
    }

    glutPostRedisplay();
}

Adding and Removing Rows and Columns

Suppose that you were drawing up something and you got out of space. It wouldn't be cool to clear things off by hitting the 'd' key, then re-try drawing a smaller version of your content.

This interactive program gives you the capability of expanding your drawing area by adding columns and\or rows. It also gives you the option of shrinking this area by removing columns and\or rows.

Let's say I want to draw a house using GL_LINE_STRIP primitive. I start out by drawing, but the grid can't hold my content as shown below.

Not Enough Space

I can resolve this by expanding the grid as shown below. You can add a row by hitting the 'R' key; remove a row by hitting the 'r' key; add a column by hitting the 'C' key; remove a column by hitting the 'c' key.

Enough Space

Notice that this functionality can also be used to zoom in and zoom out of the drawing area.

Generating Code

So you've decided to be ultimately geeky and wanted to draw something useful geometrically using only the GL_LINES primitive. You are so happy about it and you want to immortalize your work. It's simple. Right click, and select the 'Generate Code' menu item. This will generate the OpenGL C code required to render the vertices and the primitive that defines the relationship between them.

The generated code will be placed in a file called program.c. The program.c file would contain the OpenGL generated code for drawing the primitive. In case an extra file named skeleton.c is supplied, the program will read its contents, replace the first HTML comment it finds with the generated C code, and then write the results to the new file program.c file. So program.c would be a replica of the skeleton.c except that it has generated C code instead of the HTML comment. I have already supplied a skeleton.c file with the executable. You can replace this with your own.

My skeleton.c file contains the code for an OpenGL application that draws its contents using the drawObject method. Thus, it would be logical to put the HTML comment inside this method.

C++
void drawObject ()
{
    <!-- Insert Geometric Primitives here -->
}

when we generate the program.c file, it would contain the same content as skeleton.c, except that the drawObject method would now look like this.

C++
void drawObject ()
{
    // Set the drawing color
    glColor3f(0.00, 0.00, 1.00);
 
    // Display geometric primitives
    glBegin(GL_LINES);
        glVertex2f(1.26, 0.96);
        glVertex2f(1.72, 1.48);
        ...  //  and so on
    glEnd();
}

One important thing to note is that the world coordinates of your skeleton program must match the world coordinates of the interactive program, in order for your primitive to be displayed in your program in the same exact way as in the interactive program.

Conclusion

This article not only explains the OpenGL geometric primitives, but also provides you with an interactive program that allows you to check the concepts learned without having to write a piece of code. It even generates the code for you in case you ever needed it.

Hopefully, it would be useful to many people interested in graphics out there and worth the time invested. Let me know what you think!

Easter Egg

Find it!

The easter egg is disabled in the GitHub version, so you'll see more functionality if you get the code from there.

References

Revision History

02/22/2008:

  • Original article posted

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
United States United States
https://open-gl.com

Comments and Discussions

 
QuestionInfinite Lines Pin
Madhan Mohan Reddy P14-Apr-13 22:21
professionalMadhan Mohan Reddy P14-Apr-13 22:21 
Questionprogram no work on linux Pin
steve nospam5-Aug-12 3:14
steve nospam5-Aug-12 3:14 
GeneralMy vote of 5 Pin
Ajay Vijayvargiya29-Oct-10 7:56
Ajay Vijayvargiya29-Oct-10 7:56 
Generalerror Pin
minanikbakht9-Jul-10 21:11
minanikbakht9-Jul-10 21:11 
GeneralGood illustration! Pin
Ben_King14-Oct-09 23:07
Ben_King14-Oct-09 23:07 
GeneralSpline 3D Pin
tahar9-Sep-09 0:04
tahar9-Sep-09 0:04 
salut,
je veux dessiner une spline en 3d avec un ensemble du points est ce qu il est possible
merci
GeneralRe: Spline 3D Pin
Ali BaderEddin10-Sep-09 13:44
Ali BaderEddin10-Sep-09 13:44 
Generalhey ali Pin
ASDJKALJ27-Jul-09 1:24
ASDJKALJ27-Jul-09 1:24 
GeneralRe: hey ali Pin
Ali BaderEddin27-Jul-09 8:15
Ali BaderEddin27-Jul-09 8:15 
GeneralConvex Pin
Raniz29-Jun-08 21:13
Raniz29-Jun-08 21:13 
GeneralConvex or Concave [modified] Pin
bible~13-Mar-08 15:20
bible~13-Mar-08 15:20 
GeneralConvex polygons Pin
azonenberg4-Mar-08 4:04
azonenberg4-Mar-08 4:04 
GeneralRe: Convex polygons Pin
Ali BaderEddin4-Mar-08 17:09
Ali BaderEddin4-Mar-08 17:09 
News'Read' the Article Pin
Ali BaderEddin29-Feb-08 23:38
Ali BaderEddin29-Feb-08 23:38 
GeneralInteresting Pin
Arash Partow28-Feb-08 16:00
Arash Partow28-Feb-08 16:00 
GeneralRe: Interesting Pin
Ali BaderEddin29-Feb-08 23:35
Ali BaderEddin29-Feb-08 23:35 
JokeNice Pin
Ben Daniel28-Feb-08 15:19
Ben Daniel28-Feb-08 15:19 
GeneralRe: Nice Pin
Ali BaderEddin29-Feb-08 16:17
Ali BaderEddin29-Feb-08 16:17 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.