Initial Comit: First commit.
[SauerEngine.git] / src / engine / renderparticles.cpp
blobd321ad3b1029563fa9214fd92c1bc763ce6b03fc
1 // renderparticles.cpp
3 #include "pch.h"
4 #include "engine.h"
5 #include "rendertarget.h"
7 Shader *particleshader = NULL, *particlenotextureshader = NULL;
9 VARP(particlesize, 20, 100, 500);
11 // Check emit_particles() to limit the rate that paricles can be emitted for models/sparklies
12 // Automatically stops particles being emitted when paused or in reflective drawing
13 VARP(emitmillis, 1, 17, 1000);
14 static int lastemitframe = 0;
15 static bool emit = false;
17 static bool emit_particles()
19 if(reflecting || refracting) return false;
20 return emit;
23 enum
25 PT_PART = 0,
26 PT_TAPE,
27 PT_TRAIL,
28 PT_TEXT,
29 PT_TEXTUP,
30 PT_METER,
31 PT_METERVS,
32 PT_FIREBALL,
33 PT_LIGHTNING,
34 PT_FLARE,
36 PT_MOD = 1<<8,
37 PT_RND4 = 1<<9,
38 PT_LERP = 1<<10, // use very sparingly - order of blending issues
39 PT_TRACK = 1<<11,
40 PT_GLARE = 1<<12,
43 const char *partnames[] = { "part", "tape", "trail", "text", "textup", "meter", "metervs", "fireball", "lightning", "flare" };
45 struct particle
47 vec o, d;
48 int fade, millis;
49 bvec color;
50 uchar flags;
51 float size;
52 union
54 const char *text; // will call delete[] on this only if it starts with an @
55 float val;
56 physent *owner;
57 };
60 struct partvert
62 vec pos;
63 float u, v;
64 bvec color;
65 uchar alpha;
68 #define COLLIDERADIUS 8.0f
69 #define COLLIDEERROR 1.0f
71 struct partrenderer
73 Texture *tex;
74 const char *texname;
75 uint type;
76 int grav, collide;
78 partrenderer(const char *texname, int type, int grav, int collide)
79 : tex(NULL), texname(texname), type(type), grav(grav), collide(collide)
82 virtual ~partrenderer()
86 virtual void init(int n) { }
87 virtual void reset() = NULL;
88 virtual void resettracked(physent *owner) { }
89 virtual particle *addpart(const vec &o, const vec &d, int fade, int color, float size) = NULL;
90 virtual void update() { }
91 virtual void render() = NULL;
92 virtual bool haswork() = NULL;
93 virtual int count() = NULL; //for debug
94 virtual bool usesvertexarray() { return false; }
95 virtual void cleanup() {}
97 //blend = 0 => remove it
98 void calc(particle *p, int &blend, int &ts, vec &o, vec &d, bool lastpass = true)
100 o = p->o;
101 d = p->d;
102 if(type&PT_TRACK && p->owner) cl->particletrack(p->owner, o, d);
103 if(p->fade <= 5)
105 ts = 1;
106 blend = 255;
108 else
110 ts = lastmillis-p->millis;
111 blend = max(255 - (ts<<8)/p->fade, 0);
112 if(grav)
114 if(ts > p->fade) ts = p->fade;
115 float t = (float)(ts);
116 vec v = d;
117 v.mul(t/5000.0f);
118 o.add(v);
119 o.z -= t*t/(2.0f * 5000.0f * grav);
121 if(collide && o.z < p->val && lastpass)
123 vec surface;
124 float floorz = rayfloor(vec(o.x, o.y, p->val), surface, RAY_CLIPMAT, COLLIDERADIUS);
125 float collidez = floorz<0 ? o.z-COLLIDERADIUS : p->val - rayfloor(vec(o.x, o.y, p->val), surface, RAY_CLIPMAT, COLLIDERADIUS);
126 if(o.z >= collidez+COLLIDEERROR)
127 p->val = collidez+COLLIDEERROR;
128 else
130 adddecal(collide, vec(o.x, o.y, collidez), vec(p->o).sub(o).normalize(), 2*p->size, p->color, type&PT_RND4 ? detrnd((size_t)p, 4) : 0);
131 blend = 0;
138 struct listparticle : particle
140 listparticle *next;
143 static listparticle *parempty = NULL;
145 VARP(outlinemeters, 0, 0, 1);
147 struct listrenderer : partrenderer
149 listparticle *list;
151 listrenderer(const char *texname, int type, int grav, int collide)
152 : partrenderer(texname, type, grav, collide), list(NULL)
156 virtual ~listrenderer()
160 virtual void cleanup(listparticle *p)
164 void reset()
166 if(!list) return;
167 listparticle *p = list;
168 for(;;)
170 cleanup(p);
171 if(p->next) p = p->next;
172 else break;
174 p->next = parempty;
175 parempty = list;
176 list = NULL;
179 void resettracked(physent *owner)
181 if(!(type&PT_TRACK)) return;
182 for(listparticle **prev = &list, *cur = list; cur; cur = *prev)
184 if(!owner || cur->owner==owner)
186 *prev = cur->next;
187 cur->next = parempty;
188 parempty = cur;
190 else prev = &cur->next;
194 particle *addpart(const vec &o, const vec &d, int fade, int color, float size)
196 if(!parempty)
198 listparticle *ps = new listparticle[256];
199 loopi(255) ps[i].next = &ps[i+1];
200 ps[255].next = parempty;
201 parempty = ps;
203 listparticle *p = parempty;
204 parempty = p->next;
205 p->next = list;
206 list = p;
207 p->o = o;
208 p->d = d;
209 p->fade = fade;
210 p->millis = lastmillis;
211 p->color = bvec(color>>16, (color>>8)&0xFF, color&0xFF);
212 p->size = size;
213 p->owner = NULL;
214 return p;
217 int count()
219 int num = 0;
220 listparticle *lp;
221 for(lp = list; lp; lp = lp->next) num++;
222 return num;
225 bool haswork()
227 return (list != NULL);
230 virtual void startrender() = 0;
231 virtual void endrender() = 0;
232 virtual void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts, uchar *color) = 0;
234 void render()
236 startrender();
237 if(texname)
239 if(!tex) tex = textureload(texname);
240 glBindTexture(GL_TEXTURE_2D, tex->id);
243 bool lastpass = !reflecting && !refracting;
245 for(listparticle **prev = &list, *p = list; p; p = *prev)
247 vec o, d;
248 int blend, ts;
249 calc(p, blend, ts, o, d, lastpass);
250 if(blend > 0)
252 renderpart(p, o, d, blend, ts, p->color.v);
254 if(p->fade > 5 || !lastpass)
256 prev = &p->next;
257 continue;
260 //remove
261 *prev = p->next;
262 p->next = parempty;
263 cleanup(p);
264 parempty = p;
267 endrender();
271 struct meterrenderer : listrenderer
273 meterrenderer(int type)
274 : listrenderer(NULL, type, 0, 0)
277 void startrender()
279 glDisable(GL_BLEND);
280 glDisable(GL_TEXTURE_2D);
281 particlenotextureshader->set();
284 void endrender()
286 glEnable(GL_BLEND);
287 glEnable(GL_TEXTURE_2D);
288 if(fogging && renderpath!=R_FIXEDFUNCTION) setfogplane(1, reflectz);
289 particleshader->set();
292 void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts, uchar *color)
294 int basetype = type&0xFF;
296 glPushMatrix();
297 glTranslatef(o.x, o.y, o.z);
298 if(fogging && renderpath!=R_FIXEDFUNCTION) setfogplane(0, reflectz - o.z, true);
299 glRotatef(camera1->yaw-180, 0, 0, 1);
300 glRotatef(camera1->pitch-90, 1, 0, 0);
302 float scale = p->size/80.0f;
303 glScalef(-scale, scale, -scale);
305 float right = 8*FONTH, left = p->val*right;
306 glTranslatef(-right/2.0f, 0, 0);
307 glColor3ubv(color);
308 glBegin(GL_TRIANGLE_STRIP);
309 loopk(10)
311 float c = -0.5f*sinf(k/9.0f*M_PI), s = 0.5f + 0.5f*cosf(k/9.0f*M_PI);
312 glVertex2f(left - c*FONTH, s*FONTH);
313 glVertex2f(c*FONTH, s*FONTH);
315 glEnd();
317 if(basetype==PT_METERVS) glColor3ub(color[2], color[1], color[0]); //swap r<->b
318 else glColor3f(0, 0, 0);
319 glBegin(GL_TRIANGLE_STRIP);
320 loopk(10)
322 float c = 0.5f*sinf(k/9.0f*M_PI), s = 0.5f - 0.5f*cosf(k/9.0f*M_PI);
323 glVertex2f(left + c*FONTH, s*FONTH);
324 glVertex2f(right + c*FONTH, s*FONTH);
326 glEnd();
328 if(outlinemeters)
330 glColor3f(0, 0.8f, 0);
331 glBegin(GL_LINE_LOOP);
332 loopk(10)
334 float c = -0.5f*sinf(k/9.0f*M_PI), s = 0.5f + 0.5f*cosf(k/9.0f*M_PI);
335 glVertex2f(c*FONTH, s*FONTH);
337 loopk(10)
339 float c = 0.5f*sinf(k/9.0f*M_PI), s = 0.5f - 0.5f*cosf(k/9.0f*M_PI);
340 glVertex2f(right + c*FONTH, s*FONTH);
342 glEnd();
344 glBegin(GL_LINE_STRIP);
345 loopk(10)
347 float c = 0.5f*sinf(k/9.0f*M_PI), s = 0.5f - 0.5f*cosf(k/9.0f*M_PI);
348 glVertex2f(left + c*FONTH, s*FONTH);
350 glEnd();
353 glPopMatrix();
356 static meterrenderer meters(PT_METER|PT_LERP), metervs(PT_METERVS|PT_LERP);
358 struct textrenderer : listrenderer
360 textrenderer(int type, int grav = 0)
361 : listrenderer(NULL, type, grav, 0)
364 void startrender()
368 void endrender()
370 if(fogging && renderpath!=R_FIXEDFUNCTION) setfogplane(1, reflectz);
373 void cleanup(listparticle *p)
375 if(p->text && p->text[0]=='@') delete[] p->text;
378 void renderpart(listparticle *p, const vec &o, const vec &d, int blend, int ts, uchar *color)
380 glPushMatrix();
381 glTranslatef(o.x, o.y, o.z);
382 if(fogging)
384 if(renderpath!=R_FIXEDFUNCTION) setfogplane(0, reflectz - o.z, true);
385 else blend = (uchar)(blend * max(0.0f, min(1.0f, 1.0f - (reflectz - o.z)/waterfog)));
388 glRotatef(camera1->yaw-180, 0, 0, 1);
389 glRotatef(camera1->pitch-90, 1, 0, 0);
391 float scale = p->size/80.0f;
392 glScalef(-scale, scale, -scale);
394 const char *text = p->text+(p->text[0]=='@' ? 1 : 0);
395 float xoff = -text_width(text)/2;
396 float yoff = 0;
397 if((type&0xFF)==PT_TEXTUP) { xoff += detrnd((size_t)p, 100)-50; yoff -= detrnd((size_t)p, 101); } //@TODO instead in worldspace beforehand?
398 glTranslatef(xoff, yoff, 50);
400 draw_text(text, 0, 0, color[0], color[1], color[2], blend);
402 glPopMatrix();
405 static textrenderer texts(PT_TEXT|PT_LERP), textups(PT_TEXTUP|PT_LERP, -8);
407 template<int T>
408 static inline void modifyblend(const vec &o, int &blend)
410 blend = min(blend<<2, 255);
411 if(renderpath==R_FIXEDFUNCTION && fogging) blend = (uchar)(blend * max(0.0f, min(1.0f, 1.0f - (reflectz - o.z)/waterfog)));
414 template<>
415 inline void modifyblend<PT_TAPE>(const vec &o, int &blend)
419 template<int T>
420 static inline void genpos(const vec &o, const vec &d, float size, int grav, int ts, partvert *vs)
422 vec udir = vec(camup).sub(camright).mul(size);
423 vec vdir = vec(camup).add(camright).mul(size);
424 vs[0].pos = vec(o.x + udir.x, o.y + udir.y, o.z + udir.z);
425 vs[1].pos = vec(o.x + vdir.x, o.y + vdir.y, o.z + vdir.z);
426 vs[2].pos = vec(o.x - udir.x, o.y - udir.y, o.z - udir.z);
427 vs[3].pos = vec(o.x - vdir.x, o.y - vdir.y, o.z - vdir.z);
430 template<>
431 inline void genpos<PT_TAPE>(const vec &o, const vec &d, float size, int ts, int grav, partvert *vs)
433 vec dir1 = d, dir2 = d, c;
434 dir1.sub(o);
435 dir2.sub(camera1->o);
436 c.cross(dir2, dir1).normalize().mul(size);
437 vs[0].pos = vec(d.x-c.x, d.y-c.y, d.z-c.z);
438 vs[1].pos = vec(o.x-c.x, o.y-c.y, o.z-c.z);
439 vs[2].pos = vec(o.x+c.x, o.y+c.y, o.z+c.z);
440 vs[3].pos = vec(d.x+c.x, d.y+c.y, d.z+c.z);
443 template<>
444 inline void genpos<PT_TRAIL>(const vec &o, const vec &d, float size, int ts, int grav, partvert *vs)
446 vec e = d;
447 if(grav) e.z -= float(ts)/grav;
448 e.div(-75.0f);
449 e.add(o);
450 genpos<PT_TAPE>(o, e, size, ts, grav, vs);
453 template<int T>
454 struct varenderer : partrenderer
456 partvert *verts;
457 particle *parts;
458 int maxparts, numparts, lastupdate;
460 varenderer(const char *texname, int type, int grav, int collide)
461 : partrenderer(texname, type, grav, collide),
462 verts(NULL), parts(NULL), maxparts(0), numparts(0), lastupdate(-1)
466 void init(int n)
468 DELETEA(parts);
469 DELETEA(verts);
470 parts = new particle[n];
471 verts = new partvert[n*4];
472 maxparts = n;
473 numparts = 0;
474 lastupdate = -1;
477 void reset()
479 numparts = 0;
480 lastupdate = -1;
483 void resettracked(physent *owner)
485 if(!(type&PT_TRACK)) return;
486 loopi(numparts)
488 particle *p = parts+i;
489 if(!owner || (p->owner == owner)) p->fade = -1;
491 lastupdate = -1;
494 int count()
496 return numparts;
499 bool haswork()
501 return (numparts > 0);
504 bool usesvertexarray() { return true; }
506 particle *addpart(const vec &o, const vec &d, int fade, int color, float size)
508 particle *p = parts + (numparts < maxparts ? numparts++ : rnd(maxparts)); //next free slot, or kill a random kitten
509 p->o = o;
510 p->d = d;
511 p->fade = fade;
512 p->millis = lastmillis;
513 p->color = bvec(color>>16, (color>>8)&0xFF, color&0xFF);
514 p->size = size;
515 p->owner = NULL;
516 p->flags = 0x80;
517 int offset = p-parts;
518 if(type&PT_RND4) p->flags |= detrnd(offset, 4)<<2;
519 if((type&0xFF)==PT_PART) p->flags |= detrnd(offset*offset+37, 4);
520 lastupdate = -1;
521 return p;
524 void genverts(particle *p, partvert *vs, bool regen)
526 vec o, d;
527 int blend, ts;
529 calc(p, blend, ts, o, d);
530 if(blend <= 1 || p->fade <= 5) p->fade = -1; //mark to remove on next pass (i.e. after render)
532 modifyblend<T>(o, blend);
534 if(regen)
536 p->flags &= ~0x80;
538 int orient = p->flags&3;
539 #define SETTEXCOORDS(u1, u2, v1, v2) \
540 do { \
541 vs[orient].u = u1; \
542 vs[orient].v = v2; \
543 vs[(orient+1)&3].u = u2; \
544 vs[(orient+1)&3].v = v2; \
545 vs[(orient+2)&3].u = u2; \
546 vs[(orient+2)&3].v = v1; \
547 vs[(orient+3)&3].u = u1; \
548 vs[(orient+3)&3].v = v1; \
549 } while(0)
550 if(type&PT_RND4)
552 float tx = 0.5f*((p->flags>>2)&1), ty = 0.5f*((p->flags>>3)&1);
553 SETTEXCOORDS(tx, tx + 0.5f, ty, ty + 0.5f);
555 else SETTEXCOORDS(0, 1, 0, 1);
557 #define SETCOLOR(r, g, b, a) \
558 do { \
559 uchar col[4] = { r, g, b, a }; \
560 loopi(4) memcpy(vs[i].color.v, col, sizeof(col)); \
561 } while(0)
562 #define SETMODCOLOR SETCOLOR((p->color[0]*blend)>>8, (p->color[1]*blend)>>8, (p->color[2]*blend)>>8, 255)
563 if(type&PT_MOD) SETMODCOLOR;
564 else SETCOLOR(p->color[0], p->color[1], p->color[2], blend);
566 else if(type&PT_MOD) SETMODCOLOR;
567 else loopi(4) vs[i].alpha = blend;
569 genpos<T>(o, d, p->size, ts, grav, vs);
572 void update()
574 if(lastmillis == lastupdate) return;
575 lastupdate = lastmillis;
577 loopi(numparts)
579 particle *p = &parts[i];
580 partvert *vs = &verts[i*4];
581 if(p->fade < 0)
585 --numparts;
586 if(numparts <= i) return;
588 while(parts[numparts].fade < 0);
589 *p = parts[numparts];
590 genverts(p, vs, true);
592 else genverts(p, vs, (p->flags&0x80)!=0);
596 void render()
598 if(!tex) tex = textureload(texname);
599 glBindTexture(GL_TEXTURE_2D, tex->id);
600 glVertexPointer(3, GL_FLOAT, sizeof(partvert), &verts->pos);
601 glTexCoordPointer(2, GL_FLOAT, sizeof(partvert), &verts->u);
602 glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(partvert), &verts->color);
603 glDrawArrays(GL_QUADS, 0, numparts*4);
606 typedef varenderer<PT_PART> quadrenderer;
607 typedef varenderer<PT_TAPE> taperenderer;
608 typedef varenderer<PT_TRAIL> trailrenderer;
610 #include "explosion.h"
611 #include "lensflare.h"
612 #include "lightning.h"
614 static partrenderer *parts[] =
616 new quadrenderer("packages/particles/blood.png", PT_PART|PT_MOD|PT_RND4, 2, 1), // 0 blood spats (note: rgb is inverted)
617 new quadrenderer("packages/particles/spark.png", PT_PART|PT_GLARE, 2, 0), // 1 sparks
618 new quadrenderer("packages/particles/smoke.png", PT_PART, -20, 0), // 2 small slowly rising smoke
619 new quadrenderer("packages/particles/base.png", PT_PART|PT_GLARE, 20, 0), // 3 edit mode entities
620 new quadrenderer("packages/particles/ball1.png", PT_PART|PT_GLARE, 20, 0), // 4 fireball1
621 new quadrenderer("packages/particles/smoke.png", PT_PART, -20, 0), // 5 big slowly rising smoke
622 new quadrenderer("packages/particles/ball2.png", PT_PART|PT_GLARE, 20, 0), // 6 fireball2
623 new quadrenderer("packages/particles/ball3.png", PT_PART|PT_GLARE, 20, 0), // 7 big fireball3
624 &textups, // 8 TEXT, floats up
625 new taperenderer("packages/particles/flare.jpg", PT_TAPE|PT_GLARE, 0, 0), // 9 streak
626 &texts, // 10 TEXT, SMALL, NON-MOVING
627 &meters, // 11 METER, SMALL, NON-MOVING
628 &metervs, // 12 METER vs., SMALL, NON-MOVING
629 new quadrenderer("packages/particles/smoke.png", PT_PART, 20, 0), // 13 small slowly sinking smoke trail
630 &fireballs, // 14 explosion fireball
631 &lightnings, // 15 lightning
632 new quadrenderer("packages/particles/smoke.png", PT_PART, -15, 0), // 16 big fast rising smoke
633 new trailrenderer("packages/particles/base.png", PT_TRAIL|PT_LERP, 2, 0), // 17 water, entity
634 &noglarefireballs, // 18 explosion fireball no glare
635 &flares // must be done last
638 VARFP(maxparticles, 10, 4000, 40000, particleinit());
640 void particleinit()
642 if(!particleshader) particleshader = lookupshaderbyname("particle");
643 if(!particlenotextureshader) particlenotextureshader = lookupshaderbyname("particlenotexture");
644 loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->init(maxparticles);
647 void clearparticles()
649 loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->reset();
652 void cleanupparticles()
654 loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->cleanup();
657 void removetrackedparticles(physent *owner)
659 loopi(sizeof(parts)/sizeof(parts[0])) parts[i]->resettracked(owner);
662 VARP(particleglare, 0, 4, 100);
664 VAR(debugparticles, 0, 0, 1);
666 void render_particles(int time)
668 //want to debug BEFORE the lastpass render (that would delete particles)
669 if(debugparticles && !glaring && !reflecting && !refracting)
671 int n = sizeof(parts)/sizeof(parts[0]);
672 glMatrixMode(GL_PROJECTION);
673 glPushMatrix();
674 glLoadIdentity();
675 glOrtho(0, FONTH*n*2, FONTH*n*2, 0, -1, 1); //squeeze into top-left corner
676 glMatrixMode(GL_MODELVIEW);
677 glPushMatrix();
678 glLoadIdentity();
679 glDisable(GL_DEPTH_TEST);
680 glEnable(GL_BLEND);
681 defaultshader->set();
682 loopi(n)
684 int type = parts[i]->type;
685 const char *title = parts[i]->texname ? strrchr(parts[i]->texname, '/')+1 : NULL;
686 string info = "";
687 if(type&PT_GLARE) s_strcat(info, "g,");
688 if(type&PT_LERP) s_strcat(info, "l,");
689 if(type&PT_MOD) s_strcat(info, "m,");
690 if(type&PT_RND4) s_strcat(info, "r,");
691 if(type&PT_TRACK) s_strcat(info, "t,");
692 if(parts[i]->collide) s_strcat(info, "c,");
693 s_sprintfd(ds)("%d\t%s(%s%d) %s", parts[i]->count(), partnames[type&0xFF], info, parts[i]->grav, (title?title:""));
694 draw_text(ds, FONTH, (i+n/2)*FONTH);
696 glDisable(GL_BLEND);
697 glEnable(GL_DEPTH_TEST);
698 glMatrixMode(GL_PROJECTION);
699 glPopMatrix();
700 glMatrixMode(GL_MODELVIEW);
701 glPopMatrix();
704 if(glaring && !particleglare) return;
706 loopi(sizeof(parts)/sizeof(parts[0]))
708 if(glaring && !(parts[i]->type&PT_GLARE)) continue;
709 parts[i]->update();
712 static float zerofog[4] = { 0, 0, 0, 1 };
713 float oldfogc[4];
714 bool rendered = false;
715 uint lastflags = PT_LERP;
717 loopi(sizeof(parts)/sizeof(parts[0]))
719 partrenderer *p = parts[i];
720 if(glaring && !(p->type&PT_GLARE)) continue;
721 if(!p->haswork()) continue;
723 if(!rendered)
725 rendered = true;
726 glDepthMask(GL_FALSE);
727 glEnable(GL_BLEND);
728 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
730 if(glaring) setenvparamf("colorscale", SHPARAM_VERTEX, 4, particleglare, particleglare, particleglare, 1);
731 else setenvparamf("colorscale", SHPARAM_VERTEX, 4, 1, 1, 1, 1);
733 particleshader->set();
734 glGetFloatv(GL_FOG_COLOR, oldfogc);
737 uint flags = p->type & (PT_LERP|PT_MOD);
738 if(p->usesvertexarray()) flags |= 0x01; //0x01 = VA marker
739 uint changedbits = (flags ^ lastflags);
740 if(changedbits != 0x0000)
742 if(changedbits&0x01)
744 if(flags&0x01)
746 glEnableClientState(GL_VERTEX_ARRAY);
747 glEnableClientState(GL_TEXTURE_COORD_ARRAY);
748 glEnableClientState(GL_COLOR_ARRAY);
750 else
752 glDisableClientState(GL_VERTEX_ARRAY);
753 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
754 glDisableClientState(GL_COLOR_ARRAY);
757 if(changedbits&PT_LERP) glFogfv(GL_FOG_COLOR, (flags&PT_LERP) ? oldfogc : zerofog);
758 if(changedbits&(PT_LERP|PT_MOD))
760 if(flags&PT_LERP) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
761 else if(flags&PT_MOD) glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
762 else glBlendFunc(GL_SRC_ALPHA, GL_ONE);
764 lastflags = flags;
766 p->render();
769 if(rendered)
771 if(lastflags&(PT_LERP|PT_MOD)) glBlendFunc(GL_SRC_ALPHA, GL_ONE);
772 if(!(lastflags&PT_LERP)) glFogfv(GL_FOG_COLOR, oldfogc);
773 if(lastflags&0x01)
775 glDisableClientState(GL_VERTEX_ARRAY);
776 glDisableClientState(GL_TEXTURE_COORD_ARRAY);
777 glDisableClientState(GL_COLOR_ARRAY);
779 glDisable(GL_BLEND);
780 glDepthMask(GL_TRUE);
784 static inline particle *newparticle(const vec &o, const vec &d, int fade, int type, int color, float size)
786 return parts[type]->addpart(o, d, fade, color, size);
789 VARP(maxparticledistance, 256, 1024, 4096);
791 static void splash(int type, int color, int radius, int num, int fade, const vec &p, float size)
793 if(camera1->o.dist(p) > maxparticledistance) return;
794 float collidez = parts[type]->collide ? p.z - raycube(p, vec(0, 0, -1), COLLIDERADIUS, RAY_CLIPMAT) + COLLIDEERROR : -1;
795 int fmin = 1;
796 int fmax = fade*3;
797 loopi(num)
799 int x, y, z;
802 x = rnd(radius*2)-radius;
803 y = rnd(radius*2)-radius;
804 z = rnd(radius*2)-radius;
806 while(x*x+y*y+z*z>radius*radius);
807 vec tmp = vec((float)x, (float)y, (float)z);
808 int f = (num < 10) ? (fmin + rnd(fmax)) : (fmax - (i*(fmax-fmin))/(num-1)); //help deallocater by using fade distribution rather than random
809 newparticle(p, tmp, f, type, color, size)->val = collidez;
813 static void regularsplash(int type, int color, int radius, int num, int fade, const vec &p, float size, int delay=0)
815 if(!emit_particles() || (delay > 0 && rnd(delay) != 0)) return;
816 splash(type, color, radius, num, fade, p, size);
819 //maps 'classic' particles types to newer types and colors
820 // @NOTE potentially this and the following public funcs can be tidied up, but lets please defer that for a little bit...
821 static struct partmap { int type; int color; float size; } partmaps[] =
823 { 1, 0xB49B4B, 0.24f}, // 0 yellow: sparks
824 { 2, 0x897661, 0.6f }, // 1 greyish-brown: small slowly rising smoke
825 { 3, 0x3232FF, 0.32f}, // 2 blue: edit mode entities
826 { 0, 0x60FFFF, 2.96f}, // 3 red: blood spats (note: rgb is inverted)
827 { 4, 0xFFC8C8, 4.8f }, // 4 yellow: fireball1
828 { 5, 0x897661, 2.4f }, // 5 greyish-brown: big slowly rising smoke
829 { 6, 0xFFFFFF, 4.8f }, // 6 blue: fireball2
830 { 7, 0xFFFFFF, 4.8f }, // 7 green: big fireball3
831 { 8, 0xFF4B19, 4.0f }, // 8 TEXT RED
832 { 8, 0x32FF64, 4.0f }, // 9 TEXT GREEN
833 { 9, 0xFFC864, 0.28f}, // 10 yellow flare
834 { 10, 0x1EC850, 2.0f }, // 11 TEXT DARKGREEN, SMALL, NON-MOVING
835 { 7, 0xFFFFFF, 2.0f }, // 12 green small fireball3
836 { 10, 0xFF4B19, 2.0f }, // 13 TEXT RED, SMALL, NON-MOVING
837 { 10, 0xB4B4B4, 2.0f }, // 14 TEXT GREY, SMALL, NON-MOVING
838 { 8, 0xFFC864, 4.0f }, // 15 TEXT YELLOW
839 { 10, 0x6496FF, 2.0f }, // 16 TEXT BLUE, SMALL, NON-MOVING
840 { 11, 0xFF1932, 2.0f }, // 17 METER RED, SMALL, NON-MOVING
841 { 11, 0x3219FF, 2.0f }, // 18 METER BLUE, SMALL, NON-MOVING
842 { 12, 0xFF1932, 2.0f }, // 19 METER RED vs. BLUE, SMALL, NON-MOVING (note swaps r<->b)
843 { 12, 0x3219FF, 2.0f }, // 20 METER BLUE vs. RED, SMALL, NON-MOVING (note swaps r<->b)
844 { 13, 0x897661, 0.6f }, // 21 greyish-brown: small slowly sinking smoke trail
845 { 14, 0xFF8080, 4.0f }, // 22 red explosion fireball
846 { 14, 0xA0C080, 4.0f }, // 23 orange explosion fireball
847 /* @UNUSED */ { -1, 0, 0.0f}, // 24
848 { 16, 0x897661, 2.4f }, // 25 greyish-brown: big fast rising smoke
849 /* @UNUSED */ { -1, 0, 0.0f}, // 26
850 /* @UNUSED */ { -1, 0, 0.0f}, // 27
851 { 15, 0xFFFFFF, 0.28f}, // 28 lightning
852 { 15, 0xFF2222, 0.28f}, // 29 lightning: red
853 { 15, 0x2222FF, 0.28f}, // 30 lightning: blue
854 { 18, 0x802020, 4.8f }, // 31 fireball: red, no glare
855 { 18, 0x2020FF, 4.8f }, // 32 fireball: blue, no glare
856 { 18, 0x208020, 4.8f }, // 33 fireball: green, no glare
857 { 8, 0x6496FF, 4.0f }, // 34 TEXT BLUE
858 { 14, 0x802020, 4.8f }, // 35 fireball: red
859 { 14, 0x2020FF, 4.8f }, // 36 fireball: blue
860 // fill the above @UNUSED slots first!
863 static inline float partsize(int type)
865 return partmaps[type].size * particlesize/100.0f;
868 void regular_particle_splash(int type, int num, int fade, const vec &p, int delay)
870 if(shadowmapping) return;
871 int radius = (type==5 || type == 25) ? 50 : 150;
872 regularsplash(partmaps[type].type, partmaps[type].color, radius, num, fade, p, partsize(type), delay);
875 void particle_splash(int type, int num, int fade, const vec &p)
877 if(shadowmapping || renderedgame) return;
878 splash(partmaps[type].type, partmaps[type].color, 150, num, fade, p, partsize(type));
881 VARP(maxtrail, 1, 500, 10000);
883 void particle_trail(int type, int fade, const vec &s, const vec &e)
885 if(shadowmapping || renderedgame) return;
886 vec v;
887 float d = e.dist(s, v);
888 int steps = clamp(int(d*2), 1, maxtrail);
889 v.div(steps);
890 vec p = s;
891 int ptype = partmaps[type].type;
892 int color = partmaps[type].color;
893 float size = partsize(type);
894 loopi(steps)
896 p.add(v);
897 vec tmp = vec(float(rnd(11)-5), float(rnd(11)-5), float(rnd(11)-5));
898 newparticle(p, tmp, rnd(fade)+fade, ptype, color, size);
902 VARP(particletext, 0, 1, 1);
904 void particle_text(const vec &s, const char *t, int type, int fade)
906 if(shadowmapping || renderedgame) return;
907 if(!particletext || camera1->o.dist(s) > 128) return;
908 if(t[0]=='@') t = newstring(t);
909 newparticle(s, vec(0, 0, 1), fade, partmaps[type].type, partmaps[type].color, partmaps[type].size)->text = t;
912 void particle_meter(const vec &s, float val, int type, int fade)
914 if(shadowmapping || renderedgame) return;
915 newparticle(s, vec(0, 0, 1), fade, partmaps[type].type, partmaps[type].color, partmaps[type].size)->val = val;
918 void particle_flare(const vec &p, const vec &dest, int fade, int type, physent *owner)
920 if(shadowmapping || renderedgame) return;
921 newparticle(p, dest, fade, partmaps[type].type, partmaps[type].color, partsize(type))->owner = owner;
924 void particle_fireball(const vec &dest, float maxsize, int type, int fade)
926 if(shadowmapping || renderedgame) return;
927 float size = partsize(type);
928 float growth = maxsize - size;
929 if(fade < 0) fade = int(growth*25);
930 newparticle(dest, vec(0, 0, 1), fade, partmaps[type].type, partmaps[type].color, size)->val = growth;
933 //dir = 0..6 where 0=up
934 static inline vec offsetvec(vec o, int dir, int dist)
936 vec v = vec(o);
937 v[(2+dir)%3] += (dir>2)?(-dist):dist;
938 return v;
941 //converts a 16bit color to 24bit
942 static inline int colorfromattr(int attr)
944 return (((attr&0xF)<<4) | ((attr&0xF0)<<8) | ((attr&0xF00)<<12)) + 0x0F0F0F;
947 /* Experiments in shapes...
948 * dir: (where dir%3 is similar to offsetvec with 0=up)
949 * 0..2 circle
950 * 3.. 5 cylinder shell
951 * 6..11 cone shell
952 * 12..14 plane volume
953 * 15..20 line volume, i.e. wall
954 * 21 sphere
955 * +32 to inverse direction
957 void regularshape(int type, int radius, int color, int dir, int num, int fade, const vec &p, float size)
959 if(!emit_particles()) return;
961 int basetype = parts[type]->type&0xFF;
962 bool flare = (basetype == PT_TAPE) || (basetype == PT_LIGHTNING);
964 bool inv = (dir >= 32);
965 dir = dir&0x1F;
966 loopi(num)
968 vec to, from;
969 if(dir < 12)
971 float a = PI2*float(rnd(1000))/1000.0;
972 to[dir%3] = sinf(a)*radius;
973 to[(dir+1)%3] = cosf(a)*radius;
974 to[(dir+2)%3] = 0.0;
975 to.add(p);
976 if(dir < 3) //circle
977 from = p;
978 else if(dir < 6) //cylinder
980 from = to;
981 to[(dir+2)%3] += radius;
982 from[(dir+2)%3] -= radius;
984 else //cone
986 from = p;
987 to[(dir+2)%3] += (dir < 9)?radius:(-radius);
990 else if(dir < 15) //plane
992 to[dir%3] = float(rnd(radius<<4)-(radius<<3))/8.0;
993 to[(dir+1)%3] = float(rnd(radius<<4)-(radius<<3))/8.0;
994 to[(dir+2)%3] = radius;
995 to.add(p);
996 from = to;
997 from[(dir+2)%3] -= 2*radius;
999 else if(dir < 21) //line
1001 if(dir < 18)
1003 to[dir%3] = float(rnd(radius<<4)-(radius<<3))/8.0;
1004 to[(dir+1)%3] = 0.0;
1006 else
1008 to[dir%3] = 0.0;
1009 to[(dir+1)%3] = float(rnd(radius<<4)-(radius<<3))/8.0;
1011 to[(dir+2)%3] = 0.0;
1012 to.add(p);
1013 from = to;
1014 to[(dir+2)%3] += radius;
1016 else //sphere
1018 to = vec(PI2*float(rnd(1000))/1000.0, PI*float(rnd(1000)-500)/1000.0).mul(radius);
1019 to.add(p);
1020 from = p;
1023 if(flare)
1024 newparticle(inv?to:from, inv?from:to, rnd(fade*3)+1, type, color, size);
1025 else
1027 vec d(to);
1028 d.sub(from);
1029 d.normalize().mul(inv ? -200.0f : 200.0f); //velocity
1030 newparticle(inv?to:from, d, rnd(fade*3)+1, type, color, size);
1035 static void makeparticles(entity &e)
1037 switch(e.attr1)
1039 case 0: //fire
1040 regularsplash(4, 0xFFC8C8, 150, 1, 40, e.o, 4.8);
1041 regularsplash(5, 0x897661, 50, 1, 200, vec(e.o.x, e.o.y, e.o.z+3.0), 2.4, 3);
1042 break;
1043 case 1: //smoke vent - <dir>
1044 regularsplash(5, 0x897661, 50, 1, 200, offsetvec(e.o, e.attr2, rnd(10)), 2.4);
1045 break;
1046 case 2: //water fountain - <dir>
1048 uchar col[3];
1049 getwatercolour(col);
1050 int color = (col[0]<<16) | (col[1]<<8) | col[2];
1051 regularsplash(17, color, 150, 4, 200, offsetvec(e.o, e.attr2, rnd(10)), 0.6);
1052 break;
1054 case 3: //fire ball - <size> <rgb>
1055 newparticle(e.o, vec(0, 0, 1), 1, 14, colorfromattr(e.attr3), 4.0)->val = 1+e.attr2;
1056 break;
1057 case 4: //tape - <dir> <length> <rgb>
1058 case 7: //lightning
1059 case 8: //fire
1060 case 9: //smoke
1061 case 10: //water
1063 const int typemap[] = { 9, -1, -1, 15, 4, 5, 17 };
1064 const float sizemap[] = { 0.28, 0.0, 0.0, 0.28, 4.8, 2.4, 0.60 };
1065 int type = typemap[e.attr1-4];
1066 float size = sizemap[e.attr1-4];
1067 if(e.attr2 >= 256) regularshape(type, 1+e.attr3, colorfromattr(e.attr4), e.attr2-256, 5, 200, e.o, size);
1068 else newparticle(e.o, offsetvec(e.o, e.attr2, 1+e.attr3), 1, type, colorfromattr(e.attr4), size);
1069 break;
1071 case 5: //meter, metervs - <percent> <rgb>
1072 case 6:
1073 newparticle(e.o, vec(0, 0, 1), 1, (e.attr1==5)?11:12, colorfromattr(e.attr3), 2.0)->val = min(1.0f, float(e.attr2)/100);
1074 break;
1075 case 32: //lens flares - plain/sparkle/sun/sparklesun <red> <green> <blue>
1076 case 33:
1077 case 34:
1078 case 35:
1079 flares.addflare(e.o, e.attr2, e.attr3, e.attr4, (e.attr1&0x02)!=0, (e.attr1&0x01)!=0);
1080 break;
1081 default:
1082 s_sprintfd(ds)("@particles %d?", e.attr1);
1083 particle_text(e.o, ds, 16, 1);
1087 void entity_particles()
1089 if(lastmillis - lastemitframe >= emitmillis)
1091 emit = true;
1092 lastemitframe = lastmillis - (lastmillis%emitmillis);
1094 else emit = false;
1096 flares.makelightflares();
1098 const vector<extentity *> &ents = et->getents();
1099 if(!editmode)
1101 loopv(ents)
1103 entity &e = *ents[i];
1104 if(e.type != ET_PARTICLES || e.o.dist(camera1->o) > maxparticledistance) continue;
1105 makeparticles(e);
1108 else // show sparkly thingies for map entities in edit mode
1110 // note: order matters in this case as particles of the same type are drawn in the reverse order that they are added
1111 loopv(entgroup)
1113 entity &e = *ents[entgroup[i]];
1114 particle_text(e.o, entname(e), 13, 1);
1116 loopv(ents)
1118 entity &e = *ents[i];
1119 if(e.type==ET_EMPTY) continue;
1120 particle_text(e.o, entname(e), 11, 1);
1121 regular_particle_splash(2, 2, 40, e.o);