///////////////////////////////////////////////////////////////////////////////
//	gl_protex.c, 6/7/98, by Nelno
//
//	Handles generation, uploading of procedural textures
//
//	TODO:	implement this in ref_soft, or replace procedural stuff with
//			animations
///////////////////////////////////////////////////////////////////////////////

#include	"gl_local.h"
#include	"gl_protex.h"

///////////////////////////////////////////////////////////////////////////////
//	globals
///////////////////////////////////////////////////////////////////////////////

//	extern
proTex_t	protex_textures [PROTEX_NUM_TYPES];

cvar_t		*pt_type;

///////////////////////////////////////////////////////////////////////////////
//	prototypes
///////////////////////////////////////////////////////////////////////////////

qboolean GL_Upload32 (unsigned *data, int width, int height,  qboolean mipmap);

///////////////////////////////////////////////////////////////////////////////
//	stuff, crazy, crazy stuff
///////////////////////////////////////////////////////////////////////////////


///////////////////////////////////////////////////////////////////////////////
//	protex_DoFire
//
//	TODO:	can this be sped up with unsigned * instead of byte *?
///////////////////////////////////////////////////////////////////////////////

void	protex_DoFire (proTex_t *texture)
{
	int			x, y, w, h, endx, test;
	int			r, g, b, a;
	int			bit_depth = 4;
	int			center;
	byte		*cur_row, *last_row;
	unsigned	*bottom_row;
	float		dist, scale;

	last_row = texture->data;

	bit_depth = texture->bit_depth;	
	w = texture->width * bit_depth;
	h = texture->height - 1;

	/////////////////////////////////////////////////////////
	//	put a random row of reddish/yellow along the bottom
	/////////////////////////////////////////////////////////

	cur_row = texture->data + w * (h - 1);
	center = w >> 1;
	for (x = 0; x < w; x += bit_depth)
	{
		//	the closer x is to (w / bit_depth) / 2
		//	the closer the color should be to yellow
		//	and the closer alpha should be to 1.0
		dist = fabs (center - x);
		
		//	start fading to black
		scale = 1.0 / (dist * 0.15);
		if (scale > 1.0)
			scale = 1.0;

		r = (rand () % 127) + 128;

		g = (rand () % r) * scale;

		b = 0;

		a = 255 * scale * 1.3;
		if (a > 255) a = 255;

		cur_row [x] = (byte) (r * scale);
		cur_row [x + 1] = (byte) (g * scale);
		cur_row [x + 2] = (byte) (b * scale);
		cur_row [x + 3] = (byte) (a * scale);
	}

	/////////////////////////////////////////////////////////
	//	smooth and move rows up
	/////////////////////////////////////////////////////////

	endx = w - bit_depth;

	for (y = 1; y < h; y++)
	{
		//	copy each row to the row above it
		cur_row = texture->data + w * y;
	
		//	zero left-most pixel
		last_row [0] = 0;
		last_row [1] = 0;
		last_row [2] = 0;
		last_row [3] = 0;

		for (x = bit_depth; x < endx; x += bit_depth)
		{
			//	copy each pixel on the row to the row above it
			//	averaging it with the pixels around it

			scale = ((float)y / (float)h) * 1.2;
			//	average all the pixels in the middle
			r = ((cur_row [x] * 40 + cur_row [x - 4] + cur_row [x + 4] + cur_row [x + w] * 22) >> 6);
			if (r > 255) r = 255;
			
			g = ((cur_row [x + 1] * 40 + cur_row [x - 3] + cur_row [x + 5] + cur_row [x + w + 1] * 22) >> 6) * scale;
			if (g > 255) g = 255;
			
			b = ((cur_row [x + 2] * 40 + cur_row [x - 2] + cur_row [x + 6] + cur_row [x + w + 2] * 22) >> 6) * scale;
			if (b > 255) b = 255;
			
			a = cur_row [x + 3] * scale * 1.2;
			if (a > 255) a = 255;

			//	don't show really dark colors
			test = (r + g) >> 1;
			if (test < 16)
				a = 0;
			else if (test < 64)
				a = a * 0.8;

			last_row [x] = (byte) r;
			last_row [x + 1] = (byte) g;
			last_row [x + 2] = (byte) b;
			last_row [x + 3] = (byte) a;
		}

		//	zero-out right-most pixel
		last_row [x] = 0;
		last_row [x + 1] = 0;
		last_row [x + 2] = 0;
		last_row [x + 3] = 0;

		last_row = cur_row;
	}

	//	make sure bottom row has 0 alpha, cause it seems to wrap around to
	//	the top of the sprite.  Blending??

	bottom_row = (unsigned *) texture->data + texture->width * (texture->height - 1);
	for (x = 0; x < texture->width; x++)
		bottom_row [x] = 0;

	//	upload texture here, SHOULD BE BOUND ALREADY!!!
	//GL_Upload32 ((unsigned *) texture->data, texture->width, texture->height, false);
	qglTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height, GL_RGBA, GL_UNSIGNED_BYTE, texture->data);
}

