Java Lesson 4: Hopping Bunnies and Screenshots

Lesson 4 Source Files
Resource Files

The resources zip is just the graphics for the project. All the graphics go in the “resources” sub folder of your java project space. The plan is to make the resource zip work for as many lessons as possible. This means that the graphics that are in there are pretty much the final versions. More files will be added as we progress but none will be removed. They may be altered but they won’t be removed.

One of the first things I did for this lesson was get rid of all the player specific variables from the main Wolk5K class. The player is now an object just like everything else. Bunnies now collide with each other and change direction as well. When a collision occurs the object type number is passed to the collision handling method of the object. This allows the objects to react differently to colliding with different objects. When a bunny collides with another bunny they both change direction. Pressing the space bar will launch a bullet (currently represented by a tree) in the direction you were facing. If a bullet collides with a bunny the bunny disappears. Bunnies will also change direction if they collide with the player.

One of the things the original Wolfenstein game didn’t have was objects that moved vertically. Everything just slid across the floor. In our game the bunnies actually hop. When I first added the hopping ability I just subtracted the height of the bunny from the starting height value that the raycaster calculates. Then I noticed that the bunnies far away seemed to be jumping rediculously high. I had forgotten to multiply the height they were at by the inverse of the distance from the player.

Another new addition to the game is rotating sprites. This seems difficult until you figure out what the raycaster is already calculating.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	public int get_texture(double p_angle)
	{
		double t = Math.PI*2-this.facing + Math.PI/2.0 + p_angle + this.angle;
 
		while(t<0)	t+=2.0*Math.PI;
		while(t>2.0*Math.PI) t-=2.0*Math.PI;
		t*=180.0/Math.PI;
		sel_texture = 0;
		if(t>0+22.5) sel_texture = 1;
		if(t>45+22.5) sel_texture = 2;
		if(t>90+22.5) sel_texture = 3;
		if(t>135+22.5) sel_texture = 4;
		if(t>180+22.5) sel_texture = 5;
		if(t>225+22.5) sel_texture = 6;
		if(t>270+22.5) sel_texture = 7;
		if(t>315+22.5) sel_texture = 0;
 
		return textures[sel_texture];
	}

This code is quite inefficient but it makes it very easy to see what we’re doing. We subtract the direction that the object is facing from 360 degree because we’re looking at the object. We’re not going from the object’s perspective. We thing add 90 degrees so 0 degrees is north instead of east. p_angle is the angle that the player object is facing. We add that so 0 degrees is facing the same direction as the player. And finally we add the actual angle between the player and the object. This is calculated when rendering the objects.

Next we ensure that the angle of the texture is between 0 and 360 degrees. Adding and subtracting 2*PI is the slow way but it works. When programming make it work then make it fast. And finally we pick the texture we need based on the perspective corrected rotation of the object. Texture 0 is facing east and go counter clockwise from there in 45 degree increments.

No Comments

Java Lesson 3: Better Graphics and Organization

Lesson 3 Files

The code is now being broken into components. There is a generic object class which is extended by various entity classes. Code is being stored in various directories to keep things organized. Java has built in functions for loading JPG images which will save you disk space but the compression artifacts make it difficult to mask things. Also, JPG images take significantly longer to load than bitmaps. Since it’s all the same size in memory there’s no reason to use JPG images. It’s just diskspace and as long as we keep it below 650MB (the size of a CD) we’re fine.

One thing I would really like to add but havn’t figured out how to do yet, is floors. Most raycasters out there just do walls. Those that do render floors do it in a way which doesn’t seem to be compatible with the rest of my raycaster. For now I’m going to ignore rendering issues and continue building the game it self. Graphics can always be improved later.

One thing you may notice in the code that’s different from Wolf5K is that the mask is applied once. Rather than comparing color values every frame for every object being rendered, the code now just sets all the non displayed color values to a set value which is ignored by the plot pixel function. Pixels can only use 24 bits so I set the 25th bit to 1 when the pixel isn’t to be rendered. This saves us a lot of memory and speeds up rendering. Those extra 8 bits are reserved for the alpha channel which isn’t used.

In the next lesson I’m going to add basic physics to the bunny object so that it can hop around rather than just slide around the level bouncing off walls.

