LCOV - code coverage report
Current view: top level - engine/render - renderparticles.cpp (source / functions) Hit Total Coverage
Test: Libprimis Test Coverage Lines: 35 949 3.7 %
Date: 2024-11-22 05:07:59 Functions: 11 133 8.3 %

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

Generated by: LCOV version 1.14