///////////////////////////////////////////////////////////////////////////////
//	protex_DoSwampGas
//
///////////////////////////////////////////////////////////////////////////////

#define	gas_NORMAL		0
#define	gas_WEIGHTED		1	//	center pixel is prominent
#define	gas_WEIGHTED_Y	2	//	center and upper pixels are prominent
#define	gas_WEIGHTED_X	3	//	center and left/right pixels are prominent
	
void	protex_DoSwampGas (proTex_t *texture)
{
	int			x, y, w, h, endx;
	int			r, g, b, a;
	int			bit_depth = 4;
	byte		*cur_row, *last_row;
	unsigned	*bottom_row;
	float		fade_rate;

	//	for testing
	texture->sub_type = pt_type->value;

	last_row = texture->data;

	bit_depth = texture->bit_depth;	
	w = texture->width * bit_depth;
	h = texture->height;

	fade_rate = 0.94;

	/////////////////////////////////////////////////////////
	//	put a random row of pixels along the bottom
	/////////////////////////////////////////////////////////

	cur_row = texture->data + w * (h - 1);

	for (x = 0; x < w; x += bit_depth)
	{
//		r = g = b = rand () % 192 + 64;
//		a = (r + g + b) / 3;

		g = (byte) (rand () % 128 + 127);
		r = (byte) (rand () % g);
		b = 0;
		a = (g + r) >> 1;

		cur_row [x] = r;
		cur_row [x + 1] = g;
		cur_row [x + 2] = b;
		cur_row [x + 3] = a;
	}

	/////////////////////////////////////////////////////////
	//	smooth and move rows up
	/////////////////////////////////////////////////////////

	endx = w - bit_depth;

	for (y = 1; y < h; y++)
	{
		//	copy each row to the row above it
		cur_row = texture->data + w * y;

		//	FIXME:	doing this without correct weighting may make imperfection in the tiling
		//	average first pixel on row so that it blends with last pixel
		r = (cur_row [0] + cur_row [w - 4] + cur_row [4] + cur_row [w]) >> 2;
		r *= fade_rate;
		g = (cur_row [1] + cur_row [w - 3] + cur_row [5] + cur_row [w + 1]) >> 2;
		g *= fade_rate;
		b = (cur_row [2] + cur_row [w - 2] + cur_row [6] + cur_row [w + 2]) >> 2;
		b *= fade_rate;
		a = (cur_row [3] + cur_row [w - 1] + cur_row [7] + cur_row [w + 3]) >> 2;
		a *= fade_rate;
		
		last_row [0] = (byte) r;
		last_row [1] = (byte) g;
		last_row [2] = (byte) b;
		last_row [3] = (byte) a;

		switch	(texture->sub_type)
		{
			case	gas_NORMAL:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					//	copy each pixel on the row to the row above it
					//	fading the color towards black
					r = (cur_row [x] + cur_row [x - 4] + cur_row [x + 4] + cur_row [x + w]) >> 2;
					r *= fade_rate;
					g = (cur_row [x + 1] + cur_row [x - 3] + cur_row [x + 5] + cur_row [x + w + 1]) >> 2;
					g *= fade_rate;
					b = (cur_row [x + 2] + cur_row [x - 2] + cur_row [x + 6] + cur_row [x + w + 2]) >> 2;
					b *= fade_rate;
					a = (cur_row [x + 3] + cur_row [x - 1] + cur_row [x + 7] + cur_row [x + w + 3]) >> 2;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;

			case	gas_WEIGHTED:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					r = (cur_row [x] * 13 + cur_row [x - 4] + cur_row [x + 4] + cur_row [x + w]) >> 4;
					r *= fade_rate;
					g = (cur_row [x + 1] * 13 + cur_row [x - 3] + cur_row [x + 5] + cur_row [x + w + 1]) >> 4;
					g *= fade_rate;
					b = (cur_row [x + 2] * 13 + cur_row [x - 2] + cur_row [x + 6] + cur_row [x + w + 2]) >> 4;
					b *= fade_rate;
					a = (cur_row [x + 3] * 13 + cur_row [x - 1] + cur_row [x + 7] + cur_row [x + w + 3]) >> 4;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;

			case	gas_WEIGHTED_Y:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					r = (cur_row [x] * 10 + cur_row [x - 4] + cur_row [x + 4] + cur_row [x + w] * 4) >> 4;
					r *= fade_rate;
					g = (cur_row [x + 1] * 10 + cur_row [x - 3] + cur_row [x + 5] + cur_row [x + w + 1] * 4) >> 4;
					g *= fade_rate;
					b = (cur_row [x + 2] * 10 + cur_row [x - 2] + cur_row [x + 6] + cur_row [x + w + 2] * 4) >> 4;
					b *= fade_rate;
					a = (cur_row [x + 3] * 10 + cur_row [x - 1] + cur_row [x + 7] + cur_row [x + w + 3] * 4) >> 4;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;

			case	gas_WEIGHTED_X:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					r = (cur_row [x] * 2 + cur_row [x - 4] + cur_row [x + 4]) >> 2;
					r *= fade_rate;
					g = (cur_row [x + 1] * 2 + cur_row [x - 3] + cur_row [x + 5]) >> 2;
					g *= fade_rate;
					b = (cur_row [x + 2] * 2 + cur_row [x - 2] + cur_row [x + 6]) >> 2;
					b *= fade_rate;
					a = (cur_row [x + 3] * 2 + cur_row [x - 1] + cur_row [x + 7]) >> 2;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;
		}

		//	FIXME:	doing this without correct weighting may make imperfection in the tiling
		//	average last pixel so that it blends with first pixel on this row
		r = (cur_row [x] + cur_row [x - 4] + cur_row [0] + cur_row [x + w]) >> 2;
		r *= fade_rate;
		g = (cur_row [x + 1] + cur_row [x - 3] + cur_row [1] + cur_row [x + w + 1]) >> 2;
		g *= fade_rate;
		b = (cur_row [x + 2] + cur_row [x - 2] + cur_row [2] + cur_row [x + w + 2]) >> 2;
		b *= fade_rate;
		a = (cur_row [x + 3] + cur_row [x - 1] + cur_row [3] + cur_row [x + w + 3]) >> 2;
		a *= fade_rate;

		last_row [x] = (byte) r;
		last_row [x + 1] = (byte) g;
		last_row [x + 2] = (byte) b;
		last_row [x + 3] = (byte) a;

		last_row = cur_row;
	}

	//	make sure bottom row has 0 alpha, cause it seems to wrap around to
	//	the top of the sprite

	bottom_row = (unsigned *) texture->data + texture->width * (texture->height - 1);
	for (x = 0; x < texture->width; x++)
		bottom_row [x] = 0;

	//	upload texture here, SHOULD BE BOUND ALREADY!!!
	//GL_Upload32 ((unsigned *) texture->data, texture->width, texture->height, false);
	qglTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height, GL_RGBA, GL_UNSIGNED_BYTE, texture->data);
}

