//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/gl_rsurf.c                                             $
// $Revision:: 5                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 8/8/01 4:46 PM                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// surface-related refresh code
//

#include <assert.h>
#include <ctype.h>
#include "gl_local.h"

#ifndef GL_UNPACK_CLIENT_STORAGE_APPLE
#define GL_UNPACK_CLIENT_STORAGE_APPLE 0x85B2
#endif

#ifndef GL_BGRA
#define GL_BGRA 0x80E1
#endif

#if defined(macintosh) && TARGET_API_MAC_CARBON && 0
#define qglTexSubImage2D2(a,b,c,d,e,f,g,h,i) qglTexSubImage2D(a,b,c,d,e,f,GL_BGRA,GL_UNSIGNED_INT_8_8_8_8_REV,i)
#else
#define qglTexSubImage2D2(a,b,c,d,e,f,g,h,i) qglTexSubImage2D(a,b,c,d,e,f,g,h,i)
#endif

int c_randval;
vec3_t zero={0,0,0};

static vec3_t modelorg;		// relative to viewpoint

msurface_t *r_alpha_surfaces;

#define DYNAMIC_LIGHT_WIDTH  128
#define DYNAMIC_LIGHT_HEIGHT 128

int		c_visible_lightmaps;
int		c_visible_textures;

gllightmapstate_t gl_lms;

static void		LM_InitBlock( void );
static void		LM_UploadBlock( qboolean dynamic );
static qboolean	LM_AllocBlock (int w, int h, int *x, int *y);

extern void R_SetCacheState( msurface_t *surf );
extern void R_BuildLightMap (msurface_t *surf, byte *dest, int stride);

#define MAX_RENDERED_SPLATTERS 256
static int num_splatter_surfaces;
static msurface_t * splatter_surfaces[ MAX_RENDERED_SPLATTERS ];

static void R_RenderLightmappedPoly (msurface_t *surf);

/* Knightmare- added for lightmap update batching */
#ifdef BATCH_LM_UPDATES
static void R_UpdateSurfaceLightmap (msurface_t *surf);
static void R_RebuildLightmaps (void);
static void R_RenderBrushPoly_Mtex (msurface_t *fa);
#endif

/*
=============================================================

	BRUSH MODELS

=============================================================
*/

/*
===============
R_TextureAnimation

Returns the proper texture for a given time and base texture
===============
*/
image_t *R_TextureAnimation (mtexinfo_t *tex)
{
	image_t * image;
	int c;

	image = tex->image;

	if ( tex->surf )
	{
		c = tex->surf->animation_frame;
		while ( c )
		{
			tex = tex->next;
			c--;
		}
	}
	else
	{
		assert( 0 );
	}

	if ( tex )
		image = tex->image;
	else
		assert( 0 );

	return image;
}

float r_solidcolors[256*3];
void R_DrawTriangleOutlines (void);

/*
================
DrawGLPoly
================
*/

#ifndef AMD3D
extern cvar_t *gl_drawflat;
static void DrawGLPoly (msurface_t *s)
{
	int i;
	float *v;

	glpoly_t *p = s->polys;

	if (gl_drawflat->value)
	{
		int index = (((intptr_t)s>>3)&0xff);
		float *c = &r_solidcolors[index*3];
		//
		// solid poly
		//
		qglDisable (GL_TEXTURE_2D);

		qglBegin (GL_POLYGON);
		v = p->verts[0];
		for (i=0 ; i<p->numverts ; i++, v+= VERTEXSIZE)
		{
			qglColor3f ( c[0],c[1],c[2] );
			qglVertex3fv (v);
		}
		qglEnd ();
		qglColor3f ( 1.0,1.0,1.0 );
		qglEnable (GL_TEXTURE_2D);
	}
	else if (s->texinfo->flags & SURF_ENVMAPPED)
	{
		vec3_t eye_normal;
		vec3_t eye_vert;
		vec3_t normal;
		vec3_t transform[3];
		float two_nu, m, fx, fy, fz;

		if ( s->flags & SURF_PLANEBACK )
			VectorNegate(s->plane->normal, normal);
		else
			VectorCopy(s->plane->normal, normal);

		VectorCopy (vright, transform[0]);
		VectorNegate (vup, transform[1]);
		VectorCopy (vpn, transform[2]);

		eye_normal[0] = DotProduct(normal, transform[0]);
		eye_normal[1] = DotProduct(normal, transform[1]);
		eye_normal[2] = DotProduct(normal, transform[2]);

		qglBegin (GL_POLYGON);
		v = p->verts[0];
		for (i=0 ; i<p->numverts ; i++, v+= VERTEXSIZE)
		{
			eye_vert[0] = DotProduct(v, transform[0]) - r_origin[0];
			eye_vert[1] = DotProduct(v, transform[1]) - r_origin[1];
			eye_vert[2] = DotProduct(v, transform[2]) - r_origin[2];
			VectorNormalize( eye_vert );
			two_nu = 2.0f * DotProduct( eye_normal, eye_vert );
			fx = eye_vert[0] - eye_normal[0] * two_nu;
			fy = eye_vert[1] - eye_normal[1] * two_nu;
			fz = eye_vert[2] - eye_normal[2] * two_nu;
			m = 2.0F * sqrt( fx*fx + fy*fy + (fz+1.0F)*(fz+1.0F) );
			if (m==0.0F)
			{
				qglTexCoord2f ( 0.5f, 0.5f );
			}
			else
			{
				float mInv = 1.0F / m;
				qglTexCoord2f ( fx * mInv + 0.5f, fy * mInv + 0.5F );
			}
			qglVertex3fv (v);
		}
		qglEnd ();
	}
	else
	{
		qglBegin (GL_POLYGON);
		v = p->verts[0];
		for (i=0 ; i<p->numverts ; i++, v+= VERTEXSIZE)
		{
			qglTexCoord2f (v[3] + s->texinfo->surf->delta_s, v[4] + s->texinfo->surf->delta_t);
			qglVertex3fv (v);
		}
		qglEnd ();
	}
}
#endif // AMD3D

/*
** R_DrawTriangleOutlines
*/
void R_DrawTriangleOutlines (void)
{
	int			i, j;
	glpoly_t	*p;

	if (!gl_showtris->value)
		return;

	qglDisable (GL_TEXTURE_2D);
	qglDisable (GL_DEPTH_TEST);
	qglColor4f (1,1,1,1);

	for (i=0 ; i<MAX_LIGHTMAPS ; i++)
	{
		msurface_t *surf;

		for ( surf = gl_lms.lightmap_surfaces[i]; surf != 0; surf = surf->lightmapchain )
		{
			p = surf->polys;
			for ( ; p ; p=p->chain)
			{
				for (j=2 ; j<p->numverts ; j++ )
				{
					qglBegin (GL_LINE_STRIP);
					qglVertex3fv (p->verts[0]);
					qglVertex3fv (p->verts[j-1]);
					qglVertex3fv (p->verts[j]);
					qglVertex3fv (p->verts[0]);
					qglEnd ();
				}
			}
		}
	}

	qglEnable (GL_DEPTH_TEST);
	qglEnable (GL_TEXTURE_2D);
}

/*
** DrawGLPolyChain
*/
#ifndef AMD3D
static void DrawGLPolyChain( glpoly_t *p, float soffset, float toffset )
{
	if ( soffset == 0 && toffset == 0 )
	{
		for ( ; p != 0; p = p->chain )
		{
			float *v;
			int j;

			qglBegin (GL_POLYGON);
			v = p->verts[0];
			for (j=0 ; j<p->numverts ; j++, v+= VERTEXSIZE)
			{
				qglTexCoord2f (v[5], v[6] );
				qglVertex3fv (v);
			}
			qglEnd ();
		}
	}
	else
	{
		for ( ; p != 0; p = p->chain )
		{
			float *v;
			int j;

			qglBegin (GL_POLYGON);
			v = p->verts[0];
			for (j=0 ; j<p->numverts ; j++, v+= VERTEXSIZE)
			{
				qglTexCoord2f (v[5] - soffset, v[6] - toffset );
				qglVertex3fv (v);
			}
			qglEnd ();
		}
	}
}
#endif // AMD3D

