1 /*
2 ===========================================================================
3 Copyright (C) 1999 - 2005, Id Software, Inc.
4 Copyright (C) 2000 - 2013, Raven Software, Inc.
5 Copyright (C) 2001 - 2013, Activision, Inc.
6 Copyright (C) 2013 - 2015, OpenJK contributors
7
8 This file is part of the OpenJK source code.
9
10 OpenJK is free software; you can redistribute it and/or modify it
11 under the terms of the GNU General Public License version 2 as
12 published by the Free Software Foundation.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, see <http://www.gnu.org/licenses/>.
21 ===========================================================================
22 */
23
24 // cg_view.c -- setup all the parameters (position, angle, etc)
25 // for a 3D rendering
26
27 // this line must stay at top so the whole PCH thing works...
28 #include "cg_headers.h"
29
30 #include "cg_media.h"
31 #include "FxScheduler.h"
32 #include "../game/wp_saber.h"
33 #include "../game/g_vehicles.h"
34
35 #define MASK_CAMERACLIP (MASK_SOLID)
36 #define CAMERA_SIZE 4
37
38 float cg_zoomFov;
39
40 //#define CG_CAM_ABOVE 2
41 extern qboolean CG_OnMovingPlat( playerState_t *ps );
42 extern Vehicle_t *G_IsRidingVehicle( gentity_t *ent );
43
44 extern int g_crosshairSameEntTime;
45 extern int g_crosshairEntNum;
46
47 /*
48 =============================================================================
49
50 MODEL TESTING
51
52 The viewthing and gun positioning tools from Q2 have been integrated and
53 enhanced into a single model testing facility.
54
55 Model viewing can begin with either "testmodel <modelname>" or "testgun <modelname>".
56
57 The names must be the full pathname after the basedir, like
58 "models/weapons/v_launch/tris.md3" or "players/male/tris.md3"
59
60 Testmodel will create a fake entity 100 units in front of the current view
61 position, directly facing the viewer. It will remain immobile, so you can
62 move around it to view it from different angles.
63
64 Testgun will cause the model to follow the player around and supress the real
65 view weapon model. The default frame 0 of most guns is completely off screen,
66 so you will probably have to cycle a couple frames to see it.
67
68 "nextframe", "prevframe", "nextskin", and "prevskin" commands will change the
69 frame or skin of the testmodel. These are bound to F5, F6, F7, and F8 in
70 q3default.cfg.
71
72 If a gun is being tested, the "gun_x", "gun_y", and "gun_z" variables will let
73 you adjust the positioning.
74
75 Note that none of the model testing features update while the game is paused, so
76 it may be convenient to test with deathmatch set to 1 so that bringing down the
77 console doesn't pause the game.
78
79 =============================================================================
80 */
81
82 /*
83 Ghoul2 Insert Start
84 */
85
86 /*
87 =================
88 CG_TestModel_f
89
90 Creates an entity in front of the current position, which
91 can then be moved around
92 =================
93 */
CG_TestG2Model_f(void)94 void CG_TestG2Model_f (void) {
95 vec3_t angles;
96 CGhoul2Info_v *ghoul2;
97
98 memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) );
99 ghoul2 = new CGhoul2Info_v;
100 cg.testModelEntity.ghoul2 = ghoul2;
101 if ( cgi_Argc() < 2 ) {
102 return;
103 }
104
105 Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH );
106 cg.testModelEntity.hModel = cgi_R_RegisterModel( cg.testModelName );
107
108 cg.testModel = gi.G2API_InitGhoul2Model(*((CGhoul2Info_v *)cg.testModelEntity.ghoul2), cg.testModelName, cg.testModelEntity.hModel, NULL_HANDLE, NULL_HANDLE,0,0);
109 cg.testModelEntity.radius = 100.0f;
110
111 if ( cgi_Argc() == 3 ) {
112 cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) );
113 cg.testModelEntity.frame = 1;
114 cg.testModelEntity.oldframe = 0;
115 }
116 if (! cg.testModelEntity.hModel ) {
117 CG_Printf( "Can't register model\n" );
118 return;
119 }
120
121 VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin );
122
123 angles[PITCH] = 0;
124 angles[YAW] = 180 + cg.refdefViewAngles[1];
125 angles[ROLL] = 0;
126
127 AnglesToAxis( angles, cg.testModelEntity.axis );
128 }
129
CG_ListModelSurfaces_f(void)130 void CG_ListModelSurfaces_f (void)
131 {
132 CGhoul2Info_v &ghoul2 = *((CGhoul2Info_v *)cg.testModelEntity.ghoul2);
133
134 gi.G2API_ListSurfaces(&ghoul2[cg.testModel]);
135 }
136
137
CG_ListModelBones_f(void)138 void CG_ListModelBones_f (void)
139 {
140 // test to see if we got enough args
141 if ( cgi_Argc() < 2 )
142 {
143 return;
144 }
145 CGhoul2Info_v &ghoul2 = *((CGhoul2Info_v *)cg.testModelEntity.ghoul2);
146
147 gi.G2API_ListBones(&ghoul2[cg.testModel], atoi(CG_Argv(1)));
148 }
149
CG_TestModelSurfaceOnOff_f(void)150 void CG_TestModelSurfaceOnOff_f(void)
151 {
152 // test to see if we got enough args
153 if ( cgi_Argc() < 3 )
154 {
155 return;
156 }
157 CGhoul2Info_v &ghoul2 = *((CGhoul2Info_v *)cg.testModelEntity.ghoul2);
158
159 gi.G2API_SetSurfaceOnOff(&ghoul2[cg.testModel], CG_Argv(1), atoi(CG_Argv(2)));
160 }
161
CG_TestModelSetAnglespre_f(void)162 void CG_TestModelSetAnglespre_f(void)
163 {
164 vec3_t angles;
165
166 if ( cgi_Argc() < 3 )
167 {
168 return;
169 }
170 CGhoul2Info_v &ghoul2 = *((CGhoul2Info_v *)cg.testModelEntity.ghoul2);
171
172 angles[0] = atof(CG_Argv(2));
173 angles[1] = atof(CG_Argv(3));
174 angles[2] = atof(CG_Argv(4));
175 gi.G2API_SetBoneAngles(&ghoul2[cg.testModel], CG_Argv(1), angles, BONE_ANGLES_PREMULT, POSITIVE_X, POSITIVE_Z, POSITIVE_Y, NULL, 0, 0);
176 }
177
CG_TestModelSetAnglespost_f(void)178 void CG_TestModelSetAnglespost_f(void)
179 {
180 vec3_t angles;
181
182 if ( cgi_Argc() < 3 )
183 {
184 return;
185 }
186 CGhoul2Info_v &ghoul2 = *((CGhoul2Info_v *)cg.testModelEntity.ghoul2);
187
188 angles[0] = atof(CG_Argv(2));
189 angles[1] = atof(CG_Argv(3));
190 angles[2] = atof(CG_Argv(4));
191 gi.G2API_SetBoneAngles(&ghoul2[cg.testModel], CG_Argv(1), angles, BONE_ANGLES_POSTMULT, POSITIVE_X, POSITIVE_Z, POSITIVE_Y, NULL, 0, 0);
192 }
193
CG_TestModelAnimate_f(void)194 void CG_TestModelAnimate_f(void)
195 {
196 char boneName[100];
197 CGhoul2Info_v &ghoul2 = *((CGhoul2Info_v *)cg.testModelEntity.ghoul2);
198
199 strcpy(boneName, CG_Argv(1));
200 gi.G2API_SetBoneAnim(&ghoul2[cg.testModel], boneName, atoi(CG_Argv(2)), atoi(CG_Argv(3)), BONE_ANIM_OVERRIDE_LOOP, atof(CG_Argv(4)), cg.time, -1, -1);
201
202 }
203
204 /*
205 Ghoul2 Insert End
206 */
207
208
209 /*
210 =================
211 CG_TestModel_f
212
213 Creates an entity in front of the current position, which
214 can then be moved around
215 =================
216 */
CG_TestModel_f(void)217 void CG_TestModel_f (void) {
218 vec3_t angles;
219
220 memset( &cg.testModelEntity, 0, sizeof(cg.testModelEntity) );
221 if ( cgi_Argc() < 2 ) {
222 return;
223 }
224
225 Q_strncpyz (cg.testModelName, CG_Argv( 1 ), MAX_QPATH );
226 cg.testModelEntity.hModel = cgi_R_RegisterModel( cg.testModelName );
227
228 if ( cgi_Argc() == 3 ) {
229 cg.testModelEntity.backlerp = atof( CG_Argv( 2 ) );
230 cg.testModelEntity.frame = 1;
231 cg.testModelEntity.oldframe = 0;
232 }
233 if (! cg.testModelEntity.hModel ) {
234 CG_Printf( "Can't register model\n" );
235 return;
236 }
237
238 VectorMA( cg.refdef.vieworg, 100, cg.refdef.viewaxis[0], cg.testModelEntity.origin );
239
240 angles[PITCH] = 0;
241 angles[YAW] = 180 + cg.refdefViewAngles[1];
242 angles[ROLL] = 0;
243
244 AnglesToAxis( angles, cg.testModelEntity.axis );
245 }
246
247
CG_TestModelNextFrame_f(void)248 void CG_TestModelNextFrame_f (void) {
249 cg.testModelEntity.frame++;
250 CG_Printf( "frame %i\n", cg.testModelEntity.frame );
251 }
252
CG_TestModelPrevFrame_f(void)253 void CG_TestModelPrevFrame_f (void) {
254 cg.testModelEntity.frame--;
255 if ( cg.testModelEntity.frame < 0 ) {
256 cg.testModelEntity.frame = 0;
257 }
258 CG_Printf( "frame %i\n", cg.testModelEntity.frame );
259 }
260
CG_TestModelNextSkin_f(void)261 void CG_TestModelNextSkin_f (void) {
262 cg.testModelEntity.skinNum++;
263 CG_Printf( "skin %i\n", cg.testModelEntity.skinNum );
264 }
265
CG_TestModelPrevSkin_f(void)266 void CG_TestModelPrevSkin_f (void) {
267 cg.testModelEntity.skinNum--;
268 if ( cg.testModelEntity.skinNum < 0 ) {
269 cg.testModelEntity.skinNum = 0;
270 }
271 CG_Printf( "skin %i\n", cg.testModelEntity.skinNum );
272 }
273
CG_AddTestModel(void)274 static void CG_AddTestModel (void) {
275 // re-register the model, because the level may have changed
276 /* cg.testModelEntity.hModel = cgi_R_RegisterModel( cg.testModelName );
277 if (! cg.testModelEntity.hModel ) {
278 CG_Printf ("Can't register model\n");
279 return;
280 }
281 */
282 cgi_R_AddRefEntityToScene( &cg.testModelEntity );
283 }
284
285
286
287 //============================================================================
288
289
290 /*
291 =================
292 CG_CalcVrect
293
294 Sets the coordinates of the rendered window
295 =================
296 */
CG_CalcVrect(void)297 void CG_CalcVrect (void) {
298 const int size = 100;
299
300 cg.refdef.width = cgs.glconfig.vidWidth * size * 0.01;
301 cg.refdef.width &= ~1;
302
303 cg.refdef.height = cgs.glconfig.vidHeight * size * 0.01;
304 cg.refdef.height &= ~1;
305
306 cg.refdef.x = (cgs.glconfig.vidWidth - cg.refdef.width) * 0.5;
307 cg.refdef.y = (cgs.glconfig.vidHeight - cg.refdef.height) * 0.5;
308 }
309
310 //==============================================================================
311 //==============================================================================
312 #define CAMERA_DAMP_INTERVAL 50
313
314 #define CAMERA_CROUCH_NUDGE 6
315
316 static vec3_t cameramins = { -CAMERA_SIZE, -CAMERA_SIZE, -CAMERA_SIZE };
317 static vec3_t cameramaxs = { CAMERA_SIZE, CAMERA_SIZE, CAMERA_SIZE };
318 vec3_t camerafwd, cameraup, camerahorizdir;
319
320 vec3_t cameraFocusAngles, cameraFocusLoc;
321 vec3_t cameraIdealTarget, cameraIdealLoc;
322 vec3_t cameraCurTarget={0,0,0}, cameraCurLoc={0,0,0};
323 vec3_t cameraOldLoc={0,0,0}, cameraNewLoc={0,0,0};
324 int cameraLastFrame=0;
325
326 float cameraLastYaw=0;
327 float cameraStiffFactor=0.0f;
328
329 /*
330 ===============
331 Notes on the camera viewpoint in and out...
332
333 cg.refdef.vieworg
334 --at the start of the function holds the player actor's origin (center of player model).
335 --it is set to the final view location of the camera at the end of the camera code.
336 cg.refdefViewAngles
337 --at the start holds the client's view angles
338 --it is set to the final view angle of the camera at the end of the camera code.
339
340 ===============
341 */
342
343 /*
344 ===============
345 CG_CalcIdealThirdPersonViewTarget
346
347 ===============
348 */
CG_CalcIdealThirdPersonViewTarget(void)349 static void CG_CalcIdealThirdPersonViewTarget(void)
350 {
351 // Initialize IdealTarget
352 qboolean usesViewEntity = (qboolean)(cg.snap->ps.viewEntity && cg.snap->ps.viewEntity < ENTITYNUM_WORLD);
353 VectorCopy(cg.refdef.vieworg, cameraFocusLoc);
354
355 if ( usesViewEntity )
356 {
357
358 gentity_t *gent = &g_entities[cg.snap->ps.viewEntity];
359 if ( gent->client && (gent->client->NPC_class == CLASS_GONK
360 || gent->client->NPC_class == CLASS_INTERROGATOR
361 || gent->client->NPC_class == CLASS_SENTRY
362 || gent->client->NPC_class == CLASS_PROBE
363 || gent->client->NPC_class == CLASS_MOUSE
364 || gent->client->NPC_class == CLASS_R2D2
365 || gent->client->NPC_class == CLASS_R5D2) )
366 { // Droids use a generic offset
367 cameraFocusLoc[2] += 4;
368 VectorCopy( cameraFocusLoc, cameraIdealTarget );
369 return;
370 }
371
372 if( gent->client->ps.pm_flags & PMF_DUCKED )
373 { // sort of a nasty hack in order to get this to work. Don't tell Ensiform, or I'll have to kill him. --eez
374 cameraFocusLoc[2] -= CAMERA_CROUCH_NUDGE*4;
375 }
376 }
377
378 // Add in the new viewheight
379 cameraFocusLoc[2] += cg.predicted_player_state.viewheight;
380 if ( cg.snap
381 && (cg.snap->ps.eFlags&EF_HELD_BY_SAND_CREATURE) )
382 {
383 VectorCopy( cameraFocusLoc, cameraIdealTarget );
384 cameraIdealTarget[2] += 192;
385 }
386 else if ( cg.snap
387 && (cg.snap->ps.eFlags&EF_HELD_BY_WAMPA) )
388 {
389 VectorCopy( cameraFocusLoc, cameraIdealTarget );
390 cameraIdealTarget[2] -= 48;
391 }
392 else if ( cg.overrides.active & CG_OVERRIDE_3RD_PERSON_VOF )
393 {
394 // Add in a vertical offset from the viewpoint, which puts the actual target above the head, regardless of angle.
395 VectorCopy( cameraFocusLoc, cameraIdealTarget );
396 cameraIdealTarget[2] += cg.overrides.thirdPersonVertOffset;
397 //VectorMA(cameraFocusLoc, cg.overrides.thirdPersonVertOffset, cameraup, cameraIdealTarget);
398 }
399 else
400 {
401 // Add in a vertical offset from the viewpoint, which puts the actual target above the head, regardless of angle.
402 VectorCopy( cameraFocusLoc, cameraIdealTarget );
403 cameraIdealTarget[2] += cg_thirdPersonVertOffset.value;
404 //VectorMA(cameraFocusLoc, cg_thirdPersonVertOffset.value, cameraup, cameraIdealTarget);
405 }
406
407 // Now, if the player is crouching, do a little special tweak. The problem is that the player's head is way out of his bbox.
408 if (cg.predicted_player_state.pm_flags & PMF_DUCKED)
409 { // Nudge to focus location up a tad.
410 vec3_t nudgepos;
411 trace_t trace;
412
413 VectorCopy(cameraFocusLoc, nudgepos);
414 nudgepos[2]+=CAMERA_CROUCH_NUDGE;
415 CG_Trace(&trace, cameraFocusLoc, cameramins, cameramaxs, nudgepos,
416 ( usesViewEntity ) ? cg.snap->ps.viewEntity : cg.predicted_player_state.clientNum, MASK_CAMERACLIP);
417 if (trace.fraction < 1.0)
418 {
419 VectorCopy(trace.endpos, cameraFocusLoc);
420 }
421 else
422 {
423 VectorCopy(nudgepos, cameraFocusLoc);
424 }
425 }
426 }
427
428
429
430 /*
431 ===============
432 CG_CalcIdealThirdPersonViewLocation
433
434 ===============
435 */
CG_CalcIdealThirdPersonViewLocation(void)436 static void CG_CalcIdealThirdPersonViewLocation(void)
437 {
438 if ( cg.overrides.active & CG_OVERRIDE_3RD_PERSON_RNG )
439 {
440 VectorMA(cameraIdealTarget, -(cg.overrides.thirdPersonRange), camerafwd, cameraIdealLoc);
441 }
442 else if ( cg.snap
443 && (cg.snap->ps.eFlags&EF_HELD_BY_RANCOR)
444 && cg_entities[cg.snap->ps.clientNum].gent->activator )
445 {//stay back
446 VectorMA(cameraIdealTarget, -180.0f*cg_entities[cg.snap->ps.clientNum].gent->activator->s.modelScale[0], camerafwd, cameraIdealLoc);
447 }
448 else if ( cg.snap
449 && (cg.snap->ps.eFlags&EF_HELD_BY_WAMPA)
450 && cg_entities[cg.snap->ps.clientNum].gent->activator
451 && cg_entities[cg.snap->ps.clientNum].gent->activator->inuse )
452 {//stay back
453 VectorMA(cameraIdealTarget, -120.0f*cg_entities[cg.snap->ps.clientNum].gent->activator->s.modelScale[0], camerafwd, cameraIdealLoc);
454 }
455 else if ( cg.snap
456 && (cg.snap->ps.eFlags&EF_HELD_BY_SAND_CREATURE)
457 && cg_entities[cg.snap->ps.clientNum].gent->activator )
458 {//stay back
459 VectorMA(cg_entities[cg_entities[cg.snap->ps.clientNum].gent->activator->s.number].lerpOrigin, -180.0f, camerafwd, cameraIdealLoc);
460 }
461 else
462 {
463 VectorMA(cameraIdealTarget, -(cg_thirdPersonRange.value), camerafwd, cameraIdealLoc);
464 }
465
466 if ( cg.renderingThirdPerson && (cg.snap->ps.forcePowersActive&(1<<FP_SPEED)) && player->client->ps.forcePowerDuration[FP_SPEED] )
467 {
468 float timeLeft = player->client->ps.forcePowerDuration[FP_SPEED] - cg.time;
469 float length = FORCE_SPEED_DURATION*forceSpeedValue[player->client->ps.forcePowerLevel[FP_SPEED]];
470 float amt = forceSpeedRangeMod[player->client->ps.forcePowerLevel[FP_SPEED]];
471 if ( timeLeft < 500 )
472 {//start going back
473 VectorMA(cameraIdealLoc, (timeLeft)/500*amt, camerafwd, cameraIdealLoc);
474 }
475 else if ( length - timeLeft < 1000 )
476 {//start zooming in
477 VectorMA(cameraIdealLoc, (length - timeLeft)/1000*amt, camerafwd, cameraIdealLoc);
478 }
479 else
480 {
481 VectorMA(cameraIdealLoc, amt, camerafwd, cameraIdealLoc);
482 }
483 }
484 }
485
486
487
CG_ResetThirdPersonViewDamp(void)488 static void CG_ResetThirdPersonViewDamp(void)
489 {
490 trace_t trace;
491
492 // Cap the pitch within reasonable limits
493 if (cameraFocusAngles[PITCH] > 89.0)
494 {
495 cameraFocusAngles[PITCH] = 89.0;
496 }
497 else if (cameraFocusAngles[PITCH] < -89.0)
498 {
499 cameraFocusAngles[PITCH] = -89.0;
500 }
501
502 AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup);
503
504 // Set the cameraIdealTarget
505 CG_CalcIdealThirdPersonViewTarget();
506
507 // Set the cameraIdealLoc
508 CG_CalcIdealThirdPersonViewLocation();
509
510 // Now, we just set everything to the new positions.
511 VectorCopy(cameraIdealLoc, cameraCurLoc);
512 VectorCopy(cameraIdealTarget, cameraCurTarget);
513
514 // First thing we do is trace from the first person viewpoint out to the new target location.
515 CG_Trace(&trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, cg.predicted_player_state.clientNum, MASK_CAMERACLIP);
516 if (trace.fraction <= 1.0)
517 {
518 VectorCopy(trace.endpos, cameraCurTarget);
519 }
520
521 // Now we trace from the new target location to the new view location, to make sure there is nothing in the way.
522 CG_Trace(&trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, cg.predicted_player_state.clientNum, MASK_CAMERACLIP);
523 if (trace.fraction <= 1.0)
524 {
525 VectorCopy(trace.endpos, cameraCurLoc);
526 }
527
528 cameraLastFrame = cg.time;
529 cameraLastYaw = cameraFocusAngles[YAW];
530 cameraStiffFactor = 0.0f;
531 }
532
533 // This is called every frame.
CG_UpdateThirdPersonTargetDamp(void)534 static void CG_UpdateThirdPersonTargetDamp(void)
535 {
536 trace_t trace;
537 vec3_t targetdiff;
538 float dampfactor, dtime, ratio;
539
540 // Set the cameraIdealTarget
541 // Automatically get the ideal target, to avoid jittering.
542 CG_CalcIdealThirdPersonViewTarget();
543
544 if ( CG_OnMovingPlat( &cg.snap->ps ) )
545 {//if moving on a plat, camera is *tight*
546 VectorCopy(cameraIdealTarget, cameraCurTarget);
547 }
548 else if (cg_thirdPersonTargetDamp.value>=1.0)//||cg.thisFrameTeleport)
549 { // No damping.
550 VectorCopy(cameraIdealTarget, cameraCurTarget);
551 }
552 else if (cg_thirdPersonTargetDamp.value>=0.0)
553 {
554 // Calculate the difference from the current position to the new one.
555 VectorSubtract(cameraIdealTarget, cameraCurTarget, targetdiff);
556
557 // Now we calculate how much of the difference we cover in the time allotted.
558 // The equation is (Damp)^(time)
559 dampfactor = 1.0-cg_thirdPersonTargetDamp.value; // We must exponent the amount LEFT rather than the amount bled off
560 dtime = (float)(cg.time-cameraLastFrame) * (1.0/cg_timescale.value) * (1.0/(float)CAMERA_DAMP_INTERVAL); // Our dampfactor is geared towards a time interval equal to "1".
561
562 // Note that since there are a finite number of "practical" delta millisecond values possible,
563 // the ratio should be initialized into a chart ultimately.
564 if ( cg_smoothCamera.integer )
565 ratio = powf( dampfactor, dtime );
566 else
567 ratio = Q_powf( dampfactor, dtime );
568
569 // This value is how much distance is "left" from the ideal.
570 VectorMA(cameraIdealTarget, -ratio, targetdiff, cameraCurTarget);
571 /////////////////////////////////////////////////////////////////////////////////////////////////////////
572 }
573
574 // Now we trace to see if the new location is cool or not.
575
576 // First thing we do is trace from the first person viewpoint out to the new target location.
577 if ( cg.snap
578 && (cg.snap->ps.eFlags&EF_HELD_BY_SAND_CREATURE)
579 && cg_entities[cg.snap->ps.clientNum].gent->activator )
580 {//if being held by a sand creature, trace from his actual origin, since we could be underground or otherwise in solid once he eats us
581 CG_Trace(&trace, cg_entities[cg_entities[cg.snap->ps.clientNum].gent->activator->s.number].lerpOrigin, cameramins, cameramaxs, cameraCurTarget, cg.predicted_player_state.clientNum, MASK_CAMERACLIP);
582 }
583 else
584 {
585 CG_Trace(&trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurTarget, cg.predicted_player_state.clientNum, MASK_CAMERACLIP);
586 }
587 if (trace.fraction < 1.0)
588 {
589 VectorCopy(trace.endpos, cameraCurTarget);
590 }
591
592 // Note that previously there was an upper limit to the number of physics traces that are done through the world
593 // for the sake of camera collision, since it wasn't calced per frame. Now it is calculated every frame.
594 // This has the benefit that the camera is a lot smoother now (before it lerped between tested points),
595 // however two full volume traces each frame is a bit scary to think about.
596 }
597
598 // This can be called every interval, at the user's discretion.
599 static int camWaterAdjust = 0;
CG_UpdateThirdPersonCameraDamp(void)600 static void CG_UpdateThirdPersonCameraDamp(void)
601 {
602 trace_t trace;
603 vec3_t locdiff;
604 float dampfactor, dtime, ratio;
605
606 // Set the cameraIdealLoc
607 CG_CalcIdealThirdPersonViewLocation();
608
609
610 // First thing we do is calculate the appropriate damping factor for the camera.
611 dampfactor=0.0f;
612 if ( CG_OnMovingPlat( &cg.snap->ps ) )
613 {//if moving on a plat, camera is *tight*
614 dampfactor=1.0f;
615 }
616 else if ( cg.overrides.active & CG_OVERRIDE_3RD_PERSON_CDP )
617 {
618 if ( cg.overrides.thirdPersonCameraDamp != 0.0f )
619 {
620 float pitch;
621
622 // Note that the camera pitch has already been capped off to 89.
623 pitch = Q_fabs(cameraFocusAngles[PITCH]);
624
625 // The higher the pitch, the larger the factor, so as you look up, it damps a lot less.
626 pitch /=115.0f;
627 dampfactor = (1.0-cg.overrides.thirdPersonCameraDamp)*(pitch*pitch);
628
629 dampfactor += cg.overrides.thirdPersonCameraDamp;
630 }
631 }
632 else if ( cg_thirdPersonCameraDamp.value != 0.0f )
633 {
634 float pitch;
635
636 // Note that the camera pitch has already been capped off to 89.
637 pitch = Q_fabs(cameraFocusAngles[PITCH]);
638
639 // The higher the pitch, the larger the factor, so as you look up, it damps a lot less.
640 pitch /= 115.0f;
641 dampfactor = (1.0-cg_thirdPersonCameraDamp.value)*(pitch*pitch);
642
643 dampfactor += cg_thirdPersonCameraDamp.value;
644
645 // Now we also multiply in the stiff factor, so that faster yaw changes are stiffer.
646 if (cameraStiffFactor > 0.0f)
647 { // The cameraStiffFactor is how much of the remaining damp below 1 should be shaved off, i.e. approach 1 as stiffening increases.
648 dampfactor += (1.0-dampfactor)*cameraStiffFactor;
649 }
650 }
651
652 if (dampfactor>=1.0)//||cg.thisFrameTeleport)
653 { // No damping.
654 VectorCopy(cameraIdealLoc, cameraCurLoc);
655 }
656 else if (dampfactor>=0.0)
657 {
658 // Calculate the difference from the current position to the new one.
659 VectorSubtract(cameraIdealLoc, cameraCurLoc, locdiff);
660
661 // Now we calculate how much of the difference we cover in the time allotted.
662 // The equation is (Damp)^(time)
663 dampfactor = 1.0-dampfactor; // We must exponent the amount LEFT rather than the amount bled off
664 dtime = (float)(cg.time-cameraLastFrame) * (1.0/cg_timescale.value) * (1.0/(float)CAMERA_DAMP_INTERVAL); // Our dampfactor is geared towards a time interval equal to "1".
665
666 // Note that since there are a finite number of "practical" delta millisecond values possible,
667 // the ratio should be initialized into a chart ultimately.
668 if ( cg_smoothCamera.integer )
669 ratio = powf( dampfactor, dtime );
670 else
671 ratio = Q_powf( dampfactor, dtime );
672
673 // This value is how much distance is "left" from the ideal.
674 VectorMA(cameraIdealLoc, -ratio, locdiff, cameraCurLoc);
675 /////////////////////////////////////////////////////////////////////////////////////////////////////////
676 }
677
678 // Now we trace from the first person viewpoint to the new view location, to make sure there is nothing in the way between the user and the camera...
679 // CG_Trace(&trace, cameraFocusLoc, cameramins, cameramaxs, cameraCurLoc, cg.predicted_player_state.clientNum, MASK_CAMERACLIP);
680 // (OLD) Now we trace from the new target location to the new view location, to make sure there is nothing in the way.
681 if ( cg.snap
682 && (cg.snap->ps.eFlags&EF_HELD_BY_SAND_CREATURE)
683 && cg_entities[cg.snap->ps.clientNum].gent->activator )
684 {//if being held by a sand creature, trace from his actual origin, since we could be underground or otherwise in solid once he eats us
685 CG_Trace( &trace, cg_entities[cg_entities[cg.snap->ps.clientNum].gent->activator->s.number].lerpOrigin, cameramins, cameramaxs, cameraCurLoc, cg.predicted_player_state.clientNum, MASK_CAMERACLIP);
686 }
687 else
688 {
689 CG_Trace( &trace, cameraCurTarget, cameramins, cameramaxs, cameraCurLoc, cg.predicted_player_state.clientNum, MASK_CAMERACLIP);
690 }
691 if ( trace.fraction < 1.0f )
692 {
693 VectorCopy( trace.endpos, cameraCurLoc );
694
695 // We didn't trace all the way back, so push down the target accordingly.
696 // VectorSubtract(cameraCurTarget, cameraFocusLoc, locdiff);
697 // VectorMA(cameraFocusLoc, trace.fraction, locdiff, cameraCurTarget);
698
699 //FIXME: when the trace hits movers, it gets very very jaggy... ?
700 /*
701 //this doesn't actually help any
702 if ( trace.entityNum != ENTITYNUM_WORLD )
703 {
704 centity_t *cent = &cg_entities[trace.entityNum];
705 gentity_t *gent = &g_entities[trace.entityNum];
706 if ( cent != NULL && gent != NULL )
707 {
708 if ( cent->currentState.pos.trType == TR_LINEAR || cent->currentState.pos.trType == TR_LINEAR_STOP )
709 {
710 vec3_t diff;
711 VectorSubtract( cent->lerpOrigin, gent->currentOrigin, diff );
712 VectorAdd( cameraCurLoc, diff, cameraCurLoc );
713 }
714 }
715 }
716 */
717 }
718
719 // Note that previously there was an upper limit to the number of physics traces that are done through the world
720 // for the sake of camera collision, since it wasn't calced per frame. Now it is calculated every frame.
721 // This has the benefit that the camera is a lot smoother now (before it lerped between tested points),
722 // however two full volume traces each frame is a bit scary to think about.
723 }
724
725
726
727
728 /*
729 ===============
730 CG_OffsetThirdPersonView
731
732 ===============
733 */
734 extern qboolean MatrixMode;
CG_OffsetThirdPersonView(void)735 static void CG_OffsetThirdPersonView( void )
736 {
737 vec3_t diff;
738 float deltayaw;
739
740 camWaterAdjust = 0;
741 cameraStiffFactor = 0.0;
742
743 // Set camera viewing direction.
744 VectorCopy( cg.refdefViewAngles, cameraFocusAngles );
745
746 if ( cg.snap
747 && (cg.snap->ps.eFlags&EF_HELD_BY_RANCOR)
748 && cg_entities[cg.snap->ps.clientNum].gent->activator )
749 {
750 centity_t *monster = &cg_entities[cg_entities[cg.snap->ps.clientNum].gent->activator->s.number];
751 VectorSet( cameraFocusAngles, 0, AngleNormalize180(monster->lerpAngles[YAW]+180), 0 );
752 }
753 else if ( cg.snap && (cg.snap->ps.eFlags&EF_HELD_BY_SAND_CREATURE) )
754 {
755 centity_t *monster = &cg_entities[cg_entities[cg.snap->ps.clientNum].gent->activator->s.number];
756 VectorSet( cameraFocusAngles, 0, AngleNormalize180(monster->lerpAngles[YAW]+180), 0 );
757 cameraFocusAngles[PITCH] = 0.0f;//flatten it out
758 }
759 else if ( G_IsRidingVehicle( &g_entities[0] ) )
760 {
761 cameraFocusAngles[YAW] = cg_entities[g_entities[0].owner->s.number].lerpAngles[YAW];
762 if ( cg.overrides.active & CG_OVERRIDE_3RD_PERSON_ANG )
763 {
764 cameraFocusAngles[YAW] += cg.overrides.thirdPersonAngle;
765 }
766 else
767 {
768 cameraFocusAngles[YAW] += cg_thirdPersonAngle.value;
769 }
770 }
771 else if ( cg.predicted_player_state.stats[STAT_HEALTH] <= 0 )
772 {// if dead, look at killer
773 if ( MatrixMode )
774 {
775 if ( cg.overrides.active & CG_OVERRIDE_3RD_PERSON_ANG )
776 {
777 cameraFocusAngles[YAW] += cg.overrides.thirdPersonAngle;
778 }
779 else
780 {
781 cameraFocusAngles[YAW] = cg.predicted_player_state.stats[STAT_DEAD_YAW];
782 cameraFocusAngles[YAW] += cg_thirdPersonAngle.value;
783 }
784 }
785 else
786 {
787 cameraFocusAngles[YAW] = cg.predicted_player_state.stats[STAT_DEAD_YAW];
788 }
789 }
790 else
791 { // Add in the third Person Angle.
792 if ( cg.overrides.active & CG_OVERRIDE_3RD_PERSON_ANG )
793 {
794 cameraFocusAngles[YAW] += cg.overrides.thirdPersonAngle;
795 }
796 else
797 {
798 cameraFocusAngles[YAW] += cg_thirdPersonAngle.value;
799 }
800 if ( cg.overrides.active & CG_OVERRIDE_3RD_PERSON_POF )
801 {
802 cameraFocusAngles[PITCH] += cg.overrides.thirdPersonPitchOffset;
803 }
804 else
805 {
806 cameraFocusAngles[PITCH] += cg_thirdPersonPitchOffset.value;
807 }
808 }
809
810 if ( !cg.renderingThirdPerson && (cg.snap->ps.weapon == WP_SABER||cg.snap->ps.weapon == WP_MELEE) )
811 {// First person saber
812 // FIXME: use something network-friendly
813 vec3_t org, viewDir;
814 VectorCopy( cg_entities[0].gent->client->renderInfo.eyePoint, org );
815 float blend = 1.0f - fabs(cg.refdefViewAngles[PITCH])/90.0f;
816 AngleVectors( cg.refdefViewAngles, viewDir, NULL, NULL );
817 VectorMA( org, -8, viewDir, org );
818 VectorScale( org, 1.0f - blend, org );
819 VectorMA( org, blend, cg.refdef.vieworg, cg.refdef.vieworg );
820 return;
821 }
822 // The next thing to do is to see if we need to calculate a new camera target location.
823
824 // If we went back in time for some reason, or if we just started, reset the sample.
825 if (cameraLastFrame == 0 || cameraLastFrame > cg.time)
826 {
827 CG_ResetThirdPersonViewDamp();
828 }
829 else
830 {
831 // Cap the pitch within reasonable limits
832 if (cameraFocusAngles[PITCH] > 89.0)
833 {
834 cameraFocusAngles[PITCH] = 89.0;
835 }
836 else if (cameraFocusAngles[PITCH] < -89.0)
837 {
838 cameraFocusAngles[PITCH] = -89.0;
839 }
840
841 AngleVectors(cameraFocusAngles, camerafwd, NULL, cameraup);
842
843 deltayaw = fabs(cameraFocusAngles[YAW] - cameraLastYaw);
844 if (deltayaw > 180.0f)
845 { // Normalize this angle so that it is between 0 and 180.
846 deltayaw = fabs(deltayaw - 360.0f);
847 }
848 cameraStiffFactor = deltayaw / (float)(cg.time-cameraLastFrame);
849 if (cameraStiffFactor < 1.0)
850 {
851 cameraStiffFactor = 0.0;
852 }
853 else if (cameraStiffFactor > 2.5)
854 {
855 cameraStiffFactor = 0.75;
856 }
857 else
858 { // 1 to 2 scales from 0.0 to 0.5
859 cameraStiffFactor = (cameraStiffFactor-1.0f)*0.5f;
860 }
861 cameraLastYaw = cameraFocusAngles[YAW];
862
863 // Move the target to the new location.
864 CG_UpdateThirdPersonTargetDamp();
865 CG_UpdateThirdPersonCameraDamp();
866 }
867
868 // Now interestingly, the Quake method is to calculate a target focus point above the player, and point the camera at it.
869 // We won't do that for now.
870
871 // We must now take the angle taken from the camera target and location.
872 VectorSubtract(cameraCurTarget, cameraCurLoc, diff);
873 //Com_Printf( "%s\n", vtos(diff) );
874 float dist = VectorNormalize(diff);
875 if ( dist < 1.0f )
876 {//must be hitting something, need some value to calc angles, so use cam forward
877 VectorCopy( camerafwd, diff );
878 }
879 vectoangles(diff, cg.refdefViewAngles);
880
881 // Temp: just move the camera to the side a bit
882 extern vmCvar_t cg_thirdPersonHorzOffset;
883 if ( cg_thirdPersonHorzOffset.value != 0.0f )
884 {
885 AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
886 VectorMA( cameraCurLoc, cg_thirdPersonHorzOffset.value, cg.refdef.viewaxis[1], cameraCurLoc );
887 }
888
889 // ...and of course we should copy the new view location to the proper spot too.
890 VectorCopy(cameraCurLoc, cg.refdef.vieworg);
891
892 //if we hit the water, do a last-minute adjustment
893 if ( camWaterAdjust )
894 {
895 cg.refdef.vieworg[2] += camWaterAdjust;
896 }
897 cameraLastFrame=cg.time;
898 }
899
900
901
902 /*
903 ===============
904 CG_OffsetThirdPersonView
905
906 ===============
907 */
908 /*
909 #define FOCUS_DISTANCE 512
910 static void CG_OffsetThirdPersonView( void ) {
911 vec3_t forward, right, up;
912 vec3_t view;
913 vec3_t focusAngles;
914 trace_t trace;
915 static vec3_t mins = { -4, -4, -4 };
916 static vec3_t maxs = { 4, 4, 4 };
917 vec3_t focusPoint;
918 float focusDist;
919 float forwardScale, sideScale;
920
921 cg.refdef.vieworg[2] += cg.predicted_player_state.viewheight;
922
923 VectorCopy( cg.refdefViewAngles, focusAngles );
924
925 // if dead, look at killer
926 if ( cg.predicted_player_state.stats[STAT_HEALTH] <= 0 ) {
927 focusAngles[YAW] = cg.predicted_player_state.stats[STAT_DEAD_YAW];
928 cg.refdefViewAngles[YAW] = cg.predicted_player_state.stats[STAT_DEAD_YAW];
929 }
930
931 if ( focusAngles[PITCH] > 45 ) {
932 focusAngles[PITCH] = 45; // don't go too far overhead
933 }
934 AngleVectors( focusAngles, forward, NULL, NULL );
935
936 VectorMA( cg.refdef.vieworg, FOCUS_DISTANCE, forward, focusPoint );
937
938 VectorCopy( cg.refdef.vieworg, view );
939
940 view[2] += 8;
941
942 cg.refdefViewAngles[PITCH] *= 0.5;
943
944 AngleVectors( cg.refdefViewAngles, forward, right, up );
945
946 float tpAngle = cg.overrides.thirdPersonAngle ? cg.overrides.thirdPersonAngle : cg_thirdPersonAngle.value;
947 forwardScale = cos( tpAngle / 180 * M_PI );
948 sideScale = sin( tpAngle / 180 * M_PI );
949 VectorMA( view, -tpAngle * forwardScale, forward, view );
950 VectorMA( view, -tpAngle * sideScale, right, view );
951
952 // trace a ray from the origin to the viewpoint to make sure the view isn't
953 // in a solid block. Use an 8 by 8 block to prevent the view from near clipping anything
954
955 CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predicted_player_state.clientNum, MASK_CAMERACLIP );
956
957 if ( trace.fraction != 1.0 ) {
958 VectorCopy( trace.endpos, view );
959 view[2] += (1.0 - trace.fraction) * 32;
960 // try another trace to this position, because a tunnel may have the ceiling
961 // close enogh that this is poking out
962
963 CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predicted_player_state.clientNum, MASK_CAMERACLIP );
964 VectorCopy( trace.endpos, view );
965 }
966
967
968 VectorCopy( view, cg.refdef.vieworg );
969
970 // select pitch to look at focus point from vieword
971 VectorSubtract( focusPoint, cg.refdef.vieworg, focusPoint );
972 focusDist = sqrt( focusPoint[0] * focusPoint[0] + focusPoint[1] * focusPoint[1] );
973 if ( focusDist < 1 ) {
974 focusDist = 1; // should never happen
975 }
976 cg.refdefViewAngles[PITCH] = -180 / M_PI * atan2( focusPoint[2], focusDist );
977 cg.refdefViewAngles[YAW] -= tpAngle;
978 }
979
980
981 #define MIN_CAMERA_HEIGHT 75
982 #define MIN_CAMERA_PITCH 40
983 #define MAX_CAMERA_PITCH 90
984
985 //----------------------------------------------
986 static void CG_OffsetThirdPersonOverheadView( void ) {
987 vec3_t view, angs;
988 trace_t trace;
989 static vec3_t mins = { -4, -4, -4 };
990 static vec3_t maxs = { 4, 4, 4 };
991
992 VectorCopy( cg.refdef.vieworg, view );
993
994 // Move straight up from the player, making sure to always go at least the min camera height,
995 // otherwise, the camera will clip into the head of the player.
996 float tpRange = cg.overrides.thirdPersonRange ? cg.overrides.thirdPersonRange : cg_thirdPersonRange.value;
997 if ( tpRange < MIN_CAMERA_HEIGHT )
998 {
999 view[2] += MIN_CAMERA_HEIGHT;
1000 }
1001 else
1002 {
1003 view[2] += tpRange;
1004 }
1005
1006 // Now adjust the camera angles, but we shouldn't adjust the viewAngles...only the viewAxis
1007 VectorCopy( cg.refdefViewAngles, angs );
1008 angs[PITCH] = cg.overrides.thirdPersonAngle ? cg.overrides.thirdPersonAngle : cg_thirdPersonAngle.value;
1009
1010 // Simple clamp to weed out any really obviously nasty angles
1011 if ( angs[PITCH] < MIN_CAMERA_PITCH )
1012 {
1013 angs[PITCH] = MIN_CAMERA_PITCH;
1014 }
1015 else if ( angs[PITCH] > MAX_CAMERA_PITCH )
1016 {
1017 angs[PITCH] = MAX_CAMERA_PITCH;
1018 }
1019
1020 // Convert our new desired camera angles and store them where they will get used by the engine
1021 // when setting up the actual camera view.
1022 AnglesToAxis( angs, cg.refdef.viewaxis );
1023 cg.refdefViewAngles[PITCH] = 0;
1024 g_entities[0].client->ps.delta_angles[PITCH] = 0;
1025
1026 // Trace a ray from the origin to the viewpoint to make sure the view isn't
1027 // in a solid block.
1028 CG_Trace( &trace, cg.refdef.vieworg, mins, maxs, view, cg.predicted_player_state.clientNum, MASK_CAMERACLIP);
1029
1030 if ( trace.fraction != 1.0 )
1031 {
1032 VectorCopy( trace.endpos, cg.refdef.vieworg );
1033 }
1034 else
1035 {
1036 VectorCopy( view, cg.refdef.vieworg );
1037 }
1038 }
1039 */
1040 // this causes a compiler bug on mac MrC compiler
CG_StepOffset(void)1041 static void CG_StepOffset( void ) {
1042 int timeDelta;
1043
1044 // smooth out stair climbing
1045 timeDelta = cg.time - cg.stepTime;
1046 if ( timeDelta < STEP_TIME ) {
1047 cg.refdef.vieworg[2] -= cg.stepChange
1048 * (STEP_TIME - timeDelta) / STEP_TIME;
1049 }
1050 }
1051
1052 /*
1053 ===============
1054 CG_OffsetFirstPersonView
1055
1056 ===============
1057 */
1058 extern qboolean PM_InForceGetUp( playerState_t *ps );
1059 extern qboolean PM_InGetUp( playerState_t *ps );
1060 extern qboolean PM_InKnockDown( playerState_t *ps );
1061 extern int PM_AnimLength( int index, animNumber_t anim );
CG_OffsetFirstPersonView(qboolean firstPersonSaber)1062 static void CG_OffsetFirstPersonView( qboolean firstPersonSaber ) {
1063 float *origin;
1064 float *angles;
1065 float bob;
1066 float ratio;
1067 float delta;
1068 float speed;
1069 float f;
1070 vec3_t predictedVelocity;
1071 int timeDelta;
1072
1073 if ( cg.snap->ps.pm_type == PM_INTERMISSION ) {
1074 return;
1075 }
1076
1077 origin = cg.refdef.vieworg;
1078 angles = cg.refdefViewAngles;
1079
1080 // if dead, fix the angle and don't add any kick
1081 if ( cg.snap->ps.stats[STAT_HEALTH] <= 0 )
1082 {
1083 angles[ROLL] = 40;
1084 angles[PITCH] = -15;
1085 angles[YAW] = cg.snap->ps.stats[STAT_DEAD_YAW];
1086 origin[2] += cg.predicted_player_state.viewheight;
1087 return;
1088 }
1089
1090 if ( g_entities[0].client && PM_InKnockDown( &g_entities[0].client->ps ) )
1091 {
1092 float perc, animLen = (float)PM_AnimLength( g_entities[0].client->clientInfo.animFileIndex, (animNumber_t)g_entities[0].client->ps.legsAnim );
1093 if ( PM_InGetUp( &g_entities[0].client->ps ) || PM_InForceGetUp( &g_entities[0].client->ps ) )
1094 {//start righting the view
1095 perc = (float)g_entities[0].client->ps.legsAnimTimer/animLen*2;
1096 }
1097 else
1098 {//tilt the view
1099 perc = (animLen-g_entities[0].client->ps.legsAnimTimer)/animLen*2;
1100 }
1101 if ( perc > 1.0f )
1102 {
1103 perc = 1.0f;
1104 }
1105 angles[ROLL] = perc*40;
1106 angles[PITCH] = perc*-15;
1107 }
1108
1109 // add angles based on weapon kick
1110 int kickTime = (cg.time - cg.kick_time);
1111 if ( kickTime < 800 )
1112 {//kicks are always 1 second long. Deal with it.
1113 float kickPerc = 0.0f;
1114 if ( kickTime <= 200 )
1115 {//winding up
1116 kickPerc = kickTime/200.0f;
1117 }
1118 else
1119 {//returning to normal
1120 kickTime = 800 - kickTime;
1121 kickPerc = kickTime/600.0f;
1122 }
1123 VectorMA( angles, kickPerc, cg.kick_angles, angles );
1124 }
1125
1126 // add angles based on damage kick
1127 if ( cg.damageTime ) {
1128 ratio = cg.time - cg.damageTime;
1129 if ( ratio < DAMAGE_DEFLECT_TIME ) {
1130 ratio /= DAMAGE_DEFLECT_TIME;
1131 angles[PITCH] += ratio * cg.v_dmg_pitch;
1132 angles[ROLL] += ratio * cg.v_dmg_roll;
1133 } else {
1134 ratio = 1.0 - ( ratio - DAMAGE_DEFLECT_TIME ) / DAMAGE_RETURN_TIME;
1135 if ( ratio > 0 ) {
1136 angles[PITCH] += ratio * cg.v_dmg_pitch;
1137 angles[ROLL] += ratio * cg.v_dmg_roll;
1138 }
1139 }
1140 }
1141
1142 // add pitch based on fall kick
1143 #if 0
1144 ratio = ( cg.time - cg.landTime) / FALL_TIME;
1145 if (ratio < 0)
1146 ratio = 0;
1147 angles[PITCH] += ratio * cg.fall_value;
1148 #endif
1149
1150 // add angles based on velocity
1151 VectorCopy( cg.predicted_player_state.velocity, predictedVelocity );
1152
1153 delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[0]);
1154 angles[PITCH] += delta * cg_runpitch.value;
1155
1156 delta = DotProduct ( predictedVelocity, cg.refdef.viewaxis[1]);
1157 angles[ROLL] -= delta * cg_runroll.value;
1158
1159 // add angles based on bob
1160
1161 // make sure the bob is visible even at low speeds
1162 speed = cg.xyspeed > 200 ? cg.xyspeed : 200;
1163
1164 delta = cg.bobfracsin * cg_bobpitch.value * speed;
1165 if (cg.predicted_player_state.pm_flags & PMF_DUCKED)
1166 delta *= 3; // crouching
1167 angles[PITCH] += delta;
1168 delta = cg.bobfracsin * cg_bobroll.value * speed;
1169 if (cg.predicted_player_state.pm_flags & PMF_DUCKED)
1170 delta *= 3; // crouching accentuates roll
1171 if (cg.bobcycle & 1)
1172 delta = -delta;
1173 angles[ROLL] += delta;
1174
1175 //===================================
1176
1177 if ( !firstPersonSaber )//First person saber
1178 {
1179 // add view height
1180 if ( cg.snap->ps.viewEntity > 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD )
1181 {
1182 if ( &g_entities[cg.snap->ps.viewEntity] &&
1183 g_entities[cg.snap->ps.viewEntity].client &&
1184 g_entities[cg.snap->ps.viewEntity].client->ps.viewheight )
1185 {
1186 origin[2] += g_entities[cg.snap->ps.viewEntity].client->ps.viewheight;
1187 }
1188 else
1189 {
1190 origin[2] += 4;//???
1191 }
1192 }
1193 else
1194 {
1195 origin[2] += cg.predicted_player_state.viewheight;
1196 }
1197 }
1198
1199 // smooth out duck height changes
1200 timeDelta = cg.time - cg.duckTime;
1201 if ( timeDelta < DUCK_TIME) {
1202 cg.refdef.vieworg[2] -= cg.duckChange * (DUCK_TIME - timeDelta) / DUCK_TIME;
1203 }
1204
1205 // add bob height
1206 bob = cg.bobfracsin * cg.xyspeed * cg_bobup.value;
1207 if (bob > 6) {
1208 bob = 6;
1209 }
1210
1211 origin[2] += bob;
1212
1213
1214 // add fall height
1215 delta = cg.time - cg.landTime;
1216 if ( delta < LAND_DEFLECT_TIME ) {
1217 f = delta / LAND_DEFLECT_TIME;
1218 cg.refdef.vieworg[2] += cg.landChange * f;
1219 } else if ( delta < LAND_DEFLECT_TIME + LAND_RETURN_TIME ) {
1220 delta -= LAND_DEFLECT_TIME;
1221 f = 1.0 - ( delta / LAND_RETURN_TIME );
1222 cg.refdef.vieworg[2] += cg.landChange * f;
1223 }
1224
1225 // add step offset
1226 CG_StepOffset();
1227
1228 if(cg.snap->ps.leanofs != 0)
1229 {
1230 vec3_t right;
1231 //add leaning offset
1232 //FIXME: when crouching, this bounces up and down?!
1233 cg.refdefViewAngles[2] += (float)cg.snap->ps.leanofs/2;
1234 AngleVectors(cg.refdefViewAngles, NULL, right, NULL);
1235 VectorMA(cg.refdef.vieworg, (float)cg.snap->ps.leanofs, right, cg.refdef.vieworg);
1236 }
1237
1238 // pivot the eye based on a neck length
1239 #if 0
1240 {
1241 #define NECK_LENGTH 8
1242 vec3_t forward, up;
1243
1244 cg.refdef.vieworg[2] -= NECK_LENGTH;
1245 AngleVectors( cg.refdefViewAngles, forward, NULL, up );
1246 VectorMA( cg.refdef.vieworg, 3, forward, cg.refdef.vieworg );
1247 VectorMA( cg.refdef.vieworg, NECK_LENGTH, up, cg.refdef.vieworg );
1248 }
1249 #endif
1250 }
1251
1252
1253 /*
1254 ====================
1255 CG_CalcFovFromX
1256
1257 Calcs Y FOV from given X FOV
1258 ====================
1259 */
CG_CalcFOVFromX(float fov_x)1260 qboolean CG_CalcFOVFromX( float fov_x )
1261 {
1262 float x;
1263 float fov_y;
1264 qboolean inwater;
1265
1266 if ( cg_fovAspectAdjust.integer ) {
1267 // Based on LordHavoc's code for Darkplaces
1268 // http://www.quakeworld.nu/forum/topic/53/what-does-your-qw-look-like/page/30
1269 const float baseAspect = 0.75f; // 3/4
1270 const float aspect = (float)cgs.glconfig.vidWidth/(float)cgs.glconfig.vidHeight;
1271 const float desiredFov = fov_x;
1272
1273 fov_x = atan( tan( desiredFov*M_PI / 360.0f ) * baseAspect*aspect )*360.0f / M_PI;
1274 }
1275
1276 x = cg.refdef.width / tan( fov_x / 360 * M_PI );
1277 fov_y = atan2( cg.refdef.height, x );
1278 fov_y = fov_y * 360 / M_PI;
1279
1280 // there's a problem with this, it only takes the leafbrushes into account, not the entity brushes,
1281 // so if you give slime/water etc properties to a func_door area brush in order to move the whole water
1282 // level up/down this doesn't take into account the door position, so warps the view the whole time
1283 // whether the water is up or not.
1284 // warp if underwater
1285 float phase;
1286 float v;
1287 cg.refdef.viewContents = 0;
1288 if (gi.totalMapContents() & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ))
1289 {
1290 cg.refdef.viewContents = CG_PointContents( cg.refdef.vieworg, -1 );
1291 }
1292 if ( cg.refdef.viewContents & ( CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA ) )
1293 {
1294 phase = cg.time / 1000.0 * WAVE_FREQUENCY * M_PI * 2;
1295 v = WAVE_AMPLITUDE * sin( phase );
1296 fov_x += v;
1297 fov_y -= v;
1298 inwater = qtrue;
1299 }
1300 else
1301 {
1302 inwater = qfalse;
1303 }
1304
1305 // see if we are drugged by an interrogator. We muck with the FOV here, a bit later, after viewangles are calc'ed, I muck with those too.
1306 if ( cg.wonkyTime > 0 && cg.wonkyTime > cg.time )
1307 {
1308 float perc = (float)(cg.wonkyTime - cg.time) / 10000.0f; // goes for 10 seconds
1309
1310 fov_x += ( 25.0f * perc );
1311 fov_y -= ( cos( cg.time * 0.0008f ) * 5.0f * perc );
1312 }
1313
1314 // set it
1315 cg.refdef.fov_x = fov_x;
1316 cg.refdef.fov_y = fov_y;
1317
1318 return (inwater);
1319 }
1320
CG_ForceSpeedFOV(void)1321 float CG_ForceSpeedFOV( void )
1322 {
1323 float fov;
1324 float timeLeft = player->client->ps.forcePowerDuration[FP_SPEED] - cg.time;
1325 float length = FORCE_SPEED_DURATION*forceSpeedValue[player->client->ps.forcePowerLevel[FP_SPEED]];
1326 float amt = forceSpeedFOVMod[player->client->ps.forcePowerLevel[FP_SPEED]];
1327 if ( timeLeft < 500 )
1328 {//start going back
1329 fov = cg_fov.value + (timeLeft)/500*amt;
1330 }
1331 else if ( length - timeLeft < 1000 )
1332 {//start zooming in
1333 fov = cg_fov.value + (length - timeLeft)/1000*amt;
1334 }
1335 else
1336 {//stay at this FOV
1337 fov = cg_fov.value+amt;
1338 }
1339 return fov;
1340 }
1341 /*
1342 ====================
1343 CG_CalcFov
1344
1345 Fixed fov at intermissions, otherwise account for fov variable and zooms.
1346 ====================
1347 */
CG_CalcFov(void)1348 static qboolean CG_CalcFov( void ) {
1349 float fov_x;
1350 float f;
1351
1352 if ( cg.predicted_player_state.pm_type == PM_INTERMISSION ) {
1353 // if in intermission, use a fixed value
1354 fov_x = 80;
1355 }
1356 else if ( cg.snap
1357 && cg.snap->ps.viewEntity > 0
1358 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD
1359 && (!cg.renderingThirdPerson || g_entities[cg.snap->ps.viewEntity].e_DieFunc == dieF_camera_die) )
1360 {
1361 // if in entity camera view, use a special FOV
1362 if ( &g_entities[cg.snap->ps.viewEntity] &&
1363 g_entities[cg.snap->ps.viewEntity].NPC )
1364 {//FIXME: looks bad when take over a jedi... but never really do that, do we?
1365 fov_x = g_entities[cg.snap->ps.viewEntity].NPC->stats.hfov;
1366 //sanity-cap?
1367 if ( fov_x > 120 )
1368 {
1369 fov_x = 120;
1370 }
1371 else if ( fov_x < 10 )
1372 {
1373 fov_x = 10;
1374 }
1375 }
1376 else
1377 {
1378 if ( cg.overrides.active & CG_OVERRIDE_FOV )
1379 {
1380 fov_x = cg.overrides.fov;
1381 }
1382 else
1383 {
1384 fov_x = 120;//FIXME: read from the NPC's fov stats?
1385 }
1386 }
1387 }
1388 else if ( (!cg.zoomMode || cg.zoomMode > 2) && (cg.snap->ps.forcePowersActive&(1<<FP_SPEED)) && player->client->ps.forcePowerDuration[FP_SPEED] )//cg.renderingThirdPerson &&
1389 {
1390 fov_x = CG_ForceSpeedFOV();
1391 } else {
1392 // user selectable
1393 if ( cg.overrides.active & CG_OVERRIDE_FOV )
1394 {
1395 fov_x = cg.overrides.fov;
1396 }
1397 else
1398 {
1399 fov_x = cg_fov.value;
1400 }
1401 if ( fov_x < 1 ) {
1402 fov_x = 1;
1403 } else if ( fov_x > 160 ) {
1404 fov_x = 160;
1405 }
1406
1407 // Disable zooming when in third person
1408 if ( cg.zoomMode && cg.zoomMode < 3 )//&& !cg.renderingThirdPerson ) // light amp goggles do none of the zoom silliness
1409 {
1410 if ( !cg.zoomLocked )
1411 {
1412 if ( cg.zoomMode == 1 )
1413 {
1414 // binoculars zooming either in or out
1415 cg_zoomFov += cg.zoomDir * cg.frametime * 0.05f;
1416 }
1417 else
1418 {
1419 // disruptor zooming in faster
1420 cg_zoomFov -= cg.frametime * 0.075f;
1421 }
1422
1423 // Clamp zoomFov
1424 float actualFOV = (cg.overrides.active&CG_OVERRIDE_FOV) ? cg.overrides.fov : cg_fov.value;
1425 if ( cg_zoomFov < MAX_ZOOM_FOV )
1426 {
1427 cg_zoomFov = MAX_ZOOM_FOV;
1428 }
1429 else if ( cg_zoomFov > actualFOV )
1430 {
1431 cg_zoomFov = actualFOV;
1432 }
1433 else
1434 {//still zooming
1435 static int zoomSoundTime = 0;
1436
1437 if ( zoomSoundTime < cg.time )
1438 {
1439 sfxHandle_t snd;
1440
1441 if ( cg.zoomMode == 1 )
1442 {
1443 snd = cgs.media.zoomLoop;
1444 }
1445 else
1446 {
1447 snd = cgs.media.disruptorZoomLoop;
1448 }
1449
1450 // huh? This could probably just be added as a looping sound??
1451 cgi_S_StartSound( cg.refdef.vieworg, ENTITYNUM_WORLD, CHAN_LOCAL, snd );
1452 zoomSoundTime = cg.time + 150;
1453 }
1454 }
1455 }
1456
1457 fov_x = cg_zoomFov;
1458 } else {
1459 f = ( cg.time - cg.zoomTime ) / ZOOM_OUT_TIME;
1460 if ( f <= 1.0 ) {
1461 fov_x = cg_zoomFov + f * ( fov_x - cg_zoomFov );
1462 }
1463 }
1464 }
1465
1466 // g_fov = fov_x;
1467 return ( CG_CalcFOVFromX( fov_x ) );
1468 }
1469
1470
1471
1472 /*
1473 ===============
1474 CG_DamageBlendBlob
1475
1476 ===============
1477 */
CG_DamageBlendBlob(void)1478 static void CG_DamageBlendBlob( void )
1479 {
1480 int t;
1481 int maxTime;
1482 refEntity_t ent;
1483
1484 if ( !cg.damageValue ) {
1485 return;
1486 }
1487
1488 maxTime = DAMAGE_TIME;
1489 t = cg.time - cg.damageTime;
1490 if ( t <= 0 || t >= maxTime ) {
1491 return;
1492 }
1493
1494 memset( &ent, 0, sizeof( ent ) );
1495 ent.reType = RT_SPRITE;
1496 ent.renderfx = RF_FIRST_PERSON;
1497
1498 VectorMA( cg.refdef.vieworg, 8, cg.refdef.viewaxis[0], ent.origin );
1499 VectorMA( ent.origin, cg.damageX * -8, cg.refdef.viewaxis[1], ent.origin );
1500 VectorMA( ent.origin, cg.damageY * 8, cg.refdef.viewaxis[2], ent.origin );
1501
1502 ent.radius = cg.damageValue * 3 * ( 1.0 - ((float)t / maxTime) );
1503 ent.customShader = cgs.media.damageBlendBlobShader;
1504 ent.shaderRGBA[0] = 180 * ( 1.0 - ((float)t / maxTime) );
1505 ent.shaderRGBA[1] = 50 * ( 1.0 - ((float)t / maxTime) );
1506 ent.shaderRGBA[2] = 50 * ( 1.0 - ((float)t / maxTime) );
1507 ent.shaderRGBA[3] = 255;
1508
1509 cgi_R_AddRefEntityToScene( &ent );
1510 }
1511
1512 /*
1513 ====================
1514 CG_SaberClashFlare
1515 ====================
1516 */
1517 extern int g_saberFlashTime;
1518 extern vec3_t g_saberFlashPos;
1519 extern qboolean CG_WorldCoordToScreenCoord(vec3_t worldCoord, int *x, int *y);
CG_SaberClashFlare(void)1520 void CG_SaberClashFlare( void )
1521 {
1522 int t, maxTime = 150;
1523
1524 t = cg.time - g_saberFlashTime;
1525
1526 if ( t <= 0 || t >= maxTime )
1527 {
1528 return;
1529 }
1530
1531 vec3_t dif;
1532
1533 // Don't do clashes for things that are behind us
1534 VectorSubtract( g_saberFlashPos, cg.refdef.vieworg, dif );
1535
1536 if ( DotProduct( dif, cg.refdef.viewaxis[0] ) < 0.2 )
1537 {
1538 return;
1539 }
1540
1541 trace_t tr;
1542
1543 CG_Trace( &tr, cg.refdef.vieworg, NULL, NULL, g_saberFlashPos, -1, CONTENTS_SOLID );
1544
1545 if ( tr.fraction < 1.0f )
1546 {
1547 return;
1548 }
1549
1550 vec3_t color;
1551 int x,y;
1552 float v, len = VectorNormalize( dif );
1553
1554 // clamp to a known range
1555 if ( len > 800 )
1556 {
1557 len = 800;
1558 }
1559
1560 v = ( 1.0f - ((float)t / maxTime )) * ((1.0f - ( len / 800.0f )) * 2.0f + 0.35f);
1561
1562 CG_WorldCoordToScreenCoord( g_saberFlashPos, &x, &y );
1563
1564 VectorSet( color, 0.8f, 0.8f, 0.8f );
1565 cgi_R_SetColor( color );
1566
1567 CG_DrawPic( x - ( v * 300 ), y - ( v * 300 ),
1568 v * 600, v * 600,
1569 cgi_R_RegisterShader( "gfx/effects/saberFlare" ));
1570 }
1571
1572 /*
1573 ===============
1574 CG_CalcViewValues
1575
1576 Sets cg.refdef view values
1577 ===============
1578 */
CG_CalcViewValues(void)1579 static qboolean CG_CalcViewValues( void ) {
1580 playerState_t *ps;
1581 //qboolean viewEntIsHumanoid = qfalse;
1582 qboolean viewEntIsCam = qfalse;
1583
1584 memset( &cg.refdef, 0, sizeof( cg.refdef ) );
1585
1586 // calculate size of 3D view
1587 CG_CalcVrect();
1588
1589 if( cg.snap->ps.viewEntity != 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD &&
1590 g_entities[cg.snap->ps.viewEntity].client)
1591 {
1592 ps = &g_entities[cg.snap->ps.viewEntity].client->ps;
1593 //viewEntIsHumanoid = qtrue;
1594 }
1595 else
1596 {
1597 ps = &cg.predicted_player_state;
1598 }
1599 #ifndef FINAL_BUILD
1600 trap_Com_SetOrgAngles(ps->origin,ps->viewangles);
1601 #endif
1602 // intermission view
1603 if ( ps->pm_type == PM_INTERMISSION ) {
1604 VectorCopy( ps->origin, cg.refdef.vieworg );
1605 VectorCopy( ps->viewangles, cg.refdefViewAngles );
1606 AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
1607 return CG_CalcFov();
1608 }
1609
1610 cg.bobcycle = ( ps->bobCycle & 128 ) >> 7;
1611 cg.bobfracsin = fabs( sin( ( ps->bobCycle & 127 ) / 127.0 * M_PI ) );
1612 cg.xyspeed = sqrt( ps->velocity[0] * ps->velocity[0] +
1613 ps->velocity[1] * ps->velocity[1] );
1614
1615 if ( G_IsRidingVehicle( &g_entities[0] ) )
1616 {
1617 VectorCopy( ps->origin, cg.refdef.vieworg );
1618 VectorCopy( cg_entities[g_entities[0].owner->s.number].lerpAngles, cg.refdefViewAngles );
1619 if ( !(ps->eFlags&EF_NODRAW) )
1620 {//riding it, not *inside* it
1621 //let us look up & down
1622 cg.refdefViewAngles[PITCH] = cg_entities[ps->clientNum].lerpAngles[PITCH] * 0.2f;
1623 }
1624 }
1625 else if ( cg.snap->ps.viewEntity > 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD )
1626 {//in an entity camera view
1627 /*
1628 if ( g_entities[cg.snap->ps.viewEntity].client && cg.renderingThirdPerson )
1629 {
1630 VectorCopy( g_entities[cg.snap->ps.viewEntity].client->renderInfo.eyePoint, cg.refdef.vieworg );
1631 }
1632 else
1633 */
1634 {
1635 VectorCopy( cg_entities[cg.snap->ps.viewEntity].lerpOrigin, cg.refdef.vieworg );
1636 }
1637 VectorCopy( cg_entities[cg.snap->ps.viewEntity].lerpAngles, cg.refdefViewAngles );
1638 if ( !Q_stricmp( "misc_camera", g_entities[cg.snap->ps.viewEntity].classname ) || g_entities[cg.snap->ps.viewEntity].s.weapon == WP_TURRET )
1639 {
1640 viewEntIsCam = qtrue;
1641 }
1642 }
1643 else if ( cg.renderingThirdPerson && !cg.zoomMode && (cg.overrides.active&CG_OVERRIDE_3RD_PERSON_ENT) )
1644 {//different center, same angle
1645 VectorCopy( cg_entities[cg.overrides.thirdPersonEntity].lerpOrigin, cg.refdef.vieworg );
1646 VectorCopy( ps->viewangles, cg.refdefViewAngles );
1647 }
1648 else
1649 {//player's center and angles
1650 VectorCopy( ps->origin, cg.refdef.vieworg );
1651 VectorCopy( ps->viewangles, cg.refdefViewAngles );
1652 }
1653
1654 // add error decay
1655 if ( cg_errorDecay.value > 0 ) {
1656 int t;
1657 float f;
1658
1659 t = cg.time - cg.predictedErrorTime;
1660 f = ( cg_errorDecay.value - t ) / cg_errorDecay.value;
1661 if ( f > 0 && f < 1 ) {
1662 VectorMA( cg.refdef.vieworg, f, cg.predictedError, cg.refdef.vieworg );
1663 } else {
1664 cg.predictedErrorTime = 0;
1665 }
1666 }
1667
1668 if ( (cg.renderingThirdPerson||cg.snap->ps.weapon == WP_SABER||cg.snap->ps.weapon == WP_MELEE)
1669 && !cg.zoomMode
1670 && !viewEntIsCam )
1671 {
1672 // back away from character
1673 // if ( cg_thirdPerson.integer == CG_CAM_ABOVE)
1674 // { `
1675 // CG_OffsetThirdPersonOverheadView();
1676 // }
1677 // else
1678 // {
1679 // First person saber
1680 if ( !cg.renderingThirdPerson )
1681 {
1682 if ( cg.snap->ps.weapon == WP_SABER||cg.snap->ps.weapon == WP_MELEE )
1683 {
1684 vec3_t dir;
1685 CG_OffsetFirstPersonView( qtrue );
1686 cg.refdef.vieworg[2] += 32;
1687 AngleVectors( cg.refdefViewAngles, dir, NULL, NULL );
1688 VectorMA( cg.refdef.vieworg, -2, dir, cg.refdef.vieworg );
1689 }
1690 }
1691 CG_OffsetThirdPersonView();
1692 // }
1693 }
1694 else
1695 {
1696 // offset for local bobbing and kicks
1697 CG_OffsetFirstPersonView( qfalse );
1698 centity_t *playerCent = &cg_entities[0];
1699 if ( playerCent && playerCent->gent && playerCent->gent->client )
1700 {
1701 VectorCopy( cg.refdef.vieworg, playerCent->gent->client->renderInfo.eyePoint );
1702 VectorCopy( cg.refdefViewAngles, playerCent->gent->client->renderInfo.eyeAngles );
1703 if ( cg.snap->ps.viewEntity > 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD )
1704 {//in an entity camera view
1705 if ( cg_entities[cg.snap->ps.viewEntity].gent->client )
1706 {//looking through a client's eyes
1707 VectorCopy( cg.refdef.vieworg, cg_entities[cg.snap->ps.viewEntity].gent->client->renderInfo.eyePoint );
1708 VectorCopy( cg.refdefViewAngles, cg_entities[cg.snap->ps.viewEntity].gent->client->renderInfo.eyeAngles );
1709 }
1710 else
1711 {//looking through a regular ent's eyes
1712 VectorCopy( cg.refdef.vieworg, cg_entities[cg.snap->ps.viewEntity].lerpOrigin );
1713 VectorCopy( cg.refdefViewAngles, cg_entities[cg.snap->ps.viewEntity].lerpAngles );
1714 }
1715 }
1716 VectorCopy( playerCent->gent->client->renderInfo.eyePoint, playerCent->gent->client->renderInfo.headPoint );
1717 if ( cg.snap->ps.viewEntity <= 0 || cg.snap->ps.viewEntity >= ENTITYNUM_WORLD )
1718 {//not in entity cam
1719 playerCent->gent->client->renderInfo.headPoint[2] -= 8;
1720 }
1721 }
1722 }
1723
1724 //VectorCopy( cg.refdef.vieworg, cgRefdefVieworg );
1725 // shake the camera if necessary
1726 CGCam_UpdateSmooth( cg.refdef.vieworg, cg.refdefViewAngles );
1727 CGCam_UpdateShake( cg.refdef.vieworg, cg.refdefViewAngles );
1728
1729 /*
1730 if ( in_camera )
1731 {
1732 Com_Printf( "%s %s\n", vtos(client_camera.origin), vtos(cg.refdef.vieworg) );
1733 }
1734 */
1735
1736 // see if we are drugged by an interrogator. We muck with the angles here, just a bit earlier, we mucked with the FOV
1737 if ( cg.wonkyTime > 0 && cg.wonkyTime > cg.time )
1738 {
1739 float perc = (float)(cg.wonkyTime - cg.time) / 10000.0f; // goes for 10 seconds
1740
1741 cg.refdefViewAngles[ROLL] += ( sin( cg.time * 0.0004f ) * 7.0f * perc );
1742 cg.refdefViewAngles[PITCH] += ( 26.0f * perc + sin( cg.time * 0.0011f ) * 3.0f * perc );
1743 }
1744
1745 AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
1746
1747 if ( cg.hyperspace )
1748 {
1749 cg.refdef.rdflags |= RDF_NOWORLDMODEL | RDF_HYPERSPACE;
1750 }
1751
1752 // field of view
1753 return CG_CalcFov();
1754 }
1755
1756
1757 /*
1758 =====================
1759 CG_PowerupTimerSounds
1760 =====================
1761 */
CG_PowerupTimerSounds(void)1762 static void CG_PowerupTimerSounds( void )
1763 {
1764 int i, time;
1765
1766 // powerup timers going away
1767 for ( i = 0 ; i < MAX_POWERUPS ; i++ )
1768 {
1769 time = cg.snap->ps.powerups[i];
1770
1771 if ( time > 0 && time < cg.time )
1772 {
1773 // add any special powerup expiration sounds here
1774 // switch( i )
1775 // {
1776 // case PW_WEAPON_OVERCHARGE:
1777 // cgi_S_StartSound( NULL, cg.snap->ps.clientNum, CHAN_ITEM, cgs.media.overchargeEndSound );
1778 // break;
1779 // }
1780 }
1781 }
1782 }
1783
1784 /*
1785 ==============
1786 CG_DrawSkyBoxPortal
1787 ==============
1788 */
1789 extern void cgi_CM_SnapPVS(vec3_t origin,byte *buffer);
1790
CG_DrawSkyBoxPortal(void)1791 static void CG_DrawSkyBoxPortal(void)
1792 {
1793 refdef_t backuprefdef;
1794 const char *cstr;
1795 char *token;
1796
1797 cstr = CG_ConfigString(CS_SKYBOXORG);
1798
1799 if (!cstr || !strlen(cstr))
1800 {
1801 // no skybox in this map
1802 return;
1803 }
1804
1805 backuprefdef = cg.refdef;
1806
1807 // asdf --eez
1808 COM_BeginParseSession();
1809 token = COM_ParseExt(&cstr, qfalse);
1810 if (!token || !token[0])
1811 {
1812 CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n");
1813 }
1814 cg.refdef.vieworg[0] = atof(token);
1815
1816 token = COM_ParseExt(&cstr, qfalse);
1817 if (!token || !token[0])
1818 {
1819 CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n");
1820 }
1821 cg.refdef.vieworg[1] = atof(token);
1822
1823 token = COM_ParseExt(&cstr, qfalse);
1824 if (!token || !token[0])
1825 {
1826 CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring\n");
1827 }
1828 cg.refdef.vieworg[2] = atof(token);
1829
1830 // setup fog the first time, ignore this part of the configstring after that
1831 token = COM_ParseExt(&cstr, qfalse);
1832 if (!token || !token[0])
1833 {
1834 CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog state\n");
1835 }
1836 else
1837 {
1838 if ( atoi( token ) ) {
1839 // this camera has fog
1840 token = COM_ParseExt( &cstr, qfalse );
1841 if ( !VALIDSTRING( token ) ) {
1842 CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[0]\n");
1843 }
1844
1845 token = COM_ParseExt(&cstr, qfalse);
1846 if ( !VALIDSTRING( token ) ) {
1847 CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[1]\n");
1848 }
1849
1850 token = COM_ParseExt(&cstr, qfalse);
1851 if ( !VALIDSTRING( token ) ) {
1852 CG_Error( "CG_DrawSkyBoxPortal: error parsing skybox configstring. No fog[2]\n");
1853 }
1854
1855 COM_ParseExt( &cstr, qfalse );
1856 COM_ParseExt( &cstr, qfalse );
1857 }
1858 }
1859
1860 COM_EndParseSession();
1861 /*
1862 static float lastfov = cg_zoomFov; // for transitions back from zoomed in modes
1863 float fov_x;
1864 float fov_y;
1865 float x;
1866 float f = 0;
1867
1868 if ( cg.predicted_player_state.pm_type == PM_INTERMISSION )
1869 {
1870 // if in intermission, use a fixed value
1871 fov_x = (cg.overrides.active&CG_OVERRIDE_FOV) ? cg.overrides.fov : cg_fov.value;
1872 }
1873 else
1874 {
1875 if (cg.zoomMode)
1876 {
1877 fov_x = cg_zoomFov;
1878 }
1879 else
1880 {
1881 fov_x = (cg.overrides.active&CG_OVERRIDE_FOV) ? cg.overrides.fov : cg_fov.value;
1882 if ( fov_x < 1 )
1883 {
1884 fov_x = 1;
1885 }
1886 else if ( fov_x > 160 )
1887 {
1888 fov_x = 160;
1889 }
1890 }
1891
1892 // do smooth transitions for zooming
1893 if (cg.zoomMode)
1894 { //zoomed/zooming in
1895 f = ( cg.time - cg.zoomTime ) / (float)ZOOM_OUT_TIME;
1896 if ( f > 1.0 ) {
1897 fov_x = cg_zoomFov;
1898 } else {
1899 fov_x = fov_x + f * ( cg_zoomFov - fov_x );
1900 }
1901 lastfov = fov_x;
1902 }
1903 else
1904 { //zooming out
1905 f = ( cg.time - cg.zoomTime ) / (float)ZOOM_OUT_TIME;
1906 if ( f > 1.0 ) {
1907 fov_x = fov_x;
1908 } else {
1909 fov_x = cg_zoomFov + f * ( fov_x - cg_zoomFov);
1910 }
1911 }
1912 }
1913
1914 x = cg.refdef.width / tan( fov_x / 360 * M_PI );
1915 fov_y = atan2( cg.refdef.height, x );
1916 fov_y = fov_y * 360 / M_PI;
1917
1918 cg.refdef.fov_x = fov_x;
1919 cg.refdef.fov_y = fov_y;
1920 */
1921 //inherit fov and axis from whatever the player is doing (regular, camera overrides or zoomed, whatever)
1922 if ( !cg.hyperspace )
1923 {
1924 CG_AddPacketEntities(qtrue); //rww - There was no proper way to put real entities inside the portal view before.
1925 //This will put specially flagged entities in the render.
1926 //Add effects flagged to play only in portals
1927 theFxScheduler.AddScheduledEffects( true );
1928 }
1929
1930 cg.refdef.rdflags |= RDF_SKYBOXPORTAL; //mark portal scene specialness
1931 cg.refdef.rdflags |= RDF_DRAWSKYBOX; //drawk portal skies
1932
1933 cgi_CM_SnapPVS( cg.refdef.vieworg, cg.refdef.areamask ); //fill in my areamask for this view origin
1934 // draw the skybox
1935 cgi_R_RenderScene( &cg.refdef );
1936
1937 cg.refdef = backuprefdef;
1938 }
1939
1940 //----------------------------
CG_RunEmplacedWeapon()1941 void CG_RunEmplacedWeapon()
1942 {
1943 gentity_t *player = &g_entities[0],
1944 *gun = player->owner;
1945
1946 // Override the camera when we are locked onto the gun.
1947 if ( player
1948 && gun
1949 && !gun->bounceCount//not an eweb
1950 && ( player->s.eFlags & EF_LOCKED_TO_WEAPON ))
1951 {
1952 // float dist = -1; // default distance behind gun
1953
1954 // don't let the player try and change this
1955 cg.renderingThirdPerson = qtrue;
1956
1957 // cg.refdefViewAngles[PITCH] += cg.overrides.thirdPersonPitchOffset? cg.overrides.thirdPersonPitchOffset: cg_thirdPersonPitchOffset.value;
1958 // cg.refdefViewAngles[YAW] += cg.overrides.thirdPersonAngle ? cg.overrides.thirdPersonAngle : cg_thirdPersonAngle.value;;
1959
1960 AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
1961
1962 // Slide in behind the gun.
1963 // if ( gun->delay + 500 > cg.time )
1964 // {
1965 // dist -= (( gun->delay + 500 ) - cg.time ) * 0.02f;
1966 // }
1967
1968 VectorCopy( gun->pos2, cg.refdef.vieworg );
1969 VectorMA( cg.refdef.vieworg, -20.0f, gun->pos3, cg.refdef.vieworg );
1970 if ( cg.snap->ps.viewEntity <= 0 || cg.snap->ps.viewEntity >= ENTITYNUM_WORLD )
1971 {
1972 VectorMA( cg.refdef.vieworg, 35.0f, gun->pos4, cg.refdef.vieworg );
1973 }
1974
1975 }
1976 }
1977
1978 //=========================================================================
1979
1980 /*
1981 =================
1982 CG_DrawActiveFrame
1983
1984 Generates and draws a game scene and status information at the given time.
1985 =================
1986 */
1987 extern void CG_BuildSolidList( void );
1988 extern void CG_ClearHealthBarEnts( void );
1989 extern vec3_t serverViewOrg;
1990 static qboolean cg_rangedFogging = qfalse; //so we know if we should go back to normal fog
CG_DrawActiveFrame(int serverTime,stereoFrame_t stereoView)1991 void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView ) {
1992 qboolean inwater = qfalse;
1993
1994 cg.time = serverTime;
1995
1996 // update cvars
1997 CG_UpdateCvars();
1998
1999 // if we are only updating the screen as a loading
2000 // pacifier, don't even try to read snapshots
2001 if ( cg.infoScreenText[0] != 0 ) {
2002 CG_DrawInformation();
2003 return;
2004 }
2005
2006 CG_ClearHealthBarEnts();
2007
2008 CG_RunLightStyles();
2009
2010 // any looped sounds will be respecified as entities
2011 // are added to the render list
2012 cgi_S_ClearLoopingSounds();
2013
2014 // clear all the render lists
2015 cgi_R_ClearScene();
2016
2017 CG_BuildSolidList();
2018
2019 // set up cg.snap and possibly cg.nextSnap
2020 CG_ProcessSnapshots();
2021 // if we haven't received any snapshots yet, all
2022 // we can draw is the information screen
2023 if ( !cg.snap ) {
2024 //CG_DrawInformation();
2025 return;
2026 }
2027
2028 // make sure the lagometerSample and frame timing isn't done twice when in stereo
2029 if ( stereoView != STEREO_RIGHT ) {
2030 cg.frametime = cg.time - cg.oldTime;
2031 cg.oldTime = cg.time;
2032 }
2033 // Make sure the helper has the updated time
2034 theFxHelper.AdjustTime( cg.frametime );
2035
2036 // let the client system know what our weapon and zoom settings are
2037 //FIXME: should really send forcePowersActive over network onto cg.snap->ps...
2038 const int fpActive = cg_entities[0].gent->client->ps.forcePowersActive;
2039 const bool matrixMode = !!(fpActive & ((1 << FP_SPEED) | (1 << FP_RAGE)));
2040 float speed = cg.refdef.fov_y / 75.0 * (matrixMode ? 1.0f : cg_timescale.value);
2041
2042 //FIXME: junk code, BUG:168
2043
2044 static bool wasForceSpeed=false;
2045 bool isForceSpeed=cg_entities[0].gent->client->ps.forcePowersActive&(1<<FP_SPEED)?true:false;
2046 if (isForceSpeed&&!wasForceSpeed)
2047 {
2048 CGCam_Smooth(0.75f,5000);
2049 }
2050 wasForceSpeed=isForceSpeed;
2051
2052 //
2053
2054 float mPitchOverride = 0.0f;
2055 float mYawOverride = 0.0f;
2056 if ( cg.snap->ps.clientNum == 0 && cg_scaleVehicleSensitivity.integer )
2057 {//pointless check, but..
2058 if ( cg_entities[0].gent->s.eFlags & EF_LOCKED_TO_WEAPON )
2059 {
2060 speed *= 0.25f;
2061 }
2062 Vehicle_t *pVeh = NULL;
2063
2064 // Mouse turns slower.
2065 if ( ( pVeh = G_IsRidingVehicle( &g_entities[0] ) ) != NULL )
2066 {
2067 if ( pVeh->m_pVehicleInfo->mousePitch )
2068 {
2069 mPitchOverride = pVeh->m_pVehicleInfo->mousePitch;
2070 }
2071 if ( pVeh->m_pVehicleInfo->mouseYaw )
2072 {
2073 mYawOverride = pVeh->m_pVehicleInfo->mouseYaw;
2074 }
2075 }
2076 }
2077 cgi_SetUserCmdValue( cg.weaponSelect, speed, mPitchOverride, mYawOverride );
2078
2079 // this counter will be bumped for every valid scene we generate
2080 cg.clientFrame++;
2081
2082 // update cg.predicted_player_state
2083 CG_PredictPlayerState();
2084
2085 if (cg.snap->ps.eFlags&EF_HELD_BY_SAND_CREATURE)
2086 {
2087 cg.zoomMode = 0;
2088 }
2089 // decide on third person view
2090 cg.renderingThirdPerson = (qboolean)(
2091 cg_thirdPerson.integer
2092 || (cg.snap->ps.stats[STAT_HEALTH] <= 0)
2093 || (cg.snap->ps.eFlags&EF_HELD_BY_SAND_CREATURE)
2094 || ((g_entities[0].client&&g_entities[0].client->NPC_class==CLASS_ATST)
2095 || (cg.snap->ps.weapon == WP_SABER || cg.snap->ps.weapon == WP_MELEE) ));
2096
2097 if ( cg.zoomMode )
2098 {
2099 // zoomed characters should never do third person stuff??
2100 cg.renderingThirdPerson = qfalse;
2101 }
2102
2103 if ( in_camera )
2104 {
2105 // The camera takes over the view
2106 CGCam_RenderScene();
2107 }
2108 else
2109 {
2110 //Finish any fading that was happening
2111 CGCam_UpdateFade();
2112 // build cg.refdef
2113 inwater = CG_CalcViewValues();
2114 }
2115
2116 if (cg.zoomMode)
2117 { //zooming with binoculars or sniper, set the fog range based on the zoom level -rww
2118 cg_rangedFogging = qtrue;
2119 //smaller the fov the less fog we have between the view and cull dist
2120 cgi_R_SetRangeFog(cg.refdef.fov_x*64.0f);
2121 }
2122 else if (cg_rangedFogging)
2123 { //disable it
2124 cg_rangedFogging = qfalse;
2125 cgi_R_SetRangeFog(0.0f);
2126 }
2127
2128 cg.refdef.time = cg.time;
2129
2130 CG_DrawSkyBoxPortal();
2131
2132 // NOTE: this may completely override the camera
2133 CG_RunEmplacedWeapon();
2134
2135 // first person blend blobs, done after AnglesToAxis
2136 if ( !cg.renderingThirdPerson ) {
2137 CG_DamageBlendBlob();
2138 }
2139
2140 // build the render lists
2141 if ( !cg.hyperspace ) {
2142 CG_AddPacketEntities(qfalse); // adter calcViewValues, so predicted player state is correct
2143 CG_AddMarks();
2144 CG_AddLocalEntities();
2145 CG_DrawMiscEnts();
2146 }
2147
2148 //check for opaque water
2149 // this game does not have opaque water
2150 if ( 0 )
2151 {
2152 vec3_t camTest;
2153 VectorCopy( cg.refdef.vieworg, camTest );
2154 camTest[2] += 6;
2155 if ( !(CG_PointContents( camTest, 0 )&CONTENTS_SOLID) && !gi.inPVS( cg.refdef.vieworg, camTest ) )
2156 {//crossed visible line into another room
2157 cg.refdef.vieworg[2] -= 6;
2158 //cgi_CM_SnapPVS(cg.refdef.vieworg,cg.snap->areamask);
2159 }
2160 else
2161 {
2162 VectorCopy( cg.refdef.vieworg, camTest );
2163 camTest[2] -= 6;
2164 if ( !(CG_PointContents( camTest, 0 )&CONTENTS_SOLID) && !gi.inPVS( cg.refdef.vieworg, camTest ) )
2165 {
2166 cg.refdef.vieworg[2] += 6;
2167 //cgi_CM_SnapPVS(cg.refdef.vieworg,cg.snap->areamask);
2168 }
2169 else //if ( inwater )
2170 {//extra-special hack... sometimes when crouched in water with first person lightsaber, your PVS is wrong???
2171 /*
2172 if ( !cg.renderingThirdPerson && (cg.snap->ps.weapon == WP_SABER||cg.snap->ps.weapon == WP_MELEE) )
2173 {//pseudo first-person for saber and fists
2174 cgi_CM_SnapPVS(cg.refdef.vieworg,cg.snap->areamask);
2175 }
2176 */
2177 }
2178 }
2179 }
2180 //FIXME: first person crouch-uncrouch STILL FUCKS UP THE AREAMASK!!!
2181 //if ( !VectorCompare2( cg.refdef.vieworg, cg.snap->ps.serverViewOrg ) && !gi.inPVS( cg.refdef.vieworg, cg.snap->ps.serverViewOrg ) )
2182 {//actual view org and server's view org don't match and aren't same PVS, rebuild the areamask
2183 //Com_Printf( S_COLOR_RED"%s != %s\n", vtos(cg.refdef.vieworg), vtos(cg.snap->ps.serverViewOrg) );
2184 cgi_CM_SnapPVS( cg.refdef.vieworg, cg.snap->areamask );
2185 }
2186
2187 // Don't draw the in-view weapon when in camera mode
2188 if ( !in_camera
2189 && !cg_pano.integer
2190 && cg.snap->ps.weapon != WP_SABER
2191 && ( cg.snap->ps.viewEntity == 0 || cg.snap->ps.viewEntity >= ENTITYNUM_WORLD ) )
2192 {
2193 CG_AddViewWeapon( &cg.predicted_player_state );
2194 }
2195 else if( cg.snap->ps.viewEntity != 0 && cg.snap->ps.viewEntity < ENTITYNUM_WORLD )
2196 {
2197 if( g_entities[cg.snap->ps.viewEntity].client && g_entities[cg.snap->ps.viewEntity].NPC )
2198 {
2199 CG_AddViewWeapon( &g_entities[cg.snap->ps.viewEntity ].client->ps ); // HAX - because I wanted to --eez
2200 }
2201 }
2202
2203 if ( !cg.hyperspace && fx_freeze.integer<2 )
2204 {
2205 //Add all effects
2206 theFxScheduler.AddScheduledEffects( false );
2207 }
2208
2209 // finish up the rest of the refdef
2210 if ( cg.testModelEntity.hModel ) {
2211 CG_AddTestModel();
2212 }
2213
2214 memcpy( cg.refdef.areamask, cg.snap->areamask, sizeof( cg.refdef.areamask ) );
2215
2216 // update audio positions
2217 //This is done from the vieworg to get origin for non-attenuated sounds
2218 cgi_S_UpdateAmbientSet( CG_ConfigString( CS_AMBIENT_SET ), cg.refdef.vieworg );
2219 //NOTE: if we want to make you be able to hear far away sounds with electrobinoculars, add the hacked-in positional offset here (base on fov)
2220 /*
2221 vec3_t listener_origin;
2222 VectorCopy( cg.refdef.vieworg, listener_origin );
2223 if ( cg.snap->ps.forcePowersActive&(1<<FP_SEE)
2224 && cg.snap->ps.forcePowerLevel[FP_SEE] >= FORCE_LEVEL_2 )
2225 {//FIXME: if I can see through walls, why not actually move the listener_position through the wall too?
2226 if ( g_crosshairEntNum < ENTITYNUM_WORLD
2227 && g_crosshairSameEntTime >= 1000 )
2228 {//been looking at this guy for at least a full second
2229 gentity_t *crossEnt = &g_entities[g_crosshairEntNum];
2230 if ( crossEnt->client && crossEnt->health >= 0 )
2231 {//a living client, let's listen in...?
2232 //FIXME: lerp our listener_origin to their position over a second or two?
2233 VectorCopy( cg_entities[g_crosshairEntNum].lerpOrigin, listener_origin );
2234 inwater = qtrue;//do cool reverby effect...?
2235 }
2236 }
2237 }
2238 cgi_S_Respatialize( cg.snap->ps.clientNum, listener_origin, cg.refdef.viewaxis, inwater );
2239 */
2240 cgi_S_Respatialize( cg.snap->ps.clientNum, cg.refdef.vieworg, cg.refdef.viewaxis, inwater );
2241
2242 // warning sounds when powerup is wearing off
2243 CG_PowerupTimerSounds();
2244
2245 if ( cg_pano.integer ) { // let's grab a panorama!
2246 cg.levelShot = qtrue; //hide the 2d
2247 VectorClear(cg.refdefViewAngles);
2248 cg.refdefViewAngles[YAW] = -360 * cg_pano.integer/cg_panoNumShots.integer; //choose angle
2249 AnglesToAxis( cg.refdefViewAngles, cg.refdef.viewaxis );
2250 CG_DrawActive( stereoView );
2251 cg.levelShot = qfalse;
2252 } else {
2253 // actually issue the rendering calls
2254 CG_DrawActive( stereoView );
2255 }
2256 /*
2257 if ( in_camera && !cg_skippingcin.integer )
2258 {
2259 Com_Printf( S_COLOR_GREEN"ang: %s\n", vtos(cg.refdefViewAngles) );
2260 }
2261 */
2262 }
2263
2264