///////////////////////////////////////////////////////////////////////////////
//	protex_DoSteam
//
///////////////////////////////////////////////////////////////////////////////

#define	STEAM_NORMAL		0
#define	STEAM_WEIGHTED		1	//	center pixel is prominent
#define	STEAM_WEIGHTED_Y	2	//	center and upper pixels are prominent
#define	STEAM_WEIGHTED_X	3	//	center and left/right pixels are prominent
	
void	protex_DoSteam (proTex_t *texture)
{
	int			x, y, w, h, endx;
	int			r, g, b, a;
	int			bit_depth = 4;
	byte		*cur_row, *last_row;
	unsigned	*bottom_row;
	float		fade_rate;

	//	for testing
	texture->sub_type = pt_type->value;

	last_row = texture->data;

	bit_depth = texture->bit_depth;	
	w = texture->width * bit_depth;
	h = texture->height;

	fade_rate = 0.94;

	/////////////////////////////////////////////////////////
	//	put a random row of reddish/yellow along the bottom
	/////////////////////////////////////////////////////////

	cur_row = texture->data + w * (h - 1);

	for (x = 0; x < w; x += bit_depth)
	{
		r = g = b = rand () % 192 + 64;
		a = (r + g + b) / 3;

		cur_row [x] = r;
		cur_row [x + 1] = g;
		cur_row [x + 2] = b;
		cur_row [x + 3] = a;
	}

	/////////////////////////////////////////////////////////
	//	smooth and move rows up
	/////////////////////////////////////////////////////////

	endx = w - bit_depth;

	for (y = 1; y < h; y++)
	{
		//	copy each row to the row above it
		cur_row = texture->data + w * y;

		//	FIXME:	doing this without correct weighting may make imperfections in the tiling
		//	average first pixel on row so that it blends with last pixel
		r = (cur_row [0] + cur_row [w - 4] + cur_row [4] + cur_row [w]) >> 2;
		r *= fade_rate;
		g = (cur_row [1] + cur_row [w - 3] + cur_row [5] + cur_row [w + 1]) >> 2;
		g *= fade_rate;
		b = (cur_row [2] + cur_row [w - 2] + cur_row [6] + cur_row [w + 2]) >> 2;
		b *= fade_rate;
		a = (cur_row [3] + cur_row [w - 1] + cur_row [7] + cur_row [w + 3]) >> 2;
		a *= fade_rate;
		
		last_row [0] = (byte) r;
		last_row [1] = (byte) g;
		last_row [2] = (byte) b;
		last_row [3] = (byte) a;

		switch	(texture->sub_type)
		{
			case	STEAM_NORMAL:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					//	copy each pixel on the row to the row above it
					//	fading the color towards black
					r = (cur_row [x] + cur_row [x - 4] + cur_row [x + 4] + cur_row [x + w]) >> 2;
					r *= fade_rate;
					g = (cur_row [x + 1] + cur_row [x - 3] + cur_row [x + 5] + cur_row [x + w + 1]) >> 2;
					g *= fade_rate;
					b = (cur_row [x + 2] + cur_row [x - 2] + cur_row [x + 6] + cur_row [x + w + 2]) >> 2;
					b *= fade_rate;
					a = (cur_row [x + 3] + cur_row [x - 1] + cur_row [x + 7] + cur_row [x + w + 3]) >> 2;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;

			case	STEAM_WEIGHTED:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					r = (cur_row [x] * 13 + cur_row [x - 4] + cur_row [x + 4] + cur_row [x + w]) >> 4;
					r *= fade_rate;
					g = (cur_row [x + 1] * 13 + cur_row [x - 3] + cur_row [x + 5] + cur_row [x + w + 1]) >> 4;
					g *= fade_rate;
					b = (cur_row [x + 2] * 13 + cur_row [x - 2] + cur_row [x + 6] + cur_row [x + w + 2]) >> 4;
					b *= fade_rate;
					a = (cur_row [x + 3] * 13 + cur_row [x - 1] + cur_row [x + 7] + cur_row [x + w + 3]) >> 4;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;

			case	STEAM_WEIGHTED_Y:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					r = (cur_row [x] * 10 + cur_row [x - 4] + cur_row [x + 4] + cur_row [x + w] * 4) >> 4;
					r *= fade_rate;
					g = (cur_row [x + 1] * 10 + cur_row [x - 3] + cur_row [x + 5] + cur_row [x + w + 1] * 4) >> 4;
					g *= fade_rate;
					b = (cur_row [x + 2] * 10 + cur_row [x - 2] + cur_row [x + 6] + cur_row [x + w + 2] * 4) >> 4;
					b *= fade_rate;
					a = (cur_row [x + 3] * 10 + cur_row [x - 1] + cur_row [x + 7] + cur_row [x + w + 3] * 4) >> 4;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;

			case	STEAM_WEIGHTED_X:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					r = (cur_row [x] * 2 + cur_row [x - 4] + cur_row [x + 4]) >> 2;
					r *= fade_rate;
					g = (cur_row [x + 1] * 2 + cur_row [x - 3] + cur_row [x + 5]) >> 2;
					g *= fade_rate;
					b = (cur_row [x + 2] * 2 + cur_row [x - 2] + cur_row [x + 6]) >> 2;
					b *= fade_rate;
					a = (cur_row [x + 3] * 2 + cur_row [x - 1] + cur_row [x + 7]) >> 2;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;
		}

		//	FIXME:	doing this without correct weighting may make imperfections in the tiling
		//	average last pixel so that it blends with first pixel on this row
		r = (cur_row [x] + cur_row [x - 4] + cur_row [0] + cur_row [x + w]) >> 2;
		r *= fade_rate;
		g = (cur_row [x + 1] + cur_row [x - 3] + cur_row [1] + cur_row [x + w + 1]) >> 2;
		g *= fade_rate;
		b = (cur_row [x + 2] + cur_row [x - 2] + cur_row [2] + cur_row [x + w + 2]) >> 2;
		b *= fade_rate;
		a = (cur_row [x + 3] + cur_row [x - 1] + cur_row [3] + cur_row [x + w + 3]) >> 2;
		a *= fade_rate;

		last_row [x] = (byte) r;
		last_row [x + 1] = (byte) g;
		last_row [x + 2] = (byte) b;
		last_row [x + 3] = (byte) a;

		last_row = cur_row;
	}

	//	make sure bottom row has 0 alpha, cause it seems to wrap around to
	//	the top of the sprite

	bottom_row = (unsigned *) texture->data + texture->width * (texture->height - 1);
	for (x = 0; x < texture->width; x++)
		bottom_row [x] = 0;

	//	upload texture here, SHOULD BE BOUND ALREADY!!!
//	GL_Upload32 ((unsigned *) texture->data, texture->width, texture->height, false);
	qglTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height, GL_RGBA, GL_UNSIGNED_BYTE, texture->data);
}