/*
** R_BlendLightMaps
**
** This routine takes all the given light mapped surfaces in the world and
** blends them into the framebuffer.
*/
static void R_BlendLightmaps (void)
{
	int			i;
	msurface_t	*surf, *newdrawsurf = 0;

	// don't bother if we're set to fullbright
	if (r_fullbright->value)
		return;
	if (!r_worldmodel->lightdata)
		return;

	// don't bother writing Z
	qglDepthMask( 0 );

	qglDepthFunc (GL_EQUAL);

	/*
	** set the appropriate blending mode unless we're only looking at the
	** lightmaps.
	*/
	if (!gl_lightmap->value)
	{
		qglEnable (GL_BLEND);

		if ( gl_saturatelighting->value )
		{
			qglBlendFunc( GL_ONE, GL_ONE );
		}
		else
		{
			if ( gl_monolightmap->string[0] != '0' )
			{
				switch ( toupper( gl_monolightmap->string[0] ) )
				{
				case 'I':
					qglBlendFunc (GL_ZERO, GL_SRC_COLOR );
					break;
				case 'L':
					qglBlendFunc (GL_ZERO, GL_SRC_COLOR );
					break;
				case 'A':
				default:
					qglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
					break;
				}
			}
			else
			{
				qglBlendFunc (GL_ZERO, GL_SRC_COLOR );
			}
		}
	}

	if ( currentmodel == r_worldmodel )
		c_visible_lightmaps = 0;

	num_splatter_surfaces = 0;

	/*
	** render static lightmaps first
	*/
	for ( i = 1; i < MAX_LIGHTMAPS; i++ )
	{
		if ( gl_lms.lightmap_surfaces[i] )
		{
			if (currentmodel == r_worldmodel)
				c_visible_lightmaps++;
			GL_Bind( gl_state.lightmap_textures + i);

			for ( surf = gl_lms.lightmap_surfaces[i]; surf != 0; surf = surf->lightmapchain )
			{
				if ( surf->polys )
				{
					if ( surf->splatter_surface && num_splatter_surfaces < MAX_RENDERED_SPLATTERS )
					{
						splatter_surfaces[ num_splatter_surfaces ] = surf;
					}
					DrawGLPolyChain( surf->polys, 0, 0 );
				}
			}
		}
	}

	//
	// render the splatter maps
	//

/* FS: FIXME: Was this removed by Ritual or Burger for mac port? */
/*
	qglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
	for ( i = 0; i < num_splatter_surfaces; i++ )
	{
		splatter_t *splatter;
		glpoly_t *p;

		splatter = splatter_surfaces[ i ]->splatter_surface;
		GL_Bind( gl_state.splatter_textures + splatter->texture_num );
		p = surf->polys;
		for ( ; p != 0; p = p->chain )
		{
			float *v;
			int j;

			qglBegin (GL_POLYGON);
			v = p->verts[0];
			for (j=0 ; j<p->numverts ; j++, v+= VERTEXSIZE)
			{
				qglTexCoord2f( v[7] * splatter->s_mult, v[8] * splatter->t_mult );
				qglVertex3fv (v);
			}
			qglEnd ();
		}
	}
	if (!gl_lightmap->value)
	{
		if ( gl_saturatelighting->value )
		{
			qglBlendFunc( GL_ONE, GL_ONE );
		}
		else
		{
			if ( gl_monolightmap->string[0] != '0' )
			{
				switch ( toupper( gl_monolightmap->string[0] ) )
				{
				case 'I':
					qglBlendFunc (GL_ZERO, GL_SRC_COLOR );
					break;
				case 'L':
					qglBlendFunc (GL_ZERO, GL_SRC_COLOR );
					break;
				case 'A':
				default:
					qglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
					break;
				}
			}
			else
			{
				qglBlendFunc (GL_ZERO, GL_SRC_COLOR );
			}
		}
	}
*/

	/*
	** render dynamic lightmaps
	*/
	if ( gl_dynamic->value )
	{
		LM_InitBlock();

		GL_Bind( gl_state.lightmap_textures+0 );

		if (currentmodel == r_worldmodel)
			c_visible_lightmaps++;

		newdrawsurf = gl_lms.lightmap_surfaces[0];

		for ( surf = gl_lms.lightmap_surfaces[0]; surf != 0; surf = surf->lightmapchain )
		{
			int		smax, tmax;
			unsigned	*base;

			smax = (surf->extents[0]>>4)+1;
			tmax = (surf->extents[1]>>4)+1;

			if ( LM_AllocBlock( smax, tmax, &surf->dlight_s, &surf->dlight_t ) )
			{
				base = gl_lms.lightmap_buffer;
				base += ( surf->dlight_t * LM_BLOCK_WIDTH + surf->dlight_s );

				R_BuildLightMap (surf, (byte *)base, LM_BLOCK_WIDTH*LIGHTMAP_BYTES);
			}
			else
			{
				msurface_t *drawsurf;

				// upload what we have so far
				LM_UploadBlock( true );

				// draw all surfaces that use this lightmap
				for ( drawsurf = newdrawsurf; drawsurf != surf; drawsurf = drawsurf->lightmapchain )
				{
					if ( drawsurf->polys )
						DrawGLPolyChain( drawsurf->polys,
							              ( drawsurf->light_s - drawsurf->dlight_s ) * ( 1.0 / 128.0 ),
										( drawsurf->light_t - drawsurf->dlight_t ) * ( 1.0 / 128.0 ) );
				}

				newdrawsurf = drawsurf;

				// clear the block
				LM_InitBlock();

				// try uploading the block now
				if ( !LM_AllocBlock( smax, tmax, &surf->dlight_s, &surf->dlight_t ) )
				{
					ri.Sys_Error( ERR_FATAL, "Consecutive calls to LM_AllocBlock(%d,%d) failed (dynamic)\n", smax, tmax );
				}

				base = gl_lms.lightmap_buffer;
				base += ( surf->dlight_t * LM_BLOCK_WIDTH + surf->dlight_s );

				R_BuildLightMap (surf, (byte *)base, LM_BLOCK_WIDTH*LIGHTMAP_BYTES);
			}
		}

		/*
		** draw remainder of dynamic lightmaps that haven't been uploaded yet
		*/
		if ( newdrawsurf )
			LM_UploadBlock( true );

		for ( surf = newdrawsurf; surf != 0; surf = surf->lightmapchain )
		{
			if ( surf->polys )
				DrawGLPolyChain( surf->polys, ( surf->light_s - surf->dlight_s ) * ( 1.0 / 128.0 ), ( surf->light_t - surf->dlight_t ) * ( 1.0 / 128.0 ) );
		}
	}

	/*
	** restore state
	*/
	qglDisable (GL_BLEND);
	qglBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	if (gl_ztrick->value)
	{
		if (ztrickframe & 1)
		{
			qglDepthFunc (GL_LEQUAL);
		}
		else
		{
			qglDepthFunc (GL_GEQUAL);
		}
	}
	else
		qglDepthFunc (GL_LEQUAL);

	qglDepthMask( 1 );
}


