Archive for category C

C Lesson 39: Wolf5K: Making it Portable

Source Code
coresdl.cpp
Lesson39.cpp

Header Files
cLinkedList.h
cGeneral.h
cTexture2.h
wolf5k6.h

Libraries to Link
sdl.lib

the graphics files (492KB)
SDL.dll (232KB)
SDL.DLL and ReadMe (100KB)
SDL SDK for Visual Studio 6 and Up (547KB)

In order to compile SDL apps you will need the SDK. In order to run the SDL apps you will need to put the SDL.dll file in the same directory as the EXE. You will also need the graphics pack. The screenshot is showing off a 600×600 pixel brick texture and a 1024×1024 psychodelic texture. The raycaster does a very good job of making use of high resolution wall textures. The original Wolfenstein 3D was restricted to textures of size 64×64.

The first question I had was “which core is the fastest?”

1
2
3
  SDL windowed: 21.26406926 fps
 DX fullscreen: 23.03658537 fps
SDL fullscreen: 23.85221675 fps

This test was done on a 1.2Ghz Duron with a GeForce FX 5500 graphics card. The resolution of 320×240 was used. As you can see, the SDL core is slightly faster than the DirectX core. There may be ways to speed both up further so it’s not really possible to say which is definitly faster. This is the average FPS after playing the game for a few minutes.

You may have noticed that we’re up to version 6 of the wolf5k header. This is because a lot of the warnings caused by converting from one variable type to another have been corrected. Other small changes like moving variable declarations to the beginning of the methods have also been done. This version is functially the same as 5 but a bit cleaner.

The big changes come in the lesson file. SDL doesn’t have only 256 possible keys like DirectX. The SDL allows for more. Well over 256 are defined. The SDL key codes are also not the same as the Windows key codes. At the start of the Lesson file then when the SDL library is being used the following bit of code is required:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
	//MUST INCLUDE THESE LINES WHEN USING SDL
	#include <sdl.h>
	#undef VK_UP
	#undef VK_DOWN
	#undef VK_LEFT
	#undef VK_RIGHT
	#undef VK_SPACE
 
	#define VK_UP SDLK_UP
	#define VK_DOWN SDLK_DOWN
	#define VK_LEFT SDLK_LEFT
	#define VK_RIGHT SDLK_RIGHT
	#define VK_SPACE SDLK_SPACE
	//END OF SDL

We undefine the VK codes and redefine them to be the SDLK codes. This way we don’t have to hunt around the code and find all cases where we’re using VK instead of SDLK. It makes it a lot easier to switch between cores.

Also changed is the declaration of keys.

1
	bool keys[1024];	//<----- important.  SDL uses more than 256 keycodes

We now allow for up to 1024 keys to be defined. The SDL core is expecting this number while the DX core is expecting an array size of 256. When switching between cores, it will be necessary to change this as well.

I’m not going to dive into how coresdl.cpp works. That is an exercise for the interested. This tutorial simply covers how to use it. The goal of these tutorials is, as always, to show how to work with low level graphics. Not how to use various graphics APIs. All future graphics tutorials will use coresdl.cpp instead of coredx.

No Comments

C Lesson 38: Wolf5K: Making it Better Part 2

Source Code
coredx.cpp
Lesson38.cpp

Header Files
cLinkedList.h
cGeneral.h
cTexture2.h
wolf5k5.h

Libraries to Link
dxguid.lib
ddraw.lib

You also may want to download the graphics files (492KB). Otherwise you will need to supply the sprites 0 through 11. You just need to match the sprite type. The two wall tiles you want must be sprite 0 and 1 etc. One of the annoying limitations of the original Wolfenstein 3D was that not only were you limited on the size of the sprites (64×64 pixels) but you were also limited on the number of pixels each sprite could have. You could only have as many pixels as the original graphic you were replacing or less. There is no such limitation with this code. Sprites can be any size you want and they don’t even need to be consistant in size. All that matters is that you match the mask with the sprite for those sprites that need a mask.


early in-game shot with new sprites

The first thing that needed to be done was to add the texture class to the project so we could load bitmaps. We then add cTexture type variables to the Wolf5K header to hold each of the textures/sprites.

1
2
3
4
5
	public:
		cColor * p; //video memory
		cTexture texture[12];
		cTexture font[2];
		cTexture toolbar;

Notice we’ve also got two textures for the font and a toolbar texture. You can see from the above screen shot that the walls cover the original blue bar and the numbers are hard to read. We need to fix that. Next up we do the actually loading in the Init() method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	texture[0].LoadTexture("texture00.bmp");
	texture[1].LoadTexture("texture01.bmp");
	texture[2].LoadTexture("texture02.bmp");
	texture[3].LoadTexture("texture03.bmp");
	texture[4].LoadTexture("texture04.bmp");
	texture[5].LoadTexture("texture05.bmp");
	texture[6].LoadTexture("texture06.bmp");
	texture[7].LoadTexture("texture07.bmp");
	texture[8].LoadTexture("texture08.bmp");
	texture[9].LoadTexture("texture09.bmp");
	texture[10].LoadTexture("texture10.bmp");
	texture[11].LoadTexture("texture11.bmp");
 
	font[0].LoadTexture("font.bmp");
	font[1].LoadTexture("font.mask.bmp");
 
	toolbar.LoadTexture("toolbar.bmp");

Later on you’ll learn how to use a configuration file so none of this needs to be hard coded. But for now it’s good enough to just explicity load everything in the code. One of the immediate problems you may have noticed is that we have three texture variables but the DrawSprite routine takes a number. We could make three DrawSprite routines or we could simply make the existing one better.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	int DrawSprite(<b>cTexture &tile,cTexture &mask</b>,double sy,
	double sx,double dy,double dx,double f,int r)
	{
		...
					//actual color
					clr=tile.GetColorAtPer(pX/wd,pY/ht);
 
					//value of the mask
					msk=mask.GetColorAtPer(pX/wd,pY/ht);
 
					if(msk.black())
					{
						PlotPixel(k,j,clr);
						g=1;
					}
				}
			}
		}
		return g;
	}

Now, instead of a sprite number and mask number we pass by reference two textures. If we didn’t use the & symbol, every time you tried to render a sprite the entire contents of the texture you were passing in would have to be copied and then deleted because it would then be a pass by value function.

But what if you don’t want to use a mask for a sprite? A second method has been added for that purpose.

1
2
3
4
5
6
7
8
	void DrawSprite(cTexture &tile,double sy,double sx,double dy,double dx,double f,int r)
	{
		...
					PlotPixel(k,j,tile.GetColorAtPer(pX/wd,pY/ht));
				}
			}
		}
	}

It’s exactly the same as the previous function except it doesn’t take a mask and renders every pixel of the sprite. One of the modifications that has been made to the cTexture class is the GetColorAtPer() method. In the original class the pixel was calculated outside the class. With this function, you pass in the percentage and the class does the calculation.

