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, int layer)
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 0 : SETVARIANT(overbrightstain, sbuf == StainBuffer_Transparent ? 0 : -1, 0);
303 : }
304 0 : else if(flags&StainFlag_Glow)
305 : {
306 0 : glBlendFunc(GL_ONE, GL_ONE);
307 0 : colorscale = ldrscale;
308 0 : if(flags&StainFlag_Saturate)
309 : {
310 0 : colorscale *= 2;
311 : }
312 0 : alphascale = 0;
313 0 : SETSHADER(foggedstain);
314 : }
315 0 : else if(flags&StainFlag_InvMod)
316 : {
317 0 : glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
318 0 : alphascale = 0;
319 0 : SETSHADER(foggedstain);
320 : }
321 : else
322 : {
323 0 : glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
324 0 : colorscale = ldrscale;
325 0 : if(flags&StainFlag_Saturate)
326 : {
327 0 : colorscale *= 2;
328 : }
329 0 : SETVARIANT(stain, sbuf == StainBuffer_Transparent ? 0 : -1, 0);
330 : }
331 0 : LOCALPARAMF(colorscale, colorscale, colorscale, colorscale, alphascale);
332 :
333 0 : glBindTexture(GL_TEXTURE_2D, tex->id);
334 :
335 0 : verts[sbuf].render();
336 0 : }
337 :
338 0 : void addstain(const vec ¢er, const vec &dir, float radius, const bvec &color, int info, const cubeworld &world)
339 : {
340 0 : if(dir.iszero())
341 : {
342 0 : return;
343 : }
344 0 : bbmin = ivec(center).sub(radius);
345 0 : bbmax = ivec(center).add(radius).add(1);
346 :
347 0 : staincolor = vec4<uchar>(color, 255);
348 0 : staincenter = center;
349 0 : stainradius = radius;
350 0 : stainnormal = dir;
351 :
352 0 : staintangent = vec(dir.z, -dir.x, dir.y);
353 0 : staintangent.project(dir);
354 :
355 0 : if(flags&StainFlag_Rotate)
356 : {
357 0 : staintangent.rotate(sincos360[randomint(360)], dir);
358 : }
359 0 : staintangent.normalize();
360 0 : stainbitangent.cross(staintangent, dir);
361 0 : if(flags&StainFlag_Rnd4)
362 : {
363 0 : stainu = 0.5f*(info&1);
364 0 : stainv = 0.5f*((info>>1)&1);
365 : }
366 :
367 0 : for(int i = 0; i < StainBuffer_Number; ++i)
368 : {
369 0 : verts[i].lastvert = verts[i].endvert;
370 : }
371 0 : gentris(*world.worldroot, ivec(0, 0, 0), rootworld.mapsize()>>1);
372 0 : for(int i = 0; i < StainBuffer_Number; ++i)
373 : {
374 0 : stainbuffer &buf = verts[i];
375 0 : if(buf.endvert == buf.lastvert)
376 : {
377 0 : continue;
378 : }
379 0 : if(debugstain)
380 : {
381 0 : int nverts = buf.nextverts();
382 : static const char * const sbufname[StainBuffer_Number] = { "opaque", "transparent", "mapmodel" };
383 0 : conoutf(Console_Debug, "tris = %d, verts = %d, total tris = %d, %s", nverts/3, nverts, buf.totaltris(), sbufname[i]);
384 : }
385 :
386 0 : staininfo &d = newstain();
387 0 : d.owner = i;
388 0 : d.color = color;
389 0 : d.millis = lastmillis;
390 0 : d.startvert = buf.lastvert;
391 0 : d.endvert = buf.endvert;
392 0 : buf.addstain();
393 : }
394 : }
395 :
396 0 : void genmmtri(const std::array<vec, 3> &v) // gen map model triangles
397 : {
398 0 : vec n;
399 0 : n.cross(v[0], v[1], v[2]).normalize();
400 0 : float facing = n.dot(stainnormal);
401 0 : if(facing <= 0)
402 : {
403 0 : return;
404 : }
405 0 : vec p = vec(v[0]).sub(staincenter);
406 0 : float dist = n.dot(p);
407 0 : if(std::fabs(dist) > stainradius)
408 : {
409 0 : return;
410 : }
411 0 : vec pcenter = vec(n).mul(dist).add(staincenter);
412 0 : vec ft, fb;
413 0 : ft.orthogonal(n);
414 0 : ft.normalize();
415 0 : fb.cross(ft, n);
416 0 : vec pt = vec(ft).mul(ft.dot(staintangent)).add(vec(fb).mul(fb.dot(staintangent))).normalize(),
417 0 : pb = vec(ft).mul(ft.dot(stainbitangent)).add(vec(fb).mul(fb.dot(stainbitangent))).project(pt).normalize();
418 0 : vec v1[3+4],
419 0 : v2[3+4];
420 0 : float ptc = pt.dot(pcenter),
421 0 : pbc = pb.dot(pcenter);
422 0 : int numv = polyclip(v.data(), v.size(), pt, ptc - stainradius, ptc + stainradius, v1);
423 0 : if(numv<3) //check with v1
424 : {
425 0 : return;
426 : }
427 0 : numv = polyclip(v1, numv, pb, pbc - stainradius, pbc + stainradius, v2);
428 0 : if(numv<3) //check again with v2
429 : {
430 0 : return;
431 : }
432 0 : float tsz = flags&StainFlag_Rnd4 ? 0.5f : 1.0f,
433 0 : scale = tsz*0.5f/stainradius,
434 0 : tu = stainu + tsz*0.5f - ptc*scale,
435 0 : tv = stainv + tsz*0.5f - pbc*scale;
436 0 : pt.mul(scale); pb.mul(scale);
437 0 : stainvert dv1 = { v2[0], staincolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) },
438 0 : dv2 = { v2[1], staincolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) };
439 0 : int totalverts = 3*(numv-2);
440 0 : stainbuffer &buf = verts[StainBuffer_Mapmodel];
441 0 : if(totalverts > buf.maxverts-3)
442 : {
443 0 : return;
444 : }
445 0 : while(buf.availverts < totalverts)
446 : {
447 0 : if(!freestain())
448 : {
449 0 : return;
450 : }
451 : }
452 0 : for(int k = 0; k < numv-2; ++k)
453 : {
454 0 : stainvert *tri = buf.addtri();
455 0 : tri[0] = dv1;
456 0 : tri[1] = dv2;
457 0 : dv2.pos = v2[k+2];
458 0 : dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv);
459 0 : tri[2] = dv2;
460 : }
461 : }
462 :
463 : private:
464 : int flags, fadeintime, fadeouttime, timetolive;
465 : int maxstains, startstain, endstain;
466 :
467 : ivec bbmin, bbmax;
468 : vec staincenter, stainnormal, staintangent, stainbitangent;
469 : float stainradius, stainu, stainv;
470 : vec4<uchar> staincolor;
471 : Texture *tex;
472 :
473 : struct stainvert
474 : {
475 : vec pos;
476 : vec4<uchar> color;
477 : vec2 tc;
478 : };
479 :
480 : struct staininfo
481 : {
482 : int millis;
483 : bvec color;
484 : uchar owner;
485 : ushort startvert, endvert;
486 : };
487 : staininfo *stains;
488 :
489 : class stainbuffer
490 : {
491 : public:
492 : int maxverts, endvert, lastvert, availverts;
493 0 : stainbuffer() : maxverts(0), endvert(0), lastvert(0), availverts(0), verts(nullptr), startvert(0), vbo(0), dirty(false)
494 0 : {}
495 :
496 0 : ~stainbuffer()
497 : {
498 0 : delete[] verts;
499 0 : }
500 :
501 0 : void init(int tris)
502 : {
503 0 : if(verts)
504 : {
505 0 : delete[] verts;
506 0 : verts = nullptr;
507 0 : maxverts = startvert = endvert = lastvert = availverts = 0;
508 : }
509 0 : if(tris)
510 : {
511 0 : maxverts = tris*3 + 3;
512 0 : availverts = maxverts - 3;
513 0 : verts = new stainvert[maxverts];
514 : }
515 0 : }
516 :
517 0 : void cleanup()
518 : {
519 0 : if(vbo)
520 : {
521 0 : glDeleteBuffers(1, &vbo);
522 0 : vbo = 0;
523 : }
524 0 : }
525 :
526 0 : void clear()
527 : {
528 0 : startvert = endvert = lastvert = 0;
529 0 : availverts = std::max(maxverts - 3, 0);
530 0 : dirty = true;
531 0 : }
532 :
533 0 : int freestain(const staininfo &d)
534 : {
535 0 : int removed = d.endvert < d.startvert ? maxverts - (d.startvert - d.endvert) : d.endvert - d.startvert;
536 0 : startvert = d.endvert;
537 0 : if(startvert==endvert)
538 : {
539 0 : startvert = endvert = lastvert = 0;
540 : }
541 0 : availverts += removed;
542 0 : return removed;
543 : }
544 :
545 0 : void clearstains(const staininfo &d)
546 : {
547 0 : startvert = d.endvert;
548 0 : availverts = endvert < startvert ? startvert - endvert - 3 : maxverts - 3 - (endvert - startvert);
549 0 : dirty = true;
550 0 : }
551 :
552 0 : bool faded(const staininfo &d) const
553 : {
554 0 : return verts[d.startvert].color.a < 255;
555 : }
556 :
557 0 : void fadestain(const staininfo &d, const vec4<uchar> &color)
558 : {
559 0 : stainvert *vert = &verts[d.startvert];
560 0 : const stainvert *end = &verts[d.endvert < d.startvert ? maxverts : d.endvert];
561 0 : while(vert < end)
562 : {
563 0 : vert->color = color;
564 0 : vert++;
565 : }
566 0 : if(d.endvert < d.startvert)
567 : {
568 0 : vert = verts;
569 0 : end = &verts[d.endvert];
570 0 : while(vert < end)
571 : {
572 0 : vert->color = color;
573 0 : vert++;
574 : }
575 : }
576 0 : dirty = true;
577 0 : }
578 :
579 0 : void render()
580 : {
581 0 : if(startvert == endvert)
582 : {
583 0 : return;
584 : }
585 0 : if(!vbo)
586 : {
587 0 : glGenBuffers(1, &vbo);
588 0 : dirty = true;
589 : }
590 0 : gle::bindvbo(vbo);
591 0 : int count = endvert < startvert ? maxverts - startvert : endvert - startvert;
592 0 : if(dirty)
593 : {
594 0 : glBufferData(GL_ARRAY_BUFFER, maxverts*sizeof(stainvert), nullptr, GL_STREAM_DRAW);
595 0 : glBufferSubData(GL_ARRAY_BUFFER, 0, count*sizeof(stainvert), &verts[startvert]);
596 0 : if(endvert < startvert)
597 : {
598 0 : glBufferSubData(GL_ARRAY_BUFFER, count*sizeof(stainvert), endvert*sizeof(stainvert), verts);
599 0 : count += endvert;
600 : }
601 0 : dirty = false;
602 : }
603 0 : else if(endvert < startvert)
604 : {
605 0 : count += endvert;
606 : }
607 : //note: using -> on address `0` aka nullptr is undefined behavior
608 : //this allows passing the location of the fields' position in the object to opengl
609 0 : const stainvert *ptr = 0;
610 0 : gle::vertexpointer(sizeof(stainvert), ptr->pos.v);
611 0 : gle::texcoord0pointer(sizeof(stainvert), ptr->tc.v);
612 0 : gle::colorpointer(sizeof(stainvert), ptr->color.v);
613 :
614 0 : glDrawArrays(GL_TRIANGLES, 0, count);
615 0 : xtravertsva += count;
616 : }
617 :
618 0 : stainvert *addtri()
619 : {
620 0 : stainvert *tri = &verts[endvert];
621 0 : availverts -= 3;
622 0 : endvert += 3;
623 0 : if(endvert >= maxverts)
624 : {
625 0 : endvert = 0;
626 : }
627 0 : return tri;
628 : }
629 :
630 0 : void addstain()
631 : {
632 0 : dirty = true;
633 0 : }
634 :
635 0 : bool hasverts() const
636 : {
637 0 : return startvert != endvert;
638 : }
639 :
640 0 : int nextverts() const
641 : {
642 0 : return endvert < lastvert ? endvert + maxverts - lastvert : endvert - lastvert;
643 : }
644 :
645 0 : int totaltris() const
646 : {
647 0 : return (maxverts - 3 - availverts)/3;
648 : }
649 : private:
650 : stainvert *verts;
651 : int startvert;
652 : GLuint vbo;
653 : bool dirty;
654 :
655 : //debug functions, not used by any of the code
656 : int totalverts() const
657 : {
658 : return endvert < startvert ? maxverts - (startvert - endvert) : endvert - startvert;
659 : }
660 : };
661 :
662 : std::array<stainbuffer, StainBuffer_Number> verts;
663 :
664 : const char *texname;
665 :
666 0 : staininfo &newstain()
667 : {
668 0 : staininfo &d = stains[endstain];
669 0 : int next = endstain + 1;
670 0 : if(next>=maxstains)
671 : {
672 0 : next = 0;
673 : }
674 0 : if(next==startstain)
675 : {
676 0 : freestain();
677 : }
678 0 : endstain = next;
679 0 : return d;
680 : }
681 :
682 0 : bool faded(const staininfo &d) const
683 : {
684 0 : return verts[d.owner].faded(d);
685 : }
686 :
687 0 : void fadestain(const staininfo &d, uchar alpha)
688 : {
689 0 : bvec color = d.color;
690 0 : if(flags&(StainFlag_Overbright|StainFlag_Glow|StainFlag_InvMod))
691 : {
692 0 : color.scale(alpha, 255);
693 : }
694 0 : verts[d.owner].fadestain(d, vec4<uchar>(color, alpha));
695 0 : }
696 :
697 0 : int freestain()
698 : {
699 0 : if(startstain==endstain)
700 : {
701 0 : return 0;
702 : }
703 0 : staininfo &d = stains[startstain];
704 0 : startstain++;
705 0 : if(startstain >= maxstains)
706 : {
707 0 : startstain = 0;
708 : }
709 0 : return verts[d.owner].freestain(d);
710 : }
711 :
712 0 : void findmaterials(vtxarray *va)
713 : {
714 0 : int matsurfs = va->matsurfs;
715 0 : for(int i = 0; i < matsurfs; ++i)
716 : {
717 0 : materialsurface &m = va->matbuf[i];
718 0 : if(!IS_CLIPPED(m.material&MatFlag_Volume))
719 : {
720 0 : i += m.skip;
721 0 : continue;
722 : }
723 0 : int dim = DIMENSION(m.orient),
724 0 : dc = DIM_COORD(m.orient);
725 0 : if(dc ? stainnormal[dim] <= 0 : stainnormal[dim] >= 0)
726 : {
727 0 : i += m.skip;
728 0 : continue;
729 : }
730 0 : int c = C[dim],
731 0 : r = R[dim];
732 : for(;;)
733 : {
734 0 : const materialsurface &m = va->matbuf[i];
735 0 : if(m.o[dim] >= bbmin[dim] && m.o[dim] <= bbmax[dim] &&
736 0 : m.o[c] + m.csize >= bbmin[c] && m.o[c] <= bbmax[c] &&
737 0 : m.o[r] + m.rsize >= bbmin[r] && m.o[r] <= bbmax[r])
738 : {
739 : static cube dummy;
740 0 : gentris(dummy, m.orient, m.o, std::max(m.csize, m.rsize), &m);
741 : }
742 0 : if(i+1 >= matsurfs)
743 : {
744 0 : break;
745 : }
746 0 : const materialsurface &n = va->matbuf[i+1];
747 0 : if(n.material != m.material || n.orient != m.orient)
748 : {
749 : break;
750 : }
751 0 : i++;
752 0 : }
753 : }
754 0 : }
755 :
756 0 : void findescaped(const std::array<cube, 8> &c, const ivec &o, int size, int escaped)
757 : {
758 0 : for(int i = 0; i < 8; ++i)
759 : {
760 0 : const cube &cu = c[i];
761 0 : if(escaped&(1<<i))
762 : {
763 0 : ivec co(i, o, size);
764 0 : if(cu.children)
765 : {
766 0 : findescaped(*cu.children, co, size>>1, cu.escaped);
767 : }
768 : else
769 : {
770 0 : int vismask = cu.merged;
771 0 : if(vismask)
772 : {
773 0 : for(int j = 0; j < 6; ++j)
774 : {
775 0 : if(vismask&(1<<j))
776 : {
777 0 : gentris(cu, j, co, size);
778 : }
779 : }
780 : }
781 : }
782 : }
783 : }
784 0 : }
785 :
786 0 : void gentris(const std::array<cube, 8> &c, const ivec &o, int size, int escaped = 0)
787 : {
788 0 : int overlap = octaboxoverlap(o, size, bbmin, bbmax);
789 0 : for(int i = 0; i < 8; ++i)
790 : {
791 0 : const cube &cu = c[i];
792 0 : if(overlap&(1<<i))
793 : {
794 0 : ivec co(i, o, size);
795 0 : if(cu.ext)
796 : {
797 0 : if(cu.ext->va && cu.ext->va->matsurfs)
798 : {
799 0 : findmaterials(cu.ext->va);
800 : }
801 0 : if(cu.ext->ents && cu.ext->ents->mapmodels.size())
802 : {
803 0 : genmmtris(*cu.ext->ents);
804 : }
805 : }
806 0 : if(cu.children)
807 : {
808 0 : gentris(*cu.children, co, size>>1, cu.escaped);
809 : }
810 : else
811 : {
812 0 : int vismask = cu.visible; //visibility mask
813 0 : if(vismask&0xC0)
814 : {
815 0 : if(vismask&0x80)
816 : {
817 0 : for(int j = 0; j < 6; ++j)
818 : {
819 0 : gentris(cu, j, co, size, nullptr, vismask);
820 : }
821 : }
822 : else
823 : {
824 0 : for(int j = 0; j < 6; ++j)
825 : {
826 0 : if(vismask&(1<<j))
827 : {
828 0 : gentris(cu, j, co, size);
829 : }
830 : }
831 : }
832 : }
833 : }
834 : }
835 0 : else if(escaped&(1<<i))
836 : {
837 0 : ivec co(i, o, size);
838 0 : if(cu.children)
839 : {
840 0 : findescaped(*cu.children, co, size>>1, cu.escaped);
841 : }
842 : else
843 : {
844 0 : int vismask = cu.merged; //visibility mask
845 0 : if(vismask)
846 : {
847 0 : for(int j = 0; j < 6; ++j)
848 : {
849 0 : if(vismask&(1<<j))
850 : {
851 0 : gentris(cu, j, co, size);
852 : }
853 : }
854 : }
855 : }
856 : }
857 : }
858 0 : }
859 :
860 0 : void genmmtris(const octaentities &oe)
861 : {
862 0 : const std::vector<extentity *> &ents = entities::getents();
863 0 : for(uint i = 0; i < oe.mapmodels.size(); i++)
864 : {
865 0 : const extentity &e = *ents[oe.mapmodels[i]];
866 0 : model *m = loadmapmodel(e.attr1);
867 0 : if(!m)
868 : {
869 0 : continue;
870 : }
871 0 : vec center, radius;
872 0 : float rejectradius = m->collisionbox(center, radius),
873 0 : scale = e.attr5 > 0 ? e.attr5/100.0f : 1;
874 0 : center.mul(scale);
875 0 : if(staincenter.reject(vec(e.o).add(center), stainradius + rejectradius*scale))
876 : {
877 0 : continue;
878 : }
879 0 : if(m->animated() || (!m->bih && !m->setBIH()))
880 : {
881 0 : continue;
882 : }
883 0 : int yaw = e.attr2,
884 0 : pitch = e.attr3,
885 0 : roll = e.attr4;
886 0 : std::vector<std::array<vec, 3>> tris;
887 0 : m->bih->genstaintris(tris, staincenter, stainradius, e.o, yaw, pitch, roll, scale);
888 0 : for(const std::array<vec, 3> &t : tris)
889 : {
890 0 : genmmtri(t);
891 : }
892 0 : }
893 0 : }
894 :
895 0 : void gentris(const cube &cu, int orient, const ivec &o, int size, const materialsurface *mat = nullptr, int vismask = 0)
896 : {
897 0 : vec pos[Face_MaxVerts+4];
898 0 : int numverts = 0,
899 0 : numplanes = 1;
900 0 : vec planes[2];
901 0 : if(mat)
902 : {
903 0 : planes[0] = vec(0, 0, 0);
904 0 : switch(orient)
905 : {
906 : //want to define GENFACEORIENT and GENFACEVERT to pass the appropriate code to GENFACEVERTS
907 : //GENFACEVERTS has different GENFACEORIENT and GENFACEVERT for many different calls in other files
908 : #define GENFACEORIENT(orient, v0, v1, v2, v3) \
909 : case orient: \
910 : planes[0][DIMENSION(orient)] = DIM_COORD(orient) ? 1 : -1; \
911 : v0 v1 v2 v3 \
912 : break;
913 : #define GENFACEVERT(orient, vert, x,y,z, xv,yv,zv) \
914 : pos[numverts++] = vec(x xv, y yv, z zv);
915 0 : GENFACEVERTS(o.x, o.x, o.y, o.y, o.z, o.z, , + mat->csize, , + mat->rsize, + 0.1f, - 0.1f);
916 : #undef GENFACEORIENT
917 : #undef GENFACEVERT
918 : }
919 : }
920 0 : else if(cu.texture[orient] == Default_Sky)
921 : {
922 0 : return;
923 : }
924 0 : else if(cu.ext && (numverts = cu.ext->surfaces[orient].numverts&Face_MaxVerts))
925 : {
926 0 : const vertinfo *verts = cu.ext->verts() + cu.ext->surfaces[orient].verts;
927 0 : ivec vo = ivec(o).mask(~0xFFF).shl(3);
928 0 : for(int j = 0; j < numverts; ++j)
929 : {
930 0 : pos[j] = vec(verts[j].getxyz().add(vo)).mul(1/8.0f);
931 : }
932 0 : planes[0].cross(pos[0], pos[1], pos[2]).normalize();
933 0 : if(numverts >= 4 && !(cu.merged&(1<<orient)) && !flataxisface(cu, orient) && faceconvexity(verts, numverts, size))
934 : {
935 0 : planes[1].cross(pos[0], pos[2], pos[3]).normalize();
936 0 : numplanes++;
937 : }
938 : }
939 0 : else if(cu.merged&(1<<orient))
940 : {
941 0 : return;
942 : }
943 0 : else if(!vismask || (vismask&0x40 && visibleface(cu, orient, o, size, Mat_Air, (cu.material&Mat_Alpha)^Mat_Alpha, Mat_Alpha)))
944 : {
945 0 : std::array<ivec, 4> v;
946 0 : genfaceverts(cu, orient, v);
947 0 : int vis = 3,
948 0 : convex = faceconvexity(v, vis),
949 0 : order = convex < 0 ? 1 : 0;
950 0 : vec vo(o);
951 0 : pos[numverts++] = vec(v[order]).mul(size/8.0f).add(vo);
952 0 : if(vis&1)
953 : {
954 0 : pos[numverts++] = vec(v[order+1]).mul(size/8.0f).add(vo);
955 : }
956 0 : pos[numverts++] = vec(v[order+2]).mul(size/8.0f).add(vo);
957 0 : if(vis&2)
958 : {
959 0 : pos[numverts++] = vec(v[(order+3)&3]).mul(size/8.0f).add(vo);
960 : }
961 0 : planes[0].cross(pos[0], pos[1], pos[2]).normalize();
962 0 : if(convex)
963 : {
964 0 : planes[1].cross(pos[0], pos[2], pos[3]).normalize();
965 0 : numplanes++;
966 : }
967 : }
968 : else
969 : {
970 0 : return;
971 : }
972 :
973 0 : stainbuffer &buf = verts[mat || cu.material&Mat_Alpha ? StainBuffer_Transparent : StainBuffer_Opaque];
974 0 : for(int l = 0; l < numplanes; ++l) //note this is a loop l (level 4)
975 : {
976 0 : const vec &n = planes[l];
977 0 : float facing = n.dot(stainnormal);
978 0 : if(facing <= 0)
979 : {
980 0 : continue;
981 : }
982 0 : vec p = vec(pos[0]).sub(staincenter);
983 : // travel back along plane normal from the stain center
984 0 : float dist = n.dot(p);
985 0 : if(std::fabs(dist) > stainradius)
986 : {
987 0 : continue;
988 : }
989 0 : vec pcenter = vec(n).mul(dist).add(staincenter);
990 0 : vec ft, fb;
991 0 : ft.orthogonal(n);
992 0 : ft.normalize();
993 0 : fb.cross(ft, n);
994 0 : vec pt = vec(ft).mul(ft.dot(staintangent)).add(vec(fb).mul(fb.dot(staintangent))).normalize(),
995 0 : pb = vec(ft).mul(ft.dot(stainbitangent)).add(vec(fb).mul(fb.dot(stainbitangent))).project(pt).normalize();
996 0 : vec v1[Face_MaxVerts+4],
997 0 : v2[Face_MaxVerts+4];
998 0 : float ptc = pt.dot(pcenter),
999 0 : pbc = pb.dot(pcenter);
1000 : int numv;
1001 0 : if(numplanes >= 2)
1002 : {
1003 0 : if(l)
1004 : {
1005 0 : pos[1] = pos[2];
1006 0 : pos[2] = pos[3];
1007 : }
1008 0 : numv = polyclip(pos, 3, pt, ptc - stainradius, ptc + stainradius, v1);
1009 0 : if(numv<3)
1010 : {
1011 0 : continue;
1012 : }
1013 : }
1014 : else
1015 : {
1016 0 : numv = polyclip(pos, numverts, pt, ptc - stainradius, ptc + stainradius, v1);
1017 0 : if(numv<3)
1018 : {
1019 0 : continue;
1020 : }
1021 : }
1022 0 : numv = polyclip(v1, numv, pb, pbc - stainradius, pbc + stainradius, v2);
1023 0 : if(numv<3)
1024 : {
1025 0 : continue;
1026 : }
1027 0 : float tsz = flags&StainFlag_Rnd4 ? 0.5f : 1.0f,
1028 0 : scale = tsz*0.5f/stainradius,
1029 0 : tu = stainu + tsz*0.5f - ptc*scale,
1030 0 : tv = stainv + tsz*0.5f - pbc*scale;
1031 0 : pt.mul(scale); pb.mul(scale);
1032 0 : stainvert dv1 = { v2[0], staincolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) },
1033 0 : dv2 = { v2[1], staincolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) };
1034 0 : int totalverts = 3*(numv-2);
1035 0 : if(totalverts > buf.maxverts-3)
1036 : {
1037 0 : return;
1038 : }
1039 0 : while(buf.availverts < totalverts)
1040 : {
1041 0 : if(!freestain())
1042 : {
1043 0 : return;
1044 : }
1045 : }
1046 0 : for(int k = 0; k < numv-2; ++k)
1047 : {
1048 0 : stainvert *tri = buf.addtri();
1049 0 : tri[0] = dv1;
1050 0 : tri[1] = dv2;
1051 0 : dv2.pos = v2[k+2];
1052 0 : dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv);
1053 0 : tri[2] = dv2;
1054 : }
1055 : }
1056 : }
1057 : };
1058 :
1059 : std::vector<stainrenderer> stains;
1060 :
1061 : /* initstains: sets up each entry in the stains global variable array using init() method
1062 : * and then preloads them
1063 : *
1064 : * fails to do anything if initing is set (early game loading time)
1065 : */
1066 0 : void initstains()
1067 : {
1068 0 : if(initing)
1069 : {
1070 0 : return;
1071 : }
1072 0 : stains.emplace_back("<grey>media/particle/blood.png", stainrenderer::StainFlag_Rnd4|stainrenderer::StainFlag_Rotate|stainrenderer::StainFlag_InvMod);
1073 0 : stains.emplace_back("<grey>media/particle/pulse_scorch.png", stainrenderer::StainFlag_Rotate, 500);
1074 0 : stains.emplace_back("<grey>media/particle/rail_hole.png", stainrenderer::StainFlag_Rotate|stainrenderer::StainFlag_Overbright);
1075 0 : stains.emplace_back("<grey>media/particle/pulse_glow.png", stainrenderer::StainFlag_Rotate|stainrenderer::StainFlag_Glow|stainrenderer::StainFlag_Saturate, 250, 1500, 250);
1076 0 : stains.emplace_back("<grey>media/particle/rail_glow.png", stainrenderer::StainFlag_Rotate|stainrenderer::StainFlag_Glow|stainrenderer::StainFlag_Saturate, 100, 1100, 100);
1077 0 : for(stainrenderer &i : stains)
1078 : {
1079 0 : i.init(maxstaintris);
1080 : }
1081 0 : for(uint i = 0; i < stains.size(); ++i)
1082 : {
1083 0 : loadprogress = static_cast<float>(i+1)/stains.size();
1084 0 : stains[i].preload();
1085 : }
1086 0 : loadprogress = 0;
1087 : }
1088 :
1089 : /* clearstains: loops through the stains[] global variable array and runs clearstains for each entry
1090 : */
1091 0 : void clearstains()
1092 : {
1093 0 : for(stainrenderer &i : stains)
1094 : {
1095 0 : i.clearstains();
1096 : }
1097 0 : }
1098 :
1099 : VARNP(stains, showstains, 0, 1, 1); // toggles rendering stains at all
1100 :
1101 0 : bool renderstains(int sbuf, bool gbuf, int layer)
1102 : {
1103 0 : bool rendered = false;
1104 0 : for(stainrenderer& d : stains)
1105 : {
1106 0 : if(d.usegbuffer() != gbuf)
1107 : {
1108 0 : continue;
1109 : }
1110 0 : if(sbuf == StainBuffer_Opaque)
1111 : {
1112 0 : d.clearfadedstains();
1113 0 : d.fadeinstains();
1114 0 : d.fadeoutstains();
1115 : }
1116 0 : if(!showstains || !d.hasstains(sbuf))
1117 : {
1118 0 : continue;
1119 : }
1120 0 : if(!rendered)
1121 : {
1122 0 : rendered = true;
1123 0 : stainrenderer::setuprenderstate(sbuf, gbuf, layer);
1124 : }
1125 0 : d.render(sbuf);
1126 : }
1127 0 : if(!rendered)
1128 : {
1129 0 : return false;
1130 : }
1131 0 : stainrenderer::cleanuprenderstate(sbuf, gbuf, layer);
1132 0 : return true;
1133 : }
1134 :
1135 0 : void cleanupstains()
1136 : {
1137 0 : for(stainrenderer& i : stains)
1138 : {
1139 0 : i.cleanup();
1140 : }
1141 0 : }
1142 :
1143 0 : void addstain(int type, const vec ¢er, const vec &surface, float radius, const bvec &color, int info)
1144 : {
1145 0 : static VARP(maxstaindistance, 1, 512, 10000); //distance in cubes before stains stop rendering
1146 0 : if(!showstains || type<0 || static_cast<size_t>(type) >= stains.size() || center.dist(camera1->o) - radius > maxstaindistance)
1147 : {
1148 0 : return;
1149 : }
1150 0 : stainrenderer &d = stains[type];
1151 0 : d.addstain(center, surface, radius, color, info, rootworld);
1152 : }
|