/*
================
R_RenderBrushPoly
================
*/
void R_RenderBrushPoly (msurface_t *fa)
{
	int maps;
	image_t *image;
	qboolean is_dynamic = false;

	if (fa->texinfo->flags & SURF_NODRAW)
	{
		return;
	}
	GL_EnableMultitexture( false );

	c_brush_polys++;
	image = R_TextureAnimation (fa->texinfo);

	GL_BindImage( image );

	if ( fa->texinfo->flags & SURF_ADD )
	{
		GL_TexEnv( GL_MODULATE );
		qglEnable( GL_BLEND );
		qglColor4f( fa->texinfo->surf->color[0],fa->texinfo->surf->color[1],fa->texinfo->surf->color[2],1.0F );
		qglBlendFunc( GL_ONE, GL_ONE );
		qglDepthMask( 0 );
		DrawGLPoly( fa );
		qglDepthMask( 1 );
		qglDisable( GL_BLEND );
		return;
	}
	else if (fa->flags & SURF_DRAWTURB)
	{
		if (fa->texinfo->flags & SURF_WAVY)
		{
			// warp texture, no lightmaps
			GL_TexEnv( GL_MODULATE );
			qglColor4f( gl_state.inverse_intensity, gl_state.inverse_intensity, gl_state.inverse_intensity,1.0F );
			if (gl_drawflat->value)
				EmitWavySolidPolys( fa );
			else
				EmitWavyPolys ( fa );
			return;
		}
		else
		{
			// warp texture, no lightmaps
			GL_TexEnv( GL_MODULATE );
			qglColor4f( gl_state.inverse_intensity,
			            gl_state.inverse_intensity,
			            gl_state.inverse_intensity,
			            1.0F );
			if (gl_drawflat->value)
				EmitSolidPolys( fa );
			else
				EmitWaterPolys ( fa );
			GL_TexEnv( GL_REPLACE );
			return;
		}
	}
	else if (fa->texinfo->flags & SURF_MASKED)
	{
		if (fa->texinfo->flags & SURF_NONLIT)
		{
			if (r_fullbright->value)
				qglColor4f( 1.0F,1.0F,1.0F,1.0F );
			else
			{
				if (fa->texinfo->flags & SURF_USECOLOR)
				{
					qglColor4f
					(
						fa->texinfo->surf->nonlit*fa->texinfo->surf->color[0],
						fa->texinfo->surf->nonlit*fa->texinfo->surf->color[1],
						fa->texinfo->surf->nonlit*fa->texinfo->surf->color[2],
						1.0F
					);
				}
				else
				{
					qglColor4f
					(
						fa->texinfo->surf->nonlit,
						fa->texinfo->surf->nonlit,
						fa->texinfo->surf->nonlit,
						1.0F
					);
				}
			}
		}
		else
		{
			qglColor3f( 1.0F, 1.0F, 1.0F );
		}
		GL_TexEnv( GL_MODULATE );
		qglEnable( GL_ALPHA_TEST );
		DrawGLPoly ( fa );
		qglDisable ( GL_ALPHA_TEST );
		GL_TexEnv( GL_REPLACE );
		qglColor4f( 1.0F, 1.0F, 1.0F, 1.0F );
		if (fa->texinfo->flags & SURF_NONLIT)
		{
			return;
		}
	}
	else if (fa->texinfo->flags & SURF_NONLIT)
	{
		GL_TexEnv( GL_MODULATE );
		if (r_fullbright->value)
			qglColor4f( 1.0F,1.0F,1.0F,1.0F );
		else
		{
			if (fa->texinfo->flags & SURF_USECOLOR)
			{
				qglColor4f
				(
					fa->texinfo->surf->nonlit*fa->texinfo->surf->color[0],
					fa->texinfo->surf->nonlit*fa->texinfo->surf->color[1],
					fa->texinfo->surf->nonlit*fa->texinfo->surf->color[2],
					1.0F
				);
			}
			else
			{
				qglColor4f
				(
					fa->texinfo->surf->nonlit,
					fa->texinfo->surf->nonlit,
					fa->texinfo->surf->nonlit,
					1.0F
				);
			}
		}
		DrawGLPoly ( fa );
		GL_TexEnv( GL_REPLACE );
		qglColor4f( 1.0F, 1.0F, 1.0F, 1.0F );
		return;
	}
	else
	{
		GL_TexEnv( GL_REPLACE );
		DrawGLPoly ( fa );
	}

	/*
	** check for lightmap modification
	*/
	for ( maps = 0; maps < MAXLIGHTMAPS && fa->styles[maps] != 255; maps++ )
	{
		if ( r_newrefdef.lightstyles[fa->styles[maps]].rgbvalue != fa->cached_light[maps] )
			goto dynamic;
	}

	// dynamic this frame or dynamic previously
	if ( fa->dlightframe == r_framecount )
	{
dynamic:
		if ( gl_dynamic->value )
		{
			if (!( fa->texinfo->flags & (SURF_SKY|SURF_WARP|SURF_WAVY)))
			{
				if (fa->texinfo->surf->translucence == 0)
					is_dynamic = true;
			}
		}
	}
	if ( is_dynamic )
	{
		if ( ( fa->styles[maps] >= 32 || fa->styles[maps] == 0 ) && ( fa->dlightframe != r_framecount ) )
		{
			unsigned temp[34*34];
			int			smax, tmax;

			smax = (fa->extents[0]>>4)+1;
			tmax = (fa->extents[1]>>4)+1;

			R_BuildLightMap( fa, (byte *)temp, smax*4 );
			R_SetCacheState( fa );

			GL_Bind( gl_state.lightmap_textures + fa->lightmaptexturenum );

#if defined(macintosh) && TARGET_API_MAC_CARBON
			qglPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE,1);
#endif
			qglTexSubImage2D2( GL_TEXTURE_2D, 0,
							  fa->light_s, fa->light_t,
							  smax, tmax,
							  GL_LIGHTMAP_FORMAT,
							  GL_UNSIGNED_BYTE, temp );
			fa->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum];
			gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa;
		}
		else
		{
			fa->lightmapchain = gl_lms.lightmap_surfaces[0];
			gl_lms.lightmap_surfaces[0] = fa;
		}
	}
	else
	{
		fa->lightmapchain = gl_lms.lightmap_surfaces[fa->lightmaptexturenum];
		gl_lms.lightmap_surfaces[fa->lightmaptexturenum] = fa;
	}
}

/*
================
R_DrawAlphaSurfaces

Draw water surfaces and windows.
The BSP tree is waled front to back, so unwinding the chain
of alpha_surfaces will draw back to front, giving proper ordering.
================
*/
void R_DrawAlphaSurfaces (void)
{
	msurface_t	*s;
	float		intens;

	//
	// go back to the world matrix
	//
	qglLoadMatrixf (r_world_matrix);

	qglEnable (GL_BLEND);
	GL_TexEnv( GL_MODULATE );

	// the textures are prescaled up for a better lighting range,
	// so scale it back down
	intens = gl_state.inverse_intensity;

	for (s=r_alpha_surfaces ; s ; s=s->texturechain)
	{
		qglEnable( GL_BLEND );
		GL_TexEnv( GL_MODULATE );

		if (s->texinfo->flags & SURF_NODRAW)
			continue;

		if ( s->owner )
		{
			qglPushMatrix ();
			// If this surface has an origin, translate to it.  This is
			// for alpha entity surfaces.
			qglTranslatef( s->owner->origin[0], s->owner->origin[1], s->owner->origin[2] );
			qglRotatef (s->owner->angles[1],  0, 0, 1);
			qglRotatef (s->owner->angles[0],  0, 1, 0);
			qglRotatef (s->owner->angles[2],  1, 0, 0);
		}
		GL_BindImage( s->texinfo->image );

		c_brush_polys++;

		if ( s->owner && s->owner->flags & RF_TRANSLUCENT )
			qglColor4f (intens,intens,intens,s->owner->alpha);
		else
			qglColor4f (intens,intens,intens,1 - s->texinfo->surf->translucence);

		if (s->flags & SURF_DRAWTURB)
		{
			if (s->texinfo->flags & SURF_WAVY)
			{
				if (gl_drawflat->value)
					EmitWavySolidPolys (s);
				else
					EmitWavyPolys (s);
			}
			else
			{
				if (gl_drawflat->value)
					EmitSolidPolys(s);
				else
					EmitWaterPolys (s);
			}
		}
		else if ( s->texinfo->flags & SURF_ADD )
		{
			qglDepthMask( 0 );
			qglColor4f(s->texinfo->surf->color[0],s->texinfo->surf->color[1],s->texinfo->surf->color[2],1.0F );
			qglBlendFunc( GL_ONE, GL_ONE );
			DrawGLPoly( s );
			qglColor4f( 1.0F, 1.0F, 1.0F, 1.0F);
			qglDepthMask( 1 );
			qglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
		}
		else
			DrawGLPoly (s);

		if ( s->owner )
			qglPopMatrix();
	}

	GL_TexEnv( GL_REPLACE );
	qglColor4f (1,1,1,1);
	qglDisable (GL_BLEND);

	r_alpha_surfaces = NULL;
}

/*
================
R_DrawTextureChains
================
*/
static void R_DrawTextureChains (void)
{
	int		i;
	msurface_t	*s;
	image_t		*image;

	c_visible_textures = 0;

#ifndef BATCH_LM_UPDATES
	for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
	{
		if (!image->registration_sequence)
			continue;
		s = image->texturechain;
		if (!s)
			continue;
		c_visible_textures++;

		for ( ; s ; s=s->texturechain)
		{
			R_RenderBrushPoly (s);
			R_DrawConsole(s,1,zero);
		}

		image->texturechain = NULL;
	}

	GL_TexEnv( GL_REPLACE );
#else
	if (!qglMultiTexCoord2fARB || r_fullbright->intValue)
	{
		for (i=0, image=gltextures ; i<numgltextures ; i++,image++)
		{
			if (!image->registration_sequence)
				continue;
			s = image->texturechain;
			if (!s)
				continue;
			c_visible_textures++;

			for ( ; s ; s=s->texturechain)
			{
				R_RenderBrushPoly (s);
				R_DrawConsole(s,1,zero);
			}

			image->texturechain = NULL;
		}
	}
	else
	{	/* Knightmare- draw lightmapped surfs here */
		GL_EnableMultitexture (true);

		GL_SelectTexture( GL_TEXTURE0_ARB);
		GL_TexEnv (GL_REPLACE);
		GL_SelectTexture( GL_TEXTURE1_ARB);
		if (gl_lightmap->value)
			GL_TexEnv (GL_REPLACE);
		else 
			GL_TexEnv (GL_MODULATE);

/* Knightmare- added for lightmap update batching */
		R_RebuildLightmaps ();

		for (i=0, image=gltextures; i<numgltextures; i++, image++)
		{
			if (!image->registration_sequence)
				continue;
			if (!image->texturechain)
				continue;
			c_visible_textures++;

			for (s = image->texturechain; s; s=s->texturechain)
			{
				if ( !(s->flags & SURF_DRAWTURB) )
				{
//					R_RenderBrushPoly (s);
					R_RenderLightmappedPoly (s);
					R_DrawConsole(s,1,zero);
				}
			}
		}
		GL_EnableMultitexture (false);
		/* end Knightmare */

		for (i=0, image=gltextures; i<numgltextures; i++, image++)
		{
			if (!image->registration_sequence)
				continue;
			if (!image->texturechain)
				continue;

			for (s = image->texturechain; s; s=s->texturechain)
			{
				if ((s->flags & SURF_DRAWTURB)
					|| (s->texinfo->flags & SURF_ADD)
					|| (s->texinfo->flags & SURF_MASKED)
					|| (s->texinfo->flags & SURF_NONLIT))
				{
					R_RenderBrushPoly_Mtex(s); /* FS: SiN does it a little different, have to update these too */
					R_DrawConsole(s,1,zero);
				}
			}
			image->texturechain = NULL;
		}
	}
	GL_TexEnv( GL_REPLACE );
#endif	// BATCH_LM_UPDATES
}

