LCOV - code coverage report
Current view: top level - engine/render - hud.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 10.0 % 221 22
Test Date: 2025-11-03 06:43:22 Functions: 31.2 % 16 5

            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 : }
        

Generated by: LCOV version 2.0-1