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;
38 PT_LERP
= 1<<10, // use very sparingly - order of blending issues
43 const char *partnames
[] = { "part", "tape", "trail", "text", "textup", "meter", "metervs", "fireball", "lightning", "flare" };
54 const char *text
; // will call delete[] on this only if it starts with an @
68 #define COLLIDERADIUS 8.0f
69 #define COLLIDEERROR 1.0f
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)
102 if(type
&PT_TRACK
&& p
->owner
) cl
->particletrack(p
->owner
, o
, d
);
110 ts
= lastmillis
-p
->millis
;
111 blend
= max(255 - (ts
<<8)/p
->fade
, 0);
114 if(ts
> p
->fade
) ts
= p
->fade
;
115 float t
= (float)(ts
);
119 o
.z
-= t
*t
/(2.0f
* 5000.0f
* grav
);
121 if(collide
&& o
.z
< p
->val
&& lastpass
)
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
;
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);
138 struct listparticle
: particle
143 static listparticle
*parempty
= NULL
;
145 VARP(outlinemeters
, 0, 0, 1);
147 struct listrenderer
: partrenderer
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
)
167 listparticle
*p
= list
;
171 if(p
->next
) p
= p
->next
;
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
)
187 cur
->next
= parempty
;
190 else prev
= &cur
->next
;
194 particle
*addpart(const vec
&o
, const vec
&d
, int fade
, int color
, float size
)
198 listparticle
*ps
= new listparticle
[256];
199 loopi(255) ps
[i
].next
= &ps
[i
+1];
200 ps
[255].next
= parempty
;
203 listparticle
*p
= parempty
;
210 p
->millis
= lastmillis
;
211 p
->color
= bvec(color
>>16, (color
>>8)&0xFF, color
&0xFF);
221 for(lp
= list
; lp
; lp
= lp
->next
) num
++;
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;
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
)
249 calc(p
, blend
, ts
, o
, d
, lastpass
);
252 renderpart(p
, o
, d
, blend
, ts
, p
->color
.v
);
254 if(p
->fade
> 5 || !lastpass
)
271 struct meterrenderer
: listrenderer
273 meterrenderer(int type
)
274 : listrenderer(NULL
, type
, 0, 0)
280 glDisable(GL_TEXTURE_2D
);
281 particlenotextureshader
->set();
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;
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);
308 glBegin(GL_TRIANGLE_STRIP
);
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
);
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
);
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
);
330 glColor3f(0, 0.8f
, 0);
331 glBegin(GL_LINE_LOOP
);
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
);
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
);
344 glBegin(GL_LINE_STRIP
);
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
);
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)
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
)
381 glTranslatef(o
.x
, o
.y
, o
.z
);
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;
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
);
405 static textrenderer
texts(PT_TEXT
|PT_LERP
), textups(PT_TEXTUP
|PT_LERP
, -8);
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
)));
415 inline void modifyblend
<PT_TAPE
>(const vec
&o
, int &blend
)
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
);
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
;
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
);
444 inline void genpos
<PT_TRAIL
>(const vec
&o
, const vec
&d
, float size
, int ts
, int grav
, partvert
*vs
)
447 if(grav
) e
.z
-= float(ts
)/grav
;
450 genpos
<PT_TAPE
>(o
, e
, size
, ts
, grav
, vs
);
454 struct varenderer
: partrenderer
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)
470 parts
= new particle
[n
];
471 verts
= new partvert
[n
*4];
483 void resettracked(physent
*owner
)
485 if(!(type
&PT_TRACK
)) return;
488 particle
*p
= parts
+i
;
489 if(!owner
|| (p
->owner
== owner
)) p
->fade
= -1;
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
512 p
->millis
= lastmillis
;
513 p
->color
= bvec(color
>>16, (color
>>8)&0xFF, color
&0xFF);
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);
524 void genverts(particle
*p
, partvert
*vs
, bool regen
)
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
);
538 int orient
= p
->flags
&3;
539 #define SETTEXCOORDS(u1, u2, v1, 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; \
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) \
559 uchar col[4] = { r, g, b, a }; \
560 loopi(4) memcpy(vs[i].color.v, col, sizeof(col)); \
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
);
574 if(lastmillis
== lastupdate
) return;
575 lastupdate
= lastmillis
;
579 particle
*p
= &parts
[i
];
580 partvert
*vs
= &verts
[i
*4];
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);
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());
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
);
675 glOrtho(0, FONTH
*n
*2, FONTH
*n
*2, 0, -1, 1); //squeeze into top-left corner
676 glMatrixMode(GL_MODELVIEW
);
679 glDisable(GL_DEPTH_TEST
);
681 defaultshader
->set();
684 int type
= parts
[i
]->type
;
685 const char *title
= parts
[i
]->texname
? strrchr(parts
[i
]->texname
, '/')+1 : NULL
;
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
);
697 glEnable(GL_DEPTH_TEST
);
698 glMatrixMode(GL_PROJECTION
);
700 glMatrixMode(GL_MODELVIEW
);
704 if(glaring
&& !particleglare
) return;
706 loopi(sizeof(parts
)/sizeof(parts
[0]))
708 if(glaring
&& !(parts
[i
]->type
&PT_GLARE
)) continue;
712 static float zerofog
[4] = { 0, 0, 0, 1 };
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;
726 glDepthMask(GL_FALSE
);
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)
746 glEnableClientState(GL_VERTEX_ARRAY
);
747 glEnableClientState(GL_TEXTURE_COORD_ARRAY
);
748 glEnableClientState(GL_COLOR_ARRAY
);
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
);
771 if(lastflags
&(PT_LERP
|PT_MOD
)) glBlendFunc(GL_SRC_ALPHA
, GL_ONE
);
772 if(!(lastflags
&PT_LERP
)) glFogfv(GL_FOG_COLOR
, oldfogc
);
775 glDisableClientState(GL_VERTEX_ARRAY
);
776 glDisableClientState(GL_TEXTURE_COORD_ARRAY
);
777 glDisableClientState(GL_COLOR_ARRAY
);
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;
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;
887 float d
= e
.dist(s
, v
);
888 int steps
= clamp(int(d
*2), 1, maxtrail
);
891 int ptype
= partmaps
[type
].type
;
892 int color
= partmaps
[type
].color
;
893 float size
= partsize(type
);
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
)
937 v
[(2+dir
)%3] += (dir
>2)?(-dist
):dist
;
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)
950 * 3.. 5 cylinder shell
952 * 12..14 plane volume
953 * 15..20 line volume, i.e. wall
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);
971 float a
= PI2
*float(rnd(1000))/1000.0;
972 to
[dir
%3] = sinf(a
)*radius
;
973 to
[(dir
+1)%3] = cosf(a
)*radius
;
978 else if(dir
< 6) //cylinder
981 to
[(dir
+2)%3] += radius
;
982 from
[(dir
+2)%3] -= radius
;
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
;
997 from
[(dir
+2)%3] -= 2*radius
;
999 else if(dir
< 21) //line
1003 to
[dir
%3] = float(rnd(radius
<<4)-(radius
<<3))/8.0;
1004 to
[(dir
+1)%3] = 0.0;
1009 to
[(dir
+1)%3] = float(rnd(radius
<<4)-(radius
<<3))/8.0;
1011 to
[(dir
+2)%3] = 0.0;
1014 to
[(dir
+2)%3] += radius
;
1018 to
= vec(PI2
*float(rnd(1000))/1000.0, PI
*float(rnd(1000)-500)/1000.0).mul(radius
);
1024 newparticle(inv
?to
:from
, inv
?from
:to
, rnd(fade
*3)+1, type
, color
, size
);
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
)
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);
1043 case 1: //smoke vent - <dir>
1044 regularsplash(5, 0x897661, 50, 1, 200, offsetvec(e
.o
, e
.attr2
, rnd(10)), 2.4);
1046 case 2: //water fountain - <dir>
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);
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
;
1057 case 4: //tape - <dir> <length> <rgb>
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
);
1071 case 5: //meter, metervs - <percent> <rgb>
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);
1075 case 32: //lens flares - plain/sparkle/sun/sparklesun <red> <green> <blue>
1079 flares
.addflare(e
.o
, e
.attr2
, e
.attr3
, e
.attr4
, (e
.attr1
&0x02)!=0, (e
.attr1
&0x01)!=0);
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
)
1092 lastemitframe
= lastmillis
- (lastmillis
%emitmillis
);
1096 flares
.makelightflares();
1098 const vector
<extentity
*> &ents
= et
->getents();
1103 entity
&e
= *ents
[i
];
1104 if(e
.type
!= ET_PARTICLES
|| e
.o
.dist(camera1
->o
) > maxparticledistance
) continue;
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
1113 entity
&e
= *ents
[entgroup
[i
]];
1114 particle_text(e
.o
, entname(e
), 13, 1);
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
);