/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code 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.

Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// tr_shade_calc.c

#include "tr_local.h"


// input's in the [0;1] range
typedef double (*waveGenFunc_t)( double );


static double WaveGenSine( double x )
{
	return sin(x * 2.0 * M_PI);
}


static double WaveGenSquare( double x )
{
	return x < 0.5 ? 1.0 : -1.0;
}


static double WaveGenTriangle( double xs )
{
	// the original table shifts the triangle for whatever reason...
	const double x = (xs < 0.75) ? (xs + 0.25) : (xs - 0.75);
	const double l = x * 4.0 - 1.0;
	const double r = 1.0 - (x - 0.5) * 4.0;
	const double f = (x < 0.5) ? l : r;

	return f;
}


static double WaveGenSawTooth( double x )
{
	return x;
}


static double WaveGenInverseSawTooth( double x )
{
	return 1.0 - x;
}


static double WaveGenInvalid( double x )
{
	ri.Error( ERR_DROP, "WaveGenInvalid called in shader '%s'\n", tess.shader->name );
	return 0.0;
}


static double WaveValue( genFunc_t func , double base, double amplitude, double phase, double freq )
{
	static const waveGenFunc_t waveGenFunctions[GF_COUNT] =
	{
		WaveGenInvalid, // GF_NULL
		WaveGenSine,
		WaveGenSquare,
		WaveGenTriangle,
		WaveGenSawTooth,
		WaveGenInverseSawTooth,
		WaveGenInvalid // GF_NOISE
	};

	// fmod doesn't behave how we want with negative numerators
	// fmod(-2.25, 1) returns -0.25 but we really want 0.75
	const double xo = phase + tess.shaderTime * freq;
	const double xr = (double)(int)xo; // rounded towards 0
	const double x = xo >= 0.0 ? (xo - xr) : (xo - xr + 1.0);
	const double r = base + (waveGenFunctions[func])(x) * amplitude;

	return r;
}


// Evaluates a given waveForm_t, referencing backEnd.refdef.time directly
static float EvalWaveForm( const waveForm_t *wf )
{
	return WaveValue( wf->func, wf->base, wf->amplitude, wf->phase, wf->frequency );
}


static float EvalWaveFormClamped( const waveForm_t *wf )
{
	float glow  = EvalWaveForm( wf );

	if ( glow < 0 )
	{
		return 0;
	}

	if ( glow > 1 )
	{
		return 1;
	}

	return glow;
}


static void RB_CalcTransformTexCoords( const texModInfo_t *tmi, float *st, int numVertexes )
{
	int i;

	for ( i = 0; i < numVertexes; i++, st += 2 )
	{
		float s = st[0];
		float t = st[1];

		st[0] = s * tmi->matrix[0][0] + t * tmi->matrix[1][0] + tmi->translate[0];
		st[1] = s * tmi->matrix[0][1] + t * tmi->matrix[1][1] + tmi->translate[1];
	}
}


static void RB_CalcStretchTexCoords( const waveForm_t *wf, float *st, int numVertexes )
{
	float p;
	texModInfo_t tmi;

	p = 1.0f / EvalWaveForm( wf );

	tmi.matrix[0][0] = p;
	tmi.matrix[1][0] = 0;
	tmi.translate[0] = 0.5f - 0.5f * p;

	tmi.matrix[0][1] = 0;
	tmi.matrix[1][1] = p;
	tmi.translate[1] = 0.5f - 0.5f * p;

	RB_CalcTransformTexCoords( &tmi, st, numVertexes );
}


static void RB_CalcDeformVertexes( const deformStage_t* ds, int firstVertex, int numVertexes )
{
	float* xyz = (float*)&tess.xyz[firstVertex];
	float* normal = (float*)&tess.normal[firstVertex];
	vec3_t offset;

	if ( ds->deformationWave.frequency == 0 )
	{
		const float scale = EvalWaveForm( &ds->deformationWave );

		for ( int i = 0; i < numVertexes; i++, xyz += 4, normal += 4 )
		{
			VectorScale( normal, scale, offset );
			
			xyz[0] += offset[0];
			xyz[1] += offset[1];
			xyz[2] += offset[2];
		}
	}
	else
	{
		for ( int i = 0; i < numVertexes; i++, xyz += 4, normal += 4 )
		{
			const float off = ( xyz[0] + xyz[1] + xyz[2] ) * ds->deformationSpread;
			const float scale = WaveValue( ds->deformationWave.func, ds->deformationWave.base, 
				ds->deformationWave.amplitude,
				ds->deformationWave.phase + off,
				ds->deformationWave.frequency );

			VectorScale( normal, scale, offset );
			
			xyz[0] += offset[0];
			xyz[1] += offset[1];
			xyz[2] += offset[2];
		}
	}
}


