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 = {"cl_rollspeed", "200"};
34 cvar_t cl_rollangle = {"cl_rollangle", "2.0"};
36 cvar_t cl_bob = {"cl_bob","0.02", false};
37 cvar_t cl_bobcycle = {"cl_bobcycle","0.6", false};
38 cvar_t cl_bobup = {"cl_bobup","0.5", false};
40 cvar_t v_kicktime = {"v_kicktime", "0.5", false};
41 cvar_t v_kickroll = {"v_kickroll", "0.6", false};
42 cvar_t v_kickpitch = {"v_kickpitch", "0.6", false};
44 cvar_t v_punch = {"v_punch", "1", false};
46 cvar_t v_iyaw_cycle = {"v_iyaw_cycle", "2", false};
47 cvar_t v_iroll_cycle = {"v_iroll_cycle", "0.5", false};
48 cvar_t v_ipitch_cycle = {"v_ipitch_cycle", "1", false};
49 cvar_t v_iyaw_level = {"v_iyaw_level", "0.3", false};
50 cvar_t v_iroll_level = {"v_iroll_level", "0.1", false};
51 cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", false};
53 cvar_t v_idlescale = {"v_idlescale", "0", false};
55 cvar_t crosshair = {"crosshair", "0", true};
56 cvar_t cl_crossx = {"cl_crossx", "0", false};
57 cvar_t cl_crossy = {"cl_crossy", "0", false};
59 //cvar_t gl_cshiftpercent = {"gl_cshiftpercent", "100", false};
60 cvar_t gl_polyblend = {"gl_polyblend", "1", true};
62 float v_dmg_time, v_dmg_roll, v_dmg_pitch;
64 extern int in_forward, in_forward2, in_back;
71 Used by view and sv_user
74 float V_CalcRoll (vec3_t angles, vec3_t velocity)
81 AngleVectors (angles, NULL, right, NULL);
82 side = DotProduct (velocity, right);
83 sign = side < 0 ? -1 : 1;
86 value = cl_rollangle.value;
90 if (side < cl_rollspeed.value)
91 side = side * value / cl_rollspeed.value;
106 float V_CalcBob (void)
111 cycle = cl.time - (int)(cl.time/cl_bobcycle.value)*cl_bobcycle.value;
112 cycle /= cl_bobcycle.value;
113 if (cycle < cl_bobup.value)
114 cycle = M_PI * cycle / cl_bobup.value;
116 cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value);
118 // bob is proportional to velocity in the xy plane
119 // (don't count Z, or jumping messes it up)
121 bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value;
122 //Con_Printf ("speed: %5.1f\n", Length(cl.velocity));
123 bob = bob*0.3 + bob*0.7*sin(cycle);
133 //=============================================================================
136 cvar_t v_centermove = {"v_centermove", "0.15", false};
137 cvar_t v_centerspeed = {"v_centerspeed","500"};
140 void V_StartPitchDrift (void)
143 if (cl.laststop == cl.time)
145 return; // something else is keeping it from drifting
148 if (cl.nodrift || !cl.pitchvel)
150 cl.pitchvel = v_centerspeed.value;
156 void V_StopPitchDrift (void)
158 cl.laststop = cl.time;
167 Moves the client pitch angle towards cl.idealpitch sent by the server.
169 If the user is adjusting pitch manually, either with lookup/lookdown,
170 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
172 Drifting is enabled when the center view key is hit, mlook is released and
173 lookspring is non 0, or when
176 void V_DriftPitch (void)
180 if (noclip_anglehack || !cl.onground || cls.demoplayback )
187 // don't count small mouse motion
190 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
193 cl.driftmove += cl.frametime;
195 if ( cl.driftmove > v_centermove.value)
197 V_StartPitchDrift ();
202 delta = cl.idealpitch - cl.viewangles[PITCH];
210 move = cl.frametime * cl.pitchvel;
211 cl.pitchvel += cl.frametime * v_centerspeed.value;
213 //Con_Printf ("move: %f (%f)\n", move, cl.frametime);
222 cl.viewangles[PITCH] += move;
231 cl.viewangles[PITCH] -= move;
240 ==============================================================================
244 ==============================================================================
248 cshift_t cshift_empty = { {130,80,50}, 0 };
249 cshift_t cshift_water = { {130,80,50}, 128 };
250 cshift_t cshift_slime = { {0,25,5}, 150 };
251 cshift_t cshift_lava = { {255,80,0}, 150 };
254 float v_blend[4]; // rgba 0.0 - 1.0
261 void V_ParseDamage (void)
266 vec3_t forward, right;
271 armor = MSG_ReadByte ();
272 blood = MSG_ReadByte ();
273 for (i=0 ; i<3 ; i++)
274 from[i] = MSG_ReadCoord ();
276 count = blood*0.5 + armor*0.5;
280 cl.faceanimtime = cl.time + 0.2; // put sbar face into pain frame
282 if (gl_polyblend.value)
284 cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
285 if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
286 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
287 if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
288 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
292 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
293 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
294 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
298 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
299 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
300 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
304 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
305 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
306 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
311 // calculate view angle kicks
313 ent = &cl_entities[cl.viewentity];
315 VectorSubtract (from, ent->render.origin, from);
316 VectorNormalize (from);
318 AngleVectors (ent->render.angles, forward, right, NULL);
320 side = DotProduct (from, right);
321 v_dmg_roll = count*side*v_kickroll.value;
323 side = DotProduct (from, forward);
324 v_dmg_pitch = count*side*v_kickpitch.value;
326 v_dmg_time = v_kicktime.value;
335 void V_cshift_f (void)
337 cshift_empty.destcolor[0] = atoi(Cmd_Argv(1));
338 cshift_empty.destcolor[1] = atoi(Cmd_Argv(2));
339 cshift_empty.destcolor[2] = atoi(Cmd_Argv(3));
340 cshift_empty.percent = atoi(Cmd_Argv(4));
348 When you run over an item, the server sends this command
351 void V_BonusFlash_f (void)
353 if (gl_polyblend.value)
355 cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
356 cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
357 cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
358 cl.cshifts[CSHIFT_BONUS].percent = 50;
366 Underwater, lava, etc each has a color shift
369 void V_SetContentsColor (int contents)
372 c = &cl.cshifts[CSHIFT_CONTENTS]; // just to shorten the code below
373 if (!gl_polyblend.value)
382 //cl.cshifts[CSHIFT_CONTENTS] = cshift_empty;
383 c->destcolor[0] = cshift_empty.destcolor[0];
384 c->destcolor[1] = cshift_empty.destcolor[1];
385 c->destcolor[2] = cshift_empty.destcolor[2];
386 c->percent = cshift_empty.percent;
389 //cl.cshifts[CSHIFT_CONTENTS] = cshift_lava;
390 c->destcolor[0] = cshift_lava.destcolor[0];
391 c->destcolor[1] = cshift_lava.destcolor[1];
392 c->destcolor[2] = cshift_lava.destcolor[2];
393 c->percent = cshift_lava.percent;
396 //cl.cshifts[CSHIFT_CONTENTS] = cshift_slime;
397 c->destcolor[0] = cshift_slime.destcolor[0];
398 c->destcolor[1] = cshift_slime.destcolor[1];
399 c->destcolor[2] = cshift_slime.destcolor[2];
400 c->percent = cshift_slime.percent;
403 //cl.cshifts[CSHIFT_CONTENTS] = cshift_water;
404 c->destcolor[0] = cshift_water.destcolor[0];
405 c->destcolor[1] = cshift_water.destcolor[1];
406 c->destcolor[2] = cshift_water.destcolor[2];
407 c->percent = cshift_water.percent;
416 void V_CalcPowerupCshift (void)
418 if (!gl_polyblend.value)
420 cl.cshifts[CSHIFT_POWERUP].percent = 0;
423 if (cl.items & IT_QUAD)
425 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
426 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
427 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
428 cl.cshifts[CSHIFT_POWERUP].percent = 30;
430 else if (cl.items & IT_SUIT)
432 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
433 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
434 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
435 cl.cshifts[CSHIFT_POWERUP].percent = 20;
437 else if (cl.items & IT_INVISIBILITY)
439 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
440 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
441 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
442 cl.cshifts[CSHIFT_POWERUP].percent = 100;
444 else if (cl.items & IT_INVULNERABILITY)
446 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
447 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
448 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
449 cl.cshifts[CSHIFT_POWERUP].percent = 30;
452 cl.cshifts[CSHIFT_POWERUP].percent = 0;
460 // LordHavoc: fixed V_CalcBlend
461 void V_CalcBlend (void)
463 float r, g, b, a, a2;
471 // if (gl_cshiftpercent.value)
473 for (j=0 ; j<NUM_CSHIFTS ; j++)
475 // a2 = ((cl.cshifts[j].percent * gl_cshiftpercent.value) / 100.0) / 255.0;
476 a2 = cl.cshifts[j].percent * (1.0f / 255.0f);
482 r += (cl.cshifts[j].destcolor[0]-r) * a2;
483 g += (cl.cshifts[j].destcolor[1]-g) * a2;
484 b += (cl.cshifts[j].destcolor[2]-b) * a2;
485 a = 1 - (1 - a) * (1 - a2); // correct alpha multiply... took a while to find it on the web
487 // saturate color (to avoid blending in black)
497 v_blend[0] = bound(0, r * (1.0/255.0), 1);
498 v_blend[1] = bound(0, g * (1.0/255.0), 1);
499 v_blend[2] = bound(0, b * (1.0/255.0), 1);
500 v_blend[3] = bound(0, a , 1);
508 void V_UpdateBlends (void)
513 V_CalcPowerupCshift ();
517 for (i=0 ; i<NUM_CSHIFTS ; i++)
519 if (cl.cshifts[i].percent != cl.prev_cshifts[i].percent)
522 cl.prev_cshifts[i].percent = cl.cshifts[i].percent;
524 for (j=0 ; j<3 ; j++)
525 if (cl.cshifts[i].destcolor[j] != cl.prev_cshifts[i].destcolor[j])
528 cl.prev_cshifts[i].destcolor[j] = cl.cshifts[i].destcolor[j];
532 // drop the damage value
533 cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*150;
534 if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
535 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
537 // drop the bonus value
538 cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*100;
539 if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
540 cl.cshifts[CSHIFT_BONUS].percent = 0;
549 ==============================================================================
553 ==============================================================================
556 float angledelta (float a)
569 void CalcGunAngle (void)
572 float yaw, pitch, move;
573 static float oldyaw = 0;
574 static float oldpitch = 0;
576 yaw = r_refdef.viewangles[YAW];
577 pitch = -r_refdef.viewangles[PITCH];
579 yaw = angledelta(yaw - r_refdef.viewangles[YAW]) * 0.4;
584 pitch = angledelta(-pitch - r_refdef.viewangles[PITCH]) * 0.4;
589 move = cl.frametime*20;
592 if (oldyaw + move < yaw)
597 if (oldyaw - move > yaw)
601 if (pitch > oldpitch)
603 if (oldpitch + move < pitch)
604 pitch = oldpitch + move;
608 if (oldpitch - move > pitch)
609 pitch = oldpitch - move;
615 cl.viewent.angles[YAW] = r_refdef.viewangles[YAW] + yaw;
616 cl.viewent.angles[PITCH] = - (r_refdef.viewangles[PITCH] + pitch);
618 cl.viewent.render.angles[YAW] = r_refdef.viewangles[YAW];
619 cl.viewent.render.angles[PITCH] = -r_refdef.viewangles[PITCH];
621 cl.viewent.render.angles[ROLL] -= v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
622 cl.viewent.render.angles[PITCH] -= v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
623 cl.viewent.render.angles[YAW] -= v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
631 void V_BoundOffsets (void)
635 ent = &cl_entities[cl.viewentity];
637 // absolutely bound refresh relative to entity clipping hull
638 // so the view can never be inside a solid wall
640 if (r_refdef.vieworg[0] < ent->render.origin[0] - 14)
641 r_refdef.vieworg[0] = ent->render.origin[0] - 14;
642 else if (r_refdef.vieworg[0] > ent->render.origin[0] + 14)
643 r_refdef.vieworg[0] = ent->render.origin[0] + 14;
644 if (r_refdef.vieworg[1] < ent->render.origin[1] - 14)
645 r_refdef.vieworg[1] = ent->render.origin[1] - 14;
646 else if (r_refdef.vieworg[1] > ent->render.origin[1] + 14)
647 r_refdef.vieworg[1] = ent->render.origin[1] + 14;
648 if (r_refdef.vieworg[2] < ent->render.origin[2] - 22)
649 r_refdef.vieworg[2] = ent->render.origin[2] - 22;
650 else if (r_refdef.vieworg[2] > ent->render.origin[2] + 30)
651 r_refdef.vieworg[2] = ent->render.origin[2] + 30;
661 void V_AddIdle (void)
663 r_refdef.viewangles[ROLL] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
664 r_refdef.viewangles[PITCH] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
665 r_refdef.viewangles[YAW] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
673 Roll is induced by movement and damage
676 void V_CalcViewRoll (void)
680 side = V_CalcRoll (cl_entities[cl.viewentity].render.angles, cl.velocity);
681 r_refdef.viewangles[ROLL] += side;
685 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
686 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
687 v_dmg_time -= cl.frametime;
690 if (cl.stats[STAT_HEALTH] <= 0)
692 r_refdef.viewangles[ROLL] = 80; // dead view angle
701 V_CalcIntermissionRefdef
705 void V_CalcIntermissionRefdef (void)
707 entity_t *ent, *view;
710 // ent is the player model (visible when out of body)
711 ent = &cl_entities[cl.viewentity];
712 // view is the weapon model (only visible from inside body)
715 VectorCopy (ent->render.origin, r_refdef.vieworg);
716 VectorCopy (ent->render.angles, r_refdef.viewangles);
717 view->render.model = NULL;
719 // always idle in intermission
720 old = v_idlescale.value;
721 v_idlescale.value = 1;
723 v_idlescale.value = old;
732 extern qboolean intimerefresh;
733 void V_CalcRefdef (void)
735 entity_t *ent, *view;
740 static float oldz = 0;
744 // ent is the player model (visible when out of body)
745 ent = &cl_entities[cl.viewentity];
746 // view is the weapon model (only visible from inside body)
750 // transform the view offset by the model's matrix to get the offset from model origin for the view
751 if (!chase_active.value) // LordHavoc: get rid of angle problems in chase_active mode
753 ent->render.angles[YAW] = cl.viewangles[YAW]; // the model should face the view dir
754 ent->render.angles[PITCH] = -cl.viewangles[PITCH]; // the model should face the view dir
761 VectorCopy (ent->render.origin, r_refdef.vieworg);
762 r_refdef.vieworg[2] += cl.viewheight + bob;
764 // LordHavoc: the protocol has changed... so this is an obsolete approach
765 // never let it sit exactly on a node line, because a water plane can
766 // dissapear when viewed with the eye exactly on it.
767 // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis
768 // r_refdef.vieworg[0] += 1.0/32;
769 // r_refdef.vieworg[1] += 1.0/32;
770 // r_refdef.vieworg[2] += 1.0/32;
773 VectorCopy (cl.viewangles, r_refdef.viewangles);
778 angles[PITCH] = -ent->render.angles[PITCH]; // because entity pitches are actually backward
779 angles[YAW] = ent->render.angles[YAW];
780 angles[ROLL] = ent->render.angles[ROLL];
782 AngleVectors (angles, forward, NULL, NULL);
786 // set up gun position
787 VectorCopy (cl.viewangles, view->render.angles);
791 VectorCopy (ent->render.origin, view->render.origin);
792 view->render.origin[2] += cl.viewheight;
794 for (i=0 ; i<3 ; i++)
796 view->render.origin[i] += forward[i]*bob*0.4;
797 // view->origin[i] += right[i]*bob*0.4;
798 // view->origin[i] += up[i]*bob*0.8;
800 view->render.origin[2] += bob;
802 // fudge position around to keep amount of weapon visible
803 // roughly equal with different FOV
805 view->render.model = cl.model_precache[cl.stats[STAT_WEAPON]];
806 view->render.frame = cl.stats[STAT_WEAPONFRAME];
807 view->render.colormap = -1; // no special coloring
809 // set up the refresh position
813 VectorAdd (r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
816 // smooth out stair step ups
817 if (cl.onground && ent->render.origin[2] - oldz > 0)
821 steptime = cl.time - cl.oldtime;
823 //FIXME I_Error ("steptime < 0");
826 oldz += steptime * 80;
827 if (oldz > ent->render.origin[2])
828 oldz = ent->render.origin[2];
829 if (ent->render.origin[2] - oldz > 12)
830 oldz = ent->render.origin[2] - 12;
831 r_refdef.vieworg[2] += oldz - ent->render.origin[2];
832 view->render.origin[2] += oldz - ent->render.origin[2];
835 oldz = ent->render.origin[2];
837 // LordHavoc: origin view kick
838 VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
840 if (chase_active.value)
848 The player's clipping box goes from (-16 -16 -24) to (16 16 32) from
849 the entity origin, so any view position inside that will be valid
852 void V_RenderView (void)
858 V_CalcIntermissionRefdef ();
865 //============================================================================
874 Cmd_AddCommand ("v_cshift", V_cshift_f);
875 Cmd_AddCommand ("bf", V_BonusFlash_f);
876 Cmd_AddCommand ("centerview", V_StartPitchDrift);
878 Cvar_RegisterVariable (&v_centermove);
879 Cvar_RegisterVariable (&v_centerspeed);
881 Cvar_RegisterVariable (&v_iyaw_cycle);
882 Cvar_RegisterVariable (&v_iroll_cycle);
883 Cvar_RegisterVariable (&v_ipitch_cycle);
884 Cvar_RegisterVariable (&v_iyaw_level);
885 Cvar_RegisterVariable (&v_iroll_level);
886 Cvar_RegisterVariable (&v_ipitch_level);
888 Cvar_RegisterVariable (&v_idlescale);
889 Cvar_RegisterVariable (&crosshair);
890 Cvar_RegisterVariable (&cl_crossx);
891 Cvar_RegisterVariable (&cl_crossy);
892 // Cvar_RegisterVariable (&gl_cshiftpercent);
893 Cvar_RegisterVariable (&gl_polyblend);
895 Cvar_RegisterVariable (&cl_rollspeed);
896 Cvar_RegisterVariable (&cl_rollangle);
897 Cvar_RegisterVariable (&cl_bob);
898 Cvar_RegisterVariable (&cl_bobcycle);
899 Cvar_RegisterVariable (&cl_bobup);
901 Cvar_RegisterVariable (&v_kicktime);
902 Cvar_RegisterVariable (&v_kickroll);
903 Cvar_RegisterVariable (&v_kickpitch);
905 Cvar_RegisterVariable (&v_punch);