2 Copyright (C) 1996-1997 Id Software, Inc.
4 This program is free software; you can redistribute it and/or
5 modify it under the terms of the GNU General Public License
6 as published by the Free Software Foundation; either version 2
7 of the License, or (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
13 See the GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 // view.c -- player eye positioning
26 The view is allowed to move slightly from it's true position for bobbing,
27 but if it exceeds 8 pixels linear distance (spherical, not box), the list of
28 entities sent from the server may not include everything in the pvs, especially
29 when crossing a water boudnary.
33 cvar_t cl_rollspeed = {0, "cl_rollspeed", "200"};
34 cvar_t cl_rollangle = {0, "cl_rollangle", "2.0"};
36 cvar_t cl_bob = {0, "cl_bob","0.02"};
37 cvar_t cl_bobcycle = {0, "cl_bobcycle","0.6"};
38 cvar_t cl_bobup = {0, "cl_bobup","0.5"};
40 cvar_t v_kicktime = {0, "v_kicktime", "0.5"};
41 cvar_t v_kickroll = {0, "v_kickroll", "0.6"};
42 cvar_t v_kickpitch = {0, "v_kickpitch", "0.6"};
44 cvar_t v_punch = {0, "v_punch", "1"};
46 cvar_t v_iyaw_cycle = {0, "v_iyaw_cycle", "2"};
47 cvar_t v_iroll_cycle = {0, "v_iroll_cycle", "0.5"};
48 cvar_t v_ipitch_cycle = {0, "v_ipitch_cycle", "1"};
49 cvar_t v_iyaw_level = {0, "v_iyaw_level", "0.3"};
50 cvar_t v_iroll_level = {0, "v_iroll_level", "0.1"};
51 cvar_t v_ipitch_level = {0, "v_ipitch_level", "0.3"};
53 cvar_t v_idlescale = {0, "v_idlescale", "0"};
55 cvar_t crosshair = {CVAR_SAVE, "crosshair", "0"};
57 //cvar_t gl_cshiftpercent = {0, "gl_cshiftpercent", "100"};
58 cvar_t gl_polyblend = {CVAR_SAVE, "gl_polyblend", "1"};
60 cvar_t v_centermove = {0, "v_centermove", "0.15"};
61 cvar_t v_centerspeed = {0, "v_centerspeed","500"};
63 float v_dmg_time, v_dmg_roll, v_dmg_pitch;
70 Used by view and sv_user
73 float V_CalcRoll (vec3_t angles, vec3_t velocity)
80 AngleVectors (angles, NULL, right, NULL);
81 side = DotProduct (velocity, right);
82 sign = side < 0 ? -1 : 1;
85 value = cl_rollangle.value;
89 if (side < cl_rollspeed.value)
90 side = side * value / cl_rollspeed.value;
105 float V_CalcBob (void)
110 cycle = cl.time - (int)(cl.time/cl_bobcycle.value)*cl_bobcycle.value;
111 cycle /= cl_bobcycle.value;
112 if (cycle < cl_bobup.value)
113 cycle = M_PI * cycle / cl_bobup.value;
115 cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value);
117 // bob is proportional to velocity in the xy plane
118 // (don't count Z, or jumping messes it up)
120 bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value;
121 //Con_Printf ("speed: %5.1f\n", Length(cl.velocity));
122 bob = bob*0.3 + bob*0.7*sin(cycle);
132 //=============================================================================
135 void V_StartPitchDrift (void)
138 if (cl.laststop == cl.time)
140 return; // something else is keeping it from drifting
143 if (cl.nodrift || !cl.pitchvel)
145 cl.pitchvel = v_centerspeed.value;
151 void V_StopPitchDrift (void)
153 cl.laststop = cl.time;
162 Moves the client pitch angle towards cl.idealpitch sent by the server.
164 If the user is adjusting pitch manually, either with lookup/lookdown,
165 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
167 Drifting is enabled when the center view key is hit, mlook is released and
168 lookspring is non 0, or when
171 void V_DriftPitch (void)
175 if (noclip_anglehack || !cl.onground || cls.demoplayback )
182 // don't count small mouse motion
185 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
188 cl.driftmove += cl.frametime;
190 if ( cl.driftmove > v_centermove.value)
192 V_StartPitchDrift ();
197 delta = cl.idealpitch - cl.viewangles[PITCH];
205 move = cl.frametime * cl.pitchvel;
206 cl.pitchvel += cl.frametime * v_centerspeed.value;
208 //Con_Printf ("move: %f (%f)\n", move, cl.frametime);
217 cl.viewangles[PITCH] += move;
226 cl.viewangles[PITCH] -= move;
235 ==============================================================================
239 ==============================================================================
243 cshift_t cshift_empty = { {130,80,50}, 0 };
244 cshift_t cshift_water = { {130,80,50}, 128 };
245 cshift_t cshift_slime = { {0,25,5}, 150 };
246 cshift_t cshift_lava = { {255,80,0}, 150 };
249 float v_blend[4]; // rgba 0.0 - 1.0
256 void V_ParseDamage (void)
261 vec3_t forward, right;
266 armor = MSG_ReadByte ();
267 blood = MSG_ReadByte ();
268 for (i=0 ; i<3 ; i++)
269 from[i] = MSG_ReadCoord ();
271 count = blood*0.5 + armor*0.5;
275 cl.faceanimtime = cl.time + 0.2; // put sbar face into pain frame
277 if (gl_polyblend.value)
279 cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
280 if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
281 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
282 if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
283 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
287 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
288 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
289 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
293 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
294 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
295 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
299 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
300 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
301 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
306 // calculate view angle kicks
308 ent = &cl_entities[cl.viewentity];
310 VectorSubtract (from, ent->render.origin, from);
311 VectorNormalize (from);
313 AngleVectors (ent->render.angles, forward, right, NULL);
315 side = DotProduct (from, right);
316 v_dmg_roll = count*side*v_kickroll.value;
318 side = DotProduct (from, forward);
319 v_dmg_pitch = count*side*v_kickpitch.value;
321 v_dmg_time = v_kicktime.value;
330 void V_cshift_f (void)
332 cshift_empty.destcolor[0] = atoi(Cmd_Argv(1));
333 cshift_empty.destcolor[1] = atoi(Cmd_Argv(2));
334 cshift_empty.destcolor[2] = atoi(Cmd_Argv(3));
335 cshift_empty.percent = atoi(Cmd_Argv(4));
343 When you run over an item, the server sends this command
346 void V_BonusFlash_f (void)
348 if (gl_polyblend.value)
350 cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
351 cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
352 cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
353 cl.cshifts[CSHIFT_BONUS].percent = 50;
361 Underwater, lava, etc each has a color shift
364 void V_SetContentsColor (int contents)
367 c = &cl.cshifts[CSHIFT_CONTENTS]; // just to shorten the code below
368 if (!gl_polyblend.value)
377 //cl.cshifts[CSHIFT_CONTENTS] = cshift_empty;
378 c->destcolor[0] = cshift_empty.destcolor[0];
379 c->destcolor[1] = cshift_empty.destcolor[1];
380 c->destcolor[2] = cshift_empty.destcolor[2];
381 c->percent = cshift_empty.percent;
384 //cl.cshifts[CSHIFT_CONTENTS] = cshift_lava;
385 c->destcolor[0] = cshift_lava.destcolor[0];
386 c->destcolor[1] = cshift_lava.destcolor[1];
387 c->destcolor[2] = cshift_lava.destcolor[2];
388 c->percent = cshift_lava.percent;
391 //cl.cshifts[CSHIFT_CONTENTS] = cshift_slime;
392 c->destcolor[0] = cshift_slime.destcolor[0];
393 c->destcolor[1] = cshift_slime.destcolor[1];
394 c->destcolor[2] = cshift_slime.destcolor[2];
395 c->percent = cshift_slime.percent;
398 //cl.cshifts[CSHIFT_CONTENTS] = cshift_water;
399 c->destcolor[0] = cshift_water.destcolor[0];
400 c->destcolor[1] = cshift_water.destcolor[1];
401 c->destcolor[2] = cshift_water.destcolor[2];
402 c->percent = cshift_water.percent;
411 void V_CalcPowerupCshift (void)
413 if (!gl_polyblend.value)
415 cl.cshifts[CSHIFT_POWERUP].percent = 0;
418 if (cl.items & IT_QUAD)
420 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
421 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
422 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
423 cl.cshifts[CSHIFT_POWERUP].percent = 30;
425 else if (cl.items & IT_SUIT)
427 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
428 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
429 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
430 cl.cshifts[CSHIFT_POWERUP].percent = 20;
432 else if (cl.items & IT_INVISIBILITY)
434 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
435 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
436 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
437 cl.cshifts[CSHIFT_POWERUP].percent = 100;
439 else if (cl.items & IT_INVULNERABILITY)
441 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
442 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
443 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
444 cl.cshifts[CSHIFT_POWERUP].percent = 30;
447 cl.cshifts[CSHIFT_POWERUP].percent = 0;
455 // LordHavoc: fixed V_CalcBlend
456 void V_CalcBlend (void)
458 float r, g, b, a, a2;
466 // if (gl_cshiftpercent.value)
468 for (j=0 ; j<NUM_CSHIFTS ; j++)
470 // a2 = ((cl.cshifts[j].percent * gl_cshiftpercent.value) / 100.0) / 255.0;
471 a2 = cl.cshifts[j].percent * (1.0f / 255.0f);
477 r += (cl.cshifts[j].destcolor[0]-r) * a2;
478 g += (cl.cshifts[j].destcolor[1]-g) * a2;
479 b += (cl.cshifts[j].destcolor[2]-b) * a2;
480 a = 1 - (1 - a) * (1 - a2); // correct alpha multiply... took a while to find it on the web
482 // saturate color (to avoid blending in black)
492 v_blend[0] = bound(0, r * (1.0/255.0), 1);
493 v_blend[1] = bound(0, g * (1.0/255.0), 1);
494 v_blend[2] = bound(0, b * (1.0/255.0), 1);
495 v_blend[3] = bound(0, a , 1);
503 void V_UpdateBlends (void)
508 V_CalcPowerupCshift ();
512 for (i=0 ; i<NUM_CSHIFTS ; i++)
514 if (cl.cshifts[i].percent != cl.prev_cshifts[i].percent)
517 cl.prev_cshifts[i].percent = cl.cshifts[i].percent;
519 for (j=0 ; j<3 ; j++)
520 if (cl.cshifts[i].destcolor[j] != cl.prev_cshifts[i].destcolor[j])
523 cl.prev_cshifts[i].destcolor[j] = cl.cshifts[i].destcolor[j];
527 // drop the damage value
528 cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*150;
529 if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
530 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
532 // drop the bonus value
533 cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*100;
534 if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
535 cl.cshifts[CSHIFT_BONUS].percent = 0;
544 ==============================================================================
548 ==============================================================================
551 float angledelta (float a)
564 void CalcGunAngle (void)
566 cl.viewent.render.angles[YAW] = r_refdef.viewangles[YAW];
567 cl.viewent.render.angles[PITCH] = -r_refdef.viewangles[PITCH];
569 cl.viewent.render.angles[ROLL] -= v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
570 cl.viewent.render.angles[PITCH] -= v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
571 cl.viewent.render.angles[YAW] -= v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
579 void V_BoundOffsets (void)
583 ent = &cl_entities[cl.viewentity];
585 // absolutely bound refresh relative to entity clipping hull
586 // so the view can never be inside a solid wall
588 if (r_refdef.vieworg[0] < ent->render.origin[0] - 14)
589 r_refdef.vieworg[0] = ent->render.origin[0] - 14;
590 else if (r_refdef.vieworg[0] > ent->render.origin[0] + 14)
591 r_refdef.vieworg[0] = ent->render.origin[0] + 14;
592 if (r_refdef.vieworg[1] < ent->render.origin[1] - 14)
593 r_refdef.vieworg[1] = ent->render.origin[1] - 14;
594 else if (r_refdef.vieworg[1] > ent->render.origin[1] + 14)
595 r_refdef.vieworg[1] = ent->render.origin[1] + 14;
596 if (r_refdef.vieworg[2] < ent->render.origin[2] - 22)
597 r_refdef.vieworg[2] = ent->render.origin[2] - 22;
598 else if (r_refdef.vieworg[2] > ent->render.origin[2] + 30)
599 r_refdef.vieworg[2] = ent->render.origin[2] + 30;
609 void V_AddIdle (void)
611 r_refdef.viewangles[ROLL] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
612 r_refdef.viewangles[PITCH] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
613 r_refdef.viewangles[YAW] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
621 Roll is induced by movement and damage
624 void V_CalcViewRoll (void)
628 side = V_CalcRoll (cl_entities[cl.viewentity].render.angles, cl.velocity);
629 r_refdef.viewangles[ROLL] += side;
633 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
634 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
635 v_dmg_time -= cl.frametime;
638 if (cl.stats[STAT_HEALTH] <= 0)
640 r_refdef.viewangles[ROLL] = 80; // dead view angle
649 V_CalcIntermissionRefdef
653 void V_CalcIntermissionRefdef (void)
655 entity_t *ent, *view;
658 // ent is the player model (visible when out of body)
659 ent = &cl_entities[cl.viewentity];
660 // view is the weapon model (only visible from inside body)
663 VectorCopy (ent->render.origin, r_refdef.vieworg);
664 VectorCopy (ent->render.angles, r_refdef.viewangles);
665 view->render.model = NULL;
667 // always idle in intermission
668 old = v_idlescale.value;
669 v_idlescale.value = 1;
671 v_idlescale.value = old;
680 void V_CalcRefdef (void)
682 entity_t *ent, *view;
687 // static float oldz = 0;
691 // ent is the player model (visible when out of body)
692 ent = &cl_entities[cl.viewentity];
693 // view is the weapon model (only visible from inside body)
697 if (chase_active.value)
699 VectorCopy (ent->render.origin, r_refdef.vieworg);
700 VectorCopy (cl.viewangles, r_refdef.viewangles);
706 // transform the view offset by the model's matrix to get the offset from model origin for the view
707 // if (!chase_active.value) // LordHavoc: get rid of angle problems in chase_active mode
709 // ent->render.angles[YAW] = cl.viewangles[YAW]; // the model should face the view dir
710 // ent->render.angles[PITCH] = -cl.viewangles[PITCH]; // the model should face the view dir
716 VectorCopy (ent->render.origin, r_refdef.vieworg);
717 r_refdef.vieworg[2] += cl.viewheight + bob;
719 // LordHavoc: the protocol has changed... so this is an obsolete approach
720 // never let it sit exactly on a node line, because a water plane can
721 // dissapear when viewed with the eye exactly on it.
722 // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis
723 // r_refdef.vieworg[0] += 1.0/32;
724 // r_refdef.vieworg[1] += 1.0/32;
725 // r_refdef.vieworg[2] += 1.0/32;
728 VectorCopy (cl.viewangles, r_refdef.viewangles);
733 angles[PITCH] = -ent->render.angles[PITCH]; // because entity pitches are actually backward
734 angles[YAW] = ent->render.angles[YAW];
735 angles[ROLL] = ent->render.angles[ROLL];
737 AngleVectors (angles, forward, NULL, NULL);
741 // set up gun position
742 VectorCopy (ent->render.origin, view->render.origin);
743 view->render.origin[2] += cl.viewheight;
744 VectorCopy (cl.viewangles, view->render.angles);
748 for (i=0 ; i<3 ; i++)
750 view->render.origin[i] += forward[i]*bob*0.4;
751 // view->render.origin[i] += right[i]*bob*0.4;
752 // view->render.origin[i] += up[i]*bob*0.8;
754 view->render.origin[2] += bob;
756 // FIXME: this setup code is somewhat evil (CL_LerpUpdate should be private)
757 CL_LerpUpdate(view, cl.stats[STAT_WEAPONFRAME], cl.stats[STAT_WEAPON]);
759 view->render.model = cl.model_precache[cl.stats[STAT_WEAPON]];
760 view->render.frame = cl.stats[STAT_WEAPONFRAME];
761 view->render.colormap = -1; // no special coloring
762 view->render.alpha = ent->render.alpha; // LordHavoc: if the player is transparent, so is the gun
763 view->render.effects = ent->render.effects;
764 view->render.scale = 1;
766 // set up the refresh position
768 // LordHavoc: this never looked all that good to begin with...
770 // smooth out stair step ups
771 if (cl.onground && ent->render.origin[2] - oldz > 0)
775 steptime = cl.time - cl.oldtime;
777 //FIXME I_Error ("steptime < 0");
780 oldz += steptime * 80;
781 if (oldz > ent->render.origin[2])
782 oldz = ent->render.origin[2];
783 if (ent->render.origin[2] - oldz > 12)
784 oldz = ent->render.origin[2] - 12;
785 r_refdef.vieworg[2] += oldz - ent->render.origin[2];
786 view->render.origin[2] += oldz - ent->render.origin[2];
789 oldz = ent->render.origin[2];
792 // LordHavoc: origin view kick added
793 if (!intimerefresh && v_punch.value)
795 VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
796 VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
805 The player's clipping box goes from (-16 -16 -24) to (16 16 32) from
806 the entity origin, so any view position inside that will be valid
809 void V_RenderView (void)
811 if (scr_con_current >= vid.conheight)
815 V_CalcIntermissionRefdef ();
822 //============================================================================
831 Cmd_AddCommand ("v_cshift", V_cshift_f);
832 Cmd_AddCommand ("bf", V_BonusFlash_f);
833 Cmd_AddCommand ("centerview", V_StartPitchDrift);
835 Cvar_RegisterVariable (&v_centermove);
836 Cvar_RegisterVariable (&v_centerspeed);
838 Cvar_RegisterVariable (&v_iyaw_cycle);
839 Cvar_RegisterVariable (&v_iroll_cycle);
840 Cvar_RegisterVariable (&v_ipitch_cycle);
841 Cvar_RegisterVariable (&v_iyaw_level);
842 Cvar_RegisterVariable (&v_iroll_level);
843 Cvar_RegisterVariable (&v_ipitch_level);
845 Cvar_RegisterVariable (&v_idlescale);
846 Cvar_RegisterVariable (&crosshair);
847 // Cvar_RegisterVariable (&gl_cshiftpercent);
848 Cvar_RegisterVariable (&gl_polyblend);
850 Cvar_RegisterVariable (&cl_rollspeed);
851 Cvar_RegisterVariable (&cl_rollangle);
852 Cvar_RegisterVariable (&cl_bob);
853 Cvar_RegisterVariable (&cl_bobcycle);
854 Cvar_RegisterVariable (&cl_bobup);
856 Cvar_RegisterVariable (&v_kicktime);
857 Cvar_RegisterVariable (&v_kickroll);
858 Cvar_RegisterVariable (&v_kickpitch);
860 Cvar_RegisterVariable (&v_punch);