I’d also like to get sound effects going. OGG Vorbis is a free MP3 alternative. MP3 requires you to pay a license fee if you intend to use it in a commercial project. OGG doesn’t. Many commerical games use OGG and my experience with it in C++ was good. We’ll see how well Java does with it.

The controls are just the arrow keys which move you around. You can’t shoot or be shot.

No Comments

Java Lesson 2: Basic Implementation of Wolf5k

Lesson 2 Files

I’m not going to bother fully implementing or explaining Wolf5k in Java. It’s been done in Javascript and C++. The above code gets you going with basic raycasting and a ridiculous amount of objects moving around. The code is virtually identical to the C++ code although some slight modifications have been made to link it with the Java software rendering code. The resolution is hard coded so you can’t render at a higher resolution than 128×64.

Now that we’ve got the basics under way, it’s time to catch up with the C++ version.

The controls are just the arrow keys which move you around. You can’t shoot or be shot.

No Comments

Java Lesson 1: Initializing the screen and creating some static

Lesson 1 Files

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
 * This code is subject to the following license
 * http://code.dawnofthegeeks.com/about/
 * 
 * This code may not be used in conjunction with the GPL or any similar license
 */
 
import java.applet.*;
import java.awt.*;
import java.awt.image.*;
 
public final class lesson01 extends Applet implements Runnable
{
	//This is expected to be defined
	static final long serialVersionUID = 1;
 
	//we'll be running in a thread
	Thread gameThread;
	boolean running = true;
 
	//everything we need to define our buffers
	Graphics video;			//our main video screen
	int video_buffer[];  	//our video buffer
	int s_x,s_y;			//our screen resolution
	Image off_screen;		//gets our off screen to the video screen
	MemoryImageSource mis;	//video buffer manager
 
	//rendering statistics
	int frames;				//frame counter
	int fps;				//frames rendered in one second
	long timer;				//frame timer
 
	//generic temporariy variables
	int x,y;
 
	//initialize the video buffers and start the gameThread
	public void init()
	{
		//use the applet's height and width
		s_x = getSize().width;
		s_y = getSize().height;
 
		//initialize our stats
		frames = 0;
		fps = 0;
 
		//initialize our video buffers
		video_buffer = new int[s_x * s_y];
        mis=new MemoryImageSource(s_x,  s_y,  video_buffer,  0,  s_x);
        mis.setAnimated(true);
        off_screen=createImage(mis);
		video = getGraphics();
 
		//start our gameThread to get things moving
        gameThread= new Thread(this);
		gameThread.start();
	}
 
	//clean up our program when we close the applet
	public void destroy()
	{
		running = false;
		gameThread = null;
	}
 
	//NOT optional
    public void update(Graphics g) 
    {
    	/*
    	 * Although this function is empty it needs to be defined
    	 * Failing to define this function results in flicker
    	 */
	}
 
	//NOT optional
    public void paint(Graphics g) 
    {
    	/*
    	 * Although this function is empty it needs to be defined
    	 * Failing to define this function results in flicker
    	 */
	}
 
    //Our Main Loop
	public void run()
	{
		//set our timer to 1 second in the future
		timer = System.currentTimeMillis() + 1000;
 
		while (running)
		{
			try
			{
				if(System.currentTimeMillis() > timer)
				{//if 1 second has passed calculate the FPS and reset our values
					timer = System.currentTimeMillis() + 1000;
					fps = frames;
					frames = 0;
					getAppletContext().showStatus(fps+ " FPS");
				}
 
				//render the screen
				//0xFF000000 is the alpha value
				//leaving this off will prevent 
				//the pixels from showing up
				for(x = 0;x<s_x;x++)
					for(y = 0;y<s_y;y++)
						video_buffer[x+y*s_x] = 0xFF000000 + (int)(Math.random() * 256*256*256);
 
 
	            mis.newPixels(); //tell our handler that the pixels have changed
	            video.drawImage(off_screen,0,0,this); //update the visible screen
	            //NOTE: drawImage is supposed to return true on success or false otherwise
	            //It returns false when using MemoryImageSource even when it succeeds
 
	            frames++; //increment the frame counter
				gameThread.sleep(0); //let our CPU take a breath
			}
			catch(InterruptedException e)
			{
 
			}
		}
	}
}