1
2
3
4
	cColor GetColorAtPer(float x, float y)
	{
		return GetColorAt(x*width,y*height);
	}

It would be possible to use the same method name for both GetColorAt methods since the parameter types are different but it would be really confusing and if you weren’t careful you could end up calling the wrong function accidentaly by not casting the parameters correctly. Using a different name ensures that the correct method is called. If x and y are out of range the GetColorAt() method will take care of it so we don’t need any checks in this method.

We’ve also made a number of changes to the cColor class in the cGeneral header. Namely, we’ve added a number of methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	cColor()
	{
		r=g=b=0;
	}
 
	cColor(unsigned char pr, unsigned char pg, unsigned char pb)
	{
		r=pr;
		g=pg;
		b=pb;
	}
 
	bool black()
	{
		return (r==0 && g==0 && b==0);
	}
 
	void scale(double s)
	{
		r*=s;
		g*=s;
		b*=s;
	}

Now we have a default constructor, a parameterized constructor, a black() method and a scale() method. You may have noticed the if(msk.black()) statement in the DrawSprite routine. The black() method tells us if the pixel is exactly 0 for each of the components. That’s how we determine the mask. The scale() method is used to modify the value of the color to make it darker or lighter. You can think of color as a vector with three components.

1
2
3
4
5
6
7
	//start at the top of the scanline and work down
	for(y=k;y<l;y++)
	{
		cColor b2=texture[pat].GetColorAtPer(u/64.0,(double)(y-b3)/ht);
		b2.scale(1.0/((f+128.0)/128.0));//lighting
		PlotPixel(v,y,b2);
	}

You may recall the mess of code needed to see if a pixel was drawn or not for the original Wolf5K in order to fake lighting. Now that we’re working in 24 bit color we can do real lighting. For this lesson we’re just going to stick with simple lighting. You can see we scale the color of the wall by the distance from the player. You can see the effect better in the following screenshot.


640×480 render

The reason for adding 128 and then dividing the sum by 128 is to ensure that the color is scaled by a value less than 1. If you scale a color by more than 1 it’s possible to run into problems. 128 puts the light source sufficiently behind the player so that the color isn’t multiplied by a value greater than 1 where the player can see it. If we were to take out the offset you would see false colors on walls close to the player.

1
	cColor b2=texture[pat].GetColorAtPer(u/64.0,(double)(y-b3)/ht);

We’ve also simplified the math a bit. One of the problems with the ray caster is that you must hard code how wide a wall tile is. In this case, they’re 64 pixels. This makes the use of larger textures pointless since the resolution isn’t really there. A texture that is 256×256 would probably supply the most amount of detail that you can get out of this code as it is. But for now, we don’t care. The idea is to use true color textures. We’ll worry about getting higher detail later.

In the original code a mod was used to wrap the texture at 64 pixels. This could be done because we were using integers. Well, we don’t want to be limited to 64 pixel textures because they’ll get too blocky too soon. So we’re working with doubles now and you can’t mod a double.

1
2
3
4
5
6
7
	//from the original raycast method
	s=a2;
	t=b;
	a5=abs(((double)playerX-a2)/c);
	uv=<b>(int)b%64;</b>
	if(c<0)
		uv=64-uv;

There is actually a pretty easy way to “mod” a double.

1
2
3
4
5
6
7
	s=a2;
	t=b;
	a5=abs(((double)playerX-a2)/c);
	uv = b - ((int)b/64)*64.0;
 
	if(c<0)
		uv=64.0-uv;

b is a value that tells us how many pixels out we are. Every 64 pixels we need to loop the texture coordinates. But uv is now a double so we can get fractional pixels to increase detail. So what we do is divide b by 64 and take the whole value. This tells us how many blocks of 64 there are. We then subtract that whole number times 64 which leaves us with a value from 0 to 64 which is exactly the same functionality as we got with the original mod but it lets us keep the fractional part of a pixel. This is why we can use wall textures greater than 64×64 pixels and see the detail but b isn’t all that accurate so we are still limited a bit. In order to increase texture detail further we would need to increase the accuracy of b. To increase the accuracy of b we need to increase the resolution since we’re limited by the number of scan lines we’re drawing. You can see by the large render how detailed the texture for the health kit is.

Next up we need to modify how numbers are rendered. Those little tiny numbers just aren’t going to cut it. The first thing we need to do is define our font size.

1
2
3
4
5
	//added to the private members of the wolf5k class
	float fontsize;
 
	//added to the init
	fontsize = 24.0f;

24 pixels is good since we want to have a 32 pixel toolbar and the numbers need to fit in it. Now that the size is settled it’s time to change the DrawNumber() method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	void DrawNumber(int a, int m, int k) //A6()
	{
		//if the number is less than 1 then just draw 0
		if(a<1)
			DrawDigit(0,m,k);
		else
		{
			int t,j;
			for(int i=(int)(log((double)a)/log(10.0));i>=0;i--,<b>m+=fontsize</b>)
			{
				t=(int)pow(10,i);
				j=(int)((double)a/(double)t);
				DrawDigit(j,m,k);
				a-=j*t;
			}
		}
	}

In the previous code we had been adding 4 since that was the font width. We want this function to be a little more dynamic than that. We’ve also removed the parameter r since we aren’t inverting colors any more.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	//this function draws individual digits
	void DrawDigit(int a,int m,int pk) //A7()
	{
		cColor d;
		float x,y;
		float j,k;
		for(j=0.0f;j<fontsize;j++)
		for(k=0.0f;k<fontsize;k++)
		{
			x = j/fontsize*50.0f+a*50.0f;
			y = k/fontsize*79.0f;
			d=font[1].GetColorAt((int)x,(int)y);
			if(d.black())
				PlotPixel(m+j,pk+k,font[0].GetColorAt((int)x,(int)y));
		}
	}

One of the hardcoded parts of the font is it’s actual width in the bitmap file. In our case, each number is 50 pixels wide. We then do the scaling math and check the mask and then plot the pixel if it’s supposed to be. Curior New is your friend for this. Fixed width fonts such as Curior New make everything a lot easier. You don’t have to store the size of each letter. We’ll cover how to use Windows to auto generate a font texture and generate the coordinates of each charater in a later tutorial. We don’t want to be limited to fixed width forever.

And next we change the RenderStatus() method to display the toolbar.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	void RenderStatus()
	{
		//this is our status information
		<b>DrawSprite(toolbar,H-24,0,H,W,0,0);</b>
		DrawNumber(playerPoints,2,H-1-fontsize); //points
		DrawNumber(numMonsters-numKilled,16*7,H-1-fontsize); //number of enemies left
		if(!playerDead)//if we're not dead then draw the gun
		{
			hasShot--;
			DrawSprite(texture[6],texture[7],H-128-24,
				W/2+W/3-128,H-24,W/2+W/3,0,0);
			if(hasShot>0)
				DrawSprite(texture[10],texture[11],H-128-24,
					W/2+W/3-128,H-24,W/2+W/3,0,0);
		}
		for(int i=0;i<playerHealth;i++)//draw our health bar
			PlotPixel(W-2-i,H+3-16,black);
	}

