Picking, Alpha Blending, Alpha Testing, Sorting

Welcome to Tutorial 32. This tutorial is probably the largest tutorial I have written to date. Over 1000 lines of Code and more than 1540 lines of HTML. This is also the first tutorial to use my new NeHeGL basecode. This tutorial took a long time to write, but I think it was worth the wait. Some of the topics I cover in this tutorial are: Alpha Blending, Alpha Testing, Reading The Mouse, Using Both Ortho And Perspective At The Same Time, Displaying A Custom Cursor, Manually Sorting Objects By Depth, Animating Frames From A Single Texture and most important, you will learn all about PICKING!

The original version of this tutorial displayed three objects on the screen that would change color when you clicked on them. How exciting is that!?! Not exciting at all! As always, I wanted to impress you guys with a super cool tutorial. I wanted the tutorial to be exciting, packed full of information and of course... nice to look at. So, after weeks of coding, the tutorial is done! Even if you don't code you might enjoy this tutorial. It's a complete game. The object of the game is to shoot as many targets as you can before your morale hits rock bottom or your hand cramps up and you can no longer click the mouse button.

I'm sure there will be critics, but I'm very happy with this tutorial! I've taken dull topics such as picking and sorting object by depth and turned them into something fun!

Some quick notes about the code. I will only discuss the code in lesson32.cpp. There have been a few minor changes in the NeHeGL code. The most important change is that I have added mouse support to WindowProc(). I also added int mouse_x, mouse_y to store mouse movement. In NeHeGL.h the following two lines of code were added: extern int mouse_x; & extern int mouse_y;

The textures used in this tutorial were made in Adobe Photoshop. Each .TGA file is a 32 bit image with an alpha channel. If you are not sure how to add an alpha channel to an image buy yourself a good book, browse the net or read the built in help in Adobe Photoshop. The entire process is very similar to the way I created masks in the masking tutorial. Load your object into Adobe Photoshop (or some other art program that supports the alpha channel). Use select by color range to select the area around your object. Copy that area. Create a new image. Paste the selection into the new image. Negate the image so the area where your image should be is black. Make the area around it white. Select the entire image and copy it. Go back to the original image and create an alpha channel. Paste the black and white mask that you just created into the alpha channel. Save the image as a 32 bit .TGA file. Make sure preserve transparency is checked, and make sure you save it uncompressed!

As always I hope you enjoy the tutorial. I'm interested to hear what you think of it. If you have any questions or you find any mistakes, let me know. I rushed through parts of the tutorial, so if you find any part really hard to understand, send me some email and I'll try to explain things differently or in more detail!

#include <windows.h>								// Header File For Windows
#include <stdio.h>								// Header File For Standard Input / Output
#include <stdarg.h>								// Header File For Variable Argument Routines
#include <gl\gl.h>								// Header File For The OpenGL32 Library
#include <gl\glu.h>								// Header File For The GLu32 Library
#include <time.h>								// For Random Seed
#include "NeHeGL.h"								// Header File For NeHeGL

In lesson 1, I preached about the proper way to link to the OpenGL libraries. In Visual C++ click on project, settings and then the link tab. Move down to object/library modules and add OpenGL32.lib, GLu32.lib and GLaux.lib. Failing to include a required library will cause the compiler to spout out error after error. Something you don't want happening! To make matters worse, if you only include the libaries in debug mode, and someone tries to build your code in release mode... more errors. There are alot of people looking for code. Most of them are new to programming. They grab your code, and try to compile it. They get errors, delete the code and move on.

The code below tells the compiler to link to the required libraries. A little more typing, but alot less headache in the long run. For this tutorial, we will link to the OpenGL32 library, the GLu32 library and the WinMM library (for playing sound). In this tutorial we will be loading .TGA files so we don't need the GLaux library.

#pragma comment( lib, "opengl32.lib" )						// Search For OpenGL32.lib While Linking
#pragma comment( lib, "glu32.lib" )						// Search For GLu32.lib While Linking
#pragma comment( lib, "winmm.lib" )						// Search For WinMM Library While Linking

The 3 lines below check to see if CDS_FULLSCREEN has been defined by your compiler. If it has not been defined, we manually give CDS_FULLSCREEN a value of 4. For those of you that are completely lost right now... Some compilers do not give CDS_FULLSCREEN a value and will return an error message if CDS_FULLSCREEN is used! To prevent an error message, we check to see if CDS_FULLSCREEN has been defined and if not, we manually define it. Makes life easier for everyone.

We then declare DrawTargets, and set up variables for our window and keyboard handling. If you don't understand declarations, read through the MSDN glossary. Keep in mind, I'm not teaching C/C++, buy a good book if you need help with the NON GL code!

#ifndef		CDS_FULLSCREEN							// CDS_FULLSCREEN Is Not Defined By Some
#define		CDS_FULLSCREEN 4						// Compilers. By Defining It This Way,
#endif										// We Can Avoid Errors

void DrawTargets();								// Declaration

GL_Window*	g_window;
Keys*		g_keys;

The following section of code sets up our user defined variables. base will be used for our font display lists. roll will be used to move the ground and create the illusion of rolling clouds. level should be pretty straight forward (we start off on level 1). miss keeps track of how many objects were missed. It's also used to show the players morale (no misses means a high morale). kills keeps track of how many targets were hit each level. score will keep a running total of the number of objects hit, and game will be used to signal game over!

The last line lets us pass structures to our compare function. The qsort routine expects the last parameter to be type type (const *void, const *void).

// User Defined Variables
GLuint		base;								// Font Display List
GLfloat		roll;								// Rolling Clouds
GLint		level=1;							// Current Level
GLint		miss;								// Missed Targets
GLint		kills;								// Level Kill Counter
GLint		score;								// Current Score
bool		game;								// Game Over?

typedef int (*compfn)(const void*, const void*);				// Typedef For Our Compare Function

Now for our objects structure. This structure holds all the information about an object. The direction it's rotating, if it's been hit, it's location on the screen, etc.

A quick rundown of the variables... rot specifies the direction we want to rotate the object. hit will be FALSE if the object has not yet been hit. If the object was hit or manually flagged as being hit, the value of hit will be TRUE.

The variable frame is used to cycle through the frames of animation for our explosion. As frame is increased the explosion texture changes. More on this later in the tutorial.

To keep track of which direction our object is moving, we have a variable called dir. dir can be one of 4 values: 0 - object is moving Left, 1 - object is moving right, 2 - object is moving up and finally 3 - object is moving down.

texid can be any number from 0 to 4. Zero represents the BlueFace texture, 1 is the Bucket texture, 2 is the Target texture , 3 is the Coke can texture and 4 is the Vase texture. Later in the load texture code, you will see that the first 5 textures are the target images.

Both x and y are used to position the object on the screen. x represents where the object is on the x-axis, and y the location of the object on the y-axis.

The objects rotate on the z-axis based on the value of spin. Later in the code, we will increase or decrease spin based on the direction the object is travelling.

Finally, distance keeps track of how far into the screen our object is. distance is an extremely important variable, we will use it to calculate the left and right sides of the screen, and to sort the objects so the objects in the distance are drawn before the objects up close.

struct objects {
	GLuint	rot;								// Rotation (0-None, 1-Clockwise, 2-Counter Clockwise)
	bool	hit;								// Object Hit?
	GLuint	frame;								// Current Explosion Frame
	GLuint	dir;								// Object Direction (0-Left, 1-Right, 2-Up, 3-Down)
	GLuint	texid;								// Object Texture ID
	GLfloat	x;								// Object X Position
	GLfloat y;								// Object Y Position
	GLfloat	spin;								// Object Spin
	GLfloat	distance;							// Object Distance
};

No real reason to explain the code below. We are loading TGA images in this tutorial instead of bitmaps. The structure below is used to store image data, as well as information about the TGA image. Read the tutorial on loading TGA files if you need a detailed explanation of the code below.

typedef struct									// Create A Structure
{
	GLubyte	*imageData;							// Image Data (Up To 32 Bits)
	GLuint	bpp;								// Image Color Depth In Bits Per Pixel.
	GLuint	width;								// Image Width
	GLuint	height;								// Image Height
	GLuint	texID;								// Texture ID Used To Select A Texture
} TextureImage;									// Structure Name

The following code sets aside room for our 10 textures and 30 objects. If you plan to add more objects to the game make sure you increase the value from 30 to however many objects you want.

TextureImage textures[10];							// Storage For 10 Textures

