/*
Copyright (C) 2002-2003 Victor Luchits

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

#include "cg_local.h"

#ifdef DEMOCAM
#include "cg_democams.h" // [DEMOCAM] -- PLX
#endif

// cg_view.c -- player rendering positioning


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

//================
//CG_UpdateCammeraEffects
//================
cvar_t *cg_demo_sfx_rotateY;
void CG_UpdateCammeraEffects( void )
{
	if( !cg_demo_sfx_rotateY )
		cg_demo_sfx_rotateY = trap_Cvar_Get( "cg_demosfx_rotateY", "0.0", 0 );

	if( cg_demo_sfx_rotateY->value )
		trap_Cvar_SetValue( "cg_thirdPersonAngle", cg_thirdPersonAngle->value + cg_demo_sfx_rotateY->value * cg.frameTime );
}

//================
//CG_ThirdPerson_CameraUpdate
//================
void CG_ThirdPerson_CameraUpdate( void )
{
	float	dist, f, r;
	vec3_t	dest, stop;
	vec3_t	chase_dest;
	trace_t	trace;
	vec3_t	mins = { -4, -4, -4 };
	vec3_t	maxs = { 4, 4, 4 };

	if( cg.demoPlaying )
		CG_UpdateCammeraEffects();

	// calc exact destination
	VectorCopy( cg.refdef.vieworg, chase_dest );
	r = DEG2RAD( cg_thirdPersonAngle->value );
	f = -cos( r );
	r = -sin( r );
	VectorMA( chase_dest, cg_thirdPersonRange->value * f, cg.v_forward, chase_dest );
	VectorMA( chase_dest, cg_thirdPersonRange->value * r, cg.v_right, chase_dest );
	chase_dest[2] += 8;

	// find the spot the player is looking at
	VectorMA( cg.refdef.vieworg, 512, cg.v_forward, dest );
	CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, dest, cgs.playerNum + 1, MASK_SOLID );

	// calculate pitch to look at the same spot from camera
	VectorSubtract( trace.endpos, cg.refdef.vieworg, stop );
	dist = sqrt( stop[0] * stop[0] + stop[1] * stop[1] );
	if( dist < 1 )
		dist = 1;
	cg.refdef.viewangles[PITCH] = RAD2DEG( -atan2(stop[2], dist) );
	cg.refdef.viewangles[YAW] -= cg_thirdPersonAngle->value;
	AngleVectors( cg.refdef.viewangles, cg.v_forward, cg.v_right, cg.v_up );

	// move towards destination
	CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, chase_dest, cgs.playerNum + 1, MASK_SOLID );

	if( trace.fraction != 1.0 ) {
		VectorCopy( trace.endpos, stop );
		stop[2] += ( 1.0 - trace.fraction ) * 32;
		CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, stop, cgs.playerNum + 1, MASK_SOLID );
		VectorCopy( trace.endpos, chase_dest );
	}

	VectorCopy( chase_dest, cg.refdef.vieworg );
}

//================
//CG_CalcViewBob
//================
void CG_CalcViewBob( void )
{
	float bobMove, bobTime;

	if( cg.thirdPerson )
		return;

	//
	// calculate speed and cycle to be used for
	// all cyclic walking effects
	//
	cg.xyspeed = sqrt( cg.predictedVelocity[0]*cg.predictedVelocity[0] + cg.predictedVelocity[1]*cg.predictedVelocity[1] );
	
	bobMove = 0;
	if( cg.xyspeed < 5 )
		cg.oldBobTime = 0;			// start at beginning of cycle again
	else if( cg.player.viewContents & MASK_WATER )
		bobMove = cg.frameTime * cg_bobSpeed->value * 0.3;
	else if( cg.frame.playerState.pmove.pm_flags & PMF_DUCKED )
		bobMove = cg.frameTime * cg_bobSpeed->value * 0.6;
	else if( cg.player.isOnGround )
		bobMove = cg.frameTime * cg_bobSpeed->value;

	bobTime = (cg.oldBobTime += bobMove);

	cg.bobCycle = (int)bobTime;
	cg.bobFracSin = fabs(sin(bobTime*M_PI));
}

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

//==================
//CG_RenderFlags
//==================
int CG_RenderFlags( void )
{
	int rdflags, contents;

	rdflags = 0;

	contents = CG_PointContents( cg.refdef.vieworg );
	if( contents & MASK_WATER )
		rdflags |= RDF_UNDERWATER;
	else
		rdflags &= ~RDF_UNDERWATER;

	return rdflags;
}

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


//======================================================================
//					ChaseHack (In Eyes Chasecam)
//======================================================================

cg_chasecam_t	chaseCam;

void CG_ChasePrev( void )
{
	if( !chaseCam.mode < 0 || chaseCam.mode >= CAM_MODES )
		return;

	if( cg.demoPlaying )
		return;

	trap_Cmd_ExecuteText( EXEC_NOW, "chaseprev" );
}
void CG_ChaseNext( void )
{
	if( chaseCam.mode < 0 || chaseCam.mode >= CAM_MODES )
		return;

	if( cg.demoPlaying )
		return;

	trap_Cmd_ExecuteText( EXEC_NOW, "chasenext" );
}

//===============
//CG_PlayerPOV - IN-EYES chasecam
//===============
void CG_PlayerPOV( player_state_t *ps )
{	
	usercmd_t	cmd;

	if( cg.demoPlaying ) {
		cg.chasedNum = ps->POVnum - 1;

		trap_NET_GetUserCmd( trap_NET_GetCurrentUserCmdNum() - 1, &cmd );
		if( (cmd.buttons & BUTTON_ATTACK) && cg.time > chaseCam.cmd_mode_delay )
		{
			chaseCam.mode = (chaseCam.mode != CAM_THIRDPERSON);
			chaseCam.cmd_mode_delay = cg.time + 200;
		}
	}
	else if( ps->pmove.pm_type == PM_CHASECAM ) { 
		cg.chasedNum = ps->POVnum - 1;//minus one for parallel with cgs.playerNum

		trap_NET_GetUserCmd( trap_NET_GetCurrentUserCmdNum() - 1, &cmd );
		if( (cmd.buttons & BUTTON_ATTACK) && cg.time > chaseCam.cmd_mode_delay )
		{
			chaseCam.mode++;
			if( chaseCam.mode >= CAM_MODES ) { // if exceedes the cycle, start free fly
				trap_Cmd_ExecuteText( EXEC_NOW, "camswitch" );
				chaseCam.mode = 0; // smallest, to start the new cycle
			}
			chaseCam.cmd_mode_delay = cg.time + 200;
		}
	} 
	else if( ps->pmove.pm_type == PM_SPECTATOR ) { 
		cg.chasedNum = cgs.playerNum;
		trap_NET_GetUserCmd( trap_NET_GetCurrentUserCmdNum() - 1, &cmd );
		if( (cmd.buttons & BUTTON_ATTACK) && cg.time > chaseCam.cmd_mode_delay )
		{
			trap_Cmd_ExecuteText( EXEC_NOW, "camswitch" );
			chaseCam.cmd_mode_delay = cg.time + 200;
		}
	} 
	else { 
		cg.chasedNum = cgs.playerNum;
		chaseCam.mode = qfalse;
	}

	// set up third-person
	if( chaseCam.mode == CAM_THIRDPERSON && (ps->pmove.pm_type == PM_CHASECAM || cg.demoPlaying) )
		cg.thirdPerson = qtrue;
	else if( ps->pmove.pm_type == PM_SPECTATOR || ps->pmove.pm_type == PM_GIB 
		|| ps->pmove.pm_type == PM_FREEZE )
		cg.thirdPerson = qfalse;
	else
		cg.thirdPerson = ( cg_thirdPerson->integer != 0 );

	//determine if we have to draw the view weapon
	if( ps->pmove.pm_type == PM_SPECTATOR ) {
		vweap.active = qfalse;
	}
	else if( cg.frame.match.state >= MATCH_STATE_POSTMATCH )
		vweap.active = qfalse;
	else
		vweap.active = (cg.thirdPerson == qfalse);
}

//================
//CG_CathegorizePlayerStatePosition
//================
void CG_CathegorizePlayerStatePosition( void )
{
	vec3_t			lerpedorigin;
	vec3_t			point;
	trace_t			trace;
	int				i;
	centity_t		*cent;
	player_state_t	*ps, *ops;

	//JALFIXME: just rewrite this one.

	ps = cg.player.curps;
	ops = cg.player.oldps;

	//using the entity
	cent = &cg_entities[cg.chasedNum+1]; // player in POV

	if( !cg.thirdPerson && cg.chasedNum == cgs.playerNum )
	{
		float		backlerp;
		float		lerp;

		//predicted
		lerp = cg.lerpfrac;
		if( cg_predict->integer && !cg.demoPlaying &&
			!(cg.frame.playerState.pmove.pm_flags & PMF_NO_PREDICTION) && !cg.thirdPerson ) 
		{
			backlerp = 1.0f - lerp;
			for( i = 0; i < 3; i++ )
				lerpedorigin[i] = cg.predictedOrigin[i] - backlerp * cg.predictionError[i];
		}
		else
		{
			//just interpolated
			VectorCopy( cg.player.origin, lerpedorigin );
		}
		
	} else {
		// using packet entity (for chased players)
		for( i = 0; i < 3; i++ )
			lerpedorigin[i] = cent->prev.origin[i] + cg.lerpfrac * 
			(cent->current.origin[i] - cent->prev.origin[i]);
	}

	//in water check
	cg.player.viewContents = CG_PointContents( lerpedorigin );

	point[0] = lerpedorigin[0];
	point[1] = lerpedorigin[1];
	point[2] = lerpedorigin[2] - ((float)STEPSIZE * 1.4);

	//trace
	CG_Trace( &trace, lerpedorigin, playerbox_crouch_mins, playerbox_crouch_maxs, point, cent->current.number, MASK_PLAYERSOLID );
	if( trace.plane.normal[2] < 0.7 && !trace.startsolid ) {
		cg.player.isOnGround = qfalse;
		return;
	}
	
	//found solid.
	cg.player.isOnGround = qtrue;
}

//================
//CG_LerpPlayerState
//================
void CG_LerpPlayerState( void )
{
	int			i;
	frame_t		*oldframe;
	player_state_t	*ps, *ops;
	
	// find the previous frame to interpolate from
	ps = &cg.frame.playerState;
	oldframe = &cg.oldFrame;
	if( oldframe->serverFrame != cg.frame.serverFrame-1 || !oldframe->valid )
		oldframe = &cg.frame;		// previous frame was dropped or invalid
	ops = &oldframe->playerState;
	
	// see if the player entity was teleported this frame
	if( abs(ops->pmove.origin[0] - ps->pmove.origin[0]) > 256*16
		|| abs(ops->pmove.origin[1] - ps->pmove.origin[1]) > 256*16
		|| abs(ops->pmove.origin[2] - ps->pmove.origin[2]) > 256*16 )
		ops = ps;		// don't interpolate
	
	// interpolate some values into cg_clientstate_t so
	// they are easier to use
	
	for( i = 0; i < 3; i++ ) {
		cg.player.origin[i] = ops->pmove.origin[i]*(1.0/16.0) 
			+ cg.lerpfrac * ( ps->pmove.origin[i]*(1.0/16.0) - ops->pmove.origin[i]*(1.0/16.0) );
		
		cg.player.viewangles[i] = LerpAngle( ops->viewangles[i], ps->viewangles[i], cg.lerpfrac );
	}
	
	cg.player.fov = ops->fov + cg.lerpfrac * (ps->fov - ops->fov);
	VectorSet( cg.player.viewoffset, 0.0f, 0.0f, 
		ops->viewheight + cg.lerpfrac * ( ps->viewheight - ops->viewheight ) );
	
	cg.player.oldps = ops;
	cg.player.curps = ps;

	CG_PlayerPOV( ps ); // set up before cathegorize position
	
	// find out some information of common use later
	CG_CathegorizePlayerStatePosition();
	CG_CalcViewBob();
}

//==============
//CG_AddBlend - wsw
//==============
void CG_AddBlend( float r, float g, float b, float a, float *v_blend )
{
	float	a2, a3;

	if (a <= 0)
		return;
	a2 = v_blend[3] + (1-v_blend[3])*a;	// new total alpha
	a3 = v_blend[3]/a2;		// fraction of color from old

	v_blend[0] = v_blend[0]*a3 + r*(1-a3);
	v_blend[1] = v_blend[1]*a3 + g*(1-a3);
	v_blend[2] = v_blend[2]*a3 + b*(1-a3);
	v_blend[3] = a2;
}

//==============
//CG_CalcColorBlend - wsw
//==============
void CG_CalcColorBlend( void )
{
	float	time;
	float	uptime;
	float	delta;
	int		i, contents;

	//clear old values
	for( i = 0; i < 4; i++ )
		cg.refdef.blend[i] = 0.0f;

	// Add colorblend based on world position
	contents = CG_PointContents( cg.refdef.vieworg );
	if( contents & CONTENTS_WATER )
		CG_AddBlend( 0.0f, 0.1f, 8.0f, 0.4f, cg.refdef.blend );
	if( contents & CONTENTS_LAVA )
		CG_AddBlend( 1.0f, 0.3f, 0.0f, 0.6f, cg.refdef.blend );
	if( contents & CONTENTS_SLIME )
		CG_AddBlend( 0.0f, 0.1f, 0.05f, 0.6f, cg.refdef.blend );

	// Add colorblends from sfx
	for( i = 0; i < MAX_COLORBLENDS; i++ ) 
	{
		if( cg.time > cg.colorblends[i].timestamp + cg.colorblends[i].blendtime )
			continue;

		time = (float)((cg.colorblends[i].timestamp + cg.colorblends[i].blendtime) - cg.time);
		uptime = ((float)cg.colorblends[i].blendtime) * 0.5f;
		delta = 1.0f - (abs(time - uptime) / uptime);
		if( delta > 1.0f )
			delta = 1.0f;
		if( delta <= 0.0f )
			continue;

		CG_AddBlend( cg.colorblends[i].blend[0], 
			cg.colorblends[i].blend[1], 
			cg.colorblends[i].blend[2], 
			cg.colorblends[i].blend[3] * delta, 
			cg.refdef.blend );
	}
}

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

//==============
//CG_AddLocalSounds
//==============
void CG_AddLocalSounds( void )
{
	static int flagNextBipTimer = 100;
	static int lastBipTime;

	// add sounds from announcer
	CG_ReleaseAnnouncerEvents();

	// if in postmatch, play postmatch song
	if( cg.frame.match.state >= MATCH_STATE_POSTMATCH ) {
		trap_S_StopBackgroundTrack();
		trap_S_AddLoopSound( trap_S_RegisterSound( S_MUSIC_POSTMATCH ), cg.refdef.vieworg, 1.0f, qfalse );
	} else {
		// ctf flag sounds
		if( cg.frame.playerState.stats[STAT_GAMETYPE] == GAMETYPE_CTF ) {
			centity_t *cent = &cg_entities[cg.chasedNum+1];
			if( cg.frame.playerState.stats[STAT_RACE_TIME] == STAT_NOTSET ||
				!(cent->current.effects & EF_ENEMY_FLAG) ) // ignore if not a flag carrier
			{
				lastBipTime = STAT_NOTSET;
			}
			else { // the timer is up
				flagNextBipTimer -= cg.frameTime * 1000;
				if( flagNextBipTimer <= 0 ) {
					int curBipTime;

					curBipTime = cg.frame.playerState.stats[STAT_RACE_TIME];

					flagNextBipTimer = 1000;

					if( lastBipTime == STAT_NOTSET || lastBipTime > curBipTime ) {// counting down
						trap_S_StartSound( NULL, cg.chasedNum + 1, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxTimerBipBip ), 0.5f, ATTN_NONE, 0 );
						flagNextBipTimer = 1000;
					}
					else if( lastBipTime <= curBipTime ) { // counting up
						trap_S_StartSound( NULL, cg.chasedNum + 1, CHAN_AUTO, CG_MediaSfx( cgs.media.sfxTimerPloink ), 0.5f, ATTN_NONE, 0 );
						flagNextBipTimer = 2000;
					}

					lastBipTime = curBipTime;
				}
			}
		}
	}
}

//==============
//CG_AddKickAngles
//==============
void CG_AddKickAngles( vec3_t viewangles )
{
	float	time;
	float	uptime;
	float	delta;
	int		i;

	for( i = 0; i < MAX_ANGLES_KICKS; i++ ) 
	{
		if( cg.time > cg.kickangles[i].timestamp + cg.kickangles[i].kicktime )
			continue;

		time = (float)((cg.kickangles[i].timestamp + cg.kickangles[i].kicktime) - cg.time);
		uptime = ((float)cg.kickangles[i].kicktime) * 0.5f;
		delta = 1.0f - (abs(time - uptime) / uptime);
		//CG_Printf("Kick Delta:%f\n", delta );
		if( delta > 1.0f )
			delta = 1.0f;
		if( delta <= 0.0f )
			continue;

		viewangles[PITCH] += cg.kickangles[i].v_pitch * delta;
		viewangles[ROLL] += cg.kickangles[i].v_roll * delta;
	}
}

//===============
//CG_ViewAddStepOffset
//===============
static void CG_ViewAddStepOffset( void ) 
{
	int		timeDelta;

	// smooth out stair climbing
	timeDelta = cg.realTime - cg.predictedStepTime;
	if ( timeDelta < PREDICTED_STEP_TIME ) {
		cg.refdef.vieworg[2] -= cg.predictedStep 
			* (PREDICTED_STEP_TIME - timeDelta) / PREDICTED_STEP_TIME;
	}
}

//===============
//CG_CalcViewValues
//Sets refdef from player view point
//===============
void CG_CalcViewValues( void )
{
	int			i;
	float		backlerp;

	// calculate the origin
	if( cg_predict->integer && !cg.thirdPerson && !cg.demoPlaying &&
		!(cg.frame.playerState.pmove.pm_flags & PMF_NO_PREDICTION) ) {

			// use predicted values
			backlerp = 1.0f - cg.lerpfrac;
			for( i = 0; i < 3; i++ )
				cg.refdef.vieworg[i] = cg.predictedOrigin[i] + cg.player.viewoffset[i]
				- backlerp * cg.predictionError[i];

				// smooth out stair climbing
				CG_ViewAddStepOffset();

		} else {
			// just use interpolated values
			VectorAdd( cg.player.origin, cg.player.viewoffset, cg.refdef.vieworg );
		}

		// if not running a demo or on a locked frame, add the local angle movement
		if( (cg.frame.playerState.pmove.pm_type < PM_DEAD) && !cg.demoPlaying ) {
			// use predicted values
			for( i = 0; i < 3; i++ )
				cg.refdef.viewangles[i] = cg.predictedAngles[i];
		} else {
			// just use interpolated values
			VectorCopy( cg.player.viewangles, cg.refdef.viewangles );
		}

		if( cg_damage_kick->integer )
			CG_AddKickAngles( cg.refdef.viewangles );

		AngleVectors( cg.refdef.viewangles, cg.v_forward, cg.v_right, cg.v_up );

		// interpolate field of view
		if( cg.demoPlaying && !cg_demo_truePOV->integer ) {
			cg.refdef.fov_x = cg_fov->integer > 0 ? cg_fov->integer : 90;
		} else {
			cg.refdef.fov_x = cg.player.fov;
		}

		// used for outline LODs computations
		cg.view_fracDistFOV = tan( cg.refdef.fov_x * (M_PI/180) * 0.5f );
}

//==================
//CG_SetUpInterpolationTime
//==================
void CG_SetUpInterpolationTime( unsigned int serverTime )
{
	unsigned int	snapTime;

	if( cg.frame.deltaFrame <= 0 ) { // not a delta compressed frame
		// we might be able to interpolate, tho
		if( !cg.oldFrame.valid || cg.oldFrame.serverFrame >= cg.frame.serverFrame ||
			cg.oldFrame.serverFrame <= cg.frame.serverFrame - 3 )
		{
			cg.time = cg.frame.serverTime;
			cg.lerpfrac = 1.0f;
			return;
		}
	}

	// special cases
	if( cg_timedemo->integer ) {
		cg.time = cg.frame.serverTime;
		cg.lerpfrac = 1.0;
	}

	// we have a valid oldframe

	//find at what point of the interpolation we are
	snapTime = cg.frame.serverTime - cg.oldFrame.serverTime;

	// cg.time moves forward from the last snap time 
	cg.time = serverTime;
	if( cg.time < cg.frame.serverTime ) { // this one is unlikely to happen
		cg.time = cg.frame.serverTime;
		//CG_Printf( "cg.time < cg.frame.serverTime\n" );
	}
	else if( cg.time > cg.frame.serverTime + snapTime ) { // this one could happen when a packet is not received on time
		cg.time = cg.frame.serverTime + snapTime;
		//CG_Printf( "cg.time > cg.frame.serverTime + snapTime (ping raise)\n" );
	}

	cg.lerpfrac = (double)(cg.time - cg.frame.serverTime)/(double)(snapTime);
}

/*
==================
CG_RenderView

==================
*/
#define	WAVE_AMPLITUDE	0.015	// [0..1]
#define	WAVE_FREQUENCY	0.6		// [0..1]

