Lesson 16: Waving flag with OpenGL and GLSL

Standard
blue-270x270-7

With the newer versions of OpenGL we are able to manipulate the OpenGL Rendering Pipeline. This gives a lot of power to the developers to do graphical manipulations and visual effects.  So far we have used the classical method to display 3D graphics.This (simplified) OpenGL pipeline is:

OpenGL Pipeline

Essentially what we will do is upload small programs into the graphics card chip (GPU).  These small programs are called ‘shaders’.  For example, we will have a ‘vertex shader’ to manipulate vertexes and a ‘fragment shader’ to manipulate the image data.   Both will directly manipulate the OpenGL pipeline.  These programs are written in the language GLSL (abrev. OpenGL Shading Language). In this tutorial we’ll draw a waving flag. First we will display a set of quads which have a fixed x and y position for each vertex.

/* Draw here a plain surface */
float TS = 1.0 / 40; //0.025;

glBegin(GL_QUADS);
for (i = -20; i < 20; i++)
	for (j = -20; j < 20; j++)
	{
		float startX=  TS*(i+20);
		float startY = TS*(j+20);
		glTexCoord2f( startX + 0.0f, startY + 0 );  glVertex2f(i, j);
		glTexCoord2f( startX + TS, startY + 0 );  glVertex2f(i + 1, j);
		glTexCoord2f( startX + TS, startY + TS );  glVertex2f(i + 1, j + 1);
		glTexCoord2f( startX + 0.0f, startY + TS );  glVertex2f(i, j + 1);
	}
glEnd();

The variables TS, startX and startY are required to fit the texture. Texture coordinates are mapped between (0.0 and 1.0). The total size of all quads will be 40 (e.g. 20 in negative and 20 in positive direction). We will then create a vertex shader (program in the GPU to manipulate vertex coordinates)

We then create a custom vertex shader, where we manipulate the Z coordinate of each vertex according to the cosine or sine function, which are somewhat similar to the position of a waving flag:

cosine / sine

cosine / sine

We define our vertex shader as:

/* Vertex shader */
uniform float waveTime;
uniform float waveWidth;
uniform float waveHeight;

void main(void)
{
    vec4 v = vec4(gl_Vertex);
    v.z = cos(waveWidth * v.x + waveTime) * waveHeight;

    gl_Position = gl_ModelViewProjectionMatrix * v;
    gl_TexCoord[0] = gl_MultiTexCoord0;
}

And fragment shader as:

/* Fragment shader */

uniform sampler2D color_texture;

void main() {
	// Set the output color of our current pixel
	gl_FragColor = texture2D(color_texture, gl_TexCoord[0].st);
}

We should then have this result (in wireframe mode):

OpenGL Flag

OpenGL Flag

Finally the program code is:

/*
* OpenGL Water Effect. 
* It uses GLSL to create a wavey effect.
*
* http://talkera.org/opengl/
* http://openglsamples.sf.net/
*
*/
#include <stdio.h>
#include <stdlib.h>
 
#include <SDL/SDL.h>
#include <GL/glew.h>
 
void printLog(GLuint obj)
{
    int infologLength = 0;
    char infoLog[1024];
 
	if (glIsShader(obj))
		glGetShaderInfoLog(obj, 1024, &infologLength, infoLog);
	else
		glGetProgramInfoLog(obj, 1024, &infologLength, infoLog);
 
    if (infologLength > 0)
		printf("%s\n", infoLog);
}
 
char *file2string(const char *path)
{
	FILE *fd;
	long len,
		 r;
	char *str;
 
	if (!(fd = fopen(path, "r")))
	{
		fprintf(stderr, "Can't open file '%s' for reading\n", path);
		return NULL;
	}
 
	fseek(fd, 0, SEEK_END);
	len = ftell(fd);
 
	printf("File '%s' is %ld long\n", path, len);
 
	fseek(fd, 0, SEEK_SET);
 
	if (!(str = malloc(len * sizeof(char))))
	{
		fprintf(stderr, "Can't malloc space for '%s'\n", path);
		return NULL;
	}
 
	r = fread(str, sizeof(char), len, fd);
 
	str[r - 1] = '\0'; /* Shader sources have to term with null */
 
	fclose(fd);
 
	return str;
}

/* Storage For Textures */
GLuint texture[3]; 


/* Loads in a bitmap as a GL texture */
int LoadGLTextures( ) {
    int Status = 0;
 
    /* Create storage space for the texture */
    SDL_Surface *TextureImage[1]; 
 
    /* Load The Bitmap into Memory */
    if ((TextureImage[0] = IMG_Load("flag.bmp"))) {
        Status = 1;
        glGenTextures( 1, &texture[0] );
        glBindTexture( GL_TEXTURE_2D, texture[0] );
        glTexImage2D( GL_TEXTURE_2D, 0, 3, TextureImage[0]->w,
              TextureImage[0]->h, 0, GL_BGR,
              GL_UNSIGNED_BYTE, TextureImage[0]->pixels );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

        }
 
    /* Free up some memory */
    if ( TextureImage[0] )
        SDL_FreeSurface( TextureImage[0] );
 
    return Status;
}
 
int main(int argc, char **argv)
{
	SDL_Event sdlEv;
	Uint32 sdlVideoFlags = SDL_OPENGL;
	Uint8 quit;
	char *extensions;
 
	int i, j;
 
	/* Initialize */
	if (SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		fprintf(stderr, "SDL_Init: %s\n", SDL_GetError());
		exit(EXIT_FAILURE);
	}
	atexit(SDL_Quit);
 
	/* Start graphic system with OGL */
	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 
	if (!SDL_SetVideoMode(640, 480, 0, sdlVideoFlags))
	{
		fprintf(stderr, "SDL_SetVideoMode: %s\n", SDL_GetError());
		exit(EXIT_FAILURE);
	}
 
	glShadeModel(GL_SMOOTH);
	//glViewport(0, 0, 400, 400);
 
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(40, 1, 0.0001, 1000.0);
	glMatrixMode(GL_MODELVIEW);
 
	printf("Initializing glew\n");
	glewInit();
	if (GLEW_VERSION_2_0)
		fprintf(stderr, "INFO: OpenGL 2.0 supported, proceeding\n");
	else
	{
		fprintf(stderr, "INFO: OpenGL 2.0 not supported. Exit\n");
		return EXIT_FAILURE;
	}
 
	/* The vertex shader */
	char *vsSource = file2string("wave.vert");
	char *fsSource = file2string("wave.frag");
 
	/* Compile and load the program */
 
	GLuint vs, /* Vertex Shader */
		   fs, /* Fragment Shader */
		   sp; /* Shader Program */
 
 
	vs = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vs, 1, &vsSource, NULL);
	glCompileShader(vs);
	printLog(vs);
 
	fs = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fs, 1, &fsSource, NULL);
	glCompileShader(fs);
	printLog(fs);
 
	free(vsSource);
	free(fsSource);
 
	sp = glCreateProgram();
	glAttachShader(sp, vs);
	glAttachShader(sp, fs);
	glLinkProgram(sp);
	printLog(sp);
 
	glUseProgram(sp);
 
	GLfloat waveTime = 0.5,
			waveWidth = 0.3,
			waveHeight = 2.0,
			waveFreq = 0.1;
	GLint waveTimeLoc = glGetUniformLocation(sp, "waveTime");
	GLint waveWidthLoc = glGetUniformLocation(sp, "waveWidth");
	GLint waveHeightLoc = glGetUniformLocation(sp, "waveHeight");
	printLog(sp);
 
	printf("wave parameters location: %d %d %d\n", waveTimeLoc, waveWidthLoc, waveHeightLoc);
 
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	
	LoadGLTextures();
 
 	/* Initialize basics */
    glEnable( GL_TEXTURE_2D );
    glShadeModel( GL_SMOOTH );
    glClearColor( 0.0f, 0.0f, 0.0f, 0.5f );
    glClearDepth( 1.0f );
    glEnable( GL_DEPTH_TEST );
    glDepthFunc( GL_LEQUAL );
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    float ratio = 640 /  480;   
    gluPerspective( 45.0f, ratio, 0.1f, 100.0f );
    glMatrixMode( GL_MODELVIEW );
	int frameCount = 0;
	int nextUpdate = SDL_GetTicks() + 1000;

	/* Main loop */
	quit = 0;
	while (!quit)
	{
		while (SDL_PollEvent(&sdlEv))
			switch (sdlEv.type)
			{
				case SDL_QUIT:
					quit = 1;
					break;
 
				default:
					break;
			}
 
  		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  		glLoadIdentity();
 
		glTranslatef(0.0, 1.0, -70.0);
		glRotatef(-45.0, 1.0, 0.0, 0.0);
 	
		/* Change time */
		glUniform1f(waveTimeLoc, waveTime);
		glUniform1f(waveWidthLoc, waveWidth);
		glUniform1f(waveHeightLoc, waveHeight);
 
  		/* Bind texture */
  	    glActiveTexture(GL_TEXTURE0);
        int texture_location = glGetUniformLocation(fs, "color_texture");
        glUniform1i(texture_location, 0);
        glBindTexture(GL_TEXTURE_2D, texture[0]);

		/* Draw here a plain surface */
        float TS = 1.0 / 40; //0.025;

		glBegin(GL_QUADS);
		for (i = -20; i < 20; i++)
			for (j = -20; j < 20; j++)
			{

				float startX=  TS*(i+20);
				float startY = TS*(j+20);
				glTexCoord2f( startX + 0.0f, startY + 0 );  glVertex2f(i, j);
				glTexCoord2f( startX + TS, startY + 0 );  glVertex2f(i + 1, j);
				glTexCoord2f( startX + TS, startY + TS );  glVertex2f(i + 1, j + 1);
				glTexCoord2f( startX + 0.0f, startY + TS );  glVertex2f(i, j + 1);
			}
		glEnd();
 
		// Update wave variable
		waveTime += waveFreq;
 
		SDL_GL_SwapBuffers();
		SDL_Delay(30);
		frameCount++;
 
		if (SDL_GetTicks() >= nextUpdate)
		{
			fprintf(stderr, "FPS: %d\n", frameCount);
			frameCount = 0;
			nextUpdate = SDL_GetTicks() + 1000;
		}
	}
 
	glDeleteShader(vs);
	glDeleteShader(fs);
	glDeleteProgram(sp);
 
	return EXIT_SUCCESS;
}

Running it should result in an animation as:

Waving Flag OpenGL

Waving Flag OpenGL

 

Lesson 15: Water effect with OpenGL (on the GPU with GLSL)

Standard
blue-270x270-6

With the newer versions of OpenGL we are able to manipulate the OpenGL Rendering Pipeline. This gives a lot of power to the developers to do graphical manipulations and visual effects.  So far we have used the classical method to display 3D graphics.This (simplified) OpenGL pipeline is:

OpenGL Pipeline

OpenGL Pipeline

Essentially what we will do is upload small programs into the graphics card chip (GPU).  These small programs are called ‘shaders’.  For example, we will have a ‘vertex shader’ to manipulate vertexes and a ‘fragment shader’ to manipulate the image data.   Both will directly manipulate the OpenGL pipeline.  These programs are written in the language GLSL (abrev. OpenGL Shading Language).

Our fragment shader looks like this:

/* Fragment shader */

uniform sampler2D color_texture;

void main() {
	// Set the output color of our current pixel
	gl_FragColor = texture2D(color_texture, gl_TexCoord[0].st);
}

It tells the GPU to use the texture with the given texture coordinates, such that we can use texture mapping as we were used to. Then, we will create the vertex shader. As we want to create ocean waves we want to manipulate the vertexes in real time on the GPU. We will create a map of connected quads  and manipulate the height coordinate of each vertex in the quad the map. (In this case the z-coordinate). In this example we use the formula:

v.z = sin(waveWidth * v.x + waveTime) * cos(waveWidth * v.y + waveTime) * waveHeight;

Thus, we move the y-coordinate of each vertex according to a simple cosine/sine based function. You could define different functions here for different vertex effects.  :)

We can manipulate all of the parameters (waveWidth, waveTime, waveHeight) in our C program.

/* Vertex shader */
uniform float waveTime;
uniform float waveWidth;
uniform float waveHeight;

void main(void)
{
	vec4 v = vec4(gl_Vertex);
	v.z = sin(waveWidth * v.x + waveTime) * cos(waveWidth * v.y + waveTime) * waveHeight;
 	gl_Position = gl_ModelViewProjectionMatrix * v;
        gl_TexCoord[0] = gl_MultiTexCoord0;
}

To draw the field we simply use

/* Draw here a plain surface */
float TS = 1.0 / 40; //0.025;

glBegin(GL_QUADS);
for (i = -20; i < 20; i++)
	for (j = -20; j < 20; j++)
	{
		float startX=  TS*(i+20);
		float startY = TS*(j+20);
		glTexCoord2f( startX + 0.0f, startY + 0 );  glVertex2f(i, j);
		glTexCoord2f( startX + TS, startY + 0 );  glVertex2f(i + 1, j);
		glTexCoord2f( startX + TS, startY + TS );  glVertex2f(i + 1, j + 1);
		glTexCoord2f( startX + 0.0f, startY + TS );  glVertex2f(i, j + 1);
	}
glEnd();

We use the variables startX,startY and TS to fit the texture to the field.We scale the texture over the entire field, but you may want to repeat the texture on a smaller field.  TS is the width of the total field, which is a variable we use to scale the texture. startX and startY are the offset of the texture map.   We will draw the entire field using our vertex shader and fragment shader defined above.  The output should be like:

OpenGL Water

OpenGL Water

The complete program is (don’t forget to copy the vertex and fragment shader & to get a texture):

/*
* OpenGL Water Effect. 
* It uses GLSL to create a wavey effect.
*
* http://talkera.org/opengl/
* http://openglsamples.sf.net/
*
*/
#include <stdio.h>
#include <stdlib.h>
 
#include <SDL/SDL.h>
#include <GL/glew.h>
 
void printLog(GLuint obj)
{
    int infologLength = 0;
    char infoLog[1024];
 
	if (glIsShader(obj))
		glGetShaderInfoLog(obj, 1024, &infologLength, infoLog);
	else
		glGetProgramInfoLog(obj, 1024, &infologLength, infoLog);
 
    if (infologLength > 0)
		printf("%s\n", infoLog);
}
 
