//-----------------------------------------------------------------------------
//
//  $Logfile:: /Source/cl_view.c                                              $
// $Revision:: 5                                                              $
//   $Author:: Burgermac                                                      $
//     $Date:: 2/5/02 7:37 PM                                                 $
//
// Copyright (C) 1998 by Ritual Entertainment, Inc.
// All rights reserved.
//
// This source is may not be distributed and/or modified without
// expressly written permission by Ritual Entertainment, Inc.
//
// DESCRIPTION:
// player rendering positioning
//

#include "client.h"

//=============
//
// development tools for weapons
//
int			gun_frame;
struct model_t	*gun_model;

//=============

cvar_t		*crosshair;
//cvar_t		*cl_testparticles;
//cvar_t		*cl_testentities;
//cvar_t		*cl_testlights;
//cvar_t		*cl_testblend;

cvar_t		*cl_stats;


int			r_numdlights;
dlight_t	r_dlights[MAX_DLIGHTS];

int			r_numentities;
entity_t	r_entities[MAX_ENTITIES];

int cl_status_total = 0;
int cl_status_current = 0;
const char *cl_status_string1 = "";
const char *cl_status_string2 = "";
extern int scr_draw_loading;

#define MAX_CHILDREN 32

typedef struct
{
	signed short parent_entity_num;
	signed short parent_real_entity_num;
	signed short parent_member_num;
	signed short entity_num;
	signed short real_entity_num;
	signed short num_children;
	signed short children[MAX_CHILDREN];
} entitymember_t;

int r_nummembers;
entitymember_t r_members[MAX_ENTITIES];

int r_numtriangles;
triangle_t *r_triangles[MAX_TRIANGLES];

int r_numparticles;
particle_t r_particles[MAX_PARTICLES];

lightstyle_t r_lightstyles[MAX_LIGHTSTYLES];

/*
====================
V_ClearScene

Specifies the model that will be used as the world
====================
*/
static void V_ClearScene (void)
{
	r_numdlights = 0;
	r_numentities = 0;
	r_numparticles = 0;
	r_nummembers = 0;
	r_numtriangles = 0;
}


/*
=====================
V_AddEntity

=====================
*/
static int cl_temp_entity_number = MAGIC_UNUSED_NUMBER+1;
void V_AddEntity (entity_t *ent)
{
	if (r_numentities >= MAX_ENTITIES)
		return;

	//
	// make sure all temporary entities are handled properly
	//
	if (ent->real_entity_number == MAGIC_UNUSED_NUMBER)
	{
		ent->real_entity_number = cl_temp_entity_number++;
		if (cl_temp_entity_number > 0x1f000000)
			cl_temp_entity_number = MAGIC_UNUSED_NUMBER+1;
	}

	r_entities[r_numentities++] = *ent;

	// if this entity has a parent, he is a family member
	if (ent->parent)
	{
		r_members[r_nummembers].parent_real_entity_num = ent->parent;
		r_members[r_nummembers].parent_entity_num = -1;
		r_members[r_nummembers].parent_member_num = -1;
		r_members[r_nummembers].real_entity_num = ent->real_entity_number;
		r_members[r_nummembers].num_children = 0;
		r_members[r_nummembers++].entity_num = r_numentities - 1;
	}
}

/*
=====================
V_AddTriangle

=====================
*/
void V_AddTriangle ( triangle_t * new_tri )
{
	if (r_numtriangles >= MAX_TRIANGLES)
		return;
	r_triangles[r_numtriangles++] = new_tri;
}
/*

=====================
V_AddParticle

=====================
*/
void V_AddParticle (vec3_t org, float color[4], byte flags, float size)
{
	int i;
	particle_t *p;

	if (r_numparticles >= MAX_PARTICLES)
		return;
	p = &r_particles[r_numparticles++];
	VectorCopy ( org, p->origin );
	for( i = 0; i < 4; i++ )
		p->color[i] = color[i];
	p->flags = flags;
	p->size = size;
}