The comments should be enough to get you going. This is a basic applet which initializes the video buffer and links it to our one dimensional integer array which will be plotting pixels to. Every pixel is then set to a random color and when we’re done, it’s displayed to the user. This script is plotting about 4 million pixels per second on a 1.7ghz system. Future tutorials will benchmark against this number. The closer we get to 4 million pixels per second we get, the more efficient we know our calculations are.

It’s important that you call the sleep method on the gameThread or your applet will hog all your system resources. There’s no need to set the number higher than 5. 0 may still cause some problems.

No Comments

SoftGeL Lesson 10: Lighting and Bumpmapping

Source Code
Lesson10.cpp
coreSDL2.cpp

Header Files
cSGL_Shapes.h
cSGL_Color2.h
cSGL_Perlin.h
cSGL_Texture2.h
cSGL_Font.h
cSGL_2X2Matrix.h
cSGL_Light.h
SoftGL.j.h

Libraries to Link
sdl.lib
sdlmain.lib

You will additionally need fontdef.txt, font2.bmp, font2.mask.bmp, test.bmp and test.bump.bmp.

test.bmp is pure while and the bump is just a grey scale image. You can see from the screenshot how bumpmapping and lighting can turn a perfectly flat texture into something that’s very bumpy. In this tutorial we’ll cover how this effect is achieved.

No Comments

SoftGeL Lesson 9: 2D Translation and Rotation

SoftGeL Lesson 9: 2D Translation and Rotation

Source Code
Lesson9.cpp
coreSDL2.cpp

Header Files
cSGL_Shapes.h
cSGL_Color2.h
cSGL_Perlin.h
cSGL_Texture.h
cSGL_Font.h
cSGL_2X2Matrix.h
SoftGL.i.h

Libraries to Link
sdl.lib
sdlmain.lib

You will additionally need fontdef.txt, font2.bmp and font2.mask.bmp.

The first change is to the cSGL_Color header file. The SGL_RECT and SGL_TRIANGLE classes have been move out of it into the cSGL_Shapes header file. We’ve also added an SGL_POINT class. The new SGL_RECT and SGL_TRIANGLE class then use the SGL_POINT class rather than just using x,y pairs as done in previous lessons. Using operator overloading, we’re able to then peform mathematical operations using these classes which results in easy to follow code since it’s more math based than method based as are other graphics APIs such as OpenGL and DirectX. For example in the SGL_TRIANGLE class:

1
2
3
4
5
6
7
8
9
10
11
	void operator += (SGL_POINT src)
	{
		p1 += src;
		p2 += src;
		p3 += src;
	}
 
	SGL_TRIANGLE operator + (SGL_POINT src)
	{
		return SGL_TRIANGLE(p1 + src, p2 + src, p3 + src);
	}

Now rather than supplying a method such as SGL_TRIANGLE.Move(SGL_POINT) we can simply write SGL_TRIANGLE += SGL_POINT or SGL_TRIANGLE = SGL_TRIANGLE + SGL_POINT.

Aside from simplifying the shape based classes we’ve now also added a matrix class which handles doing 2D transformations. The SGL_2X2Matrix class also uses operator overloading in order to keep the mathematical look of what is going on.

1
2
3
4
5
6
7
	tri2 = tri;	//set temp triangle to original
	trans.SetRotation(deg); //set angle of rotation
	tri2 = trans * tri2; //rotate temp triangle around own axis
	tri2 += SGL_POINT(60,60); //move temp triangle
	trans.SetRotation((360.0f-deg)/2.0f); //set angle of rotation
	tri2 = trans * tri2; //rotate around new axis
	tri2 += SGL_POINT(160,120); //move to final position

tri is the original triangle shape. We don’t want to modify that. Instead we set tri2 equal to tri and then modify tri2. The only transformation that the matrix class does is the rotation. So we set the rotation in degrees and then multiply the transformation matrix by the triangle matrix in order to rotate the triangle. We then add a point to the triangle in order to translate the triangle. Order of operations matters. tri2 * trans is mathematically invalid and won’t work in this code either. Translation before rotation also produces a different output than rotation before translation.