///////////////////////////////////////////////////////////////////////////////
//	protex_DoFireSkin
//
///////////////////////////////////////////////////////////////////////////////

#define	FIRESKIN_NORMAL		0
#define	FIRESKIN_WEIGHTED		1	//	center pixel is prominent
#define	FIRESKIN_WEIGHTED_Y	2	//	center and upper pixels are prominent
#define	FIRESKIN_WEIGHTED_X	3	//	center and left/right pixels are prominent
	
void	protex_DoFireSkin (proTex_t *texture)
{
	int			x, y, w, h, endx;
	int			r, g, b, a;
	int			bit_depth = 4;
	byte		*cur_row, *last_row;
	unsigned	*bottom_row;
	float		fade_rate;

	//	for testing
	texture->sub_type = pt_type->value;

	last_row = texture->data;

	bit_depth = texture->bit_depth;	
	w = texture->width * bit_depth;
	h = texture->height;

	fade_rate = 1.0;

	/////////////////////////////////////////////////////////
	//	put a random row of reddish/yellow along the bottom
	/////////////////////////////////////////////////////////

	cur_row = texture->data + w * (h - 1);

	for (x = 0; x < w; x += bit_depth)
	{
//		r = g = b = rand () % 192 + 64;

		r = (rand () % 255) + 255;
		g = (rand () % r);
		b = 0;

		a = 255;

		cur_row [x] = (byte) r;
		cur_row [x + 1] = (byte) g;
		cur_row [x + 2] = (byte) b;
		cur_row [x + 3] = (byte) a;
	}

	/////////////////////////////////////////////////////////
	//	smooth and move rows up
	/////////////////////////////////////////////////////////

	endx = w - bit_depth;

	for (y = 1; y < h; y++)
	{
		//	copy each row to the row above it
		cur_row = texture->data + w * y;

		//	FIXME:	doing this without correct weighting may make imperfections in the tiling
		//	average first pixel on row so that it blends with last pixel
		r = (cur_row [0] + cur_row [w - 4] + cur_row [4] + cur_row [w]) >> 2;
		r *= fade_rate;
		g = (cur_row [1] + cur_row [w - 3] + cur_row [5] + cur_row [w + 1]) >> 2;
		g *= fade_rate;
		b = (cur_row [2] + cur_row [w - 2] + cur_row [6] + cur_row [w + 2]) >> 2;
		b *= fade_rate;
		a = (cur_row [3] + cur_row [w - 1] + cur_row [7] + cur_row [w + 3]) >> 2;
		a *= fade_rate;
		
		last_row [0] = (byte) r;
		last_row [1] = (byte) g;
		last_row [2] = (byte) b;
		last_row [3] = (byte) a;

		switch	(texture->sub_type)
		{
			case	FIRESKIN_NORMAL:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					//	copy each pixel on the row to the row above it
					//	fading the color towards black
					r = (cur_row [x] + cur_row [x - 4] + cur_row [x + 4] + cur_row [x + w]) >> 2;
					r *= fade_rate;
					g = (cur_row [x + 1] + cur_row [x - 3] + cur_row [x + 5] + cur_row [x + w + 1]) >> 2;
					g *= fade_rate;
					b = (cur_row [x + 2] + cur_row [x - 2] + cur_row [x + 6] + cur_row [x + w + 2]) >> 2;
					b *= fade_rate;
					a = (cur_row [x + 3] + cur_row [x - 1] + cur_row [x + 7] + cur_row [x + w + 3]) >> 2;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;

			case	FIRESKIN_WEIGHTED:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					r = (cur_row [x] * 13 + cur_row [x - 4] + cur_row [x + 4] + cur_row [x + w]) >> 4;
					r *= fade_rate;
					g = (cur_row [x + 1] * 13 + cur_row [x - 3] + cur_row [x + 5] + cur_row [x + w + 1]) >> 4;
					g *= fade_rate;
					b = (cur_row [x + 2] * 13 + cur_row [x - 2] + cur_row [x + 6] + cur_row [x + w + 2]) >> 4;
					b *= fade_rate;
					a = (cur_row [x + 3] * 13 + cur_row [x - 1] + cur_row [x + 7] + cur_row [x + w + 3]) >> 4;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;

			case	FIRESKIN_WEIGHTED_Y:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					r = (cur_row [x] * 10 + cur_row [x - 4] + cur_row [x + 4] + cur_row [x + w] * 4) >> 4;
					r *= fade_rate;
					g = (cur_row [x + 1] * 10 + cur_row [x - 3] + cur_row [x + 5] + cur_row [x + w + 1] * 4) >> 4;
					g *= fade_rate;
					b = (cur_row [x + 2] * 10 + cur_row [x - 2] + cur_row [x + 6] + cur_row [x + w + 2] * 4) >> 4;
					b *= fade_rate;
					a = (cur_row [x + 3] * 10 + cur_row [x - 1] + cur_row [x + 7] + cur_row [x + w + 3] * 4) >> 4;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;

			case	FIRESKIN_WEIGHTED_X:
				for (x = bit_depth; x < endx; x += bit_depth)
				{
					r = (cur_row [x] + cur_row [x - 4] * 7 + cur_row [x + 4] * 8) >> 4;
					r *= fade_rate;
					g = (cur_row [x + 1] + cur_row [x - 3] * 7 + cur_row [x + 5] * 8) >> 4;
					g *= fade_rate;
					b = (cur_row [x + 2] + cur_row [x - 2] * 7 + cur_row [x + 6] * 8) >> 4;
					b *= fade_rate;
					a = (cur_row [x + 3] + cur_row [x - 1] * 7 + cur_row [x + 7] * 8) >> 4;
					a *= fade_rate;

					last_row [x] = (byte) r;
					last_row [x + 1] = (byte) g;
					last_row [x + 2] = (byte) b;
					last_row [x + 3] = (byte) a;
				}
				break;
		}

		//	FIXME:	doing this without correct weighting may make imperfections in the tiling
		//	average last pixel so that it blends with first pixel on this row
		r = (cur_row [x] + cur_row [x - 4] + cur_row [0] + cur_row [x + w]) >> 2;
		r *= fade_rate;
		g = (cur_row [x + 1] + cur_row [x - 3] + cur_row [1] + cur_row [x + w + 1]) >> 2;
		g *= fade_rate;
		b = (cur_row [x + 2] + cur_row [x - 2] + cur_row [2] + cur_row [x + w + 2]) >> 2;
		b *= fade_rate;
		a = (cur_row [x + 3] + cur_row [x - 1] + cur_row [3] + cur_row [x + w + 3]) >> 2;
		a *= fade_rate;

		last_row [x] = (byte) r;
		last_row [x + 1] = (byte) g;
		last_row [x + 2] = (byte) b;
		last_row [x + 3] = (byte) a;

		last_row = cur_row;
	}

	//	make sure bottom row has 0 alpha, cause it seems to wrap around to
	//	the top of the sprite

	bottom_row = (unsigned *) texture->data + texture->width * (texture->height - 1);
	for (x = 0; x < texture->width; x++)
		bottom_row [x] = 0;

	//	upload texture here, SHOULD BE BOUND ALREADY!!!
//	GL_Upload32 ((unsigned *) texture->data, texture->width, texture->height, false);
	qglTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height, GL_RGBA, GL_UNSIGNED_BYTE, texture->data);
}


