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