1 #include "engine.h"
2 
3 struct stainvert
4 {
5     vec pos;
6     bvec4 color;
7     vec2 tc;
8 };
9 
10 struct staininfo
11 {
12     int millis;
13     bvec color;
14     uchar owner;
15     ushort startvert, endvert;
16 };
17 
18 enum
19 {
20     SF_RND4       = 1<<0,
21     SF_ROTATE     = 1<<1,
22     SF_INVMOD     = 1<<2,
23     SF_OVERBRIGHT = 1<<3,
24     SF_GLOW       = 1<<4,
25     SF_SATURATE   = 1<<5
26 };
27 
28 VARF(IDF_PERSIST, maxstaintris, 1, 2048, 16384, initstains());
29 VAR(IDF_PERSIST, stainfade, 1000, 15000, 60000);
30 VAR(0, dbgstain, 0, 0, 1);
31 
32 struct stainbuffer
33 {
34     stainvert *verts;
35     int maxverts, startvert, endvert, lastvert, availverts;
36     GLuint vbo;
37     bool dirty;
38 
stainbufferstainbuffer39     stainbuffer() : verts(NULL), maxverts(0), startvert(0), endvert(0), lastvert(0), availverts(0), vbo(0), dirty(false)
40     {}
41 
~stainbufferstainbuffer42     ~stainbuffer()
43     {
44         DELETEA(verts);
45     }
46 
initstainbuffer47     void init(int tris)
48     {
49         if(verts)
50         {
51             DELETEA(verts);
52             maxverts = startvert = endvert = lastvert = availverts = 0;
53         }
54         if(tris)
55         {
56             maxverts = tris*3 + 3;
57             availverts = maxverts - 3;
58             verts = new stainvert[maxverts];
59         }
60     }
61 
cleanupstainbuffer62     void cleanup()
63     {
64         if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; }
65     }
66 
clearstainbuffer67     void clear()
68     {
69         startvert = endvert = lastvert = 0;
70         availverts = max(maxverts - 3, 0);
71         dirty = true;
72     }
73 
freestainstainbuffer74     int freestain(const staininfo &d)
75     {
76         int removed = d.endvert < d.startvert ? maxverts - (d.startvert - d.endvert) : d.endvert - d.startvert;
77         startvert = d.endvert;
78         if(startvert==endvert) startvert = endvert = lastvert = 0;
79         availverts += removed;
80         return removed;
81     }
82 
clearstainsstainbuffer83     void clearstains(const staininfo &d)
84     {
85         startvert = d.endvert;
86         availverts = endvert < startvert ? startvert - endvert - 3 : maxverts - 3 - (endvert - startvert);
87         dirty = true;
88     }
89 
fadedstainbuffer90     bool faded(const staininfo &d) const { return verts[d.startvert].color.a < 255; }
91 
fadestainstainbuffer92     void fadestain(const staininfo &d, const bvec4 &color)
93     {
94         stainvert *vert = &verts[d.startvert],
95                   *end = &verts[d.endvert < d.startvert ? maxverts : d.endvert];
96         while(vert < end)
97         {
98             vert->color = color;
99             vert++;
100         }
101         if(d.endvert < d.startvert)
102         {
103             vert = verts;
104             end = &verts[d.endvert];
105             while(vert < end)
106             {
107                 vert->color = color;
108                 vert++;
109             }
110         }
111         dirty = true;
112     }
113 
renderstainbuffer114     void render()
115     {
116         if(startvert == endvert) return;
117 
118         if(!vbo) { glGenBuffers_(1, &vbo); dirty = true; }
119         gle::bindvbo(vbo);
120 
121         int count = endvert < startvert ? maxverts - startvert : endvert - startvert;
122         if(dirty)
123         {
124             glBufferData_(GL_ARRAY_BUFFER, maxverts*sizeof(stainvert), NULL, GL_STREAM_DRAW);
125             glBufferSubData_(GL_ARRAY_BUFFER, 0, count*sizeof(stainvert), &verts[startvert]);
126             if(endvert < startvert)
127             {
128                 glBufferSubData_(GL_ARRAY_BUFFER, count*sizeof(stainvert), endvert*sizeof(stainvert), verts);
129                 count += endvert;
130             }
131             dirty = false;
132         }
133         else if(endvert < startvert) count += endvert;
134 
135         const stainvert *ptr = 0;
136         gle::vertexpointer(sizeof(stainvert), ptr->pos.v);
137         gle::texcoord0pointer(sizeof(stainvert), ptr->tc.v);
138         gle::colorpointer(sizeof(stainvert), ptr->color.v);
139 
140         glDrawArrays(GL_TRIANGLES, 0, count);
141         xtravertsva += count;
142     }
143 
addtristainbuffer144     stainvert *addtri()
145     {
146         stainvert *tri = &verts[endvert];
147         availverts -= 3;
148         endvert += 3;
149         if(endvert >= maxverts) endvert = 0;
150         return tri;
151     }
152 
addstainstainbuffer153     void addstain(staininfo &d)
154     {
155         dirty = true;
156     }
157 
hasvertsstainbuffer158     bool hasverts() const { return startvert != endvert; }
159 
nextvertsstainbuffer160     int nextverts() const
161     {
162         return endvert < lastvert ? endvert + maxverts - lastvert : endvert - lastvert;
163     }
164 
totalvertsstainbuffer165     int totalverts() const
166     {
167         return endvert < startvert ? maxverts - (startvert - endvert) : endvert - startvert;
168     }
169 
totaltrisstainbuffer170     int totaltris() const
171     {
172         return (maxverts - 3 - availverts)/3;
173     }
174 };
175 
176 struct stainrenderer
177 {
178     const char *texname;
179     int flags, fadeintime, fadeouttime, timetolive;
180     Texture *tex;
181     staininfo *stains;
182     int maxstains, startstain, endstain;
183     stainbuffer verts[NUMSTAINBUFS];
184 
stainrendererstainrenderer185     stainrenderer(const char *texname, int flags = 0, int fadeintime = 0, int fadeouttime = 1000, int timetolive = -1)
186         : texname(texname), flags(flags),
187           fadeintime(fadeintime), fadeouttime(fadeouttime), timetolive(timetolive),
188           tex(NULL),
189           stains(NULL), maxstains(0), startstain(0), endstain(0),
190           stainu(0), stainv(0)
191     {
192     }
193 
~stainrendererstainrenderer194     ~stainrenderer()
195     {
196         DELETEA(stains);
197     }
198 
usegbufferstainrenderer199     bool usegbuffer() const { return !(flags&(SF_INVMOD|SF_GLOW)); }
200 
initstainrenderer201     void init(int tris)
202     {
203         if(stains)
204         {
205             DELETEA(stains);
206             maxstains = startstain = endstain = 0;
207         }
208         stains = new staininfo[tris];
209         maxstains = tris;
210         loopi(NUMSTAINBUFS) verts[i].init(i == STAINBUF_TRANSPARENT ? tris/2 : tris);
211     }
212 
preloadstainrenderer213     void preload()
214     {
215         tex = textureload(texname, 3);
216     }
217 
totalstainsstainrenderer218     int totalstains()
219     {
220         return endstain < startstain ? maxstains - (startstain - endstain) : endstain - startstain;
221     }
222 
hasstainsstainrenderer223     bool hasstains(int sbuf)
224     {
225         return verts[sbuf].hasverts();
226     }
227 
clearstainsstainrenderer228     void clearstains()
229     {
230         startstain = endstain = 0;
231         loopi(NUMSTAINBUFS) verts[i].clear();
232     }
233 
freestainstainrenderer234     int freestain()
235     {
236         if(startstain==endstain) return 0;
237 
238         staininfo &d = stains[startstain];
239         startstain++;
240         if(startstain >= maxstains) startstain = 0;
241 
242         return verts[d.owner].freestain(d);
243     }
244 
fadedstainrenderer245     bool faded(const staininfo &d) const { return verts[d.owner].faded(d); }
246 
fadestainstainrenderer247     void fadestain(const staininfo &d, uchar alpha)
248     {
249         bvec color = d.color;
250         if(flags&(SF_OVERBRIGHT|SF_GLOW|SF_INVMOD)) color.scale(alpha, 255);
251         verts[d.owner].fadestain(d, bvec4(color, alpha));
252     }
253 
clearfadedstainsstainrenderer254     void clearfadedstains()
255     {
256         int threshold = lastmillis - (timetolive>=0 ? timetolive : stainfade) - fadeouttime;
257         staininfo *d = &stains[startstain],
258                   *end = &stains[endstain < startstain ? maxstains : endstain],
259                   *cleared[NUMSTAINBUFS] = { NULL };
260         for(; d < end && d->millis <= threshold; d++)
261             cleared[d->owner] = d;
262         if(d >= end && endstain < startstain)
263             for(d = stains, end = &stains[endstain]; d < end && d->millis <= threshold; d++)
264                 cleared[d->owner] = d;
265         startstain = d - stains;
266         if(startstain == endstain) loopi(NUMSTAINBUFS) verts[i].clear();
267         else loopi(NUMSTAINBUFS) if(cleared[i]) verts[i].clearstains(*cleared[i]);
268     }
269 
fadeinstainsstainrenderer270     void fadeinstains()
271     {
272         if(!fadeintime) return;
273         staininfo *d = &stains[endstain],
274                   *end = &stains[endstain < startstain ? 0 : startstain];
275         while(d > end)
276         {
277             d--;
278             int fade = lastmillis - d->millis;
279             if(fade < fadeintime) fadestain(*d, (fade<<8)/fadeintime);
280             else if(faded(*d)) fadestain(*d, 255);
281             else return;
282         }
283         if(endstain < startstain)
284         {
285             d = &stains[maxstains];
286             end = &stains[startstain];
287             while(d > end)
288             {
289                 d--;
290                 int fade = lastmillis - d->millis;
291                 if(fade < fadeintime) fadestain(*d, (fade<<8)/fadeintime);
292                 else if(faded(*d)) fadestain(*d, 255);
293                 else return;
294             }
295         }
296     }
297 
fadeoutstainsstainrenderer298     void fadeoutstains()
299     {
300         staininfo *d = &stains[startstain],
301                   *end = &stains[endstain < startstain ? maxstains : endstain];
302         int offset = (timetolive>=0 ? timetolive : stainfade) + fadeouttime - lastmillis;
303         while(d < end)
304         {
305             int fade = d->millis + offset;
306             if(fade >= fadeouttime) return;
307             fadestain(*d, (fade<<8)/fadeouttime);
308             d++;
309         }
310         if(endstain < startstain)
311         {
312             d = stains;
313             end = &stains[endstain];
314             while(d < end)
315             {
316                 int fade = d->millis + offset;
317                 if(fade >= fadeouttime) return;
318                 fadestain(*d, (fade<<8)/fadeouttime);
319                 d++;
320             }
321         }
322     }
323 
setuprenderstatestainrenderer324     static void setuprenderstate(int sbuf, bool gbuf, int layer)
325     {
326         if(gbuf) maskgbuffer(sbuf == STAINBUF_TRANSPARENT ? "cg" : "c");
327         else zerofogcolor();
328 
329         if(layer && ghasstencil)
330         {
331             glStencilFunc(GL_EQUAL, layer, 0x07);
332             glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
333         }
334 
335         glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE);
336 
337         enablepolygonoffset(GL_POLYGON_OFFSET_FILL);
338 
339         glDepthMask(GL_FALSE);
340         glEnable(GL_BLEND);
341 
342         gle::enablevertex();
343         gle::enabletexcoord0();
344         gle::enablecolor();
345     }
346 
cleanuprenderstatestainrenderer347     static void cleanuprenderstate(int sbuf, bool gbuf, int layer)
348     {
349         gle::clearvbo();
350 
351         gle::disablevertex();
352         gle::disabletexcoord0();
353         gle::disablecolor();
354 
355         glDepthMask(GL_TRUE);
356         glDisable(GL_BLEND);
357 
358         disablepolygonoffset(GL_POLYGON_OFFSET_FILL);
359 
360         glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
361 
362         if(gbuf) maskgbuffer(sbuf == STAINBUF_TRANSPARENT ? "cndg" : "cnd");
363         else resetfogcolor();
364     }
365 
cleanupstainrenderer366     void cleanup()
367     {
368         loopi(NUMSTAINBUFS) verts[i].cleanup();
369     }
370 
renderstainrenderer371     void render(int sbuf)
372     {
373         float colorscale = 1, alphascale = 1;
374         if(flags&SF_OVERBRIGHT)
375         {
376             glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR);
377             SETVARIANT(overbrightstain, sbuf == STAINBUF_TRANSPARENT ? 0 : -1, 0);
378         }
379         else if(flags&SF_GLOW)
380         {
381             glBlendFunc(GL_ONE, GL_ONE);
382             colorscale = ldrscale;
383             if(flags&SF_SATURATE) colorscale *= 2;
384             alphascale = 0;
385             SETSHADER(foggedstain);
386         }
387         else if(flags&SF_INVMOD)
388         {
389             glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR);
390             alphascale = 0;
391             SETSHADER(foggedstain);
392         }
393         else
394         {
395             glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
396             colorscale = ldrscale;
397             if(flags&SF_SATURATE) colorscale *= 2;
398             SETVARIANT(stain, sbuf == STAINBUF_TRANSPARENT ? 0 : -1, 0);
399         }
400         LOCALPARAMF(colorscale, colorscale, colorscale, colorscale, alphascale);
401 
402         glBindTexture(GL_TEXTURE_2D, tex->id);
403 
404         verts[sbuf].render();
405     }
406 
newstainstainrenderer407     staininfo &newstain()
408     {
409         staininfo &d = stains[endstain];
410         int next = endstain + 1;
411         if(next>=maxstains) next = 0;
412         if(next==startstain) freestain();
413         endstain = next;
414         return d;
415     }
416 
417     ivec bbmin, bbmax;
418     vec staincenter, stainnormal, staintangent, stainbitangent;
419     float stainradius, stainu, stainv;
420     bvec4 staincolor;
421 
addstainstainrenderer422     void addstain(const vec &center, const vec &dir, float radius, const bvec &color, int info)
423     {
424         if(dir.iszero()) return;
425 
426         bbmin = ivec(center).sub(radius);
427         bbmax = ivec(center).add(radius).add(1);
428 
429         staincolor = bvec4(color, 255);
430         staincenter = center;
431         stainradius = radius;
432         stainnormal = dir;
433 #if 0
434         staintangent.orthogonal(dir);
435 #else
436         staintangent = vec(dir.z, -dir.x, dir.y);
437         staintangent.project(dir);
438 #endif
439         if(flags&SF_ROTATE) staintangent.rotate(sincos360[rnd(360)], dir);
440         staintangent.normalize();
441         stainbitangent.cross(staintangent, dir);
442         if(flags&SF_RND4)
443         {
444             stainu = 0.5f*(info&1);
445             stainv = 0.5f*((info>>1)&1);
446         }
447 
448         loopi(NUMSTAINBUFS) verts[i].lastvert = verts[i].endvert;
449         gentris(worldroot, ivec(0, 0, 0), worldsize>>1);
450         loopi(NUMSTAINBUFS)
451         {
452             stainbuffer &buf = verts[i];
453             if(buf.endvert == buf.lastvert) continue;
454 
455             if(dbgstain)
456             {
457                 int nverts = buf.nextverts();
458                 static const char * const sbufname[NUMSTAINBUFS] = { "opaque", "transparent", "mapmodel" };
459                 conoutf("tris = %d, verts = %d, total tris = %d, %s", nverts/3, nverts, buf.totaltris(), sbufname[i]);
460             }
461 
462             staininfo &d = newstain();
463             d.owner = i;
464             d.color = color;
465             d.millis = lastmillis;
466             d.startvert = buf.lastvert;
467             d.endvert = buf.endvert;
468             buf.addstain(d);
469         }
470     }
471 
gentrisstainrenderer472     void gentris(cube &cu, int orient, const ivec &o, int size, materialsurface *mat = NULL, int vismask = 0)
473     {
474         vec pos[MAXFACEVERTS+4];
475         int numverts = 0, numplanes = 1;
476         vec planes[2];
477         if(mat)
478         {
479             planes[0] = vec(0, 0, 0);
480             switch(orient)
481             {
482             #define GENFACEORIENT(orient, v0, v1, v2, v3) \
483                 case orient: \
484                     planes[0][dimension(orient)] = dimcoord(orient) ? 1 : -1; \
485                     v0 v1 v2 v3 \
486                     break;
487             #define GENFACEVERT(orient, vert, x,y,z, xv,yv,zv) \
488                     pos[numverts++] = vec(x xv, y yv, z zv);
489                 GENFACEVERTS(o.x, o.x, o.y, o.y, o.z, o.z, , + mat->csize, , + mat->rsize, + 0.1f, - 0.1f);
490             #undef GENFACEORIENT
491             #undef GENFACEVERT
492             }
493         }
494         else if(cu.texture[orient] == DEFAULT_SKY) return;
495         else if(cu.ext && (numverts = cu.ext->surfaces[orient].numverts&MAXFACEVERTS))
496         {
497             vertinfo *verts = cu.ext->verts() + cu.ext->surfaces[orient].verts;
498             ivec vo = ivec(o).mask(~0xFFF).shl(3);
499             loopj(numverts) pos[j] = vec(verts[j].getxyz().add(vo)).mul(1/8.0f);
500             planes[0].cross(pos[0], pos[1], pos[2]).normalize();
501             if(numverts >= 4 && !(cu.merged&(1<<orient)) && !flataxisface(cu, orient) && faceconvexity(verts, numverts, size))
502             {
503                 planes[1].cross(pos[0], pos[2], pos[3]).normalize();
504                 numplanes++;
505             }
506         }
507         else if(cu.merged&(1<<orient)) return;
508         else if(!vismask || (vismask&0x40 && visibleface(cu, orient, o, size, MAT_AIR, (cu.material&MAT_ALPHA)^MAT_ALPHA, MAT_ALPHA)))
509         {
510             ivec v[4];
511             genfaceverts(cu, orient, v);
512             int vis = 3, convex = faceconvexity(v, vis), order = convex < 0 ? 1 : 0;
513             vec vo(o);
514             pos[numverts++] = vec(v[order]).mul(size/8.0f).add(vo);
515             if(vis&1) pos[numverts++] = vec(v[order+1]).mul(size/8.0f).add(vo);
516             pos[numverts++] = vec(v[order+2]).mul(size/8.0f).add(vo);
517             if(vis&2) pos[numverts++] = vec(v[(order+3)&3]).mul(size/8.0f).add(vo);
518             planes[0].cross(pos[0], pos[1], pos[2]).normalize();
519             if(convex) { planes[1].cross(pos[0], pos[2], pos[3]).normalize(); numplanes++; }
520         }
521         else return;
522 
523         stainbuffer &buf = verts[mat || cu.material&MAT_ALPHA ? STAINBUF_TRANSPARENT : STAINBUF_OPAQUE];
524         loopl(numplanes)
525         {
526             const vec &n = planes[l];
527             float facing = n.dot(stainnormal);
528             if(facing <= 0) continue;
529             vec p = vec(pos[0]).sub(staincenter);
530 #if 0
531             // intersect ray along stain normal with plane
532             float dist = n.dot(p) / facing;
533             if(fabs(dist) > stainradius) continue;
534             vec pcenter = vec(stainnormal).mul(dist).add(staincenter);
535 #else
536             // travel back along plane normal from the stain center
537             float dist = n.dot(p);
538             if(fabs(dist) > stainradius) continue;
539             vec pcenter = vec(n).mul(dist).add(staincenter);
540 #endif
541             vec ft, fb;
542             ft.orthogonal(n);
543             ft.normalize();
544             fb.cross(ft, n);
545             vec pt = vec(ft).mul(ft.dot(staintangent)).add(vec(fb).mul(fb.dot(staintangent))).normalize(),
546                 pb = vec(ft).mul(ft.dot(stainbitangent)).add(vec(fb).mul(fb.dot(stainbitangent))).project(pt).normalize();
547             vec v1[MAXFACEVERTS+4], v2[MAXFACEVERTS+4];
548             float ptc = pt.dot(pcenter), pbc = pb.dot(pcenter);
549             int numv;
550             if(numplanes >= 2)
551             {
552                 if(l) { pos[1] = pos[2]; pos[2] = pos[3]; }
553                 numv = polyclip(pos, 3, pt, ptc - stainradius, ptc + stainradius, v1);
554                 if(numv<3) continue;
555             }
556             else
557             {
558                 numv = polyclip(pos, numverts, pt, ptc - stainradius, ptc + stainradius, v1);
559                 if(numv<3) continue;
560             }
561             numv = polyclip(v1, numv, pb, pbc - stainradius, pbc + stainradius, v2);
562             if(numv<3) continue;
563             float tsz = flags&SF_RND4 ? 0.5f : 1.0f, scale = tsz*0.5f/stainradius,
564                   tu = stainu + tsz*0.5f - ptc*scale, tv = stainv + tsz*0.5f - pbc*scale;
565             pt.mul(scale); pb.mul(scale);
566             stainvert dv1 = { v2[0], staincolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) },
567                       dv2 = { v2[1], staincolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) };
568             int totalverts = 3*(numv-2);
569             if(totalverts > buf.maxverts-3) return;
570             while(buf.availverts < totalverts)
571             {
572                 if(!freestain()) return;
573             }
574             loopk(numv-2)
575             {
576                 stainvert *tri = buf.addtri();
577                 tri[0] = dv1;
578                 tri[1] = dv2;
579                 dv2.pos = v2[k+2];
580                 dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv);
581                 tri[2] = dv2;
582             }
583         }
584     }
585 
findmaterialsstainrenderer586     void findmaterials(vtxarray *va)
587     {
588         materialsurface *matbuf = va->matbuf;
589         int matsurfs = va->matsurfs;
590         loopi(matsurfs)
591         {
592             materialsurface &m = matbuf[i];
593             if(!isclipped(m.material&MATF_VOLUME)) { i += m.skip; continue; }
594             int dim = dimension(m.orient), dc = dimcoord(m.orient);
595             if(dc ? stainnormal[dim] <= 0 : stainnormal[dim] >= 0) { i += m.skip; continue; }
596             int c = C[dim], r = R[dim];
597             for(;;)
598             {
599                 materialsurface &m = matbuf[i];
600                 if(m.o[dim] >= bbmin[dim] && m.o[dim] <= bbmax[dim] &&
601                    m.o[c] + m.csize >= bbmin[c] && m.o[c] <= bbmax[c] &&
602                    m.o[r] + m.rsize >= bbmin[r] && m.o[r] <= bbmax[r])
603                 {
604                     static cube dummy;
605                     gentris(dummy, m.orient, m.o, max(m.csize, m.rsize), &m);
606                 }
607                 if(i+1 >= matsurfs) break;
608                 materialsurface &n = matbuf[i+1];
609                 if(n.material != m.material || n.orient != m.orient) break;
610                 i++;
611             }
612         }
613     }
614 
findescapedstainrenderer615     void findescaped(cube *c, const ivec &o, int size, int escaped)
616     {
617         loopi(8)
618         {
619             cube &cu = c[i];
620             if(escaped&(1<<i))
621             {
622                 ivec co(i, o, size);
623                 if(cu.children) findescaped(cu.children, co, size>>1, cu.escaped);
624                 else
625                 {
626                     int vismask = cu.merged;
627                     if(vismask) loopj(6) if(vismask&(1<<j)) gentris(cu, j, co, size);
628                 }
629             }
630         }
631     }
632 
genmmtristainrenderer633     void genmmtri(const vec v[3])
634     {
635         vec n;
636         n.cross(v[0], v[1], v[2]).normalize();
637         float facing = n.dot(stainnormal);
638         if(facing <= 0) return;
639 
640         vec p = vec(v[0]).sub(staincenter);
641 #if 0
642         float dist = n.dot(p) / facing;
643         if(fabs(dist) > stainradius) return;
644         vec pcenter = vec(stainnormal).mul(dist).add(staincenter);
645 #else
646         float dist = n.dot(p);
647         if(fabs(dist) > stainradius) return;
648         vec pcenter = vec(n).mul(dist).add(staincenter);
649 #endif
650 
651         vec ft, fb;
652         ft.orthogonal(n);
653         ft.normalize();
654         fb.cross(ft, n);
655         vec pt = vec(ft).mul(ft.dot(staintangent)).add(vec(fb).mul(fb.dot(staintangent))).normalize(),
656             pb = vec(ft).mul(ft.dot(stainbitangent)).add(vec(fb).mul(fb.dot(stainbitangent))).project(pt).normalize();
657         vec v1[3+4], v2[3+4];
658         float ptc = pt.dot(pcenter), pbc = pb.dot(pcenter);
659         int numv = polyclip(v, 3, pt, ptc - stainradius, ptc + stainradius, v1);
660         if(numv<3) return;
661         numv = polyclip(v1, numv, pb, pbc - stainradius, pbc + stainradius, v2);
662         if(numv<3) return;
663         float tsz = flags&SF_RND4 ? 0.5f : 1.0f, scale = tsz*0.5f/stainradius,
664               tu = stainu + tsz*0.5f - ptc*scale, tv = stainv + tsz*0.5f - pbc*scale;
665         pt.mul(scale); pb.mul(scale);
666         stainvert dv1 = { v2[0], staincolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) },
667                   dv2 = { v2[1], staincolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) };
668         int totalverts = 3*(numv-2);
669         stainbuffer &buf = verts[STAINBUF_MAPMODEL];
670         if(totalverts > buf.maxverts-3) return;
671         while(buf.availverts < totalverts)
672         {
673             if(!freestain()) return;
674         }
675         loopk(numv-2)
676         {
677             stainvert *tri = buf.addtri();
678             tri[0] = dv1;
679             tri[1] = dv2;
680             dv2.pos = v2[k+2];
681             dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv);
682             tri[2] = dv2;
683         }
684     }
685 
genmmtrisstainrenderer686     void genmmtris(octaentities &oe)
687     {
688         const vector<extentity *> &ents = entities::getents();
689         loopv(oe.mapmodels)
690         {
691             extentity &e = *ents[oe.mapmodels[i]];
692             model *m = loadmapmodel(e.attrs[0]);
693             if(!m) continue;
694 
695             vec center, radius;
696             float rejectradius = m->collisionbox(center, radius), scale = e.attrs[5] > 0 ? e.attrs[5]/100.0f : 1;
697             center.mul(scale);
698             if(staincenter.reject(vec(e.o).add(center), stainradius + rejectradius*scale)) continue;
699 
700             if(m->animated() || (!m->bih && !m->setBIH())) continue;
701 
702             int yaw = e.attrs[1], pitch = e.attrs[2], roll = e.attrs[3];
703 
704             m->bih->genstaintris(this, staincenter, stainradius, e.o, yaw, pitch, roll, scale);
705         }
706     }
707 
gentrisstainrenderer708     void gentris(cube *c, const ivec &o, int size, int escaped = 0)
709     {
710         int overlap = octaboxoverlap(o, size, bbmin, bbmax);
711         loopi(8)
712         {
713             cube &cu = c[i];
714             if(overlap&(1<<i))
715             {
716                 ivec co(i, o, size);
717                 if(cu.ext)
718                 {
719                     if(cu.ext->va && cu.ext->va->matsurfs) findmaterials(cu.ext->va);
720                     if(cu.ext->ents && cu.ext->ents->mapmodels.length()) genmmtris(*cu.ext->ents);
721                 }
722                 if(cu.children) gentris(cu.children, co, size>>1, cu.escaped);
723                 else
724                 {
725                     int vismask = cu.visible;
726                     if(vismask&0xC0)
727                     {
728                         if(vismask&0x80) loopj(6) gentris(cu, j, co, size, NULL, vismask);
729                         else loopj(6) if(vismask&(1<<j)) gentris(cu, j, co, size);
730                     }
731                 }
732             }
733             else if(escaped&(1<<i))
734             {
735                 ivec co(i, o, size);
736                 if(cu.children) findescaped(cu.children, co, size>>1, cu.escaped);
737                 else
738                 {
739                     int vismask = cu.merged;
740                     if(vismask) loopj(6) if(vismask&(1<<j)) gentris(cu, j, co, size);
741                 }
742             }
743         }
744     }
745 };
746 
747 stainrenderer stains[] =
748 {
749     stainrenderer("<grey>particles/scorch", SF_ROTATE, 500, 1000, 10000),
750     stainrenderer("<grey>particles/scorch", SF_ROTATE, 500, 1000, 2000),
751     stainrenderer("<grey>particles/blood", SF_RND4|SF_ROTATE|SF_INVMOD, 0, 1000, 10000),
752     stainrenderer("<grey>particles/bullet", SF_OVERBRIGHT, 0, 1000, 10000),
753     stainrenderer("<grey>particles/energy", SF_ROTATE|SF_GLOW|SF_SATURATE, 150, 500, 3000),
754     stainrenderer("<grey>particles/stain", SF_SATURATE, 100, 900, 1000),
755     stainrenderer("<grey>particles/smoke", SF_ROTATE, 500, 1000, 10000)
756 };
757 
initstains()758 void initstains()
759 {
760     if(initing) return;
761     loopi(sizeof(stains)/sizeof(stains[0])) stains[i].init(maxstaintris);
762     loopi(sizeof(stains)/sizeof(stains[0]))
763     {
764         loadprogress = float(i+1)/(sizeof(stains)/sizeof(stains[0]));
765         stains[i].preload();
766     }
767     loadprogress = 0;
768 }
769 
clearstains()770 void clearstains()
771 {
772     loopi(sizeof(stains)/sizeof(stains[0])) stains[i].clearstains();
773 }
774 
775 VARN(IDF_PERSIST, stains, showstains, 0, 1, 1);
776 
renderstains(int sbuf,bool gbuf,int layer)777 bool renderstains(int sbuf, bool gbuf, int layer)
778 {
779     bool rendered = false;
780     loopi(sizeof(stains)/sizeof(stains[0]))
781     {
782         stainrenderer &d = stains[i];
783         if(d.usegbuffer() != gbuf) continue;
784         if(sbuf == STAINBUF_OPAQUE)
785         {
786             d.clearfadedstains();
787             d.fadeinstains();
788             d.fadeoutstains();
789         }
790         if(!showstains || !d.hasstains(sbuf)) continue;
791         if(!rendered)
792         {
793             rendered = true;
794             stainrenderer::setuprenderstate(sbuf, gbuf, layer);
795         }
796         d.render(sbuf);
797     }
798     if(!rendered) return false;
799     stainrenderer::cleanuprenderstate(sbuf, gbuf, layer);
800     return true;
801 }
802 
cleanupstains()803 void cleanupstains()
804 {
805     loopi(sizeof(stains)/sizeof(stains[0])) stains[i].cleanup();
806 }
807 
808 VAR(IDF_PERSIST, maxstaindistance, 1, 512, 10000);
809 
addstain(int type,const vec & center,const vec & surface,float radius,const bvec & color,int info)810 void addstain(int type, const vec &center, const vec &surface, float radius, const bvec &color, int info)
811 {
812     int id = type-1;
813     if(!showstains || type<=0 || (size_t)type>sizeof(stains)/sizeof(stains[0]) || center.dist(camera1->o) - radius > maxstaindistance) return;
814     stainrenderer &d = stains[id];
815     d.addstain(center, surface, radius, color, info);
816 }
817 
genstainmmtri(stainrenderer * s,const vec v[3])818 void genstainmmtri(stainrenderer *s, const vec v[3])
819 {
820     s->genmmtri(v);
821 }
822 
823