The toolbar uses the maskless DrawSprite() method so that everything underneath it is covered. We then draw our status information on top of it. One of the important modifications is the rendering of the gun. In the original Wolfenstien 3D all the weapons pointed directly in front of you. This made it easier to see where you were aiming. In later games the gun shifted to the side and pointed at an angle which is more realistic as most people don’t hold a gun directly in front of them to shoot. We use a bit of math to position the gun so that using a higher resolution doesn’t throw it off. The net result is that the gun does actually point at enemies directly in front of you but is angled to look a little better.

And finally, we change the BlankScreen buffer so that we have a floor and ceiling like in Wolfenstein 3D. In Wolf3D the ceiling and floor were a solid color. Since we’re using 24 bit color we can shade it a bit so it looks better.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	//from the Init() method
	cColor temp;
	int x,y;
	for(x=0;x<W;x++)
		for(y=0;y<H/2;y++)
		{
			temp = cColor(128,128,128);
			temp.scale(1.0/((y+128.0)/128.0));
			BlankScreen[x+y*W]=temp;
		}
	for(x=0;x<W;x++)
		for(y=H/2;y<H;y++)
		{
			temp = cColor(172,102,12);
			temp.scale(1.0/((H-y+128.0)/128.0));
			BlankScreen[x+y*W]=temp;
		}

First we draw the grey ceiling and darken it as it goes down. Then we draw the brown floor and lighten it as we go down. Putting everything together we now have the following:

final in-game shot with new sprites

We’ve gone from a simplistic 2-bit game to something that’s beginning to look rather impressive. One of the bugs I should mention is the remaining monster count. It was off by one originally so that with two monsters running around it would say there was only one. Shooting one of the two would end the level since it thought all of the monsters were dead. The fix was just a matter of not subtracting one from the monster count. The other bug is that monsters sometimes become hidden behind a wall. After doing some checking this is a problem all the way from the JavaScript version. The problem is that we’re not doing proper collision detection for monsters so the sprite goes into the walls on occasion. We’ll cover a fix for that at a later date.

One of the other things I should mention is the change the cTexture class. Previously it was loading the bitmap into a temporary array and then copying it into the texture array which is dumb and doesn’t work properly. It was creating a black bar at the top of the textures. What it does now is load directly from the file into the texture array which is faster and works properly.

And that concludes this lesson. There are still a lot of things that could be done to improve on the look and feel of Wolf5K including adding music and sound effects, animating the sprites, adding more types of monsters and objects, etc. We’ll eventually cover all of that but the next step is to teach you how to build a game from a design document. We’ll then come back and create a new game using the Wolf5K engine.

No Comments

C Lesson 37: Wolf5K: Making it Better Part 1

Source Code
coredx.cpp
Lesson37.cpp

Header Files
wolf5k4.h

Libraries to Link
dxguid.lib
ddraw.lib

First I’m going to cover a few bugs you may have noticed. The first bug is that the health kit and enemies can be stuck in walls. The solution to this is found in how the enemies are initialized in the start method:

1
2
	x=64*(rand()%12)+2; //random location for object
	y=64*(rand()%12)+2;

You can see that the +2 is outside the parenthesis while the function of that is to keep the objects away from the outer wall 2 whole tiles. What that code is actually doing is adjusting the objects 2/64ths of a tile. What the code should be is:

1
2
	x=64*(rand()%12+2); //random location for object
	y=64*(rand()%12+2);

When coding you always need to keep your eyes open for bugs and you need to aways keep in mind how the code is supposed to work. If you didn’t know what the intended function of the code was then you would most likely not see the +2 outside the parenthesis as the source of the problem.

The next bug is has two parts. The first part deals with how the numbers are drawn.

1
2
3
4
	//original DrawDigit code
	PlotPixel(m+1,pk+k,d&4);
	PlotPixel(m+2,pk+k,d&2);
	PlotPixel(m+3,pk+k,d&1);

It looks correct and it works in JavaScript where we only have 2 bit graphics. But in C++ we’re working with 24 bit graphics and each color component can have a value from 0 to 255. What’s actually happening is that d&4 will have the value 0 or 4, d&2 will have the value 0 or 2 and d&1 will have the value 0 or 1. You may have noticed some brighter blue around certain digits. That’s because we’re multiplying the value stored in video memory by 200. d&1 can produce 0 or 200 but d&2 and d&4 can produce a number greater than 255 which is then changed to 255 by our check in the Render() function of the lesson file. The net result is a brighter shade of blue. The fix is easy.

1
2
3
	PlotPixel(m+1,pk+k,(d&4)/4);
	PlotPixel(m+2,pk+k,(d&2)/2);
	PlotPixel(m+3,pk+k,d&1);

Now each of the pixels will have a value of 0 or 1 which is the intended function. The final bug has to deal with how the digits of the number are calculated from left to right.

1
2
3
4
5
6
7
8
9
	//original code from DrawNumber
	int t,j;
	for(int i=(int)floor(log((double)a)/log(10.0f));i>=0;i--,m+=4)
	{
		t=(int)pow(10,i);
		j=(int)floor((double)a/(double)t);
		DrawDigit(j,m,k,r);
		a-=j*t;
	}

This bug isn’t really seen until you reach level 2 and there are more than 10 monsters. Consider the case that we are drawing the number 10. If you set the game level to 10 you will not see the level number show up. You generally won’t come across the problem until you reach level 2 where there are more than 10 enemies. The solution is less than obvious and this bug doesn’t show up in the JavaScript version of the game. Not being a problem in JavaScript is actually a big clue to what is wrong. It turns out that we’re using the wrong type for a value.

1
2
3
4
5
	//what it is
	for(int i=(int)floor(log((double)a)/log(10.0<b>f</b>));i>=0;i--,m+=4)
 
	//what it should be
	for(int i=(int)floor(log((double)a)/log(10.0));i>=0;i--,m+=4)

The problem was that we were dividing a double by a float. In JavaScript, variables don’t have types which is why this wasn’t an issue. Logging was also used to check the values of i and j to verify which digits it was trying to render. In the case of 10, i’s first value was 0 and j was then 10. So the net result is that the code was attempting to render digit ‘10′ which doesn’t exist. But now, with that simple non-obvious fix, the code works like it’s supposed to.

Now, on to what this lesson is really about; rendering at a higher resolution. Up until now we’ve been rendering the screen at 128×72 and then stretching it to 320×240. But now we’re going to render the same resolution as the screen resolution. So if the screen resolution is 320×240, we’re going to render at 320×240 (see image at top of lesson). Doing this is not very difficult. It’s really just a matter of figuring out where 128×72 was hard coded and changing it to be dynamic instead.


