LCOV - code coverage report
Current view: top level - engine/render - hud.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 10.1 % 218 22
Test Date: 2026-01-07 07:46:09 Functions: 33.3 % 15 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_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 : }
        

Generated by: LCOV version 2.0-1