// wiggle the normals for wavy environment mapping
static void RB_CalcDeformNormals( const deformStage_t* ds, int firstVertex, int numVertexes )
{
	int i;
	float scale;
	const float *xyz = ( const float * ) &tess.xyz[firstVertex];
	float *normal = ( float * ) &tess.normal[firstVertex];

	for ( i = 0; i < numVertexes; i++, xyz += 4, normal += 4 ) {
		scale = 0.98f;
		scale = R_NoiseGet4f( xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
			tess.shaderTime * ds->deformationWave.frequency );
		normal[ 0 ] += ds->deformationWave.amplitude * scale;

		scale = 0.98f;
		scale = R_NoiseGet4f( 100 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
			tess.shaderTime * ds->deformationWave.frequency );
		normal[ 1 ] += ds->deformationWave.amplitude * scale;

		scale = 0.98f;
		scale = R_NoiseGet4f( 200 + xyz[0] * scale, xyz[1] * scale, xyz[2] * scale,
			tess.shaderTime * ds->deformationWave.frequency );
		normal[ 2 ] += ds->deformationWave.amplitude * scale;

		VectorNormalizeFast( normal );
	}
}


static void RB_CalcBulgeVertexes( const deformStage_t* ds, int firstVertex, int numVertexes )
{
	const float* st = (const float*)&tess.texCoords[firstVertex];
	float* xyz = (float*)&tess.xyz[firstVertex];
	float* normal = (float*)&tess.normal[firstVertex];

	const float now = (backEnd.refdef.time / 1000.0f) * ds->bulgeSpeed;

	for ( int i = 0; i < numVertexes; i++, xyz += 4, st += 2, normal += 4 ) {
		const float scale = ds->bulgeHeight * sin(st[0] * ds->bulgeWidth + now);
			
		xyz[0] += normal[0] * scale;
		xyz[1] += normal[1] * scale;
		xyz[2] += normal[2] * scale;
	}
}


// a deformation that can move an entire surface along a wave path
static void RB_CalcMoveVertexes( const deformStage_t* ds, int firstVertex, int numVertexes )
{
	const double scale = WaveValue(
		ds->deformationWave.func,
		ds->deformationWave.base, 
		ds->deformationWave.amplitude,
		ds->deformationWave.phase,
		ds->deformationWave.frequency );

	vec3_t offset;
	VectorScale( ds->moveVector, scale, offset );

	float* xyz = (float*)&tess.xyz[firstVertex];
	for ( int i = 0; i < numVertexes; i++, xyz += 4 ) {
		VectorAdd( xyz, offset, xyz );
	}
}


// @TODO:
// Change a polygon into a bunch of text polygons
static void DeformText( const char *text ) {
	int		i;
	vec3_t	origin, width, height;
	int		len;
	int		ch;
	byte	color[4];
	float	bottom, top;
	vec3_t	mid;

	height[0] = 0;
	height[1] = 0;
	height[2] = -1;
	CrossProduct( tess.normal[0], height, width );

	// find the midpoint of the box
	VectorClear( mid );
	bottom = 999999;
	top = -999999;
	for ( i = 0 ; i < 4 ; i++ ) {
		VectorAdd( tess.xyz[i], mid, mid );
		if ( tess.xyz[i][2] < bottom ) {
			bottom = tess.xyz[i][2];
		}
		if ( tess.xyz[i][2] > top ) {
			top = tess.xyz[i][2];
		}
	}
	VectorScale( mid, 0.25f, origin );

	// determine the individual character size
	height[0] = 0;
	height[1] = 0;
	height[2] = ( top - bottom ) * 0.5f;

	VectorScale( width, height[2] * -0.75f, width );

	// determine the starting position
	len = strlen( text );
	VectorMA( origin, (len-1), width, origin );

	// clear the shader indexes
	tess.numIndexes = 0;
	tess.numVertexes = 0;

	color[0] = color[1] = color[2] = color[3] = 255;

	// draw each character
	for ( i = 0 ; i < len ; i++ ) {
		ch = text[i];
		ch &= 255;

		if ( ch != ' ' ) {
			int		row, col;
			float	frow, fcol, size;

			row = ch>>4;
			col = ch&15;

			frow = row*0.0625f;
			fcol = col*0.0625f;
			size = 0.0625f;

			RB_AddQuadStampExt( origin, width, height, color, fcol, frow, fcol + size, frow + size );
		}
		VectorMA( origin, -2, width, origin );
	}
}


static void GlobalVectorToLocal( const vec3_t in, vec3_t out )
{
	out[0] = DotProduct( in, backEnd.orient.axis[0] );
	out[1] = DotProduct( in, backEnd.orient.axis[1] );
	out[2] = DotProduct( in, backEnd.orient.axis[2] );
}


