/*
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.

*/

#include <string.h>
#include <math.h>

#include "quakedef.h"
#include "gl_local.h"
#include "gl_warp.h"
#include "gl_rsurf.h"
#include "sound.h"
#include "utils.h"

static char gl_initialised;

extern int gl_max_size_default;

int vbo_number;

entity_t	r_worldentity;

qboolean	r_cache_thrash;		// compatability

vec3_t		modelorg;
entity_t	*currententity;

int			r_visframecount;	// bumped when going to a new PVS
int			r_framecount;		// used for dlight push checking

mplane_t	frustum[4];

int			c_brush_polys, c_alias_polys;

int			particletexture;	// little dot for particles
int			playertextures;		// up to 16 color translated skins
int			playerfbtextures[MAX_CLIENTS];
int			skyboxtextures;
int			underwatertexture, detailtexture;

// view origin
vec3_t		vup, vpn, vright;
vec3_t		r_origin;

float	r_world_matrix[16];
float	r_base_world_matrix[16];

// screen size info
refdef_t	r_refdef;

mleaf_t		*r_viewleaf, *r_oldviewleaf;
mleaf_t		*r_viewleaf2, *r_oldviewleaf2;	// for watervis hack

texture_t	*r_notexture_mip;

int			d_lightstylevalue[256];	// 8.8 fraction of base light value

cvar_t	r_drawentities = {"r_drawentities", "1"};
cvar_t	r_lerpframes = {"r_lerpframes", "1"};
cvar_t	r_lerpmuzzlehack = {"r_lerpmuzzlehack", "1"};
cvar_t	r_drawflame = {"r_drawflame", "1"};
cvar_t	r_speeds = {"r_speeds", "0"};
cvar_t	r_fullbright = {"r_fullbright", "0"};
cvar_t	r_lightmap = {"r_lightmap", "0"};
cvar_t	gl_shaftlight = {"gl_shaftlight", "1"};
cvar_t	r_wateralpha = {"r_wateralpha", "1"};
cvar_t  r_fastturb = {"r_fastturb", "0"};
cvar_t	r_dynamic = {"r_dynamic", "1"};
cvar_t	r_novis = {"r_novis", "0"};
cvar_t	r_netgraph = {"r_netgraph", "0"};
cvar_t	r_fullbrightSkins = {"r_fullbrightSkins", "0"};
cvar_t	r_fastsky = {"r_fastsky", "0"};
cvar_t	r_skycolor = {"r_skycolor", "4"};
cvar_t	gl_colorlights		= {"gl_colorlights", "1"};

cvar_t	r_farclip			= {"r_farclip", "4096"};
qboolean OnChange_r_skyname(cvar_t *v, char *s);
cvar_t	r_skyname			= {"r_skyname", "", 0, OnChange_r_skyname};
cvar_t	gl_detail			= {"gl_detail","0"};
cvar_t	gl_caustics			= {"gl_caustics", "0"};

cvar_t	gl_subdivide_size = {"gl_subdivide_size", "128", CVAR_ARCHIVE};
cvar_t	gl_clear = {"gl_clear", "0"};
static qboolean OnChange_gl_clearColor(cvar_t *v, char *s);
cvar_t	gl_clearColor = {"gl_clearColor", "0 0 0", 0, OnChange_gl_clearColor};
cvar_t	gl_cull = {"gl_cull", "1"};
cvar_t	gl_ztrick = {"gl_ztrick", "0"};
cvar_t	gl_smoothmodels = {"gl_smoothmodels", "1"};
cvar_t	gl_affinemodels = {"gl_affinemodels", "0"};
cvar_t	gl_polyblend = {"gl_polyblend", "1"};
cvar_t	gl_flashblend = {"gl_flashblend", "0"};
cvar_t	gl_playermip = {"gl_playermip", "0"};
cvar_t	gl_nocolors = {"gl_nocolors", "0"};
cvar_t	gl_finish = {"gl_finish", "0"};
cvar_t	gl_fb_bmodels = {"gl_fb_bmodels", "1"};
cvar_t	gl_fb_models = {"gl_fb_models", "1"};
cvar_t	gl_lightmode = {"gl_lightmode", "2"};
cvar_t  gl_solidparticles = {"gl_solidparticles", "0"};
cvar_t	gl_loadlitfiles = {"gl_loadlitfiles", "1"};


cvar_t gl_part_explosions = {"gl_part_explosions", "0"};
cvar_t gl_part_trails = {"gl_part_trails", "0"};
cvar_t gl_part_spikes = {"gl_part_spikes", "0"};
cvar_t gl_part_gunshots = {"gl_part_gunshots", "0"};
cvar_t gl_part_blood = {"gl_part_blood", "0"};
cvar_t gl_part_telesplash = {"gl_part_telesplash", "0"};
cvar_t gl_part_blobs = {"gl_part_blobs", "0"};
cvar_t gl_part_lavasplash = {"gl_part_lavasplash", "0"};
cvar_t gl_part_inferno = {"gl_part_inferno", "0"};


int		lightmode = 2;

void R_MarkLeaves (void);
void R_InitBubble (void);

//Returns true if the box is completely outside the frustom
qboolean R_CullBox (vec3_t mins, vec3_t maxs)
{
	int i;

	for (i = 0; i < 4; i++)
	{
		if (BOX_ON_PLANE_SIDE (mins, maxs, &frustum[i]) == 2)
			return true;
	}
	return false;
}

//Returns true if the sphere is completely outside the frustum
qboolean R_CullSphere (vec3_t centre, float radius)
{
	int i;
	mplane_t *p;

	for (i = 0, p = frustum; i < 4; i++, p++)
	{
		if (PlaneDiff(centre, p) <= -radius)
			return true;
	}

	return false;
}

