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