// assuming all the triangles for this shader are independant quads,
// rebuild them as forward facing sprites
static void AutospriteDeform( int firstVertex, int numVertexes, int firstIndex, int numIndexes )
{
	int		i;
	float	*xyz;
	vec3_t	mid, delta;
	float	radius;
	vec3_t	left, up;
	vec3_t	leftDir, upDir;

	if ( numVertexes & 3 ) {
		ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd vertex count", tess.shader->name );
	}
	if ( numIndexes != ( numVertexes >> 2 ) * 6 ) {
		ri.Printf( PRINT_WARNING, "Autosprite shader %s had odd index count", tess.shader->name );
	}

	tess.numVertexes = firstVertex;
	tess.numIndexes = firstIndex;

	if ( backEnd.currentEntity != &tr.worldEntity ) {
		GlobalVectorToLocal( backEnd.viewParms.orient.axis[1], leftDir );
		GlobalVectorToLocal( backEnd.viewParms.orient.axis[2], upDir );
	} else {
		VectorCopy( backEnd.viewParms.orient.axis[1], leftDir );
		VectorCopy( backEnd.viewParms.orient.axis[2], upDir );
	}

	for ( i = firstVertex ; i < firstVertex + numVertexes ; i+=4 ) {
		// find the midpoint
		xyz = tess.xyz[i];

		mid[0] = 0.25f * (xyz[0] + xyz[4] + xyz[8] + xyz[12]);
		mid[1] = 0.25f * (xyz[1] + xyz[5] + xyz[9] + xyz[13]);
		mid[2] = 0.25f * (xyz[2] + xyz[6] + xyz[10] + xyz[14]);

		VectorSubtract( xyz, mid, delta );
		radius = VectorLength( delta ) * 0.707f;		// / sqrt(2)

		VectorScale( leftDir, radius, left );
		VectorScale( upDir, radius, up );

		if ( backEnd.viewParms.isMirror ) {
			VectorSubtract( vec3_origin, left, left );
		}

		// compensate for scale in the axes if necessary
		if ( backEnd.currentEntity->e.nonNormalizedAxes ) {
			float axisLength = VectorLength( backEnd.currentEntity->e.axis[0] );
			axisLength = axisLength ? (1.0f / axisLength) : 1.0f;
			VectorScale(left, axisLength, left);
			VectorScale(up, axisLength, up);
		}

		RB_AddQuadStamp( mid, left, up, tess.vertexColors[i] );
	}
}


// Autosprite2 will pivot a rectangular quad along the center of its long axis
static void Autosprite2Deform( int firstVertex, int numVertexes, int firstIndex, int numIndexes ) {
	int		i, j, k;
	int		indexes;
	float	*xyz;
	vec3_t	forward;

	const int edgeVerts[6][2] = {
		{ 0, 1 },
		{ 0, 2 },
		{ 0, 3 },
		{ 1, 2 },
		{ 1, 3 },
		{ 2, 3 }
	};

	if ( numVertexes & 3 ) {
		ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd vertex count", tess.shader->name );
	}
	if ( numIndexes != ( numVertexes >> 2 ) * 6 ) {
		ri.Printf( PRINT_WARNING, "Autosprite2 shader %s had odd index count", tess.shader->name );
	}

	if ( backEnd.currentEntity != &tr.worldEntity ) {
		GlobalVectorToLocal( backEnd.viewParms.orient.axis[0], forward );
	} else {
		VectorCopy( backEnd.viewParms.orient.axis[0], forward );
	}

	// this is a lot of work for two triangles...
	// we could precalculate a lot of it is an issue, but it would mess up
	// the shader abstraction
	for ( i = firstVertex, indexes = firstIndex ; i < firstVertex + numVertexes ; i+=4, indexes+=6 ) {
		float	lengths[2];
		int		nums[2];
		vec3_t	mid[2];
		vec3_t	major, minor;
		float	*v1, *v2;

		// find the midpoint
		xyz = tess.xyz[i];

		// identify the two shortest edges
		nums[0] = nums[1] = 0;
		lengths[0] = lengths[1] = 999999;

		for ( j = 0 ; j < 6 ; j++ ) {
			float	l;
			vec3_t	temp;

			v1 = xyz + 4 * edgeVerts[j][0];
			v2 = xyz + 4 * edgeVerts[j][1];

			VectorSubtract( v1, v2, temp );
			
			l = DotProduct( temp, temp );
			if ( l < lengths[0] ) {
				nums[1] = nums[0];
				lengths[1] = lengths[0];
				nums[0] = j;
				lengths[0] = l;
			} else if ( l < lengths[1] ) {
				nums[1] = j;
				lengths[1] = l;
			}
		}

		for ( j = 0 ; j < 2 ; j++ ) {
			v1 = xyz + 4 * edgeVerts[nums[j]][0];
			v2 = xyz + 4 * edgeVerts[nums[j]][1];

			mid[j][0] = 0.5f * (v1[0] + v2[0]);
			mid[j][1] = 0.5f * (v1[1] + v2[1]);
			mid[j][2] = 0.5f * (v1[2] + v2[2]);
		}

		// find the vector of the major axis
		VectorSubtract( mid[1], mid[0], major );

		// cross this with the view direction to get minor axis
		CrossProduct( major, forward, minor );
		VectorNormalize( minor );
		
		// re-project the points
		for ( j = 0 ; j < 2 ; j++ ) {
			float	l;

			v1 = xyz + 4 * edgeVerts[nums[j]][0];
			v2 = xyz + 4 * edgeVerts[nums[j]][1];

			l = 0.5 * sqrt( lengths[j] );

			// we need to see which direction this edge
			// is used to determine direction of projection
			for ( k = 0 ; k < 5 ; k++ ) {
				if ( tess.indexes[ indexes + k ] == i + edgeVerts[nums[j]][0]
					&& tess.indexes[ indexes + k + 1 ] == i + edgeVerts[nums[j]][1] ) {
					break;
				}
			}

			if ( k == 5 ) {
				VectorMA( mid[j], l, minor, v1 );
				VectorMA( mid[j], -l, minor, v2 );
			} else {
				VectorMA( mid[j], -l, minor, v1 );
				VectorMA( mid[j], l, minor, v2 );
			}
		}
	}
}


