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: 2025-08-24 05:32:49 Functions: 8.4 % 131 11

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

Generated by: LCOV version 2.0-1