objects	object[30];								// Storage For 30 Objects

I didn't want to limit the size of each object. I wanted the vase to be taller than the can, I wanted the bucket to be wider than the vase. To make life easy, I create a structure that holds the objects width (w) and height (h).

I then set the width and height of each object in the last line of code. To get the coke cans width, I would check size[3].w. The Blueface is 0, the Bucket is 1, and the Target is 2, etc. The width is represented by w. Make sense?

struct dimensions {								// Object Dimensions
	GLfloat	w;								// Object Width
	GLfloat h;								// Object Height
};

// Size Of Each Object: Blueface,     Bucket,      Target,       Coke,         Vase
dimensions size[5] = { {1.0f,1.0f}, {1.0f,1.0f}, {1.0f,1.0f}, {0.5f,1.0f}, {0.75f,1.5f} };

The following large section of code loads our TGA images and converts them to textures. It's the same code I used in lesson 25 so if you need a detailed description go back and read lesson 25.

I use TGA images because they are capable of having an alpha channel. The alpha channel tells OpenGL which parts of the image are transparent and which parts are opaque. The alpha channel is created in an art program, and is saved inside the .TGA image. OpenGL loads the image, and uses the alpha channel to set the amount of transparency for each pixel in the image.

bool LoadTGA(TextureImage *texture, char *filename)				// Loads A TGA File Into Memory
{    
	GLubyte		TGAheader[12]={0,0,2,0,0,0,0,0,0,0,0,0};		// Uncompressed TGA Header
	GLubyte		TGAcompare[12];						// Used To Compare TGA Header
	GLubyte		header[6];						// First 6 Useful Bytes From The Header
	GLuint		bytesPerPixel;						// Holds Number Of Bytes Per Pixel Used In The TGA File
	GLuint		imageSize;						// Used To Store The Image Size When Setting Aside Ram
	GLuint		temp;							// Temporary Variable
	GLuint		type=GL_RGBA;						// Set The Default GL Mode To RBGA (32 BPP)

	FILE *file = fopen(filename, "rb");					// Open The TGA File

	if(	file==NULL ||							// Does File Even Exist?
		fread(TGAcompare,1,sizeof(TGAcompare),file)!=sizeof(TGAcompare) ||	// Are There 12 Bytes To Read?
		memcmp(TGAheader,TGAcompare,sizeof(TGAheader))!=0 ||		// Does The Header Match What We Want?
		fread(header,1,sizeof(header),file)!=sizeof(header))		// If So Read Next 6 Header Bytes
	{
		if (file == NULL)						// Does The File Even Exist? *Added Jim Strong*
			return FALSE;						// Return False
		else								// Otherwise
		{
			fclose(file);						// If Anything Failed, Close The File
			return FALSE;						// Return False
		}
	}

	texture->width  = header[1] * 256 + header[0];				// Determine The TGA Width	(highbyte*256+lowbyte)
	texture->height = header[3] * 256 + header[2];				// Determine The TGA Height	(highbyte*256+lowbyte)
    
 	if(	texture->width	<=0 ||						// Is The Width Less Than Or Equal To Zero
		texture->height	<=0 ||						// Is The Height Less Than Or Equal To Zero
		(header[4]!=24 && header[4]!=32))				// Is The TGA 24 or 32 Bit?
	{
		fclose(file);							// If Anything Failed, Close The File
		return FALSE;							// Return False
	}

	texture->bpp	= header[4];						// Grab The TGA's Bits Per Pixel (24 or 32)
	bytesPerPixel	= texture->bpp/8;					// Divide By 8 To Get The Bytes Per Pixel
	imageSize		= texture->width*texture->height*bytesPerPixel;	// Calculate The Memory Required For The TGA Data

	texture->imageData=(GLubyte *)malloc(imageSize);			// Reserve Memory To Hold The TGA Data

	if(	texture->imageData==NULL ||					// Does The Storage Memory Exist?
		fread(texture->imageData, 1, imageSize, file)!=imageSize)	// Does The Image Size Match The Memory Reserved?
	{
		if(texture->imageData!=NULL)					// Was Image Data Loaded
			free(texture->imageData);				// If So, Release The Image Data

		fclose(file);							// Close The File
		return FALSE;							// Return False
	}

	for(GLuint i=0; i<int(imageSize); i+=bytesPerPixel)			// Loop Through The Image Data
	{									// Swaps The 1st And 3rd Bytes ('R'ed and 'B'lue)
		temp=texture->imageData[i];					// Temporarily Store The Value At Image Data 'i'
		texture->imageData[i] = texture->imageData[i + 2];		// Set The 1st Byte To The Value Of The 3rd Byte
		texture->imageData[i + 2] = temp;				// Set The 3rd Byte To The Value In 'temp' (1st Byte Value)
	}

	fclose (file);								// Close The File

	// Build A Texture From The Data
	glGenTextures(1, &texture[0].texID);					// Generate OpenGL texture IDs

	glBindTexture(GL_TEXTURE_2D, texture[0].texID);				// Bind Our Texture
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);	// Linear Filtered
	glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);	// Linear Filtered
	
	if (texture[0].bpp==24)							// Was The TGA 24 Bits
	{
		type=GL_RGB;							// If So Set The 'type' To GL_RGB
	}

	glTexImage2D(GL_TEXTURE_2D, 0, type, texture[0].width, texture[0].height, 0, type, GL_UNSIGNED_BYTE, texture[0].imageData);

	return true;								// Texture Building Went Ok, Return True
}

The 2D texture font code is the same code I have used in previous tutorials. However, there are a few small changes. The thing you will notice is that we are only generating 95 display lists. If you look at the font texture, you will see there are only 95 characters counting the space at the top left of the image. The second thing you will notice is we divide by 16.0f for cx and we only divide by 8.0f for cy. The reason we do this is because the font texture is 256 pixels wide, but only half as tall (128 pixels). So to calculate cx we divide by 16.0f and to calculate cy we divide by half that (8.0f).

If you do not understand the code below, go back and read through Lesson 17. The font building code is explained in detail in lesson 17!

GLvoid BuildFont(GLvoid)							// Build Our Font Display List
{
	base=glGenLists(95);							// Creating 95 Display Lists
	glBindTexture(GL_TEXTURE_2D, textures[9].texID);			// Bind Our Font Texture
	for (int loop=0; loop<95; loop++)					// Loop Through All 95 Lists
	{
		float cx=float(loop%16)/16.0f;					// X Position Of Current Character
		float cy=float(loop/16)/8.0f;					// Y Position Of Current Character

		glNewList(base+loop,GL_COMPILE);				// Start Building A List
			glBegin(GL_QUADS);					// Use A Quad For Each Character
				glTexCoord2f(cx,         1.0f-cy-0.120f); glVertex2i(0,0);	// Texture / Vertex Coord (Bottom Left)
				glTexCoord2f(cx+0.0625f, 1.0f-cy-0.120f); glVertex2i(16,0);	// Texutre / Vertex Coord (Bottom Right)
				glTexCoord2f(cx+0.0625f, 1.0f-cy);	  glVertex2i(16,16);	// Texture / Vertex Coord (Top Right)
				glTexCoord2f(cx,         1.0f-cy);	  glVertex2i(0,16);	// Texture / Vertex Coord (Top Left)
			glEnd();						// Done Building Our Quad (Character)
			glTranslated(10,0,0);					// Move To The Right Of The Character
		glEndList();							// Done Building The Display List
	}									// Loop Until All 256 Are Built
}

The printing code is the code is also from lesson 17, but has been modified to allow us to print the score, level and morale to the screen (variables that continually change).

GLvoid glPrint(GLint x, GLint y, const char *string, ...)			// Where The Printing Happens
{
	char		text[256];						// Holds Our String
	va_list		ap;							// Pointer To List Of Arguments

	if (string == NULL)							// If There's No Text
		return;								// Do Nothing

	va_start(ap, string);							// Parses The String For Variables
	    vsprintf(text, string, ap);						// And Converts Symbols To Actual Numbers
	va_end(ap);								// Results Are Stored In Text

	glBindTexture(GL_TEXTURE_2D, textures[9].texID);			// Select Our Font Texture
	glPushMatrix();								// Store The Modelview Matrix
	glLoadIdentity();							// Reset The Modelview Matrix
	glTranslated(x,y,0);							// Position The Text (0,0 - Bottom Left)
	glListBase(base-32);							// Choose The Font Set
	glCallLists(strlen(text), GL_UNSIGNED_BYTE, text);			// Draws The Display List Text
	glPopMatrix();								// Restore The Old Projection Matrix
}