void RB_DeformTessGeometry( int firstVertex, int numVertexes, int firstIndex, int numIndexes )
{
	int		i;
	const deformStage_t* ds;

	for ( i = 0 ; i < tess.shader->numDeforms ; i++ ) {
		ds = &tess.shader->deforms[ i ];

		switch ( ds->deformation ) {
		case DEFORM_NONE:
			break;
		case DEFORM_NORMALS:
			RB_CalcDeformNormals( ds, firstVertex, numVertexes );
			break;
		case DEFORM_WAVE:
			RB_CalcDeformVertexes( ds, firstVertex, numVertexes );
			break;
		case DEFORM_BULGE:
			RB_CalcBulgeVertexes( ds, firstVertex, numVertexes );
			break;
		case DEFORM_MOVE:
			RB_CalcMoveVertexes( ds, firstVertex, numVertexes );
			break;
		case DEFORM_PROJECTION_SHADOW:
			//RB_ProjectionShadowDeform();
			break;
		case DEFORM_AUTOSPRITE:
			AutospriteDeform( firstVertex, numVertexes, firstIndex, numIndexes );
			break;
		case DEFORM_AUTOSPRITE2:
			Autosprite2Deform( firstVertex, numVertexes, firstIndex, numIndexes );
			break;
		case DEFORM_TEXT0:
		case DEFORM_TEXT1:
		case DEFORM_TEXT2:
		case DEFORM_TEXT3:
		case DEFORM_TEXT4:
		case DEFORM_TEXT5:
		case DEFORM_TEXT6:
		case DEFORM_TEXT7:
			// @TODO:
			DeformText( backEnd.refdef.text[ds->deformation - DEFORM_TEXT0] );
			break;
		}
	}
}


static void RB_CalcColorFromEntity( unsigned char *dstColors, int numVertexes )
{
	int	i;
	int *pColors = ( int * ) dstColors;
	int c;

	if ( !backEnd.currentEntity )
		return;

	c = * ( int * ) backEnd.currentEntity->e.shaderRGBA;

	for ( i = 0; i < numVertexes; i++, pColors++ )
	{
		*pColors = c;
	}
}


static void RB_CalcColorFromOneMinusEntity( unsigned char *dstColors, int numVertexes )
{
	int	i;
	int *pColors = ( int * ) dstColors;
	unsigned char invModulate[4];
	int c;

	if ( !backEnd.currentEntity )
		return;

	invModulate[0] = 255 - backEnd.currentEntity->e.shaderRGBA[0];
	invModulate[1] = 255 - backEnd.currentEntity->e.shaderRGBA[1];
	invModulate[2] = 255 - backEnd.currentEntity->e.shaderRGBA[2];
	invModulate[3] = 255 - backEnd.currentEntity->e.shaderRGBA[3];	// this trashes alpha, but the AGEN block fixes it

	c = * ( int * ) invModulate;

	for ( i = 0; i < numVertexes; i++, pColors++ )
	{
		*pColors = * ( int * ) invModulate;
	}
}


static void RB_CalcAlphaFromEntity( unsigned char *dstColors, int numVertexes )
{
	int	i;

	if ( !backEnd.currentEntity )
		return;

	dstColors += 3;

	for ( i = 0; i < numVertexes; i++, dstColors += 4 )
	{
		*dstColors = backEnd.currentEntity->e.shaderRGBA[3];
	}
}


static void RB_CalcAlphaFromOneMinusEntity( unsigned char *dstColors, int numVertexes )
{
	int	i;

	if ( !backEnd.currentEntity )
		return;

	dstColors += 3;

	for ( i = 0; i < numVertexes; i++, dstColors += 4 )
	{
		*dstColors = 0xff - backEnd.currentEntity->e.shaderRGBA[3];
	}
}