///////////////////////////////////////////////////////////////////////////////
//	protex_DoStatic
//
///////////////////////////////////////////////////////////////////////////////

void	protex_DoStatic (proTex_t *texture)
{
	int		i, size, r, w, x;
	byte	*data;

	w = texture->width * texture->bit_depth;
	size = texture->height * w;
	data = texture->data;

	for (i = 0; i < size; i++)
		data [i] = rand () % 255;

	//	draw a random number of lines across texture with
	//	random colors

	r = rand () % (texture->height >> 2);
	if (r)
	{
		for (i = 0; i < r; i++)
		{
			data = texture->data + (w * (rand () % texture->height));

			r = rand () % 255;
			for (x = 0; x < w; x += texture->bit_depth)
			{
				data [x] = data [x + 1] = data [x + 2] = r;
				data [x + 3] = 255;
			}
		}
	}

	//	upload texture here, SHOULD BE BOUND ALREADY!!!
	qglTexSubImage2D (GL_TEXTURE_2D, 0, 0, 0, texture->width, texture->height, GL_RGBA, GL_UNSIGNED_BYTE, texture->data);
}

///////////////////////////////////////////////////////////////////////////////
//	protex_Alloc
//
///////////////////////////////////////////////////////////////////////////////

void	protex_Alloc (proTex_t *texture, int w, int h, int bit_depth, float frame_time, void (*func)(proTex_t *texture))
{
	texture->width = w;
	texture->height = h;
	texture->bit_depth = bit_depth;
	
	texture->data = malloc (w * h * bit_depth);
	memset (texture->data, 0x00, (w * h * bit_depth));

	texture->flags = 0;
	texture->func = func;
	texture->frame_time = frame_time;
	texture->next_time = ref_globalTime + frame_time;
}