/* Knightmare added */
#ifdef BATCH_LM_UPDATES
/*
=============
R_UpdateSurfaceLightmap

Based on code from MH's experimental Q2 engine
=============
*/
static void R_UpdateSurfaceLightmap (msurface_t *surf)
{
	int			map, smax, tmax;
	qboolean	is_dynamic = false;

	if (!qglMultiTexCoord2fARB || (r_fullbright->value != 0))
		return;

	if (surf->texinfo->flags & SURF_NODRAW)
	{
		return;
	}

	for ( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )
	{
		if ( r_newrefdef.lightstyles[surf->styles[map]].rgbvalue != surf->cached_light[map] )
			goto dynamic;
	}

	// dynamic this frame or dynamic previously
	if ( (surf->dlightframe == r_framecount) || surf->cached_dlight )
	{
dynamic:
		if ( gl_dynamic->value || surf->cached_dlight )
		{
			if ( !(surf->texinfo->flags & (SURF_SKY|SURF_WARP|SURF_WAVY)) )
			{
				if (surf->texinfo->surf->translucence == 0)
					is_dynamic = true;
			}
		}
	}

	if ( is_dynamic )
	{
		unsigned	*base = gl_lms.lightmap_update[surf->lightmaptexturenum];
		rect_t		*rect = &gl_lms.lightrect[surf->lightmaptexturenum];

		smax = ( surf->extents[0] >> 4 ) + 1;
		tmax = ( surf->extents[1] >> 4 ) + 1;

		base += (surf->light_t * LM_BLOCK_WIDTH + surf->light_s);
		R_BuildLightMap (surf, (byte *)base, LM_BLOCK_WIDTH*LIGHTMAP_BYTES);
		R_SetCacheState (surf);
		gl_lms.modified[surf->lightmaptexturenum] = true;

		if (surf->light_s < rect->left)
			rect->left = surf->light_s;
		if ((surf->light_s + smax) > rect->right)
			rect->right = surf->light_s + smax;
		if (surf->light_t < rect->top)
			rect->top = surf->light_t;
		if ((surf->light_t + tmax) > rect->bottom)
			rect->bottom = surf->light_t + tmax;
	}
}

/*
=============
R_RebuildLightmaps

Based on code from MH's experimental Q2 engine
=============
*/
static void R_RebuildLightmaps (void)
{
	int			i;

	if (r_fullbright->intValue)
		return;

	for (i=1; i<gl_lms.current_lightmap_texture; i++)
	{
		if (!gl_lms.modified[i])
			continue;

		// update full width of lm texture, because glPixelStorei isn't supported
		GL_MBind( GL_TEXTURE1_ARB, gl_state.lightmap_textures + i );
					qglTexSubImage2D2 (GL_TEXTURE_2D, 0,
					0, gl_lms.lightrect[i].top, 
					LM_BLOCK_WIDTH, (gl_lms.lightrect[i].bottom - gl_lms.lightrect[i].top), 
					GL_LIGHTMAP_FORMAT, GL_UNSIGNED_BYTE,
					gl_lms.lightmap_update[i] + (gl_lms.lightrect[i].top * LM_BLOCK_WIDTH));

		gl_lms.modified[i] = false;
		gl_lms.lightrect[i].left = LM_BLOCK_WIDTH;
		gl_lms.lightrect[i].right = 0;
		gl_lms.lightrect[i].top = LM_BLOCK_HEIGHT;
		gl_lms.lightrect[i].bottom = 0;
	}
}

static void R_RenderBrushPoly_Mtex (msurface_t *fa)
{
	image_t *image;

	if ((!qglMultiTexCoord2fARB) || (fa->texinfo->flags & SURF_NODRAW))
	{
		return;
	}

	c_brush_polys++;

	image = R_TextureAnimation (fa->texinfo);

	GL_BindImage( image );

	if ( fa->texinfo->flags & SURF_ADD )
	{
		GL_TexEnv( GL_MODULATE );
		qglEnable( GL_BLEND );
		qglColor4f( fa->texinfo->surf->color[0],fa->texinfo->surf->color[1],fa->texinfo->surf->color[2],1.0F );
		qglBlendFunc( GL_ONE, GL_ONE );
		qglDepthMask( 0 );
		DrawGLPoly( fa );
		qglDepthMask( 1 );
		qglDisable( GL_BLEND );
		return;
	}
	else if (fa->flags & SURF_DRAWTURB)
	{
		if (fa->texinfo->flags & SURF_WAVY)
		{
			// warp texture, no lightmaps
			GL_TexEnv( GL_MODULATE );
			qglColor4f( gl_state.inverse_intensity, gl_state.inverse_intensity, gl_state.inverse_intensity,1.0F );
			if (gl_drawflat->value)
				EmitWavySolidPolys( fa );
			else
				EmitWavyPolys ( fa );
			return;
		}
		else
		{
			// warp texture, no lightmaps
			GL_TexEnv( GL_MODULATE );
			qglColor4f( gl_state.inverse_intensity,
			            gl_state.inverse_intensity,
			            gl_state.inverse_intensity,
			            1.0F );
			if (gl_drawflat->value)
				EmitSolidPolys( fa );
			else
				EmitWaterPolys ( fa );
			GL_TexEnv( GL_REPLACE );
			return;
		}
	}
	else if (fa->texinfo->flags & SURF_MASKED)
	{
		if (fa->texinfo->flags & SURF_NONLIT)
		{
			if (r_fullbright->value)
				qglColor4f( 1.0f,1.0f,1.0f,1.0f );
			else
			{
				if (fa->texinfo->flags & SURF_USECOLOR)
				{
					qglColor4f
					(
						fa->texinfo->surf->nonlit*fa->texinfo->surf->color[0],
						fa->texinfo->surf->nonlit*fa->texinfo->surf->color[1],
						fa->texinfo->surf->nonlit*fa->texinfo->surf->color[2],
						1.0f
					);
				}
				else
				{
					qglColor4f
					(
						fa->texinfo->surf->nonlit,
						fa->texinfo->surf->nonlit,
						fa->texinfo->surf->nonlit,
						1.0f
					);
				}
			}
			GL_TexEnv( GL_MODULATE );
			qglEnable( GL_ALPHA_TEST );
			DrawGLPoly ( fa );
			qglDisable ( GL_ALPHA_TEST );
			GL_TexEnv( GL_REPLACE );
			qglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
		}
		return;
	}
	else if (fa->texinfo->flags & SURF_NONLIT)
	{
		GL_TexEnv( GL_MODULATE );
		if (r_fullbright->value)
			qglColor4f( 1.0F,1.0F,1.0F,1.0F );
		else
		{
			if (fa->texinfo->flags & SURF_USECOLOR)
			{
				qglColor4f
				(
					fa->texinfo->surf->nonlit*fa->texinfo->surf->color[0],
					fa->texinfo->surf->nonlit*fa->texinfo->surf->color[1],
					fa->texinfo->surf->nonlit*fa->texinfo->surf->color[2],
					1.0F
				);
			}
			else
			{
				qglColor4f
				(
					fa->texinfo->surf->nonlit,
					fa->texinfo->surf->nonlit,
					fa->texinfo->surf->nonlit,
					1.0F
				);
			}
		}
		DrawGLPoly ( fa );
		GL_TexEnv( GL_REPLACE );
		qglColor4f( 1.0F, 1.0F, 1.0F, 1.0F );
		return;
	}
}
#endif // BATCH_LM_UPDATES
/* end Knightmare */

