Quake DeveLS - Wolf3d Style Engine

Author: Cryect
Difficulty: Medium

Well here is a 3d Engine similar to the wolf3d engine except for one main thing, which is it doesn't use raytracing which is fine. I make use of glut mainly so the program was faster to write. This tutorial is meant for use with Microsoft Visual Studio, but it can be easily applied to others(or at least I'm assuming). Oh and before you can take advantage of this tutorial you will need to download the glut libraries off the internet. So go get them here if you don't already have them. Next download these files from here which contains some code you need for the program to compile and work. Alright first thing after all that is to create a file and call it whatever you want. That was simple enough of course I hope Alright we are going to start off with the headers we are going to use. So insert this code in

#include 
#include 
#include 
#include 
#include 
#include "texture.h"

These will give us all the commands we are going to be using in the program. Now real fast tell the program to compile. It will ask to create a default program say yes after that cancel the build now add to the program the files 'texture.c' and 'bitmap.c'. Now goto the project settings and click on the link tab and add to the libraries to be used opengl32.lib, glu32.lib, and glut32.lib. Okay now that we have all that stuff out of the way we are going to create the map. The map will be 8x8 grid of blocks in an array. If the number '0' is in a place then the spot will be filled in. If the number is '1' its open there and if its '2', its just '1' except with a simple blood pool on the ground. So here insert this into your code.

//The Map
int Map[8][8] = {
	{1,1,0,0,0,0,1,1},
	{1,2,1,1,1,1,2,1},
	{0,1,0,1,1,0,1,0},
	{0,1,1,1,1,1,1,0},
	{0,1,1,1,1,1,1,0},
	{0,1,0,1,1,0,1,0},
	{1,2,1,1,1,1,2,1},
	{1,1,0,0,0,0,1,1}
};

Now we are going to create a structure that can hold info for walls, floors, ceilings, and those pools of blood on the floor. It will need to consist of two vectors. For the walls they stand for the two end points and we need to make sure that we keep the vectors in the right order to clipping of the counter clockwise walls. For the floors, ceilings, and blood pools the vectors each stand for a corner. So here is how we are setting up this structure so insert it in also

//Structure For Walls, Floors and anything else
struct Wall{
	float V1[2];
	float V2[2];
};

We are also going to set up some limits to the amount of walls and everything. Its not really needed for this map but for somethings you do its nice to do(though thats an advantage to linked list). Also we need to declare the walls and the rest. So here is the code for the limits and the rest.

#define MaxWalls 64
#define MaxFloors 64
#define MaxCeil 64
#define MaxBlood 16

int WallNum;
int FloorNum;
int CeilNum;
int BloodNum;

Wall WallList[MaxWalls];
Wall FloorList[MaxFloors];
Wall CeilList[MaxCeil];
Wall BloodList[MaxBlood];

Now we need some variables for various constants. Some of those variables are for holding texture numbers. One just gives a number for the constant we are using for Pi and the rest hold the numbers for our position and angles.

//Texture Numbers
GLuint WallTex,Concrete,BloodPool;
 
// Define a constant for the value of PI
#define GL_PI 3.1415f

// Rotation amounts
static GLfloat yRot = 0.0f;
static GLfloat xPos = 256.0f;
static GLfloat yPos = 256.0f;

Well now we are going to create some functions that allow us to add walls, floors, ceilings and the blood pools. I won't explain them much since you can look over it and figure it out for yourself just as easily as I can tell you.

