1 #include "engine.h"
2 
3 VARNP(blobs, showblobs, 0, 1, 1);
4 VARFP(blobintensity, 0, 60, 100, resetblobs());
5 VARFP(blobheight, 1, 32, 128, resetblobs());
6 VARFP(blobfadelow, 1, 8, 32, resetblobs());
7 VARFP(blobfadehigh, 1, 8, 32, resetblobs());
8 VARFP(blobmargin, 0, 1, 16, resetblobs());
9 
10 VAR(dbgblob, 0, 0, 1);
11 
12 struct blobinfo
13 {
14     vec o;
15     float radius;
16     int millis;
17     uint startindex, endindex;
18     ushort startvert, endvert;
19 };
20 
21 struct blobvert
22 {
23     vec pos;
24     float u, v;
25     bvec color;
26     uchar alpha;
27 };
28 
29 struct blobrenderer
30 {
31     const char *texname;
32     Texture *tex;
33     blobinfo **cache;
34     int cachesize;
35     blobinfo *blobs;
36     int maxblobs, startblob, endblob;
37     blobvert *verts;
38     int maxverts, startvert, endvert, availverts;
39     ushort *indexes;
40     int maxindexes, startindex, endindex, availindexes;
41 
42     blobinfo *lastblob, *flushblob;
43 
44     vec blobmin, blobmax;
45     ivec bborigin, bbsize;
46     float blobalphalow, blobalphahigh;
47     uchar blobalpha;
48 
blobrendererblobrenderer49     blobrenderer(const char *texname)
50       : texname(texname), tex(NULL),
51         cache(NULL), cachesize(0),
52         blobs(NULL), maxblobs(0), startblob(0), endblob(0),
53         verts(NULL), maxverts(0), startvert(0), endvert(0), availverts(0),
54         indexes(NULL), maxindexes(0), startindex(0), endindex(0), availindexes(0),
55         lastblob(NULL)
56     {}
57 
initblobrenderer58     void init(int tris)
59     {
60         if(cache)
61         {
62             DELETEA(cache);
63             cachesize = 0;
64         }
65         if(blobs)
66         {
67             DELETEA(blobs);
68             maxblobs = startblob = endblob = 0;
69         }
70         if(verts)
71         {
72             DELETEA(verts);
73             maxverts = startvert = endvert = availverts = 0;
74         }
75         if(indexes)
76         {
77             DELETEA(indexes);
78             maxindexes = startindex = endindex = availindexes = 0;
79         }
80         if(!tris) return;
81         tex = textureload(texname, 3);
82         cachesize = tris/2;
83         cache = new blobinfo *[cachesize];
84         memset(cache, 0, cachesize * sizeof(blobinfo *));
85         maxblobs = tris/2;
86         blobs = new blobinfo[maxblobs];
87         memset(blobs, 0, maxblobs * sizeof(blobinfo));
88         maxindexes = tris*3 + 3;
89         availindexes = maxindexes - 3;
90         indexes = new ushort[maxindexes];
91         maxverts = min(tris*3/2 + 1, (1<<16)-1);
92         availverts = maxverts - 1;
93         verts = new blobvert[maxverts];
94     }
95 
freeblobblobrenderer96     bool freeblob()
97     {
98         blobinfo &b = blobs[startblob];
99         if(&b == lastblob) return false;
100 
101         startblob++;
102         if(startblob >= maxblobs) startblob = 0;
103 
104         startvert = b.endvert;
105         if(startvert>=maxverts) startvert = 0;
106         availverts += b.endvert - b.startvert;
107 
108         startindex = b.endindex;
109         if(startindex>=maxindexes) startindex = 0;
110         availindexes += b.endindex - b.startindex;
111 
112         b.millis = 0;
113 
114         return true;
115     }
116 
newblobblobrenderer117     blobinfo &newblob(const vec &o, float radius)
118     {
119         blobinfo &b = blobs[endblob];
120         int next = endblob + 1;
121         if(next>=maxblobs) next = 0;
122         if(next==startblob)
123         {
124             lastblob = &b;
125             freeblob();
126         }
127         endblob = next;
128         b.o = o;
129         b.radius = radius;
130         b.millis = totalmillis;
131         b.startindex = b.endindex = endindex;
132         b.startvert = b.endvert = endvert;
133         lastblob = &b;
134         return b;
135     }
136 
clearblobsblobrenderer137     void clearblobs()
138     {
139         startblob = endblob = 0;
140         startvert = endvert = 0;
141         availverts = maxverts - 1;
142         startindex = endindex = 0;
143         availindexes = maxindexes - 3;
144     }
145 
146 
147     template<int C>
splitblobrenderer148     static int split(const vec *in, int numin, float val, vec *out)
149     {
150         int numout = 0;
151         const vec *n = in;
152         float c = (*n)[C];
153         loopi(numin-1)
154         {
155             const vec &p = *n++;
156             float pc = c;
157             c = (*n)[C];
158             out[numout++] = p;
159             if(pc < val ? c > val : pc > val && c < val) (out[numout++] = *n).sub(p).mul((pc - val) / (pc - c)).add(p);
160         }
161         float ic = (*in)[C];
162         out[numout++] = *n;
163         if(c < val ? ic > val : c > val && ic < val) (out[numout++] = *in).sub(*n).mul((c - val) / (c - ic)).add(*n);
164         return numout;
165     }
166 
167     template<int C>
clipaboveblobrenderer168     static int clipabove(const vec *in, int numin, float val, vec *out)
169     {
170         int numout = 0;
171         const vec *n = in;
172         float c = (*n)[C];
173         loopi(numin-1)
174         {
175             const vec &p = *n++;
176             float pc = c;
177             c = (*n)[C];
178             if(pc >= val)
179             {
180                 out[numout++] = p;
181                 if(pc > val && c < val) (out[numout++] = *n).sub(p).mul((pc - val) / (pc - c)).add(p);
182             }
183             else if(c > val) (out[numout++] = *n).sub(p).mul((pc - val) / (pc - c)).add(p);
184         }
185         float ic = (*in)[C];
186         if(c >= val)
187         {
188             out[numout++] = *n;
189             if(c > val && ic < val) (out[numout++] = *in).sub(*n).mul((c - val) / (c - ic)).add(*n);
190         }
191         else if(ic > val) (out[numout++] = *in).sub(*n).mul((c - val) / (c - ic)).add(*n);
192         return numout;
193     }
194 
195     template<int C>
clipbelowblobrenderer196     static int clipbelow(const vec *in, int numin, float val, vec *out)
197     {
198         int numout = 0;
199         const vec *n = in;
200         float c = (*n)[C];
201         loopi(numin-1)
202         {
203             const vec &p = *n++;
204             float pc = c;
205             c = (*n)[C];
206             if(pc <= val)
207             {
208                 out[numout++] = p;
209                 if(pc < val && c > val) (out[numout++] = *n).sub(p).mul((pc - val) / (pc - c)).add(p);
210             }
211             else if(c < val) (out[numout++] = *n).sub(p).mul((pc - val) / (pc - c)).add(p);
212         }
213         float ic = (*in)[C];
214         if(c <= val)
215         {
216             out[numout++] = *n;
217             if(c < val && ic > val) (out[numout++] = *in).sub(*n).mul((c - val) / (c - ic)).add(*n);
218         }
219         else if(ic < val) (out[numout++] = *in).sub(*n).mul((c - val) / (c - ic)).add(*n);
220         return numout;
221     }
222 
dupblobblobrenderer223     void dupblob()
224     {
225         if(lastblob->startvert >= lastblob->endvert)
226         {
227             lastblob->startindex = lastblob->endindex = endindex;
228             lastblob->startvert = lastblob->endvert = endvert;
229             return;
230         }
231         blobinfo &b = newblob(lastblob->o, lastblob->radius);
232         b.millis = -1;
233     }
234 
addvertblobrenderer235     inline int addvert(const vec &pos)
236     {
237         blobvert &v = verts[endvert];
238         v.pos = pos;
239         v.u = (pos.x - blobmin.x) / (blobmax.x - blobmin.x);
240         v.v = (pos.y - blobmin.y) / (blobmax.y - blobmin.y);
241         v.color = bvec(255, 255, 255);
242         if(pos.z < blobmin.z + blobfadelow) v.alpha = uchar(blobalphalow * (pos.z - blobmin.z));
243         else if(pos.z > blobmax.z - blobfadehigh) v.alpha = uchar(blobalphahigh * (blobmax.z - pos.z));
244         else v.alpha = blobalpha;
245         return endvert++;
246     }
247 
addtrisblobrenderer248     void addtris(const vec *v, int numv)
249     {
250         if(endvert != int(lastblob->endvert) || endindex != int(lastblob->endindex)) dupblob();
251         for(const vec *cur = &v[2], *end = &v[numv];;)
252         {
253             int limit = maxverts - endvert - 2;
254             if(limit <= 0)
255             {
256                 while(availverts < limit+2) if(!freeblob()) return;
257                 availverts -= limit+2;
258                 lastblob->endvert = maxverts;
259                 endvert = 0;
260                 dupblob();
261                 limit = maxverts - 2;
262             }
263             limit = min(int(end - cur), min(limit, (maxindexes - endindex)/3));
264             while(availverts < limit+2) if(!freeblob()) return;
265             while(availindexes < limit*3) if(!freeblob()) return;
266 
267             int i1 = addvert(v[0]), i2 = addvert(cur[-1]);
268             loopk(limit)
269             {
270                 indexes[endindex++] = i1;
271                 indexes[endindex++] = i2;
272                 i2 = addvert(*cur++);
273                 indexes[endindex++] = i2;
274             }
275 
276             availverts -= endvert - lastblob->endvert;
277             availindexes -= endindex - lastblob->endindex;
278             lastblob->endvert = endvert;
279             lastblob->endindex = endindex;
280             if(endvert >= maxverts) endvert = 0;
281             if(endindex >= maxindexes) endindex = 0;
282 
283             if(cur >= end) break;
284             dupblob();
285         }
286     }
287 
genflattrisblobrenderer288     void genflattris(cube &cu, int orient, vec *v, uint overlap)
289     {
290         int dim = dimension(orient);
291         float c = v[fv[orient][0]][dim];
292         if(c < blobmin[dim] || c > blobmax[dim]) return;
293         #define CLIPSIDE(flag, clip, val, check) \
294             if(overlap&(flag)) \
295             { \
296                 vec *in = v; \
297                 v = in==v1 ? v2 : v1; \
298                 numv = clip(in, numv, val, v); \
299                 check \
300             }
301         static vec v1[16], v2[16];
302         loopk(4) v1[k] = v[fv[orient][k]];
303         v = v1;
304         int numv = 4;
305         overlap &= ~(3<<(2*dim));
306         CLIPSIDE(1<<0, clipabove<0>, blobmin.x, { if(numv < 3) return; });
307         CLIPSIDE(1<<1, clipbelow<0>, blobmax.x, { if(numv < 3) return; });
308         CLIPSIDE(1<<2, clipabove<1>, blobmin.y, { if(numv < 3) return; });
309         CLIPSIDE(1<<3, clipbelow<1>, blobmax.y, { if(numv < 3) return; });
310         CLIPSIDE(1<<4, clipabove<2>, blobmin.z, { if(numv < 3) return; });
311         CLIPSIDE(1<<5, clipbelow<2>, blobmax.z, { if(numv < 3) return; });
312         if(dim!=2)
313         {
314             CLIPSIDE(1<<6, split<2>, blobmin.z + blobfadelow, );
315             CLIPSIDE(1<<7, split<2>, blobmax.z - blobfadehigh, );
316         }
317 
318         addtris(v, numv);
319     }
320 
genslopedtrisblobrenderer321     void genslopedtris(cube &cu, int orient, vec *v, uint overlap)
322     {
323         int convexity = faceconvexity(cu, orient), order = convexity < 0 ? 1 : 0;
324         const vec &p0 = v[fv[orient][0 + order]],
325                   &p1 = v[fv[orient][1 + order]],
326                   &p2 = v[fv[orient][2 + order]],
327                   &p3 = v[fv[orient][(3 + order)&3]];
328         if(p0 == p2) return;
329         static vec v1[16], v2[16];
330         if(p0 != p1 && p1 != p2)
331         {
332             if((p1.x - p0.x)*(p2.y - p0.y) - (p1.y - p0.y)*(p2.x - p0.x) < 0) goto nexttri;
333             v1[0] = p0; v1[1] = p1; v1[2] = p2;
334             int numv = 3;
335             if(!convexity && p0 != p3 && p2 != p3) { v1[3] = p3; numv = 4; }
336             v = v1;
337             CLIPSIDE(1<<0, clipabove<0>, blobmin.x, { if(numv < 3) goto nexttri; });
338             CLIPSIDE(1<<1, clipbelow<0>, blobmax.x, { if(numv < 3) goto nexttri; });
339             CLIPSIDE(1<<2, clipabove<1>, blobmin.y, { if(numv < 3) goto nexttri; });
340             CLIPSIDE(1<<3, clipbelow<1>, blobmax.y, { if(numv < 3) goto nexttri; });
341             CLIPSIDE(1<<4, clipabove<2>, blobmin.z, { if(numv < 3) goto nexttri; });
342             CLIPSIDE(1<<5, clipbelow<2>, blobmax.z, { if(numv < 3) goto nexttri; });
343             CLIPSIDE(1<<6, split<2>, blobmin.z + blobfadelow, );
344             CLIPSIDE(1<<7, split<2>, blobmax.z - blobfadehigh, );
345 
346             addtris(v, numv);
347         }
348         else convexity = 1;
349     nexttri:
350         if(convexity && p0 != p3 && p2 != p3)
351         {
352             if((p2.x - p0.x)*(p3.y - p0.y) - (p2.y - p0.y)*(p3.x - p0.x) < 0) return;
353             v1[0] = p0; v1[1] = p2; v1[2] = p3;
354             int numv = 3;
355             v = v1;
356             CLIPSIDE(1<<0, clipabove<0>, blobmin.x, { if(numv < 3) return; });
357             CLIPSIDE(1<<1, clipbelow<0>, blobmax.x, { if(numv < 3) return; });
358             CLIPSIDE(1<<2, clipabove<1>, blobmin.y, { if(numv < 3) return; });
359             CLIPSIDE(1<<3, clipbelow<1>, blobmax.y, { if(numv < 3) return; });
360             CLIPSIDE(1<<4, clipabove<2>, blobmin.z, { if(numv < 3) return; });
361             CLIPSIDE(1<<5, clipbelow<2>, blobmax.z, { if(numv < 3) return; });
362             CLIPSIDE(1<<6, split<2>, blobmin.z + blobfadelow, );
363             CLIPSIDE(1<<7, split<2>, blobmax.z - blobfadehigh, );
364 
365             addtris(v, numv);
366         }
367     }
368 
checkoverlapblobrenderer369     int checkoverlap(const ivec &o, int size)
370     {
371         int overlap = 0;
372         if(o.x < blobmin.x) overlap |= 1<<0;
373         if(o.x + size > blobmax.x) overlap |= 1<<1;
374         if(o.y < blobmin.y) overlap |= 1<<2;
375         if(o.y + size > blobmax.y) overlap |= 1<<3;
376         if(o.z < blobmin.z) overlap |= 1<<4;
377         if(o.z + size > blobmax.z) overlap |= 1<<5;
378         if(o.z < blobmin.z + blobfadelow && o.z + size > blobmin.z + blobfadelow) overlap |= 1<<6;
379         if(o.z < blobmax.z - blobfadehigh && o.z + size > blobmax.z - blobfadehigh) overlap |= 1<<7;
380         return overlap;
381     }
382 
gentrisblobrenderer383     void gentris(cube *cu, const ivec &o, int size, uchar *vismasks = NULL, uchar avoid = 1<<O_BOTTOM)
384     {
385         loopoctabox(o, size, bborigin, bbsize)
386         {
387             ivec co(i, o.x, o.y, o.z, size);
388             if(cu[i].children)
389             {
390                 uchar visclip = cu[i].vismask & cu[i].clipmask & ~avoid;
391                 if(visclip)
392                 {
393                     uint overlap = checkoverlap(co, size);
394                     uchar vertused = fvmasks[visclip];
395                     vec v[8];
396                     loopj(8) if(vertused&(1<<j)) calcvert(cu[i], co.x, co.y, co.z, size, v[j], j, true);
397                     loopj(6) if(visclip&(1<<j)) genflattris(cu[i], j, v, overlap);
398                 }
399                 if(cu[i].vismask & ~avoid) gentris(cu[i].children, co, size>>1, cu[i].vismasks, avoid | visclip);
400             }
401             else if(vismasks)
402             {
403                 uchar vismask = vismasks[i] & ~avoid;
404                 if(!vismask) continue;
405                 uint overlap = checkoverlap(co, size);
406                 uchar vertused = fvmasks[vismask];
407                 bool solid = cu[i].ext && isclipped(cu[i].ext->material&MATF_VOLUME);
408                 vec v[8];
409                 loopj(8) if(vertused&(1<<j)) calcvert(cu[i], co.x, co.y, co.z, size, v[j], j, solid);
410                 loopj(6) if(vismask&(1<<j))
411                 {
412                     if(solid || (flataxisface(cu[i], j) && faceedges(cu[i], j)==F_SOLID)) genflattris(cu[i], j, v, overlap);
413                     else genslopedtris(cu[i], j, v, overlap);
414                 }
415             }
416             else
417             {
418                 bool solid = cu[i].ext && isclipped(cu[i].ext->material&MATF_VOLUME);
419                 uchar vismask = 0;
420                 loopj(6) if(!(avoid&(1<<j)) && (solid ? visiblematerial(cu[i], j, co.x, co.y, co.z, size)==MATSURF_VISIBLE : cu[i].texture[j]!=DEFAULT_SKY && visibleface(cu[i], j, co.x, co.y, co.z, size))) vismask |= 1<<j;
421                 if(!vismask) continue;
422                 uint overlap = checkoverlap(co, size);
423                 uchar vertused = fvmasks[vismask];
424                 vec v[8];
425                 loopj(8) if(vertused&(1<<j)) calcvert(cu[i], co.x, co.y, co.z, size, v[j], j, solid);
426                 loopj(6) if(vismask&(1<<j))
427                 {
428                     if(solid || (flataxisface(cu[i], j) && faceedges(cu[i], j)==F_SOLID)) genflattris(cu[i], j, v, overlap);
429                     else genslopedtris(cu[i], j, v, overlap);
430                 }
431             }
432         }
433     }
434 
addblobblobrenderer435     blobinfo *addblob(const vec &o, float radius, float fade)
436     {
437         lastblob = &blobs[endblob];
438         blobinfo &b = newblob(o, radius);
439         blobmin = blobmax = o;
440         blobmin.x -= radius;
441         blobmin.y -= radius;
442         blobmin.z -= blobheight + blobfadelow;
443         blobmax.x += radius;
444         blobmax.y += radius;
445         blobmax.z += blobfadehigh;
446         (bborigin = blobmin).sub(2);
447         (bbsize = blobmax).sub(blobmin).add(4);
448         float scale =  fade*blobintensity*255/100.0f;
449         blobalphalow = scale / blobfadelow;
450         blobalphahigh = scale / blobfadehigh;
451         blobalpha = uchar(scale);
452         gentris(worldroot, ivec(0, 0, 0), hdr.worldsize>>1);
453         return b.millis >= 0 ? &b : NULL;
454     }
455 
setuprenderstateblobrenderer456     static void setuprenderstate()
457     {
458         if(renderpath!=R_FIXEDFUNCTION && fogging) setfogplane(1, reflectz);
459 
460         foggedshader->set();
461 
462         enablepolygonoffset(GL_POLYGON_OFFSET_FILL);
463 
464         glDepthMask(GL_FALSE);
465         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
466         if(!dbgblob) glEnable(GL_BLEND);
467 
468         glEnableClientState(GL_VERTEX_ARRAY);
469         glEnableClientState(GL_TEXTURE_COORD_ARRAY);
470         glEnableClientState(GL_COLOR_ARRAY);
471     }
472 
cleanuprenderstateblobrenderer473     static void cleanuprenderstate()
474     {
475         glDisableClientState(GL_VERTEX_ARRAY);
476         glDisableClientState(GL_TEXTURE_COORD_ARRAY);
477         glDisableClientState(GL_COLOR_ARRAY);
478 
479         glDepthMask(GL_TRUE);
480         glDisable(GL_BLEND);
481 
482         disablepolygonoffset(GL_POLYGON_OFFSET_FILL);
483     }
484 
485     static int lastreset;
486 
resetblobrenderer487     static void reset()
488     {
489         lastreset = totalmillis;
490     }
491 
492     static blobrenderer *lastrender;
493 
fadeblobblobrenderer494     void fadeblob(blobinfo *b, float fade)
495     {
496         float minz = b->o.z - (blobheight + blobfadelow), maxz = b->o.z + blobfadehigh,
497               scale = fade*blobintensity*255/100.0f, scalelow = scale / blobfadelow, scalehigh = scale / blobfadehigh;
498         uchar alpha = uchar(scale);
499         b->millis = totalmillis;
500         do
501         {
502             if(b->endvert - b->startvert >= 3) for(blobvert *v = &verts[b->startvert], *end = &verts[b->endvert]; v < end; v++)
503             {
504                 float z = v->pos.z;
505                 if(z < minz + blobfadelow) v->alpha = uchar(scalelow * (z - minz));
506                 else if(z > maxz - blobfadehigh) v->alpha = uchar(scalehigh * (maxz - z));
507                 else v->alpha = alpha;
508             }
509             int offset = b - &blobs[0] + 1;
510             if(offset >= maxblobs) offset = 0;
511             if(offset < endblob ? offset > startblob || startblob > endblob : offset > startblob) b = &blobs[offset];
512             else break;
513         } while(b->millis < 0);
514     }
515 
renderblobblobrenderer516     void renderblob(const vec &o, float radius, float fade)
517     {
518         if(lastrender != this)
519         {
520             if(!lastrender)
521             {
522                 if(!blobs) initblobs();
523 
524                 setuprenderstate();
525             }
526             glVertexPointer(3, GL_FLOAT, sizeof(blobvert), &verts->pos);
527             glTexCoordPointer(2, GL_FLOAT, sizeof(blobvert), &verts->u);
528             glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(blobvert), &verts->color);
529             if(!lastrender || lastrender->tex != tex) glBindTexture(GL_TEXTURE_2D, tex->id);
530             lastrender = this;
531         }
532 
533         union { int i; float f; } ox, oy;
534         ox.f = o.x; oy.f = o.y;
535         uint hash = uint(ox.i^~oy.i^(INT_MAX-oy.i)^uint(radius));
536         hash %= cachesize;
537         blobinfo *b = cache[hash];
538         if(!b || b->millis <= lastreset || b->o!=o || b->radius!=radius)
539         {
540             b = addblob(o, radius, fade);
541             cache[hash] = b;
542             if(!b) return;
543         }
544         else if(fade < 1 && b->millis < totalmillis) fadeblob(b, fade);
545         do
546         {
547             if(b->endvert - b->startvert >= 3)
548             {
549                 if(hasDRE) glDrawRangeElements_(GL_TRIANGLES, b->startvert, b->endvert-1, b->endindex - b->startindex, GL_UNSIGNED_SHORT, &indexes[b->startindex]);
550                 else glDrawElements(GL_TRIANGLES, b->endindex - b->startindex, GL_UNSIGNED_SHORT, &indexes[b->startindex]);
551                 xtravertsva += b->endvert - b->startvert;
552             }
553             int offset = b - &blobs[0] + 1;
554             if(offset >= maxblobs) offset = 0;
555             if(offset < endblob ? offset > startblob || startblob > endblob : offset > startblob) b = &blobs[offset];
556             else break;
557         } while(b->millis < 0);
558     }
559 };
560 
561 int blobrenderer::lastreset = 0;
562 blobrenderer *blobrenderer::lastrender = NULL;
563 
564 VARFP(blobstattris, 128, 4096, 1<<16, initblobs(BLOB_STATIC));
565 VARFP(blobdyntris, 128, 4096, 1<<16, initblobs(BLOB_DYNAMIC));
566 
567 static blobrenderer blobs[] =
568 {
569     blobrenderer("data/particles/blob.png"),
570     blobrenderer("data/particles/blob.png")
571 };
572 
initblobs(int type)573 void initblobs(int type)
574 {
575     if(type < 0 || (type==BLOB_STATIC && blobs[BLOB_STATIC].blobs)) blobs[BLOB_STATIC].init(showblobs ? blobstattris : 0);
576     if(type < 0 || (type==BLOB_DYNAMIC && blobs[BLOB_DYNAMIC].blobs)) blobs[BLOB_DYNAMIC].init(showblobs ? blobdyntris : 0);
577 }
578 
resetblobs()579 void resetblobs()
580 {
581     blobrenderer::lastreset = totalmillis;
582 }
583 
renderblob(int type,const vec & o,float radius,float fade)584 void renderblob(int type, const vec &o, float radius, float fade)
585 {
586     if(!showblobs) return;
587     if(refracting < 0 && o.z - blobheight - blobfadelow >= reflectz) return;
588     blobs[type].renderblob(o, radius + blobmargin, fade);
589 }
590 
flushblobs()591 void flushblobs()
592 {
593     if(blobrenderer::lastrender) blobrenderer::cleanuprenderstate();
594     blobrenderer::lastrender = NULL;
595 }
596 
597