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_drawhud(int crosshairindex, void(* hud2d)())
261 : {
262 : /* we want to get the length of the frame at the end of the frame,
263 : * not the middle, so we have a persistent variable inside the
264 : * function scope
265 : */
266 : static int framemillis = 0;
267 :
268 0 : int w = hudw(),
269 0 : h = hudh();
270 0 : if(forceaspect)
271 : {
272 0 : w = static_cast<int>(ceil(h*forceaspect));
273 : }
274 :
275 0 : gettextres(w, h);
276 :
277 0 : hudmatrix.ortho(0, w, h, 0, -1, 1);
278 0 : resethudmatrix();
279 0 : resethudshader();
280 :
281 0 : pushfont();
282 0 : setfont("default_outline");
283 :
284 0 : debuglights();
285 :
286 0 : glEnable(GL_BLEND);
287 :
288 0 : if(!mainmenu)
289 : {
290 0 : drawdamagescreen(w, h);
291 0 : drawdamagecompass(w, h);
292 : }
293 :
294 0 : float conw = w/conscale,
295 0 : conh = h/conscale,
296 0 : abovehud = conh - FONTH;
297 0 : if(showhud && !mainmenu)
298 : {
299 0 : if(showstats)
300 : {
301 0 : pushhudscale(conscale);
302 0 : ttr.fontsize(42);
303 0 : int roffset = 0;
304 0 : if(showfps)
305 : {
306 : static int lastfps = 0;
307 : static std::array<int, 3> prevfps = { 0, 0, 0 },
308 : curfps = { 0, 0, 0 };
309 0 : if(totalmillis - lastfps >= statrate)
310 : {
311 0 : prevfps = curfps;
312 0 : lastfps = totalmillis - (totalmillis%statrate);
313 : }
314 : std::array<int, 3> nextfps;
315 0 : getfps(nextfps[0], nextfps[1], nextfps[2]);
316 0 : for(size_t i = 0; i < curfps.size(); ++i)
317 : {
318 0 : if(prevfps[i]==curfps[i])
319 : {
320 0 : curfps[i] = nextfps[i];
321 : }
322 : }
323 0 : if(showfpsrange)
324 : {
325 : std::array<char, 20> fpsstring;
326 0 : std::sprintf(fpsstring.data(), "fps %d+%d-%d", curfps[0], curfps[1], curfps[2]);
327 0 : ttr.renderttf(fpsstring.data(), {0xFF, 0xFF, 0xFF, 0}, conw-(1000*conscale), conh-(360*conscale));
328 : //draw_textf("fps %d+%d-%d", conw-7*FONTH, conh-FONTH*3/2, curfps[0], curfps[1], curfps[2]);
329 : }
330 : else
331 : {
332 : std::array<char, 20> fpsstring;
333 0 : std::sprintf(fpsstring.data(), "fps %d", curfps[0]);
334 0 : ttr.renderttf(fpsstring.data(), {0xFF, 0xFF, 0xFF, 0}, conw-(1000*conscale), conh-(360*conscale));
335 : }
336 0 : roffset += FONTH;
337 : }
338 0 : printtimers(conw, framemillis);
339 0 : if(wallclock)
340 : {
341 0 : if(!walltime)
342 : {
343 0 : walltime = std::time(nullptr);
344 0 : walltime -= totalmillis/1000;
345 0 : if(!walltime)
346 : {
347 0 : walltime++;
348 : }
349 : }
350 0 : time_t walloffset = walltime + totalmillis/1000;
351 0 : std::tm* localvals = std::localtime(&walloffset);
352 : static string buf;
353 0 : if(localvals && std::strftime(buf, sizeof(buf), wallclocksecs ? (wallclock24 ? "%H:%M:%S" : "%I:%M:%S%p") : (wallclock24 ? "%H:%M" : "%I:%M%p"), localvals))
354 : {
355 : // hack because not all platforms (windows) support %P lowercase option
356 : // also strip leading 0 from 12 hour time
357 0 : char *dst = buf;
358 0 : const char *src = &buf[!wallclock24 && buf[0]=='0' ? 1 : 0];
359 0 : while(*src)
360 : {
361 0 : *dst++ = tolower(*src++);
362 : }
363 0 : *dst++ = '\0';
364 :
365 0 : ttr.renderttf(buf, { 0xFF, 0xFF, 0xFF, 0 }, conw-(1000*conscale), conh-(540*conscale));
366 : //draw_text(buf, conw-5*FONTH, conh-FONTH*3/2-roffset);
367 0 : roffset += FONTH;
368 : }
369 : }
370 0 : pophudmatrix();
371 : }
372 0 : if(!editmode)
373 : {
374 0 : resethudshader();
375 0 : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
376 0 : hud2d();
377 0 : abovehud = std::min(abovehud, conh);
378 : }
379 0 : rendertexturepanel(w, h);
380 : }
381 :
382 0 : abovehud = std::min(abovehud, conh*UI::abovehud());
383 :
384 0 : pushhudscale(conscale);
385 0 : abovehud -= rendercommand(FONTH/2, abovehud - FONTH/2, conw-FONTH);
386 0 : if(showhud && !UI::uivisible("fullconsole"))
387 : {
388 0 : renderconsole(conw, conh, abovehud - FONTH/2);
389 : }
390 0 : pophudmatrix();
391 0 : drawcrosshair(w, h, crosshairindex);
392 0 : glDisable(GL_BLEND);
393 0 : popfont();
394 0 : if(frametimer)
395 : {
396 0 : glFinish();
397 0 : framemillis = getclockmillis() - totalmillis;
398 : }
399 0 : }
400 :
401 0 : void writecrosshairs(std::fstream& f)
402 : {
403 0 : for(int i = 0; i < maxcrosshairs; ++i)
404 : {
405 0 : if(crosshairs[i] && crosshairs[i]!=notexture)
406 : {
407 0 : f << "loadcrosshair " << escapestring(crosshairs[i]->name) << " " << i << std::endl;
408 : }
409 : }
410 0 : f << std::endl;
411 0 : }
412 :
413 0 : void resethudshader()
414 : {
415 0 : hudshader->set();
416 0 : gle::colorf(1, 1, 1);
417 0 : }
418 :
419 : FVARP(conscale, 1e-3f, 0.33f, 1e3f); //size of readouts, console, and history
420 : //note: fps displayed is the average over the statrate duration
421 : VAR(statrate, 1, 200, 1000); //update time for fps and edit stats
422 : VAR(showhud, 0, 1, 1);
423 :
424 0 : void vectoryawpitch(const vec &v, float &yaw, float &pitch)
425 : {
426 0 : if(v.iszero())
427 : {
428 0 : yaw = pitch = 0;
429 : }
430 : else
431 : {
432 0 : yaw = -std::atan2(v.x, v.y)*RAD;
433 0 : pitch = std::asin(v.z/v.magnitude())*RAD;
434 : }
435 0 : }
436 :
437 : // iengine functionality
438 0 : void damagecompass(int n, const vec &loc)
439 : {
440 0 : if(!usedamagecompass || minimized)
441 : {
442 0 : return;
443 : }
444 0 : vec delta(loc);
445 0 : delta.sub(camera1->o);
446 0 : float yaw = 0,
447 : pitch;
448 0 : if(delta.magnitude() > 4)
449 : {
450 0 : vectoryawpitch(delta, yaw, pitch);
451 0 : yaw -= camera1->yaw;
452 : }
453 0 : if(yaw >= 360)
454 : {
455 0 : yaw = std::fmod(yaw, 360);
456 : }
457 0 : else if(yaw < 0)
458 : {
459 0 : yaw = 360 - std::fmod(-yaw, 360);
460 : }
461 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
462 0 : damagedirs[dir] += std::max(n, damagecompassmin)/static_cast<float>(damagecompassmax);
463 0 : if(damagedirs[dir]>1)
464 : {
465 0 : damagedirs[dir] = 1;
466 : }
467 : }
468 :
469 0 : void damageblend(int n)
470 : {
471 0 : if(!damagescreen || minimized)
472 : {
473 0 : return;
474 : }
475 0 : if(lastmillis > damageblendmillis)
476 : {
477 0 : damageblendmillis = lastmillis;
478 : }
479 0 : damageblendmillis += std::clamp(n, damagescreenmin, damagescreenmax)*damagescreenfactor;
480 : }
481 :
482 1 : void inithudcmds()
483 : {
484 2 : addcommand("loadcrosshair", reinterpret_cast<identfun>(+[](const char *name, const int *i){loadcrosshair(name, *i);}), "si", Id_Command);
485 1 : addcommand("getcrosshair", reinterpret_cast<identfun>(getcrosshair), "i", Id_Command);
486 1 : }
|