// gl_mesh.c: triangle model functions

#include <float.h>
#include "gl_local.h"

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

  ALIAS MODELS

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

// This is why Quarternions RULES!! < BEAM >

enum { combineOrderSize = MAX_VERTS };
enum { altOrderSize = MAX_VERTS * 4 };
enum { indexAltOrderSize = MAX_VERTS / 5 };
enum { smallAltOrderSize = MAX_VERTS * 4 };

// use 0.0f to turn of LOD completely
static const float LOD_resolution = ( float ) ( 16.0 / ( 640.0 * 480.0 ) );

//enum { NUMVERTEXNORMALS	= 162 };
#define NUMVERTEXNORMALS	162
float r_avertexnormals [ NUMVERTEXNORMALS ] [ 3 ] = {
#include "anorms.h"
};

// vectors <-> quarternions
typedef union _TQVec3
{
	vec3_t v;
	float data [ 4 ];
} TQVec3;

// translates my quarternions into id wierd shit
typedef union _TQuarternion
{
	float q		[ 4 ] [ 4 ];	// cool quarternions	:)
	float id	[ 3 ] [ 4 ];	// wierd shit			;p
	TQVec3 vec3 [ 4 ];			// vectors				:|
}
TQuarternion;

static float LOD_DetermineDetail ( int * * lastVertices ); // calculate detail level of these projected verts of this polygon
static void LOD_ProjVerts ( void ); // project vertices into view space for silhouette perservation
static qboolean LOD_KickPrim ( float * area );
static int LOD_StoreAltList ( int * * lastVertices, int numVert, int * * pCount, qboolean isStrip, qboolean kickVerts ); // store generated prims in alternate list
static qboolean LOD_FinalizeAltList ( float * area, int * * lastVertices, int numVert, int * * pCount, qboolean isStrip );
static void LOD_RewindList ( void );
static void LOD_RewindAltList ( void );
static qboolean LOD_Terminate ( void );
static void VIS_ScreenSpacePop ( void );
static qboolean LOD_IncrementCombine ( size_t srcLength );
static void LOD_FatalMemory ( void );

enum
{
	VT_OUT = - 3,
	VT_REJECT = - 2,
	VT_KEEP = - 1,
	VT_NEED_PROJECT = 0,
};

static qboolean getSnapShot = false;

static int		combineOrder	[ combineOrderSize ];
static int		altOrder		[ altOrderSize ];
static vec3_t	lerped			[ MAX_VERTS ];
static short	indexAltOrder	[ indexAltOrderSize ];
static int		* pCombineOrder		= combineOrder;
static int		* pAltOrder			= altOrder;
static short	* pIndexAltOrder	= indexAltOrder;
static int count	= 0;
static int *pOrder	= NULL;

static float shadelight [ 3 ];

static TQuarternion globalQuarternion;
static TQuarternion localQuarternion;
static daliasframe_t	* oldframe	= NULL;
static daliasframe_t	* frame		= NULL;
static dmdl_t			* paliashdr = NULL;

#define TransformVectorComp(i,inVector,transform)(DotProduct(transform[i],inVector)+transform[i][3]) // perform transform on a vector component, used for within a 3-loop
#define TransformVector(vector,out,transform) {int i;for(i=2;i>=0;--i)out[i]=TransformVectorComp(i,vector,transform);} // perform transform on a vector
#define ReflectQuarternion(qt){int i;for(i=2;i>=0;--i)qt.q[3][i]=qt.q[i][3];}

extern cvar_t * snapShotMode;

void
LOD_GetSnapShot ( void )
{
	if ( snapShotMode && snapShotMode->value ) { getSnapShot = true; return; }
	snapShotMode = ri.Cvar_Set ( "lod_snapshot_mode", "1" );
}

//void
//R_Alias_ClearCounter ( void )
//{
//	averageEntityCount	= entityCount / ( float ) frameElapsed;
//	averageDrawCount	= drawCount / ( float ) frameElapsed;
//	ri.Con_Printf ( PRINT_ALL, "alias models passed = %f\talias models drawn = %f\n", averageEntityCount, averageDrawCount );
//	++frameElapsed;
//}

// bonus items will pulse with time
static void GlowBonusItems ( void )
{

	const float scale = 0.1 * sin ( r_newrefdef.time * 7.0 ); // scale up ( or down ) light by periodic function

	float min;
	float * pS;

	for ( pS = shadelight + 2 ; pS >= shadelight ; --pS )
	{
		min = ( *pS ) * 0.8f; // keep track of minimum of original
		( *pS ) += scale;	// "glow"
		if ( ( *pS ) >= min ) continue; // if at least minimum next component
		( *pS ) = min;		// bound light at minimum
	}
}

// min lighting level
static void MinLight ( void )
{

	// check that light is at least 0.1f
	{
		float * pS;
		for ( pS = shadelight + 2 ; pS >= shadelight ; --pS ) if ( ( *pS ) > 0.1f ) return; 
	}

	// keep light at 0.1f
	shadelight [ 0 ] = 0.1f;
	shadelight [ 1 ] = 0.1f;
	shadelight [ 2 ] = 0.1f;
}

// draw in view space for LOD implementation
static void ViewSpaceProjection ( void )
{

	extern void MYgluPerspective( GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar );

#ifndef PROFILE_GL
	qglMatrixMode ( GL_PROJECTION );
	qglPushMatrix ( );
	qglLoadIdentity ( );
	qglScalef ( - 1, 1, 1 );
	MYgluPerspective ( r_newrefdef.fov_y, ( float ) r_newrefdef.width / r_newrefdef.height, 4, 8192 );
	qglMatrixMode ( GL_MODELVIEW );
	qglCullFace ( GL_BACK );
#else
	qglMatrixMode ( GL_PROJECTION, "ViewSpaceProjection" );
	qglPushMatrix ( "ViewSpaceProjection" );
	qglLoadIdentity ( "ViewSpaceProjection" );
	qglScalef ( - 1, 1, 1, "ViewSpaceProjection" );
	MYgluPerspective ( r_newrefdef.fov_y, ( float ) r_newrefdef.width / r_newrefdef.height, 4, 8192 );
	qglMatrixMode ( GL_MODELVIEW, "ViewSpaceProjection" );
	qglCullFace ( GL_BACK, "ViewSpaceProjection" );
#endif

}

// draw in view space for LOD implementation
static void ViewSpaceProjectionPop ( void )
{
#ifndef PROFILE_GL
	qglMatrixMode ( GL_PROJECTION );
	qglPopMatrix ( );
	qglMatrixMode ( GL_MODELVIEW );
	qglCullFace ( GL_FRONT );
#else
	qglMatrixMode ( GL_PROJECTION, "ViewSpaceProjectionPop" );
	qglPopMatrix ( "ViewSpaceProjectionPop" );
	qglMatrixMode ( GL_MODELVIEW, "ViewSpaceProjectionPop" );
	qglCullFace ( GL_FRONT, "ViewSpaceProjectionPop"  );
#endif

}

