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