char *file2string(const char *path)
{
	FILE *fd;
	long len,
		 r;
	char *str;
 
	if (!(fd = fopen(path, "r")))
	{
		fprintf(stderr, "Can't open file '%s' for reading\n", path);
		return NULL;
	}
 
	fseek(fd, 0, SEEK_END);
	len = ftell(fd);
 
	printf("File '%s' is %ld long\n", path, len);
 
	fseek(fd, 0, SEEK_SET);
 
	if (!(str = malloc(len * sizeof(char))))
	{
		fprintf(stderr, "Can't malloc space for '%s'\n", path);
		return NULL;
	}
 
	r = fread(str, sizeof(char), len, fd);
 
	str[r - 1] = '\0'; /* Shader sources have to term with null */
 
	fclose(fd);
 
	return str;
}

/* Storage For Textures */
GLuint texture[3]; 


/* Loads in a bitmap as a GL texture */
int LoadGLTextures( ) {
    int Status = 0;
 
    /* Create storage space for the texture */
    SDL_Surface *TextureImage[1]; 
 
    /* Load The Bitmap into Memory */
    if ((TextureImage[0] = IMG_Load("texcube.bmp"))) {
        Status = 1;
        glGenTextures( 1, &texture[0] );
        glBindTexture( GL_TEXTURE_2D, texture[0] );
        glTexImage2D( GL_TEXTURE_2D, 0, 3, TextureImage[0]->w,
              TextureImage[0]->h, 0, GL_BGR,
              GL_UNSIGNED_BYTE, TextureImage[0]->pixels );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );

        }
 
    /* Free up some memory */
    if ( TextureImage[0] )
        SDL_FreeSurface( TextureImage[0] );
 
    return Status;
}
 
int main(int argc, char **argv)
{
	SDL_Event sdlEv;
	Uint32 sdlVideoFlags = SDL_OPENGL;
	Uint8 quit;
	char *extensions;
 
	int i, j;
 
	/* Initialize */
	if (SDL_Init(SDL_INIT_VIDEO) < 0)
	{
		fprintf(stderr, "SDL_Init: %s\n", SDL_GetError());
		exit(EXIT_FAILURE);
	}
	atexit(SDL_Quit);
 
	/* Start graphic system with OGL */
	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
 
	if (!SDL_SetVideoMode(640, 480, 0, sdlVideoFlags))
	{
		fprintf(stderr, "SDL_SetVideoMode: %s\n", SDL_GetError());
		exit(EXIT_FAILURE);
	}
 
	glShadeModel(GL_SMOOTH);
	//glViewport(0, 0, 400, 400);
 
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(40, 1, 0.0001, 1000.0);
	glMatrixMode(GL_MODELVIEW);
 
	printf("Initializing glew\n");
	glewInit();
	if (GLEW_VERSION_2_0)
		fprintf(stderr, "INFO: OpenGL 2.0 supported, proceeding\n");
	else
	{
		fprintf(stderr, "INFO: OpenGL 2.0 not supported. Exit\n");
		return EXIT_FAILURE;
	}
 
	/* The vertex shader */
	char *vsSource = file2string("wave.vert");
	char *fsSource = file2string("wave.frag");
 
	/* Compile and load the program */
 
	GLuint vs, /* Vertex Shader */
		   fs, /* Fragment Shader */
		   sp; /* Shader Program */
 
 
	vs = glCreateShader(GL_VERTEX_SHADER);
	glShaderSource(vs, 1, &vsSource, NULL);
	glCompileShader(vs);
	printLog(vs);
 
	fs = glCreateShader(GL_FRAGMENT_SHADER);
	glShaderSource(fs, 1, &fsSource, NULL);
	glCompileShader(fs);
	printLog(fs);
 
	free(vsSource);
	free(fsSource);
 
	sp = glCreateProgram();
	glAttachShader(sp, vs);
	glAttachShader(sp, fs);
	glLinkProgram(sp);
	printLog(sp);
 
	glUseProgram(sp);
 
	GLfloat waveTime = 0.5,
			waveWidth = 0.6,
			waveHeight = 1.0,
			waveFreq = 0.1;
	GLint waveTimeLoc = glGetUniformLocation(sp, "waveTime");
	GLint waveWidthLoc = glGetUniformLocation(sp, "waveWidth");
	GLint waveHeightLoc = glGetUniformLocation(sp, "waveHeight");
	printLog(sp);
 
	printf("wave parameters location: %d %d %d\n", waveTimeLoc, waveWidthLoc, waveHeightLoc);
 
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	
	LoadGLTextures();
 
 	/* Initialize basics */
    glEnable( GL_TEXTURE_2D );
    glShadeModel( GL_SMOOTH );
    glClearColor( 0.0f, 0.0f, 0.0f, 0.5f );
    glClearDepth( 1.0f );
    glEnable( GL_DEPTH_TEST );
    glDepthFunc( GL_LEQUAL );
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    float ratio = 640 /  480;   
    gluPerspective( 45.0f, ratio, 0.1f, 100.0f );
    glMatrixMode( GL_MODELVIEW );
	int frameCount = 0;
	int nextUpdate = SDL_GetTicks() + 1000;

	/* Main loop */
	quit = 0;
	while (!quit)
	{
		while (SDL_PollEvent(&sdlEv))
			switch (sdlEv.type)
			{
				case SDL_QUIT:
					quit = 1;
					break;
 
				default:
					break;
			}
 
  		glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
  		glLoadIdentity();
 
		glTranslatef(0.0, 1.0, -60.0);
		glRotatef(-45.0, 1.0, 0.0, 0.0);
 	
		/* Change time */
		glUniform1f(waveTimeLoc, waveTime);
		glUniform1f(waveWidthLoc, waveWidth);
		glUniform1f(waveHeightLoc, waveHeight);
 
  		/* Bind texture */
  	    glActiveTexture(GL_TEXTURE0);
        int texture_location = glGetUniformLocation(fs, "color_texture");
        glUniform1i(texture_location, 0);
        glBindTexture(GL_TEXTURE_2D, texture[0]);

		/* Draw here a plain surface */
        float TS = 1.0 / 40; //0.025;

		glBegin(GL_QUADS);
		for (i = -20; i < 20; i++)
			for (j = -20; j < 20; j++)
			{

				float startX=  TS*(i+20);
				float startY = TS*(j+20);
				glTexCoord2f( startX + 0.0f, startY + 0 );  glVertex2f(i, j);
				glTexCoord2f( startX + TS, startY + 0 );  glVertex2f(i + 1, j);
				glTexCoord2f( startX + TS, startY + TS );  glVertex2f(i + 1, j + 1);
				glTexCoord2f( startX + 0.0f, startY + TS );  glVertex2f(i, j + 1);
			}
		glEnd();
 
		// Update wave variable
		waveTime += waveFreq;
 
		SDL_GL_SwapBuffers();
		SDL_Delay(30);
		frameCount++;
 
		if (SDL_GetTicks() >= nextUpdate)
		{
			fprintf(stderr, "FPS: %d\n", frameCount);
			frameCount = 0;
			nextUpdate = SDL_GetTicks() + 1000;
		}
	}
 
	glDeleteShader(vs);
	glDeleteShader(fs);
	glDeleteProgram(sp);
 
	return EXIT_SUCCESS;
}

 

Lesson 14: Reflection (Easy cool effect)

Standard
blue-270x270-5

The reflection effect is very easy to create in OpenGL, but at the same time it is a very cool effect. Creating reflection in OpenGL can be done simply by drawing the exact same scene twice, once in the positive and once in the negative direction. We can do this as (pseudo code):

drawScene()
drawPlane()
flipScreen()
drawScene()

We created some functions to easily call the drawing process.  As you draw more things, this code will scale, so it’s a good practice to do.  In addition we used blending to create a semi-transparent plane. After all, we should be able to see the reflection :) We make use of a stencil buffer to make the scene draw correctly.  This is what the program output looks like:

The output will be like this, depending on if you use texturing or not.

OpenGL Reflection

We end up with this code:

/*
*  OpenGL Textured Cube
*  http://talkera.org/opengl/  
*  http://openglsamples.sf.net
*
*  Compilation trough:
*    gcc texcube.c -lglut -lGL -lGLU -lSDL 
*
*/


#include <stdio.h>
#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include "SDL/SDL.h"

/* screen width, height, and bit depth */
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define SCREEN_BPP     16

/* Setup useful booleans */
#define TRUE  1
#define FALSE 0

/* This is our SDL surface */
SDL_Surface *surface;

/* Used for rotating the cube */
GLfloat rotationXaxis; 
GLfloat rotationYaxis; 
GLfloat rotationZaxis; 

/* Storage For One Texture ( NEW ) */
GLuint texture[1]; 

/* Release resources and quit  */
void Quit( int returnCode ) {
    SDL_Quit( );
    exit( returnCode );
}

/* Loads in a bitmap as a GL texture */
int LoadGLTextures( ) {
    int Status = FALSE;

    /* Create storage space for the texture */
    SDL_Surface *TextureImage[1]; 

    /* Load The Bitmap into Memory */
    if ((TextureImage[0] = SDL_LoadBMP("cube.bmp"))) {
	    Status = TRUE;
	    glGenTextures( 1, &texture[0] );
	    glBindTexture( GL_TEXTURE_2D, texture[0] );
	    glTexImage2D( GL_TEXTURE_2D, 0, 3, TextureImage[0]->w,
			  TextureImage[0]->h, 0, GL_BGR,
			  GL_UNSIGNED_BYTE, TextureImage[0]->pixels );
	    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
        }

    /* Free up some memory */
    if ( TextureImage[0] )
	    SDL_FreeSurface( TextureImage[0] );

    return Status;
}

/* function to reset our viewport after a window resize */
int resizeWindow( int width, int height ) {
    /* Height / width ration */
    GLfloat ratio;
 
    /* Protect against a divide by zero */
    if ( height == 0 )
	height = 1;

    ratio = ( GLfloat )width / ( GLfloat )height;

    /* Setup our viewport. */
    glViewport( 0, 0, ( GLint )width, ( GLint )height );

    /*
     * change to the projection matrix and set
     * our viewing volume.
     */
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity( );

    /* Set our perspective */
    gluPerspective( 45.0f, ratio, 0.1f, 100.0f );

    /* Make sure we're chaning the model view and not the projection */
    glMatrixMode( GL_MODELVIEW );

    /* Reset The View */
    glLoadIdentity( );

    return( TRUE );
}

/* function to handle key press events */
void handleKeyPress( SDL_keysym *keysym )
{
    switch ( keysym->sym )
	{
 	case SDLK_ESCAPE:
	    /* ESC key was pressed */
	    Quit( 0 );
	    break;
	case SDLK_F1:
	    /* F1 key was pressed
	     * this toggles fullscreen mode
	     */
	    SDL_WM_ToggleFullScreen( surface );
	    break;
	default:
	    break;
	}

    return;
}

/* OpenGL initialization function */
int initGL( GLvoid )
{

    /* Load in the texture */
    if ( !LoadGLTextures( ) )
	return FALSE;

    /* Enable Texture Mapping ( NEW ) */
    glEnable( GL_TEXTURE_2D );

    /* Enable smooth shading */
    glShadeModel( GL_SMOOTH );

    /* Set the background black */
    glClearColor( 0.0f, 0.0f, 0.0f, 0.5f );

    /* Depth buffer setup */
    glClearDepth( 1.0f );

    /* Enables Depth Testing */
    glEnable( GL_DEPTH_TEST );

    /* The Type Of Depth Test To Do */
    glDepthFunc( GL_LEQUAL );

    /* Really Nice Perspective Calculations */
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );


    GLfloat amb_light[] = { 0.5, 0.5, 0.5, 1.0 };
    GLfloat diffuse[] = { 0.6, 0.6, 0.6, 1 };
    GLfloat specular[] = { 0.7, 0.7, 0.3, 1 };
    glLightModelfv( GL_LIGHT_MODEL_AMBIENT, amb_light );
    glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse );
    glLightfv( GL_LIGHT0, GL_SPECULAR, specular );
    glEnable( GL_LIGHT0 );
    glEnable( GL_COLOR_MATERIAL );
    glShadeModel( GL_SMOOTH );
    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE );
    glDepthFunc( GL_LEQUAL );
    glEnable( GL_DEPTH_TEST );
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0); 

    return( TRUE );
}

void drawWorld()
{
  /* Select Our Texture */
    glBindTexture( GL_TEXTURE_2D, texture[0] );

    /*
     * Draw the cube. A cube consists of six quads, with four coordinates (glVertex3f) 
     * per quad.
     *
     */
    glBegin(GL_QUADS);
      /* Front Face */
      glNormal3f( 0.0f, 0.0f, 1.0f);        
      glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, 1.0f );
      glTexCoord2f( 1.0f, 1.0f ); glVertex3f(  1.0f, -1.0f, 1.0f );
      glTexCoord2f( 1.0f, 0.0f ); glVertex3f(  1.0f,  1.0f, 1.0f );
      glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -1.0f,  1.0f, 1.0f );

      /* Back Face */
      glNormal3f( 0.0f, 0.0f,-1.0f);  
      glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
      glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -1.0f,  1.0f, -1.0f );
      glTexCoord2f( 1.0f, 1.0f ); glVertex3f(  1.0f,  1.0f, -1.0f );
      glTexCoord2f( 1.0f, 0.0f ); glVertex3f(  1.0f, -1.0f, -1.0f );

      /* Top Face */
      glNormal3f( 0.0f, 1.0f, 0.0f); 
      glTexCoord2f( 1.0f, 1.0f ); glVertex3f( -1.0f,  1.0f, -1.0f );
      glTexCoord2f( 1.0f, 0.0f ); glVertex3f( -1.0f,  1.0f,  1.0f );
      glTexCoord2f( 0.0f, 0.0f ); glVertex3f(  1.0f,  1.0f,  1.0f );
      glTexCoord2f( 0.0f, 1.0f ); glVertex3f(  1.0f,  1.0f, -1.0f );

      /* Bottom Face */
      /* Top Right Of The Texture and Quad */
      glNormal3f( 0.0f,-1.0f, 0.0f); 
      glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
      glTexCoord2f( 1.0f, 1.0f ); glVertex3f(  1.0f, -1.0f, -1.0f );
      glTexCoord2f( 1.0f, 0.0f ); glVertex3f(  1.0f, -1.0f,  1.0f );
      glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -1.0f, -1.0f,  1.0f );

      /* Right face */
      glNormal3f( 1.0f, 0.0f, 0.0f); 
      glTexCoord2f( 0.0f, 0.0f ); glVertex3f( 1.0f, -1.0f, -1.0f );
      glTexCoord2f( 0.0f, 1.0f ); glVertex3f( 1.0f,  1.0f, -1.0f );
      glTexCoord2f( 1.0f, 1.0f ); glVertex3f( 1.0f,  1.0f,  1.0f );
      glTexCoord2f( 1.0f, 0.0f ); glVertex3f( 1.0f, -1.0f,  1.0f );

      /* Left Face */
      glNormal3f(-1.0f, 0.0f, 0.0f); 
      glTexCoord2f( 1.0f, 0.0f ); glVertex3f( -1.0f, -1.0f, -1.0f );
      glTexCoord2f( 0.0f, 0.0f ); glVertex3f( -1.0f, -1.0f,  1.0f );
      glTexCoord2f( 0.0f, 1.0f ); glVertex3f( -1.0f,  1.0f,  1.0f );
      glTexCoord2f( 1.0f, 1.0f ); glVertex3f( -1.0f,  1.0f, -1.0f );
    glEnd( );


}