// verify frame numbers are valid
static void CheckFrame ( int fr, const char * pName )
{
	if ( ( fr < paliashdr->num_frames ) && ( fr >= 0 ) ) return; // bound by 0 and num frames
	ri.Con_Printf ( PRINT_ALL, "R_DrawAliasModel: no such %s %d\n", pName, fr ); // hey, no such frame
	currententity->oldframe = currententity->frame = 0; // set to frame 0
}

// set up frame pointers
static void SetupFrames ( void )
{
	CheckFrame ( currententity->frame, "frame" );
	CheckFrame ( currententity->oldframe, "old frame" );
	frame		= ( daliasframe_t * ) ( ( byte * ) paliashdr + paliashdr->ofs_frames + currententity->frame		* paliashdr->framesize );
	oldframe	= ( daliasframe_t * ) ( ( byte * ) paliashdr + paliashdr->ofs_frames + currententity->oldframe	* paliashdr->framesize );
}

// create global transform quarternion
// create angle quarternion
// create local transform quarternion
static void InitAliasQuarternions ( void )
{

	int	i;

	// create global and angle quarternion
	{
		vec3_t angles;
		memcpy ( angles, currententity->angles, sizeof ( angles ) );
		angles [ 1 ] = - angles [ 1 ];
		AngleVectors ( angles, globalQuarternion.vec3 [ 0 ]. v, globalQuarternion.vec3 [ 1 ].v, globalQuarternion.vec3 [ 2 ].v );
	}

	// concatenate local quarternion
	{
		TQuarternion view;
		memcpy ( view.q [ 0 ], vright,	sizeof ( vright ) );
		memcpy ( view.q [ 1 ], vup,		sizeof ( vup	) );
		memcpy ( view.q [ 2 ], vpn,		sizeof ( vpn	) );
		for ( i = 2 ; i >= 0 ; --i )
		{
			globalQuarternion.q			[ 1 ] [ i ] = - globalQuarternion.q [ 1 ] [ i ];	// negate pitch of global quarternion
			globalQuarternion.q			[ i ] [ 3 ] = currententity->origin [ i ] - r_origin [ i ]; // set up translation of global quarternion to relative origin
			view.q						[ 1 ] [ i ] = - view.q				[ 1 ] [ i ];	// negate pitch of view quarternion
			view.q						[ i ] [ 3 ] = 0.0f;									// view offset already taken care of by global quarternion
		}
		R_ConcatTransforms ( view.id, globalQuarternion.id, localQuarternion.id );
	}

	// set up translation of global quarternion to absolute origin
	// nega?
	globalQuarternion.q [ 0 ] [ 3 ] = currententity->origin [ 0 ]; 
	globalQuarternion.q [ 1 ] [ 3 ] = currententity->origin [ 1 ]; 
	globalQuarternion.q [ 2 ] [ 3 ] = currententity->origin [ 2 ]; 
}

// trivial reject alias model
static qboolean TrivialRejectFrame ( daliasframe_t * thisFrame )
{

	int i;
	vec3_t bbox [ 2 ];

	// transform the min and max values into view space
	// then near clip to 4
	// only need to calc z component ;p
	// if either of the models are too close to eye plane, also trivially reject it
	memcpy ( bbox [ 0 ], thisFrame->translate, sizeof ( float ) * 3 );
	for ( i = 2 ; i >= 0 ; --i ) bbox [ 1 ] [ i ] = bbox [ 0 ] [ i ] + thisFrame->scale [ i ] * 255; // get the exact frame bounding box
	{
		float v;
		for ( i = 1 ; i >= 0 ; --i )
		{
			v = TransformVectorComp ( 2, bbox [ i ], localQuarternion.q );
//			if ( v > 4.0f && v < 4096.0f ) break;
			if ( v > 4.0f && v < 2048.0f ) break;
		}
		if ( i < 0 ) return true;
	}

	// build a transformed bounding box from the given min and max
	{

		int j;
		vec3_t boxSide;
		unsigned long aggregatemask = ~0UL;
		unsigned long mask;

		for ( i = 7; i >= 0 ; --i )
		{
			mask = 0UL;
			for ( j = 2 ; j >= 0 ; --j ) // create side of box by picking the "furthest" plane
				boxSide [ j ] = TransformVectorComp ( j, bbox [ i & ( 1 << j ) ? 0 : 1 ], globalQuarternion.q ); // transform the side of the box into the same system as the frustrum planes
			for ( j = 3 ; j >= 0 ; --j ) if ( ( DotProduct ( boxSide, frustum [ j ].normal ) - frustum [ j ].dist ) < 0.0f )
				mask |= 1 << j; // this will mark all the planes for which the box "point" is on the "inside" of the frustrum plane
			aggregatemask &= mask; // make sure all the frustum planes for which we are "inside" of coincide (therefore out of the frustrum)
		}
		return aggregatemask;
	}
}

// set up shade light based on gouraud or not gouraud
static void InitShadeLight ( void )
{

	if ( currententity->flags & ( RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE ) )
	{
		VectorClear ( shadelight );
		if ( currententity->flags & RF_SHELL_RED	) *shadelight		= 1.0f;
		if ( currententity->flags & RF_SHELL_GREEN	) shadelight [ 1 ]	= 1.0f;
		if ( currententity->flags & RF_SHELL_BLUE	) shadelight [ 2 ]	= 1.0f;
		return;
	}
	
	if ( currententity->flags & RF_FULLBRIGHT )
	{
		shadelight [ 0 ] = 1.0f;
		shadelight [ 1 ] = 1.0f;
		shadelight [ 2 ] = 1.0f;
		return;
	}

	R_LightPoint ( currententity->origin, shadelight );

	// player lighting hack for communication back to server
	// big hack!
	// pick the greatest component, which should be the same
	// as the mono value returned by software
	{

		float s = *shadelight;
		if ( s < shadelight [ 1 ] ) s = shadelight [ 1 ];
		if ( s < shadelight [ 2 ] ) s = shadelight [ 2 ];
		if ( currententity->flags & RF_WEAPONMODEL ) r_lightlevel->value = s * 150.0f;
		if ( * ( gl_monolightmap->string ) )
		{
			shadelight [ 0 ] = s;
			shadelight [ 1 ] = s;
			shadelight [ 2 ] = s;
		}
	}
}

// get lighting information
static void SetupLighting ( void )
{
	InitShadeLight ( );
	if ( currententity->flags & RF_MINLIGHT ) MinLight ( );
	if ( currententity->flags & RF_GLOW ) GlowBonusItems ( ); // bonus items will pulse with time
}