static void RB_CalcWaveColor( const waveForm_t *wf, unsigned char *dstColors, int numVertexes )
{
	int i;
	int v;
	float glow;
	int *colors = ( int * ) dstColors;
	byte	color[4];

  if ( wf->func == GF_NOISE ) {
		glow = wf->base + R_NoiseGet4f( 0, 0, 0, ( tess.shaderTime + wf->phase ) * wf->frequency ) * wf->amplitude;
	} else {
		glow = EvalWaveForm( wf ) * tr.identityLight;
	}
	
	if ( glow < 0 ) {
		glow = 0;
	}
	else if ( glow > 1 ) {
		glow = 1;
	}

	v = (int)( 255 * glow );
	color[0] = color[1] = color[2] = v;
	color[3] = 255;
	v = *(int *)color;
	
	for ( i = 0; i < numVertexes; i++, colors++ ) {
		*colors = v;
	}
}


static void RB_CalcWaveAlpha( const waveForm_t *wf, unsigned char *dstColors, int numVertexes )
{
	int i;
	int v;
	float glow;

	glow = EvalWaveFormClamped( wf );

	v = 255 * glow;

	for ( i = 0; i < numVertexes; i++, dstColors += 4 )
	{
		dstColors[3] = v;
	}
}


static void RB_CalcEnvironmentTexCoords( float *st, int firstVertex, int numVertexes ) 
{
	int			i;
	float		*v, *normal;
	vec3_t		viewer, reflected;
	float		d;

	v = tess.xyz[firstVertex];
	normal = tess.normal[firstVertex];
	st += firstVertex * 2;

	for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, st += 2 ) 
	{
		VectorSubtract (backEnd.orient.viewOrigin, v, viewer);
		VectorNormalizeFast (viewer);

		d = DotProduct (normal, viewer);

		reflected[0] = normal[0]*2*d - viewer[0];
		reflected[1] = normal[1]*2*d - viewer[1];
		reflected[2] = normal[2]*2*d - viewer[2];

		st[0] = 0.5 + reflected[1] * 0.5;
		st[1] = 0.5 - reflected[2] * 0.5;
	}
}


static void RB_CalcTurbulentTexCoords( const waveForm_t *wf, float *st, int firstVertex, int numVertexes )
{
	vec4_t* const v = &tess.xyz[firstVertex];
	const double now = ( wf->phase + tess.shaderTime * wf->frequency );

	for ( int i = 0; i < numVertexes; i++, st += 2 )
	{
		st[0] += sin( ( ( v[i][0] + v[i][2] )* 1.0 / 128 * 0.125 + now ) * 2.0 * M_PI ) * wf->amplitude;
		st[1] += sin( ( v[i][1] * 1.0 / 128 * 0.125 + now ) * 2.0 * M_PI ) * wf->amplitude;
	}
}


static void RB_CalcScaleTexCoords( const float scale[2], float *st, int numVertexes )
{
	int i;

	for ( i = 0; i < numVertexes; i++, st += 2 )
	{
		st[0] *= scale[0];
		st[1] *= scale[1];
	}
}


static void RB_CalcScrollTexCoords( const float scrollSpeed[2], float *st, int numVertexes )
{
	int i;
	double timeScale = tess.shaderTime;
	double adjustedScrollS, adjustedScrollT;

	adjustedScrollS = (double)scrollSpeed[0] * timeScale;
	adjustedScrollT = (double)scrollSpeed[1] * timeScale;

	// clamp so coordinates don't continuously get larger, causing problems
	// with hardware limits
	adjustedScrollS = adjustedScrollS - floor( adjustedScrollS );
	adjustedScrollT = adjustedScrollT - floor( adjustedScrollT );

	for ( i = 0; i < numVertexes; i++, st += 2 )
	{
		st[0] += adjustedScrollS;
		st[1] += adjustedScrollT;
	}
}


static void RB_CalcRotateTexCoords( float degsPerSecond, float *st, int numVertexes )
{
	const double x = -degsPerSecond * (tess.shaderTime / 360.0) * 2.0 * M_PI;
	const double sinValue = sin(x);
	const double cosValue = cos(x);

    texModInfo_t tmi;
	tmi.matrix[0][0] = cosValue;
	tmi.matrix[1][0] = -sinValue;
	tmi.translate[0] = 0.5 - 0.5 * cosValue + 0.5 * sinValue;

	tmi.matrix[0][1] = sinValue;
	tmi.matrix[1][1] = cosValue;
	tmi.translate[1] = 0.5 - 0.5 * sinValue - 0.5 * cosValue;

	RB_CalcTransformTexCoords( &tmi, st, numVertexes );
}