/*
=====================
V_AddLight

=====================
*/
void V_AddLight (vec3_t org, float intensity, float r, float g, float b)
{
	dlight_t	*dl;
	float sign = intensity < 0 ? -1 : 1;

	if (r_numdlights >= MAX_DLIGHTS)
		return;
	dl = &r_dlights[r_numdlights++];
	VectorCopy (org, dl->origin);
	dl->intensity = intensity;
	dl->color[0] = sign * r;
	dl->color[1] = sign * g;
	dl->color[2] = sign * b;
}


/*
=====================
V_AddLightStyle

=====================
*/
void V_AddLightStyle (int style, float r, float g, float b, float a)
{
	lightstyle_t	*ls;

	if (style < 0 || style > MAX_LIGHTSTYLES)
		Com_Error (ERR_DROP, "Bad light style %i", style);
	ls = &r_lightstyles[style];

	ls->white = r+g+b;
	ls->rgb[0] = r;
	ls->rgb[1] = g;
	ls->rgb[2] = b;
	ls->rgb[3] = a;
	ls->rgbvalue =
		( ((int)(r * 127.5) & 0xff ) << 16) +
		( ((int)(g * 127.5) & 0xff ) <<  8) +
		(  (int)(b * 127.5) & 0xff );
}

//===================================================================