void drawFloor() 
{
  glBegin(GL_QUADS);
    glColor3f(1,1,1);
    glTexCoord2f( 0.0f, 1.0f );  glVertex3f( -12.0f, 0.0f, -12.0f );
    glTexCoord2f( 1.0f, 1.0f );  glVertex3f(  12.0f, 0.0f, -12.0f );
    glTexCoord2f( 1.0f, 0.0f );  glVertex3f(  12.0f, 0.0f,  12.0f );
    glTexCoord2f( 0.0f, 0.0f );  glVertex3f( -12.0f, 0.0f,  12.0f );
  glEnd();  
}

/* Here goes our drawing code */
int drawGLScene( GLvoid )
{
    /* Clear The Screen And The Depth Buffer */
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
  
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity( );

    /* Move Into The Screen 5 Units */
    glTranslatef( 0.0f, -2.0f, -12.0f );

    /* Draw First Scene */
    glPushMatrix();
    glTranslatef(0, -2, 0);
    glRotatef( rotationXaxis, 1.0f, 0.0f, 0.0f); /* Rotate On The X Axis */
    glRotatef( rotationYaxis, 0.0f, 1.0f, 0.0f); /* Rotate On The Y Axis */
    glColor3f(0.2,0.2,1);
    drawWorld();
    glPopMatrix();

    // turn on alpha blending for transparency
    glEnable(GL_BLEND);    
    glDisable(GL_DEPTH_TEST); 
    glBlendFunc(GL_SRC_ALPHA,GL_ONE);   

    /* Draw Floor */
    drawFloor();

    /* Disable Blending */
    glDisable(GL_BLEND);    
    glColor3f(1,1,1);
   
    /* Enable Stencil Buffer */
    glEnable(GL_STENCIL_TEST);
    glColorMask(0, 0, 0, 0); 
    glDisable(GL_DEPTH_TEST); 
    glStencilFunc(GL_ALWAYS, 1, 1); 
    glStencilOp(GL_KEEP, GL_KEEP, GL_REPLACE);
    glColorMask(1, 1, 1, 1); 
    glEnable(GL_DEPTH_TEST); 
    glStencilFunc(GL_EQUAL, 1, 1);
    glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);

    /* Draw Scene Second time */
    glPushMatrix();
    glTranslatef(0, 2, 0);
    glScalef(1, -1, 1);
    glRotatef( rotationXaxis, 1.0f, 0.0f, 0.0f); /* Rotate On The X Axis */
    glRotatef( rotationYaxis, 0.0f, 1.0f, 0.0f); /* Rotate On The Y Axis */
    drawWorld();
    glPopMatrix();
 
  
    /* Draw it to the screen */
    SDL_GL_SwapBuffers( );

    /* Rotate Cube */
    rotationXaxis += 0.3f; 
    rotationYaxis += 0.2f;

    return( TRUE );
}

/* http://talkera.org.cp-in-1.webhostbox.net/opengl/ */
int main( int argc, char **argv )
{
    /* Flags to pass to SDL_SetVideoMode */
    int videoFlags;
    /* main loop variable */
    int done = FALSE;
    /* used to collect events */
    SDL_Event event;
    /* this holds some info about our display */
    const SDL_VideoInfo *videoInfo;
    /* whether or not the window is active */
    int isActive = TRUE;

    /* initialize SDL */
    if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
	{
	    fprintf( stderr, "Video initialization failed: %s\n",
		     SDL_GetError( ) );
	    Quit( 1 );
	}

    /* Fetch the video info */
    videoInfo = SDL_GetVideoInfo( );

    if ( !videoInfo )
	{
	    fprintf( stderr, "Video query failed: %s\n",
		     SDL_GetError( ) );
	    Quit( 1 );
	}

    /* the flags to pass to SDL_SetVideoMode */
    videoFlags  = SDL_OPENGL;          /* Enable OpenGL in SDL */
    videoFlags |= SDL_GL_DOUBLEBUFFER; /* Enable double buffering */
    videoFlags |= SDL_HWPALETTE;       /* Store the palette in hardware */
    videoFlags |= SDL_RESIZABLE;       /* Enable window resizing */

    /* This checks to see if surfaces can be stored in memory */
    if ( videoInfo->hw_available )
	videoFlags |= SDL_HWSURFACE;
    else
	videoFlags |= SDL_SWSURFACE;

    /* This checks if hardware blits can be done */
    if ( videoInfo->blit_hw )
	videoFlags |= SDL_HWACCEL;

    /* Sets up OpenGL double buffering */
    SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );

    /* get a SDL surface */
    surface = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP,
				videoFlags );

    /* Verify there is a surface */
    if ( !surface )
	{
	    fprintf( stderr,  "Video mode set failed: %s\n", SDL_GetError( ) );
	    Quit( 1 );
	}

    /* initialize OpenGL */
    initGL( );

    /* resize the initial window */
    resizeWindow( SCREEN_WIDTH, SCREEN_HEIGHT );

    /* wait for events */
    while ( !done )
	{
	    /* handle the events in the queue */

	    while ( SDL_PollEvent( &event ) )
		{
		    switch( event.type )
			{
			case SDL_ACTIVEEVENT:
			    if ( event.active.gain == 0 )
				isActive = FALSE;
			    else
				isActive = TRUE;
			    break;			    
			case SDL_VIDEORESIZE:
			    /* handle resize event */
			    surface = SDL_SetVideoMode( event.resize.w,
							event.resize.h,
							16, videoFlags );
			    if ( !surface )
				{
				    fprintf( stderr, "Could not get a surface after resize: %s\n", SDL_GetError( ) );
				    Quit( 1 );
				}
			    resizeWindow( event.resize.w, event.resize.h );
			    break;
			case SDL_KEYDOWN:
			    /* handle key presses */
			    handleKeyPress( &event.key.keysym );
			   
			    break;
			case SDL_QUIT:
			    /* handle quit requests */
			    done = TRUE;
			    break;
			default:
			    break;
			}
		}

	    /* draw the scene */
	    if ( isActive )
		drawGLScene( );
	}

    /* clean ourselves up and exit */
    Quit( 0 );

    /* Should never get here */
    return( 0 );
}

Lesson 13: Face generation

Standard
blue-270x270-1

We can generate 3D faces from 2D flat images. Using OpenGL this can be challenging, after all it is not a lot more than a primitive triangle rendering API. The technique we will use in this tutorial is very similar, if not nearly identical, to terrain generation in our previous tutorial. We got the original face image from the web:  http://everythingforpeople.files.wordpress.com/2011/11/7098-natalie-portman-black-and-white-earrings-closeup-face.jpg  A face image is not all we need. We will also need a height map image. You can create one in paint based on the original image, or use more sophisticated tools :) Finally we have two images:  the frontal face texture  and the height map texture. Such that we have this setup  (left: input data,  right: output).

OpenGL 3d Face Generation

OpenGL 3d Face Generation

The height map (gray scale image above) is a representations of the depth of each vertex.  The darker the color, the more depth it has.  In other words, the height map is a matrix M where k ∈ M is the Y-coordinates of a vertex. The X and Z coordinates of the vertexes are evenly spaced across the space. The quality of the face image depends on the height map image, e.g. garbage in, garbage out. In our case, the height map is detailed enough for an example.  Thus we simply render a set of vertexes and connect them as quads. See image below:

Human Face Quads OpenGL

Human Face Quads OpenGL

We then calculate the texture coordinates to use on those quads. In our case the height map image is 24bpp. If you decide to use another depth, you’ll have to modify the code. In addition we use a hard coded image and image width/height. These are all things you may want to modify. Basically all the code below does is allocate memory for our matrix M (in this case height[][]), load an image, and fill the area with the pixel data.

int** height;

Uint32 get_pixel(SDL_Surface *surface, int x, int y)
{
    int bpp = surface->format->BytesPerPixel;
    /* Here p is the address to the pixel we want to retrieve */
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    switch(bpp) {
    case 1:
        return *p;

    case 2:
        return *(Uint16 *)p;

    case 3:
        if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
            return p[0] << 16 | p[1] << 8 | p[2];
        else
            return p[0] | p[1] << 8 | p[2] << 16;

    case 4:
        return *(Uint32 *)p;

    default:
        return 0;       /* shouldn't happen, but avoids warnings */
    }
}

int MAP_W = 128;
int MAP_H = 128;
void loadMap()
{
    int x, y, i;
    SDL_Surface *TextureImage[1]; 
 
    int nrows = MAP_W;
    int ncolumns = MAP_H;

    height = malloc(nrows * sizeof(int *));
    if(height == NULL)
    {
        fprintf(stderr, "out of memory\n");
        exit(1);
    }

    for(i = 0; i < nrows; i++)
    {
        height[i] = malloc(ncolumns * sizeof(int));
        if(height[i] == NULL)
            {
            fprintf(stderr, "out of memory\n");
            exit(1);
            }
    }


    /* Load The Bitmap into Memory */
    float depth = 40;
    if ((TextureImage[0] = SDL_LoadBMP("terrain.bmp"))) {

        for (x = 1; x < MAP_W; x++) { 
          for (y = 1; y < MAP_H; y++) {
               height[x][y] = ((float) get_pixel(TextureImage[0],x,y) / pow(2,24)) * depth;  
          }
        }
    }
}

We can then draw the face on the screen using:

void drawMap()
{
    float tw = 1.3;
    float texturestep = 1.0f / (MAP_W);

    glPushMatrix();
    for (int x = 1; x < MAP_W-1; x++) { 
      for (int z = 1; z < MAP_H-1; z++) {
        glBegin(GL_QUADS);
         glTexCoord2f( x*texturestep, (z+1)*texturestep ); 
         glVertex3f( -tw + x*tw, height[x][z] , -tw + z*tw );
         glTexCoord2f( (x+1)*texturestep, (z+1)*texturestep ); 
         glVertex3f( -tw + x*tw + tw,height[x+1][z] , -tw + z*tw );
         glTexCoord2f( (x+1)*texturestep, (z)*texturestep ); 
         glVertex3f( -tw + x*tw + tw, height[x+1][z+1] , -tw + z*tw + tw);
         glTexCoord2f( (x)*texturestep, (z)*texturestep ); 
         glVertex3f( -tw + x*tw ,  height[x][z+1] , -tw + z*tw + tw);
        glEnd();
      }
    }
    glPopMatrix();
}

Basically what this does is draw a set of connected quads, where each vertex has a coordinate according to the height map. Once everything is setup, you should get results as:

OpenGL Human Face

OpenGL Human Face

The total code will look something like this.

/*
*  OpenGL Face Generation
*  http://talkera.org/opengl/  
*  http://openglsamples.sf.net
*
*  Compilation trough:
*    gcc cube.c -lglut -lGL -lGLU -lSDL 
*
*/
 
 
#include <stdio.h>
#include <stdlib.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include "SDL/SDL.h"

 
/* screen width, height, and bit depth */
#define SCREEN_WIDTH  640
#define SCREEN_HEIGHT 480
#define SCREEN_BPP     16
 
/* Setup useful booleans */
#define TRUE  1
#define FALSE 0
 
/* This is our SDL surface */
SDL_Surface *surface;
 
/* Used for rotating the cube */
GLfloat rotationXaxis; 
GLfloat rotationYaxis; 
GLfloat rotationZaxis; 
 
/* Storage For One Texture ( NEW ) */
GLuint texture[1]; 


 
/* Release resources and quit  */
void Quit( int returnCode ) {
    SDL_Quit( );
    exit( returnCode );
}
 
/* Loads in a bitmap as a GL texture */
int LoadGLTextures( ) {
    int Status = FALSE;
 
    /* Create storage space for the texture */
    SDL_Surface *TextureImage[1]; 
 
    /* Load The Bitmap into Memory */
    if ((TextureImage[0] = SDL_LoadBMP("face.bmp"))) {
        Status = TRUE;
        glGenTextures( 1, &texture[0] );
        glBindTexture( GL_TEXTURE_2D, texture[0] );
        glTexImage2D( GL_TEXTURE_2D, 0, 3, TextureImage[0]->w,
              TextureImage[0]->h, 0, GL_BGR,
              GL_UNSIGNED_BYTE, TextureImage[0]->pixels );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
        glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
        }
 
    /* Free up some memory */
    if ( TextureImage[0] )
        SDL_FreeSurface( TextureImage[0] );
 
    return Status;
}
 
/* function to reset our viewport after a window resize */
int resizeWindow( int width, int height ) {
    /* Height / width ration */
    GLfloat ratio;
 
    /* Protect against a divide by zero */
    if ( height == 0 )
    height = 1;
 
    ratio = ( GLfloat )width / ( GLfloat )height;
 
    /* Setup our viewport. */
    glViewport( 0, 0, ( GLint )width, ( GLint )height );
 
    /*
     * change to the projection matrix and set
     * our viewing volume.
     */
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity( );
 
    /* Set our perspective */
    gluPerspective( 45.0f, ratio, 0.1f, 1500.0f );
 
    /* Make sure we're chaning the model view and not the projection */
    glMatrixMode( GL_MODELVIEW );
 
    /* Reset The View */
    glLoadIdentity( );
 
    return( TRUE );
}
 
/* function to handle key press events */
void handleKeyPress( SDL_keysym *keysym )
{
    switch ( keysym->sym )
    {
     case SDLK_ESCAPE:
        /* ESC key was pressed */
        Quit( 0 );
        break;
    case SDLK_F1:
        /* F1 key was pressed
         * this toggles fullscreen mode
         */
        SDL_WM_ToggleFullScreen( surface );
        break;
    default:
        break;
    }
 
    return;
}
 
