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