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