/*
=================
CL_PrepRefresh

Call before entering a new level, or after changing dlls
=================
*/
void CL_PrepRefresh (void)
{
	char		mapname[32];
	int			i;
	char		name[MAX_QPATH];
	float		rotate;
	vec3_t		axis;
	const char	*s;
#ifdef SIN_CHECKSUM
	unsigned	server_checksum, client_checksum;
#endif
	char		model_name[MAX_QPATH], token[MAX_QPATH];
	size_t		sz;

	// Clear out any fades
	CONSOLE_ClearFade_f();
	CONSOLE_ClearFadeExtra();

	if (!cl.configstrings[CS_MODELS+1][0])
		return;		// no map loaded

	SCR_BeginLoadingPlaque();

	SCR_AddDirtyPoint (0, 0);
	SCR_AddDirtyPoint (viddef.width-1, viddef.height-1);

	// let the render dll load the map
	strcpy (mapname, cl.configstrings[CS_MODELS+1] + 5);	// skip "maps/"
	mapname[strlen(mapname)-4] = 0;		// cut off ".bsp"

	// register models, pics, and skins
	Com_Printf ("Map: %s\r", mapname);
	SCR_UpdateScreen ();
	Com_UpdateStatusString("Starting Sin Graphics Engine" );
	Com_UpdateStatus( cl_current++, cl_total, "Initializing" );
	re.BeginRegistration (mapname, &map_surfaces[0]);
	Com_Printf ("                                     \r");

	// precache status bar pics
	Com_Printf ("pics\r");
	SCR_UpdateScreen ();
	SCR_TouchPics ();
	Com_Printf ("                                     \r");

	Com_UpdateStatus( cl_current++, cl_total, "Registering TempEnts" );

	CL_RegisterTEntModels ();

	memset(cl.model_clip, 0, sizeof(cl.model_clip));

	Com_UpdateStatus( cl_current++, cl_total, "Loading Models" );

	for (i=1 ; i<MAX_MODELS && cl.configstrings[CS_MODELS+i][0] ; i++)
	{
		strcpy (name, cl.configstrings[CS_MODELS+i]);
		name[37] = 0;	// never go beyond one line
		if (name[0] != '*')
			Com_Printf ("%s\r", name);
		Sys_SendKeyEvents ();	// pump message loop
		s = cl.configstrings[CS_MODELS+i];
		strcpy( model_name, COM_Parse( &s ) );
		strcpy( token, COM_Parse( &s ) );

#ifdef SIN_CHECKSUM
		server_checksum = (unsigned)COM_ParseHex( token );
#endif

		cl.model_draw[i] = re.RegisterModel ( model_name );
		if (name[0] == '*')
			cl.model_clip[i] = CM_InlineModel ( model_name );
		else
			cl.model_clip[i] = NULL;
		if (name[0] != '*')
			Com_Printf ("                                     \r");

		// Only update every 20 objects
		if ( !( i % 25 ) ) /* FS: Every 25 */
		{
			if (name[0] == '*')
				Com_UpdateStatus( cl_current++, cl_total, va( "Loading Model #%s", &model_name[1] ) );
			else
				Com_UpdateStatus( cl_current++, cl_total, va( "Loading %s", model_name ) );
		}
		else
		{
			cl_current++;
		}

		//
		// process any initialization commands
		//
		sz = strlen(model_name);
		if (sz > 3 && !Q_stricmp(&model_name[sz - 3], "def"))
		{
			int handle;

			handle = re.SINMDL_GetDefHandle( cl.model_draw[i] );

			if ( handle >= 0 )
			{
				sinmdl_cmd_t * cmds;

#ifdef SIN_CHECKSUM
				// Check the checksums
				client_checksum = SINMDL_Checksum( handle );

				if ( client_checksum != server_checksum )
				{
					Com_Printf( "Checksum invalid for model index %i\n", i );
					Com_Printf( "client checksum = %x, server checksum = %x\n", client_checksum, server_checksum );
					Com_Error ( ERR_DROP,"CL_ParseConfigString: Model %s differs from server", model_name );
				}
#endif

				cmds = SINMDL_InitCommands_Client( handle );
				if ( cmds )
				{
					int j;
					for ( j = 0; j < cmds->num_cmds; j++ )
					{
						CL_CMDS_ProcessCommands( 0, handle, cmds->cmds[j].num_args, (const char **)cmds->cmds[j].args );
					}
				}
			}
		}
	}

	Com_UpdateStatus( cl_current++, cl_total, "Loading Images" );

	Com_Printf ("images\r");
	SCR_UpdateScreen ();
	for (i=1 ; i<MAX_IMAGES && cl.configstrings[CS_IMAGES+i][0] ; i++)
	{
		Com_UpdateStatus( cl_current++, cl_total, va( "Loading %s", cl.configstrings[CS_IMAGES+i] ) );
		cl.image_precache[i] = re.RegisterPic (cl.configstrings[CS_IMAGES+i]);
		Sys_SendKeyEvents ();	// pump message loop
	}
	Com_Printf ("                                     \r");

	Com_UpdateStatus( cl_current++, cl_total, "Loading Clients" );

	for (i=0; i<MAX_CLIENTS; i++)
	{
		if (!cl.configstrings[CS_PLAYERSKINS+i][0])
			continue;
		Com_Printf ( "client %i\r", i );
		SCR_UpdateScreen ();
		Sys_SendKeyEvents (); // pump message loop
		CL_ParseClientinfo ( i );
		if(cl.configstrings[CS_BIKESKINS+i][0])
			CL_ParseBikeinfo ( i );
		Com_Printf ( "                                     \r" );
	}

	CL_LoadClientinfo (&cl.baseclientinfo, "unnamed\\pl_blade\\blade_base");
	CL_BuildSurfaceList ();

	Com_UpdateStatus( cl_current++, cl_total, "Loading Sky" );

	// set sky textures and speed
	Com_Printf ("sky\r");
	SCR_UpdateScreen ();
	rotate = Sinatof (cl.configstrings[CS_SKYROTATE]);
	sscanf (cl.configstrings[CS_SKYAXIS], "%f %f %f",
		&axis[0], &axis[1], &axis[2]);
	re.SetSky (cl.configstrings[CS_SKY], rotate, axis);
	Com_Printf ("                                     \r");

	// the renderer can now free unneeded stuff
	re.EndRegistration ();

	// clear any lines of console text
	Con_ClearNotify ();

	SCR_UpdateScreen ();
	cl.refresh_prepped = true;
	cl.force_refdef = true;	// make sure we have a valid refdef

	// start the cd track
	CDAudio_Play (atoi(cl.configstrings[CS_CDTRACK]), true);

	SCR_EndLoadingPlaque();
}