static void R_RenderLightmappedPoly( msurface_t *surf )
{
	int		i, nv;
#ifndef BATCH_LM_UPDATES
	int		map;
	qboolean is_dynamic = false;
#endif //	BATCH_LM_UPDATES
	float	*v;
	image_t *image;
	unsigned lmtex;
	glpoly_t *p;

	if (surf->texinfo->flags & SURF_NODRAW)
	{
		return;
	}

	image = R_TextureAnimation( surf->texinfo );
	lmtex = surf->lightmaptexturenum;
	nv = surf->polys->numverts;

#ifndef BATCH_LM_UPDATES	/* Knighmare- removed for batch lightmap updates */
	for ( map = 0; map < MAXLIGHTMAPS && surf->styles[map] != 255; map++ )
	{
		if ( r_newrefdef.lightstyles[surf->styles[map]].rgbvalue != surf->cached_light[map] )
			goto dynamic;
	}

	// dynamic this frame or dynamic previously
	if ( surf->dlightframe == r_framecount )
	{
dynamic:
		if ( gl_dynamic->value )
		{
			if ( !(surf->texinfo->flags & (SURF_SKY|SURF_WARP|SURF_WAVY)))
			{
				if (surf->texinfo->surf->translucence == 0)
					is_dynamic = true;
			}
		}
	}

	if ( is_dynamic )
	{
//		unsigned *temp = alloca(sizeof(unsigned) * 128*128);
		static unsigned temp[LM_BLOCK_WIDTH*LM_BLOCK_HEIGHT];
		int smax, tmax;

		if ( ( surf->styles[map] >= 32 || surf->styles[map] == 0 ) && ( surf->dlightframe != r_framecount ) )
		{
			smax = (surf->extents[0]>>4)+1;
			tmax = (surf->extents[1]>>4)+1;

			R_BuildLightMap( surf, (byte *)temp, smax*4 );
			R_SetCacheState( surf );

			GL_MBind( GL_TEXTURE1_ARB, gl_state.lightmap_textures + surf->lightmaptexturenum );

			lmtex = surf->lightmaptexturenum;

#if defined(macintosh) && TARGET_API_MAC_CARBON
			qglPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE,1);
#endif
			qglTexSubImage2D2(GL_TEXTURE_2D,0,surf->light_s, surf->light_t,smax, tmax,
				GL_LIGHTMAP_FORMAT,GL_UNSIGNED_BYTE,temp);
		} else {
			smax = (surf->extents[0]>>4)+1;
			tmax = (surf->extents[1]>>4)+1;

			R_BuildLightMap( surf, (byte *)temp, smax*4 );
			GL_MBind( GL_TEXTURE1_ARB, gl_state.lightmap_textures + 0 );
			lmtex = 0;

#if defined(macintosh) && TARGET_API_MAC_CARBON
			qglPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE,1);
#endif
			qglTexSubImage2D2(GL_TEXTURE_2D,0,surf->light_s, surf->light_t,smax,tmax,
				GL_LIGHTMAP_FORMAT,GL_UNSIGNED_BYTE,temp);
		}
		c_brush_polys++;
		GL_MBindImage( GL_TEXTURE0_ARB, image );
		GL_MBind( GL_TEXTURE1_ARB, gl_state.lightmap_textures + lmtex );
	}
	else
#endif	// BATCH_LM_UPDATES
	{
		c_brush_polys++;
		GL_MBindImage( GL_TEXTURE0_ARB, image );
		GL_MBind( GL_TEXTURE1_ARB, gl_state.lightmap_textures + lmtex );
	}

	if (surf->texinfo->flags & SURF_ENVMAPPED)
	{
		vec3_t eye_normal;
		vec3_t eye_vert;
		vec3_t normal;
		vec3_t transform[3];
		float two_nu, m, fx, fy, fz;

		if ( surf->flags & SURF_PLANEBACK )
			VectorNegate(surf->plane->normal, normal);
		else
			VectorCopy(surf->plane->normal, normal);

		VectorCopy (vright, transform[0]);
		VectorNegate (vup, transform[1]);
		VectorCopy (vpn, transform[2]);

		eye_normal[0] = DotProduct(normal, transform[0]);
		eye_normal[1] = DotProduct(normal, transform[1]);
		eye_normal[2] = DotProduct(normal, transform[2]);

		for ( p = surf->polys; p; p = p->chain )
		{
			qglBegin (GL_POLYGON);
			v = p->verts[0];
			for (i=0 ; i<p->numverts ; i++, v+= VERTEXSIZE)
			{
				eye_vert[0] = DotProduct(v, transform[0]) - r_origin[0];
				eye_vert[1] = DotProduct(v, transform[1]) - r_origin[1];
				eye_vert[2] = DotProduct(v, transform[2]) - r_origin[2];
				VectorNormalize( eye_vert );
				two_nu = 2.0f * DotProduct( eye_normal, eye_vert );
				fx = eye_vert[0] - eye_normal[0] * two_nu;
				fy = eye_vert[1] - eye_normal[1] * two_nu;
				fz = eye_vert[2] - eye_normal[2] * two_nu;
				m = 2.0F * sqrt( fx*fx + fy*fy + (fz+1.0F)*(fz+1.0F) );
				if (m==0.0F)
				{
					qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, 0.5f, 0.5f);
					qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, v[5], v[6]);
				}
				else
				{
					float mInv = 1.0F / m;
					qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, fx * mInv + 0.5f, fy * mInv + 0.5F);
					qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, v[5], v[6]);
				}
				qglVertex3fv (v);
			}
			qglEnd ();
		}
	}
	else if (surf->texinfo->flags & SURF_MASKED)
	{
		//
		// masked but lit
		// non-lit goes to RenderBrushPoly
		//

		qglEnable(GL_ALPHA_TEST);
		for ( p = surf->polys; p; p = p->chain )
		{
			v = p->verts[0];
			qglBegin (GL_POLYGON);
			for (i=0 ; i< nv; i++, v+= VERTEXSIZE)
			{
				qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, v[3] + surf->texinfo->surf->delta_s, v[4] + surf->texinfo->surf->delta_t);
				qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, v[5], v[6]);
				qglVertex3fv (v);
			}
			qglEnd ();
		}
		qglDisable (GL_ALPHA_TEST);
	}
	else
	{
		for ( p = surf->polys; p; p = p->chain )
		{
			v = p->verts[0];
			qglBegin (GL_POLYGON);
			for (i=0 ; i< nv; i++, v+= VERTEXSIZE)
			{
				qglMultiTexCoord2fARB( GL_TEXTURE0_ARB, v[3] + surf->texinfo->surf->delta_s, v[4] + surf->texinfo->surf->delta_t);
				qglMultiTexCoord2fARB( GL_TEXTURE1_ARB, v[5], v[6]);
				qglVertex3fv (v);
			}
			qglEnd ();
		}
		if ( surf->texinfo->flags & SURF_ADD )
		{
			qglDisable( GL_BLEND );
		}
	}
	//
	// render the splatter map
	//
}


/*
=================
R_DrawInlineBModel
=================
*/
static void R_DrawInlineBModel (void)
{
	int			i, k;
	cplane_t	*pplane;
	float		dot;
	msurface_t	*psurf;
	dlight_t	*lt;

	// calculate dynamic lighting for bmodel
	if ( !gl_flashblend->value )
	{
		lt = r_newrefdef.dlights;
		for (k=0 ; k<r_newrefdef.num_dlights ; k++, lt++)
		{
			R_MarkLights (lt, 1<<k, currentmodel->nodes + currentmodel->firstnode);
		}
	}

	psurf = &currentmodel->surfaces[currentmodel->firstmodelsurface];

	//
	// draw texture
	//
	for (i=0 ; i<currentmodel->nummodelsurfaces ; i++, psurf++)
	{
		if (psurf->texinfo->flags & SURF_NODRAW)
			continue;

		// find which side of the node we are on
		pplane = psurf->plane;

		dot = DotProduct (modelorg, pplane->normal) - pplane->dist;

		// draw the polygon
		if  ( (currententity->flags & RF_TRANSLUCENT) ||
			( (psurf->flags & SURF_PLANEBACK) && (dot < -BACKFACE_EPSILON)) ||
			(!(psurf->flags & SURF_PLANEBACK) && (dot > BACKFACE_EPSILON)))
		{
			if (gl_drawflat->value)
			{
				DrawGLPoly(psurf);
				continue;
			}

/* Knightmare- added for lightmap update batching */
#ifdef BATCH_LM_UPDATES
			if ( qglMultiTexCoord2fARB && ( r_fullbright->value == 0 )
				&& !(psurf->texinfo->flags & (SURF_SKY|SURF_WARP|SURF_WAVY|SURF_NONLIT|SURF_NODRAW)) )
			{
				R_UpdateSurfaceLightmap (psurf);
			}
#endif

			if ( (psurf->texinfo->surf->translucence > 0) || (currententity->flags & RF_TRANSLUCENT) || ( psurf->texinfo->flags & SURF_ADD ) )
			{	// add to the translucent chain
				psurf->texturechain = r_alpha_surfaces;
				r_alpha_surfaces = psurf;
				// save off the owner of this surface
				psurf->owner = currententity;
			}
#ifndef BATCH_LM_UPDATES	/* Knightmare- removed this */
//      	else if ( qglMultiTexCoord2fARB && !( psurf->flags & (SURF_DRAWTURB|SURF_DRAWNONLIT|SURF_DRAWMASKED) ) && !gl_drawflat->value )
			else if ( qglMultiTexCoord2fARB && !( psurf->flags & (SURF_DRAWTURB|SURF_DRAWNONLIT) ) && !gl_drawflat->value && !r_fullbright->value &&
						!(psurf->texinfo->flags & SURF_ADD ) ) 
			{
				R_RenderLightmappedPoly( psurf );
	            R_DrawConsole(psurf,1,zero);
			}

			else
			{
				GL_EnableMultitexture( false );
				R_RenderBrushPoly (psurf);
				GL_EnableMultitexture( true );
	         }
#else
			else	/* Knightmare- add to texturechain */
			{
				image_t *image = R_TextureAnimation (psurf->texinfo);
				psurf->texturechain = image->texturechain;
				image->texturechain = psurf;
			}
#endif	// BATCH_LM_UPDATES
		}
	}

#ifdef BATCH_LM_UPDATES
	/* Knightmare- draw using texturechains */
	R_DrawTextureChains ();
#endif	// BATCH_LM_UPDATES

	if ( !(currententity->flags & RF_TRANSLUCENT) )
	{
		if ( !qglMultiTexCoord2fARB)
			R_BlendLightmaps ();
	}
	else
	{
		qglDisable (GL_BLEND);
		qglColor4f (1,1,1,1);
		GL_TexEnv( GL_REPLACE );
	}
}

