Line data Source code
1 : // world.cpp: core map management stuff
2 :
3 : #include "../libprimis-headers/cube.h"
4 : #include "../../shared/geomexts.h"
5 : #include "../../shared/glexts.h"
6 :
7 : #include <memory>
8 : #include <optional>
9 :
10 : #include "bih.h"
11 : #include "entities.h"
12 : #include "light.h"
13 : #include "octaedit.h"
14 : #include "octaworld.h"
15 : #include "raycube.h"
16 : #include "world.h"
17 :
18 : #include "interface/console.h"
19 : #include "interface/cs.h"
20 : #include "interface/menus.h"
21 :
22 : #include "model/model.h"
23 :
24 : #include "render/octarender.h"
25 : #include "render/renderlights.h"
26 : #include "render/rendermodel.h"
27 : #include "render/renderparticles.h"
28 : #include "render/shaderparam.h"
29 : #include "render/stain.h"
30 : #include "render/texture.h"
31 :
32 : SVARR(maptitle, "Untitled Map by Unknown");
33 :
34 : std::vector<int> outsideents;
35 : std::vector<size_t> entgroup;
36 :
37 : namespace entities
38 : {
39 : std::vector<extentity *> ents;
40 :
41 0 : std::vector<extentity *> &getents()
42 : {
43 0 : return ents;
44 : }
45 :
46 0 : const char *entmodel()
47 : {
48 0 : return nullptr;
49 : }
50 :
51 0 : extentity *newentity()
52 : {
53 0 : return new extentity();
54 : }
55 :
56 0 : void deleteentity(extentity *e)
57 : {
58 0 : delete e;
59 0 : }
60 :
61 0 : void clearents()
62 : {
63 0 : while(ents.size())
64 : {
65 0 : deleteentity(ents.back());
66 0 : ents.pop_back();
67 : }
68 0 : }
69 :
70 : }
71 : //octaents are the ones that modify the level directly: other entities like
72 : //sounds, lights, spawns etc. don't get directly rendered
73 : static VAR(entselradius, 0, 2, 10);
74 : static VAR(octaentsize, 0, 64, 1024);
75 :
76 2 : void entcancel()
77 : {
78 2 : entgroup.clear();
79 2 : }
80 :
81 : //need a getter fxn because decalslot obj not exposed to the game
82 0 : float getdecalslotdepth(const DecalSlot &s)
83 : {
84 0 : return s.depth;
85 : }
86 :
87 : //used in iengine
88 0 : void detachentity(extentity &e)
89 : {
90 0 : if(!e.attached)
91 : {
92 0 : return;
93 : }
94 0 : e.attached->attached = nullptr;
95 0 : e.attached = nullptr;
96 : }
97 :
98 : static VAR(attachradius, 1, 100, 1000);
99 :
100 : //used in iengine
101 0 : void attachentity(extentity &e)
102 : {
103 : //don't attempt to attach invalid to attach ents
104 0 : switch(e.type)
105 : {
106 0 : case EngineEnt_Spotlight:
107 : {
108 0 : break;
109 : }
110 0 : default:
111 : {
112 0 : return;
113 : }
114 : }
115 0 : detachentity(e);
116 :
117 0 : const std::vector<extentity *> &ents = entities::getents();
118 0 : int closest = -1; //index of closest entry to link to
119 0 : float closedist = 1e10f; //some arbitrary high value
120 0 : for(size_t i = 0; i < ents.size(); i++)
121 : {
122 0 : const extentity *a = ents[i];
123 0 : if(a->attached)
124 : {
125 0 : continue; //do not attempt to attach to an already attached entity
126 : }
127 0 : switch(e.type)
128 : {
129 0 : case EngineEnt_Spotlight:
130 : {
131 : //only attempt to attach to lights
132 0 : if(a->type!=EngineEnt_Light)
133 : {
134 0 : continue;
135 : }
136 0 : break;
137 : }
138 0 : default:
139 : {
140 0 : continue;
141 : }
142 : }
143 0 : float dist = e.o.dist(a->o);
144 0 : if(dist < closedist)
145 : {
146 0 : closest = i;
147 0 : closedist = dist;
148 : }
149 : }
150 0 : if(closedist>attachradius)
151 : {
152 0 : return; //we found nothing in the radius to attach to, so don't attach anything
153 : }
154 : // closedist <= attachradius implies that closest was set by the above loop,
155 : // so closest is always a valid array index
156 0 : e.attached = ents[closest];
157 0 : ents[closest]->attached = &e;
158 : }
159 :
160 : //used in iengine
161 0 : void attachentities()
162 : {
163 0 : for(extentity *& i : entities::getents())
164 : {
165 0 : attachentity(*i);
166 : }
167 0 : }
168 :
169 : enum ModOctaEnt
170 : {
171 : ModOctaEnt_Add = 1<<0,
172 : ModOctaEnt_UpdateBB = 1<<1,
173 : ModOctaEnt_Changed = 1<<2
174 : };
175 :
176 0 : static void rotatebb(vec ¢er, vec &radius, int yaw, int pitch, int roll)
177 : {
178 0 : matrix3 orientation;
179 0 : orientation.identity();
180 0 : if(yaw)
181 : {
182 0 : orientation.rotate_around_z(sincosmod360(yaw));
183 : }
184 0 : if(pitch)
185 : {
186 0 : orientation.rotate_around_x(sincosmod360(pitch));
187 : }
188 0 : if(roll)
189 : {
190 0 : orientation.rotate_around_y(sincosmod360(-roll));
191 : }
192 0 : center = orientation.transform(center);
193 0 : radius = orientation.abstransform(radius);
194 0 : }
195 :
196 0 : static void transformbb(const entity &e, vec ¢er, vec &radius)
197 : {
198 0 : if(e.attr5 > 0)
199 : {
200 0 : float scale = e.attr5/100.0f;
201 0 : center.mul(scale);
202 0 : radius.mul(scale);
203 : }
204 0 : rotatebb(center, radius, e.attr2, e.attr3, e.attr4);
205 0 : }
206 :
207 : //used in iengine
208 0 : void mmboundbox(const entity &e, model *m, vec ¢er, vec &radius)
209 : {
210 0 : m->boundbox(center, radius);
211 0 : transformbb(e, center, radius);
212 0 : }
213 :
214 0 : static void mmcollisionbox(const entity &e, model *m, vec ¢er, vec &radius)
215 : {
216 0 : m->collisionbox(center, radius);
217 0 : transformbb(e, center, radius);
218 0 : }
219 :
220 0 : static void decalboundbox(const entity &e, const DecalSlot &s, vec ¢er, vec &radius)
221 : {
222 0 : float size = std::max(static_cast<float>(e.attr5), 1.0f);
223 0 : center = vec(0, s.depth * size/2, 0);
224 0 : radius = vec(size/2, s.depth * size/2, size/2);
225 0 : rotatebb(center, radius, e.attr2, e.attr3, e.attr4);
226 0 : }
227 :
228 0 : static bool modifyoctaent(int flags, int id)
229 : {
230 0 : std::vector<extentity *> &ents = entities::getents();
231 0 : return (static_cast<int>(ents.size()) > id) && ::rootworld.modifyoctaent(flags, id, *ents[id]);
232 : }
233 :
234 0 : static void removeentity(int id)
235 : {
236 0 : modifyoctaent(ModOctaEnt_UpdateBB, id);
237 0 : }
238 :
239 0 : void freeoctaentities(cube &c)
240 : {
241 0 : if(!c.ext)
242 : {
243 0 : return;
244 : }
245 0 : if(entities::getents().size())
246 : {
247 0 : while(c.ext->ents && !c.ext->ents->mapmodels.empty())
248 : {
249 0 : int id = c.ext->ents->mapmodels.back();
250 0 : c.ext->ents->mapmodels.pop_back();
251 0 : removeentity(id);
252 : }
253 0 : while(c.ext->ents && !c.ext->ents->decals.empty())
254 : {
255 0 : int id = c.ext->ents->decals.back();
256 0 : c.ext->ents->decals.pop_back();
257 0 : removeentity(id);
258 : }
259 0 : while(c.ext->ents && !c.ext->ents->other.empty())
260 : {
261 0 : int id = c.ext->ents->other.back();
262 0 : c.ext->ents->other.pop_back();
263 0 : removeentity(id);
264 : }
265 : }
266 0 : if(c.ext->ents)
267 : {
268 0 : delete c.ext->ents;
269 0 : c.ext->ents = nullptr;
270 : }
271 : }
272 :
273 0 : static bool getentboundingbox(const extentity &e, ivec &o, ivec &r)
274 : {
275 0 : switch(e.type)
276 : {
277 0 : case EngineEnt_Empty:
278 : {
279 0 : return false;
280 : }
281 0 : case EngineEnt_Decal:
282 : {
283 0 : const DecalSlot &s = lookupdecalslot(e.attr1, false);
284 0 : vec center, radius;
285 0 : decalboundbox(e, s, center, radius);
286 0 : center.add(e.o);
287 0 : radius.max(entselradius);
288 0 : o = ivec(vec(center).sub(radius));
289 0 : r = ivec(vec(center).add(radius).add(1));
290 0 : break;
291 : }
292 0 : case EngineEnt_Mapmodel:
293 : {
294 0 : if(model *m = loadmapmodel(e.attr1))
295 : {
296 0 : vec center, radius;
297 0 : mmboundbox(e, m, center, radius);
298 0 : center.add(e.o);
299 0 : radius.max(entselradius);
300 0 : o = ivec(vec(center).sub(radius));
301 0 : r = ivec(vec(center).add(radius).add(1));
302 : }
303 0 : break;
304 : }
305 : // invisible mapmodels use entselradius: lights sounds spawns etc.
306 0 : default:
307 : {
308 0 : o = ivec(vec(e.o).sub(entselradius));
309 0 : r = ivec(vec(e.o).add(entselradius+1));
310 0 : break;
311 : }
312 : }
313 0 : return true;
314 : }
315 :
316 0 : static void modifyoctaentity(int flags, int id, const extentity &e, std::array<cube, 8> &c, const ivec &cor, int size, const ivec &bo, const ivec &br, int leafsize, vtxarray *lastva = nullptr)
317 : {
318 0 : LOOP_OCTA_BOX(cor, size, bo, br)
319 : {
320 0 : ivec o(i, cor, size);
321 0 : vtxarray *va = c[i].ext && c[i].ext->va ? c[i].ext->va : lastva;
322 0 : if(c[i].children != nullptr && size > leafsize)
323 : {
324 0 : modifyoctaentity(flags, id, e, *(c[i].children), o, size>>1, bo, br, leafsize, va);
325 : }
326 0 : else if(flags&ModOctaEnt_Add)
327 : {
328 0 : if(!c[i].ext || !c[i].ext->ents)
329 : {
330 0 : ext(c[i]).ents = new octaentities(o, size);
331 : }
332 0 : octaentities &oe = *c[i].ext->ents;
333 0 : switch(e.type)
334 : {
335 0 : case EngineEnt_Decal:
336 : {
337 0 : if(va)
338 : {
339 0 : va->bbmin.x = -1;
340 0 : if(oe.decals.empty())
341 : {
342 0 : va->decals.push_back(&oe);
343 : }
344 : }
345 0 : oe.decals.push_back(id);
346 0 : oe.bbmin.min(bo).max(oe.o);
347 0 : oe.bbmax.max(br).min(ivec(oe.o).add(oe.size));
348 0 : break;
349 : }
350 0 : case EngineEnt_Mapmodel:
351 : {
352 0 : if(loadmapmodel(e.attr1))
353 : {
354 0 : if(va)
355 : {
356 0 : va->bbmin.x = -1;
357 0 : if(oe.mapmodels.empty())
358 : {
359 0 : va->mapmodels.push_back(&oe);
360 : }
361 : }
362 0 : oe.mapmodels.push_back(id);
363 0 : oe.bbmin.min(bo).max(oe.o);
364 0 : oe.bbmax.max(br).min(ivec(oe.o).add(oe.size));
365 : }
366 0 : break;
367 : }
368 : // invisible mapmodels: lights sounds spawns etc.
369 0 : default:
370 : {
371 0 : oe.other.push_back(id);
372 0 : break;
373 : }
374 : }
375 : }
376 0 : else if(c[i].ext && c[i].ext->ents)
377 : {
378 0 : octaentities &oe = *c[i].ext->ents;
379 0 : switch(e.type)
380 : {
381 0 : case EngineEnt_Decal:
382 : {
383 0 : if(std::find(oe.decals.begin(), oe.decals.end(), id) != oe.decals.end())
384 : {
385 0 : oe.decals.erase(std::find(oe.decals.begin(), oe.decals.end(), id));
386 : }
387 0 : if(va)
388 : {
389 0 : va->bbmin.x = -1;
390 0 : if(oe.decals.empty())
391 : {
392 0 : std::vector<octaentities *>::const_iterator itr = std::find(va->decals.begin(), va->decals.end(), &oe);
393 0 : if(itr != va->decals.end())
394 : {
395 0 : va->decals.erase(itr);
396 : }
397 : }
398 : }
399 0 : oe.bbmin = oe.bbmax = oe.o;
400 0 : oe.bbmin.add(oe.size);
401 0 : for(size_t j = 0; j < oe.decals.size(); j++)
402 : {
403 0 : extentity &ext = *entities::getents()[oe.decals[j]];
404 0 : ivec eo, er;
405 0 : if(getentboundingbox(ext, eo, er))
406 : {
407 0 : oe.bbmin.min(eo);
408 0 : oe.bbmax.max(er);
409 : }
410 : }
411 0 : oe.bbmin.max(oe.o);
412 0 : oe.bbmax.min(ivec(oe.o).add(oe.size));
413 0 : break;
414 : }
415 0 : case EngineEnt_Mapmodel:
416 : {
417 0 : if(loadmapmodel(e.attr1))
418 : {
419 0 : std::vector<int>::const_iterator itr = std::find(oe.mapmodels.begin(), oe.mapmodels.end(), id);
420 0 : if(itr != oe.mapmodels.end())
421 : {
422 0 : oe.mapmodels.erase(itr);
423 : }
424 0 : if(va)
425 : {
426 0 : va->bbmin.x = -1;
427 0 : if(oe.mapmodels.empty())
428 : {
429 0 : if(std::find(va->mapmodels.begin(), va->mapmodels.end(), &oe) != va->mapmodels.end())
430 : {
431 0 : va->mapmodels.erase(std::find(va->mapmodels.begin(), va->mapmodels.end(), &oe));
432 : }
433 : }
434 : }
435 0 : oe.bbmin = oe.bbmax = oe.o;
436 0 : oe.bbmin.add(oe.size);
437 0 : for(const int &j : oe.mapmodels)
438 : {
439 0 : extentity &ext = *entities::getents()[j];
440 0 : ivec eo, er;
441 0 : if(getentboundingbox(ext, eo, er))
442 : {
443 0 : oe.bbmin.min(eo);
444 0 : oe.bbmax.max(er);
445 : }
446 : }
447 0 : oe.bbmin.max(oe.o);
448 0 : oe.bbmax.min(ivec(oe.o).add(oe.size));
449 : }
450 0 : break;
451 : }
452 : // invisible mapmodels: light sounds spawns etc.
453 0 : default:
454 : {
455 0 : if(std::find(oe.other.begin(), oe.other.end(), id) != oe.other.end())
456 : {
457 0 : oe.other.erase(std::find(oe.other.begin(), oe.other.end(), id));
458 : }
459 0 : break;
460 : }
461 : }
462 0 : if(oe.mapmodels.empty() && oe.decals.empty() && oe.other.empty())
463 : {
464 0 : freeoctaentities(c[i]);
465 : }
466 : }
467 0 : if(c[i].ext && c[i].ext->ents)
468 : {
469 0 : c[i].ext->ents->query = nullptr;
470 : }
471 0 : if(va && va!=lastva)
472 : {
473 0 : if(lastva)
474 : {
475 0 : if(va->bbmin.x < 0)
476 : {
477 0 : lastva->bbmin.x = -1;
478 : }
479 : }
480 0 : else if(flags&ModOctaEnt_UpdateBB)
481 : {
482 0 : va->updatevabb();
483 : }
484 : }
485 : }
486 0 : }
487 :
488 0 : bool cubeworld::modifyoctaent(int flags, int id, extentity &e)
489 : {
490 0 : if(flags&ModOctaEnt_Add ? e.flags&EntFlag_Octa : !(e.flags&EntFlag_Octa))
491 : {
492 0 : return false;
493 : }
494 0 : ivec o, r;
495 0 : if(!getentboundingbox(e, o, r))
496 : {
497 0 : return false;
498 : }
499 0 : if(!insideworld(e.o))
500 : {
501 0 : std::vector<int>::iterator itr = std::find(outsideents.begin(), outsideents.end(), id);
502 0 : if(flags&ModOctaEnt_Add)
503 : {
504 0 : if(itr != outsideents.end())
505 : {
506 0 : outsideents.push_back(id);
507 : }
508 : }
509 0 : else if(itr != outsideents.end())
510 : {
511 0 : outsideents.erase(itr);
512 : }
513 : }
514 : else
515 : {
516 0 : int leafsize = octaentsize,
517 0 : limit = std::max(r.x - o.x, std::max(r.y - o.y, r.z - o.z));
518 0 : while(leafsize < limit)
519 : {
520 0 : leafsize *= 2;
521 : }
522 0 : int diff = ~(leafsize-1) & ((o.x^r.x)|(o.y^r.y)|(o.z^r.z));
523 0 : if(diff && (limit > octaentsize/2 || diff < leafsize*2))
524 : {
525 0 : leafsize *= 2;
526 : }
527 0 : modifyoctaentity(flags, id, e, *worldroot, ivec(0, 0, 0), mapsize()>>1, o, r, leafsize);
528 : }
529 0 : e.flags ^= EntFlag_Octa;
530 0 : switch(e.type)
531 : {
532 0 : case EngineEnt_Light:
533 : {
534 0 : clearlightcache(id);
535 0 : if(e.attr5&LightEnt_Volumetric)
536 : {
537 0 : if(flags&ModOctaEnt_Add)
538 : {
539 0 : volumetriclights++;
540 : }
541 : else
542 : {
543 0 : --volumetriclights;
544 : }
545 : }
546 0 : if(e.attr5&LightEnt_NoSpecular)
547 : {
548 0 : if(!(flags&ModOctaEnt_Add ? nospeclights++ : --nospeclights))
549 : {
550 0 : cleardeferredlightshaders();
551 : }
552 : }
553 0 : break;
554 : }
555 0 : case EngineEnt_Spotlight:
556 : {
557 0 : if(!(flags&ModOctaEnt_Add ? spotlights++ : --spotlights))
558 : {
559 0 : cleardeferredlightshaders();
560 0 : cleanupvolumetric();
561 : }
562 0 : break;
563 : }
564 0 : case EngineEnt_Particles:
565 : {
566 0 : clearparticleemitters();
567 0 : break;
568 : }
569 0 : case EngineEnt_Decal:
570 : {
571 0 : if(flags&ModOctaEnt_Changed)
572 : {
573 0 : changed(o, r, false);
574 : }
575 0 : break;
576 : }
577 : }
578 0 : return true;
579 : }
580 :
581 0 : void addentityedit(int id)
582 : {
583 0 : modifyoctaent(ModOctaEnt_Add|ModOctaEnt_UpdateBB|ModOctaEnt_Changed, id);
584 0 : }
585 :
586 0 : void removeentityedit(int id)
587 : {
588 0 : modifyoctaent(ModOctaEnt_UpdateBB|ModOctaEnt_Changed, id);
589 0 : }
590 :
591 0 : void cubeworld::entitiesinoctanodes()
592 : {
593 0 : std::vector<extentity *> &ents = entities::getents();
594 0 : for(size_t i = 0; i < ents.size(); i++)
595 : {
596 0 : modifyoctaent(ModOctaEnt_Add, i, *ents[i]);
597 : }
598 0 : }
599 :
600 0 : void entselectionbox(const entity &e, vec &eo, vec &es)
601 : {
602 0 : model *m = nullptr;
603 0 : const char *mname = entities::entmodel();
604 0 : m = loadmodel(mname);
605 0 : if(mname && m)
606 : {
607 0 : m->collisionbox(eo, es);
608 0 : if(es.x > es.y)
609 : {
610 0 : es.y = es.x;
611 : }
612 : else
613 : {
614 0 : es.x = es.y; // square
615 : }
616 0 : es.z = (es.z + eo.z + 1 + entselradius)/2; // enclose ent radius box and model box
617 0 : eo.x += e.o.x;
618 0 : eo.y += e.o.y;
619 0 : eo.z = e.o.z - entselradius + es.z;
620 : }
621 0 : else if(e.type == EngineEnt_Mapmodel && (m = loadmapmodel(e.attr1)))
622 : {
623 0 : mmcollisionbox(e, m, eo, es);
624 0 : es.max(entselradius);
625 0 : eo.add(e.o);
626 : }
627 0 : else if(e.type == EngineEnt_Decal)
628 : {
629 0 : DecalSlot &s = lookupdecalslot(e.attr1, false);
630 0 : decalboundbox(e, s, eo, es);
631 0 : es.max(entselradius);
632 0 : eo.add(e.o);
633 : }
634 : else
635 : {
636 0 : es = vec(entselradius);
637 0 : eo = e.o;
638 : }
639 0 : eo.sub(es);
640 0 : es.mul(2);
641 0 : }
642 :
643 : ////////////////////////////// world size/octa /////////////////////////////////
644 :
645 0 : static void splitocta(std::array<cube, 8> &c, int size)
646 : {
647 0 : if(size <= 0x1000)
648 : {
649 0 : return;
650 : }
651 0 : for(int i = 0; i < 8; ++i)
652 : {
653 0 : if(!c[i].children)
654 : {
655 0 : c[i].children = newcubes(c[i].isempty() ? faceempty : facesolid);
656 : }
657 0 : splitocta(*(c[i].children), size>>1);
658 : }
659 : }
660 :
661 0 : void cubeworld::resetmap()
662 : {
663 0 : clearoverrides();
664 0 : clearlights();
665 0 : clearslots();
666 0 : clearparticles();
667 0 : clearstains();
668 0 : clearsleep();
669 0 : cancelsel();
670 0 : pruneundos();
671 0 : clearmapcrc();
672 :
673 0 : entities::clearents();
674 0 : outsideents.clear();
675 0 : spotlights = 0;
676 0 : volumetriclights = 0;
677 0 : nospeclights = 0;
678 0 : }
679 :
680 0 : bool cubeworld::emptymap(int scale, bool force, bool usecfg) // main empty world creation routine
681 : {
682 0 : if(!force && !editmode)
683 : {
684 0 : conoutf(Console_Error, "newmap only allowed in edit mode");
685 0 : return false;
686 : }
687 0 : resetmap();
688 0 : worldscale = std::clamp(scale, 10, 16);
689 0 : setvar("emptymap", 1, true, false);
690 0 : texmru.clear();
691 0 : freeocta(worldroot);
692 0 : worldroot = newcubes(faceempty);
693 0 : for(int i = 0; i < 4; ++i)
694 : {
695 0 : setcubefaces((*worldroot)[i], facesolid);
696 : }
697 0 : if(mapsize() > 0x1000)
698 : {
699 0 : splitocta(*worldroot, mapsize()>>1);
700 : }
701 0 : clearmainmenu();
702 0 : if(usecfg)
703 : {
704 0 : identflags |= Idf_Overridden;
705 0 : execfile("config/default_map_settings.cfg", false);
706 0 : identflags &= ~Idf_Overridden;
707 : }
708 0 : allchanged(true);
709 0 : return true;
710 : }
711 :
712 : /* enlargemap: grows the map to an additional gridsize
713 : * bool force forces the growth even if not in edit mode
714 : *
715 : * expands the map by placing the old map in the corner nearest the origin and
716 : * adding 7 old-map sized cubes to create a new largest cube
717 : *
718 : * this moves the worldroot cube to the new parent cube of the old map
719 : */
720 0 : bool cubeworld::enlargemap()
721 : {
722 0 : worldscale++;
723 0 : std::array<cube, 8> *c = newcubes(faceempty);
724 0 : (*c)[0].children = worldroot;
725 0 : for(int i = 0; i < 3; ++i)
726 : {
727 0 : setcubefaces((*c)[i+1], facesolid);
728 : }
729 0 : worldroot = c;
730 :
731 0 : if(mapsize() > 0x1000)
732 : {
733 0 : splitocta(*worldroot, mapsize()>>1);
734 : }
735 0 : allchanged();
736 0 : return true;
737 : }
738 :
739 : /**
740 : * @brief Checks whether the cube, and all of its children, are empty
741 : *
742 : * returns true if cube is empty and has no children, OR
743 : * returns true if cube has children cubes and none of their children (recursively)
744 : * have children that are not empty
745 : *
746 : * returns false if any cube or child cube is not empty (has geometry)
747 : *
748 : * @param c the cube to check
749 : *
750 : * @return true if cube and its children are empty, false otherwise
751 : */
752 0 : static bool isallempty(const cube &c)
753 : {
754 0 : if(!c.children)
755 : {
756 0 : return c.isempty();
757 : }
758 0 : for(int i = 0; i < 8; ++i)
759 : {
760 0 : if(!isallempty((*c.children)[i]))
761 : {
762 0 : return false;
763 : }
764 : }
765 0 : return true;
766 : }
767 :
768 : /* shrinkmap: attempts to reduce the mapsize by 1 (halves all linear dimensions)
769 : *
770 : * fails if the 7 octants not at the origin are not empty
771 : * on success, the new map will have its maximum gridsize reduced by 1
772 : */
773 0 : void cubeworld::shrinkmap()
774 : {
775 0 : if(noedit(true) || (nompedit && multiplayer))
776 : {
777 0 : multiplayerwarn();
778 0 : return;
779 : }
780 0 : if(mapsize() <= 1<<10) //do not allow maps smaller than 2^10 cubits
781 : {
782 0 : return;
783 : }
784 0 : int octant = -1;
785 0 : for(int i = 0; i < 8; ++i)
786 : {
787 0 : if(!isallempty((*worldroot)[i]))
788 : {
789 0 : if(octant >= 0)
790 : {
791 0 : return;
792 : }
793 0 : octant = i;
794 : }
795 : }
796 0 : if(octant < 0)
797 : {
798 0 : return;
799 : }
800 0 : if(!(*worldroot)[octant].children)
801 : {
802 0 : subdividecube((*worldroot)[octant], false, false);
803 : }
804 0 : std::array<cube, 8> *&root = (*worldroot)[octant].children; //change worldroot to cube 0
805 0 : (*worldroot)[octant].children = nullptr; //free the old largest cube
806 0 : freeocta(worldroot);
807 0 : worldroot = root;
808 0 : worldscale--;
809 0 : ivec offset(octant, ivec(0, 0, 0), mapsize());
810 0 : std::vector<extentity *> &ents = entities::getents();
811 0 : for(extentity * const i : ents)
812 : {
813 0 : i->o.sub(vec(offset));
814 : }
815 0 : allchanged();
816 0 : conoutf("shrunk map to size %d", worldscale);
817 : }
818 :
819 96 : int cubeworld::mapsize() const
820 : {
821 96 : return 1<<worldscale;
822 : }
823 :
824 0 : int cubeworld::mapscale() const
825 : {
826 0 : return worldscale;
827 : }
|