void AddWall(int x1, int y1,int x2,int y2)
{
	if(WallNum

Alright now we are about to get into some of the more complex stuff. We are going to setup the functions that add the floor and ceilings to the approiate arrays. Its quite simple all we have to do is search through the map array and for any spot that doesn't have a '0' must have a floor and ceiling. Also if it doesn't equal '0' we should check to see if the number equals '2' and if it does then lets add a blood pool also. So here is the code for that.

void SetupFloorAndCeil(void)
{
	for(short x=0;x<8;x++)
		for(short y=0;y<8;y++)
			if(Map[x][y]!=0)
			{
				AddFloorCeil(y,x,y+1,x+1);
				if(Map[x][y]==2)
					AddBlood(y,x,y+1,x+1);
			}

}

Alright that was pretty simple wasn't it! =) Okay the code for the wall adding code is a little more complex. We need to when we are setting up the walls there is a empty space next to it but also a filled spot. We need to set it up to go along the edges in a special way also. Here is the code for it see if you can look over it and figure it out. One of the best ways to understand code is not for someone to tell you how and what it does but for you to manually go through it once. Try it!

void SetupWalls(void)
{
	for(int n=0;n<8;n++)
		if(Map[0][n]!=0)
			AddWall(n,0,n+1,0);

	for(n=0;n<8;n++)
	{
		for(int y=1;y<8;y++)
			if((Map[y][n]==0)&&(Map[y-1][n]!=0))
				AddWall(n+1,y,n,y);
	}

	for(n=0;n<8;n++)
		if(Map[7][n]!=0)
			AddWall(n+1,8,n,8);
	
	for(n=0;n<8;n++)
	{
		for(int y=0;y<7;y++)
			if((Map[y][n]==0)&&(Map[y+1][n]!=0))
				AddWall(n,y+1,n+1,y+1);
	}

	for(n=0;n<8;n++)
		if(Map[n][0]!=0)
			AddWall(0,n+1,0,n);

	for(n=0;n<8;n++)
	{
		for(int y=1;y<8;y++)
			if((Map[n][y]==0)&&(Map[n][y-1]!=0))
				AddWall(y,n,y,n+1);
	}

	for(n=0;n<8;n++)
		if(Map[n][7]!=0)
			AddWall(8,n,8,n+1);
	
	for(n=0;n<8;n++)
	{
		for(int y=0;y<7;y++)
			if((Map[n][y]==0)&&(Map[n][y+1]!=0))
				AddWall(y+1,n+1,y+1,n);
	}

}
Okay hopefully you understand that all. Was a little confusing for me myself and it took a while to get it just right since sometimes I was facing walls the wrong direction so unless you were inside of a section you wouldn't see it. Now drawing these walls is a little easier since all we have to do is go through the array of walls and draw them all. Here is the code for that and I will explain all the opengl functions then that are used there.

void DrawWalls(void)
{
	//Here is where it goes through drawing all the walls
	glBegin(GL_TRIANGLES);
	for(int n=0;n
Alright see the command glBegin, where we pass the argument GL_TRIANGLES, that tells opengl we are about to draw a bunch of triangles. Every time we have given it 3 vertices it will draw a triangle. To give it a vertex we use the command glVertex3f which accepts 3 floats with the first one signifying the x-axis, then the y-axis and finally the z-axis. But before that we call the command glTexCoord2f which tells OpenGL the texture coordinates for the next point. Normally you have it between 0 and 1 but if you have it out of the range of that numbers it will repeat the texture or clamp the texture it based on what the texture is set to. The command glEnd tells OpenGl we are done with drawing our triangles. You need to make sure do this between changing textures otherwise you won't change the texture and not know why. Okay here is the code for drawing the rest of the things its pretty much the same except for they are all horizontal instead of vertical.

void DrawFloors(void)
{
	//Here is where it goes through drawing all the floors
	glBegin(GL_TRIANGLES);
	for(int n=0;n

As you can see that was pretty much the same as the rest. Now for the Draw Scene routine that puts all of the drawing stuff all together. Here is the code and I will explain how each part works as we go along in the comments amongst the code.

// Called to draw scene
void RenderScene(void)
{
	// Clear the window with current clearing color and clears our depth buffer
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);
	
	// Save the current matrix state(otherwards our position and how we are looking)
	glPushMatrix();

	//Rotate according to our angle
	glRotatef(yRot, 0.0f, 1.0f, 0.0);
	//Move us to the correct corresponding spot
	glTranslatef(-xPos,0.0f,-yPos);

	//Enable the use of 2d Textures
	glEnable(GL_TEXTURE_2D);
	//Tell opengl to use the wall texture
	glBindTexture(GL_TEXTURE_2D,WallTex);
	//Draw the Walls now
	DrawWalls();
	//Now tell OpenGL we want to use the concrete texture which is used for the floor and ceiling
	glBindTexture(GL_TEXTURE_2D,Concrete);
	//Draw the floor and the ceiling
	DrawFloors();
	DrawCeils();
	
	//Set the texture to the Blood Pool
	glBindTexture(GL_TEXTURE_2D,BloodPool);
	//Enable Blending so we are basiclly multitexturing(except I'm not using the stuff so it really is)
	glEnable(GL_BLEND);
	//This multiplies the source color by the destination color
	glBlendFunc(GL_DST_COLOR,GL_ZERO);
	//Draw The Blood Pools
	DrawBloodPools();
	//Disable Blending
	glDisable(GL_BLEND);

	// Restore transformations
	glPopMatrix();

	// Flush drawing commands
	glFlush();
	//Swap to the other Buffer since we are double buffering
	glutSwapBuffers();
}

Okay that wasn't too bad was it. The reason we have the graphic parts in seperate functions is to make it easier to find bugs and to help us to determine what parts need to speed up when do profiling. For this program you don't have to worry about bugs and there isn't too many things you can do speed it up but there is some which are pretty easy to implement and I will explain a little at the end. But now on to setting OpenGL. It needs to get information on we are handling things. Examples of this are the color we want it to clear the background to and how the depth buffer is handled. I have some fog code within this code but its commented due to it not working correctly with the microsoft drivers but if you are using a non-microsoft video drivers it should work fine if you uncomment it(oh and if you use mesa drivers the bloodpools will appear as a solid black for some reason).

void SetupRC()
{
	//Fog
	//GLfloat fog_color[4]={0.0f,0.0f,0.0f,1.0f};
	// Black background
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f );
	//Set Drawing Color to White
	glColor4f(1.0f,1.0f,1.0f,1.0f);
	//Cull Polygons that aren't forward(counterclockwise)
	glFrontFace(GL_CW);
	//Enable Culling of backward facing polys
	glEnable(GL_CULL_FACE);

	//Enable Depth Testing
	glEnable(GL_DEPTH_TEST);
	//Set it to draw if Less Than Equal
	glDepthFunc(GL_LEQUAL);
	//Load Texture Maps
	WallTex=TextureLoad("brickwall.bmp",0,GL_LINEAR_MIPMAP_LINEAR,GL_LINEAR,GL_REPEAT);
	Concrete=TextureLoad("concrete.bmp",0,GL_LINEAR_MIPMAP_LINEAR,GL_LINEAR,GL_REPEAT);
	BloodPool=TextureLoad("bloodpool.bmp",0,GL_LINEAR_MIPMAP_LINEAR,GL_LINEAR,GL_REPEAT);

//Fog Setup
/*	glEnable(GL_FOG);
	glFogi(GL_FOG_MODE,GL_EXP);
	glFogf(GL_FOG_DENSITY,0.0025);
	glFogfv(GL_FOG_COLOR,fog_color);*/
}

Okay so that is function we call when we need to setup OpenGL then. Okay if we have a map maybe there should be a way to move around with. Well with glut there is a special function that can be specified for handling keys and here is how we are setting up the keys
Up Arrow - Move Forward
Down Arrow - Move Backwards
Left Arrow - Turn Left
Right Arrow - Turn Right
Now here is the code to handle what we want.

void SpecialKeys(int key, int x, int y)
	{
	//Turn Left
	if(key == GLUT_KEY_LEFT)
		yRot -= 5.0f;
	//Turn Right
	if(key == GLUT_KEY_RIGHT)
		yRot += 5.0f;
	//Go Forward
	if(key == GLUT_KEY_UP)
	{
		yPos+=7.5*sin((yRot-90)/180.0f*GL_PI);
		xPos+=7.5*cos((yRot-90)/180.0f*GL_PI);
	}
	
	//Go Backwards
	if(key == GLUT_KEY_DOWN)
	{
		yPos-=5.5*sin((yRot-90)/180.0f*GL_PI);
		xPos-=5.5*cos((yRot-90)/180.0f*GL_PI);
	}

	//Check the angle
	if(yRot > 356.0f)
		yRot = 0.0f;
	else if(yRot < -1.0f)
		yRot = 355.0f;

	// Refresh the Window
	glutPostRedisplay();
}

Well as you can see that was real simple. Well we are almost done now and are in the last stretch. But first plug this code in that handles the resizing.

void ChangeSize(int w, int h)
{
	GLfloat nRange = 100.0f;
	
	// Prevent a divide by zero
	if(h == 0)
		h = 1;

	// Set Viewport to window dimensions
	glViewport(0, 0, w, h);

	// Reset projection matrix stack
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();

	gluPerspective(90,float(w)/float(h),1.0,1024.0);
	// Reset Model view matrix stack
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

Hard to explain that code really since there isn't much to say. Well here is the last bit of code. The main function that is.

int main(int argc, char* argv[])
{
	//Setup the Walls
	SetupWalls();
	//Setup the floor, ceiling, and blood pools
	SetupFloorAndCeil();
	//Tell glut to initiate with the arguments
	glutInit(&argc, argv);
	//Tell glut we need a double buffer, to be able to have red, green and blue and also we need a depth buffer
	glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
	//Tell it to create the window and name it
	glutCreateWindow("QDevels Wolf3d Style Engine");
	//Specify the function for whenever the window resizes
	glutReshapeFunc(ChangeSize);
	//Here is where tell the function for the key
	glutSpecialFunc(SpecialKeys);
	//And the must important function GLUT needs to know about and thats the rendering function
	glutDisplayFunc(RenderScene);
	//Setup OpenGl now with the rendering context
	SetupRC();
	//Initialize glut's main loop
	glutMainLoop();
	//Return back to whatever we were doing before hand
	return 0;
}

Okay compile that now and try it out. That should work pretty well. Ways to make this run faster and addon to the program by yourself, figure out how to setup the walls to be combined together and also the same for the floor. Thats not too hard. Also there are texture objects but I will tell you about them later. Don't worry another tutorial is coming soon(now the question is how soon though =)

Cryect

This site, and all content and graphics displayed on it,
are ©opyrighted to the Quake DeveLS team. All rights received.
Got a suggestion? Comment? Question? Hate mail? Send it to us!
Oh yeah, this site is best viewed in 16 Bit or higher, with the resolution on 800*600.
Thanks to Planet Quake for there great help and support with hosting.
Best viewed with Netscape 4 or IE 3