#include "stain.hh" #include #include #include #include "console.hh" /* conoutf */ #include "main.hh" // initing, loadprogress, timings #include "material.hh" #include "rendergl.hh" #include "renderlights.hh" #include "rendermodel.hh" // loadmapmodel #include "texture.hh" #include "world.hh" struct stainvert { vec pos; bvec4 color; vec2 tc; }; struct staininfo { int millis; bvec color; uchar owner; ushort startvert, endvert; }; enum { SF_RND4 = 1<<0, SF_ROTATE = 1<<1, SF_INVMOD = 1<<2, SF_OVERBRIGHT = 1<<3, SF_GLOW = 1<<4, SF_SATURATE = 1<<5 }; VARFP(maxstaintris, 1, 2048, 16384, initstains()); VARMP(stainfade, 1, 15, 60, 1000); VAR(dbgstain, 0, 0, 1); struct stainbuffer { stainvert *verts; int maxverts, startvert, endvert, lastvert, availverts; GLuint vbo; bool dirty; stainbuffer() : verts(nullptr), maxverts(0), startvert(0), endvert(0), lastvert(0), availverts(0), vbo(0), dirty(false) {} ~stainbuffer() { DELETEA(verts); } void init(int tris) { if(verts) { DELETEA(verts); maxverts = startvert = endvert = lastvert = availverts = 0; } if(tris) { maxverts = tris*3 + 3; availverts = maxverts - 3; verts = new stainvert[maxverts]; } } void cleanup() { if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; } } void clear() { startvert = endvert = lastvert = 0; availverts = max(maxverts - 3, 0); dirty = true; } int freestain(const staininfo &d) { int removed = d.endvert < d.startvert ? maxverts - (d.startvert - d.endvert) : d.endvert - d.startvert; startvert = d.endvert; if(startvert==endvert) startvert = endvert = lastvert = 0; availverts += removed; return removed; } void clearstains(const staininfo &d) { startvert = d.endvert; availverts = endvert < startvert ? startvert - endvert - 3 : maxverts - 3 - (endvert - startvert); dirty = true; } bool faded(const staininfo &d) const { return verts[d.startvert].color.a < 255; } void fadestain(const staininfo &d, const bvec4 &color) { stainvert *vert = &verts[d.startvert], *end = &verts[d.endvert < d.startvert ? maxverts : d.endvert]; while(vert < end) { vert->color = color; vert++; } if(d.endvert < d.startvert) { vert = verts; end = &verts[d.endvert]; while(vert < end) { vert->color = color; vert++; } } dirty = true; } void render() { if(startvert == endvert) return; if(!vbo) { glGenBuffers_(1, &vbo); dirty = true; } gle::bindvbo(vbo); int count = endvert < startvert ? maxverts - startvert : endvert - startvert; if(dirty) { glBufferData_(GL_ARRAY_BUFFER, maxverts*sizeof(stainvert), nullptr, GL_STREAM_DRAW); glBufferSubData_(GL_ARRAY_BUFFER, 0, count*sizeof(stainvert), &verts[startvert]); if(endvert < startvert) { glBufferSubData_(GL_ARRAY_BUFFER, count*sizeof(stainvert), endvert*sizeof(stainvert), verts); count += endvert; } dirty = false; } else if(endvert < startvert) count += endvert; const stainvert *ptr = 0; gle::vertexpointer(sizeof(stainvert), ptr->pos.v); gle::texcoord0pointer(sizeof(stainvert), ptr->tc.v); gle::colorpointer(sizeof(stainvert), ptr->color.v); glDrawArrays(GL_TRIANGLES, 0, count); xtravertsva += count; } stainvert *addtri() { stainvert *tri = &verts[endvert]; availverts -= 3; endvert += 3; if(endvert >= maxverts) endvert = 0; return tri; } void addstain(staininfo &d) { dirty = true; } bool hasverts() const { return startvert != endvert; } int nextverts() const { return endvert < lastvert ? endvert + maxverts - lastvert : endvert - lastvert; } int totalverts() const { return endvert < startvert ? maxverts - (startvert - endvert) : endvert - startvert; } int totaltris() const { return (maxverts - 3 - availverts)/3; } }; struct stainrenderer { const char *texname; int flags, fadeintime, fadeouttime, timetolive; Texture *tex; staininfo *stains; int maxstains, startstain, endstain; stainbuffer verts[NUMSTAINBUFS]; stainrenderer(const char *texname, int flags = 0, int fadeintime = 0, int fadeouttime = 1000, int timetolive = -1) : texname(texname), flags(flags), fadeintime(fadeintime), fadeouttime(fadeouttime), timetolive(timetolive), tex(nullptr), stains(nullptr), maxstains(0), startstain(0), endstain(0), stainu(0), stainv(0) { } ~stainrenderer() { DELETEA(stains); } bool usegbuffer() const { return !(flags&(SF_INVMOD|SF_GLOW)); } void init(int tris) { if(stains) { DELETEA(stains); maxstains = startstain = endstain = 0; } stains = new staininfo[tris]; maxstains = tris; loopi(NUMSTAINBUFS) verts[i].init(i == STAINBUF_TRANSPARENT ? tris/2 : tris); } void preload() { tex = textureload(texname, 3); } int totalstains() { return endstain < startstain ? maxstains - (startstain - endstain) : endstain - startstain; } bool hasstains(int sbuf) { return verts[sbuf].hasverts(); } void clearstains() { startstain = endstain = 0; loopi(NUMSTAINBUFS) verts[i].clear(); } int freestain() { if(startstain==endstain) return 0; staininfo &d = stains[startstain]; startstain++; if(startstain >= maxstains) startstain = 0; return verts[d.owner].freestain(d); } bool faded(const staininfo &d) const { return verts[d.owner].faded(d); } void fadestain(const staininfo &d, uchar alpha) { bvec color = d.color; if(flags&(SF_OVERBRIGHT|SF_GLOW|SF_INVMOD)) color.scale(alpha, 255); verts[d.owner].fadestain(d, bvec4(color, alpha)); } void clearfadedstains() { int threshold = lastmillis - (timetolive>=0 ? timetolive : stainfade) - fadeouttime; staininfo *d = &stains[startstain], *end = &stains[endstain < startstain ? maxstains : endstain], *cleared[NUMSTAINBUFS] = { nullptr }; for(; d < end && d->millis <= threshold; d++) cleared[d->owner] = d; if(d >= end && endstain < startstain) for(d = stains, end = &stains[endstain]; d < end && d->millis <= threshold; d++) cleared[d->owner] = d; startstain = d - stains; if(startstain == endstain) loopi(NUMSTAINBUFS) verts[i].clear(); else loopi(NUMSTAINBUFS) if(cleared[i]) verts[i].clearstains(*cleared[i]); } void fadeinstains() { if(!fadeintime) return; staininfo *d = &stains[endstain], *end = &stains[endstain < startstain ? 0 : startstain]; while(d > end) { d--; int fade = lastmillis - d->millis; if(fade < fadeintime) fadestain(*d, (fade<<8)/fadeintime); else if(faded(*d)) fadestain(*d, 255); else return; } if(endstain < startstain) { d = &stains[maxstains]; end = &stains[startstain]; while(d > end) { d--; int fade = lastmillis - d->millis; if(fade < fadeintime) fadestain(*d, (fade<<8)/fadeintime); else if(faded(*d)) fadestain(*d, 255); else return; } } } void fadeoutstains() { staininfo *d = &stains[startstain], *end = &stains[endstain < startstain ? maxstains : endstain]; int offset = (timetolive>=0 ? timetolive : stainfade) + fadeouttime - lastmillis; while(d < end) { int fade = d->millis + offset; if(fade >= fadeouttime) return; fadestain(*d, (fade<<8)/fadeouttime); d++; } if(endstain < startstain) { d = stains; end = &stains[endstain]; while(d < end) { int fade = d->millis + offset; if(fade >= fadeouttime) return; fadestain(*d, (fade<<8)/fadeouttime); d++; } } } static void setuprenderstate(int sbuf, bool gbuf, int layer) { if(gbuf) maskgbuffer(sbuf == STAINBUF_TRANSPARENT ? "cg" : "c"); else zerofogcolor(); if(layer && ghasstencil) { glStencilFunc(GL_EQUAL, layer, 0x07); glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP); } glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_FALSE); enablepolygonoffset(GL_POLYGON_OFFSET_FILL); glDepthMask(GL_FALSE); glEnable(GL_BLEND); gle::enablevertex(); gle::enabletexcoord0(); gle::enablecolor(); } static void cleanuprenderstate(int sbuf, bool gbuf, int layer) { gle::clearvbo(); gle::disablevertex(); gle::disabletexcoord0(); gle::disablecolor(); glDepthMask(GL_TRUE); glDisable(GL_BLEND); disablepolygonoffset(GL_POLYGON_OFFSET_FILL); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); if(gbuf) maskgbuffer(sbuf == STAINBUF_TRANSPARENT ? "cndg" : "cnd"); else resetfogcolor(); } void cleanup() { loopi(NUMSTAINBUFS) verts[i].cleanup(); } void render(int sbuf) { float colorscale = 1, alphascale = 1; if(flags&SF_OVERBRIGHT) { glBlendFunc(GL_DST_COLOR, GL_SRC_COLOR); SETVARIANT(overbrightstain, sbuf == STAINBUF_TRANSPARENT ? 0 : -1, 0); } else if(flags&SF_GLOW) { glBlendFunc(GL_ONE, GL_ONE); colorscale = ldrscale; if(flags&SF_SATURATE) colorscale *= 2; alphascale = 0; SETSHADER(foggedstain); } else if(flags&SF_INVMOD) { glBlendFunc(GL_ZERO, GL_ONE_MINUS_SRC_COLOR); alphascale = 0; SETSHADER(foggedstain); } else { glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); colorscale = ldrscale; if(flags&SF_SATURATE) colorscale *= 2; SETVARIANT(stain, sbuf == STAINBUF_TRANSPARENT ? 0 : -1, 0); } LOCALPARAMF(colorscale, colorscale, colorscale, colorscale, alphascale); glBindTexture(GL_TEXTURE_2D, tex->id); verts[sbuf].render(); } staininfo &newstain() { staininfo &d = stains[endstain]; int next = endstain + 1; if(next>=maxstains) next = 0; if(next==startstain) freestain(); endstain = next; return d; } ivec bbmin, bbmax; vec staincenter, stainnormal, staintangent, stainbitangent; float stainradius, stainu, stainv; bvec4 staincolor; void addstain(const vec ¢er, const vec &dir, float radius, const bvec &color, int info) { if(dir.iszero()) return; bbmin = ivec(center).sub(radius); bbmax = ivec(center).add(radius).add(1); staincolor = bvec4(color, 255); staincenter = center; stainradius = radius; stainnormal = dir; #if 0 staintangent.orthogonal(dir); #else staintangent = vec(dir.z, -dir.x, dir.y); staintangent.project(dir); #endif if(flags&SF_ROTATE) staintangent.rotate(sincos360[rnd(360)], dir); staintangent.normalize(); stainbitangent.cross(staintangent, dir); if(flags&SF_RND4) { stainu = 0.5f*(info&1); stainv = 0.5f*((info>>1)&1); } loopi(NUMSTAINBUFS) verts[i].lastvert = verts[i].endvert; gentris(worldroot, ivec(0, 0, 0), worldsize>>1); loopi(NUMSTAINBUFS) { stainbuffer &buf = verts[i]; if(buf.endvert == buf.lastvert) continue; if(dbgstain) { int nverts = buf.nextverts(); static const char * const sbufname[NUMSTAINBUFS] = { "opaque", "transparent", "mapmodel" }; conoutf(CON_DEBUG, "tris = %d, verts = %d, total tris = %d, %s", nverts/3, nverts, buf.totaltris(), sbufname[i]); } staininfo &d = newstain(); d.owner = i; d.color = color; d.millis = lastmillis; d.startvert = buf.lastvert; d.endvert = buf.endvert; buf.addstain(d); } } void gentris(cube &cu, int orient, const ivec &o, int size, materialsurface *mat = nullptr, int vismask = 0) { vec pos[MAXFACEVERTS+4]; int numverts = 0, numplanes = 1; vec planes[2]; if(mat) { planes[0] = vec(0, 0, 0); switch(orient) { #define GENFACEORIENT(orient, v0, v1, v2, v3) \ case orient: \ planes[0][dimension(orient)] = dimcoord(orient) ? 1 : -1; \ v0 v1 v2 v3 \ break; #define GENFACEVERT(orient, vert, x,y,z, xv,yv,zv) \ pos[numverts++] = vec(x xv, y yv, z zv); GENFACEVERTS(o.x, o.x, o.y, o.y, o.z, o.z, , + mat->csize, , + mat->rsize, + 0.1f, - 0.1f); #undef GENFACEORIENT #undef GENFACEVERT } } else if(cu.texture[orient] == DEFAULT_SKY) return; else if(cu.ext && (numverts = cu.ext->surfaces[orient].numverts&MAXFACEVERTS)) { vertinfo *verts = cu.ext->verts() + cu.ext->surfaces[orient].verts; ivec vo = ivec(o).mask(~0xFFF).shl(3); loopj(numverts) pos[j] = vec(verts[j].getxyz().add(vo)).mul(1/8.0f); planes[0].cross(pos[0], pos[1], pos[2]).normalize(); if(numverts >= 4 && !(cu.merged&(1< stainradius) continue; vec pcenter = vec(stainnormal).mul(dist).add(staincenter); #else // travel back along plane normal from the stain center float dist = n.dot(p); if(fabs(dist) > stainradius) continue; vec pcenter = vec(n).mul(dist).add(staincenter); #endif vec ft, fb; ft.orthogonal(n); ft.normalize(); fb.cross(ft, n); vec pt = vec(ft).mul(ft.dot(staintangent)).add(vec(fb).mul(fb.dot(staintangent))).normalize(), pb = vec(ft).mul(ft.dot(stainbitangent)).add(vec(fb).mul(fb.dot(stainbitangent))).project(pt).normalize(); vec v1[MAXFACEVERTS+4], v2[MAXFACEVERTS+4]; float ptc = pt.dot(pcenter), pbc = pb.dot(pcenter); int numv; if(numplanes >= 2) { if(l) { pos[1] = pos[2]; pos[2] = pos[3]; } numv = polyclip(pos, 3, pt, ptc - stainradius, ptc + stainradius, v1); if(numv<3) continue; } else { numv = polyclip(pos, numverts, pt, ptc - stainradius, ptc + stainradius, v1); if(numv<3) continue; } numv = polyclip(v1, numv, pb, pbc - stainradius, pbc + stainradius, v2); if(numv<3) continue; float tsz = flags&SF_RND4 ? 0.5f : 1.0f, scale = tsz*0.5f/stainradius, tu = stainu + tsz*0.5f - ptc*scale, tv = stainv + tsz*0.5f - pbc*scale; pt.mul(scale); pb.mul(scale); stainvert dv1 = { v2[0], staincolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) }, dv2 = { v2[1], staincolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) }; int totalverts = 3*(numv-2); if(totalverts > buf.maxverts-3) return; while(buf.availverts < totalverts) { if(!freestain()) return; } loopk(numv-2) { stainvert *tri = buf.addtri(); tri[0] = dv1; tri[1] = dv2; dv2.pos = v2[k+2]; dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv); tri[2] = dv2; } } } void findmaterials(vtxarray *va) { materialsurface *matbuf = va->matbuf; int matsurfs = va->matsurfs; loopi(matsurfs) { materialsurface &m = matbuf[i]; if(!isclipped(m.material&MATF_VOLUME)) { i += m.skip; continue; } int dim = dimension(m.orient), dc = dimcoord(m.orient); if(dc ? stainnormal[dim] <= 0 : stainnormal[dim] >= 0) { i += m.skip; continue; } int c = C[dim], r = R[dim]; for(;;) { materialsurface &m = matbuf[i]; if(m.o[dim] >= bbmin[dim] && m.o[dim] <= bbmax[dim] && m.o[c] + m.csize >= bbmin[c] && m.o[c] <= bbmax[c] && m.o[r] + m.rsize >= bbmin[r] && m.o[r] <= bbmax[r]) { static cube dummy; gentris(dummy, m.orient, m.o, max(m.csize, m.rsize), &m); } if(i+1 >= matsurfs) break; materialsurface &n = matbuf[i+1]; if(n.material != m.material || n.orient != m.orient) break; i++; } } } void findescaped(cube *c, const ivec &o, int size, int escaped) { loopi(8) { cube &cu = c[i]; if(escaped&(1<>1, cu.escaped); else { int vismask = cu.merged; if(vismask) loopj(6) if(vismask&(1< stainradius) return; vec pcenter = vec(stainnormal).mul(dist).add(staincenter); #else float dist = n.dot(p); if(fabs(dist) > stainradius) return; vec pcenter = vec(n).mul(dist).add(staincenter); #endif vec ft, fb; ft.orthogonal(n); ft.normalize(); fb.cross(ft, n); vec pt = vec(ft).mul(ft.dot(staintangent)).add(vec(fb).mul(fb.dot(staintangent))).normalize(), pb = vec(ft).mul(ft.dot(stainbitangent)).add(vec(fb).mul(fb.dot(stainbitangent))).project(pt).normalize(); vec v1[3+4], v2[3+4]; float ptc = pt.dot(pcenter), pbc = pb.dot(pcenter); int numv = polyclip(v, 3, pt, ptc - stainradius, ptc + stainradius, v1); if(numv<3) return; numv = polyclip(v1, numv, pb, pbc - stainradius, pbc + stainradius, v2); if(numv<3) return; float tsz = flags&SF_RND4 ? 0.5f : 1.0f, scale = tsz*0.5f/stainradius, tu = stainu + tsz*0.5f - ptc*scale, tv = stainv + tsz*0.5f - pbc*scale; pt.mul(scale); pb.mul(scale); stainvert dv1 = { v2[0], staincolor, vec2(pt.dot(v2[0]) + tu, pb.dot(v2[0]) + tv) }, dv2 = { v2[1], staincolor, vec2(pt.dot(v2[1]) + tu, pb.dot(v2[1]) + tv) }; int totalverts = 3*(numv-2); stainbuffer &buf = verts[STAINBUF_MAPMODEL]; if(totalverts > buf.maxverts-3) return; while(buf.availverts < totalverts) { if(!freestain()) return; } loopk(numv-2) { stainvert *tri = buf.addtri(); tri[0] = dv1; tri[1] = dv2; dv2.pos = v2[k+2]; dv2.tc = vec2(pt.dot(v2[k+2]) + tu, pb.dot(v2[k+2]) + tv); tri[2] = dv2; } } void genmmtris(octaentities &oe) { const vector &ents = entities::getents(); loopv(oe.mapmodels) { extentity &e = *ents[oe.mapmodels[i]]; model *m = loadmapmodel(e.attr1); if(!m) continue; vec center, radius; float rejectradius = m->collisionbox(center, radius), scale = e.attr5 > 0 ? e.attr5/100.0f : 1; center.mul(scale); if(staincenter.reject(vec(e.o).add(center), stainradius + rejectradius*scale)) continue; if(m->animated() || (!m->bih && !m->setBIH())) continue; int yaw = e.attr2, pitch = e.attr3, roll = e.attr4; m->bih->genstaintris(this, staincenter, stainradius, e.o, yaw, pitch, roll, scale); } } void gentris(cube *c, const ivec &o, int size, int escaped = 0) { int overlap = octaboxoverlap(o, size, bbmin, bbmax); loopi(8) { cube &cu = c[i]; if(overlap&(1<va && cu.ext->va->matsurfs) findmaterials(cu.ext->va); if(cu.ext->ents && cu.ext->ents->mapmodels.length()) genmmtris(*cu.ext->ents); } if(cu.children) gentris(cu.children, co, size>>1, cu.escaped); else { int vismask = cu.visible; if(vismask&0xC0) { if(vismask&0x80) loopj(6) gentris(cu, j, co, size, nullptr, vismask); else loopj(6) if(vismask&(1<>1, cu.escaped); else { int vismask = cu.merged; if(vismask) loopj(6) if(vismask&(1<media/particle/blood.png", SF_RND4|SF_ROTATE|SF_INVMOD), stainrenderer("media/particle/pulse_scorch.png", SF_ROTATE, 500), stainrenderer("media/particle/rail_hole.png", SF_ROTATE|SF_OVERBRIGHT), stainrenderer("media/particle/pulse_glow.png", SF_ROTATE|SF_GLOW|SF_SATURATE, 250, 1500, 250), stainrenderer("media/particle/rail_glow.png", SF_ROTATE|SF_GLOW|SF_SATURATE, 100, 1100, 100) }; void initstains() { if(initing) return; loopi(sizeof(stains)/sizeof(stains[0])) stains[i].init(maxstaintris); loopi(sizeof(stains)/sizeof(stains[0])) { loadprogress = float(i+1)/(sizeof(stains)/sizeof(stains[0])); stains[i].preload(); } loadprogress = 0; } void clearstains() { loopi(sizeof(stains)/sizeof(stains[0])) stains[i].clearstains(); } VARNP(stains, showstains, 0, 1, 1); bool renderstains(int sbuf, bool gbuf, int layer) { bool rendered = false; loopi(sizeof(stains)/sizeof(stains[0])) { stainrenderer &d = stains[i]; if(d.usegbuffer() != gbuf) continue; if(sbuf == STAINBUF_OPAQUE) { d.clearfadedstains(); d.fadeinstains(); d.fadeoutstains(); } if(!showstains || !d.hasstains(sbuf)) continue; if(!rendered) { rendered = true; stainrenderer::setuprenderstate(sbuf, gbuf, layer); } d.render(sbuf); } if(!rendered) return false; stainrenderer::cleanuprenderstate(sbuf, gbuf, layer); return true; } void cleanupstains() { loopi(sizeof(stains)/sizeof(stains[0])) stains[i].cleanup(); } VARP(maxstaindistance, 1, 512, 10000); void addstain(int type, const vec ¢er, const vec &surface, float radius, const bvec &color, int info) { if(!showstains || type<0 || (size_t)type>=sizeof(stains)/sizeof(stains[0]) || center.dist(camera1->o) - radius > maxstaindistance) return; stainrenderer &d = stains[type]; d.addstain(center, surface, radius, color, info); } void genstainmmtri(stainrenderer *s, const vec v[3]) { s->genmmtri(v); }