static void R_RotateForEntity (entity_t *e)
{
	glTranslatef (e->origin[0],  e->origin[1],  e->origin[2]);

	glRotatef (e->angles[1], 0, 0, 1);
	glRotatef (-e->angles[0], 0, 1, 0);
	glRotatef (e->angles[2], 1, 0, 0);
}


static mspriteframe_t *R_GetSpriteFrame (entity_t *currententity)
{
	msprite_t *psprite;
	mspritegroup_t *pspritegroup;
	mspriteframe_t *pspriteframe;
	int i, numframes, frame;
	float *pintervals, fullinterval, targettime, time;

	psprite = currententity->model->extradata;
	frame = currententity->frame;

	if (frame >= psprite->numframes || frame < 0)
	{
		Com_Printf ("R_GetSpriteFrame: no such frame %d\n", frame);
		frame = 0;
	}

	if (psprite->frames[frame].type == SPR_SINGLE)
	{
		pspriteframe = psprite->frames[frame].frameptr;
	}
	else
	{
		pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr;
		pintervals = pspritegroup->intervals;
		numframes = pspritegroup->numframes;
		fullinterval = pintervals[numframes-1];

		time = cl.time;

		// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values
		// are positive, so we don't have to worry about division by 0
		targettime = time - ((int) (time / fullinterval)) * fullinterval;

		for (i = 0; i < (numframes - 1); i++)
		{
			if (pintervals[i] > targettime)
				break;
		}

		pspriteframe = pspritegroup->frames[i];
	}

	return pspriteframe;
}

static void R_DrawSpriteModel (entity_t *e)
{
	vec3_t point, right, up;
	mspriteframe_t *frame;
	msprite_t *psprite;

	// don't even bother culling, because it's just a single
	// polygon without a surface cache
	frame = R_GetSpriteFrame (e);
	psprite = currententity->model->extradata;

	if (psprite->type == SPR_ORIENTED)
	{
		// bullet marks on walls
		AngleVectors (currententity->angles, NULL, right, up);
	}
	else if (psprite->type == SPR_FACING_UPRIGHT)
	{
		VectorSet (up, 0, 0, 1);
		right[0] = e->origin[1] - r_origin[1];
		right[1] = -(e->origin[0] - r_origin[0]);
		right[2] = 0;
		VectorNormalizeFast (right);
	}
	else if (psprite->type == SPR_VP_PARALLEL_UPRIGHT)
	{
		VectorSet (up, 0, 0, 1);
		VectorCopy (vright, right);
	}
	else {	// normal sprite
		VectorCopy (vup, up);
		VectorCopy (vright, right);
	}

	GL_Bind(frame->gl_texturenum);

	glBegin (GL_QUADS);

	glTexCoord2f (0, 1);
	VectorMA (e->origin, frame->down, up, point);
	VectorMA (point, frame->left, right, point);
	glVertex3fv (point);

	glTexCoord2f (0, 0);
	VectorMA (e->origin, frame->up, up, point);
	VectorMA (point, frame->left, right, point);
	glVertex3fv (point);

	glTexCoord2f (1, 0);
	VectorMA (e->origin, frame->up, up, point);
	VectorMA (point, frame->right, right, point);
	glVertex3fv (point);

	glTexCoord2f (1, 1);
	VectorMA (e->origin, frame->down, up, point);
	VectorMA (point, frame->right, right, point);
	glVertex3fv (point);

	glEnd ();
}


#define NUMVERTEXNORMALS	162

vec3_t	shadevector;

qboolean	full_light;
float		shadelight, ambientlight;

// precalculated dot products for quantized angles
#define SHADEDOT_QUANT 64
byte	r_avertexnormal_dots[SHADEDOT_QUANT][NUMVERTEXNORMALS] =
#include "anorm_dots.h"
;

byte	*shadedots = r_avertexnormal_dots[0];

int		lastposenum;

float	r_framelerp;
float	r_modelalpha;
float	r_lerpdistance;

static void GL_DrawAliasFrame_Lerp(aliashdr_t *paliashdr, int pose1, int pose2, qboolean mtex)
{
	int *order, count;
	vec3_t interpolated_verts;
	float l, lerpfrac;
	trivertx_t *verts1, *verts2;

	lerpfrac = r_framelerp;
	lastposenum = (lerpfrac >= 0.5) ? pose2 : pose1;

	verts2 = verts1 = paliashdr->posedata;

	verts1 += pose1 * paliashdr->poseverts;
	verts2 += pose2 * paliashdr->poseverts;

	order = paliashdr->commands;

	if (r_modelalpha < 1)
		glEnable(GL_BLEND);

	while ((count = *order++))
	{
		if (count < 0)
		{
			count = -count;
			glBegin(GL_TRIANGLE_FAN);
		}
		else if (count > 0)
		{
			glBegin(GL_TRIANGLE_STRIP);
		}

		do
		{
			// texture coordinates come from the draw list
			if (mtex)
			{
				qglMultiTexCoord2f (GL_TEXTURE0_ARB, ((float *) order)[0], ((float *) order)[1]);
				qglMultiTexCoord2f (GL_TEXTURE1_ARB, ((float *) order)[0], ((float *) order)[1]);
			}
			else
			{
				glTexCoord2f (((float *) order)[0], ((float *) order)[1]);
			}

			order += 2;

			if ((currententity->flags & RF_LIMITLERP))
			{
				lerpfrac = VectorL2Compare(verts1->v, verts2->v, r_lerpdistance) ? r_framelerp : 1;
			}


			l = FloatInterpolate(shadedots[verts1->lightnormalindex], lerpfrac, shadedots[verts2->lightnormalindex]) / 127.0;
			l = (l * shadelight + ambientlight) / 256.0;
			l = min(l , 1);
			glColor4f (l, l, l, r_modelalpha);

			VectorInterpolate(verts1->v, lerpfrac, verts2->v, interpolated_verts);
			glVertex3fv(interpolated_verts);


			verts1++;
			verts2++;
		} while (--count);

		glEnd();
	}

	if (r_modelalpha < 1)
		glDisable(GL_BLEND);
}