// projection matrix is localQuarternion x frame->scale + ( frame->translate adjusted by lerp )
static void LOD_ConcatenateProjectionMatrix ( float * move )
{

	int i;

	TQuarternion preProjectMatrix;
	TQuarternion scaleMatrix;

	static TQuarternion view;
	static vec3_t myOrigin;

	if ( snapShotMode && snapShotMode->value )
	{
		if ( getSnapShot )
		{
			memcpy ( myOrigin, r_origin, sizeof ( r_origin ) );
			memcpy ( view.q [ 0 ], vright,	sizeof ( vright ) );
			memcpy ( view.q [ 1 ], vup,		sizeof ( vup	) );
			memcpy ( view.q [ 2 ], vpn,		sizeof ( vpn	) );
			for ( i = 2 ; i >= 0 ; --i )
			{
				view.q						[ 1 ] [ i ] = - view.q				[ 1 ] [ i ];	// negate pitch of view quarternion
				view.q						[ i ] [ 3 ] = 0.0f;									// view offset already taken care of by global quarternion
			}
			getSnapShot = false;
		}

		// create global and angle quarternion
		{
			vec3_t angles;
			memcpy ( angles, currententity->angles, sizeof ( angles ) );
			angles [ 1 ] = - angles [ 1 ];
			AngleVectors ( angles, globalQuarternion.vec3 [ 0 ]. v, globalQuarternion.vec3 [ 1 ].v, globalQuarternion.vec3 [ 2 ].v );
		}

		// concatenate local quarternion
		{
			for ( i = 2 ; i >= 0 ; --i )
			{
				globalQuarternion.q			[ 1 ] [ i ] = - globalQuarternion.q [ 1 ] [ i ];	// negate pitch of global quarternion
				globalQuarternion.q			[ i ] [ 3 ] = currententity->origin [ i ] - myOrigin [ i ]; // set up translation of global quarternion to relative origin
			}
			R_ConcatTransforms ( view.id, globalQuarternion.id, preProjectMatrix.id );
		}
	}
	else memcpy ( preProjectMatrix.id, localQuarternion.id, sizeof ( localQuarternion.id ) ); // preserve local quarternion

	// prepare scaleMatrix
	memset ( scaleMatrix.id, 0, sizeof ( scaleMatrix.id ) );
	for ( i = 2 ; i >= 0 ; --i )
	{
		scaleMatrix.id [ i ] [ i ] = frame->scale [ i ];
		scaleMatrix.id [ i ] [ 3 ] = move [ i ];
	}
	
	R_ConcatTransforms ( preProjectMatrix.id, scaleMatrix.id, localQuarternion.id ); // concatenate projection matrix
}

// store 1st vertex of fan
static void LOD_InitFanTri ( int * * lastVertices )
{
	lastVertices [ 0 ] = pOrder += 2; // pOrder [ 2 ] = index;
	LOD_ProjVerts ( );
	++pOrder; // keep 1st vertex
}

// make strip quad
// v0 = v2 of old quad
// v1 = v3 of old quad
// v2 = new point 0
// v3 = new point 1
// make fan tri
// v0 = remains 1st point
// v1 = v2 or old point
// v2 = new point
static void LOD_MakePrim ( int * * lastVertices, qboolean isStrip )
{
	if ( isStrip ) memmove ( lastVertices, lastVertices + 2, sizeof ( int * ) * 2 ); // v0 and v1 of new quad is v2 and v3 of old quad
	else lastVertices [ 1 ] = lastVertices [ 2 ]; // v1 of new fan tri is v2 of old fan tri
	lastVertices [ 2 ] = pOrder += 2; // pOrder [ 2 ] = index // for strip and fan, new p0 is new v2
	LOD_ProjVerts ( ); 

	// second triangle of strip quad, if there is one
	if ( count && isStrip )
	{
		lastVertices [ 3 ] = pOrder += 3; // advance to next point, then advance to pOrder [ 2 ] = index // new p1 is new v3 of strip quad
		LOD_ProjVerts ( ); 
	}
	else lastVertices [ 3 ] = NULL; // is a fan, or does not another strip quad vertex, then mark v3 as invalid

	++pOrder; // iterate pointer to point to next "data"
}

// test detail of 2nd tri of strip quad
static float LOD_StripNextPolyDetail ( int * * lastVertices )
{
	if ( lastVertices [ 3 ] ) // if the next tri is valid on the quad
	{
		int * polygon [ 3 ] = { lastVertices [ 2 ], lastVertices [ 1 ], lastVertices [ 3 ] }; // GL reverse 2nd tri in strip
		return LOD_DetermineDetail ( polygon ); // get detail of this
	}
	return 0.0f; // otherwise return 0 area
}

static int LOD_FinalizeCombineList ( float * primArea, int * * lastVertices, int numVert, int * * pCombineCount, int * * pCount, qboolean isStrip )
{
	if ( ! *pCombineCount ) return - 1; // if there is not a combine prim list then we are OK
	// if there is a combine prim list
	// we terminate the combine prim list (with the last 2 vertices) and do the real writes next time
	// how do we terminate?
	if ( **pCombineCount > 2 ) // if there are enough verts
	{
		int * * pPrim;
		for ( pCombineOrder -= 3 * numVert, pPrim = lastVertices + 2; numVert ; ++pPrim, --numVert, pCombineOrder += 3 ) // then terminate it for real
		{
			( ( long * ) ( lerped [ * ( * pPrim ) ] ) ) [ 2 ] = VT_KEEP;
			memcpy ( pCombineOrder, *pPrim - 2 , sizeof ( int ) * 3 );
		}
		if ( pPrim < lastVertices + 4 ) --( **pCombineCount );
		*pCombineCount = NULL; // no more combine list
		return 1; // then do not finalize the alt list (there is not one) or do the default thing
	}
	// if there are not enough verts
	// start start the real list with the verts of the combine prim list
	if ( ! LOD_StoreAltList ( pCombineCount + 1, numVert, pCount, isStrip, true ) ) return 0; // start a real strip with the vert of the prim as the start
	pCombineOrder = *pCombineCount; // rewind order
	*pCombineCount = NULL; // no more combine list
	return 1;
}

static qboolean LOD_CreateCombineList ( float * primArea, int * * lastVertices, int numVert, int * pCount, int * * pCombineCount )
{
	// if there is not a combine prim list
	// then need to start and make a new one (clear accumulator)
	primArea [ 0 ] = primArea [ 1 ];
	if ( ! LOD_IncrementCombine ( 1 + 2 * 3 ) ) return false;
	*pCombineCount = pCombineOrder;
	*( pCombineOrder++ ) = 2;
	{
		int * * pPrim;
		for ( pPrim = lastVertices ; pPrim < lastVertices + 2 ; pCombineOrder += 3, ++pPrim )
		{
			( ( long * ) ( lerped [ * ( *pPrim ) ] ) ) [ 2 ] = VT_KEEP;
			memcpy ( pCombineOrder, *pPrim - 2, sizeof ( int ) * 3 );
		}
	}
	// need to rewind and do other stuff
	if ( pCount )
	{
		pAltOrder = pCount; // if this strip is being stored for real need to rewind it back
		return true;
	}
	if ( *pIndexAltOrder < 0 ) { if ( ! LOD_Terminate ( ) ) return false; --pAltOrder; } // if there is not a strip and in fact no listing has started need to nullify that listing
	return true;
}

