1 #include "engine.h"
2 
3 extern int outline;
4 
5 bool boxoutline = false;
6 
boxs(int orient,vec o,const vec & s,float size)7 void boxs(int orient, vec o, const vec &s, float size)
8 {
9     int d = dimension(orient), dc = dimcoord(orient);
10     float f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0;
11     o[D[d]] += dc * s[D[d]] + f;
12 
13     vec r(0, 0, 0), c(0, 0, 0);
14     r[R[d]] = s[R[d]];
15     c[C[d]] = s[C[d]];
16 
17     vec v1 = o, v2 = vec(o).add(r), v3 = vec(o).add(r).add(c), v4 = vec(o).add(c);
18 
19     r[R[d]] = 0.5f*size;
20     c[C[d]] = 0.5f*size;
21 
22     gle::defvertex();
23     gle::begin(GL_TRIANGLE_STRIP);
24     gle::attrib(vec(v1).sub(r).sub(c));
25         gle::attrib(vec(v1).add(r).add(c));
26     gle::attrib(vec(v2).add(r).sub(c));
27         gle::attrib(vec(v2).sub(r).add(c));
28     gle::attrib(vec(v3).add(r).add(c));
29         gle::attrib(vec(v3).sub(r).sub(c));
30     gle::attrib(vec(v4).sub(r).add(c));
31         gle::attrib(vec(v4).add(r).sub(c));
32     gle::attrib(vec(v1).sub(r).sub(c));
33         gle::attrib(vec(v1).add(r).add(c));
34     xtraverts += gle::end();
35 }
36 
boxs(int orient,vec o,const vec & s)37 void boxs(int orient, vec o, const vec &s)
38 {
39     int d = dimension(orient), dc = dimcoord(orient);
40     float f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0;
41     o[D[d]] += dc * s[D[d]] + f;
42 
43     gle::defvertex();
44     gle::begin(GL_LINE_LOOP);
45 
46     gle::attrib(o); o[R[d]] += s[R[d]];
47     gle::attrib(o); o[C[d]] += s[C[d]];
48     gle::attrib(o); o[R[d]] -= s[R[d]];
49     gle::attrib(o);
50 
51     xtraverts += gle::end();
52 }
53 
boxs3D(const vec & o,vec s,int g)54 void boxs3D(const vec &o, vec s, int g)
55 {
56     s.mul(g);
57     loopi(6)
58         boxs(i, o, s);
59 }
60 
boxsgrid(int orient,vec o,vec s,int g)61 void boxsgrid(int orient, vec o, vec s, int g)
62 {
63     int d = dimension(orient), dc = dimcoord(orient);
64     float ox = o[R[d]],
65           oy = o[C[d]],
66           xs = s[R[d]],
67           ys = s[C[d]],
68           f = boxoutline ? (dc>0 ? 0.2f : -0.2f) : 0;
69 
70     o[D[d]] += dc * s[D[d]]*g + f;
71 
72     gle::defvertex();
73     gle::begin(GL_LINES);
74     loop(x, xs)
75     {
76         o[R[d]] += g;
77         gle::attrib(o);
78         o[C[d]] += ys*g;
79         gle::attrib(o);
80         o[C[d]] = oy;
81     }
82     loop(y, ys)
83     {
84         o[C[d]] += g;
85         o[R[d]] = ox;
86         gle::attrib(o);
87         o[R[d]] += xs*g;
88         gle::attrib(o);
89     }
90     xtraverts += gle::end();
91 }
92 
93 selinfo sel, lastsel, savedsel;
94 
95 int orient = 0;
96 int gridsize = 8;
97 ivec cor, lastcor;
98 ivec cur, lastcur;
99 
100 extern int entediting;
101 bool editmode = false;
102 bool havesel = false;
103 bool hmapsel = false;
104 int horient  = 0;
105 
106 extern int entmoving;
107 
108 VARF(dragging, 0, 0, 1,
109     if(!dragging || cor[0]<0) return;
110     lastcur = cur;
111     lastcor = cor;
112     sel.grid = gridsize;
113     sel.orient = orient;
114 );
115 
116 int moving = 0;
117 ICOMMAND(moving, "b", (int *n),
118 {
119     if(*n >= 0)
120     {
121         if(!*n || (moving<=1 && !pointinsel(sel, vec(cur).add(1)))) moving = 0;
122         else if(!moving) moving = 1;
123     }
124     intret(moving);
125 });
126 
127 VARF(gridpower, 0, 3, 12,
128 {
129     if(dragging) return;
130     gridsize = 1<<gridpower;
131     if(gridsize>=worldsize) gridsize = worldsize/2;
132     cancelsel();
133 });
134 
135 VAR(passthroughsel, 0, 0, 1);
136 VAR(editing, 1, 0, 0);
137 VAR(selectcorners, 0, 0, 1);
138 VARF(hmapedit, 0, 0, 1, horient = sel.orient);
139 
forcenextundo()140 void forcenextundo() { lastsel.orient = -1; }
141 
142 extern void hmapcancel();
143 
cubecancel()144 void cubecancel()
145 {
146     havesel = false;
147     moving = dragging = hmapedit = passthroughsel = 0;
148     forcenextundo();
149     hmapcancel();
150 }
151 
cancelsel()152 void cancelsel()
153 {
154     cubecancel();
155     entcancel();
156 }
157 
toggleedit(bool force)158 void toggleedit(bool force)
159 {
160     if(!force)
161     {
162         if(!isconnected()) return;
163         if(player->state!=CS_ALIVE && player->state!=CS_DEAD && player->state!=CS_EDITING) return; // do not allow dead players to edit to avoid state confusion
164         if(!game::allowedittoggle()) return;         // not in most multiplayer modes
165     }
166     if(!(editmode = !editmode))
167     {
168         player->state = player->editstate;
169         player->o.z -= player->eyeheight;       // entinmap wants feet pos
170         entinmap(player);                       // find spawn closest to current floating pos
171     }
172     else
173     {
174         game::resetgamestate();
175         player->editstate = player->state;
176         player->state = CS_EDITING;
177     }
178     cancelsel();
179     stoppaintblendmap();
180     keyrepeat(editmode);
181     editing = entediting = editmode;
182     extern int fullbright;
183     if(fullbright) { initlights(); lightents(); }
184     if(!force) game::edittoggled(editmode);
185 }
186 
187 VARP(editinview, 0, 1, 1);
188 
noedit(bool view,bool msg)189 bool noedit(bool view, bool msg)
190 {
191     if(!editmode) { if(msg) conoutf(CON_ERROR, "operation only allowed in edit mode"); return true; }
192     if(view || haveselent()) return false;
193     float r = 1.0f;
194     vec o(sel.o), s(sel.s);
195     s.mul(float(sel.grid) / 2.0f);
196     o.add(s);
197     r = float(max(s.x, max(s.y, s.z)));
198     bool viewable = (isvisiblesphere(r, o) != VFC_NOT_VISIBLE);
199     if(viewable || !editinview) return false;
200     if(msg) conoutf(CON_ERROR, "selection not in view");
201     return true;
202 }
203 
reorient()204 void reorient()
205 {
206     sel.cx = 0;
207     sel.cy = 0;
208     sel.cxs = sel.s[R[dimension(orient)]]*2;
209     sel.cys = sel.s[C[dimension(orient)]]*2;
210     sel.orient = orient;
211 }
212 
selextend()213 void selextend()
214 {
215     if(noedit(true)) return;
216     loopi(3)
217     {
218         if(cur[i]<sel.o[i])
219         {
220             sel.s[i] += (sel.o[i]-cur[i])/sel.grid;
221             sel.o[i] = cur[i];
222         }
223         else if(cur[i]>=sel.o[i]+sel.s[i]*sel.grid)
224         {
225             sel.s[i] = (cur[i]-sel.o[i])/sel.grid+1;
226         }
227     }
228 }
229 
230 ICOMMAND(edittoggle, "", (), toggleedit(false));
231 COMMAND(entcancel, "");
232 COMMAND(cubecancel, "");
233 COMMAND(cancelsel, "");
234 COMMAND(reorient, "");
235 COMMAND(selextend, "");
236 
237 ICOMMAND(selmoved, "", (), { if(noedit(true)) return; intret(sel.o != savedsel.o ? 1 : 0); });
238 ICOMMAND(selsave, "", (), { if(noedit(true)) return; savedsel = sel; });
239 ICOMMAND(selrestore, "", (), { if(noedit(true)) return; sel = savedsel; });
240 ICOMMAND(selswap, "", (), { if(noedit(true)) return; swap(sel, savedsel); });
241 
242 ICOMMAND(getselpos, "", (),
243 {
244     if(noedit(true)) return;
245     defformatstring(pos, "%s %s %s", floatstr(sel.o.x), floatstr(sel.o.y), floatstr(sel.o.z));
246     result(pos);
247 });
248 
setselpos(int * x,int * y,int * z)249 void setselpos(int *x, int *y, int *z)
250 {
251     if(noedit(moving!=0)) return;
252     havesel = true;
253     sel.o = ivec(*x, *y, *z).mask(~(gridsize-1));
254 }
255 COMMAND(setselpos, "iii");
256 
movesel(int * dir,int * dim)257 void movesel(int *dir, int *dim)
258 {
259     if(noedit(moving!=0)) return;
260     if(*dim < 0 || *dim > 2) return;
261     sel.o[*dim] += *dir * sel.grid;
262 }
263 COMMAND(movesel, "ii");
264 
265 ///////// selection support /////////////
266 
blockcube(int x,int y,int z,const block3 & b,int rgrid)267 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
268 {
269     int dim = dimension(b.orient), dc = dimcoord(b.orient);
270     ivec s(dim, x*b.grid, y*b.grid, dc*(b.s[dim]-1)*b.grid);
271     s.add(b.o);
272     if(dc) s[dim] -= z*b.grid; else s[dim] += z*b.grid;
273     return lookupcube(s, rgrid);
274 }
275 
276 #define loopxy(b)        loop(y,(b).s[C[dimension((b).orient)]]) loop(x,(b).s[R[dimension((b).orient)]])
277 #define loopxyz(b, r, f) { loop(z,(b).s[D[dimension((b).orient)]]) loopxy((b)) { cube &c = blockcube(x,y,z,b,r); f; } }
278 #define loopselxyz(f)    { if(local) makeundo(); loopxyz(sel, sel.grid, f); changed(sel); }
279 #define selcube(x, y, z) blockcube(x, y, z, sel, sel.grid)
280 
281 ////////////// cursor ///////////////
282 
283 int selchildcount = 0, selchildmat = -1;
284 
285 ICOMMAND(havesel, "", (), intret(havesel ? selchildcount : 0));
286 
countselchild(cube * c,const ivec & cor,int size)287 void countselchild(cube *c, const ivec &cor, int size)
288 {
289     ivec ss = ivec(sel.s).mul(sel.grid);
290     loopoctaboxsize(cor, size, sel.o, ss)
291     {
292         ivec o(i, cor, size);
293         if(c[i].children) countselchild(c[i].children, o, size/2);
294         else
295         {
296             selchildcount++;
297             if(c[i].material != MAT_AIR && selchildmat != MAT_AIR)
298             {
299                 if(selchildmat < 0) selchildmat = c[i].material;
300                 else if(selchildmat != c[i].material) selchildmat = MAT_AIR;
301             }
302         }
303     }
304 }
305 
normalizelookupcube(const ivec & o)306 void normalizelookupcube(const ivec &o)
307 {
308     if(lusize>gridsize)
309     {
310         lu.x += (o.x-lu.x)/gridsize*gridsize;
311         lu.y += (o.y-lu.y)/gridsize*gridsize;
312         lu.z += (o.z-lu.z)/gridsize*gridsize;
313     }
314     else if(gridsize>lusize)
315     {
316         lu.x &= ~(gridsize-1);
317         lu.y &= ~(gridsize-1);
318         lu.z &= ~(gridsize-1);
319     }
320     lusize = gridsize;
321 }
322 
updateselection()323 void updateselection()
324 {
325     sel.o.x = min(lastcur.x, cur.x);
326     sel.o.y = min(lastcur.y, cur.y);
327     sel.o.z = min(lastcur.z, cur.z);
328     sel.s.x = abs(lastcur.x-cur.x)/sel.grid+1;
329     sel.s.y = abs(lastcur.y-cur.y)/sel.grid+1;
330     sel.s.z = abs(lastcur.z-cur.z)/sel.grid+1;
331 }
332 
editmoveplane(const vec & o,const vec & ray,int d,float off,vec & handle,vec & dest,bool first)333 bool editmoveplane(const vec &o, const vec &ray, int d, float off, vec &handle, vec &dest, bool first)
334 {
335     plane pl(d, off);
336     float dist = 0.0f;
337     if(!pl.rayintersect(player->o, ray, dist))
338         return false;
339 
340     dest = vec(ray).mul(dist).add(player->o);
341     if(first) handle = vec(dest).sub(o);
342     dest.sub(handle);
343     return true;
344 }
345 
346 inline bool isheightmap(int orient, int d, bool empty, cube *c);
347 extern void entdrag(const vec &ray);
348 extern bool hoveringonent(int ent, int orient);
349 extern void renderentselection(const vec &o, const vec &ray, bool entmoving);
350 extern float rayent(const vec &o, const vec &ray, float radius, int mode, int size, int &orient, int &ent);
351 
352 VAR(gridlookup, 0, 0, 1);
353 VAR(passthroughcube, 0, 1, 1);
354 VAR(passthroughent, 0, 1, 1);
355 VARF(passthrough, 0, 0, 1, { passthroughsel = passthrough; entcancel(); });
356 
rendereditcursor()357 void rendereditcursor()
358 {
359     int d   = dimension(sel.orient),
360         od  = dimension(orient),
361         odc = dimcoord(orient);
362 
363     bool hidecursor = g3d_windowhit(true, false) || blendpaintmode, hovering = false;
364     hmapsel = false;
365 
366     if(moving)
367     {
368         static vec dest, handle;
369         if(editmoveplane(vec(sel.o), camdir, od, sel.o[D[od]]+odc*sel.grid*sel.s[D[od]], handle, dest, moving==1))
370         {
371             if(moving==1)
372             {
373                 dest.add(handle);
374                 handle = vec(ivec(handle).mask(~(sel.grid-1)));
375                 dest.sub(handle);
376                 moving = 2;
377             }
378             ivec o = ivec(dest).mask(~(sel.grid-1));
379             sel.o[R[od]] = o[R[od]];
380             sel.o[C[od]] = o[C[od]];
381         }
382     }
383     else
384     if(entmoving)
385     {
386         entdrag(camdir);
387     }
388     else
389     {
390         ivec w;
391         float sdist = 0, wdist = 0, t;
392         int entorient = 0, ent = -1;
393 
394         wdist = rayent(player->o, camdir, 1e16f,
395                        (editmode && showmat ? RAY_EDITMAT : 0)   // select cubes first
396                        | (!dragging && entediting && (!passthrough || !passthroughent) ? RAY_ENTS : 0)
397                        | RAY_SKIPFIRST
398                        | (passthroughcube || passthrough ? RAY_PASS : 0), gridsize, entorient, ent);
399 
400         if((havesel || dragging) && !passthroughsel && !hmapedit)     // now try selecting the selection
401             if(rayboxintersect(vec(sel.o), vec(sel.s).mul(sel.grid), player->o, camdir, sdist, orient))
402             {   // and choose the nearest of the two
403                 if(sdist < wdist)
404                 {
405                     wdist = sdist;
406                     ent   = -1;
407                 }
408             }
409 
410         if((hovering = hoveringonent(hidecursor ? -1 : ent, entorient)))
411         {
412            if(!havesel)
413            {
414                selchildcount = 0;
415                selchildmat = -1;
416                sel.s = ivec(0, 0, 0);
417            }
418         }
419         else
420         {
421             vec w = vec(camdir).mul(wdist+0.05f).add(player->o);
422             if(!insideworld(w))
423             {
424                 loopi(3) wdist = min(wdist, ((camdir[i] > 0 ? worldsize : 0) - player->o[i]) / camdir[i]);
425                 w = vec(camdir).mul(wdist-0.05f).add(player->o);
426                 if(!insideworld(w))
427                 {
428                     wdist = 0;
429                     loopi(3) w[i] = clamp(player->o[i], 0.0f, float(worldsize));
430                 }
431             }
432             cube *c = &lookupcube(ivec(w));
433             if(gridlookup && !dragging && !moving && !havesel && hmapedit!=1) gridsize = lusize;
434             int mag = lusize / gridsize;
435             normalizelookupcube(ivec(w));
436             if(sdist == 0 || sdist > wdist) rayboxintersect(vec(lu), vec(gridsize), player->o, camdir, t=0, orient); // just getting orient
437             cur = lu;
438             cor = ivec(vec(w).mul(2).div(gridsize));
439             od = dimension(orient);
440             d = dimension(sel.orient);
441 
442             if(hmapedit==1 && dimcoord(horient) == (camdir[dimension(horient)]<0))
443             {
444                 hmapsel = isheightmap(horient, dimension(horient), false, c);
445                 if(hmapsel)
446                     od = dimension(orient = horient);
447             }
448 
449             if(dragging)
450             {
451                 updateselection();
452                 sel.cx   = min(cor[R[d]], lastcor[R[d]]);
453                 sel.cy   = min(cor[C[d]], lastcor[C[d]]);
454                 sel.cxs  = max(cor[R[d]], lastcor[R[d]]);
455                 sel.cys  = max(cor[C[d]], lastcor[C[d]]);
456 
457                 if(!selectcorners)
458                 {
459                     sel.cx &= ~1;
460                     sel.cy &= ~1;
461                     sel.cxs &= ~1;
462                     sel.cys &= ~1;
463                     sel.cxs -= sel.cx-2;
464                     sel.cys -= sel.cy-2;
465                 }
466                 else
467                 {
468                     sel.cxs -= sel.cx-1;
469                     sel.cys -= sel.cy-1;
470                 }
471 
472                 sel.cx  &= 1;
473                 sel.cy  &= 1;
474                 havesel = true;
475             }
476             else if(!havesel)
477             {
478                 sel.o = lu;
479                 sel.s.x = sel.s.y = sel.s.z = 1;
480                 sel.cx = sel.cy = 0;
481                 sel.cxs = sel.cys = 2;
482                 sel.grid = gridsize;
483                 sel.orient = orient;
484                 d = od;
485             }
486 
487             sel.corner = (cor[R[d]]-(lu[R[d]]*2)/gridsize)+(cor[C[d]]-(lu[C[d]]*2)/gridsize)*2;
488             selchildcount = 0;
489             selchildmat = -1;
490             countselchild(worldroot, ivec(0, 0, 0), worldsize/2);
491             if(mag>=1 && selchildcount==1)
492             {
493                 selchildmat = c->material;
494                 if(mag>1) selchildcount = -mag;
495             }
496         }
497     }
498 
499     glEnable(GL_BLEND);
500     glBlendFunc(GL_ONE, GL_ONE);
501 
502     // cursors
503 
504     notextureshader->set();
505 
506     renderentselection(player->o, camdir, entmoving!=0);
507 
508     boxoutline = outline!=0;
509 
510     enablepolygonoffset(GL_POLYGON_OFFSET_LINE);
511 
512     if(!moving && !hovering && !hidecursor)
513     {
514         if(hmapedit==1)
515             gle::colorub(0, hmapsel ? 255 : 40, 0);
516         else
517             gle::colorub(120,120,120);
518         boxs(orient, vec(lu), vec(lusize));
519     }
520 
521     // selections
522     if(havesel || moving)
523     {
524         d = dimension(sel.orient);
525         gle::colorub(50,50,50);   // grid
526         boxsgrid(sel.orient, vec(sel.o), vec(sel.s), sel.grid);
527         gle::colorub(200,0,0);    // 0 reference
528         boxs3D(vec(sel.o).sub(0.5f*min(gridsize*0.25f, 2.0f)), vec(min(gridsize*0.25f, 2.0f)), 1);
529         gle::colorub(200,200,200);// 2D selection box
530         vec co(sel.o.v), cs(sel.s.v);
531         co[R[d]] += 0.5f*(sel.cx*gridsize);
532         co[C[d]] += 0.5f*(sel.cy*gridsize);
533         cs[R[d]]  = 0.5f*(sel.cxs*gridsize);
534         cs[C[d]]  = 0.5f*(sel.cys*gridsize);
535         cs[D[d]] *= gridsize;
536         boxs(sel.orient, co, cs);
537         if(hmapedit==1)         // 3D selection box
538             gle::colorub(0,120,0);
539         else
540             gle::colorub(0,0,120);
541         boxs3D(vec(sel.o), vec(sel.s), sel.grid);
542     }
543 
544     disablepolygonoffset(GL_POLYGON_OFFSET_LINE);
545 
546     boxoutline = false;
547 
548     glDisable(GL_BLEND);
549 }
550 
tryedit()551 void tryedit()
552 {
553     extern int hidehud;
554     if(!editmode || hidehud || mainmenu) return;
555     if(blendpaintmode) trypaintblendmap();
556 }
557 
558 //////////// ready changes to vertex arrays ////////////
559 
560 static bool haschanged = false;
561 
readychanges(const ivec & bbmin,const ivec & bbmax,cube * c,const ivec & cor,int size)562 void readychanges(const ivec &bbmin, const ivec &bbmax, cube *c, const ivec &cor, int size)
563 {
564     loopoctabox(cor, size, bbmin, bbmax)
565     {
566         ivec o(i, cor, size);
567         if(c[i].ext)
568         {
569             if(c[i].ext->va)             // removes va s so that octarender will recreate
570             {
571                 int hasmerges = c[i].ext->va->hasmerges;
572                 destroyva(c[i].ext->va);
573                 c[i].ext->va = NULL;
574                 if(hasmerges) invalidatemerges(c[i], o, size, true);
575             }
576             freeoctaentities(c[i]);
577             c[i].ext->tjoints = -1;
578         }
579         if(c[i].children)
580         {
581             if(size<=1)
582             {
583                 solidfaces(c[i]);
584                 discardchildren(c[i], true);
585                 brightencube(c[i]);
586             }
587             else readychanges(bbmin, bbmax, c[i].children, o, size/2);
588         }
589         else brightencube(c[i]);
590     }
591 }
592 
commitchanges(bool force)593 void commitchanges(bool force)
594 {
595     if(!force && !haschanged) return;
596     haschanged = false;
597 
598     int oldlen = valist.length();
599     resetclipplanes();
600     entitiesinoctanodes();
601     inbetweenframes = false;
602     octarender();
603     inbetweenframes = true;
604     setupmaterials(oldlen);
605     invalidatepostfx();
606     updatevabbs();
607     resetblobs();
608 }
609 
changed(const block3 & sel,bool commit=true)610 void changed(const block3 &sel, bool commit = true)
611 {
612     if(sel.s.iszero()) return;
613     readychanges(ivec(sel.o).sub(1), ivec(sel.s).mul(sel.grid).add(sel.o).add(1), worldroot, ivec(0, 0, 0), worldsize/2);
614     haschanged = true;
615 
616     if(commit) commitchanges();
617 }
618 
619 //////////// copy and undo /////////////
copycube(const cube & src,cube & dst)620 static inline void copycube(const cube &src, cube &dst)
621 {
622     dst = src;
623     dst.visible = 0;
624     dst.merged = 0;
625     dst.ext = NULL; // src cube is responsible for va destruction
626     if(src.children)
627     {
628         dst.children = newcubes(F_EMPTY);
629         loopi(8) copycube(src.children[i], dst.children[i]);
630     }
631 }
632 
pastecube(const cube & src,cube & dst)633 static inline void pastecube(const cube &src, cube &dst)
634 {
635     discardchildren(dst);
636     copycube(src, dst);
637 }
638 
blockcopy(const block3 & s,int rgrid,block3 * b)639 void blockcopy(const block3 &s, int rgrid, block3 *b)
640 {
641     *b = s;
642     cube *q = b->c();
643     loopxyz(s, rgrid, copycube(c, *q++));
644 }
645 
blockcopy(const block3 & s,int rgrid)646 block3 *blockcopy(const block3 &s, int rgrid)
647 {
648     int bsize = sizeof(block3)+sizeof(cube)*s.size();
649     if(bsize <= 0 || bsize > (100<<20)) return NULL;
650     block3 *b = (block3 *)new (false) uchar[bsize];
651     if(b) blockcopy(s, rgrid, b);
652     return b;
653 }
654 
freeblock(block3 * b,bool alloced=true)655 void freeblock(block3 *b, bool alloced = true)
656 {
657     cube *q = b->c();
658     loopi(b->size()) discardchildren(*q++);
659     if(alloced) delete[] b;
660 }
661 
selgridmap(selinfo & sel,uchar * g)662 void selgridmap(selinfo &sel, uchar *g)                           // generates a map of the cube sizes at each grid point
663 {
664     loopxyz(sel, -sel.grid, (*g++ = bitscan(lusize), (void)c));
665 }
666 
freeundo(undoblock * u)667 void freeundo(undoblock *u)
668 {
669     if(!u->numents) freeblock(u->block(), false);
670     delete[] (uchar *)u;
671 }
672 
pasteundoblock(block3 * b,uchar * g)673 void pasteundoblock(block3 *b, uchar *g)
674 {
675     cube *s = b->c();
676     loopxyz(*b, 1<<min(int(*g++), worldscale-1), pastecube(*s++, c));
677 }
678 
pasteundo(undoblock * u)679 void pasteundo(undoblock *u)
680 {
681     if(u->numents) pasteundoents(u);
682     else pasteundoblock(u->block(), u->gridmap());
683 }
684 
undosize(undoblock * u)685 static inline int undosize(undoblock *u)
686 {
687     if(u->numents) return u->numents*sizeof(undoent);
688     else
689     {
690         block3 *b = u->block();
691         cube *q = b->c();
692         int size = b->size(), total = size;
693         loopj(size) total += familysize(*q++)*sizeof(cube);
694         return total;
695     }
696 }
697 
698 struct undolist
699 {
700     undoblock *first, *last;
701 
undolistundolist702     undolist() : first(NULL), last(NULL) {}
703 
emptyundolist704     bool empty() { return !first; }
705 
addundolist706     void add(undoblock *u)
707     {
708         u->next = NULL;
709         u->prev = last;
710         if(!first) first = last = u;
711         else
712         {
713             last->next = u;
714             last = u;
715         }
716     }
717 
popfirstundolist718     undoblock *popfirst()
719     {
720         undoblock *u = first;
721         first = first->next;
722         if(first) first->prev = NULL;
723         else last = NULL;
724         return u;
725     }
726 
poplastundolist727     undoblock *poplast()
728     {
729         undoblock *u = last;
730         last = last->prev;
731         if(last) last->next = NULL;
732         else first = NULL;
733         return u;
734     }
735 };
736 
737 undolist undos, redos;
738 VARP(undomegs, 0, 8, 100);                              // bounded by n megs
739 int totalundos = 0;
740 
pruneundos(int maxremain)741 void pruneundos(int maxremain)                          // bound memory
742 {
743     while(totalundos > maxremain && !undos.empty())
744     {
745         undoblock *u = undos.popfirst();
746         totalundos -= u->size;
747         freeundo(u);
748     }
749     //conoutf(CON_DEBUG, "undo: %d of %d(%%%d)", totalundos, undomegs<<20, totalundos*100/(undomegs<<20));
750     while(!redos.empty())
751     {
752         undoblock *u = redos.popfirst();
753         totalundos -= u->size;
754         freeundo(u);
755     }
756 }
757 
clearundos()758 void clearundos() { pruneundos(0); }
759 
760 COMMAND(clearundos, "");
761 
newundocube(selinfo & s)762 undoblock *newundocube(selinfo &s)
763 {
764     int ssize = s.size(),
765         selgridsize = ssize,
766         blocksize = sizeof(block3)+ssize*sizeof(cube);
767     if(blocksize <= 0 || blocksize > (undomegs<<20)) return NULL;
768     undoblock *u = (undoblock *)new (false) uchar[sizeof(undoblock) + blocksize + selgridsize];
769     if(!u) return NULL;
770     u->numents = 0;
771     block3 *b = u->block();
772     blockcopy(s, -s.grid, b);
773     uchar *g = u->gridmap();
774     selgridmap(s, g);
775     return u;
776 }
777 
addundo(undoblock * u)778 void addundo(undoblock *u)
779 {
780     u->size = undosize(u);
781     u->timestamp = totalmillis;
782     undos.add(u);
783     totalundos += u->size;
784     pruneundos(undomegs<<20);
785 }
786 
787 VARP(nompedit, 0, 1, 1);
788 
makeundo(selinfo & s)789 void makeundo(selinfo &s)
790 {
791     undoblock *u = newundocube(s);
792     if(u) addundo(u);
793 }
794 
makeundo()795 void makeundo()                        // stores state of selected cubes before editing
796 {
797     if(lastsel==sel || sel.s.iszero()) return;
798     lastsel=sel;
799     makeundo(sel);
800 }
801 
countblock(cube * c,int n=8)802 static inline int countblock(cube *c, int n = 8)
803 {
804     int r = 0;
805     loopi(n) if(c[i].children) r += countblock(c[i].children); else ++r;
806     return r;
807 }
808 
countblock(block3 * b)809 static int countblock(block3 *b) { return countblock(b->c(), b->size()); }
810 
swapundo(undolist & a,undolist & b,int op)811 void swapundo(undolist &a, undolist &b, int op)
812 {
813     if(noedit()) return;
814     if(a.empty()) { conoutf(CON_WARN, "nothing more to %s", op == EDIT_REDO ? "redo" : "undo"); return; }
815     int ts = a.last->timestamp;
816     if(multiplayer(false))
817     {
818         int n = 0, ops = 0;
819         for(undoblock *u = a.last; u && ts==u->timestamp; u = u->prev)
820         {
821             ++ops;
822             n += u->numents ? u->numents : countblock(u->block());
823             if(ops > 10 || n > 2500)
824             {
825                 conoutf(CON_WARN, "undo too big for multiplayer");
826                 if(nompedit) { multiplayer(); return; }
827                 op = -1;
828                 break;
829             }
830         }
831     }
832     selinfo l = sel;
833     while(!a.empty() && ts==a.last->timestamp)
834     {
835         if(op >= 0) game::edittrigger(sel, op);
836         undoblock *u = a.poplast(), *r;
837         if(u->numents) r = copyundoents(u);
838         else
839         {
840             block3 *ub = u->block();
841             l.o = ub->o;
842             l.s = ub->s;
843             l.grid = ub->grid;
844             l.orient = ub->orient;
845             r = newundocube(l);
846         }
847         if(r)
848         {
849             r->size = u->size;
850             r->timestamp = totalmillis;
851             b.add(r);
852         }
853         pasteundo(u);
854         if(!u->numents) changed(*u->block(), false);
855         freeundo(u);
856     }
857     commitchanges();
858     if(!hmapsel)
859     {
860         sel = l;
861         reorient();
862     }
863     forcenextundo();
864 }
865 
editundo()866 void editundo() { swapundo(undos, redos, EDIT_UNDO); }
editredo()867 void editredo() { swapundo(redos, undos, EDIT_REDO); }
868 
869 // guard against subdivision
870 #define protectsel(f) { undoblock *_u = newundocube(sel); f; if(_u) { pasteundo(_u); freeundo(_u); } }
871 
872 vector<editinfo *> editinfos;
873 editinfo *localedit = NULL;
874 
875 template<class B>
packcube(cube & c,B & buf)876 static void packcube(cube &c, B &buf)
877 {
878     if(c.children)
879     {
880         buf.put(0xFF);
881         loopi(8) packcube(c.children[i], buf);
882     }
883     else
884     {
885         cube data = c;
886         lilswap(data.texture, 6);
887         buf.put(c.material&0xFF);
888         buf.put(c.material>>8);
889         buf.put(data.edges, sizeof(data.edges));
890         buf.put((uchar *)data.texture, sizeof(data.texture));
891     }
892 }
893 
894 template<class B>
packblock(block3 & b,B & buf)895 static bool packblock(block3 &b, B &buf)
896 {
897     if(b.size() <= 0 || b.size() > (1<<20)) return false;
898     block3 hdr = b;
899     lilswap(hdr.o.v, 3);
900     lilswap(hdr.s.v, 3);
901     lilswap(&hdr.grid, 1);
902     lilswap(&hdr.orient, 1);
903     buf.put((const uchar *)&hdr, sizeof(hdr));
904     cube *c = b.c();
905     loopi(b.size()) packcube(c[i], buf);
906     return true;
907 }
908 
909 struct vslothdr
910 {
911     ushort index;
912     ushort slot;
913 };
914 
packvslots(cube & c,vector<uchar> & buf,vector<ushort> & used)915 static void packvslots(cube &c, vector<uchar> &buf, vector<ushort> &used)
916 {
917     if(c.children)
918     {
919         loopi(8) packvslots(c.children[i], buf, used);
920     }
921     else loopi(6)
922     {
923         ushort index = c.texture[i];
924         if(vslots.inrange(index) && vslots[index]->changed && used.find(index) < 0)
925         {
926             used.add(index);
927             VSlot &vs = *vslots[index];
928             vslothdr &hdr = *(vslothdr *)buf.pad(sizeof(vslothdr));
929             hdr.index = index;
930             hdr.slot = vs.slot->index;
931             lilswap(&hdr.index, 2);
932             packvslot(buf, vs);
933         }
934     }
935 }
936 
packvslots(block3 & b,vector<uchar> & buf)937 static void packvslots(block3 &b, vector<uchar> &buf)
938 {
939     vector<ushort> used;
940     cube *c = b.c();
941     loopi(b.size()) packvslots(c[i], buf, used);
942     memset(buf.pad(sizeof(vslothdr)), 0, sizeof(vslothdr));
943 }
944 
945 template<class B>
unpackcube(cube & c,B & buf)946 static void unpackcube(cube &c, B &buf)
947 {
948     int mat = buf.get();
949     if(mat == 0xFF)
950     {
951         c.children = newcubes(F_EMPTY);
952         loopi(8) unpackcube(c.children[i], buf);
953     }
954     else
955     {
956         c.material = mat | (buf.get()<<8);
957         buf.get(c.edges, sizeof(c.edges));
958         buf.get((uchar *)c.texture, sizeof(c.texture));
959         lilswap(c.texture, 6);
960     }
961 }
962 
963 template<class B>
unpackblock(block3 * & b,B & buf)964 static bool unpackblock(block3 *&b, B &buf)
965 {
966     if(b) { freeblock(b); b = NULL; }
967     block3 hdr;
968     if(buf.get((uchar *)&hdr, sizeof(hdr)) < int(sizeof(hdr))) return false;
969     lilswap(hdr.o.v, 3);
970     lilswap(hdr.s.v, 3);
971     lilswap(&hdr.grid, 1);
972     lilswap(&hdr.orient, 1);
973     if(hdr.size() > (1<<20) || hdr.grid <= 0 || hdr.grid > (1<<12)) return false;
974     b = (block3 *)new (false) uchar[sizeof(block3)+hdr.size()*sizeof(cube)];
975     if(!b) return false;
976     *b = hdr;
977     cube *c = b->c();
978     memset(c, 0, b->size()*sizeof(cube));
979     loopi(b->size()) unpackcube(c[i], buf);
980     return true;
981 }
982 
983 struct vslotmap
984 {
985     int index;
986     VSlot *vslot;
987 
vslotmapvslotmap988     vslotmap() {}
vslotmapvslotmap989     vslotmap(int index, VSlot *vslot) : index(index), vslot(vslot) {}
990 };
991 static vector<vslotmap> unpackingvslots;
992 
unpackvslots(cube & c,ucharbuf & buf)993 static void unpackvslots(cube &c, ucharbuf &buf)
994 {
995     if(c.children)
996     {
997         loopi(8) unpackvslots(c.children[i], buf);
998     }
999     else loopi(6)
1000     {
1001         ushort tex = c.texture[i];
1002         loopvj(unpackingvslots) if(unpackingvslots[j].index == tex) { c.texture[i] = unpackingvslots[j].vslot->index; break; }
1003     }
1004 }
1005 
unpackvslots(block3 & b,ucharbuf & buf)1006 static void unpackvslots(block3 &b, ucharbuf &buf)
1007 {
1008     while(buf.remaining() >= int(sizeof(vslothdr)))
1009     {
1010         vslothdr &hdr = *(vslothdr *)buf.pad(sizeof(vslothdr));
1011         lilswap(&hdr.index, 2);
1012         if(!hdr.index) break;
1013         VSlot &vs = *lookupslot(hdr.slot, false).variants;
1014         VSlot ds;
1015         if(!unpackvslot(buf, ds, false)) break;
1016         if(vs.index < 0 || vs.index == DEFAULT_SKY) continue;
1017         VSlot *edit = editvslot(vs, ds);
1018         unpackingvslots.add(vslotmap(hdr.index, edit ? edit : &vs));
1019     }
1020 
1021     cube *c = b.c();
1022     loopi(b.size()) unpackvslots(c[i], buf);
1023 
1024     unpackingvslots.setsize(0);
1025 }
1026 
compresseditinfo(const uchar * inbuf,int inlen,uchar * & outbuf,int & outlen)1027 static bool compresseditinfo(const uchar *inbuf, int inlen, uchar *&outbuf, int &outlen)
1028 {
1029     uLongf len = compressBound(inlen);
1030     if(len > (1<<20)) return false;
1031     outbuf = new (false) uchar[len];
1032     if(!outbuf || compress2((Bytef *)outbuf, &len, (const Bytef *)inbuf, inlen, Z_BEST_COMPRESSION) != Z_OK || len > (1<<16))
1033     {
1034         delete[] outbuf;
1035         outbuf = NULL;
1036         return false;
1037     }
1038     outlen = len;
1039     return true;
1040 }
1041 
uncompresseditinfo(const uchar * inbuf,int inlen,uchar * & outbuf,int & outlen)1042 static bool uncompresseditinfo(const uchar *inbuf, int inlen, uchar *&outbuf, int &outlen)
1043 {
1044     if(compressBound(outlen) > (1<<20)) return false;
1045     uLongf len = outlen;
1046     outbuf = new (false) uchar[len];
1047     if(!outbuf || uncompress((Bytef *)outbuf, &len, (const Bytef *)inbuf, inlen) != Z_OK)
1048     {
1049         delete[] outbuf;
1050         outbuf = NULL;
1051         return false;
1052     }
1053     outlen = len;
1054     return true;
1055 }
1056 
packeditinfo(editinfo * e,int & inlen,uchar * & outbuf,int & outlen)1057 bool packeditinfo(editinfo *e, int &inlen, uchar *&outbuf, int &outlen)
1058 {
1059     vector<uchar> buf;
1060     if(!e || !e->copy || !packblock(*e->copy, buf)) return false;
1061     packvslots(*e->copy, buf);
1062     inlen = buf.length();
1063     return compresseditinfo(buf.getbuf(), buf.length(), outbuf, outlen);
1064 }
1065 
unpackeditinfo(editinfo * & e,const uchar * inbuf,int inlen,int outlen)1066 bool unpackeditinfo(editinfo *&e, const uchar *inbuf, int inlen, int outlen)
1067 {
1068     if(e && e->copy) { freeblock(e->copy); e->copy = NULL; }
1069     uchar *outbuf = NULL;
1070     if(!uncompresseditinfo(inbuf, inlen, outbuf, outlen)) return false;
1071     ucharbuf buf(outbuf, outlen);
1072     if(!e) e = editinfos.add(new editinfo);
1073     if(!unpackblock(e->copy, buf))
1074     {
1075         delete[] outbuf;
1076         return false;
1077     }
1078     unpackvslots(*e->copy, buf);
1079     delete[] outbuf;
1080     return true;
1081 }
1082 
freeeditinfo(editinfo * & e)1083 void freeeditinfo(editinfo *&e)
1084 {
1085     if(!e) return;
1086     editinfos.removeobj(e);
1087     if(e->copy) freeblock(e->copy);
1088     delete e;
1089     e = NULL;
1090 }
1091 
packundo(undoblock * u,int & inlen,uchar * & outbuf,int & outlen)1092 bool packundo(undoblock *u, int &inlen, uchar *&outbuf, int &outlen)
1093 {
1094     vector<uchar> buf;
1095     buf.reserve(512);
1096     *(ushort *)buf.pad(2) = lilswap(ushort(u->numents));
1097     if(u->numents)
1098     {
1099         undoent *ue = u->ents();
1100         loopi(u->numents)
1101         {
1102             *(ushort *)buf.pad(2) = lilswap(ushort(ue[i].i));
1103             entity &e = *(entity *)buf.pad(sizeof(entity));
1104             e = ue[i].e;
1105             lilswap(&e.o.x, 3);
1106             lilswap(&e.attr1, 5);
1107         }
1108     }
1109     else
1110     {
1111         block3 &b = *u->block();
1112         if(!packblock(b, buf)) return false;
1113         buf.put(u->gridmap(), b.size());
1114         packvslots(b, buf);
1115     }
1116     inlen = buf.length();
1117     return compresseditinfo(buf.getbuf(), buf.length(), outbuf, outlen);
1118 }
1119 
unpackundo(const uchar * inbuf,int inlen,int outlen)1120 bool unpackundo(const uchar *inbuf, int inlen, int outlen)
1121 {
1122     uchar *outbuf = NULL;
1123     if(!uncompresseditinfo(inbuf, inlen, outbuf, outlen)) return false;
1124     ucharbuf buf(outbuf, outlen);
1125     if(buf.remaining() < 2)
1126     {
1127         delete[] outbuf;
1128         return false;
1129     }
1130     int numents = lilswap(*(const ushort *)buf.pad(2));
1131     if(numents)
1132     {
1133         if(buf.remaining() < numents*int(2 + sizeof(entity)))
1134         {
1135             delete[] outbuf;
1136             return false;
1137         }
1138         loopi(numents)
1139         {
1140             int idx = lilswap(*(const ushort *)buf.pad(2));
1141             entity &e = *(entity *)buf.pad(sizeof(entity));
1142             lilswap(&e.o.x, 3);
1143             lilswap(&e.attr1, 5);
1144             pasteundoent(idx, e);
1145         }
1146     }
1147     else
1148     {
1149         block3 *b = NULL;
1150         if(!unpackblock(b, buf) || b->grid >= worldsize || buf.remaining() < b->size())
1151         {
1152             freeblock(b);
1153             delete[] outbuf;
1154             return false;
1155         }
1156         uchar *g = buf.pad(b->size());
1157         unpackvslots(*b, buf);
1158         pasteundoblock(b, g);
1159         changed(*b, false);
1160         freeblock(b);
1161     }
1162     delete[] outbuf;
1163     commitchanges();
1164     return true;
1165 }
1166 
packundo(int op,int & inlen,uchar * & outbuf,int & outlen)1167 bool packundo(int op, int &inlen, uchar *&outbuf, int &outlen)
1168 {
1169     switch(op)
1170     {
1171         case EDIT_UNDO: return !undos.empty() && packundo(undos.last, inlen, outbuf, outlen);
1172         case EDIT_REDO: return !redos.empty() && packundo(redos.last, inlen, outbuf, outlen);
1173         default: return false;
1174     }
1175 }
1176 
1177 struct prefabheader
1178 {
1179     char magic[4];
1180     int version;
1181 };
1182 
1183 struct prefab : editinfo
1184 {
1185     char *name;
1186     GLuint ebo, vbo;
1187     int numtris, numverts;
1188 
prefabprefab1189     prefab() : name(NULL), ebo(0), vbo(0), numtris(0), numverts(0) {}
~prefabprefab1190     ~prefab() { DELETEA(name); if(copy) freeblock(copy); }
1191 
cleanupprefab1192     void cleanup()
1193     {
1194         if(ebo) { glDeleteBuffers_(1, &ebo); ebo = 0; }
1195         if(vbo) { glDeleteBuffers_(1, &vbo); vbo = 0; }
1196         numtris = numverts = 0;
1197     }
1198 };
1199 
1200 static hashnameset<prefab> prefabs;
1201 
cleanupprefabs()1202 void cleanupprefabs()
1203 {
1204     enumerate(prefabs, prefab, p, p.cleanup());
1205 }
1206 
delprefab(char * name)1207 void delprefab(char *name)
1208 {
1209     prefab *p = prefabs.access(name);
1210     if(p)
1211     {
1212         p->cleanup();
1213         prefabs.remove(name);
1214         conoutf("deleted prefab %s", name);
1215     }
1216 }
1217 COMMAND(delprefab, "s");
1218 
saveprefab(char * name)1219 void saveprefab(char *name)
1220 {
1221     if(!name[0] || noedit(true) || (nompedit && multiplayer())) return;
1222     prefab *b = prefabs.access(name);
1223     if(!b)
1224     {
1225         b = &prefabs[name];
1226         b->name = newstring(name);
1227     }
1228     if(b->copy) freeblock(b->copy);
1229     protectsel(b->copy = blockcopy(block3(sel), sel.grid));
1230     changed(sel);
1231     defformatstring(filename, strpbrk(name, "/\\") ? "packages/%s.obr" : "packages/prefab/%s.obr", name);
1232     path(filename);
1233     stream *f = opengzfile(filename, "wb");
1234     if(!f) { conoutf(CON_ERROR, "could not write prefab to %s", filename); return; }
1235     prefabheader hdr;
1236     memcpy(hdr.magic, "OEBR", 4);
1237     hdr.version = 0;
1238     lilswap(&hdr.version, 1);
1239     f->write(&hdr, sizeof(hdr));
1240     streambuf<uchar> s(f);
1241     if(!packblock(*b->copy, s)) { delete f; conoutf(CON_ERROR, "could not pack prefab %s", filename); return; }
1242     delete f;
1243     conoutf("wrote prefab file %s", filename);
1244 }
1245 COMMAND(saveprefab, "s");
1246 
pasteblock(block3 & b,selinfo & sel,bool local)1247 void pasteblock(block3 &b, selinfo &sel, bool local)
1248 {
1249     sel.s = b.s;
1250     int o = sel.orient;
1251     sel.orient = b.orient;
1252     cube *s = b.c();
1253     loopselxyz(if(!isempty(*s) || s->children || s->material != MAT_AIR) pastecube(*s, c); s++); // 'transparent'. old opaque by 'delcube; paste'
1254     sel.orient = o;
1255 }
1256 
prefabloaded(const char * name)1257 bool prefabloaded(const char *name)
1258 {
1259     return prefabs.access(name) != NULL;
1260 }
1261 
loadprefab(const char * name,bool msg=true)1262 prefab *loadprefab(const char *name, bool msg = true)
1263 {
1264    prefab *b = prefabs.access(name);
1265    if(b) return b;
1266 
1267    defformatstring(filename, strpbrk(name, "/\\") ? "packages/%s.obr" : "packages/prefab/%s.obr", name);
1268    path(filename);
1269    stream *f = opengzfile(filename, "rb");
1270    if(!f) { if(msg) conoutf(CON_ERROR, "could not read prefab %s", filename); return NULL; }
1271    prefabheader hdr;
1272    if(f->read(&hdr, sizeof(hdr)) != sizeof(prefabheader) || memcmp(hdr.magic, "OEBR", 4)) { delete f; if(msg) conoutf(CON_ERROR, "prefab %s has malformatted header", filename); return NULL; }
1273    lilswap(&hdr.version, 1);
1274    if(hdr.version != 0) { delete f; if(msg) conoutf(CON_ERROR, "prefab %s uses unsupported version", filename); return NULL; }
1275    streambuf<uchar> s(f);
1276    block3 *copy = NULL;
1277    if(!unpackblock(copy, s)) { delete f; if(msg) conoutf(CON_ERROR, "could not unpack prefab %s", filename); return NULL; }
1278    delete f;
1279 
1280    b = &prefabs[name];
1281    b->name = newstring(name);
1282    b->copy = copy;
1283 
1284    return b;
1285 }
1286 
pasteprefab(char * name)1287 void pasteprefab(char *name)
1288 {
1289     if(!name[0] || noedit() || (nompedit && multiplayer())) return;
1290     prefab *b = loadprefab(name, true);
1291     if(b) pasteblock(*b->copy, sel, true);
1292 }
1293 COMMAND(pasteprefab, "s");
1294 
1295 struct prefabmesh
1296 {
1297     struct vertex { vec pos; bvec4 norm; };
1298 
1299     static const int SIZE = 1<<9;
1300     int table[SIZE];
1301     vector<vertex> verts;
1302     vector<int> chain;
1303     vector<ushort> tris;
1304 
prefabmeshprefabmesh1305     prefabmesh() { memset(table, -1, sizeof(table)); }
1306 
addvertprefabmesh1307     int addvert(const vertex &v)
1308     {
1309         uint h = hthash(v.pos)&(SIZE-1);
1310         for(int i = table[h]; i>=0; i = chain[i])
1311         {
1312             const vertex &c = verts[i];
1313             if(c.pos==v.pos && c.norm==v.norm) return i;
1314         }
1315         if(verts.length() >= USHRT_MAX) return -1;
1316         verts.add(v);
1317         chain.add(table[h]);
1318         return table[h] = verts.length()-1;
1319     }
1320 
addvertprefabmesh1321     int addvert(const vec &pos, const bvec &norm)
1322     {
1323         vertex vtx;
1324         vtx.pos = pos;
1325         vtx.norm = norm;
1326         return addvert(vtx);
1327    }
1328 
setupprefabmesh1329     void setup(prefab &p)
1330     {
1331         if(tris.empty()) return;
1332 
1333         p.cleanup();
1334 
1335         loopv(verts) verts[i].norm.flip();
1336         if(!p.vbo) glGenBuffers_(1, &p.vbo);
1337         gle::bindvbo(p.vbo);
1338         glBufferData_(GL_ARRAY_BUFFER, verts.length()*sizeof(vertex), verts.getbuf(), GL_STATIC_DRAW);
1339         gle::clearvbo();
1340         p.numverts = verts.length();
1341 
1342         if(!p.ebo) glGenBuffers_(1, &p.ebo);
1343         gle::bindebo(p.ebo);
1344         glBufferData_(GL_ELEMENT_ARRAY_BUFFER, tris.length()*sizeof(ushort), tris.getbuf(), GL_STATIC_DRAW);
1345         gle::clearebo();
1346         p.numtris = tris.length()/3;
1347     }
1348 
1349 };
1350 
genprefabmesh(prefabmesh & r,cube & c,const ivec & co,int size)1351 static void genprefabmesh(prefabmesh &r, cube &c, const ivec &co, int size)
1352 {
1353     if(c.children)
1354     {
1355         neighbourstack[++neighbourdepth] = c.children;
1356         loopi(8)
1357         {
1358             ivec o(i, co, size/2);
1359             genprefabmesh(r, c.children[i], o, size/2);
1360         }
1361         --neighbourdepth;
1362     }
1363     else if(!isempty(c))
1364     {
1365         int vis;
1366         loopi(6) if((vis = visibletris(c, i, co, size)))
1367         {
1368             ivec v[4];
1369             genfaceverts(c, i, v);
1370             int convex = 0;
1371             if(!flataxisface(c, i)) convex = faceconvexity(v);
1372             int order = vis&4 || convex < 0 ? 1 : 0, numverts = 0;
1373             vec vo(co), pos[4], norm[4];
1374             pos[numverts++] = vec(v[order]).mul(size/8.0f).add(vo);
1375             if(vis&1) pos[numverts++] = vec(v[order+1]).mul(size/8.0f).add(vo);
1376             pos[numverts++] = vec(v[order+2]).mul(size/8.0f).add(vo);
1377             if(vis&2) pos[numverts++] = vec(v[(order+3)&3]).mul(size/8.0f).add(vo);
1378             guessnormals(pos, numverts, norm);
1379             int index[4];
1380             loopj(numverts) index[j] = r.addvert(pos[j], bvec(norm[j]));
1381             loopj(numverts-2) if(index[0]!=index[j+1] && index[j+1]!=index[j+2] && index[j+2]!=index[0])
1382             {
1383                 r.tris.add(index[0]);
1384                 r.tris.add(index[j+1]);
1385                 r.tris.add(index[j+2]);
1386             }
1387         }
1388     }
1389 }
1390 
genprefabmesh(prefab & p)1391 void genprefabmesh(prefab &p)
1392 {
1393     block3 b = *p.copy;
1394     b.o = ivec(0, 0, 0);
1395 
1396     cube *oldworldroot = worldroot;
1397     int oldworldscale = worldscale, oldworldsize = worldsize;
1398 
1399     worldroot = newcubes();
1400     worldscale = 1;
1401     worldsize = 2;
1402     while(worldsize < max(max(b.s.x, b.s.y), b.s.z)*b.grid)
1403     {
1404         worldscale++;
1405         worldsize *= 2;
1406     }
1407 
1408     cube *s = p.copy->c();
1409     loopxyz(b, b.grid, if(!isempty(*s) || s->children) pastecube(*s, c); s++);
1410 
1411     prefabmesh r;
1412     neighbourstack[++neighbourdepth] = worldroot;
1413     loopi(8) genprefabmesh(r, worldroot[i], ivec(i, ivec(0, 0, 0), worldsize/2), worldsize/2);
1414     --neighbourdepth;
1415     r.setup(p);
1416 
1417     freeocta(worldroot);
1418 
1419     worldroot = oldworldroot;
1420     worldscale = oldworldscale;
1421     worldsize = oldworldsize;
1422 
1423     useshaderbyname("prefab");
1424 }
1425 
1426 extern int outlinecolour;
1427 
renderprefab(prefab & p,const vec & o,float yaw,float pitch,float roll,float size,const vec & color)1428 static void renderprefab(prefab &p, const vec &o, float yaw, float pitch, float roll, float size, const vec &color)
1429 {
1430     if(!p.numtris)
1431     {
1432         genprefabmesh(p);
1433         if(!p.numtris) return;
1434     }
1435 
1436     block3 &b = *p.copy;
1437 
1438     matrix4 m;
1439     m.identity();
1440     m.settranslation(o);
1441     if(yaw) m.rotate_around_z(yaw*RAD);
1442     if(pitch) m.rotate_around_x(pitch*RAD);
1443     if(roll) m.rotate_around_y(-roll*RAD);
1444     matrix3 w(m);
1445     if(size > 0 && size != 1) m.scale(size);
1446     m.translate(vec(b.s).mul(-b.grid*0.5f));
1447 
1448     gle::bindvbo(p.vbo);
1449     gle::bindebo(p.ebo);
1450     gle::enablevertex();
1451     gle::enablenormal();
1452     prefabmesh::vertex *v = (prefabmesh::vertex *)0;
1453     gle::vertexpointer(sizeof(prefabmesh::vertex), v->pos.v);
1454     gle::normalpointer(sizeof(prefabmesh::vertex), v->norm.v, GL_BYTE);
1455 
1456     matrix4 pm;
1457     pm.mul(camprojmatrix, m);
1458     GLOBALPARAM(prefabmatrix, pm);
1459     GLOBALPARAM(prefabworld, w);
1460     SETSHADER(prefab);
1461     gle::color(color);
1462     glDrawRangeElements_(GL_TRIANGLES, 0, p.numverts-1, p.numtris*3, GL_UNSIGNED_SHORT, (ushort *)0);
1463 
1464     glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
1465     enablepolygonoffset(GL_POLYGON_OFFSET_LINE);
1466 
1467     pm.mul(camprojmatrix, m);
1468     GLOBALPARAM(prefabmatrix, pm);
1469     SETSHADER(prefab);
1470     gle::color(vec::hexcolor(outlinecolour));
1471     glDrawRangeElements_(GL_TRIANGLES, 0, p.numverts-1, p.numtris*3, GL_UNSIGNED_SHORT, (ushort *)0);
1472 
1473     disablepolygonoffset(GL_POLYGON_OFFSET_LINE);
1474     glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
1475 
1476     gle::disablevertex();
1477     gle::disablenormal();
1478     gle::clearebo();
1479     gle::clearvbo();
1480 }
1481 
renderprefab(const char * name,const vec & o,float yaw,float pitch,float roll,float size,const vec & color)1482 void renderprefab(const char *name, const vec &o, float yaw, float pitch, float roll, float size, const vec &color)
1483 {
1484     prefab *p = loadprefab(name, false);
1485     if(p) renderprefab(*p, o, yaw, pitch, roll, size, color);
1486 }
1487 
previewprefab(const char * name,const vec & color)1488 void previewprefab(const char *name, const vec &color)
1489 {
1490     prefab *p = loadprefab(name, false);
1491     if(p)
1492     {
1493         block3 &b = *p->copy;
1494         float yaw;
1495         vec o = calcmodelpreviewpos(vec(b.s).mul(b.grid*0.5f), yaw);
1496         renderprefab(*p, o, yaw, 0, 0, 1, color);
1497     }
1498 }
1499 
mpcopy(editinfo * & e,selinfo & sel,bool local)1500 void mpcopy(editinfo *&e, selinfo &sel, bool local)
1501 {
1502     if(local) game::edittrigger(sel, EDIT_COPY);
1503     if(e==NULL) e = editinfos.add(new editinfo);
1504     if(e->copy) freeblock(e->copy);
1505     e->copy = NULL;
1506     protectsel(e->copy = blockcopy(block3(sel), sel.grid));
1507     changed(sel);
1508 }
1509 
mppaste(editinfo * & e,selinfo & sel,bool local)1510 void mppaste(editinfo *&e, selinfo &sel, bool local)
1511 {
1512     if(e==NULL) return;
1513     if(local) game::edittrigger(sel, EDIT_PASTE);
1514     if(e->copy) pasteblock(*e->copy, sel, local);
1515 }
1516 
copy()1517 void copy()
1518 {
1519     if(noedit(true)) return;
1520     mpcopy(localedit, sel, true);
1521 }
1522 
pastehilite()1523 void pastehilite()
1524 {
1525     if(!localedit) return;
1526 	sel.s = localedit->copy->s;
1527     reorient();
1528     havesel = true;
1529 }
1530 
paste()1531 void paste()
1532 {
1533     if(noedit(true)) return;
1534     mppaste(localedit, sel, true);
1535 }
1536 
1537 COMMAND(copy, "");
1538 COMMAND(pastehilite, "");
1539 COMMAND(paste, "");
1540 COMMANDN(undo, editundo, "");
1541 COMMANDN(redo, editredo, "");
1542 
1543 static vector<int *> editingvslots;
1544 struct vslotref
1545 {
vslotrefvslotref1546     vslotref(int &index) { editingvslots.add(&index); }
~vslotrefvslotref1547     ~vslotref() { editingvslots.pop(); }
1548 };
1549 #define editingvslot(...) vslotref vslotrefs[] = { __VA_ARGS__ }; (void)vslotrefs;
1550 
compacteditvslots()1551 void compacteditvslots()
1552 {
1553     loopv(editingvslots) if(*editingvslots[i]) compactvslot(*editingvslots[i]);
1554     loopv(unpackingvslots) compactvslot(*unpackingvslots[i].vslot);
1555     loopv(editinfos)
1556     {
1557         editinfo *e = editinfos[i];
1558         compactvslots(e->copy->c(), e->copy->size());
1559     }
1560     for(undoblock *u = undos.first; u; u = u->next)
1561         if(!u->numents)
1562             compactvslots(u->block()->c(), u->block()->size());
1563     for(undoblock *u = redos.first; u; u = u->next)
1564         if(!u->numents)
1565             compactvslots(u->block()->c(), u->block()->size());
1566 }
1567 
1568 ///////////// height maps ////////////////
1569 
1570 #define MAXBRUSH    64
1571 #define MAXBRUSHC   63
1572 #define MAXBRUSH2   32
1573 int brush[MAXBRUSH][MAXBRUSH];
1574 VAR(brushx, 0, MAXBRUSH2, MAXBRUSH);
1575 VAR(brushy, 0, MAXBRUSH2, MAXBRUSH);
1576 bool paintbrush = 0;
1577 int brushmaxx = 0, brushminx = MAXBRUSH;
1578 int brushmaxy = 0, brushminy = MAXBRUSH;
1579 
clearbrush()1580 void clearbrush()
1581 {
1582     memset(brush, 0, sizeof brush);
1583     brushmaxx = brushmaxy = 0;
1584     brushminx = brushminy = MAXBRUSH;
1585     paintbrush = false;
1586 }
1587 
brushvert(int * x,int * y,int * v)1588 void brushvert(int *x, int *y, int *v)
1589 {
1590     *x += MAXBRUSH2 - brushx + 1; // +1 for automatic padding
1591     *y += MAXBRUSH2 - brushy + 1;
1592     if(*x<0 || *y<0 || *x>=MAXBRUSH || *y>=MAXBRUSH) return;
1593     brush[*x][*y] = clamp(*v, 0, 8);
1594     paintbrush = paintbrush || (brush[*x][*y] > 0);
1595     brushmaxx = min(MAXBRUSH-1, max(brushmaxx, *x+1));
1596     brushmaxy = min(MAXBRUSH-1, max(brushmaxy, *y+1));
1597     brushminx = max(0,          min(brushminx, *x-1));
1598     brushminy = max(0,          min(brushminy, *y-1));
1599 }
1600 
1601 vector<int> htextures;
1602 
1603 COMMAND(clearbrush, "");
1604 COMMAND(brushvert, "iii");
hmapcancel()1605 void hmapcancel() { htextures.setsize(0); }
1606 COMMAND(hmapcancel, "");
1607 ICOMMAND(hmapselect, "", (),
1608     int t = lookupcube(cur).texture[orient];
1609     int i = htextures.find(t);
1610     if(i<0)
1611         htextures.add(t);
1612     else
1613         htextures.remove(i);
1614 );
1615 
isheightmap(int o,int d,bool empty,cube * c)1616 inline bool isheightmap(int o, int d, bool empty, cube *c)
1617 {
1618     return havesel ||
1619            (empty && isempty(*c)) ||
1620            htextures.empty() ||
1621            htextures.find(c->texture[o]) >= 0;
1622 }
1623 
1624 namespace hmap
1625 {
1626 #   define PAINTED     1
1627 #   define NOTHMAP     2
1628 #   define MAPPED      16
1629     uchar  flags[MAXBRUSH][MAXBRUSH];
1630     cube   *cmap[MAXBRUSHC][MAXBRUSHC][4];
1631     int    mapz[MAXBRUSHC][MAXBRUSHC];
1632     int    map [MAXBRUSH][MAXBRUSH];
1633 
1634     selinfo changes;
1635     bool selecting;
1636     int d, dc, dr, dcr, biasup, br, hws, fg;
1637     int gx, gy, gz, mx, my, mz, nx, ny, nz, bmx, bmy, bnx, bny;
1638     uint fs;
1639     selinfo hundo;
1640 
getcube(ivec t,int f)1641     cube *getcube(ivec t, int f)
1642     {
1643         t[d] += dcr*f*gridsize;
1644         if(t[d] > nz || t[d] < mz) return NULL;
1645         cube *c = &lookupcube(t, gridsize);
1646         if(c->children) forcemip(*c, false);
1647         discardchildren(*c, true);
1648         if(!isheightmap(sel.orient, d, true, c)) return NULL;
1649         if     (t.x < changes.o.x) changes.o.x = t.x;
1650         else if(t.x > changes.s.x) changes.s.x = t.x;
1651         if     (t.y < changes.o.y) changes.o.y = t.y;
1652         else if(t.y > changes.s.y) changes.s.y = t.y;
1653         if     (t.z < changes.o.z) changes.o.z = t.z;
1654         else if(t.z > changes.s.z) changes.s.z = t.z;
1655         return c;
1656     }
1657 
getface(cube * c,int d)1658     uint getface(cube *c, int d)
1659     {
1660         return  0x0f0f0f0f & ((dc ? c->faces[d] : 0x88888888 - c->faces[d]) >> fs);
1661     }
1662 
pushside(cube & c,int d,int x,int y,int z)1663     void pushside(cube &c, int d, int x, int y, int z)
1664     {
1665         ivec a;
1666         getcubevector(c, d, x, y, z, a);
1667         a[R[d]] = 8 - a[R[d]];
1668         setcubevector(c, d, x, y, z, a);
1669     }
1670 
addpoint(int x,int y,int z,int v)1671     void addpoint(int x, int y, int z, int v)
1672     {
1673         if(!(flags[x][y] & MAPPED))
1674           map[x][y] = v + (z*8);
1675         flags[x][y] |= MAPPED;
1676     }
1677 
select(int x,int y,int z)1678     void select(int x, int y, int z)
1679     {
1680         if((NOTHMAP & flags[x][y]) || (PAINTED & flags[x][y])) return;
1681         ivec t(d, x+gx, y+gy, dc ? z : hws-z);
1682         t.shl(gridpower);
1683 
1684         // selections may damage; must makeundo before
1685         hundo.o = t;
1686         hundo.o[D[d]] -= dcr*gridsize*2;
1687         makeundo(hundo);
1688 
1689         cube **c = cmap[x][y];
1690         loopk(4) c[k] = NULL;
1691         c[1] = getcube(t, 0);
1692         if(!c[1] || !isempty(*c[1]))
1693         {   // try up
1694             c[2] = c[1];
1695             c[1] = getcube(t, 1);
1696             if(!c[1] || isempty(*c[1])) { c[0] = c[1]; c[1] = c[2]; c[2] = NULL; }
1697             else { z++; t[d]+=fg; }
1698         }
1699         else // drop down
1700         {
1701             z--;
1702             t[d]-= fg;
1703             c[0] = c[1];
1704             c[1] = getcube(t, 0);
1705         }
1706 
1707         if(!c[1] || isempty(*c[1])) { flags[x][y] |= NOTHMAP; return; }
1708 
1709         flags[x][y] |= PAINTED;
1710         mapz [x][y]  = z;
1711 
1712         if(!c[0]) c[0] = getcube(t, 1);
1713         if(!c[2]) c[2] = getcube(t, -1);
1714         c[3] = getcube(t, -2);
1715         c[2] = !c[2] || isempty(*c[2]) ? NULL : c[2];
1716         c[3] = !c[3] || isempty(*c[3]) ? NULL : c[3];
1717 
1718         uint face = getface(c[1], d);
1719         if(face == 0x08080808 && (!c[0] || !isempty(*c[0]))) { flags[x][y] |= NOTHMAP; return; }
1720         if(c[1]->faces[R[d]] == F_SOLID)   // was single
1721             face += 0x08080808;
1722         else                               // was pair
1723             face += c[2] ? getface(c[2], d) : 0x08080808;
1724         face += 0x08080808;                // c[3]
1725         uchar *f = (uchar*)&face;
1726         addpoint(x,   y,   z, f[0]);
1727         addpoint(x+1, y,   z, f[1]);
1728         addpoint(x,   y+1, z, f[2]);
1729         addpoint(x+1, y+1, z, f[3]);
1730 
1731         if(selecting) // continue to adjacent cubes
1732         {
1733             if(x>bmx) select(x-1, y, z);
1734             if(x<bnx) select(x+1, y, z);
1735             if(y>bmy) select(x, y-1, z);
1736             if(y<bny) select(x, y+1, z);
1737         }
1738     }
1739 
ripple(int x,int y,int z,bool force)1740     void ripple(int x, int y, int z, bool force)
1741     {
1742         if(force) select(x, y, z);
1743         if((NOTHMAP & flags[x][y]) || !(PAINTED & flags[x][y])) return;
1744 
1745         bool changed = false;
1746         int *o[4], best, par, q = 0;
1747         loopi(2) loopj(2) o[i+j*2] = &map[x+i][y+j];
1748         #define pullhmap(I, LT, GT, M, N, A) do { \
1749             best = I; \
1750             loopi(4) if(*o[i] LT best) best = *o[q = i] - M; \
1751             par = (best&(~7)) + N; \
1752             /* dual layer for extra smoothness */ \
1753             if(*o[q^3] GT par && !(*o[q^1] LT par || *o[q^2] LT par)) { \
1754                 if(*o[q^3] GT par A 8 || *o[q^1] != par || *o[q^2] != par) { \
1755                     *o[q^3] = (*o[q^3] GT par A 8 ? par A 8 : *o[q^3]); \
1756                     *o[q^1] = *o[q^2] = par; \
1757                     changed = true; \
1758                 } \
1759             /* single layer */ \
1760             } else { \
1761                 loopj(4) if(*o[j] GT par) { \
1762                     *o[j] = par; \
1763                     changed = true; \
1764                 } \
1765             } \
1766         } while(0)
1767 
1768         if(biasup)
1769             pullhmap(0, >, <, 1, 0, -);
1770         else
1771             pullhmap(worldsize*8, <, >, 0, 8, +);
1772 
1773         cube **c  = cmap[x][y];
1774         int e[2][2];
1775         int notempty = 0;
1776 
1777         loopk(4) if(c[k]) {
1778             loopi(2) loopj(2) {
1779                 e[i][j] = min(8, map[x+i][y+j] - (mapz[x][y]+3-k)*8);
1780                 notempty |= e[i][j] > 0;
1781             }
1782             if(notempty)
1783             {
1784                 c[k]->texture[sel.orient] = c[1]->texture[sel.orient];
1785                 solidfaces(*c[k]);
1786                 loopi(2) loopj(2)
1787                 {
1788                     int f = e[i][j];
1789                     if(f<0 || (f==0 && e[1-i][j]==0 && e[i][1-j]==0))
1790                     {
1791                         f=0;
1792                         pushside(*c[k], d, i, j, 0);
1793                         pushside(*c[k], d, i, j, 1);
1794                     }
1795                     edgeset(cubeedge(*c[k], d, i, j), dc, dc ? f : 8-f);
1796                 }
1797             }
1798             else
1799                 emptyfaces(*c[k]);
1800         }
1801 
1802         if(!changed) return;
1803         if(x>mx) ripple(x-1, y, mapz[x][y], true);
1804         if(x<nx) ripple(x+1, y, mapz[x][y], true);
1805         if(y>my) ripple(x, y-1, mapz[x][y], true);
1806         if(y<ny) ripple(x, y+1, mapz[x][y], true);
1807 
1808 #define DIAGONAL_RIPPLE(a,b,exp) if(exp) { \
1809             if(flags[x a][ y] & PAINTED) \
1810                 ripple(x a, y b, mapz[x a][y], true); \
1811             else if(flags[x][y b] & PAINTED) \
1812                 ripple(x a, y b, mapz[x][y b], true); \
1813         }
1814 
1815         DIAGONAL_RIPPLE(-1, -1, (x>mx && y>my)); // do diagonals because adjacents
1816         DIAGONAL_RIPPLE(-1, +1, (x>mx && y<ny)); //    won't unless changed
1817         DIAGONAL_RIPPLE(+1, +1, (x<nx && y<ny));
1818         DIAGONAL_RIPPLE(+1, -1, (x<nx && y>my));
1819     }
1820 
1821 #define loopbrush(i) for(int x=bmx; x<=bnx+i; x++) for(int y=bmy; y<=bny+i; y++)
1822 
paint()1823     void paint()
1824     {
1825         loopbrush(1)
1826             map[x][y] -= dr * brush[x][y];
1827     }
1828 
smooth()1829     void smooth()
1830     {
1831         int sum, div;
1832         loopbrush(-2)
1833         {
1834             sum = 0;
1835             div = 9;
1836             loopi(3) loopj(3)
1837                 if(flags[x+i][y+j] & MAPPED)
1838                     sum += map[x+i][y+j];
1839                 else div--;
1840             if(div)
1841                 map[x+1][y+1] = sum / div;
1842         }
1843     }
1844 
rippleandset()1845     void rippleandset()
1846     {
1847         loopbrush(0)
1848             ripple(x, y, gz, false);
1849     }
1850 
run(int dir,int mode)1851     void run(int dir, int mode)
1852     {
1853         d  = dimension(sel.orient);
1854         dc = dimcoord(sel.orient);
1855         dcr= dc ? 1 : -1;
1856         dr = dir>0 ? 1 : -1;
1857         br = dir>0 ? 0x08080808 : 0;
1858      //   biasup = mode == dir<0;
1859         biasup = dir<0;
1860         bool paintme = paintbrush;
1861         int cx = (sel.corner&1 ? 0 : -1);
1862         int cy = (sel.corner&2 ? 0 : -1);
1863         hws= (worldsize>>gridpower);
1864         gx = (cur[R[d]] >> gridpower) + cx - MAXBRUSH2;
1865         gy = (cur[C[d]] >> gridpower) + cy - MAXBRUSH2;
1866         gz = (cur[D[d]] >> gridpower);
1867         fs = dc ? 4 : 0;
1868         fg = dc ? gridsize : -gridsize;
1869         mx = max(0, -gx); // ripple range
1870         my = max(0, -gy);
1871         nx = min(MAXBRUSH-1, hws-gx) - 1;
1872         ny = min(MAXBRUSH-1, hws-gy) - 1;
1873         if(havesel)
1874         {   // selection range
1875             bmx = mx = max(mx, (sel.o[R[d]]>>gridpower)-gx);
1876             bmy = my = max(my, (sel.o[C[d]]>>gridpower)-gy);
1877             bnx = nx = min(nx, (sel.s[R[d]]+(sel.o[R[d]]>>gridpower))-gx-1);
1878             bny = ny = min(ny, (sel.s[C[d]]+(sel.o[C[d]]>>gridpower))-gy-1);
1879         }
1880         if(havesel && mode<0) // -ve means smooth selection
1881             paintme = false;
1882         else
1883         {   // brush range
1884             bmx = max(mx, brushminx);
1885             bmy = max(my, brushminy);
1886             bnx = min(nx, brushmaxx-1);
1887             bny = min(ny, brushmaxy-1);
1888         }
1889         nz = worldsize-gridsize;
1890         mz = 0;
1891         hundo.s = ivec(d,1,1,5);
1892         hundo.orient = sel.orient;
1893         hundo.grid = gridsize;
1894         forcenextundo();
1895 
1896         changes.grid = gridsize;
1897         changes.s = changes.o = cur;
1898         memset(map, 0, sizeof map);
1899         memset(flags, 0, sizeof flags);
1900 
1901         selecting = true;
1902         select(clamp(MAXBRUSH2-cx, bmx, bnx),
1903                clamp(MAXBRUSH2-cy, bmy, bny),
1904                dc ? gz : hws - gz);
1905         selecting = false;
1906         if(paintme)
1907             paint();
1908         else
1909             smooth();
1910         rippleandset();                       // pull up points to cubify, and set
1911         changes.s.sub(changes.o).shr(gridpower).add(1);
1912         changed(changes);
1913     }
1914 }
1915 
edithmap(int dir,int mode)1916 void edithmap(int dir, int mode) {
1917     if((nompedit && multiplayer()) || !hmapsel) return;
1918     hmap::run(dir, mode);
1919 }
1920 
1921 ///////////// main cube edit ////////////////
1922 
bounded(int n)1923 int bounded(int n) { return n<0 ? 0 : (n>8 ? 8 : n); }
1924 
pushedge(uchar & edge,int dir,int dc)1925 void pushedge(uchar &edge, int dir, int dc)
1926 {
1927     int ne = bounded(edgeget(edge, dc)+dir);
1928     edgeset(edge, dc, ne);
1929     int oe = edgeget(edge, 1-dc);
1930     if((dir<0 && dc && oe>ne) || (dir>0 && dc==0 && oe<ne)) edgeset(edge, 1-dc, ne);
1931 }
1932 
linkedpush(cube & c,int d,int x,int y,int dc,int dir)1933 void linkedpush(cube &c, int d, int x, int y, int dc, int dir)
1934 {
1935     ivec v, p;
1936     getcubevector(c, d, x, y, dc, v);
1937 
1938     loopi(2) loopj(2)
1939     {
1940         getcubevector(c, d, i, j, dc, p);
1941         if(v==p)
1942             pushedge(cubeedge(c, d, i, j), dir, dc);
1943     }
1944 }
1945 
getmaterial(cube & c)1946 static ushort getmaterial(cube &c)
1947 {
1948     if(c.children)
1949     {
1950         ushort mat = getmaterial(c.children[7]);
1951         loopi(7) if(mat != getmaterial(c.children[i])) return MAT_AIR;
1952         return mat;
1953     }
1954     return c.material;
1955 }
1956 
1957 VAR(invalidcubeguard, 0, 1, 1);
1958 
mpeditface(int dir,int mode,selinfo & sel,bool local)1959 void mpeditface(int dir, int mode, selinfo &sel, bool local)
1960 {
1961     if(mode==1 && (sel.cx || sel.cy || sel.cxs&1 || sel.cys&1)) mode = 0;
1962     int d = dimension(sel.orient);
1963     int dc = dimcoord(sel.orient);
1964     int seldir = dc ? -dir : dir;
1965 
1966     if(local)
1967         game::edittrigger(sel, EDIT_FACE, dir, mode);
1968 
1969     if(mode==1)
1970     {
1971         int h = sel.o[d]+dc*sel.grid;
1972         if(((dir>0) == dc && h<=0) || ((dir<0) == dc && h>=worldsize)) return;
1973         if(dir<0) sel.o[d] += sel.grid * seldir;
1974     }
1975 
1976     if(dc) sel.o[d] += sel.us(d)-sel.grid;
1977     sel.s[d] = 1;
1978 
1979     loopselxyz(
1980         if(c.children) solidfaces(c);
1981         ushort mat = getmaterial(c);
1982         discardchildren(c, true);
1983         c.material = mat;
1984         if(mode==1) // fill command
1985         {
1986             if(dir<0)
1987             {
1988                 solidfaces(c);
1989                 cube &o = blockcube(x, y, 1, sel, -sel.grid);
1990                 loopi(6)
1991                     c.texture[i] = o.children ? DEFAULT_GEOM : o.texture[i];
1992             }
1993             else
1994                 emptyfaces(c);
1995         }
1996         else
1997         {
1998             uint bak = c.faces[d];
1999             uchar *p = (uchar *)&c.faces[d];
2000 
2001             if(mode==2)
2002                 linkedpush(c, d, sel.corner&1, sel.corner>>1, dc, seldir); // corner command
2003             else
2004             {
2005                 loop(mx,2) loop(my,2)                                       // pull/push edges command
2006                 {
2007                     if(x==0 && mx==0 && sel.cx) continue;
2008                     if(y==0 && my==0 && sel.cy) continue;
2009                     if(x==sel.s[R[d]]-1 && mx==1 && (sel.cx+sel.cxs)&1) continue;
2010                     if(y==sel.s[C[d]]-1 && my==1 && (sel.cy+sel.cys)&1) continue;
2011                     if(p[mx+my*2] != ((uchar *)&bak)[mx+my*2]) continue;
2012 
2013                     linkedpush(c, d, mx, my, dc, seldir);
2014                 }
2015             }
2016 
2017             optiface(p, c);
2018             if(invalidcubeguard==1 && !isvalidcube(c))
2019             {
2020                 uint newbak = c.faces[d];
2021                 uchar *m = (uchar *)&bak;
2022                 uchar *n = (uchar *)&newbak;
2023                 loopk(4) if(n[k] != m[k]) // tries to find partial edit that is valid
2024                 {
2025                     c.faces[d] = bak;
2026                     c.edges[d*4+k] = n[k];
2027                     if(isvalidcube(c))
2028                         m[k] = n[k];
2029                 }
2030                 c.faces[d] = bak;
2031             }
2032         }
2033     );
2034     if (mode==1 && dir>0)
2035         sel.o[d] += sel.grid * seldir;
2036 }
2037 
editface(int * dir,int * mode)2038 void editface(int *dir, int *mode)
2039 {
2040     if(noedit(moving!=0)) return;
2041     if(hmapedit!=1)
2042         mpeditface(*dir, *mode, sel, true);
2043     else
2044         edithmap(*dir, *mode);
2045 }
2046 
2047 VAR(selectionsurf, 0, 0, 1);
2048 
pushsel(int * dir)2049 void pushsel(int *dir)
2050 {
2051     if(noedit(moving!=0)) return;
2052     int d = dimension(orient);
2053     int s = dimcoord(orient) ? -*dir : *dir;
2054     sel.o[d] += s*sel.grid;
2055     if(selectionsurf==1)
2056     {
2057         player->o[d] += s*sel.grid;
2058         player->resetinterp();
2059     }
2060 }
2061 
mpdelcube(selinfo & sel,bool local)2062 void mpdelcube(selinfo &sel, bool local)
2063 {
2064     if(local) game::edittrigger(sel, EDIT_DELCUBE);
2065     loopselxyz(discardchildren(c, true); emptyfaces(c));
2066 }
2067 
delcube()2068 void delcube()
2069 {
2070     if(noedit(true)) return;
2071     mpdelcube(sel, true);
2072 }
2073 
2074 COMMAND(pushsel, "i");
2075 COMMAND(editface, "ii");
2076 COMMAND(delcube, "");
2077 
2078 /////////// texture editing //////////////////
2079 
2080 int curtexindex = -1, lasttex = 0, lasttexmillis = -1;
2081 int texpaneltimer = 0;
2082 vector<ushort> texmru;
2083 
tofronttex()2084 void tofronttex()                                       // maintain most recently used of the texture lists when applying texture
2085 {
2086     int c = curtexindex;
2087     if(texmru.inrange(c))
2088     {
2089         texmru.insert(0, texmru.remove(c));
2090         curtexindex = -1;
2091     }
2092 }
2093 
2094 selinfo repsel;
2095 int reptex = -1;
2096 
2097 static vector<vslotmap> remappedvslots;
2098 
2099 VAR(usevdelta, 1, 0, 0);
2100 
remapvslot(int index,bool delta,const VSlot & ds)2101 static VSlot *remapvslot(int index, bool delta, const VSlot &ds)
2102 {
2103     loopv(remappedvslots) if(remappedvslots[i].index == index) return remappedvslots[i].vslot;
2104     VSlot &vs = lookupvslot(index, false);
2105     if(vs.index < 0 || vs.index == DEFAULT_SKY) return NULL;
2106     VSlot *edit = NULL;
2107     if(delta)
2108     {
2109         VSlot ms;
2110         mergevslot(ms, vs, ds);
2111         edit = ms.changed ? editvslot(vs, ms) : vs.slot->variants;
2112     }
2113     else edit = ds.changed ? editvslot(vs, ds) : vs.slot->variants;
2114     if(!edit) edit = &vs;
2115     remappedvslots.add(vslotmap(vs.index, edit));
2116     return edit;
2117 }
2118 
remapvslots(cube & c,bool delta,const VSlot & ds,int orient,bool & findrep,VSlot * & findedit)2119 static void remapvslots(cube &c, bool delta, const VSlot &ds, int orient, bool &findrep, VSlot *&findedit)
2120 {
2121     if(c.children)
2122     {
2123         loopi(8) remapvslots(c.children[i], delta, ds, orient, findrep, findedit);
2124         return;
2125     }
2126     static VSlot ms;
2127     if(orient<0) loopi(6)
2128     {
2129         VSlot *edit = remapvslot(c.texture[i], delta, ds);
2130         if(edit)
2131         {
2132             c.texture[i] = edit->index;
2133             if(!findedit) findedit = edit;
2134         }
2135     }
2136     else
2137     {
2138         int i = visibleorient(c, orient);
2139         VSlot *edit = remapvslot(c.texture[i], delta, ds);
2140         if(edit)
2141         {
2142             if(findrep)
2143             {
2144                 if(reptex < 0) reptex = c.texture[i];
2145                 else if(reptex != c.texture[i]) findrep = false;
2146             }
2147             c.texture[i] = edit->index;
2148             if(!findedit) findedit = edit;
2149         }
2150     }
2151 }
2152 
edittexcube(cube & c,int tex,int orient,bool & findrep)2153 void edittexcube(cube &c, int tex, int orient, bool &findrep)
2154 {
2155     if(orient<0) loopi(6) c.texture[i] = tex;
2156     else
2157     {
2158         int i = visibleorient(c, orient);
2159         if(findrep)
2160         {
2161             if(reptex < 0) reptex = c.texture[i];
2162             else if(reptex != c.texture[i]) findrep = false;
2163         }
2164         c.texture[i] = tex;
2165     }
2166     if(c.children) loopi(8) edittexcube(c.children[i], tex, orient, findrep);
2167 }
2168 
2169 VAR(allfaces, 0, 0, 1);
2170 
mpeditvslot(int delta,VSlot & ds,int allfaces,selinfo & sel,bool local)2171 void mpeditvslot(int delta, VSlot &ds, int allfaces, selinfo &sel, bool local)
2172 {
2173     if(local)
2174     {
2175         game::edittrigger(sel, EDIT_VSLOT, delta, allfaces, 0, &ds);
2176         if(!(lastsel==sel)) tofronttex();
2177         if(allfaces || !(repsel == sel)) reptex = -1;
2178         repsel = sel;
2179     }
2180     bool findrep = local && !allfaces && reptex < 0;
2181     VSlot *findedit = NULL;
2182     loopselxyz(remapvslots(c, delta != 0, ds, allfaces ? -1 : sel.orient, findrep, findedit));
2183     remappedvslots.setsize(0);
2184     if(local && findedit)
2185     {
2186         lasttex = findedit->index;
2187         lasttexmillis = totalmillis;
2188         curtexindex = texmru.find(lasttex);
2189         if(curtexindex < 0)
2190         {
2191             curtexindex = texmru.length();
2192             texmru.add(lasttex);
2193         }
2194     }
2195 }
2196 
mpeditvslot(int delta,int allfaces,selinfo & sel,ucharbuf & buf)2197 bool mpeditvslot(int delta, int allfaces, selinfo &sel, ucharbuf &buf)
2198 {
2199     VSlot ds;
2200     if(!unpackvslot(buf, ds, delta != 0)) return false;
2201     editingvslot(ds.layer);
2202     mpeditvslot(delta, ds, allfaces, sel, false);
2203     return true;
2204 }
2205 
vdelta(char * body)2206 void vdelta(char *body)
2207 {
2208     if(noedit()) return;
2209     usevdelta++;
2210     execute(body);
2211     usevdelta--;
2212 }
2213 COMMAND(vdelta, "s");
2214 
vrotate(int * n)2215 void vrotate(int *n)
2216 {
2217     if(noedit()) return;
2218     VSlot ds;
2219     ds.changed = 1<<VSLOT_ROTATION;
2220     ds.rotation = usevdelta ? *n : clamp(*n, 0, 7);
2221     mpeditvslot(usevdelta, ds, allfaces, sel, true);
2222 }
2223 COMMAND(vrotate, "i");
2224 ICOMMAND(getvrotate, "i", (int *tex), intret(lookupvslot(*tex, false).rotation));
2225 
voffset(int * x,int * y)2226 void voffset(int *x, int *y)
2227 {
2228     if(noedit()) return;
2229     VSlot ds;
2230     ds.changed = 1<<VSLOT_OFFSET;
2231     ds.offset = usevdelta ? ivec2(*x, *y) : ivec2(*x, *y).max(0);
2232     mpeditvslot(usevdelta, ds, allfaces, sel, true);
2233 }
2234 COMMAND(voffset, "ii");
2235 ICOMMAND(getvoffset, "i", (int *tex),
2236 {
2237     VSlot &vslot = lookupvslot(*tex, false);
2238     defformatstring(str, "%d %d", vslot.offset.x, vslot.offset.y);
2239     result(str);
2240 });
2241 
vscroll(float * s,float * t)2242 void vscroll(float *s, float *t)
2243 {
2244     if(noedit()) return;
2245     VSlot ds;
2246     ds.changed = 1<<VSLOT_SCROLL;
2247     ds.scroll = vec2(*s, *t).div(1000);
2248     mpeditvslot(usevdelta, ds, allfaces, sel, true);
2249 }
2250 COMMAND(vscroll, "ff");
2251 ICOMMAND(getvscroll, "i", (int *tex),
2252 {
2253     VSlot &vslot = lookupvslot(*tex, false);
2254     defformatstring(str, "%s %s", floatstr(vslot.scroll.x), floatstr(vslot.scroll.y));
2255     result(str);
2256 });
2257 
vscale(float * scale)2258 void vscale(float *scale)
2259 {
2260     if(noedit()) return;
2261     VSlot ds;
2262     ds.changed = 1<<VSLOT_SCALE;
2263     ds.scale = *scale <= 0 ? 1 : (usevdelta ? *scale : clamp(*scale, 1/8.0f, 8.0f));
2264     mpeditvslot(usevdelta, ds, allfaces, sel, true);
2265 }
2266 COMMAND(vscale, "f");
2267 ICOMMAND(getvscale, "i", (int *tex), floatret(lookupvslot(*tex, false).scale));
2268 
vlayer(int * n)2269 void vlayer(int *n)
2270 {
2271     if(noedit()) return;
2272     VSlot ds;
2273     ds.changed = 1<<VSLOT_LAYER;
2274     if(vslots.inrange(*n))
2275     {
2276         ds.layer = *n;
2277         if(vslots[ds.layer]->changed && nompedit && multiplayer()) return;
2278     }
2279     editingvslot(ds.layer);
2280     mpeditvslot(usevdelta, ds, allfaces, sel, true);
2281 }
2282 COMMAND(vlayer, "i");
2283 ICOMMAND(getvlayer, "i", (int *tex), intret(lookupvslot(*tex, false).layer));
2284 
valpha(float * front,float * back)2285 void valpha(float *front, float *back)
2286 {
2287     if(noedit()) return;
2288     VSlot ds;
2289     ds.changed = 1<<VSLOT_ALPHA;
2290     ds.alphafront = clamp(*front, 0.0f, 1.0f);
2291     ds.alphaback = clamp(*back, 0.0f, 1.0f);
2292     mpeditvslot(usevdelta, ds, allfaces, sel, true);
2293 }
2294 COMMAND(valpha, "ff");
2295 ICOMMAND(getvalpha, "i", (int *tex),
2296 {
2297     VSlot &vslot = lookupvslot(*tex, false);
2298     defformatstring(str, "%s %s", floatstr(vslot.alphafront), floatstr(vslot.alphaback));
2299     result(str);
2300 });
2301 
vcolor(float * r,float * g,float * b)2302 void vcolor(float *r, float *g, float *b)
2303 {
2304     if(noedit()) return;
2305     VSlot ds;
2306     ds.changed = 1<<VSLOT_COLOR;
2307     ds.colorscale = vec(clamp(*r, 0.0f, 1.0f), clamp(*g, 0.0f, 1.0f), clamp(*b, 0.0f, 1.0f));
2308     mpeditvslot(usevdelta, ds, allfaces, sel, true);
2309 }
2310 COMMAND(vcolor, "fff");
2311 ICOMMAND(getvcolor, "i", (int *tex),
2312 {
2313     VSlot &vslot = lookupvslot(*tex, false);
2314     defformatstring(str, "%s %s %s", floatstr(vslot.colorscale.r), floatstr(vslot.colorscale.g), floatstr(vslot.colorscale.b));
2315     result(str);
2316 });
2317 
vreset()2318 void vreset()
2319 {
2320     if(noedit()) return;
2321     VSlot ds;
2322     mpeditvslot(usevdelta, ds, allfaces, sel, true);
2323 }
2324 COMMAND(vreset, "");
2325 
vshaderparam(const char * name,float * x,float * y,float * z,float * w)2326 void vshaderparam(const char *name, float *x, float *y, float *z, float *w)
2327 {
2328     if(noedit()) return;
2329     VSlot ds;
2330     ds.changed = 1<<VSLOT_SHPARAM;
2331     if(name[0])
2332     {
2333         SlotShaderParam p = { getshaderparamname(name), -1, {*x, *y, *z, *w} };
2334         ds.params.add(p);
2335     }
2336     mpeditvslot(usevdelta, ds, allfaces, sel, true);
2337 }
2338 COMMAND(vshaderparam, "sffff");
2339 ICOMMAND(getvshaderparam, "is", (int *tex, const char *name),
2340 {
2341     VSlot &vslot = lookupvslot(*tex, false);
2342     loopv(vslot.params)
2343     {
2344         SlotShaderParam &p = vslot.params[i];
2345         if(!strcmp(p.name, name))
2346         {
2347             defformatstring(str, "%s %s %s %s", floatstr(p.val[0]), floatstr(p.val[1]), floatstr(p.val[2]), floatstr(p.val[3]));
2348             result(str);
2349             return;
2350         }
2351     }
2352 });
2353 ICOMMAND(getvshaderparamnames, "i", (int *tex),
2354 {
2355     VSlot &vslot = lookupvslot(*tex, false);
2356     vector<char> str;
2357     loopv(vslot.params)
2358     {
2359         SlotShaderParam &p = vslot.params[i];
2360         if(i) str.put(' ');
2361         str.put(p.name, strlen(p.name));
2362     }
2363     str.add('\0');
2364     stringret(newstring(str.getbuf(), str.length()-1));
2365 });
2366 
mpedittex(int tex,int allfaces,selinfo & sel,bool local)2367 void mpedittex(int tex, int allfaces, selinfo &sel, bool local)
2368 {
2369     if(local)
2370     {
2371         game::edittrigger(sel, EDIT_TEX, tex, allfaces);
2372         if(allfaces || !(repsel == sel)) reptex = -1;
2373         repsel = sel;
2374     }
2375     bool findrep = local && !allfaces && reptex < 0;
2376     loopselxyz(edittexcube(c, tex, allfaces ? -1 : sel.orient, findrep));
2377 }
2378 
unpacktex(int & tex,ucharbuf & buf,bool insert=true)2379 static int unpacktex(int &tex, ucharbuf &buf, bool insert = true)
2380 {
2381     if(tex < 0x10000) return true;
2382     VSlot ds;
2383     if(!unpackvslot(buf, ds, false)) return false;
2384     VSlot &vs = *lookupslot(tex & 0xFFFF, false).variants;
2385     if(vs.index < 0 || vs.index == DEFAULT_SKY) return false;
2386     VSlot *edit = insert ? editvslot(vs, ds) : findvslot(*vs.slot, vs, ds);
2387     if(!edit) return false;
2388     tex = edit->index;
2389     return true;
2390 }
2391 
shouldpacktex(int index)2392 int shouldpacktex(int index)
2393 {
2394     if(vslots.inrange(index))
2395     {
2396         VSlot &vs = *vslots[index];
2397         if(vs.changed) return 0x10000 + vs.slot->index;
2398     }
2399     return 0;
2400 }
2401 
2402 
mpedittex(int tex,int allfaces,selinfo & sel,ucharbuf & buf)2403 bool mpedittex(int tex, int allfaces, selinfo &sel, ucharbuf &buf)
2404 {
2405     if(!unpacktex(tex, buf)) return false;
2406     mpedittex(tex, allfaces, sel, false);
2407     return true;
2408 }
2409 
filltexlist()2410 void filltexlist()
2411 {
2412     if(texmru.length()!=vslots.length())
2413     {
2414         loopvrev(texmru) if(texmru[i]>=vslots.length())
2415         {
2416             if(curtexindex > i) curtexindex--;
2417             else if(curtexindex == i) curtexindex = -1;
2418             texmru.remove(i);
2419         }
2420         loopv(vslots) if(texmru.find(i)<0) texmru.add(i);
2421     }
2422 }
2423 
compactmruvslots()2424 void compactmruvslots()
2425 {
2426     remappedvslots.setsize(0);
2427     loopvrev(texmru)
2428     {
2429         if(vslots.inrange(texmru[i]))
2430         {
2431             VSlot &vs = *vslots[texmru[i]];
2432             if(vs.index >= 0)
2433             {
2434                 texmru[i] = vs.index;
2435                 continue;
2436             }
2437         }
2438         if(curtexindex > i) curtexindex--;
2439         else if(curtexindex == i) curtexindex = -1;
2440         texmru.remove(i);
2441     }
2442     if(vslots.inrange(lasttex))
2443     {
2444         VSlot &vs = *vslots[lasttex];
2445         lasttex = vs.index >= 0 ? vs.index : 0;
2446     }
2447     else lasttex = 0;
2448     reptex = vslots.inrange(reptex) ? vslots[reptex]->index : -1;
2449 }
2450 
edittex(int i,bool save=true)2451 void edittex(int i, bool save = true)
2452 {
2453     lasttex = i;
2454     lasttexmillis = totalmillis;
2455     if(save)
2456     {
2457         loopvj(texmru) if(texmru[j]==lasttex) { curtexindex = j; break; }
2458     }
2459     mpedittex(i, allfaces, sel, true);
2460 }
2461 
edittex_(int * dir)2462 void edittex_(int *dir)
2463 {
2464     if(noedit()) return;
2465     filltexlist();
2466     if(texmru.empty()) return;
2467     texpaneltimer = 5000;
2468     if(!(lastsel==sel)) tofronttex();
2469     curtexindex = clamp(curtexindex<0 ? 0 : curtexindex+*dir, 0, texmru.length()-1);
2470     edittex(texmru[curtexindex], false);
2471 }
2472 
gettex()2473 void gettex()
2474 {
2475     if(noedit(true)) return;
2476     filltexlist();
2477     int tex = -1;
2478     loopxyz(sel, sel.grid, tex = c.texture[sel.orient]);
2479     loopv(texmru) if(texmru[i]==tex)
2480     {
2481         curtexindex = i;
2482         tofronttex();
2483         return;
2484     }
2485 }
2486 
getcurtex()2487 void getcurtex()
2488 {
2489     if(noedit(true)) return;
2490     filltexlist();
2491     int index = curtexindex < 0 ? 0 : curtexindex;
2492     if(!texmru.inrange(index)) return;
2493     intret(texmru[index]);
2494 }
2495 
getseltex()2496 void getseltex()
2497 {
2498     if(noedit(true)) return;
2499     cube &c = lookupcube(sel.o, -sel.grid);
2500     if(c.children || isempty(c)) return;
2501     intret(c.texture[sel.orient]);
2502 }
2503 
gettexname(int * tex,int * subslot)2504 void gettexname(int *tex, int *subslot)
2505 {
2506     if(noedit(true) || *tex<0) return;
2507     VSlot &vslot = lookupvslot(*tex, false);
2508     Slot &slot = *vslot.slot;
2509     if(!slot.sts.inrange(*subslot)) return;
2510     result(slot.sts[*subslot].name);
2511 }
2512 
getslottex(int * idx)2513 void getslottex(int *idx)
2514 {
2515     if(*idx < 0 || !slots.inrange(*idx)) { intret(-1); return; }
2516     Slot &slot = lookupslot(*idx, false);
2517     intret(slot.variants->index);
2518 }
2519 
2520 COMMANDN(edittex, edittex_, "i");
2521 COMMAND(gettex, "");
2522 COMMAND(getcurtex, "");
2523 COMMAND(getseltex, "");
2524 ICOMMAND(getreptex, "", (), { if(!noedit()) intret(vslots.inrange(reptex) ? reptex : -1); });
2525 COMMAND(gettexname, "ii");
2526 ICOMMAND(numvslots, "", (), intret(vslots.length()));
2527 ICOMMAND(numslots, "", (), intret(slots.length()));
2528 COMMAND(getslottex, "i");
2529 ICOMMAND(texloaded, "i", (int *tex), intret(slots.inrange(*tex) && slots[*tex]->loaded ? 1 : 0));
2530 
replacetexcube(cube & c,int oldtex,int newtex)2531 void replacetexcube(cube &c, int oldtex, int newtex)
2532 {
2533     loopi(6) if(c.texture[i] == oldtex) c.texture[i] = newtex;
2534     if(c.children) loopi(8) replacetexcube(c.children[i], oldtex, newtex);
2535 }
2536 
mpreplacetex(int oldtex,int newtex,bool insel,selinfo & sel,bool local)2537 void mpreplacetex(int oldtex, int newtex, bool insel, selinfo &sel, bool local)
2538 {
2539     if(local) game::edittrigger(sel, EDIT_REPLACE, oldtex, newtex, insel ? 1 : 0);
2540     if(insel)
2541     {
2542         loopselxyz(replacetexcube(c, oldtex, newtex));
2543     }
2544     else
2545     {
2546         loopi(8) replacetexcube(worldroot[i], oldtex, newtex);
2547     }
2548     allchanged();
2549 }
2550 
mpreplacetex(int oldtex,int newtex,bool insel,selinfo & sel,ucharbuf & buf)2551 bool mpreplacetex(int oldtex, int newtex, bool insel, selinfo &sel, ucharbuf &buf)
2552 {
2553     if(!unpacktex(oldtex, buf, false)) return false;
2554     editingvslot(oldtex);
2555     if(!unpacktex(newtex, buf)) return false;
2556     mpreplacetex(oldtex, newtex, insel, sel, false);
2557     return true;
2558 }
2559 
replace(bool insel)2560 void replace(bool insel)
2561 {
2562     if(noedit()) return;
2563     if(reptex < 0) { conoutf(CON_ERROR, "can only replace after a texture edit"); return; }
2564     mpreplacetex(reptex, lasttex, insel, sel, true);
2565 }
2566 
2567 ICOMMAND(replace, "", (), replace(false));
2568 ICOMMAND(replacesel, "", (), replace(true));
2569 
2570 ////////// flip and rotate ///////////////
dflip(uint face)2571 uint dflip(uint face) { return face==F_EMPTY ? face : 0x88888888 - (((face&0xF0F0F0F0)>>4) | ((face&0x0F0F0F0F)<<4)); }
cflip(uint face)2572 uint cflip(uint face) { return ((face&0xFF00FF00)>>8) | ((face&0x00FF00FF)<<8); }
rflip(uint face)2573 uint rflip(uint face) { return ((face&0xFFFF0000)>>16)| ((face&0x0000FFFF)<<16); }
mflip(uint face)2574 uint mflip(uint face) { return (face&0xFF0000FF) | ((face&0x00FF0000)>>8) | ((face&0x0000FF00)<<8); }
2575 
flipcube(cube & c,int d)2576 void flipcube(cube &c, int d)
2577 {
2578     swap(c.texture[d*2], c.texture[d*2+1]);
2579     c.faces[D[d]] = dflip(c.faces[D[d]]);
2580     c.faces[C[d]] = cflip(c.faces[C[d]]);
2581     c.faces[R[d]] = rflip(c.faces[R[d]]);
2582     if(c.children)
2583     {
2584         loopi(8) if(i&octadim(d)) swap(c.children[i], c.children[i-octadim(d)]);
2585         loopi(8) flipcube(c.children[i], d);
2586     }
2587 }
2588 
rotatequad(cube & a,cube & b,cube & c,cube & d)2589 void rotatequad(cube &a, cube &b, cube &c, cube &d)
2590 {
2591     cube t = a; a = b; b = c; c = d; d = t;
2592 }
2593 
rotatecube(cube & c,int d)2594 void rotatecube(cube &c, int d)   // rotates cube clockwise. see pics in cvs for help.
2595 {
2596     c.faces[D[d]] = cflip (mflip(c.faces[D[d]]));
2597     c.faces[C[d]] = dflip (mflip(c.faces[C[d]]));
2598     c.faces[R[d]] = rflip (mflip(c.faces[R[d]]));
2599     swap(c.faces[R[d]], c.faces[C[d]]);
2600 
2601     swap(c.texture[2*R[d]], c.texture[2*C[d]+1]);
2602     swap(c.texture[2*C[d]], c.texture[2*R[d]+1]);
2603     swap(c.texture[2*C[d]], c.texture[2*C[d]+1]);
2604 
2605     if(c.children)
2606     {
2607         int row = octadim(R[d]);
2608         int col = octadim(C[d]);
2609         for(int i=0; i<=octadim(d); i+=octadim(d)) rotatequad
2610         (
2611             c.children[i+row],
2612             c.children[i],
2613             c.children[i+col],
2614             c.children[i+col+row]
2615         );
2616         loopi(8) rotatecube(c.children[i], d);
2617     }
2618 }
2619 
mpflip(selinfo & sel,bool local)2620 void mpflip(selinfo &sel, bool local)
2621 {
2622     if(local)
2623     {
2624         game::edittrigger(sel, EDIT_FLIP);
2625         makeundo();
2626     }
2627     int zs = sel.s[dimension(sel.orient)];
2628     loopxy(sel)
2629     {
2630         loop(z,zs) flipcube(selcube(x, y, z), dimension(sel.orient));
2631         loop(z,zs/2)
2632         {
2633             cube &a = selcube(x, y, z);
2634             cube &b = selcube(x, y, zs-z-1);
2635             swap(a, b);
2636         }
2637     }
2638     changed(sel);
2639 }
2640 
flip()2641 void flip()
2642 {
2643     if(noedit()) return;
2644     mpflip(sel, true);
2645 }
2646 
mprotate(int cw,selinfo & sel,bool local)2647 void mprotate(int cw, selinfo &sel, bool local)
2648 {
2649     if(local) game::edittrigger(sel, EDIT_ROTATE, cw);
2650     int d = dimension(sel.orient);
2651     if(!dimcoord(sel.orient)) cw = -cw;
2652     int m = sel.s[C[d]] < sel.s[R[d]] ? C[d] : R[d];
2653     int ss = sel.s[m] = max(sel.s[R[d]], sel.s[C[d]]);
2654     if(local) makeundo();
2655     loop(z,sel.s[D[d]]) loopi(cw>0 ? 1 : 3)
2656     {
2657         loopxy(sel) rotatecube(selcube(x,y,z), d);
2658         loop(y,ss/2) loop(x,ss-1-y*2) rotatequad
2659         (
2660             selcube(ss-1-y, x+y, z),
2661             selcube(x+y, y, z),
2662             selcube(y, ss-1-x-y, z),
2663             selcube(ss-1-x-y, ss-1-y, z)
2664         );
2665     }
2666     changed(sel);
2667 }
2668 
rotate(int * cw)2669 void rotate(int *cw)
2670 {
2671     if(noedit()) return;
2672     mprotate(*cw, sel, true);
2673 }
2674 
2675 COMMAND(flip, "");
2676 COMMAND(rotate, "i");
2677 
2678 enum { EDITMATF_EMPTY = 0x10000, EDITMATF_NOTEMPTY = 0x20000, EDITMATF_SOLID = 0x30000, EDITMATF_NOTSOLID = 0x40000 };
2679 static const struct { const char *name; int filter; } editmatfilters[] =
2680 {
2681     { "empty", EDITMATF_EMPTY },
2682     { "notempty", EDITMATF_NOTEMPTY },
2683     { "solid", EDITMATF_SOLID },
2684     { "notsolid", EDITMATF_NOTSOLID }
2685 };
2686 
setmat(cube & c,ushort mat,ushort matmask,ushort filtermat,ushort filtermask,int filtergeom)2687 void setmat(cube &c, ushort mat, ushort matmask, ushort filtermat, ushort filtermask, int filtergeom)
2688 {
2689     if(c.children)
2690         loopi(8) setmat(c.children[i], mat, matmask, filtermat, filtermask, filtergeom);
2691     else if((c.material&filtermask) == filtermat)
2692     {
2693         switch(filtergeom)
2694         {
2695             case EDITMATF_EMPTY: if(isempty(c)) break; return;
2696             case EDITMATF_NOTEMPTY: if(!isempty(c)) break; return;
2697             case EDITMATF_SOLID: if(isentirelysolid(c)) break; return;
2698             case EDITMATF_NOTSOLID: if(!isentirelysolid(c)) break; return;
2699         }
2700         if(mat!=MAT_AIR)
2701         {
2702             c.material &= matmask;
2703             c.material |= mat;
2704         }
2705         else c.material = MAT_AIR;
2706     }
2707 }
2708 
mpeditmat(int matid,int filter,selinfo & sel,bool local)2709 void mpeditmat(int matid, int filter, selinfo &sel, bool local)
2710 {
2711     if(local) game::edittrigger(sel, EDIT_MAT, matid, filter);
2712 
2713     ushort filtermat = 0, filtermask = 0, matmask;
2714     int filtergeom = 0;
2715     if(filter >= 0)
2716     {
2717         filtermat = filter&0xFFFF;
2718         filtermask = filtermat&(MATF_VOLUME|MATF_INDEX) ? MATF_VOLUME|MATF_INDEX : (filtermat&MATF_CLIP ? MATF_CLIP : filtermat);
2719         filtergeom = filter&~0xFFFF;
2720     }
2721     if(matid < 0)
2722     {
2723         matid = 0;
2724         matmask = filtermask;
2725         if(isclipped(filtermat&MATF_VOLUME)) matmask &= ~MATF_CLIP;
2726         if(isdeadly(filtermat&MATF_VOLUME)) matmask &= ~MAT_DEATH;
2727     }
2728     else
2729     {
2730         matmask = matid&(MATF_VOLUME|MATF_INDEX) ? 0 : (matid&MATF_CLIP ? ~MATF_CLIP : ~matid);
2731         if(isclipped(matid&MATF_VOLUME)) matid |= MAT_CLIP;
2732         if(isdeadly(matid&MATF_VOLUME)) matid |= MAT_DEATH;
2733     }
2734     loopselxyz(setmat(c, matid, matmask, filtermat, filtermask, filtergeom));
2735 }
2736 
editmat(char * name,char * filtername)2737 void editmat(char *name, char *filtername)
2738 {
2739     if(noedit()) return;
2740     int filter = -1;
2741     if(filtername[0])
2742     {
2743         loopi(sizeof(editmatfilters)/sizeof(editmatfilters[0])) if(!strcmp(editmatfilters[i].name, filtername)) { filter = editmatfilters[i].filter; break; }
2744         if(filter < 0) filter = findmaterial(filtername);
2745         if(filter < 0)
2746         {
2747             conoutf(CON_ERROR, "unknown material \"%s\"", filtername);
2748             return;
2749         }
2750     }
2751     int id = -1;
2752     if(name[0] || filter < 0)
2753     {
2754         id = findmaterial(name);
2755         if(id<0) { conoutf(CON_ERROR, "unknown material \"%s\"", name); return; }
2756     }
2757     mpeditmat(id, filter, sel, true);
2758 }
2759 
2760 COMMAND(editmat, "ss");
2761 
2762 extern int menudistance, menuautoclose;
2763 
2764 VARP(texguiwidth, 1, 15, 1000);
2765 VARP(texguiheight, 1, 8, 1000);
2766 FVARP(texguiscale, 0.1f, 1.5f, 10.0f);
2767 VARP(texguitime, 0, 15, 1000);
2768 VARP(texguiname, 0, 1, 1);
2769 
2770 static int lastthumbnail = 0;
2771 
2772 VARP(texgui2d, 0, 1, 1);
2773 VAR(texguinum, 1, -1, 0);
2774 
2775 struct texturegui : g3d_callback
2776 {
2777     bool menuon;
2778     vec menupos;
2779     int menustart, menutab;
2780 
textureguitexturegui2781     texturegui() : menustart(-1) {}
2782 
guitexturegui2783     void gui(g3d_gui &g, bool firstpass)
2784     {
2785         int origtab = menutab, numtabs = max((slots.length() + texguiwidth*texguiheight - 1)/(texguiwidth*texguiheight), 1);
2786         if(!firstpass) texguinum = -1;
2787         g.start(menustart, 0.04f, &menutab);
2788         bool oldautotab = g.allowautotab(false);
2789         loopi(numtabs)
2790         {
2791             g.tab(!i ? "Textures" : NULL, 0xFFDD88);
2792             if(i+1 != origtab) continue; //don't load textures on non-visible tabs!
2793             Slot *rollover = NULL;
2794             loop(h, texguiheight)
2795             {
2796                 g.pushlist();
2797                 loop(w, texguiwidth)
2798                 {
2799                     extern VSlot dummyvslot;
2800                     int ti = (i*texguiheight+h)*texguiwidth+w;
2801                     if(ti<slots.length())
2802                     {
2803                         Slot &slot = lookupslot(ti, false);
2804                         VSlot &vslot = *slot.variants;
2805                         if(slot.sts.empty()) continue;
2806                         else if(!slot.loaded && !slot.thumbnail)
2807                         {
2808                             if(totalmillis-lastthumbnail<texguitime)
2809                             {
2810                                 g.texture(dummyvslot, texguiscale, false); //create an empty space
2811                                 continue;
2812                             }
2813                             loadthumbnail(slot);
2814                             lastthumbnail = totalmillis;
2815                         }
2816                         int ret = g.texture(vslot, texguiscale, true);
2817                         if(ret&G3D_ROLLOVER) { rollover = &slot; texguinum = ti; }
2818                         if(ret&G3D_UP && (slot.loaded || slot.thumbnail!=notexture))
2819                         {
2820                             edittex(vslot.index);
2821                             hudshader->set();
2822                         }
2823                     }
2824                     else
2825                     {
2826                         g.texture(dummyvslot, texguiscale, false); //create an empty space
2827                     }
2828                 }
2829                 g.poplist();
2830             }
2831             if(texguiname)
2832             {
2833                 if(rollover)
2834                 {
2835                     defformatstring(name, "%d \f7:\fc %s", texguinum, rollover->sts[0].name);
2836                     g.title(name, 0xFFDD88);
2837                 }
2838                 else g.space(1);
2839             }
2840         }
2841         g.allowautotab(oldautotab);
2842         g.end();
2843     }
2844 
showtexturestexturegui2845     void showtextures(bool on)
2846     {
2847         if(on == menuon) return;
2848         if((menuon = on))
2849         {
2850             if(menustart <= lasttexmillis)
2851                 menutab = 1+clamp(lookupvslot(lasttex, false).slot->index, 0, slots.length()-1)/(texguiwidth*texguiheight);
2852             menupos = menuinfrontofplayer();
2853             menustart = starttime();
2854         }
2855         else texguinum = -1;
2856     }
2857 
showtexturegui2858     void show()
2859     {
2860         if(!menuon) return;
2861         filltexlist();
2862         extern int usegui2d;
2863         if(!editmode || ((!texgui2d || !usegui2d) && camera1->o.dist(menupos) > menuautoclose)) { menuon = false; texguinum = -1; }
2864         else g3d_addgui(this, menupos, texgui2d ? GUI_2D : 0);
2865     }
2866 } gui;
2867 
g3d_texturemenu()2868 void g3d_texturemenu()
2869 {
2870     gui.show();
2871 }
2872 
showtexgui(int * n)2873 void showtexgui(int *n)
2874 {
2875     if(!editmode) { conoutf(CON_ERROR, "operation only allowed in edit mode"); return; }
2876     gui.showtextures(*n==0 ? !gui.menuon : *n==1);
2877 }
2878 
2879 // 0/noargs = toggle, 1 = on, other = off - will autoclose if too far away or exit editmode
2880 COMMAND(showtexgui, "i");
2881 
cleartexgui()2882 bool cleartexgui()
2883 {
2884     if(!gui.menuon) return false;
2885     gui.showtextures(false);
2886     return true;
2887 }
2888 ICOMMAND(cleartexgui, "", (), intret(cleartexgui() ? 1 : 0));
2889 
rendertexturepanel(int w,int h)2890 void rendertexturepanel(int w, int h)
2891 {
2892     if((texpaneltimer -= curtime)>0 && editmode)
2893     {
2894         glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2895 
2896         pushhudmatrix();
2897         hudmatrix.scale(h/1800.0f, h/1800.0f, 1);
2898         flushhudmatrix(false);
2899         SETSHADER(hudrgb);
2900 
2901         int y = 50, gap = 10;
2902 
2903         gle::defvertex(2);
2904         gle::deftexcoord0();
2905 
2906         loopi(7)
2907         {
2908             int s = (i == 3 ? 285 : 220), ti = curtexindex+i-3;
2909             if(texmru.inrange(ti))
2910             {
2911                 VSlot &vslot = lookupvslot(texmru[ti]), *layer = NULL;
2912                 Slot &slot = *vslot.slot;
2913                 Texture *tex = slot.sts.empty() ? notexture : slot.sts[0].t, *glowtex = NULL, *layertex = NULL;
2914                 if(slot.texmask&(1<<TEX_GLOW))
2915                 {
2916                     loopvj(slot.sts) if(slot.sts[j].type==TEX_GLOW) { glowtex = slot.sts[j].t; break; }
2917                 }
2918                 if(vslot.layer)
2919                 {
2920                     layer = &lookupvslot(vslot.layer);
2921                     layertex = layer->slot->sts.empty() ? notexture : layer->slot->sts[0].t;
2922                 }
2923                 float sx = min(1.0f, tex->xs/(float)tex->ys), sy = min(1.0f, tex->ys/(float)tex->xs);
2924                 int x = w*1800/h-s-50, r = s;
2925                 vec2 tc[4] = { vec2(0, 0), vec2(1, 0), vec2(1, 1), vec2(0, 1) };
2926                 float xoff = vslot.offset.x, yoff = vslot.offset.y;
2927                 if(vslot.rotation)
2928                 {
2929                     const texrotation &r = texrotations[vslot.rotation];
2930                     if(r.swapxy) { swap(xoff, yoff); loopk(4) swap(tc[k].x, tc[k].y); }
2931                     if(r.flipx) { xoff *= -1; loopk(4) tc[k].x *= -1; }
2932                     if(r.flipy) { yoff *= -1; loopk(4) tc[k].y *= -1; }
2933                 }
2934                 loopk(4) { tc[k].x = tc[k].x/sx - xoff/tex->xs; tc[k].y = tc[k].y/sy - yoff/tex->ys; }
2935                 glBindTexture(GL_TEXTURE_2D, tex->id);
2936                 loopj(glowtex ? 3 : 2)
2937                 {
2938                     if(j < 2) gle::color(vec(vslot.colorscale).mul(j), texpaneltimer/1000.0f);
2939                     else
2940                     {
2941                         glBindTexture(GL_TEXTURE_2D, glowtex->id);
2942                         glBlendFunc(GL_SRC_ALPHA, GL_ONE);
2943                         gle::color(vslot.glowcolor, texpaneltimer/1000.0f);
2944                     }
2945                     gle::begin(GL_TRIANGLE_STRIP);
2946                     gle::attribf(x,   y);   gle::attrib(tc[0]);
2947                     gle::attribf(x+r, y);   gle::attrib(tc[1]);
2948                     gle::attribf(x,   y+r); gle::attrib(tc[3]);
2949                     gle::attribf(x+r, y+r); gle::attrib(tc[2]);
2950                     xtraverts += gle::end();
2951                     if(j==1 && layertex)
2952                     {
2953                         gle::color(layer->colorscale, texpaneltimer/1000.0f);
2954                         glBindTexture(GL_TEXTURE_2D, layertex->id);
2955                         gle::begin(GL_TRIANGLE_STRIP);
2956                         gle::attribf(x+r/2, y+r/2); gle::attrib(tc[0]);
2957                         gle::attribf(x+r,   y+r/2); gle::attrib(tc[1]);
2958                         gle::attribf(x+r/2, y+r);   gle::attrib(tc[3]);
2959                         gle::attribf(x+r,   y+r);   gle::attrib(tc[2]);
2960                         xtraverts += gle::end();
2961                     }
2962                     if(!j)
2963                     {
2964                         r -= 10;
2965                         x += 5;
2966                         y += 5;
2967                     }
2968                     else if(j == 2) glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
2969                 }
2970             }
2971             y += s+gap;
2972         }
2973 
2974         pophudmatrix(true, false);
2975         hudshader->set();
2976     }
2977 }
2978