///////////////////////////////////////////////////////////////////////////////
//	protex_LoadTexture
//
//	allocates a texture and sets it up as an image_t
///////////////////////////////////////////////////////////////////////////////

void	protex_LoadTexture (char *name, proTex_t *texture, int w, int h, int bit_depth, float frame_time, void (*func)(proTex_t *texture), int type)
{
	protex_Alloc (texture, w, h, bit_depth, frame_time, func);
	texture->image = GL_LoadPic (name, texture->data, w, h, it_procedural, 32, NULL);
	//	set type for later reference in protex_GL_Bind
	texture->image->pt_type = type;
}

///////////////////////////////////////////////////////////////////////////////
//	protex_Free
//
///////////////////////////////////////////////////////////////////////////////

void	protex_Free (proTex_t *texture)
{
	if (!texture->data)
		return;

	free (texture->data);
	texture->data = NULL;
}

///////////////////////////////////////////////////////////////////////////////
//	protex_InitTextures
//
///////////////////////////////////////////////////////////////////////////////

void	protex_InitTextures (void)
{
	memset (protex_textures, 0x00, sizeof (proTex_t) * PROTEX_NUM_TYPES);
/*
	//	just malloc up the memory for these textures
	protex_Alloc (&protex_textures [PROTEX_FIRE], 32, 64, 4, 0.05, protex_DoFire);
	protex_Alloc (&protex_textures [PROTEX_STATIC], 64, 64, 4, 0.05, protex_DoStatic);
	protex_Alloc (&protex_textures [PROTEX_STEAM], 64, 64, 4, 0.05, protex_DoSteam);
	protex_Alloc (&protex_textures [PROTEX_SWAMPGAS], 64, 64, 4, 0.05, protex_DoSwampGas);
*/
/*
	protex_Alloc (&protex_textures [PROTEX_FIRESKIN], 64, 64, 4, 0.05, protex_DoFireSkin);
	texture = &protex_textures [PROTEX_FIRESKIN];
	texture->image = GL_LoadPic ("fireskin", texture->data, texture->width, texture->height, it_procedural, 32, NULL);
	texture->image->pt_type = PROTEX_FIRESKIN;
*/
	protex_LoadTexture ("fireskin", &protex_textures [PROTEX_FIRESKIN], 64, 64, 4, 0.05, protex_DoFireSkin, PROTEX_FIRESKIN);
}

