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

This file is part of Quake 2 source code.

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

// r_alias.c: alias triangle model functions

#include "r_local.h"
#include "vlights.h"
#include "r_normals.h"

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

  ALIAS MODELS

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

vec3_t	tempVertexArray[MD3_MAX_MESHES][MD3_MAX_VERTS];

vec3_t	aliasLightDir = {0, 0, 0};
float	aliasShadowAlpha;

/*
=================
R_LightAliasVertex
=================
*/
void R_LightAliasVertex (vec3_t baselight, vec3_t normal, vec3_t lightOut, byte normalindex, qboolean shaded)
{
	int		i;
	float	l;

	if (r_fullbright->integer != 0) {
		VectorSet (lightOut, 1.0f, 1.0f, 1.0f);
		return;
	}

	if (r_model_shading->integer)
	{
		if (shaded)
		{
			if (r_model_shading->integer == 3)
				l = 2.0 * shadedots[normalindex] - 1;
			else if (r_model_shading->integer == 2)
				l = 1.5 * shadedots[normalindex] - 0.5;
			else
				l = shadedots[normalindex];
			VectorScale(baselight, l, lightOut);
		}
		else
			VectorCopy(baselight, lightOut);

		if (model_dlights_num)
			for (i=0; i<model_dlights_num; i++)
			{
				l = 2.0 * VLight_GetLightValue (normal, model_dlights[i].direction,
					currententity->angles[PITCH], currententity->angles[YAW], true);
				VectorMA(lightOut, l, model_dlights[i].color, lightOut);
			}
	}
	else
	{
		l = 2.0 * VLight_GetLightValue (normal, aliasLightDir, currententity->angles[PITCH],
			currententity->angles[YAW], false);

		VectorScale(baselight, l, lightOut);
	}

	for (i=0; i<3; i++)
		lightOut[i] = max(min(lightOut[i], 1.0f), 0.0f);
}


/*
=================
R_LightAliasVertexCel

Adds dlights only for cel shading
=================
*/
void R_LightAliasVertexCel (vec3_t baselight, vec3_t normal, vec3_t lightOut, byte normalindex)
{
	int		i;
	float	l;

	if (r_fullbright->integer != 0) {
		VectorSet (lightOut, 1.0f, 1.0f, 1.0f);
		return;
	}
		
	VectorCopy(baselight, lightOut);

	if (model_dlights_num)
		for (i=0; i<model_dlights_num; i++)
		{
			l = 2.0 * VLight_GetLightValue (normal, model_dlights[i].direction,
				currententity->angles[PITCH], currententity->angles[YAW], true);
			VectorMA(lightOut, l, model_dlights[i].color, lightOut);
		}

	for (i=0; i<3; i++)
		lightOut[i] = max(min(lightOut[i], 1.0f), 0.0f);
}


/*
=================
R_CelTexCoord
=================
*/
#define CEL_OUTLINEDROPOFF 1024.0f	// distance for cel shading outline to disappear
#define CEL_TEX_MIN (0.5f/32.0f)
#define CEL_TEX_MAX (31.5f/32.0f)
float R_CelTexCoord (vec3_t meshlight, vec3_t normal, byte lightnormalindex)
{
	float	shadeCoord;
	int		i, highest = 0;
	vec3_t	lightColor;
	
	R_LightAliasVertex (meshlight, normal, lightColor, lightnormalindex, true);

	for (i=0; i<3; i++) {
		if (lightColor[i] > lightColor[highest])
			highest = i;
	}

	for (i=0; i<3; i++) {
		lightColor[i] = min(max(lightColor[i], 0.0f), 1.0f);
	}

	shadeCoord = lightColor[highest];
	shadeCoord = min(max(shadeCoord, CEL_TEX_MIN), CEL_TEX_MAX);

	return shadeCoord;
}


/*
=================
R_AliasMeshesAreBatchable
=================
*/
qboolean R_AliasMeshesAreBatchable (maliasmodel_t *paliashdr, unsigned meshnum1, unsigned meshnum2, unsigned skinnum)
{
	maliasmesh_t	*mesh1, *mesh2;
	renderparms_t	*skinParms1, *skinParms2;
	int				skinnum1, skinnum2;

	if (!paliashdr)
		return false;

	mesh1 = &paliashdr->meshes[meshnum1];
	mesh2 = &paliashdr->meshes[meshnum2];
	skinnum1 = (skinnum<mesh1->num_skins)?skinnum:0;
	skinnum2 = (skinnum<mesh2->num_skins)?skinnum:0;
	skinParms1 = &mesh1->skins[skinnum1].renderparms;
	skinParms2 = &mesh2->skins[skinnum2].renderparms;

	if (!mesh1 || !mesh2 || !skinParms1 || !skinParms2)
		return false;

	if (currentmodel->skins[meshnum1][skinnum1] != currentmodel->skins[meshnum2][skinnum2])
		return false;
	if (mesh1->skins[skinnum1].glowimage != mesh2->skins[skinnum2].glowimage)
		return false;
	if (skinParms1->alphatest != skinParms2->alphatest)
		return false;
	if (skinParms1->basealpha != skinParms2->basealpha)
		return false;
	if (skinParms1->blend != skinParms2->blend)
		return false;
	if (skinParms1->blendfunc_src != skinParms2->blendfunc_src)
		return false;
	if (skinParms1->blendfunc_dst != skinParms2->blendfunc_dst)
		return false;
	if (skinParms1->envmap != skinParms2->envmap)
		return false;
	if ( (skinParms1->glow.type != skinParms2->glow.type)
		|| (skinParms1->glow.params[0] != skinParms2->glow.params[0])
		|| (skinParms1->glow.params[1] != skinParms2->glow.params[1])
		|| (skinParms1->glow.params[2] != skinParms2->glow.params[2])
		|| (skinParms1->glow.params[3] != skinParms2->glow.params[3]) )
		return false;
	if (skinParms1->nodraw != skinParms2->nodraw)
		return false;
	if (skinParms1->twosided != skinParms2->twosided)
		return false;

	return true;
}