static qboolean LOD_AppendCombineList ( float * primArea, int * * lastVertices, int numVert, int * pCount, int * * pCombineCount )
{
	if ( numVert )
	{
		if ( pCombineOrder > combineOrder )
		{ 
			int * polygon [ 3 ] = { pCombineOrder - 1 - 3, pCombineOrder - 1, lastVertices [ 0 ] }; // check that it is contiguous polygon
			if ( fabs ( LOD_DetermineDetail ( polygon ) ) > LOD_resolution )
				return LOD_CreateCombineList ( primArea, lastVertices, numVert, pCount, pCombineCount );
		}
		if ( primArea [ 0 ] >= LOD_resolution ) // see if need to even append the new vertices to this list
		{
			primArea [ 0 ] = 0.0f;
			if ( numVert > 1 || **pCombineCount <= 2 || pCombineOrder [ - 1 ] != pCombineOrder [ - 1 - 3 * 2 ] ) // know for sure we do not have a chance to back and stick verts
			{
				if ( ! LOD_IncrementCombine ( 3 * 2 ) ) return false;
				**pCombineCount += 2; // update count
				{
					int * * pPrim;
					for ( pPrim = lastVertices + 2 ; numVert ; ++pPrim, --numVert, pCombineOrder += 3 ) // append vertices
					{
						( ( long * ) ( lerped [ * ( *pPrim ) ] ) ) [ 2 ] = VT_KEEP;
						memcpy ( pCombineOrder, *pPrim - 2, sizeof ( int ) * 3 );
					}
					if ( pPrim < lastVertices + 4 ) // if not full 2 verts make 1 up to generate a FW edge
					{
						( ( long * ) ( lerped [ pCombineOrder [ - 1 - 3 ] ] ) ) [ 2 ] = VT_KEEP;
						memcpy ( pCombineOrder, pCombineOrder - 3 * 2, sizeof ( int ) * 3 );
						pCombineOrder += 3;
					}
				}
			}
			else // go back and stick this vert because it is possible, and the last "poly" is not really there
			{
				( ( long * ) ( lerped [ lastVertices [ 2 ] [ 0 ] ] ) ) [ 2 ] = VT_KEEP; 
				memcpy ( pCombineOrder - 3, lastVertices [ 2 ] - 2, sizeof ( int ) * 3 );
			}
		}
	}
	if ( *pIndexAltOrder >= 0 ) return true;
	if ( ! LOD_Terminate ( ) ) return false; // if there is not a strip and in fact no listing has started need to nullify that listing
	--pAltOrder;
	return true;
}

static qboolean LOD_StoreCombineList ( float * primArea, int * * lastVertices, int numVert, int * pCount, int * * pCombineCount )
{
	if ( *pCombineCount ) return LOD_AppendCombineList ( primArea, lastVertices, numVert, pCount, pCombineCount );
	return LOD_CreateCombineList ( primArea, lastVertices, numVert, pCount, pCombineCount );
}

static qboolean LOD_IncrementCombine ( size_t srcLength )
{
	if ( ( pCombineOrder - combineOrder ) + srcLength <= combineOrderSize ) return true; // bound size of alt strip
	LOD_FatalMemory ( );
	return 0; // no room, must quit
}

