6 void boxs(int orient
, vec o
, const vec
&s
)
8 int d
= dimension(orient
),
11 float f
= !outline
? 0 : (dc
>0 ? 0.2f
: -0.2f
);
12 o
[D
[d
]] += float(dc
) * s
[D
[d
]] + f
,
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
]];
25 void boxs3D(const vec
&o
, vec s
, int g
)
32 void boxsgrid(int orient
, vec o
, vec s
, int g
)
34 int d
= dimension(orient
),
40 f
= !outline
? 0 : (dc
>0 ? 0.2f
: -0.2f
);
42 o
[D
[d
]] += dc
* s
[D
[d
]]*g
+ f
;
60 xtraverts
+= 2*int(xs
+ys
);
63 selinfo sel
= { 0 }, lastsel
;
70 extern int entediting
;
71 bool editmode
= false;
78 VARF(dragging
, 0, 0, 1,
79 if(!dragging
|| cor
[0]<0) 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; }
98 moving
= dragging
= 0;
102 extern void entcancel();
110 VARF(gridpower
, 3-VVEC_FRAC
, 3, VVEC_INT
-1,
113 gridsize
= 1<<gridpower
;
114 if(gridsize
>=hdr
.worldsize
) gridsize
= hdr
.worldsize
/2;
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
);
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
135 cl
->resetgamestate();
136 player
->state
= CS_EDITING
;
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;
154 s
.mul(float(sel
.grid
) / 2.0f
);
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");
162 extern void createheightmap();
168 sel
.cxs
= sel
.s
[R
[dimension(orient
)]]*2;
169 sel
.cys
= sel
.s
[C
[dimension(orient
)]]*2;
175 if(noedit(true)) return;
180 sel
.s
[i
] += (sel
.o
[i
]-cur
[i
])/sel
.grid
;
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 ///////////////
215 ICOMMAND(havesel
, "", (), intret(havesel
? selchildcount
: 0));
217 void countselchild(cube
*c
, const ivec
&cor
, int size
)
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
)
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);
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
)
261 if(pl
.rayintersect(player
->o
, ray
, dist
))
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);
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);
292 ray
.sub(player
->o
).normalize();
293 int d
= dimension(sel
.orient
),
294 od
= dimension(orient
),
295 odc
= dimcoord(orient
);
297 bool hovering
= false;
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
);
308 (e
= handle
).mask(~(sel
.grid
-1));
312 (e
= v
).mask(~(sel
.grid
-1));
313 sel
.o
[R
[od
]] = e
[R
[od
]];
314 sel
.o
[C
[od
]] = e
[C
[od
]];
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)
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
343 if(hovering
= hoveringonent(ent
, entorient
))
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
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
);
372 od
= dimension(orient
= horient
);
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
]]);
405 sel
.s
.x
= sel
.s
.y
= sel
.s
.z
= 1;
407 sel
.cxs
= sel
.cys
= 2;
413 sel
.corner
= (cor
[R
[d
]]-(lu
[R
[d
]]*2)/gridsize
)+(cor
[C
[d
]]-(lu
[C
[d
]]*2)/gridsize
)*2;
415 countselchild(worldroot
, vec(0), hdr
.worldsize
/2);
416 if(mag
>1 && selchildcount
==1) selchildcount
= -mag
;
421 glBlendFunc(GL_ONE
, GL_ONE
);
425 renderentselection(player
->o
, ray
, entmoving
!=0);
427 glEnable(GL_POLYGON_OFFSET_LINE
);
429 if(!moving
&& !hovering
)
432 glColor3ub(0, hmapsel
? 255 : 40, 0);
434 glColor3ub(120,120,120);
435 boxs(orient
, lu
.tovec(), vec(lusize
));
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
);
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
);
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
);
477 if(hasmerges
) invalidatemerges(c
[i
]);
479 freeoctaentities(c
[i
]);
483 if(size
<=(8>>VVEC_FRAC
))
486 discardchildren(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;
499 loopi(3) b
.s
[i
] *= b
.grid
;
501 loopi(3) // the changed blocks are the selected cubes
505 readychanges(b
, worldroot
, vec(0), hdr
.worldsize
/2);
510 inbetweenframes
= false;
512 inbetweenframes
= true;
514 invalidatereflections();
515 entitiesinoctanodes();
518 //////////// copy and undo /////////////
519 cube
copycube(cube
&src
)
522 c
.ext
= NULL
; // src cube is responsible for va destruction
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
;
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()];
543 loopxyz(s
, rgrid
, *q
++ = copycube(c
));
547 void freeblock(block3
*b
)
550 loopi(b
->size()) discardchildren(*q
++);
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
));
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
)
578 loopxyz(*u
.b
, *g
++, pastecube(*s
++, c
));
583 void pruneundos(int maxremain
) // bound memory
588 undoblock
&u
= undos
[i
];
592 t
+= u
.b
->size()*sizeof(int);
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
)
612 pruneundos(undomegs
<<20);
615 void makeundo() // stores state of selected cubes before editing
617 if(lastsel
==sel
|| sel
.s
==vec(0)) return;
619 if(multiplayer(false)) return;
621 initundocube(u
, sel
);
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();
637 sel
.grid
= u
.b
->grid
;
638 sel
.orient
= u
.b
->orient
;
641 if(u
.g
) initundocube(r
, sel
);
642 if(u
.n
) copyundoents(r
, u
);
645 if(u
.b
) changed(sel
);
652 void editundo() { swapundo(undos
, redos
, "undo"); }
653 void editredo() { swapundo(redos
, undos
, "redo"); }
655 editinfo
*localedit
= NULL
;
657 void freeeditinfo(editinfo
*&e
)
660 if(e
->copy
) freeblock(e
->copy
);
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
);
674 protectsel(e
->copy
= blockcopy(block3(sel
), sel
.grid
));
678 void mppaste(editinfo
*&e
, selinfo
&sel
, bool local
)
681 if(local
) cl
->edittrigger(sel
, EDIT_PASTE
);
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'
695 if(noedit(true)) return;
696 mpcopy(localedit
, sel
, true);
701 if(!localedit
) return;
702 sel
.s
= localedit
->copy
->s
;
710 mppaste(localedit
, sel
, true);
714 COMMAND(pastehilite
, "");
716 COMMANDN(undo
, editundo
, "");
717 COMMANDN(redo
, editredo
, "");
719 ///////////// height maps ////////////////
724 int brush
[MAXBRUSH
][MAXBRUSH
];
725 VAR(brushx
, 0, MAXBRUSH2
, MAXBRUSH
);
726 VAR(brushy
, 0, MAXBRUSH2
, MAXBRUSH
);
728 int brushmaxx
= 0, brushminx
= MAXBRUSH
;
729 int brushmaxy
= 0, brushminy
= MAXBRUSH
;
733 memset(brush
, 0, sizeof brush
);
734 brushmaxx
= brushmaxy
= 0;
735 brushminx
= brushminy
= MAXBRUSH
;
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
);
766 bool ischildless(cube
&c
)
772 if(!ischildless(c
.children
[i
]) || !isempty(c
.children
[i
]))
780 inline bool ishtexture(int t
)
783 if(t
== htextures
[i
])
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
])
806 uchar flags
[MAXBRUSH
][MAXBRUSH
];
807 cube
*cmap
[MAXBRUSHC
][MAXBRUSHC
][4];
808 int mapz
[MAXBRUSHC
][MAXBRUSHC
];
809 int map
[MAXBRUSH
][MAXBRUSH
];
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
;
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
);
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
;
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
)
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
);
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]))
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
;
878 c
[1] = getcube(t
, 0);
881 if(!c
[1] || isempty(*c
[1])) { flags
[x
][y
] |= NOTHMAP
; return; }
883 flags
[x
][y
] |= PAINTED
;
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
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 { \
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; \
935 loopj(4) if(*o[j] GT par) { \
943 pullhmap(0, >, <, 1, 0, -);
945 pullhmap(hdr
.worldsize
, <, >, 0, 8, +);
947 cube
**c
= cmap
[x
][y
];
953 e
[i
][j
] = min(8, map
[x
+i
][y
+j
] - (mapz
[x
][y
]+3-k
)*8);
954 notempty
|= e
[i
][j
] > 0;
958 c
[k
]->texture
[sel
.orient
] = c
[1]->texture
[sel
.orient
];
963 if(f
<0 || (f
==0 && e
[1-i
][j
]==0 && e
[i
][1-j
]==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
);
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++)
1000 map
[x
][y
] -= dr
* brush
[x
][y
];
1011 if(flags
[x
+i
][y
+j
] & MAPPED
)
1012 sum
+= map
[x
+i
][y
+j
];
1015 map
[x
+1][y
+1] = sum
/ div
;
1022 ripple(x
, y
, gz
, false);
1025 void run(int dir
, int mode
)
1027 d
= dimension(sel
.orient
);
1028 dc
= dimcoord(sel
.orient
);
1030 dr
= dir
>0 ? 1 : -1;
1031 br
= dir
>0 ? 0x08080808 : 0;
1032 // biasup = mode == 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
);
1041 fg
= dc
? gridsize
: -gridsize
;
1042 mx
= max(0, -gx
); // ripple range
1044 nx
= min(MAXBRUSH
-1, hws
-gx
) - 1;
1045 ny
= min(MAXBRUSH
-1, hws
-gy
) - 1;
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
;
1060 changes
.grid
= gridsize
;
1061 changes
.s
= changes
.o
= cur
;
1062 memset(map
, 0, sizeof map
);
1063 memset(flags
, 0, sizeof flags
);
1066 select(clamp(MAXBRUSH2
-cx
, bmx
, bnx
),
1067 clamp(MAXBRUSH2
-cy
, bmy
, bny
),
1068 dc
? gz
: hws
- gz
);
1074 rippleandset(); // pull up points to cubify, and set
1075 changes
.s
.sub(changes
.o
).shr(gridpower
).add(1);
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
)
1100 getcubevector(c
, d
, x
, y
, dc
, v
);
1104 getcubevector(c
, d
, i
, j
, dc
, p
);
1106 pushedge(cubeedge(c
, d
, i
, j
), dir
, dc
);
1110 static uchar
getmaterial(cube
&c
)
1114 uchar mat
= getmaterial(c
.children
[7]);
1115 loopi(7) if(mat
!= getmaterial(c
.children
[i
])) return MAT_AIR
;
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
;
1131 cl
->edittrigger(sel
, EDIT_FACE
, dir
, mode
);
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
;
1144 if(c
.children
) solidfaces(c
);
1145 uchar mat
= getmaterial(c
);
1147 if(mat
!=MAT_AIR
) ext(c
).material
= mat
;
1148 if(mode
==1) // fill command
1153 cube
&o
= blockcube(x
, y
, 1, sel
, -sel
.grid
);
1155 c
.texture
[i
] = o
.texture
[i
];
1162 uint bak
= c
.faces
[d
];
1163 uchar
*p
= (uchar
*)&c
.faces
[d
];
1166 linkedpush(c
, d
, sel
.corner
&1, sel
.corner
>>1, dc
, seldir
); // corner command
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
);
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
1190 c
.edges
[d
*4+k
] = n
[k
];
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;
1206 mpeditface(*dir
, *mode
, sel
, true);
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
));
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
;
1249 texmru
.insert(0, texmru
.remove(c
));
1257 void edittexcube(cube
&c
, int tex
, int orient
, bool &findrep
)
1259 if(orient
<0) loopi(6) c
.texture
[i
] = tex
;
1262 int i
= visibleorient(c
, orient
);
1265 if(reptex
< 0) reptex
= c
.texture
[i
];
1266 else if(reptex
!= c
.texture
[i
]) findrep
= false;
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
)
1280 cl
->edittrigger(sel
, EDIT_TEX
, tex
, allfaces
);
1281 if(allfaces
|| !(repsel
== sel
)) reptex
= -1;
1284 bool findrep
= local
&& !allfaces
&& reptex
< 0;
1285 loopselxyz(edittexcube(c
, tex
, allfaces
? -1 : sel
.orient
, findrep
));
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
);
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;
1308 texpaneltimer
= 5000;
1309 if(!(lastsel
==sel
)) tofronttex();
1310 edittex(curtexindex
<0 ? 0 : curtexindex
+*dir
);
1315 if(noedit()) return;
1317 loopxyz(sel
, sel
.grid
, curtexindex
= c
.texture
[sel
.orient
]);
1318 loopi(curtexnum
) if(texmru
[i
]==curtexindex
)
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
);
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
]]);
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]);
1388 int row
= octadim(R
[d
]);
1389 int col
= octadim(C
[d
]);
1390 for(int i
=0; i
<=octadim(d
); i
+=octadim(d
)) rotatequad
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
)];
1408 loop(z
,zs
) flipcube(selcube(x
, y
, z
), dimension(sel
.orient
));
1411 cube
&a
= selcube(x
, y
, z
);
1412 cube
&b
= selcube(x
, y
, zs
-z
-1);
1421 if(noedit()) return;
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
]]);
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
),
1440 selcube(y
, ss
-1-x
-y
, z
),
1441 selcube(ss
-1-x
-y
, ss
-1-y
, z
)
1447 void rotate(int *cw
)
1449 if(noedit()) return;
1450 mprotate(*cw
, sel
, true);
1454 COMMAND(rotate
, "i");
1456 void setmat(cube
&c
, uchar mat
)
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
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
)
1506 loopk(TEXTURE_WIDTH
)
1508 int ti
= (i
*TEXTURE_HEIGHT
+j
)*TEXTURE_WIDTH
+k
;
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
))
1521 g
.texture(notexture
, 1.0); //create an empty space
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(); }
1539 if(!editmode
|| camera1
->o
.dist(menupos
) > menuautoclose
) menuon
= false;
1540 else g3d_addgui(this, menupos
); //follow?
1544 void g3d_texturemenu()
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
);
1564 glBlendFunc(GL_SRC_ALPHA
, GL_ONE_MINUS_SRC_ALPHA
);
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();
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
;
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
);
1594 glColor4f(1.0, 1.0, 1.0, texpaneltimer
/1000.0f
);
1602 glDisable(GL_BLEND
);
1603 glDepthMask(GL_TRUE
);