]> git.xonotic.org Git - xonotic/darkplaces.git/blob - view.c
r_refdef.vieworg and r_refdef.viewangles replaced by r_refdef.viewentitymatrix
[xonotic/darkplaces.git] / view.c
1 /*
2 Copyright (C) 1996-1997 Id Software, Inc.
3
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.
8
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.
12
13 See the GNU General Public License for more details.
14
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.
18
19 */
20 // view.c -- player eye positioning
21
22 #include "quakedef.h"
23 #include "cl_collision.h"
24
25 /*
26
27 The view is allowed to move slightly from it's true position for bobbing,
28 but if it exceeds 8 pixels linear distance (spherical, not box), the list of
29 entities sent from the server may not include everything in the pvs, especially
30 when crossing a water boudnary.
31
32 */
33
34 cvar_t  cl_rollspeed = {0, "cl_rollspeed", "200"};
35 cvar_t  cl_rollangle = {0, "cl_rollangle", "2.0"};
36
37 cvar_t  cl_bob = {0, "cl_bob","0.02"};
38 cvar_t  cl_bobcycle = {0, "cl_bobcycle","0.6"};
39 cvar_t  cl_bobup = {0, "cl_bobup","0.5"};
40
41 cvar_t  v_kicktime = {0, "v_kicktime", "0.5"};
42 cvar_t  v_kickroll = {0, "v_kickroll", "0.6"};
43 cvar_t  v_kickpitch = {0, "v_kickpitch", "0.6"};
44
45 cvar_t  v_iyaw_cycle = {0, "v_iyaw_cycle", "2"};
46 cvar_t  v_iroll_cycle = {0, "v_iroll_cycle", "0.5"};
47 cvar_t  v_ipitch_cycle = {0, "v_ipitch_cycle", "1"};
48 cvar_t  v_iyaw_level = {0, "v_iyaw_level", "0.3"};
49 cvar_t  v_iroll_level = {0, "v_iroll_level", "0.1"};
50 cvar_t  v_ipitch_level = {0, "v_ipitch_level", "0.3"};
51
52 cvar_t  v_idlescale = {0, "v_idlescale", "0"};
53
54 cvar_t  crosshair = {CVAR_SAVE, "crosshair", "0"};
55
56 cvar_t  v_centermove = {0, "v_centermove", "0.15"};
57 cvar_t  v_centerspeed = {0, "v_centerspeed","500"};
58
59 cvar_t cl_stairsmoothspeed = {CVAR_SAVE, "cl_stairsmoothspeed", "160"};
60
61 cvar_t chase_back = {CVAR_SAVE, "chase_back", "48"};
62 cvar_t chase_up = {CVAR_SAVE, "chase_up", "24"};
63 cvar_t chase_active = {CVAR_SAVE, "chase_active", "0"};
64 // GAME_GOODVSBAD2
65 cvar_t chase_stevie = {0, "chase_stevie", "0"};
66
67 float   v_dmg_time, v_dmg_roll, v_dmg_pitch;
68
69
70 /*
71 ===============
72 V_CalcRoll
73
74 Used by view and sv_user
75 ===============
76 */
77 float V_CalcRoll (vec3_t angles, vec3_t velocity)
78 {
79         vec3_t  right;
80         float   sign;
81         float   side;
82         float   value;
83
84         AngleVectors (angles, NULL, right, NULL);
85         side = DotProduct (velocity, right);
86         sign = side < 0 ? -1 : 1;
87         side = fabs(side);
88
89         value = cl_rollangle.value;
90
91         if (side < cl_rollspeed.value)
92                 side = side * value / cl_rollspeed.value;
93         else
94                 side = value;
95
96         return side*sign;
97
98 }
99
100 void V_StartPitchDrift (void)
101 {
102         if (cl.laststop == cl.time)
103                 return;         // something else is keeping it from drifting
104
105         if (cl.nodrift || !cl.pitchvel)
106         {
107                 cl.pitchvel = v_centerspeed.value;
108                 cl.nodrift = false;
109                 cl.driftmove = 0;
110         }
111 }
112
113 void V_StopPitchDrift (void)
114 {
115         cl.laststop = cl.time;
116         cl.nodrift = true;
117         cl.pitchvel = 0;
118 }
119
120 /*
121 ===============
122 V_DriftPitch
123
124 Moves the client pitch angle towards cl.idealpitch sent by the server.
125
126 If the user is adjusting pitch manually, either with lookup/lookdown,
127 mlook and mouse, or klook and keyboard, pitch drifting is constantly stopped.
128
129 Drifting is enabled when the center view key is hit, mlook is released and
130 lookspring is non 0, or when
131 ===============
132 */
133 void V_DriftPitch (void)
134 {
135         float           delta, move;
136
137         if (noclip_anglehack || !cl.onground || cls.demoplayback )
138         {
139                 cl.driftmove = 0;
140                 cl.pitchvel = 0;
141                 return;
142         }
143
144 // don't count small mouse motion
145         if (cl.nodrift)
146         {
147                 if ( fabs(cl.cmd.forwardmove) < cl_forwardspeed.value)
148                         cl.driftmove = 0;
149                 else
150                         cl.driftmove += cl.frametime;
151
152                 if ( cl.driftmove > v_centermove.value)
153                 {
154                         V_StartPitchDrift ();
155                 }
156                 return;
157         }
158
159         delta = cl.idealpitch - cl.viewangles[PITCH];
160
161         if (!delta)
162         {
163                 cl.pitchvel = 0;
164                 return;
165         }
166
167         move = cl.frametime * cl.pitchvel;
168         cl.pitchvel += cl.frametime * v_centerspeed.value;
169
170         if (delta > 0)
171         {
172                 if (move > delta)
173                 {
174                         cl.pitchvel = 0;
175                         move = delta;
176                 }
177                 cl.viewangles[PITCH] += move;
178         }
179         else if (delta < 0)
180         {
181                 if (move > -delta)
182                 {
183                         cl.pitchvel = 0;
184                         move = -delta;
185                 }
186                 cl.viewangles[PITCH] -= move;
187         }
188 }
189
190
191 /*
192 ==============================================================================
193
194                                                 SCREEN FLASHES
195
196 ==============================================================================
197 */
198
199
200 /*
201 ===============
202 V_ParseDamage
203 ===============
204 */
205 void V_ParseDamage (void)
206 {
207         int i, armor, blood;
208         vec3_t from;
209         //vec3_t forward, right;
210         vec3_t localfrom;
211         entity_t *ent;
212         //float side;
213         float count;
214
215         armor = MSG_ReadByte ();
216         blood = MSG_ReadByte ();
217         for (i=0 ; i<3 ; i++)
218                 from[i] = MSG_ReadCoord ();
219
220         count = blood*0.5 + armor*0.5;
221         if (count < 10)
222                 count = 10;
223
224         cl.faceanimtime = cl.time + 0.2;                // put sbar face into pain frame
225
226         cl.cshifts[CSHIFT_DAMAGE].percent += 3*count;
227         if (cl.cshifts[CSHIFT_DAMAGE].percent < 0)
228                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
229         if (cl.cshifts[CSHIFT_DAMAGE].percent > 150)
230                 cl.cshifts[CSHIFT_DAMAGE].percent = 150;
231
232         if (armor > blood)
233         {
234                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 200;
235                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 100;
236                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 100;
237         }
238         else if (armor)
239         {
240                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 220;
241                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 50;
242                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 50;
243         }
244         else
245         {
246                 cl.cshifts[CSHIFT_DAMAGE].destcolor[0] = 255;
247                 cl.cshifts[CSHIFT_DAMAGE].destcolor[1] = 0;
248                 cl.cshifts[CSHIFT_DAMAGE].destcolor[2] = 0;
249         }
250
251         // calculate view angle kicks
252         if (cl.viewentity >= 0 && cl.viewentity < MAX_EDICTS && cl_entities[cl.viewentity].state_current.active)
253         {
254                 ent = &cl_entities[cl.viewentity];
255                 Matrix4x4_Transform(&ent->render.inversematrix, from, localfrom);
256                 VectorNormalize(localfrom);
257                 v_dmg_pitch = count * localfrom[0] * v_kickpitch.value;
258                 v_dmg_roll = count * localfrom[1] * v_kickroll.value;
259                 v_dmg_time = v_kicktime.value;
260         }
261 }
262
263 static cshift_t v_cshift;
264
265 /*
266 ==================
267 V_cshift_f
268 ==================
269 */
270 static void V_cshift_f (void)
271 {
272         v_cshift.destcolor[0] = atoi(Cmd_Argv(1));
273         v_cshift.destcolor[1] = atoi(Cmd_Argv(2));
274         v_cshift.destcolor[2] = atoi(Cmd_Argv(3));
275         v_cshift.percent = atoi(Cmd_Argv(4));
276 }
277
278
279 /*
280 ==================
281 V_BonusFlash_f
282
283 When you run over an item, the server sends this command
284 ==================
285 */
286 static void V_BonusFlash_f (void)
287 {
288         cl.cshifts[CSHIFT_BONUS].destcolor[0] = 215;
289         cl.cshifts[CSHIFT_BONUS].destcolor[1] = 186;
290         cl.cshifts[CSHIFT_BONUS].destcolor[2] = 69;
291         cl.cshifts[CSHIFT_BONUS].percent = 50;
292 }
293
294 /*
295 ==============================================================================
296
297                                                 VIEW RENDERING
298
299 ==============================================================================
300 */
301
302 extern matrix4x4_t viewmodelmatrix;
303
304 #include "cl_collision.h"
305
306 /*
307 ==================
308 V_CalcRefdef
309
310 ==================
311 */
312 extern float timerefreshangle;
313 void V_CalcRefdef (void)
314 {
315         static float oldz;
316         entity_t *ent;
317         float vieworg[3], viewangles[3], newz;
318         Matrix4x4_CreateIdentity(&viewmodelmatrix);
319         Matrix4x4_CreateIdentity(&r_refdef.viewentitymatrix);
320         if (cls.state == ca_connected && cls.signon == SIGNONS)
321         {
322                 // ent is the view entity (visible when out of body)
323                 ent = &cl_entities[cl.viewentity];
324                 if (cl.intermission)
325                 {
326                         // entity is a fixed camera, just copy the matrix
327                         Matrix4x4_Copy(&r_refdef.viewentitymatrix, &ent->render.matrix);
328                         Matrix4x4_Copy(&viewmodelmatrix, &ent->render.matrix);
329                 }
330                 else
331                 {
332                         // player can look around, so take the origin from the entity,
333                         // and the angles from the input system
334                         Matrix4x4_OriginFromMatrix(&ent->render.matrix, vieworg);
335                         VectorCopy(cl.viewangles, viewangles);
336
337                         // stair smoothing
338                         newz = vieworg[2];
339                         oldz -= newz;
340                         oldz += (cl.time - cl.oldtime) * cl_stairsmoothspeed.value;
341                         oldz = bound(-16, oldz, 0);
342                         vieworg[2] += oldz;
343                         oldz += newz;
344
345                         if (chase_active.value)
346                         {
347                                 // observing entity from third person
348                                 vec_t camback, camup, dist, forward[3], stop[3], chase_dest[3], normal[3];
349
350                                 camback = bound(0, chase_back.value, 128);
351                                 if (chase_back.value != camback)
352                                         Cvar_SetValueQuick(&chase_back, camback);
353                                 camup = bound(-48, chase_up.value, 96);
354                                 if (chase_up.value != camup)
355                                         Cvar_SetValueQuick(&chase_up, camup);
356
357                                 // this + 22 is to match view_ofs for compatibility with older versions
358                                 camup += 22;
359
360                                 if (gamemode == GAME_GOODVSBAD2 && chase_stevie.integer)
361                                 {
362                                         // look straight down from high above
363                                         viewangles[0] = 90;
364                                         camback = 2048;
365                                 }
366                                 AngleVectors(viewangles, forward, NULL, NULL);
367
368                                 // trace a little further so it hits a surface more consistently (to avoid 'snapping' on the edge of the range)
369                                 dist = -camback - 8;
370                                 chase_dest[0] = vieworg[0] + forward[0] * dist;
371                                 chase_dest[1] = vieworg[1] + forward[1] * dist;
372                                 chase_dest[2] = vieworg[2] + forward[2] * dist + camup;
373                                 CL_TraceLine(vieworg, chase_dest, stop, normal, true, NULL, SUPERCONTENTS_SOLID | SUPERCONTENTS_SKY);
374                                 vieworg[0] = stop[0] + forward[0] * 8 + normal[0] * 4;
375                                 vieworg[1] = stop[1] + forward[1] * 8 + normal[1] * 4;
376                                 vieworg[2] = stop[2] + forward[2] * 8 + normal[2] * 4;
377                         }
378                         else
379                         {
380                                 // first person view from entity
381                                 // angles
382                                 if (cl.stats[STAT_HEALTH] <= 0)
383                                         viewangles[ROLL] = 80;  // dead view angle
384                                 VectorAdd(viewangles, cl.punchangle, viewangles);
385                                 viewangles[ROLL] += V_CalcRoll(cl.viewangles, cl.velocity);
386                                 if (v_dmg_time > 0)
387                                 {
388                                         viewangles[ROLL] += v_dmg_time/v_kicktime.value*v_dmg_roll;
389                                         viewangles[PITCH] += v_dmg_time/v_kicktime.value*v_dmg_pitch;
390                                         v_dmg_time -= cl.frametime;
391                                 }
392                                 // origin
393                                 VectorAdd(vieworg, cl.punchvector, vieworg);
394                                 vieworg[2] += cl.viewheight;
395                                 if (cl.stats[STAT_HEALTH] > 0 && cl_bob.value && cl_bobcycle.value)
396                                 {
397                                         double bob, cycle;
398                                         // LordHavoc: this code is *weird*, but not replacable (I think it
399                                         // should be done in QC on the server, but oh well, quake is quake)
400                                         // LordHavoc: figured out bobup: the time at which the sin is at 180
401                                         // degrees (which allows lengthening or squishing the peak or valley)
402                                         cycle = cl.time / cl_bobcycle.value;
403                                         cycle -= (int) cycle;
404                                         if (cycle < cl_bobup.value)
405                                                 cycle = sin(M_PI * cycle / cl_bobup.value);
406                                         else
407                                                 cycle = sin(M_PI + M_PI * (cycle-cl_bobup.value)/(1.0 - cl_bobup.value));
408                                         // bob is proportional to velocity in the xy plane
409                                         // (don't count Z, or jumping messes it up)
410                                         bob = sqrt(cl.velocity[0]*cl.velocity[0] + cl.velocity[1]*cl.velocity[1]) * cl_bob.value;
411                                         bob = bob*0.3 + bob*0.7*cycle;
412                                         vieworg[2] += bound(-7, bob, 4);
413                                 }
414                         }
415                         // calculate a view matrix for rendering the scene
416                         if (v_idlescale.value)
417                                 Matrix4x4_CreateFromQuakeEntity(&r_refdef.viewentitymatrix, vieworg[0], vieworg[1], vieworg[2], viewangles[0] + v_idlescale.value * sin(cl.time*v_ipitch_cycle.value) * v_ipitch_level.value, viewangles[1] + v_idlescale.value * sin(cl.time*v_iyaw_cycle.value) * v_iyaw_level.value, viewangles[2] + v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value, 1);
418                         else
419                                 Matrix4x4_CreateFromQuakeEntity(&r_refdef.viewentitymatrix, vieworg[0], vieworg[1], vieworg[2], viewangles[0], viewangles[1], viewangles[2] + v_idlescale.value * sin(cl.time*v_iroll_cycle.value) * v_iroll_level.value, 1);
420                         // calculate a viewmodel matrix for use in view-attached entities
421                         Matrix4x4_CreateFromQuakeEntity(&viewmodelmatrix, vieworg[0], vieworg[1], vieworg[2], viewangles[0], viewangles[1], viewangles[2], 0.3);
422                 }
423         }
424 }
425
426 void V_FadeViewFlashs(void)
427 {
428         // drop the damage value
429         cl.cshifts[CSHIFT_DAMAGE].percent -= (cl.time - cl.oldtime)*150;
430         if (cl.cshifts[CSHIFT_DAMAGE].percent <= 0)
431                 cl.cshifts[CSHIFT_DAMAGE].percent = 0;
432         // drop the bonus value
433         cl.cshifts[CSHIFT_BONUS].percent -= (cl.time - cl.oldtime)*100;
434         if (cl.cshifts[CSHIFT_BONUS].percent <= 0)
435                 cl.cshifts[CSHIFT_BONUS].percent = 0;
436 }
437
438 void V_CalcViewBlend(void)
439 {
440         float a2;
441         int j;
442         r_refdef.viewblend[0] = 0;
443         r_refdef.viewblend[1] = 0;
444         r_refdef.viewblend[2] = 0;
445         r_refdef.viewblend[3] = 0;
446         if (cls.state == ca_connected && cls.signon == SIGNONS)
447         {
448                 // set contents color
449                 switch (CL_PointQ1Contents(r_vieworigin))
450                 {
451                 case CONTENTS_EMPTY:
452                 case CONTENTS_SOLID:
453                         cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = v_cshift.destcolor[0];
454                         cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = v_cshift.destcolor[1];
455                         cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = v_cshift.destcolor[2];
456                         cl.cshifts[CSHIFT_CONTENTS].percent = v_cshift.percent;
457                         break;
458                 case CONTENTS_LAVA:
459                         cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 255;
460                         cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
461                         cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 0;
462                         cl.cshifts[CSHIFT_CONTENTS].percent = 150 >> 1;
463                         break;
464                 case CONTENTS_SLIME:
465                         cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 0;
466                         cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 25;
467                         cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 5;
468                         cl.cshifts[CSHIFT_CONTENTS].percent = 150 >> 1;
469                         break;
470                 default:
471                         cl.cshifts[CSHIFT_CONTENTS].destcolor[0] = 130;
472                         cl.cshifts[CSHIFT_CONTENTS].destcolor[1] = 80;
473                         cl.cshifts[CSHIFT_CONTENTS].destcolor[2] = 50;
474                         cl.cshifts[CSHIFT_CONTENTS].percent = 128 >> 1;
475                 }
476
477                 if (cl.items & IT_QUAD)
478                 {
479                         cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
480                         cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 0;
481                         cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 255;
482                         cl.cshifts[CSHIFT_POWERUP].percent = 30;
483                 }
484                 else if (cl.items & IT_SUIT)
485                 {
486                         cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 0;
487                         cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
488                         cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
489                         cl.cshifts[CSHIFT_POWERUP].percent = 20;
490                 }
491                 else if (cl.items & IT_INVISIBILITY)
492                 {
493                         cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 100;
494                         cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 100;
495                         cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 100;
496                         cl.cshifts[CSHIFT_POWERUP].percent = 100;
497                 }
498                 else if (cl.items & IT_INVULNERABILITY)
499                 {
500                         cl.cshifts[CSHIFT_POWERUP].destcolor[0] = 255;
501                         cl.cshifts[CSHIFT_POWERUP].destcolor[1] = 255;
502                         cl.cshifts[CSHIFT_POWERUP].destcolor[2] = 0;
503                         cl.cshifts[CSHIFT_POWERUP].percent = 30;
504                 }
505                 else
506                         cl.cshifts[CSHIFT_POWERUP].percent = 0;
507
508                 // LordHavoc: fixed V_CalcBlend
509                 for (j = 0;j < NUM_CSHIFTS;j++)
510                 {
511                         a2 = bound(0.0f, cl.cshifts[j].percent * (1.0f / 255.0f), 1.0f);
512                         if (a2 > 0)
513                         {
514                                 VectorLerp(r_refdef.viewblend, a2, cl.cshifts[j].destcolor, r_refdef.viewblend);
515                                 r_refdef.viewblend[3] = 1 - (1 - r_refdef.viewblend[3]) * (1 - a2); // correct alpha multiply...  took a while to find it on the web
516                         }
517                 }
518                 // saturate color (to avoid blending in black)
519                 if (r_refdef.viewblend[3])
520                 {
521                         a2 = 1 / r_refdef.viewblend[3];
522                         VectorScale(r_refdef.viewblend, a2, r_refdef.viewblend);
523                 }
524
525                 r_refdef.viewblend[0] = bound(0.0f, r_refdef.viewblend[0] * (1.0f/255.0f), 1.0f);
526                 r_refdef.viewblend[1] = bound(0.0f, r_refdef.viewblend[1] * (1.0f/255.0f), 1.0f);
527                 r_refdef.viewblend[2] = bound(0.0f, r_refdef.viewblend[2] * (1.0f/255.0f), 1.0f);
528                 r_refdef.viewblend[3] = bound(0.0f, r_refdef.viewblend[3]                , 1.0f);
529         }
530 }
531
532 //============================================================================
533
534 /*
535 =============
536 V_Init
537 =============
538 */
539 void V_Init (void)
540 {
541         Cmd_AddCommand ("v_cshift", V_cshift_f);
542         Cmd_AddCommand ("bf", V_BonusFlash_f);
543         Cmd_AddCommand ("centerview", V_StartPitchDrift);
544
545         Cvar_RegisterVariable (&v_centermove);
546         Cvar_RegisterVariable (&v_centerspeed);
547
548         Cvar_RegisterVariable (&v_iyaw_cycle);
549         Cvar_RegisterVariable (&v_iroll_cycle);
550         Cvar_RegisterVariable (&v_ipitch_cycle);
551         Cvar_RegisterVariable (&v_iyaw_level);
552         Cvar_RegisterVariable (&v_iroll_level);
553         Cvar_RegisterVariable (&v_ipitch_level);
554
555         Cvar_RegisterVariable (&v_idlescale);
556         Cvar_RegisterVariable (&crosshair);
557
558         Cvar_RegisterVariable (&cl_rollspeed);
559         Cvar_RegisterVariable (&cl_rollangle);
560         Cvar_RegisterVariable (&cl_bob);
561         Cvar_RegisterVariable (&cl_bobcycle);
562         Cvar_RegisterVariable (&cl_bobup);
563
564         Cvar_RegisterVariable (&v_kicktime);
565         Cvar_RegisterVariable (&v_kickroll);
566         Cvar_RegisterVariable (&v_kickpitch);
567
568         Cvar_RegisterVariable (&cl_stairsmoothspeed);
569
570         Cvar_RegisterVariable (&chase_back);
571         Cvar_RegisterVariable (&chase_up);
572         Cvar_RegisterVariable (&chase_active);
573         if (gamemode == GAME_GOODVSBAD2)
574                 Cvar_RegisterVariable (&chase_stevie);
575 }
576