This code will be called later in the program by qsort. It compares the distance in two structures and return -1 if the first structures distance was less than the seconds structures distance, 1 if the first structures distance is greater than the second structures distance and 0 if the distance is the same in both structures.

int Compare(struct objects *elem1, struct objects *elem2)			// Compare Function *** MSDN CODE MODIFIED FOR THIS TUT ***
{
   if ( elem1->distance < elem2->distance)					// If First Structure distance Is Less Than The Second
      return -1;								// Return -1
   else if (elem1->distance > elem2->distance)					// If First Structure distance Is Greater Than The Second
      return 1;									// Return 1
   else										// Otherwise (If The distance Is Equal)
      return 0;									// Return 0
}

The InitObject() code is where we set up each object. We start off by setting rot to 1. This gives the object clockwise rotation. Then we set the explosion animation to frame 0 (we don't want the explosion to start halfway through the animation). Next we set hit to FALSE, meaning the object has not yet been hit or set to self destruct. To select an object texture, texid is assigned a random value from 0 to 4. Zero is the blueface texture and 4 is the vase texture. This gives us one of 5 random objects.

The variable distance will be a random number from -0.0f to -40.0f (4000/100 is 40). When we actually draw the object, we translate another 10 units into the screen. So when the objects are drawn, they will be drawn from -10.0f to -50.0f units into the screen (not to close, and not too far). I divide the random number by 100.0f to get a more accurate floating point value.

After assigning a random distance, we then give the object a random y value. We don't want the object any lower than -1.5f, otherwise it will be under the ground, and we dont want the object any higher than 3.0f. So to stay in that range our random number can not be any higher than 4.5f (-1.5f+4.5f=3.0f).

To calculate the x position, we use some tricky math. We take our distance and we subtract 15.0f from it. Then we divide the result by 2 and subtract 5*level. Finally, we subtract a random amount from 0.0f to 5 multiplied by the current level. We subtract the 5*level and the random amount from 0.0f to 5*level so that our object appears further off the screen on higher levels. If we didn't, the objects would appear one after another, making it even more difficult to hit all the targets than it already is.

Finally we choose a random direction (dir) from 0 (left) to 1 (right).

To make things easier to understand in regards to the x position, I'll write out a quick example. Say our distance is -30.0f and the current level is 1:

object[num].x=((-30.0f-15.0f)/2.0f)-(5*1)-float(rand()%(5*1));
object[num].x=(-45.0f/2.0f)-5-float(rand()%5);
object[num].x=(-22.5f)-5-{lets say 3.0f};
object[num].x=(-22.5f)-5-{3.0f};
object[num].x=-27.5f-{3.0f};
object[num].x=-30.5f;

Now keeping in mind that we move 10 units into the screen before we draw our objects, and the distance in the example above is -30.0f. It's safe to say our actual distance into the screen will be -40.0f. Using the perspective code in the NeHeGL.cpp file, it's safe to assume that if the distance is -40.0f, the far left edge of the screen will be -20.0f and the far right will be +20.0f. In the code above our x value is -22.5f (which would be JUST off the left side of the screen). We then subtract 5 and our random value of 3 which guarantees the object will start off the screen (at -30.5f) which means the object would have to move roughly 8 units to the right before it even appeared on the screen.

GLvoid InitObject(int num)							// Initialize An Object
{
	object[num].rot=1;							// Clockwise Rotation
	object[num].frame=0;							// Reset The Explosion Frame To Zero
	object[num].hit=FALSE;							// Reset Object Has Been Hit Status To False
	object[num].texid=rand()%5;						// Assign A New Texture
	object[num].distance=-(float(rand()%4001)/100.0f);			// Random Distance
	object[num].y=-1.5f+(float(rand()%451)/100.0f);				// Random Y Position
	// Random Starting X Position Based On Distance Of Object And Random Amount For A Delay (Positive Value)
	object[num].x=((object[num].distance-15.0f)/2.0f)-(5*level)-float(rand()%(5*level));
	object[num].dir=(rand()%2);						// Pick A Random Direction

Now we check to see which direction the object is going to be travelling. The code below checks to see if the object is moving left. If it is, we have to change the rotation so that the object is spinning counter clockwise. We do this by changing the value of rot to 2.

Our x value by default is going to be a negative number. However, the right side of the screen would be a positive value. So the last thing we do is negate the current x value. In english, we make the x value a positive value instead of a negative value.

	if (object[num].dir==0)							// Is Random Direction Right
	{
		object[num].rot=2;						// Counter Clockwise Rotation
		object[num].x=-object[num].x;					// Start On The Left Side (Negative Value)
	}

Now we check the texid to find out what object the computer has randomly picked. If texid is equal to 0, the computer has picked the Blueface object. The blueface guys always roll across the ground. To make sure they start off at ground level, we manually set the y value to -2.0f.

	if (object[num].texid==0)						// Blue Face
		object[num].y=-2.0f;						// Always Rolling On The Ground

Next we check to see if texid is 1. If so, the computer has selected the Bucket. The bucket doesn't travel from left to right, it falls from the sky. The first thing we have to do is set dir to 3. This tells the computer that our bucket is falling or moving down.

Our initial code assumes the object will be travelling from left to right. Because the bucket is falling down, we have to give it a new random x value. If we didn't, the bucket would never be visible. It would fall either far off the left side of the screen or far off the right side of the screen. To assign a new value we randomly choose a value based on the distance into the screen. Instead of subtracting 15, we only subtract 10. This gives us a little less range, and keeps the object ON the screen instead of off the side of the screen. Assuming our distance was -30.0f, we would end up with a random value from 0.0f to 40.0f. If you're asking yourself, why from 0.0f to 40.0f? Shouldn't it be from 0.0f to -40.0f? The answer is easy. The rand() function always returns a positive number. So whatever number we get back will be a positive value. Anyways... back to the story. So we have a positive number from 0.0f to 40.0f. We then add the distance (a negative value) minus 10.0f divided by 2. As an example... assuming the random value returned is say 15 and the distance is -30.0f:

object[num].x=float(rand()%int(-30.0f-10.0f))+((-30.0f-10.0f)/2.0f);

object[num].x=float(rand()%int(-40.0f)+(-40.0f)/2.0f);

object[num].x=float(15 {assuming 15 was returned))+(-20.0f);

object[num].x=15.0f-20.0f;

object[num].x=-5.0f;

The last thing we have to do is set the y value. We want the bucket to drop from the sky. We don't want it falling through the clouds though. So we set the y value to 4.5f. Just a little below the clouds.

	if (object[num].texid==1)						// Bucket
	{
		object[num].dir=3;						// Falling Down
		object[num].x=float(rand()%int(object[num].distance-10.0f))+((object[num].distance-10.0f)/2.0f);
		object[num].y=4.5f;						// Random X, Start At Top Of The Screen
	}

We want the target to pop out of the ground and up into the air. We check to make sure the object is indeed a target (texid is 2). If so, we set the direction (dir) to 2 (up). We use the exact same code as above to get a random x location.

We don't want the target to start above ground. So we set it's initial y value to -3.0f (under the ground). We then subtract a random value from 0.0f to 5 multiplied by the current level. We do this so that the target doesn't INSTANTLY appear. On higher levels we want a delay before the target appears. Without a delay, the targets would pop out one after another, giving you very little time to hit them.

	if (object[num].texid==2)						// Target
	{
		object[num].dir=2;						// Start Off Flying Up
		object[num].x=float(rand()%int(object[num].distance-10.0f))+((object[num].distance-10.0f)/2.0f);
		object[num].y=-3.0f-float(rand()%(5*level));			// Random X, Start Under Ground + Random Value
	}

All of the other objects travel from left to right, so there is no need to assign any values to the remaining objects. They should work just fine with the random values they were assigned.

Now for the fun stuff! "For the alpha blending technique to work correctly, the transparent primitives must be drawn in back to front order and must not intersect". When drawing alpha blended objects, it is very important that objects in the distance are drawn first, and objects up close are drawn last.

The reason is simple... The Z buffer prevents OpenGL from drawing pixels that are behind things that have already been drawn. So what ends up happening is objects drawn behind transparent objects do not show up. What you end up seeing is a square shape around overlapping objects... Not pretty!

We already know the depth of each object. So after initializing a new object, we can get around this problem by sorting the objects using the qsort function (quick sort). By sorting the objects, we can be sure that the first object drawn is the object furthest away. That way when we draw the objects, starting at the first object, the objects in the distance will be drawn first. Objects that are closer (drawn later) will see the previously drawn objects behind them, and will blend properly!

As noted in the line comments I found this code in the MSDN after searching the net for hours looking for a solution. It works good and allows you to sort entire structures. qsort takes 4 parameters. The first parameter points to the object array (the array to be sorted). The second parameter is the number of arrays we want to sort... of course we want to sort through all the object currently being displayed (which is level). The third parameter specifies the size of our objects structure and the fourth parameter points to our Compare() function.

There is probably a better way to sort structures, but qsort() works... It's quick, convenient and easy to use!

It's important to note, that if you wanted to use the glAlphaFunc() and glEnable(GL_ALPHA_TEST), sorting is not necessary. However, using the Alpha Function you are restricted to completely transparent or completely opaque blending, there is no in between. Sorting and using the Blendfunc() is a little more work, but it allows for semi-transparent objects.

	// Sort Objects By Distance:	Beginning Address Of Our object Array	*** MSDN CODE MODIFIED FOR THIS TUT ***
	//								Number Of Elements To Sort
	//								Size Of Each Element
	//								Pointer To Our Compare Function
	qsort((void *) &object, level, sizeof(struct objects), (compfn)Compare );
}

The init code is same as always. The first two lines grab information about our window and our keyboard handler. We then use srand() to create a more random game based on the time. After that we load our TGA images and convert them to textures using LoadTGA(). The first 5 images are objects that will streak across the screen. Explode is our explosion animation, ground and sky make up the background scene, crosshair is the crosshair you see on the screen representing your current mouse location, and finally, the font image is the font used to display the score, title, and morale. If any of the images fail to load FALSE is returned, and the program shuts down. It's important to note that this base code will not return an INIT FAILED error message.

BOOL Initialize (GL_Window* window, Keys* keys)					// Any OpenGL Initialization Goes Here
{
	g_window	= window;
	g_keys		= keys;

	srand( (unsigned)time( NULL ) );					// Randomize Things

	if ((!LoadTGA(&textures[0],"Data/BlueFace.tga")) ||			// Load The BlueFace Texture
		(!LoadTGA(&textures[1],"Data/Bucket.tga")) ||			// Load The Bucket Texture
		(!LoadTGA(&textures[2],"Data/Target.tga")) ||			// Load The Target Texture
		(!LoadTGA(&textures[3],"Data/Coke.tga")) ||			// Load The Coke Texture
		(!LoadTGA(&textures[4],"Data/Vase.tga")) ||			// Load The Vase Texture
		(!LoadTGA(&textures[5],"Data/Explode.tga")) ||			// Load The Explosion Texture
		(!LoadTGA(&textures[6],"Data/Ground.tga")) ||			// Load The Ground Texture
		(!LoadTGA(&textures[7],"Data/Sky.tga")) ||			// Load The Sky Texture
		(!LoadTGA(&textures[8],"Data/Crosshair.tga")) ||		// Load The Crosshair Texture
		(!LoadTGA(&textures[9],"Data/Font.tga")))			// Load The Crosshair Texture
	{
		return FALSE;							// If Loading Failed, Return False
	}

If all of the images loaded and were successfully turned into textures, we can continue with initialization. The font texture is loaded, so it's safe to build our font. We do this by jumping to BuildFont().

We then set up OpenGL. The background color is set to black, the alpha is also set to 0.0f. The depth buffer is set up and enabled with less than or equal testing.

The glBlendFunc() is a VERY important line of code. We set the blend function to (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA). This blends the object with what's on the screen using the alpha values stored in the objects texture. After setting the blend mode, we enable blending. We then enable 2D texture mapping, and finally, we enable GL_CULL_FACE. This removes the back face from each object ( no point in wasting cycles drawing something we can't see ). We draw all of our quads with a counter clockwise winding so the proper face is culled.

Earlier in the tutorial I talked about using the glAlphaFunc() instead of alpha blending. If you want to use the Alpha Function, comment out the 2 lines of blending code and uncomment the 2 lines under glEnable(GL_BLEND). You can also comment out the qsort() function in the InitObject() section of code.

The program should run ok, but the sky texture will not be there. The reason is because the sky texture has an alpha value of 0.5f. When I was talking about the Alpha Function earlier on, I mentioned that it only works with alpha values of 0 or 1. You will have to modify the alpha channel for the sky texture if you want it to appear! Again, if you decide to use the Alpha Function instead, you don't have to sort the objects. Both methods have the good points! Below is a quick quote from the SGI site:

"The alpha function discards fragments instead of drawing them into the frame buffer. Therefore sorting of the primitives is not necessary (unless some other mode like alpha blending is enabled). The disadvantage is that pixels must be completely opaque or completely transparent".

	BuildFont();								// Build Our Font Display List

	glClearColor(0.0f, 0.0f, 0.0f, 0.0f);					// Black Background
	glClearDepth(1.0f);							// Depth Buffer Setup
	glDepthFunc(GL_LEQUAL);							// Type Of Depth Testing
	glEnable(GL_DEPTH_TEST);						// Enable Depth Testing
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);			// Enable Alpha Blending (disable alpha testing)
	glEnable(GL_BLEND);							// Enable Blending       (disable alpha testing)