// level of detail this alias model frame
static void LOD_Frame ( void )
{
	float primArea [ 2 ];
	float area [ 3 ];
	int * lastVertices [ 4 ];

	int OK;

	// if we are going through the first time through
	// we do NOT keep the front strip unless it is a truncated one
	// ( we would quit, not record, and go onto the next strip )
	// and we do NOT ignore the back strip if the ENTIRE strip is back
	// ( we will record the null strip instead )
	// after the first time through
	// we always keep any front strip found ( because we have already back culled something )
	// and we always ignore the back strip ( because we have already checked that the entire back strip is not culled )
	qboolean ALWAYS_keep_front_strip_and_ignore_back_strip; 

	qboolean needMakePrim;
	int * pCombineCount = NULL;
	int * pCount;

	qboolean isStrip;
	qboolean kickVerts;
	int numVert;

	primArea [ 0 ] = 0.0f;

	while ( true )
	{
		count = ( *pOrder ); // get length of original strip
		if ( ( ! count ) || ( pIndexAltOrder - indexAltOrder >= indexAltOrderSize ) ) break; // if end of original strip return // if we run out of room on alternate index, also quit

		// if we are going through the first time through
		// we do NOT keep the front strip unless it is a truncated one
		// ( we would quit, not record, and go onto the next strip )
		// and we do NOT ignore the back strip if the ENTIRE strip is back
		// ( we will record the null strip instead )
		// after the first time through
		// we always keep any front strip found ( because we have already back culled something )
		// and we always ignore the back strip ( because we have already checked that the entire back strip is not culled )
		ALWAYS_keep_front_strip_and_ignore_back_strip = false; 

		*pIndexAltOrder = - 1; // initialize to none
		++pOrder;

		if ( count > 0 ) isStrip = true;
		else
		{
			count = - count; // keep count positive
			isStrip = false;
			LOD_InitFanTri ( lastVertices ); // if fan need to store 1st vert
		}

		// initialize the 1st prim
		LOD_MakePrim ( lastVertices, isStrip ); 
		LOD_MakePrim ( lastVertices, isStrip );

		pCount = NULL; // OK new poly
		while ( true )
		{

			// **** forward phase ***
			// now find end of forward cull prim if there is any
			// if there is not one, a forward prim strip will not be made
			primArea [ 1 ] = area [ 0 ] = 0.0f;
			while ( true )
			{
				area [ 1 ] = LOD_DetermineDetail		( lastVertices );
				area [ 2 ] = LOD_StripNextPolyDetail	( lastVertices );
				kickVerts = LOD_KickPrim ( area ); // determine if detail is sufficient
				primArea [ 1 ] += area [ 0 ];
				primArea [ 0 ] += area [ 0 ];
				if ( isStrip )
				{
					if ( area [ 2 ] <= 0.0f ) // it is either all f or back front, still need to keep
					{
						needMakePrim = area [ 1 ] > 0.0f; // if front back need to advance 1 prim
						numVert = needMakePrim ? 1 : 0; // front back, kick at back // all back kick at beginning
						break;
					}
				}
				else if ( area [ 1 ] <= 0.0f ) // if it is back 
				{
					needMakePrim = false;
					numVert = 0; // all back kick at beginning
					break;
				} 
				numVert = lastVertices [ 3 ] ? 2 : 1;
				if ( ! count ) break; // if no more prims have to keep whole thing remaining

				// attempt to form or reduce polygons
				if ( ! kickVerts ) 
				{
					OK = LOD_FinalizeCombineList ( primArea, lastVertices, numVert, &pCombineCount, &pCount, isStrip );
					area [ 0 ] = 0.0f; // if detail is sufficient clear accumulator
				} else OK = - 1;
				if ( OK < 0 ) // only store to alt list if there is no combine list
				{
					// if new poly write count and first 2 verts
					// if kick vert do not write anything
					// if keep vert write last 2 verts
					OK = LOD_StoreAltList ( lastVertices, numVert, &pCount, isStrip, kickVerts );
					if ( OK > 0 ) ALWAYS_keep_front_strip_and_ignore_back_strip = true;
				}
				if ( OK )
				{
					LOD_MakePrim ( lastVertices, isStrip ); // if have prim advance prims
					continue;
				}
				break;
			}
			if ( ! OK ) break;

			if ( primArea [ 1 ] <= LOD_resolution ) // if the section is small and need to LOD down
			{
				// if there is not a combine prim list
				// then need to start and make a new one (clear accumulator)
				// need to rewind and do other stuff
				// if there is a combine prim list
				// see if need to even append the new vertices to this list
				if ( ! LOD_StoreCombineList ( primArea, lastVertices, numVert, pCount, &pCombineCount ) ) break;
				pCount = NULL; // OK new poly
			}
			else
			{
				pCount = NULL; // OK new poly
				// if there is not a combine prim list then we are OK
				// if there is a combine prim list
				// then do not finalize the alt list (there is not one) or do the default thing
				// we terminate the combine prim list (with the last 2 vertices) and do the real writes next time
				// how do we terminate?
				// if there are enough verts
				// then terminate it for real
				// if there are not enough verts
				// start start the real list with the verts of the combine prim list
				OK = LOD_FinalizeCombineList ( primArea, lastVertices, numVert, &pCombineCount, &pCount, isStrip );
				if ( OK < 0 )
				{
					if ( ALWAYS_keep_front_strip_and_ignore_back_strip || count ) // if not LOD down and this is not the "only strip"
					{
						// store prims in alternate list
						// determine if the detail is sufficient for stand alone polygons
						// if not sufficient detail still preserve edge vertices
						// if there is not a strip and in fact no listing has started need to nullify that listing
						// overwrite final vertices of last poly
						// if sufficient detail then just draw the polygon
						// if new poly write count and first 2 verts
						// write last 2 verts
						if ( numVert ) if ( ! LOD_FinalizeAltList ( area, lastVertices, numVert, &pCount, isStrip ) ) break; // if the current poly is big enough force store it instead
					}
					else // if not LOD down and this is the "only strip", there is no substitute list
					{
						( ( long * ) ( lerped [ lastVertices [ 0 ] [ 0 ] ] ) ) [ 2 ] = VT_KEEP;
						( ( long * ) ( lerped [ lastVertices [ 1 ] [ 0 ] ] ) ) [ 2 ] = VT_KEEP;
						( ( long * ) ( lerped [ lastVertices [ 2 ] [ 0 ] ] ) ) [ 2 ] = VT_KEEP;
						if ( lastVertices [ 3 ] ) ( ( long * ) ( lerped [ lastVertices [ 3 ] [ 0 ] ] ) ) [ 2 ] = VT_KEEP;
						LOD_RewindAltList ( );
						break;
					}
				}
				else if ( ! OK ) break;
			}
			if ( ! count ) break; // if this is the end we are done

			ALWAYS_keep_front_strip_and_ignore_back_strip = true; // not the 1st time any more
			if ( needMakePrim ) LOD_MakePrim ( lastVertices, isStrip ); // if strip and front-back we need to advance 1 prim 1st otherwise infinite loop

			// *** backward phase ***
			// now find end of backward cull prim
			while ( ( LOD_DetermineDetail ( lastVertices ) <= 0.0f ) &&	// stay while 1st face is back AND
					( ( ! isStrip ) || ( LOD_StripNextPolyDetail ( lastVertices ) <= 0.0f ) ) )	// 2nd face is also back
			{
				if ( count ) { LOD_MakePrim ( lastVertices, isStrip ); continue; } // if there are more check more
				LOD_Terminate ( ); break; // if no more terminate list (or make blank list)
			}
		}
		++pIndexAltOrder;
	}
	*pAltOrder = 0;
	*pCombineOrder = 0;
}

// project vertices into view space for silhouette perservation
static void LOD_ProjVerts ( void )
{

	float * pVert;
	register int index = *pOrder;

	--count; // keep track of vertex count
	pVert = lerped [ index ];

	if ( ( ( long * ) pVert ) [ 2 ] == VT_NEED_PROJECT ) // save projection
	{
		unsigned char * v = frame->verts [ index ].v;
		float v0 = ( float ) v [ 0 ];
		float v1 = ( float ) v [ 1 ];
		float v2 = ( float ) v [ 2 ];
		pVert [ 2 ] =	localQuarternion.q [ 2 ] [ 0 ] * v0 +
						localQuarternion.q [ 2 ] [ 1 ] * v1 +
						localQuarternion.q [ 2 ] [ 2 ] * v2 +
						localQuarternion.q [ 2 ] [ 3 ] - 4.0f;
		if ( pVert [ 2 ] > 0.0f )
		{
			float ooz = 1.0f / pVert [ 2 ];
			pVert [ 0 ] = ( localQuarternion.q [ 0 ] [ 0 ] * v0 +
							localQuarternion.q [ 0 ] [ 1 ] * v1 +
							localQuarternion.q [ 0 ] [ 2 ] * v2 +
							localQuarternion.q [ 0 ] [ 3 ] ) * ooz;
			pVert [ 1 ] = ( localQuarternion.q [ 1 ] [ 0 ] * v0 +
							localQuarternion.q [ 1 ] [ 1 ] * v1 +
							localQuarternion.q [ 1 ] [ 2 ] * v2 +
							localQuarternion.q [ 1 ] [ 3 ] ) * ooz;
			( ( long * ) pVert ) [ 2 ] = VT_REJECT; // initialize to reject unless kept
			return;
		}
		( ( long * ) pVert ) [ 2 ] = VT_OUT; // initialize to reject unless kept
	}
}

// calculate detail level of these projected verts of this polygon
static float LOD_DetermineDetail ( int * * lastVertices )
{
	float * x0 = lerped [ lastVertices [ 0 ] [ 0 ] ];
	float * x1 = lerped [ lastVertices [ 1 ] [ 0 ] ];
	float * x2 = lerped [ lastVertices [ 2 ] [ 0 ] ];
	if ( ( ( long * ) x0 ) [ 2 ] == VT_OUT ) return - 1.0f;
	if ( ( ( long * ) x1 ) [ 2 ] == VT_OUT ) return - 1.0f;
	if ( ( ( long * ) x2 ) [ 2 ] == VT_OUT ) return - 1.0f;
	{
		float y0 = x0 [ 1 ];
		float y1 = x1 [ 1 ];
		float y2 = x2 [ 1 ];
		return *x2 * ( y0 - y1 ) + *x0 * ( y1 - y2 ) + *x1 * ( y2 - y0 );
	}
}

