Initial sauer
[SauerbratenRemote.git] / src / engine / octaedit.cpp
blobc1caa126239faf55f74a1a118452bd68012f1f6a
1 #include "pch.h"
2 #include "engine.h"
4 extern int outline;
6 void boxs(int orient, vec o, const vec &s)
8 int d = dimension(orient),
9 dc= dimcoord(orient);
11 float f = !outline ? 0 : (dc>0 ? 0.2f : -0.2f);
12 o[D[d]] += float(dc) * s[D[d]] + f,
14 glBegin(GL_POLYGON);
16 glVertex3fv(o.v); o[R[d]] += s[R[d]];
17 glVertex3fv(o.v); o[C[d]] += s[C[d]];
18 glVertex3fv(o.v); o[R[d]] -= s[R[d]];
19 glVertex3fv(o.v);
21 glEnd();
22 xtraverts += 4;
25 void boxs3D(const vec &o, vec s, int g)
27 s.mul(g);
28 loopi(6)
29 boxs(i, o, s);
32 void boxsgrid(int orient, vec o, vec s, int g)
34 int d = dimension(orient),
35 dc= dimcoord(orient);
36 float ox = o[R[d]],
37 oy = o[C[d]],
38 xs = s[R[d]],
39 ys = s[C[d]],
40 f = !outline ? 0 : (dc>0 ? 0.2f : -0.2f);
42 o[D[d]] += dc * s[D[d]]*g + f;
44 glBegin(GL_LINES);
45 loop(x, xs) {
46 o[R[d]] += g;
47 glVertex3fv(o.v);
48 o[C[d]] += ys*g;
49 glVertex3fv(o.v);
50 o[C[d]] = oy;
52 loop(y, ys) {
53 o[C[d]] += g;
54 o[R[d]] = ox;
55 glVertex3fv(o.v);
56 o[R[d]] += xs*g;
57 glVertex3fv(o.v);
59 glEnd();
60 xtraverts += 2*int(xs+ys);
63 selinfo sel = { 0 }, lastsel;
65 int orient = 0;
66 int gridsize = 8;
67 ivec cor, lastcor;
68 ivec cur, lastcur;
70 extern int entediting;
71 bool editmode = false;
72 bool havesel = false;
73 bool hmapsel = false;
74 int horient = 0;
76 extern int entmoving;
78 VARF(dragging, 0, 0, 1,
79 if(!dragging || cor[0]<0) return;
80 lastcur = cur;
81 lastcor = cor;
82 sel.grid = gridsize;
83 sel.orient = orient;
86 VARF(moving, 0, 0, 1,
87 if(!moving) return;
88 vec v(cur.v); v.add(1);
89 moving = pointinsel(sel, v);
90 if(moving) havesel = false; // tell cursorupdate to create handle
93 void forcenextundo() { lastsel.orient = -1; }
95 void cubecancel()
97 havesel = false;
98 moving = dragging = 0;
99 forcenextundo();
102 extern void entcancel();
104 void cancelsel()
106 cubecancel();
107 entcancel();
110 VARF(gridpower, 3-VVEC_FRAC, 3, VVEC_INT-1,
112 if(dragging) return;
113 gridsize = 1<<gridpower;
114 if(gridsize>=hdr.worldsize) gridsize = hdr.worldsize/2;
115 cancelsel();
118 VAR(passthroughsel, 0, 0, 1);
119 VAR(editing, 1, 0, 0);
120 VAR(selectcorners, 0, 0, 1);
121 VARF(hmapedit, 0, 0, 1, horient = sel.orient);
123 void toggleedit()
125 if(player->state!=CS_ALIVE && player->state!=CS_EDITING) return; // do not allow dead players to edit to avoid state confusion
126 if(!editmode && !cc->allowedittoggle()) return; // not in most multiplayer modes
127 if(!(editmode = !editmode))
129 player->state = CS_ALIVE;
130 player->o.z -= player->eyeheight; // entinmap wants feet pos
131 entinmap(player); // find spawn closest to current floating pos
133 else
135 cl->resetgamestate();
136 player->state = CS_EDITING;
138 cancelsel();
139 keyrepeat(editmode);
140 editing = entediting = editmode;
141 extern int fullbright;
142 if(fullbright) initlights();
143 cc->edittoggled(editmode);
146 bool noedit(bool view)
148 if(!editmode) { conoutf("operation only allowed in edit mode"); return true; }
149 if(view || haveselent()) return false;
150 float r = 1.0f;
151 vec o, s;
152 o = sel.o.v;
153 s = sel.s.v;
154 s.mul(float(sel.grid) / 2.0f);
155 o.add(s);
156 r = float(max(s.x, max(s.y, s.z)));
157 bool viewable = (isvisiblesphere(r, o) != VFC_NOT_VISIBLE);
158 if(!viewable) conoutf("selection not in view");
159 return !viewable;
162 extern void createheightmap();
164 void reorient()
166 sel.cx = 0;
167 sel.cy = 0;
168 sel.cxs = sel.s[R[dimension(orient)]]*2;
169 sel.cys = sel.s[C[dimension(orient)]]*2;
170 sel.orient = orient;
173 void selextend()
175 if(noedit(true)) return;
176 loopi(3)
178 if(cur[i]<sel.o[i])
180 sel.s[i] += (sel.o[i]-cur[i])/sel.grid;
181 sel.o[i] = cur[i];
183 else if(cur[i]>=sel.o[i]+sel.s[i]*sel.grid)
185 sel.s[i] = (cur[i]-sel.o[i])/sel.grid+1;
190 COMMANDN(edittoggle, toggleedit, "");
191 COMMAND(entcancel, "");
192 COMMAND(cubecancel, "");
193 COMMAND(cancelsel, "");
194 COMMAND(reorient, "");
195 COMMAND(selextend, "");
197 ///////// selection support /////////////
199 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
201 ivec s(dimension(b.orient), x*b.grid, y*b.grid, dimcoord(b.orient)*(b.s[dimension(b.orient)]-1)*b.grid);
203 return neighbourcube(b.o.x+s.x, b.o.y+s.y, b.o.z+s.z, -z*b.grid, rgrid, b.orient);
206 #define loopxy(b) loop(y,(b).s[C[dimension((b).orient)]]) loop(x,(b).s[R[dimension((b).orient)]])
207 #define loopxyz(b, r, f) { loop(z,(b).s[D[dimension((b).orient)]]) loopxy((b)) { cube &c = blockcube(x,y,z,b,r); f; } }
208 #define loopselxyz(f) { makeundo(); loopxyz(sel, sel.grid, f); changed(sel); }
209 #define selcube(x, y, z) blockcube(x, y, z, sel, sel.grid)
211 ////////////// cursor ///////////////
213 int selchildcount=0;
215 ICOMMAND(havesel, "", (), intret(havesel ? selchildcount : 0));
217 void countselchild(cube *c, const ivec &cor, int size)
219 ivec ss(sel.s);
220 ss.mul(sel.grid);
221 loopoctabox(cor, size, sel.o, ss)
223 ivec o(i, cor.x, cor.y, cor.z, size);
224 if(c[i].children) countselchild(c[i].children, o, size/2);
225 else selchildcount++;
229 void normalizelookupcube(int x, int y, int z)
231 if(lusize>gridsize)
233 lu.x += (x-lu.x)/gridsize*gridsize;
234 lu.y += (y-lu.y)/gridsize*gridsize;
235 lu.z += (z-lu.z)/gridsize*gridsize;
237 else if(gridsize>lusize)
239 lu.x &= ~(gridsize-1);
240 lu.y &= ~(gridsize-1);
241 lu.z &= ~(gridsize-1);
243 lusize = gridsize;
246 void updateselection()
248 sel.o.x = min(lastcur.x, cur.x);
249 sel.o.y = min(lastcur.y, cur.y);
250 sel.o.z = min(lastcur.z, cur.z);
251 sel.s.x = abs(lastcur.x-cur.x)/sel.grid+1;
252 sel.s.y = abs(lastcur.y-cur.y)/sel.grid+1;
253 sel.s.z = abs(lastcur.z-cur.z)/sel.grid+1;
256 void editmoveplane(const vec &o, const vec &ray, int d, float off, vec &handle, vec &dest, bool first)
258 plane pl(d, off);
259 float dist = 0.0f;
261 if(pl.rayintersect(player->o, ray, dist))
263 dest = ray;
264 dest.mul(dist);
265 dest.add(player->o);
266 if(first)
268 handle = dest;
269 handle.sub(o);
271 dest.sub(handle);
275 inline bool isheightmap(int orient, int d, bool empty, cube *c);
276 extern void entdrag(const vec &ray);
277 extern bool hoveringonent(int ent, int orient);
278 extern void renderentselection(const vec &o, const vec &ray, bool entmoving);
279 extern float rayent(const vec &o, vec &ray, vec &hitpos, float radius, int mode, int size, int &orient, int &ent);
281 VAR(gridlookup, 0, 0, 1);
282 VAR(passthroughcube, 0, 1, 1);
284 void cursorupdate()
286 if(sel.grid == 0) sel.grid = gridsize;
288 vec target(worldpos);
289 if(!insideworld(target)) loopi(3)
290 target[i] = max(min(target[i], hdr.worldsize), 0);
291 vec ray(target);
292 ray.sub(player->o).normalize();
293 int d = dimension(sel.orient),
294 od = dimension(orient),
295 odc = dimcoord(orient);
297 bool hovering = false;
298 hmapsel = false;
300 if(moving)
302 ivec e;
303 static vec v, handle;
304 editmoveplane(sel.o.tovec(), ray, od, sel.o[D[od]]+odc*sel.grid*sel.s[D[od]], handle, v, !havesel);
305 if(!havesel)
307 v.add(handle);
308 (e = handle).mask(~(sel.grid-1));
309 v.sub(handle = e.v);
310 havesel = true;
312 (e = v).mask(~(sel.grid-1));
313 sel.o[R[od]] = e[R[od]];
314 sel.o[C[od]] = e[C[od]];
316 else
317 if(entmoving)
319 entdrag(ray);
321 else
323 vec v;
324 ivec w;
325 float sdist = 0, wdist = 0, t;
326 int entorient = 0, ent = -1;
328 wdist = rayent(player->o, ray, v, 0, (editmode && showmat ? RAY_EDITMAT : 0) // select cubes first
329 | (!dragging && entediting ? RAY_ENTS : 0)
330 | RAY_SKIPFIRST
331 | (passthroughcube==1 ? RAY_PASS : 0), gridsize, entorient, ent);
333 if((havesel || dragging) && !passthroughsel) // now try selecting the selection
334 if(rayrectintersect(sel.o.tovec(), vec(sel.s.tovec()).mul(sel.grid), player->o, ray, sdist, orient))
335 { // and choose the nearest of the two
336 if(sdist < wdist)
338 wdist = sdist;
339 ent = -1;
343 if(hovering = hoveringonent(ent, entorient))
345 if(!havesel) {
346 selchildcount = 0;
347 sel.s = vec(0);
350 else
353 v = ray;
354 v.mul(wdist+0.1f);
355 v.add(player->o);
356 w = v;
357 cube *c = &lookupcube(w.x, w.y, w.z);
358 if(gridlookup && !dragging && !moving && !havesel && hmapedit!=1) gridsize = lusize;
359 int mag = lusize / gridsize;
360 normalizelookupcube(w.x, w.y, w.z);
361 if(sdist == 0 || sdist > wdist) rayrectintersect(lu.tovec(), vec(gridsize), player->o, ray, t=0, orient); // just getting orient
362 cur = lu;
363 cor = w;
364 cor.mul(2).div(gridsize);
365 od = dimension(orient);
366 d = dimension(sel.orient);
368 if(mag > 0 && hmapedit==1 && dimcoord(horient) == ray[dimension(horient)]<0)
370 hmapsel = isheightmap(horient, dimension(horient), false, c);
371 if(hmapsel)
372 od = dimension(orient = horient);
375 if(dragging)
377 updateselection();
378 sel.cx = min(cor[R[d]], lastcor[R[d]]);
379 sel.cy = min(cor[C[d]], lastcor[C[d]]);
380 sel.cxs = max(cor[R[d]], lastcor[R[d]]);
381 sel.cys = max(cor[C[d]], lastcor[C[d]]);
383 if(!selectcorners)
385 sel.cx &= ~1;
386 sel.cy &= ~1;
387 sel.cxs &= ~1;
388 sel.cys &= ~1;
389 sel.cxs -= sel.cx-2;
390 sel.cys -= sel.cy-2;
392 else
394 sel.cxs -= sel.cx-1;
395 sel.cys -= sel.cy-1;
398 sel.cx &= 1;
399 sel.cy &= 1;
400 havesel = true;
402 else if(!havesel)
404 sel.o = lu;
405 sel.s.x = sel.s.y = sel.s.z = 1;
406 sel.cx = sel.cy = 0;
407 sel.cxs = sel.cys = 2;
408 sel.grid = gridsize;
409 sel.orient = orient;
410 d = od;
413 sel.corner = (cor[R[d]]-(lu[R[d]]*2)/gridsize)+(cor[C[d]]-(lu[C[d]]*2)/gridsize)*2;
414 selchildcount = 0;
415 countselchild(worldroot, vec(0), hdr.worldsize/2);
416 if(mag>1 && selchildcount==1) selchildcount = -mag;
420 glEnable(GL_BLEND);
421 glBlendFunc(GL_ONE, GL_ONE);
423 // cursors
425 renderentselection(player->o, ray, entmoving!=0);
427 glEnable(GL_POLYGON_OFFSET_LINE);
429 if(!moving && !hovering)
431 if(hmapedit==1)
432 glColor3ub(0, hmapsel ? 255 : 40, 0);
433 else
434 glColor3ub(120,120,120);
435 boxs(orient, lu.tovec(), vec(lusize));
438 // selections
439 if(havesel)
441 d = dimension(sel.orient);
442 glColor3ub(50,50,50); // grid
443 boxsgrid(sel.orient, sel.o.tovec(), sel.s.tovec(), sel.grid);
444 glColor3ub(200,0,0); // 0 reference
445 boxs3D(sel.o.tovec().sub(0.5f*min(gridsize*0.25f, 2)), vec(min(gridsize*0.25f, 2)), 1);
446 glColor3ub(200,200,200);// 2D selection box
447 vec co(sel.o.v), cs(sel.s.v);
448 co[R[d]] += 0.5f*(sel.cx*gridsize);
449 co[C[d]] += 0.5f*(sel.cy*gridsize);
450 cs[R[d]] = 0.5f*(sel.cxs*gridsize);
451 cs[C[d]] = 0.5f*(sel.cys*gridsize);
452 cs[D[d]] *= gridsize;
453 boxs(sel.orient, co, cs);
454 glColor3ub(0,0,120); // 3D selection box
455 boxs3D(sel.o.tovec(), sel.s.tovec(), sel.grid);
458 glDisable(GL_POLYGON_OFFSET_LINE);
460 glDisable(GL_BLEND);
463 //////////// ready changes to vertex arrays ////////////
465 void readychanges(block3 &b, cube *c, const ivec &cor, int size)
467 loopoctabox(cor, size, b.o, b.s)
469 ivec o(i, cor.x, cor.y, cor.z, size);
470 if(c[i].ext)
472 if(c[i].ext->va) // removes va s so that octarender will recreate
474 int hasmerges = c[i].ext->va->hasmerges;
475 destroyva(c[i].ext->va);
476 c[i].ext->va = NULL;
477 if(hasmerges) invalidatemerges(c[i]);
479 freeoctaentities(c[i]);
481 if(c[i].children)
483 if(size<=(8>>VVEC_FRAC))
485 solidfaces(c[i]);
486 discardchildren(c[i]);
487 brightencube(c[i]);
489 else readychanges(b, c[i].children, o, size/2);
491 else brightencube(c[i]);
495 void changed(const block3 &sel)
497 if(sel.s == vec(0)) return;
498 block3 b = sel;
499 loopi(3) b.s[i] *= b.grid;
500 b.grid = 1;
501 loopi(3) // the changed blocks are the selected cubes
503 b.o[i] -= 1;
504 b.s[i] += 2;
505 readychanges(b, worldroot, vec(0), hdr.worldsize/2);
506 b.o[i] += 1;
507 b.s[i] -= 2;
510 inbetweenframes = false;
511 octarender();
512 inbetweenframes = true;
513 setupmaterials();
514 invalidatereflections();
515 entitiesinoctanodes();
518 //////////// copy and undo /////////////
519 cube copycube(cube &src)
521 cube c = src;
522 c.ext = NULL; // src cube is responsible for va destruction
523 if(src.children)
525 c.children = newcubes(F_EMPTY);
526 loopi(8) c.children[i] = copycube(src.children[i]);
528 else if(src.ext && src.ext->material!=MAT_AIR) ext(c).material = src.ext->material;
529 return c;
532 void pastecube(cube &src, cube &dest)
534 discardchildren(dest);
535 dest = copycube(src);
538 block3 *blockcopy(const block3 &s, int rgrid)
540 block3 *b = (block3 *)new uchar[sizeof(block3)+sizeof(cube)*s.size()];
541 *b = s;
542 cube *q = b->c();
543 loopxyz(s, rgrid, *q++ = copycube(c));
544 return b;
547 void freeblock(block3 *b)
549 cube *q = b->c();
550 loopi(b->size()) discardchildren(*q++);
551 delete[] b;
554 int *selgridmap(selinfo &sel) // generates a map of the cube sizes at each grid point
556 int *g = new int[sel.size()];
557 loopxyz(sel, -sel.grid, (*g++ = lusize, c));
558 return g-sel.size();
561 vector<undoblock> undos; // unlimited undo
562 vector<undoblock> redos;
563 VARP(undomegs, 0, 5, 100); // bounded by n megs
565 void freeundo(undoblock u)
567 if(u.g) delete[] u.g;
568 if(u.b) freeblock(u.b);
569 if(u.e) delete[] u.e;
572 void pasteundo(undoblock &u)
574 if(u.g)
576 int *g = u.g;
577 cube *s = u.b->c();
578 loopxyz(*u.b, *g++, pastecube(*s++, c));
580 pasteundoents(u);
583 void pruneundos(int maxremain) // bound memory
585 int t = 0, p = 0;
586 loopvrev(undos)
588 undoblock &u = undos[i];
589 if(u.b)
591 cube *q = u.b->c();
592 t += u.b->size()*sizeof(int);
593 loopj(u.b->size())
594 t += familysize(*q++)*sizeof(cube);
596 t += u.n*sizeof(undoent);
597 if(t>maxremain) freeundo(undos.remove(i)); else p = t;
599 //conoutf("undo: %d of %d(%%%d)", p, undomegs<<20, p*100/(undomegs<<20));
600 while(!redos.empty()) { freeundo(redos.pop()); }
603 void initundocube(undoblock &u, selinfo &sel)
605 u.g = selgridmap(sel);
606 u.b = blockcopy(sel, -sel.grid);
609 void addundo(undoblock &u)
611 undos.add(u);
612 pruneundos(undomegs<<20);
615 void makeundo() // stores state of selected cubes before editing
617 if(lastsel==sel || sel.s==vec(0)) return;
618 lastsel=sel;
619 if(multiplayer(false)) return;
620 undoblock u;
621 initundocube(u, sel);
622 addundo(u);
625 void swapundo(vector<undoblock> &a, vector<undoblock> &b, const char *s)
627 if(noedit() || multiplayer()) return;
628 if(a.empty()) { conoutf("nothing more to %s", s); return; }
629 int ts = a.last().ts;
630 while(!a.empty() && ts==a.last().ts)
632 undoblock u = a.pop();
633 if(u.b)
635 sel.o = u.b->o;
636 sel.s = u.b->s;
637 sel.grid = u.b->grid;
638 sel.orient = u.b->orient;
640 undoblock r;
641 if(u.g) initundocube(r, sel);
642 if(u.n) copyundoents(r, u);
643 b.add(r);
644 pasteundo(u);
645 if(u.b) changed(sel);
646 freeundo(u);
648 reorient();
649 forcenextundo();
652 void editundo() { swapundo(undos, redos, "undo"); }
653 void editredo() { swapundo(redos, undos, "redo"); }
655 editinfo *localedit = NULL;
657 void freeeditinfo(editinfo *&e)
659 if(!e) return;
660 if(e->copy) freeblock(e->copy);
661 delete e;
662 e = NULL;
665 // guard against subdivision
666 #define protectsel(f) { undoblock _u; initundocube(_u, sel); f; pasteundo(_u); freeundo(_u); }
668 void mpcopy(editinfo *&e, selinfo &sel, bool local)
670 if(local) cl->edittrigger(sel, EDIT_COPY);
671 if(e==NULL) e = new editinfo;
672 if(e->copy) freeblock(e->copy);
673 e->copy = NULL;
674 protectsel(e->copy = blockcopy(block3(sel), sel.grid));
675 changed(sel);
678 void mppaste(editinfo *&e, selinfo &sel, bool local)
680 if(e==NULL) return;
681 if(local) cl->edittrigger(sel, EDIT_PASTE);
682 if(e->copy)
684 sel.s = e->copy->s;
685 int o = sel.orient;
686 sel.orient = e->copy->orient;
687 cube *s = e->copy->c();
688 loopselxyz(if (!isempty(*s) || s->children) pastecube(*s, c); s++); // 'transparent'. old opaque by 'delcube; paste'
689 sel.orient = o;
693 void copy()
695 if(noedit(true)) return;
696 mpcopy(localedit, sel, true);
699 void pastehilite()
701 if(!localedit) return;
702 sel.s = localedit->copy->s;
703 reorient();
704 havesel = true;
707 void paste()
709 if(noedit()) return;
710 mppaste(localedit, sel, true);
713 COMMAND(copy, "");
714 COMMAND(pastehilite, "");
715 COMMAND(paste, "");
716 COMMANDN(undo, editundo, "");
717 COMMANDN(redo, editredo, "");
719 ///////////// height maps ////////////////
721 #define MAXBRUSH 64
722 #define MAXBRUSHC 63
723 #define MAXBRUSH2 32
724 int brush[MAXBRUSH][MAXBRUSH];
725 VAR(brushx, 0, MAXBRUSH2, MAXBRUSH);
726 VAR(brushy, 0, MAXBRUSH2, MAXBRUSH);
727 bool paintbrush = 0;
728 int brushmaxx = 0, brushminx = MAXBRUSH;
729 int brushmaxy = 0, brushminy = MAXBRUSH;
731 void clearbrush()
733 memset(brush, 0, sizeof brush);
734 brushmaxx = brushmaxy = 0;
735 brushminx = brushminy = MAXBRUSH;
736 paintbrush = false;
739 void brushvert(int *x, int *y, int *v)
741 *x += MAXBRUSH2 - brushx + 1; // +1 for automatic padding
742 *y += MAXBRUSH2 - brushy + 1;
743 if(*x<0 || *y<0 || *x>=MAXBRUSH || *y>=MAXBRUSH) return;
744 brush[*x][*y] = clamp(*v, 0, 8);
745 paintbrush = paintbrush || (brush[*x][*y] > 0);
746 brushmaxx = min(MAXBRUSH-1, max(brushmaxx, *x+1));
747 brushmaxy = min(MAXBRUSH-1, max(brushmaxy, *y+1));
748 brushminx = max(0, min(brushminx, *x-1));
749 brushminy = max(0, min(brushminy, *y-1));
752 vector<int> htextures;
754 COMMAND(clearbrush, "");
755 COMMAND(brushvert, "iii");
756 ICOMMAND(hmapcancel, "", (), htextures.setsizenodelete(0); );
757 ICOMMAND(hmapselect, "", (),
758 int t = lookupcube(cur.x, cur.y, cur.z).texture[orient];
759 int i = htextures.find(t);
760 if(i<0)
761 htextures.add(t);
762 else
763 htextures.remove(i);
766 bool ischildless(cube &c)
768 if(!c.children)
769 return true;
770 loopi(8)
772 if(!ischildless(c.children[i]) || !isempty(c.children[i]))
773 return false;
775 emptyfaces(c);
776 discardchildren(c);
777 return true;
780 inline bool ishtexture(int t)
782 loopv(htextures)
783 if(t == htextures[i])
784 return false;
785 return true;
788 VARP(bypassheightmapcheck, 0, 0, 1); // temp
790 inline bool isheightmap(int o, int d, bool empty, cube *c)
792 return bypassheightmapcheck || (ischildless(*c) &&
793 ( (empty && isempty(*c)) ||
795 (c->faces[R[d]] & 0x77777777) == 0 &&
796 (c->faces[C[d]] & 0x77777777) == 0 &&
797 ishtexture(c->texture[o])
798 )));
801 namespace hmap
803 # define PAINTED 1
804 # define NOTHMAP 2
805 # define MAPPED 16
806 uchar flags[MAXBRUSH][MAXBRUSH];
807 cube *cmap[MAXBRUSHC][MAXBRUSHC][4];
808 int mapz[MAXBRUSHC][MAXBRUSHC];
809 int map [MAXBRUSH][MAXBRUSH];
811 selinfo changes;
812 bool selecting;
813 int d, dc, dr, dcr, biasup, br, hws, fg;
814 int gx, gy, gz, mx, my, mz, nx, ny, nz, bmx, bmy, bnx, bny;
815 uint fs;
817 cube *getcube(ivec t, int f)
819 t[d] += dcr*f*gridsize;
820 if(t[d] > nz || t[d] < mz) return NULL;
821 cube *c = &lookupcube(t.x, t.y, t.z, -gridsize);
822 if(!isheightmap(sel.orient, d, true, c)) return NULL;
823 if(lusize > gridsize)
824 c = &lookupcube(t.x, t.y, t.z, gridsize);
825 discardchildren(*c);
826 if (t.x < changes.o.x) changes.o.x = t.x;
827 else if(t.x > changes.s.x) changes.s.x = t.x;
828 if (t.y < changes.o.y) changes.o.y = t.y;
829 else if(t.y > changes.s.y) changes.s.y = t.y;
830 if (t.z < changes.o.z) changes.o.z = t.z;
831 else if(t.z > changes.s.z) changes.s.z = t.z;
832 return c;
835 uint getface(cube *c, int d)
837 return 0x0f0f0f0f & ((dc ? c->faces[d] : 0x88888888 - c->faces[d]) >> fs);
840 void pushside(cube &c, int d, int x, int y, int z)
842 ivec a;
843 getcubevector(c, d, x, y, z, a);
844 a[R[d]] = 8 - a[R[d]];
845 setcubevector(c, d, x, y, z, a);
848 void addpoint(int x, int y, int z, int v)
850 if(!(flags[x][y] & MAPPED))
851 map[x][y] = v + (z*8);
852 flags[x][y] |= MAPPED;
855 void select(int x, int y, int z)
857 if((NOTHMAP & flags[x][y]) || (PAINTED & flags[x][y])) return;
858 ivec t(d, x+gx, y+gy, dc ? z : hws-z);
859 t.shl(gridpower);
861 cube **c = cmap[x][y];
862 loopk(4) c[k] = NULL;
863 c[1] = getcube(t, 0);
864 if(!c[1] || !isempty(*c[1]))
865 { // try up
866 c[2] = c[1];
867 c[1] = getcube(t, 1);
868 if(!c[1] || isempty(*c[1])) {
869 c[0] = c[1], c[1] = c[2], c[2] = NULL;
870 }else
871 z++, t[d]+=fg;
873 else // drop down
875 z--;
876 t[d]-= fg;
877 c[0] = c[1];
878 c[1] = getcube(t, 0);
881 if(!c[1] || isempty(*c[1])) { flags[x][y] |= NOTHMAP; return; }
883 flags[x][y] |= PAINTED;
884 mapz [x][y] = z;
886 if(!c[0]) c[0] = getcube(t, 1);
887 if(!c[2]) c[2] = getcube(t, -1);
888 c[3] = getcube(t, -2);
889 c[2] = !c[2] || isempty(*c[2]) ? NULL : c[2];
890 c[3] = !c[3] || isempty(*c[3]) ? NULL : c[3];
892 uint face = getface(c[1], d);
893 if(face == 0x08080808 && (!c[0] || !isempty(*c[0]))) { flags[x][y] |= NOTHMAP; return; }
894 if(c[1]->faces[R[d]] == F_SOLID) // was single
895 face += 0x08080808;
896 else // was pair
897 face += c[2] ? getface(c[2], d) : 0x08080808;
898 face += 0x08080808; // c[3]
899 uchar *f = (uchar*)&face;
900 addpoint(x, y, z, f[0]);
901 addpoint(x+1, y, z, f[1]);
902 addpoint(x, y+1, z, f[2]);
903 addpoint(x+1, y+1, z, f[3]);
905 if(selecting) // continue to adjacent cubes
907 if(x>bmx) select(x-1, y, z);
908 if(x<bnx) select(x+1, y, z);
909 if(y>bmy) select(x, y-1, z);
910 if(y<bny) select(x, y+1, z);
914 void ripple(int x, int y, int z, bool force)
916 if(force) select(x, y, z);
917 if((NOTHMAP & flags[x][y]) || !(PAINTED & flags[x][y])) return;
919 bool changed = false;
920 int *o[4], best, par, q = 0;
921 loopi(2) loopj(2) o[i+j*2] = &map[x+i][y+j];
922 #define pullhmap(I, LT, GT, M, N, A) do { \
923 best = I; \
924 loopi(4) if(*o[i] LT best) best = *o[q = i] - M; \
925 par = (best&(~7)) + N; \
926 /* dual layer for extra smoothness */ \
927 if(*o[q^3] GT par && !(*o[q^1] LT par || *o[q^2] LT par)) { \
928 if(*o[q^3] GT par A 8 || *o[q^1] != par || *o[q^2] != par) { \
929 *o[q^3] = (*o[q^3] GT par A 8 ? par A 8 : *o[q^3]); \
930 *o[q^1] = *o[q^2] = par; \
931 changed = true; \
933 /* single layer */ \
934 } else { \
935 loopj(4) if(*o[j] GT par) { \
936 *o[j] = par; \
937 changed = true; \
940 } while(0)
942 if(biasup)
943 pullhmap(0, >, <, 1, 0, -);
944 else
945 pullhmap(hdr.worldsize, <, >, 0, 8, +);
947 cube **c = cmap[x][y];
948 int e[2][2];
949 int notempty = 0;
951 loopk(4) if(c[k]) {
952 loopi(2) loopj(2) {
953 e[i][j] = min(8, map[x+i][y+j] - (mapz[x][y]+3-k)*8);
954 notempty |= e[i][j] > 0;
956 if(notempty)
958 c[k]->texture[sel.orient] = c[1]->texture[sel.orient];
959 solidfaces(*c[k]);
960 loopi(2) loopj(2)
962 int f = e[i][j];
963 if(f<0 || (f==0 && e[1-i][j]==0 && e[i][1-j]==0))
965 f=0;
966 pushside(*c[k], d, i, j, 0);
967 pushside(*c[k], d, i, j, 1);
969 edgeset(cubeedge(*c[k], d, i, j), dc, dc ? f : 8-f);
972 else
973 emptyfaces(*c[k]);
976 if(!changed) return;
977 if(x>mx) ripple(x-1, y, mapz[x][y], true);
978 if(x<nx) ripple(x+1, y, mapz[x][y], true);
979 if(y>my) ripple(x, y-1, mapz[x][y], true);
980 if(y<ny) ripple(x, y+1, mapz[x][y], true);
982 #define DIAGONAL_RIPPLE(a,b,exp) if(exp) { \
983 if(flags[x a][ y] & PAINTED) \
984 ripple(x a, y b, mapz[x a][y], true); \
985 else if(flags[x][y b] & PAINTED) \
986 ripple(x a, y b, mapz[x][y b], true); \
989 DIAGONAL_RIPPLE(-1, -1, (x>mx && y>my)); // do diagonals because adjacents
990 DIAGONAL_RIPPLE(-1, +1, (x>mx && y<ny)); // won't unless changed
991 DIAGONAL_RIPPLE(+1, +1, (x<nx && y<ny));
992 DIAGONAL_RIPPLE(+1, -1, (x<nx && y>my));
995 #define loopbrush(i) for(int x=bmx; x<=bnx+i; x++) for(int y=bmy; y<=bny+i; y++)
997 void paint()
999 loopbrush(1)
1000 map[x][y] -= dr * brush[x][y];
1003 void smooth()
1005 int sum, div;
1006 loopbrush(-2)
1008 sum = 0;
1009 div = 9;
1010 loopi(3) loopj(3)
1011 if(flags[x+i][y+j] & MAPPED)
1012 sum += map[x+i][y+j];
1013 else div--;
1014 if(div)
1015 map[x+1][y+1] = sum / div;
1019 void rippleandset()
1021 loopbrush(0)
1022 ripple(x, y, gz, false);
1025 void run(int dir, int mode)
1027 d = dimension(sel.orient);
1028 dc = dimcoord(sel.orient);
1029 dcr= dc ? 1 : -1;
1030 dr = dir>0 ? 1 : -1;
1031 br = dir>0 ? 0x08080808 : 0;
1032 // biasup = mode == dir<0;
1033 biasup = dir<0;
1034 int cx = (sel.corner&1 ? 0 : -1);
1035 int cy = (sel.corner&2 ? 0 : -1);
1036 hws= (hdr.worldsize>>gridpower);
1037 gx = (cur[R[d]] >> gridpower) + cx - MAXBRUSH2;
1038 gy = (cur[C[d]] >> gridpower) + cy - MAXBRUSH2;
1039 gz = (cur[D[d]] >> gridpower);
1040 fs = dc ? 4 : 0;
1041 fg = dc ? gridsize : -gridsize;
1042 mx = max(0, -gx); // ripple range
1043 my = max(0, -gy);
1044 nx = min(MAXBRUSH-1, hws-gx) - 1;
1045 ny = min(MAXBRUSH-1, hws-gy) - 1;
1046 if(havesel)
1047 { // selection range
1048 mx = max(mx, (sel.o[R[d]]>>gridpower)-gx);
1049 my = max(my, (sel.o[C[d]]>>gridpower)-gy);
1050 nx = min(nx, (sel.s[R[d]]+(sel.o[R[d]]>>gridpower))-gx-1);
1051 ny = min(ny, (sel.s[C[d]]+(sel.o[C[d]]>>gridpower))-gy-1);
1053 bmx = max(mx, brushminx); // brush range
1054 bmy = max(my, brushminy);
1055 bnx = min(nx, brushmaxx-1);
1056 bny = min(ny, brushmaxy-1);
1057 nz = hdr.worldsize-gridsize;
1058 mz = 0;
1060 changes.grid = gridsize;
1061 changes.s = changes.o = cur;
1062 memset(map, 0, sizeof map);
1063 memset(flags, 0, sizeof flags);
1065 selecting = true;
1066 select(clamp(MAXBRUSH2-cx, bmx, bnx),
1067 clamp(MAXBRUSH2-cy, bmy, bny),
1068 dc ? gz : hws - gz);
1069 selecting = false;
1070 if(paintbrush)
1071 paint();
1072 else
1073 smooth();
1074 rippleandset(); // pull up points to cubify, and set
1075 changes.s.sub(changes.o).shr(gridpower).add(1);
1076 changed(changes);
1080 void edithmap(int dir, int mode) {
1081 if(multiplayer() || !hmapsel || gridsize < 8) return;
1082 hmap::run(dir, mode);
1085 ///////////// main cube edit ////////////////
1087 int bounded(int n) { return n<0 ? 0 : (n>8 ? 8 : n); }
1089 void pushedge(uchar &edge, int dir, int dc)
1091 int ne = bounded(edgeget(edge, dc)+dir);
1092 edge = edgeset(edge, dc, ne);
1093 int oe = edgeget(edge, 1-dc);
1094 if((dir<0 && dc && oe>ne) || (dir>0 && dc==0 && oe<ne)) edge = edgeset(edge, 1-dc, ne);
1097 void linkedpush(cube &c, int d, int x, int y, int dc, int dir)
1099 ivec v, p;
1100 getcubevector(c, d, x, y, dc, v);
1102 loopi(2) loopj(2)
1104 getcubevector(c, d, i, j, dc, p);
1105 if(v==p)
1106 pushedge(cubeedge(c, d, i, j), dir, dc);
1110 static uchar getmaterial(cube &c)
1112 if(c.children)
1114 uchar mat = getmaterial(c.children[7]);
1115 loopi(7) if(mat != getmaterial(c.children[i])) return MAT_AIR;
1116 return mat;
1118 return c.ext ? c.ext->material : MAT_AIR;
1121 VAR(invalidcubeguard, 0, 1, 1);
1123 void mpeditface(int dir, int mode, selinfo &sel, bool local)
1125 if(mode==1 && (sel.cx || sel.cy || sel.cxs&1 || sel.cys&1)) mode = 0;
1126 int d = dimension(sel.orient);
1127 int dc = dimcoord(sel.orient);
1128 int seldir = dc ? -dir : dir;
1130 if(local)
1131 cl->edittrigger(sel, EDIT_FACE, dir, mode);
1133 if(mode==1)
1135 int h = sel.o[d]+dc*sel.grid;
1136 if((dir>0 == dc && h<=0) || (dir<0 == dc && h>=hdr.worldsize)) return;
1137 if(dir<0) sel.o[d] += sel.grid * seldir;
1140 if(dc) sel.o[d] += sel.us(d)-sel.grid;
1141 sel.s[d] = 1;
1143 loopselxyz(
1144 if(c.children) solidfaces(c);
1145 uchar mat = getmaterial(c);
1146 discardchildren(c);
1147 if(mat!=MAT_AIR) ext(c).material = mat;
1148 if(mode==1) // fill command
1150 if(dir<0)
1152 solidfaces(c);
1153 cube &o = blockcube(x, y, 1, sel, -sel.grid);
1154 loopi(6)
1155 c.texture[i] = o.texture[i];
1157 else
1158 emptyfaces(c);
1160 else
1162 uint bak = c.faces[d];
1163 uchar *p = (uchar *)&c.faces[d];
1165 if(mode==2)
1166 linkedpush(c, d, sel.corner&1, sel.corner>>1, dc, seldir); // corner command
1167 else
1169 loop(mx,2) loop(my,2) // pull/push edges command
1171 if(x==0 && mx==0 && sel.cx) continue;
1172 if(y==0 && my==0 && sel.cy) continue;
1173 if(x==sel.s[R[d]]-1 && mx==1 && (sel.cx+sel.cxs)&1) continue;
1174 if(y==sel.s[C[d]]-1 && my==1 && (sel.cy+sel.cys)&1) continue;
1175 if(p[mx+my*2] != ((uchar *)&bak)[mx+my*2]) continue;
1177 linkedpush(c, d, mx, my, dc, seldir);
1181 optiface(p, c);
1182 if(invalidcubeguard==1 && !isvalidcube(c))
1184 uint newbak = c.faces[d];
1185 uchar *m = (uchar *)&bak;
1186 uchar *n = (uchar *)&newbak;
1187 loopk(4) if(n[k] != m[k]) // tries to find partial edit that is valid
1189 c.faces[d] = bak;
1190 c.edges[d*4+k] = n[k];
1191 if(isvalidcube(c))
1192 m[k] = n[k];
1194 c.faces[d] = bak;
1198 if (mode==1 && dir>0)
1199 sel.o[d] += sel.grid * seldir;
1202 void editface(int *dir, int *mode)
1204 if(noedit(moving!=0)) return;
1205 if(hmapedit!=1)
1206 mpeditface(*dir, *mode, sel, true);
1207 else
1208 edithmap(*dir, *mode);
1211 VAR(selectionsurf, 0, 0, 1);
1213 void pushsel(int *dir)
1215 if(noedit(moving!=0)) return;
1216 int d = dimension(orient);
1217 int s = dimcoord(orient) ? -*dir : *dir;
1218 sel.o[d] += s*sel.grid;
1219 if(selectionsurf==1) player->o[d] += s*sel.grid;
1222 void mpdelcube(selinfo &sel, bool local)
1224 if(local) cl->edittrigger(sel, EDIT_DELCUBE);
1225 loopselxyz(discardchildren(c); emptyfaces(c));
1228 void delcube()
1230 if(noedit()) return;
1231 mpdelcube(sel, true);
1234 COMMAND(pushsel, "i");
1235 COMMAND(editface, "ii");
1236 COMMAND(delcube, "");
1238 /////////// texture editing //////////////////
1240 int curtexindex = -1, lasttex = 0;
1241 int texpaneltimer = 0;
1242 vector<ushort> texmru;
1244 void tofronttex() // maintain most recently used of the texture lists when applying texture
1246 int c = curtexindex;
1247 if(c>=0)
1249 texmru.insert(0, texmru.remove(c));
1250 curtexindex = -1;
1254 selinfo repsel;
1255 int reptex = -1;
1257 void edittexcube(cube &c, int tex, int orient, bool &findrep)
1259 if(orient<0) loopi(6) c.texture[i] = tex;
1260 else
1262 int i = visibleorient(c, orient);
1263 if(findrep)
1265 if(reptex < 0) reptex = c.texture[i];
1266 else if(reptex != c.texture[i]) findrep = false;
1268 c.texture[i] = tex;
1270 if(c.children) loopi(8) edittexcube(c.children[i], tex, orient, findrep);
1273 extern int curtexnum;
1274 VAR(allfaces, 0, 0, 1);
1276 void mpedittex(int tex, int allfaces, selinfo &sel, bool local)
1278 if(local)
1280 cl->edittrigger(sel, EDIT_TEX, tex, allfaces);
1281 if(allfaces || !(repsel == sel)) reptex = -1;
1282 repsel = sel;
1284 bool findrep = local && !allfaces && reptex < 0;
1285 loopselxyz(edittexcube(c, tex, allfaces ? -1 : sel.orient, findrep));
1288 void filltexlist()
1290 if(texmru.length()!=curtexnum)
1292 loopv(texmru) if(texmru[i]>=curtexnum) texmru.remove(i--);
1293 loopi(curtexnum) if(texmru.find(i)<0) texmru.add(i);
1297 void edittex(int i)
1299 curtexindex = i = min(max(i, 0), curtexnum-1);
1300 int t = lasttex = texmru[i];
1301 mpedittex(t, allfaces, sel, true);
1304 void edittex_(int *dir)
1306 if(noedit()) return;
1307 filltexlist();
1308 texpaneltimer = 5000;
1309 if(!(lastsel==sel)) tofronttex();
1310 edittex(curtexindex<0 ? 0 : curtexindex+*dir);
1313 void gettex()
1315 if(noedit()) return;
1316 filltexlist();
1317 loopxyz(sel, sel.grid, curtexindex = c.texture[sel.orient]);
1318 loopi(curtexnum) if(texmru[i]==curtexindex)
1320 curtexindex = i;
1321 tofronttex();
1322 return;
1326 COMMANDN(edittex, edittex_, "i");
1327 COMMAND(gettex, "");
1329 void replacetexcube(cube &c, int oldtex, int newtex)
1331 loopi(6) if(c.texture[i] == oldtex) c.texture[i] = newtex;
1332 if(c.children) loopi(8) replacetexcube(c.children[i], oldtex, newtex);
1335 void mpreplacetex(int oldtex, int newtex, selinfo &sel, bool local)
1337 if(local) cl->edittrigger(sel, EDIT_REPLACE, oldtex, newtex);
1338 loopi(8) replacetexcube(worldroot[i], oldtex, newtex);
1339 allchanged();
1342 void replace()
1344 if(noedit()) return;
1345 if(reptex < 0) { conoutf("can only replace after a texture edit"); return; }
1346 mpreplacetex(reptex, lasttex, sel, true);
1349 COMMAND(replace, "");
1351 ////////// flip and rotate ///////////////
1352 uint dflip(uint face) { return face==F_EMPTY ? face : 0x88888888 - (((face&0xF0F0F0F0)>>4)+ ((face&0x0F0F0F0F)<<4)); }
1353 uint cflip(uint face) { return ((face&0xFF00FF00)>>8) + ((face&0x00FF00FF)<<8); }
1354 uint rflip(uint face) { return ((face&0xFFFF0000)>>16)+ ((face&0x0000FFFF)<<16); }
1355 uint mflip(uint face) { return (face&0xFF0000FF) + ((face&0x00FF0000)>>8) + ((face&0x0000FF00)<<8); }
1357 void flipcube(cube &c, int d)
1359 swap(ushort, c.texture[d*2], c.texture[d*2+1]);
1360 c.faces[D[d]] = dflip(c.faces[D[d]]);
1361 c.faces[C[d]] = cflip(c.faces[C[d]]);
1362 c.faces[R[d]] = rflip(c.faces[R[d]]);
1363 if (c.children)
1365 loopi(8) if (i&octadim(d)) swap(cube, c.children[i], c.children[i-octadim(d)]);
1366 loopi(8) flipcube(c.children[i], d);
1370 void rotatequad(cube &a, cube &b, cube &c, cube &d)
1372 cube t = a; a = b; b = c; c = d; d = t;
1375 void rotatecube(cube &c, int d) // rotates cube clockwise. see pics in cvs for help.
1377 c.faces[D[d]] = cflip (mflip(c.faces[D[d]]));
1378 c.faces[C[d]] = dflip (mflip(c.faces[C[d]]));
1379 c.faces[R[d]] = rflip (mflip(c.faces[R[d]]));
1380 swap(uint, c.faces[R[d]], c.faces[C[d]]);
1382 swap(uint, c.texture[2*R[d]], c.texture[2*C[d]+1]);
1383 swap(uint, c.texture[2*C[d]], c.texture[2*R[d]+1]);
1384 swap(uint, c.texture[2*C[d]], c.texture[2*C[d]+1]);
1386 if(c.children)
1388 int row = octadim(R[d]);
1389 int col = octadim(C[d]);
1390 for(int i=0; i<=octadim(d); i+=octadim(d)) rotatequad
1392 c.children[i+row],
1393 c.children[i],
1394 c.children[i+col],
1395 c.children[i+col+row]
1397 loopi(8) rotatecube(c.children[i], d);
1401 void mpflip(selinfo &sel, bool local)
1403 if(local) cl->edittrigger(sel, EDIT_FLIP);
1404 int zs = sel.s[dimension(sel.orient)];
1405 makeundo();
1406 loopxy(sel)
1408 loop(z,zs) flipcube(selcube(x, y, z), dimension(sel.orient));
1409 loop(z,zs/2)
1411 cube &a = selcube(x, y, z);
1412 cube &b = selcube(x, y, zs-z-1);
1413 swap(cube, a, b);
1416 changed(sel);
1419 void flip()
1421 if(noedit()) return;
1422 mpflip(sel, true);
1425 void mprotate(int cw, selinfo &sel, bool local)
1427 if(local) cl->edittrigger(sel, EDIT_ROTATE, cw);
1428 int d = dimension(sel.orient);
1429 if(!dimcoord(sel.orient)) cw = -cw;
1430 int &m = min(sel.s[C[d]], sel.s[R[d]]);
1431 int ss = m = max(sel.s[R[d]], sel.s[C[d]]);
1432 makeundo();
1433 loop(z,sel.s[D[d]]) loopi(cw>0 ? 1 : 3)
1435 loopxy(sel) rotatecube(selcube(x,y,z), d);
1436 loop(y,ss/2) loop(x,ss-1-y*2) rotatequad
1438 selcube(ss-1-y, x+y, z),
1439 selcube(x+y, y, z),
1440 selcube(y, ss-1-x-y, z),
1441 selcube(ss-1-x-y, ss-1-y, z)
1444 changed(sel);
1447 void rotate(int *cw)
1449 if(noedit()) return;
1450 mprotate(*cw, sel, true);
1453 COMMAND(flip, "");
1454 COMMAND(rotate, "i");
1456 void setmat(cube &c, uchar mat)
1458 if(c.children)
1459 loopi(8) setmat(c.children[i], mat);
1460 else if(mat!=MAT_AIR) ext(c).material = mat;
1461 else if(c.ext) c.ext->material = MAT_AIR;
1464 void mpeditmat(int matid, selinfo &sel, bool local)
1466 if(local) cl->edittrigger(sel, EDIT_MAT, matid);
1467 loopselxyz(setmat(c, matid));
1470 void editmat(char *name)
1472 if(noedit()) return;
1473 int id = findmaterial(name);
1474 if(id<0) { conoutf("unknown material \"%s\"", name); return; }
1475 mpeditmat(id, sel, true);
1478 COMMAND(editmat, "s");
1480 #define TEXTURE_WIDTH 10
1481 #define TEXTURE_HEIGHT 7
1482 extern int menudistance, menuautoclose;
1484 VAR(thumbtime, 0, 50, 1000);
1486 static int lastthumbnail = 0;
1488 struct texturegui : g3d_callback
1490 bool menuon;
1491 vec menupos;
1492 int menustart;
1494 void gui(g3d_gui &g, bool firstpass)
1496 int menutab = 1+curtexindex/(TEXTURE_WIDTH*TEXTURE_HEIGHT);
1497 int origtab = menutab;
1498 g.start(menustart, 0.04f, &menutab);
1499 loopi(1+curtexnum/(TEXTURE_WIDTH*TEXTURE_HEIGHT))
1501 g.tab((i==0)?"Textures":NULL, 0xAAFFAA);
1502 if(i != origtab-1) continue; //don't load textures on non-visible tabs!
1503 loopj(TEXTURE_HEIGHT)
1505 g.pushlist();
1506 loopk(TEXTURE_WIDTH)
1508 int ti = (i*TEXTURE_HEIGHT+j)*TEXTURE_WIDTH+k;
1509 if(ti<curtexnum)
1511 Texture *tex = notexture;
1512 Slot &slot = lookuptexture(texmru[ti], false);
1513 if(slot.sts.empty()) continue;
1514 else if(slot.loaded) tex = slot.sts[0].t;
1515 else if(slot.thumbnail) tex = slot.thumbnail;
1516 else if(lastmillis-lastthumbnail>=thumbtime) { tex = loadthumbnail(slot); lastthumbnail = lastmillis; }
1517 if(g.texture(tex, 1.0)&G3D_UP && (slot.loaded || tex!=notexture))
1518 edittex(ti);
1520 else
1521 g.texture(notexture, 1.0); //create an empty space
1523 g.poplist();
1526 g.end();
1527 if(origtab != menutab) curtexindex = (menutab-1)*TEXTURE_WIDTH*TEXTURE_HEIGHT;
1530 void showtextures(bool on)
1532 if(on != menuon && (menuon = on)) { menupos = menuinfrontofplayer(); menustart = starttime(); }
1535 void show()
1537 if(!menuon) return;
1538 filltexlist();
1539 if(!editmode || camera1->o.dist(menupos) > menuautoclose) menuon = false;
1540 else g3d_addgui(this, menupos); //follow?
1542 } gui;
1544 void g3d_texturemenu()
1546 gui.show();
1549 void showtexgui(int *n)
1551 if(!editmode) { conoutf("operation only allowed in edit mode"); return; }
1552 gui.showtextures(*n==0 ? !gui.menuon : *n==1);
1555 // 0/noargs = toggle, 1 = on, other = off - will autoclose if too far away or exit editmode
1556 COMMAND(showtexgui, "i");
1558 void render_texture_panel(int w, int h)
1560 if((texpaneltimer -= curtime)>0 && editmode)
1562 glDepthMask(GL_FALSE);
1563 glEnable(GL_BLEND);
1564 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
1565 glLoadIdentity();
1566 int width = w*1800/h;
1567 glOrtho(0, width, 1800, 0, -1, 1);
1568 int y = 50, gap = 10;
1570 static Shader *rgbonlyshader = NULL;
1571 if(!rgbonlyshader) rgbonlyshader = lookupshaderbyname("rgbonly");
1573 rgbonlyshader->set();
1575 loopi(7)
1577 int s = (i == 3 ? 285 : 220), ti = curtexindex+i-3;
1578 if(ti>=0 && ti<curtexnum)
1580 Texture *tex = lookuptexture(texmru[ti]).sts[0].t;
1581 float sx = min(1, tex->xs/(float)tex->ys), sy = min(1, tex->ys/(float)tex->xs);
1582 glBindTexture(GL_TEXTURE_2D, tex->gl);
1583 glColor4f(0, 0, 0, texpaneltimer/1000.0f);
1584 int x = width-s-50, r = s;
1585 loopj(2)
1587 glBegin(GL_QUADS);
1588 glTexCoord2f(0.0, 0.0); glVertex2f(x, y);
1589 glTexCoord2f(1.0/sx, 0.0); glVertex2f(x+r, y);
1590 glTexCoord2f(1.0/sx, 1.0/sy); glVertex2f(x+r, y+r);
1591 glTexCoord2f(0.0, 1.0/sy); glVertex2f(x, y+r);
1592 glEnd();
1593 xtraverts += 4;
1594 glColor4f(1.0, 1.0, 1.0, texpaneltimer/1000.0f);
1595 r -= 10;
1596 x += 5;
1597 y += 5;
1600 y += s+gap;
1602 glDisable(GL_BLEND);
1603 glDepthMask(GL_TRUE);