/* OpenGL initialization function */
int initGL( GLvoid )
{
 
    /* Load in the texture */
    if ( !LoadGLTextures( ) )
    return FALSE;
 
    /* Enable Texture Mapping ( NEW ) */
    glEnable( GL_TEXTURE_2D );
 
    /* Enable smooth shading */
    glShadeModel( GL_SMOOTH );
 
    /* Set the background black */
    glClearColor( 0.0f, 0.0f, 0.0f, 0.5f );
 
    /* Depth buffer setup */
    glClearDepth( 1.0f );
 
    /* Enables Depth Testing */
    glEnable( GL_DEPTH_TEST );
 
    /* The Type Of Depth Test To Do */
    glDepthFunc( GL_LEQUAL );
 
    /* Really Nice Perspective Calculations */
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
 
    glEnable(GL_NORMALIZE) ;
   

    return( TRUE );
}



int** height;

Uint32 get_pixel(SDL_Surface *surface, int x, int y)
{
    int bpp = surface->format->BytesPerPixel;
    /* Here p is the address to the pixel we want to retrieve */
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    switch(bpp) {
    case 1:
        return *p;

    case 2:
        return *(Uint16 *)p;

    case 3:
        if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
            return p[0] << 16 | p[1] << 8 | p[2];
        else
            return p[0] | p[1] << 8 | p[2] << 16;

    case 4:
        return *(Uint32 *)p;

    default:
        return 0;       /* shouldn't happen, but avoids warnings */
    }
}

int MAP_W = 128;
int MAP_H = 128;
void loadMap()
{
    int x, y, i;
    SDL_Surface *TextureImage[1]; 
 
    int nrows = MAP_W;
    int ncolumns = MAP_H;

    height = malloc(nrows * sizeof(int *));
    if(height == NULL)
    {
        fprintf(stderr, "out of memory\n");
        exit(1);
    }

    for(i = 0; i < nrows; i++)
    {
        height[i] = malloc(ncolumns * sizeof(int));
        if(height[i] == NULL)
            {
            fprintf(stderr, "out of memory\n");
            exit(1);
            }
    }


    /* Load The Bitmap into Memory */
    float depth = 40;
    if ((TextureImage[0] = SDL_LoadBMP("terrain.bmp"))) {

        for (x = 1; x < MAP_W; x++) { 
          for (y = 1; y < MAP_H; y++) {
               height[x][y] = ((float) get_pixel(TextureImage[0],x,y) / pow(2,24)) * depth;  
          }
        }
    }
}

void drawMap()
{
    float tw = 4.3;
    float texturestep = 1.0f / (MAP_W);

    glPushMatrix();
    for (int x = 1; x < MAP_W-1; x++) { 
      for (int z = 1; z < MAP_H-1; z++) {
        glBegin(GL_QUADS);
         glTexCoord2f( x*texturestep, (z+1)*texturestep ); 
         glVertex3f( -tw + x*tw, height[x][z] , -tw + z*tw );
         glTexCoord2f( (x+1)*texturestep, (z+1)*texturestep ); 
         glVertex3f( -tw + x*tw + tw,height[x+1][z] , -tw + z*tw );
         glTexCoord2f( (x+1)*texturestep, (z)*texturestep ); 
         glVertex3f( -tw + x*tw + tw, height[x+1][z+1] , -tw + z*tw + tw);
         glTexCoord2f( (x)*texturestep, (z)*texturestep ); 
         glVertex3f( -tw + x*tw ,  height[x][z+1] , -tw + z*tw + tw);
        glEnd();
      }
    }
    glPopMatrix();
}
 
/* Here goes our drawing code */
int drawGLScene( GLvoid )
{
    /* Clear The Screen And The Depth Buffer */
    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
 
    /* Move Into The Screen 5 Units */
    glLoadIdentity( );
    glTranslatef( -75.0f, 300.0f, 0.0f );

    /* Set camera  */
    gluLookAt( 3,866.6,20 , 0,0,-30, 0,1,0);
 
    /* Select Our Texture */
    glBindTexture( GL_TEXTURE_2D, texture[0] );

    //glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );

    /* Draw Face */
    drawMap();

    /* Draw it to the screen */
    SDL_GL_SwapBuffers( );
 
    return( TRUE );
}
 
/* http://talkera.org.cp-in-1.webhostbox.net/opengl/ */
int main( int argc, char **argv )
{
    /* Flags to pass to SDL_SetVideoMode */
    int videoFlags;
    /* main loop variable */
    int done = FALSE;
    /* used to collect events */
    SDL_Event event;
    /* this holds some info about our display */
    const SDL_VideoInfo *videoInfo;
    /* whether or not the window is active */
    int isActive = TRUE;
 
    /* initialize SDL */
    if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
        fprintf( stderr, "Video initialization failed: %s\n",
             SDL_GetError( ) );
        Quit( 1 );
    }
 
    /* Fetch the video info */
    videoInfo = SDL_GetVideoInfo( );
 
    if ( !videoInfo )
    {
        fprintf( stderr, "Video query failed: %s\n",
             SDL_GetError( ) );
        Quit( 1 );
    }
 
    /* the flags to pass to SDL_SetVideoMode */
    videoFlags  = SDL_OPENGL;          /* Enable OpenGL in SDL */
    videoFlags |= SDL_GL_DOUBLEBUFFER; /* Enable double buffering */
    videoFlags |= SDL_HWPALETTE;       /* Store the palette in hardware */
    videoFlags |= SDL_RESIZABLE;       /* Enable window resizing */
 
    /* This checks to see if surfaces can be stored in memory */
    if ( videoInfo->hw_available )
    videoFlags |= SDL_HWSURFACE;
    else
    videoFlags |= SDL_SWSURFACE;
 
    /* This checks if hardware blits can be done */
    if ( videoInfo->blit_hw )
    videoFlags |= SDL_HWACCEL;
 
    /* Sets up OpenGL double buffering */
    SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER, 1 );
 
    /* get a SDL surface */
    surface = SDL_SetVideoMode( SCREEN_WIDTH, SCREEN_HEIGHT, SCREEN_BPP,
                videoFlags );
 
    /* Verify there is a surface */
    if ( !surface )
    {
        fprintf( stderr,  "Video mode set failed: %s\n", SDL_GetError( ) );
        Quit( 1 );
    }
 
    /* initialize OpenGL */
    initGL( );
 
    /* resize the initial window */
    resizeWindow( SCREEN_WIDTH, SCREEN_HEIGHT );
 
    /* load map */
    loadMap();

    /* wait for events */
    while ( !done )
    {
        /* handle the events in the queue */
 
        while ( SDL_PollEvent( &event ) )
        {
            switch( event.type )
            {
            case SDL_ACTIVEEVENT:
                if ( event.active.gain == 0 )
                isActive = FALSE;
                else
                isActive = TRUE;
                break;                
            case SDL_VIDEORESIZE:
                /* handle resize event */
                surface = SDL_SetVideoMode( event.resize.w,
                            event.resize.h,
                            16, videoFlags );
                if ( !surface )
                {
                    fprintf( stderr, "Could not get a surface after resize: %s\n", SDL_GetError( ) );
                    Quit( 1 );
                }
                resizeWindow( event.resize.w, event.resize.h );
                break;
            case SDL_KEYDOWN:
                /* handle key presses */
                handleKeyPress( &event.key.keysym );
               
                break;
            case SDL_QUIT:
                /* handle quit requests */
                done = TRUE;
                break;
            default:
                break;
            }
        }
 
        /* draw the scene */
        if ( isActive )
        drawGLScene( );
    }
 
    /* clean ourselves up and exit */
    Quit( 0 );
 
    /* Should never get here */
    return( 0 );
}

Keep in mind that this is for 24bpp heightmaps only, thus it would need some modifications for other color depths. In addition I hardcoded 128×128 resolution. This serves as example, but if you want to include it in a game or experiment you may want to modify a bit :) You may want to use a separate class SDL_LoadBMP loads bitmap images only.

Lesson 12: Terrain Rendering with OpenGL

Standard
opengl heightmap

In this tutorial we will render terrains. Essentially a terrain is a complex 3d model.  It conists of vertexes, faces, texture coordinates, normal vectors etc.  Al though it is possible to load complete models such as Wavefront OBJ models, it is generally better to dynamically generate them.  First, we must define a terrain (or variation in heights) in some way. At the most basic level, we could define matrix M where every element of M is a height on the map.  For example, we could define this matrix:

int M[8][8] ={
{ 0, 0, 0, 0,0,0,0,0,0 },
{ 1, 1, 1, 1,1,1,1,1,1 },
{ 1, 3, 2, 3,3,4,2,1,1 },
{ 1, 2, 1, 2,2,2,2,1,1 },
{ 1, 2, 1, 2,2,2,2,1,1 },
{ 1, 2, 1, 2,2,2,2,1,1 },
{ 1, 2, 2, 3,3,3,3,2,1 },
{ 1, 2, 2, 3,4,4,3,3,1 },
{ 1, 2, 1, 1,1,1,1,1,1 },
{ 0, 1, 1, 1,1,1,1,1,1 } };

And every element represents a height (the y coordinate of a vertex). The x and z coordinates of the vertexes are evenly spaced across the map.  This map would result in the figure below. (Right: output image, Left: output image with added texture and lighting).

OpenGL Terrain Generation

OpenGL Terrain Generation

To draw this we would simply used a drawing code as:

    int MAP_SIZE = 10;
    glPushMatrix();
    for (int x = 1; x < MAP_SIZE-1; x++) { 
      for (int z = 1; z < MAP_SIZE-1; z++) {
        glBegin(GL_QUADS);
          glVertex3f(   x, height[x][z]    , z );
          glVertex3f( x+1, height[x+1][z]  , z );
          glVertex3f( x+1, height[x+1][z+1], z + 1);
          glVertex3f(   x, height[x][z+1]  , z + 1);
        glEnd();
      }
    }
    glPopMatrix();

If you want to use textures or face normals, you have to calculate them and add them here too :)  To draw in wireframe mode you can use:

   glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );

This will work well for small terrains, but for bigger terrains this gets hard to maintain. E.g. if you want a larger terrain, the matrix M would quickly increase in size too. Not to mention all the manual typing of coordinates required. To solve this problem, we could use an gray scale image representing the heights of the map. Every pixel in this image represents the y-coordinate of a vertex on the map. The program would then load the image, convert each pixel to an element of height map M.  Consider these examples:

opengl heightmap

opengl heightmap

You could render a more complicated terrain map using this technique:

OpenGL terrain

OpenGL terrain

To get a pixel value you can use this code:

Uint32 get_pixel(SDL_Surface *surface, int x, int y)
{
    int bpp = surface->format->BytesPerPixel;
    /* Here p is the address to the pixel we want to retrieve */
    Uint8 *p = (Uint8 *)surface->pixels + y * surface->pitch + x * bpp;

    switch(bpp) {
    case 1:
        return *p;

    case 2:
        return *(Uint16 *)p;

    case 3:
        if(SDL_BYTEORDER == SDL_BIG_ENDIAN)
            return p[0] << 16 | p[1] << 8 | p[2];
        else
            return p[0] | p[1] << 8 | p[2] << 16;

    case 4:
        return *(Uint32 *)p;

    default:
        return 0;     
    }
}

You may need to do normalization of the vectors and allocate enough memory (if you are using C/C++).  Finally you would end up with something like:

if ((TextureImage[0] = SDL_LoadBMP("terrain.bmp"))) {
 for (x = 1; x < MAP_W; x++) { 
  for (y = 1; y < MAP_H; y++) {
   height[x][y] = variation * ( get_pixel_normalized(TextureImage[0],x,y) ) ;  }
 }
}

This is all theory required to do basic terrain generation, happy coding :)

Lesson 11: Loading Quake II (MD2) Models

Standard
blue-270x270-3

In this tutorial we will demonstrate how to load Quake II models.  The video game was released around 1997-1999, so models might be hard to find. You can however use/buy the original game. If you search a lot, you may also find some models online. Quake II models are referred to as MD2 models.  Other than Quake II this 3d model format was also used in Sin and Soldier of Fortune.  Unlike the model formats we discussed earlier, this model format is mostly used for animation. The general features of this model format are:

MD2 model features:

  • Model’s geometric data (triangles);
  • Frame-by-frame animations;
  • Structured data for drawing the model using GL_TRIANGLE_FAN and GL_TRIANGLE_STRIP primitives (called “OpenGL commands”)

Unlike Wavefront OBJ or the PLY format, the MD2 file format starts with an MD2 header.  On an abstract level the format looks like this:

format

The MD2 header format is defined as:

Offset Data type Name Description
0 int ident Magic number. Must be equal to “IDP2″
4 int version MD2 version. Must be equal to 8
8 int skinwidth Width of the texture
12 int skinheight Height of the texture
16 int framesize Size of one frame in bytes
20 int num_skins Number of textures
24 int num_xyz Number of vertices
28 int num_st Number of texture coordinates
32 int num_tris Number of triangles
36 int num_glcmds Number of OpenGL commands
40 int num_frames Total number of frames
44 int ofs_skins Offset to skin names (each skin name is an unsigned char[64] and are null terminated)
48 int ofs_st Offset to s-t texture coordinates
52 int ofs_tris Offset to triangles
56 int ofs_frames Offset to frame data
60 int ofs_glcmds Offset to OpenGL commands
64 int ofs_end Offset to end of file

Similar to the TGA loader, we can load the entire block directly into a C struct.

typedef struct _MD2_Header
	{
		int ident;        // identifies as quake II header  (IDP2)
		int version;      // mine is 8
		int skinwidth;    // width of texture
		int skinheight;   // height of texture
		int framesize;    // number of bytes per frame
		int numSkins;     // number of textures
		int numXYZ;       // number of points
		int numST;        // number of texture
		int numTris;      // number of triangles
		int numGLcmds;
		int numFrames;    // total number of frames
		int offsetSkins;  // offset to skin names (64 bytes each)
		int offsetST;     // offset of texture s-t values
		int offsetTris;   // offset of triangle mesh
		int offsetFrames; // offset of frame data (points)
		int offsetGLcmds;
		int offsetEnd;    // end of file
	} MD2_Header;

An MD2 file format stores animation by several “key frames”, e.g. a multitude of point clouds. Depending on which set of vertexes you display you get a different model position. MD2 stores various of such point clouds for running, walking, jumping etc.  In order to get a smooth animation, you may wish to interpolate between the keypoints frames. In addition to the previous tutorials we will also load texture coordinates in this tutorial. A texture is an image which is added on top of the 3d model, to make it appear more realistic. In the end however, all these model collections are similar: they all hold vertexes, faces, vertex normal’s and potentially texture coordinates.

