/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/
// gl_rmisc.c

#include "quakedef.h"


/*
void R_InitOtherTextures (void)
{
	int	flags = TEX_MIPMAP | TEX_ALPHA;

	underwatertexture = GL_LoadTextureImage ("textures/water_caustic", NULL, 0, 0,  flags | (gl_waterfog.value ? TEX_COMPLAIN : 0));
	detailtexture = GL_LoadTextureImage ("textures/detail", NULL, 256, 256, flags | (gl_detail.value ? TEX_COMPLAIN : 0));
	chrometexture2 = GL_LoadTextureImage ("textures/shiny1", 0, 0, false, true);		// JT030105 - reflections
}
*/

void fractalnoise(byte *noise, int size, int startgrid)
{
   int x, y, g, g2, amplitude, min, max, size1 = size - 1, sizepower, gridpower;
   int *noisebuf;
#define n(x,y) noisebuf[((y)&size1)*size+((x)&size1)]

   for (sizepower = 0;(1 << sizepower) < size;sizepower++);
   if (size != (1 << sizepower))
      Sys_Error("fractalnoise: size must be power of 2\n");

   for (gridpower = 0;(1 << gridpower) < startgrid;gridpower++);
   if (startgrid != (1 << gridpower))
      Sys_Error("fractalnoise: grid must be power of 2\n");

   startgrid = bound(0, startgrid, size);

   amplitude = 0xFFFF; // this gets halved before use
   noisebuf = malloc(size*size*sizeof(int));
   memset(noisebuf, 0, size*size*sizeof(int));

   for (g2 = startgrid;g2;g2 >>= 1)
   {
      // brownian motion (at every smaller level there is random behavior)
      amplitude >>= 1;
      for (y = 0;y < size;y += g2)
         for (x = 0;x < size;x += g2)
            n(x,y) += (rand()&amplitude);

      g = g2 >> 1;
      if (g)
      {
         // subdivide, diamond-square algorithm (really this has little to do with squares)
         // diamond
         for (y = 0;y < size;y += g2)
            for (x = 0;x < size;x += g2)
               n(x+g,y+g) = (n(x,y) + n(x+g2,y) + n(x,y+g2) + n(x+g2,y+g2)) >> 2;
         // square
         for (y = 0;y < size;y += g2)
            for (x = 0;x < size;x += g2)
            {
               n(x+g,y) = (n(x,y) + n(x+g2,y) + n(x+g,y-g) + n(x+g,y+g)) >> 2;
               n(x,y+g) = (n(x,y) + n(x,y+g2) + n(x-g,y+g) + n(x+g,y+g)) >> 2;
            }
      }
   }
   // find range of noise values
   min = max = 0;
   for (y = 0;y < size;y++)
      for (x = 0;x < size;x++)
      {
         if (n(x,y) < min) min = n(x,y);
         if (n(x,y) > max) max = n(x,y);
      }
   max -= min;
   max++;
   // normalize noise and copy to output
   for (y = 0;y < size;y++)
      for (x = 0;x < size;x++)
         *noise++ = (byte) (((n(x,y) - min) * 256) / max);
   free(noisebuf);
#undef n
}


void Mod_BuildDetailTexture (void)
{
   int x, y, light;
   float vc[3], vx[3], vy[3], vn[3], lightdir[3];

#define DETAILRESOLUTION 256

   byte data[DETAILRESOLUTION][DETAILRESOLUTION][4], noise[DETAILRESOLUTION][DETAILRESOLUTION];
   lightdir[0] = 0.5;
   lightdir[1] = 1;
   lightdir[2] = -0.25;
   VectorNormalize(lightdir);

   fractalnoise(&noise[0][0], DETAILRESOLUTION, DETAILRESOLUTION >> 4);
   for (y = 0;y < DETAILRESOLUTION;y++)
   {
      for (x = 0;x < DETAILRESOLUTION;x++)
      {
         vc[0] = x;
         vc[1] = y;
         vc[2] = noise[y][x] * (1.0f / 32.0f);
         vx[0] = x + 1;
         vx[1] = y;
         vx[2] = noise[y][(x + 1) % DETAILRESOLUTION] * (1.0f / 32.0f);
         vy[0] = x;
         vy[1] = y + 1;
         vy[2] = noise[(y + 1) % DETAILRESOLUTION][x] * (1.0f / 32.0f);
         VectorSubtract(vx, vc, vx);
         VectorSubtract(vy, vc, vy);
         CrossProduct(vx, vy, vn);
         VectorNormalize(vn);
         light = 128 - DotProduct(vn, lightdir) * 128;
         light = bound(0, light, 255);
         data[y][x][0] = data[y][x][1] = data[y][x][2] = light;
         data[y][x][3] = 255;
      }
   }

   detailtexture = texture_extension_number++;

   GL_Bind (detailtexture);
   GL_Upload32 ((unsigned int *)data, DETAILRESOLUTION, DETAILRESOLUTION, TEX_MIPMAP);
}



