Tutorial *101*
Author		Tomas "Tomaz" Jakobsson

E-mail		tompsson@hotmail

Page		www.quakemods.net/tomaz

Oki... This tutorial will show how easy it is to get almost the whole engine to use PCX or TGA pictures to add some more colors to the game. The reason i have both TGA and PCX is cos TGA can have 32-bit. But they also can be kinda big in filesize. The disadvantage with PCX is that it can only have 8-bit (I think) but it has its own palette and has a smaller filesize than TGA. This code has been tested with a clean source base. So i cant guarantee it will work if u have an altered engine but i see no reason why it shouldnt. Most of this code is originally done by LordHavoc and altered by me Tomaz. So if it works u can thank LordHavoc... If it doesnt u can blame me ;)

Well on to the coding.

First off we need to alter a few loading functions in order to get more colors to work.

in gltexture_t in gl_draw.c at the bottom
add:



// Tomaz || TGA Begin

	int			bytesperpixel;

	int			lhcsum;

// Tomaz || TGA End

This is necessary for the engine so it knows if its an 8-bit or 32-bit picture we are playing with. It also fixes an annoying cache mismatch crash that might happend if u reload a map.


Then a further down replace the entire function



GL_LoadTexture


with:


// Tomaz || TGA Begin

int lhcsumtable[256];

int GL_LoadTexture (char *identifier, int width, int height, byte *data, qboolean mipmap, qboolean alpha, int bytesperpixel)

{

	qboolean	noalpha;

	int			i, p, s, lhcsum;

	gltexture_t	*glt;





	// occurances. well this isn't exactly a checksum, it's better than that but

	// not following any standards.

	lhcsum = 0;

	s = width*height*bytesperpixel;

	for (i = 0;i < 256;i++) lhcsumtable[i] = i + 1;

	for (i = 0;i < s;i++) lhcsum += (lhcsumtable[data[i] & 255]++);



	// see if the texture is allready present

	if (identifier[0])

	{

		for (i=0, glt=gltextures ; i < numgltextures ; i++, glt++)

		{

			if (!strcmp (identifier, glt->identifier))

			{

				if (lhcsum != glt->lhcsum || width != glt->width || height != glt->height)

				{

					Con_DPrintf("GL_LoadTexture: cache mismatch\n");

					goto GL_LoadTexture_setup;

				}

				return glt->texnum;

			}

		}

	}

	// whoever at id or threewave must've been half asleep...

	glt = &gltextures[numgltextures];

	numgltextures++;



	strcpy (glt->identifier, identifier);

	glt->texnum = texture_extension_number;

	texture_extension_number++;

	GL_LoadTexture_setup:

	glt->lhcsum = lhcsum;

	glt->width = width;

	glt->height = height;

	glt->mipmap = mipmap;

	glt->bytesperpixel = bytesperpixel;



	if (!isDedicated)

	{

		GL_Bind(glt->texnum);



		if (bytesperpixel == 1)

			GL_Upload8 (data, width, height, mipmap, alpha);

		else if (bytesperpixel == 4)

			GL_Upload32 ((void *)data, width, height, mipmap, true);

		else

			Sys_Error("GL_LoadTexture: unknown bytesperpixel\n");

		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

	}



	return glt->texnum;

}

// Tomaz || TGA End

Now we made some use of the two extra things we added. Well lets continue.
In glquake.h
replace:


int GL_LoadTexture (char *identifier, int width, int height, byte *data, qboolean mipmap, qboolean alpha);

with:


int GL_LoadTexture (char *identifier, int width, int height, byte *data, qboolean mipmap, qboolean alpha, int bytesperpixel);

This is also to make the engine know what color depth we are using.

Then do a search on GL_LoadTexture and add ,1 at the end of all of em except the ones that has ", int bytesperpixel" at the end. So then it should be like



char_texture = GL_LoadTexture ("charset", 128, 128, draw_chars, false, true);

becomes


char_texture = GL_LoadTexture ("charset", 128, 128, draw_chars, false, true ,1);

This makes it so all original fuctions that isnt altered and usus lmp's only uses 8-bit. Making the engine display an 8-bit pic in 32-bit doesnt improve the quality of the pic but is memory consuming. Thats the reason we have to tell it that the pics are 8-bit.

At the bottom of gl_draw.c add:



// Tomaz || TGA Begin

int		image_width;

int		image_height;



/*

===========

PCX Loading

===========

*/



typedef struct

