OctaCore/src/engine/light.cc

574 lines
17 KiB
C++

#include "light.hh"
#include <algorithm>
#include <shared/command.hh>
#include <shared/igame.hh>
#include "blend.hh"
#include "console.hh" /* conoutf */
#include "main.hh" // renderbackground, player
#include "octa.hh"
#include "octaedit.hh" // commitchanges
#include "octarender.hh"
#include "physics.hh"
#include "renderlights.hh"
#include "texture.hh"
#include "world.hh"
CVAR1R(ambient, 0x191919);
FVARR(ambientscale, 0, 1, 16);
CVAR1R(skylight, 0);
FVARR(skylightscale, 0, 1, 16);
extern void setupsunlight();
CVAR1FR(sunlight, 0,
{
setupsunlight();
cleardeferredlightshaders();
clearshadowcache();
});
FVARFR(sunlightscale, 0, 1, 16, setupsunlight());
vec sunlightdir(0, 0, 1);
extern void setsunlightdir();
FVARFR(sunlightyaw, 0, 0, 360, setsunlightdir());
FVARFR(sunlightpitch, -90, 90, 90, setsunlightdir());
void setsunlightdir()
{
sunlightdir = vec(sunlightyaw*RAD, sunlightpitch*RAD);
loopk(3) if(fabs(sunlightdir[k]) < 1e-5f) sunlightdir[k] = 0;
sunlightdir.normalize();
setupsunlight();
}
void setupsunlight()
{
clearradiancehintscache();
}
static const surfaceinfo brightsurfaces[6] =
{
brightsurface,
brightsurface,
brightsurface,
brightsurface,
brightsurface,
brightsurface
};
void brightencube(cube &c)
{
if(!c.ext) newcubeext(c, 0, false);
memcpy(c.ext->surfaces, brightsurfaces, sizeof(brightsurfaces));
}
void setsurfaces(cube &c, const surfaceinfo *surfs, const vertinfo *verts, int numverts)
{
if(!c.ext || c.ext->maxverts < numverts) newcubeext(c, numverts, false);
memcpy(c.ext->surfaces, surfs, sizeof(c.ext->surfaces));
memcpy(c.ext->verts(), verts, numverts*sizeof(vertinfo));
}
void setsurface(cube &c, int orient, const surfaceinfo &src, const vertinfo *srcverts, int numsrcverts)
{
int dstoffset = 0;
if(!c.ext) newcubeext(c, numsrcverts, true);
else
{
int numbefore = 0, beforeoffset = 0;
loopi(orient)
{
surfaceinfo &surf = c.ext->surfaces[i];
int numverts = surf.totalverts();
if(!numverts) continue;
numbefore += numverts;
beforeoffset = surf.verts + numverts;
}
int numafter = 0, afteroffset = c.ext->maxverts;
for(int i = 5; i > orient; i--)
{
surfaceinfo &surf = c.ext->surfaces[i];
int numverts = surf.totalverts();
if(!numverts) continue;
numafter += numverts;
afteroffset = surf.verts;
}
if(afteroffset - beforeoffset >= numsrcverts) dstoffset = beforeoffset;
else
{
cubeext *ext = c.ext;
if(numbefore + numsrcverts + numafter > c.ext->maxverts)
{
ext = growcubeext(c.ext, numbefore + numsrcverts + numafter);
memcpy(ext->surfaces, c.ext->surfaces, sizeof(ext->surfaces));
}
int offset = 0;
if(numbefore == beforeoffset)
{
if(numbefore && c.ext != ext) memcpy(ext->verts(), c.ext->verts(), numbefore*sizeof(vertinfo));
offset = numbefore;
}
else loopi(orient)
{
surfaceinfo &surf = ext->surfaces[i];
int numverts = surf.totalverts();
if(!numverts) continue;
memmove(ext->verts() + offset, c.ext->verts() + surf.verts, numverts*sizeof(vertinfo));
surf.verts = offset;
offset += numverts;
}
dstoffset = offset;
offset += numsrcverts;
if(numafter && offset > afteroffset)
{
offset += numafter;
for(int i = 5; i > orient; i--)
{
surfaceinfo &surf = ext->surfaces[i];
int numverts = surf.totalverts();
if(!numverts) continue;
offset -= numverts;
memmove(ext->verts() + offset, c.ext->verts() + surf.verts, numverts*sizeof(vertinfo));
surf.verts = offset;
}
}
if(c.ext != ext) setcubeext(c, ext);
}
}
surfaceinfo &dst = c.ext->surfaces[orient];
dst = src;
dst.verts = dstoffset;
if(srcverts) memcpy(c.ext->verts() + dstoffset, srcverts, numsrcverts*sizeof(vertinfo));
}
bool PackNode::insert(ushort &tx, ushort &ty, ushort tw, ushort th)
{
if((available < tw && available < th) || w < tw || h < th)
return false;
if(child1)
{
bool inserted = child1->insert(tx, ty, tw, th) ||
child2->insert(tx, ty, tw, th);
available = max(child1->available, child2->available);
if(!available) discardchildren();
return inserted;
}
if(w == tw && h == th)
{
available = 0;
tx = x;
ty = y;
return true;
}
if(w - tw > h - th)
{
child1 = new PackNode(x, y, tw, h);
child2 = new PackNode(x + tw, y, w - tw, h);
}
else
{
child1 = new PackNode(x, y, w, th);
child2 = new PackNode(x, y + th, w, h - th);
}
bool inserted = child1->insert(tx, ty, tw, th);
available = max(child1->available, child2->available);
return inserted;
}
void PackNode::reserve(ushort tx, ushort ty, ushort tw, ushort th)
{
if(tx + tw <= x || tx >= x + w || ty + th <= y || ty >= y + h) return;
if(child1)
{
child1->reserve(tx, ty, tw, th);
child2->reserve(tx, ty, tw, th);
available = max(child1->available, child2->available);
return;
}
int dx1 = tx - x, dx2 = x + w - tx - tw, dx = max(dx1, dx2),
dy1 = ty - y, dy2 = y + h - ty - th, dy = max(dy1, dy2),
split;
if(dx > dy)
{
if(dx1 > dx2) split = min(dx1, int(w));
else split = w - max(dx2, 0);
if(w - split <= 0)
{
w = split;
available = min(w, h);
if(dy > 0) reserve(tx, ty, tw, th);
else if(tx <= x && tx + tw >= x + w) available = 0;
return;
}
if(split <= 0)
{
x += split;
w -= split;
available = min(w, h);
if(dy > 0) reserve(tx, ty, tw, th);
else if(tx <= x && tx + tw >= x + w) available = 0;
return;
}
child1 = new PackNode(x, y, split, h);
child2 = new PackNode(x + split, y, w - split, h);
}
else
{
if(dy1 > dy2) split = min(dy1, int(h));
else split = h - max(dy2, 0);
if(h - split <= 0)
{
h = split;
available = min(w, h);
if(dx > 0) reserve(tx, ty, tw, th);
else if(ty <= y && ty + th >= y + h) available = 0;
return;
}
if(split <= 0)
{
y += split;
h -= split;
available = min(w, h);
if(dx > 0) reserve(tx, ty, tw, th);
else if(ty <= y && ty + th >= y + h) available = 0;
return;
}
child1 = new PackNode(x, y, w, split);
child2 = new PackNode(x, y + split, w, h - split);
}
child1->reserve(tx, ty, tw, th);
child2->reserve(tx, ty, tw, th);
available = max(child1->available, child2->available);
}
static void clearsurfaces(cube *c)
{
loopi(8)
{
if(c[i].ext)
{
loopj(6)
{
surfaceinfo &surf = c[i].ext->surfaces[j];
if(!surf.used()) continue;
surf.clear();
int numverts = surf.numverts&MAXFACEVERTS;
if(numverts)
{
if(!(c[i].merged&(1<<j))) { surf.numverts &= ~MAXFACEVERTS; continue; }
vertinfo *verts = c[i].ext->verts() + surf.verts;
loopk(numverts)
{
vertinfo &v = verts[k];
v.norm = 0;
}
}
}
}
if(c[i].children) clearsurfaces(c[i].children);
}
}
static uint lightprogress = 0;
bool calclight_canceled = false;
volatile bool check_calclight_progress = false;
void check_calclight_canceled()
{
if(interceptkey(SDLK_ESCAPE))
{
calclight_canceled = true;
}
if(!calclight_canceled) check_calclight_progress = false;
}
void show_calclight_progress()
{
float bar1 = float(lightprogress) / float(allocnodes);
defformatstring(text1, "%d%%", int(bar1 * 100));
renderprogress(bar1, text1);
}
static void calcsurfaces(cube &c, const ivec &co, int size, int usefacemask, int preview = 0)
{
surfaceinfo surfaces[6];
vertinfo litverts[6*2*MAXFACEVERTS];
int numlitverts = 0;
memset(surfaces, 0, sizeof(surfaces));
loopi(6)
{
int usefaces = usefacemask&0xF;
usefacemask >>= 4;
if(!usefaces)
{
if(!c.ext) continue;
surfaceinfo &surf = surfaces[i];
surf = c.ext->surfaces[i];
int numverts = surf.totalverts();
if(numverts)
{
memcpy(&litverts[numlitverts], c.ext->verts() + surf.verts, numverts*sizeof(vertinfo));
surf.verts = numlitverts;
numlitverts += numverts;
}
continue;
}
VSlot &vslot = lookupvslot(c.texture[i], false),
*layer = vslot.layer && !(c.material&MAT_ALPHA) ? &lookupvslot(vslot.layer, false) : nullptr;
Shader *shader = vslot.slot->shader;
int shadertype = shader->type;
if(layer) shadertype |= layer->slot->shader->type;
surfaceinfo &surf = surfaces[i];
vertinfo *curlitverts = &litverts[numlitverts];
int numverts = c.ext ? c.ext->surfaces[i].numverts&MAXFACEVERTS : 0;
ivec mo(co);
int msz = size, convex = 0;
if(numverts)
{
vertinfo *verts = c.ext->verts() + c.ext->surfaces[i].verts;
loopj(numverts) curlitverts[j].set(verts[j].getxyz());
if(c.merged&(1<<i))
{
msz = 1<<calcmergedsize(i, mo, size, verts, numverts);
mo.mask(~(msz-1));
if(!(surf.numverts&MAXFACEVERTS))
{
surf.verts = numlitverts;
surf.numverts |= numverts;
numlitverts += numverts;
}
}
else if(!flataxisface(c, i)) convex = faceconvexity(verts, numverts, size);
}
else
{
ivec v[4];
genfaceverts(c, i, v);
if(!flataxisface(c, i)) convex = faceconvexity(v);
int order = usefaces&4 || convex < 0 ? 1 : 0;
ivec vo = ivec(co).mask(0xFFF).shl(3);
curlitverts[numverts++].set(v[order].mul(size).add(vo));
if(usefaces&1) curlitverts[numverts++].set(v[order+1].mul(size).add(vo));
curlitverts[numverts++].set(v[order+2].mul(size).add(vo));
if(usefaces&2) curlitverts[numverts++].set(v[(order+3)&3].mul(size).add(vo));
}
vec pos[MAXFACEVERTS], n[MAXFACEVERTS], po(ivec(co).mask(~0xFFF));
loopj(numverts) pos[j] = vec(curlitverts[j].getxyz()).mul(1.0f/8).add(po);
int smooth = vslot.slot->smooth;
plane planes[2];
int numplanes = 0;
planes[numplanes++].toplane(pos[0], pos[1], pos[2]);
if(numverts < 4 || !convex) loopk(numverts) findnormal(pos[k], smooth, planes[0], n[k]);
else
{
planes[numplanes++].toplane(pos[0], pos[2], pos[3]);
vec avg = vec(planes[0]).add(planes[1]).normalize();
findnormal(pos[0], smooth, avg, n[0]);
findnormal(pos[1], smooth, planes[0], n[1]);
findnormal(pos[2], smooth, avg, n[2]);
for(int k = 3; k < numverts; k++) findnormal(pos[k], smooth, planes[1], n[k]);
}
loopk(numverts) curlitverts[k].norm = encodenormal(n[k]);
if(!(surf.numverts&MAXFACEVERTS))
{
surf.verts = numlitverts;
surf.numverts |= numverts;
numlitverts += numverts;
}
if(preview) { surf.numverts |= preview; continue; }
int surflayer = LAYER_TOP;
if(vslot.layer)
{
int x1 = curlitverts[numverts-1].x, y1 = curlitverts[numverts-1].y, x2 = x1, y2 = y1;
loopj(numverts-1)
{
const vertinfo &v = curlitverts[j];
x1 = min(x1, int(v.x));
y1 = min(y1, int(v.y));
x2 = max(x2, int(v.x));
y2 = max(y2, int(v.y));
}
x2 = max(x2, x1+1);
y2 = max(y2, y1+1);
x1 = (x1>>3) + (co.x&~0xFFF);
y1 = (y1>>3) + (co.y&~0xFFF);
x2 = ((x2+7)>>3) + (co.x&~0xFFF);
y2 = ((y2+7)>>3) + (co.y&~0xFFF);
surflayer = calcblendlayer(x1, y1, x2, y2);
}
surf.numverts |= surflayer;
}
if(preview) setsurfaces(c, surfaces, litverts, numlitverts);
else loopk(6)
{
surfaceinfo &surf = surfaces[k];
if(surf.used())
{
cubeext *ext = c.ext && c.ext->maxverts >= numlitverts ? c.ext : growcubeext(c.ext, numlitverts);
memcpy(ext->surfaces, surfaces, sizeof(ext->surfaces));
memcpy(ext->verts(), litverts, numlitverts*sizeof(vertinfo));
if(c.ext != ext) setcubeext(c, ext);
break;
}
}
}
static void calcsurfaces(cube *c, const ivec &co, int size)
{
CHECK_CALCLIGHT_PROGRESS(return, show_calclight_progress);
lightprogress++;
loopi(8)
{
ivec o(i, co, size);
if(c[i].children)
calcsurfaces(c[i].children, o, size >> 1);
else if(!isempty(c[i]))
{
if(c[i].ext)
{
loopj(6) c[i].ext->surfaces[j].clear();
}
int usefacemask = 0;
loopj(6) if(c[i].texture[j] != DEFAULT_SKY && (!(c[i].merged&(1<<j)) || (c[i].ext && c[i].ext->surfaces[j].numverts&MAXFACEVERTS)))
{
usefacemask |= visibletris(c[i], j, o, size)<<(4*j);
}
if(usefacemask) calcsurfaces(c[i], o, size, usefacemask);
}
}
}
static inline bool previewblends(cube &c, const ivec &o, int size)
{
if(isempty(c) || c.material&MAT_ALPHA) return false;
int usefacemask = 0;
loopj(6) if(c.texture[j] != DEFAULT_SKY && lookupvslot(c.texture[j], false).layer)
usefacemask |= visibletris(c, j, o, size)<<(4*j);
if(!usefacemask) return false;
int layer = calcblendlayer(o.x, o.y, o.x + size, o.y + size);
if(!(layer&LAYER_BOTTOM))
{
if(!c.ext) return false;
bool blends = false;
loopi(6) if(c.ext->surfaces[i].numverts&LAYER_BOTTOM)
{
c.ext->surfaces[i].brighten();
blends = true;
}
return blends;
}
calcsurfaces(c, o, size, usefacemask, layer);
return true;
}
static bool previewblends(cube *c, const ivec &co, int size, const ivec &bo, const ivec &bs)
{
bool changed = false;
loopoctabox(co, size, bo, bs)
{
ivec o(i, co, size);
cubeext *ext = c[i].ext;
if(ext && ext->va && ext->va->hasmerges)
{
changed = true;
destroyva(ext->va);
ext->va = nullptr;
invalidatemerges(c[i], co, size, true);
}
if(c[i].children ? previewblends(c[i].children, o, size/2, bo, bs) : previewblends(c[i], o, size))
{
changed = true;
ext = c[i].ext;
if(ext && ext->va)
{
destroyva(ext->va);
ext->va = nullptr;
}
}
}
return changed;
}
void previewblends(const ivec &bo, const ivec &bs)
{
updateblendtextures(bo.x, bo.y, bo.x+bs.x, bo.y+bs.y);
if(previewblends(worldroot, ivec(0, 0, 0), worldsize/2, bo, bs))
commitchanges(true);
}
extern int filltjoints;
static Uint32 calclighttimer(Uint32 interval, void *param)
{
check_calclight_progress = true;
return interval;
}
void calclight()
{
renderbackground("computing lighting... (esc to abort)");
remip();
optimizeblendmap();
clearsurfaces(worldroot);
lightprogress = 0;
calclight_canceled = false;
check_calclight_progress = false;
SDL_TimerID timer = SDL_AddTimer(250, calclighttimer, nullptr);
Uint32 start = SDL_GetTicks();
calcnormals(filltjoints > 0);
calcsurfaces(worldroot, ivec(0, 0, 0), worldsize >> 1);
clearnormals();
Uint32 end = SDL_GetTicks();
if(timer) SDL_RemoveTimer(timer);
renderbackground("lighting done...");
allchanged();
if(calclight_canceled)
conoutf("calclight aborted");
else
conoutf("computed lighting (%.1f seconds)",
(end - start) / 1000.0f);
}
void mpcalclight(bool local)
{
extern selinfo sel;
if(local) game::edittrigger(sel, EDIT_CALCLIGHT);
calclight();
}
ICOMMAND(calclight, "", (), mpcalclight(true));
VAR(fullbright, 0, 0, 1);
VAR(fullbrightlevel, 0, 160, 255);
void clearlights()
{
clearshadowcache();
cleardeferredlightshaders();
resetsmoothgroups();
}
void initlights()
{
clearshadowcache();
loaddeferredlightshaders();
}