static void LOD_FatalMemory ( void )
{
	LOD_RewindAltList ( );
	while ( count-- ) { pOrder += 2; ( ( long * ) lerped [ *pOrder ] ) [ 2 ] = VT_KEEP; ++pOrder; }
}

static void LOD_RewindAltList ( void )
{
	if ( *pIndexAltOrder < 0 ) return;
	pAltOrder = altOrder + *pIndexAltOrder; // rewind to beginning of lists
	*pIndexAltOrder = - 1; // nullify
}

static qboolean LOD_Increment ( size_t srcLength )
{
	if ( *pIndexAltOrder < 0 ) *pIndexAltOrder = ( short ) ( pAltOrder - altOrder ); // store alt order list index if not initialized
	if ( ( pAltOrder - altOrder ) + srcLength <= altOrderSize ) return true; // bound size of alt strip
	LOD_FatalMemory ( );
	return 0; // no room, must quit
}

static qboolean LOD_Terminate ( void )
{
	if ( LOD_Increment ( 1 ) ) { * ( pAltOrder++ ) = 0; return true; } // if no primitive just append zero
	return false;
}

// if new poly write count and first 2 verts
// if kick vert do not write anything
// if keep vert write last 2 verts
static int LOD_StoreAltList ( int * * lastVertices, int numVert, int * * pCount, qboolean isStrip, qboolean kickVerts )
{
	// precalc data length
	{
		size_t srcLength = *pCount ? 0 : 2 * 3 + 1; // update count // if new poly write new poly 
		if ( ! kickVerts ) srcLength += numVert * 3; // now just write "2" vertices for old or new poly
		if ( ! LOD_Increment ( srcLength ) ) return 0;
	}

	// create prim
	{
		int * * pPrim;
		if ( ! ( *pCount ) ) // if new poly write poly
		{
			*pCount = pAltOrder++;
			for ( pPrim = lastVertices ; pPrim < lastVertices + 2 ; ++pPrim, pAltOrder += 3 )
			{
				( ( long * ) ( lerped [ * ( *pPrim ) ] ) ) [ 2 ] = VT_KEEP;
				memcpy ( pAltOrder, *pPrim - 2, sizeof ( int ) * 3 );
			}
			* ( *pCount ) = isStrip ? 2 : - 2; // update count
		}
		else pPrim = lastVertices + 2;

		if ( kickVerts ) return 1;

		* ( *pCount ) += isStrip ? numVert : - numVert; // update count
		for ( ; numVert ; ++pPrim, --numVert ) // now just write "2" vertices for old or new poly
		{
			( ( long * ) ( lerped [ * ( *pPrim ) ] ) ) [ 2 ] = VT_KEEP;
			memcpy ( pAltOrder, *pPrim - 2, sizeof ( int ) * 3 );
			pAltOrder += 3;
		}
	}
	return - 1;
}

// update total prim area and determine kick verts
static qboolean LOD_KickPrim ( float * area )
{
	if ( area [ 1 ] > 0.0f ) area [ 0 ] += area [ 1 ];
	if ( area [ 2 ] > 0.0f ) area [ 0 ] += area [ 2 ];
	return area [ 0 ] <= LOD_resolution;
}

// store prims in alternate list
// determine if the detail is sufficient for stand alone polygons
// if not sufficient detail still preserve edge vertices
// if there is not a strip and in fact no listing has started need to nullify that listing
// overwrite final vertices of last poly
// if sufficient detail then just draw the polygon
static qboolean LOD_FinalizeAltList ( float * area, int * * lastVertices, int numVert, int * * pCount, qboolean isStrip )
{
	qboolean kickVerts = LOD_KickPrim ( area ); // determine if the detail is sufficient for stand alone polygons
	if ( kickVerts ) // if not sufficient detail still preserve edge vertices
	{
		if ( ! *pCount )
		{
			if ( *pIndexAltOrder < 0 ) { if ( LOD_Terminate ( ) ) return false; --pAltOrder; }; // if there is not a strip and in fact no listing has started need to nullify that listing
			return true; // if there is no list and this is kicked out do not draw
		}

		// overwrite final vertices of last poly
		{
			int cnt = **pCount; // cnt is [ ]
			if ( ! isStrip ) cnt = - cnt; // cnt is count
			if ( cnt < 2 + numVert )
			{
				if ( cnt < 3 ) pAltOrder = *pCount; // if this strip is being stored for real need to rewind it back
				return true; // if there is not enough space to stuff the vertices in then also skip them
			}
		}
		pAltOrder -= 3 * numVert; // if there are more than 2 verts then rewind
		lastVertices += 2; // write terminating verts

		for ( ; numVert ; --numVert, ++lastVertices, pAltOrder += 3 )
		{
			( ( long * ) ( lerped [ **lastVertices ] ) ) [ 2 ] = VT_KEEP;
			memcpy ( pAltOrder, *lastVertices - 2, sizeof ( int ) * 3 );
		}
		return true;
	}
	return LOD_StoreAltList ( lastVertices, numVert, pCount, isStrip, false ); // if sufficient detail then just draw the polygon
}

// lerp 2 frames verts based on motion
static void LerpFrameVerts ( dtrivertx_t *v, dtrivertx_t * ov, float * move, float * frontv, float * backv )
{

	const float frontv0 = frontv [ 0 ];
	const float frontv1 = frontv [ 1 ];
	const float frontv2 = frontv [ 2 ];
	const float backv0 = backv [ 0 ];
	const float backv1 = backv [ 1 ];
	const float backv2 = backv [ 2 ];
	const float move0 = move [ 0 ];
	const float move1 = move [ 1 ];
	const float move2 = move [ 2 ];

	int i;
	unsigned char * pV;
	unsigned char * pOV;
	float * pLerp = lerped [ 0 ];

	for ( i = 0 ; i < paliashdr->num_xyz && i < MAX_VERTS ; ++i, ++v, ++ov, pLerp += 3 )
//		if ( ( ( long * ) pLerp ) [ 2 ] != VT_REJECT && ( ( long * ) pLerp ) [ 2 ] != VT_OUT )
		{
			pV = v->v;
			pOV = ov->v;
			pLerp [ 0 ] = frontv0 * pV [ 0 ] + backv0 * pOV [ 0 ] + move0;
			pLerp [ 1 ] = frontv1 * pV [ 1 ] + backv1 * pOV [ 1 ] + move1;
			pLerp [ 2 ] = frontv2 * pV [ 2 ] + backv2 * pOV [ 2 ] + move2;
		}

	if ( currententity->flags & ( RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE ) )
	{
		float * normal;
		pLerp = lerped [ 0 ];
		for ( i = 0 ; i < paliashdr->num_xyz && i < MAX_VERTS ; ++i, pLerp += 3 )
//			if ( ( ( long * ) pLerp ) [ 2 ] != VT_REJECT && ( ( long * ) pLerp ) [ 2 ] != VT_OUT )
			{
				normal = r_avertexnormals [ frame->verts [ i ].lightnormalindex ]; // if gouraud, need to get normal
				pLerp [ 0 ] += normal [ 0 ] * POWERSUIT_SCALE;
				pLerp [ 1 ] += normal [ 1 ] * POWERSUIT_SCALE;
				pLerp [ 2 ] += normal [ 2 ] * POWERSUIT_SCALE;
			}
	}
}

