LCOV - code coverage report
Current view: top level - engine/world - octaedit.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 9.6 % 1115 107
Test Date: 2026-06-16 06:16:16 Functions: 28.1 % 114 32

            Line data    Source code
       1              : /**
       2              :  * @file octaedit.cpp
       3              :  * @brief world modification core functionality
       4              :  *
       5              :  * modifying the octree grid can be done by changing the states of cube nodes within
       6              :  * the world, which is made easier with octaedit.cpp's notions of selections (a
       7              :  * rectangular selection of cubes with which to modify together). Selections can
       8              :  * be modified all at once, copied, and pasted throughout the world instead of individual
       9              :  * cubes being modified.
      10              :  *
      11              :  * additionally, this file contains core functionality for rendering of selections
      12              :  * and other constructs generally useful for modifying the level, such as entity
      13              :  * locations and radii.
      14              :  */
      15              : #include "../libprimis-headers/cube.h"
      16              : #include "../../shared/geomexts.h"
      17              : #include "../../shared/glemu.h"
      18              : #include "../../shared/glexts.h"
      19              : #include "../../shared/stream.h"
      20              : 
      21              : #include "light.h"
      22              : #include "octaedit.h"
      23              : #include "octaworld.h"
      24              : #include "raycube.h"
      25              : 
      26              : #include "interface/console.h"
      27              : #include "interface/control.h"
      28              : #include "interface/input.h"
      29              : 
      30              : #include "render/hud.h"
      31              : #include "render/octarender.h"
      32              : #include "render/rendergl.h"
      33              : #include "render/renderlights.h"
      34              : #include "render/renderva.h"
      35              : #include "render/renderwindow.h"
      36              : #include "render/shader.h"
      37              : #include "render/shaderparam.h"
      38              : #include "render/texture.h"
      39              : 
      40              : #include "heightmap.h"
      41              : #include "material.h"
      42              : #include "world.h"
      43              : 
      44              : //used in iengine.h
      45            0 : void boxs(int orient, vec o, const vec &s, float size, bool boxoutline)
      46              : {
      47            0 :     int d  = DIMENSION(orient),
      48            0 :         dc = DIM_COORD(orient);
      49            0 :     float f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0;
      50            0 :     o[D[d]] += dc * s[D[d]] + f;
      51              : 
      52            0 :     vec r(0, 0, 0),
      53            0 :         c(0, 0, 0);
      54            0 :     r[R[d]] = s[R[d]];
      55            0 :     c[C[d]] = s[C[d]];
      56              : 
      57            0 :     vec v1 = o,
      58            0 :         v2 = vec(o).add(r),
      59            0 :         v3 = vec(o).add(r).add(c),
      60            0 :         v4 = vec(o).add(c);
      61              : 
      62            0 :     r[R[d]] = 0.5f*size;
      63            0 :     c[C[d]] = 0.5f*size;
      64              : 
      65            0 :     gle::defvertex();
      66            0 :     gle::begin(GL_TRIANGLE_STRIP);
      67            0 :     gle::attrib(vec(v1).sub(r).sub(c));
      68            0 :     gle::attrib(vec(v1).add(r).add(c));
      69              : 
      70            0 :     gle::attrib(vec(v2).add(r).sub(c));
      71            0 :     gle::attrib(vec(v2).sub(r).add(c));
      72              : 
      73            0 :     gle::attrib(vec(v3).add(r).add(c));
      74            0 :     gle::attrib(vec(v3).sub(r).sub(c));
      75              : 
      76            0 :     gle::attrib(vec(v4).sub(r).add(c));
      77            0 :     gle::attrib(vec(v4).add(r).sub(c));
      78              : 
      79            0 :     gle::attrib(vec(v1).sub(r).sub(c));
      80            0 :     gle::attrib(vec(v1).add(r).add(c));
      81            0 :     xtraverts += gle::end();
      82            0 : }
      83              : 
      84              : //used in iengine.h
      85              : //boxsquare, draws a 2d square at a specified location
      86            0 : void boxs(int orient, vec origin, const vec &s, bool boxoutline)
      87              : {
      88            0 :     int d  = DIMENSION(orient),
      89            0 :         dc = DIM_COORD(orient);
      90            0 :     float f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0;
      91            0 :     origin[D[d]] += dc * s[D[d]] + f;
      92              : 
      93            0 :     gle::defvertex();
      94            0 :     gle::begin(GL_LINE_LOOP);
      95              :     //draw four lines
      96            0 :     gle::attrib(origin); origin[R[d]] += s[R[d]];
      97            0 :     gle::attrib(origin); origin[C[d]] += s[C[d]];
      98            0 :     gle::attrib(origin); origin[R[d]] -= s[R[d]];
      99            0 :     gle::attrib(origin);
     100              : 
     101            0 :     xtraverts += gle::end();
     102            0 : }
     103              : 
     104              : //used in iengine.h
     105            0 : void boxs3D(const vec &origin, vec s, int g, bool boxoutline)
     106              : {
     107            0 :     s.mul(g); //multiply displacement by g(ridpower)
     108            0 :     for(int i = 0; i < 6; ++i) //for each face
     109              :     {
     110            0 :         boxs(i, origin, s, boxoutline);
     111              :     }
     112            0 : }
     113              : 
     114              : //used in iengine.h
     115            0 : void boxsgrid(int orient, vec origin, vec s, int g, bool boxoutline)
     116              : {
     117            0 :     int d  = DIMENSION(orient),
     118            0 :         dc = DIM_COORD(orient);
     119            0 :     float ox = origin[R[d]],
     120            0 :           oy = origin[C[d]],
     121            0 :           xs = s[R[d]],
     122            0 :           ys = s[C[d]],
     123            0 :           f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0;
     124              : 
     125            0 :     origin[D[d]] += dc * s[D[d]]*g + f;
     126              : 
     127            0 :     gle::defvertex();
     128            0 :     gle::begin(GL_LINES);
     129            0 :     for(int x = 0; x < xs; ++x)
     130              :     {
     131            0 :         origin[R[d]] += g;
     132            0 :         gle::attrib(origin);
     133            0 :         origin[C[d]] += ys*g;
     134            0 :         gle::attrib(origin);
     135            0 :         origin[C[d]] = oy;
     136              :     }
     137            0 :     for(int y = 0; y < ys; ++y)
     138              :     {
     139            0 :         origin[C[d]] += g;
     140            0 :         origin[R[d]] = ox;
     141            0 :         gle::attrib(origin);
     142            0 :         origin[R[d]] += xs*g;
     143            0 :         gle::attrib(origin);
     144              :     }
     145            0 :     xtraverts += gle::end();
     146            0 : }
     147              : 
     148              : selinfo sel, lastsel; //lastsel is used only in iengine
     149              : static selinfo savedsel;
     150              : 
     151            0 : bool selinfo::validate()
     152              : {
     153            0 :     if(grid <= 0 || grid >= rootworld.mapsize())
     154              :     {
     155            0 :         return false;
     156              :     }
     157            0 :     if(o.x >= rootworld.mapsize() || o.y >= rootworld.mapsize() || o.z >= rootworld.mapsize())
     158              :     {
     159            0 :         return false;
     160              :     }
     161            0 :     if(o.x < 0)
     162              :     {
     163            0 :         s.x -= (grid - 1 - o.x)/grid;
     164            0 :         o.x = 0;
     165              :     }
     166            0 :     if(o.y < 0)
     167              :     {
     168            0 :         s.y -= (grid - 1 - o.y)/grid;
     169            0 :         o.y = 0;
     170              :     }
     171            0 :     if(o.z < 0)
     172              :     {
     173            0 :         s.z -= (grid - 1 - o.z)/grid;
     174            0 :         o.z = 0;
     175              :     }
     176            0 :     s.x = std::clamp(s.x, 0, (rootworld.mapsize() - o.x)/grid);
     177            0 :     s.y = std::clamp(s.y, 0, (rootworld.mapsize() - o.y)/grid);
     178            0 :     s.z = std::clamp(s.z, 0, (rootworld.mapsize() - o.z)/grid);
     179            0 :     return s.x > 0 && s.y > 0 && s.z > 0;
     180              : }
     181              : 
     182              : int orient = 0,
     183              :     gridsize = 8;
     184              : static ivec lastcur;
     185              : ivec cor, lastcor, cur; //used in iengine
     186              : 
     187              : bool editmode     = false, //used in iengine
     188              :      multiplayer  = false, //used in iengine
     189              :      allowediting = false, //used in iengine
     190              :      havesel      = false; //used in iengine
     191              : int horient  = 0,
     192              :     entmoving = 0;
     193              : 
     194              : //used in iengine
     195            0 : VARF(entediting, 0, 0, 1,
     196              : {
     197              :     if(!entediting)
     198              :     {
     199              :         entcancel();
     200              :     }
     201              : });
     202              : 
     203              : //used in iengine
     204            3 : void multiplayerwarn()
     205              : {
     206            3 :     conoutf(Console_Error, "operation not available in multiplayer");
     207            3 : }
     208              : 
     209              : //used in iengine
     210            0 : bool pointinsel(const selinfo &sel, const vec &origin)
     211              : {
     212            0 :     return(origin.x <= sel.o.x+sel.s.x*sel.grid
     213            0 :         && origin.x >= sel.o.x
     214            0 :         && origin.y <= sel.o.y+sel.s.y*sel.grid
     215            0 :         && origin.y >= sel.o.y
     216            0 :         && origin.z <= sel.o.z+sel.s.z*sel.grid
     217            0 :         && origin.z >= sel.o.z);
     218              : }
     219              : 
     220              : //used in iengine
     221            0 : VARF(dragging, 0, 0, 1,
     222              :     if(!dragging || cor[0]<0)
     223              :     {
     224              :         return;
     225              :     }
     226              :     lastcur = cur;
     227              :     lastcor = cor;
     228              :     sel.grid = gridsize;
     229              :     sel.orient = orient;
     230              : );
     231              : 
     232              : int moving = 0; //used in iengine
     233              : 
     234            0 : VARF(gridpower, 0, 3, 12,
     235              : {
     236              :     if(dragging)
     237              :     {
     238              :         return;
     239              :     }
     240              :     gridsize = 1<<gridpower;
     241              :     if(gridsize>=rootworld.mapsize())
     242              :     {
     243              :         gridsize = rootworld.mapsize()/2;
     244              :     }
     245              :     cancelsel();
     246              : });
     247              : 
     248              : VAR(passthroughsel, 0, 0, 1); //used in iengine
     249              : VAR(selectcorners, 0, 0, 1); //used in iengine
     250            0 : VARF(hmapedit, 0, 0, 1, horient = sel.orient); //used in iengine
     251              : 
     252              : //used in iengine
     253            2 : void forcenextundo()
     254              : {
     255            2 :     lastsel.orient = -1;
     256            2 : }
     257              : 
     258            2 : static void cubecancel()
     259              : {
     260            2 :     havesel = false;
     261            2 :     moving = dragging = hmapedit = passthroughsel = 0;
     262            2 :     forcenextundo();
     263            2 :     hmapcancel();
     264            2 : }
     265              : 
     266              : //used in iengine
     267            1 : void cancelsel()
     268              : {
     269            1 :     cubecancel();
     270            1 :     entcancel();
     271            1 : }
     272              : 
     273              : //used in iengine
     274            0 : bool haveselent()
     275              : {
     276            0 :     return entgroup.size() > 0;
     277              : }
     278              : 
     279              : //used in iengine
     280            5 : bool noedit(bool inview, bool msg)
     281              : {
     282            5 :     if(!editmode)
     283              :     {
     284            5 :         if(msg)
     285              :         {
     286            5 :             conoutf(Console_Error, "operation only allowed in edit mode");
     287              :         }
     288            5 :         return true;
     289              :     }
     290            0 :     if(inview || haveselent())
     291              :     {
     292            0 :         return false;
     293              :     }
     294            0 :     vec o(sel.o),
     295            0 :         s(sel.s);
     296            0 :     s.mul(sel.grid / 2.0f);
     297            0 :     o.add(s);
     298            0 :     float r = std::max(std::max(s.x, s.y), s.z);
     299            0 :     bool viewable = view.isvisiblesphere(r, o) != ViewFrustumCull_NotVisible;
     300            0 :     if(!viewable && msg)
     301              :     {
     302            0 :         conoutf(Console_Error, "selection not in view");
     303              :     }
     304            0 :     return !viewable;
     305              : }
     306              : 
     307              : //used in iengine
     308            1 : void reorient()
     309              : {
     310            1 :     sel.cx = 0;
     311            1 :     sel.cy = 0;
     312            1 :     sel.cxs = sel.s[R[DIMENSION(orient)]]*2;
     313            1 :     sel.cys = sel.s[C[DIMENSION(orient)]]*2;
     314            1 :     sel.orient = orient;
     315            1 : }
     316              : 
     317              : ///////// selection support /////////////
     318              : 
     319            0 : 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
     320              : {
     321            0 :     int dim = DIMENSION(b.orient),
     322            0 :         dc = DIM_COORD(b.orient);
     323              :     //ivec::ivec(int d, int row, int col, int depth)
     324            0 :     ivec s(dim, x*b.grid, y*b.grid, dc*(b.s[dim]-1)*b.grid);
     325            0 :     s.add(b.o);
     326            0 :     if(dc)
     327              :     {
     328            0 :         s[dim] -= z*b.grid;
     329              :     }
     330              :     else
     331              :     {
     332            0 :         s[dim] += z*b.grid;
     333              :     }
     334            0 :     return rootworld.lookupcube(s, rgrid);
     335              : }
     336              : 
     337              : ////////////// cursor ///////////////
     338              : 
     339              : int selchildcount = 0, //used in iengine
     340              :     selchildmat = -1; //used in iengine
     341              : 
     342              : //used in iengine.h
     343            0 : void countselchild(const std::array<cube, 8> &c, const ivec &cor, int size)
     344              : {
     345            0 :     ivec ss = ivec(sel.s).mul(sel.grid);
     346            0 :     uchar possible = octaboxoverlap(cor, size, sel.o, ivec(sel.o).add(ss));
     347            0 :     for(int i = 0; i < 8; ++i)
     348              :     {
     349            0 :         if(possible&(1<<i))
     350              :         {
     351            0 :             ivec o(i, cor, size);
     352            0 :             if(c[i].children)
     353              :             {
     354            0 :                 countselchild(*(c[i].children), o, size/2);
     355              :             }
     356              :             else
     357              :             {
     358            0 :                 selchildcount++;
     359            0 :                 if(c[i].material != Mat_Air && selchildmat != Mat_Air)
     360              :                 {
     361            0 :                     if(selchildmat < 0)
     362              :                     {
     363            0 :                         selchildmat = c[i].material;
     364              :                     }
     365            0 :                     else if(selchildmat != c[i].material)
     366              :                     {
     367            0 :                         selchildmat = Mat_Air;
     368              :                     }
     369              :                 }
     370              :             }
     371              :         }
     372              :     }
     373            0 : }
     374              : 
     375              : //used in iengine.h
     376            0 : void normalizelookupcube(const ivec &o)
     377              : {
     378            0 :     if(lusize>gridsize)
     379              :     {
     380            0 :         lu.x += (o.x-lu.x)/gridsize*gridsize;
     381            0 :         lu.y += (o.y-lu.y)/gridsize*gridsize;
     382            0 :         lu.z += (o.z-lu.z)/gridsize*gridsize;
     383              :     }
     384            0 :     else if(gridsize>lusize)
     385              :     {
     386            0 :         lu.x &= ~(gridsize-1);
     387            0 :         lu.y &= ~(gridsize-1);
     388            0 :         lu.z &= ~(gridsize-1);
     389              :     }
     390            0 :     lusize = gridsize;
     391            0 : }
     392              : 
     393              : //used in iengine.h
     394            0 : void updateselection()
     395              : {
     396            0 :     sel.o.x = std::min(lastcur.x, cur.x);
     397            0 :     sel.o.y = std::min(lastcur.y, cur.y);
     398            0 :     sel.o.z = std::min(lastcur.z, cur.z);
     399            0 :     sel.s.x = std::abs(lastcur.x-cur.x)/sel.grid+1;
     400            0 :     sel.s.y = std::abs(lastcur.y-cur.y)/sel.grid+1;
     401            0 :     sel.s.z = std::abs(lastcur.z-cur.z)/sel.grid+1;
     402            0 : }
     403              : 
     404            0 : bool editmoveplane(const vec &o, const vec &ray, int d, float off, vec &handle, vec &dest, bool first)
     405              : {
     406            0 :     plane pl(d, off);
     407            0 :     float dist = 0.0f;
     408            0 :     if(!pl.rayintersect(player->o, ray, dist))
     409              :     {
     410            0 :         return false;
     411              :     }
     412            0 :     dest = vec(ray).mul(dist).add(player->o);
     413            0 :     if(first)
     414              :     {
     415            0 :         handle = vec(dest).sub(o);
     416              :     }
     417            0 :     dest.sub(handle);
     418            0 :     return true;
     419              : }
     420              : 
     421              : //////////// ready changes to vertex arrays ////////////
     422              : 
     423            0 : static void readychanges(const ivec &bbmin, const ivec &bbmax, std::array<cube, 8> &c, const ivec &cor, int size)
     424              : {
     425            0 :     LOOP_OCTA_BOX(cor, size, bbmin, bbmax)
     426              :     {
     427            0 :         ivec o(i, cor, size);
     428            0 :         if(c[i].ext)
     429              :         {
     430            0 :             if(c[i].ext->va)             // removes va s so that octarender will recreate
     431              :             {
     432            0 :                 int hasmerges = c[i].ext->va->hasmerges;
     433            0 :                 destroyva(c[i].ext->va);
     434            0 :                 c[i].ext->va = nullptr;
     435            0 :                 if(hasmerges)
     436              :                 {
     437            0 :                     invalidatemerges(c[i]);
     438              :                 }
     439              :             }
     440            0 :             freeoctaentities(c[i]);
     441            0 :             c[i].ext->tjoints = -1;
     442              :         }
     443            0 :         if(c[i].children)
     444              :         {
     445            0 :             if(size<=1)
     446              :             {
     447            0 :                 setcubefaces(c[i], facesolid);
     448            0 :                 c[i].discardchildren(true);
     449            0 :                 brightencube(c[i]);
     450              :             }
     451              :             else
     452              :             {
     453            0 :                 readychanges(bbmin, bbmax, *(c[i].children), o, size/2);
     454              :             }
     455              :         }
     456              :         else
     457              :         {
     458            0 :             brightencube(c[i]);
     459              :         }
     460              :     }
     461            0 : }
     462              : 
     463            0 : void cubeworld::commitchanges(bool force)
     464              : {
     465            0 :     if(!force && !haschanged)
     466              :     {
     467            0 :         return;
     468              :     }
     469            0 :     haschanged = false;
     470            0 :     int oldlen = valist.size();
     471            0 :     resetclipplanes();
     472            0 :     entitiesinoctanodes();
     473            0 :     inbetweenframes = false;
     474            0 :     octarender();
     475            0 :     inbetweenframes = true;
     476            0 :     setupmaterials(oldlen);
     477            0 :     clearshadowcache();
     478            0 :     updatevabbs();
     479              : }
     480              : 
     481            0 : void cubeworld::changed(const ivec &bbmin, const ivec &bbmax, bool commit)
     482              : {
     483            0 :     readychanges(bbmin, bbmax, *worldroot, ivec(0, 0, 0), mapsize()/2);
     484            0 :     haschanged = true;
     485              : 
     486            0 :     if(commit)
     487              :     {
     488            0 :         commitchanges();
     489              :     }
     490            0 : }
     491              : 
     492            0 : void cubeworld::changed(const block3 &sel, bool commit)
     493              : {
     494            0 :     if(!sel.s)
     495              :     {
     496            0 :         return;
     497              :     }
     498            0 :     readychanges(ivec(sel.o).sub(1), ivec(sel.s).mul(sel.grid).add(sel.o).add(1), *worldroot, ivec(0, 0, 0), mapsize()/2);
     499            0 :     haschanged = true;
     500            0 :     if(commit)
     501              :     {
     502            0 :         commitchanges();
     503              :     }
     504              : }
     505              : 
     506              : //////////// copy and undo /////////////
     507            0 : static void copycube(const cube &src, cube &dst)
     508              : {
     509            0 :     dst = src;
     510            0 :     dst.visible = 0;
     511            0 :     dst.merged = 0;
     512            0 :     dst.ext = nullptr; // src cube is responsible for va destruction
     513              :     //recursively apply to children
     514            0 :     if(src.children)
     515              :     {
     516            0 :         dst.children = newcubes(faceempty);
     517            0 :         for(int i = 0; i < 8; ++i)
     518              :         {
     519            0 :             copycube((*src.children)[i], (*dst.children)[i]);
     520              :         }
     521              :     }
     522            0 : }
     523              : 
     524            0 : void pastecube(const cube &src, cube &dst)
     525              : {
     526            0 :     dst.discardchildren();
     527            0 :     copycube(src, dst);
     528            0 : }
     529              : 
     530            0 : static void blockcopy(const block3 &s, int rgrid, block3 *b)
     531              : {
     532            0 :     *b = s;
     533            0 :     cube *q = b->c();
     534            0 :     uint i = 0;
     535            0 :     LOOP_XYZ(s, rgrid, copycube(c, q[i]); i++);
     536            0 : }
     537              : 
     538              : //used in iengine.h
     539            0 : block3 *blockcopy(const block3 &s, int rgrid)
     540              : {
     541            0 :     int bsize = sizeof(block3)+sizeof(cube)*s.size();
     542            0 :     if(bsize <= 0 || bsize > (100<<20))
     543              :     {
     544            0 :         return nullptr;
     545              :     }
     546            0 :     block3 *b = reinterpret_cast<block3 *>(new uchar[bsize]); //create a new block3 pointing to an appropriate sized memory area
     547            0 :     if(b) //should always be true
     548              :     {
     549            0 :         blockcopy(s, rgrid, b); //copy the block3 s to b
     550              :     }
     551            0 :     return b;
     552              : }
     553              : 
     554              : //used in iengine.h
     555            0 : void freeblock(block3 *b, bool alloced = true)
     556              : {
     557            0 :     cube *q = b->c();
     558            0 :     uint j = 0;
     559            0 :     for(int i = 0; i < b->size(); ++i)
     560              :     {
     561            0 :         (q[j]).discardchildren();
     562            0 :         j++;
     563              :     }
     564            0 :     if(alloced)
     565              :     {
     566            0 :         delete[] b;
     567              :     }
     568            0 : }
     569              : 
     570            0 : void selgridmap(const selinfo &sel, uchar *g)
     571              : {
     572            0 :     for(int z = 0; z < sel.s[D[DIMENSION(sel.orient)]]; ++z)
     573              :     {
     574            0 :         for(int y = 0; y < sel.s[C[DIMENSION(sel.orient)]]; ++y)
     575              :         {
     576            0 :             for(int x = 0; x < sel.s[R[DIMENSION(sel.orient)]]; ++x)
     577              :             {
     578            0 :                 blockcube(x,y,z,sel,-sel.grid);
     579            0 :                 *g++ = BITSCAN(lusize);
     580              :             }
     581              :         }
     582              :     }
     583            0 : }
     584              : 
     585            0 : void freeundo(undoblock *u)
     586              : {
     587            0 :     if(!u->numents)
     588              :     {
     589            0 :         freeblock(u->block(), false);
     590              :     }
     591            0 :     delete[] reinterpret_cast<uchar *>(u);  //re-cast to uchar array so it can be destructed properly
     592            0 : }
     593              : 
     594            0 : static int undosize(undoblock *u)
     595              : {
     596            0 :     if(u->numents)
     597              :     {
     598            0 :         return u->numents*sizeof(undoent);
     599              :     }
     600              :     else
     601              :     {
     602            0 :         const block3 *b = u->block();
     603            0 :         const cube *q = b->getcube();
     604            0 :         int size = b->size(),
     605            0 :             total = size;
     606            0 :         uint i = 0;
     607            0 :         for(int j = 0; j < size; ++j)
     608              :         {
     609            0 :             total += familysize(q[i])*sizeof(cube);
     610            0 :             i++;
     611              :         }
     612            0 :         return total;
     613              :     }
     614              : }
     615              : 
     616              : std::deque<undoblock *> undos, redos; //used in iengine
     617              : static VARP(undomegs, 0, 5, 100);                              // bounded by n megs, zero means no undo history
     618              : static int totalundos = 0;
     619              : 
     620            1 : void pruneundos(int maxremain)                          // bound memory
     621              : {
     622            1 :     while(totalundos > maxremain && !undos.empty())
     623              :     {
     624            0 :         undoblock *u = undos.front();
     625            0 :         undos.pop_front();
     626            0 :         totalundos -= u->size;
     627            0 :         freeundo(u);
     628              :     }
     629              :     //conoutf(CON_DEBUG, "undo: %d of %d(%%%d)", totalundos, undomegs<<20, totalundos*100/(undomegs<<20));
     630            1 :     while(!redos.empty())
     631              :     {
     632            0 :         undoblock *u = redos.front();
     633            0 :         redos.pop_front();
     634            0 :         totalundos -= u->size;
     635            0 :         freeundo(u);
     636              :     }
     637            1 : }
     638              : 
     639            0 : undoblock *newundocube(const selinfo &s)
     640              : {
     641            0 :     int ssize = s.size(),
     642            0 :         selgridsize = ssize,
     643            0 :         blocksize = sizeof(block3)+ssize*sizeof(cube);
     644            0 :     if(blocksize <= 0 || blocksize > (undomegs<<20))
     645              :     {
     646            0 :         return nullptr;
     647              :     }
     648            0 :     undoblock *u = reinterpret_cast<undoblock *>(new uchar[sizeof(undoblock) + blocksize + selgridsize]);
     649            0 :     if(!u)
     650              :     {
     651            0 :         return nullptr;
     652              :     }
     653            0 :     u->numents = 0;
     654            0 :     block3 *b = u->block();
     655            0 :     blockcopy(s, -s.grid, b);
     656            0 :     uchar *g = u->gridmap();
     657            0 :     selgridmap(s, g);
     658            0 :     return u;
     659              : }
     660              : 
     661            0 : void addundo(undoblock *u)
     662              : {
     663            0 :     u->size = undosize(u);
     664            0 :     u->timestamp = totalmillis;
     665            0 :     undos.push_back(u);
     666            0 :     totalundos += u->size;
     667            0 :     pruneundos(undomegs<<20);
     668            0 : }
     669              : 
     670              : VARP(nompedit, 0, 1, 1); //used in iengine
     671              : 
     672            0 : static int countblock(const cube * const c, int n = 8)
     673              : {
     674            0 :     int r = 0;
     675            0 :     for(int i = 0; i < n; ++i)
     676              :     {
     677            0 :         if(c[i].children)
     678              :         {
     679            0 :             r += countblock(c[i].children->data());
     680              :         }
     681              :         else
     682              :         {
     683            0 :             ++r;
     684              :         }
     685              :     }
     686            0 :     return r;
     687              : }
     688              : 
     689            0 : int countblock(block3 *b)
     690              : {
     691            0 :     return countblock(b->getcube(), b->size());
     692              : }
     693              : 
     694              : std::vector<editinfo *> editinfos; //used in iengine
     695              : 
     696              : template<class B>
     697            0 : static void packcube(const cube &c, B &buf)
     698              : {
     699              :     //recursvely apply to children
     700            0 :     if(c.children)
     701              :     {
     702            0 :         buf.push_back(0xFF);
     703            0 :         for(int i = 0; i < 8; ++i)
     704              :         {
     705            0 :             packcube((*c.children)[i], buf);
     706              :         }
     707              :     }
     708              :     else
     709              :     {
     710            0 :         const cube &data = c;
     711            0 :         buf.push_back(c.material&0xFF);
     712            0 :         buf.push_back(c.material>>8);
     713            0 :         for(uint i = 0; i < sizeof(data.edges); ++i)
     714              :         {
     715            0 :             buf.push_back(data.edges[i]);
     716              :         }
     717            0 :         for(uint i = 0; i < sizeof(data.texture); ++i)
     718              :         {
     719            0 :             buf.push_back(reinterpret_cast<const uchar *>(data.texture)[i]);
     720              :         }
     721              :     }
     722            0 : }
     723              : 
     724              : template<class B>
     725            0 : static bool packblock(const block3 &b, B &buf)
     726              : {
     727            0 :     if(b.size() <= 0 || b.size() > (1<<20))
     728              :     {
     729            0 :         return false;
     730              :     }
     731            0 :     block3 hdr = b;
     732            0 :     for(uint i = 0; i < sizeof(hdr); ++i)
     733              :     {
     734            0 :         buf.push_back(reinterpret_cast<const uchar *>(&hdr)[i]);
     735              :     }
     736            0 :     const cube *c = b.getcube();
     737            0 :     for(int i = 0; i < b.size(); ++i)
     738              :     {
     739            0 :         packcube(c[i], buf);
     740              :     }
     741            0 :     return true;
     742              : }
     743              : 
     744              : struct VSlotHeader final
     745              : {
     746              :     ushort index;
     747              :     ushort slot;
     748              : };
     749              : 
     750            0 : static void packvslots(const cube &c, std::vector<uchar> &buf, std::vector<ushort> &used)
     751              : {
     752              :     //recursively apply to children
     753            0 :     if(c.children)
     754              :     {
     755            0 :         for(int i = 0; i < 8; ++i)
     756              :         {
     757            0 :             packvslots((*c.children)[i], buf, used);
     758              :         }
     759              :     }
     760              :     else
     761              :     {
     762            0 :         for(int i = 0; i < 6; ++i) //for each face
     763              :         {
     764            0 :             ushort index = c.texture[i];
     765            0 :             if((vslots.size() > index) && vslots[index]->changed && std::find(used.begin(), used.end(), index) != used.end())
     766              :             {
     767            0 :                 used.push_back(index);
     768            0 :                 VSlot &vs = *vslots[index];
     769            0 :                 for(uint i = 0; i < sizeof(VSlotHeader); ++i)
     770              :                 {
     771            0 :                     buf.emplace_back();
     772              :                 }
     773            0 :                 VSlotHeader &hdr = *reinterpret_cast<VSlotHeader *>(&(*(buf.end())) - sizeof(VSlotHeader));
     774            0 :                 hdr.index = index;
     775            0 :                 hdr.slot = vs.slot->index;
     776            0 :                 packvslot(buf, vs);
     777              :             }
     778              :         }
     779              :     }
     780            0 : }
     781              : 
     782            0 : static void packvslots(const block3 &b, std::vector<uchar> &buf)
     783              : {
     784            0 :     std::vector<ushort> used;
     785            0 :     const cube *c = b.getcube();
     786            0 :     for(int i = 0; i < b.size(); ++i)
     787              :     {
     788            0 :         packvslots(c[i], buf, used);
     789              :     }
     790            0 :     for(uint i = 0; i < sizeof(VSlotHeader); ++i)
     791              :     {
     792            0 :         buf.push_back(0);
     793              :     }
     794            0 : }
     795              : 
     796              : template<class B>
     797            0 : static void unpackcube(cube &c, B &buf)
     798              : {
     799            0 :     int mat = buf.get();
     800            0 :     if(mat == 0xFF)
     801              :     {
     802            0 :         c.children = newcubes(faceempty);
     803              :         //recursively apply to children
     804            0 :         for(int i = 0; i < 8; ++i)
     805              :         {
     806            0 :             unpackcube((*c.children)[i], buf);
     807              :         }
     808              :     }
     809              :     else
     810              :     {
     811            0 :         c.material = mat | (buf.get()<<8);
     812            0 :         buf.get(c.edges, sizeof(c.edges));
     813            0 :         buf.get(reinterpret_cast<uchar *>(c.texture), sizeof(c.texture));
     814              :     }
     815            0 : }
     816              : 
     817              : template<class B>
     818            0 : static bool unpackblock(block3 *&b, B &buf)
     819              : {
     820            0 :     if(b)
     821              :     {
     822            0 :         freeblock(b);
     823            0 :         b = nullptr;
     824              :     }
     825            0 :     block3 hdr;
     826            0 :     if(buf.get(reinterpret_cast<uchar *>(&hdr), sizeof(hdr)) < static_cast<int>(sizeof(hdr)))
     827              :     {
     828            0 :         return false;
     829              :     }
     830            0 :     if(hdr.size() > (1<<20) || hdr.grid <= 0 || hdr.grid > (1<<12))
     831              :     {
     832            0 :         return false;
     833              :     }
     834            0 :     b = reinterpret_cast<block3 *>(new uchar[sizeof(block3)+hdr.size()*sizeof(cube)]);
     835            0 :     if(!b)
     836              :     {
     837            0 :         return false;
     838              :     }
     839            0 :     *b = hdr;
     840            0 :     cube *c = b->c();
     841            0 :     std::memset(c, 0, b->size()*sizeof(cube));
     842            0 :     for(int i = 0; i < b->size(); ++i)
     843              :     {
     844            0 :         unpackcube(c[i], buf);
     845              :     }
     846            0 :     return true;
     847              : }
     848              : 
     849              : struct vslotmap final
     850              : {
     851              :     int index;
     852              :     VSlot *vslot;
     853              : 
     854              :     vslotmap() {}
     855            0 :     vslotmap(int index, VSlot *vslot) : index(index), vslot(vslot) {}
     856              : };
     857              : 
     858              : static std::vector<vslotmap> remappedvslots;
     859              : 
     860              : //used in iengine.h so remappedvslots does not need to be exposed
     861            0 : void clearremappedvslots()
     862              : {
     863            0 :     remappedvslots.clear();
     864            0 : }
     865              : static std::vector<vslotmap> unpackingvslots;
     866              : 
     867            0 : static void unpackvslots(cube &c, ucharbuf &buf)
     868              : {
     869              :     //recursively apply to children
     870            0 :     if(c.children)
     871              :     {
     872            0 :         for(int i = 0; i < 8; ++i)
     873              :         {
     874            0 :             unpackvslots((*c.children)[i], buf);
     875              :         }
     876              :     }
     877              :     else
     878              :     {
     879            0 :         for(int i = 0; i < 6; ++i) //one for each face
     880              :         {
     881            0 :             ushort tex = c.texture[i];
     882            0 :             for(size_t j = 0; j < unpackingvslots.size(); j++)
     883              :             {
     884            0 :                 if(unpackingvslots[j].index == tex)
     885              :                 {
     886            0 :                     c.texture[i] = unpackingvslots[j].vslot->index;
     887            0 :                     break;
     888              :                 }
     889              :             }
     890              :         }
     891              :     }
     892            0 : }
     893              : 
     894            0 : static void unpackvslots(block3 &b, ucharbuf &buf)
     895              : {
     896            0 :     while(buf.remaining() >= static_cast<int>(sizeof(VSlotHeader)))
     897              :     {
     898            0 :         VSlotHeader &hdr = *reinterpret_cast<VSlotHeader *>(buf.pad(sizeof(VSlotHeader)));
     899            0 :         if(!hdr.index)
     900              :         {
     901            0 :             break;
     902              :         }
     903            0 :         VSlot &vs = *lookupslot(hdr.slot, false).variants;
     904            0 :         VSlot ds;
     905            0 :         if(!unpackvslot(buf, ds, false))
     906              :         {
     907            0 :             break;
     908              :         }
     909            0 :         if(vs.index < 0 || vs.index == Default_Sky)
     910              :         {
     911            0 :             continue;
     912              :         }
     913            0 :         VSlot *edit = editvslot(vs, ds);
     914            0 :         unpackingvslots.emplace_back(vslotmap(hdr.index, edit ? edit : &vs));
     915            0 :     }
     916              : 
     917            0 :     cube *c = b.c();
     918            0 :     for(int i = 0; i < b.size(); ++i)
     919              :     {
     920            0 :         unpackvslots(c[i], buf);
     921              :     }
     922              : 
     923            0 :     unpackingvslots.clear();
     924            0 : }
     925              : 
     926            0 : static bool compresseditinfo(const uchar *inbuf, int inlen, uchar *&outbuf, int &outlen)
     927              : {
     928            0 :     uLongf len = compressBound(inlen);
     929            0 :     if(len > (1<<20))
     930              :     {
     931            0 :         return false;
     932              :     }
     933            0 :     outbuf = new uchar[len];
     934            0 :     if(!outbuf || compress2(static_cast<Bytef *>(outbuf), &len, static_cast<const Bytef *>(inbuf), inlen, Z_BEST_COMPRESSION) != Z_OK || len > (1<<16))
     935              :     {
     936            0 :         delete[] outbuf;
     937            0 :         outbuf = nullptr;
     938            0 :         return false;
     939              :     }
     940            0 :     outlen = len;
     941            0 :     return true;
     942              : }
     943              : 
     944              : //used in iengine.h
     945            0 : bool uncompresseditinfo(const uchar *inbuf, int inlen, uchar *&outbuf, int &outlen)
     946              : {
     947            0 :     if(compressBound(outlen) > (1<<20))
     948              :     {
     949            0 :         return false;
     950              :     }
     951            0 :     uLongf len = outlen;
     952            0 :     outbuf = new uchar[len];
     953            0 :     if(!outbuf || uncompress(static_cast<Bytef *>(outbuf), &len, static_cast<const Bytef *>(inbuf), inlen) != Z_OK)
     954              :     {
     955            0 :         delete[] outbuf;
     956            0 :         outbuf = nullptr;
     957            0 :         return false;
     958              :     }
     959            0 :     outlen = len;
     960            0 :     return true;
     961              : }
     962              : 
     963              : //used in iengine.h
     964            0 : bool packeditinfo(const editinfo *e, int &inlen, uchar *&outbuf, int &outlen)
     965              : {
     966            0 :     std::vector<uchar> buf;
     967            0 :     if(!e || !e->copy || !packblock(*e->copy, buf))
     968              :     {
     969            0 :         return false;
     970              :     }
     971            0 :     packvslots(*e->copy, buf);
     972            0 :     inlen = buf.size();
     973            0 :     return compresseditinfo(buf.data(), buf.size(), outbuf, outlen);
     974            0 : }
     975              : 
     976              : //used in iengine.h
     977            0 : bool unpackeditinfo(editinfo *&e, const uchar *inbuf, int inlen, int outlen)
     978              : {
     979            0 :     if(e && e->copy)
     980              :     {
     981            0 :         freeblock(e->copy);
     982            0 :         e->copy = nullptr;
     983              :     }
     984            0 :     uchar *outbuf = nullptr;
     985            0 :     if(!uncompresseditinfo(inbuf, inlen, outbuf, outlen))
     986              :     {
     987            0 :         return false;
     988              :     }
     989            0 :     ucharbuf buf(outbuf, outlen);
     990            0 :     if(!e)
     991              :     {
     992            0 :         editinfo *e = nullptr;
     993            0 :         editinfos.push_back(e);
     994              :     }
     995            0 :     if(!unpackblock(e->copy, buf))
     996              :     {
     997            0 :         delete[] outbuf;
     998            0 :         return false;
     999              :     }
    1000            0 :     unpackvslots(*e->copy, buf);
    1001            0 :     delete[] outbuf;
    1002            0 :     return true;
    1003              : }
    1004              : 
    1005              : //used in iengine.h
    1006            0 : void freeeditinfo(editinfo *&e)
    1007              : {
    1008            0 :     if(!e)
    1009              :     {
    1010            0 :         return;
    1011              :     }
    1012            0 :     editinfos.erase(std::find(editinfos.begin(), editinfos.end(), e));
    1013            0 :     if(e->copy)
    1014              :     {
    1015            0 :         freeblock(e->copy);
    1016              :     }
    1017            0 :     delete e;
    1018            0 :     e = nullptr;
    1019              : }
    1020              : 
    1021              : //used in iengine.h
    1022            0 : bool packundo(undoblock *u, int &inlen, uchar *&outbuf, int &outlen)
    1023              : {
    1024            0 :     std::vector<uchar> buf;
    1025            0 :     buf.reserve(512);
    1026            0 :     for(uint i = 0; i < sizeof(ushort); ++i)
    1027              :     {
    1028            0 :         buf.emplace_back();
    1029              :     }
    1030            0 :     *reinterpret_cast<ushort *>(buf.data()) = static_cast<ushort>(u->numents);
    1031            0 :     if(u->numents)
    1032              :     {
    1033            0 :         const undoent *ue = u->ents();
    1034            0 :         for(int i = 0; i < u->numents; ++i)
    1035              :         {
    1036            0 :             for(uint i = 0; i < sizeof(ushort); ++i)
    1037              :             {
    1038            0 :                 buf.emplace_back();
    1039              :             }
    1040            0 :             *reinterpret_cast<ushort *>(&(*buf.end()) - sizeof(ushort)) = static_cast<ushort>(ue[i].i);
    1041            0 :             for(uint i = 0; i < sizeof(entity); ++i)
    1042              :             {
    1043            0 :                 buf.emplace_back();
    1044              :             }
    1045            0 :             entity &e = *reinterpret_cast<entity *>(&(*buf.end()) - sizeof(entity));
    1046            0 :             e = ue[i].e;
    1047              :         }
    1048              :     }
    1049              :     else
    1050              :     {
    1051            0 :         const block3 &b = *u->block();
    1052            0 :         if(!packblock(b, buf))
    1053              :         {
    1054            0 :             return false;
    1055              :         }
    1056            0 :         for(int i = 0; i < b.size(); ++i)
    1057              :         {
    1058            0 :             buf.push_back(u->gridmap()[i]);
    1059              :         }
    1060            0 :         packvslots(b, buf);
    1061              :     }
    1062            0 :     inlen = buf.size();
    1063            0 :     return compresseditinfo(buf.data(), buf.size(), outbuf, outlen);
    1064            0 : }
    1065              : 
    1066              : //used in iengine.h
    1067            0 : bool packundo(bool undo, int &inlen, uchar *&outbuf, int &outlen)
    1068              : {
    1069            0 :     if(undo)
    1070              :     {
    1071            0 :         return !undos.empty() && packundo(undos.back(), inlen, outbuf, outlen);
    1072              :     }
    1073              :     else
    1074              :     {
    1075            0 :         return !redos.empty() && packundo(redos.back(), inlen, outbuf, outlen);
    1076              :     }
    1077              : }
    1078              : 
    1079              : struct prefab final : editinfo
    1080              : {
    1081              :     std::string name;
    1082              :     GLuint ebo, vbo;
    1083              :     int numtris, numverts;
    1084              : 
    1085            0 :     prefab() : name(""), ebo(0), vbo(0), numtris(0), numverts(0) {}
    1086            0 :     ~prefab()
    1087              :     {
    1088            0 :         if(copy)
    1089              :         {
    1090            0 :             freeblock(copy);
    1091              :         }
    1092            0 :     }
    1093              : 
    1094            0 :     void cleanup()
    1095              :     {
    1096            0 :         if(ebo)
    1097              :         {
    1098            0 :             glDeleteBuffers(1, &ebo);
    1099            0 :             ebo = 0;
    1100              :         }
    1101            0 :         if(vbo)
    1102              :         {
    1103            0 :             glDeleteBuffers(1, &vbo);
    1104            0 :             vbo = 0;
    1105              :         }
    1106            0 :         numtris = numverts = 0;
    1107            0 :     }
    1108              : };
    1109              : 
    1110              : static std::unordered_map<std::string, prefab> prefabs;
    1111              : 
    1112            0 : void cleanupprefabs()
    1113              : {
    1114            0 :     for(auto &[k, i] : prefabs)
    1115              :     {
    1116            0 :         i.cleanup();
    1117              :     }
    1118            0 : }
    1119              : 
    1120            0 : void pasteundoblock(block3 *b, const uchar *g)
    1121              : {
    1122            0 :     const cube *s = b->c();
    1123            0 :     uint i = 0;
    1124            0 :     LOOP_XYZ(*b, 1<<std::min(static_cast<int>(*g++), rootworld.mapscale()-1), pastecube(s[i], c); i++; );
    1125            0 : }
    1126              : 
    1127              : /**
    1128              :  * @brief Unpacks an undocube into a uchar buffer
    1129              :  *
    1130              :  * used in client prefab unpacking, handles the octree unpacking (not the entities,
    1131              :  * which are game-dependent). Used in iengine.
    1132              :  *
    1133              :  * @param buf the buffer to unpack
    1134              :  * @param outbuf output buffer of the buf intput
    1135              :  */
    1136            0 : void unpackundocube(ucharbuf &buf, uchar *outbuf)
    1137              : {
    1138            0 :     block3 *b = nullptr;
    1139            0 :     if(!unpackblock(b, buf) || b->grid >= rootworld.mapsize() || buf.remaining() < b->size())
    1140              :     {
    1141            0 :         freeblock(b);
    1142            0 :         delete[] outbuf;
    1143            0 :         return;
    1144              :     }
    1145            0 :     uchar *g = buf.pad(b->size());
    1146            0 :     unpackvslots(*b, buf);
    1147            0 :     pasteundoblock(b, g);
    1148            0 :     rootworld.changed(*b, false);
    1149            0 :     freeblock(b);
    1150              : }
    1151              : 
    1152            0 : void makeundo(selinfo &s)
    1153              : {
    1154            0 :     undoblock *u = newundocube(s);
    1155            0 :     if(u)
    1156              :     {
    1157            0 :         addundo(u);
    1158              :     }
    1159            0 : }
    1160              : 
    1161            0 : void makeundo()                        // stores state of selected cubes before editing
    1162              : {
    1163            0 :     if(lastsel==sel || !sel.s)
    1164              :     {
    1165            0 :         return;
    1166              :     }
    1167            0 :     lastsel=sel;
    1168            0 :     makeundo(sel);
    1169              : }
    1170              : 
    1171            0 : void pasteblock(const block3 &b, selinfo &sel, bool local)
    1172              : {
    1173            0 :     sel.s = b.s;
    1174            0 :     int o = sel.orient;
    1175            0 :     sel.orient = b.orient;
    1176            0 :     const cube *s = b.getcube();
    1177            0 :     uint i = 0;
    1178            0 :     LOOP_SEL_XYZ(if(!(s[i].isempty()) || s[i].children || s[i].material != Mat_Air) pastecube(s[i], c); i++); // 'transparent'. old opaque by 'delcube; paste'
    1179            0 :     sel.orient = o;
    1180            0 : }
    1181              : 
    1182              : struct prefabheader final
    1183              : {
    1184              :     std::array<char, 4> magic;
    1185              :     int version;
    1186              : };
    1187              : 
    1188            0 : prefab *loadprefab(const char *name, bool msg = true)
    1189              : {
    1190            0 :     static std::unordered_map<std::string, prefab>::iterator itr = prefabs.find(name);
    1191            0 :     if(itr != prefabs.end())
    1192              :     {
    1193            0 :         return &(*itr).second;
    1194              :     }
    1195            0 :     DEF_FORMAT_STRING(filename, "media/prefab/%s.obr", name);
    1196            0 :     path(filename);
    1197            0 :     stream *f = opengzfile(filename, "rb");
    1198            0 :     if(!f)
    1199              :     {
    1200            0 :         if(msg)
    1201              :         {
    1202            0 :             conoutf(Console_Error, "could not read prefab %s", filename);
    1203              :         }
    1204            0 :         return nullptr;
    1205              :     }
    1206              :     prefabheader hdr;
    1207            0 :     if(f->read(&hdr, sizeof(hdr)) != sizeof(prefabheader) || std::memcmp(hdr.magic.data(), "OEBR", 4))
    1208              :     {
    1209            0 :         delete f;
    1210            0 :         if(msg)
    1211              :         {
    1212            0 :             conoutf(Console_Error, "prefab %s has malformatted header", filename);
    1213            0 :             return nullptr;
    1214              :         }
    1215              :     }
    1216            0 :     if(hdr.version != 0)
    1217              :     {
    1218            0 :         delete f;
    1219            0 :         if(msg)
    1220              :         {
    1221            0 :            conoutf(Console_Error, "prefab %s uses unsupported version", filename);
    1222            0 :            return nullptr;
    1223              :         }
    1224              :     }
    1225            0 :     streambuf<uchar> s(f);
    1226            0 :     block3 *copy = nullptr;
    1227            0 :     if(!unpackblock(copy, s))
    1228              :     {
    1229            0 :         delete f;
    1230            0 :         if(msg)
    1231              :         {
    1232            0 :             conoutf(Console_Error, "could not unpack prefab %s", filename);
    1233            0 :             return nullptr;
    1234              :         }
    1235              :     }
    1236            0 :     delete f;
    1237              : 
    1238            0 :     prefab *b = &(*prefabs.insert_or_assign(name, prefab()).first).second;
    1239            0 :     b->name = name ? name : "";
    1240            0 :     b->copy = copy;
    1241              : 
    1242            0 :     return b;
    1243              : }
    1244              : 
    1245              : class prefabmesh final
    1246              : {
    1247              :     public:
    1248              :         struct vertex final
    1249              :         {
    1250              :             vec pos;
    1251              :             vec4<uchar> norm;
    1252              :         };
    1253              : 
    1254              :         std::vector<vertex> verts;
    1255              :         std::vector<int> chain;
    1256              :         std::vector<ushort> tris;
    1257              : 
    1258            0 :         prefabmesh()
    1259            0 :         {
    1260            0 :             table.fill(-1);
    1261            0 :         }
    1262              : 
    1263            0 :         int addvert(const vec &pos, const bvec &norm)
    1264              :         {
    1265            0 :             vertex vtx;
    1266            0 :             vtx.pos = pos;
    1267            0 :             vtx.norm = norm;
    1268            0 :             return addvert(vtx);
    1269              :         }
    1270              : 
    1271            0 :         void setup(prefab &p)
    1272              :         {
    1273            0 :             if(tris.empty())
    1274              :             {
    1275            0 :                 return;
    1276              :             }
    1277            0 :             p.cleanup();
    1278              : 
    1279            0 :             for(size_t i = 0; i < verts.size(); i++)
    1280              :             {
    1281            0 :                 verts[i].norm.flip();
    1282              :             }
    1283            0 :             if(!p.vbo)
    1284              :             {
    1285            0 :                 glGenBuffers(1, &p.vbo);
    1286              :             }
    1287            0 :             gle::bindvbo(p.vbo);
    1288            0 :             glBufferData(GL_ARRAY_BUFFER, verts.size()*sizeof(vertex), verts.data(), GL_STATIC_DRAW);
    1289            0 :             gle::clearvbo();
    1290            0 :             p.numverts = verts.size();
    1291              : 
    1292            0 :             if(!p.ebo)
    1293              :             {
    1294            0 :                 glGenBuffers(1, &p.ebo);
    1295              :             }
    1296            0 :             gle::bindebo(p.ebo);
    1297            0 :             glBufferData(GL_ELEMENT_ARRAY_BUFFER, tris.size()*sizeof(ushort), tris.data(), GL_STATIC_DRAW);
    1298            0 :             gle::clearebo();
    1299            0 :             p.numtris = tris.size()/3;
    1300              :         }
    1301              :     private:
    1302              :         static constexpr int prefabmeshsize = 1<<9;
    1303              :         std::array<int, prefabmeshsize> table;
    1304            0 :         int addvert(const vertex &v)
    1305              :         {
    1306              :             auto vechash = std::hash<vec>();
    1307            0 :             uint h = vechash(v.pos)&(prefabmeshsize-1);
    1308            0 :             for(int i = table[h]; i>=0; i = chain[i])
    1309              :             {
    1310            0 :                 const vertex &c = verts[i];
    1311            0 :                 if(c.pos==v.pos && c.norm==v.norm)
    1312              :                 {
    1313            0 :                     return i;
    1314              :                 }
    1315              :             }
    1316            0 :             if(verts.size() >= USHRT_MAX)
    1317              :             {
    1318            0 :                 return -1;
    1319              :             }
    1320            0 :             verts.emplace_back(v);
    1321            0 :             chain.emplace_back(table[h]);
    1322            0 :             return table[h] = verts.size()-1;
    1323              :         }
    1324              : 
    1325              : };
    1326              : 
    1327            0 : static void genprefabmesh(prefabmesh &r, const cube &c, const ivec &co, int size)
    1328              : {
    1329              :     //recursively apply to children
    1330            0 :     if(c.children)
    1331              :     {
    1332            0 :         neighborstack[++neighbordepth] = &(*c.children)[0];
    1333            0 :         for(int i = 0; i < 8; ++i)
    1334              :         {
    1335            0 :             ivec o(i, co, size/2);
    1336            0 :             genprefabmesh(r, (*c.children)[i], o, size/2);
    1337              :         }
    1338            0 :         --neighbordepth;
    1339              :     }
    1340            0 :     else if(!(c.isempty()))
    1341              :     {
    1342              :         int vis;
    1343            0 :         for(int i = 0; i < 6; ++i) //for each face
    1344              :         {
    1345            0 :             if((vis = visibletris(c, i, co, size)))
    1346              :             {
    1347            0 :                 std::array<ivec, 4> v;
    1348            0 :                 genfaceverts(c, i, v);
    1349            0 :                 int convex = 0;
    1350            0 :                 if(!flataxisface(c, i))
    1351              :                 {
    1352            0 :                     convex = faceconvexity(v);
    1353              :                 }
    1354            0 :                 int order = vis&4 || convex < 0 ? 1 : 0, numverts = 0;
    1355            0 :                 vec vo(co);
    1356            0 :                 std::array<vec, 4> pos, norm;
    1357            0 :                 pos[numverts++] = vec(v[order]).mul(size/8.0f).add(vo);
    1358            0 :                 if(vis&1)
    1359              :                 {
    1360            0 :                     pos[numverts++] = vec(v[order+1]).mul(size/8.0f).add(vo);
    1361              :                 }
    1362            0 :                 pos[numverts++] = vec(v[order+2]).mul(size/8.0f).add(vo);
    1363            0 :                 if(vis&2)
    1364              :                 {
    1365            0 :                     pos[numverts++] = vec(v[(order+3)&3]).mul(size/8.0f).add(vo);
    1366              :                 }
    1367            0 :                 guessnormals(pos.data(), numverts, norm.data());
    1368              :                 std::array<int, 4> index;
    1369            0 :                 for(int j = 0; j < numverts; ++j)
    1370              :                 {
    1371            0 :                     index[j] = r.addvert(pos[j], bvec(norm[j]));
    1372              :                 }
    1373            0 :                 for(int j = 0; j < numverts-2; ++j)
    1374              :                 {
    1375            0 :                     if(index[0]!=index[j+1] && index[j+1]!=index[j+2] && index[j+2]!=index[0])
    1376              :                     {
    1377            0 :                         r.tris.emplace_back(index[0]);
    1378            0 :                         r.tris.emplace_back(index[j+1]);
    1379            0 :                         r.tris.emplace_back(index[j+2]);
    1380              :                     }
    1381              :                 }
    1382              :             }
    1383              :         }
    1384              :     }
    1385            0 : }
    1386              : 
    1387            0 : void cubeworld::genprefabmesh(prefab &p)
    1388              : {
    1389            0 :     block3 b = *p.copy;
    1390            0 :     b.o = ivec(0, 0, 0);
    1391              : 
    1392            0 :     std::array<cube, 8> *oldworldroot = worldroot;
    1393            0 :     int oldworldscale = worldscale;
    1394              : 
    1395            0 :     worldroot = newcubes();
    1396            0 :     worldscale = 1;
    1397            0 :     while(mapscale() < std::max(std::max(b.s.x, b.s.y), b.s.z)*b.grid)
    1398              :     {
    1399            0 :         worldscale++;
    1400              :     }
    1401              : 
    1402            0 :     cube *s = p.copy->c();
    1403            0 :     uint i = 0;
    1404            0 :     LOOP_XYZ(b, b.grid, if(!(s[i].isempty()) || s[i].children) pastecube(s[i], c); i++);
    1405              : 
    1406            0 :     prefabmesh r;
    1407            0 :     neighborstack[++neighbordepth] = &(*worldroot)[0];
    1408              :     //recursively apply to children
    1409            0 :     for(int i = 0; i < 8; ++i)
    1410              :     {
    1411            0 :         ::genprefabmesh(r, (*worldroot)[i], ivec(i, ivec(0, 0, 0), mapsize()/2), mapsize()/2);
    1412              :     }
    1413            0 :     --neighbordepth;
    1414            0 :     r.setup(p);
    1415              : 
    1416            0 :     freeocta(worldroot);
    1417              : 
    1418            0 :     worldroot = oldworldroot;
    1419            0 :     worldscale = oldworldscale;
    1420              : 
    1421            0 :     useshaderbyname("prefab");
    1422            0 : }
    1423              : 
    1424            0 : static void renderprefab(prefab &p, const vec &o, float yaw, float pitch, float roll, float size, const vec &color)
    1425              : {
    1426            0 :     if(!p.numtris)
    1427              :     {
    1428            0 :         rootworld.genprefabmesh(p);
    1429            0 :         if(!p.numtris)
    1430              :         {
    1431            0 :             return;
    1432              :         }
    1433              :     }
    1434              : 
    1435            0 :     block3 &b = *p.copy;
    1436              : 
    1437            0 :     matrix4 m;
    1438            0 :     m.identity();
    1439            0 :     m.settranslation(o);
    1440            0 :     if(yaw)
    1441              :     {
    1442            0 :         m.rotate_around_z(yaw/RAD);
    1443              :     }
    1444            0 :     if(pitch)
    1445              :     {
    1446            0 :         m.rotate_around_x(pitch/RAD);
    1447              :     }
    1448            0 :     if(roll)
    1449              :     {
    1450            0 :         m.rotate_around_y(-roll/RAD);
    1451              :     }
    1452            0 :     matrix3 w(m);
    1453            0 :     if(size > 0 && size != 1)
    1454              :     {
    1455            0 :         m.scale(size);
    1456              :     }
    1457            0 :     m.translate(vec(b.s).mul(-b.grid*0.5f));
    1458              : 
    1459            0 :     gle::bindvbo(p.vbo);
    1460            0 :     gle::bindebo(p.ebo);
    1461            0 :     gle::enablevertex();
    1462            0 :     gle::enablenormal();
    1463            0 :     prefabmesh::vertex *v = nullptr;
    1464            0 :     gle::vertexpointer(sizeof(prefabmesh::vertex), v->pos.data());
    1465            0 :     gle::normalpointer(sizeof(prefabmesh::vertex), v->norm.data(), GL_BYTE);
    1466              : 
    1467            0 :     matrix4 pm;
    1468            0 :     pm.mul(camprojmatrix, m);
    1469            0 :     GLOBALPARAM(prefabmatrix, pm);
    1470            0 :     GLOBALPARAM(prefabworld, w);
    1471            0 :     SETSHADER(prefab);
    1472            0 :     gle::color(vec(color).mul(ldrscale));
    1473            0 :     glDrawRangeElements(GL_TRIANGLES, 0, p.numverts-1, p.numtris*3, GL_UNSIGNED_SHORT, nullptr);
    1474              : 
    1475            0 :     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    1476            0 :     enablepolygonoffset(GL_POLYGON_OFFSET_LINE);
    1477              : 
    1478            0 :     pm.mul(camprojmatrix, m);
    1479            0 :     GLOBALPARAM(prefabmatrix, pm);
    1480            0 :     SETSHADER(prefab);
    1481            0 :     gle::color((outlinecolor).tocolor().mul(ldrscale));
    1482            0 :     glDrawRangeElements(GL_TRIANGLES, 0, p.numverts-1, p.numtris*3, GL_UNSIGNED_SHORT, nullptr);
    1483              : 
    1484            0 :     disablepolygonoffset(GL_POLYGON_OFFSET_LINE);
    1485            0 :     glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    1486              : 
    1487            0 :     gle::disablevertex();
    1488            0 :     gle::disablenormal();
    1489            0 :     gle::clearebo();
    1490            0 :     gle::clearvbo();
    1491              : }
    1492              : 
    1493            0 : void renderprefab(const char *name, const vec &o, float yaw, float pitch, float roll, float size, const vec &color)
    1494              : {
    1495            0 :     prefab *p = loadprefab(name, false);
    1496            0 :     if(p)
    1497              :     {
    1498            0 :         renderprefab(*p, o, yaw, pitch, roll, size, color);
    1499              :     }
    1500            0 : }
    1501              : 
    1502            0 : void previewprefab(const char *name, const vec &color)
    1503              : {
    1504            0 :     prefab *p = loadprefab(name, false);
    1505            0 :     if(p)
    1506              :     {
    1507            0 :         block3 &b = *p->copy;
    1508              :         float yaw;
    1509            0 :         vec o = calcmodelpreviewpos(vec(b.s).mul(b.grid*0.5f), yaw);
    1510            0 :         renderprefab(*p, o, yaw, 0, 0, 1, color);
    1511              :     }
    1512            0 : }
    1513              : 
    1514              : std::vector<int *> editingvslots;
    1515              : 
    1516            0 : void compacteditvslots()
    1517              : {
    1518            0 :     for(size_t i = 0; i < editingvslots.size(); i++)
    1519              :     {
    1520            0 :         if(*editingvslots[i])
    1521              :         {
    1522            0 :             compactvslot(*editingvslots[i]);
    1523              :         }
    1524              :     }
    1525            0 :     for(size_t i = 0; i < unpackingvslots.size(); i++)
    1526              :     {
    1527            0 :         compactvslot(*unpackingvslots[i].vslot);
    1528              :     }
    1529            0 :     for(size_t i = 0; i < editinfos.size(); i++)
    1530              :     {
    1531            0 :         const editinfo *e = editinfos[i];
    1532            0 :         compactvslots(e->copy->c(), e->copy->size());
    1533              :     }
    1534            0 :     for(undoblock *u : undos)
    1535              :     {
    1536            0 :         if(!u->numents)
    1537              :         {
    1538            0 :             compactvslots(u->block()->c(), u->block()->size());
    1539              :         }
    1540              :     }
    1541            0 :     for(undoblock *u : redos)
    1542              :     {
    1543            0 :         if(!u->numents)
    1544              :         {
    1545            0 :             compactvslots(u->block()->c(), u->block()->size());
    1546              :         }
    1547              :     }
    1548            0 : }
    1549              : 
    1550              : ///////////// height maps ////////////////
    1551              : 
    1552            0 : ushort getmaterial(const cube &c)
    1553              : {
    1554            0 :     if(c.children)
    1555              :     {
    1556            0 :         ushort mat = getmaterial((*c.children)[7]);
    1557            0 :         for(int i = 0; i < 7; ++i)
    1558              :         {
    1559            0 :             if(mat != getmaterial((*c.children)[i]))
    1560              :             {
    1561            0 :                 return Mat_Air;
    1562              :             }
    1563              :         }
    1564            0 :         return mat;
    1565              :     }
    1566            0 :     return c.material;
    1567              : }
    1568              : 
    1569              : /////////// texture editing //////////////////
    1570              : 
    1571              : int curtexindex = -1;
    1572              : std::vector<ushort> texmru;
    1573              : 
    1574              : int reptex = -1;
    1575              : 
    1576            0 : static VSlot *remapvslot(int index, bool delta, const VSlot &ds)
    1577              : {
    1578            0 :     for(vslotmap &v : remappedvslots)
    1579              :     {
    1580            0 :         if(v.index == index)
    1581              :         {
    1582            0 :             return v.vslot;
    1583              :         }
    1584              :     }
    1585            0 :     VSlot &vs = lookupvslot(index, false);
    1586            0 :     if(vs.index < 0 || vs.index == Default_Sky)
    1587              :     {
    1588            0 :         return nullptr;
    1589              :     }
    1590            0 :     VSlot *edit = nullptr;
    1591            0 :     if(delta)
    1592              :     {
    1593            0 :         VSlot ms;
    1594            0 :         mergevslot(ms, vs, ds);
    1595            0 :         edit = ms.changed ? editvslot(vs, ms) : vs.slot->variants;
    1596            0 :     }
    1597              :     else
    1598              :     {
    1599            0 :         edit = ds.changed ? editvslot(vs, ds) : vs.slot->variants;
    1600              :     }
    1601            0 :     if(!edit)
    1602              :     {
    1603            0 :         edit = &vs;
    1604              :     }
    1605            0 :     remappedvslots.emplace_back(vslotmap(vs.index, edit));
    1606            0 :     return edit;
    1607              : }
    1608              : 
    1609            0 : void remapvslots(cube &c, bool delta, const VSlot &ds, int orientation, bool &findrep, VSlot *&findedit)
    1610              : {
    1611              :     //recursively apply to children
    1612            0 :     if(c.children)
    1613              :     {
    1614            0 :         for(int i = 0; i < 8; ++i)
    1615              :         {
    1616            0 :             remapvslots((*c.children)[i], delta, ds, orientation, findrep, findedit);
    1617              :         }
    1618            0 :         return;
    1619              :     }
    1620            0 :     static VSlot ms;
    1621            0 :     if(orientation<0)
    1622              :     {
    1623            0 :         for(int i = 0; i < 6; ++i) //for each face
    1624              :         {
    1625            0 :             VSlot *edit = remapvslot(c.texture[i], delta, ds);
    1626            0 :             if(edit)
    1627              :             {
    1628            0 :                 c.texture[i] = edit->index;
    1629            0 :                 if(!findedit)
    1630              :                 {
    1631            0 :                     findedit = edit;
    1632              :                 }
    1633              :             }
    1634              :         }
    1635              :     }
    1636              :     else
    1637              :     {
    1638            0 :         int i = visibleorient(c, orientation);
    1639            0 :         VSlot *edit = remapvslot(c.texture[i], delta, ds);
    1640            0 :         if(edit)
    1641              :         {
    1642            0 :             if(findrep)
    1643              :             {
    1644            0 :                 if(reptex < 0)
    1645              :                 {
    1646            0 :                     reptex = c.texture[i];
    1647              :                 }
    1648            0 :                 else if(reptex != c.texture[i])
    1649              :                 {
    1650            0 :                     findrep = false;
    1651              :                 }
    1652              :             }
    1653            0 :             c.texture[i] = edit->index;
    1654            0 :             if(!findedit)
    1655              :             {
    1656            0 :                 findedit = edit;
    1657              :             }
    1658              :         }
    1659              :     }
    1660              : }
    1661              : 
    1662            0 : void compactmruvslots()
    1663              : {
    1664              :     static int lasttex = 0;
    1665            0 :     remappedvslots.clear();
    1666            0 :     for(int i = static_cast<int>(texmru.size()); --i >=0;) //note reverse iteration
    1667              :     {
    1668            0 :         if(vslots.size() > texmru[i])
    1669              :         {
    1670            0 :             VSlot &vs = *vslots[texmru[i]];
    1671            0 :             if(vs.index >= 0)
    1672              :             {
    1673            0 :                 texmru[i] = vs.index;
    1674            0 :                 continue;
    1675              :             }
    1676              :         }
    1677            0 :         if(curtexindex > i)
    1678              :         {
    1679            0 :             curtexindex--;
    1680              :         }
    1681            0 :         else if(curtexindex == i)
    1682              :         {
    1683            0 :             curtexindex = -1;
    1684              :         }
    1685            0 :         texmru.erase(texmru.begin() + i);
    1686              :     }
    1687            0 :     if(vslots.size() > static_cast<size_t>(lasttex))
    1688              :     {
    1689            0 :         VSlot &vs = *vslots[lasttex];
    1690            0 :         lasttex = vs.index >= 0 ? vs.index : 0;
    1691              :     }
    1692              :     else
    1693              :     {
    1694            0 :         lasttex = 0;
    1695              :     }
    1696            0 :     reptex = (vslots.size() > static_cast<size_t>(reptex)) ? vslots[reptex]->index : -1;
    1697            0 : }
    1698              : 
    1699            0 : void edittexcube(cube &c, int tex, int orientation, bool &findrep)
    1700              : {
    1701            0 :     if(orientation<0)
    1702              :     {
    1703            0 :         for(int i = 0; i < 6; ++i) //for each face
    1704              :         {
    1705            0 :             c.texture[i] = tex;
    1706              :         }
    1707              :     }
    1708              :     else
    1709              :     {
    1710            0 :         int i = visibleorient(c, orientation);
    1711            0 :         if(findrep)
    1712              :         {
    1713            0 :             if(reptex < 0)
    1714              :             {
    1715            0 :                 reptex = c.texture[i];
    1716              :             }
    1717            0 :             else if(reptex != c.texture[i])
    1718              :             {
    1719            0 :                 findrep = false;
    1720              :             }
    1721              :         }
    1722            0 :         c.texture[i] = tex;
    1723              :     }
    1724              :     //recursively apply to children
    1725            0 :     if(c.children)
    1726              :     {
    1727            0 :         for(int i = 0; i < 8; ++i)
    1728              :         {
    1729            0 :             edittexcube((*c.children)[i], tex, orientation, findrep);
    1730              :         }
    1731              :     }
    1732            0 : }
    1733              : 
    1734              : /**
    1735              :  * @brief Sets a cube's materials, given a material & filter to use
    1736              :  *
    1737              :  * @param c the cube object to use
    1738              :  * @param mat material index to apply
    1739              :  * @param matmask material mask
    1740              :  * @param filtermat if nonzero, determines what existing mats to apply to
    1741              :  * @param filtermask filter material mask
    1742              :  * @param filtergeom type of geometry inside the cube (empty, solid, partially solid)
    1743              :  */
    1744            0 : void cube::setmat(ushort mat, ushort matmask, ushort filtermat, ushort filtermask, int filtergeom)
    1745              : {
    1746              :     //recursively sets material for all child nodes
    1747            0 :     if(children)
    1748              :     {
    1749            0 :         for(int i = 0; i < 8; ++i)
    1750              :         {
    1751            0 :             (*children)[i].setmat( mat, matmask, filtermat, filtermask, filtergeom);
    1752              :         }
    1753              :     }
    1754            0 :     else if((material&filtermask) == filtermat)
    1755              :     {
    1756            0 :         switch(filtergeom)
    1757              :         {
    1758            0 :             case EditMatFlag_Empty:
    1759              :             {
    1760            0 :                 if(isempty())
    1761              :                 {
    1762            0 :                     break;
    1763              :                 }
    1764            0 :                 return;
    1765              :             }
    1766            0 :             case EditMatFlag_NotEmpty:
    1767              :             {
    1768            0 :                 if(!(isempty()))
    1769              :                 {
    1770            0 :                     break;
    1771              :                 }
    1772            0 :                 return;
    1773              :             }
    1774            0 :             case EditMatFlag_Solid:
    1775              :             {
    1776            0 :                 if(issolid())
    1777              :                 {
    1778            0 :                     break;
    1779              :                 }
    1780            0 :                 return;
    1781              :             }
    1782            0 :             case EditMatFlag_NotSolid:
    1783              :             {
    1784            0 :                 if(!(issolid()))
    1785              :                 {
    1786            0 :                     break;
    1787              :                 }
    1788            0 :                 return;
    1789              :             }
    1790              :         }
    1791            0 :         if(mat!=Mat_Air)
    1792              :         {
    1793            0 :             material &= matmask;
    1794            0 :             material |= mat;
    1795              :         }
    1796              :         else
    1797              :         {
    1798            0 :             material = Mat_Air;
    1799              :         }
    1800              :     }
    1801              : }
    1802              : 
    1803            0 : void rendertexturepanel(int w, int h)
    1804              : {
    1805              :     static int texpaneltimer = 0;
    1806            0 :     if((texpaneltimer -= curtime)>0 && editmode)
    1807              :     {
    1808            0 :         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    1809              : 
    1810            0 :         pushhudmatrix();
    1811            0 :         hudmatrix.scale(h/1800.0f, h/1800.0f, 1);
    1812            0 :         flushhudmatrix(false);
    1813            0 :         SETSHADER(hudrgb);
    1814            0 :         int y = 50,
    1815            0 :             gap = 10;
    1816            0 :         gle::defvertex(2);
    1817            0 :         gle::deftexcoord0();
    1818              : 
    1819            0 :         for(int i = 0; i < 7; ++i)
    1820              :         {
    1821            0 :             int s = (i == 3 ? 285 : 220),
    1822            0 :                 ti = curtexindex+i-3;
    1823            0 :             if(static_cast<int>(texmru.size()) > ti)
    1824              :             {
    1825            0 :                 VSlot &vslot = lookupvslot(texmru[ti]);
    1826            0 :                 Slot &slot = *vslot.slot;
    1827            0 :                 Texture *tex = slot.sts.empty() ? notexture : slot.sts[0].t,
    1828            0 :                         *glowtex = nullptr;
    1829            0 :                 if(slot.texmask&(1 << Tex_Glow))
    1830              :                 {
    1831            0 :                     for(const Slot::Tex &t : slot.sts)
    1832              :                     {
    1833            0 :                         if(t.type == Tex_Glow)
    1834              :                         {
    1835            0 :                             glowtex = t.t;
    1836            0 :                             break;
    1837              :                         }
    1838              :                     }
    1839              :                 }
    1840            0 :                 float sx = std::min(1.0f, tex->xs/static_cast<float>(tex->ys)),
    1841            0 :                       sy = std::min(1.0f, tex->ys/static_cast<float>(tex->xs));
    1842            0 :                 int x = w*1800/h-s-50,
    1843            0 :                     r = s;
    1844            0 :                 std::array<vec2, 4> tc = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) };
    1845            0 :                 float xoff = vslot.offset.x(),
    1846            0 :                       yoff = vslot.offset.y();
    1847            0 :                 if(vslot.rotation)
    1848              :                 {
    1849            0 :                     const TexRotation &rot = texrotations[vslot.rotation];
    1850            0 :                     if(rot.swapxy)
    1851              :                     {
    1852            0 :                         std::swap(xoff, yoff);
    1853            0 :                         for(int k = 0; k < 4; ++k)
    1854              :                         {
    1855            0 :                             std::swap(tc[k].x, tc[k].y);
    1856              :                         }
    1857              :                     }
    1858            0 :                     if(rot.flipx)
    1859              :                     {
    1860            0 :                         xoff *= -1;
    1861            0 :                         for(int k = 0; k < 4; ++k)
    1862              :                         {
    1863            0 :                             tc[k].x *= -1;
    1864              :                         }
    1865              :                     }
    1866            0 :                     if(rot.flipy)
    1867              :                     {
    1868            0 :                         yoff *= -1;
    1869            0 :                         for(int k = 0; k < 4; ++k)
    1870              :                         {
    1871            0 :                             tc[k].y *= -1;
    1872              :                         }
    1873              :                     }
    1874              :                 }
    1875            0 :                 for(int k = 0; k < 4; ++k)
    1876              :                 {
    1877            0 :                     tc[k].x = tc[k].x/sx - xoff/tex->xs;
    1878            0 :                     tc[k].y = tc[k].y/sy - yoff/tex->ys;
    1879              :                 }
    1880            0 :                 glBindTexture(GL_TEXTURE_2D, tex->id);
    1881            0 :                 for(int j = 0; j < (glowtex ? 3 : 2); ++j)
    1882              :                 {
    1883            0 :                     if(j < 2)
    1884              :                     {
    1885            0 :                         gle::color(vec(vslot.colorscale).mul(j), texpaneltimer/1000.0f);
    1886              :                     }
    1887              :                     else
    1888              :                     {
    1889            0 :                         glBindTexture(GL_TEXTURE_2D, glowtex->id);
    1890            0 :                         glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    1891            0 :                         gle::color(vslot.glowcolor, texpaneltimer/1000.0f);
    1892              :                     }
    1893            0 :                     gle::begin(GL_TRIANGLE_STRIP);
    1894            0 :                     gle::attribf(x,   y);   gle::attrib(tc[0]);
    1895            0 :                     gle::attribf(x+r, y);   gle::attrib(tc[1]);
    1896            0 :                     gle::attribf(x,   y+r); gle::attrib(tc[3]);
    1897            0 :                     gle::attribf(x+r, y+r); gle::attrib(tc[2]);
    1898            0 :                     xtraverts += gle::end();
    1899            0 :                     if(!j)
    1900              :                     {
    1901            0 :                         r -= 10;
    1902            0 :                         x += 5;
    1903            0 :                         y += 5;
    1904              :                     }
    1905            0 :                     else if(j == 2)
    1906              :                     {
    1907            0 :                         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
    1908              :                     }
    1909              :                 }
    1910              :             }
    1911            0 :             y += s+gap;
    1912              :         }
    1913              : 
    1914            0 :         pophudmatrix(true, false);
    1915            0 :         resethudshader();
    1916              :     }
    1917            0 : }
    1918              : 
    1919            0 : static void pushedge(uchar &edge, int dir, int dc)
    1920              : {
    1921            0 :     int ne = std::clamp(EDGE_GET(edge, dc)+dir, 0, 8);
    1922            0 :     EDGE_SET(edge, dc, ne);
    1923            0 :     int oe = EDGE_GET(edge, 1-dc);
    1924            0 :     if((dir<0 && dc && oe>ne) || (dir>0 && dc==0 && oe<ne))
    1925              :     {
    1926            0 :         EDGE_SET(edge, 1-dc, ne);
    1927              :     }
    1928            0 : }
    1929              : 
    1930              : //used in iengine
    1931            0 : void linkedpush(cube &c, int d, int x, int y, int dc, int dir)
    1932              : {
    1933            0 :     ivec v, p;
    1934            0 :     getcubevector(c, d, x, y, dc, v);
    1935              : 
    1936            0 :     for(int i = 0; i < 2; ++i)
    1937              :     {
    1938            0 :         for(int j = 0; j < 2; ++j)
    1939              :         {
    1940            0 :             getcubevector(c, d, i, j, dc, p);
    1941            0 :             if(v==p)
    1942              :             {
    1943            0 :                 pushedge(CUBE_EDGE(c, d, i, j), dir, dc);
    1944              :             }
    1945              :         }
    1946              :     }
    1947            0 : }
    1948              : 
    1949            1 : void initoctaeditcmds()
    1950              : {
    1951              :     //some of these commands use code only needed for the command itself, so
    1952              :     //they are declared as lambdas inside the local scope
    1953              : 
    1954              :     //others use functions in the global namespace, which are implemented elsewhere
    1955              :     //in this file
    1956              : 
    1957              :     //static to make sure that these lambdas have constant location in memory for identmap to look up
    1958            1 :     static auto movingcmd = [] (const int *n) -> void
    1959              :     {
    1960            1 :         if(*n >= 0)
    1961              :         {
    1962            0 :             if(!*n || (moving<=1 && !pointinsel(sel, vec(cur).add(1))))
    1963              :             {
    1964            0 :                 moving = 0;
    1965              :             }
    1966            0 :             else if(!moving)
    1967              :             {
    1968            0 :                 moving = 1;
    1969              :             }
    1970              :         }
    1971            1 :         intret(moving);
    1972            1 :     };
    1973              :     //unary + operator converts to function pointer
    1974            1 :     addcommand("moving",        reinterpret_cast<identfun>(+movingcmd), "b", Id_Command);
    1975              : 
    1976            1 :     addcommand("entcancel",     reinterpret_cast<identfun>(entcancel), "", Id_Command); ///
    1977            1 :     addcommand("cubecancel",    reinterpret_cast<identfun>(cubecancel), "", Id_Command); ///
    1978            1 :     addcommand("cancelsel",     reinterpret_cast<identfun>(cancelsel), "", Id_Command); ///
    1979            1 :     addcommand("reorient",      reinterpret_cast<identfun>(reorient), "", Id_Command); ///
    1980              : 
    1981            1 :     static auto selextend = [] () -> void
    1982              :     {
    1983            1 :         if(noedit(true))
    1984              :         {
    1985            1 :             return;
    1986              :         }
    1987            0 :         for(int i = 0; i < 3; ++i)
    1988              :         {
    1989            0 :             if(cur[i]<sel.o[i])
    1990              :             {
    1991            0 :                 sel.s[i] += (sel.o[i]-cur[i])/sel.grid;
    1992            0 :                 sel.o[i] = cur[i];
    1993              :             }
    1994            0 :             else if(cur[i]>=sel.o[i]+sel.s[i]*sel.grid)
    1995              :             {
    1996            0 :                 sel.s[i] = (cur[i]-sel.o[i])/sel.grid+1;
    1997              :             }
    1998              :         }
    1999              :     };
    2000            1 :     addcommand("selextend",     reinterpret_cast<identfun>(+selextend), "", Id_Command);
    2001              : 
    2002            1 :     static auto selmoved = [] () -> void
    2003              :     {
    2004            1 :         if(noedit(true))
    2005              :         {
    2006            1 :             return;
    2007              :         }
    2008            0 :         intret(sel.o != savedsel.o ? 1 : 0);
    2009              :     };
    2010              : 
    2011            1 :     static auto selsave = [] () -> void
    2012              :     {
    2013            1 :         if(noedit(true))
    2014              :         {
    2015            1 :             return;
    2016              :         }
    2017            0 :         savedsel = sel;
    2018              :     };
    2019              : 
    2020            1 :     static auto selrestore = [] () -> void
    2021              :     {
    2022            1 :         if(noedit(true))
    2023              :         {
    2024            1 :             return;
    2025              :         }
    2026            0 :         sel = savedsel;
    2027              :     };
    2028              : 
    2029            1 :     static auto selswap = [] () -> void
    2030              :     {
    2031            1 :         if(noedit(true))
    2032              :         {
    2033            1 :             return;
    2034              :         }
    2035            0 :         std::swap(sel, savedsel);
    2036              :     };
    2037              : 
    2038            1 :     addcommand("selmoved",      reinterpret_cast<identfun>(+selmoved), "", Id_Command);
    2039            1 :     addcommand("selsave",       reinterpret_cast<identfun>(+selsave), "", Id_Command);
    2040            1 :     addcommand("selrestore",    reinterpret_cast<identfun>(+selrestore), "", Id_Command);
    2041            1 :     addcommand("selswap",       reinterpret_cast<identfun>(+selswap), "", Id_Command);
    2042              : 
    2043            1 :     static auto haveselcmd = [] () -> void
    2044              :     {
    2045            1 :         intret(havesel ? selchildcount : 0);
    2046            1 :     };
    2047              : 
    2048            1 :     addcommand("havesel",       reinterpret_cast<identfun>(+haveselcmd), "", Id_Command);
    2049              : 
    2050              : 
    2051            1 :     static auto selchildcountcmd = [] () -> void
    2052              :     {
    2053            1 :         if(selchildcount < 0)
    2054              :         {
    2055            0 :             result(tempformatstring("1/%d", -selchildcount));
    2056              :         }
    2057              :         else
    2058              :         {
    2059            1 :             intret(selchildcount);
    2060              :         }
    2061            1 :     };
    2062            1 :     addcommand("selchildnum", reinterpret_cast<identfun>(+selchildcountcmd), "", Id_Command);
    2063              : 
    2064              : 
    2065            1 :     static auto selchildmatcmd = [] (const char *prefix) -> void
    2066              :     {
    2067            1 :         if(selchildmat > 0)
    2068              :         {
    2069            0 :             result(getmaterialdesc(selchildmat, prefix));
    2070              :         }
    2071            1 :     };
    2072            1 :     addcommand("selchildmat",   reinterpret_cast<identfun>(+selchildmatcmd), "s", Id_Command);
    2073              : 
    2074            1 :     static auto clearundos = [] () -> void
    2075              :     {
    2076            1 :         pruneundos(0);
    2077            1 :     };
    2078            1 :     addcommand("clearundos",    reinterpret_cast<identfun>(+clearundos), "", Id_Command); //run pruneundos but with a cache size of zero
    2079              : 
    2080            1 :     static auto delprefab = [] (const char *name) -> void
    2081              :     {
    2082            3 :         static std::unordered_map<std::string, prefab>::iterator itr = prefabs.find(name);
    2083            1 :         if(itr != prefabs.end())
    2084              :         {
    2085            0 :             (*itr).second.cleanup();
    2086            0 :             prefabs.erase(name);
    2087            0 :             conoutf("deleted prefab %s", name);
    2088              :         }
    2089              :         else
    2090              :         {
    2091            1 :             conoutf("no such prefab %s", name);
    2092              :         }
    2093            1 :     };
    2094            1 :     addcommand("delprefab",     reinterpret_cast<identfun>(+delprefab), "s", Id_Command);
    2095              : 
    2096              :     /** @brief Saves the current selection to a prefab file.
    2097              :      *
    2098              :      * Using the global variables for selection information, writes the current selection
    2099              :      * to a prefab file with the given name. Does not save slot information, so pasting
    2100              :      * into a map with a different texture slot list will result in meaningless textures.
    2101              :      *
    2102              :      * @param name a string containing the name of the prefab to save (sans file type)
    2103              :      *
    2104              :      */
    2105            1 :     static auto saveprefab = [] (const char *name) -> void
    2106              :     {
    2107            1 :         if(!name[0] || noedit(true) || (nompedit && multiplayer))
    2108              :         {
    2109            1 :             multiplayerwarn();
    2110            1 :             return;
    2111              :         }
    2112            0 :         static std::unordered_map<std::string, prefab>::iterator itr = prefabs.find(name);
    2113            0 :         prefab *b = nullptr;
    2114            0 :         if(itr == prefabs.end())
    2115              :         {
    2116            0 :             b = &(*prefabs.insert( { std::string(name), prefab() } ).first).second;
    2117            0 :             b->name = newstring(name);
    2118            0 :             b->name = name ? name : "";
    2119              :         }
    2120              :         else
    2121              :         {
    2122            0 :             b = &(*itr).second;
    2123              :         }
    2124            0 :         if(b->copy)
    2125              :         {
    2126            0 :             freeblock(b->copy);
    2127              :         }
    2128            0 :         PROTECT_SEL(b->copy = blockcopy(block3(sel), sel.grid));
    2129            0 :         rootworld.changed(sel);
    2130            0 :         DEF_FORMAT_STRING(filename, "media/prefab/%s.obr", name);
    2131            0 :         path(filename);
    2132            0 :         stream *f = opengzfile(filename, "wb");
    2133            0 :         if(!f)
    2134              :         {
    2135            0 :             conoutf(Console_Error, "could not write prefab to %s", filename);
    2136            0 :             return;
    2137              :         }
    2138              :         prefabheader hdr;
    2139            0 :         std::string headermagic = "OEBR";
    2140            0 :         std::copy(headermagic.begin(), headermagic.end(), hdr.magic.begin());
    2141            0 :         hdr.version = 0;
    2142            0 :         f->write(&hdr, sizeof(hdr));
    2143            0 :         streambuf<uchar> s(f);
    2144            0 :         if(!packblock(*b->copy, s))
    2145              :         {
    2146            0 :             delete f;
    2147            0 :             conoutf(Console_Error, "could not pack prefab %s", filename);
    2148            0 :             return;
    2149              :         }
    2150            0 :         delete f;
    2151            0 :         conoutf("wrote prefab file %s", filename);
    2152            0 :     };
    2153            1 :     addcommand("saveprefab",    reinterpret_cast<identfun>(+saveprefab), "s", Id_Command);
    2154              : 
    2155            1 :     static auto pasteprefab = [] (const char *name) -> void
    2156              :     {
    2157            1 :         if(!name[0] || noedit() || (nompedit && multiplayer))
    2158              :         {
    2159            1 :             multiplayerwarn();
    2160            1 :             return;
    2161              :         }
    2162            0 :         prefab *b = loadprefab(name, true);
    2163            0 :         if(b)
    2164              :         {
    2165            0 :             pasteblock(*b->copy, sel, true);
    2166              :         }
    2167              :     };
    2168            1 :     addcommand("pasteprefab",   reinterpret_cast<identfun>(+pasteprefab), "s", Id_Command);
    2169              : 
    2170              :     //defines editing readonly variables, useful for the HUD
    2171              :     #define EDITSTAT(name, val) \
    2172              :         static auto name = [] ()  -> void\
    2173              :         { \
    2174              :             static int laststat = 0; \
    2175              :             static int prevstat = 0; \
    2176              :             static int curstat = 0; \
    2177              :             if(totalmillis - laststat >= statrate) \
    2178              :             { \
    2179              :                 prevstat = curstat; \
    2180              :                 laststat = totalmillis - (totalmillis%statrate); \
    2181              :             } \
    2182              :             if(prevstat == curstat) curstat = (val); \
    2183              :             intret(curstat); \
    2184              :         }; \
    2185              :         addcommand(#name, reinterpret_cast<identfun>(+name), "", Id_Command);
    2186              : 
    2187            2 :     EDITSTAT(wtr, wtris);
    2188            2 :     EDITSTAT(vtr, (vtris*100)/std::max(wtris, 1));
    2189            2 :     EDITSTAT(wvt, wverts);
    2190            2 :     EDITSTAT(vvt, (vverts*100)/std::max(wverts, 1));
    2191            2 :     EDITSTAT(evt, xtraverts);
    2192            2 :     EDITSTAT(eva, xtravertsva);
    2193            2 :     EDITSTAT(octa, allocnodes*8);
    2194            2 :     EDITSTAT(va, allocva);
    2195            2 :     EDITSTAT(gldes, glde);
    2196            2 :     EDITSTAT(geombatch, gbatches);
    2197            2 :     EDITSTAT(oq, occlusionengine.getnumqueries());
    2198              : 
    2199              :     #undef EDITSTAT
    2200            1 : }
        

Generated by: LCOV version 2.0-1