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;
69 Used by view and sv_user
72 float V_CalcRoll (vec3_t angles, vec3_t velocity)
79 AngleVectors (angles, NULL, right, NULL);
80 side = DotProduct (velocity, right);
81 sign = side < 0 ? -1 : 1;
84 value = cl_rollangle.value;
88 if (side < cl_rollspeed.value)
89 side = side * value / cl_rollspeed.value;
104 float V_CalcBob (void)
109 cycle = cl.time - (int)(cl.time/cl_bobcycle.value)*cl_bobcycle.value;
110 cycle /= cl_bobcycle.value;
111 if (cycle < cl_bobup.value)
112 cycle = M_PI * cycle / cl_bobup.value;
114 cycle = M_PI + M_PI*(cycle-cl_bobup.value)/(1.0 - cl_bobup.value);
116 // bob is proportional to velocity in the xy plane
117 // (don't count Z, or jumping messes it up)
119 bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value;
120 //Con_Printf ("speed: %5.1f\n", Length(cl.velocity));
121 bob = bob*0.3 + bob*0.7*sin(cycle);
131 //=============================================================================
134 cvar_t v_centermove = {"v_centermove", "0.15", false};
135 cvar_t v_centerspeed = {"v_centerspeed","500"};
138 void V_StartPitchDrift (void)
141 if (cl.laststop == cl.time)
143 return; // something else is keeping it from drifting
146 if (cl.nodrift || !cl.pitchvel)
148 cl.pitchvel = v_centerspeed.value;
154 void V_StopPitchDrift (void)
156 cl.laststop = cl.time;
165 Moves the client pitch angle towards cl.idealpitch sent by the server.
167 If the user is adjusting pitch manually, either with lookup/lookdown,
168 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
170 Drifting is enabled when the center view key is hit, mlook is released and
171 lookspring is non 0, or when
174 void V_DriftPitch (void)
178 if (noclip_anglehack || !cl.onground || cls.demoplayback )
185 // don't count small mouse motion
188 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
191 cl.driftmove += cl.frametime;
193 if ( cl.driftmove > v_centermove.value)
195 V_StartPitchDrift ();
200 delta = cl.idealpitch - cl.viewangles[PITCH];
208 move = cl.frametime * cl.pitchvel;
209 cl.pitchvel += cl.frametime * v_centerspeed.value;
211 //Con_Printf ("move: %f (%f)\n", move, cl.frametime);
220 cl.viewangles[PITCH] += move;
229 cl.viewangles[PITCH] -= move;
238 ==============================================================================
242 ==============================================================================
246 cshift_t cshift_empty = { {130,80,50}, 0 };
247 cshift_t cshift_water = { {130,80,50}, 128 };
248 cshift_t cshift_slime = { {0,25,5}, 150 };
249 cshift_t cshift_lava = { {255,80,0}, 150 };
252 float v_blend[4]; // rgba 0.0 - 1.0
259 void V_ParseDamage (void)
264 vec3_t forward, right;
269 armor = MSG_ReadByte ();
270 blood = MSG_ReadByte ();
271 for (i=0 ; i<3 ; i++)
272 from[i] = MSG_ReadCoord ();
274 count = blood*0.5 + armor*0.5;
278 cl.faceanimtime = cl.time + 0.2; // put sbar face into pain frame
280 if (gl_polyblend.value)
282 cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
283 if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
284 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
285 if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
286 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
290 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
291 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
292 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
296 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
297 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
298 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
302 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
303 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
304 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
309 // calculate view angle kicks
311 ent = &cl_entities[cl.viewentity];
313 VectorSubtract (from, ent->render.origin, from);
314 VectorNormalize (from);
316 AngleVectors (ent->render.angles, forward, right, NULL);
318 side = DotProduct (from, right);
319 v_dmg_roll = count*side*v_kickroll.value;
321 side = DotProduct (from, forward);
322 v_dmg_pitch = count*side*v_kickpitch.value;
324 v_dmg_time = v_kicktime.value;
333 void V_cshift_f (void)
335 cshift_empty.destcolor[0] = atoi(Cmd_Argv(1));
336 cshift_empty.destcolor[1] = atoi(Cmd_Argv(2));
337 cshift_empty.destcolor[2] = atoi(Cmd_Argv(3));
338 cshift_empty.percent = atoi(Cmd_Argv(4));
346 When you run over an item, the server sends this command
349 void V_BonusFlash_f (void)
351 if (gl_polyblend.value)
353 cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
354 cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
355 cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
356 cl.cshifts[CSHIFT_BONUS].percent = 50;
364 Underwater, lava, etc each has a color shift
367 void V_SetContentsColor (int contents)
370 c = &cl.cshifts[CSHIFT_CONTENTS]; // just to shorten the code below
371 if (!gl_polyblend.value)
380 //cl.cshifts[CSHIFT_CONTENTS] = cshift_empty;
381 c->destcolor[0] = cshift_empty.destcolor[0];
382 c->destcolor[1] = cshift_empty.destcolor[1];
383 c->destcolor[2] = cshift_empty.destcolor[2];
384 c->percent = cshift_empty.percent;
387 //cl.cshifts[CSHIFT_CONTENTS] = cshift_lava;
388 c->destcolor[0] = cshift_lava.destcolor[0];
389 c->destcolor[1] = cshift_lava.destcolor[1];
390 c->destcolor[2] = cshift_lava.destcolor[2];
391 c->percent = cshift_lava.percent;
394 //cl.cshifts[CSHIFT_CONTENTS] = cshift_slime;
395 c->destcolor[0] = cshift_slime.destcolor[0];
396 c->destcolor[1] = cshift_slime.destcolor[1];
397 c->destcolor[2] = cshift_slime.destcolor[2];
398 c->percent = cshift_slime.percent;
401 //cl.cshifts[CSHIFT_CONTENTS] = cshift_water;
402 c->destcolor[0] = cshift_water.destcolor[0];
403 c->destcolor[1] = cshift_water.destcolor[1];
404 c->destcolor[2] = cshift_water.destcolor[2];
405 c->percent = cshift_water.percent;
414 void V_CalcPowerupCshift (void)
416 if (!gl_polyblend.value)
418 cl.cshifts[CSHIFT_POWERUP].percent = 0;
421 if (cl.items & IT_QUAD)
423 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
424 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
425 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
426 cl.cshifts[CSHIFT_POWERUP].percent = 30;
428 else if (cl.items & IT_SUIT)
430 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
431 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
432 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
433 cl.cshifts[CSHIFT_POWERUP].percent = 20;
435 else if (cl.items & IT_INVISIBILITY)
437 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
438 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
439 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
440 cl.cshifts[CSHIFT_POWERUP].percent = 100;
442 else if (cl.items & IT_INVULNERABILITY)
444 cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
445 cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
446 cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
447 cl.cshifts[CSHIFT_POWERUP].percent = 30;
450 cl.cshifts[CSHIFT_POWERUP].percent = 0;
458 // LordHavoc: fixed V_CalcBlend
459 void V_CalcBlend (void)
461 float r, g, b, a, a2;
469 // if (gl_cshiftpercent.value)
471 for (j=0 ; j<NUM_CSHIFTS ; j++)
473 // a2 = ((cl.cshifts[j].percent * gl_cshiftpercent.value) / 100.0) / 255.0;
474 a2 = cl.cshifts[j].percent * (1.0f / 255.0f);
480 r += (cl.cshifts[j].destcolor[0]-r) * a2;
481 g += (cl.cshifts[j].destcolor[1]-g) * a2;
482 b += (cl.cshifts[j].destcolor[2]-b) * a2;
483 a = 1 - (1 - a) * (1 - a2); // correct alpha multiply... took a while to find it on the web
485 // saturate color (to avoid blending in black)
495 v_blend[0] = bound(0, r * (1.0/255.0), 1);
496 v_blend[1] = bound(0, g * (1.0/255.0), 1);
497 v_blend[2] = bound(0, b * (1.0/255.0), 1);
498 v_blend[3] = bound(0, a , 1);
506 void V_UpdateBlends (void)
511 V_CalcPowerupCshift ();
515 for (i=0 ; i<NUM_CSHIFTS ; i++)
517 if (cl.cshifts[i].percent != cl.prev_cshifts[i].percent)
520 cl.prev_cshifts[i].percent = cl.cshifts[i].percent;
522 for (j=0 ; j<3 ; j++)
523 if (cl.cshifts[i].destcolor[j] != cl.prev_cshifts[i].destcolor[j])
526 cl.prev_cshifts[i].destcolor[j] = cl.cshifts[i].destcolor[j];
530 // drop the damage value
531 cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*150;
532 if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
533 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
535 // drop the bonus value
536 cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*100;
537 if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
538 cl.cshifts[CSHIFT_BONUS].percent = 0;
547 ==============================================================================
551 ==============================================================================
554 float angledelta (float a)
567 void CalcGunAngle (void)
570 float yaw, pitch, move;
571 static float oldyaw = 0;
572 static float oldpitch = 0;
574 yaw = r_refdef.viewangles[YAW];
575 pitch = -r_refdef.viewangles[PITCH];
577 yaw = angledelta(yaw - r_refdef.viewangles[YAW]) * 0.4;
582 pitch = angledelta(-pitch - r_refdef.viewangles[PITCH]) * 0.4;
587 move = cl.frametime*20;
590 if (oldyaw + move < yaw)
595 if (oldyaw - move > yaw)
599 if (pitch > oldpitch)
601 if (oldpitch + move < pitch)
602 pitch = oldpitch + move;
606 if (oldpitch - move > pitch)
607 pitch = oldpitch - move;
613 cl.viewent.angles[YAW] = r_refdef.viewangles[YAW] + yaw;
614 cl.viewent.angles[PITCH] = - (r_refdef.viewangles[PITCH] + pitch);
616 cl.viewent.render.angles[YAW] = r_refdef.viewangles[YAW];
617 cl.viewent.render.angles[PITCH] = -r_refdef.viewangles[PITCH];
619 cl.viewent.render.angles[ROLL] -= v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
620 cl.viewent.render.angles[PITCH] -= v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
621 cl.viewent.render.angles[YAW] -= v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
629 void V_BoundOffsets (void)
633 ent = &cl_entities[cl.viewentity];
635 // absolutely bound refresh relative to entity clipping hull
636 // so the view can never be inside a solid wall
638 if (r_refdef.vieworg[0] < ent->render.origin[0] - 14)
639 r_refdef.vieworg[0] = ent->render.origin[0] - 14;
640 else if (r_refdef.vieworg[0] > ent->render.origin[0] + 14)
641 r_refdef.vieworg[0] = ent->render.origin[0] + 14;
642 if (r_refdef.vieworg[1] < ent->render.origin[1] - 14)
643 r_refdef.vieworg[1] = ent->render.origin[1] - 14;
644 else if (r_refdef.vieworg[1] > ent->render.origin[1] + 14)
645 r_refdef.vieworg[1] = ent->render.origin[1] + 14;
646 if (r_refdef.vieworg[2] < ent->render.origin[2] - 22)
647 r_refdef.vieworg[2] = ent->render.origin[2] - 22;
648 else if (r_refdef.vieworg[2] > ent->render.origin[2] + 30)
649 r_refdef.vieworg[2] = ent->render.origin[2] + 30;
659 void V_AddIdle (void)
661 r_refdef.viewangles[ROLL] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
662 r_refdef.viewangles[PITCH] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
663 r_refdef.viewangles[YAW] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
671 Roll is induced by movement and damage
674 void V_CalcViewRoll (void)
678 side = V_CalcRoll (cl_entities[cl.viewentity].render.angles, cl.velocity);
679 r_refdef.viewangles[ROLL] += side;
683 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
684 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
685 v_dmg_time -= cl.frametime;
688 if (cl.stats[STAT_HEALTH] <= 0)
690 r_refdef.viewangles[ROLL] = 80; // dead view angle
699 V_CalcIntermissionRefdef
703 void V_CalcIntermissionRefdef (void)
705 entity_t *ent, *view;
708 // ent is the player model (visible when out of body)
709 ent = &cl_entities[cl.viewentity];
710 // view is the weapon model (only visible from inside body)
713 VectorCopy (ent->render.origin, r_refdef.vieworg);
714 VectorCopy (ent->render.angles, r_refdef.viewangles);
715 view->render.model = NULL;
717 // always idle in intermission
718 old = v_idlescale.value;
719 v_idlescale.value = 1;
721 v_idlescale.value = old;
730 void V_CalcRefdef (void)
732 entity_t *ent, *view;
737 // static float oldz = 0;
741 // ent is the player model (visible when out of body)
742 ent = &cl_entities[cl.viewentity];
743 // view is the weapon model (only visible from inside body)
747 // transform the view offset by the model's matrix to get the offset from model origin for the view
748 if (!chase_active.value) // LordHavoc: get rid of angle problems in chase_active mode
750 ent->render.angles[YAW] = cl.viewangles[YAW]; // the model should face the view dir
751 ent->render.angles[PITCH] = -cl.viewangles[PITCH]; // the model should face the view dir
758 VectorCopy (ent->render.origin, r_refdef.vieworg);
759 r_refdef.vieworg[2] += cl.viewheight + bob;
761 // LordHavoc: the protocol has changed... so this is an obsolete approach
762 // never let it sit exactly on a node line, because a water plane can
763 // dissapear when viewed with the eye exactly on it.
764 // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis
765 // r_refdef.vieworg[0] += 1.0/32;
766 // r_refdef.vieworg[1] += 1.0/32;
767 // r_refdef.vieworg[2] += 1.0/32;
770 VectorCopy (cl.viewangles, r_refdef.viewangles);
775 angles[PITCH] = -ent->render.angles[PITCH]; // because entity pitches are actually backward
776 angles[YAW] = ent->render.angles[YAW];
777 angles[ROLL] = ent->render.angles[ROLL];
779 AngleVectors (angles, forward, NULL, NULL);
783 // set up gun position
784 VectorCopy (cl.viewangles, view->render.angles);
788 VectorCopy (ent->render.origin, view->render.origin);
789 view->render.origin[2] += cl.viewheight;
791 for (i=0 ; i<3 ; i++)
793 view->render.origin[i] += forward[i]*bob*0.4;
794 // view->render.origin[i] += right[i]*bob*0.4;
795 // view->render.origin[i] += up[i]*bob*0.8;
797 view->render.origin[2] += bob;
799 view->render.model = cl.model_precache[cl.stats[STAT_WEAPON]];
800 view->render.frame = cl.stats[STAT_WEAPONFRAME];
801 view->render.colormap = -1; // no special coloring
803 // set up the refresh position
805 // LordHavoc: this never looked all that good to begin with...
807 // smooth out stair step ups
808 if (cl.onground && ent->render.origin[2] - oldz > 0)
812 steptime = cl.time - cl.oldtime;
814 //FIXME I_Error ("steptime < 0");
817 oldz += steptime * 80;
818 if (oldz > ent->render.origin[2])
819 oldz = ent->render.origin[2];
820 if (ent->render.origin[2] - oldz > 12)
821 oldz = ent->render.origin[2] - 12;
822 r_refdef.vieworg[2] += oldz - ent->render.origin[2];
823 view->render.origin[2] += oldz - ent->render.origin[2];
826 oldz = ent->render.origin[2];
829 // LordHavoc: origin view kick added
830 if (!intimerefresh && v_punch.value)
832 VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
833 VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
836 if (chase_active.value)
844 The player's clipping box goes from (-16 -16 -24) to (16 16 32) from
845 the entity origin, so any view position inside that will be valid
848 void V_RenderView (void)
854 V_CalcIntermissionRefdef ();
861 //============================================================================
870 Cmd_AddCommand ("v_cshift", V_cshift_f);
871 Cmd_AddCommand ("bf", V_BonusFlash_f);
872 Cmd_AddCommand ("centerview", V_StartPitchDrift);
874 Cvar_RegisterVariable (&v_centermove);
875 Cvar_RegisterVariable (&v_centerspeed);
877 Cvar_RegisterVariable (&v_iyaw_cycle);
878 Cvar_RegisterVariable (&v_iroll_cycle);
879 Cvar_RegisterVariable (&v_ipitch_cycle);
880 Cvar_RegisterVariable (&v_iyaw_level);
881 Cvar_RegisterVariable (&v_iroll_level);
882 Cvar_RegisterVariable (&v_ipitch_level);
884 Cvar_RegisterVariable (&v_idlescale);
885 Cvar_RegisterVariable (&crosshair);
886 Cvar_RegisterVariable (&cl_crossx);
887 Cvar_RegisterVariable (&cl_crossy);
888 // Cvar_RegisterVariable (&gl_cshiftpercent);
889 Cvar_RegisterVariable (&gl_polyblend);
891 Cvar_RegisterVariable (&cl_rollspeed);
892 Cvar_RegisterVariable (&cl_rollangle);
893 Cvar_RegisterVariable (&cl_bob);
894 Cvar_RegisterVariable (&cl_bobcycle);
895 Cvar_RegisterVariable (&cl_bobup);
897 Cvar_RegisterVariable (&v_kicktime);
898 Cvar_RegisterVariable (&v_kickroll);
899 Cvar_RegisterVariable (&v_kickpitch);
901 Cvar_RegisterVariable (&v_punch);