LCOV - code coverage report
Current view: top level - engine/render - renderparticles.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 3.7 % 944 35
Test Date: 2026-06-16 06:16:16 Functions: 8.4 % 131 11

            Line data    Source code
       1              : /**
       2              :  * @file renderparticles.cpp
       3              :  * @brief billboard particle rendering
       4              :  *
       5              :  * renderparticles.cpp handles rendering of entity-and-weapon defined billboard particle
       6              :  * rendering. Particle entities spawn particles randomly (is not synced between different
       7              :  * clients in multiplayer), while weapon projectiles are placed at world-synced locations.
       8              :  *
       9              :  * Particles, being merely diffuse textures placed on the scene, do not have any special
      10              :  * effects (such as refraction or lights). Particles, being of the "billboard" type, always
      11              :  * face the camera, which works fairly well for small, simple effects. Particles are not
      12              :  * recommended for large, high detail special effects.
      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 "renderlights.h"
      20              : #include "rendergl.h"
      21              : #include "renderparticles.h"
      22              : #include "renderva.h"
      23              : #include "renderwindow.h"
      24              : #include "rendertext.h"
      25              : #include "renderttf.h"
      26              : #include "shader.h"
      27              : #include "shaderparam.h"
      28              : #include "stain.h"
      29              : #include "texture.h"
      30              : #include "water.h"
      31              : 
      32              : #include "interface/console.h"
      33              : #include "interface/control.h"
      34              : #include "interface/input.h"
      35              : 
      36              : #include "world/octaedit.h"
      37              : #include "world/octaworld.h"
      38              : #include "world/raycube.h"
      39              : #include "world/world.h"
      40              : 
      41              : static Shader *particleshader          = nullptr,
      42              :               *particlenotextureshader = nullptr,
      43              :               *particlesoftshader      = nullptr,
      44              :               *particletextshader      = nullptr;
      45              : 
      46              : static FVARP(particlebright, 0, 2, 100); //multiply particle colors by this factor in brightness
      47              : static VARP(particlesize, 20, 100, 500); //particle size factor
      48              : 
      49              : static VARP(softparticleblend, 1, 8, 64); //inverse of blend factor for soft particle blending
      50              : 
      51              : // Check canemitparticles() to limit the rate that paricles can be emitted for models/sparklies
      52              : // Automatically stops particles being emitted when paused or in reflective drawing
      53              : static VARP(emitmillis, 1, 17, 1000); //note: 17 ms = ~60fps
      54              : 
      55              : static int emitoffset     = 0;
      56              : static bool canemit       = false,
      57              :             regenemitters = false,
      58              :             canstep       = false;
      59              : 
      60            0 : static bool canemitparticles()
      61              : {
      62            0 :     return canemit || emitoffset;
      63              : }
      64              : std::vector<std::string> entnames; //used in iengine
      65              : 
      66              : static VARP(showparticles,  0, 1, 1);                  //toggles showing billboarded particles
      67              : static VAR(cullparticles,   0, 1, 1);                  //toggles culling particles beyond fog distance
      68              : static VAR(replayparticles, 0, 1, 1);                  //toggles re-rendering previously generated particles
      69              : static int seedmillis = variable("seedparticles", 0, 3000, 10000, &seedmillis, nullptr, 0);//sets the time between seeding particles
      70              : static VAR(debugparticlecull, 0, 0, 1);                //print out console information about particles culled
      71              : static VAR(debugparticleseed, 0, 0, 1);                //print out radius/maxfade info for particles upon spawn
      72              : 
      73              : class ParticleEmitter final
      74              : {
      75              :     public:
      76              :         const extentity *ent;
      77              :         vec center;
      78              :         float radius;
      79              :         int maxfade, lastemit, lastcull;
      80              : 
      81            0 :         ParticleEmitter(const extentity *ent)
      82            0 :             : ent(ent), maxfade(-1), lastemit(0), lastcull(0), bbmin(ent->o), bbmax(ent->o)
      83            0 :         {}
      84              : 
      85            0 :         void finalize()
      86              :         {
      87            0 :             center = vec(bbmin).add(bbmax).mul(0.5f);
      88            0 :             radius = bbmin.dist(bbmax)/2;
      89            0 :             if(debugparticleseed)
      90              :             {
      91            0 :                 conoutf(Console_Debug, "radius: %f, maxfade: %d", radius, maxfade);
      92              :             }
      93            0 :         }
      94              : 
      95            0 :         void extendbb(const vec &o, float size = 0)
      96              :         {
      97            0 :             bbmin.x = std::min(bbmin.x, o.x - size);
      98            0 :             bbmin.y = std::min(bbmin.y, o.y - size);
      99            0 :             bbmin.z = std::min(bbmin.z, o.z - size);
     100            0 :             bbmax.x = std::max(bbmax.x, o.x + size);
     101            0 :             bbmax.y = std::max(bbmax.y, o.y + size);
     102            0 :             bbmax.z = std::max(bbmax.z, o.z + size);
     103            0 :         }
     104              : 
     105            0 :         void extendbb(float z, float size = 0)
     106              :         {
     107            0 :             bbmin.z = std::min(bbmin.z, z - size);
     108            0 :             bbmax.z = std::max(bbmax.z, z + size);
     109            0 :         }
     110              :     private:
     111              :         vec bbmin, bbmax;
     112              : };
     113              : 
     114              : static std::vector<ParticleEmitter> emitters;
     115              : static ParticleEmitter *seedemitter = nullptr;
     116              : 
     117              : //used in iengine
     118            0 : const char * getentname(int i)
     119              : {
     120            0 :     return i>=0 && static_cast<size_t>(i) < entnames.size() ? entnames[i].c_str() : "";
     121              : }
     122              : 
     123              : //used in world
     124            0 : void clearparticleemitters()
     125              : {
     126            0 :     emitters.clear();
     127            0 :     regenemitters = true;
     128            0 : }
     129              : 
     130              : /**
     131              :  * @brief Populates the emitters vector with entries corresponding to particle ents.
     132              :  *
     133              :  * Clears the emitters vector, then parses the entities vector for any particle-containing
     134              :  * entities and creates a particle emitter corresponding to that entity.
     135              :  *
     136              :  * Clears the regenemitters flag to indicate that the particle emitters vector no
     137              :  * longer needs regenerating.
     138              :  */
     139            0 : static void addparticleemitters()
     140              : {
     141            0 :     emitters.clear();
     142            0 :     const std::vector<extentity *> &ents = entities::getents();
     143            0 :     for(const extentity *e : ents)
     144              :     {
     145            0 :         if(e->type != EngineEnt_Particles)
     146              :         {
     147            0 :             continue;
     148              :         }
     149            0 :         emitters.emplace_back(ParticleEmitter(e));
     150              :     }
     151            0 :     regenemitters = false;
     152            0 : }
     153              : //particle types
     154              : enum ParticleTypes
     155              : {
     156              :     PT_PART = 0,
     157              :     PT_TAPE,
     158              :     PT_TRAIL,
     159              :     PT_TEXT,
     160              :     PT_TEXTUP,
     161              :     PT_METER,
     162              :     PT_METERVS,
     163              :     PT_FIREBALL,
     164              : };
     165              : //particle properties
     166              : enum ParticleProperties
     167              : {
     168              :     PT_MOD       = 1<<8,
     169              :     PT_RND4      = 1<<9,
     170              :     PT_LERP      = 1<<10, // use very sparingly - order of blending issues
     171              :     PT_TRACK     = 1<<11,
     172              :     PT_BRIGHT    = 1<<12,
     173              :     PT_SOFT      = 1<<13,
     174              :     PT_HFLIP     = 1<<14,
     175              :     PT_VFLIP     = 1<<15,
     176              :     PT_ROT       = 1<<16,
     177              :     PT_CULL      = 1<<17,
     178              :     PT_FEW       = 1<<18,
     179              :     PT_ICON      = 1<<19,
     180              :     PT_NOTEX     = 1<<20,
     181              :     PT_SHADER    = 1<<21,
     182              :     PT_NOLAYER   = 1<<22,
     183              :     PT_COLLIDE   = 1<<23,
     184              :     PT_FLIP      = PT_HFLIP | PT_VFLIP | PT_ROT
     185              : };
     186              : 
     187              : const std::array<std::string, 8> partnames = { "part", "tape", "trail", "text", "textup", "meter", "metervs", "fireball"};
     188              : 
     189              : struct particle
     190              : {
     191              :     vec o, d;                  //o: origin d: dir
     192              :     int gravity, fade, millis; //gravity intensity, fade rate, lifetime
     193              :     bvec color;                //color vector triple
     194              :     uchar flags;               //particle-specific flags
     195              :     float size;                //radius or scale factor
     196              :     union                      //for unique properties of particular entities
     197              :     {
     198              :         const char *text;      //text particle
     199              :         float val;             //fireball particle
     200              :         physent *owner;        //particle owner (a player/bot)
     201              :         struct                 //meter particle
     202              :         {
     203              :             std::array<uchar,3 > color2; //color of bar
     204              :             uchar progress;              //bar fill %
     205              :         } meter;
     206              :     };
     207              : };
     208              : 
     209              : struct partvert final
     210              : {
     211              :     vec pos;     //x,y,z of particle
     212              :     vec4<uchar> color; //r,g,b,a color
     213              :     vec2 tc;     //texture coordinate
     214              : };
     215              : 
     216              : static constexpr float collideradius = 8.0f;
     217              : static constexpr float collideerror  = 1.0f;
     218              : class partrenderer
     219              : {
     220              :     public:
     221           18 :         partrenderer(const char *texname, int texclamp, int type, int stain = -1)
     222           36 :             : tex(nullptr), type(type), stain(stain), texname(texname), texclamp(texclamp)
     223              :         {
     224           18 :         }
     225            2 :         partrenderer(int type, int stain = -1)
     226            4 :             : tex(nullptr), type(type), stain(stain), texname(""), texclamp(0)
     227              :         {
     228            2 :         }
     229            4 :         virtual ~partrenderer()
     230            4 :         {
     231            4 :         }
     232              : 
     233              :         virtual void init(int n) = 0;
     234              :         virtual void reset() = 0;
     235              :         virtual void resettracked(const physent *owner) = 0;
     236              :         virtual particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity = 0) = 0;
     237              :         virtual void render() = 0;
     238              :         virtual bool haswork() const = 0;
     239              :         virtual int count() const = 0; //for debug
     240            0 :         virtual void cleanup() {}
     241              : 
     242              :         virtual void seedemitter(ParticleEmitter &pe, const vec &o, const vec &d, int fade, float size, int gravity) = 0;
     243              : 
     244            0 :         virtual void preload()
     245              :         {
     246            0 :             if(!texname.empty() && !tex)
     247              :             {
     248            0 :                 tex = textureload(texname.c_str(), texclamp);
     249              :             }
     250            0 :         }
     251              : 
     252              :         //blend = 0 => remove it
     253            0 :         void calc(particle *p, int &blend, int &ts, vec &o, vec &d, bool step = true) const
     254              :         {
     255            0 :             o = p->o;
     256            0 :             d = p->d;
     257            0 :             if(p->fade <= 5)
     258              :             {
     259            0 :                 ts = 1;
     260            0 :                 blend = 255;
     261              :             }
     262              :             else
     263              :             {
     264            0 :                 ts = lastmillis-p->millis;
     265            0 :                 blend = std::max(255 - (ts<<8)/p->fade, 0);
     266            0 :                 if(p->gravity)
     267              :                 {
     268            0 :                     if(ts > p->fade)
     269              :                     {
     270            0 :                         ts = p->fade;
     271              :                     }
     272            0 :                     float t = ts;
     273            0 :                     constexpr float tfactor = 5000.f;
     274            0 :                     o.add(vec(d).mul(t/tfactor));
     275            0 :                     o.z -= t*t/(2.0f * tfactor * p->gravity);
     276              :                 }
     277            0 :                 if(type&PT_COLLIDE && o.z < p->val && step)
     278              :                 {
     279            0 :                     if(stain >= 0)
     280              :                     {
     281            0 :                         vec surface;
     282            0 :                         float floorz = rayfloor(vec(o.x, o.y, p->val), surface, Ray_ClipMat, collideradius),
     283            0 :                               collidez = floorz<0 ? o.z-collideradius : p->val - floorz;
     284            0 :                         if(o.z >= collidez+collideerror)
     285              :                         {
     286            0 :                             p->val = collidez+collideerror;
     287              :                         }
     288              :                         else
     289              :                         {
     290            0 :                             int staintype = type&PT_RND4 ? (p->flags>>5)&3 : 0;
     291            0 :                             addstain(stain, vec(o.x, o.y, collidez), vec(p->o).sub(o).normalize(), 2*p->size, p->color, staintype);
     292            0 :                             blend = 0;
     293              :                         }
     294              :                     }
     295              :                     else
     296              :                     {
     297            0 :                         blend = 0;
     298              :                     }
     299              :                 }
     300              :             }
     301            0 :         }
     302              : 
     303              :         //prints out info for a particle, with its letter denoting particle type
     304            0 :         void debuginfo() const
     305              :         {
     306              :             string info;
     307            0 :             formatstring(info, "%d\t%s(", count(), partnames[type&0xFF].c_str());
     308            0 :             if(type&PT_LERP)    concatstring(info, "l,");
     309            0 :             if(type&PT_MOD)     concatstring(info, "m,");
     310            0 :             if(type&PT_RND4)    concatstring(info, "r,");
     311            0 :             if(type&PT_TRACK)   concatstring(info, "t,");
     312            0 :             if(type&PT_FLIP)    concatstring(info, "f,");
     313            0 :             if(type&PT_COLLIDE) concatstring(info, "c,");
     314            0 :             int len = std::strlen(info);
     315            0 :             info[len-1] = info[len-1] == ',' ? ')' : '\0';
     316            0 :             if(!texname.empty())
     317              :             {
     318            0 :                 const char *title = std::strrchr(texname.c_str(), '/');
     319            0 :                 if(title)
     320              :                 {
     321            0 :                     concformatstring(info, ": %s", title+1);
     322              :                 }
     323              :             }
     324            0 :         }
     325              : 
     326            0 :         bool hasstain() const
     327              :         {
     328            0 :             return stain >= 0;
     329              :         }
     330              : 
     331            0 :         uint parttype() const
     332              :         {
     333            0 :             return type;
     334              :         }
     335              : 
     336              :     protected:
     337              :         const Texture *tex;
     338              :     private:
     339              :         uint type;
     340              :         int stain;
     341              :         std::string texname;
     342              :         int texclamp;
     343              : 
     344              : };
     345              : 
     346              : struct listparticle final : particle
     347              : {
     348              :     listparticle *next;
     349              : };
     350              : 
     351              : static VARP(outlinemeters, 0, 0, 1);
     352              : 
     353              : class listrenderer : public partrenderer
     354              : {
     355              :     public:
     356            2 :         listrenderer(const char *texname, int texclamp, int type, int stain = -1)
     357            2 :             : partrenderer(texname, texclamp, type, stain), list(nullptr)
     358              :         {
     359            2 :         }
     360            2 :         listrenderer(int type, int stain = -1)
     361            2 :             : partrenderer(type, stain), list(nullptr)
     362              :         {
     363            2 :         }
     364              : 
     365            4 :         virtual ~listrenderer()
     366            4 :         {
     367            4 :         }
     368              : 
     369              :     private:
     370              : 
     371              :         virtual void startrender() = 0;
     372              :         virtual void endrender() = 0;
     373              :         virtual void renderpart(const listparticle &p, const vec &o, int blend, int ts) = 0;
     374              : 
     375            0 :         bool haswork() const final
     376              :         {
     377            0 :             return (list != nullptr);
     378              :         }
     379              : 
     380            0 :         bool renderpart(listparticle *p)
     381              :         {
     382            0 :             vec o, d;
     383              :             int blend, ts;
     384            0 :             calc(p, blend, ts, o, d, canstep);
     385            0 :             if(blend <= 0)
     386              :             {
     387            0 :                 return false;
     388              :             }
     389            0 :             renderpart(*p, o, blend, ts);
     390            0 :             return p->fade > 5;
     391              :         }
     392              : 
     393            0 :         void render() final
     394              :         {
     395            0 :             startrender();
     396            0 :             if(tex)
     397              :             {
     398            0 :                 glBindTexture(GL_TEXTURE_2D, tex->id);
     399              :             }
     400            0 :             if(canstep)
     401              :             {
     402            0 :                 for(listparticle **prev = &list, *p = list; p; p = *prev)
     403              :                 {
     404            0 :                     if(renderpart(p))
     405              :                     {
     406            0 :                         prev = &p->next;
     407              :                     }
     408              :                     else
     409              :                     { // remove
     410            0 :                         *prev = p->next;
     411            0 :                         p->next = parempty;
     412            0 :                         parempty = p;
     413              :                     }
     414              :                 }
     415              :             }
     416              :             else
     417              :             {
     418            0 :                 for(listparticle *p = list; p; p = p->next)
     419              :                 {
     420            0 :                     renderpart(p);
     421              :                 }
     422              :             }
     423            0 :             endrender();
     424            0 :         }
     425            0 :         void reset() final
     426              :         {
     427            0 :             if(!list)
     428              :             {
     429            0 :                 return;
     430              :             }
     431            0 :             listparticle *p = list;
     432              :             for(;;)
     433              :             {
     434            0 :                 if(p->next)
     435              :                 {
     436            0 :                     p = p->next;
     437              :                 }
     438              :                 else
     439              :                 {
     440            0 :                     break;
     441              :                 }
     442              :             }
     443            0 :             p->next = parempty;
     444            0 :             parempty = list;
     445            0 :             list = nullptr;
     446              :         }
     447              : 
     448            0 :         void resettracked(const physent *owner) final
     449              :         {
     450            0 :             if(!(parttype()&PT_TRACK))
     451              :             {
     452            0 :                 return;
     453              :             }
     454            0 :             for(listparticle **prev = &list, *cur = list; cur; cur = *prev)
     455              :             {
     456            0 :                 if(!owner || cur->owner==owner)
     457              :                 {
     458            0 :                     *prev = cur->next;
     459            0 :                     cur->next = parempty;
     460            0 :                     parempty = cur;
     461              :                 }
     462              :                 else
     463              :                 {
     464            0 :                     prev = &cur->next;
     465              :                 }
     466              :             }
     467              :         }
     468              : 
     469            0 :         particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity) final
     470              :         {
     471            0 :             if(!parempty)
     472              :             {
     473            0 :                 listparticle *ps = new listparticle[256];
     474            0 :                 for(int i = 0; i < 255; ++i)
     475              :                 {
     476            0 :                     ps[i].next = &ps[i+1];
     477              :                 }
     478            0 :                 ps[255].next = parempty;
     479            0 :                 parempty = ps;
     480              :             }
     481            0 :             listparticle *p = parempty;
     482            0 :             parempty = p->next;
     483            0 :             p->next = list;
     484            0 :             list = p;
     485            0 :             p->o = o;
     486            0 :             p->d = d;
     487            0 :             p->gravity = gravity;
     488            0 :             p->fade = fade;
     489            0 :             p->millis = lastmillis + emitoffset;
     490            0 :             p->color = bvec::hexcolor(color);
     491            0 :             p->size = size;
     492            0 :             p->owner = nullptr;
     493            0 :             p->flags = 0;
     494            0 :             return p;
     495              :         }
     496              : 
     497            0 :         int count() const final
     498              :         {
     499            0 :             int num = 0;
     500              :             const listparticle *lp;
     501            0 :             for(lp = list; lp; lp = lp->next)
     502              :             {
     503            0 :                 num++;
     504              :             }
     505            0 :             return num;
     506              :         }
     507              :         static listparticle *parempty;
     508              :         listparticle *list;
     509              : };
     510              : 
     511              : listparticle *listrenderer::parempty = nullptr;
     512              : 
     513              : class meterrenderer final : public listrenderer
     514              : {
     515              :     public:
     516            2 :         meterrenderer(int type)
     517            2 :             : listrenderer(type|PT_NOTEX|PT_LERP|PT_NOLAYER)
     518              :         {
     519            2 :         }
     520              : 
     521            0 :         void init(int) final
     522              :         {
     523            0 :         }
     524              : 
     525            0 :         void seedemitter(ParticleEmitter &, const vec &, const vec &, int, float, int) final
     526              :         {
     527            0 :         }
     528              : 
     529              :     private:
     530            0 :         void startrender() final
     531              :         {
     532            0 :             glDisable(GL_BLEND);
     533            0 :             gle::defvertex();
     534            0 :         }
     535              : 
     536            0 :         void endrender() final
     537              :         {
     538            0 :             glEnable(GL_BLEND);
     539            0 :         }
     540              : 
     541            0 :         void renderpart(const listparticle &p, const vec &o, int, int) final
     542              :         {
     543            0 :             int basetype = parttype()&0xFF;
     544            0 :             float scale  = FONTH*p.size/80.0f,
     545            0 :                   right  = 8,
     546            0 :                   left   = p.meter.progress/100.0f*right;
     547            0 :             matrix4x3 m(camright(), camup().neg(), camdir().neg(), o);
     548            0 :             m.scale(scale);
     549            0 :             m.translate(-right/2.0f, 0, 0);
     550              : 
     551            0 :             if(outlinemeters)
     552              :             {
     553            0 :                 gle::colorf(0, 0.8f, 0);
     554            0 :                 gle::begin(GL_TRIANGLE_STRIP);
     555            0 :                 for(int k = 0; k < 10; ++k)
     556              :                 {
     557            0 :                     const vec2 &sc = sincos360[k*(180/(10-1))];
     558            0 :                     float c = (0.5f + 0.1f)*sc.y,
     559            0 :                           s = 0.5f - (0.5f + 0.1f)*sc.x;
     560            0 :                     gle::attrib(m.transform(vec2(-c, s)));
     561            0 :                     gle::attrib(m.transform(vec2(right + c, s)));
     562              :                 }
     563            0 :                 gle::end();
     564              :             }
     565            0 :             if(basetype==PT_METERVS)
     566              :             {
     567            0 :                 gle::colorub(p.meter.color2[0], p.meter.color2[1], p.meter.color2[2]);
     568              :             }
     569              :             else
     570              :             {
     571            0 :                 gle::colorf(0, 0, 0);
     572              :             }
     573            0 :             gle::begin(GL_TRIANGLE_STRIP);
     574            0 :             for(int k = 0; k < 10; ++k)
     575              :             {
     576            0 :                 const vec2 &sc = sincos360[k*(180/(10-1))];
     577            0 :                 float c = 0.5f*sc.y,
     578            0 :                       s = 0.5f - 0.5f*sc.x;
     579            0 :                 gle::attrib(m.transform(vec2(left + c, s)));
     580            0 :                 gle::attrib(m.transform(vec2(right + c, s)));
     581              :             }
     582            0 :             gle::end();
     583              : 
     584            0 :             if(outlinemeters)
     585              :             {
     586            0 :                 gle::colorf(0, 0.8f, 0);
     587            0 :                 gle::begin(GL_TRIANGLE_FAN);
     588            0 :                 for(int k = 0; k < 10; ++k)
     589              :                 {
     590            0 :                     const vec2 &sc = sincos360[k*(180/(10-1))];
     591            0 :                     float c = (0.5f + 0.1f)*sc.y,
     592            0 :                           s = 0.5f - (0.5f + 0.1f)*sc.x;
     593            0 :                     gle::attrib(m.transform(vec2(left + c, s)));
     594              :                 }
     595            0 :                 gle::end();
     596              :             }
     597              : 
     598            0 :             gle::color(p.color);
     599            0 :             gle::begin(GL_TRIANGLE_STRIP);
     600            0 :             for(int k = 0; k < 10; ++k)
     601              :             {
     602            0 :                 const vec2 &sc = sincos360[k*(180/(10-1))];
     603            0 :                 float c = 0.5f*sc.y,
     604            0 :                       s = 0.5f - 0.5f*sc.x;
     605            0 :                 gle::attrib(m.transform(vec2(-c, s)));
     606            0 :                 gle::attrib(m.transform(vec2(left + c, s)));
     607              :             }
     608            0 :             gle::end();
     609            0 :         }
     610              : };
     611              : static meterrenderer meters(PT_METER),
     612              :                      metervs(PT_METERVS);
     613              : 
     614              : template<int T>
     615            0 : static void modifyblend(int &blend)
     616              : {
     617            0 :     blend = std::min(blend<<2, 255);
     618            0 : }
     619              : 
     620              : template<int T>
     621            0 : static void genpos(const vec &o, const vec &, float size, int, int, partvert *vs)
     622              : {
     623            0 :     vec udir = camup().sub(camright()).mul(size),
     624            0 :         vdir = camup().add(camright()).mul(size);
     625            0 :     vs[0].pos = vec(o.x + udir.x, o.y + udir.y, o.z + udir.z);
     626            0 :     vs[1].pos = vec(o.x + vdir.x, o.y + vdir.y, o.z + vdir.z);
     627            0 :     vs[2].pos = vec(o.x - udir.x, o.y - udir.y, o.z - udir.z);
     628            0 :     vs[3].pos = vec(o.x - vdir.x, o.y - vdir.y, o.z - vdir.z);
     629            0 : }
     630              : 
     631              : template<>
     632            0 : void genpos<PT_TAPE>(const vec &o, const vec &d, float size, int, int, partvert *vs)
     633              : {
     634            0 :     vec dir1 = vec(d).sub(o),
     635            0 :         dir2 = vec(d).sub(camera1->o), c;
     636            0 :     c.cross(dir2, dir1).normalize().mul(size);
     637            0 :     vs[0].pos = vec(d.x-c.x, d.y-c.y, d.z-c.z);
     638            0 :     vs[1].pos = vec(o.x-c.x, o.y-c.y, o.z-c.z);
     639            0 :     vs[2].pos = vec(o.x+c.x, o.y+c.y, o.z+c.z);
     640            0 :     vs[3].pos = vec(d.x+c.x, d.y+c.y, d.z+c.z);
     641            0 : }
     642              : 
     643              : template<>
     644            0 : void genpos<PT_TRAIL>(const vec &o, const vec &d, float size, int ts, int grav, partvert *vs)
     645              : {
     646            0 :     vec e = d;
     647            0 :     if(grav)
     648              :     {
     649            0 :         e.z -= static_cast<float>(ts)/grav;
     650              :     }
     651            0 :     e.div(-75.0f).add(o);
     652            0 :     genpos<PT_TAPE>(o, e, size, ts, grav, vs);
     653            0 : }
     654              : 
     655              : template<int T>
     656            0 : void genrotpos(const vec &o, const vec &d, float size, int grav, int ts, partvert *vs, int)
     657              : {
     658            0 :     genpos<T>(o, d, size, grav, ts, vs);
     659            0 : }
     660              : 
     661              : //==================================================================== ROTCOEFFS
     662              : #define ROTCOEFFS(n) { \
     663              :     vec2(-1,  1).rotate_around_z(n*2*M_PI/32.0f), \
     664              :     vec2( 1,  1).rotate_around_z(n*2*M_PI/32.0f), \
     665              :     vec2( 1, -1).rotate_around_z(n*2*M_PI/32.0f), \
     666              :     vec2(-1, -1).rotate_around_z(n*2*M_PI/32.0f) \
     667              : }
     668              : static const vec2 rotcoeffs[32][4] =
     669              : {
     670              :     ROTCOEFFS(0),  ROTCOEFFS(1),  ROTCOEFFS(2),  ROTCOEFFS(3),  ROTCOEFFS(4),  ROTCOEFFS(5),  ROTCOEFFS(6),  ROTCOEFFS(7),
     671              :     ROTCOEFFS(8),  ROTCOEFFS(9),  ROTCOEFFS(10), ROTCOEFFS(11), ROTCOEFFS(12), ROTCOEFFS(13), ROTCOEFFS(14), ROTCOEFFS(15),
     672              :     ROTCOEFFS(16), ROTCOEFFS(17), ROTCOEFFS(18), ROTCOEFFS(19), ROTCOEFFS(20), ROTCOEFFS(21), ROTCOEFFS(22), ROTCOEFFS(7),
     673              :     ROTCOEFFS(24), ROTCOEFFS(25), ROTCOEFFS(26), ROTCOEFFS(27), ROTCOEFFS(28), ROTCOEFFS(29), ROTCOEFFS(30), ROTCOEFFS(31),
     674              : };
     675              : #undef ROTCOEFFS
     676              : //==============================================================================
     677              : 
     678              : template<>
     679            0 : void genrotpos<PT_PART>(const vec &o, const vec &, float size, int, int, partvert *vs, int rot)
     680              : {
     681            0 :     const vec2 *coeffs = rotcoeffs[rot];
     682            0 :     vs[0].pos = vec(o).madd(camright(), coeffs[0].x*size).madd(camup(), coeffs[0].y*size);
     683            0 :     vs[1].pos = vec(o).madd(camright(), coeffs[1].x*size).madd(camup(), coeffs[1].y*size);
     684            0 :     vs[2].pos = vec(o).madd(camright(), coeffs[2].x*size).madd(camup(), coeffs[2].y*size);
     685            0 :     vs[3].pos = vec(o).madd(camright(), coeffs[3].x*size).madd(camup(), coeffs[3].y*size);
     686            0 : }
     687              : 
     688              : template<int T>
     689            0 : void seedpos(ParticleEmitter &pe, const vec &o, const vec &d, int fade, float size, int grav)
     690              : {
     691            0 :     constexpr float scale = 5000.f;
     692            0 :     if(grav)
     693              :     {
     694            0 :         float t = fade;
     695            0 :         vec end = vec(o).madd(d, t/scale);
     696            0 :         end.z -= t*t/(2.0f * scale * grav);
     697            0 :         pe.extendbb(end, size);
     698            0 :         float tpeak = d.z*grav;
     699            0 :         if(tpeak > 0 && tpeak < fade)
     700              :         {
     701            0 :             pe.extendbb(o.z + 1.5f*d.z*tpeak/scale, size);
     702              :         }
     703              :     }
     704            0 : }
     705              : 
     706              : template<>
     707            0 : void seedpos<PT_TAPE>(ParticleEmitter &pe, const vec &, const vec &d, int, float size, int)
     708              : {
     709            0 :     pe.extendbb(d, size);
     710            0 : }
     711              : 
     712              : template<>
     713            0 : void seedpos<PT_TRAIL>(ParticleEmitter &pe, const vec &o, const vec &d, int fade, float size, int grav)
     714              : {
     715            0 :     vec e = d;
     716            0 :     if(grav)
     717              :     {
     718            0 :         e.z -= static_cast<float>(fade)/grav;
     719              :     }
     720            0 :     e.div(-75.0f).add(o);
     721            0 :     pe.extendbb(e, size);
     722            0 : }
     723              : 
     724              : //only used by template<> varenderer, but cannot be declared in a template in c++17
     725            0 : static void setcolor(float r, float g, float b, float a, partvert * vs)
     726              : {
     727            0 :     vec4<uchar> col(r, g, b, a);
     728            0 :     for(int i = 0; i < 4; ++i)
     729              :     {
     730            0 :         vs[i].color = col;
     731              :     }
     732            0 : };
     733              : 
     734              : template<int T>
     735              : class varenderer final : public partrenderer
     736              : {
     737              :     public:
     738           16 :         varenderer(const char *texname, int type, int stain = -1)
     739              :             : partrenderer(texname, 3, type, stain),
     740           16 :               verts(nullptr), parts(nullptr), maxparts(0), numparts(0), lastupdate(-1), rndmask(0), vbo(0)
     741              :         {
     742           16 :             if(type & PT_HFLIP)
     743              :             {
     744           10 :                 rndmask |= 0x01;
     745              :             }
     746           16 :             if(type & PT_VFLIP)
     747              :             {
     748            9 :                 rndmask |= 0x02;
     749              :             }
     750           16 :             if(type & PT_ROT)
     751              :             {
     752            9 :                 rndmask |= 0x1F<<2;
     753              :             }
     754           16 :             if(type & PT_RND4)
     755              :             {
     756            3 :                 rndmask |= 0x03<<5;
     757              :             }
     758           16 :         }
     759              : 
     760            0 :         void cleanup() final
     761              :         {
     762            0 :             if(vbo)
     763              :             {
     764            0 :                 glDeleteBuffers(1, &vbo);
     765            0 :                 vbo = 0;
     766              :             }
     767            0 :         }
     768              : 
     769            0 :         void init(int n) final
     770              :         {
     771            0 :             delete[] parts;
     772            0 :             delete[] verts;
     773            0 :             parts = new particle[n];
     774            0 :             verts = new partvert[n*4];
     775            0 :             maxparts = n;
     776            0 :             numparts = 0;
     777            0 :             lastupdate = -1;
     778            0 :         }
     779              : 
     780            0 :         void reset() final
     781              :         {
     782            0 :             numparts = 0;
     783            0 :             lastupdate = -1;
     784            0 :         }
     785              : 
     786            0 :         void resettracked(const physent *owner) final
     787              :         {
     788            0 :             if(!(parttype()&PT_TRACK))
     789              :             {
     790            0 :                 return;
     791              :             }
     792            0 :             for(int i = 0; i < numparts; ++i)
     793              :             {
     794            0 :                 particle *p = parts+i;
     795            0 :                 if(!owner || (p->owner == owner))
     796              :                 {
     797            0 :                     p->fade = -1;
     798              :                 }
     799              :             }
     800            0 :             lastupdate = -1;
     801              :         }
     802              : 
     803            0 :         int count() const final
     804              :         {
     805            0 :             return numparts;
     806              :         }
     807              : 
     808            0 :         bool haswork() const final
     809              :         {
     810            0 :             return (numparts > 0);
     811              :         }
     812              : 
     813            0 :         particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity) final
     814              :         {
     815            0 :             particle *p = parts + (numparts < maxparts ? numparts++ : randomint(maxparts)); //next free slot, or kill a random kitten
     816            0 :             p->o = o;
     817            0 :             p->d = d;
     818            0 :             p->gravity = gravity;
     819            0 :             p->fade = fade;
     820            0 :             p->millis = lastmillis + emitoffset;
     821            0 :             p->color = bvec::hexcolor(color);
     822            0 :             p->size = size;
     823            0 :             p->owner = nullptr;
     824            0 :             p->flags = 0x80 | (rndmask ? randomint(0x80) & rndmask : 0);
     825            0 :             lastupdate = -1;
     826            0 :             return p;
     827              :         }
     828              : 
     829            0 :         void seedemitter(ParticleEmitter &pe, const vec &o, const vec &d, int fade, float size, int gravity) final
     830              :         {
     831            0 :             pe.maxfade = std::max(pe.maxfade, fade);
     832            0 :             size *= SQRT2;
     833            0 :             pe.extendbb(o, size);
     834              : 
     835            0 :             seedpos<T>(pe, o, d, fade, size, gravity);
     836            0 :             if(!gravity)
     837              :             {
     838            0 :                 return;
     839              :             }
     840            0 :             vec end(o);
     841            0 :             float t = fade;
     842            0 :             end.add(vec(d).mul(t/5000.0f));
     843            0 :             end.z -= t*t/(2.0f * 5000.0f * gravity);
     844            0 :             pe.extendbb(end, size);
     845            0 :             float tpeak = d.z*gravity;
     846            0 :             if(tpeak > 0 && tpeak < fade)
     847              :             {
     848            0 :                 pe.extendbb(o.z + 1.5f*d.z*tpeak/5000.0f, size);
     849              :             }
     850              :         }
     851              : 
     852            0 :         void render() final
     853              :         {
     854            0 :             genvbo();
     855              : 
     856            0 :             glBindTexture(GL_TEXTURE_2D, tex->id);
     857              : 
     858            0 :             gle::bindvbo(vbo);
     859            0 :             const partvert *ptr = 0;
     860            0 :             gle::vertexpointer(sizeof(partvert), ptr->pos.data());
     861            0 :             gle::texcoord0pointer(sizeof(partvert), ptr->tc.data());
     862            0 :             gle::colorpointer(sizeof(partvert), ptr->color.data());
     863            0 :             gle::enablevertex();
     864            0 :             gle::enabletexcoord0();
     865            0 :             gle::enablecolor();
     866            0 :             gle::enablequads();
     867            0 :             gle::drawquads(0, numparts);
     868            0 :             gle::disablequads();
     869            0 :             gle::disablevertex();
     870            0 :             gle::disabletexcoord0();
     871            0 :             gle::disablecolor();
     872            0 :             gle::clearvbo();
     873            0 :         }
     874              : 
     875              :     private:
     876              :         partvert *verts; //an array of vert objects
     877              :         particle *parts; //an array of particle objects, length numparts
     878              :         int maxparts, numparts, lastupdate, rndmask;
     879              :         GLuint vbo;
     880              : 
     881            0 :         void genverts(particle *p, partvert *vs, bool regen)
     882              :         {
     883            0 :             vec o, d;
     884              :             int blend, ts;
     885            0 :             calc(p, blend, ts, o, d);
     886            0 :             if(blend <= 1 || p->fade <= 5)
     887              :             {
     888            0 :                 p->fade = -1; //mark to remove on next pass (i.e. after render)
     889              :             }
     890            0 :             modifyblend<T>(blend);
     891            0 :             if(regen)
     892              :             {
     893            0 :                 p->flags &= ~0x80;
     894              : 
     895            0 :                 auto swaptexcoords = [&] (float u1, float u2, float v1, float v2)
     896              :                 {
     897            0 :                     if(p->flags&0x01)
     898              :                     {
     899            0 :                         std::swap(u1, u2);
     900              :                     }
     901            0 :                     if(p->flags&0x02)
     902              :                     {
     903            0 :                         std::swap(v1, v2);
     904              :                     }
     905              :                 };
     906              : 
     907              :                 //sets the partvert vs array's tc fields to four permutations of input parameters
     908            0 :                 auto settexcoords = [vs, swaptexcoords] (float u1c, float u2c, float v1c, float v2c, bool swap)
     909              :                 {
     910            0 :                     float u1 = u1c,
     911            0 :                           u2 = u2c,
     912            0 :                           v1 = v1c,
     913            0 :                           v2 = v2c;
     914            0 :                     if(swap)
     915              :                     {
     916            0 :                         swaptexcoords(u1, u2, v1, v2);
     917              :                     }
     918            0 :                     vs[0].tc = vec2(u1, v1);
     919            0 :                     vs[1].tc = vec2(u2, v1);
     920            0 :                     vs[2].tc = vec2(u2, v2);
     921            0 :                     vs[3].tc = vec2(u1, v2);
     922              :                 };
     923              : 
     924            0 :                 if(parttype()&PT_RND4)
     925              :                 {
     926            0 :                     float tx = 0.5f*((p->flags>>5)&1),
     927            0 :                           ty = 0.5f*((p->flags>>6)&1);
     928            0 :                     settexcoords(tx, tx + 0.5f, ty, ty + 0.5f, true);
     929              :                 }
     930            0 :                 else if(parttype()&PT_ICON)
     931              :                 {
     932            0 :                     float tx = 0.25f*(p->flags&3),
     933            0 :                           ty = 0.25f*((p->flags>>2)&3);
     934            0 :                     settexcoords(tx, tx + 0.25f, ty, ty + 0.25f, false);
     935              :                 }
     936              :                 else
     937              :                 {
     938            0 :                     settexcoords(0, 1, 0, 1, false);
     939              :                 }
     940              : 
     941            0 :                 if(parttype()&PT_MOD)
     942              :                 {
     943            0 :                     setcolor((p->color.r()*blend)>>8, (p->color.g()*blend)>>8, (p->color.b()*blend)>>8, 255, vs);
     944              :                 }
     945              :                 else
     946              :                 {
     947            0 :                     setcolor(p->color.r(), p->color.g(), p->color.b(), blend, vs);
     948              :                 }
     949              : 
     950              :             }
     951            0 :             else if(parttype()&PT_MOD)
     952              :             {
     953              :                 //note: same call as `if(type&PT_MOD)` above
     954            0 :                 setcolor((p->color.r()*blend)>>8, (p->color.g()*blend)>>8, (p->color.b()*blend)>>8, 255, vs);
     955              :             }
     956              :             else
     957              :             {
     958            0 :                 for(int i = 0; i < 4; ++i)
     959              :                 {
     960            0 :                     vs[i].color.a() = blend;
     961              :                 }
     962              :             }
     963            0 :             if(parttype()&PT_ROT)
     964              :             {
     965            0 :                 genrotpos<T>(o, d, p->size, ts, p->gravity, vs, (p->flags>>2)&0x1F);
     966              :             }
     967              :             else
     968              :             {
     969            0 :                 genpos<T>(o, d, p->size, ts, p->gravity, vs);
     970              :             }
     971            0 :         }
     972              : 
     973            0 :         void genverts()
     974              :         {
     975            0 :             for(int i = 0; i < numparts; ++i)
     976              :             {
     977            0 :                 particle *p = &parts[i];
     978            0 :                 partvert *vs = &verts[i*4];
     979            0 :                 if(p->fade < 0)
     980              :                 {
     981              :                     do
     982              :                     {
     983            0 :                         --numparts;
     984            0 :                         if(numparts <= i)
     985              :                         {
     986            0 :                             return;
     987              :                         }
     988            0 :                     } while(parts[numparts].fade < 0);
     989            0 :                     *p = parts[numparts];
     990            0 :                     genverts(p, vs, true);
     991              :                 }
     992              :                 else
     993              :                 {
     994            0 :                     genverts(p, vs, (p->flags&0x80)!=0);
     995              :                 }
     996              :             }
     997              :         }
     998              : 
     999            0 :         void genvbo()
    1000              :         {
    1001            0 :             if(lastmillis == lastupdate && vbo)
    1002              :             {
    1003            0 :                 return;
    1004              :             }
    1005            0 :             lastupdate = lastmillis;
    1006            0 :             genverts();
    1007            0 :             if(!vbo)
    1008              :             {
    1009            0 :                 glGenBuffers(1, &vbo);
    1010              :             }
    1011            0 :             gle::bindvbo(vbo);
    1012            0 :             glBufferData(GL_ARRAY_BUFFER, maxparts*4*sizeof(partvert), nullptr, GL_STREAM_DRAW);
    1013            0 :             glBufferSubData(GL_ARRAY_BUFFER, 0, numparts*4*sizeof(partvert), verts);
    1014            0 :             gle::clearvbo();
    1015              :         }
    1016              : };
    1017              : 
    1018              : // explosions
    1019              : 
    1020              : static VARP(softexplosion, 0, 1, 1); //toggles EXPLOSIONSOFT shader
    1021              : static VARP(softexplosionblend, 1, 16, 64);
    1022              : 
    1023              : class fireballrenderer final : public listrenderer
    1024              : {
    1025              :     public:
    1026            2 :         fireballrenderer(const char *newtexname)
    1027            2 :             : listrenderer(newtexname, 0, +PT_FIREBALL|+PT_SHADER) //unary plus to promote to an integer, c++20 deprecates arithmetic conversion on enums (see C++ doc P2864R2)
    1028            2 :         {}
    1029              : 
    1030              :         static constexpr float wobble = 1.25f; //factor to extend particle hitbox by due to placement movement
    1031              : 
    1032            0 :         void startrender() final
    1033              :         {
    1034            0 :             if(softexplosion)
    1035              :             {
    1036            0 :                 SETSHADER(explosionsoft);
    1037              :             }
    1038              :             else
    1039              :             {
    1040            0 :                 SETSHADER(explosion);
    1041              :             }
    1042            0 :             sr.enable();
    1043            0 :         }
    1044              : 
    1045            0 :         void endrender() final
    1046              :         {
    1047            0 :             sr.disable();
    1048            0 :         }
    1049              : 
    1050            0 :         void cleanup() final
    1051              :         {
    1052            0 :             sr.cleanup();
    1053            0 :         }
    1054              : 
    1055            0 :         void seedemitter(ParticleEmitter &pe, const vec &o, const vec &, int fade, float size, int) final
    1056              :         {
    1057            0 :             pe.maxfade = std::max(pe.maxfade, fade);
    1058            0 :             pe.extendbb(o, (size+1+pe.ent->attr2)*wobble);
    1059            0 :         }
    1060              : 
    1061            0 :         void renderpart(const listparticle &p, const vec &o, int blend, int ts) final
    1062              :         {
    1063            0 :             float pmax = p.val,
    1064            0 :                   size = p.fade ? static_cast<float>(ts)/p.fade : 1,
    1065            0 :                   psize = p.size + pmax * size;
    1066              : 
    1067            0 :             if(view.isfoggedsphere(psize*wobble, p.o))
    1068              :             {
    1069            0 :                 return;
    1070              :             }
    1071            0 :             vec dir = static_cast<vec>(o).sub(camera1->o), s, t;
    1072            0 :             float dist = dir.magnitude();
    1073            0 :             bool inside = dist <= psize*wobble;
    1074            0 :             if(inside)
    1075              :             {
    1076            0 :                 s = camright();
    1077            0 :                 t = camup();
    1078              :             }
    1079              :             else
    1080              :             {
    1081            0 :                 float mag2 = dir.magnitude2();
    1082            0 :                 dir.x /= mag2;
    1083            0 :                 dir.y /= mag2;
    1084            0 :                 dir.z /= dist;
    1085            0 :                 s = vec(dir.y, -dir.x, 0);
    1086            0 :                 t = vec(dir.x*dir.z, dir.y*dir.z, -mag2/dist);
    1087              :             }
    1088              : 
    1089            0 :             matrix3 rot(lastmillis/1000.0f*143/RAD, vec(1/SQRT3, 1/SQRT3, 1/SQRT3));
    1090            0 :             LOCALPARAM(texgenS, rot.transposedtransform(s));
    1091            0 :             LOCALPARAM(texgenT, rot.transposedtransform(t));
    1092              : 
    1093            0 :             matrix4 m(rot, o);
    1094            0 :             m.scale(psize, psize, inside ? -psize : psize);
    1095            0 :             m.mul(camprojmatrix, m);
    1096            0 :             LOCALPARAM(explosionmatrix, m);
    1097              : 
    1098            0 :             LOCALPARAM(center, o);
    1099            0 :             LOCALPARAMF(blendparams, inside ? 0.5f : 4, inside ? 0.25f : 0);
    1100            0 :             if(2*(p.size + pmax)*wobble >= softexplosionblend)
    1101              :             {
    1102            0 :                 LOCALPARAMF(softparams, -1.0f/softexplosionblend, 0, inside ? blend/(2*255.0f) : 0);
    1103              :             }
    1104              :             else
    1105              :             {
    1106            0 :                 LOCALPARAMF(softparams, 0, -1, inside ? blend/(2*255.0f) : 0);
    1107              :             }
    1108              : 
    1109            0 :             vec color = p.color.tocolor().mul(ldrscale);
    1110            0 :             float alpha = blend/255.0f;
    1111              : 
    1112            0 :             for(int i = 0; i < (inside ? 2 : 1); ++i)
    1113              :             {
    1114            0 :                 gle::color(color, i ? alpha/2 : alpha);
    1115            0 :                 if(i)
    1116              :                 {
    1117            0 :                     glDepthFunc(GL_GEQUAL);
    1118              :                 }
    1119            0 :                 sr.draw();
    1120            0 :                 if(i)
    1121              :                 {
    1122            0 :                     glDepthFunc(GL_LESS);
    1123              :                 }
    1124              :             }
    1125              :         }
    1126              : 
    1127            0 :         void init(int) final
    1128              :         {
    1129            0 :         }
    1130              : 
    1131              :     private:
    1132              :         class sphererenderer final
    1133              :         {
    1134              :             public:
    1135            0 :                 void cleanup()
    1136              :                 {
    1137            0 :                     if(vbuf)
    1138              :                     {
    1139            0 :                         glDeleteBuffers(1, &vbuf);
    1140            0 :                         vbuf = 0;
    1141              :                     }
    1142            0 :                     if(ebuf)
    1143              :                     {
    1144            0 :                         glDeleteBuffers(1, &ebuf);
    1145            0 :                         ebuf = 0;
    1146              :                     }
    1147            0 :                 }
    1148              : 
    1149            0 :                 void enable()
    1150              :                 {
    1151            0 :                     if(!vbuf)
    1152              :                     {
    1153            0 :                         init(12, 6); //12 slices, 6 stacks
    1154              :                     }
    1155            0 :                     gle::bindvbo(vbuf);
    1156            0 :                     gle::bindebo(ebuf);
    1157              : 
    1158            0 :                     gle::vertexpointer(sizeof(vert), &verts->pos);
    1159            0 :                     gle::texcoord0pointer(sizeof(vert), &verts->s, GL_UNSIGNED_SHORT, 2, GL_TRUE);
    1160            0 :                     gle::enablevertex();
    1161            0 :                     gle::enabletexcoord0();
    1162            0 :                 }
    1163              : 
    1164            0 :                 void draw()
    1165              :                 {
    1166            0 :                     glDrawRangeElements(GL_TRIANGLES, 0, numverts-1, numindices, GL_UNSIGNED_SHORT, indices);
    1167            0 :                     xtraverts += numindices;
    1168            0 :                     glde++;
    1169            0 :                 }
    1170              : 
    1171            0 :                 void disable()
    1172              :                 {
    1173            0 :                     gle::disablevertex();
    1174            0 :                     gle::disabletexcoord0();
    1175              : 
    1176            0 :                     gle::clearvbo();
    1177            0 :                     gle::clearebo();
    1178            0 :                 }
    1179              :             private:
    1180              :                 struct vert final
    1181              :                 {
    1182              :                     vec pos;
    1183              :                     ushort s, t;
    1184              :                 } *verts = nullptr;
    1185              :                 GLushort *indices = nullptr;
    1186              :                 int numverts   = 0,
    1187              :                     numindices = 0;
    1188              :                 GLuint vbuf = 0,
    1189              :                        ebuf = 0;
    1190              : 
    1191            0 :                 void init(int slices, int stacks)
    1192              :                 {
    1193            0 :                     numverts = (stacks+1)*(slices+1);
    1194            0 :                     verts = new vert[numverts];
    1195            0 :                     float ds = 1.0f/slices,
    1196            0 :                           dt = 1.0f/stacks,
    1197            0 :                           t  = 1.0f;
    1198            0 :                     for(int i = 0; i < stacks+1; ++i)
    1199              :                     {
    1200            0 :                         float rho = M_PI*(1-t),
    1201            0 :                               s = 0.0f,
    1202            0 :                               sinrho = i && i < stacks ? std::sin(rho) : 0,
    1203            0 :                               cosrho = !i ? 1 : (i < stacks ? std::cos(rho) : -1);
    1204            0 :                         for(int j = 0; j < slices+1; ++j)
    1205              :                         {
    1206            0 :                             float theta = j==slices ? 0 : 2*M_PI*s;
    1207            0 :                             vert &v = verts[i*(slices+1) + j];
    1208            0 :                             v.pos = vec(std::sin(theta)*sinrho, std::cos(theta)*sinrho, -cosrho);
    1209            0 :                             v.s = static_cast<ushort>(s*0xFFFF);
    1210            0 :                             v.t = static_cast<ushort>(t*0xFFFF);
    1211            0 :                             s += ds;
    1212              :                         }
    1213            0 :                         t -= dt;
    1214              :                     }
    1215              : 
    1216            0 :                     numindices = (stacks-1)*slices*3*2;
    1217            0 :                     indices = new ushort[numindices];
    1218            0 :                     GLushort *curindex = indices;
    1219            0 :                     for(int i = 0; i < stacks; ++i)
    1220              :                     {
    1221            0 :                         for(int k = 0; k < slices; ++k)
    1222              :                         {
    1223            0 :                             int j = i%2 ? slices-k-1 : k;
    1224            0 :                             if(i)
    1225              :                             {
    1226            0 :                                 *curindex++ = i*(slices+1)+j;
    1227            0 :                                 *curindex++ = (i+1)*(slices+1)+j;
    1228            0 :                                 *curindex++ = i*(slices+1)+j+1;
    1229              :                             }
    1230            0 :                             if(i+1 < stacks)
    1231              :                             {
    1232            0 :                                 *curindex++ = i*(slices+1)+j+1;
    1233            0 :                                 *curindex++ = (i+1)*(slices+1)+j;
    1234            0 :                                 *curindex++ = (i+1)*(slices+1)+j+1;
    1235              :                             }
    1236              :                         }
    1237              :                     }
    1238            0 :                     if(!vbuf)
    1239              :                     {
    1240            0 :                         glGenBuffers(1, &vbuf);
    1241              :                     }
    1242            0 :                     gle::bindvbo(vbuf);
    1243            0 :                     glBufferData(GL_ARRAY_BUFFER, numverts*sizeof(vert), verts, GL_STATIC_DRAW);
    1244            0 :                     delete[] verts;
    1245            0 :                     verts = nullptr;
    1246            0 :                     if(!ebuf)
    1247              :                     {
    1248            0 :                         glGenBuffers(1, &ebuf);
    1249              :                     }
    1250            0 :                     gle::bindebo(ebuf);
    1251            0 :                     glBufferData(GL_ELEMENT_ARRAY_BUFFER, numindices*sizeof(GLushort), indices, GL_STATIC_DRAW);
    1252            0 :                     delete[] indices;
    1253            0 :                     indices = nullptr;
    1254            0 :                 }
    1255              :         };
    1256              :         sphererenderer sr;
    1257              : 
    1258              : };
    1259              : static fireballrenderer fireballs("media/particle/explosion.png"), pulsebursts("media/particle/pulse_burst.png");
    1260              : 
    1261              : //end explosion code
    1262              : 
    1263              : static partrenderer *parts[] =
    1264              : {
    1265              :     //unary pluses to promote to an integer, c++20 deprecates arithmetic conversion on enums (see C++ doc P2864R2)
    1266              :     new varenderer<+PT_PART>("<grey>media/particle/blood.png", +PT_PART|+PT_FLIP|+PT_MOD|+PT_RND4|+PT_COLLIDE, Stain_Blood), // blood spats (note: rgb is inverted)
    1267              :     new varenderer<+PT_TRAIL>("media/particle/base.png", +PT_TRAIL|+PT_LERP),                                  // water, entity
    1268              :     new varenderer<+PT_PART>("<grey>media/particle/smoke.png", +PT_PART|+PT_FLIP|+PT_LERP),                     // smoke
    1269              :     new varenderer<+PT_PART>("<grey>media/particle/steam.png", +PT_PART|+PT_FLIP),                             // steam
    1270              :     new varenderer<+PT_PART>("<grey>media/particle/flames.png", +PT_PART|+PT_HFLIP|+PT_RND4|+PT_BRIGHT),         // flame
    1271              :     new varenderer<+PT_TAPE>("media/particle/flare.png", +PT_TAPE|+PT_BRIGHT),                                 // streak
    1272              :     new varenderer<+PT_TAPE>("media/particle/rail_trail.png", +PT_TAPE|+PT_FEW|+PT_BRIGHT),                     // rail trail
    1273              :     new varenderer<+PT_TAPE>("media/particle/pulse_side.png", +PT_TAPE|+PT_FEW|+PT_BRIGHT),                     // pulse side
    1274              :     new varenderer<+PT_PART>("media/particle/pulse_front.png", +PT_PART|+PT_FLIP|+PT_FEW|+PT_BRIGHT),            // pulse front
    1275              :     &fireballs,                                                                                             // explosion fireball
    1276              :     &pulsebursts,                                                                                           // pulse burst
    1277              :     new varenderer<+PT_PART>("media/particle/spark.png", +PT_PART|+PT_FLIP|+PT_BRIGHT),                         // sparks
    1278              :     new varenderer<+PT_PART>("media/particle/base.png",  +PT_PART|+PT_FLIP|+PT_BRIGHT),                         // edit mode entities
    1279              :     new varenderer<+PT_PART>("media/particle/snow.png", +PT_PART|+PT_FLIP|+PT_RND4|+PT_COLLIDE),                 // colliding snow
    1280              :     new varenderer<+PT_PART>("media/particle/rail_muzzle.png", +PT_PART|+PT_FEW|+PT_FLIP|+PT_BRIGHT|+PT_TRACK),   // rail muzzle flash
    1281              :     new varenderer<+PT_PART>("media/particle/pulse_muzzle.png", +PT_PART|+PT_FEW|+PT_FLIP|+PT_BRIGHT|+PT_TRACK),  // pulse muzzle flash
    1282              :     new varenderer<+PT_PART>("media/interface/hud/items.png", +PT_PART|+PT_FEW|+PT_ICON),                       // hud icon
    1283              :     new varenderer<+PT_PART>("<colorify:1/1/1>media/interface/hud/items.png", +PT_PART|+PT_FEW|+PT_ICON),       // grey hud icon
    1284              :     &meters,                                                                                                // meter
    1285              :     &metervs,                                                                                               // meter vs.
    1286              : };
    1287              : 
    1288              : /**
    1289              :  * @brief Gets number of entries in the parts array.
    1290              :  *
    1291              :  * This function will return the same value at all times.
    1292              :  *
    1293              :  * @return number of entries in the parts array.
    1294              :  */
    1295            0 : static constexpr size_t numparts()
    1296              : {
    1297            0 :     return sizeof(parts)/sizeof(parts[0]);
    1298              : }
    1299              : 
    1300              : void initparticles(); //need to prototype either the vars or the the function
    1301              : 
    1302            0 : static VARFP(maxparticles, 10, 4000, 10000, initparticles()); //maximum number of particle objects to create
    1303            0 : static VARFP(fewparticles, 10, 100, 10000, initparticles()); //if PT_FEW enabled, # of particles to create
    1304              : 
    1305              : //used in iengine
    1306            0 : void initparticles()
    1307              : {
    1308            0 :     if(initing)
    1309              :     {
    1310            0 :         return;
    1311              :     }
    1312            0 :     if(!particleshader)
    1313              :     {
    1314            0 :         particleshader = lookupshaderbyname("particle");
    1315              :     }
    1316            0 :     if(!particlenotextureshader)
    1317              :     {
    1318            0 :         particlenotextureshader = lookupshaderbyname("particlenotexture");
    1319              :     }
    1320            0 :     if(!particlesoftshader)
    1321              :     {
    1322            0 :         particlesoftshader = lookupshaderbyname("particlesoft");
    1323              :     }
    1324            0 :     if(!particletextshader)
    1325              :     {
    1326            0 :         particletextshader = lookupshaderbyname("particletext");
    1327              :     }
    1328            0 :     for(size_t i = 0; i < numparts(); ++i)
    1329              :     {
    1330            0 :         parts[i]->init(parts[i]->parttype()&PT_FEW ? std::min(fewparticles, maxparticles) : maxparticles);
    1331              :     }
    1332            0 :     for(size_t i = 0; i < numparts(); ++i)
    1333              :     {
    1334            0 :         loadprogress = static_cast<float>(i+1)/numparts();
    1335            0 :         parts[i]->preload();
    1336              :     }
    1337            0 :     loadprogress = 0;
    1338              : }
    1339              : 
    1340              : //used in world
    1341            0 : void clearparticles()
    1342              : {
    1343            0 :     for(size_t i = 0; i < numparts(); ++i)
    1344              :     {
    1345            0 :         parts[i]->reset();
    1346              :     }
    1347            0 :     clearparticleemitters();
    1348            0 : }
    1349              : 
    1350              : //used in renderwindow
    1351            0 : void cleanupparticles()
    1352              : {
    1353            0 :     for(size_t i = 0; i < numparts(); ++i)
    1354              :     {
    1355            0 :         parts[i]->cleanup();
    1356              :     }
    1357            0 : }
    1358              : 
    1359              : //used in iengine
    1360            0 : void removetrackedparticles(physent *owner)
    1361              : {
    1362            0 :     for(size_t i = 0; i < numparts(); ++i)
    1363              :     {
    1364            0 :         parts[i]->resettracked(owner);
    1365              :     }
    1366            0 : }
    1367              : 
    1368              : static int debugparts = variable("debugparticles", 0, 0, 1, &debugparts, nullptr, 0);
    1369              : 
    1370            0 : void GBuffer::renderparticles(int layer) const
    1371              : {
    1372            0 :     canstep = layer != ParticleLayer_Under;
    1373              : 
    1374              :     //want to debug BEFORE the lastpass render (that would delete particles)
    1375            0 :     if(debugparts && (layer == ParticleLayer_All || layer == ParticleLayer_Under))
    1376              :     {
    1377            0 :         for(size_t i = 0; i < numparts(); ++i)
    1378              :         {
    1379            0 :             parts[i]->debuginfo();
    1380              :         }
    1381              :     }
    1382              : 
    1383            0 :     bool rendered = false;
    1384            0 :     uint lastflags = PT_LERP|PT_SHADER,
    1385            0 :          flagmask = PT_LERP|PT_MOD|PT_BRIGHT|PT_NOTEX|PT_SOFT|PT_SHADER,
    1386            0 :          excludemask = layer == ParticleLayer_All ? ~0 : (layer != ParticleLayer_NoLayer ? PT_NOLAYER : 0);
    1387              : 
    1388            0 :     for(size_t i = 0; i < numparts(); ++i)
    1389              :     {
    1390            0 :         partrenderer *p = parts[i];
    1391            0 :         if((p->parttype()&PT_NOLAYER) == excludemask || !p->haswork())
    1392              :         {
    1393            0 :             continue;
    1394              :         }
    1395            0 :         if(!rendered)
    1396              :         {
    1397            0 :             rendered = true;
    1398            0 :             glDepthMask(GL_FALSE);
    1399            0 :             glEnable(GL_BLEND);
    1400            0 :             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    1401              : 
    1402            0 :             glActiveTexture(GL_TEXTURE2);
    1403            0 :             if(msaalight)
    1404              :             {
    1405            0 :                 glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
    1406              :             }
    1407              :             else
    1408              :             {
    1409            0 :                 glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
    1410              :             }
    1411            0 :             glActiveTexture(GL_TEXTURE0);
    1412              :         }
    1413              : 
    1414            0 :         uint flags = p->parttype() & flagmask,
    1415            0 :              changedbits = flags ^ lastflags;
    1416            0 :         if(changedbits)
    1417              :         {
    1418            0 :             if(changedbits&PT_LERP)
    1419              :             {
    1420            0 :                 if(flags&PT_LERP)
    1421              :                 {
    1422            0 :                     resetfogcolor();
    1423              :                 }
    1424              :                 else
    1425              :                 {
    1426            0 :                     zerofogcolor();
    1427              :                 }
    1428              :             }
    1429            0 :             if(changedbits&(PT_LERP|PT_MOD))
    1430              :             {
    1431            0 :                 if(flags&PT_LERP)
    1432              :                 {
    1433            0 :                     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    1434              :                 }
    1435            0 :                 else if(flags&PT_MOD)
    1436              :                 {
    1437            0 :                     glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
    1438              :                 }
    1439              :                 else
    1440              :                 {
    1441            0 :                     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    1442              :                 }
    1443              :             }
    1444            0 :             if(!(flags&PT_SHADER))
    1445              :             {
    1446            0 :                 if(changedbits&(PT_LERP|PT_SOFT|PT_NOTEX|PT_SHADER))
    1447              :                 {
    1448            0 :                     if(flags&PT_SOFT)
    1449              :                     {
    1450            0 :                         particlesoftshader->set();
    1451            0 :                         LOCALPARAMF(softparams, -1.0f/softparticleblend, 0, 0);
    1452              :                     }
    1453            0 :                     else if(flags&PT_NOTEX)
    1454              :                     {
    1455            0 :                         particlenotextureshader->set();
    1456              :                     }
    1457              :                     else
    1458              :                     {
    1459            0 :                         particleshader->set();
    1460              :                     }
    1461              :                 }
    1462            0 :                 if(changedbits&(PT_MOD|PT_BRIGHT|PT_SOFT|PT_NOTEX|PT_SHADER))
    1463              :                 {
    1464            0 :                     float colorscale = flags&PT_MOD ? 1 : ldrscale;
    1465            0 :                     if(flags&PT_BRIGHT)
    1466              :                     {
    1467            0 :                         colorscale *= particlebright;
    1468              :                     }
    1469            0 :                     LOCALPARAMF(colorscale, colorscale, colorscale, colorscale, 1);
    1470              :                 }
    1471              :             }
    1472            0 :             lastflags = flags;
    1473              :         }
    1474            0 :         p->render();
    1475              :     }
    1476            0 :     if(rendered)
    1477              :     {
    1478            0 :         if(lastflags&(PT_LERP|PT_MOD))
    1479              :         {
    1480            0 :             glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    1481              :         }
    1482            0 :         if(!(lastflags&PT_LERP))
    1483              :         {
    1484            0 :             resetfogcolor();
    1485              :         }
    1486            0 :         glDisable(GL_BLEND);
    1487            0 :         glDepthMask(GL_TRUE);
    1488              :     }
    1489            0 : }
    1490              : 
    1491              : static int addedparticles = 0;
    1492              : 
    1493            0 : static particle *newparticle(const vec &o, const vec &d, int fade, int type, int color, float size, int gravity = 0)
    1494              : {
    1495            0 :     static particle dummy;
    1496            0 :     if(seedemitter)
    1497              :     {
    1498            0 :         parts[type]->seedemitter(*seedemitter, o, d, fade, size, gravity);
    1499            0 :         return &dummy;
    1500              :     }
    1501            0 :     if(fade + emitoffset < 0)
    1502              :     {
    1503            0 :         return &dummy;
    1504              :     }
    1505            0 :     addedparticles++;
    1506            0 :     return parts[type]->addpart(o, d, fade, color, size, gravity);
    1507              : }
    1508              : 
    1509              : static VARP(maxparticledistance, 256, 1024, 4096); //cubits before particles stop rendering (1024 = 128m) (note that text particles have their own var)
    1510              : 
    1511            0 : static void splash(int type, int color, int radius, int num, int fade, const vec &p, float size, int gravity)
    1512              : {
    1513            0 :     if(camera1->o.dist(p) > maxparticledistance && !seedemitter)
    1514              :     {
    1515            0 :         return;
    1516              :     }
    1517              :     //ugly ternary assignment
    1518            0 :     float collidez = parts[type]->parttype()&PT_COLLIDE ?
    1519            0 :                      p.z - rootworld.raycube(p, vec(0, 0, -1), collideradius, Ray_ClipMat) + (parts[type]->hasstain() ? collideerror : 0) :
    1520            0 :                      -1;
    1521            0 :     const int fmin = 1,
    1522            0 :               fmax = fade*3;
    1523            0 :     for(int i = 0; i < num; ++i)
    1524              :     {
    1525              :         int x, y, z;
    1526              :         do
    1527              :         {
    1528            0 :             x = randomint(radius*2)-radius;
    1529            0 :             y = randomint(radius*2)-radius;
    1530            0 :             z = randomint(radius*2)-radius;
    1531            0 :         } while(x*x + y*y + z*z > radius*radius);
    1532            0 :         const vec tmp = vec(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z));
    1533            0 :         const int f = (num < 10) ? (fmin + randomint(fmax)) : (fmax - (i*(fmax-fmin))/(num-1)); //help deallocater by using fade distribution rather than random
    1534            0 :         newparticle(p, tmp, f, type, color, size, gravity)->val = collidez;
    1535              :     }
    1536              : }
    1537              : 
    1538            0 : static void regularsplash(int type, int color, int radius, int num, int fade, const vec &p, float size, int gravity, int delay = 0)
    1539              : {
    1540            0 :     if(!canemitparticles() || (delay > 0 && randomint(delay) != 0))
    1541              :     {
    1542            0 :         return;
    1543              :     }
    1544            0 :     splash(type, color, radius, num, fade, p, size, gravity);
    1545              : }
    1546              : 
    1547            0 : void regular_particle_splash(int type, int num, int fade, const vec &p, int color, float size, int radius, int gravity, int delay)
    1548              : {
    1549            0 :     if(minimized)
    1550              :     {
    1551            0 :         return;
    1552              :     }
    1553            0 :     regularsplash(type, color, radius, num, fade, p, size, gravity, delay);
    1554              : }
    1555              : 
    1556            0 : void particle_splash(int type, int num, int fade, const vec &p, int color, float size, int radius, int gravity)
    1557              : {
    1558            0 :     if(minimized)
    1559              :     {
    1560            0 :         return;
    1561              :     }
    1562            0 :     splash(type, color, radius, num, fade, p, size, gravity);
    1563              : }
    1564              : 
    1565              : static VARP(maxtrail, 1, 500, 10000); //maximum number of steps allowed in a trail projectile
    1566              : 
    1567            0 : void particle_trail(int type, int fade, const vec &s, const vec &e, int color, float size, int gravity)
    1568              : {
    1569            0 :     if(minimized)
    1570              :     {
    1571            0 :         return;
    1572              :     }
    1573            0 :     vec v;
    1574            0 :     float d = e.dist(s, v);
    1575            0 :     int steps = std::clamp(static_cast<int>(d*2), 1, maxtrail);
    1576            0 :     v.div(steps);
    1577            0 :     vec p = s;
    1578            0 :     for(int i = 0; i < steps; ++i)
    1579              :     {
    1580            0 :         p.add(v);
    1581              :         //ugly long vec assignment
    1582            0 :         vec tmp = vec(static_cast<float>(randomint(11)-5),
    1583            0 :                       static_cast<float>(randomint(11)-5),
    1584            0 :                       static_cast<float>(randomint(11)-5));
    1585            0 :         newparticle(p, tmp, randomint(fade)+fade, type, color, size, gravity);
    1586              :     }
    1587              : }
    1588              : 
    1589              : static VARP(particletext, 0, 1, 1);
    1590              : 
    1591            0 : void particle_icon(const vec &s, int ix, int iy, int type, int fade, int color, float size, int gravity)
    1592              : {
    1593            0 :     if(minimized)
    1594              :     {
    1595            0 :         return;
    1596              :     }
    1597            0 :     particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity);
    1598            0 :     p->flags |= ix | (iy<<2);
    1599              : }
    1600              : 
    1601            0 : void particle_meter(const vec &s, float val, int type, int fade, int color, int color2, float size)
    1602              : {
    1603            0 :     if(minimized)
    1604              :     {
    1605            0 :         return;
    1606              :     }
    1607            0 :     particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size);
    1608            0 :     p->meter.color2[0] = color2>>16;
    1609            0 :     p->meter.color2[1] = (color2>>8)&0xFF;
    1610            0 :     p->meter.color2[2] = color2&0xFF;
    1611            0 :     p->meter.progress = std::clamp(static_cast<int>(val*100), 0, 100);
    1612              : }
    1613              : 
    1614            0 : void particle_flare(const vec &p, const vec &dest, int fade, int type, int color, float size, physent *owner)
    1615              : {
    1616            0 :     if(minimized)
    1617              :     {
    1618            0 :         return;
    1619              :     }
    1620            0 :     newparticle(p, dest, fade, type, color, size)->owner = owner;
    1621              : }
    1622              : 
    1623            0 : void particle_fireball(const vec &dest, float maxsize, int type, int fade, int color, float size)
    1624              : {
    1625            0 :     if(minimized)
    1626              :     {
    1627            0 :         return;
    1628              :     }
    1629            0 :     float growth = maxsize - size;
    1630            0 :     if(fade < 0)
    1631              :     {
    1632            0 :         fade = static_cast<int>(growth*20);
    1633              :     }
    1634            0 :     newparticle(dest, vec(0, 0, 1), fade, type, color, size)->val = growth;
    1635              : }
    1636              : 
    1637              : //dir = 0..6 where 0=up
    1638            0 : static vec offsetvec(vec o, int dir, int dist)
    1639              : {
    1640            0 :     vec v = vec(o);
    1641            0 :     v[(2+dir)%3] += (dir>2)?(-dist):dist;
    1642            0 :     return v;
    1643              : }
    1644              : 
    1645              : //converts a 16bit color to 24bit
    1646            0 : static int colorfromattr(int attr)
    1647              : {
    1648            0 :     return (((attr&0xF)<<4) | ((attr&0xF0)<<8) | ((attr&0xF00)<<12)) + 0x0F0F0F;
    1649              : }
    1650              : 
    1651              : /* Experiments in shapes...
    1652              :  * dir: (where dir%3 is similar to offsetvec with 0=up)
    1653              :  * 0..2 circle
    1654              :  * 3.. 5 cylinder shell
    1655              :  * 6..11 cone shell
    1656              :  * 12..14 plane volume
    1657              :  * 15..20 line volume, i.e. wall
    1658              :  * 21 sphere
    1659              :  * 24..26 flat plane
    1660              :  * +32 to inverse direction
    1661              :  */
    1662            0 : static void regularshape(int type, int radius, int color, int dir, int num, int fade, const vec &p, float size, int gravity, int vel = 200)
    1663              : {
    1664            0 :     if(!canemitparticles())
    1665              :     {
    1666            0 :         return;
    1667              :     }
    1668            0 :     const int basetype = parts[type]->parttype()&0xFF;
    1669            0 :     const bool flare = (basetype == PT_TAPE),
    1670            0 :                inv = (dir&0x20)!=0,
    1671            0 :                taper = (dir&0x40)!=0 && !seedemitter;
    1672            0 :     dir &= 0x1F;
    1673            0 :     for(int i = 0; i < num; ++i)
    1674              :     {
    1675            0 :         vec to, from;
    1676            0 :         if(dir < 12)
    1677              :         {
    1678            0 :             const vec2 &sc = sincos360[randomint(360)];
    1679            0 :             to[dir%3] = sc.y*radius;
    1680            0 :             to[(dir+1)%3] = sc.x*radius;
    1681            0 :             to[(dir+2)%3] = 0.0;
    1682            0 :             to.add(p);
    1683            0 :             if(dir < 3) //circle
    1684              :             {
    1685            0 :                 from = p;
    1686              :             }
    1687            0 :             else if(dir < 6) //cylinder
    1688              :             {
    1689            0 :                 from = to;
    1690            0 :                 to[(dir+2)%3] += radius;
    1691            0 :                 from[(dir+2)%3] -= radius;
    1692              :             }
    1693              :             else //cone
    1694              :             {
    1695            0 :                 from = p;
    1696            0 :                 to[(dir+2)%3] += (dir < 9)?radius:(-radius);
    1697              :             }
    1698              :         }
    1699            0 :         else if(dir < 15) //plane
    1700              :         {
    1701            0 :             to[dir%3] = static_cast<float>(randomint(radius<<4)-(radius<<3))/8.0;
    1702            0 :             to[(dir+1)%3] = static_cast<float>(randomint(radius<<4)-(radius<<3))/8.0;
    1703            0 :             to[(dir+2)%3] = radius;
    1704            0 :             to.add(p);
    1705            0 :             from = to;
    1706            0 :             from[(dir+2)%3] -= 2*radius;
    1707              :         }
    1708            0 :         else if(dir < 21) //line
    1709              :         {
    1710            0 :             if(dir < 18)
    1711              :             {
    1712            0 :                 to[dir%3] = static_cast<float>(randomint(radius<<4)-(radius<<3))/8.0;
    1713            0 :                 to[(dir+1)%3] = 0.0;
    1714              :             }
    1715              :             else
    1716              :             {
    1717            0 :                 to[dir%3] = 0.0;
    1718            0 :                 to[(dir+1)%3] = static_cast<float>(randomint(radius<<4)-(radius<<3))/8.0;
    1719              :             }
    1720            0 :             to[(dir+2)%3] = 0.0;
    1721            0 :             to.add(p);
    1722            0 :             from = to;
    1723            0 :             to[(dir+2)%3] += radius;
    1724              :         }
    1725            0 :         else if(dir < 24) //sphere
    1726              :         {
    1727            0 :             to = vec(2*M_PI*static_cast<float>(randomint(1000))/1000.0, M_PI*static_cast<float>(randomint(1000)-500)/1000.0).mul(radius);
    1728            0 :             to.add(p);
    1729            0 :             from = p;
    1730              :         }
    1731            0 :         else if(dir < 27) // flat plane
    1732              :         {
    1733            0 :             to[dir%3] = static_cast<float>(randomfloat(2*radius)-radius);
    1734            0 :             to[(dir+1)%3] = static_cast<float>(randomfloat(2*radius)-radius);
    1735            0 :             to[(dir+2)%3] = 0.0;
    1736            0 :             to.add(p);
    1737            0 :             from = to;
    1738              :         }
    1739              :         else
    1740              :         {
    1741            0 :             from = to = p;
    1742              :         }
    1743            0 :         if(inv)
    1744              :         {
    1745            0 :             std::swap(from, to);
    1746              :         }
    1747            0 :         if(taper)
    1748              :         {
    1749            0 :             float dist = std::clamp(from.dist2(camera1->o)/maxparticledistance, 0.0f, 1.0f);
    1750            0 :             if(dist > 0.2f)
    1751              :             {
    1752            0 :                 dist = 1 - (dist - 0.2f)/0.8f;
    1753            0 :                 if(randomint(0x10000) > dist*dist*0xFFFF)
    1754              :                 {
    1755            0 :                     continue;
    1756              :                 }
    1757              :             }
    1758              :         }
    1759            0 :         if(flare)
    1760              :         {
    1761            0 :             newparticle(from, to, randomint(fade*3)+1, type, color, size, gravity);
    1762              :         }
    1763              :         else
    1764              :         {
    1765            0 :             const vec d = vec(to).sub(from).rescale(vel); //velocity
    1766            0 :             particle *n = newparticle(from, d, randomint(fade*3)+1, type, color, size, gravity);
    1767            0 :             if(parts[type]->parttype()&PT_COLLIDE)
    1768              :             {
    1769              :                 //long nasty ternary assignment
    1770            0 :                 n->val = from.z - rootworld.raycube(from, vec(0, 0, -1), parts[type]->hasstain() ?
    1771              :                          collideradius :
    1772            0 :                          std::max(from.z, 0.0f), Ray_ClipMat) + (parts[type]->hasstain() ? collideerror : 0);
    1773              :             }
    1774              :         }
    1775              :     }
    1776              : }
    1777              : 
    1778            0 : static void regularflame(int type, const vec &p, float radius, float height, int color, int density = 3, float scale = 2.0f, float speed = 200.0f, float fade = 600.0f, int gravity = -15)
    1779              : {
    1780            0 :     if(!canemitparticles())
    1781              :     {
    1782            0 :         return;
    1783              :     }
    1784            0 :     const float size = scale * std::min(radius, height);
    1785            0 :     const vec v(0, 0, std::min(1.0f, height)*speed);
    1786            0 :     for(int i = 0; i < density; ++i)
    1787              :     {
    1788            0 :         vec s = p;
    1789            0 :         s.x += randomfloat(radius*2.0f)-radius;
    1790            0 :         s.y += randomfloat(radius*2.0f)-radius;
    1791            0 :         newparticle(s, v, randomint(std::max(static_cast<int>(fade*height), 1))+1, type, color, size, gravity);
    1792              :     }
    1793              : }
    1794              : 
    1795            0 : void regular_particle_flame(int type, const vec &p, float radius, float height, int color, int density, float scale, float speed, float fade, int gravity)
    1796              : {
    1797            0 :     if(minimized)
    1798              :     {
    1799            0 :         return;
    1800              :     }
    1801            0 :     regularflame(type, p, radius, height, color, density, scale, speed, fade, gravity);
    1802              : }
    1803            0 : void cubeworld::makeparticles(const entity &e)
    1804              : {
    1805            0 :     switch(e.attr1)
    1806              :     {
    1807            0 :         case 0: //fire and smoke -  <radius> <height> <rgb> - 0 values default to compat for old maps
    1808              :         {
    1809            0 :             float radius = e.attr2 ? static_cast<float>(e.attr2)/100.0f : 1.5f,
    1810            0 :                   height = e.attr3 ? static_cast<float>(e.attr3)/100.0f : radius/3;
    1811            0 :             regularflame(Part_Flame, e.o, radius, height, e.attr4 ? colorfromattr(e.attr4) : 0x903020, 3, 2.0f);
    1812            0 :             regularflame(Part_Smoke, vec(e.o.x, e.o.y, e.o.z + 4.0f*std::min(radius, height)), radius, height, 0x303020, 1, 4.0f, 100.0f, 2000.0f, -20);
    1813            0 :             break;
    1814              :         }
    1815            0 :         case 1: //steam vent - <dir>
    1816              :         {
    1817            0 :             regularsplash(Part_Steam, 0x897661, 50, 1, 200, offsetvec(e.o, e.attr2, randomint(10)), 2.4f, -20);
    1818            0 :             break;
    1819              :         }
    1820            0 :         case 2: //water fountain - <dir>
    1821              :         {
    1822              :             int color;
    1823            0 :             if(e.attr3 > 0)
    1824              :             {
    1825            0 :                 color = colorfromattr(e.attr3);
    1826              :             }
    1827              :             else
    1828              :             {
    1829            0 :                 int mat = Mat_Water + std::clamp(-e.attr3, 0, 3);
    1830            0 :                 color = getwaterfallcolor(mat).tohexcolor();
    1831            0 :                 if(!color)
    1832              :                 {
    1833            0 :                     color = getwatercolor(mat).tohexcolor();
    1834              :                 }
    1835              :             }
    1836            0 :             regularsplash(Part_Water, color, 150, 4, 200, offsetvec(e.o, e.attr2, randomint(10)), 0.6f, 2);
    1837            0 :             break;
    1838              :         }
    1839            0 :         case 3: //fire ball - <size> <rgb>
    1840              :         {
    1841            0 :             newparticle(e.o, vec(0, 0, 1), 1, Part_Explosion, colorfromattr(e.attr3), 4.0f)->val = 1+e.attr2;
    1842            0 :             break;
    1843              :         }
    1844            0 :         case 4:  //tape - <dir> <length> <rgb>
    1845              :         case 9:  //steam
    1846              :         case 10: //water
    1847              :         case 13: //snow
    1848              :         {
    1849              :             static constexpr int typemap[]   = { Part_Streak, -1, -1, -1, -1, Part_Steam, Part_Water, -1, -1, Part_Snow };
    1850              :             static constexpr float sizemap[] = { 0.28f, 0.0f, 0.0f, 1.0f, 0.0f, 2.4f, 0.60f, 0.0f, 0.0f, 0.5f };
    1851              :             static constexpr int gravmap[]   = { 0, 0, 0, 0, 0, -20, 2, 0, 0, 20 };
    1852            0 :             int type = typemap[e.attr1-4];
    1853            0 :             float size = sizemap[e.attr1-4];
    1854            0 :             int gravity = gravmap[e.attr1-4];
    1855            0 :             if(e.attr2 >= 256)
    1856              :             {
    1857            0 :                 regularshape(type, std::max(1+e.attr3, 1), colorfromattr(e.attr4), e.attr2-256, 5, e.attr5 > 0 ? std::min(static_cast<int>(e.attr5), 10000) : 200, e.o, size, gravity);
    1858              :             }
    1859              :             else
    1860              :             {
    1861            0 :                 newparticle(e.o, offsetvec(e.o, e.attr2, std::max(1+e.attr3, 0)), 1, type, colorfromattr(e.attr4), size, gravity);
    1862              :             }
    1863            0 :             break;
    1864              :         }
    1865            0 :         case 5: //meter, metervs - <percent> <rgb> <rgb2>
    1866              :         case 6:
    1867              :         {
    1868            0 :             particle *p = newparticle(e.o, vec(0, 0, 1), 1, e.attr1==5 ? Part_Meter : Part_MeterVS, colorfromattr(e.attr3), 2.0f);
    1869            0 :             int color2 = colorfromattr(e.attr4);
    1870            0 :             p->meter.color2[0] = color2>>16;
    1871            0 :             p->meter.color2[1] = (color2>>8)&0xFF;
    1872            0 :             p->meter.color2[2] = color2&0xFF;
    1873            0 :             p->meter.progress = std::clamp(static_cast<int>(e.attr2), 0, 100);
    1874            0 :             break;
    1875              :         }
    1876            0 :         case 11: // flame <radius> <height> <rgb> - radius=100, height=100 is the classic size
    1877              :         {
    1878            0 :             regularflame(Part_Flame, e.o, static_cast<float>(e.attr2)/100.0f, static_cast<float>(e.attr3)/100.0f, colorfromattr(e.attr4), 3, 2.0f);
    1879            0 :             break;
    1880              :         }
    1881            0 :         case 12: // smoke plume <radius> <height> <rgb>
    1882              :         {
    1883            0 :             regularflame(Part_Smoke, e.o, static_cast<float>(e.attr2)/100.0f, static_cast<float>(e.attr3)/100.0f, colorfromattr(e.attr4), 1, 4.0f, 100.0f, 2000.0f, -20);
    1884            0 :             break;
    1885              :         }
    1886            0 :         default:
    1887              :         {
    1888            0 :             break;
    1889              :         }
    1890              :     }
    1891            0 : }
    1892              : 
    1893            0 : void cubeworld::seedparticles()
    1894              : {
    1895            0 :     renderprogress(0, "seeding particles");
    1896            0 :     addparticleemitters();
    1897            0 :     canemit = true;
    1898            0 :     for(ParticleEmitter &pe : emitters)
    1899              :     {
    1900            0 :         const extentity &e = *pe.ent;
    1901            0 :         seedemitter = &pe;
    1902            0 :         for(int millis = 0; millis < seedmillis; millis += std::min(emitmillis, seedmillis/10))
    1903              :         {
    1904            0 :             makeparticles(e);
    1905              :         }
    1906            0 :         seedemitter = nullptr;
    1907            0 :         pe.lastemit = -seedmillis;
    1908            0 :         pe.finalize();
    1909              :     }
    1910            0 : }
    1911              : 
    1912            0 : void cubeworld::updateparticles()
    1913              : {
    1914              :     //note: static int carried across all calls of function
    1915              :     static int lastemitframe = 0;
    1916              : 
    1917            0 :     if(regenemitters) //regenemitters called whenever a new particle generator is placed
    1918              :     {
    1919            0 :         addparticleemitters();
    1920              :     }
    1921            0 :     if(minimized) //don't emit particles unless window visible
    1922              :     {
    1923            0 :         canemit = false;
    1924            0 :         return;
    1925              :     }
    1926            0 :     if(lastmillis - lastemitframe >= emitmillis) //don't update particles too often
    1927              :     {
    1928            0 :         canemit = true;
    1929            0 :         lastemitframe = lastmillis - (lastmillis%emitmillis);
    1930              :     }
    1931              :     else
    1932              :     {
    1933            0 :         canemit = false;
    1934              :     }
    1935            0 :     if(!editmode || showparticles)
    1936              :     {
    1937            0 :         int emitted = 0,
    1938            0 :             replayed = 0;
    1939            0 :         addedparticles = 0;
    1940            0 :         for(ParticleEmitter& pe : emitters) //foreach particle emitter
    1941              :         {
    1942            0 :             const extentity &e = *pe.ent; //get info for the entity associated w/ent
    1943            0 :             if(e.o.dist(camera1->o) > maxparticledistance) //distance check (don't update faraway particle ents)
    1944              :             {
    1945            0 :                 pe.lastemit = lastmillis;
    1946            0 :                 continue;
    1947              :             }
    1948            0 :             if(cullparticles && pe.maxfade >= 0)
    1949              :             {
    1950            0 :                 if(view.isfoggedsphere(pe.radius, pe.center))
    1951              :                 {
    1952            0 :                     pe.lastcull = lastmillis;
    1953            0 :                     continue;
    1954              :                 }
    1955              :             }
    1956            0 :             makeparticles(e);
    1957            0 :             emitted++;
    1958            0 :             if(replayparticles && pe.maxfade > 5 && pe.lastcull > pe.lastemit) //recreate particles from previous ticks
    1959              :             {
    1960            0 :                 for(emitoffset = std::max(pe.lastemit + emitmillis - lastmillis, -pe.maxfade); emitoffset < 0; emitoffset += emitmillis)
    1961              :                 {
    1962            0 :                     makeparticles(e);
    1963            0 :                     replayed++;
    1964              :                 }
    1965            0 :                 emitoffset = 0;
    1966              :             }
    1967            0 :             pe.lastemit = lastmillis;
    1968              :         }
    1969            0 :         if(debugparticlecull && (canemit || replayed) && addedparticles)
    1970              :         {
    1971            0 :             conoutf(Console_Debug, "%d emitters, %d particles", emitted, addedparticles);
    1972              :         }
    1973              :     }
    1974            0 :     if(editmode) // show sparkly thingies for map entities in edit mode
    1975              :     {
    1976            0 :         const std::vector<extentity *> &ents = entities::getents();
    1977            0 :         for(extentity * const &e : ents)
    1978              :         {
    1979              :                                     //type  num  fade  p   color       size                  radius grav delay
    1980            0 :             regular_particle_splash(Part_Edit, 2, 40, e->o, 0x3232FF, 0.32f*particlesize/100.0f, 150, 2, 0);
    1981              :         }
    1982              :     }
    1983              : }
        

Generated by: LCOV version 2.0-1