Line data Source code
1 : /**
2 : * @brief dynamic world geometry decals
3 : *
4 : * Stains are mostly useful for player-created effects, mainly those left behind
5 : * by weapons. They fade at a fixed period (not controllable by individual stains)
6 : * and can be culled if there are too many (`maxstaintris`).
7 : *
8 : * Stains apply to world (octree) geometry only and cannot be applied to models
9 : * (players, mapmodels, or otherwise).
10 : *
11 : * The performance of stains is generally high enough that many thousands of stain
12 : * particles must be present at once for there to be a noticable performance drop.
13 : */
14 :
15 : #include "../libprimis-headers/cube.h"
16 : #include "../../shared/geomexts.h"
17 : #include "../../shared/glemu.h"
18 : #include "../../shared/glexts.h"
19 :
20 : #include <memory>
21 : #include <optional>
22 :
23 : #include "octarender.h"
24 : #include "rendergl.h"
25 : #include "renderlights.h"
26 : #include "rendermodel.h"
27 : #include "renderwindow.h"
28 : #include "stain.h"
29 : #include "shader.h"
30 : #include "shaderparam.h"
31 : #include "texture.h"
32 :
33 : #include "interface/console.h"
34 : #include "interface/control.h"
35 :
36 : #include "world/bih.h"
37 : #include "world/entities.h"
38 : #include "world/material.h"
39 : #include "world/octaworld.h"
40 : #include "world/world.h"
41 :
42 : #include "model/model.h"
43 :
44 : void initstains();
45 :
46 0 : static VARFP(maxstaintris, 1, 2048, 16384, initstains()); //need to call initstains to potentially cull extra stain tris
47 : static VARP(stainfade, 1000, 15000, 60000); //number of milliseconds before stain geom fades
48 : static VAR(debugstain, 0, 0, 1); //toggles printout of stain information to console
49 :
50 : //StainRenderer: handles rendering to the gbuffer of a single class of particle
51 : //each StainRenderer handles the rendering of a single type of particle
52 : //all the level's particles of a single type will be handled by a single StainRenderer object
53 : class StainRenderer final
54 : {
55 : public:
56 : enum
57 : {
58 : StainFlag_Rnd4 = 1<<0,
59 : StainFlag_Rotate = 1<<1,
60 : StainFlag_InvMod = 1<<2,
61 : StainFlag_Overbright = 1<<3,
62 : StainFlag_Glow = 1<<4,
63 : StainFlag_Saturate = 1<<5
64 : };
65 :
66 0 : StainRenderer(const char *texname, int flags = 0, int fadeintime = 0, int fadeouttime = 1000, int timetolive = -1)
67 0 : : flags(flags),
68 0 : fadeintime(fadeintime), fadeouttime(fadeouttime), timetolive(timetolive),
69 0 : maxstains(0), startstain(0), endstain(0),
70 0 : stainu(0), stainv(0), tex(nullptr), stains(nullptr), texname(texname)
71 : {
72 0 : }
73 :
74 0 : ~StainRenderer()
75 : {
76 0 : delete[] stains;
77 0 : }
78 :
79 0 : bool usegbuffer() const
80 : {
81 0 : return !(flags&(StainFlag_InvMod|StainFlag_Glow));
82 : }
83 :
84 0 : void init(int tris)
85 : {
86 0 : if(stains)
87 : {
88 0 : delete[] stains; //do not need to set null, immediately reassigned below
89 0 : maxstains = startstain = endstain = 0;
90 : }
91 0 : stains = new staininfo[tris];
92 0 : maxstains = tris;
93 0 : for(int i = 0; i < StainBuffer_Number; ++i)
94 : {
95 0 : verts[i].init(i == StainBuffer_Transparent ? tris/2 : tris);
96 : }
97 0 : }
98 :
99 0 : void preload()
100 : {
101 0 : tex = textureload(texname, 3);
102 0 : }
103 :
104 0 : bool hasstains(int sbuf)
105 : {
106 0 : return verts[sbuf].hasverts();
107 : }
108 :
109 0 : void clearstains()
110 : {
111 0 : startstain = endstain = 0;
112 0 : for(stainbuffer &i : verts)
113 : {
114 0 : i.clear();
115 : }
116 0 : }
117 :
118 0 : void clearfadedstains()
119 : {
120 0 : int threshold = lastmillis - (timetolive>=0 ? timetolive : stainfade) - fadeouttime;
121 0 : staininfo *d = &stains[startstain],
122 0 : *end = &stains[endstain < startstain ? maxstains : endstain],
123 0 : *cleared[StainBuffer_Number] = {nullptr};
124 0 : for(; d < end && d->millis <= threshold; d++)
125 0 : cleared[d->owner] = d;
126 0 : if(d >= end && endstain < startstain)
127 : {
128 0 : for(d = stains, end = &stains[endstain]; d < end && d->millis <= threshold; d++)
129 : {
130 0 : cleared[d->owner] = d;
131 : }
132 : }
133 0 : startstain = d - stains;
134 0 : if(startstain == endstain)
135 : {
136 0 : for(stainbuffer &i : verts)
137 : {
138 0 : i.clear();
139 : }
140 : }
141 : else
142 : {
143 0 : for(int i = 0; i < StainBuffer_Number; ++i)
144 : {
145 0 : if(cleared[i])
146 : {
147 0 : verts[i].clearstains(*cleared[i]);
148 : }
149 : }
150 : }
151 0 : }
152 :
153 0 : void fadeinstains()
154 : {
155 0 : if(!fadeintime)
156 : {
157 0 : return;
158 : }
159 0 : staininfo *d = &stains[endstain],
160 0 : *end = &stains[endstain < startstain ? 0 : startstain];
161 0 : while(d > end)
162 : {
163 0 : d--;
164 0 : int fade = lastmillis - d->millis;
165 0 : if(fade < fadeintime)
166 : {
167 0 : fadestain(*d, (fade<<8)/fadeintime);
168 : }
169 0 : else if(faded(*d))
170 : {
171 0 : fadestain(*d, 255);
172 : }
173 : else
174 : {
175 0 : return;
176 : }
177 : }
178 0 : if(endstain < startstain)
179 : {
180 0 : d = &stains[maxstains];
181 0 : end = &stains[startstain];
182 0 : while(d > end)
183 : {
184 0 : d--;
185 0 : int fade = lastmillis - d->millis;
186 0 : if(fade < fadeintime)
187 : {
188 0 : fadestain(*d, (fade<<8)/fadeintime);
189 : }
190 0 : else if(faded(*d))
191 : {
192 0 : fadestain(*d, 255);
193 : }
194 : else
195 : {
196 0 : return;
197 : }
198 : }
199 : }
200 : }
201 :
202 0 : void fadeoutstains()
203 : {
204 0 : staininfo *d = &stains[startstain],
205 0 : *end = &stains[endstain < startstain ? maxstains : endstain];
206 0 : int offset = (timetolive>=0 ? timetolive : stainfade) + fadeouttime - lastmillis;
207 0 : while(d < end)
208 : {
209 0 : int fade = d->millis + offset;
210 0 : if(fade >= fadeouttime)
211 : {
212 0 : return;
213 : }
214 0 : fadestain(*d, (fade<<8)/fadeouttime);
215 0 : d++;
216 : }
217 0 : if(endstain < startstain)
218 : {
219 0 : d = stains;
220 0 : end = &stains[endstain];
221 0 : while(d < end)
222 : {
223 0 : int fade = d->millis + offset;
224 0 : if(fade >= fadeouttime)
225 : {
226 0 : return;
227 : }
228 0 : fadestain(*d, (fade<<8)/fadeouttime);
229 0 : d++;
230 : }
231 : }
232 : }
233 :
234 0 : static void setuprenderstate(int sbuf, bool gbuf, int layer)
235 : {
236 0 : if(gbuf)
237 : {
238 0 : maskgbuffer(sbuf == StainBuffer_Transparent ? "cg" : "c");
239 : }
240 : else
241 : {
242 0 : zerofogcolor();
243 : }
244 :
245 0 : if(layer && ghasstencil)
246 : {
247 0 : glStencilFunc(GL_EQUAL, layer, 0x07);
248 0 : glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
249 : }
250 :
251 0 : glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
252 :
253 0 : enablepolygonoffset(GL_POLYGON_OFFSET_FILL);
254 :
255 0 : glDepthMask(GL_FALSE);
256 0 : glEnable(GL_BLEND);
257 :
258 0 : gle::enablevertex();
259 0 : gle::enabletexcoord0();
260 0 : gle::enablecolor();
261 0 : }
262 :
263 0 : static void cleanuprenderstate(int sbuf, bool gbuf)
264 : {
265 0 : gle::clearvbo();
266 :
267 0 : gle::disablevertex();
268 0 : gle::disabletexcoord0();
269 0 : gle::disablecolor();
270 :
271 0 : glDepthMask(GL_TRUE);
272 0 : glDisable(GL_BLEND);
273 :
274 0 : disablepolygonoffset(GL_POLYGON_OFFSET_FILL);
275 :
276 0 : glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
277 :
278 0 : if(gbuf)
279 : {
280 0 : maskgbuffer(sbuf == StainBuffer_Transparent ? "cndg" : "cnd");
281 : }
282 : else
283 : {
284 0 : resetfogcolor();
285 : }
286 0 : }
287 :
288 0 : void cleanup()
289 : {
290 0 : for(stainbuffer &i : verts)
291 : {
292 0 : i.cleanup();
293 : }
294 0 : }
295 :
296 0 : void render(int sbuf)
297 : {
298 0 : float colorscale = 1,
299 0 : alphascale = 1;
300 0 : if(flags&StainFlag_Overbright)
301 : {
302 0 : glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
303 : {
304 : static Shader *overbrightstainshader = nullptr;
305 0 : if(!overbrightstainshader)
306 : {
307 0 : overbrightstainshader = lookupshaderbyname("overbrightstain");
308 : }
309 0 : overbrightstainshader->setvariant(sbuf == StainBuffer_Transparent ? 0 : -1, 0);
310 : }
311 : }
312 0 : else if(flags&StainFlag_Glow)
313 : {
314 0 : glBlendFunc(GL_ONE, GL_ONE);
315 0 : colorscale = ldrscale;
316 0 : if(flags&StainFlag_Saturate)
317 : {
318 0 : colorscale *= 2;
319 : }
320 0 : alphascale = 0;
321 0 : SETSHADER(foggedstain);
322 : }
323 0 : else if(flags&StainFlag_InvMod)
324 : {
325 0 : glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
326 0 : alphascale = 0;
327 0 : SETSHADER(foggedstain);
328 : }
329 : else
330 : {
331 0 : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
332 0 : colorscale = ldrscale;
333 0 : if(flags&StainFlag_Saturate)
334 : {
335 0 : colorscale *= 2;
336 : }
337 : {
338 : static Shader *stainshader = nullptr;
339 0 : if(!stainshader)
340 : {
341 0 : stainshader = lookupshaderbyname("stain");
342 : }
343 0 : stainshader->setvariant(sbuf == StainBuffer_Transparent ? 0 : -1, 0);
344 : }
345 : }
346 0 : LOCALPARAMF(colorscale, colorscale, colorscale, colorscale, alphascale);
347 :
348 0 : glBindTexture(GL_TEXTURE_2D, tex->id);
349 :
350 0 : verts[sbuf].render();
351 0 : }
352 :
353 :
354 0 : void addstain(const vec ¢er, const vec &dir, float radius, const bvec &color, int info, const cubeworld &world)
355 : {
356 0 : if(dir.iszero())
357 : {
358 0 : return;
359 : }
360 0 : bbmin = ivec(center).sub(radius);
361 0 : bbmax = ivec(center).add(radius).add(1);
362 :
363 0 : staincolor = vec4<uchar>(color, 255);
364 0 : staincenter = center;
365 0 : stainradius = radius;
366 0 : stainnormal = dir;
367 :
368 0 : staintangent = vec(dir.z, -dir.x, dir.y);
369 0 : staintangent.project(dir);
370 :
371 0 : if(flags&StainFlag_Rotate)
372 : {
373 0 : staintangent.rotate(sincos360[randomint(360)], dir);
374 : }
375 0 : staintangent.normalize();
376 0 : stainbitangent.cross(staintangent, dir);
377 0 : if(flags&StainFlag_Rnd4)
378 : {
379 0 : stainu = 0.5f*(info&1);
380 0 : stainv = 0.5f*((info>>1)&1);
381 : }
382 :
383 0 : for(int i = 0; i < StainBuffer_Number; ++i)
384 : {
385 0 : verts[i].lastvert = verts[i].endvert;
386 : }
387 0 : gentris(*world.worldroot, ivec(0, 0, 0), rootworld.mapsize()>>1);
388 0 : for(int i = 0; i < StainBuffer_Number; ++i)
389 : {
390 0 : stainbuffer &buf = verts[i];
391 0 : if(buf.endvert == buf.lastvert)
392 : {
393 0 : continue;
394 : }
395 0 : if(debugstain)
396 : {
397 0 : int nverts = buf.nextverts();
398 : static const char * const sbufname[StainBuffer_Number] = { "opaque", "transparent", "mapmodel" };
399 0 : conoutf(Console_Debug, "tris = %d, verts = %d, total tris = %d, %s", nverts/3, nverts, buf.totaltris(), sbufname[i]);
400 : }
401 :
402 0 : staininfo &d = newstain();
403 0 : d.owner = i;
404 0 : d.color = color;
405 0 : d.millis = lastmillis;
406 0 : d.startvert = buf.lastvert;
407 0 : d.endvert = buf.endvert;
408 0 : buf.addstain();
409 : }
410 : }
411 :
412 0 : void genmmtri(const std::array<vec, 3> &v) // gen map model triangles
413 : {
414 0 : vec n;
415 0 : n.cross(v[0], v[1], v[2]).normalize();
416 0 : float facing = n.dot(stainnormal);
417 0 : if(facing <= 0)
418 : {
419 0 : return;
420 : }
421 0 : vec p = vec(v[0]).sub(staincenter);
422 0 : float dist = n.dot(p);
423 0 : if(std::fabs(dist) > stainradius)
424 : {
425 0 : return;
426 : }
427 0 : vec pcenter = vec(n).mul(dist).add(staincenter);
428 0 : vec ft, fb;
429 0 : ft.orthogonal(n);
430 0 : ft.normalize();
431 0 : fb.cross(ft, n);
432 0 : vec pt = vec(ft).mul(ft.dot(staintangent)).add(vec(fb).mul(fb.dot(staintangent))).normalize(),
433 0 : pb = vec(ft).mul(ft.dot(stainbitangent)).add(vec(fb).mul(fb.dot(stainbitangent))).project(pt).normalize();
434 0 : vec v1[3+4],
435 0 : v2[3+4];
436 0 : float ptc = pt.dot(pcenter),
437 0 : pbc = pb.dot(pcenter);
438 0 : int numv = polyclip(v.data(), v.size(), pt, ptc - stainradius, ptc + stainradius, v1);
439 0 : if(numv<3) //check with v1
440 : {
441 0 : return;
442 : }
443 0 : numv = polyclip(v1, numv, pb, pbc - stainradius, pbc + stainradius, v2);
444 0 : if(numv<3) //check again with v2
445 : {
446 0 : return;
447 : }
448 0 : float tsz = flags&StainFlag_Rnd4 ? 0.5f : 1.0f,
449 0 : scale = tsz*0.5f/stainradius,
450 0 : tu = stainu + tsz*0.5f - ptc*scale,
451 0 : tv = stainv + tsz*0.5f - pbc*scale;
452 0 : pt.mul(scale); pb.mul(scale);
453 0 : stainvert dv1 = { v2[0], staincolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) },
454 0 : dv2 = { v2[1], staincolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) };
455 0 : int totalverts = 3*(numv-2);
456 0 : stainbuffer &buf = verts[StainBuffer_Mapmodel];
457 0 : if(totalverts > buf.maxverts-3)
458 : {
459 0 : return;
460 : }
461 0 : while(buf.availverts < totalverts)
462 : {
463 0 : if(!freestain())
464 : {
465 0 : return;
466 : }
467 : }
468 0 : for(int k = 0; k < numv-2; ++k)
469 : {
470 0 : stainvert *tri = buf.addtri();
471 0 : tri[0] = dv1;
472 0 : tri[1] = dv2;
473 0 : dv2.pos = v2[k+2];
474 0 : dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv);
475 0 : tri[2] = dv2;
476 : }
477 : }
478 :
479 : private:
480 : int flags, fadeintime, fadeouttime, timetolive;
481 : int maxstains, startstain, endstain;
482 :
483 : ivec bbmin, bbmax;
484 : vec staincenter, stainnormal, staintangent, stainbitangent;
485 : float stainradius, stainu, stainv;
486 : vec4<uchar> staincolor;
487 : Texture *tex;
488 :
489 : struct stainvert final
490 : {
491 : vec pos;
492 : vec4<uchar> color;
493 : vec2 tc;
494 : };
495 :
496 : struct staininfo final
497 : {
498 : int millis;
499 : bvec color;
500 : uchar owner;
501 : ushort startvert, endvert;
502 : };
503 : staininfo *stains;
504 :
505 : class stainbuffer final
506 : {
507 : public:
508 : int maxverts, endvert, lastvert, availverts;
509 0 : stainbuffer() : maxverts(0), endvert(0), lastvert(0), availverts(0), verts(nullptr), startvert(0), vbo(0), dirty(false)
510 0 : {}
511 :
512 0 : ~stainbuffer()
513 : {
514 0 : delete[] verts;
515 0 : }
516 :
517 0 : void init(int tris)
518 : {
519 0 : if(verts)
520 : {
521 0 : delete[] verts;
522 0 : verts = nullptr;
523 0 : maxverts = startvert = endvert = lastvert = availverts = 0;
524 : }
525 0 : if(tris)
526 : {
527 0 : maxverts = tris*3 + 3;
528 0 : availverts = maxverts - 3;
529 0 : verts = new stainvert[maxverts];
530 : }
531 0 : }
532 :
533 0 : void cleanup()
534 : {
535 0 : if(vbo)
536 : {
537 0 : glDeleteBuffers(1, &vbo);
538 0 : vbo = 0;
539 : }
540 0 : }
541 :
542 0 : void clear()
543 : {
544 0 : startvert = endvert = lastvert = 0;
545 0 : availverts = std::max(maxverts - 3, 0);
546 0 : dirty = true;
547 0 : }
548 :
549 0 : int freestain(const staininfo &d)
550 : {
551 0 : int removed = d.endvert < d.startvert ? maxverts - (d.startvert - d.endvert) : d.endvert - d.startvert;
552 0 : startvert = d.endvert;
553 0 : if(startvert==endvert)
554 : {
555 0 : startvert = endvert = lastvert = 0;
556 : }
557 0 : availverts += removed;
558 0 : return removed;
559 : }
560 :
561 0 : void clearstains(const staininfo &d)
562 : {
563 0 : startvert = d.endvert;
564 0 : availverts = endvert < startvert ? startvert - endvert - 3 : maxverts - 3 - (endvert - startvert);
565 0 : dirty = true;
566 0 : }
567 :
568 0 : bool faded(const staininfo &d) const
569 : {
570 0 : return verts[d.startvert].color.a() < 255;
571 : }
572 :
573 0 : void fadestain(const staininfo &d, const vec4<uchar> &color)
574 : {
575 0 : stainvert *vert = &verts[d.startvert];
576 0 : const stainvert *end = &verts[d.endvert < d.startvert ? maxverts : d.endvert];
577 0 : while(vert < end)
578 : {
579 0 : vert->color = color;
580 0 : vert++;
581 : }
582 0 : if(d.endvert < d.startvert)
583 : {
584 0 : vert = verts;
585 0 : end = &verts[d.endvert];
586 0 : while(vert < end)
587 : {
588 0 : vert->color = color;
589 0 : vert++;
590 : }
591 : }
592 0 : dirty = true;
593 0 : }
594 :
595 0 : void render()
596 : {
597 0 : if(startvert == endvert)
598 : {
599 0 : return;
600 : }
601 0 : if(!vbo)
602 : {
603 0 : glGenBuffers(1, &vbo);
604 0 : dirty = true;
605 : }
606 0 : gle::bindvbo(vbo);
607 0 : int count = endvert < startvert ? maxverts - startvert : endvert - startvert;
608 0 : if(dirty)
609 : {
610 0 : glBufferData(GL_ARRAY_BUFFER, maxverts*sizeof(stainvert), nullptr, GL_STREAM_DRAW);
611 0 : glBufferSubData(GL_ARRAY_BUFFER, 0, count*sizeof(stainvert), &verts[startvert]);
612 0 : if(endvert < startvert)
613 : {
614 0 : glBufferSubData(GL_ARRAY_BUFFER, count*sizeof(stainvert), endvert*sizeof(stainvert), verts);
615 0 : count += endvert;
616 : }
617 0 : dirty = false;
618 : }
619 0 : else if(endvert < startvert)
620 : {
621 0 : count += endvert;
622 : }
623 : //note: using -> on address `0` aka nullptr is undefined behavior
624 : //this allows passing the location of the fields' position in the object to opengl
625 0 : const stainvert *ptr = 0;
626 0 : gle::vertexpointer(sizeof(stainvert), ptr->pos.data());
627 0 : gle::texcoord0pointer(sizeof(stainvert), ptr->tc.data());
628 0 : gle::colorpointer(sizeof(stainvert), ptr->color.data());
629 :
630 0 : glDrawArrays(GL_TRIANGLES, 0, count);
631 0 : xtravertsva += count;
632 : }
633 :
634 0 : stainvert *addtri()
635 : {
636 0 : stainvert *tri = &verts[endvert];
637 0 : availverts -= 3;
638 0 : endvert += 3;
639 0 : if(endvert >= maxverts)
640 : {
641 0 : endvert = 0;
642 : }
643 0 : return tri;
644 : }
645 :
646 0 : void addstain()
647 : {
648 0 : dirty = true;
649 0 : }
650 :
651 0 : bool hasverts() const
652 : {
653 0 : return startvert != endvert;
654 : }
655 :
656 0 : int nextverts() const
657 : {
658 0 : return endvert < lastvert ? endvert + maxverts - lastvert : endvert - lastvert;
659 : }
660 :
661 0 : int totaltris() const
662 : {
663 0 : return (maxverts - 3 - availverts)/3;
664 : }
665 : private:
666 : stainvert *verts;
667 : int startvert;
668 : GLuint vbo;
669 : bool dirty;
670 :
671 : //debug functions, not used by any of the code
672 : int totalverts() const
673 : {
674 : return endvert < startvert ? maxverts - (startvert - endvert) : endvert - startvert;
675 : }
676 : };
677 :
678 : std::array<stainbuffer, StainBuffer_Number> verts;
679 :
680 : const char *texname;
681 :
682 0 : staininfo &newstain()
683 : {
684 0 : staininfo &d = stains[endstain];
685 0 : int next = endstain + 1;
686 0 : if(next>=maxstains)
687 : {
688 0 : next = 0;
689 : }
690 0 : if(next==startstain)
691 : {
692 0 : freestain();
693 : }
694 0 : endstain = next;
695 0 : return d;
696 : }
697 :
698 0 : bool faded(const staininfo &d) const
699 : {
700 0 : return verts[d.owner].faded(d);
701 : }
702 :
703 0 : void fadestain(const staininfo &d, uchar alpha)
704 : {
705 0 : bvec color = d.color;
706 0 : if(flags&(StainFlag_Overbright|StainFlag_Glow|StainFlag_InvMod))
707 : {
708 0 : color.scale(alpha, 255);
709 : }
710 0 : verts[d.owner].fadestain(d, vec4<uchar>(color, alpha));
711 0 : }
712 :
713 0 : int freestain()
714 : {
715 0 : if(startstain==endstain)
716 : {
717 0 : return 0;
718 : }
719 0 : staininfo &d = stains[startstain];
720 0 : startstain++;
721 0 : if(startstain >= maxstains)
722 : {
723 0 : startstain = 0;
724 : }
725 0 : return verts[d.owner].freestain(d);
726 : }
727 :
728 0 : void findmaterials(const vtxarray *va)
729 : {
730 0 : const int matsurfs = va->matsurfs;
731 0 : for(int i = 0; i < matsurfs; ++i)
732 : {
733 0 : const materialsurface &m = va->matbuf[i];
734 0 : if(!IS_CLIPPED(m.material&MatFlag_Volume))
735 : {
736 0 : i += m.skip;
737 0 : continue;
738 : }
739 0 : const int dim = DIMENSION(m.orient),
740 0 : dc = DIM_COORD(m.orient);
741 0 : if(dc ? stainnormal[dim] <= 0 : stainnormal[dim] >= 0)
742 : {
743 0 : i += m.skip;
744 0 : continue;
745 : }
746 0 : const int c = C[dim],
747 0 : r = R[dim];
748 : for(;;)
749 : {
750 0 : const materialsurface &m = va->matbuf[i];
751 0 : if(m.o[dim] >= bbmin[dim] && m.o[dim] <= bbmax[dim] &&
752 0 : m.o[c] + m.csize >= bbmin[c] && m.o[c] <= bbmax[c] &&
753 0 : m.o[r] + m.rsize >= bbmin[r] && m.o[r] <= bbmax[r])
754 : {
755 : static cube dummy;
756 0 : gentris(dummy, m.orient, m.o, std::max(m.csize, m.rsize), &m);
757 : }
758 0 : if(i+1 >= matsurfs)
759 : {
760 0 : break;
761 : }
762 0 : const materialsurface &n = va->matbuf[i+1];
763 0 : if(n.material != m.material || n.orient != m.orient)
764 : {
765 : break;
766 : }
767 0 : i++;
768 0 : }
769 : }
770 0 : }
771 :
772 0 : void findescaped(const std::array<cube, 8> &c, const ivec &o, int size, int escaped)
773 : {
774 0 : for(int i = 0; i < 8; ++i)
775 : {
776 0 : const cube &cu = c[i];
777 0 : if(escaped&(1<<i))
778 : {
779 0 : ivec co(i, o, size);
780 0 : if(cu.children)
781 : {
782 0 : findescaped(*cu.children, co, size>>1, cu.escaped);
783 : }
784 : else
785 : {
786 0 : int vismask = cu.merged;
787 0 : if(vismask)
788 : {
789 0 : for(int j = 0; j < 6; ++j)
790 : {
791 0 : if(vismask&(1<<j))
792 : {
793 0 : gentris(cu, j, co, size);
794 : }
795 : }
796 : }
797 : }
798 : }
799 : }
800 0 : }
801 :
802 0 : void gentris(const std::array<cube, 8> &c, const ivec &o, int size, int escaped = 0)
803 : {
804 0 : int overlap = octaboxoverlap(o, size, bbmin, bbmax);
805 0 : for(int i = 0; i < 8; ++i)
806 : {
807 0 : const cube &cu = c[i];
808 0 : if(overlap&(1<<i))
809 : {
810 0 : ivec co(i, o, size);
811 0 : if(cu.ext)
812 : {
813 0 : if(cu.ext->va && cu.ext->va->matsurfs)
814 : {
815 0 : findmaterials(cu.ext->va);
816 : }
817 0 : if(cu.ext->ents && cu.ext->ents->mapmodels.size())
818 : {
819 0 : genmmtris(*cu.ext->ents);
820 : }
821 : }
822 0 : if(cu.children)
823 : {
824 0 : gentris(*cu.children, co, size>>1, cu.escaped);
825 : }
826 : else
827 : {
828 0 : int vismask = cu.visible; //visibility mask
829 0 : if(vismask&0xC0)
830 : {
831 0 : if(vismask&0x80)
832 : {
833 0 : for(int j = 0; j < 6; ++j)
834 : {
835 0 : gentris(cu, j, co, size, nullptr, vismask);
836 : }
837 : }
838 : else
839 : {
840 0 : for(int j = 0; j < 6; ++j)
841 : {
842 0 : if(vismask&(1<<j))
843 : {
844 0 : gentris(cu, j, co, size);
845 : }
846 : }
847 : }
848 : }
849 : }
850 : }
851 0 : else if(escaped&(1<<i))
852 : {
853 0 : ivec co(i, o, size);
854 0 : if(cu.children)
855 : {
856 0 : findescaped(*cu.children, co, size>>1, cu.escaped);
857 : }
858 : else
859 : {
860 0 : int vismask = cu.merged; //visibility mask
861 0 : if(vismask)
862 : {
863 0 : for(int j = 0; j < 6; ++j)
864 : {
865 0 : if(vismask&(1<<j))
866 : {
867 0 : gentris(cu, j, co, size);
868 : }
869 : }
870 : }
871 : }
872 : }
873 : }
874 0 : }
875 :
876 0 : void genmmtris(const octaentities &oe)
877 : {
878 0 : const std::vector<extentity *> &ents = entities::getents();
879 0 : for(uint i = 0; i < oe.mapmodels.size(); i++)
880 : {
881 0 : const extentity &e = *ents[oe.mapmodels[i]];
882 0 : model *m = loadmapmodel(e.attr1);
883 0 : if(!m)
884 : {
885 0 : continue;
886 : }
887 0 : vec center, radius;
888 0 : float rejectradius = m->collisionbox(center, radius),
889 0 : scale = e.attr5 > 0 ? e.attr5/100.0f : 1;
890 0 : center.mul(scale);
891 0 : if(staincenter.reject(vec(e.o).add(center), stainradius + rejectradius*scale))
892 : {
893 0 : continue;
894 : }
895 0 : m->setBIH();
896 0 : if(m->animated())
897 : {
898 0 : continue;
899 : }
900 0 : int yaw = e.attr2,
901 0 : pitch = e.attr3,
902 0 : roll = e.attr4;
903 0 : std::vector<std::array<vec, 3>> tris;
904 0 : m->bih->genstaintris(tris, staincenter, stainradius, e.o, yaw, pitch, roll, scale);
905 0 : for(const std::array<vec, 3> &t : tris)
906 : {
907 0 : genmmtri(t);
908 : }
909 0 : }
910 0 : }
911 :
912 0 : void gentris(const cube &cu, int orient, const ivec &o, int size, const materialsurface *mat = nullptr, int vismask = 0)
913 : {
914 0 : std::array<vec, Face_MaxVerts+4> pos;
915 0 : int numverts = 0,
916 0 : numplanes = 1;
917 0 : std::array<vec, 2> planes;
918 0 : if(mat)
919 : {
920 0 : planes[0] = vec(0, 0, 0);
921 0 : switch(orient)
922 : {
923 : //want to define GENFACEORIENT and GENFACEVERT to pass the appropriate code to GENFACEVERTS
924 : //GENFACEVERTS has different GENFACEORIENT and GENFACEVERT for many different calls in other files
925 : #define GENFACEORIENT(orient, v0, v1, v2, v3) \
926 : case orient: \
927 : planes[0][DIMENSION(orient)] = DIM_COORD(orient) ? 1 : -1; \
928 : v0 v1 v2 v3 \
929 : break;
930 : #define GENFACEVERT(orient, vert, x,y,z, xv,yv,zv) \
931 : pos[numverts++] = vec(x xv, y yv, z zv);
932 0 : GENFACEVERTS(o.x, o.x, o.y, o.y, o.z, o.z, , + mat->csize, , + mat->rsize, + 0.1f, - 0.1f);
933 : #undef GENFACEORIENT
934 : #undef GENFACEVERT
935 : }
936 : }
937 0 : else if(cu.texture[orient] == Default_Sky)
938 : {
939 0 : return;
940 : }
941 0 : else if(cu.ext && (numverts = cu.ext->surfaces[orient].numverts&Face_MaxVerts))
942 : {
943 0 : const vertinfo *verts = cu.ext->verts() + cu.ext->surfaces[orient].verts;
944 0 : ivec vo = ivec(o).mask(~0xFFF).shl(3);
945 0 : for(int j = 0; j < numverts; ++j)
946 : {
947 0 : pos[j] = vec(verts[j].getxyz().add(vo)).mul(1/8.0f);
948 : }
949 0 : planes[0].cross(pos[0], pos[1], pos[2]).normalize();
950 0 : if(numverts >= 4 && !(cu.merged&(1<<orient)) && !flataxisface(cu, orient) && faceconvexity(verts, numverts, size))
951 : {
952 0 : planes[1].cross(pos[0], pos[2], pos[3]).normalize();
953 0 : numplanes++;
954 : }
955 : }
956 0 : else if(cu.merged&(1<<orient))
957 : {
958 0 : return;
959 : }
960 0 : else if(!vismask || (vismask&0x40 && visibleface(cu, orient, o, size, Mat_Air, (cu.material&Mat_Alpha)^Mat_Alpha, Mat_Alpha)))
961 : {
962 0 : std::array<ivec, 4> v;
963 0 : genfaceverts(cu, orient, v);
964 0 : int vis = 3,
965 0 : convex = faceconvexity(v, vis),
966 0 : order = convex < 0 ? 1 : 0;
967 0 : vec vo(o);
968 0 : pos[numverts++] = vec(v[order]).mul(size/8.0f).add(vo);
969 0 : if(vis&1)
970 : {
971 0 : pos[numverts++] = vec(v[order+1]).mul(size/8.0f).add(vo);
972 : }
973 0 : pos[numverts++] = vec(v[order+2]).mul(size/8.0f).add(vo);
974 0 : if(vis&2)
975 : {
976 0 : pos[numverts++] = vec(v[(order+3)&3]).mul(size/8.0f).add(vo);
977 : }
978 0 : planes[0].cross(pos[0], pos[1], pos[2]).normalize();
979 0 : if(convex)
980 : {
981 0 : planes[1].cross(pos[0], pos[2], pos[3]).normalize();
982 0 : numplanes++;
983 : }
984 : }
985 : else
986 : {
987 0 : return;
988 : }
989 :
990 0 : stainbuffer &buf = verts[mat || cu.material&Mat_Alpha ? StainBuffer_Transparent : StainBuffer_Opaque];
991 0 : for(int l = 0; l < numplanes; ++l) //note this is a loop l (level 4)
992 : {
993 0 : const vec &n = planes[l];
994 0 : float facing = n.dot(stainnormal);
995 0 : if(facing <= 0)
996 : {
997 0 : continue;
998 : }
999 0 : vec p = vec(pos[0]).sub(staincenter);
1000 : // travel back along plane normal from the stain center
1001 0 : float dist = n.dot(p);
1002 0 : if(std::fabs(dist) > stainradius)
1003 : {
1004 0 : continue;
1005 : }
1006 0 : vec pcenter = vec(n).mul(dist).add(staincenter);
1007 0 : vec ft, fb;
1008 0 : ft.orthogonal(n);
1009 0 : ft.normalize();
1010 0 : fb.cross(ft, n);
1011 0 : vec pt = vec(ft).mul(ft.dot(staintangent)).add(vec(fb).mul(fb.dot(staintangent))).normalize(),
1012 0 : pb = vec(ft).mul(ft.dot(stainbitangent)).add(vec(fb).mul(fb.dot(stainbitangent))).project(pt).normalize();
1013 0 : vec v1[Face_MaxVerts+4],
1014 0 : v2[Face_MaxVerts+4];
1015 0 : float ptc = pt.dot(pcenter),
1016 0 : pbc = pb.dot(pcenter);
1017 : int numv;
1018 0 : if(numplanes >= 2)
1019 : {
1020 0 : if(l)
1021 : {
1022 0 : pos[1] = pos[2];
1023 0 : pos[2] = pos[3];
1024 : }
1025 0 : numv = polyclip(pos.data(), 3, pt, ptc - stainradius, ptc + stainradius, v1);
1026 0 : if(numv<3)
1027 : {
1028 0 : continue;
1029 : }
1030 : }
1031 : else
1032 : {
1033 0 : numv = polyclip(pos.data(), numverts, pt, ptc - stainradius, ptc + stainradius, v1);
1034 0 : if(numv<3)
1035 : {
1036 0 : continue;
1037 : }
1038 : }
1039 0 : numv = polyclip(v1, numv, pb, pbc - stainradius, pbc + stainradius, v2);
1040 0 : if(numv<3)
1041 : {
1042 0 : continue;
1043 : }
1044 0 : float tsz = flags&StainFlag_Rnd4 ? 0.5f : 1.0f,
1045 0 : scale = tsz*0.5f/stainradius,
1046 0 : tu = stainu + tsz*0.5f - ptc*scale,
1047 0 : tv = stainv + tsz*0.5f - pbc*scale;
1048 0 : pt.mul(scale); pb.mul(scale);
1049 0 : stainvert dv1 = { v2[0], staincolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) },
1050 0 : dv2 = { v2[1], staincolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) };
1051 0 : int totalverts = 3*(numv-2);
1052 0 : if(totalverts > buf.maxverts-3)
1053 : {
1054 0 : return;
1055 : }
1056 0 : while(buf.availverts < totalverts)
1057 : {
1058 0 : if(!freestain())
1059 : {
1060 0 : return;
1061 : }
1062 : }
1063 0 : for(int k = 0; k < numv-2; ++k)
1064 : {
1065 0 : stainvert *tri = buf.addtri();
1066 0 : tri[0] = dv1;
1067 0 : tri[1] = dv2;
1068 0 : dv2.pos = v2[k+2];
1069 0 : dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv);
1070 0 : tri[2] = dv2;
1071 : }
1072 : }
1073 : }
1074 : };
1075 :
1076 : std::vector<StainRenderer> stains;
1077 :
1078 : /**
1079 : * @brief Sets up stains array.
1080 : *
1081 : * Sets up each entry in the stains global variable array using init() method
1082 : * and then preloads them
1083 : *
1084 : * Fails to do anything if initing is set (early game loading time)
1085 : *
1086 : * Used in iengine.h.
1087 : */
1088 0 : void initstains()
1089 : {
1090 0 : if(initing)
1091 : {
1092 0 : return;
1093 : }
1094 0 : stains.emplace_back("<grey>media/particle/blood.png", StainRenderer::StainFlag_Rnd4|StainRenderer::StainFlag_Rotate|StainRenderer::StainFlag_InvMod);
1095 0 : stains.emplace_back("<grey>media/particle/pulse_scorch.png", StainRenderer::StainFlag_Rotate, 500);
1096 0 : stains.emplace_back("<grey>media/particle/rail_hole.png", StainRenderer::StainFlag_Rotate|StainRenderer::StainFlag_Overbright);
1097 0 : stains.emplace_back("<grey>media/particle/pulse_glow.png", StainRenderer::StainFlag_Rotate|StainRenderer::StainFlag_Glow|StainRenderer::StainFlag_Saturate, 250, 1500, 250);
1098 0 : stains.emplace_back("<grey>media/particle/rail_glow.png", StainRenderer::StainFlag_Rotate|StainRenderer::StainFlag_Glow|StainRenderer::StainFlag_Saturate, 100, 1100, 100);
1099 0 : for(StainRenderer &i : stains)
1100 : {
1101 0 : i.init(maxstaintris);
1102 : }
1103 0 : for(uint i = 0; i < stains.size(); ++i)
1104 : {
1105 0 : loadprogress = static_cast<float>(i+1)/stains.size();
1106 0 : stains[i].preload();
1107 : }
1108 0 : loadprogress = 0;
1109 : }
1110 :
1111 0 : void clearstains()
1112 : {
1113 0 : for(StainRenderer &i : stains)
1114 : {
1115 0 : i.clearstains();
1116 : }
1117 0 : }
1118 :
1119 : static VARNP(stains, showstains, 0, 1, 1); // toggles rendering stains at all
1120 :
1121 0 : bool renderstains(int sbuf, bool gbuf, int layer)
1122 : {
1123 0 : bool rendered = false;
1124 0 : for(StainRenderer& d : stains)
1125 : {
1126 0 : if(d.usegbuffer() != gbuf)
1127 : {
1128 0 : continue;
1129 : }
1130 0 : if(sbuf == StainBuffer_Opaque)
1131 : {
1132 0 : d.clearfadedstains();
1133 0 : d.fadeinstains();
1134 0 : d.fadeoutstains();
1135 : }
1136 0 : if(!showstains || !d.hasstains(sbuf))
1137 : {
1138 0 : continue;
1139 : }
1140 0 : if(!rendered)
1141 : {
1142 0 : rendered = true;
1143 0 : StainRenderer::setuprenderstate(sbuf, gbuf, layer);
1144 : }
1145 0 : d.render(sbuf);
1146 : }
1147 0 : if(!rendered)
1148 : {
1149 0 : return false;
1150 : }
1151 0 : StainRenderer::cleanuprenderstate(sbuf, gbuf);
1152 0 : return true;
1153 : }
1154 :
1155 0 : void cleanupstains()
1156 : {
1157 0 : for(StainRenderer& i : stains)
1158 : {
1159 0 : i.cleanup();
1160 : }
1161 0 : }
1162 :
1163 0 : void addstain(int type, const vec ¢er, const vec &surface, float radius, const bvec &color, int info)
1164 : {
1165 0 : static VARP(maxstaindistance, 1, 512, 10000); //distance in cubes before stains stop rendering
1166 0 : if(!showstains || type<0 || static_cast<size_t>(type) >= stains.size() || center.dist(camera1->o) - radius > maxstaindistance)
1167 : {
1168 0 : return;
1169 : }
1170 0 : StainRenderer &d = stains[type];
1171 0 : d.addstain(center, surface, radius, color, info, rootworld);
1172 : }
|