/*
====================
CalcFov
====================
*/
float CalcFov (float fov_x, float width, float height)
{
	float a;
	float x;

	if (fov_x < 1 || fov_x > 179) {
		Com_Error (ERR_DROP, "Bad fov: %f", fov_x);
	}
	x = width/tan(fov_x/360*M_PI);

	a = atan (height/x);

	a = a*360/M_PI;

	return a;
}

//============================================================================

// gun frame debugging functions
static void V_Gun_Next_f (void)
{
	gun_frame++;
	Com_Printf ("frame %i\n", gun_frame);
}

static void V_Gun_Prev_f (void)
{
	gun_frame--;
	if (gun_frame < 0)
		gun_frame = 0;
	Com_Printf ("frame %i\n", gun_frame);
}

static void V_Gun_Model_f (void)
{
	char	name[MAX_QPATH];

	if (Cmd_Argc() != 2)
	{
		gun_model = NULL;
		return;
	}
	Com_sprintf (name, sizeof(name), "models/%s", Cmd_Argv(1));
	gun_model = re.RegisterModel (name);
}


//============================================================================

/*
=================
CL_UpdateBarrelPosition
=================
*/
#if 0
static vec3_t cl_barrel_position = {0, 0, 0};
#endif

void CL_UpdateBarrelPosition (entity_t *ent)
{
#if 0
	int defhandle;
	vec3_t trans[3];
	vec3_t orient;
	vec3_t offset;
	int groupindex;
	int tri_num;
	int anim;

	if ( !ent || !ent->model )
		return;

	defhandle = re.SINMDL_GetDefHandle( ent->model );
	if (defhandle < 0)
		return;

	anim = SINMDL_Anim_Random( defhandle, "fire" );
	if ( anim < 0 )
		return;
	// get the bone information
	if ( SINMDL_GetBoneInfo( defhandle, "barrel", &groupindex, &tri_num, orient ) )
	{
		if ( SINMDL_GetBoneTransform( defhandle, groupindex, tri_num, orient, anim, 0, ent->scale, trans, offset ) )
		{
			//
			// we scale the pos by 0.3 because of RF_DEPTHHACK
			//
			VectorScale( offset, 0.3f, cl_barrel_position );
		}
	}
#endif
}

/*
=================
CL_DrawAdvancedCrosshair
=================
*/
static void CL_DrawAdvancedCrosshair( const char * pic, int width, int height )
{
	vec3_t pos, end;
	trace_t trace;
	char picname[128];

	VectorCopy( cl.refdef.vieworg, pos );
	AngleVectors ( cl.refdef.viewangles, cl.v_forward, cl.v_right, cl.v_up );
	VectorMA( pos, 8192, cl.v_forward, end );
	trace = CL_GenericTrace ( pos, vec3_origin, vec3_origin, end, MASK_SHOT, true, cl.playernum+1 );

	if (trace.intersect.valid)
		sprintf( picname, "%s_on", pic );
	else
		sprintf( picname, "%s_off", pic );

	re.DrawPic ( scr_vrect.x + ((scr_vrect.width - width)>>1),
		     scr_vrect.y + ((scr_vrect.height-height)>>1), picname );
}

/*
=================
SCR_DrawCrosshair
=================
*/
static void SCR_DrawCrosshair (void)
{
	// Don't draw if stats are off
	if ( !(cl.frame.playerstate.stats[STAT_LAYOUTS] & DRAW_STATS ) )
		return;

	if (!crosshair->value)
		return;

	if ( crosshair->modified )
	{
		crosshair->modified = false;
		SCR_TouchPics ();
	}

	if ((cl.frame.playerstate.pmove.pm_type == PM_ZOOM) || (cl.frame.playerstate.pmove.pm_type == PM_INVEHICLE_ZOOM))
	{
		if ( zoom_crosshair_pic[0] )
			CL_DrawAdvancedCrosshair( zoom_crosshair_pic, zoom_crosshair_width, zoom_crosshair_height );
	}
	else
	{
		if ( crosshair_pic[0] )
			CL_DrawAdvancedCrosshair( crosshair_pic, crosshair_width, crosshair_height );
	}
}

