OctaCore/src/engine/octaedit.cc

2945 lines
82 KiB
C++
Raw Normal View History

2020-04-18 02:06:25 +00:00
#include "blend.hh"
2020-04-16 18:28:40 +00:00
#include "engine.hh"
2020-04-15 16:39:17 +00:00
extern int outline;
bool boxoutline = false;
void boxs(int orient, vec o, const vec &s, float size)
{
int d = dimension(orient), dc = dimcoord(orient);
float f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0;
o[D[d]] += dc * s[D[d]] + f;
vec r(0, 0, 0), c(0, 0, 0);
r[R[d]] = s[R[d]];
c[C[d]] = s[C[d]];
vec v1 = o, v2 = vec(o).add(r), v3 = vec(o).add(r).add(c), v4 = vec(o).add(c);
r[R[d]] = 0.5f*size;
c[C[d]] = 0.5f*size;
gle::defvertex();
gle::begin(GL_TRIANGLE_STRIP);
gle::attrib(vec(v1).sub(r).sub(c));
gle::attrib(vec(v1).add(r).add(c));
gle::attrib(vec(v2).add(r).sub(c));
gle::attrib(vec(v2).sub(r).add(c));
gle::attrib(vec(v3).add(r).add(c));
gle::attrib(vec(v3).sub(r).sub(c));
gle::attrib(vec(v4).sub(r).add(c));
gle::attrib(vec(v4).add(r).sub(c));
gle::attrib(vec(v1).sub(r).sub(c));
gle::attrib(vec(v1).add(r).add(c));
xtraverts += gle::end();
}
void boxs(int orient, vec o, const vec &s)
{
int d = dimension(orient), dc = dimcoord(orient);
float f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0;
o[D[d]] += dc * s[D[d]] + f;
gle::defvertex();
gle::begin(GL_LINE_LOOP);
gle::attrib(o); o[R[d]] += s[R[d]];
gle::attrib(o); o[C[d]] += s[C[d]];
gle::attrib(o); o[R[d]] -= s[R[d]];
gle::attrib(o);
xtraverts += gle::end();
}
void boxs3D(const vec &o, vec s, int g)
{
s.mul(g);
loopi(6)
boxs(i, o, s);
}
void boxsgrid(int orient, vec o, vec s, int g)
{
int d = dimension(orient), dc = dimcoord(orient);
float ox = o[R[d]],
oy = o[C[d]],
xs = s[R[d]],
ys = s[C[d]],
f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0;
o[D[d]] += dc * s[D[d]]*g + f;
gle::defvertex();
gle::begin(GL_LINES);
loop(x, xs)
{
o[R[d]] += g;
gle::attrib(o);
o[C[d]] += ys*g;
gle::attrib(o);
o[C[d]] = oy;
}
loop(y, ys)
{
o[C[d]] += g;
o[R[d]] = ox;
gle::attrib(o);
o[R[d]] += xs*g;
gle::attrib(o);
}
xtraverts += gle::end();
}
selinfo sel, lastsel, savedsel;
int orient = 0;
int gridsize = 8;
ivec cor, lastcor;
ivec cur, lastcur;
extern int entediting;
bool editmode = false;
bool havesel = false;
bool hmapsel = false;
int horient = 0;
extern int entmoving;
VARF(dragging, 0, 0, 1,
if(!dragging || cor[0]<0) return;
lastcur = cur;
lastcor = cor;
sel.grid = gridsize;
sel.orient = orient;
);
int moving = 0;
ICOMMAND(moving, "b", (int *n),
{
if(*n >= 0)
{
if(!*n || (moving<=1 && !pointinsel(sel, vec(cur).add(1)))) moving = 0;
else if(!moving) moving = 1;
}
intret(moving);
});
VARF(gridpower, 0, 3, 12,
{
if(dragging) return;
gridsize = 1<<gridpower;
if(gridsize>=worldsize) gridsize = worldsize/2;
cancelsel();
});
VAR(passthroughsel, 0, 0, 1);
VAR(editing, 1, 0, 0);
VAR(selectcorners, 0, 0, 1);
VARF(hmapedit, 0, 0, 1, horient = sel.orient);
void forcenextundo() { lastsel.orient = -1; }
namespace hmap { void cancel(); }
void cubecancel()
{
havesel = false;
moving = dragging = hmapedit = passthroughsel = 0;
forcenextundo();
hmap::cancel();
}
void cancelsel()
{
cubecancel();
entcancel();
}
void toggleedit(bool force)
{
if(!force)
{
if(!isconnected()) return;
if(player->state!=CS_ALIVE && player->state!=CS_DEAD && player->state!=CS_EDITING) return; // do not allow dead players to edit to avoid state confusion
if(!game::allowedittoggle()) return; // not in most multiplayer modes
}
if(!(editmode = !editmode))
{
player->state = player->editstate;
player->o.z -= player->eyeheight; // entinmap wants feet pos
entinmap(player); // find spawn closest to current floating pos
}
else
{
game::resetgamestate();
player->editstate = player->state;
player->state = CS_EDITING;
}
cancelsel();
stoppaintblendmap();
keyrepeat(editmode, KR_EDITMODE);
editing = entediting = editmode;
if(!force) game::edittoggled(editmode);
execident("edittoggled");
}
VARP(editinview, 0, 1, 1);
bool noedit(bool view, bool msg)
{
if(!editmode) { if(msg) conoutf(CON_ERROR, "operation only allowed in edit mode"); return true; }
if(view || haveselent()) return false;
vec o(sel.o), s(sel.s);
s.mul(sel.grid / 2.0f);
o.add(s);
float r = max(s.x, s.y, s.z);
bool viewable = (isvisiblesphere(r, o) != VFC_NOT_VISIBLE);
if(viewable || !editinview) return false;
if(msg) conoutf(CON_ERROR, "selection not in view");
return true;
}
void reorient()
{
sel.cx = 0;
sel.cy = 0;
sel.cxs = sel.s[R[dimension(orient)]]*2;
sel.cys = sel.s[C[dimension(orient)]]*2;
sel.orient = orient;
}
void selextend()
{
if(noedit(true)) return;
loopi(3)
{
if(cur[i]<sel.o[i])
{
sel.s[i] += (sel.o[i]-cur[i])/sel.grid;
sel.o[i] = cur[i];
}
else if(cur[i]>=sel.o[i]+sel.s[i]*sel.grid)
{
sel.s[i] = (cur[i]-sel.o[i])/sel.grid+1;
}
}
}
ICOMMAND(edittoggle, "", (), toggleedit(false));
COMMAND(entcancel, "");
COMMAND(cubecancel, "");
COMMAND(cancelsel, "");
COMMAND(reorient, "");
COMMAND(selextend, "");
ICOMMAND(selmoved, "", (), { if(noedit(true)) return; intret(sel.o != savedsel.o ? 1 : 0); });
ICOMMAND(selsave, "", (), { if(noedit(true)) return; savedsel = sel; });
ICOMMAND(selrestore, "", (), { if(noedit(true)) return; sel = savedsel; });
ICOMMAND(selswap, "", (), { if(noedit(true)) return; swap(sel, savedsel); });
ICOMMAND(getselpos, "", (),
{
if(noedit(true)) return;
defformatstring(pos, "%s %s %s", floatstr(sel.o.x), floatstr(sel.o.y), floatstr(sel.o.z));
result(pos);
});
void setselpos(int *x, int *y, int *z)
{
if(noedit(moving!=0)) return;
havesel = true;
sel.o = ivec(*x, *y, *z).mask(~(gridsize-1));
}
COMMAND(setselpos, "iii");
void movesel(int *dir, int *dim)
{
if(noedit(moving!=0)) return;
if(*dim < 0 || *dim > 2) return;
sel.o[*dim] += *dir * sel.grid;
}
COMMAND(movesel, "ii");
///////// selection support /////////////
cube &blockcube(int x, int y, int z, const block3 &b, int rgrid) // looks up a world cube, based on coordinates mapped by the block
{
int dim = dimension(b.orient), dc = dimcoord(b.orient);
ivec s(dim, x*b.grid, y*b.grid, dc*(b.s[dim]-1)*b.grid);
s.add(b.o);
if(dc) s[dim] -= z*b.grid; else s[dim] += z*b.grid;
return lookupcube(s, rgrid);
}
#define loopxy(b) loop(y,(b).s[C[dimension((b).orient)]]) loop(x,(b).s[R[dimension((b).orient)]])
#define loopxyz(b, r, f) { loop(z,(b).s[D[dimension((b).orient)]]) loopxy((b)) { cube &c = blockcube(x,y,z,b,r); f; } }
#define loopselxyz(f) { if(local) makeundo(); loopxyz(sel, sel.grid, f); changed(sel); }
#define selcube(x, y, z) blockcube(x, y, z, sel, sel.grid)
////////////// cursor ///////////////
int selchildcount = 0, selchildmat = -1;
ICOMMAND(havesel, "", (), intret(havesel ? selchildcount : 0));
ICOMMAND(selchildcount, "", (), { if(selchildcount < 0) result(tempformatstring("1/%d", -selchildcount)); else intret(selchildcount); });
ICOMMAND(selchildmat, "s", (char *prefix), { if(selchildmat > 0) result(getmaterialdesc(selchildmat, prefix)); });
void countselchild(cube *c, const ivec &cor, int size)
{
ivec ss = ivec(sel.s).mul(sel.grid);
loopoctaboxsize(cor, size, sel.o, ss)
{
ivec o(i, cor, size);
if(c[i].children) countselchild(c[i].children, o, size/2);
else
{
selchildcount++;
if(c[i].material != MAT_AIR && selchildmat != MAT_AIR)
{
if(selchildmat < 0) selchildmat = c[i].material;
else if(selchildmat != c[i].material) selchildmat = MAT_AIR;
}
}
}
}
void normalizelookupcube(const ivec &o)
{
if(lusize>gridsize)
{
lu.x += (o.x-lu.x)/gridsize*gridsize;
lu.y += (o.y-lu.y)/gridsize*gridsize;
lu.z += (o.z-lu.z)/gridsize*gridsize;
}
else if(gridsize>lusize)
{
lu.x &= ~(gridsize-1);
lu.y &= ~(gridsize-1);
lu.z &= ~(gridsize-1);
}
lusize = gridsize;
}
void updateselection()
{
sel.o.x = min(lastcur.x, cur.x);
sel.o.y = min(lastcur.y, cur.y);
sel.o.z = min(lastcur.z, cur.z);
sel.s.x = abs(lastcur.x-cur.x)/sel.grid+1;
sel.s.y = abs(lastcur.y-cur.y)/sel.grid+1;
sel.s.z = abs(lastcur.z-cur.z)/sel.grid+1;
}
bool editmoveplane(const vec &o, const vec &ray, int d, float off, vec &handle, vec &dest, bool first)
{
plane pl(d, off);
float dist = 0.0f;
if(!pl.rayintersect(player->o, ray, dist))
return false;
dest = vec(ray).mul(dist).add(player->o);
if(first) handle = vec(dest).sub(o);
dest.sub(handle);
return true;
}
namespace hmap { inline bool isheightmap(int orient, int d, bool empty, cube *c); }
extern void entdrag(const vec &ray);
extern bool hoveringonent(int ent, int orient);
extern void renderentselection(const vec &o, const vec &ray, bool entmoving);
extern float rayent(const vec &o, const vec &ray, float radius, int mode, int size, int &orient, int &ent);
VAR(gridlookup, 0, 0, 1);
VAR(passthroughcube, 0, 1, 1);
void rendereditcursor()
{
int d = dimension(sel.orient),
od = dimension(orient),
odc = dimcoord(orient);
bool hidecursor = UI::hascursor() || blendpaintmode, hovering = false;
hmapsel = false;
if(moving)
{
static vec dest, handle;
if(editmoveplane(vec(sel.o), camdir, od, sel.o[D[od]]+odc*sel.grid*sel.s[D[od]], handle, dest, moving==1))
{
if(moving==1)
{
dest.add(handle);
handle = vec(ivec(handle).mask(~(sel.grid-1)));
dest.sub(handle);
moving = 2;
}
ivec o = ivec(dest).mask(~(sel.grid-1));
sel.o[R[od]] = o[R[od]];
sel.o[C[od]] = o[C[od]];
}
}
else if(entmoving)
{
entdrag(camdir);
}
else
{
ivec w;
float sdist = 0, wdist = 0, t;
int entorient = 0, ent = -1;
wdist = rayent(player->o, camdir, 1e16f,
(editmode && showmat ? RAY_EDITMAT : 0) // select cubes first
| (!dragging && entediting ? RAY_ENTS : 0)
| RAY_SKIPFIRST
| (passthroughcube==1 ? RAY_PASS : 0), gridsize, entorient, ent);
if((havesel || dragging) && !passthroughsel && !hmapedit) // now try selecting the selection
if(rayboxintersect(vec(sel.o), vec(sel.s).mul(sel.grid), player->o, camdir, sdist, orient))
{ // and choose the nearest of the two
if(sdist < wdist)
{
wdist = sdist;
ent = -1;
}
}
if((hovering = hoveringonent(hidecursor ? -1 : ent, entorient)))
{
if(!havesel)
{
selchildcount = 0;
selchildmat = -1;
sel.s = ivec(0, 0, 0);
}
}
else
{
vec w = vec(camdir).mul(wdist+0.05f).add(player->o);
if(!insideworld(w))
{
loopi(3) wdist = min(wdist, ((camdir[i] > 0 ? worldsize : 0) - player->o[i]) / camdir[i]);
w = vec(camdir).mul(wdist-0.05f).add(player->o);
if(!insideworld(w))
{
wdist = 0;
loopi(3) w[i] = clamp(player->o[i], 0.0f, float(worldsize));
}
}
cube *c = &lookupcube(ivec(w));
if(gridlookup && !dragging && !moving && !havesel && hmapedit!=1) gridsize = lusize;
int mag = lusize / gridsize;
normalizelookupcube(ivec(w));
if(sdist == 0 || sdist > wdist) rayboxintersect(vec(lu), vec(gridsize), player->o, camdir, t=0, orient); // just getting orient
cur = lu;
cor = ivec(vec(w).mul(2).div(gridsize));
od = dimension(orient);
d = dimension(sel.orient);
if(hmapedit==1 && dimcoord(horient) == (camdir[dimension(horient)]<0))
{
hmapsel = hmap::isheightmap(horient, dimension(horient), false, c);
if(hmapsel)
od = dimension(orient = horient);
}
if(dragging)
{
updateselection();
sel.cx = min(cor[R[d]], lastcor[R[d]]);
sel.cy = min(cor[C[d]], lastcor[C[d]]);
sel.cxs = max(cor[R[d]], lastcor[R[d]]);
sel.cys = max(cor[C[d]], lastcor[C[d]]);
if(!selectcorners)
{
sel.cx &= ~1;
sel.cy &= ~1;
sel.cxs &= ~1;
sel.cys &= ~1;
sel.cxs -= sel.cx-2;
sel.cys -= sel.cy-2;
}
else
{
sel.cxs -= sel.cx-1;
sel.cys -= sel.cy-1;
}
sel.cx &= 1;
sel.cy &= 1;
havesel = true;
}
else if(!havesel)
{
sel.o = lu;
sel.s.x = sel.s.y = sel.s.z = 1;
sel.cx = sel.cy = 0;
sel.cxs = sel.cys = 2;
sel.grid = gridsize;
sel.orient = orient;
d = od;
}
sel.corner = (cor[R[d]]-(lu[R[d]]*2)/gridsize)+(cor[C[d]]-(lu[C[d]]*2)/gridsize)*2;
selchildcount = 0;
selchildmat = -1;
countselchild(worldroot, ivec(0, 0, 0), worldsize/2);
if(mag>=1 && selchildcount==1)
{
selchildmat = c->material;
if(mag>1) selchildcount = -mag;
}
}
}
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
// cursors
ldrnotextureshader->set();
renderentselection(player->o, camdir, entmoving!=0);
boxoutline = outline!=0;
enablepolygonoffset(GL_POLYGON_OFFSET_LINE);
if(!moving && !hovering && !hidecursor)
{
if(hmapedit==1)
gle::colorub(0, hmapsel ? 255 : 40, 0);
else
gle::colorub(120,120,120);
boxs(orient, vec(lu), vec(lusize));
}
// selections
if(havesel || moving)
{
d = dimension(sel.orient);
gle::colorub(50,50,50); // grid
boxsgrid(sel.orient, vec(sel.o), vec(sel.s), sel.grid);
gle::colorub(200,0,0); // 0 reference
boxs3D(vec(sel.o).sub(0.5f*min(gridsize*0.25f, 2.0f)), vec(min(gridsize*0.25f, 2.0f)), 1);
gle::colorub(200,200,200);// 2D selection box
vec co(sel.o.v), cs(sel.s.v);
co[R[d]] += 0.5f*(sel.cx*gridsize);
co[C[d]] += 0.5f*(sel.cy*gridsize);
cs[R[d]] = 0.5f*(sel.cxs*gridsize);
cs[C[d]] = 0.5f*(sel.cys*gridsize);
cs[D[d]] *= gridsize;
boxs(sel.orient, co, cs);
if(hmapedit==1) // 3D selection box
gle::colorub(0,120,0);
else
gle::colorub(0,0,120);
boxs3D(vec(sel.o), vec(sel.s), sel.grid);
}
disablepolygonoffset(GL_POLYGON_OFFSET_LINE);
boxoutline = false;
glDisable(GL_BLEND);
glEnable(GL_CULL_FACE);
}
void tryedit()
{
extern int hidehud;
if(!editmode || hidehud || mainmenu) return;
if(blendpaintmode) trypaintblendmap();
}
//////////// ready changes to vertex arrays ////////////
static bool haschanged = false;
void readychanges(const ivec &bbmin, const ivec &bbmax, cube *c, const ivec &cor, int size)
{
loopoctabox(cor, size, bbmin, bbmax)
{
ivec o(i, cor, size);
if(c[i].ext)
{
if(c[i].ext->va) // removes va s so that octarender will recreate
{
int hasmerges = c[i].ext->va->hasmerges;
destroyva(c[i].ext->va);
c[i].ext->va = NULL;
if(hasmerges) invalidatemerges(c[i], o, size, true);
}
freeoctaentities(c[i]);
c[i].ext->tjoints = -1;
}
if(c[i].children)
{
if(size<=1)
{
solidfaces(c[i]);
discardchildren(c[i], true);
brightencube(c[i]);
}
else readychanges(bbmin, bbmax, c[i].children, o, size/2);
}
else brightencube(c[i]);
}
}
void commitchanges(bool force)
{
if(!force && !haschanged) return;
haschanged = false;
int oldlen = valist.length();
resetclipplanes();
entitiesinoctanodes();
inbetweenframes = false;
octarender();
inbetweenframes = true;
setupmaterials(oldlen);
clearshadowcache();
updatevabbs();
}
void changed(const ivec &bbmin, const ivec &bbmax, bool commit)
{
readychanges(bbmin, bbmax, worldroot, ivec(0, 0, 0), worldsize/2);
haschanged = true;
if(commit) commitchanges();
}
void changed(const block3 &sel, bool commit)
{
if(sel.s.iszero()) return;
readychanges(ivec(sel.o).sub(1), ivec(sel.s).mul(sel.grid).add(sel.o).add(1), worldroot, ivec(0, 0, 0), worldsize/2);
haschanged = true;
if(commit) commitchanges();
}
//////////// copy and undo /////////////
static inline void copycube(const cube &src, cube &dst)
{
dst = src;
dst.visible = 0;
dst.merged = 0;
dst.ext = NULL; // src cube is responsible for va destruction
if(src.children)
{
dst.children = newcubes(F_EMPTY);
loopi(8) copycube(src.children[i], dst.children[i]);
}
}
static inline void pastecube(const cube &src, cube &dst)
{
discardchildren(dst);
copycube(src, dst);
}
void blockcopy(const block3 &s, int rgrid, block3 *b)
{
*b = s;
cube *q = b->c();
loopxyz(s, rgrid, copycube(c, *q++));
}
block3 *blockcopy(const block3 &s, int rgrid)
{
int bsize = sizeof(block3)+sizeof(cube)*s.size();
if(bsize <= 0 || bsize > (100<<20)) return NULL;
block3 *b = (block3 *)new (false) uchar[bsize];
if(b) blockcopy(s, rgrid, b);
return b;
}
void freeblock(block3 *b, bool alloced = true)
{
cube *q = b->c();
loopi(b->size()) discardchildren(*q++);
if(alloced) delete[] b;
}
void selgridmap(const selinfo &sel, uchar *g) // generates a map of the cube sizes at each grid point
{
loopxyz(sel, -sel.grid, (*g++ = bitscan(lusize), (void)c));
}
void freeundo(undoblock *u)
{
if(!u->numents) freeblock(u->block(), false);
delete[] (uchar *)u;
}
void pasteundoblock(block3 *b, uchar *g)
{
cube *s = b->c();
loopxyz(*b, 1<<min(int(*g++), worldscale-1), pastecube(*s++, c));
}
void pasteundo(undoblock *u)
{
if(u->numents) pasteundoents(u);
else pasteundoblock(u->block(), u->gridmap());
}
static inline int undosize(undoblock *u)
{
if(u->numents) return u->numents*sizeof(undoent);
else
{
block3 *b = u->block();
cube *q = b->c();
int size = b->size(), total = size;
loopj(size) total += familysize(*q++)*sizeof(cube);
return total;
}
}
struct undolist
{
undoblock *first, *last;
undolist() : first(NULL), last(NULL) {}
bool empty() { return !first; }
void add(undoblock *u)
{
u->next = NULL;
u->prev = last;
if(!first) first = last = u;
else
{
last->next = u;
last = u;
}
}
undoblock *popfirst()
{
undoblock *u = first;
first = first->next;
if(first) first->prev = NULL;
else last = NULL;
return u;
}
undoblock *poplast()
{
undoblock *u = last;
last = last->prev;
if(last) last->next = NULL;
else first = NULL;
return u;
}
};
undolist undos, redos;
VARP(undomegs, 0, 5, 100); // bounded by n megs
int totalundos = 0;
void pruneundos(int maxremain) // bound memory
{
while(totalundos > maxremain && !undos.empty())
{
undoblock *u = undos.popfirst();
totalundos -= u->size;
freeundo(u);
}
//conoutf(CON_DEBUG, "undo: %d of %d(%%%d)", totalundos, undomegs<<20, totalundos*100/(undomegs<<20));
while(!redos.empty())
{
undoblock *u = redos.popfirst();
totalundos -= u->size;
freeundo(u);
}
}
void clearundos() { pruneundos(0); }
COMMAND(clearundos, "");
undoblock *newundocube(const selinfo &s)
{
int ssize = s.size(),
selgridsize = ssize,
blocksize = sizeof(block3)+ssize*sizeof(cube);
if(blocksize <= 0 || blocksize > (undomegs<<20)) return NULL;
undoblock *u = (undoblock *)new (false) uchar[sizeof(undoblock) + blocksize + selgridsize];
if(!u) return NULL;
u->numents = 0;
block3 *b = u->block();
blockcopy(s, -s.grid, b);
uchar *g = u->gridmap();
selgridmap(s, g);
return u;
}
void addundo(undoblock *u)
{
u->size = undosize(u);
u->timestamp = totalmillis;
undos.add(u);
totalundos += u->size;
pruneundos(undomegs<<20);
}
VARP(nompedit, 0, 1, 1);
void makeundo(selinfo &s)
{
undoblock *u = newundocube(s);
if(u) addundo(u);
}
void makeundo() // stores state of selected cubes before editing
{
if(lastsel==sel || sel.s.iszero()) return;
lastsel=sel;
makeundo(sel);
}
static inline int countblock(cube *c, int n = 8)
{
int r = 0;
loopi(n) if(c[i].children) r += countblock(c[i].children); else ++r;
return r;
}
static int countblock(block3 *b) { return countblock(b->c(), b->size()); }
void swapundo(undolist &a, undolist &b, int op)
{
if(noedit()) return;
if(a.empty()) { conoutf(CON_WARN, "nothing more to %s", op == EDIT_REDO ? "redo" : "undo"); return; }
int ts = a.last->timestamp;
if(multiplayer(false))
{
int n = 0, ops = 0;
for(undoblock *u = a.last; u && ts==u->timestamp; u = u->prev)
{
++ops;
n += u->numents ? u->numents : countblock(u->block());
if(ops > 10 || n > 2500)
{
conoutf(CON_WARN, "undo too big for multiplayer");
if(nompedit) { multiplayer(); return; }
op = -1;
break;
}
}
}
selinfo l = sel;
while(!a.empty() && ts==a.last->timestamp)
{
if(op >= 0) game::edittrigger(sel, op);
undoblock *u = a.poplast(), *r;
if(u->numents) r = copyundoents(u);
else
{
block3 *ub = u->block();
l.o = ub->o;
l.s = ub->s;
l.grid = ub->grid;
l.orient = ub->orient;
r = newundocube(l);
}
if(r)
{
r->size = u->size;
r->timestamp = totalmillis;
b.add(r);
}
pasteundo(u);
if(!u->numents) changed(*u->block(), false);
freeundo(u);
}
commitchanges();
if(!hmapsel)
{
sel = l;
reorient();
}
forcenextundo();
}
void editundo() { swapundo(undos, redos, EDIT_UNDO); }
void editredo() { swapundo(redos, undos, EDIT_REDO); }
// guard against subdivision
#define protectsel(f) { undoblock *_u = newundocube(sel); f; if(_u) { pasteundo(_u); freeundo(_u); } }
vector<editinfo *> editinfos;
editinfo *localedit = NULL;
template<class B>
static void packcube(cube &c, B &buf)
{
if(c.children)
{
buf.put(0xFF);
loopi(8) packcube(c.children[i], buf);
}
else
{
cube data = c;
lilswap(data.texture, 6);
buf.put(c.material&0xFF);
buf.put(c.material>>8);
buf.put(data.edges, sizeof(data.edges));
buf.put((uchar *)data.texture, sizeof(data.texture));
}
}
template<class B>
static bool packblock(block3 &b, B &buf)
{
if(b.size() <= 0 || b.size() > (1<<20)) return false;
block3 hdr = b;
lilswap(hdr.o.v, 3);
lilswap(hdr.s.v, 3);
lilswap(&hdr.grid, 1);
lilswap(&hdr.orient, 1);
buf.put((const uchar *)&hdr, sizeof(hdr));
cube *c = b.c();
loopi(b.size()) packcube(c[i], buf);
return true;
}
struct vslothdr
{
ushort index;
ushort slot;
};
static void packvslots(cube &c, vector<uchar> &buf, vector<ushort> &used)
{
if(c.children)
{
loopi(8) packvslots(c.children[i], buf, used);
}
else loopi(6)
{
ushort index = c.texture[i];
if(vslots.inrange(index) && vslots[index]->changed && used.find(index) < 0)
{
used.add(index);
VSlot &vs = *vslots[index];
vslothdr &hdr = *(vslothdr *)buf.pad(sizeof(vslothdr));
hdr.index = index;
hdr.slot = vs.slot->index;
lilswap(&hdr.index, 2);
packvslot(buf, vs);
}
}
}
static void packvslots(block3 &b, vector<uchar> &buf)
{
vector<ushort> used;
cube *c = b.c();
loopi(b.size()) packvslots(c[i], buf, used);
memset(buf.pad(sizeof(vslothdr)), 0, sizeof(vslothdr));
}
template<class B>
static void unpackcube(cube &c, B &buf)
{
int mat = buf.get();
if(mat == 0xFF)
{
c.children = newcubes(F_EMPTY);
loopi(8) unpackcube(c.children[i], buf);
}
else
{
c.material = mat | (buf.get()<<8);
buf.get(c.edges, sizeof(c.edges));
buf.get((uchar *)c.texture, sizeof(c.texture));
lilswap(c.texture, 6);
}
}
template<class B>
static bool unpackblock(block3 *&b, B &buf)
{
if(b) { freeblock(b); b = NULL; }
block3 hdr;
if(buf.get((uchar *)&hdr, sizeof(hdr)) < int(sizeof(hdr))) return false;
lilswap(hdr.o.v, 3);
lilswap(hdr.s.v, 3);
lilswap(&hdr.grid, 1);
lilswap(&hdr.orient, 1);
if(hdr.size() > (1<<20) || hdr.grid <= 0 || hdr.grid > (1<<12)) return false;
b = (block3 *)new (false) uchar[sizeof(block3)+hdr.size()*sizeof(cube)];
if(!b) return false;
*b = hdr;
cube *c = b->c();
memset(c, 0, b->size()*sizeof(cube));
loopi(b->size()) unpackcube(c[i], buf);
return true;
}
struct vslotmap
{
int index;
VSlot *vslot;
vslotmap() {}
vslotmap(int index, VSlot *vslot) : index(index), vslot(vslot) {}
};
static vector<vslotmap> unpackingvslots;
static void unpackvslots(cube &c, ucharbuf &buf)
{
if(c.children)
{
loopi(8) unpackvslots(c.children[i], buf);
}
else loopi(6)
{
ushort tex = c.texture[i];
loopvj(unpackingvslots) if(unpackingvslots[j].index == tex) { c.texture[i] = unpackingvslots[j].vslot->index; break; }
}
}
static void unpackvslots(block3 &b, ucharbuf &buf)
{
while(buf.remaining() >= int(sizeof(vslothdr)))
{
vslothdr &hdr = *(vslothdr *)buf.pad(sizeof(vslothdr));
lilswap(&hdr.index, 2);
if(!hdr.index) break;
VSlot &vs = *lookupslot(hdr.slot, false).variants;
VSlot ds;
if(!unpackvslot(buf, ds, false)) break;
if(vs.index < 0 || vs.index == DEFAULT_SKY) continue;
VSlot *edit = editvslot(vs, ds);
unpackingvslots.add(vslotmap(hdr.index, edit ? edit : &vs));
}
cube *c = b.c();
loopi(b.size()) unpackvslots(c[i], buf);
unpackingvslots.setsize(0);
}
static bool compresseditinfo(const uchar *inbuf, int inlen, uchar *&outbuf, int &outlen)
{
uLongf len = compressBound(inlen);
if(len > (1<<20)) return false;
outbuf = new (false) uchar[len];
if(!outbuf || compress2((Bytef *)outbuf, &len, (const Bytef *)inbuf, inlen, Z_BEST_COMPRESSION) != Z_OK || len > (1<<16))
{
delete[] outbuf;
outbuf = NULL;
return false;
}
outlen = len;
return true;
}
static bool uncompresseditinfo(const uchar *inbuf, int inlen, uchar *&outbuf, int &outlen)
{
if(compressBound(outlen) > (1<<20)) return false;
uLongf len = outlen;
outbuf = new (false) uchar[len];
if(!outbuf || uncompress((Bytef *)outbuf, &len, (const Bytef *)inbuf, inlen) != Z_OK)
{
delete[] outbuf;
outbuf = NULL;
return false;
}
outlen = len;
return true;
}
bool packeditinfo(editinfo *e, int &inlen, uchar *&outbuf, int &outlen)
{
vector<uchar> buf;