//	glAlphaFunc(GL_GREATER,0.1f);						// Set Alpha Testing     (disable blending)
//	glEnable(GL_ALPHA_TEST);						// Enable Alpha Testing  (disable blending)
	glEnable(GL_TEXTURE_2D);						// Enable Texture Mapping
	glEnable(GL_CULL_FACE);							// Remove Back Face

At this point in the program, none of the objects have been defined. So we loop through all thirty objects calling InitObject() for each object.

	for (int loop=0; loop<30; loop++)					// Loop Through 30 Objects
		InitObject(loop);						// Initialize Each Object
	
	return TRUE;								// Return TRUE (Initialization Successful)
}

In our init code, we called BuildFont() which builds our 95 display lists. The following line of code deletes all 95 display lists before the program quits.

void Deinitialize (void)							// Any User DeInitialization Goes Here
{
	glDeleteLists(base,95);							// Delete All 95 Font Display Lists
}

Now for the tricky stuff... The code that does the actual selecting of the objects. The first line of code below allocates a buffer that we can use to store information about our selected objects into. The variable hits will hold the number of hits detected while in selection mode.

void Selection(void)								// This Is Where Selection Is Done
{
	GLuint	buffer[512];							// Set Up A Selection Buffer
	GLint	hits;								// The Number Of Objects That We Selected

In the code below, we check to see if the game is over (FALSE). If it is, there is no point in selecting anything, so we return (exit). If the game is still active (TRUE), we play a gunshot sound using the Playsound() command. The only time Selection() is called is when the mouse button has been pressed, and every time the button is pressed, we want to play the gunshot sound. The sound is played in async mode so that it doesn't halt the program while the sound is playing.

	if (game)								// Is Game Over?
		return;								// If So, Don't Bother Checking For Hits
	
	PlaySound("data/shot.wav",NULL,SND_ASYNC);				// Play Gun Shot Sound

Now we set up a viewport. viewport[] will hold the current x, y, length and width of the current viewport (OpenGL Window).

glGetIntegerv(GL_VIEWPORT, viewport) gets the current viewport boundries and stores them in viewport[]. Initially, the boundries are equal the the OpenGL window dimensions. glSelectBuffer(512, buffer) tells OpenGL to use buffer for it's selection buffer.

	// The Size Of The Viewport. [0] Is <x>, [1] Is <y>, [2] Is <length>, [3] Is <width>
	GLint	viewport[4];

	// This Sets The Array <viewport> To The Size And Location Of The Screen Relative To The Window
	glGetIntegerv(GL_VIEWPORT, viewport);
	glSelectBuffer(512, buffer);						// Tell OpenGL To Use Our Array For Selection

All of the code below is very important. The first line puts OpenGL in selection mode. In selection mode, nothing is drawn to the screen. Instead, information about objects rendered while in selection mode will be stored in the selection buffer.

Next we initialize the name stack by calling glInitNames() and glPushName(0). It's important to note that if the program is not in selection mode, a call to glPushName() will be ignored. Of course we are in selection mode, but it's something to keep in mind.

	// Puts OpenGL In Selection Mode. Nothing Will Be Drawn. Object ID's and Extents Are Stored In The Buffer.
	(void) glRenderMode(GL_SELECT);

	glInitNames();								// Initializes The Name Stack
	glPushName(0);								// Push 0 (At Least One Entry) Onto The Stack

After preparing the name stack, we have to to restrict drawing to the area just under our crosshair. In order to do this we have to select the projection matrix. After selecting the projection matrix we push it onto the stack. We then reset the projection matrix using glLoadIdentity().

We restrict drawing using gluPickMatrix(). The first parameter is our current mouse position on the x-axis, the second parameter is the current mouse position on the y-axis, then the width and height of the picking region. Finally the current viewport[]. The viewport[] indicates the current viewport boundaries. mouse_x and mouse_y will be the center of the picking region.

	glMatrixMode(GL_PROJECTION);						// Selects The Projection Matrix
	glPushMatrix();								// Push The Projection Matrix
	glLoadIdentity();							// Resets The Matrix

	// This Creates A Matrix That Will Zoom Up To A Small Portion Of The Screen, Where The Mouse Is.
	gluPickMatrix((GLdouble) mouse_x, (GLdouble) (viewport[3]-mouse_y), 1.0f, 1.0f, viewport);

Calling gluPerspective() multiplies the perspective matrix by the pick matrix which restricts the drawing to the area requested by gluPickMatrix().

We then switch to the modelview matrix and draw our targets by calling DrawTargets(). We draw the targets in DrawTargets() and not in Draw() because we only want selection to check for hits with objects (targets) and not the sky, ground or crosshair.

After drawing our targets, we switch back to the projection matrix and pop the stored matrix off the stack. We then switch back to the modelview matrix.

The last line of code below switches back to render mode so that objects we draw actually appear on the screen. hits will hold the number of objects that were rendered in the viewing area requested by gluPickMatrix().

	// Apply The Perspective Matrix
	gluPerspective(45.0f, (GLfloat) (viewport[2]-viewport[0])/(GLfloat) (viewport[3]-viewport[1]), 0.1f, 100.0f);
	glMatrixMode(GL_MODELVIEW);						// Select The Modelview Matrix
	DrawTargets();								// Render The Targets To The Selection Buffer
	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glPopMatrix();								// Pop The Projection Matrix
	glMatrixMode(GL_MODELVIEW);						// Select The Modelview Matrix
	hits=glRenderMode(GL_RENDER);						// Switch To Render Mode, Find Out How Many

Now we check to see if there were more than 0 hits recorded. If so, we set choose to equal the name of the first object drawn into the picking area. depth holds how deep into the screen, the object is.

Each hit takes 4 items in the buffer. The first item is the number of names on the name stack when the hit occured. The second item is the minimum z value of all the verticies that intersected the viewing area at the time of the hit. The third item is the maximum z value of all the vertices that intersected the viewing area at the time of the hit and the last item is the content of the name stack at the time of the hit (name of the object). We are only interested in the minimum z value and the object name in this tutorial.

	if (hits > 0)								// If There Were More Than 0 Hits
	{
		int	choose = buffer[3];					// Make Our Selection The First Object
		int	depth = buffer[1];					// Store How Far Away It Is

We then loop through all of the hits to make sure none of the objects are closer than the first object hit. If we didn't do this, and two objects were overlapping, the first object hit might behind another object, and clicking the mouse would take away the first object, even though it was behind another object. When you shoot at something, the closest object should be the object that gets hit.

So, we check through all of the hits. Remember that each object takes 4 items in the buffer, so to search through each hit we have to multiply the current loop value by 4. We add 1 to get the depth of each object hit. If the depth is less than the the current selected objects depth, we store the name of the closer object in choose and we store the depth of the closer object in depth. After we have looped through all of our hits, choose will hold the name of the closest object hit, and depth will hold the depth of the closest object hit.

		for (int loop = 1; loop < hits; loop++)				// Loop Through All The Detected Hits
		{
			// If This Object Is Closer To Us Than The One We Have Selected
			if (buffer[loop*4+1] < GLuint(depth))
			{
				choose = buffer[loop*4+3];			// Select The Closer Object
				depth = buffer[loop*4+1];			// Store How Far Away It Is
			}       
		}

All we have to do is mark the object as being hit. We check to make sure the object has not already been hit. If it has not been hit, we mark it as being hit by setting hit to TRUE. We increase the players score by 1 point, and we increase the kills counter by 1.

		if (!object[choose].hit)					// If The Object Hasn't Already Been Hit
		{
			object[choose].hit=TRUE;				// Mark The Object As Being Hit
			score+=1;						// Increase Score
			kills+=1;						// Increase Level Kills

I use kills to keep track of how many objects have been destroyed on each level. I wanted each level to have more objects (making it harder to get through the level). So I check to see if the players kills is greater than the current level multiplied by 5. On level 1, the player only has to kill 5 objects (1*5). On level 2 the player has to kill 10 objects (2*5), progressively getting harder each level.

So, the first line of code checks to see if kills is higher than the level multiplied by 5. If so, we set miss to 0. This sets the player morale back to 10 out of 10 (the morale is 10-miss). We then set kills to 0 (which starts the counting process over again).

Finally, we increase the value of level by 1 and check to see if we've hit the last level. I have set the maximum level to 30 for the following two reasons... Level 30 is insanely difficult. I am pretty sure no one will ever have that good of a game. The second reason... At the top of the code, we only set up 30 objects. If you want more objects, you have to increase the value accordingly.

It is VERY important to note that you can have a maximum of 64 objects on the screen (0-63). If you try to render 65 or more objects, picking becomes confused, and odd things start to happen. Everything from objects randomly exploding to your computer crashing. It's a physical limit in OpenGL (just like the 8 lights limit).

If by some chance you are a god, and you finish level 30, the level will no longer increase, but your score will. Your morale will also reset to 10 every time you finish the 30th level.

			if (kills>level*5)					// New Level Yet?
			{
				miss=0;						// Misses Reset Back To Zero
				kills=0;					// Reset Level Kills
				level+=1;					// Increase Level
				if (level>30)					// Higher Than 30?
					level=30;				// Set Level To 30 (Are You A God?)
			}
		}
	}
}