static void GL_DrawAliasFrame_NoLerp(aliashdr_t *paliashdr, int pose, qboolean mtex)
{
	int *order, count;
	float l;
	trivertx_t *verts;
	vec3_t v3;

	verts = paliashdr->posedata;

	verts += pose * paliashdr->poseverts;

	order = paliashdr->commands;

	if (r_modelalpha < 1)
		glEnable(GL_BLEND);

	while ((count = *order++))
	{
		if (count < 0)
		{
			count = -count;
			glBegin(GL_TRIANGLE_FAN);
		}
		else if (count > 0)
		{
			glBegin(GL_TRIANGLE_STRIP);
		}

		do
		{
			// texture coordinates come from the draw list
			if (mtex)
			{
				qglMultiTexCoord2f (GL_TEXTURE0_ARB, ((float *) order)[0], ((float *) order)[1]);
				qglMultiTexCoord2f (GL_TEXTURE1_ARB, ((float *) order)[0], ((float *) order)[1]);
			}
			else
			{
				glTexCoord2f (((float *) order)[0], ((float *) order)[1]);
			}

			order += 2;

			l = (float)shadedots[verts->lightnormalindex] / 127.0;
			l = (l * shadelight + ambientlight) / 256.0;
			l = min(l , 1);
			glColor4f (l, l, l, r_modelalpha);

			v3[0] = verts->v[0];
			v3[1] = verts->v[1];
			v3[2] = verts->v[2];
			glVertex3fv(v3);

			verts++;
		} while (--count);

		glEnd();
	}

	if (r_modelalpha < 1)
		glDisable(GL_BLEND);
}

static void GL_DrawAliasFrame(aliashdr_t *paliashdr, int pose1, int pose2, qboolean mtex)
{
	if (pose1 == pose2)
		GL_DrawAliasFrame_NoLerp(paliashdr, pose1, mtex);
	else
		GL_DrawAliasFrame_Lerp(paliashdr, pose1, pose2, mtex);
}

static void R_SetupAliasFrame (maliasframedesc_t *oldframe, maliasframedesc_t *frame, aliashdr_t *paliashdr, qboolean mtex)
{
	int oldpose, pose, numposes;
	float interval;

	oldpose = oldframe->firstpose;
	numposes = oldframe->numposes;
	if (numposes > 1)
	{
		interval = oldframe->interval;
		oldpose += (int) (cl.time / interval) % numposes;
	}

	pose = frame->firstpose;
	numposes = frame->numposes;
	if (numposes > 1)
	{
		interval = frame->interval;
		pose += (int) (cl.time / interval) % numposes;
	}

	GL_DrawAliasFrame (paliashdr, oldpose, pose, mtex);
}

static void R_AliasSetupFullLight(model_t *model)
{
	if (model->modhint == MOD_THUNDERBOLT
	 || model->modhint == MOD_FLAME
	 || (model->modhint == MOD_PLAYER && bound(0, r_fullbrightSkins.value, cl.fbskins)))
	{
		full_light = true;
	}
	else
	{
		full_light = false;
	}
}

static void R_AliasSetupLighting(entity_t *ent)
{
	int minlight, lnum;
	float add, fbskins;
	vec3_t dist;
	model_t *clmodel;

	clmodel = ent->model;

	// make thunderbolt and torches full light
	if (clmodel->modhint == MOD_THUNDERBOLT)
	{
		ambientlight = 60 + 150 * bound(0, gl_shaftlight.value, 1);
		shadelight = 0;
		return;
	}
	else if (clmodel->modhint == MOD_FLAME)
	{
		ambientlight = 255;
		shadelight = 0;
		return;
	}

	//normal lighting
	ambientlight = shadelight = R_LightPoint (ent->origin);

	for (lnum = 0; lnum < MAX_DLIGHTS; lnum++)
	{
		if (cl_dlights[lnum].die < cl.time || !cl_dlights[lnum].radius)
			continue;

		VectorSubtract (ent->origin, cl_dlights[lnum].origin, dist);
		add = cl_dlights[lnum].radius - VectorLength(dist);

		if (add > 0)
			ambientlight += add;
	}

	// clamp lighting so it doesn't overbright as much
	if (ambientlight > 128)
		ambientlight = 128;
	if (ambientlight + shadelight > 192)
		shadelight = 192 - ambientlight;

	// always give the gun some light
	if ((ent->flags & RF_WEAPONMODEL) && ambientlight < 24)
		ambientlight = shadelight = 24;

	// never allow players to go totally black
	if (clmodel->modhint == MOD_PLAYER)
	{
		if (ambientlight < 8)
			ambientlight = shadelight = 8;
	}


	if (clmodel->modhint == MOD_PLAYER)
	{
		fbskins = bound(0, r_fullbrightSkins.value, cl.fbskins);
		if (fbskins)
		{
			ambientlight = max(ambientlight, 8 + fbskins * 120);
			shadelight = max(shadelight, 8 + fbskins * 120);
		}
	}

	minlight = cl.minlight;

	if (ambientlight < minlight)
		ambientlight = shadelight = minlight;
}

