Line data Source code
1 : /* hud.cpp: hud & main menu rendering code
2 : *
3 : * includes hud compass, hud readouts, crosshair handling
4 : * main hud rendering
5 : */
6 : #include "../libprimis-headers/cube.h"
7 : #include "../../shared/geomexts.h"
8 : #include "../../shared/glemu.h"
9 : #include "../../shared/glexts.h"
10 :
11 : #include "hud.h"
12 : #include "rendergl.h"
13 : #include "renderlights.h"
14 : #include "renderparticles.h"
15 : #include "rendertext.h"
16 : #include "renderttf.h"
17 : #include "rendertimers.h"
18 : #include "renderwindow.h"
19 : #include "shader.h"
20 : #include "shaderparam.h"
21 : #include "texture.h"
22 :
23 : #include "interface/console.h"
24 : #include "interface/control.h"
25 : #include "interface/input.h"
26 : #include "interface/menus.h"
27 : #include "interface/ui.h"
28 :
29 : #include "world/octaedit.h"
30 :
31 : //internal functionality not seen by other files
32 : namespace
33 : {
34 : //damagecompass* vars control display of directional hints as to damage location
35 : VARNP(damagecompass, usedamagecompass, 0, 1, 1);
36 : VARP(damagecompassfade, 1, 1000, 10000); //sets milliseconds before damage hints fade
37 : VARP(damagecompasssize, 1, 30, 100);
38 : VARP(damagecompassalpha, 1, 25, 100);
39 : VARP(damagecompassmin, 1, 25, 1000);
40 : VARP(damagecompassmax, 1, 200, 1000);
41 :
42 : std::array<float, 8> damagedirs = { 0, 0, 0, 0, 0, 0, 0, 0 };
43 :
44 0 : void drawdamagecompass(int w, int h)
45 : {
46 0 : hudnotextureshader->set();
47 :
48 0 : int dirs = 0;
49 0 : float size = damagecompasssize/100.0f*std::min(h, w)/2.0f;
50 0 : for(float &dir : damagedirs)
51 : {
52 0 : if(dir > 0)
53 : {
54 0 : if(!dirs)
55 : {
56 0 : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
57 0 : gle::colorf(1, 0, 0, damagecompassalpha/100.0f);
58 0 : gle::defvertex();
59 0 : gle::begin(GL_TRIANGLES);
60 : }
61 0 : dirs++;
62 :
63 0 : float logscale = 32,
64 0 : scale = log(1 + (logscale - 1)*dir) / std::log(logscale),
65 0 : offset = -size/2.0f-std::min(h, w)/4.0f;
66 0 : matrix4x3 m;
67 0 : m.identity();
68 0 : m.settranslation(w/2, h/2, 0);
69 0 : m.rotate_around_z(dir*45/RAD);
70 0 : m.translate(0, offset, 0);
71 0 : m.scale(size*scale);
72 :
73 0 : gle::attrib(m.transform(vec2(1, 1)));
74 0 : gle::attrib(m.transform(vec2(-1, 1)));
75 0 : gle::attrib(m.transform(vec2(0, 0)));
76 :
77 : // fade in log space so short blips don't disappear too quickly
78 0 : scale -= static_cast<float>(curtime)/damagecompassfade;
79 0 : dir = scale > 0 ? (std::pow(logscale, scale) - 1) / (logscale - 1) : 0;
80 : }
81 : }
82 0 : if(dirs)
83 : {
84 0 : gle::end();
85 : }
86 0 : }
87 :
88 : int damageblendmillis = 0;
89 :
90 : //damagescreen variables control the display of a texture upon player being damaged
91 0 : VARFP(damagescreen, 0, 1, 1, { if(!damagescreen) damageblendmillis = 0; });
92 : VARP(damagescreenfactor, 1, 75, 100);
93 : VARP(damagescreenalpha, 1, 45, 100);
94 : VARP(damagescreenfade, 0, 1000, 1000); //number of ms before screen damage fades
95 : VARP(damagescreenmin, 1, 10, 1000);
96 : VARP(damagescreenmax, 1, 100, 1000);
97 :
98 0 : void drawdamagescreen(int w, int h)
99 : {
100 : static Texture *damagetex = nullptr;
101 : //preload this texture even if not going to draw, to prevent stutter when first hit
102 0 : if(!damagetex)
103 : {
104 0 : damagetex = textureload("media/interface/hud/damage.png", 3);
105 : }
106 0 : if(lastmillis >= damageblendmillis)
107 : {
108 0 : return;
109 : }
110 0 : hudshader->set();
111 0 : glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
112 0 : glBindTexture(GL_TEXTURE_2D, damagetex->id);
113 0 : float fade = damagescreenalpha/100.0f;
114 0 : if(damageblendmillis - lastmillis < damagescreenfade)
115 : {
116 0 : fade *= static_cast<float>(damageblendmillis - lastmillis)/damagescreenfade;
117 : }
118 0 : gle::colorf(fade, fade, fade, fade);
119 :
120 0 : hudquad(0, 0, w, h);
121 : }
122 :
123 : VAR(showstats, 0, 1, 1);
124 :
125 : //crosshair & cursor vars
126 : VARP(crosshairsize, 0, 15, 50);
127 : VARP(cursorsize, 0, 15, 30);
128 : VARP(crosshairfx, 0, 1, 1); // hit fx
129 :
130 :
131 2 : const char *defaultcrosshair(int index)
132 : {
133 2 : switch(index)
134 : {
135 0 : case 2:
136 : {
137 0 : return "media/interface/crosshair/default_hit.png";
138 : }
139 0 : case 1:
140 : {
141 0 : return "media/interface/crosshair/teammate.png";
142 : }
143 2 : default:
144 : {
145 2 : return "media/interface/crosshair/default.png";
146 : }
147 : }
148 : }
149 :
150 : const int maxcrosshairs = 4;
151 : std::array<Texture *, maxcrosshairs> crosshairs = { nullptr, nullptr, nullptr, nullptr };
152 :
153 1 : void loadcrosshair(const char *name, int i)
154 : {
155 1 : if(i < 0 || i >= maxcrosshairs)
156 : {
157 0 : return;
158 : }
159 1 : crosshairs[i] = name ? textureload(name, 3, true) : notexture;
160 1 : if(crosshairs[i] == notexture)
161 : {
162 1 : name = defaultcrosshair(i);
163 1 : if(!name)
164 : {
165 0 : name = "media/interface/crosshair/default.png";
166 : }
167 1 : crosshairs[i] = textureload(name, 3, true);
168 : }
169 : }
170 :
171 1 : void getcrosshair(int *i)
172 : {
173 1 : const char *name = "";
174 1 : if(*i >= 0 && *i < maxcrosshairs)
175 : {
176 1 : name = crosshairs[*i] ? crosshairs[*i]->name : defaultcrosshair(*i);
177 1 : if(!name)
178 : {
179 0 : name = "media/interface/crosshair/default.png";
180 : }
181 : }
182 1 : result(name);
183 1 : }
184 :
185 0 : void drawcrosshair(int w, int h, int crosshairindex)
186 : {
187 0 : bool windowhit = UI::hascursor();
188 0 : if(!windowhit && (!showhud || mainmenu))
189 : {
190 0 : return; //(!showhud || player->state==CS_SPECTATOR || player->state==CS_DEAD)) return;
191 : }
192 0 : float cx = 0.5f,
193 0 : cy = 0.5f,
194 : chsize;
195 : Texture *crosshair;
196 0 : if(windowhit)
197 : {
198 : static Texture *cursor = nullptr;
199 0 : if(!cursor)
200 : {
201 0 : cursor = textureload("media/interface/cursor.png", 3, true);
202 : }
203 0 : crosshair = cursor;
204 0 : chsize = cursorsize*w/900.0f;
205 0 : UI::getcursorpos(cx, cy);
206 : }
207 : else
208 : {
209 0 : int index = crosshairindex;
210 0 : if(index < 0)
211 : {
212 0 : return;
213 : }
214 0 : if(!crosshairfx)
215 : {
216 0 : index = 0;
217 : }
218 0 : crosshair = crosshairs[index];
219 0 : if(!crosshair)
220 : {
221 0 : loadcrosshair(nullptr, index);
222 0 : crosshair = crosshairs[index];
223 : }
224 0 : chsize = crosshairsize*w/900.0f;
225 : }
226 0 : vec color = vec(1, 1, 1);
227 0 : if(crosshair->type&Texture::ALPHA)
228 : {
229 0 : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
230 : }
231 : else
232 : {
233 0 : glBlendFunc(GL_ONE, GL_ONE);
234 : }
235 0 : hudshader->set();
236 0 : gle::color(color);
237 0 : float x = cx*w - (windowhit ? 0 : chsize/2.0f),
238 0 : y = cy*h - (windowhit ? 0 : chsize/2.0f);
239 0 : glBindTexture(GL_TEXTURE_2D, crosshair->id);
240 :
241 0 : hudquad(x, y, chsize, chsize);
242 : }
243 :
244 : //hud time displays
245 : VARP(wallclock, 0, 0, 1); //toggles hud readout
246 : VARP(wallclock24, 0, 0, 1); //toggles 12h (US) or 24h time
247 : VARP(wallclocksecs, 0, 0, 1); //seconds precision on hud readout
248 :
249 : time_t walltime = 0;
250 :
251 : //hud fps displays
252 : VARP(showfps, 0, 1, 1); //toggles showing game framerate
253 : VARP(showfpsrange, 0, 0, 1); //toggles showing min/max framerates as well
254 : }
255 :
256 : //externally relevant functionality
257 : //from here to iengine section, functions stay local to the libprimis codebase
258 :
259 0 : void gl_drawmainmenu()
260 : {
261 0 : renderbackground(nullptr, nullptr, nullptr, nullptr, true);
262 0 : }
263 :
264 0 : void gl_drawhud(int crosshairindex, void(* hud2d)())
265 : {
266 : /* we want to get the length of the frame at the end of the frame,
267 : * not the middle, so we have a persistent variable inside the
268 : * function scope
269 : */
270 : static int framemillis = 0;
271 :
272 0 : int w = hudw(),
273 0 : h = hudh();
274 0 : if(forceaspect)
275 : {
276 0 : w = static_cast<int>(ceil(h*forceaspect));
277 : }
278 :
279 0 : gettextres(w, h);
280 :
281 0 : hudmatrix.ortho(0, w, h, 0, -1, 1);
282 0 : resethudmatrix();
283 0 : resethudshader();
284 :
285 0 : pushfont();
286 0 : setfont("default_outline");
287 :
288 0 : debuglights();
289 :
290 0 : glEnable(GL_BLEND);
291 :
292 0 : if(!mainmenu)
293 : {
294 0 : drawdamagescreen(w, h);
295 0 : drawdamagecompass(w, h);
296 : }
297 :
298 0 : float conw = w/conscale,
299 0 : conh = h/conscale,
300 0 : abovehud = conh - FONTH;
301 0 : if(showhud && !mainmenu)
302 : {
303 0 : if(showstats)
304 : {
305 0 : pushhudscale(conscale);
306 0 : ttr.fontsize(42);
307 0 : int roffset = 0;
308 0 : if(showfps)
309 : {
310 : static int lastfps = 0;
311 : static std::array<int, 3> prevfps = { 0, 0, 0 },
312 : curfps = { 0, 0, 0 };
313 0 : if(totalmillis - lastfps >= statrate)
314 : {
315 0 : prevfps = curfps;
316 0 : lastfps = totalmillis - (totalmillis%statrate);
317 : }
318 : std::array<int, 3> nextfps;
319 0 : getfps(nextfps[0], nextfps[1], nextfps[2]);
320 0 : for(size_t i = 0; i < curfps.size(); ++i)
321 : {
322 0 : if(prevfps[i]==curfps[i])
323 : {
324 0 : curfps[i] = nextfps[i];
325 : }
326 : }
327 0 : if(showfpsrange)
328 : {
329 : char fpsstring[20];
330 0 : std::sprintf(fpsstring, "fps %d+%d-%d", curfps[0], curfps[1], curfps[2]);
331 0 : ttr.renderttf(fpsstring, {0xFF, 0xFF, 0xFF, 0}, conw-(1000*conscale), conh-(360*conscale));
332 : //draw_textf("fps %d+%d-%d", conw-7*FONTH, conh-FONTH*3/2, curfps[0], curfps[1], curfps[2]);
333 : }
334 : else
335 : {
336 : char fpsstring[20];
337 0 : std::sprintf(fpsstring, "fps %d", curfps[0]);
338 0 : ttr.renderttf(fpsstring, {0xFF, 0xFF, 0xFF, 0}, conw-(1000*conscale), conh-(360*conscale));
339 : }
340 0 : roffset += FONTH;
341 : }
342 0 : printtimers(conw, conh, framemillis);
343 0 : if(wallclock)
344 : {
345 0 : if(!walltime)
346 : {
347 0 : walltime = std::time(nullptr);
348 0 : walltime -= totalmillis/1000;
349 0 : if(!walltime)
350 : {
351 0 : walltime++;
352 : }
353 : }
354 0 : time_t walloffset = walltime + totalmillis/1000;
355 0 : std::tm* localvals = std::localtime(&walloffset);
356 : static string buf;
357 0 : if(localvals && std::strftime(buf, sizeof(buf), wallclocksecs ? (wallclock24 ? "%H:%M:%S" : "%I:%M:%S%p") : (wallclock24 ? "%H:%M" : "%I:%M%p"), localvals))
358 : {
359 : // hack because not all platforms (windows) support %P lowercase option
360 : // also strip leading 0 from 12 hour time
361 0 : char *dst = buf;
362 0 : const char *src = &buf[!wallclock24 && buf[0]=='0' ? 1 : 0];
363 0 : while(*src)
364 : {
365 0 : *dst++ = tolower(*src++);
366 : }
367 0 : *dst++ = '\0';
368 :
369 0 : ttr.renderttf(buf, { 0xFF, 0xFF, 0xFF, 0 }, conw-(1000*conscale), conh-(540*conscale));
370 : //draw_text(buf, conw-5*FONTH, conh-FONTH*3/2-roffset);
371 0 : roffset += FONTH;
372 : }
373 : }
374 0 : pophudmatrix();
375 : }
376 0 : if(!editmode)
377 : {
378 0 : resethudshader();
379 0 : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
380 0 : hud2d();
381 0 : abovehud = std::min(abovehud, conh);
382 : }
383 0 : rendertexturepanel(w, h);
384 : }
385 :
386 0 : abovehud = std::min(abovehud, conh*UI::abovehud());
387 :
388 0 : pushhudscale(conscale);
389 0 : abovehud -= rendercommand(FONTH/2, abovehud - FONTH/2, conw-FONTH);
390 0 : if(showhud && !UI::uivisible("fullconsole"))
391 : {
392 0 : renderconsole(conw, conh, abovehud - FONTH/2);
393 : }
394 0 : pophudmatrix();
395 0 : drawcrosshair(w, h, crosshairindex);
396 0 : glDisable(GL_BLEND);
397 0 : popfont();
398 0 : if(frametimer)
399 : {
400 0 : glFinish();
401 0 : framemillis = getclockmillis() - totalmillis;
402 : }
403 0 : }
404 :
405 0 : void writecrosshairs(std::fstream& f)
406 : {
407 0 : for(int i = 0; i < maxcrosshairs; ++i)
408 : {
409 0 : if(crosshairs[i] && crosshairs[i]!=notexture)
410 : {
411 0 : f << "loadcrosshair " << escapestring(crosshairs[i]->name) << " " << i << std::endl;
412 : }
413 : }
414 0 : f << std::endl;
415 0 : }
416 :
417 0 : void resethudshader()
418 : {
419 0 : hudshader->set();
420 0 : gle::colorf(1, 1, 1);
421 0 : }
422 :
423 : FVARP(conscale, 1e-3f, 0.33f, 1e3f); //size of readouts, console, and history
424 : //note: fps displayed is the average over the statrate duration
425 : VAR(statrate, 1, 200, 1000); //update time for fps and edit stats
426 : VAR(showhud, 0, 1, 1);
427 :
428 0 : void vectoryawpitch(const vec &v, float &yaw, float &pitch)
429 : {
430 0 : if(v.iszero())
431 : {
432 0 : yaw = pitch = 0;
433 : }
434 : else
435 : {
436 0 : yaw = -std::atan2(v.x, v.y)*RAD;
437 0 : pitch = std::asin(v.z/v.magnitude())*RAD;
438 : }
439 0 : }
440 :
441 : // iengine functionality
442 0 : void damagecompass(int n, const vec &loc)
443 : {
444 0 : if(!usedamagecompass || minimized)
445 : {
446 0 : return;
447 : }
448 0 : vec delta(loc);
449 0 : delta.sub(camera1->o);
450 0 : float yaw = 0,
451 : pitch;
452 0 : if(delta.magnitude() > 4)
453 : {
454 0 : vectoryawpitch(delta, yaw, pitch);
455 0 : yaw -= camera1->yaw;
456 : }
457 0 : if(yaw >= 360)
458 : {
459 0 : yaw = std::fmod(yaw, 360);
460 : }
461 0 : else if(yaw < 0)
462 : {
463 0 : yaw = 360 - std::fmod(-yaw, 360);
464 : }
465 0 : int dir = (static_cast<int>(yaw+22.5f)%360)/45; //360/45 = 8, so divide into 8 octants with 0 degrees centering octant 0
466 0 : damagedirs[dir] += std::max(n, damagecompassmin)/static_cast<float>(damagecompassmax);
467 0 : if(damagedirs[dir]>1)
468 : {
469 0 : damagedirs[dir] = 1;
470 : }
471 : }
472 :
473 0 : void damageblend(int n)
474 : {
475 0 : if(!damagescreen || minimized)
476 : {
477 0 : return;
478 : }
479 0 : if(lastmillis > damageblendmillis)
480 : {
481 0 : damageblendmillis = lastmillis;
482 : }
483 0 : damageblendmillis += std::clamp(n, damagescreenmin, damagescreenmax)*damagescreenfactor;
484 : }
485 :
486 1 : void inithudcmds()
487 : {
488 2 : addcommand("loadcrosshair", reinterpret_cast<identfun>(+[](const char *name, int *i){loadcrosshair(name, *i);}), "si", Id_Command);
489 1 : addcommand("getcrosshair", reinterpret_cast<identfun>(getcrosshair), "i", Id_Command);
490 1 : }
|