1 /*
2 ===========================================================================
3 Copyright (C) 1999-2005 Id Software, Inc.
4 
5 This file is part of Quake III Arena source code.
6 
7 Quake III Arena source code is free software; you can redistribute it
8 and/or modify it under the terms of the GNU General Public License as
9 published by the Free Software Foundation; either version 2 of the License,
10 or (at your option) any later version.
11 
12 Quake III Arena source code is distributed in the hope that it will be
13 useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16 
17 You should have received a copy of the GNU General Public License
18 along with Quake III Arena source code; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 ===========================================================================
21 */
22 //
23 // cg_view.c -- setup all the parameters (position, angle, etc)
24 // for a 3D rendering
25 #include "cg_local.h"
26 
27 
28 /*
29 =============================================================================
30 
31   MODEL TESTING
32 
33 The viewthing and gun positioning tools from Q2 have been integrated and
34 enhanced into a single model testing facility.
35 
36 Model viewing can begin with either "testmodel <modelname>" or "testgun <modelname>".
37 
38 The names must be the full pathname after the basedir, like
39 "models/weapons/v_launch/tris.md3" or "players/male/tris.md3"
40 
41 Testmodel will create a fake entity 100 units in front of the current view
42 position, directly facing the viewer.  It will remain immobile, so you can
43 move around it to view it from different angles.
44 
45 Testgun will cause the model to follow the player around and supress the real
46 view weapon model.  The default frame 0 of most guns is completely off screen,
47 so you will probably have to cycle a couple frames to see it.
48 
49 "nextframe", "prevframe", "nextskin", and "prevskin" commands will change the
50 frame or skin of the testmodel.  These are bound to F5, F6, F7, and F8 in
51 q3default.cfg.
52 
53 If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let
54 you adjust the positioning.
55 
56 Note that none of the model testing features update while the game is paused, so
57 it may be convenient to test with deathmatch set to 1 so that bringing down the
58 console doesn't pause the game.
59 
60 =============================================================================
61 */
62 
63 /*
64 =================
65 CG_TestModel_f
66 
67 Creates an entity in front of the current position, which
68 can then be moved around
69 =================
70 */
CG_TestModel_f(void)71 void CG_TestModel_f (void) {
72 	vec3_t		angles;
73 
74 	memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) );
75 	if ( trap_Argc() < 2 ) {
76 		return;
77 	}
78 
79 	Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH );
80 	cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName );
81 
82 	if ( trap_Argc() == 3 ) {
83 		cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) );
84 		cg.testModelEntity.frame = 1;
85 		cg.testModelEntity.oldframe = 0;
86 	}
87 	if (! cg.testModelEntity.hModel ) {
88 		CG_Printf( "Can't register model\n" );
89 		return;
90 	}
91 
92 	VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin );
93 
94 	angles[PITCH] = 0;
95 	angles[YAW] = 180 + cg.refdefViewAngles[1];
96 	angles[ROLL] = 0;
97 
98 	AnglesToAxis( angles, cg.testModelEntity.axis );
99 	cg.testGun = qfalse;
100 }
101 
102 /*
103 =================
104 CG_TestGun_f
105 
106 Replaces the current view weapon with the given model
107 =================
108 */
CG_TestGun_f(void)109 void CG_TestGun_f (void) {
110 	CG_TestModel_f();
111 	cg.testGun = qtrue;
112 	cg.testModelEntity.renderfx = RF_MINLIGHT | RF_DEPTHHACK | RF_FIRST_PERSON;
113 }
114 
115 
CG_TestModelNextFrame_f(void)116 void CG_TestModelNextFrame_f (void) {
117 	cg.testModelEntity.frame++;
118 	CG_Printf( "frame %i\n", cg.testModelEntity.frame );
119 }
120 
CG_TestModelPrevFrame_f(void)121 void CG_TestModelPrevFrame_f (void) {
122 	cg.testModelEntity.frame--;
123 	if ( cg.testModelEntity.frame < 0 ) {
124 		cg.testModelEntity.frame = 0;
125 	}
126 	CG_Printf( "frame %i\n", cg.testModelEntity.frame );
127 }
128 
CG_TestModelNextSkin_f(void)129 void CG_TestModelNextSkin_f (void) {
130 	cg.testModelEntity.skinNum++;
131 	CG_Printf( "skin %i\n", cg.testModelEntity.skinNum );
132 }
133 
CG_TestModelPrevSkin_f(void)134 void CG_TestModelPrevSkin_f (void) {
135 	cg.testModelEntity.skinNum--;
136 	if ( cg.testModelEntity.skinNum < 0 ) {
137 		cg.testModelEntity.skinNum = 0;
138 	}
139 	CG_Printf( "skin %i\n", cg.testModelEntity.skinNum );
140 }
141 
CG_AddTestModel(void)142 static void CG_AddTestModel (void) {
143 	int		i;
144 
145 	// re-register the model, because the level may have changed
146 	cg.testModelEntity.hModel = trap_R_RegisterModel( cg.testModelName );
147 	if (! cg.testModelEntity.hModel ) {
148 		CG_Printf ("Can't register model\n");
149 		return;
150 	}
151 
152 	// if testing a gun, set the origin reletive to the view origin
153 	if ( cg.testGun ) {
154 		VectorCopy( cg.refdef.vieworg, cg.testModelEntity.origin );
155 		VectorCopy( cg.refdef.viewaxis[0], cg.testModelEntity.axis[0] );
156 		VectorCopy( cg.refdef.viewaxis[1], cg.testModelEntity.axis[1] );
157 		VectorCopy( cg.refdef.viewaxis[2], cg.testModelEntity.axis[2] );
158 
159 		// allow the position to be adjusted
160 		for (i=0 ; i<3 ; i++) {
161 			cg.testModelEntity.origin[i] += cg.refdef.viewaxis[0][i] * cg_gun_x.value;
162 			cg.testModelEntity.origin[i] += cg.refdef.viewaxis[1][i] * cg_gun_y.value;
163 			cg.testModelEntity.origin[i] += cg.refdef.viewaxis[2][i] * cg_gun_z.value;
164 		}
165 	}
166 
167 	trap_R_AddRefEntityToScene( &cg.testModelEntity );
168 }
169 
170 
171 
172 //============================================================================
173 
174 
175 /*
176 =================
177 CG_CalcVrect
178 
179 Sets the coordinates of the rendered window
180 =================
181 */
CG_CalcVrect(void)182 static void CG_CalcVrect (void) {
183 	int		size;
184 
185 	// the intermission should allways be full screen
186 	if ( cg.snap->ps.pm_type == PM_INTERMISSION ) {
187 		size = 100;
188 	} else {
189 		// bound normal viewsize
190 		if (cg_viewsize.integer < 30) {
191 			trap_Cvar_Set ("cg_viewsize","30");
192 			size = 30;
193 		} else if (cg_viewsize.integer > 100) {
194 			trap_Cvar_Set ("cg_viewsize","100");
195 			size = 100;
196 		} else {
197 			size = cg_viewsize.integer;
198 		}
199 
200 	}
201 	cg.refdef.width = cgs.glconfig.vidWidth*size/100;
202 	cg.refdef.width &= ~1;
203 
204 	cg.refdef.height = cgs.glconfig.vidHeight*size/100;
205 	cg.refdef.height &= ~1;
206 
207 	cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width)/2;
208 	cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height)/2;
209 }
210 
211 //==============================================================================
212 
213 
214 /*
215 ===============
216 CG_OffsetThirdPersonView
217 
218 ===============
219 */
220 #define	FOCUS_DISTANCE	512
CG_OffsetThirdPersonView(void)221 static void CG_OffsetThirdPersonView( void ) {
222 	vec3_t		forward, right, up;
223 	vec3_t		view;
224 	vec3_t		focusAngles;
225 	trace_t		trace;
226 	static vec3_t	mins = { -4, -4, -4 };
227 	static vec3_t	maxs = { 4, 4, 4 };
228 	vec3_t		focusPoint;
229 	float		focusDist;
230 	float		forwardScale, sideScale;
231 
232 	cg.refdef.vieworg[2] += cg.predictedPlayerState.viewheight;
233 
234 	VectorCopy( cg.refdefViewAngles, focusAngles );
235 
236 	// if dead, look at killer
237 	if ( cg.predictedPlayerState.stats[STAT_HEALTH] <= 0 ) {
238 		focusAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW];
239 		cg.refdefViewAngles[YAW] = cg.predictedPlayerState.stats[STAT_DEAD_YAW];
240 	}
241 
242 	if ( focusAngles[PITCH] > 45 ) {
243 		focusAngles[PITCH] = 45;		// don't go too far overhead
244 	}
245 	AngleVectors( focusAngles, forward, NULL, NULL );
246 
247 	VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint );
248 
249 	VectorCopy( cg.refdef.vieworg, view );
250 
251 	view[2] += 8;
252 
253 	cg.refdefViewAngles[PITCH] *= 0.5;
254 
255 	AngleVectors( cg.refdefViewAngles, forward, right, up );
256 
257 	forwardScale = cos( cg_thirdPersonAngle.value / 180 * M_PI );
258 	sideScale = sin( cg_thirdPersonAngle.value / 180 * M_PI );
259 	VectorMA( view, -cg_thirdPersonRange.value * forwardScale, forward, view );
260 	VectorMA( view, -cg_thirdPersonRange.value * sideScale, right, view );
261 
262 	// trace a ray from the origin to the viewpoint to make sure the view isn't
263 	// in a solid block.  Use an 8 by 8 block to prevent the view from near clipping anything
264 
265 	if (!cg_cameraMode.integer) {
266 		CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID );
267 
268 		if ( trace.fraction != 1.0 ) {
269 			VectorCopy( trace.endpos, view );
270 			view[2] += (1.0 - trace.fraction) * 32;
271 			// try another trace to this position, because a tunnel may have the ceiling
272 			// close enogh that this is poking out
273 
274 			CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predictedPlayerState.clientNum, MASK_SOLID );
275 			VectorCopy( trace.endpos, view );
276 		}
277 	}
278 
279 
280 	VectorCopy( view, cg.refdef.vieworg );
281 
282 	// select pitch to look at focus point from vieword
283 	VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint );
284 	focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] );
285 	if ( focusDist < 1 ) {
286 		focusDist = 1;	// should never happen
287 	}
288 	cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist );
289 	cg.refdefViewAngles[YAW] -= cg_thirdPersonAngle.value;
290 }
291 
292 
293 // this causes a compiler bug on mac MrC compiler
CG_StepOffset(void)294 static void CG_StepOffset( void ) {
295 	int		timeDelta;
296 
297 	// smooth out stair climbing
298 	timeDelta = cg.time - cg.stepTime;
299 	if ( timeDelta < STEP_TIME ) {
300 		cg.refdef.vieworg[2] -= cg.stepChange
301 			* (STEP_TIME - timeDelta) / STEP_TIME;
302 	}
303 }
304 
305 /*
306 ===============
307 CG_OffsetFirstPersonView
308 
309 ===============
310 */
CG_OffsetFirstPersonView(void)311 static void CG_OffsetFirstPersonView( void ) {
312 	float			*origin;
313 	float			*angles;
314 	float			bob;
315 	float			ratio;
316 	float			delta;
317 	float			speed;
318 	float			f;
319 	vec3_t			predictedVelocity;
320 	int				timeDelta;
321 
322 	if ( cg.snap->ps.pm_type == PM_INTERMISSION ) {
323 		return;
324 	}
325 
326 	origin = cg.refdef.vieworg;
327 	angles = cg.refdefViewAngles;
328 
329 	// if dead, fix the angle and don't add any kick
330 	if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 ) {
331 		angles[ROLL] = 40;
332 		angles[PITCH] = -15;
333 		angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW];
334 		origin[2] += cg.predictedPlayerState.viewheight;
335 		return;
336 	}
337 
338 	// add angles based on weapon kick
339 	VectorAdd (angles, cg.kick_angles, angles);
340 
341 	// add angles based on damage kick
342 	if ( cg.damageTime ) {
343 		ratio = cg.time - cg.damageTime;
344 		if ( ratio < DAMAGE_DEFLECT_TIME ) {
345 			ratio /= DAMAGE_DEFLECT_TIME;
346 			angles[PITCH] += ratio * cg.v_dmg_pitch;
347 			angles[ROLL] += ratio * cg.v_dmg_roll;
348 		} else {
349 			ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME;
350 			if ( ratio > 0 ) {
351 				angles[PITCH] += ratio * cg.v_dmg_pitch;
352 				angles[ROLL] += ratio * cg.v_dmg_roll;
353 			}
354 		}
355 	}
356 
357 	// add pitch based on fall kick
358 #if 0
359 	ratio = ( cg.time - cg.landTime) / FALL_TIME;
360 	if (ratio < 0)
361 		ratio = 0;
362 	angles[PITCH] += ratio * cg.fall_value;
363 #endif
364 
365 	// add angles based on velocity
366 	VectorCopy( cg.predictedPlayerState.velocity, predictedVelocity );
367 
368 	delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]);
369 	angles[PITCH] += delta * cg_runpitch.value;
370 
371 	delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]);
372 	angles[ROLL] -= delta * cg_runroll.value;
373 
374 	// add angles based on bob
375 
376 	// make sure the bob is visible even at low speeds
377 	speed = cg.xyspeed > 200 ? cg.xyspeed : 200;
378 
379 	delta = cg.bobfracsin * cg_bobpitch.value * speed;
380 	if (cg.predictedPlayerState.pm_flags & PMF_DUCKED)
381 		delta *= 3;		// crouching
382 	angles[PITCH] += delta;
383 	delta = cg.bobfracsin * cg_bobroll.value * speed;
384 	if (cg.predictedPlayerState.pm_flags & PMF_DUCKED)
385 		delta *= 3;		// crouching accentuates roll
386 	if (cg.bobcycle & 1)
387 		delta = -delta;
388 	angles[ROLL] += delta;
389 
390 //===================================
391 
392 	// add view height
393 	origin[2] += cg.predictedPlayerState.viewheight;
394 
395 	// smooth out duck height changes
396 	timeDelta = cg.time - cg.duckTime;
397 	if ( timeDelta < DUCK_TIME) {
398 		cg.refdef.vieworg[2] -= cg.duckChange
399 			* (DUCK_TIME - timeDelta) / DUCK_TIME;
400 	}
401 
402 	// add bob height
403 	bob = cg.bobfracsin * cg.xyspeed * cg_bobup.value;
404 	if (bob > 6) {
405 		bob = 6;
406 	}
407 
408 	origin[2] += bob;
409 
410 
411 	// add fall height
412 	delta = cg.time - cg.landTime;
413 	if ( delta < LAND_DEFLECT_TIME ) {
414 		f = delta / LAND_DEFLECT_TIME;
415 		cg.refdef.vieworg[2] += cg.landChange * f;
416 	} else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
417 		delta -= LAND_DEFLECT_TIME;
418 		f = 1.0 - ( delta / LAND_RETURN_TIME );
419 		cg.refdef.vieworg[2] += cg.landChange * f;
420 	}
421 
422 	// add step offset
423 	CG_StepOffset();
424 
425 	// add kick offset
426 
427 	VectorAdd (origin, cg.kick_origin, origin);
428 
429 	// pivot the eye based on a neck length
430 #if 0
431 	{
432 #define	NECK_LENGTH		8
433 	vec3_t			forward, up;
434 
435 	cg.refdef.vieworg[2] -= NECK_LENGTH;
436 	AngleVectors( cg.refdefViewAngles, forward, NULL, up );
437 	VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg );
438 	VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg );
439 	}
440 #endif
441 }
442 
443 //======================================================================
444 
CG_ZoomDown_f(void)445 void CG_ZoomDown_f( void ) {
446 	if ( cg.zoomed ) {
447 		return;
448 	}
449 	cg.zoomed = qtrue;
450 	cg.zoomTime = cg.time;
451 }
452 
CG_ZoomUp_f(void)453 void CG_ZoomUp_f( void ) {
454 	if ( !cg.zoomed ) {
455 		return;
456 	}
457 	cg.zoomed = qfalse;
458 	cg.zoomTime = cg.time;
459 }
460 
461 
462 /*
463 ====================
464 CG_CalcFov
465 
466 Fixed fov at intermissions, otherwise account for fov variable and zooms.
467 ====================
468 */
469 #define	WAVE_AMPLITUDE	1
470 #define	WAVE_FREQUENCY	0.4
471 
CG_CalcFov(void)472 static int CG_CalcFov( void ) {
473 	float	x;
474 	float	phase;
475 	float	v;
476 	int		contents;
477 	float	fov_x, fov_y;
478 	float	zoomFov;
479 	float	f;
480 	int		inwater;
481 
482 	if ( cg.predictedPlayerState.pm_type == PM_INTERMISSION ) {
483 		// if in intermission, use a fixed value
484 		fov_x = 90;
485 	} else {
486 		// user selectable
487 		if ( cgs.dmflags & DF_FIXED_FOV ) {
488 			// dmflag to prevent wide fov for all clients
489 			fov_x = 90;
490 		} else {
491 			fov_x = cg_fov.value;
492 			if ( fov_x < 1 ) {
493 				fov_x = 1;
494 			} else if ( fov_x > 160 ) {
495 				fov_x = 160;
496 			}
497 		}
498 
499 		// account for zooms
500 		zoomFov = cg_zoomFov.value;
501 		if ( zoomFov < 1 ) {
502 			zoomFov = 1;
503 		} else if ( zoomFov > 160 ) {
504 			zoomFov = 160;
505 		}
506 
507 		if ( cg.zoomed ) {
508 			f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME;
509 			if ( f > 1.0 ) {
510 				fov_x = zoomFov;
511 			} else {
512 				fov_x = fov_x + f * ( zoomFov - fov_x );
513 			}
514 		} else {
515 			f = ( cg.time - cg.zoomTime ) / (float)ZOOM_TIME;
516 			if ( f > 1.0 ) {
517 				fov_x = fov_x;
518 			} else {
519 				fov_x = zoomFov + f * ( fov_x - zoomFov );
520 			}
521 		}
522 	}
523 
524 	x = cg.refdef.width / tan( fov_x / 360 * M_PI );
525 	fov_y = atan2( cg.refdef.height, x );
526 	fov_y = fov_y * 360 / M_PI;
527 
528 	// warp if underwater
529 	contents = CG_PointContents( cg.refdef.vieworg, -1 );
530 	if ( contents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) ){
531 		phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2;
532 		v = WAVE_AMPLITUDE * sin( phase );
533 		fov_x += v;
534 		fov_y -= v;
535 		inwater = qtrue;
536 	}
537 	else {
538 		inwater = qfalse;
539 	}
540 
541 
542 	// set it
543 	cg.refdef.fov_x = fov_x;
544 	cg.refdef.fov_y = fov_y;
545 
546 	if ( !cg.zoomed ) {
547 		cg.zoomSensitivity = 1;
548 	} else {
549 		cg.zoomSensitivity = cg.refdef.fov_y / 75.0;
550 	}
551 
552 	return inwater;
553 }
554 
555 
556 
557 /*
558 ===============
559 CG_DamageBlendBlob
560 
561 ===============
562 */
CG_DamageBlendBlob(void)563 static void CG_DamageBlendBlob( void ) {
564 	int			t;
565 	int			maxTime;
566 	refEntity_t		ent;
567 
568 	if ( !cg.damageValue ) {
569 		return;
570 	}
571 
572 	//if (cg.cameraMode) {
573 	//	return;
574 	//}
575 
576 	// ragePro systems can't fade blends, so don't obscure the screen
577 	if ( cgs.glconfig.hardwareType == GLHW_RAGEPRO ) {
578 		return;
579 	}
580 
581 	maxTime = DAMAGE_TIME;
582 	t = cg.time - cg.damageTime;
583 	if ( t <= 0 || t >= maxTime ) {
584 		return;
585 	}
586 
587 
588 	memset( &ent, 0, sizeof( ent ) );
589 	ent.reType = RT_SPRITE;
590 	ent.renderfx = RF_FIRST_PERSON;
591 
592 	VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin );
593 	VectorMA( ent.origin, cg.damageX * -8, cg.refdef.viewaxis[1], ent.origin );
594 	VectorMA( ent.origin, cg.damageY * 8, cg.refdef.viewaxis[2], ent.origin );
595 
596 	ent.radius = cg.damageValue * 3;
597 	ent.customShader = cgs.media.viewBloodShader;
598 	ent.shaderRGBA[0] = 255;
599 	ent.shaderRGBA[1] = 255;
600 	ent.shaderRGBA[2] = 255;
601 	ent.shaderRGBA[3] = 200 * ( 1.0 - ((float)t / maxTime) );
602 	trap_R_AddRefEntityToScene( &ent );
603 }
604 
605 
606 /*
607 ===============
608 CG_CalcViewValues
609 
610 Sets cg.refdef view values
611 ===============
612 */
CG_CalcViewValues(void)613 static int CG_CalcViewValues( void ) {
614 	playerState_t	*ps;
615 
616 	memset( &cg.refdef, 0, sizeof( cg.refdef ) );
617 
618 	// strings for in game rendering
619 	// Q_strncpyz( cg.refdef.text[0], "Park Ranger", sizeof(cg.refdef.text[0]) );
620 	// Q_strncpyz( cg.refdef.text[1], "19", sizeof(cg.refdef.text[1]) );
621 
622 	// calculate size of 3D view
623 	CG_CalcVrect();
624 
625 	ps = &cg.predictedPlayerState;
626 /*
627 	if (cg.cameraMode) {
628 		vec3_t origin, angles;
629 		if (trap_getCameraInfo(cg.time, &origin, &angles)) {
630 			VectorCopy(origin, cg.refdef.vieworg);
631 			angles[ROLL] = 0;
632 			VectorCopy(angles, cg.refdefViewAngles);
633 			AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
634 			return CG_CalcFov();
635 		} else {
636 			cg.cameraMode = qfalse;
637 		}
638 	}
639 */
640 	// intermission view
641 	if ( ps->pm_type == PM_INTERMISSION ) {
642 		VectorCopy( ps->origin, cg.refdef.vieworg );
643 		VectorCopy( ps->viewangles, cg.refdefViewAngles );
644 		AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
645 		return CG_CalcFov();
646 	}
647 
648 	cg.bobcycle = ( ps->bobCycle & 128 ) >> 7;
649 	cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) );
650 	cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] +
651 		ps->velocity[1] * ps->velocity[1] );
652 
653 
654 	VectorCopy( ps->origin, cg.refdef.vieworg );
655 	VectorCopy( ps->viewangles, cg.refdefViewAngles );
656 
657 	if (cg_cameraOrbit.integer) {
658 		if (cg.time > cg.nextOrbitTime) {
659 			cg.nextOrbitTime = cg.time + cg_cameraOrbitDelay.integer;
660 			cg_thirdPersonAngle.value += cg_cameraOrbit.value;
661 		}
662 	}
663 	// add error decay
664 	if ( cg_errorDecay.value > 0 ) {
665 		int		t;
666 		float	f;
667 
668 		t = cg.time - cg.predictedErrorTime;
669 		f = ( cg_errorDecay.value - t ) / cg_errorDecay.value;
670 		if ( f > 0 && f < 1 ) {
671 			VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg );
672 		} else {
673 			cg.predictedErrorTime = 0;
674 		}
675 	}
676 
677 	if ( cg.renderingThirdPerson ) {
678 		// back away from character
679 		CG_OffsetThirdPersonView();
680 	} else {
681 		// offset for local bobbing and kicks
682 		CG_OffsetFirstPersonView();
683 	}
684 
685 	// position eye reletive to origin
686 	AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
687 
688 	if ( cg.hyperspace ) {
689 		cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE;
690 	}
691 
692 	// field of view
693 	return CG_CalcFov();
694 }
695 
696 
697 /*
698 =====================
699 CG_PowerupTimerSounds
700 =====================
701 */
CG_PowerupTimerSounds(void)702 static void CG_PowerupTimerSounds( void ) {
703 	int		i;
704 	int		t;
705 
706 	// powerup timers going away
707 	for ( i = 0 ; i < MAX_POWERUPS ; i++ ) {
708 		t = cg.snap->ps.powerups[i];
709 		if ( t <= cg.time ) {
710 			continue;
711 		}
712 		if ( t - cg.time >= POWERUP_BLINKS * POWERUP_BLINK_TIME ) {
713 			continue;
714 		}
715 		if ( ( t - cg.time ) / POWERUP_BLINK_TIME != ( t - cg.oldTime ) / POWERUP_BLINK_TIME ) {
716 			trap_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.wearOffSound );
717 		}
718 	}
719 }
720 
721 /*
722 =====================
723 CG_AddBufferedSound
724 =====================
725 */
CG_AddBufferedSound(sfxHandle_t sfx)726 void CG_AddBufferedSound( sfxHandle_t sfx ) {
727 	if ( !sfx )
728 		return;
729 	cg.soundBuffer[cg.soundBufferIn] = sfx;
730 	cg.soundBufferIn = (cg.soundBufferIn + 1) % MAX_SOUNDBUFFER;
731 	if (cg.soundBufferIn == cg.soundBufferOut) {
732 		cg.soundBufferOut++;
733 	}
734 }
735 
736 /*
737 =====================
738 CG_PlayBufferedSounds
739 =====================
740 */
CG_PlayBufferedSounds(void)741 static void CG_PlayBufferedSounds( void ) {
742 	if ( cg.soundTime < cg.time ) {
743 		if (cg.soundBufferOut != cg.soundBufferIn && cg.soundBuffer[cg.soundBufferOut]) {
744 			trap_S_StartLocalSound(cg.soundBuffer[cg.soundBufferOut], CHAN_ANNOUNCER);
745 			cg.soundBuffer[cg.soundBufferOut] = 0;
746 			cg.soundBufferOut = (cg.soundBufferOut + 1) % MAX_SOUNDBUFFER;
747 			cg.soundTime = cg.time + 750;
748 		}
749 	}
750 }
751 
752 //=========================================================================
753 
754 /*
755 =================
756 CG_DrawActiveFrame
757 
758 Generates and draws a game scene and status information at the given time.
759 =================
760 */
CG_DrawActiveFrame(int serverTime,stereoFrame_t stereoView,qboolean demoPlayback)761 void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demoPlayback ) {
762 	int		inwater;
763 
764 	cg.time = serverTime;
765 	cg.demoPlayback = demoPlayback;
766 
767 	// update cvars
768 	CG_UpdateCvars();
769 
770 	// if we are only updating the screen as a loading
771 	// pacifier, don't even try to read snapshots
772 	if ( cg.infoScreenText[0] != 0 ) {
773 		CG_DrawInformation();
774 		return;
775 	}
776 
777 	// any looped sounds will be respecified as entities
778 	// are added to the render list
779 	trap_S_ClearLoopingSounds(qfalse);
780 
781 	// clear all the render lists
782 	trap_R_ClearScene();
783 
784 	// set up cg.snap and possibly cg.nextSnap
785 	CG_ProcessSnapshots();
786 
787 	// if we haven't received any snapshots yet, all
788 	// we can draw is the information screen
789 	if ( !cg.snap || ( cg.snap->snapFlags & SNAPFLAG_NOT_ACTIVE ) ) {
790 		CG_DrawInformation();
791 		return;
792 	}
793 
794 	// let the client system know what our weapon and zoom settings are
795 	trap_SetUserCmdValue( cg.weaponSelect, cg.zoomSensitivity );
796 
797 	// this counter will be bumped for every valid scene we generate
798 	cg.clientFrame++;
799 
800 	// update cg.predictedPlayerState
801 	CG_PredictPlayerState();
802 
803 	// decide on third person view
804 	cg.renderingThirdPerson = cg_thirdPerson.integer || (cg.snap->ps.stats[STAT_HEALTH] <= 0);
805 
806 	// build cg.refdef
807 	inwater = CG_CalcViewValues();
808 
809 	// first person blend blobs, done after AnglesToAxis
810 	if ( !cg.renderingThirdPerson ) {
811 		CG_DamageBlendBlob();
812 	}
813 
814 	// build the render lists
815 	if ( !cg.hyperspace ) {
816 		CG_AddPacketEntities();			// adter calcViewValues, so predicted player state is correct
817 		CG_AddMarks();
818 		CG_AddParticles ();
819 		CG_AddLocalEntities();
820 	}
821 	CG_AddViewWeapon( &cg.predictedPlayerState );
822 
823 	// add buffered sounds
824 	CG_PlayBufferedSounds();
825 
826 	// play buffered voice chats
827 	CG_PlayBufferedVoiceChats();
828 
829 	// finish up the rest of the refdef
830 	if ( cg.testModelEntity.hModel ) {
831 		CG_AddTestModel();
832 	}
833 	cg.refdef.time = cg.time;
834 	memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) );
835 
836 	// warning sounds when powerup is wearing off
837 	CG_PowerupTimerSounds();
838 
839 	// update audio positions
840 	trap_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater );
841 
842 	// make sure the lagometerSample and frame timing isn't done twice when in stereo
843 	if ( stereoView != STEREO_RIGHT ) {
844 		cg.frametime = cg.time - cg.oldTime;
845 		if ( cg.frametime < 0 ) {
846 			cg.frametime = 0;
847 		}
848 		cg.oldTime = cg.time;
849 		CG_AddLagometerFrameInfo();
850 	}
851 	if (cg_timescale.value != cg_timescaleFadeEnd.value) {
852 		if (cg_timescale.value < cg_timescaleFadeEnd.value) {
853 			cg_timescale.value += cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000;
854 			if (cg_timescale.value > cg_timescaleFadeEnd.value)
855 				cg_timescale.value = cg_timescaleFadeEnd.value;
856 		}
857 		else {
858 			cg_timescale.value -= cg_timescaleFadeSpeed.value * ((float)cg.frametime) / 1000;
859 			if (cg_timescale.value < cg_timescaleFadeEnd.value)
860 				cg_timescale.value = cg_timescaleFadeEnd.value;
861 		}
862 		if (cg_timescaleFadeSpeed.value) {
863 			trap_Cvar_Set("timescale", va("%f", cg_timescale.value));
864 		}
865 	}
866 
867 	// actually issue the rendering calls
868 	CG_DrawActive( stereoView );
869 
870 	if ( cg_stats.integer ) {
871 		CG_Printf( "cg.clientFrame:%i\n", cg.clientFrame );
872 	}
873 
874 
875 }
876 
877