Update() is where I check for key presses, and update object movement. One of the nice things about Update() is the milliseconds timer. You can use the milliseconds timer to move objects based on the amount of time that has passed since Update() was last called. It's important to note that moving object based on time keeps the objects moving at the same speed on any processor... BUT there are drawbacks! Lets say you have an object moving 5 units in 10 seconds. On a fast system, the computer will move the object half a unit every second. On a slow system, it could be 2 seconds before the update procedure is even called. So when the object moves, it will appear to skip a spot. The animation will not be as smooth on a slower system. (Note: this is just an exaggerated example... computers update ALOT faster than once every two seconds).

Anyways... with that out of the way... on to the code. The code below checks to see if the escape key is being pressed. If it is, we quit the application by calling TerminateApplication(). g_window holds the information about our window.

void Update(DWORD milliseconds)							// Perform Motion Updates Here
{
	if (g_keys->keyDown[VK_ESCAPE])						// Is ESC Being Pressed?
	{
		TerminateApplication (g_window);				// Terminate The Program
	}

The code below checks to see if the space bar is pressed and the game is over. If both conditions are true, we initialize all 30 object (give them new directions, textures, etc). We set game to FALSE, telling the program the game is no longer over. We set the score back to 0, the level back to 1, the player kills to 0 and finally we set the miss variable back to zero. This restarts the game on the first level with full morale and a score of 0.

	if (g_keys->keyDown[' '] && game)					// Space Bar Being Pressed After Game Has Ended?
	{
		for (int loop=0; loop<30; loop++)				// Loop Through 30 Objects
			InitObject(loop);					// Initialize Each Object

		game=FALSE;							// Set game (Game Over) To False
		score=0;							// Set score To 0
		level=1;							// Set level Back To 1
		kills=0;							// Zero Player Kills
		miss=0;								// Set miss (Missed Shots) To 0
	}

The code below checks to see if the F1 key has been pressed. If F1 is being pressed, ToggleFullscreen will switch from windowed to fullscreen mode or fullscreen mode to windowed mode.

	if (g_keys->keyDown[VK_F1])						// Is F1 Being Pressed?
	{
		ToggleFullscreen (g_window);					// Toggle Fullscreen Mode
	}

To create the illusion of rolling clouds and moving ground, we decrease roll by .00005f multiplied by the number of milliseconds that have passed. This keeps the clouds moving at the same speed on all systems (fast or slow).

We then set up a loop to loop through all of the objects on the screen. Level 1 has one object, level 10 has 10 objects, etc.

	roll-=milliseconds*0.00005f;						// Roll The Clouds

	for (int loop=0; loop<level; loop++)					// Loop Through The Objects
	{

We need to find out which way the object should be spinning. We do this by checking the value of rot. If rot equals 1, we need to spin the object clockwise. To do this, we decrease the value of spin. We decrease spin by 0.2f multiplied by value of loop plus the number of milliseconds that have passed. By using milliseconds the objects will rotate the same speed on all systems. Adding loop makes each NEW object spin a little faster than the last object. So object 2 will spin faster than object 1 and object 3 will spin faster than object 2.

		if (object[loop].rot==1)					// If Rotation Is Clockwise
			object[loop].spin-=0.2f*(float(loop+milliseconds));	// Spin Clockwise

Next we check to see if rot equals 2. If rot equals 2, we need to spin counter clockwise. The only difference from the code above is that we are increasing the value of spin instead of decreasing it. This causes the object to spin in the opposite direction.

		if (object[loop].rot==2)					// If Rotation Is Counter Clockwise
			object[loop].spin+=0.2f*(float(loop+milliseconds));	// Spin Counter Clockwise

Now for the movement code. We check the value of dir if it's equal to 1, we increase the objects x value based on the milliseconds passed multiplied by 0.012f. This moves the object right. Because we use milliseconds the objects should move the same speed on all systems.

		if (object[loop].dir==1)					// If Direction Is Right
			object[loop].x+=0.012f*float(milliseconds);		// Move Right

If dir equals 0, the object is moving left. We move the object left by decreasing the objects x value. Again we decrease x based on the amount of time that has passed in milliseconds multiplied by our fixed value of 0.012f.

		if (object[loop].dir==0)					// If Direction Is Left
			object[loop].x-=0.012f*float(milliseconds);		// Move Left

Only two more directions to watch for. This time we check to see if dir equals 2. If so, we increase the objects y value. This causes the object to move UP the screen. Keep in mind the positive y axis is at the top of the screen and the negative y axis is at the bottom. So increasing y moves from the bottom to the top. Again movement is based on time passed.

		if (object[loop].dir==2)					// If Direction Is Up
			object[loop].y+=0.012f*float(milliseconds);		// Move Up

The last direction our object can travel is down. If dir equals three, we want to move the object down the screen. We do this by increasing the objects y value based on the amount of time that has passed. Notice we move down slower than we move up. When an object is falling, our fixed falling rate is 0.0025f. When we move up, the fixed rate is 0.012f.

		if (object[loop].dir==3)					// If Direction Is Down
			object[loop].y-=0.0025f*float(milliseconds);		// Move Down

After moving our objects we have to check if they are still in view. The code below first checks to see where our object is on the screen. We can roughly calculate how far left an object can travel by taking the objects distance into the screen minus 15.0f (to make sure it's a little past the screen) and dividing it by 2. For those of you that don't already know... If you are 20 units into the screen, depending on the way you set up the perspective, you have roughly 10 units from the left of the screen to the center and 10 from the center to the right. so -20.0f(distance)-15.0f(extra padding)=-35.0f... divide that by 2 and you get -17.5f. That's roughly 7.5 units off the left side of the screen. Meaning our object is completely out of view.

Anyways... after making sure the object is far off the left side of the screen, we check to see if it was moving left (dir=0). If it's not moving left, we don't care if it's off the left side of the screen!

Finally, we check to see if the object was hit. If the object is off the left of the screen, it's travelling left and it wasn't hit, it's too late for the player to hit it. So we increase the value of miss. This lowers morale and increases the number of missed targets. We set the objects hit value to TRUE so the computer thinks it's been hit. This forces the object to self destruct (allowing us to give the object a new texture, directions, spin, etc).

		// If We Are To Far Left, Direction Is Left And The Object Was Not Hit
		if ((object[loop].x<(object[loop].distance-15.0f)/2.0f) && (object[loop].dir==0) && !object[loop].hit)
		{
			miss+=1;						// Increase miss (Missed Object)
			object[loop].hit=TRUE;					// Set hit To True To Manually Blow Up The Object
		}

The following code does the exact same thing as the code above, but instead of checking to see if we've gone off the left side of the screen, we check to see if it's gone off the right side of the screen. We also check to make sure the object is moving right and not some other direction. If the object is off the screen, we increase the value of miss and self destruct the object by telling our program it's been hit.

		// If We Are To Far Right, Direction Is Left And The Object Was Not Hit
		if ((object[loop].x>-(object[loop].distance-15.0f)/2.0f) && (object[loop].dir==1) && !object[loop].hit)
		{
			miss+=1;						// Increase miss (Missed Object)
			object[loop].hit=TRUE;					// Set hit To True To Manually Blow Up The Object
		}

The falling code is pretty straight forward. We check to see if the object has just about hit the ground. We don't want it to fall through the ground which is at -3.0f. Instead, we check to see if the object is below -2.0f. We then check to make sure the object is indeed falling (dir=3) and that the object has not yet been hit. If the object is below -2.0f on the y axis, we increase miss and set the objects hit variable to TRUE (causing it to self destruct as it hits the ground... nice effect).

		// If We Are To Far Down, Direction Is Down And The Object Was Not Hit
		if ((object[loop].y<-2.0f) && (object[loop].dir==3) && !object[loop].hit)
		{
			miss+=1;						// Increase miss (Missed Object)
			object[loop].hit=TRUE;					// Set hit To True To Manually Blow Up The Object
		}

Unlike the previous code, the going up code is a little different. We don't want the object to go through the clouds! We check to see if the objects y variable is greater than 4.5f (close to the clouds). We also make sure the object is travelling up (dir=2). If the objects y value is greater than 4.5f, instead of destroying the object, we change it's direction. That way the object will quickly pop out of the ground (remember, it goes up faster than it comes down) and once it gets to high we change its direction so it starts to fall toward the ground.

There is no need to destroy the object, or increase the miss variable. If you miss the object as it's flying into the sky, there's always a chance to hit it as it falls. The falling code will handle the final destruction of the object.

		if ((object[loop].y>4.5f) && (object[loop].dir==2))		// If We Are To Far Up And The Direction Is Up
			object[loop].dir=3;					// Change The Direction To Down
	}
}

Next we have the object drawing code. I wanted a quick and easy way to draw the game objects, along with the crosshair with as little code as possible. Object takes 3 parameters. First we have the width. The width controls how wide the object will be when it's drawn. Then we have the height. The height controls how tall the object will be when it's drawn. Finally, we have the texid. The texid selects the texture we want to use. If we wanted to draw a bucket, which is texture 1, we would pass a value of 1 for the texid. Pretty simple!

A quick breakdown. We select the texture, and then draw a quad. We use standard texture coordinates so the entire textue is mapped to the face of the quad. The quad is drawn in a counter-clockwise direction (required for culling to work).

void Object(float width,float height,GLuint texid)				// Draw Object Using Requested Width, Height And Texture
{
	glBindTexture(GL_TEXTURE_2D, textures[texid].texID);			// Select The Correct Texture
	glBegin(GL_QUADS);							// Start Drawing A Quad
		glTexCoord2f(0.0f,0.0f); glVertex3f(-width,-height,0.0f);	// Bottom Left
		glTexCoord2f(1.0f,0.0f); glVertex3f( width,-height,0.0f);	// Bottom Right
		glTexCoord2f(1.0f,1.0f); glVertex3f( width, height,0.0f);	// Top Right
		glTexCoord2f(0.0f,1.0f); glVertex3f(-width, height,0.0f);	// Top Left
	glEnd();								// Done Drawing Quad
}

The explosion code takes one parameter. num is the object identifier. In order to create the explosion we need to grab a portion of the explosion texture similar to the way we grab each letter from the font texture. The two lines below calculate the column (ex) and row (ey) from a single number (frame).

The first line below grabs the current frame and divides it by 4. The division by 4 is to slow down the animation. %4 keeps the value in the 0-3 range. If the value is higher than 3 it would wrap around and become 0. If the value is 5 it would become 1. A value of 9 would be 0,1,2,3,0,1,2,3,0. We divide the final result by 4.0f because texture coordinates are in the 0.0f to 1.0f range. Our explosion texture has 4 explosion images from left to right and 4 up and down.

Hopefully you're not completely confused. So if our number before division can only be 0,1,2 or 3 our number after we divide it by 4.0f can only be 0.0f, 0.25f (1/4), 0.50f (2/4) or 0.75f (3/4). This gives us our left to right texture coordinate (ex).

Next we calculate the row (ey). We grab the current object frame and divide it by 4 to slow the animation down a little. We then divide by 4 again to eliminate an entire row. Finally we divide by 4 one last time to get our vertical texture coordinate.

A quick example. If our current frame was 16. ey=((16/4)/4)/4 or 4/4/4 or 0.25f. One row down. If our current frame was 60. ey=((60/4)/4)/4 or 15/4/4 or 3/4 or 0.75f. The reason 15/4 isn't 3.75 is because we are working with integers up until we do the final division. With that in mind, the value of ey can only be one of 4 values... 0.0f, 0.25f, 0.50f or 0.75f. Assuming we stay inside our texture (prevent frame from going over a value of 63).

Hope that made sense... it's simple, but intimidating math.

void Explosion(int num)								// Draws An Animated Explosion For Object "num"
{
	float ex = (float)((object[num].frame/4)%4)/4.0f;			// Calculate Explosion X Frame (0.0f - 0.75f)
	float ey = (float)((object[num].frame/4)/4)/4.0f;			// Calculate Explosion Y Frame (0.0f - 0.75f)

Now that we have the texture coordinates, all that's left to do is draw our textured quad. The vertex coordinates are fixed at -1.0f and 1.0f. You will notice we subract ey from 1.0f. If we didn't, the animation would be drawn in the reverse order... The explosion would get bigger, rather than fade out. The effect won't look right!

We bind the explosion texture before we draw the textured quad. Again, the quad is drawn counter-clockwise.

	glBindTexture(GL_TEXTURE_2D, textures[5].texID);			// Select The Explosion Texture
	glBegin(GL_QUADS);							// Begin Drawing A Quad
		glTexCoord2f(ex      ,1.0f-(ey      )); glVertex3f(-1.0f,-1.0f,0.0f);	// Bottom Left
		glTexCoord2f(ex+0.25f,1.0f-(ey      )); glVertex3f( 1.0f,-1.0f,0.0f);	// Bottom Right
		glTexCoord2f(ex+0.25f,1.0f-(ey+0.25f)); glVertex3f( 1.0f, 1.0f,0.0f);	// Top Right
		glTexCoord2f(ex      ,1.0f-(ey+0.25f)); glVertex3f(-1.0f, 1.0f,0.0f);	// Top Left
	glEnd();								// Done Drawing Quad

As I mentioned above, the value of frame should not exceed 63 otherwise the animation will start over again. So we increase the value of frame and then we check to see if the value is greater than 63. If it is, we call InitObject(num) which destroys the object and gives it new values to create an entirely new object.

	object[num].frame+=1;							// Increase Current Explosion Frame
	if (object[num].frame>63)						// Have We Gone Through All 16 Frames?
	{
		InitObject(num);						// Init The Object (Assign New Values)
	}
}

This section of code draws all of the targets (objects) to the screen. We start off by resetting the modelview matrix. We then translate 10 units into the screen and set up a loop from 0 to the players current level.

void DrawTargets(void)								// Draws The Targets (Needs To Be Seperate)
{
	glLoadIdentity();							// Reset The Modelview Matrix
	glTranslatef(0.0f,0.0f,-10.0f);						// Move Into The Screen 20 Units
	for (int loop=0; loop<level; loop++)					// Loop Through 9 Objects
	{

The first line of code is the secret to picking individual objects. What it does is assigns a name (number) to each object. The first object drawn will be 0. The second object will be 1, etc... If the loop was to hit 29, the last object drawn would be given the name 29. After assigning a name to the object, we push the modelview matrix onto the stack. It's important to note the calls to glLoadName() are ignored if the program is not in selection mode.

We then move to the location on the screen where we want our object to be drawn. We use object[loop].x to position on the x-axis, object[loop].y to position on the y-axis and object[loop].distance to position the object on the z-axis (depth into the screen). We have already translated 10 units into the screen, so the actual distance at which the object will be drawn is going to be object[loop].distance-10.0f.

		glLoadName(loop);						// Assign Object A Name (ID)
		glPushMatrix();							// Push The Modelview Matrix
		glTranslatef(object[loop].x,object[loop].y,object[loop].distance);	// Position The Object (x,y)

Before we draw the object, we have to check if it's been hit or not. We do this by checking to see if object[loop].hit is TRUE. If it is, we jump to Explosion(loop) which will draw the explosion animation instead of the actual object. If the object was not hit, we spin the object on it's z-axis by object[loop].spin degrees before we call Object().

Object takes 3 parameters. The first one is the width, the second one is the height and the third one is the number of the texture to use. To get the width and height, we use the array size[object[loop].texid].w and size[object[loop].texid].h. This looks up the width and height from our predefined object size array at the beginning of this program. The reason we use object[loop].texid is because it represents the type of object we are drawing. A texid of 0 is always the blueface... a texid of 3 is always the coke can, etc.

After drawing an object, we pop the matrix resetting the view, so our next object is drawn at the proper location on the screen.

		if (object[loop].hit)						// If Object Has Been Hit
		{
			Explosion(loop);					// Draw An Explosion
		}
		else								// Otherwise
		{
			glRotatef(object[loop].spin,0.0f,0.0f,1.0f);		// Rotate The Object
			Object(size[object[loop].texid].w,size[object[loop].texid].h,object[loop].texid);	// Draw The Object
		}
		glPopMatrix();							// Pop The Modelview Matrix
	}
}

This is where the drawing occurs. We start off by clearing the screen, and resetting our modelview matrix.

void Draw(void)									// Draw Our Scene
{
	glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);			// Clear Screen And Depth Buffer
	glLoadIdentity();							// Reset The Modelview Matrix

Next we push the modelview matrix onto the stack and select the sky texture (texture 7). The sky is made up of 4 textured quads. The first 4 vertices draw the sky way in the distance from the ground straight up. The texture on this quad will roll fairly slowly. The next 4 vertices draw the sky again at the exact same location but the sky texture will roll faster. The two textures will blend together in alpha blending mode to create a neat multilayered effect.

	glPushMatrix();								// Push The Modelview Matrix
	glBindTexture(GL_TEXTURE_2D, textures[7].texID);			// Select The Sky Texture
	glBegin(GL_QUADS);							// Begin Drawing Quads
		glTexCoord2f(1.0f,roll/1.5f+1.0f); glVertex3f( 28.0f,+7.0f,-50.0f);	// Top Right
		glTexCoord2f(0.0f,roll/1.5f+1.0f); glVertex3f(-28.0f,+7.0f,-50.0f);	// Top Left
		glTexCoord2f(0.0f,roll/1.5f+0.0f); glVertex3f(-28.0f,-3.0f,-50.0f);	// Bottom Left
		glTexCoord2f(1.0f,roll/1.5f+0.0f); glVertex3f( 28.0f,-3.0f,-50.0f);	// Bottom Right

		glTexCoord2f(1.5f,roll+1.0f); glVertex3f( 28.0f,+7.0f,-50.0f);		// Top Right
		glTexCoord2f(0.5f,roll+1.0f); glVertex3f(-28.0f,+7.0f,-50.0f);		// Top Left
		glTexCoord2f(0.5f,roll+0.0f); glVertex3f(-28.0f,-3.0f,-50.0f);		// Bottom Left
		glTexCoord2f(1.5f,roll+0.0f); glVertex3f( 28.0f,-3.0f,-50.0f);		// Bottom Right

To give the illusion that the sky is coming towards the viewer, we draw two more quads, but this time we draw them from way in the distance coming toward the viewer. The first 4 verticies draw slow rolling clouds and the remaining 4 draw faster moving clouds. The two layers will blend together in alpha blending mode to create a multilayered effect. The second layer of clouds is offset by 0.5f so that the two textures don't line up. Same with the two layers of clouds above. The second layer is offset by 0.5f.

The final effect of all 4 quads is a sky that appears to move up way out in the distance and then toward the viewer up high. I could have used a textured half sphere for the sky, but I was too lazy, and the effect is still pretty good as is.

		glTexCoord2f(1.0f,roll/1.5f+1.0f); glVertex3f( 28.0f,+7.0f,0.0f);	// Top Right
		glTexCoord2f(0.0f,roll/1.5f+1.0f); glVertex3f(-28.0f,+7.0f,0.0f);	// Top Left
		glTexCoord2f(0.0f,roll/1.5f+0.0f); glVertex3f(-28.0f,+7.0f,-50.0f);	// Bottom Left
		glTexCoord2f(1.0f,roll/1.5f+0.0f); glVertex3f( 28.0f,+7.0f,-50.0f);	// Bottom Right

		glTexCoord2f(1.5f,roll+1.0f); glVertex3f( 28.0f,+7.0f,0.0f);		// Top Right
		glTexCoord2f(0.5f,roll+1.0f); glVertex3f(-28.0f,+7.0f,0.0f);		// Top Left
		glTexCoord2f(0.5f,roll+0.0f); glVertex3f(-28.0f,+7.0f,-50.0f);		// Bottom Left
		glTexCoord2f(1.5f,roll+0.0f); glVertex3f( 28.0f,+7.0f,-50.0f);		// Bottom Right
	glEnd();								// Done Drawing Quads

With the sky out of the way, it's time to draw the ground. We draw the ground starting where the sky texture is the lowest coming towards the viewer. The ground texture rolls at the same speed as the fast moving clouds.

The texture is repeated 7 times from left to right and 4 times from back to front to add a little more detail and to prevent the texture from getting all blocky looking. This is done by increasing the texture coordinates from 0.0f - 1.0f to 0.0f - 7.0f (left to right) and 0.0f - 4.0f (up and down).

	glBindTexture(GL_TEXTURE_2D, textures[6].texID);			// Select The Ground Texture
	glBegin(GL_QUADS);							// Draw A Quad
		glTexCoord2f(7.0f,4.0f-roll); glVertex3f( 27.0f,-3.0f,-50.0f);	// Top Right
		glTexCoord2f(0.0f,4.0f-roll); glVertex3f(-27.0f,-3.0f,-50.0f);	// Top Left
		glTexCoord2f(0.0f,0.0f-roll); glVertex3f(-27.0f,-3.0f,0.0f);	// Bottom Left
		glTexCoord2f(7.0f,0.0f-roll); glVertex3f( 27.0f,-3.0f,0.0f);	// Bottom Right
	glEnd();								// Done Drawing Quad

After drawing the sky and the ground, we jump to the section of code that draws all of our targets (objects) called none other than DrawTargets().

After drawing out targets, we pop the modelview matrix off the stack (restoring it to it's previous state).

	DrawTargets();								// Draw Our Targets
	glPopMatrix();								// Pop The Modelview Matrix

The code below draws the crosshair. We start off by grabbing our current window dimensions. We do this in case the window was resized in windowed mode. GetClientRect grabs the dimensions and stores them in window. We then select our projection matrix and push it onto the stack. We reset the view with glLoadIdentity() and then set the screen up in ortho mode instead of perspective. The window will go from 0 to window.right from left to right, and from 0 to window.bottom from the bottom to the top of the screen.

The third parameter of glOrtho() is supposed to be the bottom value, instead I swapped the bottom and top values. I did this so that the crosshair would be rendered in a counter clockwise direction. With 0 at the top and window.bottom at the bottom, the winding would go the opposite direction and the crosshair and text would not appear.

After setting up the ortho view, we select the modelview matrix, and position the crosshair. Because the screen is upside down, we have to invert the mouse as well. Otherwise our crosshair would move down if we moved the mouse up and up if we moved the mouse down. To do this we subtract the current mouse_y value from the bottom of the window (window.bottom).

After translating to the current mouse position, we draw the crosshair. We do this by calling Object(). Instead of units, we are going to specify the width and height in pixels. The crosshair will be 16x16 pixels wide and tall and the texture used to draw the object is texture 8 (the crosshair texture).

I decided to use a custom cursor for two reasons... first and most important, it looks cool, and it can be modified using any art program that supports the alpha channel. Secondly, some video cards do not display a cursor in fullscreen mode. Playing the game without a cursor in fullscreen mode is not easy :)

	// Crosshair (In Ortho View)
	RECT window;								// Storage For Window Dimensions
	GetClientRect (g_window->hWnd,&window);					// Get Window Dimensions
	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glPushMatrix();								// Store The Projection Matrix
	glLoadIdentity();							// Reset The Projection Matrix
	glOrtho(0,window.right,0,window.bottom,-1,1);				// Set Up An Ortho Screen
	glMatrixMode(GL_MODELVIEW);						// Select The Modelview Matrix
	glTranslated(mouse_x,window.bottom-mouse_y,0.0f);			// Move To The Current Mouse Position
	Object(16,16,8);							// Draw The Crosshair

This section of code put the title at the top of the screen, and displays the level and score in the bottom left and right corners of the screen. The reason I put this code here is because it's easier to position the text accurately in ortho mode.

	// Game Stats / Title
	glPrint(240,450,"NeHe Productions");					// Print Title
	glPrint(10,10,"Level: %i",level);					// Print Level
	glPrint(250,10,"Score: %i",score);					// Print Score

This section checks to see if the player has missed more than 10 objects. If so, we set the number of misses (miss) to 9 and we set game to TRUE. Setting the game to TRUE means the game is over!

	if (miss>9)								// Have We Missed 10 Objects?
	{
		miss=9;								// Limit Misses To 10
		game=TRUE;							// Game Over TRUE
	}

In the code below, we check to see if game is TRUE. If game is TRUE, we print the GAME OVER messages. If game is false, we print the players morale (out of 10). The morale is calculated by subtracting the players misses (miss) from 10. The more the player misses, the lower his morale.

	if (game)								// Is Game Over?
		glPrint(490,10,"GAME OVER");					// Game Over Message
	else
		glPrint(490,10,"Morale: %i/10",10-miss);			// Print Morale #/10

The last thing we do is select the projection matrix, restore (pop) our matrix back to it's previous state, set the matrix mode to modelview and flush the buffer to make sure all objects have been rendered.

	glMatrixMode(GL_PROJECTION);						// Select The Projection Matrix
	glPopMatrix();								// Restore The Old Projection Matrix
	glMatrixMode(GL_MODELVIEW);						// Select The Modelview Matrix

	glFlush();								// Flush The GL Rendering Pipeline
}