The other changes were done to the SGL_Interface class. Now that we have a triangle class we don’t need to pass x,y pairs into the triangle rendering functions.

1
2
3
4
5
6
7
	void SGL_DrawTriangleSolidFill(SGL_TRIANGLE tri,SGL_Color c)
	void SGL_DrawTriangleGradFill(	SGL_TRIANGLE tri,
							SGL_Color c1,
							SGL_Color c2,
							SGL_Color c3)
	void SGL_DrawTriangleTexture(SGL_TRIANGLE tri, SGL_TRIANGLE tex,
					SGL_Texture * texture,  SGL_Texture * mask = 0)

And that’s it. There’s so much new information that I’m not going to cover it all here. This lesson simply introduces the changes and how to use the end result rather than details on how it all works on the low level. Rather, I’ll be using The Programmer’s Wiki to post the detailed workings of each part.

2×2 Matrix Math - learn how the math behind the rotations works.

Operator Overloading - Learn how operating overloading works.

Both of the above are key parts to how Lesson 9 works. You will need to understand matrix math and operator overloading to fully be able to understand what exactly is going on and be able to proceed as we move into more complicated math.

No Comments

SoftGeL Lesson 8: Displaying Text With a Mask

Source Code
Lesson8.cpp
coreSDL2.cpp

Header Files
cSGL_Color.h
cSGL_Perlin.h
cSGL_Texture.h
cSGL_Font.h
SoftGL.h.h

Libraries to Link
sdl.lib
sdlmain.lib

You will additionally need fontdef.txt, font2.bmp and font2.mask.bmp.

It’s okay for in progress programs to look ugly which this one definitly does. Both the font bitmap and the mask bitmap are low res which results in very blocky rendering. When we make our first real game, we’ll improve the graphics.

The size parameter of the Print() method now specifies the height in pixels the font will be rendered at. We’ve also added a mask parameter with a default value of 0. This allows the cSGL_Font.h header to work with prior tutorials even though the method for loading the font has been changed.

1
	font.LoadFont(textures[1],"fontdef.txt",textures[2]);

textures[2] is used as the mask. If the mask value is zero then the pixel is drawn, otherwise it’s not. The mask must also be the same size and aligned with the texture being rendered. It’s not possible to select a region on the mask different from the religion on the source texture.

1
2
3
4
5
6
7
8
9
10
11
12
13
	void SGL_BlitStretch(SGL_RECT dest, SGL_RECT src,
			SGL_Texture * texture, SGL_Texture * mask = 0)
	{
		...
			dx = (int)(src.x1+(float)(x*xsd)*xratio);
			dy = (int)(src.y1+(float)(y*ysd)*yratio);
 
			if(mask == 0 || mask->GetColorAt(dx,dy).black())
			SGL_PlotPixel((int)dest.x1+x*xdd,
					(int)dest.y1+y*ydd,
					texture->GetColorAt(dx,dy));
		...
	}

With the OR operator, conditions are only checked until a TRUE is found. So, if the mask pointer is zero then the pixel is drawn and no attempt is made to access the GetColorAt method at the mask memory location which would cause a crash. If the mask pointer is non zero then the the color is checked. If the color of the mask is black (0) then the pixel is drawn. The only change to the Print() method is the line which calls the BlitStretch() method. We now reference the mask pointer of the font class and pass it to the BlitStretch() method. We also default the mask pointer value to 0 in the font constructor method.

1
2
3
	...
	SGL_BlitStretch(dest,src,font->font,font->mask);
	...

And that’s it. Masking is actually a very simple thing to do. This logic was applied to the other blit function and the textured triangle function. We now have a nearly complete 2D graphics library. In our next lesson we’ll cover 2D translation and rotation. This will allow us more advanced sprite movement which will allow us a much greater range of game types this library can be used to create.

No Comments

SoftGeL Lesson 7: Displaying Text

Source Code
Lesson7.cpp
coreSDL2.cpp

Header Files
cSGL_Color.h
cSGL_Perlin.h
cSGL_Texture.h
cSGL_Font.h
SoftGL.g.h