static void R_DrawAliasModelList2TMU(entity_t *ent, unsigned int entcount)
{
	int i, anim, skinnum, texture, fb_texture;
	int oldtexture;
	int oldfb_texture;
	int oldmtex;
	float scale;
	vec3_t mins, maxs;
	aliashdr_t *paliashdr;
	model_t *clmodel;
	maliasframedesc_t *oldframe, *frame;
	extern	cvar_t r_viewmodelsize, cl_drawgun;

	oldmtex = 0;
	oldtexture = -1;
	oldfb_texture = -1;

	VectorSubtract (r_origin, ent->origin, modelorg);

	clmodel = ent->model;
	paliashdr = (aliashdr_t *) Mod_Extradata (ent->model);	//locate the proper data

	R_AliasSetupFullLight(clmodel);

	if (gl_smoothmodels.value)
		glShadeModel (GL_SMOOTH);

	if (gl_affinemodels.value)
		glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);

	c_alias_polys += paliashdr->numtris * entcount;
	anim = (int) (cl.time * 10) & 3;

	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	GL_SelectTexture(GL_TEXTURE1_ARB);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);
	GL_SelectTexture(GL_TEXTURE0_ARB);

	/* Stuff that depends on the entity itself. */

	ent--;

	while(ent++, entcount--)
	{
		currententity = ent;

		if (ent->frame >= paliashdr->numframes || ent->frame < 0)
		{
			Com_DPrintf ("R_DrawAliasModel: no such frame %d\n", ent->frame);
			ent->frame = 0;
		}
		if (ent->oldframe >= paliashdr->numframes || ent->oldframe < 0)
		{
			Com_DPrintf ("R_DrawAliasModel: no such oldframe %d\n", ent->oldframe);
			ent->oldframe = 0;
		}

		frame = &paliashdr->frames[ent->frame];
		oldframe = &paliashdr->frames[ent->oldframe];

		if (!r_lerpframes.value || ent->framelerp < 0 || ent->oldframe == ent->frame)
			r_framelerp = 1.0;
		else
			r_framelerp = min (ent->framelerp, 1);

		//culling
		if (!(ent->flags & RF_WEAPONMODEL))
		{
			if (ent->angles[0] || ent->angles[1] || ent->angles[2])
			{
				if (R_CullSphere (ent->origin, max(oldframe->radius, frame->radius)))
					continue;
			}
			else
			{
				if (r_framelerp == 1)
				{
					VectorAdd(ent->origin, frame->bboxmin, mins);
					VectorAdd(ent->origin, frame->bboxmax, maxs);
				}
				else
				{
					for (i = 0; i < 3; i++)
					{
						mins[i] = ent->origin[i] + min (oldframe->bboxmin[i], frame->bboxmin[i]);
						maxs[i] = ent->origin[i] + max (oldframe->bboxmax[i], frame->bboxmax[i]);
					}
				}
				if (R_CullBox (mins, maxs))
					continue;
			}
		}

		//get lighting information
		R_AliasSetupLighting(ent);

		shadedots = r_avertexnormal_dots[((int) (ent->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1)];

		//draw all the triangles
		glPushMatrix ();
		R_RotateForEntity (ent);

		if (clmodel->modhint == MOD_EYES)
		{
			glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2] - (22 + 8));
			// double size of eyes, since they are really hard to see in gl
			glScalef (paliashdr->scale[0] * 2, paliashdr->scale[1] * 2, paliashdr->scale[2] * 2);
		}
		else if (ent->flags & RF_WEAPONMODEL)
		{
			scale = 0.5 + bound(0, r_viewmodelsize.value, 1) / 2;
			glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]);
			glScalef (paliashdr->scale[0] * scale, paliashdr->scale[1], paliashdr->scale[2]);
		}
		else
		{
			glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]);
			glScalef (paliashdr->scale[0], paliashdr->scale[1], paliashdr->scale[2]);
		}

		skinnum = ent->skinnum;
		if (skinnum >= paliashdr->numskins || skinnum < 0)
		{
			Com_DPrintf ("R_DrawAliasModel: no such skin # %d\n", skinnum);
			skinnum = 0;
		}

		texture = paliashdr->gl_texturenum[skinnum][anim];
		fb_texture = paliashdr->fb_texturenum[skinnum][anim];

		r_modelalpha = ((ent->flags & RF_WEAPONMODEL) && gl_mtexable) ? bound(0, cl_drawgun.value, 1) : 1;

		// we can't dynamically colormap textures, so they are cached separately for the players.  Heads are just uncolored.
		if (ent->scoreboard && !gl_nocolors.value)
		{
			i = ent->scoreboard - cl.players;
			if (i >= 0 && i < MAX_CLIENTS)
			{
				if (!ent->scoreboard->skin)
					CL_NewTranslation(i);
				texture = playertextures + i;
				fb_texture = playerfbtextures[i];
			}
		}

		if (full_light || !gl_fb_models.value)
			fb_texture = 0;

		if (fb_texture)
		{
			if (oldmtex != 1)
			{
				GL_EnableMultitexture();
				oldmtex = 1;
			}

			if (texture != oldtexture)
			{
				GL_SelectTexture(GL_TEXTURE0_ARB);
				GL_Bind(texture);
				oldtexture = texture;
			}

			if (fb_texture != oldfb_texture)
			{
				GL_SelectTexture(GL_TEXTURE1_ARB);
				GL_Bind(fb_texture);
				oldfb_texture = fb_texture;
			}

			R_SetupAliasFrame(oldframe, frame, paliashdr, true);
		}
		else
		{
			if (oldmtex != 0)
			{
				GL_SelectTexture(GL_TEXTURE1_ARB);
				GL_DisableMultitexture();
				oldmtex = 0;
			}

			if (texture != oldtexture)
			{
				GL_Bind(texture);
				oldtexture = texture;
			}

			R_SetupAliasFrame(oldframe, frame, paliashdr, false);
		}

		glPopMatrix ();
	}

	/* End of per-entity stuff */

	if (oldmtex == 1)
	{
		GL_SelectTexture(GL_TEXTURE1_ARB);
		GL_DisableMultitexture();
	}

	if (gl_smoothmodels.value)
		glShadeModel (GL_FLAT);

	if (gl_affinemodels.value)
		glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	glColor3ubv (color_white);
}