This tutorial is the result of many late nights, and many many hours of coding & writing HTML. By the end of this tutorial you should have a good understanding of how picking, sorting, alpha blending and alpha testing works. Picking allows you to create interactive point and click software. Everything from games, to fancy GUI's. The best feature of picking is that you don't have to keep track of where your objects are. You assign a name and check for hits. It's that easy! With alpha blending and alpha testing you can make your objects completely solid, or full of holes. The results are great, and you don't have to worry about objects showing through your textures, unless you want them to! As always, I hope you have enjoyed this tutorial, and hope to see some cool games, or projects based on code from this tutorial. If you have any questions or you find mistakes in the tutorial please let me know... I'm only human :)

I could have spent alot more time adding things like physics, more graphics, more sound, etc. This is just a tutorial though! I didn't write it to impress you with bells and whistles. I wrote it to teach you OpenGL with as little confusion as possible. I hope to see some cool modifications to the code. If you add something cool the the tutorial send me the demo. If it's a cool modification I'll post it to the downloads page. If I get enough modifications I may set up a page dedicated to modified versions of this tutorial! I am here to give you a starting point. The rest is up to you :)

NOTE: It is VERY important that in the call to glTexImage2D you set both the format and the internal format to GL_RGBA. Otherwise alpha blending will not work!

Jeff Molofee (NeHe)

* DOWNLOAD Visual C++ Code For This Lesson.

* DOWNLOAD Borland C++ Builder 6 Code For This Lesson. ( Conversion by Christian Kindahl )
* DOWNLOAD Code Warrior 5.3 Code For This Lesson. ( Conversion by Scott Lupton )
* DOWNLOAD Delphi Code For This Lesson. ( Conversion by Michal Tucek )
* DOWNLOAD Dev C++ Code For This Lesson. ( Conversion by Dan )
* DOWNLOAD Euphoria Code For This Lesson. ( Conversion by Evan Marshall )
* DOWNLOAD Linux/SDL Code For This Lesson. ( Conversion by Edgar Costanzo )
* DOWNLOAD LWJGL Code For This Lesson. ( Conversion by Mark Bernard )
* DOWNLOAD Mac OS X/Cocoa Code For This Lesson. ( Conversion by Bryan Blackburn )
* DOWNLOAD Visual Studio .NET Code For This Lesson. ( Conversion by Grant James )

 

< Lesson 31Lesson 33 >