Libraries to Link
sdl.lib
sdlmain.lib

The BlitStretch() function has had a bug correction made in SoftGL.g.h header file. This will need to be applied to previous tutorials.

1
2
3
4
5
	...
	texture->GetColorAt(
			(int)(src.x1+(float)(x*xsd)*xratio),
			(int)(src.y1+(float)(y*ysd)*yratio))
	...

In the original lesson the ratio was also being applied to the x1,y1 variables which is incorrect.

You will additionally need fontdef.txt and font.bmp.

Windows supplies functions which can be used to generate font textures at load time. But, since this is software rendering intended to be as universally applicable as possible, we won’t be using those functions. Instead, we’ll be doing it the hard way; with a paint program.

Standard APIs such as DirectX and OpenGL demand that textures be a power of two. We don’t have that limitation. So, instead of fitting the fonts into a power of 2 texture we’re going to put all the characters in one long texture that’s only as long as it needs to be. This allows us to set the top of the texture source to 0 and the bottom of the texture source to the height of the font texture. No vertical calculations needed. Since we’re inputing the numbers by hand this also saves time defining the texture location of each letter.

In order to simplify things further the character locations are stored in a plain text, tab delimited file. The characters don’t need to be in any particular order as the first number is the character number. The above definition and BMP file defines the upper and lower case letters. That’s enough to get you started. It’s hard to miss how blocky the characters look when rendered. This is because 16 pixel characters are being stretched to five times the size. You will need to supply your own high resolution character set. One will most likely not be provided here.

The SGL_Font class is quite simple to use and implement.