/*
=================
RB_RenderAliasMesh

Backend for R_DrawAliasMeshes
=================
*/
void RB_RenderAliasMesh (maliasmodel_t *paliashdr, unsigned meshnum, unsigned skinnum, image_t *skin, qboolean reverseCull)
{
	entity_t		*e = currententity;
	maliasmesh_t	*mesh;
	renderparms_t	*skinParms;
	int				i;
	float			thisalpha = colorArray[0][3];
	qboolean		shellModel = e->flags & RF_MASK_SHELL;

	if (!paliashdr)
		return;

	mesh = &paliashdr->meshes[meshnum];

	if (!shellModel)
		GL_Bind(skin->texnum);

	// md3 skin scripting
	skinParms = &mesh->skins[skinnum].renderparms;

	if (skinParms->twosided)
		GL_Disable (GL_CULL_FACE);
	else
		GL_Enable (GL_CULL_FACE);

	if (skinParms->alphatest && !shellModel)
		GL_Enable (GL_ALPHA_TEST);
	else
		GL_Disable (GL_ALPHA_TEST);

	if (thisalpha < 1.0f || skinParms->blend)
		GL_Enable (GL_BLEND);
	else
		GL_Disable (GL_BLEND);

	if (skinParms->blend && !shellModel)
		GL_BlendFunc (skinParms->blendfunc_src, skinParms->blendfunc_dst);
	else if (shellModel)
		GL_BlendFunc (GL_ONE, GL_ONE);
	else
		GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	// md3 skin scripting

	// draw
	RB_DrawArrays ();

	// glow pass
	if (mesh->skins[skinnum].glowimage && !shellModel)
	{
		float	glowcolor;
		if (skinParms->glow.type > -1)
			glowcolor = RB_CalcGlowColor (skinParms);
		else
			glowcolor = 1.0;
		qglDisableClientState (GL_COLOR_ARRAY);
		qglColor4f(glowcolor, glowcolor, glowcolor, 1.0);

		GL_Enable (GL_BLEND);
		GL_BlendFunc (GL_ONE, GL_ONE);

		GL_Bind(mesh->skins[skinnum].glowimage->texnum);

		RB_DrawArrays ();

		qglColor4f(1.0, 1.0, 1.0, 1.0);
		qglEnableClientState (GL_COLOR_ARRAY);
	}

	// envmap pass
	if (skinParms->envmap > 0.0f && !shellModel)
	{
		GL_Enable (GL_BLEND);
		GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		qglTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
		qglTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_SPHERE_MAP);
		// apply alpha to array
		for (i=0; i<rb_vertex; i++)
			colorArray[i][3] = thisalpha*skinParms->envmap;

		GL_Bind(glMedia.envmappic->texnum);

		qglEnable(GL_TEXTURE_GEN_S);
		qglEnable(GL_TEXTURE_GEN_T);

		RB_DrawArrays ();

		qglDisable(GL_TEXTURE_GEN_S);
		qglDisable(GL_TEXTURE_GEN_T);
	}

	// cel shading
	if ( r_celshading->integer && !(thisalpha < 1.0f || skinParms->blend || skinParms->alphatest) )
	{
		float	strength, len;
		vec3_t	offset;

		// blend cel shade texture
		qglDepthMask (false);
		GL_Enable (GL_BLEND);
		GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		GL_Bind (glMedia.celshadetexture->texnum);

		qglTexCoordPointer (2, GL_FLOAT, sizeof(celTexCoordArray[0]), celTexCoordArray[0]);
		qglDisableClientState (GL_COLOR_ARRAY);
		qglColor4f(1.0f, 1.0f, 1.0f, 1.0f);

		RB_DrawArrays ();

		qglTexCoordPointer (2, GL_FLOAT, sizeof(texCoordArray[0][0]), texCoordArray[0][0]);
	//	qglEnableClientState (GL_COLOR_ARRAY);

		GL_Disable (GL_BLEND);
		qglDepthMask (true);

		// draw outlines
		VectorSubtract (r_newrefdef.vieworg, currententity->origin, offset);
		len = VectorNormalize(offset);
		strength = (CEL_OUTLINEDROPOFF - len) / CEL_OUTLINEDROPOFF;
		strength = min(max(strength, 0.0f), 1.0f);

		qglPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		if (reverseCull)
			GL_CullFace(GL_FRONT);
		else
			GL_CullFace(GL_BACK);
		qglColor4f(0.0f, 0.0f, 0.0f, 1.0f);
		qglLineWidth(r_celshading_width->value * strength);

		RB_DrawArrays ();

		qglLineWidth(1.0f);
		qglColor4f(1.0f, 1.0f, 1.0f, 1.0f);
		if (reverseCull)
			GL_CullFace(GL_BACK);
		else
			GL_CullFace(GL_FRONT);
		qglEnableClientState (GL_COLOR_ARRAY);
		qglPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	}

	RB_DrawMeshTris ();
	rb_vertex = rb_index = 0;

	// restore state
	GL_Enable (GL_CULL_FACE);
	GL_Disable (GL_ALPHA_TEST);
	GL_Disable (GL_BLEND);
	GL_BlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}


