1 // renderparticles.cpp
2 
3 #include "engine.h"
4 
5 Shader *particleshader = NULL, *particlenotextureshader = NULL, *particlesoftshader = NULL, *particletextshader = NULL;
6 
7 VAR(IDF_PERSIST, particlelayers, 0, 1, 1);
8 FVAR(IDF_PERSIST, particlebright, 0, 2, 100);
9 VAR(IDF_PERSIST, particlesize, 20, 100, 500);
10 
11 VAR(IDF_PERSIST, softparticles, 0, 1, 1);
12 VAR(IDF_PERSIST, softparticleblend, 1, 8, 64);
13 
14 VAR(IDF_PERSIST, particlewind, 0, 1, 1);
15 
16 // Check canemitparticles() to limit the rate that paricles can be emitted for models/sparklies
17 // Automatically stops particles being emitted when paused or in reflective drawing
18 VAR(IDF_PERSIST, emitmillis, 1, 17, VAR_MAX);
19 static int lastemitframe = 0, emitoffset = 0;
20 static bool canemit = false, regenemitters = false, canstep = false;
21 
canemitparticles()22 static bool canemitparticles()
23 {
24     return canemit || emitoffset;
25 }
26 
27 VAR(IDF_PERSIST, showparticles, 0, 1, 1);
28 VAR(0, cullparticles, 0, 1, 1);
29 VAR(0, replayparticles, 0, 1, 1);
30 VARN(0, seedparticles, seedmillis, 0, 3000, 10000);
31 VAR(0, dbgpcull, 0, 0, 1);
32 VAR(0, dbgpseed, 0, 0, 1);
33 
34 struct particleemitter
35 {
36     extentity *ent;
37     vec bbmin, bbmax;
38     vec center;
39     float radius;
40     ivec cullmin, cullmax;
41     int maxfade, lastemit, lastcull;
42 
particleemitterparticleemitter43     particleemitter(extentity *ent)
44         : ent(ent), bbmin(ent->o), bbmax(ent->o), maxfade(-1), lastemit(0), lastcull(0)
45     {}
46 
finalizeparticleemitter47     void finalize()
48     {
49         center = vec(bbmin).add(bbmax).mul(0.5f);
50         radius = bbmin.dist(bbmax)/2;
51         cullmin = ivec::floor(bbmin);
52         cullmax = ivec::ceil(bbmax);
53         if(dbgpseed) conoutf("radius: %f, maxfade: %d", radius, maxfade);
54     }
55 
extendbbparticleemitter56     void extendbb(const vec &o, float size = 0)
57     {
58         bbmin.x = min(bbmin.x, o.x - size);
59         bbmin.y = min(bbmin.y, o.y - size);
60         bbmin.z = min(bbmin.z, o.z - size);
61         bbmax.x = max(bbmax.x, o.x + size);
62         bbmax.y = max(bbmax.y, o.y + size);
63         bbmax.z = max(bbmax.z, o.z + size);
64     }
65 
extendbbparticleemitter66     void extendbb(float z, float size = 0)
67     {
68         bbmin.z = min(bbmin.z, z - size);
69         bbmax.z = max(bbmax.z, z + size);
70     }
71 };
72 
73 static vector<particleemitter> emitters;
74 static particleemitter *seedemitter = NULL;
75 
clearparticleemitters()76 void clearparticleemitters()
77 {
78     emitters.setsize(0);
79     regenemitters = true;
80 }
81 
addparticleemitters()82 void addparticleemitters()
83 {
84     emitters.setsize(0);
85     const vector<extentity *> &ents = entities::getents();
86     loopv(ents)
87     {
88         extentity &e = *ents[i];
89         if(e.type != ET_PARTICLES) continue;
90         emitters.add(particleemitter(&e));
91     }
92     regenemitters = false;
93 }
94 
95 const char *partnames[] = { "part", "tape", "trail", "text", "explosion", "lightning", "flare", "portal", "icon", "line", "triangle", "ellipse", "cone" };
96 
97 struct partvert
98 {
99     vec pos;
100     bvec4 color;
101     vec2 tc;
102 };
103 
104 #define COLLIDERADIUS 8.0f
105 #define COLLIDEERROR 1.0f
106 
107 struct partrenderer
108 {
109     Texture *tex;
110     const char *texname;
111     int texclamp;
112     uint type;
113     string info;
114 
partrendererpartrenderer115     partrenderer(const char *texname, int texclamp, int type)
116         : tex(NULL), texname(texname), texclamp(texclamp), type(type) { }
partrendererpartrenderer117     partrenderer(int type)
118         : tex(NULL), texname(NULL), texclamp(0), type(type) {}
119 
~partrendererpartrenderer120     virtual ~partrenderer() { }
121 
initpartrenderer122     virtual void init(int n) { }
123     virtual void reset() = 0;
resettrackedpartrenderer124     virtual void resettracked(physent *owner) { }
125     virtual particle *addpart(const vec &o, const vec &d, int fade, int color, float size, float blend = 1, float gravity = 0, int collide = 0, physent *pl = NULL) = 0;
updatepartrenderer126     virtual void update() { }
127     virtual void render() = 0;
128     virtual bool haswork() = 0;
129     virtual int count() = 0; //for debug
cleanuppartrenderer130     virtual void cleanup() {}
131 
seedemitterpartrenderer132     virtual void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, float gravity)
133     {
134     }
135 
preloadpartrenderer136     virtual void preload()
137     {
138         if(texname && !tex) tex = textureload(texname, texclamp);
139     }
140 
141     //blend = 0 => remove it
calcpartrenderer142     void calc(particle *p, int &blend, int &ts, float &size, bool step = true)
143     {
144         vec o = p->o;
145         if(p->fade <= 5)
146         {
147             ts = 1;
148             blend = 255;
149             size = p->size;
150         }
151         else
152         {
153             ts = lastmillis-p->millis;
154             blend = max(255-((ts<<8)/p->fade), 0);
155             float weight = p->gravity;
156             if((type&PT_SHRINK || type&PT_GROW) && p->fade >= 50)
157             {
158                 float amt = clamp(ts/float(p->fade), 0.f, 1.f);
159                 if(type&PT_SHRINK)
160                 {
161                     if(type&PT_GROW) { if((amt *= 2) > 1) amt = 2-amt; amt *= amt; }
162                     else amt = 1-(amt*amt);
163                 }
164                 else amt *= amt;
165                 size = p->size*amt;
166                 if(weight != 0) weight += weight*(p->size-size);
167             }
168             else size = p->size;
169             float secs = curtime/1000.f;
170             int part = type&0xFF;
171             vec v = part == PT_PART ? vec(p->d).mul(secs) : vec(0, 0, 0);
172             if(weight != 0)
173             {
174                 if(ts > p->fade) ts = p->fade;
175                 static struct particleent : physent
176                 {
177                     particleent()
178                     {
179                         physent::reset();
180                         type = ENT_DUMMY;
181                     }
182                 } d;
183                 d.weight = weight;
184                 v.z -= physics::gravityvel(&d)*secs;
185             }
186             p->o.add(v);
187             if(particlewind && type&PT_WIND) p->o.add(p->wind.probe(o).mul(secs * 10.0f));
188             if(step && p->collide && p->o.z < p->val)
189             {
190                 if(p->collide > 0)
191                 {
192                     vec surface;
193                     float floorz = rayfloor(vec(p->o.x, p->o.y, p->val), surface, RAY_CLIPMAT, COLLIDERADIUS);
194                     float collidez = floorz<0 ? o.z-COLLIDERADIUS : p->val - floorz;
195                     if(p->o.z >= collidez+COLLIDEERROR) p->val = collidez+COLLIDEERROR;
196                     else
197                     {
198                         addstain(p->collide, vec(p->o.x, p->o.y, collidez), vec(o).sub(p->o).normalize(), 2*p->size, p->color, type&PT_RND4 ? (p->flags>>5)&3 : 0);
199                         blend = 0;
200                     }
201                 }
202                 else blend = 0;
203             }
204             else p->m.add(vec(p->o).sub(o));
205         }
206         game::particletrack(p, type, ts, step);
207     }
208 
debuginfopartrenderer209     void debuginfo()
210     {
211         formatstring(info, "%d\t%s(", count(), partnames[type&0xFF]);
212         if(type&PT_LERP) concatstring(info, "l,");
213         if(type&PT_MOD) concatstring(info, "m,");
214         if(type&PT_RND4) concatstring(info, "r,");
215         if(type&PT_FLIP) concatstring(info, "f,");
216         int len = strlen(info);
217         info[len-1] = info[len-1] == ',' ? ')' : '\0';
218         if(texname)
219         {
220             const char *title = strrchr(texname, '/');
221             if(title) concformatstring(info, ": %s", title+1);
222         }
223     }
224 };
225 
226 template<class T>
227 struct listparticle : particle
228 {
229     T *next;
230 };
231 
232 struct sharedlistparticle : listparticle<sharedlistparticle> {};
233 
234 template<class T>
235 struct listrenderer : partrenderer
236 {
237     static T *parempty;
238     T *list;
239 
listrendererlistrenderer240     listrenderer(const char *texname, int texclamp, int type)
241         : partrenderer(texname, texclamp, type), list(NULL)
242     {
243     }
listrendererlistrenderer244     listrenderer(int type)
245         : partrenderer(type), list(NULL)
246     {
247     }
248 
~listrendererlistrenderer249     virtual ~listrenderer()
250     {
251     }
252 
killpartlistrenderer253     virtual void killpart(T *p)
254     {
255     }
256 
resetlistrenderer257     void reset()
258     {
259         if(!list) return;
260         T *p = list;
261         for(;;)
262         {
263             killpart(p);
264             if(p->next) p = p->next;
265             else break;
266         }
267         p->next = parempty;
268         parempty = list;
269         list = NULL;
270     }
271 
resettrackedlistrenderer272     void resettracked(physent *owner)
273     {
274         for(T **prev = &list, *cur = list; cur; cur = *prev)
275         {
276             if(!owner || cur->owner==owner)
277             {
278                 *prev = cur->next;
279                 cur->next = parempty;
280                 parempty = cur;
281             }
282             else prev = &cur->next;
283         }
284     }
285 
addpartlistrenderer286     particle *addpart(const vec &o, const vec &d, int fade, int color, float size, float blend = 1, float gravity = 0, int collide = 0, physent *pl = NULL)
287     {
288         if(!parempty)
289         {
290             T *ps = new T[256];
291             loopi(255) ps[i].next = &ps[i+1];
292             ps[255].next = parempty;
293             parempty = ps;
294         }
295         T *p = parempty;
296         parempty = p->next;
297         p->next = list;
298         list = p;
299         p->o = o;
300         p->m = vec(0, 0, 0);
301         p->d = d;
302         p->fade = fade;
303         p->millis = lastmillis;
304         p->color = bvec::hexcolor(color);
305         p->size = size;
306         p->blend = blend;
307         p->gravity = gravity;
308         p->collide = collide;
309         if((p->owner = pl) != NULL && (p->owner->type == ENT_PLAYER || p->owner->type == ENT_AI)) switch(type&PT_TYPE)
310         {
311             case PT_TEXT: case PT_ICON: p->m.add(vec(p->o).sub(p->owner->abovehead())); break;
312             default: break;
313         }
314         p->flags = 0;
315         return p;
316     }
317 
countlistrenderer318     int count()
319     {
320         int num = 0;
321         T *lp;
322         for(lp = list; lp; lp = lp->next) num++;
323         return num;
324     }
325 
hasworklistrenderer326     bool haswork()
327     {
328         return (list != NULL);
329     }
330 
331     virtual void startrender() = 0;
332     virtual void endrender() = 0;
333     virtual void renderpart(T *p, int blend, int ts, float size) = 0;
334 
renderpartlistrenderer335     bool renderpart(T *p)
336     {
337         int blend, ts;
338         float size;
339         calc(p, blend, ts, size, canstep);
340         if(blend <= 0) return false;
341         renderpart(p, blend, ts, size);
342         return p->fade > 5;
343     }
344 
renderlistrenderer345     void render()
346     {
347         startrender();
348         if(tex) glBindTexture(GL_TEXTURE_2D, tex->id);
349         if(canstep) for(T **prev = &list, *p = list; p; p = *prev)
350         {
351             if(renderpart(p)) prev = &p->next;
352             else
353             { // remove
354                 *prev = p->next;
355                 p->next = parempty;
356                 killpart(p);
357                 parempty = p;
358             }
359         }
360         else for(T *p = list; p; p = p->next) renderpart(p);
361         endrender();
362     }
363 };
364 
365 template<class T> T *listrenderer<T>::parempty = NULL;
366 
367 typedef listrenderer<sharedlistparticle> sharedlistrenderer;
368 
369 struct textrenderer : sharedlistrenderer
370 {
textrenderertextrenderer371     textrenderer(int type = 0)
372         : sharedlistrenderer(type|PT_TEXT|PT_LERP|PT_SHADER|PT_NOLAYER)
373     {}
374 
startrendertextrenderer375     void startrender()
376     {
377         textshader = particletextshader;
378 
379         pushfont("default");
380     }
381 
endrendertextrenderer382     void endrender()
383     {
384         textshader = NULL;
385 
386         popfont();
387     }
388 
killparttextrenderer389     void killpart(sharedlistparticle *p)
390     {
391         if(p->text && p->flags&1) delete[] p->text;
392     }
393 
renderparttextrenderer394     void renderpart(sharedlistparticle *p, int blend, int ts, float size)
395     {
396         const char *text = p->text;
397         stringz(font);
398         if(*text == '<')
399         {
400             const char *start = text;
401             while(*text && *text != '>') text++;
402             if(*text) { int len = text-(start+1); memcpy(font, start+1, len); font[len] = '\0'; text++; }
403             else text = start;
404         }
405         pushfont(*font ? font : "default");
406         float scale = p->size/80.0f, xoff = -text_widthf(text)*0.5f;
407 
408         matrix4x3 m(camright, vec(camup).neg(), vec(camdir).neg(), p->o);
409         m.scale(scale);
410         m.translate(xoff, 0, 50);
411 
412         textmatrix = &m;
413         draw_text(text, 0, 0, p->color.r, p->color.g, p->color.b, int(p->blend*blend));
414         textmatrix = NULL;
415         popfont();
416     }
417 };
418 static textrenderer texts, textontop(PT_ONTOP);
419 
420 struct portal : listparticle<portal>
421 {
422     float yaw, pitch;
423 };
424 
425 struct portalrenderer : listrenderer<portal>
426 {
portalrendererportalrenderer427     portalrenderer(const char *texname)
428         : listrenderer<portal>(texname, 3, PT_PORTAL|PT_LERP)
429     {}
430 
startrenderportalrenderer431     void startrender()
432     {
433         glDisable(GL_CULL_FACE);
434         gle::defvertex();
435         gle::deftexcoord0();
436         gle::defcolor(4, GL_UNSIGNED_BYTE);
437         gle::begin(GL_QUADS);
438     }
439 
endrenderportalrenderer440     void endrender()
441     {
442         gle::end();
443         glEnable(GL_CULL_FACE);
444     }
445 
renderpartportalrenderer446     void renderpart(portal *p, int blend, int ts, float size)
447     {
448         matrix4x3 m(vec(size, 0, 0), vec(0, size, 0), vec(0, 0, size), p->o);
449         m.rotate_around_z(p->yaw*RAD);
450         m.rotate_around_x(p->pitch*RAD);
451 
452         bvec4 color(p->color.r, p->color.g, p->color.b, uchar(p->blend*blend));
453         gle::attrib(m.transform(vec(-1, 0,  1))); gle::attribf(1, 0); gle::color(color);
454         gle::attrib(m.transform(vec( 1, 0,  1))); gle::attribf(0, 0); gle::color(color);
455         gle::attrib(m.transform(vec( 1, 0, -1))); gle::attribf(0, 1); gle::color(color);
456         gle::attrib(m.transform(vec(-1, 0, -1))); gle::attribf(1, 1); gle::color(color);
457     }
458 
addportalportalrenderer459     portal *addportal(const vec &o, int fade, int color, float size, float blend, float yaw, float pitch)
460     {
461         portal *p = (portal *)listrenderer<portal>::addpart(o, vec(0, 0, 0), fade, color, size, blend);
462         p->yaw = yaw;
463         p->pitch = pitch;
464         return p;
465     }
466 
467     // use addportal() instead
addpartportalrenderer468     particle *addpart(const vec &o, const vec &d, int fade, int color, float size, float blend = 1, float gravity = 0, int collide = 0, physent *pl = NULL) { return NULL; }
469 };
470 
471 struct icon : listparticle<icon>
472 {
473     Texture *tex;
474     float start, length, end;
475 };
476 
477 struct iconrenderer : listrenderer<icon>
478 {
479     Texture *lasttex;
480 
iconrenderericonrenderer481     iconrenderer(int type = 0)
482         : listrenderer<icon>(type|PT_ICON|PT_LERP)
483     {}
484 
startrendericonrenderer485     void startrender()
486     {
487         lasttex = NULL;
488         gle::defvertex();
489         gle::deftexcoord0();
490         gle::defcolor(4, GL_UNSIGNED_BYTE);
491         gle::begin(GL_QUADS);
492     }
493 
endrendericonrenderer494     void endrender()
495     {
496         gle::end();
497     }
498 
renderparticonrenderer499     void renderpart(icon *p, int blend, int ts, float size)
500     {
501         if(p->tex != lasttex)
502         {
503             gle::end();
504             glBindTexture(GL_TEXTURE_2D, p->tex->id);
505             lasttex = p->tex;
506         }
507 
508         matrix4x3 m(camright, vec(camup).neg(), vec(camdir).neg(), p->o);
509         float aspect = p->tex->w/float(p->tex->h);
510         m.scale(size*aspect, size, 1);
511 
512         #define iconvert(vx,vy) do { \
513             gle::attrib(m.transform(vec2(vx, vy))); \
514             gle::attribf(0.5f*(vx) + 0.5f, 0.5f*(vy) + 0.5f); \
515             gle::attrib(color); \
516         } while(0)
517 
518         bvec4 color(p->color.r, p->color.g, p->color.b, uchar(p->blend*blend));
519         if(p->start > 0 || p->length < 1)
520         {
521             float sx = cosf((p->start + 0.25f)*2*M_PI), sy = -sinf((p->start + 0.25f)*2*M_PI),
522                   ex = cosf((p->end + 0.25f)*2*M_PI), ey = -sinf((p->end + 0.25f)*2*M_PI);
523             gle::end(); gle::begin(GL_TRIANGLE_FAN);
524             iconvert(0, 0);
525 
526             if(p->start < 0.125f || p->start >= 0.875f) iconvert(sx/sy, -1);
527             else if(p->start < 0.375f) iconvert(1, -sy/sx);
528             else if(p->start < 0.625f) iconvert(-sx/sy, 1);
529             else iconvert(-1, sy/sx);
530 
531             if(p->start <= 0.125f && p->end >= 0.125f) iconvert(1, -1);
532             if(p->start <= 0.375f && p->end >= 0.375f) iconvert(1, 1);
533             if(p->start <= 0.625f && p->end >= 0.625f) iconvert(-1, 1);
534             if(p->start <= 0.875f && p->end >= 0.875f) iconvert(-1, -1);
535 
536             if(p->end < 0.125f || p->end >= 0.875f) iconvert(ex/ey, -1);
537             else if(p->end < 0.375f) iconvert(1, -ey/ex);
538             else if(p->end < 0.625f) iconvert(-ex/ey, 1);
539             else iconvert(-1, ey/ex);
540             gle::end(); gle::begin(GL_QUADS);
541         }
542         else
543         {
544             iconvert( 1,  1);
545             iconvert(-1,  1);
546             iconvert(-1, -1);
547             iconvert( 1, -1);
548         }
549     }
550 
addiconiconrenderer551     icon *addicon(const vec &o, Texture *tex, int fade, int color, float size, float blend, float gravity, int collide, float start, float length, physent *pl = NULL)
552     {
553         icon *p = (icon *)listrenderer<icon>::addpart(o, vec(0, 0, 0), fade, color, size, blend, gravity, collide);
554         p->tex = tex;
555         p->start = start;
556         p->length = length;
557         p->end = p->start + p->length;
558         p->owner = pl;
559         return p;
560     }
561 
562     // use addicon() instead
addparticonrenderer563     particle *addpart(const vec &o, const vec &d, int fade, int color, float size, float blend = 1, float gravity = 0, int collide = 0, physent *pl = NULL) { return NULL; }
564 };
565 static iconrenderer icons;
566 
567 template<int T>
modifyblend(const vec & o,int & blend)568 static inline void modifyblend(const vec &o, int &blend)
569 {
570     blend = min(blend<<2, 255);
571 }
572 
573 template<>
modifyblend(const vec & o,int & blend)574 inline void modifyblend<PT_TAPE>(const vec &o, int &blend)
575 {
576 }
577 
578 template<int T>
genpos(const vec & o,const vec & d,float size,float gravity,int ts,partvert * vs)579 static inline void genpos(const vec &o, const vec &d, float size, float gravity, int ts, partvert *vs)
580 {
581     vec udir = vec(camup).sub(camright).mul(size);
582     vec vdir = vec(camup).add(camright).mul(size);
583     vs[0].pos = vec(o.x + udir.x, o.y + udir.y, o.z + udir.z);
584     vs[1].pos = vec(o.x + vdir.x, o.y + vdir.y, o.z + vdir.z);
585     vs[2].pos = vec(o.x - udir.x, o.y - udir.y, o.z - udir.z);
586     vs[3].pos = vec(o.x - vdir.x, o.y - vdir.y, o.z - vdir.z);
587 }
588 
589 template<>
genpos(const vec & o,const vec & d,float size,float gravity,int ts,partvert * vs)590 inline void genpos<PT_TAPE>(const vec &o, const vec &d, float size, float gravity, int ts, partvert *vs)
591 {
592     vec dir1 = vec(d).sub(o), dir2 = vec(d).sub(camera1->o), c;
593     c.cross(dir2, dir1).normalize().mul(size);
594     vs[0].pos = vec(d.x-c.x, d.y-c.y, d.z-c.z);
595     vs[1].pos = vec(o.x-c.x, o.y-c.y, o.z-c.z);
596     vs[2].pos = vec(o.x+c.x, o.y+c.y, o.z+c.z);
597     vs[3].pos = vec(d.x+c.x, d.y+c.y, d.z+c.z);
598 }
599 
600 template<>
genpos(const vec & o,const vec & d,float size,float gravity,int ts,partvert * vs)601 inline void genpos<PT_TRAIL>(const vec &o, const vec &d, float size, float gravity, int ts, partvert *vs)
602 {
603     vec e = d;
604     if(gravity) e.z -= float(ts)/gravity;
605     e.div(-75.0f).add(o);
606     genpos<PT_TAPE>(o, e, size, gravity, ts, vs);
607 }
608 
609 template<int T>
genrotpos(const vec & o,const vec & d,float size,float gravity,int ts,partvert * vs,int rot)610 static inline void genrotpos(const vec &o, const vec &d, float size, float gravity, int ts, partvert *vs, int rot)
611 {
612     genpos<T>(o, d, size, gravity, ts, vs);
613 }
614 
615 #define ROTCOEFFS(n) { \
616     vec2(-1,  1).rotate_around_z(n*2*M_PI/32.0f), \
617     vec2( 1,  1).rotate_around_z(n*2*M_PI/32.0f), \
618     vec2( 1, -1).rotate_around_z(n*2*M_PI/32.0f), \
619     vec2(-1, -1).rotate_around_z(n*2*M_PI/32.0f) \
620 }
621 static const vec2 rotcoeffs[32][4] =
622 {
623     ROTCOEFFS(0),  ROTCOEFFS(1),  ROTCOEFFS(2),  ROTCOEFFS(3),  ROTCOEFFS(4),  ROTCOEFFS(5),  ROTCOEFFS(6),  ROTCOEFFS(7),
624     ROTCOEFFS(8),  ROTCOEFFS(9),  ROTCOEFFS(10), ROTCOEFFS(11), ROTCOEFFS(12), ROTCOEFFS(13), ROTCOEFFS(14), ROTCOEFFS(15),
625     ROTCOEFFS(16), ROTCOEFFS(17), ROTCOEFFS(18), ROTCOEFFS(19), ROTCOEFFS(20), ROTCOEFFS(21), ROTCOEFFS(22), ROTCOEFFS(7),
626     ROTCOEFFS(24), ROTCOEFFS(25), ROTCOEFFS(26), ROTCOEFFS(27), ROTCOEFFS(28), ROTCOEFFS(29), ROTCOEFFS(30), ROTCOEFFS(31),
627 };
628 
629 template<>
genrotpos(const vec & o,const vec & d,float size,float gravity,int ts,partvert * vs,int rot)630 inline void genrotpos<PT_PART>(const vec &o, const vec &d, float size, float gravity, int ts, partvert *vs, int rot)
631 {
632     const vec2 *coeffs = rotcoeffs[rot];
633     vs[0].pos = vec(o).madd(camright, coeffs[0].x*size).madd(camup, coeffs[0].y*size);
634     vs[1].pos = vec(o).madd(camright, coeffs[1].x*size).madd(camup, coeffs[1].y*size);
635     vs[2].pos = vec(o).madd(camright, coeffs[2].x*size).madd(camup, coeffs[2].y*size);
636     vs[3].pos = vec(o).madd(camright, coeffs[3].x*size).madd(camup, coeffs[3].y*size);
637 }
638 
639 template<int T>
seedpos(particleemitter & pe,const vec & o,const vec & d,int fade,float size,float gravity)640 static inline void seedpos(particleemitter &pe, const vec &o, const vec &d, int fade, float size, float gravity)
641 {
642     if(gravity)
643     {
644         float t = fade;
645         vec end = vec(o).madd(d, t/5000.0f);
646         end.z -= t*t/(2.0f * 5000.0f * gravity);
647         pe.extendbb(end, size);
648 
649         float tpeak = d.z*gravity;
650         if(tpeak > 0 && tpeak < fade) pe.extendbb(o.z + 1.5f*d.z*tpeak/5000.0f, size);
651     }
652 }
653 
654 template<>
seedpos(particleemitter & pe,const vec & o,const vec & d,int fade,float size,float gravity)655 inline void seedpos<PT_TAPE>(particleemitter &pe, const vec &o, const vec &d, int fade, float size, float gravity)
656 {
657     pe.extendbb(d, size);
658 }
659 
660 template<>
seedpos(particleemitter & pe,const vec & o,const vec & d,int fade,float size,float gravity)661 inline void seedpos<PT_TRAIL>(particleemitter &pe, const vec &o, const vec &d, int fade, float size, float gravity)
662 {
663     vec e = d;
664     if(gravity) e.z -= float(fade)/gravity;
665     e.div(-75.0f).add(o);
666     pe.extendbb(e, size);
667 }
668 
669 template<int T>
670 struct varenderer : partrenderer
671 {
672     partvert *verts;
673     particle *parts;
674     int maxparts, numparts, lastupdate, rndmask;
675     GLuint vbo;
676 
varenderervarenderer677     varenderer(const char *texname, int type, int texclamp = 3)
678         : partrenderer(texname, texclamp, type),
679           verts(NULL), parts(NULL), maxparts(0), numparts(0), lastupdate(-1), rndmask(0), vbo(0)
680     {
681         if(type & PT_HFLIP) rndmask |= 0x01;
682         if(type & PT_VFLIP) rndmask |= 0x02;
683         if(type & PT_ROT) rndmask |= 0x1F<<2;
684         if(type & PT_RND4) rndmask |= 0x03<<5;
685     }
686 
cleanupvarenderer687     void cleanup()
688     {
689         if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; }
690     }
691 
initvarenderer692     void init(int n)
693     {
694         DELETEA(parts);
695         DELETEA(verts);
696         parts = new particle[n];
697         verts = new partvert[n*4];
698         maxparts = n;
699         numparts = 0;
700         lastupdate = -1;
701     }
702 
resetvarenderer703     void reset()
704     {
705         numparts = 0;
706         lastupdate = -1;
707     }
708 
resettrackedvarenderer709     void resettracked(physent *owner)
710     {
711         loopi(numparts)
712         {
713             particle *p = parts+i;
714             if(!owner || (p->owner == owner)) p->fade = -1;
715         }
716         lastupdate = -1;
717     }
718 
countvarenderer719     int count()
720     {
721         return numparts;
722     }
723 
hasworkvarenderer724     bool haswork()
725     {
726         return (numparts > 0);
727     }
728 
addpartvarenderer729     particle *addpart(const vec &o, const vec &d, int fade, int color, float size, float blend = 1, float gravity = 0, int collide = 0, physent *pl = NULL)
730     {
731         particle *p = parts + (numparts < maxparts ? numparts++ : rnd(maxparts)); //next free slot, or kill a random kitten
732         p->o = o;
733         p->d = d;
734         p->m = vec(0, 0, 0);
735         p->fade = fade;
736         p->millis = lastmillis + emitoffset;
737         p->color = bvec::hexcolor(color);
738         p->blend = blend;
739         p->size = size;
740         p->gravity = gravity;
741         p->collide = collide;
742         p->owner = pl;
743         p->flags = 0x80 | (rndmask ? rnd(0x80) & rndmask : 0);
744         p->wind.reset();
745         lastupdate = -1;
746         return p;
747     }
748 
seedemittervarenderer749     void seedemitter(particleemitter &pe, const vec &o, const vec &d, int fade, float size, float gravity)
750     {
751         pe.maxfade = max(pe.maxfade, fade);
752         size *= SQRT2;
753         pe.extendbb(o, size);
754 
755         seedpos<T>(pe, o, d, fade, size, gravity);
756         #if 0
757         vec end(o);
758         float secs = fade/5000.0f;
759         vec v = vec(d).mul(secs);
760         if(gravity)
761         {
762             static struct particleent : physent
763             {
764                 particleent()
765                 {
766                     physent::reset();
767                     type = ENT_DUMMY;
768                 }
769             } p;
770             p.weight = gravity;
771             v.z -= physics::gravityvel(&p)*secs;
772             v.add(v);
773         }
774         end.add(v);
775         pe.extendbb(end, size);
776         if(!gravity) return;
777         #endif
778         float t = fade;
779         vec end = vec(o).madd(d, t/5000.0f);
780         end.z -= t*t/(2.0f * 5000.0f * gravity);
781         pe.extendbb(end, size);
782         float tpeak = d.z*gravity;
783         if(tpeak > 0 && tpeak < fade) pe.extendbb(o.z + 1.5f*d.z*tpeak/5000.0f, size);
784     }
785 
genvertsvarenderer786     void genverts(particle *p, partvert *vs, bool regen)
787     {
788         int blend = 255, ts = 1;
789         float size = 1;
790         calc(p, blend, ts, size);
791         if(blend <= 1 || p->fade <= 5) p->fade = -1; //mark to remove on next pass (i.e. after render)
792 
793         modifyblend<T>(p->o, blend);
794 
795         if(regen)
796         {
797             p->flags &= ~0x80;
798 
799             #define SETTEXCOORDS(u1c, u2c, v1c, v2c, body) \
800             { \
801                 float u1 = u1c, u2 = u2c, v1 = v1c, v2 = v2c; \
802                 body; \
803                 vs[0].tc = vec2(u1, v1); \
804                 vs[1].tc = vec2(u2, v1); \
805                 vs[2].tc = vec2(u2, v2); \
806                 vs[3].tc = vec2(u1, v2); \
807             }
808             if(type&PT_RND4)
809             {
810                 float tx = 0.5f*((p->flags>>5)&1), ty = 0.5f*((p->flags>>6)&1);
811                 SETTEXCOORDS(tx, tx + 0.5f, ty, ty + 0.5f,
812                 {
813                     if(p->flags&0x01) swap(u1, u2);
814                     if(p->flags&0x02) swap(v1, v2);
815                 });
816 
817             }
818             else if(type&PT_ICON)
819             {
820                 float tx = 0.25f*(p->flags&3), ty = 0.25f*((p->flags>>2)&3);
821                 SETTEXCOORDS(tx, tx + 0.25f, ty, ty + 0.25f, {});
822             }
823             else SETTEXCOORDS(0, 1, 0, 1, {});
824 
825             #define SETCOLOR(r, g, b, a) \
826             do { \
827                 bvec4 col(r, g, b, a); \
828                 loopi(4) vs[i].color = col; \
829             } while(0)
830             #define SETMODCOLOR SETCOLOR((p->color.r*blend)>>8, (p->color.g*blend)>>8, (p->color.b*blend)>>8, uchar(p->blend*255))
831             if(type&PT_MOD) SETMODCOLOR;
832             else SETCOLOR(p->color.r, p->color.g, p->color.b, uchar(p->blend*blend));
833         }
834         else if(type&PT_MOD) SETMODCOLOR;
835         else loopi(4) vs[i].color.a = uchar(p->blend*blend);
836 
837         if(type&PT_ROT) genrotpos<T>(p->o, p->d, size, ts, p->gravity, vs, (p->flags>>2)&0x1F);
838         else genpos<T>(p->o, p->d, size, p->gravity, ts, vs);
839     }
840 
genvertsvarenderer841     void genverts()
842     {
843         loopi(numparts)
844         {
845             particle *p = &parts[i];
846             partvert *vs = &verts[i*4];
847             if(p->fade < 0)
848             {
849                 do
850                 {
851                     --numparts;
852                     if(numparts <= i) return;
853                 }
854                 while(parts[numparts].fade < 0);
855                 *p = parts[numparts];
856                 genverts(p, vs, true);
857             }
858             else genverts(p, vs, (p->flags&0x80)!=0);
859         }
860     }
861 
genvbovarenderer862     void genvbo()
863     {
864         if(lastmillis == lastupdate && vbo) return;
865         lastupdate = lastmillis;
866 
867         genverts();
868 
869         if(!vbo) glGenBuffers_(1, &vbo);
870         gle::bindvbo(vbo);
871         glBufferData_(GL_ARRAY_BUFFER, maxparts*4*sizeof(partvert), NULL, GL_STREAM_DRAW);
872         glBufferSubData_(GL_ARRAY_BUFFER, 0, numparts*4*sizeof(partvert), verts);
873         gle::clearvbo();
874     }
875 
rendervarenderer876     void render()
877     {
878         genvbo();
879 
880         glBindTexture(GL_TEXTURE_2D, tex->id);
881 
882         gle::bindvbo(vbo);
883         const partvert *ptr = 0;
884         gle::vertexpointer(sizeof(partvert), ptr->pos.v);
885         gle::texcoord0pointer(sizeof(partvert), ptr->tc.v);
886         gle::colorpointer(sizeof(partvert), ptr->color.v);
887         gle::enablevertex();
888         gle::enabletexcoord0();
889         gle::enablecolor();
890         gle::enablequads();
891 
892         gle::drawquads(0, numparts);
893 
894         gle::disablequads();
895         gle::disablevertex();
896         gle::disabletexcoord0();
897         gle::disablecolor();
898         gle::clearvbo();
899     }
900 };
901 
902 typedef varenderer<PT_PART> quadrenderer;
903 typedef varenderer<PT_TAPE> taperenderer;
904 typedef varenderer<PT_TRAIL> trailrenderer;
905 
906 #include "explosion.h"
907 #include "lensflare.h"
908 #include "lightning.h"
909 
910 struct softquadrenderer : quadrenderer
911 {
softquadrenderersoftquadrenderer912     softquadrenderer(const char *texname, int type)
913         : quadrenderer(texname, type|PT_SOFT)
914     {
915     }
916 };
917 
918 struct lineprimitive : listparticle<lineprimitive>
919 {
920     vec value;
921 };
922 
923 struct lineprimitiverenderer : listrenderer<lineprimitive>
924 {
lineprimitiverendererlineprimitiverenderer925     lineprimitiverenderer(int type = 0)
926         : listrenderer<lineprimitive>(type|PT_LINE|PT_LERP)
927     {}
928 
startrenderlineprimitiverenderer929     void startrender()
930     {
931         glDisable(GL_CULL_FACE);
932         gle::defvertex();
933         gle::defcolor(4, GL_UNSIGNED_BYTE);
934         gle::begin(GL_LINES);
935     }
936 
endrenderlineprimitiverenderer937     void endrender()
938     {
939         gle::end();
940         glEnable(GL_CULL_FACE);
941     }
942 
renderpartlineprimitiverenderer943     void renderpart(lineprimitive *p, int blend, int ts, float size)
944     {
945         glBindTexture(GL_TEXTURE_2D, blanktexture->id);
946         bvec4 color(p->color.r, p->color.g, p->color.b, uchar(p->blend*blend));
947         gle::attrib(p->o);
948             gle::attrib(color);
949         gle::attrib(vec(p->value).mul(size).add(p->o));
950             gle::attrib(color);
951     }
952 
addlinelineprimitiverenderer953     lineprimitive *addline(const vec &o, const vec &v, int fade, int color, float size, float blend)
954     {
955         lineprimitive *p = (lineprimitive *)listrenderer<lineprimitive>::addpart(o, vec(0, 0, 0), fade, color, size, blend);
956         p->value = vec(v).sub(o).div(size);
957         return p;
958     }
959 
960     // use addline() instead
addpartlineprimitiverenderer961     particle *addpart(const vec &o, const vec &d, int fade, int color, float size, float blend = 1, float gravity = 0, int collide = 0, physent *pl = NULL) { return NULL; }
962 };
963 static lineprimitiverenderer lineprimitives, lineontopprimitives(PT_ONTOP);
964 
965 struct trisprimitive : listparticle<trisprimitive>
966 {
967     vec value[2];
968     bool fill;
969 };
970 
971 struct trisprimitiverenderer : listrenderer<trisprimitive>
972 {
trisprimitiverenderertrisprimitiverenderer973     trisprimitiverenderer(int type = 0)
974         : listrenderer<trisprimitive>(type|PT_TRIANGLE|PT_LERP)
975     {}
976 
startrendertrisprimitiverenderer977     void startrender()
978     {
979         glDisable(GL_CULL_FACE);
980         gle::defvertex();
981         gle::defcolor(4, GL_UNSIGNED_BYTE);
982         gle::begin(GL_TRIANGLES);
983     }
984 
endrendertrisprimitiverenderer985     void endrender()
986     {
987         gle::end();
988         glEnable(GL_CULL_FACE);
989     }
990 
renderparttrisprimitiverenderer991     void renderpart(trisprimitive *p, int blend, int ts, float size)
992     {
993         glBindTexture(GL_TEXTURE_2D, blanktexture->id);
994         bvec4 color(p->color.r, p->color.g, p->color.b, uchar(p->blend*blend));
995         if(!p->fill) { gle::end(); gle::begin(GL_LINE_LOOP); }
996         gle::attrib(p->o);
997             gle::attrib(color);
998         gle::attrib(vec(p->value[0]).mul(size).add(p->o));
999             gle::attrib(color);
1000         gle::attrib(vec(p->value[1]).mul(size).add(p->o));
1001             gle::attrib(color);
1002         if(!p->fill) { gle::end(); gle::begin(GL_TRIANGLES); }
1003     }
1004 
addtriangletrisprimitiverenderer1005     trisprimitive *addtriangle(const vec &o, float yaw, float pitch, int fade, int color, float size, float blend, bool fill)
1006     {
1007         vec dir[3];
1008         vecfromyawpitch(yaw, pitch, 1, 0, dir[0]);
1009         vecfromyawpitch(yaw, pitch, -1, 1, dir[1]);
1010         vecfromyawpitch(yaw, pitch, -1, -1, dir[2]);
1011 
1012         trisprimitive *p = (trisprimitive *)listrenderer<trisprimitive>::addpart(dir[0].mul(size*2).add(o), vec(0, 0, 0), fade, color, size, blend);
1013         p->value[0] = dir[1];
1014         p->value[1] = dir[2];
1015         p->fill = fill;
1016         return p;
1017     }
1018 
1019     // use addtriangle() instead
addparttrisprimitiverenderer1020     particle *addpart(const vec &o, const vec &d, int fade, int color, float size, float blend = 1, float gravity = 0, int collide = 0, physent *pl = NULL) { return NULL; }
1021 };
1022 static trisprimitiverenderer trisprimitives, trisontopprimitives(PT_ONTOP);
1023 
1024 struct loopprimitive : listparticle<loopprimitive>
1025 {
1026     vec value;
1027     int axis;
1028     bool fill;
1029 };
1030 
1031 struct loopprimitiverenderer : listrenderer<loopprimitive>
1032 {
loopprimitiverendererloopprimitiverenderer1033     loopprimitiverenderer(int type = 0)
1034         : listrenderer<loopprimitive>(type|PT_ELLIPSE|PT_LERP)
1035     {}
1036 
startrenderloopprimitiverenderer1037     void startrender()
1038     {
1039         glDisable(GL_CULL_FACE);
1040         gle::defvertex();
1041     }
1042 
endrenderloopprimitiverenderer1043     void endrender()
1044     {
1045         glEnable(GL_CULL_FACE);
1046     }
1047 
renderpartloopprimitiverenderer1048     void renderpart(loopprimitive *p, int blend, int ts, float size)
1049     {
1050         glBindTexture(GL_TEXTURE_2D, blanktexture->id);
1051         gle::colorub(p->color.r, p->color.g, p->color.b, uchar(p->blend*blend));
1052         gle::begin(p->fill ? GL_TRIANGLE_FAN : GL_LINE_LOOP);
1053         loopi(15 + (p->fill ? 1 : 0))
1054         {
1055             const vec2 &sc = sincos360[i*(360/15)];
1056             vec v;
1057             switch(p->axis)
1058             {
1059                 case 0:
1060                     v = vec(p->value.x*sc.x, 0, p->value.z*sc.y);
1061                     break;
1062                 case 1:
1063                     v = vec(0, p->value.y*sc.y, p->value.z*sc.x);
1064                     break;
1065                 case 2: default:
1066                     v = vec(-p->value.y*sc.y, p->value.x*sc.x, 0);
1067                     break;
1068             }
1069             gle::attrib(v.mul(size).add(p->o));
1070         }
1071         gle::end();
1072     }
1073 
addellipseloopprimitiverenderer1074     loopprimitive *addellipse(const vec &o, const vec &v, int fade, int color, float size, float blend, int axis, bool fill)
1075     {
1076         loopprimitive *p = (loopprimitive *)listrenderer<loopprimitive>::addpart(o, vec(0, 0, 0), fade, color, size, blend);
1077         p->value = vec(v).div(size);
1078         p->axis = axis;
1079         p->fill = fill;
1080         return p;
1081     }
1082 
1083     // use addellipse() instead
addpartloopprimitiverenderer1084     particle *addpart(const vec &o, const vec &d, int fade, int color, float size, float blend = 1, float gravity = 0, int collide = 0, physent *pl = NULL) { return NULL; }
1085 };
1086 static loopprimitiverenderer loopprimitives, loopontopprimitives(PT_ONTOP);
1087 
1088 struct coneprimitive : listparticle<coneprimitive>
1089 {
1090     vec dir, spot, spoke;
1091     float radius, angle;
1092     bool fill;
1093     int spokenum;
1094 };
1095 
1096 struct coneprimitiverenderer : listrenderer<coneprimitive>
1097 {
coneprimitiverendererconeprimitiverenderer1098     coneprimitiverenderer(int type = 0)
1099         : listrenderer<coneprimitive>(type|PT_CONE|PT_LERP)
1100     {}
1101 
startrenderconeprimitiverenderer1102     void startrender()
1103     {
1104         glDisable(GL_CULL_FACE);
1105         gle::defvertex();
1106     }
1107 
endrenderconeprimitiverenderer1108     void endrender()
1109     {
1110         glEnable(GL_CULL_FACE);
1111     }
1112 
renderpartconeprimitiverenderer1113     void renderpart(coneprimitive *p, int blend, int ts, float size)
1114     {
1115         glBindTexture(GL_TEXTURE_2D, blanktexture->id);
1116         gle::colorub(p->color.r, p->color.g, p->color.b, uchar(p->blend*blend));
1117 
1118         gle::begin(GL_LINES);
1119         loopi(p->spokenum)
1120         {
1121             gle::attrib(p->o);
1122             gle::attrib(vec(p->spoke).rotate(sincos360[i*(360/p->spokenum)], p->dir).add(p->spot).mul(size).add(p->o));
1123         }
1124         gle::end();
1125 
1126         gle::begin(GL_LINE_LOOP);
1127         loopi(p->spokenum)
1128         {
1129             gle::attrib(vec(p->spoke).rotate(sincos360[i*(360/p->spokenum)], p->dir).add(p->spot).mul(size).add(p->o));
1130         }
1131         gle::end();
1132     }
1133 
addconeconeprimitiverenderer1134     coneprimitive *addcone(const vec &o, const vec &dir, float radius, float angle, int fade, int color, float size, float blend, bool fill, int spokenum = 15)
1135     {
1136         coneprimitive *p = (coneprimitive *)listrenderer<coneprimitive>::addpart(o, vec(0, 0, 0), fade, color, size, blend);
1137         p->dir = dir;
1138         p->radius = radius;
1139         p->angle = angle;
1140         p->fill = fill;
1141         p->spot = vec(p->dir).mul(p->radius*cosf(p->angle*RAD));
1142         p->spoke.orthogonal(p->dir);
1143         p->spoke.normalize().mul(p->radius*sinf(p->angle*RAD));
1144         p->spokenum = clamp(spokenum, 3, 359);
1145         return p;
1146     }
1147 
1148     // use addcone() instead
addpartconeprimitiverenderer1149     particle *addpart(const vec &o, const vec &d, int fade, int color, float size, float blend = 1, float gravity = 0, int collide = 0, physent *pl = NULL) { return NULL; }
1150 };
1151 static coneprimitiverenderer coneprimitives, coneontopprimitives(PT_ONTOP);
1152 
1153 static partrenderer *parts[] =
1154 {
1155     new portalrenderer("<grey>particles/teleport"), &icons,
1156     &lineprimitives, &lineontopprimitives, &trisprimitives, &trisontopprimitives,
1157     &loopprimitives, &loopontopprimitives, &coneprimitives, &coneontopprimitives,
1158     new softquadrenderer("<grey>particles/fire", PT_PART|PT_BRIGHT|PT_RND4|PT_FLIP|PT_SHRINK|PT_WIND),
1159     new softquadrenderer("<grey>particles/plasma", PT_PART|PT_BRIGHT|PT_FLIP|PT_SHRINK|PT_WIND),
1160     new taperenderer("<grey>particles/sflare", PT_TAPE|PT_BRIGHT|PT_FEW),
1161     new taperenderer("<grey>particles/mflare", PT_TAPE|PT_BRIGHT|PT_RND4|PT_VFLIP|PT_FEW),
1162     new softquadrenderer("<grey>particles/smoke", PT_PART|PT_LERP|PT_FLIP|PT_SHRINK|PT_WIND),
1163     new quadrenderer("<grey>particles/smoke", PT_PART|PT_LERP|PT_FLIP|PT_SHRINK|PT_WIND),
1164     new softquadrenderer("<grey>particles/hint", PT_PART|PT_BRIGHT),
1165     new quadrenderer("<grey>particles/hint", PT_PART|PT_BRIGHT),
1166     new softquadrenderer("<grey>particles/hint_bold", PT_PART|PT_BRIGHT),
1167     new quadrenderer("<grey>particles/hint_bold", PT_PART|PT_BRIGHT),
1168     new softquadrenderer("<grey>particles/hint_vert", PT_PART|PT_BRIGHT),
1169     new quadrenderer("<grey><rotate:1>particles/hint_vert", PT_PART|PT_BRIGHT),
1170     new softquadrenderer("<grey><rotate:1>particles/hint_vert", PT_PART|PT_BRIGHT),
1171     new quadrenderer("<grey>particles/hint_vert", PT_PART|PT_BRIGHT),
1172     new softquadrenderer("<grey>particles/smoke", PT_PART|PT_FLIP|PT_SHRINK|PT_WIND),
1173     new quadrenderer("<grey>particles/smoke", PT_PART|PT_FLIP|PT_SHRINK|PT_WIND),
1174     new softquadrenderer("<grey>particles/hint", PT_PART|PT_BRIGHT),
1175     new quadrenderer("<grey>particles/hint", PT_PART|PT_BRIGHT),
1176     new softquadrenderer("<grey>particles/hint_bold", PT_PART|PT_BRIGHT),
1177     new quadrenderer("<grey>particles/hint_bold", PT_PART|PT_BRIGHT),
1178     new softquadrenderer("<grey>particles/hint_vert", PT_PART|PT_BRIGHT),
1179     new quadrenderer("<grey>particles/hint_vert", PT_PART|PT_BRIGHT),
1180     new softquadrenderer("<grey><rotate:1>particles/hint_vert", PT_PART|PT_BRIGHT),
1181     new quadrenderer("<grey><rotate:1>particles/hint_vert", PT_PART|PT_BRIGHT),
1182     new quadrenderer("<grey>particles/blood", PT_PART|PT_MOD|PT_RND4|PT_FLIP),
1183     new quadrenderer("<grey>particles/entity", PT_PART|PT_BRIGHT),
1184     new quadrenderer("<grey>particles/entity", PT_PART|PT_BRIGHT|PT_ONTOP),
1185     new quadrenderer("<grey>particles/spark", PT_PART|PT_BRIGHT|PT_RND4|PT_FLIP|PT_SHRINK),
1186     new softquadrenderer("<grey>particles/fire", PT_PART|PT_BRIGHT|PT_RND4|PT_FLIP|PT_SHRINK|PT_WIND),
1187     new quadrenderer("<grey>particles/fire", PT_PART|PT_BRIGHT|PT_RND4|PT_FLIP|PT_SHRINK|PT_WIND),
1188     new softquadrenderer("<grey>particles/plasma", PT_PART|PT_BRIGHT|PT_FLIP|PT_SHRINK|PT_WIND),
1189     new quadrenderer("<grey>particles/plasma", PT_PART|PT_BRIGHT|PT_FLIP|PT_SHRINK|PT_WIND),
1190     new softquadrenderer("<grey>particles/electric", PT_PART|PT_BRIGHT|PT_RND4|PT_FLIP|PT_SHRINK|PT_WIND),
1191     new quadrenderer("<grey>particles/electric", PT_PART|PT_BRIGHT|PT_RND4|PT_FLIP|PT_SHRINK|PT_WIND),
1192     new softquadrenderer("<grey>particles/eleczap", PT_PART|PT_BRIGHT|PT_RND4|PT_FLIP|PT_SHRINK|PT_WIND),
1193     new quadrenderer("<grey>particles/eleczap", PT_PART|PT_BRIGHT|PT_RND4|PT_FLIP|PT_SHRINK|PT_WIND),
1194     new quadrenderer("<grey>particles/fire", PT_PART|PT_BRIGHT|PT_FLIP|PT_RND4|PT_BRIGHT|PT_SHRINK|PT_WIND),
1195     new taperenderer("<grey>particles/sflare", PT_TAPE|PT_BRIGHT),
1196     new taperenderer("<grey>particles/mflare", PT_TAPE|PT_BRIGHT|PT_RND4|PT_VFLIP|PT_BRIGHT),
1197     new taperenderer("<grey>particles/lightning", PT_TAPE|PT_BRIGHT|PT_HFLIP|PT_VFLIP, 2), // uses same clamp setting as normal lightning to avoid conflict
1198     new taperenderer("<grey>particles/lightzap", PT_TAPE|PT_BRIGHT|PT_HFLIP|PT_VFLIP, 2),
1199     new quadrenderer("<grey>particles/muzzle", PT_PART|PT_BRIGHT|PT_RND4|PT_FLIP),
1200     new quadrenderer("<grey>particles/snow", PT_PART|PT_BRIGHT|PT_FLIP|PT_WIND),
1201     &texts, &textontop,
1202     &explosions, &shockwaves, &shockballs, &glimmerballs, &lightnings, &lightzaps,
1203     &flares // must be done last!
1204 };
1205 
1206 VARF(IDF_PERSIST, maxparticles, 10, 4000, 10000, initparticles());
1207 VARF(IDF_PERSIST, fewparticles, 10, 100, 10000, initparticles());
1208 
1209 int partsorder[sizeof(parts)/sizeof(parts[0])];
1210 
orderparts()1211 void orderparts()
1212 {
1213     int j = 0, n = sizeof(parts)/sizeof(parts[0]);
1214     loopi(n) if(!(parts[i]->type&PT_LERP)) partsorder[j++] = i;
1215     loopi(n) if(parts[i]->type&PT_LERP) partsorder[j++] = i;
1216 }
1217 
initparticles()1218 void initparticles()
1219 {
1220     if(initing) return;
1221     if(!particleshader) particleshader = lookupshaderbyname("particle");
1222     if(!particlenotextureshader) particlenotextureshader = lookupshaderbyname("particlenotexture");
1223     if(!particlesoftshader) particlesoftshader = lookupshaderbyname("particlesoft");
1224     if(!particletextshader) particletextshader = lookupshaderbyname("particletext");
1225     loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->init(parts[i]->type&PT_FEW ? min(fewparticles, maxparticles) : maxparticles);
1226     loopi(sizeof(parts)/sizeof(parts[0]))
1227     {
1228         loadprogress = float(i+1)/(sizeof(parts)/sizeof(parts[0]));
1229         parts[i]->preload();
1230     }
1231     loadprogress = 0;
1232     orderparts();
1233 }
1234 
clearparticles()1235 void clearparticles()
1236 {
1237     loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->reset();
1238     clearparticleemitters();
1239 }
1240 
cleanupparticles()1241 void cleanupparticles()
1242 {
1243     loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->cleanup();
1244 }
1245 
removetrackedparticles(physent * owner)1246 void removetrackedparticles(physent *owner)
1247 {
1248     loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->resettracked(owner);
1249 }
1250 
1251 VARN(0, debugparticles, dbgparts, 0, 0, 1);
1252 
debugparticles()1253 void debugparticles()
1254 {
1255     if(!dbgparts) return;
1256     int n = sizeof(parts)/sizeof(parts[0]);
1257     pushhudmatrix();
1258     hudmatrix.ortho(0, FONTH*n*2*vieww/float(viewh), FONTH*n*2, 0, -1, 1); // squeeze into top-left corner
1259     flushhudmatrix();
1260     loopi(n) draw_text(parts[i]->info, FONTH, (i+n/2)*FONTH);
1261     pophudmatrix();
1262 }
1263 
renderparticles(int layer)1264 void renderparticles(int layer)
1265 {
1266     timer *parttimer = begintimer("Particles", false);
1267 
1268     canstep = layer != PL_UNDER;
1269 
1270     //want to debug BEFORE the lastpass render (that would delete particles)
1271     if(dbgparts && (layer == PL_ALL || layer == PL_UNDER)) loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->debuginfo();
1272 
1273     bool rendered = false;
1274     uint lastflags = PT_LERP|PT_SHADER,
1275          flagmask = PT_LERP|PT_MOD|PT_ONTOP|PT_BRIGHT|PT_NOTEX|PT_SOFT|PT_SHADER,
1276          excludemask = layer == PL_ALL ? ~0 : (layer != PL_NOLAYER ? PT_NOLAYER : 0);
1277 
1278     loopi(sizeof(parts)/sizeof(parts[0]))
1279     {
1280         partrenderer *p = parts[partsorder[i]];
1281         if((p->type&PT_NOLAYER) == excludemask || !p->haswork()) continue;
1282 
1283         if(!rendered)
1284         {
1285             rendered = true;
1286             glDepthMask(GL_FALSE);
1287             glEnable(GL_BLEND);
1288             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1289 
1290             glActiveTexture_(GL_TEXTURE2);
1291             if(msaalight) glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, msdepthtex);
1292             else glBindTexture(GL_TEXTURE_RECTANGLE, gdepthtex);
1293             glActiveTexture_(GL_TEXTURE0);
1294         }
1295 
1296         uint flags = p->type & flagmask, changedbits = (flags ^ lastflags);
1297         if(changedbits)
1298         {
1299             if(changedbits&PT_LERP) { if(flags&PT_LERP) resetfogcolor(); else zerofogcolor(); }
1300             if(changedbits&(PT_LERP|PT_MOD))
1301             {
1302                 if(flags&PT_LERP) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1303                 else if(flags&PT_MOD) glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
1304                 else glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1305             }
1306             if(!(flags&PT_SHADER))
1307             {
1308                 if(changedbits&(PT_LERP|PT_SOFT|PT_NOTEX|PT_SHADER))
1309                 {
1310                     if(flags&PT_SOFT && softparticles)
1311                     {
1312                         particlesoftshader->set();
1313                         LOCALPARAMF(softparams, -1.0f/softparticleblend, 0, 0);
1314                     }
1315                     else if(flags&PT_NOTEX) particlenotextureshader->set();
1316                     else particleshader->set();
1317                 }
1318                 if(changedbits&(PT_MOD|PT_BRIGHT|PT_SOFT|PT_NOTEX|PT_SHADER))
1319                 {
1320                     float colorscale = flags&PT_MOD ? 1 : ldrscale;
1321                     if(flags&PT_BRIGHT) colorscale *= particlebright;
1322                     LOCALPARAMF(colorscale, colorscale, colorscale, colorscale, 1);
1323                 }
1324             }
1325             if(changedbits&PT_ONTOP)
1326             {
1327                 if(flags&PT_ONTOP) glDisable(GL_DEPTH_TEST);
1328                 else glEnable(GL_DEPTH_TEST);
1329             }
1330             lastflags = flags;
1331         }
1332         p->render();
1333     }
1334 
1335     if(rendered)
1336     {
1337         if(lastflags&(PT_LERP|PT_MOD)) glBlendFunc(GL_SRC_ALPHA, GL_ONE);
1338         if(!(lastflags&PT_LERP)) resetfogcolor();
1339         if(lastflags&PT_ONTOP) glEnable(GL_DEPTH_TEST);
1340         glDisable(GL_BLEND);
1341         glDepthMask(GL_TRUE);
1342     }
1343 
1344     endtimer(parttimer);
1345 }
1346 
1347 static int addedparticles = 0;
1348 
newparticle(const vec & o,const vec & d,int fade,int type,int color,float size,float blend,float gravity,int collide,physent * pl)1349 particle *newparticle(const vec &o, const vec &d, int fade, int type, int color, float size, float blend, float gravity, int collide, physent *pl)
1350 {
1351     static particle dummy;
1352     if(seedemitter)
1353     {
1354         parts[type]->seedemitter(*seedemitter, o, d, fade, size, gravity);
1355         return &dummy;
1356     }
1357     if(fade + emitoffset < 0) return &dummy;
1358     addedparticles++;
1359     return parts[type]->addpart(o, d, fade, color, size, blend, gravity, collide, pl);
1360 }
1361 
create(int type,int color,int fade,const vec & p,float size,float blend,float gravity,int collide,physent * pl)1362 void create(int type, int color, int fade, const vec &p, float size, float blend, float gravity, int collide, physent *pl)
1363 {
1364     if(camera1->o.dist(p) > maxparticledistance) return;
1365     float collidez = collide ? p.z - raycube(p, vec(0, 0, -1), collide >= 0 ? COLLIDERADIUS : max(p.z, 0.0f), RAY_CLIPMAT) + (collide >= 0 ? COLLIDEERROR : 0) : -1;
1366     int fmin = 1;
1367     int fmax = fade*3;
1368     int f = fmin + rnd(fmax); //help deallocater by using fade distribution rather than random
1369     newparticle(p, vec(0, 0, 0), f, type, color, size, blend, gravity, collide, pl)->val = collidez;
1370 }
1371 
regularcreate(int type,int color,int fade,const vec & p,float size,float blend,float gravity,int collide,physent * pl,int delay)1372 void regularcreate(int type, int color, int fade, const vec &p, float size, float blend, float gravity, int collide, physent *pl, int delay)
1373 {
1374     if(!canemitparticles() || (delay > 0 && rnd(delay) != 0)) return;
1375     create(type, color, fade, p, size, blend, gravity, collide, pl);
1376 }
1377 
1378 VAR(IDF_PERSIST, maxparticledistance, 256, 1024, 4096);
1379 
splash(int type,int color,float radius,int num,int fade,const vec & p,float size,float blend,float gravity,int collide,float vel)1380 void splash(int type, int color, float radius, int num, int fade, const vec &p, float size, float blend, float gravity, int collide, float vel)
1381 {
1382     if(camera1->o.dist(p) > maxparticledistance && !seedemitter) return;
1383 #if 0
1384     float collidez = collide ? p.z - raycube(p, vec(0, 0, -1), COLLIDERADIUS, RAY_CLIPMAT) + (collide >= 0 ? COLLIDEERROR : 0) : -1;
1385     int fmin = 1;
1386     int fmax = fade*3;
1387     loopi(num)
1388     {
1389         vec tmp(rnd(max(int(ceilf(radius*2)),1))-radius, rnd(max(int(ceilf(radius*2)),1))-radius, rnd(max(int(ceilf(radius*2)),1))-radius);
1390         int f = (num < 10) ? (fmin + rnd(fmax)) : (fmax - (i*(fmax-fmin))/(num-1)); //help deallocater by using fade distribution rather than random
1391         newparticle(p, tmp, f, type, color, size, blend, gravity, collide)->val = collidez;
1392     }
1393 #endif
1394     regularshape(type, radius, color, 21, num, fade, p, size, blend, gravity, collide, vel);
1395 }
1396 
regularsplash(int type,int color,float radius,int num,int fade,const vec & p,float size,float blend,float gravity,int collide,float vel,int delay)1397 void regularsplash(int type, int color, float radius, int num, int fade, const vec &p, float size, float blend, float gravity, int collide, float vel, int delay)
1398 {
1399     if(!canemitparticles() || (delay > 0 && rnd(delay) != 0)) return;
1400     splash(type, color, radius, num, fade, p, size, blend, gravity, collide, vel);
1401 }
1402 
canaddparticles()1403 bool canaddparticles()
1404 {
1405     return !minimized || renderunfocused;
1406 }
1407 
regular_part_create(int type,int fade,const vec & p,int color,float size,float blend,float gravity,int collide,physent * pl,int delay)1408 void regular_part_create(int type, int fade, const vec &p, int color, float size, float blend, float gravity, int collide, physent *pl, int delay)
1409 {
1410     if(!canaddparticles()) return;
1411     regularcreate(type, color, fade, p, size, blend, gravity, collide, pl, delay);
1412 }
1413 
part_create(int type,int fade,const vec & p,int color,float size,float blend,float gravity,int collide,physent * pl)1414 void part_create(int type, int fade, const vec &p, int color, float size, float blend, float gravity, int collide, physent *pl)
1415 {
1416     if(!canaddparticles()) return;
1417     create(type, color, fade, p, size, blend, gravity, collide, pl);
1418 }
1419 
regular_part_splash(int type,int num,int fade,const vec & p,int color,float size,float blend,float gravity,int collide,float radius,float vel,int delay)1420 void regular_part_splash(int type, int num, int fade, const vec &p, int color, float size, float blend, float gravity, int collide, float radius, float vel, int delay)
1421 {
1422     if(!canaddparticles()) return;
1423     regularsplash(type, color, radius, num, fade, p, size, blend, gravity, collide, vel, delay);
1424 }
1425 
part_splash(int type,int num,int fade,const vec & p,int color,float size,float blend,float gravity,int collide,float radius,float vel)1426 void part_splash(int type, int num, int fade, const vec &p, int color, float size, float blend, float gravity, int collide, float radius, float vel)
1427 {
1428     if(!canaddparticles()) return;
1429     splash(type, color, radius, num, fade, p, size, blend, gravity, collide, vel);
1430 }
1431 
1432 VAR(IDF_PERSIST, maxparticletrail, 256, 512, VAR_MAX);
1433 
part_trail(int type,int fade,const vec & s,const vec & e,int color,float size,float blend,float gravity,int collide)1434 void part_trail(int type, int fade, const vec &s, const vec &e, int color, float size, float blend, float gravity, int collide)
1435 {
1436     if(!canaddparticles()) return;
1437     vec v;
1438     float d = e.dist(s, v);
1439     int steps = clamp(int(d*2), 1, maxparticletrail);
1440     v.div(steps);
1441     vec p = s;
1442     loopi(steps)
1443     {
1444         p.add(v);
1445         vec tmp = vec(float(rnd(11)-5), float(rnd(11)-5), float(rnd(11)-5));
1446         newparticle(p, tmp, rnd(fade)+fade, type, color, size, blend, gravity, collide);
1447     }
1448 }
1449 
1450 VAR(IDF_PERSIST, particletext, 0, 1, 1);
1451 VAR(IDF_PERSIST, maxparticletextdistance, 0, 512, 10000);
1452 
part_text(const vec & s,const char * t,int type,int fade,int color,float size,float blend,float gravity,int collide,physent * pl)1453 void part_text(const vec &s, const char *t, int type, int fade, int color, float size, float blend, float gravity, int collide, physent *pl)
1454 {
1455     if(!canaddparticles() || !t[0]) return;
1456     if(!particletext || camera1->o.dist(s) > maxparticletextdistance) return;
1457     particle *p = newparticle(s, vec(0, 0, 0), fade, type, color, size, blend, gravity, collide, pl);
1458     p->text = t;
1459 }
1460 
part_textcopy(const vec & s,const char * t,int type,int fade,int color,float size,float blend,float gravity,int collide,physent * pl)1461 void part_textcopy(const vec &s, const char *t, int type, int fade, int color, float size, float blend, float gravity, int collide, physent *pl)
1462 {
1463     if(!canaddparticles() || !t[0]) return;
1464     if(!particletext || camera1->o.dist(s) > maxparticletextdistance) return;
1465     particle *p = newparticle(s, vec(0, 0, 0), fade, type, color, size, blend, gravity, collide, pl);
1466     p->text = newstring(t);
1467     p->flags = 1;
1468 }
1469 
part_flare(const vec & p,const vec & dest,int fade,int type,int color,float size,float blend,float gravity,int collide,physent * pl)1470 void part_flare(const vec &p, const vec &dest, int fade, int type, int color, float size, float blend, float gravity, int collide, physent *pl)
1471 {
1472     if(!canaddparticles()) return;
1473     newparticle(p, dest, fade, type, color, size, blend, gravity, collide, pl);
1474 }
1475 
part_explosion(const vec & dest,float maxsize,int type,int fade,int color,float size,float blend,float gravity,int collide)1476 void part_explosion(const vec &dest, float maxsize, int type, int fade, int color, float size, float blend, float gravity, int collide)
1477 {
1478     if(!canaddparticles()) return;
1479     float growth = maxsize - size;
1480     if(fade < 0) fade = int(growth*25);
1481     newparticle(dest, vec(0, 0, 0), fade, type, color, size, blend, gravity, collide)->val = growth;
1482 }
1483 
regular_part_explosion(const vec & dest,float maxsize,int type,int fade,int color,float size,float blend,float gravity,int collide)1484 void regular_part_explosion(const vec &dest, float maxsize, int type, int fade, int color, float size, float blend, float gravity, int collide)
1485 {
1486     if(!canaddparticles() || !canemitparticles()) return;
1487     part_explosion(dest, maxsize, type, fade, color, size, blend, gravity, collide);
1488 }
1489 
part_spawn(const vec & o,const vec & v,float z,uchar type,int amt,int fade,int color,float size,float blend,float gravity,int collide)1490 void part_spawn(const vec &o, const vec &v, float z, uchar type, int amt, int fade, int color, float size, float blend, float gravity, int collide)
1491 {
1492     if(!canaddparticles()) return;
1493     loopi(amt)
1494     {
1495         vec w(rnd(int(v.x*2))-int(v.x), rnd(int(v.y*2))-int(v.y), rnd(int(v.z*2))-int(v.z)+z);
1496         w.add(o);
1497         part_splash(type, 1, fade, w, color, size, blend, gravity, collide, 1, 1);
1498     }
1499 }
1500 
part_flares(const vec & o,const vec & v,float z1,const vec & d,const vec & w,float z2,uchar type,int amt,int fade,int color,float size,float blend,float gravity,int collide,physent * pl)1501 void part_flares(const vec &o, const vec &v, float z1, const vec &d, const vec &w, float z2, uchar type, int amt, int fade, int color, float size, float blend, float gravity, int collide, physent *pl)
1502 {
1503     if(!canaddparticles()) return;
1504     loopi(amt)
1505     {
1506         vec from(rnd(int(v.x*2))-int(v.x), rnd(int(v.y*2))-int(v.y), rnd(int(v.z*2))-int(v.z)+z1);
1507         from.add(o);
1508 
1509         vec to(rnd(int(w.x*2))-int(w.x), rnd(int(w.y*2))-int(w.y), rnd(int(w.z*2))-int(w.z)+z1);
1510         to.add(d);
1511 
1512         newparticle(from, to, fade, type, color, size, blend, gravity, collide, pl);
1513     }
1514 }
1515 
part_portal(const vec & o,float size,float blend,float yaw,float pitch,int type,int fade,int color)1516 void part_portal(const vec &o, float size, float blend, float yaw, float pitch, int type, int fade, int color)
1517 {
1518     if(!canaddparticles() || (parts[type]->type&PT_TYPE) != PT_PORTAL) return;
1519     portalrenderer *p = (portalrenderer *)parts[type];
1520     p->addportal(o, fade, color, size, blend, yaw, pitch);
1521 }
1522 
1523 VAR(IDF_PERSIST, particleicon, 0, 1, 1);
1524 VAR(IDF_PERSIST, maxparticleicondistance, 0, 512, 10000);
1525 
part_icon(const vec & o,Texture * tex,float size,float blend,float gravity,int collide,int fade,int color,float start,float length,physent * pl)1526 void part_icon(const vec &o, Texture *tex, float size, float blend, float gravity, int collide, int fade, int color, float start, float length, physent *pl)
1527 {
1528     if(!canaddparticles() || (parts[PART_ICON]->type&PT_TYPE) != PT_ICON) return;
1529     if(!particleicon || camera1->o.dist(o) > maxparticleicondistance) return;
1530     iconrenderer *p = (iconrenderer *)parts[PART_ICON];
1531     p->addicon(o, tex, fade, color, size, blend, gravity, collide, start, length, pl);
1532 }
1533 
part_line(const vec & o,const vec & v,float size,float blend,int fade,int color,int type)1534 void part_line(const vec &o, const vec &v, float size, float blend, int fade, int color, int type)
1535 {
1536     if(!canaddparticles() || (parts[type]->type&PT_TYPE) != PT_LINE) return;
1537     lineprimitiverenderer *p = (lineprimitiverenderer *)parts[type];
1538     p->addline(o, v, fade, color, size, blend);
1539 }
1540 
part_triangle(const vec & o,float yaw,float pitch,float size,float blend,int fade,int color,bool fill,int type)1541 void part_triangle(const vec &o, float yaw, float pitch, float size, float blend, int fade, int color, bool fill, int type)
1542 {
1543     if(!canaddparticles() || (parts[type]->type&PT_TYPE) != PT_TRIANGLE) return;
1544     trisprimitiverenderer *p = (trisprimitiverenderer *)parts[type];
1545     p->addtriangle(o, yaw, pitch, fade, color, size, blend, fill);
1546 }
1547 
part_dir(const vec & o,float yaw,float pitch,float length,float size,float blend,int fade,int color,int interval,bool fill)1548 void part_dir(const vec &o, float yaw, float pitch, float length, float size, float blend, int fade, int color, int interval, bool fill)
1549 {
1550     if(!canaddparticles()) return;
1551 
1552     vec v(yaw*RAD, pitch*RAD);
1553     part_line(o, vec(v).mul(length).add(o), size, blend, fade, color);
1554     if(interval)
1555     {
1556         int count = int(length/float(interval));
1557         vec q = o;
1558         loopi(count)
1559         {
1560             q.add(vec(v).mul(interval));
1561             part_triangle(q, yaw, pitch, size, blend, fade, color, fill);
1562         }
1563     }
1564     part_triangle(vec(v).mul(length-size*2).add(o), yaw, pitch, size, blend, fade, color, fill);
1565 }
1566 
part_trace(const vec & o,const vec & v,float size,float blend,int fade,int color,int interval,bool fill)1567 void part_trace(const vec &o, const vec &v, float size, float blend, int fade, int color, int interval, bool fill)
1568 {
1569     part_line(o, v, size, blend, fade, color);
1570     float yaw, pitch;
1571     vec dir = vec(v).sub(o).normalize();
1572     vectoyawpitch(dir, yaw, pitch);
1573     if(interval)
1574     {
1575         int count = int(v.dist(o)/float(interval));
1576         vec q = o;
1577         loopi(count)
1578         {
1579             q.add(vec(dir).mul(interval));
1580             part_triangle(q, yaw, pitch, size, blend, fade, color, fill);
1581         }
1582     }
1583     part_triangle(vec(v).sub(vec(dir).mul(size*2)), yaw, pitch, size, blend, fade, color, fill);
1584 }
1585 
part_ellipse(const vec & o,const vec & v,float size,float blend,int fade,int color,int axis,bool fill,int type)1586 void part_ellipse(const vec &o, const vec &v, float size, float blend, int fade, int color, int axis, bool fill, int type)
1587 {
1588     if(!canaddparticles() || (parts[type]->type&PT_TYPE) != PT_ELLIPSE) return;
1589     loopprimitiverenderer *p = (loopprimitiverenderer *)parts[type];
1590     p->addellipse(o, v, fade, color, size, blend, axis, fill);
1591 }
1592 
part_radius(const vec & o,const vec & v,float size,float blend,int fade,int color,bool fill)1593 void part_radius(const vec &o, const vec &v, float size, float blend, int fade, int color, bool fill)
1594 {
1595     if(!canaddparticles() || (parts[PART_ELLIPSE]->type&PT_TYPE) != PT_ELLIPSE) return;
1596     loopprimitiverenderer *p = (loopprimitiverenderer *)parts[PART_ELLIPSE];
1597     p->addellipse(o, v, fade, color, size, blend, 0, fill);
1598     p->addellipse(o, v, fade, color, size, blend, 1, fill);
1599     p->addellipse(o, v, fade, color, size, blend, 2, fill);
1600 }
1601 
part_cone(const vec & o,const vec & dir,float radius,float angle,float size,float blend,int fade,int color,bool fill,int spokenum,int type)1602 void part_cone(const vec &o, const vec &dir, float radius, float angle, float size, float blend, int fade, int color, bool fill, int spokenum, int type)
1603 {
1604     if(!canaddparticles() || (parts[type]->type&PT_TYPE) != PT_CONE) return;
1605     coneprimitiverenderer *p = (coneprimitiverenderer *)parts[type];
1606     p->addcone(o, dir, radius, angle, fade, color, size, blend, fill, spokenum);
1607 }
1608 
1609 //dir = 0..6 where 0=up
offsetvec(vec o,int dir,int dist)1610 static inline vec offsetvec(vec o, int dir, int dist)
1611 {
1612     vec v = vec(o);
1613     v[(2+dir)%3] += (dir>2)?(-dist):dist;
1614     return v;
1615 }
1616 
1617 /* Experiments in shapes...
1618  * dir: (where dir%3 is similar to offsetvec with 0=up)
1619  * 0..2 circle
1620  * 3.. 5 cylinder shell
1621  * 6..11 cone shell
1622  * 12..14 plane volume
1623  * 15..20 line volume, i.e. wall
1624  * 21 sphere
1625  * 24..26 flat plane
1626  * +32 to inverse direction
1627  */
createshape(int type,float radius,int color,int dir,int num,int fade,const vec & p,float size,float blend,float gravity,int collide,float vel)1628  void createshape(int type, float radius, int color, int dir, int num, int fade, const vec &p, float size, float blend, float gravity, int collide, float vel)
1629 {
1630     int basetype = parts[type]->type&PT_TYPE;
1631     bool flare = (basetype == PT_TAPE) || (basetype == PT_LIGHTNING),
1632          inv = (dir&0x20)!=0, taper = (dir&0x40)!=0 && !seedemitter;
1633     dir &= 0x1F;
1634     loopi(num)
1635     {
1636         vec to, from = p;
1637         if(dir < 12)
1638         {
1639             const vec2 &sc = sincos360[rnd(360)];
1640             to[dir%3] = sc.y*radius;
1641             to[(dir+1)%3] = sc.x*radius;
1642             to[(dir+2)%3] = 0.0f;
1643             to.add(p);
1644             if(dir < 3) // circle
1645                 from = p;
1646             else if(dir < 6) // cylinder
1647             {
1648                 from = to;
1649                 to = vec(p).sub(from).rescale(radius).add(from);
1650             }
1651             else // cone
1652             {
1653                 from = p;
1654                 to[(dir+2)%3] += (dir < 9)?radius:(-radius);
1655             }
1656         }
1657         else if(dir < 15) // plane
1658         {
1659             to[dir%3] = float(rnd(int(ceilf(radius))<<4)-(int(ceilf(radius))<<3))/8.0;
1660             to[(dir+1)%3] = float(rnd(int(ceilf(radius))<<4)-(int(ceilf(radius))<<3))/8.0;
1661             to[(dir+2)%3] = radius;
1662             to.add(p);
1663             from = to;
1664             from[(dir+2)%3] -= 2*radius;
1665         }
1666         else if(dir < 21) // line
1667         {
1668             if(dir < 18)
1669             {
1670                 to[dir%3] = float(rnd(int(ceilf(radius))<<4)-(int(ceilf(radius))<<3))/8.0;
1671                 to[(dir+1)%3] = 0.0;
1672             }
1673             else
1674             {
1675                 to[dir%3] = 0.0;
1676                 to[(dir+1)%3] = float(rnd(int(ceilf(radius))<<4)-(int(ceilf(radius))<<3))/8.0;
1677             }
1678             to[(dir+2)%3] = 0.0;
1679             to.add(p);
1680             from = to;
1681             to[(dir+2)%3] += radius;
1682         }
1683         else if(dir < 24) // sphere
1684         {
1685             to = vec(2*M_PI*float(rnd(1000))/1000.0, M_PI*float(rnd(1000)-500)/1000.0).mul(radius);
1686             to.add(p);
1687             from = p;
1688         }
1689         else if(dir < 27) // flat plane
1690         {
1691             to[dir%3] = float(rndscale(2*radius)-radius);
1692             to[(dir+1)%3] = float(rndscale(2*radius)-radius);
1693             to[(dir+2)%3] = 0.0;
1694             to.add(p);
1695             from = to;
1696         }
1697         else to = p;
1698 
1699         if(inv) swap(from, to);
1700 
1701         if(taper)
1702         {
1703             float dist = clamp(from.dist2(camera1->o)/maxparticledistance, 0.0f, 1.0f);
1704             if(dist > 0.2f)
1705             {
1706                 dist = 1 - (dist - 0.2f)/0.8f;
1707                 if(rnd(0x10000) > dist*dist*0xFFFF) continue;
1708             }
1709         }
1710 
1711         if(flare)
1712             newparticle(from, to, rnd(fade*3)+1, type, color, size, blend, gravity, collide);
1713         else
1714         {
1715             vec d = vec(from).sub(to).rescale(vel);
1716             particle *np = newparticle(from, d, rnd(fade*3)+1, type, color, size, blend, gravity, collide);
1717             if(np->collide)
1718                 np->val = from.z - raycube(from, vec(0, 0, -1), np->collide >= 0 ? COLLIDERADIUS : max(from.z, 0.0f), RAY_CLIPMAT) + (np->collide >= 0 ? COLLIDEERROR : 0);
1719         }
1720     }
1721 }
1722 
regularshape(int type,float radius,int color,int dir,int num,int fade,const vec & p,float size,float blend,float gravity,int collide,float vel)1723 void regularshape(int type, float radius, int color, int dir, int num, int fade, const vec &p, float size, float blend, float gravity, int collide, float vel)
1724 {
1725     if(!canemitparticles()) return;
1726     createshape(type, radius, color, dir, num, fade, p, size, blend, gravity, collide, vel);
1727 }
1728 
regularflame(int type,const vec & p,float radius,float height,int color,int density,int fade,float size,float blend,float gravity,int collide,float vel)1729 void regularflame(int type, const vec &p, float radius, float height, int color, int density, int fade, float size, float blend, float gravity, int collide, float vel)
1730 {
1731     if(!canemitparticles()) return;
1732 
1733     float s = size*min(radius, height);
1734     vec v(0, 0, min(1.0f, height)*vel);
1735     float collidez = collide ? p.z - raycube(p, vec(0, 0, -1), collide >= 0 ? COLLIDERADIUS : max(p.z, 0.0f), RAY_CLIPMAT) + (collide >= 0 ? COLLIDEERROR : 0) : -1;
1736     loopi(density)
1737     {
1738         vec q = vec(p).add(vec(rndscale(radius*2.f)-radius, rndscale(radius*2.f)-radius, 0));
1739         newparticle(q, v, rnd(max(int(fade*height), 1))+1, type, color, s, blend, gravity, collide)->val = collidez;
1740     }
1741 }
1742 
partcolour(int c,int p,int x)1743 static int partcolour(int c, int p, int x)
1744 {
1745     if(c <= 0) c = 0xFFFFFF;
1746     if(p || x)
1747     {
1748         vec r(1, 1, 1);
1749         if(c > 0) r = vec::fromcolor(c);
1750         r.mul(game::getpalette(p, x));
1751         return (int(r.x*255)<<16)|(int(r.y*255)<<8)|(int(r.z*255));
1752     }
1753     return c;
1754 }
1755 
makeparticle(const vec & o,attrvector & attr)1756 void makeparticle(const vec &o, attrvector &attr)
1757 {
1758     bool oldemit = canemit;
1759     if(attr[11]) canemit = true;
1760     switch(attr[0])
1761     {
1762         case 0: //fire
1763         {
1764             float radius = attr[1] ? float(attr[1])/100.0f : 1.5f,
1765                   height = attr[2] ? float(attr[2])/100.0f : radius*3,
1766                   size = attr[7] ? float(attr[7])/100.f : 2.f,
1767                   blend = attr[8] ? float(attr[8])/100.f : 1.f,
1768                   vel = attr[10] ? float(attr[10]) : 30.f;
1769             int fade = attr[4] > 0 ? attr[4] : 1000, gravity = attr[9] ? attr[9] : -10;
1770             regularflame(PART_FLAME, o, radius, height, partcolour(attr[3] ? attr[3] : 0xF05010, attr[5], attr[6]), 3, fade/2, size, blend, gravity/2, 0, vel);
1771             regularflame(PART_SMOKE, vec(o).addz(2.f*min(radius, height)), radius, height, 0x101008, 1, fade, size, blend, gravity, 0, vel);
1772             break;
1773         }
1774         case 1: //smoke vent - <dir>
1775             regularsplash(PART_SMOKE, 0x897661, 2, 1, 200, offsetvec(o, attr[1], rnd(10)), 2.4f, 1, 0);
1776             break;
1777         case 2: //water fountain - <dir>
1778         {
1779             int mat = MAT_WATER + clamp(-attr[2], 0, 3);
1780             const bvec &wfcol = getwaterfallcolour(mat);
1781             int color = (int(wfcol[0])<<16) | (int(wfcol[1])<<8) | int(wfcol[2]);
1782             if(!color)
1783             {
1784                 const bvec &wcol = getwatercolour(mat);
1785                 color = (int(wcol[0])<<16) | (int(wcol[1])<<8) | int(wcol[2]);
1786             }
1787             regularsplash(PART_SPARK, color, 10, 4, 200, offsetvec(o, attr[1], rnd(10)), 0.6f, 1, 0);
1788             break;
1789         }
1790         case 3: //fire ball - <size> <rgb> <type> <blend>
1791         {
1792             int types[4] = { PART_EXPLOSION, PART_SHOCKWAVE, PART_SHOCKBALL, PART_GLIMMERY },
1793                 type = types[attr[3] >= 0 && attr[3] <= 3 ? attr[3] : 0];
1794             float blend = attr[4] > 0 && attr[4] < 100 ? attr[4]/100.f : 1.f;
1795             newparticle(o, vec(0, 0, 0), 1, type, partcolour(attr[2], attr[3], attr[4]), 4.f, blend, 0)->val = 1+attr[1];
1796             break;
1797         }
1798         case 4:  //tape - <dir> <length> <rgb>
1799         case 7:  //lightning
1800         case 8:  //fire
1801         case 9:  //smoke
1802         case 10: //water
1803         case 11: //plasma
1804         case 12: //snow
1805         case 13: //sparks
1806         {
1807             const int typemap[] = { PART_FLARE, -1, -1, PART_LIGHTNING, PART_FIREBALL, PART_SMOKE, PART_ELECTRIC, PART_PLASMA, PART_SNOW, PART_SPARK };
1808             const float sizemap[] = { 0.28f, 0.0f, 0.0f, 0.25f, 4.f, 2.f, 0.6f, 4.f, 0.5f, 0.2f };
1809             int type = typemap[attr[0]-4], fade = attr[4] > 0 ? attr[4] : 250,
1810                 gravity = attr[0] > 7 ? attr[7] : 0,
1811                 stain = attr[0] > 7 ? (attr[6] > 0 && attr[6] <= STAIN_MAX ? attr[6] : -1) : 0,
1812                 colour = attr[0] > 7 ? partcolour(attr[3], attr[9], attr[10]) : partcolour(attr[3], attr[6], attr[7]);
1813             float size = attr[5] != 0 ? attr[5]/100.f : sizemap[attr[0]-4],
1814                   vel = attr[0] > 7 ? attr[8] : 1;
1815             if(attr[1] >= 256) regularshape(type, max(1+attr[2], 1), colour, attr[1]-256, 5, fade, o, size, 1, gravity, stain, vel);
1816             else newparticle(o, vec(offsetvec(attr[0] > 7 ? vec(0, 0, 0) : o, attr[1], max(1+attr[2], 0))).mul(vel), fade, type, colour, size, 1, gravity, stain);
1817             break;
1818         }
1819         case 14: // flames <radius> <height> <rgb>
1820         case 15: // smoke plume
1821         {
1822             const int typemap[] = { PART_FLAME, PART_SMOKE }, fademap[] = { 500, 1000 }, densitymap[] = { 3, 1 }, gravitymap[] = { -5, -10 };
1823             const float sizemap[] = { 2, 2 }, velmap[] = { 25, 50 };
1824             int type = typemap[attr[0]-14], density = densitymap[attr[0]-14];
1825             regularflame(type, o, float(attr[1])/100.0f, float(attr[2])/100.0f, attr[3], density, attr[4] > 0 ? attr[4] : fademap[attr[0]-14], attr[5] != 0 ? attr[5]/100.f : sizemap[attr[0]-14], 1, attr[6] != 0 ? attr[6] : gravitymap[attr[0]-14], 0, attr[7] != 0 ? attr[7] : velmap[attr[0]-14]);
1826             break;
1827         }
1828         case 6: //meter, metervs - <percent> <rgb> <rgb2>
1829         {
1830             float length = clamp(attr[1], 0, 100)/100.f;
1831             part_icon(o, textureload(hud::progresstex, 3), 2, 1, 0, 0, 1, partcolour(attr[3], attr[6], attr[7]), length, 1-length); // fall through
1832         }
1833         case 5:
1834         {
1835             float length = clamp(attr[1], 0, 100)/100.f;
1836             int colour = partcolour(attr[2], attr[4], attr[5]);
1837             part_icon(o, textureload(hud::progringtex, 3), 3, 1, 0, 0, 1, colour, (totalmillis%1000)/1000.f, 0.1f);
1838             part_icon(o, textureload(hud::progresstex, 3), 3, 1, 0, 0, 1, colour, 0, length);
1839             break;
1840         }
1841         case 32: //lens flares - plain/sparkle/sun/sparklesun <red> <green> <blue>
1842         case 33:
1843         case 34:
1844         case 35:
1845             flares.addflare(o, attr[1], attr[2], attr[3], (attr[0]&2)!=0, (attr[0]&1)!=0 ? ((attr[0]&2)!=0 ? 1 : 2) : 0);
1846             break;
1847         default:
1848             defformatstring(ds, "%d?", attr[0]);
1849             part_textcopy(o, ds);
1850             break;
1851     }
1852     canemit = oldemit;
1853 }
1854 
seedparticles()1855 void seedparticles()
1856 {
1857     progress(0, "Seeding particles...");
1858     addparticleemitters();
1859     canemit = true;
1860     loopv(emitters)
1861     {
1862         particleemitter &pe = emitters[i];
1863         extentity &e = *pe.ent;
1864         seedemitter = &pe;
1865         for(int millis = 0; millis < seedmillis; millis += min(emitmillis, seedmillis/10))
1866             if(entities::checkparticle(e))
1867                 makeparticle(e.o, e.attrs);
1868         seedemitter = NULL;
1869         pe.lastemit = -seedmillis;
1870         pe.finalize();
1871     }
1872 }
1873 
updateparticles()1874 void updateparticles()
1875 {
1876     if(regenemitters) addparticleemitters();
1877 
1878     if(minimized && !renderunfocused) { canemit = false; return; }
1879 
1880     if(lastmillis - lastemitframe >= emitmillis)
1881     {
1882         canemit = true;
1883         lastemitframe = lastmillis - (lastmillis%emitmillis);
1884     }
1885     else canemit = false;
1886 
1887     loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->update();
1888 
1889     entities::drawparticles();
1890     flares.drawflares(); // do after drawparticles so that we can make flares for them too
1891 
1892     if(!editmode || showparticles)
1893     {
1894         int emitted = 0, replayed = 0;
1895         addedparticles = 0;
1896         loopv(emitters)
1897         {
1898             particleemitter &pe = emitters[i];
1899             extentity &e = *pe.ent;
1900             if(!entities::checkparticle(e) || e.o.dist(camera1->o) > maxparticledistance) { pe.lastemit = lastmillis; continue; }
1901             if(cullparticles && pe.maxfade >= 0)
1902             {
1903                 if(isfoggedsphere(pe.radius, pe.center)) { pe.lastcull = lastmillis; continue; }
1904                 if(pvsoccluded(pe.cullmin, pe.cullmax)) { pe.lastcull = lastmillis; continue; }
1905             }
1906             makeparticle(e.o, e.attrs);
1907             emitted++;
1908             if(replayparticles && pe.maxfade > 5 && pe.lastcull > pe.lastemit)
1909             {
1910                 for(emitoffset = max(pe.lastemit + emitmillis - lastmillis, -pe.maxfade); emitoffset < 0; emitoffset += emitmillis)
1911                 {
1912                     makeparticle(e.o, e.attrs);
1913                     replayed++;
1914                 }
1915                 emitoffset = 0;
1916             }
1917             pe.lastemit = lastmillis;
1918         }
1919         if(dbgpcull && (canemit || replayed) && addedparticles) conoutf("\fr%d emitters, %d particles", emitted, addedparticles);
1920     }
1921 }
1922