1
2
3
4
5
6
	class SGL_Font
	{
	public:
		SGL_Texture * font;
		uint32 charWidth[256],charStart[256];
	...

All we need is a pointer to a texture that will hold the characters (remember that the handler actually loads the textures and we don’t want to intialize a whole texture handler just for this) and then two variables to hold the width of each character and the starting pixel position. The standard ascii table contains 256 characters so that’s what we allow for. This also elimiates the need for bounds checking which speeds things up a bit. The constructor for this class initializes everything to zero. There is no need for a constructor since the texture will be cleared out by the handler outside of this class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
	void LoadFont(SGL_Texture * tex, char * def)
	{
		font = tex;
		FILE * defs = fopen(def,"r");
 
		if(!defs)
			return;
 
		int j,k,l;
		while(!feof(defs))
		{
			fscanf(defs,"%i\t%i\t%i",&j,&k,&l);
			if(j<256 && j>=0)
			{
				charStart[j] = k;
				charWidth[j] = l;
			}
		}
		fclose(defs);
	}

To use a texture we simply pass in the pointer to the texture we will be using for the characters. We then pass in the name of the file which defines the location of each of the characters. This allows us to change the font without recompiling the code.

FILE and fscanf are used to read in the formated strings in the definition file. For simplicity we just read in all the file values to integers even though the j variable can only be 0-255. j is the character number, k is the starting x pixel position of the character and l is the width in pixels of the character.

Next we have a series of helper functions to get access to the variables of the font class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	SGL_Texture * GetTexture()
	{
		return font;
	}
 
	uint32 Height()
	{
		return font->height;
	}
 
	uint32 Width(uint8 letter)
	{
		return charWidth[letter];
	}
 
	uint32 Start(uint8 letter)
	{
		return charStart[letter];
	}

GetTexture() returns the pointer to the texture being used. Height() gives us the stored height of the font. Width() gives us the pixel width of the character and Start() gives us the starting pixel of the character. Finally we go to the SGL_Interface class to actually render the text.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	void SGL_Print(SGL_Font * font, char * text, float size, SGL_RECT box)
	{
		uint32 j = strlen(text);
		float h = (float)font->Height() * size;
 
		SGL_RECT src, dest;
 
		dest.x1 = box.x1;
		dest.y1 = box.y1;
 
		for(uint32 k = 0; k<j;k++)
		{
			src.x1 = (float)font->Start(text[k]);
			src.x2 = src.x1+(float)font->Width(text[k]);
			src.y1 = 0.0f;
			src.y2 = (float)font->Height();
 
			dest.x2=dest.x1 + (float)font->Width(text[k]) * size;
			dest.y2=dest.y1 + h;
 
			SGL_BlitStretch(dest,src,font->font);
			dest.x1+=(float)font->Width(text[k]) * size;
		}
	}

SGL_Print takes the font class, the text, the size to render and the box which tells the method where to start drawing. As you can see, there is nothing fancy here. All we’re doing is using the size variable to scale the text being printed. If the height of the font texture is 16 pixels and size is 2.0f then the text will be rendered 32 pixels high. This is actually “wrong.” Size should define the final render size and not a relative size based on the size of the texture. A high resolution font texture should render the same size as a low resolution font texture given the same “size” value into this method.

That change will be left as an exercise for the reader until a later tutorial. In future tutorials we’ll also cover handling special characters such as the carriage return and word wrap. We also need to introduce masking so that we can remove the background color of the font and make it transparent.

No Comments

SoftGeL Lesson 6: Textured Triangles

Source Code
Lesson6.cpp
coreSDL2.cpp

Header Files
cSGL_Color.h
cSGL_Perlin.h
cSGL_Texture.h
SoftGL.f.h

Libraries to Link
sdl.lib
sdlmain.lib

For this lesson we’ve simplified some things.

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
	...
 
	SGL_TRIANGLE tri, tex, vel;
 
	...
 
	void Lesson_Render()
	{
		SGL.SGL_ClearScreen();
		SGL.SGL_DrawTriangleTexture(tri,tex,textures[0]);
 
		tri.x1 += vel.x1;
		tri.y1 += vel.y1;
		tri.x2 += vel.x2;
		tri.y2 += vel.y2;
		tri.x3 += vel.x3;
		tri.y3 += vel.y3;
 
		if(tri.x1>screenW || tri.x1<0)	vel.x1 = -vel.x1;
		if(tri.y1>screenH || tri.y1<0)	vel.y1 = -vel.y1;
		if(tri.x2>screenW || tri.x2<0)	vel.x2 = -vel.x2;
		if(tri.y2>screenH || tri.y2<0)	vel.y2 = -vel.y2;
		if(tri.x3>screenW || tri.x3<0)	vel.x3 = -vel.x3;
		if(tri.y3>screenH || tri.y3<0)	vel.y3 = -vel.y3;
	}

We’ve introduce the SGL_TRIANGLE class which holds three pairs of x,y values. We can then use that for vertices of a triangle and also our velocity values. Next, instead of explicity setting the velocity values as positive or negative with two if statements, we just negate the velocity value and use only one check.

The DrawTriangleTexture() method is identical to the DrawTriangleGradFill() method except we duplicate some of the math to calculate the texture coordinate.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
		float px1=tri.x1;
		float px2=tri.x3;
		float py1=tri.y1;
		float py2=tri.y3;
 
		float ptx1=tex.x1;
		float ptx2=tex.x3;
		float pty1=tex.y1;
		float pty2=tex.y3;
 
		float dx1 = (tri.x2-tri.x1)/h;
		float dx2 = (tri.x2-tri.x3)/h;
		float dy1 = (tri.y2-tri.y1)/h;
		float dy2 = (tri.y2-tri.y3)/h;
 
		float dtx1 = (tex.x2-tex.x1)/h;
		float dtx2 = (tex.x2-tex.x3)/h;
		float dty1 = (tex.y2-tex.y1)/h;
		float dty2 = (tex.y2-tex.y3)/h;

The extra t signifies the variable is for the texture coordinates. We then do the same thing when actually drawing the triangle.

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
		for(j=0;j<h;j++)
		{
			dist = sqrtf((px2-px1)*(px2-px1)+(py2-py1)*(py2-py1));
 
			if(dist>0)
			{
				px = px1;
				py = py1;
				ptx=ptx1;
				pty=pty1;
				dx = (px2-px1)/dist;
				dy = (py2-py1)/dist;
				dtx = (ptx2-ptx1)/dist;
				dty = (pty2-pty1)/dist;
 
				for(k=0;k<dist;k++)
				{
					c = texture->GetColorAt((int)ptx,(int)pty);
 
					SGL_PlotPixel((int)px,(int)py,c);
					px+=dx;
					py+=dy;
					ptx+=dtx;
					pty+=dty;
				}
 
				px1+=dx1;
				px2+=dx2;
				py1+=dy1;
				py2+=dy2;
 
				ptx1+=dtx1;
				ptx2+=dtx2;
				pty1+=dty1;
				pty2+=dty2;
			}
		}

And that’s all there is to it. All we’re doing is setting it so there are the same number of steps for the texture scanline as there are for the triangle scanline. It’s the same concept used for the BlitStretch() method.

In the next lesson we’ll finally cover how to create a font and display text to the user. It’s very hard to make games if you can’t display information.

No Comments

SoftGeL Lesson 5: Stretch Blitting

Source Code
Lesson5.cpp
coreSDL2.cpp

Header Files
cSGL_Color.h
cSGL_Perlin.h
cSGL_Texture.h
SoftGL.e.h

Libraries to Link
sdl.lib
sdlmain.lib

Previously xdir2 and ydir2 were used to specify the direction of the second texture. Now they’re used to specify the direction of the bottom right of the destination rectangle.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
	void Lesson_Render()
	{
		SGL.SGL_ClearScreen();
		SGL.SGL_BlitStretch(dest,src,textures[0]);
 
		dest.x1+=xdir;
		dest.y1+=ydir;
		dest.x2+=xdir2;
		dest.y2+=ydir2;
		if(dest.x1>screenW)	xdir = -1;
		if(dest.x1<0)	xdir = 1;
		if(dest.y1>screenH)	ydir = -1;
		if(dest.y1<0)	ydir = 1;
 
		if(dest.x2>screenW)	xdir2 = -2;
		if(dest.x2<0)	xdir2 = 2;
		if(dest.y2>screenH)	ydir2 = -2;
		if(dest.y2<0)	ydir2 = 2;
	}

Our texture source rectangle always stays the same but it will be stretched to fit the destination rectangle. Next up is the SGL_BlitStretch method which does all the work.

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
	void SGL_BlitStretch(SGL_RECT dest, SGL_RECT src, SGL_Texture * texture)
	{
		int x,y;
		int xss = (int)fabs(src.x2-src.x1);
		int yss = (int)fabs(src.y2-src.y1);
		int xds = (int)fabs(dest.x2-dest.x1);
		int yds = (int)fabs(dest.y2-dest.y1);
 
		float yratio = (yds>0) ? (float)yss/(float)yds : 0;
		float xratio = (xds>0) ? (float)xss/(float)xds : 0;
 
		int xsd = (src.x2-src.x1) > 0 ? 1:-1;
		int ysd = (src.y2-src.y1) > 0 ? 1:-1;
		int xdd = (dest.x2-dest.x1) > 0 ? 1:-1;
		int ydd = (dest.y2-dest.y1) > 0 ? 1:-1;
 
		for(x=0;x<xds;x++)
			for(y=0;y<yds;y++)
				SGL_PlotPixel(
				(int)dest.x1+x*xdd,
				(int)dest.y1+y*ydd,
				texture->GetColorAt(
						(int)(((src.x1)+(float)(x*xsd))*xratio),
						(int)(((src.y1)+(float)(y*ysd))*yratio))
					);
	}

Previously we only checked to see what the size of the source rectangle was. Now we also check to see what the size of the destination rectangle is. Next, we find the ratio between the two. Finally we check the direction of both the source rectangle and the destination rectangle. In a regular blit the destination rectangle is used to define only a single point and so it had no direction.

And now we do the actual render. Previously the source retangle defined the size of the area we were drawing. Now the destination rectangle defines the size. The only changes to the PlotPixel call is that the direction is applied to the destination location and we multiply the texture pixel location by the ratio.

And that’s all there is to it. You can now blit any portion of a texture to any size rectangle. On a 1.2Ghz system this function can pump out over 100 rectangles per second.

If we left the 0 check out of the calculation of the y and x ratio this program would still run fine. However, not all OSes/CPUs may be so kind. Historically divide by 0 errors will crash a program. Any time you divide by a variable it’s important to make sure it’s not zero and supply a valid result for the 0 case.

No Comments