/*
** RB_CalcSpecularAlpha
**
** Calculates specular coefficient and places it in the alpha channel
*/
vec3_t lightOrigin = { -960, 1980, 96 };		// FIXME: track dynamically

void RB_CalcSpecularAlpha( unsigned char *alphas, int firstVertex, int numVertexes ) {
	int			i;
	float		*v, *normal;
	vec3_t		viewer,  reflected;
	float		l, d;
	int			b;
	vec3_t		lightDir;

	v = tess.xyz[firstVertex];
	normal = tess.normal[firstVertex];
	alphas += (firstVertex * 4) + 3;

	for (i = 0 ; i < numVertexes ; i++, v += 4, normal += 4, alphas += 4) {
		float ilength;

		VectorSubtract( lightOrigin, v, lightDir );
		VectorNormalizeFast( lightDir );

		// calculate the specular color
		d = DotProduct (normal, lightDir);

		// we don't optimize for the d < 0 case since this tends to
		// cause visual artifacts such as faceted "snapping"
		reflected[0] = normal[0]*2*d - lightDir[0];
		reflected[1] = normal[1]*2*d - lightDir[1];
		reflected[2] = normal[2]*2*d - lightDir[2];

		VectorSubtract (backEnd.orient.viewOrigin, v, viewer);
		ilength = Q_rsqrt( DotProduct( viewer, viewer ) );
		l = DotProduct (reflected, viewer);
		l *= ilength;

		if (l < 0) {
			b = 0;
		} else {
			l = l*l;
			l = l*l;
			b = l * 255;
			if (b > 255) {
				b = 255;
			}
		}

		*alphas = b;
	}
}


static void RB_CalcDiffuseColor( unsigned char *colors, int firstVertex, int numVertexes )
{
	trRefEntity_t* const ent = backEnd.currentEntity;
	if (!ent || !numVertexes)
		return;

	int ambientLightInt = ent->ambientLightInt;
	vec3_t ambientLight, lightDir, directedLight;
	VectorCopy( ent->ambientLight, ambientLight );
	VectorCopy( ent->directedLight, directedLight );
	VectorCopy( ent->lightDir, lightDir );

	float* v = tess.xyz[firstVertex];
	float* normal = tess.normal[firstVertex];

	const float t = r_mapGreyscale->value;
	const float ti = 1.0f - t;

	// fix up ambientLightInt for the case where the dot product is 0 or negative
	vec3_t ambientLightMixed;
	const float ambientGrey = 0.299f * ambientLight[0] + 0.587f * ambientLight[1] + 0.114f * ambientLight[2];
	const float ambientGrey_t = ambientGrey * t;
	ambientLightMixed[0] = ambientLight[0] * ti + ambientGrey_t;
	ambientLightMixed[1] = ambientLight[1] * ti + ambientGrey_t;
	ambientLightMixed[2] = ambientLight[2] * ti + ambientGrey_t;
	((byte*)&ambientLightInt)[0] = ambientLightMixed[0];
	((byte*)&ambientLightInt)[1] = ambientLightMixed[1];
	((byte*)&ambientLightInt)[2] = ambientLightMixed[2];
	((byte*)&ambientLightInt)[3] = 0xFF;

	for (int i = 0 ; i < numVertexes ; i++, v += 4, normal += 4) {
		const float incoming = DotProduct( normal, lightDir );
		if ( incoming <= 0 ) {
			*(int*)&colors[i * 4] = ambientLightInt;
			continue;
		} 

		const float inR = ambientLight[0] + incoming * directedLight[0];
		const float inG = ambientLight[1] + incoming * directedLight[1];
		const float inB = ambientLight[2] + incoming * directedLight[2];
		const float grey = 0.299f * inR + 0.587f * inG + 0.114f * inB;
		const float grey_t = grey * t;
		const float outR = inR * ti + grey_t;
		const float outG = inG * ti + grey_t;
		const float outB = inB * ti + grey_t;
		colors[i * 4 + 0] = (byte)min( outR, 255.0f );
		colors[i * 4 + 1] = (byte)min( outG, 255.0f );
		colors[i * 4 + 2] = (byte)min( outB, 255.0f );
		colors[i * 4 + 3] = 255;
	}
}