static void R_DrawAliasModel(entity_t *ent)
{
	int i, anim, skinnum, texture, fb_texture;
	float scale;
	vec3_t mins, maxs;
	aliashdr_t *paliashdr;
	model_t *clmodel;
	maliasframedesc_t *oldframe, *frame;
	extern	cvar_t r_viewmodelsize, cl_drawgun;

	VectorSubtract (r_origin, ent->origin, modelorg);

	clmodel = ent->model;
	paliashdr = (aliashdr_t *) Mod_Extradata (ent->model);	//locate the proper data

	if (ent->frame >= paliashdr->numframes || ent->frame < 0)
	{
		Com_DPrintf ("R_DrawAliasModel: no such frame %d\n", ent->frame);
		ent->frame = 0;
	}
	if (ent->oldframe >= paliashdr->numframes || ent->oldframe < 0)
	{
		Com_DPrintf ("R_DrawAliasModel: no such oldframe %d\n", ent->oldframe);
		ent->oldframe = 0;
	}

	frame = &paliashdr->frames[ent->frame];
	oldframe = &paliashdr->frames[ent->oldframe];


	if (!r_lerpframes.value || ent->framelerp < 0 || ent->oldframe == ent->frame)
		r_framelerp = 1.0;
	else
		r_framelerp = min (ent->framelerp, 1);

	//culling
	if (!(ent->flags & RF_WEAPONMODEL))
	{
		if (ent->angles[0] || ent->angles[1] || ent->angles[2])
		{
			if (R_CullSphere (ent->origin, max(oldframe->radius, frame->radius)))
				return;
		}
		else
		{
			if (r_framelerp == 1)
			{
				VectorAdd(ent->origin, frame->bboxmin, mins);
				VectorAdd(ent->origin, frame->bboxmax, maxs);
			}
			else
			{
				for (i = 0; i < 3; i++)
				{
					mins[i] = ent->origin[i] + min (oldframe->bboxmin[i], frame->bboxmin[i]);
					maxs[i] = ent->origin[i] + max (oldframe->bboxmax[i], frame->bboxmax[i]);
				}
			}
			if (R_CullBox (mins, maxs))
				return;
		}
	}

	//get lighting information
	R_AliasSetupFullLight(clmodel);
	R_AliasSetupLighting(ent);

	shadedots = r_avertexnormal_dots[((int) (ent->angles[1] * (SHADEDOT_QUANT / 360.0))) & (SHADEDOT_QUANT - 1)];

	//draw all the triangles
	c_alias_polys += paliashdr->numtris;
	glPushMatrix ();
	R_RotateForEntity (ent);

	if (clmodel->modhint == MOD_EYES)
	{
		glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2] - (22 + 8));
		// double size of eyes, since they are really hard to see in gl
		glScalef (paliashdr->scale[0] * 2, paliashdr->scale[1] * 2, paliashdr->scale[2] * 2);
	}
	else if (ent->flags & RF_WEAPONMODEL)
	{
		scale = 0.5 + bound(0, r_viewmodelsize.value, 1) / 2;
		glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]);
		glScalef (paliashdr->scale[0] * scale, paliashdr->scale[1], paliashdr->scale[2]);
	}
	else
	{
		glTranslatef (paliashdr->scale_origin[0], paliashdr->scale_origin[1], paliashdr->scale_origin[2]);
		glScalef (paliashdr->scale[0], paliashdr->scale[1], paliashdr->scale[2]);
	}

	anim = (int) (cl.time * 10) & 3;
	skinnum = ent->skinnum;
	if (skinnum >= paliashdr->numskins || skinnum < 0)
	{
		Com_DPrintf ("R_DrawAliasModel: no such skin # %d\n", skinnum);
		skinnum = 0;
	}

	texture = paliashdr->gl_texturenum[skinnum][anim];
	fb_texture = paliashdr->fb_texturenum[skinnum][anim];

	r_modelalpha = ((ent->flags & RF_WEAPONMODEL) && gl_mtexable) ? bound(0, cl_drawgun.value, 1) : 1;

	// we can't dynamically colormap textures, so they are cached separately for the players.  Heads are just uncolored.
	if (ent->scoreboard && !gl_nocolors.value)
	{
		i = ent->scoreboard - cl.players;
		if (i >= 0 && i < MAX_CLIENTS)
		{
			if (!ent->scoreboard->skin)
				CL_NewTranslation(i);
			texture = playertextures + i;
			fb_texture = playerfbtextures[i];
		}
	}

	if (full_light || !gl_fb_models.value)
		fb_texture = 0;

	if (gl_smoothmodels.value)
		glShadeModel (GL_SMOOTH);

	if (gl_affinemodels.value)
		glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_FASTEST);

	if (fb_texture && gl_mtexable)
	{
		GL_Bind (texture);
		glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

		GL_EnableMultitexture ();
		GL_Bind (fb_texture);
		glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL);

		R_SetupAliasFrame (oldframe, frame, paliashdr, true);

		GL_DisableMultitexture ();
	}
	else
	{
		GL_Bind (texture);
		glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);

		R_SetupAliasFrame (oldframe, frame, paliashdr, false);

		if (fb_texture)
		{
			glTexEnvf (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
			glEnable (GL_ALPHA_TEST);
			GL_Bind (fb_texture);

			R_SetupAliasFrame (oldframe, frame, paliashdr, false);

			glDisable (GL_ALPHA_TEST);
		}
	}

	if (gl_smoothmodels.value)
		glShadeModel (GL_FLAT);

	if (gl_affinemodels.value)
		glHint (GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	glPopMatrix ();

	glColor3ubv (color_white);
}