We have created a class for loading MD2 models and PCX images. PCX images or textures generally came along with these models. This code needs re factoring, but it serves as simple example. You may want to re-implement it using this as reference, do not use in production code! We will need two files:  md2.cpp and main.cpp.  md2.cpp will contain the class which parses the md2 model format, while main.cpp will set the scene.

main.cpp

/*
 *
 * Demonstrates how to load and display an MD2 file.
 * Using triangles and normals as static object. No texture mapping.
 * http://talkera.org/opengl/
 *
 * OBJ files must be triangulated!!!
 * Non triangulated objects wont work!
 * You can use Blender to triangulate
 *
 */
 
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <string.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <cmath>
 #include "md2.h"

 
#define KEY_ESCAPE 27
 
using namespace std;
 
/************************************************************************
  Window
 ************************************************************************/
 
typedef struct {
    int width;
	int height;
	char* title;
 
	float field_of_view_angle;
	float z_near;
	float z_far;
} glutWindow;
 
 
 
/***************************************************************************
 * Program code
 ***************************************************************************/
 
Model_MD2 obj;
float g_rotation;
glutWindow win;
 
void display() 
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	gluLookAt( 0,1,-100, 0,0,0, 0,1,0);
	glPushMatrix();
		glRotatef(g_rotation,0,1,0);
		glRotatef(-90,1,0,0);
		g_rotation++;
		obj.Draw();
	glPopMatrix();
	glutSwapBuffers();
}
 
 
void initialize () 
{
    glMatrixMode(GL_PROJECTION);
	glViewport(0, 0, win.width, win.height);
	GLfloat aspect = (GLfloat) win.width / win.height;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
	gluPerspective(win.field_of_view_angle, aspect, win.z_near, win.z_far);
    glMatrixMode(GL_MODELVIEW);
    glShadeModel( GL_SMOOTH );
    glClearColor( 0.5f, 0.5f, 0.5f, 0.5f );
    glClearDepth( 1.0f );
    glEnable( GL_DEPTH_TEST );
    glDepthFunc( GL_LEQUAL );
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
 
    GLfloat amb_light[] = { 0.5, 0.5, 0.5, 1.0 };
    GLfloat diffuse[] = { 0.6, 0.6, 0.6, 1 };
    GLfloat specular[] = { 0.7, 0.7, 0.3, 1 };
    glLightModelfv( GL_LIGHT_MODEL_AMBIENT, amb_light );
    glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse );
    glLightfv( GL_LIGHT0, GL_SPECULAR, specular );
    glEnable( GL_LIGHT0 );
    glEnable( GL_COLOR_MATERIAL );
    glShadeModel( GL_SMOOTH );
    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE );
    glDepthFunc( GL_LEQUAL );
    glEnable( GL_DEPTH_TEST );
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0); 
}
 
 
void keyboard ( unsigned char key, int x, int y ) 
{
  switch ( key ) {
    case KEY_ESCAPE:        
      exit ( 0 );   
      break;      
    default:      
      break;
  }
}
 
int main(int argc, char **argv) 
{
	// set window values
	win.width = 640;
	win.height = 480;
	win.title = "OpenGL MD2 Loader.";
	win.field_of_view_angle = 45;
	win.z_near = 1.0f;
	win.z_far = 500.0f;
 
	// initialize and run program
	glutInit(&argc, argv);                                      // GLUT initialization
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );  // Display Mode
	glutInitWindowSize(win.width,win.height);					// set window size
	glutCreateWindow(win.title);								// create Window
	glutDisplayFunc(display);									// register Display Function
	glutIdleFunc( display );									// register Idle Function
    glutKeyboardFunc( keyboard );								// register Keyboard Handler
	initialize();
	obj.Load("tris.md2","red.pcx");
	glutMainLoop();												// run GLUT mainloop
	return 0;
}

md2.h

class Model_MD2 
{
  public: 
    void LoadPCX(char* textureFilename);
    int Load(char *filename, char* textureFilename);
	void Draw();
	void Play(int animation);

	void Stand();
	void Run();
	void Attack();
	void Pain1();
	void Pain2();
	void Pain3();
	void Jump();
	void Flip();
	void Salute(); // 10
	void Taunt(); // 16
	void Wave(); // 10
	void Point(); //11
	void Crstnd(); // 18 
	void Crwalk(); // 5
	void Crattack(); // 8
	void Crpain(); // 3
	void Crdeath(); // 4
	void Death1(); // 5
	void Death2(); // 5
	void Death3(); // 7

	Model_MD2();
    
	float Points[1000000];
    float Faces_Triangles[512][14096];
	float Normals[1000000];
    float Faces_Textures[512][14096];

	float TextureCoords[120048];

	int TotalConnectedPoints;
	int TotalConnectedTriangles;	

	int Scale;
	int AngleX, AngleY, AngleZ;

  private:

	typedef struct _MD2_Header
	{
		int ident;        // identifies as quake II header  (IDP2)
		int version;      // mine is 8
		int skinwidth;    // width of texture
		int skinheight;   // height of texture
		int framesize;    // number of bytes per frame
		int numSkins;     // number of textures
		int numXYZ;       // number of points
		int numST;        // number of texture
		int numTris;      // number of triangles
		int numGLcmds;
		int numFrames;    // total number of frames
		int offsetSkins;  // offset to skin names (64 bytes each)
		int offsetST;     // offset of texture s-t values
		int offsetTris;   // offset of triangle mesh
		int offsetFrames; // offset of frame data (points)
		int offsetGLcmds;
		int offsetEnd;    // end of file
	} MD2_Header;
		
	typedef struct _framePoint
	{
		 unsigned char v[3];             // the vertex
		 unsigned char lightNormalIndex;
	} framePoint;

	typedef struct _frame
	{
	 float scale[3];                 // vetex scaling
	 float translate[3];             // vertex translation
	 char name[16];                  // name of this model
	 framePoint fp[1];               // start of a list of framePoints
	} frame;

	typedef struct _mesh
  {
     unsigned short meshIndex[3];     // indices to triangle vertices
     unsigned short stIndex[3];       // indices to texture coordinates
  } mesh;

	int framenr;
	int updatecounter;
	frame *frm;

	int frame_length[3];
	int frame_start[3];
	int animation;

	int texWidth;
	int texHeight;
	int imgWidth;
	int imgHeight;
	unsigned char* texture;			
	GLuint	texturen[1];
};

md2.cpp

/*
 * Just the class to load Quake 2 .MD2 and PCX files in OpenGL/GLUT.
 *
 *
 * Needs A LOT OF refactoring and cleaning! 
 * 
 *
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <cmath>
#include "md2.h"

typedef struct _pcxHeader
{
   short id[2];
   short offset[2];
   short size[2];
} pcxHeader;

struct Mesh_UV
 {
  unsigned short s;
  unsigned short t;
 };


float* calculateNormal( float *coord1, float *coord2, float *coord3 )
{
   /* calculate Vector1 and Vector2 */
   float va[3], vb[3], vr[3], val;
   va[0] = coord1[0] - coord2[0];
   va[1] = coord1[1] - coord2[1];
   va[2] = coord1[2] - coord2[2];

   vb[0] = coord1[0] - coord3[0];
   vb[1] = coord1[1] - coord3[1];
   vb[2] = coord1[2] - coord3[2];

   /* cross product */
   vr[0] = va[1] * vb[2] - vb[1] * va[2];
   vr[1] = vb[0] * va[2] - va[0] * vb[2];
   vr[2] = va[0] * vb[1] - vb[0] * va[1];

   /* normalization factor */
   val = sqrt( vr[0]*vr[0] + vr[1]*vr[1] + vr[2]*vr[2] );

 //  glNormal3f( vr[0]/val, vr[1]/val, vr[2]/val );
	float norm[3];
	norm[0] = vr[0]/val;
	norm[1] = vr[1]/val;
	norm[2] = vr[2]/val;


	return norm;
}


Model_MD2::Model_MD2()
{
	this->Scale = 1;
	framenr = 0;
	updatecounter = 0;

	frame_length[0] = 39;	// stand
	frame_length[1] = 5;	// run
	frame_length[2] = 7;	// attack
	frame_length[3] = 3;	//	"pain1" 
	frame_length[4] = 3;	//	"pain2" 3
	frame_length[5]	= 3;	// "pain3" 3
	frame_length[6] = 5;	// "jump" 5
	frame_length[7] = 11;	// "flip" 11
	frame_length[8] = 10;	// "salute" 10
	frame_length[9] = 16;	// "taunt" 16
	frame_length[10] = 10;	// "wave" 10


	frame_start[0] = 0;		// stand
	frame_start[1] = 39;	// run
	frame_start[2] = 46;	// shoot
	frame_start[3] = 50;	// pain1
	frame_start[4] = 39 + 5+ 7 + 3;
	frame_start[5] = frame_length[0] + frame_length[1] + frame_length[2] + frame_length[3] + frame_length[4];


	animation = 0;
}



void Model_MD2::LoadPCX(char* textureFilename)
{
	// Load texture
	FILE* texFile = fopen(textureFilename,"rb");
	
	if (texFile)
	{
      int imgWidth, imgHeight, texFileLen, imgBufferPtr, i;
               pcxHeader *pcxPtr;
               unsigned char *imgBuffer, *texBuffer, *pcxBufferPtr, *paletteBuffer;

               /* find length of file */
               fseek( texFile, 0, SEEK_END );
               texFileLen = ftell( texFile );
               fseek( texFile, 0, SEEK_SET );

               /* read in file */
               texBuffer = (unsigned char*) malloc( texFileLen+1 );
               fread( texBuffer, sizeof( char ), texFileLen, texFile );

               /* get the image dimensions */
               pcxPtr = (pcxHeader *)texBuffer;
               imgWidth = pcxPtr->size[0] - pcxPtr->offset[0] + 1;
               imgHeight = pcxPtr->size[1] - pcxPtr->offset[1] + 1;

			   this->imgWidth = imgWidth;
			   this->imgHeight = imgHeight;

               /* image starts at 128 from the beginning of the buffer */
               imgBuffer = (unsigned char*) malloc( imgWidth * imgHeight );
               imgBufferPtr = 0;
               pcxBufferPtr = &texBuffer[128];
               /* decode the pcx image */
               while( imgBufferPtr < (imgWidth * imgHeight) )
               {
                  if( *pcxBufferPtr > 0xbf )
                  {
                     int repeat = *pcxBufferPtr++ & 0x3f;
                     for( i=0; i<repeat; i++ )
                        imgBuffer[imgBufferPtr++] = *pcxBufferPtr;
                  } else {
                     imgBuffer[imgBufferPtr++] = *pcxBufferPtr;
                  }
                  pcxBufferPtr++;
               }
               /* read in the image palette */
               paletteBuffer = (unsigned char*) malloc( 768 );
               for( i=0; i<768; i++ )
                  paletteBuffer[i] = texBuffer[ texFileLen-768+i ];

               /* find the nearest greater power of 2 for each dimension */
               {
                  int imageWidth = imgWidth, imageHeight = imgHeight;
                  i = 0;
                  while( imageWidth )
                  {
                     imageWidth /= 2;
                     i++;
                  }
                  this->texWidth = pow( 2, (double) i );
                  i = 0;
                  while( imageHeight )
                  {
                     imageHeight /= 2;
                     i++;
                  }
                  this->texHeight = pow( 2, (double) i );
               }
               /* now create the OpenGL texture */
               {
                  int i, j;
                  this->texture = (unsigned char*) malloc( this->texWidth * this->texHeight * 3 );
                  for (j = 0; j < imgHeight; j++)
                  {
                     for (i = 0; i < imgWidth; i++)
                     {
                        this->texture[3*(j * this->texWidth + i)+0]
                              = paletteBuffer[ 3*imgBuffer[j*imgWidth+i]+0 ];
                        this->texture[3*(j * this->texWidth + i)+1]
                              = paletteBuffer[ 3*imgBuffer[j*imgWidth+i]+1 ];
                        this->texture[3*(j * this->texWidth + i)+2]
                              = paletteBuffer[ 3*imgBuffer[j*imgWidth+i]+2 ];
                     }
                  }
               }

   
       /* cleanup */
       free( paletteBuffer );
       free( imgBuffer );




	    glGenTextures( 1, &texturen[0] );
	    glBindTexture( GL_TEXTURE_2D, texturen[0] );

	    /* Generate The Texture */
	    glTexImage2D( GL_TEXTURE_2D, 0, 3, this->texWidth,
			  this->texHeight, 0, GL_RGB,
			  GL_UNSIGNED_BYTE, this->texture );

		printf(" %i %i ", this->texWidth, this->texHeight);
	    /* Linear Filtering */
	    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
	    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
	}

}

