1326 lines
41 KiB
C++
1326 lines
41 KiB
C++
#include "pvs.hh"
|
|
|
|
#include <climits>
|
|
|
|
#include <shared/command.hh>
|
|
|
|
#include "console.hh" /* conoutf */
|
|
#include "main.hh" // numcpus, interceptkey, renderbackground
|
|
#include "octa.hh"
|
|
#include "octarender.hh" // valist
|
|
#include "rendergl.hh" // camera1
|
|
#include "world.hh"
|
|
|
|
enum
|
|
{
|
|
PVS_HIDE_GEOM = 1<<0,
|
|
PVS_HIDE_BB = 1<<1
|
|
};
|
|
|
|
struct pvsnode
|
|
{
|
|
bvec edges;
|
|
uchar flags;
|
|
uint children;
|
|
};
|
|
|
|
static vector<pvsnode> origpvsnodes;
|
|
|
|
static bool mergepvsnodes(pvsnode &p, pvsnode *children)
|
|
{
|
|
loopi(7) if(children[i].flags!=children[7].flags) return false;
|
|
bvec bbs[4];
|
|
loop(x, 2) loop(y, 2)
|
|
{
|
|
const bvec &lo = children[octaindex(2, x, y, 0)].edges,
|
|
&hi = children[octaindex(2, x, y, 1)].edges;
|
|
if(lo.x!=0xFF && (lo.x&0x11 || lo.y&0x11 || lo.z&0x11)) return false;
|
|
if(hi.x!=0xFF && (hi.x&0x11 || hi.y&0x11 || hi.z&0x11)) return false;
|
|
|
|
#define MERGEBBS(res, coord, row, col) \
|
|
if(lo.coord==0xFF) \
|
|
{ \
|
|
if(hi.coord!=0xFF) \
|
|
{ \
|
|
res.coord = ((hi.coord&~0x11)>>1) + 0x44; \
|
|
res.row = hi.row; \
|
|
res.col = hi.col; \
|
|
} \
|
|
} \
|
|
else if(hi.coord==0xFF) \
|
|
{ \
|
|
res.coord = (lo.coord&0xEE)>>1; \
|
|
res.row = lo.row; \
|
|
res.col = lo.col; \
|
|
} \
|
|
else if(lo.row!=hi.row || lo.col!=hi.col || (lo.coord&0xF0)!=0x80 || (hi.coord&0xF)!=0) return false; \
|
|
else \
|
|
{ \
|
|
res.coord = ((lo.coord&~0xF1)>>1) | (((hi.coord&~0x1F)>>1) + 0x40); \
|
|
res.row = lo.row; \
|
|
res.col = lo.col; \
|
|
}
|
|
|
|
bvec &res = bbs[x + 2*y];
|
|
MERGEBBS(res, z, x, y);
|
|
res.x = lo.x;
|
|
res.y = lo.y;
|
|
}
|
|
loop(x, 2)
|
|
{
|
|
bvec &lo = bbs[x], &hi = bbs[x+2];
|
|
MERGEBBS(lo, y, x, z);
|
|
}
|
|
bvec &lo = bbs[0], &hi = bbs[1];
|
|
MERGEBBS(p.edges, x, y, z);
|
|
|
|
return true;
|
|
}
|
|
|
|
static void genpvsnodes(cube *c, int parent = 0, const ivec &co = ivec(0, 0, 0), int size = worldsize/2)
|
|
{
|
|
int index = origpvsnodes.length();
|
|
loopi(8)
|
|
{
|
|
ivec o(i, co, size);
|
|
pvsnode &n = origpvsnodes.add();
|
|
n.flags = 0;
|
|
n.children = 0;
|
|
if(c[i].children || isempty(c[i]) || c[i].material&MAT_ALPHA) memset(n.edges.v, 0xFF, 3);
|
|
else loopk(3)
|
|
{
|
|
uint face = c[i].faces[k];
|
|
if(face==F_SOLID) n.edges[k] = 0x80;
|
|
else
|
|
{
|
|
uchar low = max(max(face&0xF, (face>>8)&0xF), max((face>>16)&0xF, (face>>24)&0xF)),
|
|
high = min(min((face>>4)&0xF, (face>>12)&0xF), min((face>>20)&0xF, (face>>28)&0xF));
|
|
if(size<8)
|
|
{
|
|
if(low&((8/size)-1)) { low += 8/size - (low&((8/size)-1)); }
|
|
if(high&((8/size)-1)) high &= ~(8/size-1);
|
|
}
|
|
if(low >= high) { memset(n.edges.v, 0xFF, 3); break; }
|
|
n.edges[k] = low | (high<<4);
|
|
}
|
|
}
|
|
}
|
|
int branches = 0;
|
|
loopi(8) if(c[i].children)
|
|
{
|
|
ivec o(i, co, size);
|
|
genpvsnodes(c[i].children, index+i, o, size>>1);
|
|
if(origpvsnodes[index+i].children) branches++;
|
|
}
|
|
if(!branches && mergepvsnodes(origpvsnodes[parent], &origpvsnodes[index])) origpvsnodes.setsize(index);
|
|
else origpvsnodes[parent].children = index;
|
|
}
|
|
|
|
struct shaftplane
|
|
{
|
|
float r, c, offset;
|
|
uchar rnear, cnear, rfar, cfar;
|
|
};
|
|
|
|
struct shaftbb
|
|
{
|
|
union
|
|
{
|
|
ushort v[6];
|
|
struct { usvec min, max; };
|
|
};
|
|
|
|
shaftbb() {}
|
|
shaftbb(const ivec &o, int size)
|
|
{
|
|
min.x = o.x;
|
|
min.y = o.y;
|
|
min.z = o.z;
|
|
max.x = o.x + size;
|
|
max.y = o.y + size;
|
|
max.z = o.z + size;
|
|
}
|
|
shaftbb(const ivec &o, int size, const bvec &edges)
|
|
{
|
|
min.x = o.x + (size*(edges.x&0xF))/8;
|
|
min.y = o.y + (size*(edges.y&0xF))/8;
|
|
min.z = o.z + (size*(edges.z&0xF))/8;
|
|
max.x = o.x + (size*(edges.x>>4))/8;
|
|
max.y = o.y + (size*(edges.y>>4))/8;
|
|
max.z = o.z + (size*(edges.z>>4))/8;
|
|
}
|
|
|
|
ushort &operator[](int i) { return v[i]; }
|
|
ushort operator[](int i) const { return v[i]; }
|
|
|
|
bool contains(const shaftbb &o) const
|
|
{
|
|
return min.x<=o.min.x && min.y<=o.min.y && min.z<=o.min.z &&
|
|
max.x>=o.max.x && max.y>=o.max.y && max.z>=o.max.z;
|
|
}
|
|
|
|
bool outside(const ivec &o, int size) const
|
|
{
|
|
return o.x>=max.x || o.y>=max.y || o.z>=max.z ||
|
|
o.x+size<=min.x || o.y+size<=min.y || o.z+size<=min.z;
|
|
}
|
|
|
|
bool outside(const shaftbb &o) const
|
|
{
|
|
return o.min.x>max.x || o.min.y>max.y || o.min.z>max.z ||
|
|
o.max.x<min.x || o.max.y<min.y || o.max.z<min.z;
|
|
}
|
|
|
|
bool notinside(const shaftbb &o) const
|
|
{
|
|
return o.min.x<min.x || o.min.y<min.y || o.min.z<min.z ||
|
|
o.max.x>max.x || o.max.y>max.y || o.max.z>max.z;
|
|
}
|
|
};
|
|
|
|
struct shaft
|
|
{
|
|
shaftbb bounds;
|
|
shaftplane planes[8];
|
|
int numplanes;
|
|
|
|
shaft(const shaftbb &from, const shaftbb &to)
|
|
{
|
|
calcshaft(from, to);
|
|
}
|
|
|
|
void calcshaft(const shaftbb &from, const shaftbb &to)
|
|
{
|
|
uchar match = 0, color = 0;
|
|
loopi(3)
|
|
{
|
|
if(to.min[i] < from.min[i]) { color |= 1<<i; bounds.min[i] = 0; }
|
|
else if(to.min[i] > from.min[i]) bounds.min[i] = to.min[i]+1;
|
|
else { match |= 1<<i; bounds.min[i] = to.min[i]; }
|
|
|
|
if(to.max[i] > from.max[i]) { color |= 8<<i; bounds.max[i] = USHRT_MAX; }
|
|
else if(to.max[i] < from.max[i]) bounds.max[i] = to.max[i]-1;
|
|
else { match |= 8<<i; bounds.max[i] = to.max[i]; }
|
|
}
|
|
numplanes = 0;
|
|
loopi(5) if(!(match&(1<<i))) for(int j = i+1; j<6; j++) if(!(match&(1<<j)) && i+3!=j && ((color>>i)^(color>>j))&1)
|
|
{
|
|
int r = i%3, c = j%3, d = (r+1)%3;
|
|
if(d==c) d = (c+1)%3;
|
|
shaftplane &p = planes[numplanes++];
|
|
p.r = from[j] - to[j];
|
|
if(i<3 ? p.r >= 0 : p.r < 0)
|
|
{
|
|
p.r = -p.r;
|
|
p.c = from[i] - to[i];
|
|
}
|
|
else p.c = to[i] - from[i];
|
|
p.offset = -(from[i]*p.r + from[j]*p.c);
|
|
p.rnear = p.r >= 0 ? r : 3+r;
|
|
p.cnear = p.c >= 0 ? c : 3+c;
|
|
p.rfar = p.r < 0 ? r : 3+r;
|
|
p.cfar = p.c < 0 ? c : 3+c;
|
|
}
|
|
}
|
|
|
|
bool outside(const shaftbb &o) const
|
|
{
|
|
if(bounds.outside(o)) return true;
|
|
|
|
for(const shaftplane *p = planes; p < &planes[numplanes]; p++)
|
|
{
|
|
if(o[p->rnear]*p->r + o[p->cnear]*p->c + p->offset > 0) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool inside(const shaftbb &o) const
|
|
{
|
|
if(bounds.notinside(o)) return false;
|
|
|
|
for(const shaftplane *p = planes; p < &planes[numplanes]; p++)
|
|
{
|
|
if(o[p->rfar]*p->r + o[p->cfar]*p->c + p->offset > 0) return false;
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
struct pvsdata
|
|
{
|
|
int offset, len;
|
|
|
|
pvsdata() {}
|
|
pvsdata(int offset, int len) : offset(offset), len(len) {}
|
|
};
|
|
|
|
static vector<uchar> pvsbuf;
|
|
|
|
static inline uint hthash(const pvsdata &k)
|
|
{
|
|
uint h = 5381;
|
|
loopi(k.len) h = ((h<<5)+h)^pvsbuf[k.offset+i];
|
|
return h;
|
|
}
|
|
|
|
static inline bool htcmp(const pvsdata &x, const pvsdata &y)
|
|
{
|
|
return x.len==y.len && !memcmp(&pvsbuf[x.offset], &pvsbuf[y.offset], x.len);
|
|
}
|
|
|
|
static SDL_mutex *pvsmutex = nullptr;
|
|
static hashtable<pvsdata, int> pvscompress;
|
|
static vector<pvsdata> pvs;
|
|
|
|
static SDL_mutex *viewcellmutex = nullptr;
|
|
struct viewcellrequest
|
|
{
|
|
int *result;
|
|
ivec o;
|
|
int size;
|
|
};
|
|
static vector<viewcellrequest> viewcellrequests;
|
|
|
|
static bool genpvs_canceled = false;
|
|
static int numviewcells = 0;
|
|
|
|
VAR(maxpvsblocker, 1, 512, 1<<16);
|
|
VAR(pvsleafsize, 1, 64, 1024);
|
|
|
|
#define MAXWATERPVS 32
|
|
|
|
static struct
|
|
{
|
|
int height;
|
|
vector<materialsurface *> matsurfs;
|
|
} waterplanes[MAXWATERPVS];
|
|
static vector<materialsurface *> waterfalls;
|
|
static uint numwaterplanes = 0;
|
|
|
|
struct pvsworker
|
|
{
|
|
pvsworker() : thread(nullptr), pvsnodes(new pvsnode[origpvsnodes.length()])
|
|
{
|
|
}
|
|
~pvsworker()
|
|
{
|
|
delete[] pvsnodes;
|
|
}
|
|
|
|
SDL_Thread *thread;
|
|
pvsnode *pvsnodes;
|
|
|
|
shaftbb viewcellbb;
|
|
|
|
pvsnode *levels[32];
|
|
int curlevel;
|
|
ivec origin;
|
|
|
|
void resetlevels()
|
|
{
|
|
curlevel = worldscale;
|
|
levels[curlevel] = &pvsnodes[0];
|
|
origin = ivec(0, 0, 0);
|
|
}
|
|
|
|
int hasvoxel(const ivec &p, int coord, int dir, int ocoord = 0, int odir = 0, int *omin = nullptr)
|
|
{
|
|
uint diff = (origin.x^p.x)|(origin.y^p.y)|(origin.z^p.z);
|
|
if(diff >= uint(worldsize)) return 0;
|
|
diff >>= curlevel;
|
|
while(diff)
|
|
{
|
|
curlevel++;
|
|
diff >>= 1;
|
|
}
|
|
|
|
pvsnode *cur = levels[curlevel];
|
|
while(cur->children && !(cur->flags&PVS_HIDE_BB))
|
|
{
|
|
cur = &pvsnodes[cur->children];
|
|
curlevel--;
|
|
cur += ((p.z>>(curlevel-2))&4) | ((p.y>>(curlevel-1))&2) | ((p.x>>curlevel)&1);
|
|
levels[curlevel] = cur;
|
|
}
|
|
|
|
origin = ivec(p.x&(~0U<<curlevel), p.y&(~0U<<curlevel), p.z&(~0U<<curlevel));
|
|
|
|
if(cur->flags&PVS_HIDE_BB || cur->edges==bvec(0x80, 0x80, 0x80))
|
|
{
|
|
if(omin)
|
|
{
|
|
int step = origin[ocoord] + (odir<<curlevel) - p[ocoord] + odir - 1;
|
|
if(odir ? step < *omin : step > *omin) *omin = step;
|
|
}
|
|
return origin[coord] + (dir<<curlevel) - p[coord] + dir - 1;
|
|
}
|
|
|
|
if(cur->edges.x==0xFF) return 0;
|
|
ivec bbp(p);
|
|
bbp.sub(origin);
|
|
ivec bbmin, bbmax;
|
|
bbmin.x = ((cur->edges.x&0xF)<<curlevel)/8;
|
|
if(bbp.x < bbmin.x) return 0;
|
|
bbmax.x = ((cur->edges.x>>4)<<curlevel)/8;
|
|
if(bbp.x >= bbmax.x) return 0;
|
|
bbmin.y = ((cur->edges.y&0xF)<<curlevel)/8;
|
|
if(bbp.y < bbmin.y) return 0;
|
|
bbmax.y = ((cur->edges.y>>4)<<curlevel)/8;
|
|
if(bbp.y >= bbmax.y) return 0;
|
|
bbmin.z = ((cur->edges.z&0xF)<<curlevel)/8;
|
|
if(bbp.z < bbmin.z) return 0;
|
|
bbmax.z = ((cur->edges.z>>4)<<curlevel)/8;
|
|
if(bbp.z >= bbmax.z) return 0;
|
|
|
|
if(omin)
|
|
{
|
|
int step = (odir ? bbmax[ocoord] : bbmin[ocoord]) - bbp[ocoord] + (odir - 1);
|
|
if(odir ? step < *omin : step > *omin) *omin = step;
|
|
}
|
|
return (dir ? bbmax[coord] : bbmin[coord]) - bbp[coord] + (dir - 1);
|
|
}
|
|
|
|
void hidepvs(pvsnode &p)
|
|
{
|
|
if(p.children)
|
|
{
|
|
pvsnode *children = &pvsnodes[p.children];
|
|
loopi(8) hidepvs(children[i]);
|
|
p.flags |= PVS_HIDE_BB;
|
|
return;
|
|
}
|
|
p.flags |= PVS_HIDE_BB;
|
|
if(p.edges.x!=0xFF) p.flags |= PVS_HIDE_GEOM;
|
|
}
|
|
|
|
void shaftcullpvs(shaft &s, pvsnode &p, const ivec &co = ivec(0, 0, 0), int size = worldsize)
|
|
{
|
|
if(p.flags&PVS_HIDE_BB) return;
|
|
shaftbb bb(co, size);
|
|
if(s.outside(bb)) return;
|
|
if(s.inside(bb)) { hidepvs(p); return; }
|
|
if(p.children)
|
|
{
|
|
pvsnode *children = &pvsnodes[p.children];
|
|
uchar flags = 0xFF;
|
|
loopi(8)
|
|
{
|
|
ivec o(i, co, size>>1);
|
|
shaftcullpvs(s, children[i], o, size>>1);
|
|
flags &= children[i].flags;
|
|
}
|
|
if(flags & PVS_HIDE_BB) p.flags |= PVS_HIDE_BB;
|
|
return;
|
|
}
|
|
if(p.edges.x==0xFF) return;
|
|
shaftbb geom(co, size, p.edges);
|
|
if(s.inside(geom)) p.flags |= PVS_HIDE_GEOM;
|
|
}
|
|
|
|
queue<shaftbb, 32> prevblockers;
|
|
|
|
struct cullorder
|
|
{
|
|
int index, dist;
|
|
|
|
cullorder() {}
|
|
cullorder(int index, int dist) : index(index), dist(dist) {}
|
|
};
|
|
|
|
void cullpvs(pvsnode &p, const ivec &co = ivec(0, 0, 0), int size = worldsize)
|
|
{
|
|
if(p.flags&(PVS_HIDE_BB | PVS_HIDE_GEOM) || genpvs_canceled) return;
|
|
if(p.children && !(p.flags&PVS_HIDE_BB))
|
|
{
|
|
pvsnode *children = &pvsnodes[p.children];
|
|
int csize = size>>1;
|
|
ivec dmin = ivec(co).add(csize>>1).sub(ivec(viewcellbb.min).add(ivec(viewcellbb.max)).shr(1)), dmax = ivec(dmin).add(csize);
|
|
dmin.mul(dmin);
|
|
dmax.mul(dmax);
|
|
ivec diff = ivec(dmax).sub(dmin);
|
|
cullorder order[8];
|
|
int dir = 0;
|
|
if(diff.x < 0) { diff.x = -diff.x; dir |= 1; }
|
|
if(diff.y < 0) { diff.y = -diff.y; dir |= 2; }
|
|
if(diff.z < 0) { diff.z = -diff.z; dir |= 4; }
|
|
order[0] = cullorder(0, 0);
|
|
order[7] = cullorder(7, diff.x + diff.y + diff.z);
|
|
order[1] = cullorder(1, diff.x);
|
|
order[2] = cullorder(2, diff.y);
|
|
order[3] = cullorder(4, diff.z);
|
|
if(order[2].dist < order[1].dist) swap(order[1], order[2]);
|
|
if(order[3].dist < order[2].dist) swap(order[2], order[3]);
|
|
if(order[2].dist < order[1].dist) swap(order[1], order[2]);
|
|
cullorder dxy(order[1].index|order[2].index, order[1].dist+order[2].dist),
|
|
dxz(order[1].index|order[3].index, order[1].dist+order[3].dist),
|
|
dyz(order[2].index|order[3].index, order[2].dist+order[3].dist);
|
|
int j;
|
|
for(j = 4; j > 0 && dxy.dist < order[j-1].dist; --j) order[j] = order[j-1];
|
|
order[j] = dxy;
|
|
for(j = 5; j > 0 && dxz.dist < order[j-1].dist; --j) order[j] = order[j-1];
|
|
order[j] = dxz;
|
|
for(j = 6; j > 0 && dyz.dist < order[j-1].dist; --j) order[j] = order[j-1];
|
|
order[j] = dyz;
|
|
loopi(8)
|
|
{
|
|
int index = order[i].index^dir;
|
|
ivec o(index, co, csize);
|
|
cullpvs(children[index], o, csize);
|
|
}
|
|
if(!(p.flags & PVS_HIDE_BB)) return;
|
|
}
|
|
bvec edges = p.children ? bvec(0x80, 0x80, 0x80) : p.edges;
|
|
if(edges.x==0xFF) return;
|
|
shaftbb geom(co, size, edges);
|
|
ivec diff = ivec(geom.max).sub(ivec(viewcellbb.min)).abs();
|
|
cullorder order[3] = { cullorder(0, diff.x), cullorder(1, diff.y), cullorder(2, diff.z) };
|
|
if(order[1].dist > order[0].dist) swap(order[0], order[1]);
|
|
if(order[2].dist > order[1].dist) swap(order[1], order[2]);
|
|
if(order[1].dist > order[0].dist) swap(order[0], order[1]);
|
|
loopi(6)
|
|
{
|
|
int dim = order[i >= 3 ? i-3 : i].index, dc = (i >= 3) != (geom.max[dim] <= viewcellbb.min[dim]) ? 1 : 0, r = R[dim], c = C[dim];
|
|
int ccenter = geom.min[c];
|
|
if(geom.min[r]==geom.max[r] || geom.min[c]==geom.max[c]) continue;
|
|
while(ccenter < geom.max[c])
|
|
{
|
|
ivec rmin;
|
|
rmin[dim] = geom[dim + 3*dc] + (dc ? -1 : 0);
|
|
rmin[r] = geom.min[r];
|
|
rmin[c] = ccenter;
|
|
ivec rmax = rmin;
|
|
rmax[r] = geom.max[r] - 1;
|
|
int rcenter = (rmin[r] + rmax[r])/2;
|
|
resetlevels();
|
|
for(int minstep = -1, maxstep = 1; (minstep || maxstep) && rmax[r] - rmin[r] < maxpvsblocker;)
|
|
{
|
|
if(minstep) minstep = hasvoxel(rmin, r, 0);
|
|
if(maxstep) maxstep = hasvoxel(rmax, r, 1);
|
|
rmin[r] += minstep;
|
|
rmax[r] += maxstep;
|
|
}
|
|
rmin[r] = rcenter + (rmin[r] - rcenter)/2;
|
|
rmax[r] = rcenter + (rmax[r] - rcenter)/2;
|
|
if(rmin[r]>=geom.min[r] && rmax[r]<geom.max[r]) { rmin[r] = geom.min[r]; rmax[r] = geom.max[r] - 1; }
|
|
ivec cmin = rmin, cmax = rmin;
|
|
if(rmin[r]>=geom.min[r] && rmax[r]<geom.max[r])
|
|
{
|
|
cmin[c] = geom.min[c];
|
|
cmax[c] = geom.max[c]-1;
|
|
}
|
|
int cminstep = -1, cmaxstep = 1;
|
|
for(; (cminstep || cmaxstep) && cmax[c] - cmin[c] < maxpvsblocker;)
|
|
{
|
|
if(cminstep)
|
|
{
|
|
cmin[c] += cminstep; cminstep = INT_MIN;
|
|
cmin[r] = rmin[r];
|
|
resetlevels();
|
|
for(int rstep = 1; rstep && cmin[r] <= rmax[r];)
|
|
{
|
|
rstep = hasvoxel(cmin, r, 1, c, 0, &cminstep);
|
|
cmin[r] += rstep;
|
|
}
|
|
if(cmin[r] <= rmax[r]) cminstep = 0;
|
|
}
|
|
if(cmaxstep)
|
|
{
|
|
cmax[c] += cmaxstep; cmaxstep = INT_MAX;
|
|
cmax[r] = rmin[r];
|
|
resetlevels();
|
|
for(int rstep = 1; rstep && cmax[r] <= rmax[r];)
|
|
{
|
|
rstep = hasvoxel(cmax, r, 1, c, 1, &cmaxstep);
|
|
cmax[r] += rstep;
|
|
}
|
|
if(cmax[r] <= rmax[r]) cmaxstep = 0;
|
|
}
|
|
}
|
|
if(!cminstep) cmin[c]++;
|
|
if(!cmaxstep) cmax[c]--;
|
|
ivec emin = rmin, emax = rmax;
|
|
if(cmin[c]>=geom.min[c] && cmax[c]<geom.max[c])
|
|
{
|
|
if(emin[r]>geom.min[r]) emin[r] = geom.min[r];
|
|
if(emax[r]<geom.max[r]-1) emax[r] = geom.max[r]-1;
|
|
}
|
|
int rminstep = -1, rmaxstep = 1;
|
|
for(; (rminstep || rmaxstep) && emax[r] - emin[r] < maxpvsblocker;)
|
|
{
|
|
if(rminstep)
|
|
{
|
|
emin[r] += -1; rminstep = INT_MIN;
|
|
emin[c] = cmin[c];
|
|
resetlevels();
|
|
for(int cstep = 1; cstep && emin[c] <= cmax[c];)
|
|
{
|
|
cstep = hasvoxel(emin, c, 1, r, 0, &rminstep);
|
|
emin[c] += cstep;
|
|
}
|
|
if(emin[c] <= cmax[c]) rminstep = 0;
|
|
}
|
|
if(rmaxstep)
|
|
{
|
|
emax[r] += 1; rmaxstep = INT_MAX;
|
|
emax[c] = cmin[c];
|
|
resetlevels();
|
|
for(int cstep = 1; cstep && emax[c] <= cmax[c];)
|
|
{
|
|
cstep = hasvoxel(emax, c, 1, r, 1, &rmaxstep);
|
|
emax[c] += cstep;
|
|
}
|
|
if(emax[c] <= cmax[c]) rmaxstep = 0;
|
|
}
|
|
}
|
|
if(!rminstep) emin[r]++;
|
|
if(!rmaxstep) emax[r]--;
|
|
shaftbb bb;
|
|
bb.min[dim] = rmin[dim];
|
|
bb.max[dim] = rmin[dim]+1;
|
|
bb.min[r] = emin[r];
|
|
bb.max[r] = emax[r]+1;
|
|
bb.min[c] = cmin[c];
|
|
bb.max[c] = cmax[c]+1;
|
|
if(bb.min[dim] >= viewcellbb.max[dim] || bb.max[dim] <= viewcellbb.min[dim])
|
|
{
|
|
int ddir = bb.min[dim] >= viewcellbb.max[dim] ? 1 : -1,
|
|
dval = ddir>0 ? USHRT_MAX-1 : 0,
|
|
dlimit = maxpvsblocker,
|
|
numsides = 0;
|
|
loopj(4)
|
|
{
|
|
ivec dmax;
|
|
int odim = j < 2 ? c : r;
|
|
if(j&1)
|
|
{
|
|
if(bb.max[odim] >= viewcellbb.max[odim]) continue;
|
|
dmax[odim] = bb.max[odim]-1;
|
|
}
|
|
else
|
|
{
|
|
if(bb.min[odim] <= viewcellbb.min[odim]) continue;
|
|
dmax[odim] = bb.min[odim];
|
|
}
|
|
numsides++;
|
|
dmax[dim] = bb.min[dim];
|
|
int stepdim = j < 2 ? r : c, stepstart = bb.min[stepdim], stepend = bb.max[stepdim];
|
|
int dstep = ddir;
|
|
for(; dstep && ddir*(dmax[dim] - (int)bb.min[dim]) < dlimit;)
|
|
{
|
|
dmax[dim] += dstep; dstep = ddir > 0 ? INT_MAX : INT_MIN;
|
|
dmax[stepdim] = stepstart;
|
|
resetlevels();
|
|
for(int step = 1; step && dmax[stepdim] < stepend;)
|
|
{
|
|
step = hasvoxel(dmax, stepdim, 1, dim, (ddir+1)/2, &dstep);
|
|
dmax[stepdim] += step;
|
|
}
|
|
if(dmax[stepdim] < stepend) dstep = 0;
|
|
}
|
|
dlimit = min(dlimit, ddir*(dmax[dim] - (int)bb.min[dim]));
|
|
if(!dstep) dmax[dim] -= ddir;
|
|
if(ddir>0) dval = min(dval, dmax[dim]);
|
|
else dval = max(dval, dmax[dim]);
|
|
}
|
|
if(numsides>0)
|
|
{
|
|
if(ddir>0) bb.max[dim] = dval+1;
|
|
else bb.min[dim] = dval;
|
|
}
|
|
//printf("(%d,%d,%d) x %d,%d,%d, side %d, ccenter = %d, origin = (%d,%d,%d), size = %d\n", bb.min.x, bb.min.y, bb.min.z, bb.max.x-bb.min.x, bb.max.y-bb.min.y, bb.max.z-bb.min.z, i, ccenter, co.x, co.y, co.z, size);
|
|
}
|
|
bool dup = false;
|
|
loopvj(prevblockers)
|
|
{
|
|
if(prevblockers[j].contains(bb)) { dup = true; break; }
|
|
}
|
|
if(!dup)
|
|
{
|
|
shaft s(viewcellbb, bb);
|
|
shaftcullpvs(s, pvsnodes[0]);
|
|
prevblockers.add(bb);
|
|
}
|
|
if(bb.contains(geom)) return;
|
|
ccenter = cmax[c] + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool compresspvs(pvsnode &p, int size, int threshold)
|
|
{
|
|
if(!p.children) return true;
|
|
if(p.flags&PVS_HIDE_BB) { p.children = 0; return true; }
|
|
pvsnode *children = &pvsnodes[p.children];
|
|
bool canreduce = true;
|
|
loopi(8)
|
|
{
|
|
if(!compresspvs(children[i], size/2, threshold)) canreduce = false;
|
|
}
|
|
if(canreduce)
|
|
{
|
|
int hide = children[7].flags&PVS_HIDE_BB;
|
|
loopi(7) if((children[i].flags&PVS_HIDE_BB)!=hide) canreduce = false;
|
|
if(canreduce)
|
|
{
|
|
p.flags = (p.flags & ~PVS_HIDE_BB) | hide;
|
|
p.children = 0;
|
|
return true;
|
|
}
|
|
}
|
|
if(size <= threshold)
|
|
{
|
|
p.children = 0;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
vector<uchar> outbuf;
|
|
|
|
bool serializepvs(pvsnode &p, int storage = -1)
|
|
{
|
|
if(!p.children)
|
|
{
|
|
outbuf.add(0xFF);
|
|
loopi(8) outbuf.add(p.flags&PVS_HIDE_BB ? 0xFF : 0);
|
|
return true;
|
|
}
|
|
int index = outbuf.length();
|
|
pvsnode *children = &pvsnodes[p.children];
|
|
int i = 0;
|
|
uchar leafvalues = 0;
|
|
if(storage>=0)
|
|
{
|
|
for(; i < 8; i++)
|
|
{
|
|
pvsnode &child = children[i];
|
|
if(child.flags&PVS_HIDE_BB) leafvalues |= 1<<i;
|
|
else if(child.children) break;
|
|
}
|
|
if(i==8) { outbuf[storage] = leafvalues; return false; }
|
|
// if offset won't fit, just mark the space as a visible to avoid problems
|
|
int offset = (index - storage + 8)/9;
|
|
if(offset>255) { outbuf[storage] = 0; return false; }
|
|
outbuf[storage] = uchar(offset);
|
|
}
|
|
outbuf.add(0);
|
|
loopj(8) outbuf.add(leafvalues&(1<<j) ? 0xFF : 0);
|
|
uchar leafmask = (1<<i)-1;
|
|
for(; i < 8; i++)
|
|
{
|
|
pvsnode &child = children[i];
|
|
if(child.children) { if(!serializepvs(child, index+1+i)) leafmask |= 1<<i; }
|
|
else { leafmask |= 1<<i; outbuf[index+1+i] = child.flags&PVS_HIDE_BB ? 0xFF : 0; }
|
|
}
|
|
outbuf[index] = leafmask;
|
|
return true;
|
|
}
|
|
|
|
bool materialoccluded(pvsnode &p, const ivec &co, int size, const ivec &bbmin, const ivec &bbmax)
|
|
{
|
|
pvsnode *children = &pvsnodes[p.children];
|
|
loopoctabox(co, size, bbmin, bbmax)
|
|
{
|
|
ivec o(i, co, size);
|
|
if(children[i].flags & PVS_HIDE_BB) continue;
|
|
if(!children[i].children || !materialoccluded(children[i], o, size/2, bbmin, bbmax)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool materialoccluded(vector<materialsurface *> &matsurfs)
|
|
{
|
|
if(pvsnodes[0].flags & PVS_HIDE_BB) return true;
|
|
if(!pvsnodes[0].children) return false;
|
|
loopv(matsurfs)
|
|
{
|
|
materialsurface &m = *matsurfs[i];
|
|
ivec bbmin(m.o), bbmax(m.o);
|
|
int dim = dimension(m.orient);
|
|
bbmin[dim] += dimcoord(m.orient) ? -2 : 2;
|
|
bbmax[C[dim]] += m.csize;
|
|
bbmax[R[dim]] += m.rsize;
|
|
if(!materialoccluded(pvsnodes[0], ivec(0, 0, 0), worldsize/2, bbmin, bbmax)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int wateroccluded, waterbytes;
|
|
|
|
void calcpvs(const ivec &co, int size)
|
|
{
|
|
loopk(3)
|
|
{
|
|
viewcellbb.min[k] = co[k];
|
|
viewcellbb.max[k] = co[k]+size;
|
|
}
|
|
memcpy(pvsnodes, origpvsnodes.getbuf(), origpvsnodes.length()*sizeof(pvsnode));
|
|
prevblockers.clear();
|
|
cullpvs(pvsnodes[0]);
|
|
|
|
wateroccluded = 0;
|
|
loopi(numwaterplanes)
|
|
{
|
|
if(waterplanes[i].height < 0)
|
|
{
|
|
if(waterfalls.length() && materialoccluded(waterfalls)) wateroccluded |= 1<<i;
|
|
}
|
|
else if(waterplanes[i].matsurfs.length() && materialoccluded(waterplanes[i].matsurfs)) wateroccluded |= 1<<i;
|
|
}
|
|
waterbytes = 0;
|
|
loopi(4) if(wateroccluded&(0xFF<<(i*8))) waterbytes = i+1;
|
|
|
|
compresspvs(pvsnodes[0], worldsize, pvsleafsize);
|
|
outbuf.setsize(0);
|
|
serializepvs(pvsnodes[0]);
|
|
}
|
|
|
|
uchar *testviewcell(const ivec &co, int size, int *waterpvs = nullptr, int *len = nullptr)
|
|
{
|
|
calcpvs(co, size);
|
|
|
|
uchar *buf = new uchar[outbuf.length()];
|
|
memcpy(buf, outbuf.getbuf(), outbuf.length());
|
|
if(waterpvs) *waterpvs = wateroccluded;
|
|
if(len) *len = outbuf.length();
|
|
return buf;
|
|
}
|
|
|
|
int genviewcell(const ivec &co, int size)
|
|
{
|
|
calcpvs(co, size);
|
|
|
|
if(pvsmutex) SDL_LockMutex(pvsmutex);
|
|
numviewcells++;
|
|
pvsdata key(pvsbuf.length(), waterbytes + outbuf.length());
|
|
loopi(waterbytes) pvsbuf.add((wateroccluded>>(i*8))&0xFF);
|
|
pvsbuf.put(outbuf.getbuf(), outbuf.length());
|
|
int *val = pvscompress.access(key);
|
|
if(val) pvsbuf.setsize(key.offset);
|
|
else
|
|
{
|
|
val = &pvscompress[key];
|
|
*val = pvs.length();
|
|
pvs.add(key);
|
|
}
|
|
if(pvsmutex) SDL_UnlockMutex(pvsmutex);
|
|
return *val;
|
|
}
|
|
|
|
static int run(void *data)
|
|
{
|
|
pvsworker *w = (pvsworker *)data;
|
|
SDL_LockMutex(viewcellmutex);
|
|
while(viewcellrequests.length())
|
|
{
|
|
viewcellrequest req = viewcellrequests.pop();
|
|
SDL_UnlockMutex(viewcellmutex);
|
|
int result = w->genviewcell(req.o, req.size);
|
|
SDL_LockMutex(viewcellmutex);
|
|
*req.result = result;
|
|
}
|
|
SDL_UnlockMutex(viewcellmutex);
|
|
return 0;
|
|
}
|
|
};
|
|
|
|
struct viewcellnode
|
|
{
|
|
uchar leafmask;
|
|
union viewcellchild
|
|
{
|
|
int pvs;
|
|
viewcellnode *node;
|
|
} children[8];
|
|
|
|
viewcellnode() : leafmask(0xFF)
|
|
{
|
|
loopi(8) children[i].pvs = -1;
|
|
}
|
|
~viewcellnode()
|
|
{
|
|
loopi(8) if(!(leafmask&(1<<i))) delete children[i].node;
|
|
}
|
|
};
|
|
|
|
VARP(pvsthreads, 0, 0, 16);
|
|
static vector<pvsworker *> pvsworkers;
|
|
|
|
static volatile bool check_genpvs_progress = false;
|
|
|
|
static Uint32 genpvs_timer(Uint32 interval, void *param)
|
|
{
|
|
check_genpvs_progress = true;
|
|
return interval;
|
|
}
|
|
|
|
static int totalviewcells = 0;
|
|
|
|
static void show_genpvs_progress(int unique = pvs.length(), int processed = numviewcells)
|
|
{
|
|
float bar1 = float(processed) / float(totalviewcells>0 ? totalviewcells : 1);
|
|
|
|
defformatstring(text1, "%d%% - %d of %d view cells (%d unique)", int(bar1 * 100), processed, totalviewcells, unique);
|
|
|
|
renderprogress(bar1, text1);
|
|
|
|
if(interceptkey(SDLK_ESCAPE)) genpvs_canceled = true;
|
|
check_genpvs_progress = false;
|
|
}
|
|
|
|
static shaftbb pvsbounds;
|
|
|
|
static void calcpvsbounds()
|
|
{
|
|
loopk(3) pvsbounds.min[k] = USHRT_MAX;
|
|
loopk(3) pvsbounds.max[k] = 0;
|
|
loopv(valist)
|
|
{
|
|
vtxarray *va = valist[i];
|
|
loopk(3)
|
|
{
|
|
if(va->geommin[k]>va->geommax[k]) continue;
|
|
pvsbounds.min[k] = min(pvsbounds.min[k], (ushort)va->geommin[k]);
|
|
pvsbounds.max[k] = max(pvsbounds.max[k], (ushort)va->geommax[k]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline bool isallclip(cube *c)
|
|
{
|
|
loopi(8)
|
|
{
|
|
cube &h = c[i];
|
|
if(h.children ? !isallclip(h.children) : (!isentirelysolid(h) && (h.material&MATF_CLIP)!=MAT_CLIP))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static int countviewcells(cube *c, const ivec &co, int size, int threshold)
|
|
{
|
|
int count = 0;
|
|
loopi(8)
|
|
{
|
|
ivec o(i, co, size);
|
|
if(pvsbounds.outside(o, size)) continue;
|
|
cube &h = c[i];
|
|
if(h.children)
|
|
{
|
|
if(size>threshold)
|
|
{
|
|
count += countviewcells(h.children, o, size>>1, threshold);
|
|
continue;
|
|
}
|
|
if(isallclip(h.children)) continue;
|
|
}
|
|
else if(isentirelysolid(h) || (h.material&MATF_CLIP)==MAT_CLIP) continue;
|
|
count++;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static void genviewcells(viewcellnode &p, cube *c, const ivec &co, int size, int threshold)
|
|
{
|
|
if(genpvs_canceled) return;
|
|
loopi(8)
|
|
{
|
|
ivec o(i, co, size);
|
|
if(pvsbounds.outside(o, size)) continue;
|
|
cube &h = c[i];
|
|
if(h.children)
|
|
{
|
|
if(size>threshold)
|
|
{
|
|
p.leafmask &= ~(1<<i);
|
|
p.children[i].node = new viewcellnode;
|
|
genviewcells(*p.children[i].node, h.children, o, size>>1, threshold);
|
|
continue;
|
|
}
|
|
if(isallclip(h.children)) continue;
|
|
}
|
|
else if(isentirelysolid(h) || (h.material&MATF_CLIP)==MAT_CLIP) continue;
|
|
if(pvsworkers.length())
|
|
{
|
|
if(genpvs_canceled) return;
|
|
p.children[i].pvs = pvsworkers[0]->genviewcell(o, size);
|
|
if(check_genpvs_progress) show_genpvs_progress();
|
|
}
|
|
else
|
|
{
|
|
viewcellrequest &req = viewcellrequests.add();
|
|
req.result = &p.children[i].pvs;
|
|
req.o = o;
|
|
req.size = size;
|
|
}
|
|
}
|
|
}
|
|
|
|
static viewcellnode *viewcells = nullptr;
|
|
static int lockedwaterplanes[MAXWATERPVS];
|
|
static uchar *curpvs = nullptr, *lockedpvs = nullptr;
|
|
static int curwaterpvs = 0, lockedwaterpvs = 0;
|
|
|
|
static inline pvsdata *lookupviewcell(const vec &p)
|
|
{
|
|
uint x = uint(floor(p.x)), y = uint(floor(p.y)), z = uint(floor(p.z));
|
|
if(!viewcells || (x|y|z)>=uint(worldsize)) return nullptr;
|
|
viewcellnode *vc = viewcells;
|
|
for(int scale = worldscale-1; scale>=0; scale--)
|
|
{
|
|
int i = octastep(x, y, z, scale);
|
|
if(vc->leafmask&(1<<i))
|
|
{
|
|
return vc->children[i].pvs>=0 ? &pvs[vc->children[i].pvs] : nullptr;
|
|
}
|
|
vc = vc->children[i].node;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
static void lockpvs_(bool lock)
|
|
{
|
|
if(lockedpvs) DELETEA(lockedpvs);
|
|
if(!lock) return;
|
|
pvsdata *d = lookupviewcell(camera1->o);
|
|
if(!d) return;
|
|
int wbytes = d->len%9, len = d->len - wbytes;
|
|
lockedpvs = new uchar[len];
|
|
memcpy(lockedpvs, &pvsbuf[d->offset + wbytes], len);
|
|
lockedwaterpvs = 0;
|
|
loopi(wbytes) lockedwaterpvs |= pvsbuf[d->offset + i] << (i*8);
|
|
loopi(MAXWATERPVS) lockedwaterplanes[i] = waterplanes[i].height;
|
|
conoutf("locked view cell at %.1f, %.1f, %.1f", camera1->o.x, camera1->o.y, camera1->o.z);
|
|
}
|
|
|
|
VARF(lockpvs, 0, 0, 1, lockpvs_(lockpvs!=0));
|
|
|
|
VARN(pvs, usepvs, 0, 1, 1);
|
|
VARN(waterpvs, usewaterpvs, 0, 1, 1);
|
|
|
|
void setviewcell(const vec &p)
|
|
{
|
|
if(!usepvs) curpvs = nullptr;
|
|
else if(lockedpvs)
|
|
{
|
|
curpvs = lockedpvs;
|
|
curwaterpvs = lockedwaterpvs;
|
|
}
|
|
else
|
|
{
|
|
pvsdata *d = lookupviewcell(p);
|
|
curpvs = d ? &pvsbuf[d->offset] : nullptr;
|
|
curwaterpvs = 0;
|
|
if(d)
|
|
{
|
|
loopi(d->len%9) curwaterpvs |= *curpvs++ << (i*8);
|
|
}
|
|
}
|
|
if(!usepvs || !usewaterpvs) curwaterpvs = 0;
|
|
}
|
|
|
|
void clearpvs()
|
|
{
|
|
DELETEP(viewcells);
|
|
pvs.setsize(0);
|
|
pvsbuf.setsize(0);
|
|
curpvs = nullptr;
|
|
numwaterplanes = 0;
|
|
lockpvs = 0;
|
|
lockpvs_(false);
|
|
}
|
|
|
|
COMMAND(clearpvs, "");
|
|
|
|
static void findwaterplanes()
|
|
{
|
|
loopi(MAXWATERPVS)
|
|
{
|
|
waterplanes[i].height = -1;
|
|
waterplanes[i].matsurfs.setsize(0);
|
|
}
|
|
waterfalls.setsize(0);
|
|
numwaterplanes = 0;
|
|
loopv(valist)
|
|
{
|
|
vtxarray *va = valist[i];
|
|
loopj(va->matsurfs)
|
|
{
|
|
materialsurface &m = va->matbuf[j];
|
|
if((m.material&MATF_VOLUME)!=MAT_WATER || m.orient==O_BOTTOM) { j += m.skip; continue; }
|
|
if(m.orient!=O_TOP)
|
|
{
|
|
waterfalls.add(&m);
|
|
continue;
|
|
}
|
|
loopk(numwaterplanes) if(waterplanes[k].height == m.o.z)
|
|
{
|
|
waterplanes[k].matsurfs.add(&m);
|
|
goto nextmatsurf;
|
|
}
|
|
if(numwaterplanes < MAXWATERPVS)
|
|
{
|
|
waterplanes[numwaterplanes].height = m.o.z;
|
|
waterplanes[numwaterplanes].matsurfs.add(&m);
|
|
numwaterplanes++;
|
|
}
|
|
nextmatsurf:;
|
|
}
|
|
}
|
|
if(waterfalls.length() > 0 && numwaterplanes < MAXWATERPVS) numwaterplanes++;
|
|
}
|
|
|
|
static void testpvs(int *vcsize)
|
|
{
|
|
lockpvs_(false);
|
|
|
|
uint oldnumwaterplanes = numwaterplanes;
|
|
int oldwaterplanes[MAXWATERPVS];
|
|
loopi(numwaterplanes) oldwaterplanes[i] = waterplanes[i].height;
|
|
|
|
findwaterplanes();
|
|
|
|
pvsnode &root = origpvsnodes.add();
|
|
memset(root.edges.v, 0xFF, 3);
|
|
root.flags = 0;
|
|
root.children = 0;
|
|
genpvsnodes(worldroot);
|
|
|
|
genpvs_canceled = false;
|
|
check_genpvs_progress = false;
|
|
|
|
int size = *vcsize>0 ? *vcsize : 32;
|
|
for(int mask = 1; mask < size; mask <<= 1) size &= ~mask;
|
|
|
|
ivec o = ivec(camera1->o).mask(~(size-1));
|
|
pvsworker w;
|
|
int len;
|
|
lockedpvs = w.testviewcell(o, size, &lockedwaterpvs, &len);
|
|
loopi(MAXWATERPVS) lockedwaterplanes[i] = waterplanes[i].height;
|
|
lockpvs = 1;
|
|
conoutf("generated test view cell of size %d at %.1f, %.1f, %.1f (%d B)", size, camera1->o.x, camera1->o.y, camera1->o.z, len);
|
|
|
|
origpvsnodes.setsize(0);
|
|
numwaterplanes = oldnumwaterplanes;
|
|
loopi(numwaterplanes) waterplanes[i].height = oldwaterplanes[i];
|
|
}
|
|
|
|
COMMAND(testpvs, "i");
|
|
|
|
static void genpvs(int *viewcellsize)
|
|
{
|
|
if(worldsize > 1<<15)
|
|
{
|
|
conoutf(CON_ERROR, "map is too large for PVS");
|
|
return;
|
|
}
|
|
|
|
renderbackground("generating PVS (esc to abort)");
|
|
genpvs_canceled = false;
|
|
Uint32 start = SDL_GetTicks();
|
|
|
|
renderprogress(0, "finding view cells");
|
|
|
|
clearpvs();
|
|
calcpvsbounds();
|
|
findwaterplanes();
|
|
|
|
pvsnode &root = origpvsnodes.add();
|
|
memset(root.edges.v, 0xFF, 3);
|
|
root.flags = 0;
|
|
root.children = 0;
|
|
genpvsnodes(worldroot);
|
|
|
|
totalviewcells = countviewcells(worldroot, ivec(0, 0, 0), worldsize>>1, *viewcellsize>0 ? *viewcellsize : 32);
|
|
numviewcells = 0;
|
|
genpvs_canceled = false;
|
|
check_genpvs_progress = false;
|
|
SDL_TimerID timer = 0;
|
|
int numthreads = pvsthreads > 0 ? pvsthreads : numcpus;
|
|
if(numthreads<=1)
|
|
{
|
|
pvsworkers.add(new pvsworker);
|
|
timer = SDL_AddTimer(500, genpvs_timer, nullptr);
|
|
}
|
|
viewcells = new viewcellnode;
|
|
genviewcells(*viewcells, worldroot, ivec(0, 0, 0), worldsize>>1, *viewcellsize>0 ? *viewcellsize : 32);
|
|
if(numthreads<=1)
|
|
{
|
|
SDL_RemoveTimer(timer);
|
|
}
|
|
else
|
|
{
|
|
renderprogress(0, "creating threads");
|
|
if(!pvsmutex) pvsmutex = SDL_CreateMutex();
|
|
if(!viewcellmutex) viewcellmutex = SDL_CreateMutex();
|
|
loopi(numthreads)
|
|
{
|
|
pvsworker *w = pvsworkers.add(new pvsworker);
|
|
w->thread = SDL_CreateThread(pvsworker::run, "pvs worker", w);
|
|
}
|
|
show_genpvs_progress(0, 0);
|
|
while(!genpvs_canceled)
|
|
{
|
|
SDL_Delay(500);
|
|
SDL_LockMutex(viewcellmutex);
|
|
int unique = pvs.length(), processed = numviewcells, remaining = viewcellrequests.length();
|
|
SDL_UnlockMutex(viewcellmutex);
|
|
show_genpvs_progress(unique, processed);
|
|
if(!remaining) break;
|
|
}
|
|
SDL_LockMutex(viewcellmutex);
|
|
viewcellrequests.setsize(0);
|
|
SDL_UnlockMutex(viewcellmutex);
|
|
loopv(pvsworkers) SDL_WaitThread(pvsworkers[i]->thread, nullptr);
|
|
}
|
|
pvsworkers.deletecontents();
|
|
|
|
origpvsnodes.setsize(0);
|
|
pvscompress.clear();
|
|
|
|
Uint32 end = SDL_GetTicks();
|
|
if(genpvs_canceled)
|
|
{
|
|
clearpvs();
|
|
conoutf("genpvs aborted");
|
|
}
|
|
else conoutf("generated %d unique view cells totaling %.1f kB and averaging %d B (%.1f seconds)",
|
|
pvs.length(), pvsbuf.length()/1024.0f, pvsbuf.length()/max(pvs.length(), 1), (end - start) / 1000.0f);
|
|
}
|
|
|
|
COMMAND(genpvs, "i");
|
|
|
|
static void pvsstats()
|
|
{
|
|
conoutf("%d unique view cells totaling %.1f kB and averaging %d B",
|
|
pvs.length(), pvsbuf.length()/1024.0f, pvsbuf.length()/max(pvs.length(), 1));
|
|
}
|
|
|
|
COMMAND(pvsstats, "");
|
|
|
|
static inline bool pvsoccluded(uchar *buf, const ivec &co, int size, const ivec &bbmin, const ivec &bbmax)
|
|
{
|
|
uchar leafmask = buf[0];
|
|
loopoctabox(co, size, bbmin, bbmax)
|
|
{
|
|
ivec o(i, co, size);
|
|
if(leafmask&(1<<i))
|
|
{
|
|
uchar leafvalues = buf[1+i];
|
|
if(!leafvalues || (leafvalues!=0xFF && octaboxoverlap(o, size>>1, bbmin, bbmax)&~leafvalues))
|
|
return false;
|
|
}
|
|
else if(!pvsoccluded(buf+9*buf[1+i], o, size>>1, bbmin, bbmax)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static inline bool pvsoccluded(uchar *buf, const ivec &bbmin, const ivec &bbmax)
|
|
{
|
|
int diff = (bbmin.x^bbmax.x) | (bbmin.y^bbmax.y) | (bbmin.z^bbmax.z);
|
|
if(diff&~((1<<worldscale)-1)) return false;
|
|
int scale = worldscale-1;
|
|
while(!(diff&(1<<scale)))
|
|
{
|
|
int i = octastep(bbmin.x, bbmin.y, bbmin.z, scale);
|
|
scale--;
|
|
uchar leafmask = buf[0];
|
|
if(leafmask&(1<<i))
|
|
{
|
|
uchar leafvalues = buf[1+i];
|
|
return leafvalues && (leafvalues==0xFF || !(octaboxoverlap(ivec(bbmin).mask(~((2<<scale)-1)), 1<<scale, bbmin, bbmax)&~leafvalues));
|
|
}
|
|
buf += 9*buf[1+i];
|
|
}
|
|
return pvsoccluded(buf, ivec(bbmin).mask(~((2<<scale)-1)), 1<<scale, bbmin, bbmax);
|
|
}
|
|
|
|
bool pvsoccluded(const ivec &bbmin, const ivec &bbmax)
|
|
{
|
|
return curpvs!=nullptr && pvsoccluded(curpvs, bbmin, bbmax);
|
|
}
|
|
|
|
bool pvsoccludedsphere(const vec ¢er, float radius)
|
|
{
|
|
if(curpvs==nullptr) return false;
|
|
ivec bbmin(vec(center).sub(radius)), bbmax(vec(center).add(radius+1));
|
|
return pvsoccluded(curpvs, bbmin, bbmax);
|
|
}
|
|
|
|
bool waterpvsoccluded(int height)
|
|
{
|
|
if(!curwaterpvs) return false;
|
|
if(lockedpvs)
|
|
{
|
|
loopi(MAXWATERPVS) if(lockedwaterplanes[i]==height) return (curwaterpvs&(1<<i))!=0;
|
|
}
|
|
else
|
|
{
|
|
loopi(numwaterplanes) if(waterplanes[i].height==height) return (curwaterpvs&(1<<i))!=0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void saveviewcells(stream *f, viewcellnode &p)
|
|
{
|
|
f->putchar(p.leafmask);
|
|
loopi(8)
|
|
{
|
|
if(p.leafmask&(1<<i)) f->putlil<int>(p.children[i].pvs);
|
|
else saveviewcells(f, *p.children[i].node);
|
|
}
|
|
}
|
|
|
|
void savepvs(stream *f)
|
|
{
|
|
uint totallen = pvsbuf.length() | (numwaterplanes>0 ? 0x80000000U : 0);
|
|
f->putlil<uint>(totallen);
|
|
if(numwaterplanes>0)
|
|
{
|
|
f->putlil<uint>(numwaterplanes);
|
|
loopi(numwaterplanes)
|
|
{
|
|
f->putlil<int>(waterplanes[i].height);
|
|
if(waterplanes[i].height < 0) break;
|
|
}
|
|
}
|
|
loopv(pvs) f->putlil<ushort>(pvs[i].len);
|
|
f->write(pvsbuf.getbuf(), pvsbuf.length());
|
|
saveviewcells(f, *viewcells);
|
|
}
|
|
|
|
static viewcellnode *loadviewcells(stream *f)
|
|
{
|
|
viewcellnode *p = new viewcellnode;
|
|
p->leafmask = f->getchar();
|
|
loopi(8)
|
|
{
|
|
if(p->leafmask&(1<<i)) p->children[i].pvs = f->getlil<int>();
|
|
else p->children[i].node = loadviewcells(f);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
void loadpvs(stream *f, int numpvs)
|
|
{
|
|
uint totallen = f->getlil<uint>();
|
|
if(totallen & 0x80000000U)
|
|
{
|
|
totallen &= ~0x80000000U;
|
|
numwaterplanes = f->getlil<uint>();
|
|
loopi(numwaterplanes) waterplanes[i].height = f->getlil<int>();
|
|
}
|
|
int offset = 0;
|
|
loopi(numpvs)
|
|
{
|
|
ushort len = f->getlil<ushort>();
|
|
pvs.add(pvsdata(offset, len));
|
|
offset += len;
|
|
}
|
|
f->read(pvsbuf.reserve(totallen).buf, totallen);
|
|
pvsbuf.advance(totallen);
|
|
viewcells = loadviewcells(f);
|
|
}
|
|
|
|
int getnumviewcells() { return pvs.length(); }
|
|
|