void R_DrawEntitiesOnList(visentlist_t *vislist)
{
	int i;
	int j;

	if (!r_drawentities.value || !vislist->count)
		return;

	if (vislist->alpha)
		glEnable (GL_ALPHA_TEST);

	// draw sprites separately, because of alpha_test
	for (i = 0; i < vislist->count; i++)
	{
		currententity = &vislist->list[i];
		switch (currententity->model->type)
		{
			case mod_alias:
				if (gl_mtexable)
				{
					for(j=1;i+j < vislist->count && vislist->list[i+j].model == currententity->model;j++);
					if (j > 1)
					{
						R_DrawAliasModelList2TMU(currententity, j);
						i += j-1;
						break;
					}
				}

				R_DrawAliasModel (currententity);
				break;
			case mod_brush:
				R_DrawBrushModel (currententity);
				break;
			case mod_sprite:
				R_DrawSpriteModel (currententity);
				break;
		}
	}

	if (vislist->alpha)
		glDisable (GL_ALPHA_TEST);
}

static void R_DrawViewModel(void)
{
	centity_t *cent;
	static entity_t gun;

	if (!r_drawentities.value || !cl.viewent.current.modelindex)
		return;

	memset(&gun, 0, sizeof(gun));
	cent = &cl.viewent;
	currententity = &gun;

	if (!(gun.model = cl.model_precache[cent->current.modelindex]))
		Host_Error ("R_DrawViewModel: bad modelindex");

	VectorCopy(cent->current.origin, gun.origin);
	VectorCopy(cent->current.angles, gun.angles);
	gun.colormap = vid.colormap;
	gun.flags = RF_WEAPONMODEL | RF_NOSHADOW;
	if (r_lerpmuzzlehack.value)
	{
		if (cent->current.modelindex != cl_modelindices[mi_vaxe]
		 && cent->current.modelindex != cl_modelindices[mi_vbio]
		 && cent->current.modelindex != cl_modelindices[mi_vgrap]
		 && cent->current.modelindex != cl_modelindices[mi_vknife]
		 && cent->current.modelindex != cl_modelindices[mi_vknife2]
		 && cent->current.modelindex != cl_modelindices[mi_vmedi]
		 && cent->current.modelindex != cl_modelindices[mi_vspan])
		{
			gun.flags |= RF_LIMITLERP;
			r_lerpdistance =  135;
		}
	}

	gun.frame = cent->current.frame;
	if (cent->frametime >= 0 && cent->frametime <= cl.time)
	{
		gun.oldframe = cent->oldframe;
		gun.framelerp = (cl.time - cent->frametime) * 10;
	}
	else
	{
		gun.oldframe = gun.frame;
		gun.framelerp = -1;
	}

	// hack the depth range to prevent view model from poking into walls
	glDepthRange (gldepthmin, gldepthmin + 0.3 * (gldepthmax - gldepthmin));
	R_DrawAliasModel (currententity);
	glDepthRange (gldepthmin, gldepthmax);
}


void R_PolyBlend (void)
{
	extern cvar_t gl_hwblend;

	if (VID_HWGammaSupported() && gl_hwblend.value && !cl.teamfortress)
		return;
	if (!v_blend[3])
		return;

	glDisable (GL_ALPHA_TEST);
	glEnable (GL_BLEND);
	glDisable (GL_TEXTURE_2D);

	glColor4fv (v_blend);

	glBegin (GL_QUADS);
	glVertex2f (r_refdef.vrect.x, r_refdef.vrect.y);
	glVertex2f (r_refdef.vrect.x + r_refdef.vrect.width, r_refdef.vrect.y);
	glVertex2f (r_refdef.vrect.x + r_refdef.vrect.width, r_refdef.vrect.y + r_refdef.vrect.height);
	glVertex2f (r_refdef.vrect.x, r_refdef.vrect.y + r_refdef.vrect.height);
	glEnd ();

	glDisable (GL_BLEND);
	glEnable (GL_TEXTURE_2D);
	glEnable (GL_ALPHA_TEST);

	glColor3ubv (color_white);
}

void R_BrightenScreen (void)
{
	extern float vid_gamma;
	float f;

	if (VID_HWGammaSupported())
		return;
	if (v_contrast.value <= 1.0)
		return;

	f = min (v_contrast.value, 3);
	f = pow (f, vid_gamma);

	glDisable (GL_TEXTURE_2D);
	glEnable (GL_BLEND);
	glBlendFunc (GL_DST_COLOR, GL_ONE);
	glBegin (GL_QUADS);
	while (f > 1)
	{
		if (f >= 2)
			glColor3ubv (color_white);
		else
			glColor3f (f - 1, f - 1, f - 1);
		glVertex2f (0, 0);
		glVertex2f (vid.width, 0);
		glVertex2f (vid.width, vid.height);
		glVertex2f (0, vid.height);
		f *= 0.5;
	}
	glEnd ();
	glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glEnable (GL_TEXTURE_2D);
	glDisable (GL_BLEND);
	glColor3ubv (color_white);
}

static int SignbitsForPlane (mplane_t *out)
{
	int	bits, j;

	// for fast box on planeside test
	bits = 0;
	for (j = 0; j < 3; j++)
	{
		if (out->normal[j] < 0)
			bits |= 1 << j;
	}
	return bits;
}


static void R_SetFrustum (void)
{
	int i;

	// rotate VPN right by FOV_X/2 degrees
	RotatePointAroundVector( frustum[0].normal, vup, vpn, -(90-r_refdef.fov_x / 2 ) );
	// rotate VPN left by FOV_X/2 degrees
	RotatePointAroundVector( frustum[1].normal, vup, vpn, 90-r_refdef.fov_x / 2 );
	// rotate VPN up by FOV_X/2 degrees
	RotatePointAroundVector( frustum[2].normal, vright, vpn, 90-r_refdef.fov_y / 2 );
	// rotate VPN down by FOV_X/2 degrees
	RotatePointAroundVector( frustum[3].normal, vright, vpn, -( 90 - r_refdef.fov_y / 2 ) );

	for (i = 0; i < 4; i++)
	{
		frustum[i].type = PLANE_ANYZ;
		frustum[i].dist = DotProduct (r_origin, frustum[i].normal);
		frustum[i].signbits = SignbitsForPlane (&frustum[i]);
	}
}

