LCOV - code coverage report
Current view: top level - engine/render - stain.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 0.0 % 586 0
Test Date: 2025-02-18 06:21:28 Functions: 0.0 % 46 0

            Line data    Source code
       1              : /* stain.cpp: dynamic world geometry decals
       2              :  *
       3              :  * Stains are mostly useful for player-created effects, mainly those left behind
       4              :  * by weapons. They fade at a fixed period (not controllable by individual stains)
       5              :  * and can be culled if there are too many (`maxstaintris`).
       6              :  *
       7              :  * Stains apply to world (octree) geometry only and cannot be applied to models
       8              :  * (players, mapmodels, or otherwise).
       9              :  *
      10              :  * The performance of stains is generally high enough that many thousands of stain
      11              :  * particles must be present at once for there to be a noticable performance drop.
      12              :  */
      13              : 
      14              : #include "../libprimis-headers/cube.h"
      15              : #include "../../shared/geomexts.h"
      16              : #include "../../shared/glemu.h"
      17              : #include "../../shared/glexts.h"
      18              : 
      19              : #include <memory>
      20              : #include <optional>
      21              : 
      22              : #include "octarender.h"
      23              : #include "rendergl.h"
      24              : #include "renderlights.h"
      25              : #include "rendermodel.h"
      26              : #include "renderwindow.h"
      27              : #include "stain.h"
      28              : #include "shader.h"
      29              : #include "shaderparam.h"
      30              : #include "texture.h"
      31              : 
      32              : #include "interface/console.h"
      33              : #include "interface/control.h"
      34              : 
      35              : #include "world/bih.h"
      36              : #include "world/entities.h"
      37              : #include "world/material.h"
      38              : #include "world/octaworld.h"
      39              : #include "world/world.h"
      40              : 
      41              : #include "model/model.h"
      42              : 
      43              : void initstains();
      44              : 
      45            0 : VARFP(maxstaintris, 1, 2048, 16384, initstains());  //need to call initstains to potentially cull extra stain tris
      46              : VARP(stainfade, 1000, 15000, 60000);                //number of milliseconds before stain geom fades
      47              : VAR(debugstain, 0, 0, 1);                           //toggles printout of stain information to console
      48              : 
      49              : //stainrenderer: handles rendering to the gbuffer of a single class of particle
      50              : //each stainrenderer handles the rendering of a single type of particle
      51              : //all the level's particles of a single type will be handled by a single stainrenderer object
      52              : class stainrenderer
      53              : {
      54              :     public:
      55              :         enum
      56              :         {
      57              :             StainFlag_Rnd4       = 1<<0,
      58              :             StainFlag_Rotate     = 1<<1,
      59              :             StainFlag_InvMod     = 1<<2,
      60              :             StainFlag_Overbright = 1<<3,
      61              :             StainFlag_Glow       = 1<<4,
      62              :             StainFlag_Saturate   = 1<<5
      63              :         };
      64              : 
      65            0 :         stainrenderer(const char *texname, int flags = 0, int fadeintime = 0, int fadeouttime = 1000, int timetolive = -1)
      66            0 :             : flags(flags),
      67            0 :               fadeintime(fadeintime), fadeouttime(fadeouttime), timetolive(timetolive),
      68            0 :               maxstains(0), startstain(0), endstain(0),
      69            0 :               stainu(0), stainv(0), tex(nullptr), stains(nullptr), texname(texname)
      70              :         {
      71            0 :         }
      72              : 
      73            0 :         ~stainrenderer()
      74              :         {
      75            0 :             delete[] stains;
      76            0 :         }
      77              : 
      78            0 :         bool usegbuffer() const
      79              :         {
      80            0 :             return !(flags&(StainFlag_InvMod|StainFlag_Glow));
      81              :         }
      82              : 
      83            0 :         void init(int tris)
      84              :         {
      85            0 :             if(stains)
      86              :             {
      87            0 :                 delete[] stains; //do not need to set null, immediately reassigned below
      88            0 :                 maxstains = startstain = endstain = 0;
      89              :             }
      90            0 :             stains = new staininfo[tris];
      91            0 :             maxstains = tris;
      92            0 :             for(int i = 0; i < StainBuffer_Number; ++i)
      93              :             {
      94            0 :                 verts[i].init(i == StainBuffer_Transparent ? tris/2 : tris);
      95              :             }
      96            0 :         }
      97              : 
      98            0 :         void preload()
      99              :         {
     100            0 :             tex = textureload(texname, 3);
     101            0 :         }
     102              : 
     103            0 :         bool hasstains(int sbuf)
     104              :         {
     105            0 :             return verts[sbuf].hasverts();
     106              :         }
     107              : 
     108            0 :         void clearstains()
     109              :         {
     110            0 :             startstain = endstain = 0;
     111            0 :             for(stainbuffer &i : verts)
     112              :             {
     113            0 :                 i.clear();
     114              :             }
     115            0 :         }
     116              : 
     117            0 :         void clearfadedstains()
     118              :         {
     119            0 :             int threshold = lastmillis - (timetolive>=0 ? timetolive : stainfade) - fadeouttime;
     120            0 :             staininfo *d = &stains[startstain],
     121            0 :                       *end = &stains[endstain < startstain ? maxstains : endstain],
     122            0 :                       *cleared[StainBuffer_Number] = {nullptr};
     123            0 :             for(; d < end && d->millis <= threshold; d++)
     124            0 :                 cleared[d->owner] = d;
     125            0 :             if(d >= end && endstain < startstain)
     126              :             {
     127            0 :                 for(d = stains, end = &stains[endstain]; d < end && d->millis <= threshold; d++)
     128              :                 {
     129            0 :                     cleared[d->owner] = d;
     130              :                 }
     131              :             }
     132            0 :             startstain = d - stains;
     133            0 :             if(startstain == endstain)
     134              :             {
     135            0 :                 for(stainbuffer &i : verts)
     136              :                 {
     137            0 :                     i.clear();
     138              :                 }
     139              :             }
     140              :             else
     141              :             {
     142            0 :                 for(int i = 0; i < StainBuffer_Number; ++i)
     143              :                 {
     144            0 :                     if(cleared[i])
     145              :                     {
     146            0 :                         verts[i].clearstains(*cleared[i]);
     147              :                     }
     148              :                 }
     149              :             }
     150            0 :         }
     151              : 
     152            0 :         void fadeinstains()
     153              :         {
     154            0 :             if(!fadeintime)
     155              :             {
     156            0 :                 return;
     157              :             }
     158            0 :             staininfo *d = &stains[endstain],
     159            0 :                       *end = &stains[endstain < startstain ? 0 : startstain];
     160            0 :             while(d > end)
     161              :             {
     162            0 :                 d--;
     163            0 :                 int fade = lastmillis - d->millis;
     164            0 :                 if(fade < fadeintime)
     165              :                 {
     166            0 :                     fadestain(*d, (fade<<8)/fadeintime);
     167              :                 }
     168            0 :                 else if(faded(*d))
     169              :                 {
     170            0 :                     fadestain(*d, 255);
     171              :                 }
     172              :                 else
     173              :                 {
     174            0 :                     return;
     175              :                 }
     176              :             }
     177            0 :             if(endstain < startstain)
     178              :             {
     179            0 :                 d = &stains[maxstains];
     180            0 :                 end = &stains[startstain];
     181            0 :                 while(d > end)
     182              :                 {
     183            0 :                     d--;
     184            0 :                     int fade = lastmillis - d->millis;
     185            0 :                     if(fade < fadeintime)
     186              :                     {
     187            0 :                         fadestain(*d, (fade<<8)/fadeintime);
     188              :                     }
     189            0 :                     else if(faded(*d))
     190              :                     {
     191            0 :                         fadestain(*d, 255);
     192              :                     }
     193              :                     else
     194              :                     {
     195            0 :                         return;
     196              :                     }
     197              :                 }
     198              :             }
     199              :         }
     200              : 
     201            0 :         void fadeoutstains()
     202              :         {
     203            0 :             staininfo *d = &stains[startstain],
     204            0 :                       *end = &stains[endstain < startstain ? maxstains : endstain];
     205            0 :             int offset = (timetolive>=0 ? timetolive : stainfade) + fadeouttime - lastmillis;
     206            0 :             while(d < end)
     207              :             {
     208            0 :                 int fade = d->millis + offset;
     209            0 :                 if(fade >= fadeouttime)
     210              :                 {
     211            0 :                     return;
     212              :                 }
     213            0 :                 fadestain(*d, (fade<<8)/fadeouttime);
     214            0 :                 d++;
     215              :             }
     216            0 :             if(endstain < startstain)
     217              :             {
     218            0 :                 d = stains;
     219            0 :                 end = &stains[endstain];
     220            0 :                 while(d < end)
     221              :                 {
     222            0 :                     int fade = d->millis + offset;
     223            0 :                     if(fade >= fadeouttime)
     224              :                     {
     225            0 :                         return;
     226              :                     }
     227            0 :                     fadestain(*d, (fade<<8)/fadeouttime);
     228            0 :                     d++;
     229              :                 }
     230              :             }
     231              :         }
     232              : 
     233            0 :         static void setuprenderstate(int sbuf, bool gbuf, int layer)
     234              :         {
     235            0 :             if(gbuf)
     236              :             {
     237            0 :                 maskgbuffer(sbuf == StainBuffer_Transparent ? "cg" : "c");
     238              :             }
     239              :             else
     240              :             {
     241            0 :                 zerofogcolor();
     242              :             }
     243              : 
     244            0 :             if(layer && ghasstencil)
     245              :             {
     246            0 :                 glStencilFunc(GL_EQUAL, layer, 0x07);
     247            0 :                 glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
     248              :             }
     249              : 
     250            0 :             glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
     251              : 
     252            0 :             enablepolygonoffset(GL_POLYGON_OFFSET_FILL);
     253              : 
     254            0 :             glDepthMask(GL_FALSE);
     255            0 :             glEnable(GL_BLEND);
     256              : 
     257            0 :             gle::enablevertex();
     258            0 :             gle::enabletexcoord0();
     259            0 :             gle::enablecolor();
     260            0 :         }
     261              : 
     262            0 :         static void cleanuprenderstate(int sbuf, bool gbuf)
     263              :         {
     264            0 :             gle::clearvbo();
     265              : 
     266            0 :             gle::disablevertex();
     267            0 :             gle::disabletexcoord0();
     268            0 :             gle::disablecolor();
     269              : 
     270            0 :             glDepthMask(GL_TRUE);
     271            0 :             glDisable(GL_BLEND);
     272              : 
     273            0 :             disablepolygonoffset(GL_POLYGON_OFFSET_FILL);
     274              : 
     275            0 :             glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
     276              : 
     277            0 :             if(gbuf)
     278              :             {
     279            0 :                 maskgbuffer(sbuf == StainBuffer_Transparent ? "cndg" : "cnd");
     280              :             }
     281              :             else
     282              :             {
     283            0 :                 resetfogcolor();
     284              :             }
     285            0 :         }
     286              : 
     287            0 :         void cleanup()
     288              :         {
     289            0 :             for(stainbuffer &i : verts)
     290              :             {
     291            0 :                 i.cleanup();
     292              :             }
     293            0 :         }
     294              : 
     295            0 :         void render(int sbuf)
     296              :         {
     297            0 :             float colorscale = 1,
     298            0 :                   alphascale = 1;
     299            0 :             if(flags&StainFlag_Overbright)
     300              :             {
     301            0 :                 glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
     302            0 :                 SETVARIANT(overbrightstain, sbuf == StainBuffer_Transparent ? 0 : -1, 0);
     303              :             }
     304            0 :             else if(flags&StainFlag_Glow)
     305              :             {
     306            0 :                 glBlendFunc(GL_ONE, GL_ONE);
     307            0 :                 colorscale = ldrscale;
     308            0 :                 if(flags&StainFlag_Saturate)
     309              :                 {
     310            0 :                     colorscale *= 2;
     311              :                 }
     312            0 :                 alphascale = 0;
     313            0 :                 SETSHADER(foggedstain,);
     314              :             }
     315            0 :             else if(flags&StainFlag_InvMod)
     316              :             {
     317            0 :                 glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
     318            0 :                 alphascale = 0;
     319            0 :                 SETSHADER(foggedstain,);
     320              :             }
     321              :             else
     322              :             {
     323            0 :                 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
     324            0 :                 colorscale = ldrscale;
     325            0 :                 if(flags&StainFlag_Saturate)
     326              :                 {
     327            0 :                     colorscale *= 2;
     328              :                 }
     329            0 :                 SETVARIANT(stain, sbuf == StainBuffer_Transparent ? 0 : -1, 0);
     330              :             }
     331            0 :             LOCALPARAMF(colorscale, colorscale, colorscale, colorscale, alphascale);
     332              : 
     333            0 :             glBindTexture(GL_TEXTURE_2D, tex->id);
     334              : 
     335            0 :             verts[sbuf].render();
     336            0 :         }
     337              : 
     338            0 :         void addstain(const vec &center, const vec &dir, float radius, const bvec &color, int info, const cubeworld &world)
     339              :         {
     340            0 :             if(dir.iszero())
     341              :             {
     342            0 :                 return;
     343              :             }
     344            0 :             bbmin = ivec(center).sub(radius);
     345            0 :             bbmax = ivec(center).add(radius).add(1);
     346              : 
     347            0 :             staincolor = vec4<uchar>(color, 255);
     348            0 :             staincenter = center;
     349            0 :             stainradius = radius;
     350            0 :             stainnormal = dir;
     351              : 
     352            0 :             staintangent = vec(dir.z, -dir.x, dir.y);
     353            0 :             staintangent.project(dir);
     354              : 
     355            0 :             if(flags&StainFlag_Rotate)
     356              :             {
     357            0 :                 staintangent.rotate(sincos360[randomint(360)], dir);
     358              :             }
     359            0 :             staintangent.normalize();
     360            0 :             stainbitangent.cross(staintangent, dir);
     361            0 :             if(flags&StainFlag_Rnd4)
     362              :             {
     363            0 :                 stainu = 0.5f*(info&1);
     364            0 :                 stainv = 0.5f*((info>>1)&1);
     365              :             }
     366              : 
     367            0 :             for(int i = 0; i < StainBuffer_Number; ++i)
     368              :             {
     369            0 :                 verts[i].lastvert = verts[i].endvert;
     370              :             }
     371            0 :             gentris(*world.worldroot, ivec(0, 0, 0), rootworld.mapsize()>>1);
     372            0 :             for(int i = 0; i < StainBuffer_Number; ++i)
     373              :             {
     374            0 :                 stainbuffer &buf = verts[i];
     375            0 :                 if(buf.endvert == buf.lastvert)
     376              :                 {
     377            0 :                     continue;
     378              :                 }
     379            0 :                 if(debugstain)
     380              :                 {
     381            0 :                     int nverts = buf.nextverts();
     382              :                     static const char * const sbufname[StainBuffer_Number] = { "opaque", "transparent", "mapmodel" };
     383            0 :                     conoutf(Console_Debug, "tris = %d, verts = %d, total tris = %d, %s", nverts/3, nverts, buf.totaltris(), sbufname[i]);
     384              :                 }
     385              : 
     386            0 :                 staininfo &d = newstain();
     387            0 :                 d.owner = i;
     388            0 :                 d.color = color;
     389            0 :                 d.millis = lastmillis;
     390            0 :                 d.startvert = buf.lastvert;
     391            0 :                 d.endvert = buf.endvert;
     392            0 :                 buf.addstain();
     393              :             }
     394              :         }
     395              : 
     396            0 :         void genmmtri(const std::array<vec, 3> &v) // gen map model triangles
     397              :         {
     398            0 :             vec n;
     399            0 :             n.cross(v[0], v[1], v[2]).normalize();
     400            0 :             float facing = n.dot(stainnormal);
     401            0 :             if(facing <= 0)
     402              :             {
     403            0 :                 return;
     404              :             }
     405            0 :             vec p = vec(v[0]).sub(staincenter);
     406            0 :             float dist = n.dot(p);
     407            0 :             if(std::fabs(dist) > stainradius)
     408              :             {
     409            0 :                 return;
     410              :             }
     411            0 :             vec pcenter = vec(n).mul(dist).add(staincenter);
     412            0 :             vec ft, fb;
     413            0 :             ft.orthogonal(n);
     414            0 :             ft.normalize();
     415            0 :             fb.cross(ft, n);
     416            0 :             vec pt = vec(ft).mul(ft.dot(staintangent)).add(vec(fb).mul(fb.dot(staintangent))).normalize(),
     417            0 :                 pb = vec(ft).mul(ft.dot(stainbitangent)).add(vec(fb).mul(fb.dot(stainbitangent))).project(pt).normalize();
     418            0 :             vec v1[3+4],
     419            0 :                 v2[3+4];
     420            0 :             float ptc = pt.dot(pcenter),
     421            0 :                   pbc = pb.dot(pcenter);
     422            0 :             int numv = polyclip(v.data(), v.size(), pt, ptc - stainradius, ptc + stainradius, v1);
     423            0 :             if(numv<3) //check with v1
     424              :             {
     425            0 :                 return;
     426              :             }
     427            0 :             numv = polyclip(v1, numv, pb, pbc - stainradius, pbc + stainradius, v2);
     428            0 :             if(numv<3) //check again with v2
     429              :             {
     430            0 :                 return;
     431              :             }
     432            0 :             float tsz = flags&StainFlag_Rnd4 ? 0.5f : 1.0f,
     433            0 :                   scale = tsz*0.5f/stainradius,
     434            0 :                   tu = stainu + tsz*0.5f - ptc*scale,
     435            0 :                   tv = stainv + tsz*0.5f - pbc*scale;
     436            0 :             pt.mul(scale); pb.mul(scale);
     437            0 :             stainvert dv1 = { v2[0], staincolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) },
     438            0 :                       dv2 = { v2[1], staincolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) };
     439            0 :             int totalverts = 3*(numv-2);
     440            0 :             stainbuffer &buf = verts[StainBuffer_Mapmodel];
     441            0 :             if(totalverts > buf.maxverts-3)
     442              :             {
     443            0 :                 return;
     444              :             }
     445            0 :             while(buf.availverts < totalverts)
     446              :             {
     447            0 :                 if(!freestain())
     448              :                 {
     449            0 :                     return;
     450              :                 }
     451              :             }
     452            0 :             for(int k = 0; k < numv-2; ++k)
     453              :             {
     454            0 :                 stainvert *tri = buf.addtri();
     455            0 :                 tri[0] = dv1;
     456            0 :                 tri[1] = dv2;
     457            0 :                 dv2.pos = v2[k+2];
     458            0 :                 dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv);
     459            0 :                 tri[2] = dv2;
     460              :             }
     461              :         }
     462              : 
     463              :     private:
     464              :         int flags, fadeintime, fadeouttime, timetolive;
     465              :         int maxstains, startstain, endstain;
     466              : 
     467              :         ivec bbmin, bbmax;
     468              :         vec staincenter, stainnormal, staintangent, stainbitangent;
     469              :         float stainradius, stainu, stainv;
     470              :         vec4<uchar> staincolor;
     471              :         Texture *tex;
     472              : 
     473              :         struct stainvert
     474              :         {
     475              :             vec pos;
     476              :             vec4<uchar> color;
     477              :             vec2 tc;
     478              :         };
     479              : 
     480              :         struct staininfo
     481              :         {
     482              :             int millis;
     483              :             bvec color;
     484              :             uchar owner;
     485              :             ushort startvert, endvert;
     486              :         };
     487              :         staininfo *stains;
     488              : 
     489              :         class stainbuffer
     490              :         {
     491              :             public:
     492              :                 int maxverts, endvert, lastvert, availverts;
     493            0 :                 stainbuffer() : maxverts(0), endvert(0), lastvert(0), availverts(0), verts(nullptr), startvert(0), vbo(0), dirty(false)
     494            0 :                 {}
     495              : 
     496            0 :                 ~stainbuffer()
     497              :                 {
     498            0 :                     delete[] verts;
     499            0 :                 }
     500              : 
     501            0 :                 void init(int tris)
     502              :                 {
     503            0 :                     if(verts)
     504              :                     {
     505            0 :                         delete[] verts;
     506            0 :                         verts = nullptr;
     507            0 :                         maxverts = startvert = endvert = lastvert = availverts = 0;
     508              :                     }
     509            0 :                     if(tris)
     510              :                     {
     511            0 :                         maxverts = tris*3 + 3;
     512            0 :                         availverts = maxverts - 3;
     513            0 :                         verts = new stainvert[maxverts];
     514              :                     }
     515            0 :                 }
     516              : 
     517            0 :                 void cleanup()
     518              :                 {
     519            0 :                     if(vbo)
     520              :                     {
     521            0 :                         glDeleteBuffers(1, &vbo);
     522            0 :                         vbo = 0;
     523              :                     }
     524            0 :                 }
     525              : 
     526            0 :                 void clear()
     527              :                 {
     528            0 :                     startvert = endvert = lastvert = 0;
     529            0 :                     availverts = std::max(maxverts - 3, 0);
     530            0 :                     dirty = true;
     531            0 :                 }
     532              : 
     533            0 :                 int freestain(const staininfo &d)
     534              :                 {
     535            0 :                     int removed = d.endvert < d.startvert ? maxverts - (d.startvert - d.endvert) : d.endvert - d.startvert;
     536            0 :                     startvert = d.endvert;
     537            0 :                     if(startvert==endvert)
     538              :                     {
     539            0 :                         startvert = endvert = lastvert = 0;
     540              :                     }
     541            0 :                     availverts += removed;
     542            0 :                     return removed;
     543              :                 }
     544              : 
     545            0 :                 void clearstains(const staininfo &d)
     546              :                 {
     547            0 :                     startvert = d.endvert;
     548            0 :                     availverts = endvert < startvert ? startvert - endvert - 3 : maxverts - 3 - (endvert - startvert);
     549            0 :                     dirty = true;
     550            0 :                 }
     551              : 
     552            0 :                 bool faded(const staininfo &d) const
     553              :                 {
     554            0 :                     return verts[d.startvert].color.a() < 255;
     555              :                 }
     556              : 
     557            0 :                 void fadestain(const staininfo &d, const vec4<uchar> &color)
     558              :                 {
     559            0 :                     stainvert *vert = &verts[d.startvert];
     560            0 :                     const stainvert *end = &verts[d.endvert < d.startvert ? maxverts : d.endvert];
     561            0 :                     while(vert < end)
     562              :                     {
     563            0 :                         vert->color = color;
     564            0 :                         vert++;
     565              :                     }
     566            0 :                     if(d.endvert < d.startvert)
     567              :                     {
     568            0 :                         vert = verts;
     569            0 :                         end = &verts[d.endvert];
     570            0 :                         while(vert < end)
     571              :                         {
     572            0 :                             vert->color = color;
     573            0 :                             vert++;
     574              :                         }
     575              :                     }
     576            0 :                     dirty = true;
     577            0 :                 }
     578              : 
     579            0 :                 void render()
     580              :                 {
     581            0 :                     if(startvert == endvert)
     582              :                     {
     583            0 :                         return;
     584              :                     }
     585            0 :                     if(!vbo)
     586              :                     {
     587            0 :                         glGenBuffers(1, &vbo);
     588            0 :                         dirty = true;
     589              :                     }
     590            0 :                     gle::bindvbo(vbo);
     591            0 :                     int count = endvert < startvert ? maxverts - startvert : endvert - startvert;
     592            0 :                     if(dirty)
     593              :                     {
     594            0 :                         glBufferData(GL_ARRAY_BUFFER, maxverts*sizeof(stainvert), nullptr, GL_STREAM_DRAW);
     595            0 :                         glBufferSubData(GL_ARRAY_BUFFER, 0, count*sizeof(stainvert), &verts[startvert]);
     596            0 :                         if(endvert < startvert)
     597              :                         {
     598            0 :                             glBufferSubData(GL_ARRAY_BUFFER, count*sizeof(stainvert), endvert*sizeof(stainvert), verts);
     599            0 :                             count += endvert;
     600              :                         }
     601            0 :                         dirty = false;
     602              :                     }
     603            0 :                     else if(endvert < startvert)
     604              :                     {
     605            0 :                         count += endvert;
     606              :                     }
     607              :                     //note: using -> on address `0` aka nullptr is undefined behavior
     608              :                     //this allows passing the location of the fields' position in the object to opengl
     609            0 :                     const stainvert *ptr = 0;
     610            0 :                     gle::vertexpointer(sizeof(stainvert), ptr->pos.data());
     611            0 :                     gle::texcoord0pointer(sizeof(stainvert), ptr->tc.data());
     612            0 :                     gle::colorpointer(sizeof(stainvert), ptr->color.data());
     613              : 
     614            0 :                     glDrawArrays(GL_TRIANGLES, 0, count);
     615            0 :                     xtravertsva += count;
     616              :                 }
     617              : 
     618            0 :                 stainvert *addtri()
     619              :                 {
     620            0 :                     stainvert *tri = &verts[endvert];
     621            0 :                     availverts -= 3;
     622            0 :                     endvert += 3;
     623            0 :                     if(endvert >= maxverts)
     624              :                     {
     625            0 :                         endvert = 0;
     626              :                     }
     627            0 :                     return tri;
     628              :                 }
     629              : 
     630            0 :                 void addstain()
     631              :                 {
     632            0 :                     dirty = true;
     633            0 :                 }
     634              : 
     635            0 :                 bool hasverts() const
     636              :                 {
     637            0 :                     return startvert != endvert;
     638              :                 }
     639              : 
     640            0 :                 int nextverts() const
     641              :                 {
     642            0 :                     return endvert < lastvert ? endvert + maxverts - lastvert : endvert - lastvert;
     643              :                 }
     644              : 
     645            0 :                 int totaltris() const
     646              :                 {
     647            0 :                     return (maxverts - 3 - availverts)/3;
     648              :                 }
     649              :             private:
     650              :                 stainvert *verts;
     651              :                 int startvert;
     652              :                 GLuint vbo;
     653              :                 bool dirty;
     654              : 
     655              :                 //debug functions, not used by any of the code
     656              :                 int totalverts() const
     657              :                 {
     658              :                     return endvert < startvert ? maxverts - (startvert - endvert) : endvert - startvert;
     659              :                 }
     660              :         };
     661              : 
     662              :         std::array<stainbuffer, StainBuffer_Number> verts;
     663              : 
     664              :         const char *texname;
     665              : 
     666            0 :         staininfo &newstain()
     667              :         {
     668            0 :             staininfo &d = stains[endstain];
     669            0 :             int next = endstain + 1;
     670            0 :             if(next>=maxstains)
     671              :             {
     672            0 :                 next = 0;
     673              :             }
     674            0 :             if(next==startstain)
     675              :             {
     676            0 :                 freestain();
     677              :             }
     678            0 :             endstain = next;
     679            0 :             return d;
     680              :         }
     681              : 
     682            0 :         bool faded(const staininfo &d) const
     683              :         {
     684            0 :             return verts[d.owner].faded(d);
     685              :         }
     686              : 
     687            0 :         void fadestain(const staininfo &d, uchar alpha)
     688              :         {
     689            0 :             bvec color = d.color;
     690            0 :             if(flags&(StainFlag_Overbright|StainFlag_Glow|StainFlag_InvMod))
     691              :             {
     692            0 :                 color.scale(alpha, 255);
     693              :             }
     694            0 :             verts[d.owner].fadestain(d, vec4<uchar>(color, alpha));
     695            0 :         }
     696              : 
     697            0 :         int freestain()
     698              :         {
     699            0 :             if(startstain==endstain)
     700              :             {
     701            0 :                 return 0;
     702              :             }
     703            0 :             staininfo &d = stains[startstain];
     704            0 :             startstain++;
     705            0 :             if(startstain >= maxstains)
     706              :             {
     707            0 :                 startstain = 0;
     708              :             }
     709            0 :             return verts[d.owner].freestain(d);
     710              :         }
     711              : 
     712            0 :         void findmaterials(vtxarray *va)
     713              :         {
     714            0 :             int matsurfs = va->matsurfs;
     715            0 :             for(int i = 0; i < matsurfs; ++i)
     716              :             {
     717            0 :                 materialsurface &m = va->matbuf[i];
     718            0 :                 if(!IS_CLIPPED(m.material&MatFlag_Volume))
     719              :                 {
     720            0 :                     i += m.skip;
     721            0 :                     continue;
     722              :                 }
     723            0 :                 int dim = DIMENSION(m.orient),
     724            0 :                     dc = DIM_COORD(m.orient);
     725            0 :                 if(dc ? stainnormal[dim] <= 0 : stainnormal[dim] >= 0)
     726              :                 {
     727            0 :                     i += m.skip;
     728            0 :                     continue;
     729              :                 }
     730            0 :                 int c = C[dim],
     731            0 :                     r = R[dim];
     732              :                 for(;;)
     733              :                 {
     734            0 :                     const materialsurface &m = va->matbuf[i];
     735            0 :                     if(m.o[dim] >= bbmin[dim] && m.o[dim] <= bbmax[dim] &&
     736            0 :                        m.o[c] + m.csize >= bbmin[c] && m.o[c] <= bbmax[c] &&
     737            0 :                        m.o[r] + m.rsize >= bbmin[r] && m.o[r] <= bbmax[r])
     738              :                     {
     739              :                         static cube dummy;
     740            0 :                         gentris(dummy, m.orient, m.o, std::max(m.csize, m.rsize), &m);
     741              :                     }
     742            0 :                     if(i+1 >= matsurfs)
     743              :                     {
     744            0 :                         break;
     745              :                     }
     746            0 :                     const materialsurface &n = va->matbuf[i+1];
     747            0 :                     if(n.material != m.material || n.orient != m.orient)
     748              :                     {
     749              :                         break;
     750              :                     }
     751            0 :                     i++;
     752            0 :                 }
     753              :             }
     754            0 :         }
     755              : 
     756            0 :         void findescaped(const std::array<cube, 8> &c, const ivec &o, int size, int escaped)
     757              :         {
     758            0 :             for(int i = 0; i < 8; ++i)
     759              :             {
     760            0 :                 const cube &cu = c[i];
     761            0 :                 if(escaped&(1<<i))
     762              :                 {
     763            0 :                     ivec co(i, o, size);
     764            0 :                     if(cu.children)
     765              :                     {
     766            0 :                         findescaped(*cu.children, co, size>>1, cu.escaped);
     767              :                     }
     768              :                     else
     769              :                     {
     770            0 :                         int vismask = cu.merged;
     771            0 :                         if(vismask)
     772              :                         {
     773            0 :                             for(int j = 0; j < 6; ++j)
     774              :                             {
     775            0 :                                 if(vismask&(1<<j))
     776              :                                 {
     777            0 :                                     gentris(cu, j, co, size);
     778              :                                 }
     779              :                             }
     780              :                         }
     781              :                     }
     782              :                 }
     783              :             }
     784            0 :         }
     785              : 
     786            0 :         void gentris(const std::array<cube, 8> &c, const ivec &o, int size, int escaped = 0)
     787              :         {
     788            0 :             int overlap = octaboxoverlap(o, size, bbmin, bbmax);
     789            0 :             for(int i = 0; i < 8; ++i)
     790              :             {
     791            0 :                 const cube &cu = c[i];
     792            0 :                 if(overlap&(1<<i))
     793              :                 {
     794            0 :                     ivec co(i, o, size);
     795            0 :                     if(cu.ext)
     796              :                     {
     797            0 :                         if(cu.ext->va && cu.ext->va->matsurfs)
     798              :                         {
     799            0 :                             findmaterials(cu.ext->va);
     800              :                         }
     801            0 :                         if(cu.ext->ents && cu.ext->ents->mapmodels.size())
     802              :                         {
     803            0 :                             genmmtris(*cu.ext->ents);
     804              :                         }
     805              :                     }
     806            0 :                     if(cu.children)
     807              :                     {
     808            0 :                         gentris(*cu.children, co, size>>1, cu.escaped);
     809              :                     }
     810              :                     else
     811              :                     {
     812            0 :                         int vismask = cu.visible; //visibility mask
     813            0 :                         if(vismask&0xC0)
     814              :                         {
     815            0 :                             if(vismask&0x80)
     816              :                             {
     817            0 :                                 for(int j = 0; j < 6; ++j)
     818              :                                 {
     819            0 :                                     gentris(cu, j, co, size, nullptr, vismask);
     820              :                                 }
     821              :                             }
     822              :                             else
     823              :                             {
     824            0 :                                 for(int j = 0; j < 6; ++j)
     825              :                                 {
     826            0 :                                     if(vismask&(1<<j))
     827              :                                     {
     828            0 :                                         gentris(cu, j, co, size);
     829              :                                     }
     830              :                                 }
     831              :                             }
     832              :                         }
     833              :                     }
     834              :                 }
     835            0 :                 else if(escaped&(1<<i))
     836              :                 {
     837            0 :                     ivec co(i, o, size);
     838            0 :                     if(cu.children)
     839              :                     {
     840            0 :                         findescaped(*cu.children, co, size>>1, cu.escaped);
     841              :                     }
     842              :                     else
     843              :                     {
     844            0 :                         int vismask = cu.merged; //visibility mask
     845            0 :                         if(vismask)
     846              :                         {
     847            0 :                             for(int j = 0; j < 6; ++j)
     848              :                             {
     849            0 :                                 if(vismask&(1<<j))
     850              :                                 {
     851            0 :                                     gentris(cu, j, co, size);
     852              :                                 }
     853              :                             }
     854              :                         }
     855              :                     }
     856              :                 }
     857              :             }
     858            0 :         }
     859              : 
     860            0 :         void genmmtris(const octaentities &oe)
     861              :         {
     862            0 :             const std::vector<extentity *> &ents = entities::getents();
     863            0 :             for(uint i = 0; i < oe.mapmodels.size(); i++)
     864              :             {
     865            0 :                 const extentity &e = *ents[oe.mapmodels[i]];
     866            0 :                 model *m = loadmapmodel(e.attr1);
     867            0 :                 if(!m)
     868              :                 {
     869            0 :                     continue;
     870              :                 }
     871            0 :                 vec center, radius;
     872            0 :                 float rejectradius = m->collisionbox(center, radius),
     873            0 :                       scale = e.attr5 > 0 ? e.attr5/100.0f : 1;
     874            0 :                 center.mul(scale);
     875            0 :                 if(staincenter.reject(vec(e.o).add(center), stainradius + rejectradius*scale))
     876              :                 {
     877            0 :                     continue;
     878              :                 }
     879            0 :                 m->setBIH();
     880            0 :                 if(m->animated())
     881              :                 {
     882            0 :                     continue;
     883              :                 }
     884            0 :                 int yaw = e.attr2,
     885            0 :                     pitch = e.attr3,
     886            0 :                     roll = e.attr4;
     887            0 :                 std::vector<std::array<vec, 3>> tris;
     888            0 :                 m->bih->genstaintris(tris, staincenter, stainradius, e.o, yaw, pitch, roll, scale);
     889            0 :                 for(const std::array<vec, 3> &t : tris)
     890              :                 {
     891            0 :                     genmmtri(t);
     892              :                 }
     893            0 :             }
     894            0 :         }
     895              : 
     896            0 :         void gentris(const cube &cu, int orient, const ivec &o, int size, const materialsurface *mat = nullptr, int vismask = 0)
     897              :         {
     898            0 :             vec pos[Face_MaxVerts+4];
     899            0 :             int numverts = 0,
     900            0 :                 numplanes = 1;
     901            0 :             vec planes[2];
     902            0 :             if(mat)
     903              :             {
     904            0 :                 planes[0] = vec(0, 0, 0);
     905            0 :                 switch(orient)
     906              :                 {
     907              :                 //want to define GENFACEORIENT and GENFACEVERT to pass the appropriate code to GENFACEVERTS
     908              :                 //GENFACEVERTS has different GENFACEORIENT and GENFACEVERT for many different calls in other files
     909              :                 #define GENFACEORIENT(orient, v0, v1, v2, v3) \
     910              :                     case orient: \
     911              :                         planes[0][DIMENSION(orient)] = DIM_COORD(orient) ? 1 : -1; \
     912              :                         v0 v1 v2 v3 \
     913              :                         break;
     914              :                 #define GENFACEVERT(orient, vert, x,y,z, xv,yv,zv) \
     915              :                         pos[numverts++] = vec(x xv, y yv, z zv);
     916            0 :                     GENFACEVERTS(o.x, o.x, o.y, o.y, o.z, o.z, , + mat->csize, , + mat->rsize, + 0.1f, - 0.1f);
     917              :                 #undef GENFACEORIENT
     918              :                 #undef GENFACEVERT
     919              :                 }
     920              :             }
     921            0 :             else if(cu.texture[orient] == Default_Sky)
     922              :             {
     923            0 :                 return;
     924              :             }
     925            0 :             else if(cu.ext && (numverts = cu.ext->surfaces[orient].numverts&Face_MaxVerts))
     926              :             {
     927            0 :                 const vertinfo *verts = cu.ext->verts() + cu.ext->surfaces[orient].verts;
     928            0 :                 ivec vo = ivec(o).mask(~0xFFF).shl(3);
     929            0 :                 for(int j = 0; j < numverts; ++j)
     930              :                 {
     931            0 :                     pos[j] = vec(verts[j].getxyz().add(vo)).mul(1/8.0f);
     932              :                 }
     933            0 :                 planes[0].cross(pos[0], pos[1], pos[2]).normalize();
     934            0 :                 if(numverts >= 4 && !(cu.merged&(1<<orient)) && !flataxisface(cu, orient) && faceconvexity(verts, numverts, size))
     935              :                 {
     936            0 :                     planes[1].cross(pos[0], pos[2], pos[3]).normalize();
     937            0 :                     numplanes++;
     938              :                 }
     939              :             }
     940            0 :             else if(cu.merged&(1<<orient))
     941              :             {
     942            0 :                 return;
     943              :             }
     944            0 :             else if(!vismask || (vismask&0x40 && visibleface(cu, orient, o, size, Mat_Air, (cu.material&Mat_Alpha)^Mat_Alpha, Mat_Alpha)))
     945              :             {
     946            0 :                 std::array<ivec, 4> v;
     947            0 :                 genfaceverts(cu, orient, v);
     948            0 :                 int vis = 3,
     949            0 :                     convex = faceconvexity(v, vis),
     950            0 :                     order = convex < 0 ? 1 : 0;
     951            0 :                 vec vo(o);
     952            0 :                 pos[numverts++] = vec(v[order]).mul(size/8.0f).add(vo);
     953            0 :                 if(vis&1)
     954              :                 {
     955            0 :                     pos[numverts++] = vec(v[order+1]).mul(size/8.0f).add(vo);
     956              :                 }
     957            0 :                 pos[numverts++] = vec(v[order+2]).mul(size/8.0f).add(vo);
     958            0 :                 if(vis&2)
     959              :                 {
     960            0 :                     pos[numverts++] = vec(v[(order+3)&3]).mul(size/8.0f).add(vo);
     961              :                 }
     962            0 :                 planes[0].cross(pos[0], pos[1], pos[2]).normalize();
     963            0 :                 if(convex)
     964              :                 {
     965            0 :                     planes[1].cross(pos[0], pos[2], pos[3]).normalize();
     966            0 :                     numplanes++;
     967              :                 }
     968              :             }
     969              :             else
     970              :             {
     971            0 :                 return;
     972              :             }
     973              : 
     974            0 :             stainbuffer &buf = verts[mat || cu.material&Mat_Alpha ? StainBuffer_Transparent : StainBuffer_Opaque];
     975            0 :             for(int l = 0; l < numplanes; ++l) //note this is a loop l (level 4)
     976              :             {
     977            0 :                 const vec &n = planes[l];
     978            0 :                 float facing = n.dot(stainnormal);
     979            0 :                 if(facing <= 0)
     980              :                 {
     981            0 :                     continue;
     982              :                 }
     983            0 :                 vec p = vec(pos[0]).sub(staincenter);
     984              :                 // travel back along plane normal from the stain center
     985            0 :                 float dist = n.dot(p);
     986            0 :                 if(std::fabs(dist) > stainradius)
     987              :                 {
     988            0 :                     continue;
     989              :                 }
     990            0 :                 vec pcenter = vec(n).mul(dist).add(staincenter);
     991            0 :                 vec ft, fb;
     992            0 :                 ft.orthogonal(n);
     993            0 :                 ft.normalize();
     994            0 :                 fb.cross(ft, n);
     995            0 :                 vec pt = vec(ft).mul(ft.dot(staintangent)).add(vec(fb).mul(fb.dot(staintangent))).normalize(),
     996            0 :                     pb = vec(ft).mul(ft.dot(stainbitangent)).add(vec(fb).mul(fb.dot(stainbitangent))).project(pt).normalize();
     997            0 :                 vec v1[Face_MaxVerts+4],
     998            0 :                     v2[Face_MaxVerts+4];
     999            0 :                 float ptc = pt.dot(pcenter),
    1000            0 :                       pbc = pb.dot(pcenter);
    1001              :                 int numv;
    1002            0 :                 if(numplanes >= 2)
    1003              :                 {
    1004            0 :                     if(l)
    1005              :                     {
    1006            0 :                         pos[1] = pos[2];
    1007            0 :                         pos[2] = pos[3];
    1008              :                     }
    1009            0 :                     numv = polyclip(pos, 3, pt, ptc - stainradius, ptc + stainradius, v1);
    1010            0 :                     if(numv<3)
    1011              :                     {
    1012            0 :                         continue;
    1013              :                     }
    1014              :                 }
    1015              :                 else
    1016              :                 {
    1017            0 :                     numv = polyclip(pos, numverts, pt, ptc - stainradius, ptc + stainradius, v1);
    1018            0 :                     if(numv<3)
    1019              :                     {
    1020            0 :                         continue;
    1021              :                     }
    1022              :                 }
    1023            0 :                 numv = polyclip(v1, numv, pb, pbc - stainradius, pbc + stainradius, v2);
    1024            0 :                 if(numv<3)
    1025              :                 {
    1026            0 :                     continue;
    1027              :                 }
    1028            0 :                 float tsz = flags&StainFlag_Rnd4 ? 0.5f : 1.0f,
    1029            0 :                       scale = tsz*0.5f/stainradius,
    1030            0 :                       tu = stainu + tsz*0.5f - ptc*scale,
    1031            0 :                       tv = stainv + tsz*0.5f - pbc*scale;
    1032            0 :                 pt.mul(scale); pb.mul(scale);
    1033            0 :                 stainvert dv1 = { v2[0], staincolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) },
    1034            0 :                           dv2 = { v2[1], staincolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) };
    1035            0 :                 int totalverts = 3*(numv-2);
    1036            0 :                 if(totalverts > buf.maxverts-3)
    1037              :                 {
    1038            0 :                     return;
    1039              :                 }
    1040            0 :                 while(buf.availverts < totalverts)
    1041              :                 {
    1042            0 :                     if(!freestain())
    1043              :                     {
    1044            0 :                         return;
    1045              :                     }
    1046              :                 }
    1047            0 :                 for(int k = 0; k < numv-2; ++k)
    1048              :                 {
    1049            0 :                     stainvert *tri = buf.addtri();
    1050            0 :                     tri[0] = dv1;
    1051            0 :                     tri[1] = dv2;
    1052            0 :                     dv2.pos = v2[k+2];
    1053            0 :                     dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv);
    1054            0 :                     tri[2] = dv2;
    1055              :                 }
    1056              :             }
    1057              :         }
    1058              : };
    1059              : 
    1060              : std::vector<stainrenderer> stains;
    1061              : 
    1062              : /* initstains: sets up each entry in the stains global variable array using init() method
    1063              :  * and then preloads them
    1064              :  *
    1065              :  * fails to do anything if initing is set (early game loading time)
    1066              :  */
    1067            0 : void initstains()
    1068              : {
    1069            0 :     if(initing)
    1070              :     {
    1071            0 :         return;
    1072              :     }
    1073            0 :     stains.emplace_back("<grey>media/particle/blood.png", stainrenderer::StainFlag_Rnd4|stainrenderer::StainFlag_Rotate|stainrenderer::StainFlag_InvMod);
    1074            0 :     stains.emplace_back("<grey>media/particle/pulse_scorch.png", stainrenderer::StainFlag_Rotate, 500);
    1075            0 :     stains.emplace_back("<grey>media/particle/rail_hole.png", stainrenderer::StainFlag_Rotate|stainrenderer::StainFlag_Overbright);
    1076            0 :     stains.emplace_back("<grey>media/particle/pulse_glow.png", stainrenderer::StainFlag_Rotate|stainrenderer::StainFlag_Glow|stainrenderer::StainFlag_Saturate, 250, 1500, 250);
    1077            0 :     stains.emplace_back("<grey>media/particle/rail_glow.png",  stainrenderer::StainFlag_Rotate|stainrenderer::StainFlag_Glow|stainrenderer::StainFlag_Saturate, 100, 1100, 100);
    1078            0 :     for(stainrenderer &i : stains)
    1079              :     {
    1080            0 :         i.init(maxstaintris);
    1081              :     }
    1082            0 :     for(uint i = 0; i < stains.size(); ++i)
    1083              :     {
    1084            0 :         loadprogress = static_cast<float>(i+1)/stains.size();
    1085            0 :         stains[i].preload();
    1086              :     }
    1087            0 :     loadprogress = 0;
    1088              : }
    1089              : 
    1090              : /* clearstains: loops through the stains[] global variable array and runs clearstains for each entry
    1091              :  */
    1092            0 : void clearstains()
    1093              : {
    1094            0 :     for(stainrenderer &i : stains)
    1095              :     {
    1096            0 :         i.clearstains();
    1097              :     }
    1098            0 : }
    1099              : 
    1100              : VARNP(stains, showstains, 0, 1, 1); // toggles rendering stains at all
    1101              : 
    1102            0 : bool renderstains(int sbuf, bool gbuf, int layer)
    1103              : {
    1104            0 :     bool rendered = false;
    1105            0 :     for(stainrenderer& d : stains)
    1106              :     {
    1107            0 :         if(d.usegbuffer() != gbuf)
    1108              :         {
    1109            0 :             continue;
    1110              :         }
    1111            0 :         if(sbuf == StainBuffer_Opaque)
    1112              :         {
    1113            0 :             d.clearfadedstains();
    1114            0 :             d.fadeinstains();
    1115            0 :             d.fadeoutstains();
    1116              :         }
    1117            0 :         if(!showstains || !d.hasstains(sbuf))
    1118              :         {
    1119            0 :             continue;
    1120              :         }
    1121            0 :         if(!rendered)
    1122              :         {
    1123            0 :             rendered = true;
    1124            0 :             stainrenderer::setuprenderstate(sbuf, gbuf, layer);
    1125              :         }
    1126            0 :         d.render(sbuf);
    1127              :     }
    1128            0 :     if(!rendered)
    1129              :     {
    1130            0 :         return false;
    1131              :     }
    1132            0 :     stainrenderer::cleanuprenderstate(sbuf, gbuf);
    1133            0 :     return true;
    1134              : }
    1135              : 
    1136            0 : void cleanupstains()
    1137              : {
    1138            0 :     for(stainrenderer& i : stains)
    1139              :     {
    1140            0 :         i.cleanup();
    1141              :     }
    1142            0 : }
    1143              : 
    1144            0 : void addstain(int type, const vec &center, const vec &surface, float radius, const bvec &color, int info)
    1145              : {
    1146            0 :     static VARP(maxstaindistance, 1, 512, 10000); //distance in cubes before stains stop rendering
    1147            0 :     if(!showstains || type<0 || static_cast<size_t>(type) >= stains.size() || center.dist(camera1->o) - radius > maxstaindistance)
    1148              :     {
    1149            0 :         return;
    1150              :     }
    1151            0 :     stainrenderer &d = stains[type];
    1152            0 :     d.addstain(center, surface, radius, color, info, rootworld);
    1153              : }
        

Generated by: LCOV version 2.0-1