int Model_MD2::Load(char* filename, char* textureFilename)
{

	LoadPCX(textureFilename);
	// Load model
    MD2_Header *mdh;

	mesh* m;

	for (int i = 0; i < TotalConnectedTriangles; i++){  Faces_Triangles[0][i] = 0x00; }
	this->AngleX = 0;
	this->AngleY = 0;
	this->AngleZ = 0;
	this->Scale = 1;
    this->TotalConnectedTriangles = 0; 

    char* pch = strstr(filename,".md2");
     
    if (pch != NULL)
    {
	   FILE* file = fopen(filename,"r");
    
       if (file)
       {

		   // get size of file
           fseek( file, 0, SEEK_END );
		   size_t  fileSize = ftell( file );
		   fseek( file, 0, SEEK_SET );

		   // read in entire file
		   char* buffer;
 		   buffer = (char*) malloc( fileSize+1 );
		   fread( buffer, sizeof( char ), fileSize, file );


		   // start analyzing the buffer
	       mdh = (MD2_Header *)buffer;

		   printf("mdh framesize %i \n", mdh->framesize);

		   for (int z = 0; z< mdh->numFrames; z++)
		   {
			   frm = (frame *)&buffer[ mdh->offsetFrames + z*mdh->framesize  ];
			  m = (mesh *)&buffer[mdh->offsetTris];
			  int ti = 0;
			  int point_index = 0;
	
			  /* offset to points of frame */
			  for(int i=0; i<mdh->numXYZ; i++ )
			  {
				this->Points[point_index]   = frm->scale[0] * frm->fp[i].v[0] + frm->translate[0]; // X
				this->Points[point_index+1] = frm->scale[1] * frm->fp[i].v[1] + frm->translate[1]; // Y
				this->Points[point_index+2] = frm->scale[2] * frm->fp[i].v[2] + frm->translate[2]; // Z
				point_index += 3;
			  }
			  this->TotalConnectedTriangles = mdh->numTris * 3;
			  int n = 0;
		
			  //-------------------------------------------------------------
			  //-- create texture coordinate list ---------------------------

			  ti = 0;
				  		
			  for(int i=0; i<mdh->numST; i++ )
			  {
				 Mesh_UV* mUV = (Mesh_UV *)&buffer[mdh->offsetST + i*4 ];
			
				  this->TextureCoords[ti] = (float) mUV->s / this->texWidth;	
				  this->TextureCoords[ti+1] = (float) mUV->t / this->texHeight;	
				// printf(" (%i %i) ", mUV->s, mUV->t);
				// printf(" %f %f \n ", this->TextureCoords[ti], this->TextureCoords[ti+1]);
				ti+=2;
			  }


			  //---
			  m = (mesh *)&buffer[mdh->offsetTris];

			  n = 0;
			  ti = 0;


			  for(int i=0; i<mdh->numTris; i++ )
			  {

			
				 this->Faces_Triangles[z][n] = Points[ 3*m[i].meshIndex[0] ];
				 this->Faces_Triangles[z][n+1] = Points[ 3*m[i].meshIndex[0]+1 ];
				 this->Faces_Triangles[z][n+2] = Points[ 3*m[i].meshIndex[0]+2 ];		
						 
				 this->Faces_Textures[z][ti] = this->TextureCoords[ 2*m[i].stIndex[0] ];
				 this->Faces_Textures[z][ti+1] = this->TextureCoords[ 2*m[i].stIndex[0]+1 ];

  				n+=3;
				ti += 2;

				 this->Faces_Triangles[z][n] = Points[ 3*m[i].meshIndex[1] ];
				 this->Faces_Triangles[z][n+1] = Points[3* m[i].meshIndex[1]+1 ];
				 this->Faces_Triangles[z][n+2] = Points[3* m[i].meshIndex[1]+2 ];

				 this->Faces_Textures[z][ti] = TextureCoords[ 2*m[i].stIndex[1] ];
				 this->Faces_Textures[z][ti+1] = TextureCoords[ 2*m[i].stIndex[1] + 1 ];

				 n+=3;
				ti += 2;
	
				 this->Faces_Triangles[z][n] = Points[3* m[i].meshIndex[2] ];
				 this->Faces_Triangles[z][n+1] = Points[3* m[i].meshIndex[2]+1 ];
				 this->Faces_Triangles[z][n+2] = Points[3* m[i].meshIndex[2]+2 ];

				 this->Faces_Textures[z][ti] = TextureCoords[ 2*m[i].stIndex[2] ];
				 this->Faces_Textures[z][ti+1] = TextureCoords[ 2*m[i].stIndex[2] + 1 ];

				 n+=3;
				ti += 2;
			


	//			 m = (mesh *)&buffer[mdh->offsetTris + 12*i ];
			  }
		   }
		   //mdh->offsetST

		   printf("\n");

	  mdh = (MD2_Header *)buffer;

	for (int i = 0; i< 10; i+=2)
	{
		printf(" %f %f \n", this->Faces_Textures[0][i], this->Faces_Textures[0][i+1]);
	}

		int normal_index = 0;
	for (int triangle_index = 0; triangle_index < TotalConnectedTriangles*9; triangle_index += 9)
	{
		float coord1[3] = { Faces_Triangles[0][triangle_index], Faces_Triangles[0][triangle_index+1],Faces_Triangles[0][triangle_index+2]};
		float coord2[3] = {Faces_Triangles[0][triangle_index+3],Faces_Triangles[0][triangle_index+4],Faces_Triangles[0][triangle_index+5]};
		float coord3[3] = {Faces_Triangles[0][triangle_index+6],Faces_Triangles[0][triangle_index+7],Faces_Triangles[0][triangle_index+8]};
		float *norm = calculateNormal( coord1, coord2, coord3 );

		printf("\nnormal %f %f %f \n", norm[0],norm[1],norm[2]);

		Normals[normal_index] = norm[0];
		Normals[normal_index+1] = norm[1];
		Normals[normal_index+2] = norm[2];

		Normals[normal_index+3] = norm[0];
		Normals[normal_index+4] = norm[1];
		Normals[normal_index+5] = norm[2];

		Normals[normal_index+6] = norm[0];
		Normals[normal_index+7] = norm[1];
		Normals[normal_index+8] = norm[2];

		normal_index += 9;
	}
	

	   }
      else { printf("File can't be opened\n"); }
    } else {
      printf("File does not have a .MD2 extension. ");    
    }   

	return 0;
}

void Model_MD2::Play(int animation)
{
	updatecounter++;
	if (updatecounter > 10)
	{
		//if (framenr == frame_length[animation]) 
		//	framenr = 0;
		//else
		//{
			//if (framenr < frame_length[animation])	framenr++;
		//}
		framenr++;
		printf("%i \n", framenr);
		//framenr++;
		updatecounter = 0;
	}

	this->animation = animation;
}


void Model_MD2::Crstnd()
{
	updatecounter++;
	if (updatecounter > 10)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 147) framenr = 147;
		if (framenr < 147+5) framenr++; else framenr = 147;
		
		updatecounter = 0;
	}
}

void Model_MD2::Point()
{
	updatecounter++;
	if (updatecounter > 20)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 121) framenr = 121;
		if (framenr < 121+11) framenr++; else framenr = 121;
		
		updatecounter = 0;
	}
}

void Model_MD2::Wave()
{
	updatecounter++;
	if (updatecounter > 20)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 111) framenr = 111;
		if (framenr < 111+10) framenr++; else framenr = 111;
		
		updatecounter = 0;
	}
}

void Model_MD2::Taunt()
{
	updatecounter++;
	if (updatecounter > 20)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 91) framenr = 91;
		if (framenr < 91+20) framenr++; else framenr = 91;
		
		updatecounter = 0;
	}
}

void Model_MD2::Salute()
{
	updatecounter++;
	if (updatecounter > 20)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 81) framenr = 81;
		if (framenr < 81+10) framenr++; else framenr = 81;
		
		updatecounter = 0;
	}
}

void Model_MD2::Flip()
{
	updatecounter++;
	if (updatecounter > 20)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 69) framenr = 69;
		if (framenr < 69+11) framenr++; else framenr = 69;
		
		updatecounter = 0;
	}
}

void Model_MD2::Jump()
{
	updatecounter++;
	if (updatecounter > 20)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 64) framenr = 64;
		if (framenr < 64+5) framenr++; else framenr = 64;
		
		updatecounter = 0;
	}
}

void Model_MD2::Pain3()
{
	updatecounter++;
	if (updatecounter > 20)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 59) framenr = 59;
		if (framenr < 59+3) framenr++; else framenr = 59;
		
		updatecounter = 0;
	}
}

void Model_MD2::Pain2()
{
	updatecounter++;
	if (updatecounter > 20)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 56) framenr = 56;
		if (framenr < 56+3) framenr++; else framenr = 56;
		
		updatecounter = 0;
	}

}

void Model_MD2::Pain1()
{
	updatecounter++;
	if (updatecounter > 20)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 53) framenr = 53;
		if (framenr < 53+3) framenr++; else framenr = 53;
		
		updatecounter = 0;
	}

	this->animation = 1;
}

void Model_MD2::Attack()
{
	updatecounter++;
	if (updatecounter > 20)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 46) framenr = 46;
		if (framenr < 46+7) framenr++; else framenr = 46;
		
		updatecounter = 0;
	}

	this->animation = 1;
}

void Model_MD2::Run()
{
	updatecounter++;
	if (updatecounter > 1)
	{
		//if (framenr == (framenr+frame_length[animation]) ) framenr = 0; else framenr++;
		if (framenr < 40) framenr = 40;
		if (framenr < 40+5) framenr++; else framenr = 40;
		
		updatecounter = 0;
	}

	this->animation = 1;

}


void Model_MD2::Stand()
{
	updatecounter++;
	if (updatecounter > 1)
	{
		if (framenr > 28) framenr = 0; else framenr++;
		updatecounter = 0;
	}

	this->animation = 0;

}


void Model_MD2::Draw()
{
	/*
	glRasterPos2i(0,0);
	glDrawPixels(this->texWidth , this->texHeight, GL_RGB, GL_UNSIGNED_BYTE, this->texture);
*/



    glEnable(GL_TEXTURE_2D);	
	glBindTexture(GL_TEXTURE_2D, texturen[0]);

	
 	glEnableClientState(GL_VERTEX_ARRAY);	
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	glEnableClientState(GL_NORMAL_ARRAY);
	glNormalPointer(GL_FLOAT, 0, Normals);

	glTexCoordPointer(2,GL_FLOAT,0, this->Faces_Textures[ framenr] );
	glVertexPointer(3,GL_FLOAT,	0,Faces_Triangles[ framenr ]);	 
	glDrawArrays(GL_TRIANGLES, 0, TotalConnectedTriangles);	

	glDisableClientState(GL_NORMAL_ARRAY);
	glDisableClientState(GL_VERTEX_ARRAY);    
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	glDisable(GL_TEXTURE_2D);
}

Finally you can compile using:

g++ md2.cpp main.cpp -o md2 -lGLU -lGL -lglut -fpermissive

The output should be something like (depending on your model) :

MD2Loader

 

Lesson 10: Loading Stanford PLY 3D Models

Standard
blue-270x270

Stanford PLY 3D models are similiar to Wavefront OBJs. Both are static objects. However, models stored in the PLY format are generally in binary format (e.g. unreadable with a normal text editor), but an ascii version of the format also exists. The files themselves store vertexes and faces.  If you are unfamiliar with these, please read the previous tutorial. To load and display them, we can use this code:

/* Demonstrates how to load PLY files
 * Needs some refactoring.
 *
 * http://talkera.org/opengl/
 * Model needs to be triangulated
 * Use blender
 *
 * Just the class for loading PLY files. 
 *
 */ 
 
#include <windows.h>
#include <stdio.h>
#include <string.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <cmath>
#include <string>
 
 
class Model_PLY 
{
public:
	int Model_PLY::Load(char *filename);
	void Model_PLY::Draw();
	float* Model_PLY::calculateNormal( float *coord1, float *coord2, float *coord3 );
	Model_PLY();
 
    float* Faces_Triangles;
    float* Faces_Quads;
	float* Vertex_Buffer;
	float* Normals;
 
	int TotalConnectedTriangles;	
	int TotalConnectedQuads;	
	int TotalConnectedPoints;
	int TotalFaces;
 
 
};
 
 
 
Model_PLY::Model_PLY()
{
 
}
 
 
float* Model_PLY::calculateNormal( float *coord1, float *coord2, float *coord3 )
{
   /* calculate Vector1 and Vector2 */
   float va[3], vb[3], vr[3], val;
   va[0] = coord1[0] - coord2[0];
   va[1] = coord1[1] - coord2[1];
   va[2] = coord1[2] - coord2[2];
 
   vb[0] = coord1[0] - coord3[0];
   vb[1] = coord1[1] - coord3[1];
   vb[2] = coord1[2] - coord3[2];
 
   /* cross product */
   vr[0] = va[1] * vb[2] - vb[1] * va[2];
   vr[1] = vb[0] * va[2] - va[0] * vb[2];
   vr[2] = va[0] * vb[1] - vb[0] * va[1];
 
   /* normalization factor */
   val = sqrt( vr[0]*vr[0] + vr[1]*vr[1] + vr[2]*vr[2] );
 
	float norm[3];
	norm[0] = vr[0]/val;
	norm[1] = vr[1]/val;
	norm[2] = vr[2]/val;
 
 
	return norm;
}
 
 
 
int Model_PLY::Load(char* filename)
{
    this->TotalConnectedTriangles = 0; 
	this->TotalConnectedQuads = 0;
	this->TotalConnectedPoints = 0;
 
    char* pch = strstr(filename,".ply");
 
    if (pch != NULL)
    {
	   FILE* file = fopen(filename,"r");
 
		fseek(file,0,SEEK_END);
		long fileSize = ftell(file);
 
		try
		{
		Vertex_Buffer = (float*) malloc (ftell(file));
		}
		catch (char* )
		{
			return -1;
		}
		if (Vertex_Buffer == NULL) return -1;
		fseek(file,0,SEEK_SET); 
 
	   Faces_Triangles = (float*) malloc(fileSize*sizeof(float));
	   Normals  = (float*) malloc(fileSize*sizeof(float));
 
       if (file)
       {
			int i = 0;   
			int temp = 0;
			int quads_index = 0;
            int triangle_index = 0;
			int normal_index = 0;
			char buffer[1000];
 
 
			fgets(buffer,300,file);			// ply
 
 
			// READ HEADER
			// -----------------
 
			// Find number of vertexes
			while (  strncmp( "element vertex", buffer,strlen("element vertex")) != 0  )
			{
				fgets(buffer,300,file);			// format
			}
			strcpy(buffer, buffer+strlen("element vertex"));
			sscanf(buffer,"%i", &this->TotalConnectedPoints);
 
 
			// Find number of vertexes
			fseek(file,0,SEEK_SET);
			while (  strncmp( "element face", buffer,strlen("element face")) != 0  )
			{
				fgets(buffer,300,file);			// format
			}
			strcpy(buffer, buffer+strlen("element face"));
			sscanf(buffer,"%i", &this->TotalFaces);
 
 
			// go to end_header
			while (  strncmp( "end_header", buffer,strlen("end_header")) != 0  )
			{
				fgets(buffer,300,file);			// format
			}
 
			//----------------------
 
 
			// read verteces
			i =0;
			for (int iterator = 0; iterator < this->TotalConnectedPoints; iterator++)
			{
				fgets(buffer,300,file);
 
				sscanf(buffer,"%f %f %f", &Vertex_Buffer[i], &Vertex_Buffer[i+1], &Vertex_Buffer[i+2]);
				i += 3;
			}
 
			// read faces
			i =0;
			for (int iterator = 0; iterator < this->TotalFaces; iterator++)
			{
				fgets(buffer,300,file);
 
					if (buffer[0] == '3')
					{
 
						int vertex1 = 0, vertex2 = 0, vertex3 = 0;
						//sscanf(buffer,"%i%i%i\n", vertex1,vertex2,vertex3 );
						buffer[0] = ' ';
						sscanf(buffer,"%i%i%i", &vertex1,&vertex2,&vertex3 );
						/*vertex1 -= 1;
						vertex2 -= 1;
						vertex3 -= 1;
*/
						//  vertex == punt van vertex lijst
						// vertex_buffer -> xyz xyz xyz xyz
						printf("%f %f %f ", Vertex_Buffer[3*vertex1], Vertex_Buffer[3*vertex1+1], Vertex_Buffer[3*vertex1+2]);
 
						Faces_Triangles[triangle_index] = Vertex_Buffer[3*vertex1];
						Faces_Triangles[triangle_index+1] = Vertex_Buffer[3*vertex1+1];
						Faces_Triangles[triangle_index+2] = Vertex_Buffer[3*vertex1+2];
						Faces_Triangles[triangle_index+3] = Vertex_Buffer[3*vertex2];
						Faces_Triangles[triangle_index+4] = Vertex_Buffer[3*vertex2+1];
						Faces_Triangles[triangle_index+5] = Vertex_Buffer[3*vertex2+2];
						Faces_Triangles[triangle_index+6] = Vertex_Buffer[3*vertex3];
						Faces_Triangles[triangle_index+7] = Vertex_Buffer[3*vertex3+1];
						Faces_Triangles[triangle_index+8] = Vertex_Buffer[3*vertex3+2];
 
						float coord1[3] = { Faces_Triangles[triangle_index], Faces_Triangles[triangle_index+1],Faces_Triangles[triangle_index+2]};
						float coord2[3] = {Faces_Triangles[triangle_index+3],Faces_Triangles[triangle_index+4],Faces_Triangles[triangle_index+5]};
						float coord3[3] = {Faces_Triangles[triangle_index+6],Faces_Triangles[triangle_index+7],Faces_Triangles[triangle_index+8]};
						float *norm = this->calculateNormal( coord1, coord2, coord3 );
 
						Normals[normal_index] = norm[0];
						Normals[normal_index+1] = norm[1];
						Normals[normal_index+2] = norm[2];
						Normals[normal_index+3] = norm[0];
						Normals[normal_index+4] = norm[1];
						Normals[normal_index+5] = norm[2];
						Normals[normal_index+6] = norm[0];
						Normals[normal_index+7] = norm[1];
						Normals[normal_index+8] = norm[2];
 
						normal_index += 9;
 
						triangle_index += 9;
						TotalConnectedTriangles += 3;
					}
 
 
					i += 3;
			}
 
 
			fclose(file);
		}
 
      else { printf("File can't be opened\n"); }
    } else {
      printf("File does not have a .PLY extension. ");    
    }   
	return 0;
}
 
