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