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