{

	char		manufacturer;

	char		version;

	char		encoding;

	char		bits_per_pixel;

	unsigned short	xmin,ymin,xmax,ymax;

	unsigned short	hres,vres;

	unsigned char	palette[48];

	char		reserved;

	char		color_planes;

	unsigned short	bytes_per_line;

	unsigned short	palette_type;

	char		filler[58];

	unsigned 	data;			// unbounded

} pcx_t;



/*

========

LoadPCX

========

*/

byte* LoadPCX (FILE *f, int matchwidth, int matchheight)

{

	pcx_t	*pcx, pcxbuf;

	byte	palette[768];

	byte	*pix, *image_rgba;

	int		x, y;

	int		dataByte, runLength;

	int		count;



//

// parse the PCX file

//

	fread (&pcxbuf, 1, sizeof(pcxbuf), f);



	pcx = &pcxbuf;



	if (pcx->manufacturer != 0x0a

		|| pcx->version != 5

		|| pcx->encoding != 1

		|| pcx->bits_per_pixel != 8

		|| pcx->xmax > 320

		|| pcx->ymax > 256)

	{

		Con_Printf ("Bad pcx file\n");

		return NULL;

	}



	if (matchwidth && (pcx->xmax+1) != matchwidth)

		return NULL;

	if (matchheight && (pcx->ymax+1) != matchheight)

		return NULL;



	// seek to palette

	fseek (f, -768, SEEK_END);

	fread (palette, 1, 768, f);



	fseek (f, sizeof(pcxbuf) - 4, SEEK_SET);



	count = (pcx->xmax+1) * (pcx->ymax+1);

	image_rgba = malloc( count * 4);



	for (y=0 ; y<=pcx->ymax ; y++)

	{

		pix = image_rgba + 4*y*(pcx->xmax+1);

		for (x=0 ; x<=pcx->xmax ; )

		{

			dataByte = fgetc(f);



			if((dataByte & 0xC0) == 0xC0)

			{

				runLength = dataByte & 0x3F;

				dataByte = fgetc(f);

			}

			else

				runLength = 1;



			while(runLength-- > 0)

			{

				pix[0] = palette[dataByte*3];

				pix[1] = palette[dataByte*3+1];

				pix[2] = palette[dataByte*3+2];

				pix[3] = 255;

				pix += 4;

				x++;

			}

		}

	}

	fclose(f);

	image_width = pcx->xmax+1;

	image_height = pcx->ymax+1;

	return image_rgba;

}



/*

=============

TARGA LOADING

=============

*/



typedef struct _TargaHeader

{

	unsigned char 	id_length, colormap_type, image_type;

	unsigned short	colormap_index, colormap_length;

	unsigned char	colormap_size;

	unsigned short	x_origin, y_origin, width, height;

	unsigned char	pixel_size, attributes;

} TargaHeader;





TargaHeader		targa_header;



int fgetLittleShort (FILE *f)

{

	byte	b1, b2;



	b1 = fgetc(f);

	b2 = fgetc(f);



	return (short)(b1 + b2*256);

}



int fgetLittleLong (FILE *f)

{

	byte	b1, b2, b3, b4;



	b1 = fgetc(f);

	b2 = fgetc(f);

	b3 = fgetc(f);

	b4 = fgetc(f);



	return b1 + (b2<<8) + (b3<<16) + (b4<<24);

}





/*

========

LoadTGA

========

*/

byte* LoadTGA (FILE *fin, int matchwidth, int matchheight)