void Model_PLY::Draw()
{
	glEnableClientState(GL_VERTEX_ARRAY);	
 	glEnableClientState(GL_NORMAL_ARRAY);
	glVertexPointer(3,GL_FLOAT,	0,Faces_Triangles);	
	glNormalPointer(GL_FLOAT, 0, Normals);
	glDrawArrays(GL_TRIANGLES, 0, TotalConnectedTriangles);	
	glDisableClientState(GL_VERTEX_ARRAY);    
	glDisableClientState(GL_NORMAL_ARRAY);
}

 

 

 

 

 

Lesson 9: Loading Wavefront OBJ 3D models

Standard
blue-270x270-4

In this tutorial we will load Wavefront OBJ 3D models. These models are static models (e.g. the model itself does not have any movement definition). You can get a set of example models here : http://people.sc.fsu.edu/~jburkardt/data/obj/obj.html  Remember to triangulate them in Blender or another 3d editing program.

Every OBJ model consists of a point cloud. For example, a simple pyramid consists of 5 points. The more complicated your model is, the more points you will have. For example, an airplane point cloud could look like this:

Wavefront OBJ

Wavefront OBJ

Every point is defined in the vector space using their (x,y,z) coordinate.  The Wavefront OBJ format stores everything in plain text, and thus if you open an OBJ model in a text editor you will see a list like:

v -1.227048 -1.158217 1.417186
v -1.227048 -1.576351 1.426747
v -1.202536 -1.576423 1.426747
v -1.202536 -1.169531 1.417441
v 0.064052 -1.584108 1.403254
v 0.064052 -1.186619 1.394201

Where every line represents a vector in the vector space. Note that ‘v’ in front of the files which indicates a vector. Once you have loaded the vectors, you want to connect each of the faces. A face is a connection of multiple points, in the form of a triangle, quad etc. In our case we will only support triangles. If the concept of faces is still unclear to you, consider a cube has 6 faces (sides). Every face is defined on a single line and consists of several vectors, in our case exactly three. You will find a list like this in the file:

f 1 2 3
f 1 3 4
f 5 6 7
f 5 7 8
f 9 10 4
f 9 4 3
f 8 7 10
f 8 10 9

Note that the points indicate the index of the vector array you have stored earlier.  E.g. every point of the face consists of an x,y and z value.  Thus, every line consist of 9 numbers (3 for each coordinate).

Once we know all of the faces, we can display them and end up with something like this:

Wavefront OBJ Model

Wavefront OBJ Model

Finally, we need to load the normal vectors (e.g. orthagonal lines from the faces) to display the correct lighting. These can either be calculated from the faces we already have, or be loaded from the file. (vn is the indicator). We end up with this scene:

Wavefront OBJ Model OpenGL

Wavefront OBJ Model OpenGL

In code this will look like this:

 

/*
 *
 * Demonstrates how to load and display an Wavefront OBJ file. 
 * Using triangles and normals as static object. No texture mapping.
 * http://talkera.org/opengl/
 *
 * OBJ files must be triangulated!!!
 * Non triangulated objects wont work!
 * You can use Blender to triangulate
 *
 */
 
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <string.h>
#include <GL/gl.h>
#include <GL/glu.h>
#include <GL/glut.h>
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
#include <cmath>
 
#define KEY_ESCAPE 27
 
using namespace std;
 
/************************************************************************
  Window
 ************************************************************************/
 
typedef struct {
    int width;
	int height;
	char* title;
 
	float field_of_view_angle;
	float z_near;
	float z_far;
} glutWindow;
 
 
 
/*************************************************************************** 
  OBJ Loading 
 ***************************************************************************/
 
class Model_OBJ
{
  public: 
	Model_OBJ();			
    float* Model_OBJ::calculateNormal(float* coord1,float* coord2,float* coord3 );
    int Model_OBJ::Load(char *filename);	// Loads the model
	void Model_OBJ::Draw();					// Draws the model on the screen
	void Model_OBJ::Release();				// Release the model
 
	float* normals;							// Stores the normals
    float* Faces_Triangles;					// Stores the triangles
	float* vertexBuffer;					// Stores the points which make the object
	long TotalConnectedPoints;				// Stores the total number of connected verteces
	long TotalConnectedTriangles;			// Stores the total number of connected triangles
 
};
 
 
#define POINTS_PER_VERTEX 3
#define TOTAL_FLOATS_IN_TRIANGLE 9
using namespace std;
 
Model_OBJ::Model_OBJ()
{
	this->TotalConnectedTriangles = 0; 
	this->TotalConnectedPoints = 0;
}
 
float* Model_OBJ::calculateNormal( float *coord1, float *coord2, float *coord3 )
{
   /* calculate Vector1 and Vector2 */
   float va[3], vb[3], vr[3], val;
   va[0] = coord1[0] - coord2[0];
   va[1] = coord1[1] - coord2[1];
   va[2] = coord1[2] - coord2[2];
 
   vb[0] = coord1[0] - coord3[0];
   vb[1] = coord1[1] - coord3[1];
   vb[2] = coord1[2] - coord3[2];
 
   /* cross product */
   vr[0] = va[1] * vb[2] - vb[1] * va[2];
   vr[1] = vb[0] * va[2] - va[0] * vb[2];
   vr[2] = va[0] * vb[1] - vb[0] * va[1];
 
   /* normalization factor */
   val = sqrt( vr[0]*vr[0] + vr[1]*vr[1] + vr[2]*vr[2] );
 
	float norm[3];
	norm[0] = vr[0]/val;
	norm[1] = vr[1]/val;
	norm[2] = vr[2]/val;
 
 
	return norm;
}
 
 
int Model_OBJ::Load(char* filename)
{
	string line;
	ifstream objFile (filename);	
	if (objFile.is_open())													// If obj file is open, continue
	{
		objFile.seekg (0, ios::end);										// Go to end of the file, 
		long fileSize = objFile.tellg();									// get file size
		objFile.seekg (0, ios::beg);										// we'll use this to register memory for our 3d model
 
		vertexBuffer = (float*) malloc (fileSize);							// Allocate memory for the verteces
		Faces_Triangles = (float*) malloc(fileSize*sizeof(float));			// Allocate memory for the triangles
		normals  = (float*) malloc(fileSize*sizeof(float));					// Allocate memory for the normals
 
		int triangle_index = 0;												// Set triangle index to zero
		int normal_index = 0;												// Set normal index to zero
 
		while (! objFile.eof() )											// Start reading file data
		{		
			getline (objFile,line);											// Get line from file
 
			if (line.c_str()[0] == 'v')										// The first character is a v: on this line is a vertex stored.
			{
				line[0] = ' ';												// Set first character to 0. This will allow us to use sscanf
 
				sscanf(line.c_str(),"%f %f %f ",							// Read floats from the line: v X Y Z
					&vertexBuffer[TotalConnectedPoints],
					&vertexBuffer[TotalConnectedPoints+1], 
					&vertexBuffer[TotalConnectedPoints+2]);
 
				TotalConnectedPoints += POINTS_PER_VERTEX;					// Add 3 to the total connected points
			}
			if (line.c_str()[0] == 'f')										// The first character is an 'f': on this line is a point stored
			{
		    	line[0] = ' ';												// Set first character to 0. This will allow us to use sscanf
 
				int vertexNumber[4] = { 0, 0, 0 };
                sscanf(line.c_str(),"%i%i%i",								// Read integers from the line:  f 1 2 3
					&vertexNumber[0],										// First point of our triangle. This is an 
					&vertexNumber[1],										// pointer to our vertexBuffer list
					&vertexNumber[2] );										// each point represents an X,Y,Z.
 
				vertexNumber[0] -= 1;										// OBJ file starts counting from 1
				vertexNumber[1] -= 1;										// OBJ file starts counting from 1
				vertexNumber[2] -= 1;										// OBJ file starts counting from 1
 
 
				/********************************************************************
				 * Create triangles (f 1 2 3) from points: (v X Y Z) (v X Y Z) (v X Y Z). 
				 * The vertexBuffer contains all verteces
				 * The triangles will be created using the verteces we read previously
				 */
 
				int tCounter = 0;
				for (int i = 0; i < POINTS_PER_VERTEX; i++)					
				{
					Faces_Triangles[triangle_index + tCounter   ] = vertexBuffer[3*vertexNumber[i] ];
					Faces_Triangles[triangle_index + tCounter +1 ] = vertexBuffer[3*vertexNumber[i]+1 ];
					Faces_Triangles[triangle_index + tCounter +2 ] = vertexBuffer[3*vertexNumber[i]+2 ];
					tCounter += POINTS_PER_VERTEX;
				}
 
				/*********************************************************************
				 * Calculate all normals, used for lighting
				 */ 
				float coord1[3] = { Faces_Triangles[triangle_index], Faces_Triangles[triangle_index+1],Faces_Triangles[triangle_index+2]};
				float coord2[3] = {Faces_Triangles[triangle_index+3],Faces_Triangles[triangle_index+4],Faces_Triangles[triangle_index+5]};
				float coord3[3] = {Faces_Triangles[triangle_index+6],Faces_Triangles[triangle_index+7],Faces_Triangles[triangle_index+8]};
				float *norm = this->calculateNormal( coord1, coord2, coord3 );
 
				tCounter = 0;
				for (int i = 0; i < POINTS_PER_VERTEX; i++)
				{
					normals[normal_index + tCounter ] = norm[0];
					normals[normal_index + tCounter +1] = norm[1];
					normals[normal_index + tCounter +2] = norm[2];
					tCounter += POINTS_PER_VERTEX;
				}
 
				triangle_index += TOTAL_FLOATS_IN_TRIANGLE;
				normal_index += TOTAL_FLOATS_IN_TRIANGLE;
				TotalConnectedTriangles += TOTAL_FLOATS_IN_TRIANGLE;			
			}	
		}
		objFile.close();														// Close OBJ file
	}
	else 
	{
		cout << "Unable to open file";								
	}
	return 0;
}
 
void Model_OBJ::Release()
{
	free(this->Faces_Triangles);
	free(this->normals);
	free(this->vertexBuffer);
}
 
void Model_OBJ::Draw()
{
	
glEnableClientState(GL_VERTEX_ARRAY);						// Enable vertex arrays
 	glEnableClientState(GL_NORMAL_ARRAY);						// Enable normal arrays
	glVertexPointer(3,GL_FLOAT,	0,Faces_Triangles);				// Vertex Pointer to triangle array
	glNormalPointer(GL_FLOAT, 0, normals);						// Normal pointer to normal array
	glDrawArrays(GL_TRIANGLES, 0, TotalConnectedTriangles);		// Draw the triangles
	glDisableClientState(GL_VERTEX_ARRAY);						// Disable vertex arrays
	glDisableClientState(GL_NORMAL_ARRAY);						// Disable normal arrays
}
 
/***************************************************************************
 * Program code
 ***************************************************************************/
 
Model_OBJ obj;
float g_rotation;
glutWindow win;
 
void display() 
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();
	gluLookAt( 0,1,20, 0,0,0, 0,1,0);
	glPushMatrix();
		glRotatef(45,0,1,0);
		glRotatef(90,0,1,0);
	//	g_rotation++;
		obj.Draw();
	glPopMatrix();
	glutSwapBuffers();
}
 
 
void initialize () 
{
    glMatrixMode(GL_PROJECTION);
	glViewport(0, 0, win.width, win.height);
	GLfloat aspect = (GLfloat) win.width / win.height;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
	gluPerspective(win.field_of_view_angle, aspect, win.z_near, win.z_far);
    glMatrixMode(GL_MODELVIEW);
    glShadeModel( GL_SMOOTH );
    glClearColor( 0.0f, 0.1f, 0.0f, 0.5f );
    glClearDepth( 1.0f );
    glEnable( GL_DEPTH_TEST );
    glDepthFunc( GL_LEQUAL );
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );
 
    GLfloat amb_light[] = { 0.1, 0.1, 0.1, 1.0 };
    GLfloat diffuse[] = { 0.6, 0.6, 0.6, 1 };
    GLfloat specular[] = { 0.7, 0.7, 0.3, 1 };
    glLightModelfv( GL_LIGHT_MODEL_AMBIENT, amb_light );
    glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse );
    glLightfv( GL_LIGHT0, GL_SPECULAR, specular );
    glEnable( GL_LIGHT0 );
    glEnable( GL_COLOR_MATERIAL );
    glShadeModel( GL_SMOOTH );
    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE );
    glDepthFunc( GL_LEQUAL );
    glEnable( GL_DEPTH_TEST );
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0); 
}
 
 
void keyboard ( unsigned char key, int x, int y ) 
{
  switch ( key ) {
    case KEY_ESCAPE:        
      exit ( 0 );   
      break;      
    default:      
      break;
  }
}
 
