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)
567 float yaw, pitch, move;
568 static float oldyaw = 0;
569 static float oldpitch = 0;
571 yaw = r_refdef.viewangles[YAW];
572 pitch = -r_refdef.viewangles[PITCH];
574 yaw = angledelta(yaw - r_refdef.viewangles[YAW]) * 0.4;
579 pitch = angledelta(-pitch - r_refdef.viewangles[PITCH]) * 0.4;
584 move = cl.frametime*20;
587 if (oldyaw + move < yaw)
592 if (oldyaw - move > yaw)
596 if (pitch > oldpitch)
598 if (oldpitch + move < pitch)
599 pitch = oldpitch + move;
603 if (oldpitch - move > pitch)
604 pitch = oldpitch - move;
610 cl.viewent.angles[YAW] = r_refdef.viewangles[YAW] + yaw;
611 cl.viewent.angles[PITCH] = - (r_refdef.viewangles[PITCH] + pitch);
613 cl.viewent.render.angles[YAW] = r_refdef.viewangles[YAW];
614 cl.viewent.render.angles[PITCH] = -r_refdef.viewangles[PITCH];
616 cl.viewent.render.angles[ROLL] -= v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
617 cl.viewent.render.angles[PITCH] -= v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
618 cl.viewent.render.angles[YAW] -= v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
626 void V_BoundOffsets (void)
630 ent = &cl_entities[cl.viewentity];
632 // absolutely bound refresh relative to entity clipping hull
633 // so the view can never be inside a solid wall
635 if (r_refdef.vieworg[0] < ent->render.origin[0] - 14)
636 r_refdef.vieworg[0] = ent->render.origin[0] - 14;
637 else if (r_refdef.vieworg[0] > ent->render.origin[0] + 14)
638 r_refdef.vieworg[0] = ent->render.origin[0] + 14;
639 if (r_refdef.vieworg[1] < ent->render.origin[1] - 14)
640 r_refdef.vieworg[1] = ent->render.origin[1] - 14;
641 else if (r_refdef.vieworg[1] > ent->render.origin[1] + 14)
642 r_refdef.vieworg[1] = ent->render.origin[1] + 14;
643 if (r_refdef.vieworg[2] < ent->render.origin[2] - 22)
644 r_refdef.vieworg[2] = ent->render.origin[2] - 22;
645 else if (r_refdef.vieworg[2] > ent->render.origin[2] + 30)
646 r_refdef.vieworg[2] = ent->render.origin[2] + 30;
656 void V_AddIdle (void)
658 r_refdef.viewangles[ROLL] += v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value;
659 r_refdef.viewangles[PITCH] += v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value;
660 r_refdef.viewangles[YAW] += v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value;
668 Roll is induced by movement and damage
671 void V_CalcViewRoll (void)
675 side = V_CalcRoll (cl_entities[cl.viewentity].render.angles, cl.velocity);
676 r_refdef.viewangles[ROLL] += side;
680 r_refdef.viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
681 r_refdef.viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
682 v_dmg_time -= cl.frametime;
685 if (cl.stats[STAT_HEALTH] <= 0)
687 r_refdef.viewangles[ROLL] = 80; // dead view angle
696 V_CalcIntermissionRefdef
700 void V_CalcIntermissionRefdef (void)
702 entity_t *ent, *view;
705 // ent is the player model (visible when out of body)
706 ent = &cl_entities[cl.viewentity];
707 // view is the weapon model (only visible from inside body)
710 VectorCopy (ent->render.origin, r_refdef.vieworg);
711 VectorCopy (ent->render.angles, r_refdef.viewangles);
712 view->render.model = NULL;
714 // always idle in intermission
715 old = v_idlescale.value;
716 v_idlescale.value = 1;
718 v_idlescale.value = old;
727 void V_CalcRefdef (void)
729 entity_t *ent, *view;
734 // static float oldz = 0;
738 // ent is the player model (visible when out of body)
739 ent = &cl_entities[cl.viewentity];
740 // view is the weapon model (only visible from inside body)
744 if (chase_active.value)
746 VectorCopy (ent->render.origin, r_refdef.vieworg);
747 VectorCopy (cl.viewangles, r_refdef.viewangles);
753 // transform the view offset by the model's matrix to get the offset from model origin for the view
754 // if (!chase_active.value) // LordHavoc: get rid of angle problems in chase_active mode
756 // ent->render.angles[YAW] = cl.viewangles[YAW]; // the model should face the view dir
757 // ent->render.angles[PITCH] = -cl.viewangles[PITCH]; // the model should face the view dir
763 VectorCopy (ent->render.origin, r_refdef.vieworg);
764 r_refdef.vieworg[2] += cl.viewheight + bob;
766 // LordHavoc: the protocol has changed... so this is an obsolete approach
767 // never let it sit exactly on a node line, because a water plane can
768 // dissapear when viewed with the eye exactly on it.
769 // the server protocol only specifies to 1/16 pixel, so add 1/32 in each axis
770 // r_refdef.vieworg[0] += 1.0/32;
771 // r_refdef.vieworg[1] += 1.0/32;
772 // r_refdef.vieworg[2] += 1.0/32;
775 VectorCopy (cl.viewangles, r_refdef.viewangles);
780 angles[PITCH] = -ent->render.angles[PITCH]; // because entity pitches are actually backward
781 angles[YAW] = ent->render.angles[YAW];
782 angles[ROLL] = ent->render.angles[ROLL];
784 AngleVectors (angles, forward, NULL, NULL);
788 // set up gun position
789 VectorCopy (cl.viewangles, view->render.angles);
793 VectorCopy (ent->render.origin, view->render.origin);
794 view->render.origin[2] += cl.viewheight;
796 for (i=0 ; i<3 ; i++)
798 view->render.origin[i] += forward[i]*bob*0.4;
799 // view->render.origin[i] += right[i]*bob*0.4;
800 // view->render.origin[i] += up[i]*bob*0.8;
802 view->render.origin[2] += bob;
804 view->render.model = cl.model_precache[cl.stats[STAT_WEAPON]];
805 view->render.frame = cl.stats[STAT_WEAPONFRAME];
806 view->render.colormap = -1; // no special coloring
808 // set up the refresh position
810 // LordHavoc: this never looked all that good to begin with...
812 // smooth out stair step ups
813 if (cl.onground && ent->render.origin[2] - oldz > 0)
817 steptime = cl.time - cl.oldtime;
819 //FIXME I_Error ("steptime < 0");
822 oldz += steptime * 80;
823 if (oldz > ent->render.origin[2])
824 oldz = ent->render.origin[2];
825 if (ent->render.origin[2] - oldz > 12)
826 oldz = ent->render.origin[2] - 12;
827 r_refdef.vieworg[2] += oldz - ent->render.origin[2];
828 view->render.origin[2] += oldz - ent->render.origin[2];
831 oldz = ent->render.origin[2];
834 // LordHavoc: origin view kick added
835 if (!intimerefresh && v_punch.value)
837 VectorAdd(r_refdef.viewangles, cl.punchangle, r_refdef.viewangles);
838 VectorAdd(r_refdef.vieworg, cl.punchvector, r_refdef.vieworg);
847 The player's clipping box goes from (-16 -16 -24) to (16 16 32) from
848 the entity origin, so any view position inside that will be valid
851 void V_RenderView (void)
857 V_CalcIntermissionRefdef ();
864 //============================================================================
873 Cmd_AddCommand ("v_cshift", V_cshift_f);
874 Cmd_AddCommand ("bf", V_BonusFlash_f);
875 Cmd_AddCommand ("centerview", V_StartPitchDrift);
877 Cvar_RegisterVariable (&v_centermove);
878 Cvar_RegisterVariable (&v_centerspeed);
880 Cvar_RegisterVariable (&v_iyaw_cycle);
881 Cvar_RegisterVariable (&v_iroll_cycle);
882 Cvar_RegisterVariable (&v_ipitch_cycle);
883 Cvar_RegisterVariable (&v_iyaw_level);
884 Cvar_RegisterVariable (&v_iroll_level);
885 Cvar_RegisterVariable (&v_ipitch_level);
887 Cvar_RegisterVariable (&v_idlescale);
888 Cvar_RegisterVariable (&crosshair);
889 // Cvar_RegisterVariable (&gl_cshiftpercent);
890 Cvar_RegisterVariable (&gl_polyblend);
892 Cvar_RegisterVariable (&cl_rollspeed);
893 Cvar_RegisterVariable (&cl_rollangle);
894 Cvar_RegisterVariable (&cl_bob);
895 Cvar_RegisterVariable (&cl_bobcycle);
896 Cvar_RegisterVariable (&cl_bobup);
898 Cvar_RegisterVariable (&v_kicktime);
899 Cvar_RegisterVariable (&v_kickroll);
900 Cvar_RegisterVariable (&v_kickpitch);
902 Cvar_RegisterVariable (&v_punch);