void R_ComputeColors( const shaderStage_t* pStage, stageVars_t& svars, int firstVertex, int numVertexes )
{
	//
	// rgbGen
	//
	switch ( pStage->rgbGen )
	{
	case CGEN_IDENTITY:
		Com_Memset( &svars.colors[firstVertex], 0xff, numVertexes * 4 );
		break;
	default:
	case CGEN_IDENTITY_LIGHTING:
		Com_Memset( &svars.colors[firstVertex], tr.identityLightByte, numVertexes * 4 );
		break;
	case CGEN_LIGHTING_DIFFUSE:
		RB_CalcDiffuseColor( ( unsigned char * ) &svars.colors[firstVertex], firstVertex, numVertexes );
		break;
	case CGEN_CONST:
		for (int i = firstVertex; i < firstVertex + numVertexes; i++) {
			*(int *)svars.colors[i] = *(int *)pStage->constantColor;
		}
		break;
	case CGEN_VERTEX:
		if ( tr.identityLight == 1 )
		{
			Com_Memcpy( &svars.colors[firstVertex], &tess.vertexColors[firstVertex], numVertexes * sizeof( tess.vertexColors[0] ) );
		}
		else
		{
			for ( int i = firstVertex; i < firstVertex + numVertexes; i++ )
			{
				svars.colors[i][0] = tess.vertexColors[i][0] * tr.identityLight;
				svars.colors[i][1] = tess.vertexColors[i][1] * tr.identityLight;
				svars.colors[i][2] = tess.vertexColors[i][2] * tr.identityLight;
				svars.colors[i][3] = tess.vertexColors[i][3];
			}
		}
		break;
	case CGEN_EXACT_VERTEX:
		Com_Memcpy( &svars.colors[firstVertex], &tess.vertexColors[firstVertex], numVertexes * sizeof( tess.vertexColors[0] ) );
		break;
	case CGEN_ONE_MINUS_VERTEX:
		if ( tr.identityLight == 1 )
		{
			for ( int i = firstVertex; i < firstVertex + numVertexes; i++ )
			{
				svars.colors[i][0] = 255 - tess.vertexColors[i][0];
				svars.colors[i][1] = 255 - tess.vertexColors[i][1];
				svars.colors[i][2] = 255 - tess.vertexColors[i][2];
			}
		}
		else
		{
			for ( int i = firstVertex; i < firstVertex + numVertexes; i++ )
			{
				svars.colors[i][0] = ( 255 - tess.vertexColors[i][0] ) * tr.identityLight;
				svars.colors[i][1] = ( 255 - tess.vertexColors[i][1] ) * tr.identityLight;
				svars.colors[i][2] = ( 255 - tess.vertexColors[i][2] ) * tr.identityLight;
			}
		}
		break;
	case CGEN_WAVEFORM:
		RB_CalcWaveColor( &pStage->rgbWave, ( unsigned char * ) &svars.colors[firstVertex], numVertexes );
		break;
	case CGEN_ENTITY:
		RB_CalcColorFromEntity( ( unsigned char * ) &svars.colors[firstVertex], numVertexes );
		break;
	case CGEN_ONE_MINUS_ENTITY:
		RB_CalcColorFromOneMinusEntity( ( unsigned char * ) &svars.colors[firstVertex], numVertexes );
		break;
	case CGEN_DEBUG_ALPHA:
		for ( int i = firstVertex; i < firstVertex + numVertexes; i++ )
		{
			const byte alpha = tess.vertexColors[i][3];
			svars.colors[i][0] = alpha;
			svars.colors[i][1] = alpha;
			svars.colors[i][2] = alpha;
			svars.colors[i][3] = 255;
		}
		break;
	}

	//
	// alphaGen
	//
	switch ( pStage->alphaGen )
	{
	case AGEN_SKIP:
		break;
	case AGEN_IDENTITY:
		if ( pStage->rgbGen != CGEN_IDENTITY ) {
			if ( ( pStage->rgbGen == CGEN_VERTEX && tr.identityLight != 1 ) ||
				 pStage->rgbGen != CGEN_VERTEX ) {
				for ( int i = firstVertex; i < firstVertex + numVertexes; i++ ) {
					svars.colors[i][3] = 0xff;
				}
			}
		}
		break;
	case AGEN_CONST:
		if ( pStage->rgbGen != CGEN_CONST ) {
			for ( int i = firstVertex; i < firstVertex + numVertexes; i++ ) {
				svars.colors[i][3] = pStage->constantColor[3];
			}
		}
		break;
	case AGEN_WAVEFORM:
		RB_CalcWaveAlpha( &pStage->alphaWave, ( unsigned char * ) &svars.colors[firstVertex], numVertexes );
		break;
	case AGEN_LIGHTING_SPECULAR:
		RB_CalcSpecularAlpha( ( unsigned char * ) svars.colors, firstVertex, numVertexes );
		break;
	case AGEN_ENTITY:
		RB_CalcAlphaFromEntity( ( unsigned char * ) &svars.colors[firstVertex], numVertexes );
		break;
	case AGEN_ONE_MINUS_ENTITY:
		RB_CalcAlphaFromOneMinusEntity( ( unsigned char * ) &svars.colors[firstVertex], numVertexes );
		break;
	case AGEN_VERTEX:
		if ( pStage->rgbGen != CGEN_VERTEX ) {
			for ( int i = firstVertex; i < firstVertex + numVertexes; i++ ) {
				svars.colors[i][3] = tess.vertexColors[i][3];
			}
		}
		break;
	case AGEN_ONE_MINUS_VERTEX:
		for ( int i = firstVertex; i < firstVertex + numVertexes; i++ ) {
			svars.colors[i][3] = 255 - tess.vertexColors[i][3];
		}
		break;
	case AGEN_PORTAL:
		{
			for ( int i = firstVertex; i < firstVertex + numVertexes; i++ ) {
				vec3_t v;
				VectorSubtract( tess.xyz[i], backEnd.viewParms.orient.origin, v );
				float len = VectorLength( v ) / tess.shader->portalRange;
				svars.colors[i][3] = (byte)Com_Clamp( 0, 255, len * 255 );
			}
		}
		break;
	}
}