/*
==================
V_SetupBone
==================
*/
static void V_SetupBone( entity_t * parent, entity_t * current )
{
	int group, i;
	int tri_num;
	float trans[3][3];
	float newtrans[3][3];
	float oldtrans[3][3];
	float temptrans[3][3];
	float ang[3];
	float oldang[3];
	vec3_t pos;
	vec3_t newpos;
	vec3_t neworigin;
	int defhandle;
	// 2015 code
	float oldquat[4];
	float newquat[4];
	float tempquat[4];
	qboolean do2015attach; // set to true when using corrected attach method

	defhandle = re.SINMDL_GetDefHandle( parent->model );
	if (defhandle < 0)
	{
		Com_DPrintf(DEVELOPER_MSG_CLIENT, "V_SetupBone: trying to setup bone of non-sinmdl model.\n");
		return;
	}
	current->scale *= parent->scale;
	group = current->bone.group_num;
	tri_num = current->bone.tri_num;

// 2015 code
// added support for a fake "origin" bone
// that works like the modelindexes in Quake2
	if ( tri_num == BONE_ORIGIN )
	{
		VectorCopy( parent->oldorigin, current->oldorigin );
		VectorCopy( parent->origin, current->origin );
		VectorCopy( parent->angles, current->angles );

		for (i=0; i<3; i++)
		{
			current->origin[i] = current->oldorigin[i] = current->oldorigin[i] + cl.lerpfrac *
									 (current->origin[i] - current->oldorigin[i]);
		}
		AnglesToMat( current->angles, current->mat );

		return;
	}

	if ( tri_num & BONE_2015ATTACH )
	{
		do2015attach = true;
	}
	else
	{
		do2015attach = false;
	}

	//
	// calculate old origin
	//
	if (SINMDL_GetBoneTransform( defhandle, group, tri_num, current->bone.orientation, parent->oldanim,
									parent->oldframe, parent->scale, trans, pos ))
	{
		// get the pos in the parents transform space
		MatrixTransformVector( pos, parent->mat, neworigin );
		// concat the current matrix, with the bone's orientation
		R_ConcatRotations( current->mat, trans, temptrans );
		// concat with the parent's matrix
		R_ConcatRotations( temptrans, parent->mat, oldtrans );
		// get the current oldorigin in the current transform space
		MatrixTransformVector( current->oldorigin, oldtrans, newpos );
		// add the transformed origin and neworigin
		VectorAdd( neworigin, newpos, pos );
		// add the parent'soldorigin to the newpos
		VectorAdd( pos, parent->oldorigin, current->oldorigin );
		//
		// calculate current origin
		//
		if (SINMDL_GetBoneTransform( defhandle, group, tri_num, current->bone.orientation, parent->anim,
										parent->frame, parent->scale, trans, pos ))
		{
			// get the pos in the parents transform space
			MatrixTransformVector( pos, parent->mat, neworigin );
			// concat the current matrix, with the bone's orientation
			R_ConcatRotations( current->mat, trans, temptrans );
			// concat with the parent's matrix
			R_ConcatRotations( temptrans, parent->mat, newtrans );
			// get the current origin in the current transform space
			MatrixTransformVector( current->origin, newtrans, newpos );
			// add the transformed origin and neworigin
			VectorAdd( neworigin, newpos, pos );
			// add the parent'soldorigin to the newpos
			VectorAdd( pos, parent->origin, current->origin );
		}
		else
		{
			VectorCopy( current->oldorigin, current->origin );
		}
	}
	else
	{
		VectorCopy( parent->origin, current->origin );
		VectorCopy( parent->oldorigin, current->oldorigin );
	}

	// 2015 code
	if ( do2015attach )
	{
		for (i=0; i<3; i++)
		{
			current->origin[i] = current->oldorigin[i] = current->oldorigin[i] + cl.lerpfrac *
									 (current->origin[i] - current->oldorigin[i]);
		}
		MatToQuat( oldtrans, oldquat );
		MatToQuat( newtrans, newquat );
		SlerpQuaternion( oldquat, newquat, cl.lerpfrac, tempquat );
		QuatToMat( tempquat, current->mat );
		MatrixToEulerAngles( current->mat, current->angles );
	}
	else
	{
		MatrixToEulerAngles( oldtrans, oldang );
		MatrixToEulerAngles( newtrans, ang );

		for (i=0; i<3; i++)
		{
			current->angles[i] = LerpAngle ( oldang[i], ang[i], cl.lerpfrac );
			current->origin[i] = current->oldorigin[i] = current->oldorigin[i] + cl.lerpfrac *
									 (current->origin[i] - current->oldorigin[i]);
		}
		AnglesToMat( current->angles, current->mat );
	}
}