/*
=================
R_DrawBrushModel
=================
*/
void R_DrawBrushModel (entity_t *e)
{
	vec3_t		mins, maxs;
	int			i;
	qboolean	rotated;

	if (currentmodel->nummodelsurfaces == 0)
		return;

	currententity = e;
	gl_state.currenttextures[0] = gl_state.currenttextures[1] = -1;

	if (e->angles[0] || e->angles[1] || e->angles[2])
	{
		rotated = true;
		for (i=0 ; i<3 ; i++)
		{
			mins[i] = e->origin[i] - currentmodel->radius;
			maxs[i] = e->origin[i] + currentmodel->radius;
		}
	}
	else
	{
		rotated = false;
		VectorAdd (e->origin, currentmodel->mins, mins);
		VectorAdd (e->origin, currentmodel->maxs, maxs);
	}

	if (R_CullBox (mins, maxs))
		return;

	qglColor3f (1,1,1);
	memset (gl_lms.lightmap_surfaces, 0, sizeof(gl_lms.lightmap_surfaces));

	VectorSubtract ( r_newrefdef.vieworg, e->origin, modelorg );

	if (rotated)
	{
		vec3_t temp;

		VectorCopy ( modelorg, temp );
		modelorg[0] = DotProduct ( temp, e->mat[0] );
		modelorg[1] = DotProduct ( temp, e->mat[1] );
		modelorg[2] = DotProduct ( temp, e->mat[2] );
	}

	qglPushMatrix ();

	e->angles[0] = -e->angles[0]; // stupid quake bug
	e->angles[2] = -e->angles[2]; // stupid quake bug
	R_RotateForEntity ( e );
	e->angles[0] = -e->angles[0]; // stupid quake bug
	e->angles[2] = -e->angles[2]; // stupid quake bug

#ifndef BATCH_LM_UPDATES	/* Knightmare- removed this */
	GL_EnableMultitexture( true );
#endif	// BATCH_LM_UPDATES
	R_DrawInlineBModel ();
#ifndef BATCH_LM_UPDATES	/* Knightmare- removed this */
	GL_EnableMultitexture( false );
#endif	// BATCH_LM_UPDATES

	qglPopMatrix ();

	qglPushMatrix ();
	qglTranslatef ( e->origin[0],  e->origin[1],  e->origin[2] );
	qglRotatef ( e->angles[1],  0, 0, 1 );
	qglRotatef ( -e->angles[0],  0, 1, 0 );
	qglRotatef ( e->angles[2],  1, 0, 0 );
#ifndef BATCH_LM_UPDATES	/* Knightmare- removed this */
	GL_EnableMultitexture( false );
#endif	// BATCH_LM_UPDATES
	R_DrawConsole( &e->model->surfaces[e->model->firstmodelsurface], e->model->nummodelsurfaces, e->origin );
	qglPopMatrix ();
}

/*
=============================================================

	WORLD MODEL

=============================================================
*/

/*
================
R_RecursiveWorldNode
================
*/
static void R_RecursiveWorldNode (mnode_t *node)
{
	int			c, side, sidebit;
	cplane_t	*plane;
	msurface_t	*surf, **mark;
	mleaf_t		*pleaf;
	float		dot;
	image_t		*image;

	if (node->contents == CONTENTS_SOLID)
		return;		// solid

	if (node->visframe != r_visframecount)
		return;
	if (R_CullBox (node->minmaxs, node->minmaxs+3))
		return;

// if a leaf node, draw stuff
	if (node->contents != -1)
	{
		pleaf = (mleaf_t *)node;

		// check for door connected areas
		if (r_newrefdef.areabits)
		{
			if (! (r_newrefdef.areabits[pleaf->area>>3] & (1<<(pleaf->area&7)) ) )
				return;		// not visible
		}

		mark = pleaf->firstmarksurface;
		c = pleaf->nummarksurfaces;

		if (c)
		{
			do
			{
				(*mark)->visframe = r_framecount;
				mark++;
			} while (--c);
		}

		return;
	}

// node is just a decision point, so go down the apropriate sides

// find which side of the node we are on
	plane = node->plane;

	switch (plane->type)
	{
	case PLANE_X:
		dot = modelorg[0] - plane->dist;
		break;
	case PLANE_Y:
		dot = modelorg[1] - plane->dist;
		break;
	case PLANE_Z:
		dot = modelorg[2] - plane->dist;
		break;
	default:
		dot = DotProduct (modelorg, plane->normal) - plane->dist;
		break;
	}

	if (dot >= 0)
	{
		side = 0;
		sidebit = 0;
	}
	else
	{
		side = 1;
		sidebit = SURF_PLANEBACK;
	}

// recurse down the children, front side first
	R_RecursiveWorldNode ( node->children[side] );

	// add translucent surfaces to list in bsp order
	for ( c = node->numsurfaces, surf = r_worldmodel->surfaces + node->firstsurface; c; c--, surf++)
	{
		if (surf->texinfo->flags & SURF_NODRAW)
			continue;

		if (surf->visframe != r_framecount)
			continue;

		if ( (surf->flags & SURF_PLANEBACK) != sidebit )
			continue;       // wrong side

		if (gl_drawflat->value)
		{
			DrawGLPoly(surf);
			continue;
		}

/* Knightmare- added for lightmap update batching */
#ifdef BATCH_LM_UPDATES
		if (qglMultiTexCoord2fARB && (!r_fullbright->value) &&
			!(surf->texinfo->flags & (SURF_SKY|SURF_WARP|SURF_WAVY|SURF_NONLIT|SURF_NODRAW)))
		{
			R_UpdateSurfaceLightmap (surf);
		}
#endif	// BATCH_LM_UPDATES

		if (surf->texinfo->flags & SURF_SKY)
		{ // just adds to visible sky bounds
			R_AddSkySurface ( surf );
		}

		// Sin has arbitrary translucency
		else if ( ( surf->texinfo->surf->translucence > 0) || ( surf->texinfo->flags & SURF_ADD ) )

		{   // add to the translucent chain
			surf->texturechain = r_alpha_surfaces;
			r_alpha_surfaces = surf;
		}
		else
		{
#ifndef BATCH_LM_UPDATES	/* Knightmare- removed this, draw these surfaces AFTER traversing BSP tree! */
			if ( qglMultiTexCoord2fARB && !( surf->flags & (SURF_DRAWTURB|SURF_DRAWNONLIT) ) && !gl_drawflat->value && !r_fullbright->value )
			{
				R_RenderLightmappedPoly( surf );
				R_DrawConsole( surf, 1, zero );
			}
			else
#endif	// BATCH_LM_UPDATES
			{
				// the polygon is visible, so add it to the texture
				// sorted chain
				// FIXME: this is a hack for animation
				image = R_TextureAnimation ( surf->texinfo );
				surf->texturechain = image->texturechain;
				image->texturechain = surf;
			}
		}
	}

	// recurse down the back side
	R_RecursiveWorldNode ( node->children[!side] );

/* FS: FIXME: Was this removed by Ritual or Burger for mac port? */
/*
	// draw stuff
	c = node->numsurfaces;
	surf = r_worldmodel->surfaces + node->firstsurface;

	for ( ; c ; c--, surf++)
	{
		if (surf->visframe != r_framecount)
			continue;

		if ( (surf->flags & SURF_PLANEBACK) != sidebit )
			continue;		// wrong side

		if (surf->texinfo->flags & SURF_SKY)
		{	// just adds to visible sky bounds
			R_AddSkySurface (surf);
		}
		else if (surf->texinfo->translucence > 0)
		{	// add to the translucent chain
			// surf->texturechain = alpha_surfaces;
			// alpha_surfaces = surf;
        }
        else
        {
			// if ( qglMultiTexCoord2fARB && !( surf->flags & (SURF_DRAWTURB|SURF_DRAWNONLIT|SURF_DRAWMASKED) ) && !gl_drawflat->value )
			if ( qglMultiTexCoord2fARB && !( surf->flags & (SURF_DRAWTURB|SURF_DRAWNONLIT) ) && !gl_drawflat->value && !r_fullbright->value )
			{
				R_RenderLightmappedPoly( surf );
			}
			else
			{
				// the polygon is visible, so add it to the texture
				// sorted chain
				// FIXME: this is a hack for animation
				image = R_TextureAnimation (surf->texinfo);
				surf->texturechain = image->texturechain;
				image->texturechain = surf;
			}
		}
	}
*/
}