640×480 Render - actual size

At 640×480 the game runs at about 8fps on a 1.2Ghz Duron which is about the speed it runs in JavaScript at 128×72 on the same system. The first step to being able to do this is to change how our “video memory” is initialized.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
	//original code in the class definition
	...
		double zBuffer[128];
		int BlankScreen[128*72];
		int w[64];
 
		cNodeHandler objects;
		FILE * logfile;
 
	public:
		int p[128*72];
	...
 
	//new code in the class definition
	...
		double * zBuffer;
		int * BlankScreen;
		int * w;
 
		cNodeHandler objects;
		FILE * logfile;
 
	public:
		int * p; //video memory
	...

w is the array that holds the map. This is now also going to be dynamic so we can have larger and smaller levels. In the original Wolfenstein 3D, maps were 64×64 units. Wolf5K uses a 16×16 map. Now that we have dynamic variables it’s time to allocate memory for them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
	void Init(int sw, int sh)
	{
		H=sh;
		W=sw;
 
		zBuffer = new double[W];
		BlankScreen = new int[W*H];
		w = new int[64];
		p = new int[W*H];
 
		...
 
		playerEyeLevel=H/2;
 
		...
 
		R=2.0*F/(double)W;	//this is the angle of each ray to cover W pixels
		DistToProjPlane=floor(((double)W/2.0)/tan(F));
 
		...
 
		for(int i=0;i<H*W;i++)
		{
			if(i>=(H-16)*W)
				BlankScreen[i]=1;
			else
				BlankScreen[i]=0;
		}
 
		...
	}

Previously we had just used the constructor to initialize everything but now we need the screen resolution so we move everything to the Init() method which takes the screen width and screen height as parameters. Everywhere a 128 or 72 was we’ve modified to use W and H.

1
2
3
4
5
6
7
8
9
	~cWolf5K()
	{
		delete zBuffer;
		delete BlankScreen;
		delete w;
		delete p;
 
		fclose(logfile);
	}

We can forget to free the dynamically allocated memory so we do that in the destructor so that the class is self cleaning. The user doesn’t need to call anything to prevent a memory leak. One very important thing also is how to ZeroMemory and memcpy dynamic memory. Previously we were just using sizeof to get the size of the array we were copying or zeroing out. You can’t do that with pointers. The size of a pointer is the size of the pointer type.

1
2
3
4
	int * p;
	p = new int[100];
	int t = sizeof(p); //t will equal 4 since p is an integer type
	delete p;

So we’ve gone through and modified all the occurances of memcpy and ZeroMemory.

1
2
3
	memcpy(p,BlankScreen,H*W*sizeof(int));
 
	ZeroMemory(w,64);

This could be cleaned up a bit more. Since w is dynamic you don’t really want to ZeroMemory a fixed value. But, we’ll care later. Wolf5K still has a lot of work to be done on it. The final two steps in this lesson deal with the lesson file itself and how it uses the Wolf5K header.

1
2
3
4
5
6
7
8
9
10
11
12
	void StartUp()
	{
		screenW = 320;
		screenH = 240;
 
		wolf5k.Init(screenW,screenH);
		wolf5k.Start();
 
		//default all keys to not pressed
		for (j=0;j<256;j++)
			keys[j]=false;
	}

You can see that we now call the Init() method with the screen resolution to initialize our video buffers. If you fail to call this, things will break.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
	unsigned short Render(int x, int y)
	{
		rb = 0;
		gb = 0;
		bb = wolf5k.p[y*screenW+x];
		bb *= 200;
 
		if(rb>255) rb=255;
		if(gb>255) gb=255;
		if(bb>255) bb=255;
 
		if(rb<0) rb=0;
		if(gb<0) gb=0;
		if(bb<0) bb=0;
 
		return _16BIT(rb,gb,bb);
	}

Since we’re no longer stretching the screen we don’t have to do any math. We can just use the parameters and the screen resolution to grab the pixel value we want. And that’s all there is to it.

In this lesson we’ve squished a few bugs and have shown how to modify the original Wolf5K code to be able to render any resolution without overhauling any major functions such as the RayCast() method. One thing I should mention is that in this lesson’s wolk5k header, damage is disabled for the player. You can kill the monsters but they can’t kill you. This will allow you to progress through the levels to see how the game changes. And that concludes this lesson. In the next lesson we’re going to convert Wolf5K from 2-bit graphics to full color and use bitmaps as textures.

No Comments

C Lesson 36: Wolf5K: Completed

Source Code
coredx.cpp
Lesson36.cpp

Header Files
wolf5k3.h

Libraries to Link
dxguid.lib
ddraw.lib

In the previous lesson we called each part of the rendering and game logic individually from the lesson file. Now we’ve added a main() method to the Wolf5K class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	void main()
	{
		frameDelay--;
		if(frameDelay<0)
		{
			frameDelay=0;
			cz=0;
			UpdateObjects();
			RayCast(); //render the screen
			RenderObjects();
			RenderStatus();
		}
		else
			if(cz==2)
				RenderBlood();
	}

Just like in the JavaScript version, frameDelay is used to pause the action to display some full screen information to the user. For Wolf5K there are to reasons to halt the action; to show the level number and to indicate being shot. We use cz to indicate which we’re displaying. The level screen is rendered in the Start() method so we just don’t draw anything in main when we want to show it. Otherwise we call the RenderBlood() method which flashes the screen blue.

1
2
3
4
5
6
7
8
9
	void RenderBlood()
	{
		//store the default background in the video memory
		memcpy(&p,&BlankScreen,sizeof(BlankScreen));
		int x,y;
		for(x=0;x<W;x++)
			for(y=0;y<H;y++)
				PlotPixel(x,y,1);
	}

One of the other things added is the hasShot variable. This tells us if the user has fired or not. It’s also used to force a delay between shots. In the Move() method we’ve added the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
	if(d==' ') //if we are shooting
	{
		if(!playerDead && !frameDelay && hasShot<-2)
		{
			cNode * o = objects.head;
			hasShot = 2;
			while(o!=0)
			{
				if(o->i && !o->z && o->l<W/2 && o->r>W/2 && o->c==1)
				{
					o->z=1;
					numKilled++;	//numKilled is the kill count
					playerPoints+=10*(gameLevel+o->d/64);
				}
				o=o->next;
			}
		}
 
		//check to see if we've beaten the level
		if(numMonsters-numKilled<2)
		//numMosters is 1 greater than there actually are
		//so if 1 is left then there are zero left
		{
			Start();	//start a new level
		}
	}