/*
==================
V_RecurseChildren
==================
*/
static void V_RecurseChildren( int parent_num, int current_num )
{
	int j;
	entitymember_t *parent;
	entitymember_t *current;
	entity_t *parent_ent;
	entity_t *current_ent;

	parent = &r_members[parent_num];
	current = &r_members[current_num];
	parent_ent = &cl.refdef.entities[parent->entity_num];
	current_ent = &cl.refdef.entities[current->entity_num];
	//
	// setup this bone
	//
	V_SetupBone( parent_ent, current_ent );
	//
	// process all my children
	//
	for (j=0; j<current->num_children; j++)
		V_RecurseChildren( current_num, current->children[j] );
}

/*
==================
V_FindEntity
==================
*/
entity_t * V_FindEntity( int realnum )
{
	int k;
	//
	// find the parent in the entity list
	//
	for (k=0; k<cl.refdef.num_entities; k++)
	{
		if (cl.refdef.entities[k].real_entity_number == realnum)
			return &cl.refdef.entities[k];
	}
	return NULL;
}

/*
==================
V_ResolveOffspring

links all children to parents
==================
*/
void V_ResolveOffspring( void )
{
	int i, j, k, parent_real_entity_num;
	entitymember_t *parent;
	int nummembers;

	//
	// early exit clause
	//
	if (!r_nummembers)
	{
		qsort( cl.refdef.entities, cl.refdef.num_entities, sizeof( cl.refdef.entities[0] ), entitycmpfnc );
		return;
	}
	//
	// gather and add all the parents as family members if necessary
	//
	nummembers = r_nummembers;
	for ( i=0; i < nummembers; i++ )
	{
		parent_real_entity_num = r_members[i].parent_real_entity_num;
		for ( j=0; j < r_nummembers; j++ )
			if (parent_real_entity_num == r_members[j].real_entity_num)
				break;
		if (j == r_nummembers)
		{
			// new member of the family
			//
			// find the parent in the entity list
			//
			for (k=0; k<cl.refdef.num_entities; k++)
			{
				if (cl.refdef.entities[k].real_entity_number == parent_real_entity_num)
					break;
			}
			if (k == cl.refdef.num_entities)
			{
				//Com_Printf("V_ResolveOffspring: failed to find parent.\n");
				r_members[i].parent_entity_num = -1;
				continue;
			}
			r_members[r_nummembers].parent_real_entity_num = 0;
			r_members[r_nummembers].parent_entity_num = -1;
			r_members[r_nummembers].parent_member_num = -1;
			r_members[r_nummembers].real_entity_num = cl.refdef.entities[k].real_entity_number;
			r_members[r_nummembers].num_children = 0;
			r_members[r_nummembers++].entity_num = k;
			//
			// make the children's parent number point to the one in the list
			//
			r_members[i].parent_entity_num = k;
			r_members[i].parent_member_num = j;
		}
		else
		{
			r_members[i].parent_member_num = j;
			r_members[i].parent_entity_num = r_members[j].entity_num;
		}
	}
	//
	// now we have all the parents and children,
	// add each child to its parent
	//
	for ( i=0; i < r_nummembers; i++ )
	{
		if (r_members[i].parent_member_num >= 0 )
		{
			parent = &r_members[r_members[i].parent_member_num];
			if (parent->num_children < MAX_CHILDREN)
			{
				parent->children[parent->num_children++] = i;
			}
			else
			{
				// we can't draw this child or any of its children
				// FIXME need to kill its children the right way
				cl.refdef.entities[r_members[i].entity_num].flags |= RF_DONTDRAW;
				for ( j = 0; j < r_members[i].num_children; j++ )
				{
					cl.refdef.entities[r_members[r_members[i].children[j]].entity_num].flags |= RF_DONTDRAW;
				}
				Com_DPrintf(DEVELOPER_MSG_CLIENT, "V_ResolveOffspring: too many children.\n");
			}
		}
	}
	//
	// now setup all the bone info for each child
	//
	for ( i=0; i < r_nummembers; i++ )
	{
		parent = &r_members[i];
		//
		// only process those parents who are parentless
		//
		if (!parent->parent_real_entity_num)
		{
			for (j=0; j<parent->num_children; j++)
				V_RecurseChildren( i, parent->children[j] );
		}
	}
	qsort( cl.refdef.entities, cl.refdef.num_entities, sizeof( cl.refdef.entities[0] ), entitycmpfnc );
}

