#include "engine.h" extern int intel_mapbufferrange_bug; VARNP(blobs, showblobs, 0, 1, 1); VARFP(blobintensity, 0, 60, 100, resetblobs()); VARFP(blobheight, 1, 32, 128, resetblobs()); VARFP(blobfadelow, 1, 8, 32, resetblobs()); VARFP(blobfadehigh, 1, 8, 32, resetblobs()); VARFP(blobmargin, 0, 1, 16, resetblobs()); VAR(dbgblob, 0, 0, 1); enum { BL_DUP = 1<<0, BL_RENDER = 1<<1 }; struct blobinfo { vec o; float radius; int millis; ushort startindex, endindex, startvert, endvert, next, flags; }; struct blobvert { vec pos; bvec4 color; vec2 tc; }; struct blobrenderer { const char *texname; Texture *tex; ushort *cache; int cachesize; blobinfo *blobs; int maxblobs, startblob, endblob; blobvert *verts; int maxverts, startvert, endvert, availverts; ushort *indexes; int maxindexes, startindex, endindex, availindexes; GLuint ebo, vbo; ushort *edata; blobvert *vdata; int numedata, numvdata; blobinfo *startrender, *endrender; blobinfo *lastblob; vec blobmin, blobmax; ivec bbmin, bbmax; float blobalphalow, blobalphahigh; uchar blobalpha; blobrenderer(const char *texname) : texname(texname), tex(NULL), cache(NULL), cachesize(0), blobs(NULL), maxblobs(0), startblob(0), endblob(0), verts(NULL), maxverts(0), startvert(0), endvert(0), availverts(0), indexes(NULL), maxindexes(0), startindex(0), endindex(0), availindexes(0), ebo(0), vbo(0), edata(NULL), vdata(NULL), numedata(0), numvdata(0), startrender(NULL), endrender(NULL), lastblob(NULL) {} ~blobrenderer() { DELETEA(cache); DELETEA(blobs); DELETEA(verts); DELETEA(indexes); } void cleanup() { if(ebo) { glDeleteBuffers_(1, &ebo); ebo = 0; } if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; } DELETEA(edata); DELETEA(vdata); numedata = numvdata = 0; startrender = endrender = NULL; } void init(int tris) { cleanup(); if(cache) { DELETEA(cache); cachesize = 0; } if(blobs) { DELETEA(blobs); maxblobs = startblob = endblob = 0; } if(verts) { DELETEA(verts); maxverts = startvert = endvert = availverts = 0; } if(indexes) { DELETEA(indexes); maxindexes = startindex = endindex = availindexes = 0; } if(!tris) return; tex = textureload(texname, 3); cachesize = tris/2; cache = new ushort[cachesize]; memset(cache, 0xFF, cachesize * sizeof(ushort)); maxblobs = tris/2; blobs = new blobinfo[maxblobs]; memclear(blobs, maxblobs); maxindexes = tris*3 + 3; availindexes = maxindexes - 3; indexes = new ushort[maxindexes]; maxverts = min(tris*3/2 + 1, (1<<16)-1); availverts = maxverts - 1; verts = new blobvert[maxverts]; } bool freeblob() { blobinfo &b = blobs[startblob]; if(&b == lastblob) return false; if(b.flags & BL_RENDER) flushblobs(); startblob++; if(startblob >= maxblobs) startblob = 0; startvert = b.endvert; if(startvert>=maxverts) startvert = 0; availverts += b.endvert - b.startvert; startindex = b.endindex; if(startindex>=maxindexes) startindex = 0; availindexes += b.endindex - b.startindex; b.millis = lastreset; b.flags = 0; return true; } blobinfo &newblob(const vec &o, float radius) { blobinfo &b = blobs[endblob]; int next = endblob + 1; if(next>=maxblobs) next = 0; if(next==startblob) { lastblob = &b; freeblob(); } endblob = next; b.o = o; b.radius = radius; b.millis = totalmillis; b.flags = 0; b.next = 0xFFFF; b.startindex = b.endindex = endindex; b.startvert = b.endvert = endvert; lastblob = &b; return b; } template static int split(const vec *in, int numin, float below, float above, vec *out) { int numout = 0; const vec *p = &in[numin-1]; float pc = (*p)[C]; loopi(numin) { const vec &v = in[i]; float c = v[C]; if(c < below) { if(pc > above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); if(pc > below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); } else if(c > above) { if(pc < below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); if(pc < above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); } else if(pc < below) { if(c > below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); } else if(pc > above && c < above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); out[numout++] = v; p = &v; pc = c; } return numout; } template static int clip(const vec *in, int numin, float below, float above, vec *out) { int numout = 0; const vec *p = &in[numin-1]; float pc = (*p)[C]; loopi(numin) { const vec &v = in[i]; float c = v[C]; if(c < below) { if(pc > above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); if(pc > below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); } else if(c > above) { if(pc < below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); if(pc < above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); } else { if(pc < below) { if(c > below) out[numout++] = vec(*p).sub(v).mul((below - c)/(pc - c)).add(v); } else if(pc > above && c < above) out[numout++] = vec(*p).sub(v).mul((above - c)/(pc - c)).add(v); out[numout++] = v; } p = &v; pc = c; } return numout; } void dupblob() { if(lastblob->startvert >= lastblob->endvert) { lastblob->startindex = lastblob->endindex = endindex; lastblob->startvert = lastblob->endvert = endvert; return; } blobinfo &b = newblob(lastblob->o, lastblob->radius); b.flags |= BL_DUP; } inline int addvert(const vec &pos) { blobvert &v = verts[endvert]; v.pos = pos; v.tc = vec2((pos.x - blobmin.x) / (blobmax.x - blobmin.x), (pos.y - blobmin.y) / (blobmax.y - blobmin.y)); uchar alpha; if(pos.z < blobmin.z + blobfadelow) alpha = uchar(blobalphalow * (pos.z - blobmin.z)); else if(pos.z > blobmax.z - blobfadehigh) alpha = uchar(blobalphahigh * (blobmax.z - pos.z)); else alpha = blobalpha; v.color = bvec4(255, 255, 255, alpha); return endvert++; } void addtris(const vec *v, int numv) { if(endvert != int(lastblob->endvert) || endindex != int(lastblob->endindex)) dupblob(); for(const vec *cur = &v[2], *end = &v[numv];;) { int limit = maxverts - endvert - 2; if(limit <= 0) { while(availverts < limit+2) if(!freeblob()) return; availverts -= limit+2; lastblob->endvert = maxverts; endvert = 0; dupblob(); limit = maxverts - 2; } limit = min(int(end - cur), min(limit, (maxindexes - endindex)/3)); while(availverts < limit+2) if(!freeblob()) return; while(availindexes < limit*3) if(!freeblob()) return; int i1 = addvert(v[0]), i2 = addvert(cur[-1]); loopk(limit) { indexes[endindex++] = i1; indexes[endindex++] = i2; i2 = addvert(*cur++); indexes[endindex++] = i2; } availverts -= endvert - lastblob->endvert; availindexes -= endindex - lastblob->endindex; lastblob->endvert = endvert; lastblob->endindex = endindex; if(endvert >= maxverts) endvert = 0; if(endindex >= maxindexes) endindex = 0; if(cur >= end) break; dupblob(); } } void gentris(cube &cu, int orient, const ivec &o, int size, materialsurface *mat = NULL, int vismask = 0) { vec pos[MAXFACEVERTS+8]; int dim = dimension(orient), numverts = 0, numplanes = 1, flat = -1; if(mat) { switch(orient) { #define GENFACEORIENT(orient, v0, v1, v2, v3) \ case orient: 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 default: return; } flat = dim; } 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); if(numverts >= 4 && !(cu.merged&(1<= 0) { float offset = pos[0][dim]; if(offset < blobmin[dim] || offset > blobmax[dim]) return; flat = dim; } vec vmin = pos[0], vmax = pos[0]; for(int i = 1; i < numverts; i++) { vmin.min(pos[i]); vmax.max(pos[i]); } if(vmax.x < blobmin.x || vmin.x > blobmax.x || vmax.y < blobmin.y || vmin.y > blobmax.y || vmax.z < blobmin.z || vmin.z > blobmax.z) return; vec v1[MAXFACEVERTS+6+4], v2[MAXFACEVERTS+6+4]; loopl(numplanes) { vec *v = pos; int numv = numverts; if(numplanes >= 2) { if(l) { pos[1] = pos[2]; pos[2] = pos[3]; } numv = 3; } if(vec().cross(v[0], v[1], v[2]).z <= 0) continue; #define CLIPSIDE(clip, below, above) \ { \ vec *in = v; \ v = in==v1 ? v2 : v1; \ numv = clip(in, numv, below, above, v); \ if(numv < 3) continue; \ } if(flat!=0) CLIPSIDE(clip<0>, blobmin.x, blobmax.x); if(flat!=1) CLIPSIDE(clip<1>, blobmin.y, blobmax.y); if(flat!=2) { CLIPSIDE(clip<2>, blobmin.z, blobmax.z); CLIPSIDE(split<2>, blobmin.z + blobfadelow, blobmax.z - blobfadehigh); } addtris(v, numv); } } void findmaterials(vtxarray *va) { materialsurface *matbuf = va->matbuf; int matsurfs = va->matsurfs; loopi(matsurfs) { materialsurface &m = matbuf[i]; if(!isclipped(m.material&MATF_VOLUME) || m.orient == O_BOTTOM) { i += m.skip; continue; } int dim = dimension(m.orient), c = C[dim], r = R[dim]; for(;;) { materialsurface &m = matbuf[i]; if(m.o[dim] >= blobmin[dim] && m.o[dim] <= blobmax[dim] && m.o[c] + m.csize >= blobmin[c] && m.o[c] <= blobmax[c] && m.o[r] + m.rsize >= blobmin[r] && m.o[r] <= blobmax[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 *cu, const ivec &o, int size, int escaped) { loopi(8) { if(escaped&(1<>1, cu[i].escaped); else { int vismask = cu[i].merged; if(vismask) loopj(6) if(vismask&(1<va && cu[i].ext->va->matsurfs) findmaterials(cu[i].ext->va); if(cu[i].children) gentris(cu[i].children, co, size>>1, cu[i].escaped); else { int vismask = cu[i].visible; if(vismask&0xC0) { if(vismask&0x80) loopj(6) gentris(cu[i], j, co, size, NULL, vismask); else loopj(6) if(vismask&(1<>1, cu[i].escaped); else { int vismask = cu[i].merged; if(vismask) loopj(6) if(vismask&(1<>1); return !(b.flags & BL_DUP) ? &b : NULL; } static void setuprenderstate() { foggedshader->set(); enablepolygonoffset(GL_POLYGON_OFFSET_FILL); glDepthMask(GL_FALSE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); if(!dbgblob) glEnable(GL_BLEND); gle::enablevertex(); gle::enabletexcoord0(); gle::enablecolor(); } static void cleanuprenderstate() { gle::disablevertex(); gle::disabletexcoord0(); gle::disablecolor(); gle::clearvbo(); if(glversion >= 300) gle::clearebo(); glDepthMask(GL_TRUE); glDisable(GL_BLEND); disablepolygonoffset(GL_POLYGON_OFFSET_FILL); } static int lastreset; static void reset() { lastreset = totalmillis; } static blobrenderer *lastrender; void fadeblob(blobinfo *b, float fade) { float minz = b->o.z - (blobheight + blobfadelow), maxz = b->o.z + blobfadehigh, scale = fade*blobintensity*255/100.0f, scalelow = scale / blobfadelow, scalehigh = scale / blobfadehigh; uchar alpha = uchar(scale); b->millis = totalmillis; do { if(b->endvert - b->startvert >= 3) for(blobvert *v = &verts[b->startvert], *end = &verts[b->endvert]; v < end; v++) { float z = v->pos.z; if(z < minz + blobfadelow) v->color.a = uchar(scalelow * (z - minz)); else if(z > maxz - blobfadehigh) v->color.a = uchar(scalehigh * (maxz - z)); else v->color.a = alpha; } int offset = b - &blobs[0] + 1; if(offset >= maxblobs) offset = 0; if(offset < endblob ? offset > startblob || startblob > endblob : offset > startblob) b = &blobs[offset]; else break; } while(b->flags & BL_DUP); } void renderblob(const vec &o, float radius, float fade) { if(!blobs) initblobs(); if(glversion < 300 && lastrender != this) { if(!lastrender) setuprenderstate(); gle::vertexpointer(sizeof(blobvert), verts->pos.v); gle::texcoord0pointer(sizeof(blobvert), verts->tc.v); gle::colorpointer(sizeof(blobvert), verts->color.v); if(!lastrender || lastrender->tex != tex) glBindTexture(GL_TEXTURE_2D, tex->id); lastrender = this; } union { int i; float f; } ox, oy; ox.f = o.x; oy.f = o.y; uint hash = uint(ox.i^~oy.i^(INT_MAX-oy.i)^uint(radius)); hash %= cachesize; blobinfo *b = &blobs[cache[hash]]; if(b >= &blobs[maxblobs] || b->millis - lastreset <= 0 || b->o!=o || b->radius!=radius) { b = addblob(o, radius, fade); cache[hash] = ushort(b - blobs); if(!b) return; } else if(fade < 1 && totalmillis - b->millis > 0) fadeblob(b, fade); do { if(b->endvert - b->startvert >= 3) { if(glversion >= 300) { if(!startrender) { numedata = numvdata = 0; startrender = endrender = b; } else { endrender->next = ushort(b - blobs); endrender = b; } b->flags |= BL_RENDER; b->next = 0xFFFF; numedata += b->endindex - b->startindex; numvdata += b->endvert - b->startvert; } else { glDrawRangeElements_(GL_TRIANGLES, b->startvert, b->endvert-1, b->endindex - b->startindex, GL_UNSIGNED_SHORT, &indexes[b->startindex]); xtravertsva += b->endvert - b->startvert; } } int offset = b - &blobs[0] + 1; if(offset >= maxblobs) offset = 0; if(offset < endblob ? offset > startblob || startblob > endblob : offset > startblob) b = &blobs[offset]; else break; } while(b->flags & BL_DUP); } void flushblobs() { if(glversion < 300 || !startrender) return; if(lastrender != this) { if(!lastrender) setuprenderstate(); lastrender = this; } if(!ebo) glGenBuffers_(1, &ebo); if(!vbo) glGenBuffers_(1, &vbo); gle::bindebo(ebo); glBufferData_(GL_ELEMENT_ARRAY_BUFFER, maxindexes*sizeof(ushort), NULL, GL_STREAM_DRAW); gle::bindvbo(vbo); glBufferData_(GL_ARRAY_BUFFER, maxverts*sizeof(blobvert), NULL, GL_STREAM_DRAW); ushort *estart; blobvert *vstart; if(intel_mapbufferrange_bug) { if(!edata) edata = new ushort[maxindexes]; if(!vdata) vdata = new blobvert[maxverts]; estart = edata; vstart = vdata; } else { estart = (ushort *)glMapBufferRange_(GL_ELEMENT_ARRAY_BUFFER, 0, numedata*sizeof(ushort), GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_RANGE_BIT|GL_MAP_UNSYNCHRONIZED_BIT); vstart = (blobvert *)glMapBufferRange_(GL_ARRAY_BUFFER, 0, numvdata*sizeof(blobvert), GL_MAP_WRITE_BIT|GL_MAP_INVALIDATE_RANGE_BIT|GL_MAP_UNSYNCHRONIZED_BIT); if(!estart || !vstart) { if(estart) glUnmapBuffer_(GL_ELEMENT_ARRAY_BUFFER); if(vstart) glUnmapBuffer_(GL_ARRAY_BUFFER); for(blobinfo *b = startrender;; b = &blobs[b->next]) { b->flags &= ~BL_RENDER; if(b->next >= maxblobs) break; } startrender = endrender = NULL; return; } } ushort *edst = estart; blobvert *vdst = vstart; for(blobinfo *b = startrender;; b = &blobs[b->next]) { b->flags &= ~BL_RENDER; ushort offset = ushort(vdst - vstart) - b->startvert; for(int i = b->startindex; i < b->endindex; ++i) *edst++ = indexes[i] + offset; memcpy(vdst, &verts[b->startvert], (b->endvert - b->startvert)*sizeof(blobvert)); vdst += b->endvert - b->startvert; if(b->next >= maxblobs) break; } startrender = endrender = NULL; if(intel_mapbufferrange_bug) { glBufferSubData_(GL_ELEMENT_ARRAY_BUFFER, 0, numedata*sizeof(ushort), estart); glBufferSubData_(GL_ARRAY_BUFFER, 0, numvdata*sizeof(blobvert), vstart); } else { glUnmapBuffer_(GL_ELEMENT_ARRAY_BUFFER); glUnmapBuffer_(GL_ARRAY_BUFFER); } const blobvert *ptr = 0; gle::vertexpointer(sizeof(blobvert), ptr->pos.v); gle::texcoord0pointer(sizeof(blobvert), ptr->tc.v); gle::colorpointer(sizeof(blobvert), ptr->color.v); glBindTexture(GL_TEXTURE_2D, tex->id); glDrawRangeElements_(GL_TRIANGLES, 0, numvdata-1, numedata, GL_UNSIGNED_SHORT, (ushort *)0); } }; int blobrenderer::lastreset = 0; blobrenderer *blobrenderer::lastrender = NULL; VARFP(blobstattris, 128, 4096, 16384, initblobs(BLOB_STATIC)); VARFP(blobdyntris, 128, 4096, 16384, initblobs(BLOB_DYNAMIC)); static blobrenderer blobs[] = { blobrenderer("packages/particles/blob.png"), blobrenderer("packages/particles/blob.png") }; void initblobs(int type) { if(type < 0 || (type==BLOB_STATIC && blobs[BLOB_STATIC].blobs)) blobs[BLOB_STATIC].init(showblobs ? blobstattris : 0); if(type < 0 || (type==BLOB_DYNAMIC && blobs[BLOB_DYNAMIC].blobs)) blobs[BLOB_DYNAMIC].init(showblobs ? blobdyntris : 0); } void resetblobs() { blobrenderer::reset(); } void renderblob(int type, const vec &o, float radius, float fade) { if(!showblobs) return; if(refracting < 0 && o.z - blobheight - blobfadelow >= reflectz) return; blobs[type].renderblob(o, radius + blobmargin, fade); } void flushblobs() { loopi(sizeof(blobs)/sizeof(blobs[0])) blobs[i].flushblobs(); if(blobrenderer::lastrender) blobrenderer::cleanuprenderstate(); blobrenderer::lastrender = NULL; } void cleanupblobs() { loopi(sizeof(blobs)/sizeof(blobs[0])) blobs[i].cleanup(); }