Notice that we check to see if hasShot is less than -2 before allowing a shot to be processed. This results in a two frame delay between shots. When we render the status we then check the value of hasShot.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	void RenderStatus()
	{
		//this is our status information
		DrawNumber(playerPoints,2,H+1,1); //points
		DrawNumber(numMonsters-numKilled-1,26,H+1,1); //number of enemies left
		if(!playerDead)//if we're not dead then draw the gun
		{
			hasShot--;
			DrawSprite(6,7,H-32,W/2-16,H,W/2+16,0,1);
			if(hasShot>0)
				DrawSprite(10,10,H-32,W/2-16,H,W/2+16,0,0);
		}
		for(int i=0;i<playerHealth;i++)//draw our health bar
			PlotPixel(W-2-i,H+3,0);
	}

If hasShot is greater than 0 then we render the blast icon on top of the gun. And that’s all there is to it. We’ve now fully translated Wolf5K to a C++ class in only about 900 lines of code including comments. Now we’re going to work on upgrading Wolf5K to render 320×240 pixels instead of just stretching a smaller screen. We’re also going to add in better graphics using bitmaps as textures instead of storing the graphics in the code.

No Comments

C Lesson 35: Wolf5K: The Objects

Source Code
coredx.cpp
Lesson35.cpp

Header Files
wolf5k2.h

Libraries to Link
dxguid.lib
ddraw.lib

Since the workings of the sprite rendering was covered in detail already in the JavaScript tutorials I’m just going to focus on the challenges of translating this part of the code and the usage of the new code.

1
2
3
4
5
6
7
8
9
10
11
12
	void StartScene()
	{
		wolf5k.UpdateObjects();
		wolf5k.RayCast(); //render the screen
		wolf5k.RenderObjects();
		wolf5k.RenderStatus();
 
		if(keys[VK_UP]) wolf5k.Move('U');
		if(keys[VK_DOWN]) wolf5k.Move('D');
		if(keys[VK_LEFT]) wolf5k.Move('L');
		if(keys[VK_RIGHT]) wolf5k.Move('R');
	}

In the previous lesson we called only RayCast(). Now we’ve added UpdateObjects(), RenderObjects() and RenderStatus(). Eventually this will all be tucked away into a single method.

One of the first things added to the Wolf5K header was the cNode and cNodeHandler class. The code from the original JavaScript was then copied over and translated to C++. Compiling the code revealed which object variables were missing from our cNode class. Lather, rinse, repeat until all the variables are found.