// make move vector output angle vector quarternion
// make front vector from frame
// make back vector from old frame
// then lerp the verts based on move, back and front vectors
static void LOD_LerpVerts ( void )
{

	int i;
	vec3_t delta;
	vec3_t move;
	vec3_t frontv;
	vec3_t backv;

	VectorSubtract ( currententity->oldorigin, currententity->origin, delta ); // move should be the delta back to the previous frame * backlerp

	for ( i = 2 ; i >= 0 ; --i )
	{
		move	[ i ] = ( DotProduct ( globalQuarternion.q [ i ], delta ) + oldframe->translate [ i ] - frame->translate [ i ] ) * currententity->backlerp + frame->translate [ i ]; // move offset is translation of frame, and projected delta backlerped
		frontv	[ i ] = frame->		scale [ i ] - frame->scale [ i ] * currententity->backlerp; // front scale is backlerped
		backv	[ i ] = oldframe->	scale [ i ] * currententity->backlerp; // back scale is backlerped
	}

	for ( i = paliashdr->num_xyz - 1 ; i >= 0 ; -- i ) ( ( long * ) lerped [ i ] ) [ 2 ] = VT_NEED_PROJECT; // init to need projection
	if ( ! ( currententity->flags & RF_WEAPONMODEL ) ) // if not weapon model, need LOD
	{
		LOD_ConcatenateProjectionMatrix ( move ); LOD_Frame ( ); // LOD it
	}
	LerpFrameVerts ( frame->verts, oldframe->verts, move, frontv, backv ); // lerp remaining verts
}

static void LOD_RewindList ( void )
{
	pOrder =  ( int * ) ( ( byte * ) paliashdr + paliashdr->ofs_glcmds ); // orig prim list begin
	pIndexAltOrder = indexAltOrder; // whether prim list is replaced
	pAltOrder = altOrder; // replacement prim list
	pCombineOrder = combineOrder;
}

// precalculated dot products for quantized angles
#define SHADEDOT_QUANT 16
float	r_avertexnormal_dots [ SHADEDOT_QUANT ] [ 256 ] =
#include "anormtab.h"
;

// draw view space alias verts
static void LOD_DrawLocalVerts ( qboolean hasAlt )
{

	const qboolean gouraud = currententity->flags & ( RF_SHELL_RED | RF_SHELL_GREEN | RF_SHELL_BLUE ); // whether it is gouraud shaded

	TQuarternion light;
	float light0;
	float light1;
	float light2;
	float color [ 4 ];
	int mode;
	int * pGLData;

	AngleVectors ( currententity->angles, light.vec3 [ 0 ].v, light.vec3 [ 1 ].v, light.vec3 [ 2 ].v );
	LOD_RewindList ( );

	// whether or not it is translucent
	// color [ 3 ] is alpha, this way alpha does not need to be pushed onto the stack during every call of glColor
	color [ 3 ] = currententity->flags & RF_TRANSLUCENT ? currententity->alpha : 1.0f;  // do not use gouraud right after it is assigned to pair Pentium assembly UV
	if ( gouraud ) // now use gouraud
	{
		memcpy ( color, shadelight, sizeof ( float ) * 3 ); // if gouraud shaded then color is just shade light

#ifndef PROFILE_GL
		qglDisable ( GL_TEXTURE_2D ); // set up 2D texture if not gouraud shaded
#else
		qglDisable ( GL_TEXTURE_2D, "LOD_DrawLocalVerts" ); // set up 2D texture if not gouraud shaded
#endif

	}
	else
	{
		light0 = shadelight [ 0 ];
		light1 = shadelight [ 1 ];
		light2 = shadelight [ 2 ];
	}

	mode = 0; // we are in real mode
	pGLData = pOrder; // init GL data pointer to real data

	while ( 1 )
	{
		if ( ( mode > 0 ) && ( ! ( *pGLData ) ) ) // if the alternate list is termminated, switch back to real mode
		{
			mode = 0; 
			pGLData = pOrder;
		}
		if ( ! ( mode || *pGLData ) )
		{
			mode = - 1;
			pGLData = combineOrder;
		}
		if ( *pGLData ) // if we are done with small list we are done
		{

			// if in real mode and we are not terminated
			// we need to check to see if we need to switch to alternate mode
			// there there is an alternate entry then we need to switch to alternate mode
			if ( ( ! mode ) && hasAlt && ( pIndexAltOrder - indexAltOrder < indexAltOrderSize ) )
			{
				if ( *pIndexAltOrder >= 0 )
				{
					mode = 1; // switch to alternate mode
					count = *pGLData;
					if ( count < 0 ) count = - count;
					pOrder = pGLData + count * 3 + 1; // increment order past its real data
					pGLData = altOrder + ( * pIndexAltOrder ); // increment alternate list pointer to the next real list pointer
				}
				++pIndexAltOrder; // keep track of index
				if ( ! ( *pGLData ) ) continue; // and if alternate list is empty check again
			}
			count = * ( pGLData++ ); // get the vertex count and primitive type

			// fans or strips

#ifndef PROFILE_GL
			if ( count < 0 )
			{
				count = - count;
				qglBegin ( GL_TRIANGLE_FAN );
			}
			else qglBegin ( GL_TRIANGLE_STRIP );
#else
			if ( count < 0 )
			{
				count = - count;
				qglBegin ( GL_TRIANGLE_FAN, "LOD_DrawLocalVerts" );
			}
			else qglBegin ( GL_TRIANGLE_STRIP, "LOD_DrawLocalVerts" );
#endif

			do
			{
				register int index = pGLData [ 2 ]; // once we pull out the index this removes the stalling depedency using index

				 // texture coordinates come from the draw list
				if ( ! gouraud )
				{

#ifndef PROFILE_GL
					qglTexCoord2fv ( ( float * ) pGLData ); // less pushing, more efficient
#else
					qglTexCoord2fv ( ( float * ) pGLData, "LOD_DrawLocalVerts" ); // less pushing, more efficient
#endif

					{
						float * normal = r_avertexnormals [ frame->verts [ index ].lightnormalindex ]; 
						float normalLight =	normal [ 0 ] * light.q [ 0 ] [ 1 ] + // pipe f muls;
											normal [ 1 ] * light.q [ 1 ] [ 1 ] +
											normal [ 2 ] * light.q [ 2 ] [ 1 ] + 1.0f;
						color [ 0 ] = normalLight * light0; // pipe de-ref's and f ops
						color [ 1 ] = normalLight * light1;
						color [ 2 ] = normalLight * light2;
					}
				}

				pGLData += 3; // can increment order now that nothing is dependent on it

				// normals and vertexes come from the frame list

#ifndef PROFILE_GL
				qglColor4fv ( color ); // does not stall the FPU with fp muls and pushes
				qglVertex3fv ( lerped [ index ] );
#else
				qglColor4fv ( color, "LOD_DrawLocalVerts" ); // does not stall the FPU with fp muls and pushes
				qglVertex3fv ( lerped [ index ], "LOD_DrawLocalVerts" );
#endif

			}
			while ( --count );

#ifndef PROFILE_GL
			qglEnd ( ); // end of fan or strip
#else
			qglEnd ( "LOD_DrawLocalVerts" ); // end of fan or strip
#endif

			continue;
		}
		break;
	} 

#ifndef PROFILE_GL
	if ( gouraud ) qglEnable ( GL_TEXTURE_2D ); // disable 2D texture set up 
#else
	if ( gouraud ) qglEnable ( GL_TEXTURE_2D, "LOD_DrawLocalVerts" ); // disable 2D texture set up 
#endif

}