void R_InitOtherTextures (void)
{
   int   flags = TEX_MIPMAP | TEX_ALPHA;

   underwatertexture = GL_LoadTextureImage ("textures/water_caustic", NULL, 0, 0,  flags | (gl_waterfog.value ? TEX_COMPLAIN : 0));

   Mod_BuildDetailTexture ();
} 


/*
==================
R_InitTextures
==================
*/
void R_InitTextures (void)
{
	int	x, y, m;
	byte	*dest;

// create a simple checkerboard texture for the default
	r_notexture_mip = Hunk_AllocName (sizeof(texture_t) + 16*16+8*8+4*4+2*2, "notexture");

	r_notexture_mip->width = r_notexture_mip->height = 16;
	r_notexture_mip->offsets[0] = sizeof(texture_t);
	r_notexture_mip->offsets[1] = r_notexture_mip->offsets[0] + 16*16;
	r_notexture_mip->offsets[2] = r_notexture_mip->offsets[1] + 8*8;
	r_notexture_mip->offsets[3] = r_notexture_mip->offsets[2] + 4*4;

	for (m=0 ; m<4 ; m++)
	{
		dest = (byte *)r_notexture_mip + r_notexture_mip->offsets[m];
		for (y=0 ; y<(16>>m) ; y++)
			for (x=0 ; x<(16>>m) ; x++)
				*dest++ = ((y < (8 >> m)) ^ (x < (8 >> m))) ? 0 : 0x0e;
	}
}

int	fb_skins[MAX_SCOREBOARD];	// by joe