/*
==================
V_RenderView

==================
*/
void V_RenderView( float stereo_separation )
{
	if (cls.state != ca_active)
		return;

	if (!cl.refresh_prepped)
		return;			// still loading

	if (cl.map_sequence_number != map_sequence_number)
	{
		return;
	}

	if (CL_IsADemoWithTimeDemo()) /* FS: timedemo only in demos, please */
	{
		if (!cl.timedemo_start)
			cl.timedemo_start = Sys_Milliseconds ();
		cl.timedemo_frames++;
	}

	// an invalid frame will just use the exact previous refdef
	// we can't use the old frame if the video mode has changed, though...
	if ( cl.frame.valid && (cl.force_refdef || !cl_paused->value) )
	{
		cl.force_refdef = false;

		V_ClearScene ();

		// build a refresh entity list and calc cl.sim*
		// this also calls CL_CalcViewValues which loads
		// v_forward, etc.
		CL_AddEntities ();

		// offset vieworg appropriately if we're doing stereo separation
		if ( stereo_separation != 0 )
		{
			vec3_t tmp;

			VectorScale( cl.v_right, stereo_separation, tmp );
			VectorAdd( cl.refdef.vieworg, tmp, cl.refdef.vieworg );
		}

		// never let it sit exactly on a node line, because a water plane can
		// dissapear when viewed with the eye exactly on it.
		// the server protocol only specifies to 1/8 pixel, so add 1/16 in each axis
		cl.refdef.vieworg[0] += 1.0/16;
		cl.refdef.vieworg[1] += 1.0/16;
		cl.refdef.vieworg[2] += 1.0/16;

		cl.refdef.x = scr_vrect.x;
		cl.refdef.y = scr_vrect.y;
		cl.refdef.width = scr_vrect.width;
		cl.refdef.height = scr_vrect.height;
		// Knightmare- adjust fov for wide aspect ratio
		if (cl_widescreen_fov->value)
		{
			float aspectRatio = (float)cl.refdef.width/(float)cl.refdef.height;
			// changed to improved algorithm by Dopefish
			if (aspectRatio > STANDARD_ASPECT_RATIO)
				cl.refdef.fov_x = RAD2DEG( 2 * atan( (aspectRatio/ STANDARD_ASPECT_RATIO) * tan(DEG2RAD(cl.refdef.fov_x) * 0.5) ) );
			//	cl.refdef.fov_x *= (1 + (0.5 * (aspectRatio / STANDARD_ASPECT_RATIO - 1)));
			cl.refdef.fov_x = min(cl.refdef.fov_x, 160);
		}

		if (cl.refdef.fov_x<1.0f) {		/* This hack prevents a crash bug */
			cl.refdef.fov_x = 90.0f;
		}
		cl.refdef.fov_y = CalcFov (cl.refdef.fov_x, cl.refdef.width, cl.refdef.height);
		cl.refdef.time = cl.time*0.001;

		cl.refdef.areabits = cl.frame.areabits;

		if (!cl_add_entities->value)
			r_numentities = 0;
		if (!cl_add_particles->value)
			r_numparticles = 0;
		if (!cl_add_lights->value)
			r_numdlights = 0;
		if (!cl_add_blend->value)
		{
			VectorClear (cl.refdef.blend);
		}
		if (!cl_add_triangles->value)
			r_numtriangles = 0;

		cl.refdef.num_entities = r_numentities;
		cl.refdef.entities = r_entities;
		cl.refdef.num_particles = r_numparticles;
		cl.refdef.particles = r_particles;
		cl.refdef.num_dlights = r_numdlights;
		cl.refdef.dlights = r_dlights;
		cl.refdef.lightstyles = r_lightstyles;
		cl.refdef.num_triangles = r_numtriangles;
		cl.refdef.triangles = r_triangles;

		cl.refdef.rdflags = cl.frame.playerstate.rdflags;

		V_ResolveOffspring();
	}

	if (cl_paused->value)
		cl.refdef.paused = true;
	else
		cl.refdef.paused = false;

	re.RenderFrame (&cl.refdef);
	if (cl_stats->value)
		Com_Printf ("ent:%i  lt:%i  part:%i tri:%i\n", r_numentities, r_numdlights, r_numparticles, r_numtriangles);
	if ( log_stats->value && ( log_stats_file != 0 ) )
		fprintf( log_stats_file, "%i,%i,%i,%i,",r_numentities, r_numdlights, r_numparticles, r_numtriangles);

	SCR_AddDirtyPoint (scr_vrect.x, scr_vrect.y);
	SCR_AddDirtyPoint (scr_vrect.x+scr_vrect.width-1,
			   scr_vrect.y+scr_vrect.height-1);

	if ( cl.frame.playerstate.pmove.pm_type != PM_LOCKVIEW )
	{
		SCR_DrawCrosshair ();
	}
}


/*
=============
V_Viewpos_f
=============
*/
static void V_Viewpos_f (void)
{
	Com_Printf ("(%i %i %i) : %i\n", (int)cl.refdef.vieworg[0],
			(int)cl.refdef.vieworg[1], (int)cl.refdef.vieworg[2],
			(int)cl.refdef.viewangles[YAW]);
}

/*
=============
V_Init
=============
*/
void V_Init (void)
{
	Cmd_AddCommand ("gunnext", V_Gun_Next_f);
	Cmd_AddCommand ("gunprev", V_Gun_Prev_f);
	Cmd_AddCommand ("gunmodel", V_Gun_Model_f);

	Cmd_AddCommand ("viewpos", V_Viewpos_f);

	crosshair = Cvar_Get ("crosshair", "0", CVAR_ARCHIVE);

//	cl_testblend = Cvar_Get ("cl_testblend", "0", 0);
//	cl_testparticles = Cvar_Get ("cl_testparticles", "0", 0);
//	cl_testentities = Cvar_Get ("cl_testentities", "0", 0);
//	cl_testlights = Cvar_Get ("cl_testlights", "0", 0);

	cl_stats = Cvar_Get ("cl_stats", "0", 0);
}