void R_ComputeTexCoords( const shaderStage_t* pStage, stageVars_t& svars, int firstVertex, int numVertexes, qbool ptrOpt )
{
	svars.texcoordsptr = svars.texcoords;

	// generate the base texture coordinates

	switch ( pStage->tcGen )
	{
	case TCGEN_IDENTITY:
		Com_Memset( svars.texcoords + firstVertex, 0, sizeof( float ) * 2 * numVertexes );
		break;

	case TCGEN_TEXTURE:
		if ( !ptrOpt || pStage->numTexMods > 0 || pStage->type == ST_LIGHTMAP )
			Com_Memcpy( svars.texcoords[firstVertex], tess.texCoords[firstVertex], numVertexes * sizeof(vec2_t) );
		else
			svars.texcoordsptr = tess.texCoords;
		break;

	case TCGEN_LIGHTMAP:
		if ( !ptrOpt || pStage->numTexMods > 0 )
			Com_Memcpy( svars.texcoords[firstVertex], tess.texCoords2[firstVertex], numVertexes * sizeof(vec2_t) );
		else
			svars.texcoordsptr = tess.texCoords2;
		break;

	case TCGEN_VECTOR:
		for ( int i = firstVertex ; i < firstVertex + numVertexes ; i++ ) {
			svars.texcoords[i][0] = DotProduct( tess.xyz[i], pStage->tcGenVectors[0] );
			svars.texcoords[i][1] = DotProduct( tess.xyz[i], pStage->tcGenVectors[1] );
		}
		break;

	case TCGEN_ENVIRONMENT_MAPPED:
		RB_CalcEnvironmentTexCoords( ( float * ) svars.texcoords, firstVertex, numVertexes );
		break;

	case TCGEN_BAD:
		return;
	}

	// then alter for any tcmods

	for ( int i = 0; i < pStage->numTexMods; ++i ) {
		switch ( pStage->texMods[i].type )
		{
		case TMOD_NONE:
			i = TR_MAX_TEXMODS;		// break out of for loop
			break;

		case TMOD_TURBULENT:
			RB_CalcTurbulentTexCoords( &pStage->texMods[i].wave, (float*)&svars.texcoords[firstVertex], firstVertex, numVertexes );
			break;

		case TMOD_ENTITY_TRANSLATE:
			RB_CalcScrollTexCoords( backEnd.currentEntity->e.shaderTexCoord, (float*)&svars.texcoords[firstVertex], numVertexes );
			break;

		case TMOD_SCROLL:
			RB_CalcScrollTexCoords( pStage->texMods[i].scroll, (float*)&svars.texcoords[firstVertex], numVertexes );
			break;

		case TMOD_SCALE:
			RB_CalcScaleTexCoords( pStage->texMods[i].scale, (float*)&svars.texcoords[firstVertex], numVertexes );
			break;

		case TMOD_STRETCH:
			RB_CalcStretchTexCoords( &pStage->texMods[i].wave, (float*)&svars.texcoords[firstVertex], numVertexes );
			break;

		case TMOD_TRANSFORM:
			RB_CalcTransformTexCoords( &pStage->texMods[i], (float*)&svars.texcoords[firstVertex], numVertexes );
			break;

		case TMOD_ROTATE:
			RB_CalcRotateTexCoords( pStage->texMods[i].rotateSpeed, (float*)&svars.texcoords[firstVertex], numVertexes );
			break;

		default:
			ri.Error( ERR_DROP, "ERROR: unknown texmod '%d' in shader '%s'\n", pStage->texMods[i].type, tess.shader->name );
			break;
		}
	}

	// fix up uncorrected lightmap texture coordinates

	if ( pStage->type == ST_LIGHTMAP && pStage->tcGen != TCGEN_LIGHTMAP )
	{
		const shader_t* const shader = tess.shader;

		for ( int i = firstVertex; i < firstVertex + numVertexes; ++i )
		{
			svars.texcoords[i][0] = svars.texcoords[i][0] * shader->lmScale[0] + shader->lmBias[0];
			svars.texcoords[i][1] = svars.texcoords[i][1] * shader->lmScale[1] + shader->lmBias[1];
		}
	}
}