/*
=============
R_DrawWorld
=============
*/
void R_DrawWorld (void)
{
	entity_t	ent;

   if (!r_drawworld->value)
		return;

	if ( r_newrefdef.rdflags & RDF_NOWORLDMODEL )
		return;

	currentmodel = r_worldmodel;

	VectorCopy (r_newrefdef.vieworg, modelorg);

	// auto cycle the world frame for texture animation
	memset (&ent, 0, sizeof(ent));
    ent.frame = (int)(r_newrefdef.time*2);
    c_randval  = rand();
    currententity = &ent;

	gl_state.currenttextures[0] = gl_state.currenttextures[1] = -1;

	qglColor3f (1,1,1);
	memset (gl_lms.lightmap_surfaces, 0, sizeof(gl_lms.lightmap_surfaces));
	R_ClearSkyBox ();

#ifndef BATCH_LM_UPDATES	/* Knightmare- removed this */
	if ( qglMultiTexCoord2fARB )
	{
		GL_EnableMultitexture( true );
	
		if ( gl_lightmap->value )
		{
			GL_SelectTexture( GL_TEXTURE1_ARB );
			GL_TexEnv( GL_REPLACE );
		}
		R_RecursiveWorldNode (r_worldmodel->nodes);
		GL_EnableMultitexture( false );
	}
	else
#endif	// BATCH_LM_UPDATES
	{
		R_RecursiveWorldNode (r_worldmodel->nodes);
	}

	/*
	** theoretically nothing should happen in the next two functions
	** if multitexture is enabled
	*/
	/* Knightmare- R_DrawTextureChains is now used in multitexture mode */
	R_DrawTextureChains ();
	R_BlendLightmaps ();

	R_DrawSkyBox ();

	R_DrawTriangleOutlines ();
}


/*
===============
R_MarkLeaves

Mark the leaves and nodes that are in the PVS for the current
cluster
===============
*/

#ifndef AMD3D

void R_MarkLeaves (void)
{
	byte *vis;
	byte fatvis[MAX_MAP_LEAFS/8];
	mnode_t *node;
	int i, c;
	mleaf_t *leaf;
	int cluster;

	if (r_oldviewcluster == r_viewcluster && r_oldviewcluster2 == r_viewcluster2 && !r_novis->value && r_viewcluster != -1)
		return;

	// development aid to let you run around and see exactly where
	// the pvs ends
	if (gl_lockpvs->value)
		return;

	r_visframecount++;
	r_oldviewcluster = r_viewcluster;
	r_oldviewcluster2 = r_viewcluster2;

	if (r_novis->value || r_viewcluster == -1 || !r_worldmodel->vis)
	{
		// mark everything
		for (i=0; i<r_worldmodel->numleafs; i++)
			r_worldmodel->leafs[i].visframe = r_visframecount;
		for (i=0; i<r_worldmodel->numnodes; i++)
			r_worldmodel->nodes[i].visframe = r_visframecount;
		return;
	}

	vis = Mod_ClusterPVS ( r_viewcluster, r_worldmodel );
	// may have to combine two clusters because of solid water boundaries
	if (r_viewcluster2 != r_viewcluster)
	{
		memcpy( fatvis, vis, (r_worldmodel->numleafs+7)/8 );
		vis = Mod_ClusterPVS ( r_viewcluster2, r_worldmodel );
		c = (r_worldmodel->numleafs+31)/32;
		for (i=0; i<c; i++)
			((int *)fatvis)[i] |= ((int *)vis)[i];
		vis = fatvis;
	}

	for (i=0,leaf=r_worldmodel->leafs; i<r_worldmodel->numleafs; i++, leaf++)
	{
		cluster = leaf->cluster;
		if (cluster == -1)
			continue;
		if (vis[cluster>>3] & (1<<(cluster&7)))
		{
			node = (mnode_t *)leaf;
			do
			{
				if (node->visframe == r_visframecount)
					break;
				node->visframe = r_visframecount;
				node = node->parent;
			} while (node);
		}
	}

}

#endif



/*
=============================================================================

  LIGHTMAP ALLOCATION

=============================================================================
*/

static void LM_InitBlock( void )
{
	memset( gl_lms.allocated, 0, sizeof( gl_lms.allocated ) );

/* Knightmare- added for lightmap update batching */
#ifdef BATCH_LM_UPDATES
	if (!gl_lms.lightmap_update[gl_lms.current_lightmap_texture])
	{
		gl_lms.lightmap_update[gl_lms.current_lightmap_texture] = malloc (LM_BLOCK_WIDTH*LM_BLOCK_HEIGHT*LIGHTMAP_BYTES);
		memset (gl_lms.lightmap_update[gl_lms.current_lightmap_texture], 0, LM_BLOCK_WIDTH*LM_BLOCK_HEIGHT*LIGHTMAP_BYTES);
	}
#endif	// BATCH_LM_UPDATES
}

static void LM_UploadBlock( qboolean dynamic )
{
	int texture;
	int height = 0;

	if ( dynamic )
	{
		texture = 0;
	}
	else
	{
		texture = gl_lms.current_lightmap_texture;
	}

	GL_Bind( gl_state.lightmap_textures + texture );
	qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	if ( dynamic )
	{
		int i;

		for ( i = 0; i < LM_BLOCK_WIDTH; i++ )
		{
			if ( gl_lms.allocated[i] > height )
			{
				height = gl_lms.allocated[i];
			}
		}
#if defined(macintosh) && TARGET_API_MAC_CARBON
		qglPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE,1);
#endif
		qglTexSubImage2D2( GL_TEXTURE_2D,
						   0,
						   0, 0,
						   LM_BLOCK_WIDTH, height,
						   GL_LIGHTMAP_FORMAT,
						   GL_UNSIGNED_BYTE,
						   gl_lms.lightmap_buffer );
	}
	else
	{
		qglTexImage2D( GL_TEXTURE_2D,
						0,
						gl_lms.internal_format,
						LM_BLOCK_WIDTH, LM_BLOCK_HEIGHT,
						0,
						GL_LIGHTMAP_FORMAT,
						GL_UNSIGNED_BYTE,
/* Knightmare- added for lightmap update batching */
#ifdef BATCH_LM_UPDATES
						gl_lms.lightmap_update[gl_lms.current_lightmap_texture] );
#else
						gl_lms.lightmap_buffer );
#endif	// BATCH_LM_UPDATES
		if ( ++gl_lms.current_lightmap_texture == MAX_LIGHTMAPS )
		{
			ri.Sys_Error( ERR_DROP, "LM_UploadBlock() - MAX_LIGHTMAPS exceeded\n" );
		}
	}
}

// returns a texture number and the position inside it
static qboolean LM_AllocBlock (int w, int h, int *x, int *y)
{
	int		i, j;
	int		best, best2;

	best = LM_BLOCK_HEIGHT;

	for (i=0 ; i<LM_BLOCK_WIDTH-w ; i++)
	{
		best2 = 0;

		for (j=0 ; j<w ; j++)
		{
			if (gl_lms.allocated[i+j] >= best)
				break;
			if (gl_lms.allocated[i+j] > best2)
				best2 = gl_lms.allocated[i+j];
		}
		if (j == w)
		{	// this is a valid spot
			*x = i;
			*y = best = best2;
		}
	}

	if (best + h > LM_BLOCK_HEIGHT)
		return false;

	for (i=0 ; i<w ; i++)
		gl_lms.allocated[*x + i] = best + h;

	return true;
}

