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