/*
=================
R_DrawAliasMeshes
=================
*/
//void R_DrawAliasMeshes (maliasmodel_t *paliashdr, entity_t *e, qboolean lerpOnly, qboolean mirrored, qboolean viewFlipped)
void R_DrawAliasMeshes (maliasmodel_t *paliashdr, entity_t *e, qboolean mirrored, qboolean viewFlipped, qboolean preLerped, qboolean lerpOnly)
{
	int				i, k, meshnum, skinnum, baseindex;	// numCalls
	maliasframe_t	*frame, *oldframe;
	maliasmesh_t	mesh;
	maliasvertex_t	*v, *ov;
	vec3_t			move, delta, vectors[3];
	vec3_t			curScale, oldScale, curNormal, oldNormal;
	vec3_t			tempNormalsArray[MD3_MAX_VERTS];
	vec2_t			tempSkinCoord;
	vec3_t			meshlight, lightcolor;
	float			alpha, meshalpha, thisalpha, shellscale, frontlerp, backlerp = e->backlerp, mirrormult;
	image_t			*skin;
	renderparms_t	skinParms;
	qboolean		shellModel = e->flags & RF_MASK_SHELL;
	qboolean		meshCelShaded;	// added for cel shading

	if (lerpOnly)	preLerped = false;

	frontlerp = 1.0 - backlerp;

	if (shellModel && FlowingShell())
		alpha = 0.7;
	else if (e->flags & RF_TRANSLUCENT)
		alpha = e->alpha;
	else
		alpha = 1.0;

	frame = paliashdr->frames + e->frame;
	oldframe = paliashdr->frames + e->oldframe;

	if (!preLerped)
	{
		VectorScale(frame->scale, frontlerp, curScale);
		VectorScale(oldframe->scale, backlerp, oldScale);

		mirrormult = (mirrored) ? -1.0f : 1.0f;

		// move should be the delta back to the previous frame * backlerp
		VectorSubtract (e->oldorigin, e->origin, delta);
		AngleVectors (e->angles, vectors[0], vectors[1], vectors[2]);

		move[0] = DotProduct (delta, vectors[0]);	// forward
		move[1] = -DotProduct (delta, vectors[1]);	// left
		move[2] = DotProduct (delta, vectors[2]);	// up

		VectorAdd (move, oldframe->translate, move);

		for (i=0 ; i<3 ; i++)
			move[i] = backlerp*move[i] + frontlerp*frame->translate[i];
	}

	GL_ShadeModel (GL_SMOOTH);
	GL_TexEnv (GL_MODULATE);
	R_SetVertexRGBScale(true);
	R_SetShellBlend (true);

	rb_vertex = rb_index = 0;
//	numCalls = 0;

	// new outer loop for whole model
	for (k=0, meshnum=0; k < paliashdr->num_meshes; k++, meshnum++)
	{
		mesh = paliashdr->meshes[k];

		// select skin
		if (e->skin) {	// custom player skin
			skinnum = 0;
			skin = e->skin;
		}
		else {
			skinnum = (e->skinnum<mesh.num_skins)?e->skinnum:0; // catch bad skinnums
			skin = currentmodel->skins[k][skinnum];
			if (!skin) {
				skinnum = 0;
				skin = currentmodel->skins[k][0];
			}
		}
		if (!skin) {
			skinnum = 0;
			skin = glMedia.notexture;
		}

		// md3 skin scripting
		skinParms = mesh.skins[skinnum].renderparms;

		if (skinParms.nodraw) 
			continue; // skip this mesh for this skin

		if (skinParms.fullbright)
			VectorSet(meshlight, 1.0f, 1.0f, 1.0f);
		else
			VectorCopy(shadelight, meshlight);

		meshalpha = alpha * skinParms.basealpha;
		// md3 skin scripting

		// is this mesh cel shaded?
		meshCelShaded = (r_celshading->integer && !(meshalpha < 1.0f || skinParms.blend || skinParms.alphatest));

		v = mesh.vertexes + e->frame * mesh.num_verts;
		ov = mesh.vertexes + e->oldframe * mesh.num_verts;
		baseindex = rb_vertex;

		// set indices for each triangle
		for (i=0; i<mesh.num_tris; i++)
		{
			indexArray[rb_index++] = rb_vertex + mesh.indexes[3*i+0];
			indexArray[rb_index++] = rb_vertex + mesh.indexes[3*i+1];
			indexArray[rb_index++] = rb_vertex + mesh.indexes[3*i+2];
		}

		for (i=0; i<mesh.num_verts; i++, v++, ov++)
		{
			// lerp verts
			if (!preLerped)
			{
				curNormal[0] = r_sinTable[v->normal[0]] * r_cosTable[v->normal[1]];
				curNormal[1] = r_sinTable[v->normal[0]] * r_sinTable[v->normal[1]];
				curNormal[2] = r_cosTable[v->normal[0]];

				oldNormal[0] = r_sinTable[ov->normal[0]] * r_cosTable[ov->normal[1]];
				oldNormal[1] = r_sinTable[ov->normal[0]] * r_sinTable[ov->normal[1]];
				oldNormal[2] = r_cosTable[ov->normal[0]];

				VectorSet ( tempNormalsArray[i],
						curNormal[0] + (oldNormal[0] - curNormal[0])*backlerp,
						curNormal[1] + (oldNormal[1] - curNormal[1])*backlerp,
						curNormal[2] + (oldNormal[2] - curNormal[2])*backlerp );

				if (shellModel) 
					shellscale = (e->flags & RF_WEAPONMODEL) ? WEAPON_SHELL_SCALE: POWERSUIT_SCALE;
				else
					shellscale = 0.0;

				VectorSet ( tempVertexArray[meshnum][i], 
						move[0] + ov->xyz[0]*oldScale[0] + v->xyz[0]*curScale[0] + tempNormalsArray[i][0]*shellscale,
						mirrormult * (move[1] + ov->xyz[1]*oldScale[1] + v->xyz[1]*curScale[1] + tempNormalsArray[i][1]*shellscale),
						move[2] + ov->xyz[2]*oldScale[2] + v->xyz[2]*curScale[2] + tempNormalsArray[i][2]*shellscale );

				tempNormalsArray[i][1] *= mirrormult;
			}

			// skip drawing if we're only lerping the verts for a shadow-only rendering pass
			if (lerpOnly)	continue;

			// calc lighting and alpha
			if (shellModel)
				VectorCopy(meshlight, lightcolor);
			else if (meshCelShaded)
				R_LightAliasVertexCel (meshlight, tempNormalsArray[i], lightcolor, v->lightnormalindex);	// added for cel shading
			else
				R_LightAliasVertex (meshlight, tempNormalsArray[i], lightcolor, v->lightnormalindex, !skinParms.nodiffuse);
			//thisalpha = R_CalcEntAlpha(meshalpha, tempVertexArray[meshnum][i]);
			thisalpha = meshalpha;

			// get tex coords
			if (shellModel && FlowingShell()) {
				tempSkinCoord[0] = (tempVertexArray[meshnum][i][0] + tempVertexArray[meshnum][i][1]) * DIV40 + shellFlowH;
				tempSkinCoord[1] = tempVertexArray[meshnum][i][2] * DIV40 + shellFlowV; // was / 40
			} else {
				tempSkinCoord[0] = mesh.stcoords[i].st[0];
				tempSkinCoord[1] = mesh.stcoords[i].st[1];
			}

			// add to arrays
			VA_SetElem2(texCoordArray[0][rb_vertex], tempSkinCoord[0], tempSkinCoord[1]);
			VA_SetElem3(vertexArray[rb_vertex], tempVertexArray[meshnum][i][0], tempVertexArray[meshnum][i][1], tempVertexArray[meshnum][i][2]);
			VA_SetElem4(colorArray[rb_vertex], lightcolor[0], lightcolor[1], lightcolor[2], thisalpha);
			if (meshCelShaded) {
				VA_SetElem2(celTexCoordArray[rb_vertex], R_CelTexCoord(meshlight, tempNormalsArray[i], v->lightnormalindex), 0);	// added for cel shading
			}
			rb_vertex++;
		}

		if (!shellModel)
			RB_ModifyTextureCoords (&texCoordArray[0][baseindex][0], &vertexArray[baseindex][0], mesh.num_verts, &skinParms);

		// compare renderparms for next mesh and check for overflow
		if ( k < (paliashdr->num_meshes-1) ) {
			if ( ( shellModel || R_AliasMeshesAreBatchable (paliashdr, k, k+1, e->skinnum) )
				&& !RB_CheckArrayOverflow (paliashdr->meshes[k+1].num_verts, paliashdr->meshes[k+1].num_tris*3) )
				continue;
		}

		RB_RenderAliasMesh (paliashdr, meshnum, skinnum, skin, (mirrored || viewFlipped));

	//	numCalls++;
	} // end new outer loop

//	if (paliashdr->num_meshes > numCalls)
//		VID_Printf (PRINT_DEVELOPER, "%s: rendered %i  meshes in %i pass(es)\n", currentmodel->name, paliashdr->num_meshes, numCalls);
	
	R_SetShellBlend (false);
	R_SetVertexRGBScale(false);
	GL_TexEnv (GL_REPLACE);
	GL_ShadeModel (GL_FLAT);
}


