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