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