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