void CG_RenderView( float frameTime, int realTime, unsigned int serverTime, float stereo_separation, qboolean demoplaying )
{
	if( !cg.frame.valid ) {
		SCR_DrawLoading();
		return;
	}

	SCR_CalcVrect(); // find sizes of the 3d drawing screen
	SCR_TileClear(); // clear any dirty part of the background

	if( !cg.demoPlaying && demoplaying ) {
		CG_UnregisterGameCommands();
		CG_RegisterDemoCommands();
	} else if( cg.demoPlaying && !demoplaying ) {
		cg.demoShowScoreboard = qfalse;
		CG_UnregisterDemoCommands();
		CG_RegisterGameCommands();
	}

	cg.demoPlaying = demoplaying;

	// update time
	cg.realTime = realTime;
	cg.frameTime = frameTime;
	cg.frameCount++;

	CG_SetUpInterpolationTime( serverTime );

	// predict all unacknowledged movements
	CG_PredictMovement();
	
	CG_LerpPlayerState();	// interpolated player state info
	CG_LerpEntities();		// interpolate packet entities positions

	// run lightstyles
	CG_RunLightStyles();

	trap_R_ClearScene();

	// finds the refdef, loads v_forward, etc.
	CG_CalcViewValues();
	if( cg.thirdPerson )
		CG_ThirdPerson_CameraUpdate();
#ifdef DEMOCAM
	// [DEMOCAM] DemoCam Hack -- PLX
	DemoCam();
#endif

	// build a refresh entity list
	CG_AddEntities();

	CG_AddLightStyles();
#ifdef _DEBUG
	CG_AddTest();
#endif

	// offset vieworg appropriately if we're doing stereo separation
	if( stereo_separation != 0 )
		VectorMA( cg.refdef.vieworg, stereo_separation, cg.v_right, cg.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
	cg.refdef.vieworg[0] += 1.0/16;
	cg.refdef.vieworg[1] += 1.0/16;
	cg.refdef.vieworg[2] += 1.0/16;

	cg.refdef.x = scr_vrect.x;
	cg.refdef.y = scr_vrect.y;
	cg.refdef.width = scr_vrect.width;
	cg.refdef.height = scr_vrect.height;
	cg.refdef.fov_y = CalcFov( cg.refdef.fov_x, cg.refdef.width, cg.refdef.height );

	cg.refdef.time = cg.time * 0.001;
	cg.refdef.areabits = cg.frame.areabits;

	cg.refdef.rdflags = CG_RenderFlags();

	// warp if underwater
	if( cg.refdef.rdflags & RDF_UNDERWATER ) {
		float phase = cg.refdef.time * WAVE_FREQUENCY * M_TWOPI;
		float v = WAVE_AMPLITUDE * (sin( phase ) - 1.0) + 1;
		cg.refdef.fov_x *= v;
		cg.refdef.fov_y *= v;
	}

	CG_CalcColorBlend();

	cg.refdef.rdflags |= RDF_BLOOM;   //BLOOMS
	if( cg.frame.playerState.pmove.pm_type == PM_SPECTATOR || // force clear if spectator
		cg.frame.playerState.pmove.pm_type == PM_CHASECAM )
		cg.refdef.rdflags |= RDF_FORCECLEAR;

	CG_AddLocalSounds();
	
	trap_R_RenderScene( &cg.refdef );

	// update audio
	trap_S_Update( cg.refdef.vieworg, cg.v_forward, cg.v_right, cg.v_up );

#ifdef DEMOCAM
	if(!CamIsFree) SCR_Draw2D(); // [DEMOCAM] Dont draw HUD if DemoCam is activated -- PLX
#else
	SCR_Draw2D();
#endif

	CG_ResetTemporaryBoneposesCache(); // skelmod : reset for next frame
}