1
2
3
4
5
6
7
	class cNode
	{
	public:
		cNode * next;
		double x,y,d,l,a,r;
		double dx,dy;
		int i,c,z;

Just like Lesson 34, the challenge wasn’t so much figuring out what variables were needed so much as figuring out what type the variables needed to be. You could copy all the relavent code over, translate it to a compilable state and still have problems simply because you have a variable defined as an integer that should be a double. Using a log file and logging variable values is a good way to correct those problems.

1
2
3
4
5
6
7
8
9
10
	//in the RenderObjects() method
 
	fprintf(logfile,"object %i: d %f\n",o,o->d);
	if(o->d>64.0f && DrawSprite(pat,a9,k,o->l,l,o->r,o->d,1))
	{
		o->c=1; //o.c 1 indicates the monster is visible
		fprintf(logfile,"drawsprite: pat %i\n",pat);
	}
	else
		o->c++; //otherwise count frames monster not visible to player

One of the problems I ran into was the sprites not rendering. The original code had the DrawSprite routine return 1 if a pixel was plotted. So what I did was check values all the way up to the call to the DrawSprite routine and determined that all the conditions were true except that DrawSprite was returning 0. That meant that the problem was with the DrawSprite routine. It turned out that a variable or few were the wrong type. Effective debugging requires knowing what the code is supposed to do so you can do effective logging and figure out what it’s not doing properly. By logging o->d I can see if the first condition is true. If that condition is true and the fprintf inside the check still isn’t being written then obviously the problem lies with the DrawSprite routine. If the DrawSprite routine were correct but still nothing was being drawn then we would have to check the paremeters being passed into the method and so on. Debugging takes practice and common sense.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
	cNode()
	{
		x=y=d=l=a=r=dx=dy = 0;
		i=c=z=0;
		next = 0;
	}
 
	cNode(cNode &node)
	{
		x=node.x;
		y=node.y;
		z=node.z;
		d=node.d;
		l=node.l;
		a=node.a;
		r=node.r;
		dx=node.dx;
		dy=node.dy;
		i=node.i;
		c=node.c;
 
		next = 0;
	}
 
	cNode(cNode * node)
	{
		x=node->x;
		y=node->y;
		z=node->z;
		d=node->d;
		l=node->l;
		a=node->a;
		r=node->r;
		dx=node->dx;
		dy=node->dy;
		i=node->i;
		c=node->c;
 
		next = 0;
	}

In JavaScript, all variables are defaulted to a value of 0. That doesn’t happen in C/C++. If you don’t initialize your variables to 0 and/or forget to copy the values explicitly, you will run into problems.

The sort is nothing we havn’t done before. All we’ve done is change which variable is used to determine which node comes first. For Wolf5K the variable to use is “d” which is the distance from the player. We want to render the objects farthest away first so we sort from largest to smallest. One interesting thing about JavaScript is that the sort routine takes a function pointer. We’ve been using pointers quite a bit to point to variables but it’s also possible to point to functions. In a future FYI we’ll cover function pointers and modify the sort function so that you don’t have to define the conditional inside the function which will allow you to use the same sort method but sort lists in multiple ways. For now, we’ll just stick with the simple way which works just as well.

And that concludes this lesson. I changed the h file name in order to avoid conflicts with the previous lesson. It would be a good idea for you to go through the code to make sure you understand what’s going on. Compare it to the JavaScript to see what’s the same and what’s different. You may also have noticed that there is no game logic. That will be added in the next lesson when we complete the translation.

No Comments

C Lesson 34: Wolf5K: The Ray Caster

Source Code
coredx.cpp
Lesson34.cpp

Header Files
wolf5k.h

Libraries to Link
dxguid.lib
ddraw.lib

I’m not going to go into great detail about how this works since that is already covered in the JavaScript tutorials.

One of the “features” of JavaScript (and PHP for that matter) is that they are loosely typed. In other words, you don’t define a variable as a string or an integer or whatever. And you can set a variable that previously held a number to a string.

1
2
	var t = 5;
	t = "hello";

That’s perfectly valid in JavaScript and PHP and other scripting languages. C++ on the other hand is not quite that forgiving. All C variable types are essentially numbers. Even chars can be thought of as a variable that can hold a value from 0 to 255 rather than a variable which holds a character such as “a”. As a result you can do the following:

1
2
	int a = 5;
	char b = a;

That will at the most give you a warning about loss of data. The reason I bring this up is because if you don’t apply the proper types when doing the translation you get problems. The “big” problem I had is that moving forward wasn’t in sync with moving backwards. You didn’t move in a consistant line essentially. Moving foward didn’t follow the same line as moving backwards. The solution, it turns out, was to make the player location variables doubles. I had previously set them to integers which they do end up being treated as. The problem was caused by rounding errors. In the wolf5k.h file you will find that everything that isn’t an integer is a double. This is to maximize accuracy. Everything that is a double could just as well be float. The reason I’m not bothing to go back to floats is because the frame rate is about 44fps where if we just returned a solid color we would get about 45fps. Simply put, this ray casting stuff is so fast that adding accuracy doesn’t hurt the performace. And the rule is to make it work first and then make it work fast.

Using the wolf5k.h header file is very simple. You simply include it and then create a variable of type cWolf5K.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
	#include <windows.h>
	#include <math.h>
	#include <stdio.h>
 
	#include "wolf5k.h"
 
	int screenW,screenH,j;
	bool keys[256];
 
	cWolf5K wolf5k;
 
	void StartUp()
	{
		screenW = 320;
		screenH = 240;
 
		wolf5k.Start();
 
		//default all keys to not pressed
		for (j=0;j<256;j++)
			keys[j]=false;
	}

The start method initializes the game level as you may recall. We call this function once when the program starts. The ShutDown() function is empty since we’re not initializing anything. Wolf5k.h is entirely self contained.

1
2
3
4
5
6
7
8
9
	void StartScene()
	{
		wolf5k.RayCast(); //render the screen
 
		if(keys[VK_UP]) wolf5k.Move('U');
		if(keys[VK_DOWN]) wolf5k.Move('D');
		if(keys[VK_LEFT]) wolf5k.Move('L');
		if(keys[VK_RIGHT]) wolf5k.Move('R');
	}

Since our coredx.cpp file is expecting to render one pixel at a time and wolf5k renders the entire screen at once, we call the RayCast() method when the core calls StartScene(). We also process input at this time. As you can see, we’re using the character trick again to make the code a little more readable. Rather than passing in a number we just pass in a character value. The net result is that wolf5k renders the screen once per frame. Now we just need to pass Wolf5K’s “video memory” to the core.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
	int zb, rb, gb, bb;
 
	unsigned short Render(int x, int y)
	{
		int xp = (float)x * 128.0f / (float)screenW;
		int yp = (float)y * 72.0f / (float)screenH;
 
		rb = 0;
		gb = 0;
		bb = wolf5k.p[yp*128+xp];
		bb *= 200;
 
		if(rb>255) rb=255;
		if(gb>255) gb=255;
		if(bb>255) bb=255;
 
		if(rb<0) rb=0;
		if(gb<0) gb=0;
		if(bb<0) bb=0;
 
		return _16BIT(rb,gb,bb);
	}

It’s been a very long time since the Render function has been this simple. Since the Wolf5k screen is only 128×72 and our screen is 320×240 we need to do the stretching trick we learned long ago when learning how to use textures. In the original Wolf5K source the video memory was only 16×72 and bits were used. Now that we’re moving to C++ the “video memory” has been changed to hold bytes and the Wolf5K code has been modified accordingly.

In the Wolf5K header you will find a namespace which holds the bm and FontMap variables. This is because using a namespace is the easiest way to initialize the graphics. You can’t just drop that code into the class itself because classes do not allow a variable to be initialized outside of a method. We also don’t want to make them global since they are specifically for the Wolf5K code. Most of the work done for the translation was simply fixing invalid variable names and changing the math function names.

Translating code is actually a pretty easy task if you have a decent understanding of the two languages and understand the code that you’re translating. If you don’t know what it’s supposed to do then you’ll have a tough time figuring out if your translation is broken. I’ll leave it as an exercise to the reader to delve further into the code to compare it against the original JavaScript.

In our next lesson we’re again going to take a break from graphics and work on linked lists. We need a sortable linked list in order to add objects to our translation. JavaScript supplies sorting functions which we’re going to have to write ourselves.

No Comments

C Lesson 33: Putting it All Together

Source Code
coredx.cpp
Lesson33.cpp

Header Files
cVector.h
cTransform.h
cRay.h
cCamera2.h
cGeneral.h
cTexture.h
cLight.h
cSphere.h

Libraries to Link
dxguid.lib
ddraw.lib

Now there’s something we havn’t seen before; the south pole. Unfortunatly, something still isn’t quite right. The image still distorts and view doesn’t rotate properly. I’m supplying the code as is and going over what I’m doing so that it’s easier for someone to take a look and figure out what’s going wrong and Contact Us with working code.

First off, let’s take a look at what rays the camera class is generating.


x and z direction value


x and y direction value

You can see that the camera is generating rays which fill a portion of a sphere’s surface. The images were generated by logging the x,y, and z value of the camera ray’s direction vector from 0 to 1 in both the xamnt and yamnt in steps of 0.01. The results were then put in Excel and plotted. If we were to square and add the resulting x,y, and z values we always get 1 which is correct since the direction the ray is going is a unit vector. So I think we can safely rule out the camera class itself. One would expect that if the camera were the source of the distortion then the rays would form a distorted image.

1
2
3
4
5
6
7
	void move(float m)
	{
		setRay(0.5f,0.5f);
		loc.x+=ray.dir.x*m;
		loc.y+=ray.dir.y*m;
		loc.z+=ray.dir.z*m;
	}

The first new method we’ve added to the cCamera2 class is the move() method. What it does is find the ray going through the center of the view area and then moves the location vector along it with a multiple of m. This is much easier than doing transforms and what not. This does however limit you to being able to move only in directions that the camera rays cover. But for now we only care about moving forward and back which this does perfectly. Even if the image on the screen is distorted you always move directly into or out of the screen.

1
2
3
4
5
6
7
8
9
	void rotate(float dt, float dp)
	{
		theta+=dt;
		phi+=dp;
		reset();
		tt[0] = Transform::rotate('x',phi);
		tt[1] = Transform::rotate('y',theta);
		transform(Transform::multipletrans(tt,2));
	}

The final change to the cCamera2 class is the addition of the rotate method which takes a change in theta (dt) and a change in phi (dp). Both in radians. We then modify the camera’s rotation values and reset the direction vectors for the camera. Next we create the transformation matrix based on phi and theta and then transform the camera’s direction vectors.

And that’s it. Aside from breaking out all the classes into seperate files there really wasn’t much of a change to adding the camera class to the ray tracer. Now it’s just a matter of figuring out what is causing the distortion. But, sometimes the best way to solve a problem is to just walk away from it for awhile and work on something else. The camera simply determines how we can view the scene. It doesn’t determine what we can render. If you can figure out the solution before I do and send me the code I’d really appreciate it. You’ll recieve full credit in the source and on this site.

No Comments

C Lesson 32: The Camera Class

Source Code
Lesson32.cpp

Header Files
cVector.h
cTransform.h
cRay.h
cCamera2.h

The camera is the end goal of all the previously created classes. It provides the interface to create the rays needed to shoot from the eye point to the projection plane. Since we already have a camera class previously defined, this is cCamera2.

1
2
3
4
5
	class cCamera2
	{
	public:
		cVector loc,dir,right,down;
		cRay ray;

First up the in class are the variables that define the camera. We have a vector for the location, the direction the camera is point, a vector pointing right and a vector pointing down. We also have a cRay variable which is what will be passed to the cSphere class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	cCamera2()
	{
		loc = Vector::O;
		dir = Vector::Z;
		right = Vector::scalar(Vector::X,4.0f/3.0f);
		down = Vector::Y.neg();
	}
 
	cCamera2(cVector &ploc,cVector &pdir,cVector &pright,cVector &pdown)
	{
		setTo(ploc,pdir,pright,pdown);
	}
 
	void setTo(cVector &ploc,cVector &pdir,cVector &pright,cVector &pdown)
	{
		loc = ploc;
		dir = pdir;
		right = pright;
		down = pdown;
	}

Next we have our contructor methods. By default the camera is placed at the origin and faces directly into the screen. If you already have those variables defined you can also initialize a camera with given vectors. The setTo function allows you to pass in given vector values after the class is already initialized. The goal however, is to contain all the needed logic inside the class so that you don’t need to generate any of the vectors yourself.

1
2
3
4
5
6
7
8
9
10
11
	void setPos(float x, float y, float z)
	{
		loc.setto(x,y,z);
	}
 
	void reset()
	{
		dir = Vector::Z;
		right = Vector::scalar(Vector::X,4.0f/3.0f);
		down = Vector::Y.neg();
	}

Next up are a couple simple methods. setPos() simply sets the camera to the given x,y and z location. reset() sets the camera back to facing directly into the camera. This function is here so that we don’t have to undo transformations using an inverse transform before rotating the camera to it’s new direction.

1
2
3
4
5
6
7
	void transform(cTransform &trans)
	{
		loc = Transform::transformed(trans,loc,false);
		dir = Transform::transformed(trans,dir,true);
		right = Transform::transformed(trans,right,true);
		down = Transform::transformed(trans,down,true);
	}

This is the “brains” of the camera. What this does is apply the transformation created by the transform class, as you learned how to use in the last class, to the camera’s vectors resulting in it being rotated properly. This eliminates the need to do any complex math yourself. It’s all hidden.

1
2
3
4
5
6
7
	void setRay(float xamnt, float yamnt)
	{
		ray.setTo(loc,
			Vector::add(Vector::add(dir,Vector::scalar(right,(xamnt-0.5f))),
			Vector::scalar(down,(yamnt-0.5f))),
			1.0f);
	}

Finally we have the setRay method. This does the same thing as the setRay class in our original camera class except this version does it properly and faster. xamnt and yamnt are the percentage of the screen so the values go from 0 to 1. So for example, if the x resolution is 320 pixels then we pass in x/320.0f where x is the x location on the screen. We do the same for y using the y resolution.

And that’s it. A camera class when properly abstracted is not very complicated. All the real work is hidden away in classes we covered previously. We aren’t quite done yet though. We need to provide additional functionality so that we can easily move and rotate the camera without needing to know how anything but the cCamera2 class works. When someone plugs the cCamera2 class into their project they should not have to provide any code to handle creating the transform. They should be able to pass in a location and rotation values and not have to worry about anything else.

We’ll cover putting it all together in the next lesson where we will plug this class into our existing real time ray tracer.

No Comments

C Lesson 31: The Ray Class

Source Code
Lesson31.cpp

Header Files
cVector.h
cTransform.h
cRay.h

Now that we have a vector class and a transform class, it’s time to do something useful with them. We’re going to build a ray class. A ray consists of a starting point and a unit direction vector. Previously we had been passing six values into the cSphere class to determine intersection points. Three values for the position and three values for the direction. With the cRay class we can pass in a single variable.

1
2
3
4
5
6
7
8
9
10
11
12
13
	cRay()
	{
		start = Vector::O;
		dir = Vector::Z;
		distmult=1.0f;
	}
 
	cRay(cVector &pstart, cVector &pdir, float dm)
	{
		start = pstart;
		dir = Vector::normalize(pdir);
		distmult = dm;
	}

There are two ways to initialize a ray using this class. The first way is to just default it to the origin pointing into the screen. distmult is only stored in the ray class. It’s not used by it. So for now, we’re just going to ignore it. The next way to initialize the vector is to give it a starting position vector and a direction vector along with the distmult. The passed in direction vector doesn’t need to be a unit vector as it will be normalized before being stored in the ray class.

1
2
3
4
5
6
7
8
9
10
11
12
	cRay copy()
	{
		return cRay(start.copy(),dir.copy(),distmult);
	}
 
	void toString()
	{
		printf("Ray from:\n");
		start.toString();
		printf("in the direction of:\n");
		dir.toString();
	}

Next up we have two simple functions. copy simply returns a copy of the ray. Again, this function was probably in the original JavaScript because of of how that langage handles objects. With C++ a simple equals will suffice. And also we have a toString function which will print out the position of the vector as well as where it’s pointing at.

1
2
3
4
5
6
7
8
	void transform(cTransform &trans)
	{
		start = Transform::transformed(trans,start,false);
		dir = Transform::transformed(trans,dir,true);
		float dirlength = dir.length();
		distmult = distmult/dirlength;
		dir = Vector::scalar(dir,1.0f/dirlength); // (normalize)
	}

This is the key part of this class. This is how we pass in a transformation matrix created with the cTransform class and rotate and position the ray based on it. With the start position we translate the resulting transform and with dir we don’t. This is because start is a position while dir is simply a directional vector. We also adjust distmult accordingly and normalize the direction vector to make sure it is a unit vector. If it’s not a unit vector, things will be rendered wrong.

1
2
3
4
5
6
7
	cRay transformed(cTransform &trans)
	{
		cRay toreturn = copy();
		toreturn.distmult = 1.0f;
		toreturn.transform(trans);
		return toreturn;
	}

And finally we have the transformed function. transform applies the transformation to the current ray while transformed makes a copy of the current ray, then transforms and returns the copy, leaving the original ray alone.

And that’s it. All the complex stuff is tucked away neatly in the cTransform class. And now that we have a ray class, we can create a class that generates rays; the camera class. We’ll cover that in the next tutorial.

No Comments

C Lesson 30: The Transform Class

Source Code
Lesson30.cpp

Header Files
cVector.h
cTransform.h

Since cTransform.h is dependent on cVector.h you must include cVector.h first. The original JavaScript version had both classes interdependent on each other. The vector class referenced the transform class and the transform class referenced the vector class. That doesn’t work in C/C++ so things have been slightly reorganized. The lesson file simply demonstrates some of the functionality of the transform class.

Like the cVector class, we’re broken up the cTransform into two main sections: the class and the namespace. The class handles initializing the cTransform instance and allows for one instance to be copied to another. This isn’t really necessary as you could just as well do the following:

1
2
3
4
5
6
7
	//the copy way
	cTransform a,b;
 
	b = a.copy();
 
	//the easy way
	b = a;

I think the reason for the copy function is that in JavaScript all variables defined as an object are references. If you do it the “easy way” what will actually happen is that b will point to the object a instead of being another instance of the object with the values set to a. JavaScript is more like the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
	cTransform * a, * b;
 
	a = new cTransform();
	b = a; //b is the same object as a
 
	delete a;
	delete b; //this will fail since b is pointing to the
		//memory space that a occupied
 
	b = a.copy(); //b is a new object with the same values as a
 
	delete a;
	detele b; //will now succeed since b has it's own memory space

Now onto the actual class functionality.

1
2
3
4
5
	cTransform(cVector &pvx, cVector &pvy, cVector &pvz,
			cVector &pc, bool actualorder)
	{
		if (actualorder)
		{

When instantiating a cTransform you have the option of just leaving the vectors that the cTransform class uses in whatever state they happen to be in, or by passing in 4 vectors and telling the class if the vectors are set to the actual order or not. This last parameter is necessary because there are two ways to store a matrix.

1
2
3
		X Y Z
		X Y Z
		...

or

1
2
3
		X X X ...
		Y Y Y ...
		Z Z Z ...

Our transform class expects the values to be entered using the second form. Generally a vector contains an x,y and z value. If that’s the case, then when you pass that vector into the cTransform you want to set actualorder to false. pc (c in the cTransform class) is always treated as though it has an x,y and z value. Now lets go over the namespace.

1
2
	void reducedrowechelonform(float rows[], int rowc, int colc)
	{

You may need to use this function but it’s contained in the namespace because the it’s needed to do a transform. It takes a one dimensional array of values and a value for the number of rows and the number of columns. Reduced Row Echelon Form is used to invert the transform matrix. First I’ll cover the functionality of the function though.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	//original matrix
	a b c
	d e f
	g h i
 
 
	//first loop
	a b c
	0 e f
	0 0 i
 
	//second loop
	a 0 0
	0 e 0
	0 0 i
 
	//third loop
	1 0 0
	0 1 0
	0 0 1

And of course the values of a through i don’t necessarily stay the same as the function progresses until finally you get an identity matrix. Inverting a matrix is done the following way:

1
2
3
4
5
6
7
8
9
	//original matrix
	a b c 1 0 0
	d e f 0 1 0
	g h i 0 0 1
 
	//after RRE
	1 0 0 a b c
	0 1 0 d e f
	0 0 1 g h i

After reduction a through i are now the inverted values of the original a through i. If you want to learn more about matricies the topic you want to explore is “Linear Algebra.”

1
2
	cTransform findinverse(cTransform &trans)
	{

findinverse then takes a cTransform, creates the one dimensional array and passes in the dimensions of the matrix which is 3×6 to the RRE function and then returns a cTransform with the inverse values. The RRE function is a good place to start if you’re looking for a way to speed things up. There are also cases where during reduction and row will become all zeros. This function doesn’t take those cases into account since they shouldn’t ever be generated by the class.

1
	cTransform IdentityTrans(Vector::X,Vector::Y,Vector::Z,Vector::O,false);

Just like in our cVector class we have our default vector values, we also have an identity transformation matrix which uses those default vector values.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
	cTransform multipletrans(cTransform * transs, int num)
	{
		if (num == 0) return Transform::IdentityTrans;
 
		cTransform currenttrans;
 
		if (num == 1)
			currenttrans = transs[0].copy();
		else
			currenttrans = transs[0];
 
		for (int a=1; a<num; a++)
		{
			// multiply this one times what we already have
			// (see source for this section)
		}
		return currenttrans;
	}

The identity transform is used by the multipletrans function. In our lesson file this function is used to create a cTransform with x,y, and z rotations. Inside the loop the current transformation matrix is multiplied against the cumulative matrix.

1
2
3
	where a and b are matricies and * is a matrix multiplication
 
	a * b != b * a

This is mathematical reason the order in which you apply rotations matters. Matrix multiplication is not the same as multiplying two numbers together.

1
2
3
4
5
6
7
8
	cTransform scale(cVector &amount)
	{
		return cTransform(
			Vector::scalar(Vector::X,amount.x),
			Vector::scalar(Vector::Y,amount.y),
			Vector::scalar(Vector::Z,amount.z),
			Vector::O,false);
	}

The scale function uses the identity vectors to create an identity matrix scaled by a vector.

1
2
3
	x 0 0
	0 y 0
	0 0 z
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
	cTransform rotate(int dim, float amnt)
	{
		if (dim == 0 || dim == 'x')
			return cTransform(
				Vector::X,
				Vector::add(Vector::scalar(Vector::Y,cosf(amnt)),
				Vector::scalar(Vector::Z,sinf(amnt))),
				Vector::add(Vector::scalar(Vector::Z,cosf(amnt)),
				Vector::scalar(Vector::Y,sinf(amnt)).neg()),
				Vector::O,false);
 
		if (dim == 1 || dim == 'y')
			return cTransform(
				Vector::add(Vector::scalar(Vector::X,cosf(amnt)),
				Vector::scalar(Vector::Z,sinf(amnt)).neg()),
				Vector::Y,
				Vector::add(Vector::scalar(Vector::Z,cosf(amnt)),
				Vector::scalar(Vector::X,sinf(amnt))),
				Vector::O,false);
 
		//if (dim == 2 || dim == 'z')
			return cTransform(
				Vector::add(Vector::scalar(Vector::X,cosf(amnt)),
				Vector::scalar(Vector::Y,sinf(amnt))),
				Vector::add(Vector::scalar(Vector::Y,cosf(amnt)),
				Vector::scalar(Vector::X,sinf(amnt)).neg()),
				Vector::Z,
				Vector::O,false);
	}

For the sake of simplifying the use of this function you can pass in the letter of the axis or a numerical equivelent to select which axis to apply the rotation to. amnt is the angle of rotation in radians. We remed out the last check because we have to return something and if the first two checks fail then we might as well assume a z axis rotation.

1
2
3
4
5
	//x axis rotation
	X' = X
	Y' = Y * cos(amnt) + Z * sin(amnt)
	Z' = Z * cos(amnt) - Y * sin(amnt)
	O' = O

If you want to learn more about the math behind this, check out SigGraph.org.

1
2
3
4
	cTransform translate(cVector &amount)
	{
		return cTransform(Vector::X,Vector::Y,Vector::Z,amount,false);
	}

And finally we have the translate function which adjusts the Identity Matrix by the vector, amount.

And that concludes this lesson. With this lesson you can start to see how we build up a complex class by using simpler classes. When solving complex problems the key is to break the problem up into its component parts and then build the solution by solving the component problems one at a time. In the next lesson we’ll use the cTransform class to build the cRay class.

No Comments