static void R_SetupFrame (void)
{
	vec3_t testorigin;
	mleaf_t	*leaf;

	// don't allow cheats in multiplayer
	r_fullbright.value = 0;
	r_lightmap.value = 0;

	R_AnimateLight ();

	r_framecount++;

	// build the transformation matrix for the given view angles
	VectorCopy (r_refdef.vieworg, r_origin);
	AngleVectors (r_refdef.viewangles, vpn, vright, vup);

	// current viewleaf
	r_oldviewleaf = r_viewleaf;
	r_oldviewleaf2 = r_viewleaf2;

	r_viewleaf = Mod_PointInLeaf (r_origin, cl.worldmodel);
	r_viewleaf2 = NULL;

	// check above and below so crossing solid water doesn't draw wrong
	if (r_viewleaf->contents <= CONTENTS_WATER && r_viewleaf->contents >= CONTENTS_LAVA)
	{
		// look up a bit
		VectorCopy (r_origin, testorigin);
		testorigin[2] += 10;
		leaf = Mod_PointInLeaf (testorigin, cl.worldmodel);
		if (leaf->contents == CONTENTS_EMPTY)
			r_viewleaf2 = leaf;
	}
	else if (r_viewleaf->contents == CONTENTS_EMPTY)
	{
		// look down a bit
		VectorCopy (r_origin, testorigin);
		testorigin[2] -= 10;
		leaf = Mod_PointInLeaf (testorigin, cl.worldmodel);
		if (leaf->contents <= CONTENTS_WATER &&	leaf->contents >= CONTENTS_LAVA)
			r_viewleaf2 = leaf;
	}

	V_SetContentsColor (r_viewleaf->contents);
	V_CalcBlend ();

	r_cache_thrash = false;

	c_brush_polys = 0;
	c_alias_polys = 0;
}

__inline void MYgluPerspective(GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar)
{
	GLdouble xmin, xmax, ymin, ymax;

	ymax = zNear * tan(fovy * M_PI / 360.0);
	ymin = -ymax;

	xmin = ymin * aspect;
	xmax = ymax * aspect;

	glFrustum(xmin, xmax, ymin, ymax, zNear, zFar);
}

static void R_SetupGL (void)
{
	float screenaspect;
	extern int glwidth, glheight;
	int x, x2, y2, y, w, h, farclip;

	// set up viewpoint
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity ();
	x = r_refdef.vrect.x * glwidth / vid.width;
	x2 = (r_refdef.vrect.x + r_refdef.vrect.width) * glwidth / vid.width;
	y = (vid.height-r_refdef.vrect.y) * glheight / vid.height;
	y2 = (vid.height - (r_refdef.vrect.y + r_refdef.vrect.height)) * glheight / vid.height;

	w = x2 - x;
	h = y - y2;

	glViewport (glx + x, gly + y2, w, h);
	screenaspect = (float)r_refdef.vrect.width/r_refdef.vrect.height;
	farclip = max((int) r_farclip.value, 4096);
	MYgluPerspective (r_refdef.fov_y, screenaspect, 4, farclip);

	glCullFace(GL_FRONT);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity ();

	glRotatef (-90, 1, 0, 0);	    // put Z going up
	glRotatef (90,  0, 0, 1);	    // put Z going up
	glRotatef (-r_refdef.viewangles[2], 1, 0, 0);
	glRotatef (-r_refdef.viewangles[0], 0, 1, 0);
	glRotatef (-r_refdef.viewangles[1], 0, 0, 1);
	glTranslatef (-r_refdef.vieworg[0], -r_refdef.vieworg[1], -r_refdef.vieworg[2]);

	glGetFloatv (GL_MODELVIEW_MATRIX, r_world_matrix);

	// set drawing parms
	if (gl_cull.value)
		glEnable(GL_CULL_FACE);
	else
		glDisable(GL_CULL_FACE);

	glDisable(GL_BLEND);
	glDisable(GL_ALPHA_TEST);
	glEnable(GL_DEPTH_TEST);
}

void R_CvarInit(void)
{
	Cmd_AddCommand ("loadsky", R_LoadSky_f);
	Cmd_AddCommand ("timerefresh", R_TimeRefresh_f);
#ifndef CLIENTONLY
	Cmd_AddCommand ("pointfile", R_ReadPointFile_f);
#endif

	Cvar_SetCurrentGroup(CVAR_GROUP_PARTICLES);
	Cvar_Register (&gl_solidparticles);
	Cvar_Register (&gl_part_explosions);
	Cvar_Register (&gl_part_trails);
	Cvar_Register (&gl_part_spikes);
	Cvar_Register (&gl_part_gunshots);
	Cvar_Register (&gl_part_blood);
	Cvar_Register (&gl_part_telesplash);
	Cvar_Register (&gl_part_blobs);
	Cvar_Register (&gl_part_lavasplash);
	Cvar_Register (&gl_part_inferno);

	Cvar_SetCurrentGroup(CVAR_GROUP_TURB);
	Cvar_Register (&r_skyname);
	Cvar_Register (&r_fastsky);
	Cvar_Register (&r_skycolor);
	Cvar_Register (&r_wateralpha);
	Cvar_Register (&r_fastturb);
	Cvar_Register (&r_novis);

	Cvar_SetCurrentGroup(CVAR_GROUP_EYECANDY);
	Cvar_Register (&r_drawentities);
	Cvar_Register (&r_lerpframes);
	Cvar_Register (&r_lerpmuzzlehack);
	Cvar_Register (&r_drawflame);
	Cvar_Register (&gl_detail);
	Cvar_Register (&gl_caustics);

	Cvar_SetCurrentGroup(CVAR_GROUP_BLEND);
	Cvar_Register (&gl_polyblend);

	Cvar_SetCurrentGroup(CVAR_GROUP_SKIN);
	Cvar_Register (&r_fullbrightSkins);

	Cvar_SetCurrentGroup(CVAR_GROUP_LIGHTING);
	Cvar_Register (&r_dynamic);
	Cvar_Register (&gl_fb_bmodels);
	Cvar_Register (&gl_fb_models);
	Cvar_Register (&gl_lightmode);
	Cvar_Register (&gl_flashblend);
	Cvar_Register (&r_fullbright);
	Cvar_Register (&r_lightmap);
	Cvar_Register (&gl_shaftlight);
	Cvar_Register (&gl_loadlitfiles);
	Cvar_Register (&gl_colorlights);

	Cvar_SetCurrentGroup(CVAR_GROUP_TEXTURES);
	Cvar_Register (&gl_playermip);
	Cvar_Register (&gl_subdivide_size);

	Cvar_SetCurrentGroup(CVAR_GROUP_OPENGL);
	Cvar_Register (&r_farclip);
	Cvar_Register (&gl_smoothmodels);
	Cvar_Register (&gl_affinemodels);
	Cvar_Register (&gl_clear);
	Cvar_Register (&gl_clearColor);
	Cvar_Register (&gl_cull);
	Cvar_Register (&gl_ztrick);
	Cvar_Register (&gl_nocolors);
	Cvar_Register (&gl_finish);

	Cvar_SetCurrentGroup(CVAR_GROUP_SCREEN);
	Cvar_Register (&r_speeds);
	Cvar_Register (&r_netgraph);

	Cvar_ResetCurrentGroup();

	GL_Particles_CvarInit();
}