///////////////////////////////////////////////////////////////////////////////
//	protex_FreeTextures
//
///////////////////////////////////////////////////////////////////////////////

void	protex_FreeTextures (void)
{
	int		i;

	for (i = 0; i < PROTEX_NUM_TYPES; i++)
		protex_Free (&protex_textures [i]);
}

///////////////////////////////////////////////////////////////////////////////
//	protex_InitForFrame
//
//	clear run_this_frame flag for all textures
///////////////////////////////////////////////////////////////////////////////

void	protex_InitForFrame (void)
{
	int			i;

	for (i = 0; i < PROTEX_NUM_TYPES; i++)
	{
		//	clear RUN_THIS_FRAME flag
		protex_textures [i].flags &= ~PTF_RAN_THIS_FRAME;
	}
}

///////////////////////////////////////////////////////////////////////////////
//	protex_InitProceduralFromWAL
//
///////////////////////////////////////////////////////////////////////////////

image_t	*protex_InitProceduralFromWAL (const char *name, int width, int height)
{
	char		fname [_MAX_FNAME];
	proTex_t	*texture;
	protex_type	type;
	void		(*func)(proTex_t *texture);

	_splitpath (name, NULL, NULL, fname, NULL);
	
	//	find the correct texture based on the name
	if (!stricmp (fname, "pt_fire1"))
	{
		func = protex_DoFire;
		type = PROTEX_FIRE;
	}
	else if (!stricmp (fname, "pt_steam1"))
	{
		func = protex_DoSteam;
		type = PROTEX_STEAM;
	}
	else if (!stricmp (fname, "pt_static1"))
	{
		func = protex_DoStatic;
		type = PROTEX_STATIC;
	}
	else if (!stricmp (fname, "pt_swampgas1"))
	{
		func = protex_DoSwampGas;
		type = PROTEX_SWAMPGAS;
	}

	texture = &protex_textures [type];

//	now done in protex_InitTextures
	protex_Alloc (texture, width, height, 4, 0.05, func);

	//	set up image_t in global gltextures
	texture->image = GL_LoadPic (name, texture->data, width, height, it_procedural, 32, NULL);

	//	set type for later reference in protex_GL_Bind
	texture->image->pt_type = type;

	return	texture->image;
}

