2986 lines
83 KiB
C++
2986 lines
83 KiB
C++
#include "octaedit.hh"
|
|
|
|
#include <new>
|
|
#include <algorithm>
|
|
|
|
#include <zlib.h>
|
|
|
|
#include <sauerlib/endian.hh>
|
|
|
|
#include <shared/command.hh>
|
|
#include <shared/glemu.hh>
|
|
#include <shared/igame.hh>
|
|
#include <shared/gzstream.hh>
|
|
|
|
#include "blend.hh"
|
|
#include "command.hh"
|
|
#include "console.hh" /* conoutf */
|
|
#include "light.hh"
|
|
#include "main.hh"
|
|
#include "material.hh"
|
|
#include "octa.hh"
|
|
#include "octarender.hh"
|
|
#include "physics.hh"
|
|
#include "pvs.hh"
|
|
#include "rendergl.hh"
|
|
#include "renderlights.hh"
|
|
#include "renderva.hh"
|
|
#include "texture.hh"
|
|
#include "world.hh"
|
|
|
|
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);
|
|
}
|
|
|
|
static 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; // FIXME: extern elsewhere
|
|
static selinfo lastsel, savedsel;
|
|
|
|
static int orient = 0;
|
|
static int gridsize = 8;
|
|
static ivec cor, lastcor;
|
|
static ivec cur, lastcur;
|
|
|
|
extern int entediting;
|
|
bool editmode = false;
|
|
bool havesel = false;
|
|
static bool hmapsel = false;
|
|
static 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;
|
|
);
|
|
|
|
static 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);
|
|
|
|
static void forcenextundo() { lastsel.orient = -1; }
|
|
|
|
namespace hmap { static void cancel(); }
|
|
|
|
static 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;
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static 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);
|
|
});
|
|
|
|
static 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");
|
|
|
|
static 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 /////////////
|
|
|
|
static 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; // octa.hh
|
|
|
|
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)); });
|
|
|
|
static 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static 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;
|
|
}
|
|
|
|
static 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 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] = std::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;
|
|
|
|
static 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 = nullptr;
|
|
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 = nullptr; // 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);
|
|
}
|
|
|
|
static void blockcopy(const block3 &s, int rgrid, block3 *b)
|
|
{
|
|
*b = s;
|
|
cube *q = b->c();
|
|
loopxyz(s, rgrid, copycube(c, *q++));
|
|
}
|
|
|
|
static block3 *blockcopy(const block3 &s, int rgrid)
|
|
{
|
|
int bsize = sizeof(block3)+sizeof(cube)*s.size();
|
|
if(bsize <= 0 || bsize > (100<<20)) return nullptr;
|
|
try {
|
|
auto *b = (block3 *)new uchar[bsize];
|
|
blockcopy(s, rgrid, b);
|
|
return b;
|
|
} catch (...) {}
|
|
return nullptr;
|
|
}
|
|
|
|
static void freeblock(block3 *b, bool alloced = true)
|
|
{
|
|
cube *q = b->c();
|
|
loopi(b->size()) discardchildren(*q++);
|
|
if(alloced) delete[] b;
|
|
}
|
|
|
|
static 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));
|
|
}
|
|
|
|
static void freeundo(undoblock *u)
|
|
{
|
|
if(!u->numents) freeblock(u->block(), false);
|
|
delete[] (uchar *)u;
|
|
}
|
|
|
|
static void pasteundoblock(block3 *b, uchar *g)
|
|
{
|
|
cube *s = b->c();
|
|
loopxyz(*b, 1<<min(int(*g++), worldscale-1), pastecube(*s++, c));
|
|
}
|
|
|
|
static 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(nullptr), last(nullptr) {}
|
|
|
|
bool empty() { return !first; }
|
|
|
|
void add(undoblock *u)
|
|
{
|
|
u->next = nullptr;
|
|
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 = nullptr;
|
|
else last = nullptr;
|
|
return u;
|
|
}
|
|
|
|
undoblock *poplast()
|
|
{
|
|
undoblock *u = last;
|
|
last = last->prev;
|
|
if(last) last->next = nullptr;
|
|
else first = nullptr;
|
|
return u;
|
|
}
|
|
};
|
|
|
|
static undolist undos, redos;
|
|
VARP(undomegs, 0, 5, 100); // bounded by n megs
|
|
static 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);
|
|
}
|
|
}
|
|
|
|
static void clearundos() { pruneundos(0); }
|
|
|
|
COMMAND(clearundos, "");
|
|
|
|
static undoblock *newundocube(const selinfo &s)
|
|
{
|
|
int ssize = s.size(),
|
|
selgridsize = ssize,
|
|
blocksize = sizeof(block3)+ssize*sizeof(cube);
|
|
if(blocksize <= 0 || blocksize > (undomegs<<20)) return nullptr;
|
|
undoblock *u = nullptr;
|
|
try {
|
|
u = (undoblock *)new uchar[sizeof(undoblock) + blocksize + selgridsize];
|
|
} catch (...) {
|
|
return nullptr;
|
|
}
|
|
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);
|
|
|
|
static void makeundo(selinfo &s)
|
|
{
|
|
undoblock *u = newundocube(s);
|
|
if(u) addundo(u);
|
|
}
|
|
|
|
static 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()); }
|
|
|
|
static 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();
|
|
}
|
|
|
|
static void editundo() { swapundo(undos, redos, EDIT_UNDO); }
|
|
static void editredo() { swapundo(redos, undos, EDIT_REDO); }
|
|
|
|
// guard against subdivision
|
|
#define protectsel(f) { undoblock *_u = newundocube(sel); f; if(_u) { pasteundo(_u); freeundo(_u); } }
|
|
|
|
static vector<editinfo *> editinfos;
|
|
editinfo *localedit = nullptr;
|
|
|
|
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 = nullptr; }
|
|
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;
|
|
try {
|
|
b = (block3 *)new uchar[sizeof(block3)+hdr.size()*sizeof(cube)];
|
|
} catch (...) {
|
|
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;
|
|
try {
|
|
outbuf = new uchar[len];
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
if(compress2((Bytef *)outbuf, &len, (const Bytef *)inbuf, inlen, Z_BEST_COMPRESSION) != Z_OK || len > (1<<16))
|
|
{
|
|
delete[] outbuf;
|
|
outbuf = nullptr;
|
|
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;
|
|
try {
|
|
outbuf = new uchar[len];
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
if(uncompress((Bytef *)outbuf, &len, (const Bytef *)inbuf, inlen) != Z_OK)
|
|
{
|
|
delete[] outbuf;
|
|
outbuf = nullptr;
|
|
return false;
|
|
}
|
|
outlen = len;
|
|
return true;
|
|
}
|
|
|
|
bool packeditinfo(editinfo *e, int &inlen, uchar *&outbuf, int &outlen)
|
|
{
|
|
vector<uchar> buf;
|
|
if(!e || !e->copy || !packblock(*e->copy, buf)) return false;
|
|
packvslots(*e->copy, buf);
|
|
inlen = buf.length();
|
|
return compresseditinfo(buf.getbuf(), buf.length(), outbuf, outlen);
|
|
}
|
|
|
|
bool unpackeditinfo(editinfo *&e, const uchar *inbuf, int inlen, int outlen)
|
|
{
|
|
if(e && e->copy) { freeblock(e->copy); e->copy = nullptr; }
|
|
uchar *outbuf = nullptr;
|
|
if(!uncompresseditinfo(inbuf, inlen, outbuf, outlen)) return false;
|
|
ucharbuf buf(outbuf, outlen);
|
|
if(!e) e = editinfos.add(new editinfo);
|
|
if(!unpackblock(e->copy, buf))
|
|
{
|
|
delete[] outbuf;
|
|
return false;
|
|
}
|
|
unpackvslots(*e->copy, buf);
|
|
delete[] outbuf;
|
|
return true;
|
|
}
|
|
|
|
void freeeditinfo(editinfo *&e)
|
|
{
|
|
if(!e) return;
|
|
editinfos.removeobj(e);
|
|
if(e->copy) freeblock(e->copy);
|
|
delete e;
|
|
e = nullptr;
|
|
}
|
|
|
|
static bool packundo(undoblock *u, int &inlen, uchar *&outbuf, int &outlen)
|
|
{
|
|
vector<uchar> buf;
|
|
buf.reserve(512);
|
|
*(ushort *)buf.pad(2) = lilswap(ushort(u->numents));
|
|
if(u->numents)
|
|
{
|
|
undoent *ue = u->ents();
|
|
loopi(u->numents)
|
|
{
|
|
*(ushort *)buf.pad(2) = lilswap(ushort(ue[i].i));
|
|
entity &e = *(entity *)buf.pad(sizeof(entity));
|
|
e = ue[i].e;
|
|
lilswap(&e.o.x, 3);
|
|
lilswap(&e.attr1, 5);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
block3 &b = *u->block();
|
|
if(!packblock(b, buf)) return false;
|
|
buf.put(u->gridmap(), b.size());
|
|
packvslots(b, buf);
|
|
}
|
|
inlen = buf.length();
|
|
return compresseditinfo(buf.getbuf(), buf.length(), outbuf, outlen);
|
|
}
|
|
|
|
bool unpackundo(const uchar *inbuf, int inlen, int outlen)
|
|
{
|
|
uchar *outbuf = nullptr;
|
|
if(!uncompresseditinfo(inbuf, inlen, outbuf, outlen)) return false;
|
|
ucharbuf buf(outbuf, outlen);
|
|
if(buf.remaining() < 2)
|
|
{
|
|
delete[] outbuf;
|
|
return false;
|
|
}
|
|
int numents = lilswap(*(const ushort *)buf.pad(2));
|
|
if(numents)
|
|
{
|
|
if(buf.remaining() < numents*int(2 + sizeof(entity)))
|
|
{
|
|
delete[] outbuf;
|
|
return false;
|
|
}
|
|
loopi(numents)
|
|
{
|
|
int idx = lilswap(*(const ushort *)buf.pad(2));
|
|
entity &e = *(entity *)buf.pad(sizeof(entity));
|
|
lilswap(&e.o.x, 3);
|
|
lilswap(&e.attr1, 5);
|
|
pasteundoent(idx, e);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
block3 *b = nullptr;
|
|
if(!unpackblock(b, buf) || b->grid >= worldsize || buf.remaining() < b->size())
|
|
{
|
|
freeblock(b);
|
|
delete[] outbuf;
|
|
return false;
|
|
}
|
|
uchar *g = buf.pad(b->size());
|
|
unpackvslots(*b, buf);
|
|
pasteundoblock(b, g);
|
|
changed(*b, false);
|
|
freeblock(b);
|
|
}
|
|
delete[] outbuf;
|
|
commitchanges();
|
|
return true;
|
|
}
|
|
|
|
bool packundo(int op, int &inlen, uchar *&outbuf, int &outlen)
|
|
{
|
|
switch(op)
|
|
{
|
|
case EDIT_UNDO: return !undos.empty() && packundo(undos.last, inlen, outbuf, outlen);
|
|
case EDIT_REDO: return !redos.empty() && packundo(redos.last, inlen, outbuf, outlen);
|
|
default: return false;
|
|
}
|
|
}
|
|
|
|
struct prefabheader
|
|
{
|
|
char magic[4];
|
|
int version;
|
|
};
|
|
|
|
struct prefab : editinfo
|
|
{
|
|
char *name;
|
|
GLuint ebo, vbo;
|
|
int numtris, numverts;
|
|
|
|
prefab() : name(nullptr), ebo(0), vbo(0), numtris(0), numverts(0) {}
|
|
~prefab() { DELETEA(name); if(copy) freeblock(copy); }
|
|
|
|
void cleanup()
|
|
{
|
|
if(ebo) { glDeleteBuffers_(1, &ebo); ebo = 0; }
|
|
if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; }
|
|
numtris = numverts = 0;
|
|
}
|
|
};
|
|
|
|
static hashnameset<prefab> prefabs;
|
|
|
|
void cleanupprefabs()
|
|
{
|
|
enumerate(prefabs, prefab, p, p.cleanup());
|
|
}
|
|
|
|
static void delprefab(char *name)
|
|
{
|
|
prefab *p = prefabs.access(name);
|
|
if(p)
|
|
{
|
|
p->cleanup();
|
|
prefabs.remove(name);
|
|
conoutf("deleted prefab %s", name);
|
|
}
|
|
}
|
|
COMMAND(delprefab, "s");
|
|
|
|
static void saveprefab(char *name)
|
|
{
|
|
if(!name[0] || noedit(true) || (nompedit && multiplayer())) return;
|
|
prefab *b = prefabs.access(name);
|
|
if(!b)
|
|
{
|
|
b = &prefabs[name];
|
|
b->name = newstring(name);
|
|
}
|
|
if(b->copy) freeblock(b->copy);
|
|
protectsel(b->copy = blockcopy(block3(sel), sel.grid));
|
|
changed(sel);
|
|
defformatstring(filename, "media/prefab/%s.obr", name);
|
|
path(filename);
|
|
stream *f = opengzfile(filename, "wb");
|
|
if(!f) { conoutf(CON_ERROR, "could not write prefab to %s", filename); return; }
|
|
prefabheader hdr;
|
|
memcpy(hdr.magic, "OEBR", 4);
|
|
hdr.version = 0;
|
|
lilswap(&hdr.version, 1);
|
|
f->write(&hdr, sizeof(hdr));
|
|
streambuf<uchar> s(f);
|
|
if(!packblock(*b->copy, s)) { delete f; conoutf(CON_ERROR, "could not pack prefab %s", filename); return; }
|
|
delete f;
|
|
conoutf("wrote prefab file %s", filename);
|
|
}
|
|
COMMAND(saveprefab, "s");
|
|
|
|
static void pasteblock(block3 &b, selinfo &sel, bool local)
|
|
{
|
|
sel.s = b.s;
|
|
int o = sel.orient;
|
|
sel.orient = b.orient;
|
|
cube *s = b.c();
|
|
loopselxyz(if(!isempty(*s) || s->children || s->material != MAT_AIR) pastecube(*s, c); s++); // 'transparent'. old opaque by 'delcube; paste'
|
|
sel.orient = o;
|
|
}
|
|
|
|
static prefab *loadprefab(const char *name, bool msg = true)
|
|
{
|
|
prefab *b = prefabs.access(name);
|
|
if(b) return b;
|
|
|
|
defformatstring(filename, "media/prefab/%s.obr", name);
|
|
path(filename);
|
|
stream *f = opengzfile(filename, "rb");
|
|
if(!f) { if(msg) conoutf(CON_ERROR, "could not read prefab %s", filename); return nullptr; }
|
|
prefabheader hdr;
|
|
if(f->read(&hdr, sizeof(hdr)) != sizeof(prefabheader) || memcmp(hdr.magic, "OEBR", 4)) { delete f; if(msg) conoutf(CON_ERROR, "prefab %s has malformatted header", filename); return nullptr; }
|
|
lilswap(&hdr.version, 1);
|
|
if(hdr.version != 0) { delete f; if(msg) conoutf(CON_ERROR, "prefab %s uses unsupported version", filename); return nullptr; }
|
|
streambuf<uchar> s(f);
|
|
block3 *copy = nullptr;
|
|
if(!unpackblock(copy, s)) { delete f; if(msg) conoutf(CON_ERROR, "could not unpack prefab %s", filename); return nullptr; }
|
|
delete f;
|
|
|
|
b = &prefabs[name];
|
|
b->name = newstring(name);
|
|
b->copy = copy;
|
|
|
|
return b;
|
|
}
|
|
|
|
static void pasteprefab(char *name)
|
|
{
|
|
if(!name[0] || noedit() || (nompedit && multiplayer())) return;
|
|
prefab *b = loadprefab(name, true);
|
|
if(b) pasteblock(*b->copy, sel, true);
|
|
}
|
|
COMMAND(pasteprefab, "s");
|
|
|
|
struct prefabmesh
|
|
{
|
|
struct vertex { vec pos; bvec4 norm; };
|
|
|
|
static const int SIZE = 1<<9;
|
|
int table[SIZE];
|
|
vector<vertex> verts;
|
|
vector<int> chain;
|
|
vector<ushort> tris;
|
|
|
|
prefabmesh() { memset(table, -1, sizeof(table)); }
|
|
|
|
int addvert(const vertex &v)
|
|
{
|
|
uint h = hthash(v.pos)&(SIZE-1);
|
|
for(int i = table[h]; i>=0; i = chain[i])
|
|
{
|
|
const vertex &c = verts[i];
|
|
if(c.pos==v.pos && c.norm==v.norm) return i;
|
|
}
|
|
if(verts.length() >= USHRT_MAX) return -1;
|
|
verts.add(v);
|
|
chain.add(table[h]);
|
|
return table[h] = verts.length()-1;
|
|
}
|
|
|
|
int addvert(const vec &pos, const bvec &norm)
|
|
{
|
|
vertex vtx;
|
|
vtx.pos = pos;
|
|
vtx.norm = norm;
|
|
return addvert(vtx);
|
|
}
|
|
|
|
void setup(prefab &p)
|
|
{
|
|
if(tris.empty()) return;
|
|
|
|
p.cleanup();
|
|
|
|
loopv(verts) verts[i].norm.flip();
|
|
if(!p.vbo) glGenBuffers_(1, &p.vbo);
|
|
gle::bindvbo(p.vbo);
|
|
glBufferData_(GL_ARRAY_BUFFER, verts.length()*sizeof(vertex), verts.getbuf(), GL_STATIC_DRAW);
|
|
gle::clearvbo();
|
|
p.numverts = verts.length();
|
|
|
|
if(!p.ebo) glGenBuffers_(1, &p.ebo);
|
|
gle::bindebo(p.ebo);
|
|
glBufferData_(GL_ELEMENT_ARRAY_BUFFER, tris.length()*sizeof(ushort), tris.getbuf(), GL_STATIC_DRAW);
|
|
gle::clearebo();
|
|
p.numtris = tris.length()/3;
|
|
}
|
|
|
|
};
|
|
|
|
static void genprefabmesh(prefabmesh &r, cube &c, const ivec &co, int size)
|
|
{
|
|
if(c.children)
|
|
{
|
|
neighbourstack[++neighbourdepth] = c.children;
|
|
loopi(8)
|
|
{
|
|
ivec o(i, co, size/2);
|
|
genprefabmesh(r, c.children[i], o, size/2);
|
|
}
|
|
--neighbourdepth;
|
|
}
|
|
else if(!isempty(c))
|
|
{
|
|
int vis;
|
|
loopi(6) if((vis = visibletris(c, i, co, size)))
|
|
{
|
|
ivec v[4];
|
|
genfaceverts(c, i, v);
|
|
int convex = 0;
|
|
if(!flataxisface(c, i)) convex = faceconvexity(v);
|
|
int order = vis&4 || convex < 0 ? 1 : 0, numverts = 0;
|
|
vec vo(co), pos[4], norm[4];
|
|
pos[numverts++] = vec(v[order]).mul(size/8.0f).add(vo);
|
|
if(vis&1) pos[numverts++] = vec(v[order+1]).mul(size/8.0f).add(vo);
|
|
pos[numverts++] = vec(v[order+2]).mul(size/8.0f).add(vo);
|
|
if(vis&2) pos[numverts++] = vec(v[(order+3)&3]).mul(size/8.0f).add(vo);
|
|
guessnormals(pos, numverts, norm);
|
|
int index[4];
|
|
loopj(numverts) index[j] = r.addvert(pos[j], bvec(norm[j]));
|
|
loopj(numverts-2) if(index[0]!=index[j+1] && index[j+1]!=index[j+2] && index[j+2]!=index[0])
|
|
{
|
|
r.tris.add(index[0]);
|
|
r.tris.add(index[j+1]);
|
|
r.tris.add(index[j+2]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void genprefabmesh(prefab &p)
|
|
{
|
|
block3 b = *p.copy;
|
|
b.o = ivec(0, 0, 0);
|
|
|
|
cube *oldworldroot = worldroot;
|
|
int oldworldscale = worldscale, oldworldsize = worldsize;
|
|
|
|
worldroot = newcubes();
|
|
worldscale = 1;
|
|
worldsize = 2;
|
|
while(worldsize < max(max(b.s.x, b.s.y), b.s.z)*b.grid)
|
|
{
|
|
worldscale++;
|
|
worldsize *= 2;
|
|
}
|
|
|
|
cube *s = p.copy->c();
|
|
loopxyz(b, b.grid, if(!isempty(*s) || s->children) pastecube(*s, c); s++);
|
|
|
|
prefabmesh r;
|
|
neighbourstack[++neighbourdepth] = worldroot;
|
|
loopi(8) genprefabmesh(r, worldroot[i], ivec(i, ivec(0, 0, 0), worldsize/2), worldsize/2);
|
|
--neighbourdepth;
|
|
r.setup(p);
|
|
|
|
freeocta(worldroot);
|
|
|
|
worldroot = oldworldroot;
|
|
worldscale = oldworldscale;
|
|
worldsize = oldworldsize;
|
|
|
|
useshaderbyname("prefab");
|
|
}
|
|
|
|
extern bvec outlinecolour;
|
|
|
|
static void renderprefab(prefab &p, const vec &o, float yaw, float pitch, float roll, float size, const vec &color)
|
|
{
|
|
if(!p.numtris)
|
|
{
|
|
genprefabmesh(p);
|
|
if(!p.numtris) return;
|
|
}
|
|
|
|
block3 &b = *p.copy;
|
|
|
|
matrix4 m;
|
|
m.identity();
|
|
m.settranslation(o);
|
|
if(yaw) m.rotate_around_z(yaw*RAD);
|
|
if(pitch) m.rotate_around_x(pitch*RAD);
|
|
if(roll) m.rotate_around_y(-roll*RAD);
|
|
matrix3 w(m);
|
|
if(size > 0 && size != 1) m.scale(size);
|
|
m.translate(vec(b.s).mul(-b.grid*0.5f));
|
|
|
|
gle::bindvbo(p.vbo);
|
|
gle::bindebo(p.ebo);
|
|
gle::enablevertex();
|
|
gle::enablenormal();
|
|
prefabmesh::vertex *v = (prefabmesh::vertex *)0;
|
|
gle::vertexpointer(sizeof(prefabmesh::vertex), v->pos.v);
|
|
gle::normalpointer(sizeof(prefabmesh::vertex), v->norm.v, GL_BYTE);
|
|
|
|
matrix4 pm;
|
|
pm.mul(camprojmatrix, m);
|
|
GLOBALPARAM(prefabmatrix, pm);
|
|
GLOBALPARAM(prefabworld, w);
|
|
SETSHADER(prefab);
|
|
gle::color(vec(color).mul(ldrscale));
|
|
glDrawRangeElements_(GL_TRIANGLES, 0, p.numverts-1, p.numtris*3, GL_UNSIGNED_SHORT, (ushort *)0);
|
|
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
|
|
enablepolygonoffset(GL_POLYGON_OFFSET_LINE);
|
|
|
|
pm.mul(camprojmatrix, m);
|
|
GLOBALPARAM(prefabmatrix, pm);
|
|
SETSHADER(prefab);
|
|
gle::color((outlinecolour).tocolor().mul(ldrscale));
|
|
glDrawRangeElements_(GL_TRIANGLES, 0, p.numverts-1, p.numtris*3, GL_UNSIGNED_SHORT, (ushort *)0);
|
|
|
|
disablepolygonoffset(GL_POLYGON_OFFSET_LINE);
|
|
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
|
|
|
|
gle::disablevertex();
|
|
gle::disablenormal();
|
|
gle::clearebo();
|
|
gle::clearvbo();
|
|
}
|
|
|
|
void renderprefab(const char *name, const vec &o, float yaw, float pitch, float roll, float size, const vec &color)
|
|
{
|
|
prefab *p = loadprefab(name, false);
|
|
if(p) renderprefab(*p, o, yaw, pitch, roll, size, color);
|
|
}
|
|
|
|
void previewprefab(const char *name, const vec &color)
|
|
{
|
|
prefab *p = loadprefab(name, false);
|
|
if(p)
|
|
{
|
|
block3 &b = *p->copy;
|
|
float yaw;
|
|
vec o = calcmodelpreviewpos(vec(b.s).mul(b.grid*0.5f), yaw);
|
|
renderprefab(*p, o, yaw, 0, 0, 1, color);
|
|
}
|
|
}
|
|
|
|
void mpcopy(editinfo *&e, selinfo &sel, bool local)
|
|
{
|
|
if(local) game::edittrigger(sel, EDIT_COPY);
|
|
if(e==nullptr) e = editinfos.add(new editinfo);
|
|
if(e->copy) freeblock(e->copy);
|
|
e->copy = nullptr;
|
|
protectsel(e->copy = blockcopy(block3(sel), sel.grid));
|
|
changed(sel);
|
|
}
|
|
|
|
void mppaste(editinfo *&e, selinfo &sel, bool local)
|
|
{
|
|
if(e==nullptr) return;
|
|
if(local) game::edittrigger(sel, EDIT_PASTE);
|
|
if(e->copy) pasteblock(*e->copy, sel, local);
|
|
}
|
|
|
|
static void copy()
|
|
{
|
|
if(noedit(true)) return;
|
|
mpcopy(localedit, sel, true);
|
|
}
|
|
|
|
static void pastehilite()
|
|
{
|
|
if(!localedit) return;
|
|
sel.s = localedit->copy->s;
|
|
reorient();
|
|
havesel = true;
|
|
}
|
|
|
|
static void paste()
|
|
{
|
|
if(noedit(true)) return;
|
|
mppaste(localedit, sel, true);
|
|
}
|
|
|
|
COMMAND(copy, "");
|
|
COMMAND(pastehilite, "");
|
|
COMMAND(paste, "");
|
|
COMMANDN(undo, editundo, "");
|
|
COMMANDN(redo, editredo, "");
|
|
|
|
static vector<int *> editingvslots;
|
|
struct vslotref
|
|
{
|
|
vslotref(int &index) { editingvslots.add(&index); }
|
|
~vslotref() { editingvslots.pop(); }
|
|
};
|
|
#define editingvslot(...) vslotref vslotrefs[] = { __VA_ARGS__ }; (void)vslotrefs;
|
|
|
|
void compacteditvslots()
|
|
{
|
|
loopv(editingvslots) if(*editingvslots[i]) compactvslot(*editingvslots[i]);
|
|
loopv(unpackingvslots) compactvslot(*unpackingvslots[i].vslot);
|
|
loopv(editinfos)
|
|
{
|
|
editinfo *e = editinfos[i];
|
|
compactvslots(e->copy->c(), e->copy->size());
|
|
}
|
|
for(undoblock *u = undos.first; u; u = u->next)
|
|
if(!u->numents)
|
|
compactvslots(u->block()->c(), u->block()->size());
|
|
for(undoblock *u = redos.first; u; u = u->next)
|
|
if(!u->numents)
|
|
compactvslots(u->block()->c(), u->block()->size());
|
|
}
|
|
|
|
///////////// height maps ////////////////
|
|
|
|
namespace hmap
|
|
{
|
|
static vector<int> textures;
|
|
|
|
static void cancel() { textures.setsize(0); }
|
|
|
|
ICOMMAND(hmapcancel, "", (), cancel());
|
|
ICOMMAND(hmapselect, "", (),
|
|
int t = lookupcube(cur).texture[orient];
|
|
int i = textures.find(t);
|
|
if(i<0)
|
|
textures.add(t);
|
|
else
|
|
textures.remove(i);
|
|
);
|
|
|
|
inline bool isheightmap(int o, int d, bool empty, cube *c)
|
|
{
|
|
return havesel ||
|
|
(empty && isempty(*c)) ||
|
|
textures.empty() ||
|
|
textures.find(c->texture[o]) >= 0;
|
|
}
|
|
|
|
#define MAXBRUSH 64
|
|
#define MAXBRUSHC 63
|
|
#define MAXBRUSH2 32
|
|
int brush[MAXBRUSH][MAXBRUSH];
|
|
VARN(hbrushx, brushx, 0, MAXBRUSH2, MAXBRUSH);
|
|
VARN(hbrushy, brushy, 0, MAXBRUSH2, MAXBRUSH);
|
|
bool paintbrush = 0;
|
|
int brushmaxx = 0, brushminx = MAXBRUSH;
|
|
int brushmaxy = 0, brushminy = MAXBRUSH;
|
|
|
|
static void clearhbrush()
|
|
{
|
|
memset(brush, 0, sizeof brush);
|
|
brushmaxx = brushmaxy = 0;
|
|
brushminx = brushminy = MAXBRUSH;
|
|
paintbrush = false;
|
|
}
|
|
COMMAND(clearhbrush, "");
|
|
|
|
static void hbrushvert(int *x, int *y, int *v)
|
|
{
|
|
*x += MAXBRUSH2 - brushx + 1; // +1 for automatic padding
|
|
*y += MAXBRUSH2 - brushy + 1;
|
|
if(*x<0 || *y<0 || *x>=MAXBRUSH || *y>=MAXBRUSH) return;
|
|
brush[*x][*y] = std::clamp(*v, 0, 8);
|
|
paintbrush = paintbrush || (brush[*x][*y] > 0);
|
|
brushmaxx = min(MAXBRUSH-1, max(brushmaxx, *x+1));
|
|
brushmaxy = min(MAXBRUSH-1, max(brushmaxy, *y+1));
|
|
brushminx = max(0, min(brushminx, *x-1));
|
|
brushminy = max(0, min(brushminy, *y-1));
|
|
}
|
|
COMMAND(hbrushvert, "iii");
|
|
|
|
#define PAINTED 1
|
|
#define NOTHMAP 2
|
|
#define MAPPED 16
|
|
static uchar flags[MAXBRUSH][MAXBRUSH];
|
|
static cube *cmap[MAXBRUSHC][MAXBRUSHC][4];
|
|
static int mapz[MAXBRUSHC][MAXBRUSHC];
|
|
static int map [MAXBRUSH][MAXBRUSH];
|
|
|
|
static selinfo changes;
|
|
static bool selecting;
|
|
static int d, dc, dr, dcr, biasup, br, hws, fg;
|
|
static int gx, gy, gz, mx, my, mz, nx, ny, nz, bmx, bmy, bnx, bny;
|
|
static uint fs;
|
|
static selinfo hundo;
|
|
|
|
static cube *getcube(ivec t, int f)
|
|
{
|
|
t[d] += dcr*f*gridsize;
|
|
if(t[d] > nz || t[d] < mz) return nullptr;
|
|
cube *c = &lookupcube(t, gridsize);
|
|
if(c->children) forcemip(*c, false);
|
|
discardchildren(*c, true);
|
|
if(!isheightmap(sel.orient, d, true, c)) return nullptr;
|
|
if (t.x < changes.o.x) changes.o.x = t.x;
|
|
else if(t.x > changes.s.x) changes.s.x = t.x;
|
|
if (t.y < changes.o.y) changes.o.y = t.y;
|
|
else if(t.y > changes.s.y) changes.s.y = t.y;
|
|
if (t.z < changes.o.z) changes.o.z = t.z;
|
|
else if(t.z > changes.s.z) changes.s.z = t.z;
|
|
return c;
|
|
}
|
|
|
|
static uint getface(cube *c, int d)
|
|
{
|
|
return 0x0f0f0f0f & ((dc ? c->faces[d] : 0x88888888 - c->faces[d]) >> fs);
|
|
}
|
|
|
|
static void pushside(cube &c, int d, int x, int y, int z)
|
|
{
|
|
ivec a;
|
|
getcubevector(c, d, x, y, z, a);
|
|
a[R[d]] = 8 - a[R[d]];
|
|
setcubevector(c, d, x, y, z, a);
|
|
}
|
|
|
|
static void addpoint(int x, int y, int z, int v)
|
|
{
|
|
if(!(flags[x][y] & MAPPED))
|
|
map[x][y] = v + (z*8);
|
|
flags[x][y] |= MAPPED;
|
|
}
|
|
|
|
static void select(int x, int y, int z)
|
|
{
|
|
if((NOTHMAP & flags[x][y]) || (PAINTED & flags[x][y])) return;
|
|
ivec t(d, x+gx, y+gy, dc ? z : hws-z);
|
|
t.shl(gridpower);
|
|
|
|
// selections may damage; must makeundo before
|
|
hundo.o = t;
|
|
hundo.o[D[d]] -= dcr*gridsize*2;
|
|
makeundo(hundo);
|
|
|
|
cube **c = cmap[x][y];
|
|
loopk(4) c[k] = nullptr;
|
|
c[1] = getcube(t, 0);
|
|
if(!c[1] || !isempty(*c[1]))
|
|
{ // try up
|
|
c[2] = c[1];
|
|
c[1] = getcube(t, 1);
|
|
if(!c[1] || isempty(*c[1])) { c[0] = c[1]; c[1] = c[2]; c[2] = nullptr; }
|
|
else { z++; t[d]+=fg; }
|
|
}
|
|
else // drop down
|
|
{
|
|
z--;
|
|
t[d]-= fg;
|
|
c[0] = c[1];
|
|
c[1] = getcube(t, 0);
|
|
}
|
|
|
|
if(!c[1] || isempty(*c[1])) { flags[x][y] |= NOTHMAP; return; }
|
|
|
|
flags[x][y] |= PAINTED;
|
|
mapz [x][y] = z;
|
|
|
|
if(!c[0]) c[0] = getcube(t, 1);
|
|
if(!c[2]) c[2] = getcube(t, -1);
|
|
c[3] = getcube(t, -2);
|
|
c[2] = !c[2] || isempty(*c[2]) ? nullptr : c[2];
|
|
c[3] = !c[3] || isempty(*c[3]) ? nullptr : c[3];
|
|
|
|
uint face = getface(c[1], d);
|
|
if(face == 0x08080808 && (!c[0] || !isempty(*c[0]))) { flags[x][y] |= NOTHMAP; return; }
|
|
if(c[1]->faces[R[d]] == F_SOLID) // was single
|
|
face += 0x08080808;
|
|
else // was pair
|
|
face += c[2] ? getface(c[2], d) : 0x08080808;
|
|
face += 0x08080808; // c[3]
|
|
uchar *f = (uchar*)&face;
|
|
addpoint(x, y, z, f[0]);
|
|
addpoint(x+1, y, z, f[1]);
|
|
addpoint(x, y+1, z, f[2]);
|
|
addpoint(x+1, y+1, z, f[3]);
|
|
|
|
if(selecting) // continue to adjacent cubes
|
|
{
|
|
if(x>bmx) select(x-1, y, z);
|
|
if(x<bnx) select(x+1, y, z);
|
|
if(y>bmy) select(x, y-1, z);
|
|
if(y<bny) select(x, y+1, z);
|
|
}
|
|
}
|
|
|
|
static void ripple(int x, int y, int z, bool force)
|
|
{
|
|
if(force) select(x, y, z);
|
|
if((NOTHMAP & flags[x][y]) || !(PAINTED & flags[x][y])) return;
|
|
|
|
bool changed = false;
|
|
int *o[4], best, par, q = 0;
|
|
loopi(2) loopj(2) o[i+j*2] = &map[x+i][y+j];
|
|
#define pullhmap(I, LT, GT, M, N, A) do { \
|
|
best = I; \
|
|
loopi(4) if(*o[i] LT best) best = *o[q = i] - M; \
|
|
par = (best&(~7)) + N; \
|
|
/* dual layer for extra smoothness */ \
|
|
if(*o[q^3] GT par && !(*o[q^1] LT par || *o[q^2] LT par)) { \
|
|
if(*o[q^3] GT par A 8 || *o[q^1] != par || *o[q^2] != par) { \
|
|
*o[q^3] = (*o[q^3] GT par A 8 ? par A 8 : *o[q^3]); \
|
|
*o[q^1] = *o[q^2] = par; \
|
|
changed = true; \
|
|
} \
|
|
/* single layer */ \
|
|
} else { \
|
|
loopj(4) if(*o[j] GT par) { \
|
|
*o[j] = par; \
|
|
changed = true; \
|
|
} \
|
|
} \
|
|
} while(0)
|
|
|
|
if(biasup)
|
|
pullhmap(0, >, <, 1, 0, -);
|
|
else
|
|
pullhmap(worldsize*8, <, >, 0, 8, +);
|
|
|
|
cube **c = cmap[x][y];
|
|
int e[2][2];
|
|
int notempty = 0;
|
|
|
|
loopk(4) if(c[k])
|
|
{
|
|
loopi(2) loopj(2)
|
|
{
|
|
e[i][j] = min(8, map[x+i][y+j] - (mapz[x][y]+3-k)*8);
|
|
notempty |= e[i][j] > 0;
|
|
}
|
|
if(notempty)
|
|
{
|
|
c[k]->texture[sel.orient] = c[1]->texture[sel.orient];
|
|
solidfaces(*c[k]);
|
|
loopi(2) loopj(2)
|
|
{
|
|
int f = e[i][j];
|
|
if(f<0 || (f==0 && e[1-i][j]==0 && e[i][1-j]==0))
|
|
{
|
|
f=0;
|
|
pushside(*c[k], d, i, j, 0);
|
|
pushside(*c[k], d, i, j, 1);
|
|
}
|
|
edgeset(cubeedge(*c[k], d, i, j), dc, dc ? f : 8-f);
|
|
}
|
|
}
|
|
else
|
|
emptyfaces(*c[k]);
|
|
}
|
|
|
|
if(!changed) return;
|
|
if(x>mx) ripple(x-1, y, mapz[x][y], true);
|
|
if(x<nx) ripple(x+1, y, mapz[x][y], true);
|
|
if(y>my) ripple(x, y-1, mapz[x][y], true);
|
|
if(y<ny) ripple(x, y+1, mapz[x][y], true);
|
|
|
|
#define DIAGONAL_RIPPLE(a,b,exp) if(exp) { \
|
|
if(flags[x a][ y] & PAINTED) \
|
|
ripple(x a, y b, mapz[x a][y], true); \
|
|
else if(flags[x][y b] & PAINTED) \
|
|
ripple(x a, y b, mapz[x][y b], true); \
|
|
}
|
|
|
|
DIAGONAL_RIPPLE(-1, -1, (x>mx && y>my)); // do diagonals because adjacents
|
|
DIAGONAL_RIPPLE(-1, +1, (x>mx && y<ny)); // won't unless changed
|
|
DIAGONAL_RIPPLE(+1, +1, (x<nx && y<ny));
|
|
DIAGONAL_RIPPLE(+1, -1, (x<nx && y>my));
|
|
}
|
|
|
|
#define loopbrush(i) for(int x=bmx; x<=bnx+i; x++) for(int y=bmy; y<=bny+i; y++)
|
|
|
|
static void paint()
|
|
{
|
|
loopbrush(1)
|
|
map[x][y] -= dr * brush[x][y];
|
|
}
|
|
|
|
static void smooth()
|
|
{
|
|
int sum, div;
|
|
loopbrush(-2)
|
|
{
|
|
sum = 0;
|
|
div = 9;
|
|
loopi(3) loopj(3)
|
|
if(flags[x+i][y+j] & MAPPED)
|
|
sum += map[x+i][y+j];
|
|
else div--;
|
|
if(div)
|
|
map[x+1][y+1] = sum / div;
|
|
}
|
|
}
|
|
|
|
static void rippleandset()
|
|
{
|
|
loopbrush(0)
|
|
ripple(x, y, gz, false);
|
|
}
|
|
|
|
static void run(int dir, int mode)
|
|
{
|
|
d = dimension(sel.orient);
|
|
dc = dimcoord(sel.orient);
|
|
dcr= dc ? 1 : -1;
|
|
dr = dir>0 ? 1 : -1;
|
|
br = dir>0 ? 0x08080808 : 0;
|
|
// biasup = mode == dir<0;
|
|
biasup = dir<0;
|
|
bool paintme = paintbrush;
|
|
int cx = (sel.corner&1 ? 0 : -1);
|
|
int cy = (sel.corner&2 ? 0 : -1);
|
|
hws= (worldsize>>gridpower);
|
|
gx = (cur[R[d]] >> gridpower) + cx - MAXBRUSH2;
|
|
gy = (cur[C[d]] >> gridpower) + cy - MAXBRUSH2;
|
|
gz = (cur[D[d]] >> gridpower);
|
|
fs = dc ? 4 : 0;
|
|
fg = dc ? gridsize : -gridsize;
|
|
mx = max(0, -gx); // ripple range
|
|
my = max(0, -gy);
|
|
nx = min(MAXBRUSH-1, hws-gx) - 1;
|
|
ny = min(MAXBRUSH-1, hws-gy) - 1;
|
|
if(havesel)
|
|
{ // selection range
|
|
bmx = mx = max(mx, (sel.o[R[d]]>>gridpower)-gx);
|
|
bmy = my = max(my, (sel.o[C[d]]>>gridpower)-gy);
|
|
bnx = nx = min(nx, (sel.s[R[d]]+(sel.o[R[d]]>>gridpower))-gx-1);
|
|
bny = ny = min(ny, (sel.s[C[d]]+(sel.o[C[d]]>>gridpower))-gy-1);
|
|
}
|
|
if(havesel && mode<0) // -ve means smooth selection
|
|
paintme = false;
|
|
else
|
|
{ // brush range
|
|
bmx = max(mx, brushminx);
|
|
bmy = max(my, brushminy);
|
|
bnx = min(nx, brushmaxx-1);
|
|
bny = min(ny, brushmaxy-1);
|
|
}
|
|
nz = worldsize-gridsize;
|
|
mz = 0;
|
|
hundo.s = ivec(d,1,1,5);
|
|
hundo.orient = sel.orient;
|
|
hundo.grid = gridsize;
|
|
forcenextundo();
|
|
|
|
changes.grid = gridsize;
|
|
changes.s = changes.o = cur;
|
|
memset(map, 0, sizeof map);
|
|
memset(flags, 0, sizeof flags);
|
|
|
|
selecting = true;
|
|
select(std::clamp(MAXBRUSH2-cx, bmx, bnx),
|
|
std::clamp(MAXBRUSH2-cy, bmy, bny),
|
|
dc ? gz : hws - gz);
|
|
selecting = false;
|
|
if(paintme)
|
|
paint();
|
|
else
|
|
smooth();
|
|
rippleandset(); // pull up points to cubify, and set
|
|
changes.s.sub(changes.o).shr(gridpower).add(1);
|
|
changed(changes);
|
|
}
|
|
}
|
|
|
|
static void edithmap(int dir, int mode)
|
|
{
|
|
if((nompedit && multiplayer()) || !hmapsel) return;
|
|
hmap::run(dir, mode);
|
|
}
|
|
|
|
///////////// main cube edit ////////////////
|
|
|
|
inline int bounded(int n) { return n<0 ? 0 : (n>8 ? 8 : n); }
|
|
|
|
static void pushedge(uchar &edge, int dir, int dc)
|
|
{
|
|
int ne = bounded(edgeget(edge, dc)+dir);
|
|
edgeset(edge, dc, ne);
|
|
int oe = edgeget(edge, 1-dc);
|
|
if((dir<0 && dc && oe>ne) || (dir>0 && dc==0 && oe<ne)) edgeset(edge, 1-dc, ne);
|
|
}
|
|
|
|
static void linkedpush(cube &c, int d, int x, int y, int dc, int dir)
|
|
{
|
|
ivec v, p;
|
|
getcubevector(c, d, x, y, dc, v);
|
|
|
|
loopi(2) loopj(2)
|
|
{
|
|
getcubevector(c, d, i, j, dc, p);
|
|
if(v==p)
|
|
pushedge(cubeedge(c, d, i, j), dir, dc);
|
|
}
|
|
}
|
|
|
|
static ushort getmaterial(cube &c)
|
|
{
|
|
if(c.children)
|
|
{
|
|
ushort mat = getmaterial(c.children[7]);
|
|
loopi(7) if(mat != getmaterial(c.children[i])) return MAT_AIR;
|
|
return mat;
|
|
}
|
|
return c.material;
|
|
}
|
|
|
|
VAR(invalidcubeguard, 0, 1, 1);
|
|
|
|
void mpeditface(int dir, int mode, selinfo &sel, bool local)
|
|
{
|
|
if(mode==1 && (sel.cx || sel.cy || sel.cxs&1 || sel.cys&1)) mode = 0;
|
|
int d = dimension(sel.orient);
|
|
int dc = dimcoord(sel.orient);
|
|
int seldir = dc ? -dir : dir;
|
|
|
|
if(local)
|
|
game::edittrigger(sel, EDIT_FACE, dir, mode);
|
|
|
|
if(mode==1)
|
|
{
|
|
int h = sel.o[d]+dc*sel.grid;
|
|
if(((dir>0) == dc && h<=0) || ((dir<0) == dc && h>=worldsize)) return;
|
|
if(dir<0) sel.o[d] += sel.grid * seldir;
|
|
}
|
|
|
|
if(dc) sel.o[d] += sel.us(d)-sel.grid;
|
|
sel.s[d] = 1;
|
|
|
|
loopselxyz(
|
|
if(c.children) solidfaces(c);
|
|
ushort mat = getmaterial(c);
|
|
discardchildren(c, true);
|
|
c.material = mat;
|
|
if(mode==1) // fill command
|
|
{
|
|
if(dir<0)
|
|
{
|
|
solidfaces(c);
|
|
cube &o = blockcube(x, y, 1, sel, -sel.grid);
|
|
loopi(6)
|
|
c.texture[i] = o.children ? DEFAULT_GEOM : o.texture[i];
|
|
}
|
|
else
|
|
emptyfaces(c);
|
|
}
|
|
else
|
|
{
|
|
uint bak = c.faces[d];
|
|
uchar *p = (uchar *)&c.faces[d];
|
|
|
|
if(mode==2)
|
|
linkedpush(c, d, sel.corner&1, sel.corner>>1, dc, seldir); // corner command
|
|
else
|
|
{
|
|
loop(mx,2) loop(my,2) // pull/push edges command
|
|
{
|
|
if(x==0 && mx==0 && sel.cx) continue;
|
|
if(y==0 && my==0 && sel.cy) continue;
|
|
if(x==sel.s[R[d]]-1 && mx==1 && (sel.cx+sel.cxs)&1) continue;
|
|
if(y==sel.s[C[d]]-1 && my==1 && (sel.cy+sel.cys)&1) continue;
|
|
if(p[mx+my*2] != ((uchar *)&bak)[mx+my*2]) continue;
|
|
|
|
linkedpush(c, d, mx, my, dc, seldir);
|
|
}
|
|
}
|
|
|
|
optiface(p, c);
|
|
if(invalidcubeguard==1 && !isvalidcube(c))
|
|
{
|
|
uint newbak = c.faces[d];
|
|
uchar *m = (uchar *)&bak;
|
|
uchar *n = (uchar *)&newbak;
|
|
loopk(4) if(n[k] != m[k]) // tries to find partial edit that is valid
|
|
{
|
|
c.faces[d] = bak;
|
|
c.edges[d*4+k] = n[k];
|
|
if(isvalidcube(c))
|
|
m[k] = n[k];
|
|
}
|
|
c.faces[d] = bak;
|
|
}
|
|
}
|
|
);
|
|
if (mode==1 && dir>0)
|
|
sel.o[d] += sel.grid * seldir;
|
|
}
|
|
|
|
static void editface(int *dir, int *mode)
|
|
{
|
|
if(noedit(moving!=0)) return;
|
|
if(hmapedit!=1)
|
|
mpeditface(*dir, *mode, sel, true);
|
|
else
|
|
edithmap(*dir, *mode);
|
|
}
|
|
|
|
VAR(selectionsurf, 0, 0, 1);
|
|
|
|
static void pushsel(int *dir)
|
|
{
|
|
if(noedit(moving!=0)) return;
|
|
int d = dimension(orient);
|
|
int s = dimcoord(orient) ? -*dir : *dir;
|
|
sel.o[d] += s*sel.grid;
|
|
if(selectionsurf==1)
|
|
{
|
|
player->o[d] += s*sel.grid;
|
|
player->resetinterp();
|
|
}
|
|
}
|
|
|
|
void mpdelcube(selinfo &sel, bool local)
|
|
{
|
|
if(local) game::edittrigger(sel, EDIT_DELCUBE);
|
|
loopselxyz(discardchildren(c, true); emptyfaces(c));
|
|
}
|
|
|
|
static void delcube()
|
|
{
|
|
if(noedit(true)) return;
|
|
mpdelcube(sel, true);
|
|
}
|
|
|
|
COMMAND(pushsel, "i");
|
|
COMMAND(editface, "ii");
|
|
COMMAND(delcube, "");
|
|
|
|
/////////// texture editing //////////////////
|
|
|
|
static int curtexindex = -1, lasttex = 0, lasttexmillis = -1;
|
|
static int texpaneltimer = 0;
|
|
vector<ushort> texmru;
|
|
|
|
static void tofronttex() // maintain most recently used of the texture lists when applying texture
|
|
{
|
|
int c = curtexindex;
|
|
if(texmru.inrange(c))
|
|
{
|
|
texmru.insert(0, texmru.remove(c));
|
|
curtexindex = -1;
|
|
}
|
|
}
|
|
|
|
static selinfo repsel;
|
|
static int reptex = -1;
|
|
|
|
static vector<vslotmap> remappedvslots;
|
|
|
|
static VSlot *remapvslot(int index, bool delta, const VSlot &ds)
|
|
{
|
|
loopv(remappedvslots) if(remappedvslots[i].index == index) return remappedvslots[i].vslot;
|
|
VSlot &vs = lookupvslot(index, false);
|
|
if(vs.index < 0 || vs.index == DEFAULT_SKY) return nullptr;
|
|
VSlot *edit = nullptr;
|
|
if(delta)
|
|
{
|
|
VSlot ms;
|
|
mergevslot(ms, vs, ds);
|
|
edit = ms.changed ? editvslot(vs, ms) : vs.slot->variants;
|
|
}
|
|
else edit = ds.changed ? editvslot(vs, ds) : vs.slot->variants;
|
|
if(!edit) edit = &vs;
|
|
remappedvslots.add(vslotmap(vs.index, edit));
|
|
return edit;
|
|
}
|
|
|
|
static void remapvslots(cube &c, bool delta, const VSlot &ds, int orient, bool &findrep, VSlot *&findedit)
|
|
{
|
|
if(c.children)
|
|
{
|
|
loopi(8) remapvslots(c.children[i], delta, ds, orient, findrep, findedit);
|
|
return;
|
|
}
|
|
static VSlot ms;
|
|
if(orient<0) loopi(6)
|
|
{
|
|
VSlot *edit = remapvslot(c.texture[i], delta, ds);
|
|
if(edit)
|
|
{
|
|
c.texture[i] = edit->index;
|
|
if(!findedit) findedit = edit;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int i = visibleorient(c, orient);
|
|
VSlot *edit = remapvslot(c.texture[i], delta, ds);
|
|
if(edit)
|
|
{
|
|
if(findrep)
|
|
{
|
|
if(reptex < 0) reptex = c.texture[i];
|
|
else if(reptex != c.texture[i]) findrep = false;
|
|
}
|
|
c.texture[i] = edit->index;
|
|
if(!findedit) findedit = edit;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void edittexcube(cube &c, int tex, int orient, bool &findrep)
|
|
{
|
|
if(orient<0) loopi(6) c.texture[i] = tex;
|
|
else
|
|
{
|
|
int i = visibleorient(c, orient);
|
|
if(findrep)
|
|
{
|
|
if(reptex < 0) reptex = c.texture[i];
|
|
else if(reptex != c.texture[i]) findrep = false;
|
|
}
|
|
c.texture[i] = tex;
|
|
}
|
|
if(c.children) loopi(8) edittexcube(c.children[i], tex, orient, findrep);
|
|
}
|
|
|
|
void mpeditvslot(int delta, VSlot &ds, int allfaces, selinfo &sel, bool local)
|
|
{
|
|
if(local)
|
|
{
|
|
game::edittrigger(sel, EDIT_VSLOT, delta, allfaces, 0, &ds);
|
|
if(!(lastsel==sel)) tofronttex();
|
|
if(allfaces || !(repsel == sel)) reptex = -1;
|
|
repsel = sel;
|
|
}
|
|
bool findrep = local && !allfaces && reptex < 0;
|
|
VSlot *findedit = nullptr;
|
|
loopselxyz(remapvslots(c, delta != 0, ds, allfaces ? -1 : sel.orient, findrep, findedit));
|
|
remappedvslots.setsize(0);
|
|
if(local && findedit)
|
|
{
|
|
lasttex = findedit->index;
|
|
lasttexmillis = totalmillis;
|
|
curtexindex = texmru.find(lasttex);
|
|
if(curtexindex < 0)
|
|
{
|
|
curtexindex = texmru.length();
|
|
texmru.add(lasttex);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool mpeditvslot(int delta, int allfaces, selinfo &sel, ucharbuf &buf)
|
|
{
|
|
VSlot ds;
|
|
if(!unpackvslot(buf, ds, delta != 0)) return false;
|
|
editingvslot(ds.layer, ds.detail);
|
|
mpeditvslot(delta, ds, allfaces, sel, false);
|
|
return true;
|
|
}
|
|
|
|
VAR(allfaces, 0, 0, 1);
|
|
VAR(usevdelta, 1, 0, 0);
|
|
|
|
static void vdelta(uint *body)
|
|
{
|
|
if(noedit()) return;
|
|
usevdelta++;
|
|
execute(body);
|
|
usevdelta--;
|
|
}
|
|
COMMAND(vdelta, "e");
|
|
|
|
static void vrotate(int *n)
|
|
{
|
|
if(noedit()) return;
|
|
VSlot ds;
|
|
ds.changed = 1<<VSLOT_ROTATION;
|
|
ds.rotation = usevdelta ? *n : std::clamp(*n, 0, 7);
|
|
mpeditvslot(usevdelta, ds, allfaces, sel, true);
|
|
}
|
|
COMMAND(vrotate, "i");
|
|
ICOMMAND(getvrotate, "i", (int *tex), intret(lookupvslot(*tex, false).rotation));
|
|
|
|
static void voffset(int *x, int *y)
|
|
{
|
|
if(noedit()) return;
|
|
VSlot ds;
|
|
ds.changed = 1<<VSLOT_OFFSET;
|
|
ds.offset = usevdelta ? ivec2(*x, *y) : ivec2(*x, *y).max(0);
|
|
mpeditvslot(usevdelta, ds, allfaces, sel, true);
|
|
}
|
|
COMMAND(voffset, "ii");
|
|
ICOMMAND(getvoffset, "i", (int *tex),
|
|
{
|
|
VSlot &vslot = lookupvslot(*tex, false);
|
|
defformatstring(str, "%d %d", vslot.offset.x, vslot.offset.y);
|
|
result(str);
|
|
});
|
|
|
|
static void vscroll(float *s, float *t)
|
|
{
|
|
if(noedit()) return;
|
|
VSlot ds;
|
|
ds.changed = 1<<VSLOT_SCROLL;
|
|
ds.scroll = vec2(*s/1000.0f, *t/1000.0f);
|
|
mpeditvslot(usevdelta, ds, allfaces, sel, true);
|
|
}
|
|
COMMAND(vscroll, "ff");
|
|
ICOMMAND(getvscroll, "i", (int *tex),
|
|
{
|
|
VSlot &vslot = lookupvslot(*tex, false);
|
|
defformatstring(str, "%s %s", floatstr(vslot.scroll.x), floatstr(vslot.scroll.y));
|
|
result(str);
|
|
});
|
|
|
|
static void vscale(float *scale)
|
|
{
|
|
if(noedit()) return;
|
|
VSlot ds;
|
|
ds.changed = 1<<VSLOT_SCALE;
|
|
ds.scale = *scale <= 0 ? 1 : (usevdelta ? *scale : std::clamp(*scale, 1/8.0f, 8.0f));
|
|
mpeditvslot(usevdelta, ds, allfaces, sel, true);
|
|
}
|
|
COMMAND(vscale, "f");
|
|
ICOMMAND(getvscale, "i", (int *tex), floatret(lookupvslot(*tex, false).scale));
|
|
|
|
static void vlayer(int *n)
|
|
{
|
|
if(noedit()) return;
|
|
VSlot ds;
|
|
ds.changed = 1<<VSLOT_LAYER;
|
|
if(vslots.inrange(*n))
|
|
{
|
|
ds.layer = *n;
|
|
if(vslots[ds.layer]->changed && nompedit && multiplayer()) return;
|
|
}
|
|
editingvslot(ds.layer);
|
|
mpeditvslot(usevdelta, ds, allfaces, sel, true);
|
|
}
|
|
COMMAND(vlayer, "i");
|
|
ICOMMAND(getvlayer, "i", (int *tex), intret(lookupvslot(*tex, false).layer));
|
|
|
|
static void vdetail(int *n)
|
|
{
|
|
if(noedit()) return;
|
|
VSlot ds;
|
|
ds.changed = 1<<VSLOT_DETAIL;
|
|
if(vslots.inrange(*n))
|
|
{
|
|
ds.detail = *n;
|
|
if(vslots[ds.detail]->changed && nompedit && multiplayer()) return;
|
|
}
|
|
editingvslot(ds.detail);
|
|
mpeditvslot(usevdelta, ds, allfaces, sel, true);
|
|
}
|
|
COMMAND(vdetail, "i");
|
|
ICOMMAND(getvdetail, "i", (int *tex), intret(lookupvslot(*tex, false).detail));
|
|
|
|
static void valpha(float *front, float *back)
|
|
{
|
|
if(noedit()) return;
|
|
VSlot ds;
|
|
ds.changed = 1<<VSLOT_ALPHA;
|
|
ds.alphafront = std::clamp(*front, 0.0f, 1.0f);
|
|
ds.alphaback = std::clamp(*back, 0.0f, 1.0f);
|
|
mpeditvslot(usevdelta, ds, allfaces, sel, true);
|
|
}
|
|
COMMAND(valpha, "ff");
|
|
ICOMMAND(getvalpha, "i", (int *tex),
|
|
{
|
|
VSlot &vslot = lookupvslot(*tex, false);
|
|
defformatstring(str, "%s %s", floatstr(vslot.alphafront), floatstr(vslot.alphaback));
|
|
result(str);
|
|
});
|
|
|
|
static void vcolor(float *r, float *g, float *b)
|
|
{
|
|
if(noedit()) return;
|
|
VSlot ds;
|
|
ds.changed = 1<<VSLOT_COLOR;
|
|
ds.colorscale = vec(std::clamp(*r, 0.0f, 2.0f), std::clamp(*g, 0.0f, 2.0f), std::clamp(*b, 0.0f, 2.0f));
|
|
mpeditvslot(usevdelta, ds, allfaces, sel, true);
|
|
}
|
|
COMMAND(vcolor, "fff");
|
|
ICOMMAND(getvcolor, "i", (int *tex),
|
|
{
|
|
VSlot &vslot = lookupvslot(*tex, false);
|
|
defformatstring(str, "%s %s %s", floatstr(vslot.colorscale.r), floatstr(vslot.colorscale.g), floatstr(vslot.colorscale.b));
|
|
result(str);
|
|
});
|
|
|
|
static void vrefract(float *k, float *r, float *g, float *b)
|
|
{
|
|
if(noedit()) return;
|
|
VSlot ds;
|
|
ds.changed = 1<<VSLOT_REFRACT;
|
|
ds.refractscale = std::clamp(*k, 0.0f, 1.0f);
|
|
if(ds.refractscale > 0 && (*r > 0 || *g > 0 || *b > 0))
|
|
ds.refractcolor = vec(std::clamp(*r, 0.0f, 1.0f), std::clamp(*g, 0.0f, 1.0f), std::clamp(*b, 0.0f, 1.0f));
|
|
else
|
|
ds.refractcolor = vec(1, 1, 1);
|
|
mpeditvslot(usevdelta, ds, allfaces, sel, true);
|
|
|
|
}
|
|
COMMAND(vrefract, "ffff");
|
|
ICOMMAND(getvrefract, "i", (int *tex),
|
|
{
|
|
VSlot &vslot = lookupvslot(*tex, false);
|
|
defformatstring(str, "%s %s %s %s", floatstr(vslot.refractscale), floatstr(vslot.refractcolor.r), floatstr(vslot.refractcolor.g), floatstr(vslot.refractcolor.b));
|
|
result(str);
|
|
});
|
|
|
|
static void vreset()
|
|
{
|
|
if(noedit()) return;
|
|
VSlot ds;
|
|
mpeditvslot(usevdelta, ds, allfaces, sel, true);
|
|
}
|
|
COMMAND(vreset, "");
|
|
|
|
static void vshaderparam(const char *name, float *x, float *y, float *z, float *w)
|
|
{
|
|
if(noedit()) return;
|
|
VSlot ds;
|
|
ds.changed = 1<<VSLOT_SHPARAM;
|
|
if(name[0])
|
|
{
|
|
SlotShaderParam p = { getshaderparamname(name), -1, 0, {*x, *y, *z, *w} };
|
|
ds.params.add(p);
|
|
}
|
|
mpeditvslot(usevdelta, ds, allfaces, sel, true);
|
|
}
|
|
COMMAND(vshaderparam, "sfFFf");
|
|
ICOMMAND(getvshaderparam, "is", (int *tex, const char *name),
|
|
{
|
|
VSlot &vslot = lookupvslot(*tex, false);
|
|
loopv(vslot.params)
|
|
{
|
|
SlotShaderParam &p = vslot.params[i];
|
|
if(!strcmp(p.name, name))
|
|
{
|
|
defformatstring(str, "%s %s %s %s", floatstr(p.val[0]), floatstr(p.val[1]), floatstr(p.val[2]), floatstr(p.val[3]));
|
|
result(str);
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
ICOMMAND(getvshaderparamnames, "i", (int *tex),
|
|
{
|
|
VSlot &vslot = lookupvslot(*tex, false);
|
|
vector<char> str;
|
|
loopv(vslot.params)
|
|
{
|
|
SlotShaderParam &p = vslot.params[i];
|
|
if(i) str.put(' ');
|
|
str.put(p.name, strlen(p.name));
|
|
}
|
|
str.add('\0');
|
|
stringret(newstring(str.getbuf(), str.length()-1));
|
|
});
|
|
|
|
void mpedittex(int tex, int allfaces, selinfo &sel, bool local)
|
|
{
|
|
if(local)
|
|
{
|
|
game::edittrigger(sel, EDIT_TEX, tex, allfaces);
|
|
if(allfaces || !(repsel == sel)) reptex = -1;
|
|
repsel = sel;
|
|
}
|
|
bool findrep = local && !allfaces && reptex < 0;
|
|
loopselxyz(edittexcube(c, tex, allfaces ? -1 : sel.orient, findrep));
|
|
}
|
|
|
|
static int unpacktex(int &tex, ucharbuf &buf, bool insert = true)
|
|
{
|
|
if(tex < 0x10000) return true;
|
|
VSlot ds;
|
|
if(!unpackvslot(buf, ds, false)) return false;
|
|
VSlot &vs = *lookupslot(tex & 0xFFFF, false).variants;
|
|
if(vs.index < 0 || vs.index == DEFAULT_SKY) return false;
|
|
VSlot *edit = insert ? editvslot(vs, ds) : findvslot(*vs.slot, vs, ds);
|
|
if(!edit) return false;
|
|
tex = edit->index;
|
|
return true;
|
|
}
|
|
|
|
int shouldpacktex(int index)
|
|
{
|
|
if(vslots.inrange(index))
|
|
{
|
|
VSlot &vs = *vslots[index];
|
|
if(vs.changed) return 0x10000 + vs.slot->index;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
bool mpedittex(int tex, int allfaces, selinfo &sel, ucharbuf &buf)
|
|
{
|
|
if(!unpacktex(tex, buf)) return false;
|
|
mpedittex(tex, allfaces, sel, false);
|
|
return true;
|
|
}
|
|
|
|
static void filltexlist()
|
|
{
|
|
if(texmru.length()!=vslots.length())
|
|
{
|
|
loopvrev(texmru) if(texmru[i]>=vslots.length())
|
|
{
|
|
if(curtexindex > i) curtexindex--;
|
|
else if(curtexindex == i) curtexindex = -1;
|
|
texmru.remove(i);
|
|
}
|
|
loopv(vslots) if(texmru.find(i)<0) texmru.add(i);
|
|
}
|
|
}
|
|
|
|
void compactmruvslots()
|
|
{
|
|
remappedvslots.setsize(0);
|
|
loopvrev(texmru)
|
|
{
|
|
if(vslots.inrange(texmru[i]))
|
|
{
|
|
VSlot &vs = *vslots[texmru[i]];
|
|
if(vs.index >= 0)
|
|
{
|
|
texmru[i] = vs.index;
|
|
continue;
|
|
}
|
|
}
|
|
if(curtexindex > i) curtexindex--;
|
|
else if(curtexindex == i) curtexindex = -1;
|
|
texmru.remove(i);
|
|
}
|
|
if(vslots.inrange(lasttex))
|
|
{
|
|
VSlot &vs = *vslots[lasttex];
|
|
lasttex = vs.index >= 0 ? vs.index : 0;
|
|
}
|
|
else lasttex = 0;
|
|
reptex = vslots.inrange(reptex) ? vslots[reptex]->index : -1;
|
|
}
|
|
|
|
static void edittex(int i, bool save = true)
|
|
{
|
|
lasttex = i;
|
|
lasttexmillis = totalmillis;
|
|
if(save)
|
|
{
|
|
loopvj(texmru) if(texmru[j]==lasttex) { curtexindex = j; break; }
|
|
}
|
|
mpedittex(i, allfaces, sel, true);
|
|
}
|
|
|
|
static void edittex_(int *dir)
|
|
{
|
|
if(noedit()) return;
|
|
filltexlist();
|
|
if(texmru.empty()) return;
|
|
texpaneltimer = 5000;
|
|
if(!(lastsel==sel)) tofronttex();
|
|
curtexindex = std::clamp(curtexindex<0 ? 0 : curtexindex+*dir, 0, texmru.length()-1);
|
|
edittex(texmru[curtexindex], false);
|
|
}
|
|
|
|
static void gettex()
|
|
{
|
|
if(noedit(true)) return;
|
|
filltexlist();
|
|
int tex = -1;
|
|
loopxyz(sel, sel.grid, tex = c.texture[sel.orient]);
|
|
loopv(texmru) if(texmru[i]==tex)
|
|
{
|
|
curtexindex = i;
|
|
tofronttex();
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void getcurtex()
|
|
{
|
|
if(noedit(true)) return;
|
|
filltexlist();
|
|
int index = curtexindex < 0 ? 0 : curtexindex;
|
|
if(!texmru.inrange(index)) return;
|
|
intret(texmru[index]);
|
|
}
|
|
|
|
static void getseltex()
|
|
{
|
|
if(noedit(true)) return;
|
|
cube &c = lookupcube(sel.o, -sel.grid);
|
|
if(c.children || isempty(c)) return;
|
|
intret(c.texture[sel.orient]);
|
|
}
|
|
|
|
static void gettexname(int *tex, int *subslot)
|
|
{
|
|
if(*tex<0) return;
|
|
VSlot &vslot = lookupvslot(*tex, false);
|
|
Slot &slot = *vslot.slot;
|
|
if(!slot.sts.inrange(*subslot)) return;
|
|
result(slot.sts[*subslot].name);
|
|
}
|
|
|
|
static void getslottex(int *idx)
|
|
{
|
|
if(*idx < 0 || !slots.inrange(*idx)) { intret(-1); return; }
|
|
Slot &slot = lookupslot(*idx, false);
|
|
intret(slot.variants->index);
|
|
}
|
|
|
|
COMMANDN(edittex, edittex_, "i");
|
|
ICOMMAND(settex, "i", (int *tex), { if(!vslots.inrange(*tex) || noedit()) return; filltexlist(); edittex(*tex); });
|
|
COMMAND(gettex, "");
|
|
COMMAND(getcurtex, "");
|
|
COMMAND(getseltex, "");
|
|
ICOMMAND(getreptex, "", (), { if(!noedit()) intret(vslots.inrange(reptex) ? reptex : -1); });
|
|
COMMAND(gettexname, "ii");
|
|
ICOMMAND(texmru, "b", (int *idx), { filltexlist(); intret(texmru.inrange(*idx) ? texmru[*idx] : texmru.length()); });
|
|
ICOMMAND(looptexmru, "re", (ident *id, uint *body),
|
|
{
|
|
loopstart(id, stack);
|
|
filltexlist();
|
|
loopv(texmru) { loopiter(id, stack, texmru[i]); execute(body); }
|
|
loopend(id, stack);
|
|
});
|
|
ICOMMAND(numvslots, "", (), intret(vslots.length()));
|
|
ICOMMAND(numslots, "", (), intret(slots.length()));
|
|
COMMAND(getslottex, "i");
|
|
ICOMMAND(texloaded, "i", (int *tex), intret(slots.inrange(*tex) && slots[*tex]->loaded ? 1 : 0));
|
|
|
|
static void replacetexcube(cube &c, int oldtex, int newtex)
|
|
{
|
|
loopi(6) if(c.texture[i] == oldtex) c.texture[i] = newtex;
|
|
if(c.children) loopi(8) replacetexcube(c.children[i], oldtex, newtex);
|
|
}
|
|
|
|
void mpreplacetex(int oldtex, int newtex, bool insel, selinfo &sel, bool local)
|
|
{
|
|
if(local) game::edittrigger(sel, EDIT_REPLACE, oldtex, newtex, insel ? 1 : 0);
|
|
if(insel)
|
|
{
|
|
loopselxyz(replacetexcube(c, oldtex, newtex));
|
|
}
|
|
else
|
|
{
|
|
loopi(8) replacetexcube(worldroot[i], oldtex, newtex);
|
|
}
|
|
allchanged();
|
|
}
|
|
|
|
bool mpreplacetex(int oldtex, int newtex, bool insel, selinfo &sel, ucharbuf &buf)
|
|
{
|
|
if(!unpacktex(oldtex, buf, false)) return false;
|
|
editingvslot(oldtex);
|
|
if(!unpacktex(newtex, buf)) return false;
|
|
mpreplacetex(oldtex, newtex, insel, sel, false);
|
|
return true;
|
|
}
|
|
|
|
static void replace(bool insel)
|
|
{
|
|
if(noedit()) return;
|
|
if(reptex < 0) { conoutf(CON_ERROR, "can only replace after a texture edit"); return; }
|
|
mpreplacetex(reptex, lasttex, insel, sel, true);
|
|
}
|
|
|
|
ICOMMAND(replace, "", (), replace(false));
|
|
ICOMMAND(replacesel, "", (), replace(true));
|
|
|
|
////////// flip and rotate ///////////////
|
|
static inline uint dflip(uint face) { return face==F_EMPTY ? face : 0x88888888 - (((face&0xF0F0F0F0)>>4) | ((face&0x0F0F0F0F)<<4)); }
|
|
static inline uint cflip(uint face) { return ((face&0xFF00FF00)>>8) | ((face&0x00FF00FF)<<8); }
|
|
static inline uint rflip(uint face) { return ((face&0xFFFF0000)>>16)| ((face&0x0000FFFF)<<16); }
|
|
static inline uint mflip(uint face) { return (face&0xFF0000FF) | ((face&0x00FF0000)>>8) | ((face&0x0000FF00)<<8); }
|
|
|
|
static void flipcube(cube &c, int d)
|
|
{
|
|
swap(c.texture[d*2], c.texture[d*2+1]);
|
|
c.faces[D[d]] = dflip(c.faces[D[d]]);
|
|
c.faces[C[d]] = cflip(c.faces[C[d]]);
|
|
c.faces[R[d]] = rflip(c.faces[R[d]]);
|
|
if(c.children)
|
|
{
|
|
loopi(8) if(i&octadim(d)) swap(c.children[i], c.children[i-octadim(d)]);
|
|
loopi(8) flipcube(c.children[i], d);
|
|
}
|
|
}
|
|
|
|
static inline void rotatequad(cube &a, cube &b, cube &c, cube &d)
|
|
{
|
|
cube t = a; a = b; b = c; c = d; d = t;
|
|
}
|
|
|
|
static void rotatecube(cube &c, int d) // rotates cube clockwise. see pics in cvs for help.
|
|
{
|
|
c.faces[D[d]] = cflip(mflip(c.faces[D[d]]));
|
|
c.faces[C[d]] = dflip(mflip(c.faces[C[d]]));
|
|
c.faces[R[d]] = rflip(mflip(c.faces[R[d]]));
|
|
swap(c.faces[R[d]], c.faces[C[d]]);
|
|
|
|
swap(c.texture[2*R[d]], c.texture[2*C[d]+1]);
|
|
swap(c.texture[2*C[d]], c.texture[2*R[d]+1]);
|
|
swap(c.texture[2*C[d]], c.texture[2*C[d]+1]);
|
|
|
|
if(c.children)
|
|
{
|
|
int row = octadim(R[d]);
|
|
int col = octadim(C[d]);
|
|
for(int i=0; i<=octadim(d); i+=octadim(d)) rotatequad
|
|
(
|
|
c.children[i+row],
|
|
c.children[i],
|
|
c.children[i+col],
|
|
c.children[i+col+row]
|
|
);
|
|
loopi(8) rotatecube(c.children[i], d);
|
|
}
|
|
}
|
|
|
|
void mpflip(selinfo &sel, bool local)
|
|
{
|
|
if(local)
|
|
{
|
|
game::edittrigger(sel, EDIT_FLIP);
|
|
makeundo();
|
|
}
|
|
int zs = sel.s[dimension(sel.orient)];
|
|
loopxy(sel)
|
|
{
|
|
loop(z,zs) flipcube(selcube(x, y, z), dimension(sel.orient));
|
|
loop(z,zs/2)
|
|
{
|
|
cube &a = selcube(x, y, z);
|
|
cube &b = selcube(x, y, zs-z-1);
|
|
swap(a, b);
|
|
}
|
|
}
|
|
changed(sel);
|
|
}
|
|
|
|
static void flip()
|
|
{
|
|
if(noedit()) return;
|
|
mpflip(sel, true);
|
|
}
|
|
|
|
void mprotate(int cw, selinfo &sel, bool local)
|
|
{
|
|
if(local) game::edittrigger(sel, EDIT_ROTATE, cw);
|
|
int d = dimension(sel.orient);
|
|
if(!dimcoord(sel.orient)) cw = -cw;
|
|
int m = sel.s[C[d]] < sel.s[R[d]] ? C[d] : R[d];
|
|
int ss = sel.s[m] = max(sel.s[R[d]], sel.s[C[d]]);
|
|
if(local) makeundo();
|
|
loop(z,sel.s[D[d]]) loopi(cw>0 ? 1 : 3)
|
|
{
|
|
loopxy(sel) rotatecube(selcube(x,y,z), d);
|
|
loop(y,ss/2) loop(x,ss-1-y*2) rotatequad
|
|
(
|
|
selcube(ss-1-y, x+y, z),
|
|
selcube(x+y, y, z),
|
|
selcube(y, ss-1-x-y, z),
|
|
selcube(ss-1-x-y, ss-1-y, z)
|
|
);
|
|
}
|
|
changed(sel);
|
|
}
|
|
|
|
static void rotate(int *cw)
|
|
{
|
|
if(noedit()) return;
|
|
mprotate(*cw, sel, true);
|
|
}
|
|
|
|
COMMAND(flip, "");
|
|
COMMAND(rotate, "i");
|
|
|
|
enum { EDITMATF_EMPTY = 0x10000, EDITMATF_NOTEMPTY = 0x20000, EDITMATF_SOLID = 0x30000, EDITMATF_NOTSOLID = 0x40000 };
|
|
static const struct { const char *name; int filter; } editmatfilters[] =
|
|
{
|
|
{ "empty", EDITMATF_EMPTY },
|
|
{ "notempty", EDITMATF_NOTEMPTY },
|
|
{ "solid", EDITMATF_SOLID },
|
|
{ "notsolid", EDITMATF_NOTSOLID }
|
|
};
|
|
|
|
static void setmat(cube &c, ushort mat, ushort matmask, ushort filtermat, ushort filtermask, int filtergeom)
|
|
{
|
|
if(c.children)
|
|
loopi(8) setmat(c.children[i], mat, matmask, filtermat, filtermask, filtergeom);
|
|
else if((c.material&filtermask) == filtermat)
|
|
{
|
|
switch(filtergeom)
|
|
{
|
|
case EDITMATF_EMPTY: if(isempty(c)) break; return;
|
|
case EDITMATF_NOTEMPTY: if(!isempty(c)) break; return;
|
|
case EDITMATF_SOLID: if(isentirelysolid(c)) break; return;
|
|
case EDITMATF_NOTSOLID: if(!isentirelysolid(c)) break; return;
|
|
}
|
|
if(mat!=MAT_AIR)
|
|
{
|
|
c.material &= matmask;
|
|
c.material |= mat;
|
|
}
|
|
else c.material = MAT_AIR;
|
|
}
|
|
}
|
|
|
|
void mpeditmat(int matid, int filter, selinfo &sel, bool local)
|
|
{
|
|
if(local) game::edittrigger(sel, EDIT_MAT, matid, filter);
|
|
|
|
ushort filtermat = 0, filtermask = 0, matmask;
|
|
int filtergeom = 0;
|
|
if(filter >= 0)
|
|
{
|
|
filtermat = filter&0xFF;
|
|
filtermask = filtermat&(MATF_VOLUME|MATF_INDEX) ? MATF_VOLUME|MATF_INDEX : (filtermat&MATF_CLIP ? MATF_CLIP : filtermat);
|
|
filtergeom = filter&~0xFF;
|
|
}
|
|
if(matid < 0)
|
|
{
|
|
matid = 0;
|
|
matmask = filtermask;
|
|
if(isclipped(filtermat&MATF_VOLUME)) matmask &= ~MATF_CLIP;
|
|
if(isdeadly(filtermat&MATF_VOLUME)) matmask &= ~MAT_DEATH;
|
|
}
|
|
else
|
|
{
|
|
matmask = matid&(MATF_VOLUME|MATF_INDEX) ? 0 : (matid&MATF_CLIP ? ~MATF_CLIP : ~matid);
|
|
if(isclipped(matid&MATF_VOLUME)) matid |= MAT_CLIP;
|
|
if(isdeadly(matid&MATF_VOLUME)) matid |= MAT_DEATH;
|
|
}
|
|
loopselxyz(setmat(c, matid, matmask, filtermat, filtermask, filtergeom));
|
|
}
|
|
|
|
static void editmat(char *name, char *filtername)
|
|
{
|
|
if(noedit()) return;
|
|
int filter = -1;
|
|
if(filtername[0])
|
|
{
|
|
loopi(sizeof(editmatfilters)/sizeof(editmatfilters[0])) if(!strcmp(editmatfilters[i].name, filtername)) { filter = editmatfilters[i].filter; break; }
|
|
if(filter < 0) filter = findmaterial(filtername);
|
|
if(filter < 0)
|
|
{
|
|
conoutf(CON_ERROR, "unknown material \"%s\"", filtername);
|
|
return;
|
|
}
|
|
}
|
|
int id = -1;
|
|
if(name[0] || filter < 0)
|
|
{
|
|
id = findmaterial(name);
|
|
if(id<0) { conoutf(CON_ERROR, "unknown material \"%s\"", name); return; }
|
|
}
|
|
mpeditmat(id, filter, sel, true);
|
|
}
|
|
|
|
COMMAND(editmat, "ss");
|
|
|
|
void rendertexturepanel(int w, int h)
|
|
{
|
|
if((texpaneltimer -= curtime)>0 && editmode)
|
|
{
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
|
|
pushhudmatrix();
|
|
hudmatrix.scale(h/1800.0f, h/1800.0f, 1);
|
|
flushhudmatrix(false);
|
|
SETSHADER(hudrgb);
|
|
|
|
int y = 50, gap = 10;
|
|
|
|
gle::defvertex(2);
|
|
gle::deftexcoord0();
|
|
|
|
loopi(7)
|
|
{
|
|
int s = (i == 3 ? 285 : 220), ti = curtexindex+i-3;
|
|
if(texmru.inrange(ti))
|
|
{
|
|
VSlot &vslot = lookupvslot(texmru[ti]), *layer = nullptr, *detail = nullptr;
|
|
Slot &slot = *vslot.slot;
|
|
Texture *tex = slot.sts.empty() ? notexture : slot.sts[0].t, *glowtex = nullptr, *layertex = nullptr, *detailtex = nullptr;
|
|
if(slot.texmask&(1<<TEX_GLOW))
|
|
{
|
|
loopvj(slot.sts) if(slot.sts[j].type==TEX_GLOW) { glowtex = slot.sts[j].t; break; }
|
|
}
|
|
if(vslot.layer)
|
|
{
|
|
layer = &lookupvslot(vslot.layer);
|
|
layertex = layer->slot->sts.empty() ? notexture : layer->slot->sts[0].t;
|
|
}
|
|
if(vslot.detail)
|
|
{
|
|
detail = &lookupvslot(vslot.detail);
|
|
detailtex = detail->slot->sts.empty() ? notexture : detail->slot->sts[0].t;
|
|
}
|
|
float sx = min(1.0f, tex->xs/(float)tex->ys), sy = min(1.0f, tex->ys/(float)tex->xs);
|
|
int x = w*1800/h-s-50, r = s;
|
|
vec2 tc[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) };
|
|
float xoff = vslot.offset.x, yoff = vslot.offset.y;
|
|
if(vslot.rotation)
|
|
{
|
|
const texrotation &r = texrotations[vslot.rotation];
|
|
if(r.swapxy) { swap(xoff, yoff); loopk(4) swap(tc[k].x, tc[k].y); }
|
|
if(r.flipx) { xoff *= -1; loopk(4) tc[k].x *= -1; }
|
|
if(r.flipy) { yoff *= -1; loopk(4) tc[k].y *= -1; }
|
|
}
|
|
loopk(4) { tc[k].x = tc[k].x/sx - xoff/tex->xs; tc[k].y = tc[k].y/sy - yoff/tex->ys; }
|
|
glBindTexture(GL_TEXTURE_2D, tex->id);
|
|
loopj(glowtex ? 3 : 2)
|
|
{
|
|
if(j < 2) gle::color(vec(vslot.colorscale).mul(j), texpaneltimer/1000.0f);
|
|
else
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, glowtex->id);
|
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE);
|
|
gle::color(vslot.glowcolor, texpaneltimer/1000.0f);
|
|
}
|
|
gle::begin(GL_TRIANGLE_STRIP);
|
|
gle::attribf(x, y); gle::attrib(tc[0]);
|
|
gle::attribf(x+r, y); gle::attrib(tc[1]);
|
|
gle::attribf(x, y+r); gle::attrib(tc[3]);
|
|
gle::attribf(x+r, y+r); gle::attrib(tc[2]);
|
|
xtraverts += gle::end();
|
|
if(j==1 && detailtex)
|
|
{
|
|
glBindTexture(GL_TEXTURE_2D, detailtex->id);
|
|
gle::begin(GL_TRIANGLE_STRIP);
|
|
gle::attribf(x, y); gle::attrib(tc[0]);
|
|
gle::attribf(x+r/2, y); gle::attrib(tc[1]);
|
|
gle::attribf(x, y+r/2); gle::attrib(tc[3]);
|
|
gle::attribf(x+r/2, y+r/2); gle::attrib(tc[2]);
|
|
xtraverts += gle::end();
|
|
}
|
|
if(j==1 && layertex)
|
|
{
|
|
gle::color(layer->colorscale, texpaneltimer/1000.0f);
|
|
glBindTexture(GL_TEXTURE_2D, layertex->id);
|
|
gle::begin(GL_TRIANGLE_STRIP);
|
|
gle::attribf(x+r/2, y+r/2); gle::attrib(tc[0]);
|
|
gle::attribf(x+r, y+r/2); gle::attrib(tc[1]);
|
|
gle::attribf(x+r/2, y+r); gle::attrib(tc[3]);
|
|
gle::attribf(x+r, y+r); gle::attrib(tc[2]);
|
|
xtraverts += gle::end();
|
|
}
|
|
if(!j)
|
|
{
|
|
r -= 10;
|
|
x += 5;
|
|
y += 5;
|
|
}
|
|
else if(j == 2) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
|
}
|
|
}
|
|
y += s+gap;
|
|
}
|
|
|
|
pophudmatrix(true, false);
|
|
resethudshader();
|
|
}
|
|
}
|
|
|
|
#define EDITSTAT(name, type, val) \
|
|
ICOMMAND(editstat##name, "", (), \
|
|
{ \
|
|
extern int statrate; \
|
|
static int laststat = 0; \
|
|
static type prevstat = 0; \
|
|
static type curstat = 0; \
|
|
if(totalmillis - laststat >= statrate) \
|
|
{ \
|
|
prevstat = curstat; \
|
|
laststat = totalmillis - (totalmillis%statrate); \
|
|
} \
|
|
if(prevstat == curstat) curstat = (val); \
|
|
type##ret(curstat); \
|
|
});
|
|
EDITSTAT(wtr, int, wtris/1024);
|
|
EDITSTAT(vtr, int, (vtris*100)/max(wtris, 1));
|
|
EDITSTAT(wvt, int, wverts/1024);
|
|
EDITSTAT(vvt, int, (vverts*100)/max(wverts, 1));
|
|
EDITSTAT(evt, int, xtraverts/1024);
|
|
EDITSTAT(eva, int, xtravertsva/1024);
|
|
EDITSTAT(octa, int, allocnodes*8);
|
|
EDITSTAT(va, int, allocva);
|
|
EDITSTAT(glde, int, glde);
|
|
EDITSTAT(geombatch, int, gbatches);
|
|
EDITSTAT(oq, int, getnumqueries());
|
|
EDITSTAT(pvs, int, getnumviewcells());
|
|
|