int R_Init(void)
{
	texture_extension_number = 1;
	vbo_number = 1;

	GL_Init();

	glGetIntegerv(GL_MAX_TEXTURE_SIZE, &gl_max_size_default);
	Cvar_SetDefault(&gl_max_size, gl_max_size_default);

	GL_Texture_Init();

	GL_RSurf_Init();

	GL_Warp_Init();

	if (R_InitTextures())
	{
		R_InitBubble();

		if (R_InitParticles())
		{
			netgraphtexture = texture_extension_number;
			texture_extension_number++;

			playertextures = texture_extension_number;
			texture_extension_number += MAX_CLIENTS;

			// fullbright skins
			texture_extension_number += MAX_CLIENTS;
			skyboxtextures = texture_extension_number;
			texture_extension_number += 6;

			Draw_Init();

			return 1;
		}

		R_ShutdownTextures();
	}

	Sys_Error("R_Init() failed.");

	return 0;
}

void R_Shutdown()
{
	R_ShutdownParticles();
	R_ShutdownTextures();
	GL_RSurf_Shutdown();
	GL_Warp_Shutdown();
	GL_Texture_Shutdown();
}

void R_InitGL(void)
{
	byte *clearColor;

	// this minigl driver seems to slow us down if the particles are drawn WITHOUT Z buffer bits
	if (!strcmp(gl_vendor, "METABYTE/WICKED3D"))
		Cvar_SetDefault(&gl_solidparticles, 1);

	Classic_LoadParticleTextures();

	R_InitOtherTextures ();

	clearColor = StringToRGB(gl_clearColor.string);
	glClearColor(clearColor[0] / 255.0, clearColor[1] / 255.0, clearColor[2] / 255.0, 1.0);

	gl_initialised = 1;
}

extern msurface_t *alphachain;

static void R_RenderScene(void)
{
	R_SetupFrame();

	R_SetFrustum();

	R_SetupGL();

	R_MarkLeaves();	// done here so we know if we're in water

	R_DrawWorld();		// adds static entities to the list

	S_ExtraUpdate();	// don't let sound get messed up if going slow

	R_DrawEntitiesOnList(&cl_visents);
	R_DrawEntitiesOnList(&cl_alphaents);

	R_DrawWaterSurfaces();

	GL_DisableMultitexture();
}

int gl_ztrickframe = 0;

static qboolean OnChange_gl_clearColor(cvar_t *v, char *s)
{
	byte *clearColor;

	if (gl_initialised)
	{
		clearColor = StringToRGB(s);
		glClearColor(clearColor[0] / 255.0, clearColor[1] / 255.0, clearColor[2] / 255.0, 1.0);
	}

	return false;
}

static void R_Clear(void)
{
	int clearbits = 0;

	if (gl_clear.value || (!VID_HWGammaSupported() && v_contrast.value > 1))
		clearbits |= GL_COLOR_BUFFER_BIT;

	if (gl_ztrick.value)
	{
		if (clearbits)
			glClear(clearbits);

		gl_ztrickframe = !gl_ztrickframe;
		if (gl_ztrickframe)
		{
			gldepthmin = 0;
			gldepthmax = 0.49999;
			glDepthFunc(GL_LEQUAL);
		}
		else
		{
			gldepthmin = 1;
			gldepthmax = 0.5;
			glDepthFunc(GL_GEQUAL);
		}
	}
	else
	{
		clearbits |= GL_DEPTH_BUFFER_BIT;
		glClear(clearbits);
		gldepthmin = 0;
		gldepthmax = 1;
		glDepthFunc(GL_LEQUAL);
	}

	glDepthRange(gldepthmin, gldepthmax);
}


void R_RenderView(void)
{
	double time1 = 0, time2;

	if (!r_worldentity.model || !cl.worldmodel)
		Sys_Error("R_RenderView: NULL worldmodel");

	if (r_speeds.value)
	{
		glFinish();
		time1 = Sys_DoubleTime();
		c_brush_polys = 0;
		c_alias_polys = 0;
	}

	if (gl_finish.value)
		glFinish();

	R_Clear();

	// render normal view
	R_RenderScene();
	R_RenderDlights();
	R_DrawParticles();
	R_DrawViewModel();

	if (r_speeds.value)
	{
		time2 = Sys_DoubleTime();
		Com_Printf("%3i ms  %4i wpoly %4i epoly\n", (int)((time2 - time1) * 1000), c_brush_polys, c_alias_polys);
	}
}