///////////////////////////////////////////////////////////////////////////////
//	protex_RunProceduralTexture
//
///////////////////////////////////////////////////////////////////////////////

void	protex_RunProceduralTexture (proTex_t *texture)
{
	//	limit procedural textures to 20 fps
	if (ref_globalTime >= texture->next_time)
	{
		if (!(texture->flags & PTF_RAN_THIS_FRAME))
		{
			texture->func (texture);

			texture->flags |= PTF_RAN_THIS_FRAME;
			texture->next_time = ref_globalTime + texture->frame_time;
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
//	protex_GL_Bind
//
//	just like GL_Bind, but this checks for a procedural texture type and runs
//	the procedural function if necessary and it has an image_t parameter instead
//	of and int for texnum
///////////////////////////////////////////////////////////////////////////////

void protex_GL_Bind (image_t *image)
{
	extern	image_t	*draw_chars;
	int		texnum;

	texnum = image->texnum;

	if (gl_nobind->value && draw_chars)		// performance evaluation option
	{
		if (protex_textures [PROTEX_FIRE].image)
			texnum = protex_textures [PROTEX_FIRE].image->texnum;
		else
			texnum = draw_chars->texnum;
	}
//		texnum = draw_chars->texnum;

	////////////////////////////////////////////////////////////////////////////
	//	if this is a procedural texture, then run the procedural function here
	//	this will only happen once per frame MAX for each procedural texture
	////////////////////////////////////////////////////////////////////////////

	//	don't bind if already current texture
	if ( gl_state.currenttextures [gl_state.currenttmu] == texnum)
	{
		if (image->type == it_procedural)
			protex_RunProceduralTexture (&protex_textures [image->pt_type]);
		return;
	}

	gl_state.currenttextures[gl_state.currenttmu] = texnum;

	qglBindTexture (GL_TEXTURE_2D, texnum);

	if (image->type == it_procedural)
		protex_RunProceduralTexture (&protex_textures [image->pt_type]);
}

///////////////////////////////////////////////////////////////////////////////
//	protex_GL_Bind
//
//	just like GL_Bind, but this checks for a procedural texture type and runs
//	the procedural function if necessary and it has an image_t parameter instead
//	of and int for texnum
///////////////////////////////////////////////////////////////////////////////

void protex_GL_MBind (GLenum target, image_t *image)
{
	GL_SelectTexture (target);

	////////////////////////////////////////////////////////////////////////////
	//	if this is a procedural texture, then run the procedural function here
	//	this will only happen once per frame MAX for each procedural texture
	////////////////////////////////////////////////////////////////////////////

	if (target == GL_TEXTURE0_SGIS)
	{
		if (gl_state.currenttextures[0] == image->texnum)
		{
			if (image->type == it_procedural)
				protex_RunProceduralTexture (&protex_textures [image->pt_type]);
			return;
		}
	}
	else
	{
		if (gl_state.currenttextures[1] == image->texnum)
		{
			if (image->type == it_procedural)
				protex_RunProceduralTexture (&protex_textures [image->pt_type]);
			return;
		}
	}
	
	GL_Bind (image->texnum);

	if (image->type == it_procedural)
		protex_RunProceduralTexture (&protex_textures [image->pt_type]);
}