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