/*
================
GL_BuildPolygonFromSurface
================
*/
void GL_BuildPolygonFromSurface(msurface_t *fa)
{
	int			i, lindex, lnumverts;
	medge_t		*pedges, *r_pedge;
	float		*vec;
	float		s, t;
	glpoly_t	*poly;
	vec3_t		total;

// reconstruct the polygon
	pedges = currentmodel->edges;
	lnumverts = fa->numedges;

	VectorClear (total);
	//
	// draw texture
	//
	poly = (glpoly_t*)Hunk_Alloc (sizeof(glpoly_t) + (lnumverts-4) * VERTEXSIZE*sizeof(float));
	poly->next = fa->polys;
	poly->flags = fa->flags;
	fa->polys = poly;
	poly->numverts = lnumverts;

	for (i=0 ; i<lnumverts ; i++)
	{
		lindex = currentmodel->surfedges[fa->firstedge + i];

		if (lindex > 0)
		{
			r_pedge = &pedges[lindex];
			vec = currentmodel->vertexes[r_pedge->v[0]].position;
		}
		else
		{
			r_pedge = &pedges[-lindex];
			vec = currentmodel->vertexes[r_pedge->v[1]].position;
		}
		s = DotProduct (vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3];
		s /= fa->texinfo->image->width;

		t = DotProduct (vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3];
		t /= fa->texinfo->image->height;

		VectorAdd (total, vec, total);
		VectorCopy (vec, poly->verts[i]);
		poly->verts[i][3] = s;
		poly->verts[i][4] = t;

		//
		// lightmap texture coordinates
		//
		s = DotProduct (vec, fa->texinfo->vecs[0]) + fa->texinfo->vecs[0][3];
		s -= fa->texturemins[0];
		s += fa->light_s*16;
		s += 8;
		s /= LM_BLOCK_WIDTH*16; //fa->texinfo->texture->width;

		t = DotProduct (vec, fa->texinfo->vecs[1]) + fa->texinfo->vecs[1][3];
		t -= fa->texturemins[1];
		t += fa->light_t*16;
		t += 8;
		t /= LM_BLOCK_HEIGHT*16; //fa->texinfo->texture->height;

		poly->verts[i][5] = s;
		poly->verts[i][6] = t;
	}

	poly->numverts = lnumverts;

}

/*
========================
GL_CreateSurfaceLightmap
========================
*/
void GL_CreateSurfaceLightmap (msurface_t *surf)
{
	int smax, tmax;
	unsigned *base;

	if (surf->flags & (SURF_DRAWSKY|SURF_DRAWTURB))
		return;

	smax = (surf->extents[0]>>4)+1;
	tmax = (surf->extents[1]>>4)+1;

	if ( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ) )
	{
		LM_UploadBlock( false );
		LM_InitBlock();
		if ( !LM_AllocBlock( smax, tmax, &surf->light_s, &surf->light_t ) )
		{
			ri.Sys_Error( ERR_FATAL, "Consecutive calls to LM_AllocBlock(%d,%d) failed\n", smax, tmax );
		}
	}

	surf->lightmaptexturenum = gl_lms.current_lightmap_texture;

/* Knightmare- added for lightmap update batching */
#ifdef BATCH_LM_UPDATES
	base = gl_lms.lightmap_update[surf->lightmaptexturenum];
#else
	base = gl_lms.lightmap_buffer;
#endif	// BATCH_LM_UPDATES

	base += (surf->light_t * LM_BLOCK_WIDTH + surf->light_s);	// * LIGHTMAP_BYTES;

	R_SetCacheState( surf );
	R_BuildLightMap (surf, (byte *)base, LM_BLOCK_WIDTH*LIGHTMAP_BYTES);
}


/*
==================
GL_BeginBuildingLightmaps

==================
*/
void GL_BeginBuildingLightmaps (model_t *m)
{
	static lightstyle_t	lightstyles[MAX_LIGHTSTYLES];
	int i;
	static unsigned dummy[128*128];

	memset( gl_lms.allocated, 0, sizeof(gl_lms.allocated) );

/* Knightmare- added for lightmap update batching */
#ifdef BATCH_LM_UPDATES
	// free lightmap update buffers
	for (i=0; i<MAX_LIGHTMAPS; i++)
	{
		if (gl_lms.lightmap_update[i])
			free(gl_lms.lightmap_update[i]);
		gl_lms.lightmap_update[i] = NULL;
		gl_lms.modified[i] = false;
		gl_lms.lightrect[i].left = LM_BLOCK_WIDTH;
		gl_lms.lightrect[i].right = 0;
		gl_lms.lightrect[i].top = LM_BLOCK_HEIGHT;
		gl_lms.lightrect[i].bottom = 0;
	}
#endif	// BATCH_LM_UPDATES
/* end Knightmare */

	r_framecount = 1;		// no dlightcache

	GL_EnableMultitexture( true );
	GL_SelectTexture( GL_TEXTURE1_ARB );

	/*
	** setup the base lightstyles so the lightmaps won't have to be regenerated
	** the first time they're seen
	*/
	for (i=0 ; i<MAX_LIGHTSTYLES ; i++)
	{
		lightstyles[i].rgb[0] = 1;
		lightstyles[i].rgb[1] = 1;
		lightstyles[i].rgb[2] = 1;
		lightstyles[i].white = 3;
	}
	r_newrefdef.lightstyles = lightstyles;

	if (!gl_state.lightmap_textures)
	{
		gl_state.lightmap_textures	= TEXNUM_LIGHTMAPS;
//		gl_state.lightmap_textures	= gl_state.texture_extension_number;
//		gl_state.texture_extension_number = gl_state.lightmap_textures + MAX_LIGHTMAPS;
	}

	gl_lms.current_lightmap_texture = 1;

/* Knightmare- added for lightmap update batching */
#ifdef BATCH_LM_UPDATES
	// alloc lightmap update buffer if needed
	if (!gl_lms.lightmap_update[gl_lms.current_lightmap_texture]) {
		gl_lms.lightmap_update[gl_lms.current_lightmap_texture] = malloc (LM_BLOCK_WIDTH*LM_BLOCK_HEIGHT*LIGHTMAP_BYTES);
		memset (gl_lms.lightmap_update[gl_lms.current_lightmap_texture], 0, LM_BLOCK_WIDTH*LM_BLOCK_HEIGHT*LIGHTMAP_BYTES);
	}
#endif	// BATCH_LM_UPDATES

	/*
	** if mono lightmaps are enabled and we want to use alpha
	** blending (a,1-a) then we're likely running on a 3DLabs
	** Permedia2.  In a perfect world we'd use a GL_ALPHA lightmap
	** in order to conserve space and maximize bandwidth, however
	** this isn't a perfect world.
	**
	** So we have to use alpha lightmaps, but stored in GL_RGBA format,
	** which means we only get 1/16th the color resolution we should when
	** using alpha lightmaps.  If we find another board that supports
	** only alpha lightmaps but that can at least support the GL_ALPHA
	** format then we should change this code to use real alpha maps.
	*/
	if ( toupper( gl_monolightmap->string[0] ) == 'A' )
	{
      gl_lms.internal_format = gl_tex_alpha_format;
	}
	/*
	** try to do hacked colored lighting with a blended texture
	*/
	else if ( toupper( gl_monolightmap->string[0] ) == 'C' )
	{
      gl_lms.internal_format = gl_tex_alpha_format;
	}
	else if ( toupper( gl_monolightmap->string[0] ) == 'I' )
	{
		gl_lms.internal_format = GL_INTENSITY8;
	}
	else if ( toupper( gl_monolightmap->string[0] ) == 'L' )
	{
		gl_lms.internal_format = GL_LUMINANCE8;
	}
	else
	{
      gl_lms.internal_format = gl_tex_solid_format;
	}

	/*
	** initialize the dynamic lightmap texture
	*/
	GL_Bind( gl_state.lightmap_textures + 0 );
	qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	qglTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	qglTexImage2D( GL_TEXTURE_2D,0,gl_lms.internal_format,LM_BLOCK_WIDTH, LM_BLOCK_HEIGHT,0,GL_LIGHTMAP_FORMAT,GL_UNSIGNED_BYTE,dummy );
}

/*
=======================
GL_EndBuildingLightmaps
=======================
*/
void GL_EndBuildingLightmaps (void)
{
	LM_UploadBlock( false );
	GL_EnableMultitexture( false );
}

/*
=======================
GL_UpdateLightmap
=======================
*/
void GL_UpdateLightmap( msurface_t * surf )
{
//	unsigned *	temp = alloca(sizeof(unsigned) * 128*128);
	static unsigned temp[128*128];
	int			smax, tmax;

	GL_EnableMultitexture( true );
	GL_SelectTexture( GL_TEXTURE1_ARB );

	smax = (surf->extents[0]>>4)+1;
	tmax = (surf->extents[1]>>4)+1;

	R_BuildLightMap( surf, (byte *)temp, smax*4 );
	R_SetCacheState( surf );

	GL_MBind( GL_TEXTURE1_ARB, gl_state.lightmap_textures + surf->lightmaptexturenum );

#if defined(macintosh) && TARGET_API_MAC_CARBON
	qglPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE,1);
#endif
	qglTexSubImage2D2( GL_TEXTURE_2D, 0,
					  surf->light_s, surf->light_t,
					  smax, tmax,
					  GL_LIGHTMAP_FORMAT,
					  GL_UNSIGNED_BYTE, temp );
	GL_EnableMultitexture( false );
}