int main(int argc, char **argv) 
{
	// set window values
	win.width = 640;
	win.height = 480;
	win.title = "OpenGL/GLUT OBJ Loader.";
	win.field_of_view_angle = 45;
	win.z_near = 1.0f;
	win.z_far = 500.0f;
 
	// initialize and run program
	glutInit(&argc, argv);                                      // GLUT initialization
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );  // Display Mode
	glutInitWindowSize(win.width,win.height);					// set window size
	glutCreateWindow(win.title);								// create Window
	glutDisplayFunc(display);									// register Display Function
	glutIdleFunc( display );									// register Idle Function
    glutKeyboardFunc( keyboard );								// register Keyboard Handler
	initialize();
	obj.Load("cessna.obj");
	glutMainLoop();												// run GLUT mainloop
	return 0;
}

You can compile using:

g++ obj.c -o obj -lGLU -lGL -lglut -fpermissive

If you do not see any model, make sure you have the correct file path, and that the camera is in the right position. If the model looks deformed, you may have forgotten to triangulate the object.

Lesson 8: Parsing and loading custom image formats, TGA

Standard
blue-270x270-8

In this tutorial we’ll look into custom image formats. Al though you can get away with most of the image formats provided by an additional library, at some point you may want to load a custom format. Proprietary video games often have their own custom image formats. In our case we’ll load an image stored in the uncompressed Truevision TGA format. The file contains a description header and raw image data. The header looks like this:

Field no. Length Field name Description
1 1 byte ID length Length of the image ID field
2 1 byte Color map type Whether a color map is included
3 1 byte Image type Compression and color types
4 5 bytes Color map specification Describes the color map
5 10 bytes Image specification Image dimensions and format

We could define a struct defining how to load this header, and this is exactly what we will do:

	struct tga_header
	{
		unsigned char idLength;
		unsigned char colorMapType;
		unsigned char imageTypeCode;
		unsigned char colorMapSpec[5];
		unsigned short xOrigin;
		unsigned short yOrigin;	
		unsigned short width;
		unsigned short height;
		unsigned char bpp;
		unsigned char imageDesc;
	};
	tga_header tgaheader;

We would also need to store the raw image data, thus we define:

	char *imageData;

Finally we would need methods to load, parse and close the image. We end up with this class:

class TGA
{
  public:
 
	bool Load(const char *filename);
	void Release();
	void Draw();
 
  private:
	char *imageData;
 
 
	struct tga_header
	{
		unsigned char idLength;
		unsigned char colorMapType;
		unsigned char imageTypeCode;
		unsigned char colorMapSpec[5];
		unsigned short xOrigin;
		unsigned short yOrigin;	
		unsigned short width;
		unsigned short height;
		unsigned char bpp;
		unsigned char imageDesc;
	};
	tga_header tgaheader;
};

Thus, we have defined the general data structure to hold the image data. However, we do not have the functionality yet.

We can load the entire hire as memory block into our data stucture using this function call :

filestr.read((char*) &tgaheader , sizeof(struct tga_header));

Finally, we can use these methods to load, parse and draw the image:

 
void TGA::Draw()
{
	glPixelStorei (GL_UNPACK_ROW_LENGTH, this->tgaheader.width);
 
	if (this->tgaheader.bpp == 32)
	{
		glPixelStorei(GL_UNPACK_ALIGNMENT, 2); 
		glDrawPixels(this->tgaheader.width, this->tgaheader.height, GL_RGBA, GL_UNSIGNED_BYTE, this->imageData);
	}
	if (this->tgaheader.bpp == 24)
	{
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 
		glDrawPixels(this->tgaheader.width, this->tgaheader.height, GL_RGB, GL_UNSIGNED_BYTE, this->imageData);
	}
}
 
void TGA::Release()
{
	free(imageData);		// Free image data from memory
}
 
bool TGA::Load(const char* filename)
{
	fstream filestr;
 
	filestr.open (filename, ios::in | ios::binary);								// Open file
	if (filestr.is_open())														// Do the following actions, if file is opened
	{
		// read TGA header
		filestr.read((char*) &tgaheader , sizeof(struct tga_header));			// Read tga header. For more information: see tga.h and link above
		printf("image type: %i \n", tgaheader.imageTypeCode);	
 
		// read pixel data
		int imageSize = tgaheader.width * tgaheader.height * tgaheader.bpp;		// Calculate image size
 
		this->imageData = (char*) malloc(imageSize);							// Reserve space in the memory to store our image data
		filestr.read((char*) this->imageData, imageSize);						// Read image data from file, into the reserved memory place
 
 
		/*
		 * TGA is stored in BGR (Blue-Green-Red) format,
		 * we need to convert this to Red-Green-Blue (RGB).
		 * The following section does BGR to RGB conversion
		 */
 
		if (tgaheader.bpp == 24)
		{
			for (int i = 0; i < imageSize; i+=3)
			{	
				char c = this->imageData[i];
				this->imageData[i] = this->imageData[i+2];
				this->imageData[i+2] = c;
			}
		}
		else
		if (tgaheader.bpp == 32)
		{
			for (int i = 0; i < imageSize; i+=4)
			{	
				// 32 bits per pixel   =  4 byte per pixel			
				char c = this->imageData[i];
				this->imageData[i] = this->imageData[i+2];
				this->imageData[i+2] = c;
			}
		}
 
		filestr.close();
	}
	else
	{
		cout << "Error opening file" << endl;
		return -1;
	}
 
	return 0;
}

Now that we have the functionality to load, display and parse the data we are not done yet.  We still need the OpenGL scene where we define the entire scene, viewport etc.

We end up with this code:

/*
 * OpenGL Tutorials (http://talkera.org/opengl)
 * VC++ users should create a Win32 Console project and link 
 * the program with glut32.lib, glu32.lib, opengl32.lib
 *
 * GLUT can be downloaded from http://www.xmission.com/~nate/glut.html
 * OpenGL is by default installed on your system.
 *
 */
 
/*
 * TGA Loader
 *
 * 24BPP or 32BPP TGA Images.
 * This loader does NOT support RLE Compression
 *
 * For more info about TGA:
 * http://en.wikipedia.org/wiki/Truevision_TGA
 */
 
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <windows.h>	   // Standard header for MS Windows applications
#include <GL/gl.h>		   // Open Graphics Library (OpenGL) header
#include <GL/glut.h>	   // The GL Utility Toolkit (GLUT) Header
 
#define KEY_ESCAPE 27
#define g_rotation_speed 0.2
float g_rotation;
 
 
using namespace std;
 
/************************************************************************************
 *  TGA CLASS
 ************************************************************************************/
class TGA
{
  public:
 
	bool Load(const char *filename);
	void Release();
	void Draw();
 
  private:
	char *imageData;
 
 
	struct tga_header
	{
		unsigned char idLength;
		unsigned char colorMapType;
		unsigned char imageTypeCode;
		unsigned char colorMapSpec[5];
		unsigned short xOrigin;
		unsigned short yOrigin;	
		unsigned short width;
		unsigned short height;
		unsigned char bpp;
		unsigned char imageDesc;
	};
	tga_header tgaheader;
};
 
 
void TGA::Draw()
{
	glPixelStorei (GL_UNPACK_ROW_LENGTH, this->tgaheader.width);
 
	if (this->tgaheader.bpp == 32)
	{
		glPixelStorei(GL_UNPACK_ALIGNMENT, 2); 
		glDrawPixels(this->tgaheader.width, this->tgaheader.height, GL_RGBA, GL_UNSIGNED_BYTE, this->imageData);
	}
	if (this->tgaheader.bpp == 24)
	{
		glPixelStorei(GL_UNPACK_ALIGNMENT, 1); 
		glDrawPixels(this->tgaheader.width, this->tgaheader.height, GL_RGB, GL_UNSIGNED_BYTE, this->imageData);
	}
}
 
void TGA::Release()
{
	free(imageData);		// Free image data from memory
}
 
bool TGA::Load(const char* filename)
{
	fstream filestr;
 
	filestr.open (filename, ios::in | ios::binary);								// Open file
	if (filestr.is_open())														// Do the following actions, if file is opened
	{
		// read TGA header
		filestr.read((char*) &tgaheader , sizeof(struct tga_header));			// Read tga header. For more information: see tga.h and link above
		printf("image type: %i \n", tgaheader.imageTypeCode);	
 
		// read pixel data
		int imageSize = tgaheader.width * tgaheader.height * tgaheader.bpp;		// Calculate image size
 
		this->imageData = (char*) malloc(imageSize);							// Reserve space in the memory to store our image data
		filestr.read((char*) this->imageData, imageSize);						// Read image data from file, into the reserved memory place
 
 
		/*
		 * TGA is stored in BGR (Blue-Green-Red) format,
		 * we need to convert this to Red-Green-Blue (RGB).
		 * The following section does BGR to RGB conversion
		 */
 
		if (tgaheader.bpp == 24)
		{
			for (int i = 0; i < imageSize; i+=3)
			{	
				char c = this->imageData[i];
				this->imageData[i] = this->imageData[i+2];
				this->imageData[i+2] = c;
			}
		}
		else
		if (tgaheader.bpp == 32)
		{
			for (int i = 0; i < imageSize; i+=4)
			{	
				// 32 bits per pixel   =  4 byte per pixel			
				char c = this->imageData[i];
				this->imageData[i] = this->imageData[i+2];
				this->imageData[i+2] = c;
			}
		}
 
		filestr.close();
	}
	else
	{
		cout << "Error opening file" << endl;
		return -1;
	}
 
	return 0;
}
 
 
/************************************************************************************
 *  Rest of code
 ************************************************************************************/
 
 
typedef struct {
    int width;
	int height;
	char* title;
 
	float field_of_view_angle;
	float z_near;
	float z_far;
} glutWindow;
glutWindow win;
 
 
TGA g_image;
 
void display() 
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);		// Clear Screen and Depth Buffer
	glLoadIdentity();
	gluLookAt( 4,2,0, 0,0,0, 0,1,0);					    // Define a viewing transformation
	g_image.Draw();											// Draw image to the screen
	glutSwapBuffers();
}
 
 
void initialize () 
{
    glMatrixMode(GL_PROJECTION);												// select projection matrix
    glViewport(0, 0, win.width, win.height);									// set the viewport
    glMatrixMode(GL_PROJECTION);												// set matrix mode
    glLoadIdentity();															// reset projection matrix
    GLfloat aspect = (GLfloat) win.width / win.height;
	gluPerspective(win.field_of_view_angle, aspect, win.z_near, win.z_far);		// set up a perspective projection matrix
    glMatrixMode(GL_MODELVIEW);													// specify which matrix is the current matrix
    glShadeModel( GL_SMOOTH );
    glClearDepth( 1.0f );														// specify the clear value for the depth buffer
    glEnable( GL_DEPTH_TEST );
    glDepthFunc( GL_LEQUAL );
    glHint( GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST );						// specify implementation-specific hints
 
	GLfloat amb_light[] = { 0.1, 0.1, 0.1, 1.0 };
    GLfloat diffuse[] = { 0.6, 0.6, 0.6, 1 };
    GLfloat specular[] = { 0.7, 0.7, 0.3, 1 };
    glLightModelfv( GL_LIGHT_MODEL_AMBIENT, amb_light );
    glLightfv( GL_LIGHT0, GL_DIFFUSE, diffuse );
    glLightfv( GL_LIGHT0, GL_SPECULAR, specular );
    glEnable( GL_LIGHT0 );
    glEnable( GL_COLOR_MATERIAL );
    glShadeModel( GL_SMOOTH );
    glLightModeli( GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE );
    glDepthFunc( GL_LEQUAL );
    glEnable( GL_DEPTH_TEST );
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0); 
	glClearColor(0.0, 0.0, 0.0, 1.0);
 
}
 
 
void keyboard ( unsigned char key, int mousePositionX, int mousePositionY )		
{ 
  switch ( key ) 
  {
    case KEY_ESCAPE:        
      exit ( 0 );   
      break;      
 
    default:      
      break;
  }
}
 
int main(int argc, char **argv) 
{
	// set window values
	win.width = 640;
	win.height = 480;
	win.title = "OpenGL/GLUT TGA Loader";
	win.field_of_view_angle = 45;
	win.z_near = 1.0f;
	win.z_far = 500.0f;
 
	g_image.Load("image.tga");
 
	// initialize and run program
	glutInit(&argc, argv);                                      // GLUT initialization
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH );  // Display Mode
	glutInitWindowSize(win.width,win.height);					// set window size
	glutCreateWindow(win.title);								// create Window
	glutDisplayFunc(display);									// register Display Function
	glutIdleFunc( display );									// register Idle Function
    glutKeyboardFunc( keyboard );								// register Keyboard Handler
	initialize();
	glutMainLoop();												// run GLUT mainloop
	return 0;
}

Note if you are on a unix machine you may need to change some code. In addition, this image parser does not deal with compressed TGA images. Finally, OpenGL likes images in a specific format, e.g. 256×256, 512×512, 1024×1024 and so on. In case the image is not correctly displayed, that might be the reason.

Lesson 7: OpenGL Keyboard Control

Standard
blue-270x270-0

You will need to change the previous code for this tutorial.

OpenGL is used to communicate with the Graphical Processing Unit, so by default it does not contain keyboard control. You will need a library to communicate with the keyboard. We can use SDL (which we have been using all this time) to take keyboard input.
You may have noticed the function handleKeypress, we have extended it to:

/* function to handle key press events */
void handleKeyPress( SDL_keysym *keysym )
{
    switch ( keysym->sym )
	{
 	case SDLK_ESCAPE:
	    /* ESC key was pressed */
	    Quit( 0 );
	    break;
	case SDLK_F1:
	    /* F1 key was pressed
	     * this toggles fullscreen mode
	     */
	    SDL_WM_ToggleFullScreen( surface );
	    break;

        case SDLK_RIGHT:
	    ox ++;
	    break;

        case SDLK_LEFT:
	    ox--;
	    break;
 
        case SDLK_UP:
	    oz--;
	    break;

        case SDLK_DOWN:
            oz++;
	    break;

	default:
	    break;
	}

    return;
}

Where ox,oy and oz are the objects position.
When drawing, we added the line:

    // draw cube
    glPushMatrix();
      glTranslatef(ox,oy,oz);
      drawCube();
    glPopMatrix();

Thus, using the cursor keys will move the object. :-)