unsigned	shadow_va, shadow_index;
/*
=============
R_BuildShadowVolume
based on code from BeefQuake R6
=============
*/
void R_BuildShadowVolume (maliasmodel_t *hdr, int meshnum, vec3_t light, float projectdistance, qboolean nocap)
{
	int				i, j;
	BOOL			triangleFacingLight[MD3_MAX_TRIANGLES];
	vec3_t			v0, v1, v2, v3;
	float			thisAlpha;
	maliasmesh_t	mesh;
	maliasvertex_t	*verts;

	mesh = hdr->meshes[meshnum];

	verts = mesh.vertexes;

	thisAlpha = aliasShadowAlpha; // was r_shadowalpha->value

	for (i=0; i<mesh.num_tris; i++)
	{
		VectorCopy(tempVertexArray[meshnum][mesh.indexes[3*i+0]], v0);
		VectorCopy(tempVertexArray[meshnum][mesh.indexes[3*i+1]], v1);
		VectorCopy(tempVertexArray[meshnum][mesh.indexes[3*i+2]], v2);

		triangleFacingLight[i] =
			(light[0] - v0[0]) * ((v0[1] - v1[1]) * (v2[2] - v1[2]) - (v0[2] - v1[2]) * (v2[1] - v1[1]))
			+ (light[1] - v0[1]) * ((v0[2] - v1[2]) * (v2[0] - v1[0]) - (v0[0] - v1[0]) * (v2[2] - v1[2]))
			+ (light[2] - v0[2]) * ((v0[0] - v1[0]) * (v2[1] - v1[1]) - (v0[1] - v1[1]) * (v2[0] - v1[0])) > 0;
	}

	shadow_va = shadow_index = 0;
	for (i=0; i<mesh.num_tris; i++)
	{
		if (!triangleFacingLight[i])
			continue;

		if (mesh.trneighbors[i*3+0] < 0 || !triangleFacingLight[mesh.trneighbors[i*3+0]])
		{
			for (j=0; j<3; j++)
			{
				v0[j]=tempVertexArray[meshnum][mesh.indexes[3*i+1]][j];
				v1[j]=tempVertexArray[meshnum][mesh.indexes[3*i+0]][j];
				v2[j]=v1[j]+((v1[j]-light[j]) * projectdistance);
				v3[j]=v0[j]+((v0[j]-light[j]) * projectdistance);
			}
			indexArray[shadow_index++] = shadow_va+0;
			indexArray[shadow_index++] = shadow_va+1;
			indexArray[shadow_index++] = shadow_va+2;
			indexArray[shadow_index++] = shadow_va+0;
			indexArray[shadow_index++] = shadow_va+2;
			indexArray[shadow_index++] = shadow_va+3;

			VA_SetElem3(vertexArray[shadow_va], v0[0], v0[1], v0[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
			VA_SetElem3(vertexArray[shadow_va], v1[0], v1[1], v1[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
			VA_SetElem3(vertexArray[shadow_va], v2[0], v2[1], v2[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
			VA_SetElem3(vertexArray[shadow_va], v3[0], v3[1], v3[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
		}

		if (mesh.trneighbors[i*3+1] < 0 || !triangleFacingLight[mesh.trneighbors[i*3+1]])
		{
			for (j=0; j<3; j++)
			{
				v0[j]=tempVertexArray[meshnum][mesh.indexes[3*i+2]][j];
				v1[j]=tempVertexArray[meshnum][mesh.indexes[3*i+1]][j];
				v2[j]=v1[j]+((v1[j]-light[j]) * projectdistance);
				v3[j]=v0[j]+((v0[j]-light[j]) * projectdistance);
			}
			indexArray[shadow_index++] = shadow_va+0;
			indexArray[shadow_index++] = shadow_va+1;
			indexArray[shadow_index++] = shadow_va+2;
			indexArray[shadow_index++] = shadow_va+0;
			indexArray[shadow_index++] = shadow_va+2;
			indexArray[shadow_index++] = shadow_va+3;

			VA_SetElem3(vertexArray[shadow_va], v0[0], v0[1], v0[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
			VA_SetElem3(vertexArray[shadow_va], v1[0], v1[1], v1[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
			VA_SetElem3(vertexArray[shadow_va], v2[0], v2[1], v2[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
			VA_SetElem3(vertexArray[shadow_va], v3[0], v3[1], v3[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
		}

		if (mesh.trneighbors[i*3+2] < 0 || !triangleFacingLight[mesh.trneighbors[i*3+2]])
		{
			for (j=0; j<3; j++)
			{
				v0[j]=tempVertexArray[meshnum][mesh.indexes[3*i+0]][j];
				v1[j]=tempVertexArray[meshnum][mesh.indexes[3*i+2]][j];
				v2[j]=v1[j]+((v1[j]-light[j]) * projectdistance);
				v3[j]=v0[j]+((v0[j]-light[j]) * projectdistance);
			}
			indexArray[shadow_index++] = shadow_va+0;
			indexArray[shadow_index++] = shadow_va+1;
			indexArray[shadow_index++] = shadow_va+2;
			indexArray[shadow_index++] = shadow_va+0;
			indexArray[shadow_index++] = shadow_va+2;
			indexArray[shadow_index++] = shadow_va+3;

			VA_SetElem3(vertexArray[shadow_va], v0[0], v0[1], v0[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
			VA_SetElem3(vertexArray[shadow_va], v1[0], v1[1], v1[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
			VA_SetElem3(vertexArray[shadow_va], v2[0], v2[1], v2[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
			VA_SetElem3(vertexArray[shadow_va], v3[0], v3[1], v3[2]);
			VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
			shadow_va++;
		}
	}

	if (nocap)	return;

	// cap the volume
	for (i=0; i<mesh.num_tris; i++)
	{
		if (!triangleFacingLight[i]) // changed to draw only front facing polys- thanx to Kirk Barnes
			continue;

		VectorCopy(tempVertexArray[meshnum][mesh.indexes[3*i+0]], v0);
		VectorCopy(tempVertexArray[meshnum][mesh.indexes[3*i+1]], v1);
		VectorCopy(tempVertexArray[meshnum][mesh.indexes[3*i+2]], v2);

		VA_SetElem3(vertexArray[shadow_va], v0[0], v0[1], v0[2]);
		VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
		indexArray[shadow_index++] = shadow_va;
		shadow_va++;
		VA_SetElem3(vertexArray[shadow_va], v1[0], v1[1], v1[2]);
		VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
		indexArray[shadow_index++] = shadow_va;
		shadow_va++;
		VA_SetElem3(vertexArray[shadow_va], v2[0], v2[1], v2[2]);
		VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
		indexArray[shadow_index++] = shadow_va;
		shadow_va++;

		// rear with reverse order
		for (j=0; j<3; j++)
		{
			v0[j]=tempVertexArray[meshnum][mesh.indexes[3*i+0]][j];
			v1[j]=tempVertexArray[meshnum][mesh.indexes[3*i+1]][j];
			v2[j]=tempVertexArray[meshnum][mesh.indexes[3*i+2]][j];

			v0[j]=v0[j]+((v0[j]-light[j]) * projectdistance);
			v1[j]=v1[j]+((v1[j]-light[j]) * projectdistance);
			v2[j]=v2[j]+((v2[j]-light[j]) * projectdistance);
		}
		VA_SetElem3(vertexArray[shadow_va], v2[0], v2[1], v2[2]);
		VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
		indexArray[shadow_index++] = shadow_va;
		shadow_va++;
		VA_SetElem3(vertexArray[shadow_va], v1[0], v1[1], v1[2]);
		VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
		indexArray[shadow_index++] = shadow_va;
		shadow_va++;
		VA_SetElem3(vertexArray[shadow_va], v0[0], v0[1], v0[2]);
		VA_SetElem4(colorArray[shadow_va], 0, 0, 0, thisAlpha);
		indexArray[shadow_index++] = shadow_va;
		shadow_va++;
	}
}


/*
=============
R_DrawShadowVolume 
=============
*/
void R_DrawShadowVolume (void)
{
	if (glConfig.drawRangeElements)
		qglDrawRangeElementsEXT(GL_TRIANGLES, 0, shadow_va, shadow_index, GL_UNSIGNED_INT, indexArray);
	else
		qglDrawElements(GL_TRIANGLES, shadow_index, GL_UNSIGNED_INT, indexArray);
}


/*
=============
R_CalcAliasVolumeShadowLightVector
=============
*/
float R_CalcAliasVolumeShadowLightVector (vec3_t bbox[8], vec3_t lightVec)
{
	vec3_t		temp, vecAdd;
	int			i, lnum;
	float		dist, highest, lowest, projected_distance;
	float		angle, cosp, sinp, cosy, siny, cosr, sinr, ix, iy, iz;
	dlight_t	*dl;

	dl = r_newrefdef.dlights;

	VectorSet(vecAdd, 680, 0, 1024); // set base vector, was 576,0,1024

	// compute average light vector from dlights
	for (i=0, lnum=0; i<r_newrefdef.num_dlights; i++, dl++)
	{
		if (VectorCompare(dl->origin, currententity->origin))
			continue;
		
		VectorSubtract(dl->origin, currententity->origin, temp);
		dist = dl->intensity - VectorLength(temp);
		if (dist <= 0)
			continue;
		
		lnum++;
		// Factor in the intensity of a dlight
		VectorScale (temp, dist*0.25, temp);
		VectorAdd (vecAdd, temp, vecAdd);
	}
	VectorNormalize(vecAdd);
	VectorScale(vecAdd, 1024, vecAdd);

	// get projection distance from lightspot height
	highest = lowest = bbox[0][2];
	for (i=0; i<8; i++) {
		if (bbox[i][2] > highest) highest = bbox[i][2];
		if (bbox[i][2] < lowest) lowest = bbox[i][2];
	}
	projected_distance = fabs((highest - lightspot[2]) + (highest-lowest)) / fabs(vecAdd[2]);
//	projected_distance = 1.5f * (fabs(highest - lightspot[2])) / fabs(vecAdd[2]);

	VectorCopy(vecAdd, lightVec);
	
	// reverse-rotate light vector based on angles
	angle = -currententity->angles[PITCH] / 180 * M_PI;
	cosp = cos(angle), sinp = sin(angle);
	angle = -currententity->angles[YAW] / 180 * M_PI;
	cosy = cos(angle), siny = sin(angle);
	angle = -currententity->angles[ROLL] / 180 * M_PI * R_RollMult(); // roll is backwards
	cosr = cos(angle), sinr = sin(angle);

	// rotate for yaw (z axis)
	ix = lightVec[0], iy = lightVec[1];
	lightVec[0] = cosy * ix - siny * iy + 0;
	lightVec[1] = siny * ix + cosy * iy + 0;

	// rotate for pitch (y axis)
	ix = lightVec[0], iz = lightVec[2];
	lightVec[0] = cosp * ix + 0 + sinp * iz;
	lightVec[2] = -sinp * ix + 0 + cosp * iz;

	// rotate for roll (x axis)
	iy = lightVec[1], iz = lightVec[2];
	lightVec[1] = 0 + cosr * iy - sinr * iz;
	lightVec[2] = 0 + sinr * iy + cosr * iz;

//	for (i=0; i<3; i++)
//		shadowVec[i] = -lightVec[i] * projected_distance;
//	for (i=0; i<8; i++)
//		VectorAdd (bbox[i], shadowVec, endBBox[i]);

	return projected_distance;
}


/*
=============
R_DrawAliasVolumeShadow
based on code from BeefQuake R6
=============
*/
void R_DrawAliasVolumeShadow (maliasmodel_t *paliashdr, vec3_t bbox[8])
{
	vec3_t		light, vecAdd;	// temp
	vec3_t		shadowVec, endBBox[8], volumeMins, volumeMaxs;
	float		projected_distance;
	int			i, j, skinnum;	// lnum
	qboolean	zFail = (r_shadow_zfail->integer != 0);
	qboolean	inVolume = false;
//	GLenum		incr, decr;
//	float		dist, highest, lowest;
//	float		angle, cosp, sinp, cosy, siny, cosr, sinr, ix, iy, iz;
//	dlight_t	*dl;

#if 0
	dl = r_newrefdef.dlights;

	VectorSet(vecAdd, 680,0,1024); // set base vector, was 576,0,1024

	// compute average light vector from dlights
	for (i=0, lnum=0; i<r_newrefdef.num_dlights; i++, dl++)
	{
		if (VectorCompare(dl->origin, currententity->origin))
			continue;
		
		VectorSubtract(dl->origin, currententity->origin, temp);
		dist = dl->intensity - VectorLength(temp);
		if (dist <= 0)
			continue;
		
		lnum++;
		// Factor in the intensity of a dlight
		VectorScale (temp, dist*0.25, temp);
		VectorAdd (vecAdd, temp, vecAdd);
	}
	VectorNormalize(vecAdd);
	VectorScale(vecAdd, 1024, vecAdd);

	// get projection distance from lightspot height
	highest = lowest = bbox[0][2];
	for (i=0; i<8; i++) {
		if (bbox[i][2] > highest) highest = bbox[i][2];
		if (bbox[i][2] < lowest) lowest = bbox[i][2];
	}
	projected_distance = (fabs(highest - lightspot[2]) + (highest-lowest)) / vecAdd[2];

	VectorCopy(vecAdd, light);
	
	// reverse-rotate light vector based on angles
	angle = -currententity->angles[PITCH] / 180 * M_PI;
	cosp = cos(angle), sinp = sin(angle);
	angle = -currententity->angles[YAW] / 180 * M_PI;
	cosy = cos(angle), siny = sin(angle);
	angle = -currententity->angles[ROLL] / 180 * M_PI * R_RollMult(); // roll is backwards
	cosr = cos(angle), sinr = sin(angle);

	// rotate for yaw (z axis)
	ix = light[0], iy = light[1];
	light[0] = cosy * ix - siny * iy + 0;
	light[1] = siny * ix + cosy * iy + 0;

	// rotate for pitch (y axis)
	ix = light[0], iz = light[2];
	light[0] = cosp * ix + 0 + sinp * iz;
	light[2] = -sinp * ix + 0 + cosp * iz;

	// rotate for roll (x axis)
	iy = light[1], iz = light[2];
	light[1] = 0 + cosr * iy - sinr * iz;
	light[2] = 0 + sinr * iy + cosr * iz;
#endif

	projected_distance = R_CalcAliasVolumeShadowLightVector (bbox, light);

	// For Z-Pass method, calc bbox for shadow volume to see if vieworg is likely to be inside it
	if (!zFail)
	{
		// calc bbox for end of shadow volume
		for (i=0; i<3; i++)
			shadowVec[i] = -vecAdd[i] * projected_distance;
		for (i=0; i<8; i++)
			VectorAdd (bbox[i], shadowVec, endBBox[i]);

		// get bbox for entire shadow volume
		VectorCopy (currententity->origin, volumeMaxs);
		VectorCopy (currententity->origin, volumeMins);
		for (i=0; i<8; i++)
		{
			for (j=0; j<3; j++)
			{
				if (bbox[i][j] < volumeMins[j])
					volumeMins[j] = bbox[i][j];
				if (endBBox[i][j] < volumeMins[j])
					volumeMins[j] = endBBox[i][j];

				if (bbox[i][j] > volumeMaxs[j])
					volumeMaxs[j] = bbox[i][j];
				if (endBBox[i][j] > volumeMaxs[j])
					volumeMaxs[j] = endBBox[i][j];
			}
		}
		// if the vieworg is inside the volume bbox, assume it's inside the volume
		if ( (r_newrefdef.vieworg[0] >= volumeMins[0] && r_newrefdef.vieworg[1] >= volumeMins[1] && r_newrefdef.vieworg[2] >= volumeMins[2]) &&
			(r_newrefdef.vieworg[0] <= volumeMaxs[0] && r_newrefdef.vieworg[1] <= volumeMaxs[1] && r_newrefdef.vieworg[2] <= volumeMaxs[2]) )
			inVolume = true;
	}

	// set up stenciling
	if (!r_shadowvolumes->integer)
	{
		/*if (glConfig.extStencilWrap)
		{	incr = GL_INCR_WRAP_EXT;	decr = GL_DECR_WRAP_EXT;	}
		else
		{	incr = GL_INCR;				decr = GL_DECR;	}*/

		qglPushAttrib(GL_STENCIL_BUFFER_BIT); // save stencil buffer
		qglClear(GL_STENCIL_BUFFER_BIT);

		qglColorMask(0,0,0,0);
		GL_DepthMask(0);
		GL_DepthFunc(GL_LESS);

		GL_Enable(GL_STENCIL_TEST);
		qglStencilFunc(GL_ALWAYS, 0, 255);
	//	qglStencilOp (GL_KEEP, GL_KEEP, GL_KEEP);
	//	qglStencilMask (255);
	}

	// build shadow volumes and render each to stencil buffer
	for (i=0; i<paliashdr->num_meshes; i++)
	{
		skinnum = (currententity->skinnum<paliashdr->meshes[i].num_skins)?currententity->skinnum:0;
		if (paliashdr->meshes[i].skins[skinnum].renderparms.noshadow)
			continue;

		R_BuildShadowVolume (paliashdr, i, light, projected_distance, r_shadowvolumes->integer);
		GL_LockArrays (shadow_va);

		if (!r_shadowvolumes->integer)
		{
			if (zFail &&glConfig.atiSeparateStencil && glConfig.extStencilWrap && r_stencilTwoSide->integer) // Barnes ATI stenciling
			{
				GL_Disable(GL_CULL_FACE);

				qglStencilOpSeparateATI (GL_BACK, GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP); 
				qglStencilOpSeparateATI (GL_FRONT, GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);

				R_DrawShadowVolume ();

				GL_Enable(GL_CULL_FACE);
			}
			else if (zFail && glConfig.extStencilTwoSide && glConfig.extStencilWrap && r_stencilTwoSide->integer) // Echon's two-sided stenciling
			{
				GL_Disable(GL_CULL_FACE);
				qglEnable (GL_STENCIL_TEST_TWO_SIDE_EXT);

				qglActiveStencilFaceEXT (GL_BACK);
				qglStencilOp (GL_KEEP, GL_INCR_WRAP_EXT, GL_KEEP);
				qglActiveStencilFaceEXT (GL_FRONT);
				qglStencilOp (GL_KEEP, GL_DECR_WRAP_EXT, GL_KEEP);

				R_DrawShadowVolume ();

				qglDisable (GL_STENCIL_TEST_TWO_SIDE_EXT);
				GL_Enable(GL_CULL_FACE);
			}
			else if (zFail)
			{
				// increment stencil if backface is behind depthbuffer
				GL_CullFace(GL_BACK); // quake is backwards, this culls front faces
				qglStencilOp(GL_KEEP, GL_INCR, GL_KEEP);
				R_DrawShadowVolume ();

				// decrement stencil if frontface is behind depthbuffer
				GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
				qglStencilOp(GL_KEEP, GL_DECR, GL_KEEP);
				R_DrawShadowVolume ();
			}
			else	// Z-Pass
			{
				// Fix for z-Pass shadows if viewpoint is inside volume
				// Same as Carmack's patent-free method for Doom3 GPL source
				// This pre-loads the stencil buffer with # of volumes
				// that get clipped by the near or far clip plane.
				if (inVolume)
				{
					GL_CullFace(GL_BACK); // quake is backwards, this culls front faces
					qglStencilOp(GL_KEEP, GL_INCR, GL_INCR);
					R_DrawShadowVolume ();
					GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
					qglStencilOp(GL_KEEP, GL_DECR, GL_DECR);
					R_DrawShadowVolume ();
				}

				// increment stencil if frontface is behind depthbuffer
				GL_CullFace(GL_FRONT); // quake is backwards, this culls back faces
				qglStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
				R_DrawShadowVolume ();

				// decrement stencil if backface is behind depthbuffer
				GL_CullFace(GL_BACK); // quake is backwards, this culls front faces
				qglStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
				R_DrawShadowVolume ();
			}
		}
		else
			R_DrawShadowVolume ();

		GL_UnlockArrays ();
	}

	// end stenciling and draw stenciled volume
	if (!r_shadowvolumes->integer)
	{
		GL_CullFace(GL_FRONT);
		GL_Disable(GL_STENCIL_TEST);
		
		GL_DepthFunc(GL_LEQUAL);
		GL_DepthMask(1);
		qglColorMask(1,1,1,1);
		
		// draw shadows for this model now
		R_ShadowBlend (aliasShadowAlpha * currententity->alpha); // was r_shadowalpha->value
		qglPopAttrib(); // restore stencil buffer
	}
}


/*
=================
R_DrawAliasPlanarShadow
=================
*/
void R_DrawAliasPlanarShadow (maliasmodel_t *paliashdr)
{
	maliasmesh_t	mesh;
	float	height, lheight, thisAlpha;
	vec3_t	point, shadevector;
	int		i, j, skinnum;

	R_ShadowLight (currententity->origin, shadevector);

	lheight = currententity->origin[2] - lightspot[2];
	height = -lheight + 0.1f;
	if (currententity->flags & RF_TRANSLUCENT)
		thisAlpha = aliasShadowAlpha * currententity->alpha; // was r_shadowalpha->value
	else
		thisAlpha = aliasShadowAlpha; // was r_shadowalpha->value

	// don't draw shadows above view origin, thnx to MrG
	if (r_newrefdef.vieworg[2] < (currententity->origin[2] + height))
		return;

	GL_Stencil (true, false);
	GL_BlendFunc (GL_SRC_ALPHA_SATURATE, GL_ONE_MINUS_SRC_ALPHA);

	rb_vertex = rb_index = 0;
	for (i=0; i<paliashdr->num_meshes; i++) 
	{
		mesh = paliashdr->meshes[i];

		skinnum = (currententity->skinnum<mesh.num_skins)?currententity->skinnum:0;
		if (mesh.skins[skinnum].renderparms.noshadow)
			continue;

		for (j=0; j < mesh.num_tris; j++)
		{
			indexArray[rb_index++] = rb_vertex + mesh.indexes[3*j+0];
			indexArray[rb_index++] = rb_vertex + mesh.indexes[3*j+1];
			indexArray[rb_index++] = rb_vertex + mesh.indexes[3*j+2];
		}

		for (j=0; j < mesh.num_verts; j++)
		{
			VectorCopy(tempVertexArray[i][j], point);
			point[0] -= shadevector[0]*(point[2]+lheight);
			point[1] -= shadevector[1]*(point[2]+lheight);
			point[2] = height;
			VA_SetElem3(vertexArray[rb_vertex], point[0], point[1], point[2]);
			VA_SetElem4(colorArray[rb_vertex], 0, 0, 0, thisAlpha);
			rb_vertex++;
		}
	}
	RB_DrawArrays ();
	rb_vertex = rb_index = 0;

	GL_Stencil (false, false);
}


/*
=================
R_CullAliasModel
=================
*/
//static qboolean R_CullAliasModel ( vec3_t bbox[8], entity_t *e )
static qboolean R_CullAliasModel (vec3_t bbox[8], vec3_t shadowBBox[8], entity_t *e, qboolean volumeShadow)
{
	int			i, j, mask, aggregatemask = ~0;
	float		dp, volProjDist;
	vec3_t		mins, maxs, tmp, vectors[3]; //angles;
	vec3_t		lightVec, shadowVec, tmp_bbox[8], end_bbox[8], volumeMins, volumeMaxs;
	maliasmodel_t	*paliashdr;
	maliasframe_t	*pframe, *poldframe;

	paliashdr = (maliasmodel_t *)currentmodel->extradata;

	if ( ( e->frame >= paliashdr->num_frames ) || ( e->frame < 0 ) )
	{
		VID_Printf (PRINT_ALL, "R_CullAliasModel %s: no such frame %d\n", 
			currentmodel->name, e->frame);
		e->frame = 0;
	}
	if ( ( e->oldframe >= paliashdr->num_frames ) || ( e->oldframe < 0 ) )
	{
		VID_Printf (PRINT_ALL, "R_CullAliasModel %s: no such oldframe %d\n", 
			currentmodel->name, e->oldframe);
		e->oldframe = 0;
	}

	pframe = paliashdr->frames + e->frame;
	poldframe = paliashdr->frames + e->oldframe;

	// compute axially aligned mins and maxs
	if ( pframe == poldframe )
	{
		VectorCopy(pframe->mins, mins);
		VectorCopy(pframe->maxs, maxs);
	}
	else
	{
		for ( i = 0; i < 3; i++ )
		{
			if (pframe->mins[i] < poldframe->mins[i])
				mins[i] = pframe->mins[i];
			else
				mins[i] = poldframe->mins[i];

			if (pframe->maxs[i] > poldframe->maxs[i])
				maxs[i] = pframe->maxs[i];
			else
				maxs[i] = poldframe->maxs[i];
		}
	}

	// jitspoe's bbox rotation fix
	// compute and rotate bonding box
	e->angles[ROLL] = -e->angles[ROLL]; // roll is backwards
	AngleVectors(e->angles, vectors[0], vectors[1], vectors[2]);
	e->angles[ROLL] = -e->angles[ROLL]; // roll is backwards
	VectorSubtract(vec3_origin, vectors[1], vectors[1]); // AngleVectors returns "right" instead of "left"
	for (i = 0; i < 8; i++)
	{
		tmp[0] = ((i & 1) ? mins[0] : maxs[0]);
		tmp[1] = ((i & 2) ? mins[1] : maxs[1]);
		tmp[2] = ((i & 4) ? mins[2] : maxs[2]);

		VectorCopy(tmp, tmp_bbox[i]);	// save off un-rotated bbox
		VectorAdd(tmp, e->origin, end_bbox[i]);	// version with e->origin added for light vector calc

		bbox[i][0] = vectors[0][0] * tmp[0] + vectors[1][0] * tmp[1] + vectors[2][0] * tmp[2] + e->origin[0];
		bbox[i][1] = vectors[0][1] * tmp[0] + vectors[1][1] * tmp[1] + vectors[2][1] * tmp[2] + e->origin[1];
		bbox[i][2] = vectors[0][2] * tmp[0] + vectors[1][2] * tmp[1] + vectors[2][2] * tmp[2] + e->origin[2];
	}

	// calc shadow volume bbox and rotate
	if (volumeShadow)
	{
		R_LightPoint (e->origin, shadelight, false);
		volProjDist = R_CalcAliasVolumeShadowLightVector (end_bbox, lightVec);

		for (i=0; i<3; i++)
			shadowVec[i] = -lightVec[i] * volProjDist;

		VectorCopy (vec3_origin, volumeMaxs);
		VectorCopy (vec3_origin, volumeMins);

		for (i = 0; i < 8; i++)
		{
			VectorAdd(tmp_bbox[i], shadowVec, end_bbox[i]);
		//	VectorCopy(end_bbox[i], tmp);
		
			for (j=0; j<3; j++)
			{
				if (tmp_bbox[i][j] < volumeMins[j])
					volumeMins[j] = tmp_bbox[i][j];
				if (end_bbox[i][j] < volumeMins[j])
					volumeMins[j] = end_bbox[i][j];

				if (tmp_bbox[i][j] > volumeMaxs[j])
					volumeMaxs[j] = tmp_bbox[i][j];
				if (end_bbox[i][j] > volumeMaxs[j])
					volumeMaxs[j] = end_bbox[i][j];
			}

			tmp[0] = ((i & 1) ? volumeMins[0] : volumeMaxs[0]);
			tmp[1] = ((i & 2) ? volumeMins[1] : volumeMaxs[1]);
			tmp[2] = ((i & 4) ? volumeMins[2] : volumeMaxs[2]);
		
			shadowBBox[i][0] = vectors[0][0] * tmp[0] + vectors[1][0] * tmp[1] + vectors[2][0] * tmp[2] + e->origin[0];
			shadowBBox[i][1] = vectors[0][1] * tmp[0] + vectors[1][1] * tmp[1] + vectors[2][1] * tmp[2] + e->origin[1];
			shadowBBox[i][2] = vectors[0][2] * tmp[0] + vectors[1][2] * tmp[1] + vectors[2][2] * tmp[2] + e->origin[2];
		}
	}

	// cull
	for (i=0; i<8; i++)
	{
		mask = 0;
		for (j=0; j<4; j++)
		{
			dp = DotProduct(frustum[j].normal, bbox[i]);
			if ( ( dp - frustum[j].dist ) < 0 )
				mask |= (1<<j);
		}

		aggregatemask &= mask;
	}

	if ( aggregatemask )
		return true;

	return false;
}


/*
=================
R_CullAliasShadow
=================
*/
static qboolean R_CullAliasShadow (vec3_t bbox[8], entity_t *e)
{
	int			i, j, mask, aggregatemask = ~0;
	float		dp;

	for (i=0; i<8; i++)
	{
		mask = 0;
		for (j=0; j<4; j++)
		{
			dp = DotProduct(frustum[j].normal, bbox[i]);
			if ( ( dp - frustum[j].dist ) < 0 )
				mask |= (1<<j);
		}
		aggregatemask &= mask;
	}

	if ( aggregatemask )
		return true;

	return false;
}


/*
=================
R_DrawAliasModel
=================
*/
void R_DrawAliasModel (entity_t *e)
{
	maliasmodel_t	*paliashdr;
	vec3_t		bbox[8], shadowBBox[8];
	qboolean	mirrorview = false, mirrormodel = false;
	qboolean	planarShadow = false, volumeShadow = false, volumeShadowOnly = false;
	qboolean	preLerped = false;
	int			i;

	// determine if this model will have a volume shadow
	if ( (r_shadows->integer >= 1) &&
		!(r_newrefdef.rdflags & RDF_NOWORLDMODEL) &&
		(r_worldmodel != NULL) && (r_worldmodel->lightdata != 0) &&
		!(e->flags & (RF_WEAPONMODEL | RF_NOSHADOW)) &&
		!( (e->flags & RF_MASK_SHELL) && (e->flags & RF_TRANSLUCENT) ) )	// no shadows from shells
	{
		aliasShadowAlpha = R_CalcShadowAlpha(e);
		if ( (r_shadows->integer == 3) && (aliasShadowAlpha >= DIV255) )
			volumeShadow = true;
		else if (aliasShadowAlpha >= DIV255)
			planarShadow = true;
	}

	// also skip this for viewermodels and cameramodels
	if ( !(e->flags & RF_WEAPONMODEL || e->flags & RF_VIEWERMODEL || e->renderfx & RF2_CAMERAMODEL) )
	{
	//	if (R_CullAliasModel(bbox, e))
	//		return;
		qboolean culled = R_CullAliasModel(bbox, shadowBBox, e, volumeShadow);
		if (volumeShadow)
		{
			if (culled) {
				if ( R_CullAliasShadow(shadowBBox, e) )
					return;
				else
					volumeShadowOnly = true;
			}
		}
		else if (culled)
			return;
	}

	// mirroring support
	if (e->flags & RF_WEAPONMODEL)
	{
		if (r_lefthand->integer == 2)
			return;
		else if (r_lefthand->integer == 1)
			mirrorview = true;
	}
	else if (e->renderfx & RF2_CAMERAMODEL)
	{
		if (r_lefthand->integer == 1)
			mirrormodel = true;
	}
	else if (e->flags & RF_MIRRORMODEL)
		mirrormodel = true;
	// end mirroring support

	// clamp r_celshading_width to >= 1.0
	if (!r_celshading_width)
		r_celshading_width = Cvar_Get("r_celshading_width", "4", 0);
	if (r_celshading_width->value < 1.0f)
		Cvar_SetValue( "r_celshading_width", 1.0f);

	paliashdr = (maliasmodel_t *)currentmodel->extradata;

	R_SetShadeLight ();

	if (e->flags & RF_DEPTHHACK) // hack the depth range to prevent view model from poking into walls
	{
		if (r_newrefdef.rdflags & RDF_NOWORLDMODEL)
			GL_DepthRange (gldepthmin, gldepthmin + 0.01*(gldepthmax-gldepthmin));
		else
			GL_DepthRange (gldepthmin, gldepthmin + 0.3*(gldepthmax-gldepthmin));
	}

	// mirroring support
//	if (mirrormodel)
//		R_FlipModel (true);
	if (mirrorview || mirrormodel)
		R_FlipModel (true, mirrormodel);

	for (i=0; i < paliashdr->num_meshes; i++)
		c_alias_polys += paliashdr->meshes[i].num_tris;

	qglPushMatrix ();
	e->angles[ROLL] = e->angles[ROLL] * R_RollMult();		// roll is backwards
	R_RotateForEntity (e, true);
	e->angles[ROLL] = e->angles[ROLL] * R_RollMult();		// roll is backwards

	if ( (e->frame >= paliashdr->num_frames) || (e->frame < 0) )
	{
		VID_Printf (PRINT_ALL, "R_DrawAliasModel %s: no such frame %d\n", currentmodel->name, e->frame);
		e->frame = 0;
		e->oldframe = 0;
	}

	if ( (e->oldframe >= paliashdr->num_frames) || (e->oldframe < 0))
	{
		VID_Printf (PRINT_ALL, "R_DrawAliasModel %s: no such oldframe %d\n",
			currentmodel->name, e->oldframe);
		e->frame = 0;
		e->oldframe = 0;
	}

	if (!r_lerpmodels->integer)
		e->backlerp = 0;

	// draw shadow only here
	if ( volumeShadowOnly || (volumeShadow && r_shadow_self->integer == 0) )
	{
		preLerped = true;
		R_DrawAliasMeshes (paliashdr, e, mirrormodel, mirrorview, false, true);

		GL_DisableTexture(0);
		GL_Enable (GL_BLEND);

		R_DrawAliasVolumeShadow (paliashdr, bbox);

		GL_Disable (GL_BLEND);
		GL_EnableTexture(0);

		// the following is not called if drawing shadow volume before model
		if ( volumeShadowOnly )
		{
			qglPopMatrix ();
			if (mirrorview || mirrormodel)
				R_FlipModel (false, mirrormodel);
			if (e->flags & RF_DEPTHHACK)
				GL_DepthRange (gldepthmin, gldepthmax);
			if (r_showbbox->integer) {
				GL_Disable (GL_DEPTH_TEST);
				R_DrawAliasModelBBox (shadowBBox, e, 0.0f, 0.0f, 1.0f, 1.0f);
				GL_Enable (GL_DEPTH_TEST);
			}
			return;
		}
	}

//	R_DrawAliasMeshes (paliashdr, e, false, mirrormodel, mirrorview);
	R_DrawAliasMeshes (paliashdr, e, mirrormodel, mirrorview, preLerped, false);

	qglPopMatrix ();

	// mirroring support
//	if (mirrormodel)
//		R_FlipModel (false);
	if (mirrorview || mirrormodel)
		R_FlipModel (false, mirrormodel);

	// show model bounding box
//	R_DrawAliasModelBBox (bbox, e, 1.0f, 1.0f, 1.0f, 1.0f);
	if (r_showbbox->integer) {
		R_DrawAliasModelBBox (bbox, e, 1.0f, 1.0f, 1.0f, 1.0f);
		if (volumeShadow) {
			GL_Disable (GL_DEPTH_TEST);
			R_DrawAliasModelBBox (shadowBBox, e, 0.0f, 0.0f, 1.0f, 1.0f);
			GL_Enable (GL_DEPTH_TEST);
		}
	}

	if (e->flags & RF_DEPTHHACK)
		GL_DepthRange (gldepthmin, gldepthmax);

//	aliasShadowAlpha = R_CalcShadowAlpha(e);

/*	if ( !(e->flags & (RF_WEAPONMODEL | RF_NOSHADOW))
		// no shadows from shells
		&& !( (e->flags & RF_MASK_SHELL) && (e->flags & RF_TRANSLUCENT) )
		&& r_shadows->integer >= 1 && aliasShadowAlpha >= DIV255) */
	if ( volumeShadow || planarShadow)
	{
 		qglPushMatrix ();
		GL_DisableTexture(0);
		GL_Enable (GL_BLEND);

	//	if (r_shadows->integer == 3) {
		if (volumeShadow && r_shadow_self->integer != 0) {
			e->angles[ROLL] = e->angles[ROLL] * R_RollMult();	// roll is backwards
			R_RotateForEntity (e, true);
			e->angles[ROLL] = e->angles[ROLL] * R_RollMult();	// roll is backwards
			R_DrawAliasVolumeShadow (paliashdr, bbox);
		}
	//	else {
		else if (planarShadow) {
			R_RotateForEntity (e, false);
			R_DrawAliasPlanarShadow (paliashdr);
		}

		GL_Disable (GL_BLEND);
		GL_EnableTexture(0);
		qglPopMatrix ();
	}
}

#if 0
/*
=================
R_DrawAliasModelShadow
Just draws the shadow for a model
=================
*/
void R_DrawAliasModelShadow (entity_t *e)
{
	maliasmodel_t	*paliashdr;
	vec3_t		bbox[8];
	qboolean	mirrormodel = false;

	if (!r_shadows->integer)
		return;
	if (e->flags & (RF_WEAPONMODEL | RF_NOSHADOW))
		return;
	// no shadows from shells
	if ( (e->flags & RF_MASK_SHELL) && (e->flags & RF_TRANSLUCENT) )
		return;

	// also skip this for viewermodels and cameramodels
	if ( !(e->flags & RF_WEAPONMODEL || e->flags & RF_VIEWERMODEL || e->renderfx & RF2_CAMERAMODEL) )
	{
		if (R_CullAliasModel(bbox, e))
			return;
	}

	aliasShadowAlpha = R_CalcShadowAlpha(e);
	if (aliasShadowAlpha < DIV255) // out of range
		return;

	if (e->renderfx & RF2_CAMERAMODEL)
	{
		if (r_lefthand->integer == 1)
			mirrormodel = true;
	}
	else if (e->flags & RF_MIRRORMODEL)
		mirrormodel = true;

	paliashdr = (maliasmodel_t *)currentmodel->extradata;

	if ( (e->frame >= paliashdr->num_frames) || (e->frame < 0) )
	{
		e->frame = 0;
		e->oldframe = 0;
	}

	if ( (e->oldframe >= paliashdr->num_frames) || (e->oldframe < 0))
	{
		e->frame = 0;
		e->oldframe = 0;
	}

//	if ( !r_lerpmodels->integer )
//		e->backlerp = 0;
	
//	R_DrawAliasMeshes (paliashdr, e, true, mirrormodel);
	R_DrawAliasMeshes (paliashdr, e, mirrormodel, false, false, true);

	qglPushMatrix ();
	GL_DisableTexture(0);
	GL_Enable (GL_BLEND);
				
	if (r_shadows->integer == 3) {
		e->angles[ROLL] = e->angles[ROLL] * R_RollMult();	// roll is backwards
		R_RotateForEntity (e, true);
		e->angles[ROLL] = e->angles[ROLL] * R_RollMult();	// roll is backwards
		R_DrawAliasVolumeShadow (paliashdr, bbox);
	}
	else {
		R_RotateForEntity (e, false);
		R_DrawAliasPlanarShadow (paliashdr);
	}

	GL_Disable (GL_BLEND);
	GL_EnableTexture(0);
	qglPopMatrix ();
}
#endif