{

	int	columns, rows, numPixels;

	byte	*pixbuf;

	int	row, column;

	byte	*image_rgba;



	targa_header.id_length = fgetc(fin);

	targa_header.colormap_type = fgetc(fin);

	targa_header.image_type = fgetc(fin);



	targa_header.colormap_index = fgetLittleShort(fin);

	targa_header.colormap_length = fgetLittleShort(fin);

	targa_header.colormap_size = fgetc(fin);

	targa_header.x_origin = fgetLittleShort(fin);

	targa_header.y_origin = fgetLittleShort(fin);

	targa_header.width = fgetLittleShort(fin);

	targa_header.height = fgetLittleShort(fin);

	if (matchwidth && targa_header.width != matchwidth)

		return NULL;

	if (matchheight && targa_header.height != matchheight)

		return NULL;

	targa_header.pixel_size = fgetc(fin);

	targa_header.attributes = fgetc(fin);



	if (targa_header.image_type!=2 && targa_header.image_type!=10)

		Host_Error ("LoadTGA: Only type 2 and 10 targa RGB images supported\n");



	if (targa_header.colormap_type !=0 || (targa_header.pixel_size!=32 && targa_header.pixel_size!=24))

		Host_Error ("LoadTGA: Only 32 or 24 bit images supported (no colormaps)\n");



	columns = targa_header.width;

	rows = targa_header.height;

	numPixels = columns * rows;



	image_rgba = malloc (numPixels*4);



	if (targa_header.id_length != 0)

		fseek(fin, targa_header.id_length, SEEK_CUR);  // skip TARGA image comment



	if (targa_header.image_type==2)

	{  // Uncompressed, RGB images

		for(row=rows-1; row>=0; row--)

		{

			pixbuf = image_rgba + row*columns*4;

			for(column=0; column=0; row--)

		{

			pixbuf = image_rgba + row*columns*4;

			for(column=0; column0)

								row--;

							else

								goto breakOut;

							pixbuf = image_rgba + row*columns*4;

						}

					}

				}

				else

				{	// non run-length packet

					for(j=0;j0)

								row--;

							else

								goto breakOut;

							pixbuf = image_rgba + row*columns*4;

						}

					}

				}

			}

			breakOut:;

		}

	}



	fclose(fin);

	image_width = columns;

	image_height = rows;

	return image_rgba;

}



byte* loadimagepixels (char* filename, qboolean complain, int matchwidth, int matchheight)

{

	FILE	*f;

	char	basename[128], name[128];

	byte	*image_rgba, *c;

	COM_StripExtension(filename, basename); // strip the extension to allow TGA and PCX

	c = basename;

	while (*c)

	{

		if (*c == '*')

			*c = '+';

		c++;

	}



	sprintf (name, "%s.tga", basename);

	COM_FOpenFile (name, &f);

	if (f)

		return LoadTGA (f, matchwidth, matchheight);



	sprintf (name, "%s.pcx", basename);

	COM_FOpenFile (name, &f);

	if (f)

		return LoadPCX (f, matchwidth, matchheight);



	if (complain)

		Con_Printf ("Couldn't load %s.tga or .pcx\n", filename);

	return NULL;

}



int loadtextureimage (char* filename, int matchwidth, int matchheight, qboolean complain, qboolean mipmap)

{

	int texnum;

	byte *data;

	if (!(data = loadimagepixels (filename, complain, matchwidth, matchheight)))

		return 0;

	texnum = GL_LoadTexture (filename, image_width, image_height, data, mipmap, true, 4);

	free(data);

	return texnum;

}

// Tomaz || TGA End

This tells the engine how it loads TGA's and PCX's. Also in that part are loadimagepixels and loadtextureimage. When we want to be able to use a TGA or a PCX then we use loadtextureimage. Ill show u later how.

Thats it with the TGA Loading functions... Now we only have to make all different functions to use it... First of is Sprites.

Go to Mod_LoadSpriteFrame in gl_model.c

change



char				name[64];

to


char				name[64], sprite[64], sprite2[64];

A bit further down, replace:


sprintf (name, "%s_%i", loadmodel->name, framenum);

pspriteframe->gl_texturenum = GL_LoadTexture (name, width, height, (byte *)(pinframe + 1), true, true, 1);

with


COM_StripExtension(loadmodel->name, sprite);

sprintf (sprite2, "%s_%i", sprite, framenum);



pspriteframe->gl_texturenum = loadtextureimage (sprite2, 0, 0, false, true);

if (pspriteframe->gl_texturenum == 0)// did not find a matching TGA...

{

	sprintf (name, "%s_%i", loadmodel->name, framenum);

	pspriteframe->gl_texturenum = GL_LoadTexture (name, width, height, (byte *)(pinframe + 1), true, true, 1);

}

Go to R_DrawSpriteModel in gl_rmain.c and add:


glDisable(GL_ALPHA_TEST);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glEnable(GL_BLEND);

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

at the top of the function after all the variables
and add


glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

glDisable(GL_BLEND);

glDisable(GL_ALPHA_TEST);

at the bottom of the function
Oki now im gonna explain what we did and hopefully you will be able to use it yourself later on. First of the "name" is the path to the image we are looking for. So what we do is that instead of using GL_LoadTexture we use loadtextureimage. If it cant find any TGA it looks for a PCX and if it cant find that either it uses the original LMP. It searches for the images in the same folder as the sprite is. For sprites we also need to know how many frames the sprite is using. But for the explosion its 6. So the TGA's will be "s_explod_0.tga" and all the way up to "s_explod_5.tga". I hope this is farely understandable. If not then let me know.


