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