/*
===============
R_TranslatePlayerSkin

Translates a skin texture by the per-player color lookup
===============
*/
void R_TranslatePlayerSkin (int playernum)
{
	int		top, bottom, i, j, size, scaled_width, scaled_height, inwidth, inheight;
	byte		translate[256], *original, *inrow;
	unsigned	translate32[256], pixels[512*256], *out, frac, fracstep;
	model_t		*model;
	aliashdr_t	*paliashdr;
	extern qboolean	Img_HasFullbrights (byte *pixels, int size);

	GL_DisableMultitexture ();

	top = cl.scores[playernum].colors & 0xf0;
	bottom = (cl.scores[playernum].colors & 15) << 4;

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

	for (i=0 ; i<16 ; i++)
	{
		// the artists made some backward ranges. sigh.
		translate[TOP_RANGE+i] = (top < 128) ? top + i : top + 15 - i;
		translate[BOTTOM_RANGE+i] = (bottom < 128) ? bottom + i : bottom + 15 - i;
	}

	// locate the original skin pixels
	currententity = &cl_entities[1+playernum];
	if (!(model = currententity->model))
		return;		// player doesn't have a model yet
	if (model->type != mod_alias)
		return;		// only translate skins on alias models

	paliashdr = (aliashdr_t *)Mod_Extradata (model);
	size = paliashdr->skinwidth * paliashdr->skinheight;
	if (currententity->skinnum < 0 || currententity->skinnum >= paliashdr->numskins)
	{
		Con_DPrintf ("(%d): Invalid player skin #%d\n", playernum, currententity->skinnum);
		original = (byte *)paliashdr + paliashdr->texels[0];
	}
	else
	{
		original = (byte *)paliashdr + paliashdr->texels[currententity->skinnum];
	}

	if (size & 3)
		Sys_Error ("R_TranslatePlayerSkin: size & 3");

	inwidth = paliashdr->skinwidth;
	inheight = paliashdr->skinheight;

	// because this happens during gameplay, do it fast
	// instead of sending it through gl_upload 8
	GL_Bind (playertextures + playernum);

	scaled_width = gl_picmip_all.value ? min(gl_max_size.value, 512) : min(gl_max_size_default, 512);
	scaled_height = gl_picmip_all.value ? min(gl_max_size.value, 256) : min(gl_max_size_default, 256);

	// allow users to crunch sizes down even more if they want
	scaled_width >>= max(1, (int)gl_playermip.value);
	scaled_height >>= max(1, (int)gl_playermip.value);

	for (i=0 ; i<256 ; i++)
		translate32[i] = d_8to24table[translate[i]];

	out = pixels;
	memset (pixels, 0, sizeof(pixels));
	fracstep = (inwidth << 16) / scaled_width;

	for (i = 0 ; i < scaled_height ; i++, out += scaled_width)
	{
		inrow = original + inwidth * (i * inheight / scaled_height);
		frac = fracstep >> 1;
		for (j = 0 ; j < scaled_width ; j += 4)
		{
			out[j] = translate32[inrow[frac>>16]];
			frac += fracstep;
			out[j+1] = translate32[inrow[frac>>16]];
			frac += fracstep;
			out[j+2] = translate32[inrow[frac>>16]];
			frac += fracstep;
			out[j+3] = translate32[inrow[frac>>16]];
			frac += fracstep;
		}
	}

	glTexImage2D (GL_TEXTURE_2D, 0, gl_solid_format, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

	glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	fb_skins[playernum] = 0;
	if (Img_HasFullbrights(original, inwidth*inheight))
	{
		fb_skins[playernum] = playertextures + playernum + MAX_SCOREBOARD;

		GL_Bind (fb_skins[playernum]);

		out = pixels;
		memset (pixels, 0, sizeof(pixels));
		fracstep = (inwidth << 16) / scaled_width;

		// make all non-fullbright colors transparent
		for (i = 0 ; i < scaled_height ; i++, out += scaled_width)
		{
			inrow = original + inwidth * (i * inheight / scaled_height);
			frac = fracstep >> 1;
			for (j = 0 ; j < scaled_width ; j += 4)
			{
				if (inrow[frac>>16] < 224)
					out[j] = translate32[inrow[frac>>16]] & 0x00FFFFFF; // transparent
				else
					out[j] = translate32[inrow[frac>>16]]; // fullbright
				frac += fracstep;
				if (inrow[frac>>16] < 224)
					out[j+1] = translate32[inrow[frac>>16]] & 0x00FFFFFF;
				else
					out[j+1] = translate32[inrow[frac>>16]];
				frac += fracstep;
				if (inrow[frac>>16] < 224)
					out[j+2] = translate32[inrow[frac>>16]] & 0x00FFFFFF;
				else
					out[j+2] = translate32[inrow[frac>>16]];
				frac += fracstep;
				if (inrow[frac>>16] < 224)
					out[j+3] = translate32[inrow[frac>>16]] & 0x00FFFFFF;
				else
					out[j+3] = translate32[inrow[frac>>16]];
				frac += fracstep;
			}
		}

		glTexImage2D (GL_TEXTURE_2D, 0, gl_alpha_format, scaled_width, scaled_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);

		glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameterf (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	}
}

// joe: added from FuhQuake
void R_PreMapLoad (char *mapname)
{
	Cvar_Set ("mapname", mapname);
	lightmode = 2;
}

/*
===============
R_NewMap
===============
*/
void R_NewMap (void)
{
	int	i, waterline;
	
	for (i=0 ; i<256 ; i++)
		d_lightstylevalue[i] = 264;		// normal light value

	memset (&r_worldentity, 0, sizeof(r_worldentity));
	r_worldentity.model = cl.worldmodel;

	// clear out efrags in case the level hasn't been reloaded
	// FIXME: is this one short?
	for (i=0 ; i<cl.worldmodel->numleafs ; i++)
		cl.worldmodel->leafs[i].efrags = NULL;
		 	
	r_viewleaf = NULL;
	R_ClearParticles ();

	GL_BuildLightmaps ();

	// identify sky texture
	for (i=0 ; i<cl.worldmodel->numtextures ; i++)
	{
		if (!cl.worldmodel->textures[i])
			continue;
		for (waterline=0 ; waterline<2 ; waterline++)
		{
 			cl.worldmodel->textures[i]->texturechain[waterline] = NULL;
			cl.worldmodel->textures[i]->texturechain_tail[waterline] = &cl.worldmodel->textures[i]->texturechain[waterline];
		}
	}
}

/*
====================
R_TimeRefresh_f

For program optimization
====================
*/
void R_TimeRefresh_f (void)
{
	int	i;
	float	start, stop, time;

	glDrawBuffer (GL_FRONT);
	glFinish ();

	start = Sys_DoubleTime ();
	for (i=0 ; i<128 ; i++)
	{
		r_refdef.viewangles[1] = i * (360.0 / 128.0);
		R_RenderView ();
	}

	glFinish ();
	stop = Sys_DoubleTime ();
	time = stop - start;
	Con_Printf ("%f seconds (%f fps)\n", time, 128/time);

	glDrawBuffer (GL_BACK);
	GL_EndRendering ();
}

void D_FlushCaches (void)
{
}
