LCOV - code coverage report
Current view: top level - engine/render - stain.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 0.0 % 590 0
Test Date: 2025-10-13 07:05:02 Functions: 0.0 % 46 0

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

Generated by: LCOV version 2.0-1