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-02-18 06:21:28 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
      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              :             uchar color2[3];   //color of bar
     192              :             uchar progress;    //bar fill %
     193              :         } meter;
     194              :     };
     195              : };
     196              : 
     197              : struct partvert
     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           18 :             : tex(nullptr), type(type), stain(stain), texname(texname), texclamp(texclamp)
     211              :         {
     212           18 :         }
     213            2 :         partrenderer(int type, int stain = -1)
     214            2 :             : tex(nullptr), type(type), stain(stain), texname(nullptr), 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 && !tex)
     235              :             {
     236            0 :                 tex = textureload(texname, 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)
     305              :             {
     306            0 :                 const char *title = std::strrchr(texname, '/');
     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              :         const char *texname;
     329              :         int texclamp;
     330              : 
     331              : };
     332              : 
     333              : struct listparticle : particle
     334              : {
     335              :     listparticle *next;
     336              : };
     337              : 
     338              : 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 override 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() override 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() override 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) override 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) override 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 override 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) override final
     509              :         {
     510            0 :         }
     511              : 
     512            0 :         void seedemitter(particleemitter &, const vec &, const vec &, int, float, int) override final
     513              :         {
     514            0 :         }
     515              : 
     516              :     private:
     517            0 :         void startrender() override final
     518              :         {
     519            0 :             glDisable(GL_BLEND);
     520            0 :             gle::defvertex();
     521            0 :         }
     522              : 
     523            0 :         void endrender() override final
     524              :         {
     525            0 :             glEnable(GL_BLEND);
     526            0 :         }
     527              : 
     528            0 :         void renderpart(const listparticle &p, const vec &o, int, int) override 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), metervs(PT_METERVS);
     599              : 
     600              : template<int T>
     601            0 : static void modifyblend(int &blend)
     602              : {
     603            0 :     blend = std::min(blend<<2, 255);
     604            0 : }
     605              : 
     606              : template<int T>
     607            0 : static void genpos(const vec &o, const vec &, float size, int, int, partvert *vs)
     608              : {
     609            0 :     vec udir = camup().sub(camright()).mul(size),
     610            0 :         vdir = camup().add(camright()).mul(size);
     611            0 :     vs[0].pos = vec(o.x + udir.x, o.y + udir.y, o.z + udir.z);
     612            0 :     vs[1].pos = vec(o.x + vdir.x, o.y + vdir.y, o.z + vdir.z);
     613            0 :     vs[2].pos = vec(o.x - udir.x, o.y - udir.y, o.z - udir.z);
     614            0 :     vs[3].pos = vec(o.x - vdir.x, o.y - vdir.y, o.z - vdir.z);
     615            0 : }
     616              : 
     617              : template<>
     618            0 : void genpos<PT_TAPE>(const vec &o, const vec &d, float size, int, int, partvert *vs)
     619              : {
     620            0 :     vec dir1 = vec(d).sub(o),
     621            0 :         dir2 = vec(d).sub(camera1->o), c;
     622            0 :     c.cross(dir2, dir1).normalize().mul(size);
     623            0 :     vs[0].pos = vec(d.x-c.x, d.y-c.y, d.z-c.z);
     624            0 :     vs[1].pos = vec(o.x-c.x, o.y-c.y, o.z-c.z);
     625            0 :     vs[2].pos = vec(o.x+c.x, o.y+c.y, o.z+c.z);
     626            0 :     vs[3].pos = vec(d.x+c.x, d.y+c.y, d.z+c.z);
     627            0 : }
     628              : 
     629              : template<>
     630            0 : void genpos<PT_TRAIL>(const vec &o, const vec &d, float size, int ts, int grav, partvert *vs)
     631              : {
     632            0 :     vec e = d;
     633            0 :     if(grav)
     634              :     {
     635            0 :         e.z -= static_cast<float>(ts)/grav;
     636              :     }
     637            0 :     e.div(-75.0f).add(o);
     638            0 :     genpos<PT_TAPE>(o, e, size, ts, grav, vs);
     639            0 : }
     640              : 
     641              : template<int T>
     642            0 : void genrotpos(const vec &o, const vec &d, float size, int grav, int ts, partvert *vs, int)
     643              : {
     644            0 :     genpos<T>(o, d, size, grav, ts, vs);
     645            0 : }
     646              : 
     647              : //==================================================================== ROTCOEFFS
     648              : #define ROTCOEFFS(n) { \
     649              :     vec2(-1,  1).rotate_around_z(n*2*M_PI/32.0f), \
     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              : }
     654              : static const vec2 rotcoeffs[32][4] =
     655              : {
     656              :     ROTCOEFFS(0),  ROTCOEFFS(1),  ROTCOEFFS(2),  ROTCOEFFS(3),  ROTCOEFFS(4),  ROTCOEFFS(5),  ROTCOEFFS(6),  ROTCOEFFS(7),
     657              :     ROTCOEFFS(8),  ROTCOEFFS(9),  ROTCOEFFS(10), ROTCOEFFS(11), ROTCOEFFS(12), ROTCOEFFS(13), ROTCOEFFS(14), ROTCOEFFS(15),
     658              :     ROTCOEFFS(16), ROTCOEFFS(17), ROTCOEFFS(18), ROTCOEFFS(19), ROTCOEFFS(20), ROTCOEFFS(21), ROTCOEFFS(22), ROTCOEFFS(7),
     659              :     ROTCOEFFS(24), ROTCOEFFS(25), ROTCOEFFS(26), ROTCOEFFS(27), ROTCOEFFS(28), ROTCOEFFS(29), ROTCOEFFS(30), ROTCOEFFS(31),
     660              : };
     661              : #undef ROTCOEFFS
     662              : //==============================================================================
     663              : 
     664              : template<>
     665            0 : void genrotpos<PT_PART>(const vec &o, const vec &, float size, int, int, partvert *vs, int rot)
     666              : {
     667            0 :     const vec2 *coeffs = rotcoeffs[rot];
     668            0 :     vs[0].pos = vec(o).madd(camright(), coeffs[0].x*size).madd(camup(), coeffs[0].y*size);
     669            0 :     vs[1].pos = vec(o).madd(camright(), coeffs[1].x*size).madd(camup(), coeffs[1].y*size);
     670            0 :     vs[2].pos = vec(o).madd(camright(), coeffs[2].x*size).madd(camup(), coeffs[2].y*size);
     671            0 :     vs[3].pos = vec(o).madd(camright(), coeffs[3].x*size).madd(camup(), coeffs[3].y*size);
     672            0 : }
     673              : 
     674              : template<int T>
     675            0 : void seedpos(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int grav)
     676              : {
     677            0 :     constexpr float scale = 5000.f;
     678            0 :     if(grav)
     679              :     {
     680            0 :         float t = fade;
     681            0 :         vec end = vec(o).madd(d, t/scale);
     682            0 :         end.z -= t*t/(2.0f * scale * grav);
     683            0 :         pe.extendbb(end, size);
     684            0 :         float tpeak = d.z*grav;
     685            0 :         if(tpeak > 0 && tpeak < fade)
     686              :         {
     687            0 :             pe.extendbb(o.z + 1.5f*d.z*tpeak/scale, size);
     688              :         }
     689              :     }
     690            0 : }
     691              : 
     692              : template<>
     693            0 : void seedpos<PT_TAPE>(particleemitter &pe, const vec &, const vec &d, int, float size, int)
     694              : {
     695            0 :     pe.extendbb(d, size);
     696            0 : }
     697              : 
     698              : template<>
     699            0 : void seedpos<PT_TRAIL>(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int grav)
     700              : {
     701            0 :     vec e = d;
     702            0 :     if(grav)
     703              :     {
     704            0 :         e.z -= static_cast<float>(fade)/grav;
     705              :     }
     706            0 :     e.div(-75.0f).add(o);
     707            0 :     pe.extendbb(e, size);
     708            0 : }
     709              : 
     710              : //only used by template<> varenderer, but cannot be declared in a template in c++17
     711            0 : static void setcolor(float r, float g, float b, float a, partvert * vs)
     712              : {
     713            0 :     vec4<uchar> col(r, g, b, a);
     714            0 :     for(int i = 0; i < 4; ++i)
     715              :     {
     716            0 :         vs[i].color = col;
     717              :     }
     718            0 : };
     719              : 
     720              : template<int T>
     721              : struct varenderer final : partrenderer
     722              : {
     723              :     partvert *verts;
     724              :     particle *parts;
     725              :     int maxparts, numparts, lastupdate, rndmask;
     726              :     GLuint vbo;
     727              : 
     728           16 :     varenderer(const char *texname, int type, int stain = -1)
     729              :         : partrenderer(texname, 3, type, stain),
     730           16 :           verts(nullptr), parts(nullptr), maxparts(0), numparts(0), lastupdate(-1), rndmask(0), vbo(0)
     731              :     {
     732           16 :         if(type & PT_HFLIP)
     733              :         {
     734           10 :             rndmask |= 0x01;
     735              :         }
     736           16 :         if(type & PT_VFLIP)
     737              :         {
     738            9 :             rndmask |= 0x02;
     739              :         }
     740           16 :         if(type & PT_ROT)
     741              :         {
     742            9 :             rndmask |= 0x1F<<2;
     743              :         }
     744           16 :         if(type & PT_RND4)
     745              :         {
     746            3 :             rndmask |= 0x03<<5;
     747              :         }
     748           16 :     }
     749              : 
     750            0 :     void cleanup() override final
     751              :     {
     752            0 :         if(vbo)
     753              :         {
     754            0 :             glDeleteBuffers(1, &vbo);
     755            0 :             vbo = 0;
     756              :         }
     757            0 :     }
     758              : 
     759            0 :     void init(int n) override final
     760              :     {
     761            0 :         delete[] parts;
     762            0 :         delete[] verts;
     763            0 :         parts = new particle[n];
     764            0 :         verts = new partvert[n*4];
     765            0 :         maxparts = n;
     766            0 :         numparts = 0;
     767            0 :         lastupdate = -1;
     768            0 :     }
     769              : 
     770            0 :     void reset() override final
     771              :     {
     772            0 :         numparts = 0;
     773            0 :         lastupdate = -1;
     774            0 :     }
     775              : 
     776            0 :     void resettracked(const physent *owner) override final
     777              :     {
     778            0 :         if(!(parttype()&PT_TRACK))
     779              :         {
     780            0 :             return;
     781              :         }
     782            0 :         for(int i = 0; i < numparts; ++i)
     783              :         {
     784            0 :             particle *p = parts+i;
     785            0 :             if(!owner || (p->owner == owner))
     786              :             {
     787            0 :                 p->fade = -1;
     788              :             }
     789              :         }
     790            0 :         lastupdate = -1;
     791              :     }
     792              : 
     793            0 :     int count() const override final
     794              :     {
     795            0 :         return numparts;
     796              :     }
     797              : 
     798            0 :     bool haswork() const override final
     799              :     {
     800            0 :         return (numparts > 0);
     801              :     }
     802              : 
     803            0 :     particle *addpart(const vec &o, const vec &d, int fade, int color, float size, int gravity) override final
     804              :     {
     805            0 :         particle *p = parts + (numparts < maxparts ? numparts++ : randomint(maxparts)); //next free slot, or kill a random kitten
     806            0 :         p->o = o;
     807            0 :         p->d = d;
     808            0 :         p->gravity = gravity;
     809            0 :         p->fade = fade;
     810            0 :         p->millis = lastmillis + emitoffset;
     811            0 :         p->color = bvec::hexcolor(color);
     812            0 :         p->size = size;
     813            0 :         p->owner = nullptr;
     814            0 :         p->flags = 0x80 | (rndmask ? randomint(0x80) & rndmask : 0);
     815            0 :         lastupdate = -1;
     816            0 :         return p;
     817              :     }
     818              : 
     819            0 :     void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, int gravity) override final
     820              :     {
     821            0 :         pe.maxfade = std::max(pe.maxfade, fade);
     822            0 :         size *= SQRT2;
     823            0 :         pe.extendbb(o, size);
     824              : 
     825            0 :         seedpos<T>(pe, o, d, fade, size, gravity);
     826            0 :         if(!gravity)
     827              :         {
     828            0 :             return;
     829              :         }
     830            0 :         vec end(o);
     831            0 :         float t = fade;
     832            0 :         end.add(vec(d).mul(t/5000.0f));
     833            0 :         end.z -= t*t/(2.0f * 5000.0f * gravity);
     834            0 :         pe.extendbb(end, size);
     835            0 :         float tpeak = d.z*gravity;
     836            0 :         if(tpeak > 0 && tpeak < fade)
     837              :         {
     838            0 :             pe.extendbb(o.z + 1.5f*d.z*tpeak/5000.0f, size);
     839              :         }
     840              :     }
     841              : 
     842            0 :     void genverts(particle *p, partvert *vs, bool regen)
     843              :     {
     844            0 :         vec o, d;
     845              :         int blend, ts;
     846            0 :         calc(p, blend, ts, o, d);
     847            0 :         if(blend <= 1 || p->fade <= 5)
     848              :         {
     849            0 :             p->fade = -1; //mark to remove on next pass (i.e. after render)
     850              :         }
     851            0 :         modifyblend<T>(blend);
     852            0 :         if(regen)
     853              :         {
     854            0 :             p->flags &= ~0x80;
     855              : 
     856            0 :             auto swaptexcoords = [&] (float u1, float u2, float v1, float v2)
     857              :             {
     858            0 :                 if(p->flags&0x01)
     859              :                 {
     860            0 :                     std::swap(u1, u2);
     861              :                 }
     862            0 :                 if(p->flags&0x02)
     863              :                 {
     864            0 :                     std::swap(v1, v2);
     865              :                 }
     866              :             };
     867              : 
     868              :             //sets the partvert vs array's tc fields to four permutations of input parameters
     869            0 :             auto settexcoords = [vs, swaptexcoords] (float u1c, float u2c, float v1c, float v2c, bool swap)
     870              :             {
     871            0 :                 float u1 = u1c,
     872            0 :                       u2 = u2c,
     873            0 :                       v1 = v1c,
     874            0 :                       v2 = v2c;
     875            0 :                 if(swap)
     876              :                 {
     877            0 :                     swaptexcoords(u1, u2, v1, v2);
     878              :                 }
     879            0 :                 vs[0].tc = vec2(u1, v1);
     880            0 :                 vs[1].tc = vec2(u2, v1);
     881            0 :                 vs[2].tc = vec2(u2, v2);
     882            0 :                 vs[3].tc = vec2(u1, v2);
     883              :             };
     884              : 
     885            0 :             if(parttype()&PT_RND4)
     886              :             {
     887            0 :                 float tx = 0.5f*((p->flags>>5)&1),
     888            0 :                       ty = 0.5f*((p->flags>>6)&1);
     889            0 :                 settexcoords(tx, tx + 0.5f, ty, ty + 0.5f, true);
     890              :             }
     891            0 :             else if(parttype()&PT_ICON)
     892              :             {
     893            0 :                 float tx = 0.25f*(p->flags&3),
     894            0 :                       ty = 0.25f*((p->flags>>2)&3);
     895            0 :                 settexcoords(tx, tx + 0.25f, ty, ty + 0.25f, false);
     896              :             }
     897              :             else
     898              :             {
     899            0 :                 settexcoords(0, 1, 0, 1, false);
     900              :             }
     901              : 
     902            0 :             if(parttype()&PT_MOD)
     903              :             {
     904            0 :                 setcolor((p->color.r()*blend)>>8, (p->color.g()*blend)>>8, (p->color.b()*blend)>>8, 255, vs);
     905              :             }
     906              :             else
     907              :             {
     908            0 :                 setcolor(p->color.r(), p->color.g(), p->color.b(), blend, vs);
     909              :             }
     910              : 
     911              :         }
     912            0 :         else if(parttype()&PT_MOD)
     913              :         {
     914              :             //note: same call as `if(type&PT_MOD)` above
     915            0 :             setcolor((p->color.r()*blend)>>8, (p->color.g()*blend)>>8, (p->color.b()*blend)>>8, 255, vs);
     916              :         }
     917              :         else
     918              :         {
     919            0 :             for(int i = 0; i < 4; ++i)
     920              :             {
     921            0 :                 vs[i].color.a() = blend;
     922              :             }
     923              :         }
     924            0 :         if(parttype()&PT_ROT)
     925              :         {
     926            0 :             genrotpos<T>(o, d, p->size, ts, p->gravity, vs, (p->flags>>2)&0x1F);
     927              :         }
     928              :         else
     929              :         {
     930            0 :             genpos<T>(o, d, p->size, ts, p->gravity, vs);
     931              :         }
     932            0 :     }
     933              : 
     934            0 :     void genverts()
     935              :     {
     936            0 :         for(int i = 0; i < numparts; ++i)
     937              :         {
     938            0 :             particle *p = &parts[i];
     939            0 :             partvert *vs = &verts[i*4];
     940            0 :             if(p->fade < 0)
     941              :             {
     942              :                 do
     943              :                 {
     944            0 :                     --numparts;
     945            0 :                     if(numparts <= i)
     946              :                     {
     947            0 :                         return;
     948              :                     }
     949            0 :                 } while(parts[numparts].fade < 0);
     950            0 :                 *p = parts[numparts];
     951            0 :                 genverts(p, vs, true);
     952              :             }
     953              :             else
     954              :             {
     955            0 :                 genverts(p, vs, (p->flags&0x80)!=0);
     956              :             }
     957              :         }
     958              :     }
     959              : 
     960            0 :     void genvbo()
     961              :     {
     962            0 :         if(lastmillis == lastupdate && vbo)
     963              :         {
     964            0 :             return;
     965              :         }
     966            0 :         lastupdate = lastmillis;
     967            0 :         genverts();
     968            0 :         if(!vbo)
     969              :         {
     970            0 :             glGenBuffers(1, &vbo);
     971              :         }
     972            0 :         gle::bindvbo(vbo);
     973            0 :         glBufferData(GL_ARRAY_BUFFER, maxparts*4*sizeof(partvert), nullptr, GL_STREAM_DRAW);
     974            0 :         glBufferSubData(GL_ARRAY_BUFFER, 0, numparts*4*sizeof(partvert), verts);
     975            0 :         gle::clearvbo();
     976              :     }
     977              : 
     978            0 :     void render() override final
     979              :     {
     980            0 :         genvbo();
     981              : 
     982            0 :         glBindTexture(GL_TEXTURE_2D, tex->id);
     983              : 
     984            0 :         gle::bindvbo(vbo);
     985            0 :         const partvert *ptr = 0;
     986            0 :         gle::vertexpointer(sizeof(partvert), ptr->pos.data());
     987            0 :         gle::texcoord0pointer(sizeof(partvert), ptr->tc.data());
     988            0 :         gle::colorpointer(sizeof(partvert), ptr->color.data());
     989            0 :         gle::enablevertex();
     990            0 :         gle::enabletexcoord0();
     991            0 :         gle::enablecolor();
     992            0 :         gle::enablequads();
     993            0 :         gle::drawquads(0, numparts);
     994            0 :         gle::disablequads();
     995            0 :         gle::disablevertex();
     996            0 :         gle::disabletexcoord0();
     997            0 :         gle::disablecolor();
     998            0 :         gle::clearvbo();
     999            0 :     }
    1000              : };
    1001              : 
    1002              : // explosions
    1003              : 
    1004              : VARP(softexplosion, 0, 1, 1); //toggles EXPLOSIONSOFT shader
    1005              : VARP(softexplosionblend, 1, 16, 64);
    1006              : 
    1007              : class fireballrenderer final : public listrenderer
    1008              : {
    1009              :     public:
    1010            2 :         fireballrenderer(const char *texname)
    1011            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)
    1012            2 :         {}
    1013              : 
    1014              :         static constexpr float wobble = 1.25f; //factor to extend particle hitbox by due to placement movement
    1015              : 
    1016            0 :         void startrender() override final
    1017              :         {
    1018            0 :             if(softexplosion)
    1019              :             {
    1020            0 :                 SETSHADER(explosionsoft,);
    1021              :             }
    1022              :             else
    1023              :             {
    1024            0 :                 SETSHADER(explosion,);
    1025              :             }
    1026            0 :             sr.enable();
    1027            0 :         }
    1028              : 
    1029            0 :         void endrender() override final
    1030              :         {
    1031            0 :             sr.disable();
    1032            0 :         }
    1033              : 
    1034            0 :         void cleanup() override final
    1035              :         {
    1036            0 :             sr.cleanup();
    1037            0 :         }
    1038              : 
    1039            0 :         void seedemitter(particleemitter &pe, const vec &o, const vec &, int fade, float size, int) override final
    1040              :         {
    1041            0 :             pe.maxfade = std::max(pe.maxfade, fade);
    1042            0 :             pe.extendbb(o, (size+1+pe.ent->attr2)*wobble);
    1043            0 :         }
    1044              : 
    1045            0 :         void renderpart(const listparticle &p, const vec &o, int blend, int ts) override final
    1046              :         {
    1047            0 :             float pmax = p.val,
    1048            0 :                   size = p.fade ? static_cast<float>(ts)/p.fade : 1,
    1049            0 :                   psize = p.size + pmax * size;
    1050              : 
    1051            0 :             if(view.isfoggedsphere(psize*wobble, p.o))
    1052              :             {
    1053            0 :                 return;
    1054              :             }
    1055            0 :             vec dir = static_cast<vec>(o).sub(camera1->o), s, t;
    1056            0 :             float dist = dir.magnitude();
    1057            0 :             bool inside = dist <= psize*wobble;
    1058            0 :             if(inside)
    1059              :             {
    1060            0 :                 s = camright();
    1061            0 :                 t = camup();
    1062              :             }
    1063              :             else
    1064              :             {
    1065            0 :                 float mag2 = dir.magnitude2();
    1066            0 :                 dir.x /= mag2;
    1067            0 :                 dir.y /= mag2;
    1068            0 :                 dir.z /= dist;
    1069            0 :                 s = vec(dir.y, -dir.x, 0);
    1070            0 :                 t = vec(dir.x*dir.z, dir.y*dir.z, -mag2/dist);
    1071              :             }
    1072              : 
    1073            0 :             matrix3 rot(lastmillis/1000.0f*143/RAD, vec(1/SQRT3, 1/SQRT3, 1/SQRT3));
    1074            0 :             LOCALPARAM(texgenS, rot.transposedtransform(s));
    1075            0 :             LOCALPARAM(texgenT, rot.transposedtransform(t));
    1076              : 
    1077            0 :             matrix4 m(rot, o);
    1078            0 :             m.scale(psize, psize, inside ? -psize : psize);
    1079            0 :             m.mul(camprojmatrix, m);
    1080            0 :             LOCALPARAM(explosionmatrix, m);
    1081              : 
    1082            0 :             LOCALPARAM(center, o);
    1083            0 :             LOCALPARAMF(blendparams, inside ? 0.5f : 4, inside ? 0.25f : 0);
    1084            0 :             if(2*(p.size + pmax)*wobble >= softexplosionblend)
    1085              :             {
    1086            0 :                 LOCALPARAMF(softparams, -1.0f/softexplosionblend, 0, inside ? blend/(2*255.0f) : 0);
    1087              :             }
    1088              :             else
    1089              :             {
    1090            0 :                 LOCALPARAMF(softparams, 0, -1, inside ? blend/(2*255.0f) : 0);
    1091              :             }
    1092              : 
    1093            0 :             vec color = p.color.tocolor().mul(ldrscale);
    1094            0 :             float alpha = blend/255.0f;
    1095              : 
    1096            0 :             for(int i = 0; i < (inside ? 2 : 1); ++i)
    1097              :             {
    1098            0 :                 gle::color(color, i ? alpha/2 : alpha);
    1099            0 :                 if(i)
    1100              :                 {
    1101            0 :                     glDepthFunc(GL_GEQUAL);
    1102              :                 }
    1103            0 :                 sr.draw();
    1104            0 :                 if(i)
    1105              :                 {
    1106            0 :                     glDepthFunc(GL_LESS);
    1107              :                 }
    1108              :             }
    1109              :         }
    1110              : 
    1111            0 :         void init(int) override final
    1112              :         {
    1113            0 :         }
    1114              : 
    1115              :     private:
    1116              :         class sphererenderer
    1117              :         {
    1118              :             public:
    1119            0 :                 void cleanup()
    1120              :                 {
    1121            0 :                     if(vbuf)
    1122              :                     {
    1123            0 :                         glDeleteBuffers(1, &vbuf);
    1124            0 :                         vbuf = 0;
    1125              :                     }
    1126            0 :                     if(ebuf)
    1127              :                     {
    1128            0 :                         glDeleteBuffers(1, &ebuf);
    1129            0 :                         ebuf = 0;
    1130              :                     }
    1131            0 :                 }
    1132              : 
    1133            0 :                 void enable()
    1134              :                 {
    1135            0 :                     if(!vbuf)
    1136              :                     {
    1137            0 :                         init(12, 6); //12 slices, 6 stacks
    1138              :                     }
    1139            0 :                     gle::bindvbo(vbuf);
    1140            0 :                     gle::bindebo(ebuf);
    1141              : 
    1142            0 :                     gle::vertexpointer(sizeof(vert), &verts->pos);
    1143            0 :                     gle::texcoord0pointer(sizeof(vert), &verts->s, GL_UNSIGNED_SHORT, 2, GL_TRUE);
    1144            0 :                     gle::enablevertex();
    1145            0 :                     gle::enabletexcoord0();
    1146            0 :                 }
    1147              : 
    1148            0 :                 void draw()
    1149              :                 {
    1150            0 :                     glDrawRangeElements(GL_TRIANGLES, 0, numverts-1, numindices, GL_UNSIGNED_SHORT, indices);
    1151            0 :                     xtraverts += numindices;
    1152            0 :                     glde++;
    1153            0 :                 }
    1154              : 
    1155            0 :                 void disable()
    1156              :                 {
    1157            0 :                     gle::disablevertex();
    1158            0 :                     gle::disabletexcoord0();
    1159              : 
    1160            0 :                     gle::clearvbo();
    1161            0 :                     gle::clearebo();
    1162            0 :                 }
    1163              :             private:
    1164              :                 struct vert
    1165              :                 {
    1166              :                     vec pos;
    1167              :                     ushort s, t;
    1168              :                 } *verts = nullptr;
    1169              :                 GLushort *indices = nullptr;
    1170              :                 int numverts   = 0,
    1171              :                     numindices = 0;
    1172              :                 GLuint vbuf = 0,
    1173              :                        ebuf = 0;
    1174              : 
    1175            0 :                 void init(int slices, int stacks)
    1176              :                 {
    1177            0 :                     numverts = (stacks+1)*(slices+1);
    1178            0 :                     verts = new vert[numverts];
    1179            0 :                     float ds = 1.0f/slices,
    1180            0 :                           dt = 1.0f/stacks,
    1181            0 :                           t  = 1.0f;
    1182            0 :                     for(int i = 0; i < stacks+1; ++i)
    1183              :                     {
    1184            0 :                         float rho = M_PI*(1-t),
    1185            0 :                               s = 0.0f,
    1186            0 :                               sinrho = i && i < stacks ? std::sin(rho) : 0,
    1187            0 :                               cosrho = !i ? 1 : (i < stacks ? std::cos(rho) : -1);
    1188            0 :                         for(int j = 0; j < slices+1; ++j)
    1189              :                         {
    1190            0 :                             float theta = j==slices ? 0 : 2*M_PI*s;
    1191            0 :                             vert &v = verts[i*(slices+1) + j];
    1192            0 :                             v.pos = vec(std::sin(theta)*sinrho, std::cos(theta)*sinrho, -cosrho);
    1193            0 :                             v.s = static_cast<ushort>(s*0xFFFF);
    1194            0 :                             v.t = static_cast<ushort>(t*0xFFFF);
    1195            0 :                             s += ds;
    1196              :                         }
    1197            0 :                         t -= dt;
    1198              :                     }
    1199              : 
    1200            0 :                     numindices = (stacks-1)*slices*3*2;
    1201            0 :                     indices = new ushort[numindices];
    1202            0 :                     GLushort *curindex = indices;
    1203            0 :                     for(int i = 0; i < stacks; ++i)
    1204              :                     {
    1205            0 :                         for(int k = 0; k < slices; ++k)
    1206              :                         {
    1207            0 :                             int j = i%2 ? slices-k-1 : k;
    1208            0 :                             if(i)
    1209              :                             {
    1210            0 :                                 *curindex++ = i*(slices+1)+j;
    1211            0 :                                 *curindex++ = (i+1)*(slices+1)+j;
    1212            0 :                                 *curindex++ = i*(slices+1)+j+1;
    1213              :                             }
    1214            0 :                             if(i+1 < stacks)
    1215              :                             {
    1216            0 :                                 *curindex++ = i*(slices+1)+j+1;
    1217            0 :                                 *curindex++ = (i+1)*(slices+1)+j;
    1218            0 :                                 *curindex++ = (i+1)*(slices+1)+j+1;
    1219              :                             }
    1220              :                         }
    1221              :                     }
    1222            0 :                     if(!vbuf)
    1223              :                     {
    1224            0 :                         glGenBuffers(1, &vbuf);
    1225              :                     }
    1226            0 :                     gle::bindvbo(vbuf);
    1227            0 :                     glBufferData(GL_ARRAY_BUFFER, numverts*sizeof(vert), verts, GL_STATIC_DRAW);
    1228            0 :                     delete[] verts;
    1229            0 :                     verts = nullptr;
    1230            0 :                     if(!ebuf)
    1231              :                     {
    1232            0 :                         glGenBuffers(1, &ebuf);
    1233              :                     }
    1234            0 :                     gle::bindebo(ebuf);
    1235            0 :                     glBufferData(GL_ELEMENT_ARRAY_BUFFER, numindices*sizeof(GLushort), indices, GL_STATIC_DRAW);
    1236            0 :                     delete[] indices;
    1237            0 :                     indices = nullptr;
    1238            0 :                 }
    1239              :         };
    1240              :         sphererenderer sr;
    1241              : 
    1242              : };
    1243              : static fireballrenderer fireballs("media/particle/explosion.png"), pulsebursts("media/particle/pulse_burst.png");
    1244              : 
    1245              : //end explosion code
    1246              : 
    1247              : static partrenderer *parts[] =
    1248              : {
    1249              :     //unary pluses to promote to an integer, c++20 deprecates arithmetic conversion on enums (see C++ doc P2864R2)
    1250              :     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)
    1251              :     new varenderer<+PT_TRAIL>("media/particle/base.png", +PT_TRAIL|+PT_LERP),                                  // water, entity
    1252              :     new varenderer<+PT_PART>("<grey>media/particle/smoke.png", +PT_PART|+PT_FLIP|+PT_LERP),                     // smoke
    1253              :     new varenderer<+PT_PART>("<grey>media/particle/steam.png", +PT_PART|+PT_FLIP),                             // steam
    1254              :     new varenderer<+PT_PART>("<grey>media/particle/flames.png", +PT_PART|+PT_HFLIP|+PT_RND4|+PT_BRIGHT),         // flame
    1255              :     new varenderer<+PT_TAPE>("media/particle/flare.png", +PT_TAPE|+PT_BRIGHT),                                 // streak
    1256              :     new varenderer<+PT_TAPE>("media/particle/rail_trail.png", +PT_TAPE|+PT_FEW|+PT_BRIGHT),                     // rail trail
    1257              :     new varenderer<+PT_TAPE>("media/particle/pulse_side.png", +PT_TAPE|+PT_FEW|+PT_BRIGHT),                     // pulse side
    1258              :     new varenderer<+PT_PART>("media/particle/pulse_front.png", +PT_PART|+PT_FLIP|+PT_FEW|+PT_BRIGHT),            // pulse front
    1259              :     &fireballs,                                                                                             // explosion fireball
    1260              :     &pulsebursts,                                                                                           // pulse burst
    1261              :     new varenderer<+PT_PART>("media/particle/spark.png", +PT_PART|+PT_FLIP|+PT_BRIGHT),                         // sparks
    1262              :     new varenderer<+PT_PART>("media/particle/base.png",  +PT_PART|+PT_FLIP|+PT_BRIGHT),                         // edit mode entities
    1263              :     new varenderer<+PT_PART>("media/particle/snow.png", +PT_PART|+PT_FLIP|+PT_RND4|+PT_COLLIDE),                 // colliding snow
    1264              :     new varenderer<+PT_PART>("media/particle/rail_muzzle.png", +PT_PART|+PT_FEW|+PT_FLIP|+PT_BRIGHT|+PT_TRACK),   // rail muzzle flash
    1265              :     new varenderer<+PT_PART>("media/particle/pulse_muzzle.png", +PT_PART|+PT_FEW|+PT_FLIP|+PT_BRIGHT|+PT_TRACK),  // pulse muzzle flash
    1266              :     new varenderer<+PT_PART>("media/interface/hud/items.png", +PT_PART|+PT_FEW|+PT_ICON),                       // hud icon
    1267              :     new varenderer<+PT_PART>("<colorify:1/1/1>media/interface/hud/items.png", +PT_PART|+PT_FEW|+PT_ICON),       // grey hud icon
    1268              :     &meters,                                                                                                // meter
    1269              :     &metervs,                                                                                               // meter vs.
    1270              : };
    1271              : 
    1272              : //helper function to return int with # of entries in *parts[]
    1273            0 : static constexpr size_t numparts()
    1274              : {
    1275            0 :     return sizeof(parts)/sizeof(parts[0]);
    1276              : }
    1277              : 
    1278              : void initparticles(); //need to prototype either the vars or the the function
    1279              : 
    1280            0 : VARFP(maxparticles, 10, 4000, 10000, initparticles()); //maximum number of particle objects to create
    1281            0 : VARFP(fewparticles, 10, 100, 10000, initparticles()); //if PT_FEW enabled, # of particles to create
    1282              : 
    1283            0 : void initparticles()
    1284              : {
    1285            0 :     if(initing)
    1286              :     {
    1287            0 :         return;
    1288              :     }
    1289            0 :     if(!particleshader)
    1290              :     {
    1291            0 :         particleshader = lookupshaderbyname("particle");
    1292              :     }
    1293            0 :     if(!particlenotextureshader)
    1294              :     {
    1295            0 :         particlenotextureshader = lookupshaderbyname("particlenotexture");
    1296              :     }
    1297            0 :     if(!particlesoftshader)
    1298              :     {
    1299            0 :         particlesoftshader = lookupshaderbyname("particlesoft");
    1300              :     }
    1301            0 :     if(!particletextshader)
    1302              :     {
    1303            0 :         particletextshader = lookupshaderbyname("particletext");
    1304              :     }
    1305            0 :     for(size_t i = 0; i < numparts(); ++i)
    1306              :     {
    1307            0 :         parts[i]->init(parts[i]->parttype()&PT_FEW ? std::min(fewparticles, maxparticles) : maxparticles);
    1308              :     }
    1309            0 :     for(size_t i = 0; i < numparts(); ++i)
    1310              :     {
    1311            0 :         loadprogress = static_cast<float>(i+1)/numparts();
    1312            0 :         parts[i]->preload();
    1313              :     }
    1314            0 :     loadprogress = 0;
    1315              : }
    1316              : 
    1317            0 : void clearparticles()
    1318              : {
    1319            0 :     for(size_t i = 0; i < numparts(); ++i)
    1320              :     {
    1321            0 :         parts[i]->reset();
    1322              :     }
    1323            0 :     clearparticleemitters();
    1324            0 : }
    1325              : 
    1326            0 : void cleanupparticles()
    1327              : {
    1328            0 :     for(size_t i = 0; i < numparts(); ++i)
    1329              :     {
    1330            0 :         parts[i]->cleanup();
    1331              :     }
    1332            0 : }
    1333              : 
    1334            0 : void removetrackedparticles(physent *owner)
    1335              : {
    1336            0 :     for(size_t i = 0; i < numparts(); ++i)
    1337              :     {
    1338            0 :         parts[i]->resettracked(owner);
    1339              :     }
    1340            0 : }
    1341              : 
    1342              : static int debugparts = variable("debugparticles", 0, 0, 1, &debugparts, nullptr, 0);
    1343              : 
    1344            0 : void GBuffer::renderparticles(int layer) const
    1345              : {
    1346            0 :     canstep = layer != ParticleLayer_Under;
    1347              : 
    1348              :     //want to debug BEFORE the lastpass render (that would delete particles)
    1349            0 :     if(debugparts && (layer == ParticleLayer_All || layer == ParticleLayer_Under))
    1350              :     {
    1351            0 :         for(size_t i = 0; i < numparts(); ++i)
    1352              :         {
    1353            0 :             parts[i]->debuginfo();
    1354              :         }
    1355              :     }
    1356              : 
    1357            0 :     bool rendered = false;
    1358            0 :     uint lastflags = PT_LERP|PT_SHADER,
    1359            0 :          flagmask = PT_LERP|PT_MOD|PT_BRIGHT|PT_NOTEX|PT_SOFT|PT_SHADER,
    1360            0 :          excludemask = layer == ParticleLayer_All ? ~0 : (layer != ParticleLayer_NoLayer ? PT_NOLAYER : 0);
    1361              : 
    1362            0 :     for(size_t i = 0; i < numparts(); ++i)
    1363              :     {
    1364            0 :         partrenderer *p = parts[i];
    1365            0 :         if((p->parttype()&PT_NOLAYER) == excludemask || !p->haswork())
    1366              :         {
    1367            0 :             continue;
    1368              :         }
    1369            0 :         if(!rendered)
    1370              :         {
    1371            0 :             rendered = true;
    1372            0 :             glDepthMask(GL_FALSE);
    1373            0 :             glEnable(GL_BLEND);
    1374            0 :             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    1375              : 
    1376            0 :             glActiveTexture(GL_TEXTURE2);
    1377            0 :             if(msaalight)
    1378              :             {
    1379            0 :                 glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
    1380              :             }
    1381              :             else
    1382              :             {
    1383            0 :                 glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
    1384              :             }
    1385            0 :             glActiveTexture(GL_TEXTURE0);
    1386              :         }
    1387              : 
    1388            0 :         uint flags = p->parttype() & flagmask,
    1389            0 :              changedbits = flags ^ lastflags;
    1390            0 :         if(changedbits)
    1391              :         {
    1392            0 :             if(changedbits&PT_LERP)
    1393              :             {
    1394            0 :                 if(flags&PT_LERP)
    1395              :                 {
    1396            0 :                     resetfogcolor();
    1397              :                 }
    1398              :                 else
    1399              :                 {
    1400            0 :                     zerofogcolor();
    1401              :                 }
    1402              :             }
    1403            0 :             if(changedbits&(PT_LERP|PT_MOD))
    1404              :             {
    1405            0 :                 if(flags&PT_LERP)
    1406              :                 {
    1407            0 :                     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    1408              :                 }
    1409            0 :                 else if(flags&PT_MOD)
    1410              :                 {
    1411            0 :                     glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
    1412              :                 }
    1413              :                 else
    1414              :                 {
    1415            0 :                     glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    1416              :                 }
    1417              :             }
    1418            0 :             if(!(flags&PT_SHADER))
    1419              :             {
    1420            0 :                 if(changedbits&(PT_LERP|PT_SOFT|PT_NOTEX|PT_SHADER))
    1421              :                 {
    1422            0 :                     if(flags&PT_SOFT)
    1423              :                     {
    1424            0 :                         particlesoftshader->set();
    1425            0 :                         LOCALPARAMF(softparams, -1.0f/softparticleblend, 0, 0);
    1426              :                     }
    1427            0 :                     else if(flags&PT_NOTEX)
    1428              :                     {
    1429            0 :                         particlenotextureshader->set();
    1430              :                     }
    1431              :                     else
    1432              :                     {
    1433            0 :                         particleshader->set();
    1434              :                     }
    1435              :                 }
    1436            0 :                 if(changedbits&(PT_MOD|PT_BRIGHT|PT_SOFT|PT_NOTEX|PT_SHADER))
    1437              :                 {
    1438            0 :                     float colorscale = flags&PT_MOD ? 1 : ldrscale;
    1439            0 :                     if(flags&PT_BRIGHT)
    1440              :                     {
    1441            0 :                         colorscale *= particlebright;
    1442              :                     }
    1443            0 :                     LOCALPARAMF(colorscale, colorscale, colorscale, colorscale, 1);
    1444              :                 }
    1445              :             }
    1446            0 :             lastflags = flags;
    1447              :         }
    1448            0 :         p->render();
    1449              :     }
    1450            0 :     if(rendered)
    1451              :     {
    1452            0 :         if(lastflags&(PT_LERP|PT_MOD))
    1453              :         {
    1454            0 :             glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    1455              :         }
    1456            0 :         if(!(lastflags&PT_LERP))
    1457              :         {
    1458            0 :             resetfogcolor();
    1459              :         }
    1460            0 :         glDisable(GL_BLEND);
    1461            0 :         glDepthMask(GL_TRUE);
    1462              :     }
    1463            0 : }
    1464              : 
    1465              : static int addedparticles = 0;
    1466              : 
    1467            0 : static particle *newparticle(const vec &o, const vec &d, int fade, int type, int color, float size, int gravity = 0)
    1468              : {
    1469            0 :     static particle dummy;
    1470            0 :     if(seedemitter)
    1471              :     {
    1472            0 :         parts[type]->seedemitter(*seedemitter, o, d, fade, size, gravity);
    1473            0 :         return &dummy;
    1474              :     }
    1475            0 :     if(fade + emitoffset < 0)
    1476              :     {
    1477            0 :         return &dummy;
    1478              :     }
    1479            0 :     addedparticles++;
    1480            0 :     return parts[type]->addpart(o, d, fade, color, size, gravity);
    1481              : }
    1482              : 
    1483              : VARP(maxparticledistance, 256, 1024, 4096); //cubits before particles stop rendering (1024 = 128m) (note that text particles have their own var)
    1484              : 
    1485            0 : static void splash(int type, int color, int radius, int num, int fade, const vec &p, float size, int gravity)
    1486              : {
    1487            0 :     if(camera1->o.dist(p) > maxparticledistance && !seedemitter)
    1488              :     {
    1489            0 :         return;
    1490              :     }
    1491              :     //ugly ternary assignment
    1492            0 :     float collidez = parts[type]->parttype()&PT_COLLIDE ?
    1493            0 :                      p.z - rootworld.raycube(p, vec(0, 0, -1), collideradius, Ray_ClipMat) + (parts[type]->hasstain() ? collideerror : 0) :
    1494            0 :                      -1;
    1495            0 :     int fmin = 1,
    1496            0 :         fmax = fade*3;
    1497            0 :     for(int i = 0; i < num; ++i)
    1498              :     {
    1499              :         int x, y, z;
    1500              :         do
    1501              :         {
    1502            0 :             x = randomint(radius*2)-radius;
    1503            0 :             y = randomint(radius*2)-radius;
    1504            0 :             z = randomint(radius*2)-radius;
    1505            0 :         } while(x*x + y*y + z*z > radius*radius);
    1506            0 :         vec tmp = vec(static_cast<float>(x), static_cast<float>(y), static_cast<float>(z));
    1507            0 :         int f = (num < 10) ? (fmin + randomint(fmax)) : (fmax - (i*(fmax-fmin))/(num-1)); //help deallocater by using fade distribution rather than random
    1508            0 :         newparticle(p, tmp, f, type, color, size, gravity)->val = collidez;
    1509              :     }
    1510              : }
    1511              : 
    1512            0 : static void regularsplash(int type, int color, int radius, int num, int fade, const vec &p, float size, int gravity, int delay = 0)
    1513              : {
    1514            0 :     if(!canemitparticles() || (delay > 0 && randomint(delay) != 0))
    1515              :     {
    1516            0 :         return;
    1517              :     }
    1518            0 :     splash(type, color, radius, num, fade, p, size, gravity);
    1519              : }
    1520              : 
    1521            0 : void regular_particle_splash(int type, int num, int fade, const vec &p, int color, float size, int radius, int gravity, int delay)
    1522              : {
    1523            0 :     if(minimized)
    1524              :     {
    1525            0 :         return;
    1526              :     }
    1527            0 :     regularsplash(type, color, radius, num, fade, p, size, gravity, delay);
    1528              : }
    1529              : 
    1530            0 : void particle_splash(int type, int num, int fade, const vec &p, int color, float size, int radius, int gravity)
    1531              : {
    1532            0 :     if(minimized)
    1533              :     {
    1534            0 :         return;
    1535              :     }
    1536            0 :     splash(type, color, radius, num, fade, p, size, gravity);
    1537              : }
    1538              : 
    1539              : VARP(maxtrail, 1, 500, 10000); //maximum number of steps allowed in a trail projectile
    1540              : 
    1541            0 : void particle_trail(int type, int fade, const vec &s, const vec &e, int color, float size, int gravity)
    1542              : {
    1543            0 :     if(minimized)
    1544              :     {
    1545            0 :         return;
    1546              :     }
    1547            0 :     vec v;
    1548            0 :     float d = e.dist(s, v);
    1549            0 :     int steps = std::clamp(static_cast<int>(d*2), 1, maxtrail);
    1550            0 :     v.div(steps);
    1551            0 :     vec p = s;
    1552            0 :     for(int i = 0; i < steps; ++i)
    1553              :     {
    1554            0 :         p.add(v);
    1555              :         //ugly long vec assignment
    1556            0 :         vec tmp = vec(static_cast<float>(randomint(11)-5),
    1557            0 :                       static_cast<float>(randomint(11)-5),
    1558            0 :                       static_cast<float>(randomint(11)-5));
    1559            0 :         newparticle(p, tmp, randomint(fade)+fade, type, color, size, gravity);
    1560              :     }
    1561              : }
    1562              : 
    1563              : VARP(particletext, 0, 1, 1);
    1564              : VARP(maxparticletextdistance, 0, 128, 10000); //cubits at which text can be rendered (128 = 16m)
    1565              : 
    1566            0 : void particle_icon(const vec &s, int ix, int iy, int type, int fade, int color, float size, int gravity)
    1567              : {
    1568            0 :     if(minimized)
    1569              :     {
    1570            0 :         return;
    1571              :     }
    1572            0 :     particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size, gravity);
    1573            0 :     p->flags |= ix | (iy<<2);
    1574              : }
    1575              : 
    1576            0 : void particle_meter(const vec &s, float val, int type, int fade, int color, int color2, float size)
    1577              : {
    1578            0 :     if(minimized)
    1579              :     {
    1580            0 :         return;
    1581              :     }
    1582            0 :     particle *p = newparticle(s, vec(0, 0, 1), fade, type, color, size);
    1583            0 :     p->meter.color2[0] = color2>>16;
    1584            0 :     p->meter.color2[1] = (color2>>8)&0xFF;
    1585            0 :     p->meter.color2[2] = color2&0xFF;
    1586            0 :     p->meter.progress = std::clamp(static_cast<int>(val*100), 0, 100);
    1587              : }
    1588              : 
    1589            0 : void particle_flare(const vec &p, const vec &dest, int fade, int type, int color, float size, physent *owner)
    1590              : {
    1591            0 :     if(minimized)
    1592              :     {
    1593            0 :         return;
    1594              :     }
    1595            0 :     newparticle(p, dest, fade, type, color, size)->owner = owner;
    1596              : }
    1597              : 
    1598            0 : void particle_fireball(const vec &dest, float maxsize, int type, int fade, int color, float size)
    1599              : {
    1600            0 :     if(minimized)
    1601              :     {
    1602            0 :         return;
    1603              :     }
    1604            0 :     float growth = maxsize - size;
    1605            0 :     if(fade < 0)
    1606              :     {
    1607            0 :         fade = static_cast<int>(growth*20);
    1608              :     }
    1609            0 :     newparticle(dest, vec(0, 0, 1), fade, type, color, size)->val = growth;
    1610              : }
    1611              : 
    1612              : //dir = 0..6 where 0=up
    1613            0 : static vec offsetvec(vec o, int dir, int dist)
    1614              : {
    1615            0 :     vec v = vec(o);
    1616            0 :     v[(2+dir)%3] += (dir>2)?(-dist):dist;
    1617            0 :     return v;
    1618              : }
    1619              : 
    1620              : //converts a 16bit color to 24bit
    1621            0 : static int colorfromattr(int attr)
    1622              : {
    1623            0 :     return (((attr&0xF)<<4) | ((attr&0xF0)<<8) | ((attr&0xF00)<<12)) + 0x0F0F0F;
    1624              : }
    1625              : 
    1626              : /* Experiments in shapes...
    1627              :  * dir: (where dir%3 is similar to offsetvec with 0=up)
    1628              :  * 0..2 circle
    1629              :  * 3.. 5 cylinder shell
    1630              :  * 6..11 cone shell
    1631              :  * 12..14 plane volume
    1632              :  * 15..20 line volume, i.e. wall
    1633              :  * 21 sphere
    1634              :  * 24..26 flat plane
    1635              :  * +32 to inverse direction
    1636              :  */
    1637            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)
    1638              : {
    1639            0 :     if(!canemitparticles())
    1640              :     {
    1641            0 :         return;
    1642              :     }
    1643            0 :     int basetype = parts[type]->parttype()&0xFF;
    1644            0 :     bool flare = (basetype == PT_TAPE),
    1645            0 :          inv = (dir&0x20)!=0,
    1646            0 :          taper = (dir&0x40)!=0 && !seedemitter;
    1647            0 :     dir &= 0x1F;
    1648            0 :     for(int i = 0; i < num; ++i)
    1649              :     {
    1650            0 :         vec to, from;
    1651            0 :         if(dir < 12)
    1652              :         {
    1653            0 :             const vec2 &sc = sincos360[randomint(360)];
    1654            0 :             to[dir%3] = sc.y*radius;
    1655            0 :             to[(dir+1)%3] = sc.x*radius;
    1656            0 :             to[(dir+2)%3] = 0.0;
    1657            0 :             to.add(p);
    1658            0 :             if(dir < 3) //circle
    1659              :             {
    1660            0 :                 from = p;
    1661              :             }
    1662            0 :             else if(dir < 6) //cylinder
    1663              :             {
    1664            0 :                 from = to;
    1665            0 :                 to[(dir+2)%3] += radius;
    1666            0 :                 from[(dir+2)%3] -= radius;
    1667              :             }
    1668              :             else //cone
    1669              :             {
    1670            0 :                 from = p;
    1671            0 :                 to[(dir+2)%3] += (dir < 9)?radius:(-radius);
    1672              :             }
    1673              :         }
    1674            0 :         else if(dir < 15) //plane
    1675              :         {
    1676            0 :             to[dir%3] = static_cast<float>(randomint(radius<<4)-(radius<<3))/8.0;
    1677            0 :             to[(dir+1)%3] = static_cast<float>(randomint(radius<<4)-(radius<<3))/8.0;
    1678            0 :             to[(dir+2)%3] = radius;
    1679            0 :             to.add(p);
    1680            0 :             from = to;
    1681            0 :             from[(dir+2)%3] -= 2*radius;
    1682              :         }
    1683            0 :         else if(dir < 21) //line
    1684              :         {
    1685            0 :             if(dir < 18)
    1686              :             {
    1687            0 :                 to[dir%3] = static_cast<float>(randomint(radius<<4)-(radius<<3))/8.0;
    1688            0 :                 to[(dir+1)%3] = 0.0;
    1689              :             }
    1690              :             else
    1691              :             {
    1692            0 :                 to[dir%3] = 0.0;
    1693            0 :                 to[(dir+1)%3] = static_cast<float>(randomint(radius<<4)-(radius<<3))/8.0;
    1694              :             }
    1695            0 :             to[(dir+2)%3] = 0.0;
    1696            0 :             to.add(p);
    1697            0 :             from = to;
    1698            0 :             to[(dir+2)%3] += radius;
    1699              :         }
    1700            0 :         else if(dir < 24) //sphere
    1701              :         {
    1702            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);
    1703            0 :             to.add(p);
    1704            0 :             from = p;
    1705              :         }
    1706            0 :         else if(dir < 27) // flat plane
    1707              :         {
    1708            0 :             to[dir%3] = static_cast<float>(randomfloat(2*radius)-radius);
    1709            0 :             to[(dir+1)%3] = static_cast<float>(randomfloat(2*radius)-radius);
    1710            0 :             to[(dir+2)%3] = 0.0;
    1711            0 :             to.add(p);
    1712            0 :             from = to;
    1713              :         }
    1714              :         else
    1715              :         {
    1716            0 :             from = to = p;
    1717              :         }
    1718            0 :         if(inv)
    1719              :         {
    1720            0 :             std::swap(from, to);
    1721              :         }
    1722            0 :         if(taper)
    1723              :         {
    1724            0 :             float dist = std::clamp(from.dist2(camera1->o)/maxparticledistance, 0.0f, 1.0f);
    1725            0 :             if(dist > 0.2f)
    1726              :             {
    1727            0 :                 dist = 1 - (dist - 0.2f)/0.8f;
    1728            0 :                 if(randomint(0x10000) > dist*dist*0xFFFF)
    1729              :                 {
    1730            0 :                     continue;
    1731              :                 }
    1732              :             }
    1733              :         }
    1734            0 :         if(flare)
    1735              :         {
    1736            0 :             newparticle(from, to, randomint(fade*3)+1, type, color, size, gravity);
    1737              :         }
    1738              :         else
    1739              :         {
    1740            0 :             vec d = vec(to).sub(from).rescale(vel); //velocity
    1741            0 :             particle *n = newparticle(from, d, randomint(fade*3)+1, type, color, size, gravity);
    1742            0 :             if(parts[type]->parttype()&PT_COLLIDE)
    1743              :             {
    1744              :                 //long nasty ternary assignment
    1745            0 :                 n->val = from.z - rootworld.raycube(from, vec(0, 0, -1), parts[type]->hasstain() ?
    1746              :                          collideradius :
    1747            0 :                          std::max(from.z, 0.0f), Ray_ClipMat) + (parts[type]->hasstain() ? collideerror : 0);
    1748              :             }
    1749              :         }
    1750              :     }
    1751              : }
    1752              : 
    1753            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)
    1754              : {
    1755            0 :     if(!canemitparticles())
    1756              :     {
    1757            0 :         return;
    1758              :     }
    1759            0 :     float size = scale * std::min(radius, height);
    1760            0 :     vec v(0, 0, std::min(1.0f, height)*speed);
    1761            0 :     for(int i = 0; i < density; ++i)
    1762              :     {
    1763            0 :         vec s = p;
    1764            0 :         s.x += randomfloat(radius*2.0f)-radius;
    1765            0 :         s.y += randomfloat(radius*2.0f)-radius;
    1766            0 :         newparticle(s, v, randomint(std::max(static_cast<int>(fade*height), 1))+1, type, color, size, gravity);
    1767              :     }
    1768              : }
    1769              : 
    1770            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)
    1771              : {
    1772            0 :     if(minimized)
    1773              :     {
    1774            0 :         return;
    1775              :     }
    1776            0 :     regularflame(type, p, radius, height, color, density, scale, speed, fade, gravity);
    1777              : }
    1778            0 : void cubeworld::makeparticles(const entity &e)
    1779              : {
    1780            0 :     switch(e.attr1)
    1781              :     {
    1782            0 :         case 0: //fire and smoke -  <radius> <height> <rgb> - 0 values default to compat for old maps
    1783              :         {
    1784            0 :             float radius = e.attr2 ? static_cast<float>(e.attr2)/100.0f : 1.5f,
    1785            0 :                   height = e.attr3 ? static_cast<float>(e.attr3)/100.0f : radius/3;
    1786            0 :             regularflame(Part_Flame, e.o, radius, height, e.attr4 ? colorfromattr(e.attr4) : 0x903020, 3, 2.0f);
    1787            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);
    1788            0 :             break;
    1789              :         }
    1790            0 :         case 1: //steam vent - <dir>
    1791              :         {
    1792            0 :             regularsplash(Part_Steam, 0x897661, 50, 1, 200, offsetvec(e.o, e.attr2, randomint(10)), 2.4f, -20);
    1793            0 :             break;
    1794              :         }
    1795            0 :         case 2: //water fountain - <dir>
    1796              :         {
    1797              :             int color;
    1798            0 :             if(e.attr3 > 0)
    1799              :             {
    1800            0 :                 color = colorfromattr(e.attr3);
    1801              :             }
    1802              :             else
    1803              :             {
    1804            0 :                 int mat = Mat_Water + std::clamp(-e.attr3, 0, 3);
    1805            0 :                 color = getwaterfallcolor(mat).tohexcolor();
    1806            0 :                 if(!color)
    1807              :                 {
    1808            0 :                     color = getwatercolor(mat).tohexcolor();
    1809              :                 }
    1810              :             }
    1811            0 :             regularsplash(Part_Water, color, 150, 4, 200, offsetvec(e.o, e.attr2, randomint(10)), 0.6f, 2);
    1812            0 :             break;
    1813              :         }
    1814            0 :         case 3: //fire ball - <size> <rgb>
    1815              :         {
    1816            0 :             newparticle(e.o, vec(0, 0, 1), 1, Part_Explosion, colorfromattr(e.attr3), 4.0f)->val = 1+e.attr2;
    1817            0 :             break;
    1818              :         }
    1819            0 :         case 4:  //tape - <dir> <length> <rgb>
    1820              :         case 9:  //steam
    1821              :         case 10: //water
    1822              :         case 13: //snow
    1823              :         {
    1824              :             static constexpr int typemap[]   = { Part_Streak, -1, -1, -1, -1, Part_Steam, Part_Water, -1, -1, Part_Snow };
    1825              :             static constexpr float sizemap[] = { 0.28f, 0.0f, 0.0f, 1.0f, 0.0f, 2.4f, 0.60f, 0.0f, 0.0f, 0.5f };
    1826              :             static constexpr int gravmap[]   = { 0, 0, 0, 0, 0, -20, 2, 0, 0, 20 };
    1827            0 :             int type = typemap[e.attr1-4];
    1828            0 :             float size = sizemap[e.attr1-4];
    1829            0 :             int gravity = gravmap[e.attr1-4];
    1830            0 :             if(e.attr2 >= 256)
    1831              :             {
    1832            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);
    1833              :             }
    1834              :             else
    1835              :             {
    1836            0 :                 newparticle(e.o, offsetvec(e.o, e.attr2, std::max(1+e.attr3, 0)), 1, type, colorfromattr(e.attr4), size, gravity);
    1837              :             }
    1838            0 :             break;
    1839              :         }
    1840            0 :         case 5: //meter, metervs - <percent> <rgb> <rgb2>
    1841              :         case 6:
    1842              :         {
    1843            0 :             particle *p = newparticle(e.o, vec(0, 0, 1), 1, e.attr1==5 ? Part_Meter : Part_MeterVS, colorfromattr(e.attr3), 2.0f);
    1844            0 :             int color2 = colorfromattr(e.attr4);
    1845            0 :             p->meter.color2[0] = color2>>16;
    1846            0 :             p->meter.color2[1] = (color2>>8)&0xFF;
    1847            0 :             p->meter.color2[2] = color2&0xFF;
    1848            0 :             p->meter.progress = std::clamp(static_cast<int>(e.attr2), 0, 100);
    1849            0 :             break;
    1850              :         }
    1851            0 :         case 11: // flame <radius> <height> <rgb> - radius=100, height=100 is the classic size
    1852              :         {
    1853            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);
    1854            0 :             break;
    1855              :         }
    1856            0 :         case 12: // smoke plume <radius> <height> <rgb>
    1857              :         {
    1858            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);
    1859            0 :             break;
    1860              :         }
    1861            0 :         default:
    1862              :         {
    1863            0 :             break;
    1864              :         }
    1865              :     }
    1866            0 : }
    1867              : 
    1868            0 : void cubeworld::seedparticles()
    1869              : {
    1870            0 :     renderprogress(0, "seeding particles");
    1871            0 :     addparticleemitters();
    1872            0 :     canemit = true;
    1873            0 :     for(particleemitter &pe : emitters)
    1874              :     {
    1875            0 :         const extentity &e = *pe.ent;
    1876            0 :         seedemitter = &pe;
    1877            0 :         for(int millis = 0; millis < seedmillis; millis += std::min(emitmillis, seedmillis/10))
    1878              :         {
    1879            0 :             makeparticles(e);
    1880              :         }
    1881            0 :         seedemitter = nullptr;
    1882            0 :         pe.lastemit = -seedmillis;
    1883            0 :         pe.finalize();
    1884              :     }
    1885            0 : }
    1886              : 
    1887            0 : void cubeworld::updateparticles()
    1888              : {
    1889              :     //note: static int carried across all calls of function
    1890              :     static int lastemitframe = 0;
    1891              : 
    1892            0 :     if(regenemitters) //regenemitters called whenever a new particle generator is placed
    1893              :     {
    1894            0 :         addparticleemitters();
    1895              :     }
    1896            0 :     if(minimized) //don't emit particles unless window visible
    1897              :     {
    1898            0 :         canemit = false;
    1899            0 :         return;
    1900              :     }
    1901            0 :     if(lastmillis - lastemitframe >= emitmillis) //don't update particles too often
    1902              :     {
    1903            0 :         canemit = true;
    1904            0 :         lastemitframe = lastmillis - (lastmillis%emitmillis);
    1905              :     }
    1906              :     else
    1907              :     {
    1908            0 :         canemit = false;
    1909              :     }
    1910            0 :     if(!editmode || showparticles)
    1911              :     {
    1912            0 :         int emitted = 0,
    1913            0 :             replayed = 0;
    1914            0 :         addedparticles = 0;
    1915            0 :         for(particleemitter& pe : emitters) //foreach particle emitter
    1916              :         {
    1917            0 :             const extentity &e = *pe.ent; //get info for the entity associated w/ent
    1918            0 :             if(e.o.dist(camera1->o) > maxparticledistance) //distance check (don't update faraway particle ents)
    1919              :             {
    1920            0 :                 pe.lastemit = lastmillis;
    1921            0 :                 continue;
    1922              :             }
    1923            0 :             if(cullparticles && pe.maxfade >= 0)
    1924              :             {
    1925            0 :                 if(view.isfoggedsphere(pe.radius, pe.center))
    1926              :                 {
    1927            0 :                     pe.lastcull = lastmillis;
    1928            0 :                     continue;
    1929              :                 }
    1930              :             }
    1931            0 :             makeparticles(e);
    1932            0 :             emitted++;
    1933            0 :             if(replayparticles && pe.maxfade > 5 && pe.lastcull > pe.lastemit) //recreate particles from previous ticks
    1934              :             {
    1935            0 :                 for(emitoffset = std::max(pe.lastemit + emitmillis - lastmillis, -pe.maxfade); emitoffset < 0; emitoffset += emitmillis)
    1936              :                 {
    1937            0 :                     makeparticles(e);
    1938            0 :                     replayed++;
    1939              :                 }
    1940            0 :                 emitoffset = 0;
    1941              :             }
    1942            0 :             pe.lastemit = lastmillis;
    1943              :         }
    1944            0 :         if(debugparticlecull && (canemit || replayed) && addedparticles)
    1945              :         {
    1946            0 :             conoutf(Console_Debug, "%d emitters, %d particles", emitted, addedparticles);
    1947              :         }
    1948              :     }
    1949            0 :     if(editmode) // show sparkly thingies for map entities in edit mode
    1950              :     {
    1951            0 :         const std::vector<extentity *> &ents = entities::getents();
    1952            0 :         for(extentity * const &e : ents)
    1953              :         {
    1954            0 :             regular_particle_splash(Part_Edit, 2, 40, e->o, 0x3232FF, 0.32f*particlesize/100.0f);
    1955              :         }
    1956              :     }
    1957              : }
        

Generated by: LCOV version 2.0-1