// worldlight.cpp #include "cube.h" #define LIGHTSCALE 4 void lightray(float bx, float by, const persistent_entity &light, float fade = 1, bool flicker = false) // done in realtime, needs to be fast { float lx = light.x+(flicker ? (rnd(21)-10)*0.1f : 0); float ly = light.y+(flicker ? (rnd(21)-10)*0.1f : 0); float dx = bx-lx; float dy = by-ly; float dist = sqrtf(dx*dx+dy*dy); if(dist<1.0f) return; int reach = light.attr1; int steps = (int)(reach*reach*1.6f/dist); const int PRECBITS = 12; const float PRECF = 4096.0f; int x = (int)(lx*PRECF); int y = (int)(ly*PRECF); int fadescale = (int)(fade*PRECF); int l = light.attr2*fadescale; int stepx = (int)(dx/(float)steps*PRECF); int stepy = (int)(dy/(float)steps*PRECF); int stepl = (int)(l/(float)steps); if(maxtmus) { l /= LIGHTSCALE; stepl /= LIGHTSCALE; if(light.attr3 || light.attr4) // coloured light version, special case because most lights are white { if(flicker) { int dimness = rnd((((255<>(PRECBITS+4))+1); x += stepx*dimness; y += stepy*dimness; } if(OUTBORD(x>>PRECBITS, y>>PRECBITS)) return; int g = light.attr3*fadescale; int stepg = (int)(g/(float)steps); int b = light.attr4*fadescale; int stepb = (int)(b/(float)steps); g /= LIGHTSCALE; stepg /= LIGHTSCALE; b /= LIGHTSCALE; stepb /= LIGHTSCALE; loopi(steps) { sqr *s = S(x>>PRECBITS, y>>PRECBITS); s->r = min((l>>PRECBITS)+s->r, 255); s->g = min((g>>PRECBITS)+s->g, 255); s->b = min((b>>PRECBITS)+s->b, 255); if(SOLID(s)) return; x += stepx; y += stepy; l -= stepl; g -= stepg; b -= stepb; stepl -= 25; stepg -= 25; stepb -= 25; } } else // white light, special optimized version { if(flicker) { int dimness = rnd((((255<>(PRECBITS+4))+1); x += stepx*dimness; y += stepy*dimness; } if(OUTBORD(x>>PRECBITS, y>>PRECBITS)) return; if(hdr.ambient > 0xFF) loopi(steps) { sqr *s = S(x>>PRECBITS, y>>PRECBITS); s->r = min((l>>PRECBITS)+s->r, 255); s->g = min((l>>PRECBITS)+s->g, 255); s->b = min((l>>PRECBITS)+s->b, 255); if(SOLID(s)) return; x += stepx; y += stepy; l -= stepl; stepl -= 25; } else loopi(steps) { sqr *s = S(x>>PRECBITS, y>>PRECBITS); s->r = s->g = s->b = min((l>>PRECBITS)+s->r, 255); if(SOLID(s)) return; x += stepx; y += stepy; l -= stepl; stepl -= 25; } } } else // the old (white) light code, here for the few people with old video cards that don't support overbright { loopi(steps) { sqr *s = S(x>>PRECBITS, y>>PRECBITS); int light = l>>PRECBITS; if(light>s->r) s->r = s->g = s->b = (uchar)light; if(SOLID(s)) return; x += stepx; y += stepy; l -= stepl; } } } void calclightsource(const persistent_entity &l, float fade = 1, bool flicker = true) { int reach = l.attr1; int sx = l.x-reach; int ex = l.x+reach; int sy = l.y-reach; int ey = l.y+reach; const float s = 0.8f; for(float sx2 = (float)sx; sx2<=ex; sx2+=s*2) { lightray(sx2, (float)sy, l, fade, flicker); lightray(sx2, (float)ey, l, fade, flicker); } for(float sy2 = sy+s; sy2<=ey-s; sy2+=s*2) { lightray((float)sx, sy2, l, fade, flicker); lightray((float)ex, sy2, l, fade, flicker); } } void postlightarealine(sqr *s, int len) { loopirev(len) { #define median(m) s->m = (s->m*2 + SW(s,1,0)->m*2 + SW(s,0,1)->m*2 \ + SW(s,-1,0)->m*2 + SW(s,0,-1)->m*2 \ + SW(s,1,1)->m + SW(s,1,-1)->m \ + SW(s,-1,1)->m + SW(s,-1,-1)->m)/14; // median is 4/2/1 instead median(r); median(g); median(b); s += 2; } } void postlightarea(const block &a) // median filter, smooths out random noise in light and makes it more mipable { int ia = (a.xs + 1) >> 1, ib = a.xs - ia;; for(int y = a.ys - 1; y >= 0; y -= 2) { sqr *s = S(a.x,y+a.y); postlightarealine(s, ia); postlightarealine(s + 1, ib); } for(int y = a.ys - 2; y >= 0; y -= 2) { sqr *s = S(a.x,y+a.y); postlightarealine(s, ia); postlightarealine(s + 1, ib); } remip(a); } int lastcalclight = 0; VARP(fullbrightlevel, 0, 176, 255); void fullbrightlight(int level) { if(level < 0) level = fullbrightlevel; loopi(mipsize) world[i].r = world[i].g = world[i].b = level; lastcalclight = totalmillis; } VARF(ambient, 0, 0, 0xFFFFFF, if(!noteditmode("ambient")) { hdr.ambient = ambient; calclight(); unsavededits++;}); void calclight() { if(editmode) { servsqr *servworld = createservworld(world, cubicsize); calcmapdims(clmapdims, servworld, ssize); delete[] servworld; } uchar r = (hdr.ambient>>16) & 0xFF, g = (hdr.ambient>>8) & 0xFF, b = hdr.ambient & 0xFF; if(!r && !g) { if(!b) b = 10; r = g = b; } else if(!maxtmus) r = g = b = max(max(r, g), b); // the old (white) light code, here for the few people with old video cards that don't support overbright sqr *s = S(0,0); loopirev(cubicsize) { s->r = r; s->g = g; s->b = b; s++; } seedMT(ents.length() + hdr.maprevision); // static seed -> nothing random here loopv(ents) { entity &e = ents[i]; if(e.type==LIGHT) calclightsource(e); } popMT(); // undo the static seedMT() from above block bb = { clmapdims.x1 - 1, clmapdims.y1 - 1, clmapdims.xspan + 2, clmapdims.yspan + 2 }; postlightarea(bb); setvar("fullbright", 0); lastcalclight = totalmillis; } struct dlight { physent *owner; vec offset, o; block *area; int reach, fade, expire; uchar r, g, b; float calcintensity() const { if(!fade || lastmillis < expire - fade) return 1.0f; return max(float(expire - lastmillis)/fade, 0.0f); } }; vector dlights; VARP(dynlight, 0, 1, 1); static inline bool insidearea(const block &a, const block &b) { return b.x >= a.x && b.y >= a.y && b.x+b.xs <= a.x+a.xs && b.y+b.ys <= a.y+a.ys; } void preparedynlight(dlight &d) { block area = { (int)d.o.x-d.reach, (int)d.o.y-d.reach, d.reach*2+1, d.reach*2+1 }; if(area.x<1) { area.xs = max(area.xs - (1 - area.x), 0); area.x = 1; } else if(area.x>ssize-2) { area.x = ssize-2; area.xs = 0; } if(area.y<1) { area.ys = max(area.ys - (1 - area.y), 0); area.y = 1; } else if(area.y>ssize-2) { area.y = ssize-2; area.ys = 0; } if(area.x+area.xs>ssize-2) area.xs = ssize-2-area.x; if(area.y+area.ys>ssize-2) area.ys = ssize-2-area.y; if(d.area) { if(insidearea(*d.area, area)) return; freeblock(d.area); } d.area = blockcopy(area); // backup area before rendering in dynlight } void adddynlight(physent *owner, const vec &o, int reach, int expire, int fade, uchar r, uchar g, uchar b) { if(!dynlight) return; dlight &d = dlights.add(); d.owner = owner; d.o = o; if(d.owner) { d.offset = d.o; d.offset.sub(d.owner->o); } else d.offset = vec(0, 0, 0); d.reach = reach; d.fade = fade; d.expire = lastmillis + expire; d.r = r; d.g = g; d.b = b; d.area = NULL; preparedynlight(d); } void cleardynlights() { loopv(dlights) freeblock(dlights[i].area); dlights.shrink(0); } void removedynlights(physent *owner) { loopv(dlights) if(dlights[i].owner==owner) { freeblock(dlights[i].area); dlights.remove(i--); } } void dodynlights() { if(dlights.empty()) return; const block *area = NULL; loopv(dlights) { dlight &d = dlights[i]; if(lastmillis >= d.expire) { freeblock(d.area); dlights.remove(i--); continue; } if(d.owner) { vec oldo(d.o); d.o = d.owner->o; d.o.add(d.offset); if(d.o != oldo) preparedynlight(dlights[i]); } } loopv(dlights) { dlight &d = dlights[i]; persistent_entity l((int)d.o.x, (int)d.o.y, (int)d.o.z, LIGHT, d.reach, d.r, d.g, d.b); calclightsource(l, d.calcintensity(), false); if(area) { if(insidearea(*area, *d.area)) continue; if(!insidearea(*d.area, *area)) postlightarea(*area); } area = d.area; } if(area) postlightarea(*area); lastcalclight = totalmillis; } void undodynlights() { if(dlights.empty()) return; const block *area = NULL; loopvrev(dlights) { const dlight &d = dlights[i]; if(area) { if(insidearea(*area, *d.area)) continue; if(!insidearea(*d.area, *area)) blockpaste(*area); } area = d.area; } if(area) blockpaste(*area); } // utility functions also used by editing code block *blockcopy(const block &s) { block *b = (block *)new uchar[sizeof(block)+s.xs*s.ys*sizeof(sqr)]; *b = s; sqr *q = (sqr *)(b+1), *r = S(s.x,s.y); size_t bs = s.xs * sizeof(sqr); loopirev(s.ys) { memcpy(q, r, bs); q += s.xs; r += ssize; } return b; } void blocktexusage(const block &b, uchar *used) { const sqr *q = (const sqr *)((&b)+1); loopirev(b.xs * b.ys) { // collect used texture slots in block used[q->wtex] = 1; if(q->type != SOLID) { used[q->ctex] = 1; used[q->ftex] = 1; used[q->utex] = 1; } q++; } } void blockpaste(const block &b, int bx, int by, bool light, uchar *texmap) // slow version, editmode only { const sqr *q = (const sqr *)((&b)+1); sqr *dest = 0; uchar tr, tg, tb; for(int y = by; yr; tg = dest->g; tb = dest->b; *dest = *q; if(texmap) { // translate texture slot numbers dest->wtex = texmap[q->wtex]; dest->ctex = texmap[q->ctex]; dest->ftex = texmap[q->ftex]; dest->utex = texmap[q->utex]; } if (light) //edit mode paste { dest->r = tr; dest->g = tg; dest->b = tb; } q++; } block bb = { bx, by, b.xs, b.ys }; remipmore(bb); } void blockpaste(const block &b) // fast version, used by dynlight { const sqr *q = (const sqr *)((&b)+1); sqr *r = S(b.x, b.y); const size_t bs = b.xs * sizeof(sqr); loopirev(b.ys) { memcpy(r, q, bs); r += ssize; q += b.xs; } remipmore(b); } void freeblockp(block *b) { delete[] (uchar *)b; } void freeblock(block *&b) { if(b) { freeblockp(b); b = NULL; } }