void GL_DrawAliasFrameLerp ( void )
{
	LOD_RewindList ( );
	LOD_LerpVerts ( ); // lerp vectors based only angle and frames
	LOD_DrawLocalVerts ( pAltOrder > altOrder );
}

static void VIS_DrawBegin ( void )
{
	// draw all the triangles

	if ( ( currententity->flags & RF_WEAPONMODEL ) && ( r_lefthand->value == 1.0f ) ) ViewSpaceProjection ( ); // draw in view space for LOD implementation

#ifndef PROFILE_GL
    qglPushMatrix ( );
#else
    qglPushMatrix ( "VIS_DrawBegin" );
#endif

	currententity->angles [ PITCH ] = - currententity->angles [ PITCH ];
	R_RotateForEntity ( currententity );
	currententity->angles [ PITCH ] = - currententity->angles [ PITCH ];

	// draw it
}

static void VIS_DrawEnd ( void )
{

#ifndef PROFILE_GL
	qglPopMatrix ( );
#else
	qglPopMatrix ( "VIS_DrawEnd" );
#endif

	if (  ( currententity->flags & RF_WEAPONMODEL ) && ( r_lefthand->value == 1.0f ) ) ViewSpaceProjectionPop ( ); // draw in view space for LOD implementation
}

// perform transform on a vector component, used for within a 3-loop
static void VIS_DrawAliasModel ( void ) // assume quarternions are set up
{

//	++drawCount;

	SetupLighting ( ); // get lighting information

	c_alias_polys += paliashdr->num_tris; // locate the proper data

	// draw all the triangles

	// set up shit

#ifndef PROFILE_GL
	if ( currententity->flags & RF_DEPTHHACK ) qglDepthRange ( gldepthmin, gldepthmin + 0.3 * ( gldepthmax-gldepthmin ) ); // hack the depth range to prevent view model from poking into walls
#else
	if ( currententity->flags & RF_DEPTHHACK ) qglDepthRange ( gldepthmin, gldepthmin + 0.3 * ( gldepthmax-gldepthmin ), "VIS_DrawAliasModel" ); // hack the depth range to prevent view model from poking into walls
#endif

	VIS_DrawBegin ( );

	// select skin
	GL_Bind	(
		( currententity->skin ? currententity->skin : // custom player skin
		( currententity->skinnum >= MAX_MD2SKINS || ! currentmodel->skins [ currententity->skinnum ] ?
		( * ( currentmodel->skins ) ? * ( currentmodel->skins ) : r_notexture ) : // fallback ...
		currentmodel->skins [ currententity->skinnum ] ) )->texnum
	);

	// draw it

	// set up more shit

#ifndef PROFILE_GL
	qglShadeModel ( GL_SMOOTH );
#else
	qglShadeModel ( GL_SMOOTH, "VIS_DrawAliasModel" );
#endif

	GL_TexEnv ( GL_MODULATE );

#ifndef PROFILE_GL
	if ( currententity->flags & RF_TRANSLUCENT ) qglEnable ( GL_BLEND );
#else
	if ( currententity->flags & RF_TRANSLUCENT ) qglEnable ( GL_BLEND, "VIS_DrawAliasModel" );
#endif

	GL_DrawAliasFrameLerp ( );

	// restore shit

	GL_TexEnv ( GL_REPLACE );

#ifndef PROFILE_GL
	qglShadeModel ( GL_FLAT );
#else
	qglShadeModel ( GL_FLAT, "VIS_DrawAliasModel" );
#endif

	VIS_DrawEnd ( );

#ifndef PROFILE_GL
	if ( currententity->flags & RF_TRANSLUCENT ) qglDisable ( GL_BLEND );
	if ( currententity->flags & RF_DEPTHHACK ) qglDepthRange ( gldepthmin, gldepthmax );
	qglColor4f ( 1, 1, 1, 1 );
#else
	if ( currententity->flags & RF_TRANSLUCENT ) qglDisable ( GL_BLEND, "VIS_DrawAliasModel" );
	if ( currententity->flags & RF_DEPTHHACK ) qglDepthRange ( gldepthmin, gldepthmax, "VIS_DrawAliasModel" );
	qglColor4f ( 1, 1, 1, 1, "VIS_DrawAliasModel" );
#endif

}

static void VIS_Setup ( void )
{
//	++entityCount;

	if ( ! r_lerpmodels->value ) currententity->backlerp = 0.0f;

	paliashdr = ( dmdl_t * ) currentmodel->extradata;

	// we have to set our frame pointers and quarternions
	SetupFrames ( );
	InitAliasQuarternions ( );
}

static qboolean VIS_Reject ( void ) // reject entity
{

	VIS_Setup ( );

	// see if the bounding box lets us trivially reject
	// either makes sure both frames are trivially rejected
	// or that that it is a non-lerping model
	if ( ! ( currententity->flags & RF_WEAPONMODEL ) )
	{
		if ( TrivialRejectFrame ( frame ) ) return currententity->backlerp == 0.0f || TrivialRejectFrame ( oldframe );
	}
	return false;
}

static void VIS_DrawWeapon ( void ) // check left hand then reject then draw
{
	if ( r_lefthand->value == 2.0f || VIS_Reject ( ) ) return; // if reject do not draw
	VIS_DrawAliasModel ( ); // can just draw because quarternions are already set up
}

// perform transform on a vector component, used for within a 3-loop
void R_DrawAliasModel ( entity_t * e ) { R_DrawAliasModelImmediate ( e ); }

void R_DrawAliasModelImmediate ( entity_t * e )
{
	if ( ! ( e->flags & RF_WEAPONMODEL ) ) // if not weapon
	{
		if ( VIS_Reject ( ) ) return; // if reject do not draw
		VIS_DrawAliasModel ( ); // can just draw because quarternions are already set up
		return;
	}
	VIS_DrawWeapon ( ); // draw weapon
}

void R_DrawAliasEnd ( void ) { }

void R_DrawAliasStart ( void ) { }

void R_TermAlias ( void ) { }
void R_InitAlias ( void ) { R_TermAlias ( ); }