Oki... now we will try to do Map Textures to.

Go to Mod_loadTextures in gl_model.c
below dmiptexlump_t *m;
Add:



// Tomaz || TGA Begin

	char		texname[64];

// Tomaz || TGA End

This is used so we can specify what folder we want it to look for the pics in.
And then go a bit further down to:


tx->gl_texturenum = GL_LoadTexture (mt->name, tx->width, tx->height, (byte *)(tx+1), true, false ,1);

Replace it with:


sprintf (texname, "textures/%s", mt->name);

tx->gl_texturenum = loadtextureimage (texname, 0, 0, false, true);

if (tx->gl_texturenum == 0)// did not find a matching TGA...

{

	tx->gl_texturenum = GL_LoadTexture (mt->name, tx->width, tx->height, (byte *)(tx+1), true, false ,1);

}

This is VERY similar to the sprite code. But since map textures doesnt have any folder but only the name we use sprintf to make it look in a "textures" folder. Otherwise its pretty much the same as the sprite code.

So sprite images goes in the same folder as the sprite and map textures goes in quake/id1/textures or quake/whatever you have named your mod/textures

If anything is unclear then let me know...

Another tutorial on how to change more things in quake to use TGA or PCX will come in a near future.

THATS IT...
And as a bonus cos i screwed up u also get to know how to get TGA and PCX on models!!!!
Still in gl_model.c. Go to Mod_LoadAllSkins

change



char	name[32];

to


char	name[64], model[64], model2[64];

Then a bit further down
replace


sprintf (name, "%s_%i", loadmodel->name, i);

pheader->gl_texturenum[i][0] =

pheader->gl_texturenum[i][1] =

pheader->gl_texturenum[i][2] =

pheader->gl_texturenum[i][3] =

GL_LoadTexture (name, pheader->skinwidth, pheader->skinheight, (byte *)(pskintype + 1), true, false, 1);

with


COM_StripExtension(loadmodel->name, model);

sprintf (model2, "%s_%i", model, i);



pheader->gl_texturenum[i][0] =

pheader->gl_texturenum[i][1] =

pheader->gl_texturenum[i][2] =

pheader->gl_texturenum[i][3] =

loadtextureimage (model2, 0, 0, false, true);

if (pheader->gl_texturenum[i][0] == 0)// did not find a matching TGA...

{

	sprintf (name, "%s_%i", loadmodel->name, i);

	pheader->gl_texturenum[i][0] =

	pheader->gl_texturenum[i][1] =

	pheader->gl_texturenum[i][2] =

	pheader->gl_texturenum[i][3] =

	GL_LoadTexture (name, pheader->skinwidth, pheader->skinheight, (byte *)(pskintype + 1), true, false,1);

}

Go to R_DrawAliasModel in gl_rmain.c
add


glDisable(GL_ALPHA_TEST);

glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

glEnable(GL_BLEND);

glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

at top after all the variables
and add


glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);

glDisable(GL_BLEND);

glDisable(GL_ALPHA_TEST);

at the bottom

This uses the same way of finding the image as the sprite does. It searches for the image in the same folder as the model is. The first skin is always _0. So if you want to do a new skin on the quad. Then it will be like quaddama_0.tga. If the model has more skins then it just have to be a bigger number.

Now we gonna do it so we can use an PCX or TGA for console background and for conchars.

Go to Draw_Init in gl_draw.c

replace



char_texture = GL_LoadTexture ("charset", 128, 128, draw_chars, false, true);

with


char_texture = loadtextureimage ("charset", 0, 0, false, false);

if (char_texture == 0)// did not find a matching TGA...

	char_texture = GL_LoadTexture ("charset", 128, 128, draw_chars, false, true, 1);

and replace


gl->texnum = GL_LoadTexture ("conback", conback->width, conback->height, ncdata, false, false);

with


gl->texnum = loadtextureimage ("conback", 0, 0, false, false);

if (gl->texnum == 0)// did not find a matching TGA...

	gl->texnum = GL_LoadTexture ("conback", conback->width, conback->height, ncdata, false, false, 1);

Now just put a file called conback.pcx/tga and conchars.pcx/tga in id1/gfx

Hopefully this will work now... If it for some reason doesnt work then just contact me and im sure we can sort it out.



 
Sign up
Login:
Passwd:
[Remember Me]