Line data Source code
1 : /**
2 : * @brief terrain-like cube pushing functionality
3 : *
4 : * to help the creation of natural terrain like geometry with cubes, heightmapping
5 : * allows for convenient pushing and pulling of areas of geometry
6 : *
7 : * heightmapping is merely a different way of modifying cubes, and under the hood
8 : * (and to other clients) behaves like bulk modification of cubes; the underlying
9 : * geometry is still just cubes
10 : *
11 : * for this reason, heightmapping can be done along any of the main axes, though
12 : * only along one at a time
13 : */
14 : #include "../libprimis-headers/cube.h"
15 : #include "../../shared/geomexts.h"
16 :
17 : #include "octaedit.h"
18 : #include "octaworld.h"
19 : #include "world.h"
20 :
21 :
22 : class hmap final
23 : {
24 : public:
25 3 : void cancel()
26 : {
27 3 : textures.clear();
28 3 : }
29 :
30 1 : void hmapselect()
31 : {
32 1 : const cube &c = rootworld.lookupcube(cur);
33 1 : int t = c.texture[orient],
34 1 : i = std::distance(textures.begin(), std::find(textures.begin(), textures.end(), t));
35 2 : if(i == std::distance(textures.begin(), textures.end()))
36 : {
37 1 : textures.push_back(t);
38 : }
39 : else
40 : {
41 0 : textures.erase(textures.begin() + i);
42 : }
43 1 : }
44 :
45 0 : bool isheightmap(int o, bool empty, const cube &c) const
46 : {
47 0 : return havesel ||
48 0 : (empty && c.isempty()) ||
49 0 : textures.empty() ||
50 0 : (std::find(textures.begin(), textures.end(), c.texture[o]) != textures.end());
51 : }
52 :
53 1 : void clearhbrush()
54 : {
55 65 : for(std::array<int, 64> &i : brush)
56 : {
57 64 : i.fill(0);
58 : }
59 1 : brushmaxx = brushmaxy = 0;
60 1 : brushminx = brushminy = maxbrush;
61 1 : paintbrush = false;
62 1 : }
63 :
64 1 : void hbrushvert(const int *x, const int *y, const int * const v)
65 : {
66 : int x1,
67 : y1;
68 1 : x1 = *x + maxbrush/2 - brushx + 1; // +1 for automatic padding
69 1 : y1 = *y + maxbrush/2 - brushy + 1;
70 1 : if(x1<0 || y1<0 || x1>=maxbrush || y1>=maxbrush)
71 : {
72 0 : return;
73 : }
74 1 : brush[x1][y1] = std::clamp(*v, 0, 8);
75 1 : paintbrush = paintbrush || (brush[x1][y1] > 0);
76 1 : brushmaxx = std::min(maxbrush-1, std::max(brushmaxx, x1+1));
77 1 : brushmaxy = std::min(maxbrush-1, std::max(brushmaxy, y1+1));
78 1 : brushminx = std::max(0, std::min(brushminx, x1-1));
79 1 : brushminy = std::max(0, std::min(brushminy, y1-1));
80 : }
81 :
82 0 : void run(int dir, int mode)
83 : {
84 0 : d = DIMENSION(sel.orient);
85 0 : dc = DIM_COORD(sel.orient);
86 0 : dcr= dc ? 1 : -1;
87 0 : dr = dir>0 ? 1 : -1;
88 : // biasup = mode == dir<0;
89 0 : biasup = dir < 0;
90 0 : int cx = (sel.corner&1 ? 0 : -1),
91 0 : cy = (sel.corner&2 ? 0 : -1);
92 0 : hws= (rootworld.mapsize()>>gridpower);
93 0 : gx = (cur[R[d]] >> gridpower) + cx - maxbrush/2;
94 0 : gy = (cur[C[d]] >> gridpower) + cy - maxbrush/2;
95 0 : gz = (cur[D[d]] >> gridpower);
96 0 : fs = dc ? 4 : 0;
97 0 : fg = dc ? gridsize : -gridsize;
98 0 : mx = std::max(0, -gx); // ripple range
99 0 : my = std::max(0, -gy);
100 0 : nx = std::min(maxbrush-1, hws-gx) - 1;
101 0 : ny = std::min(maxbrush-1, hws-gy) - 1;
102 0 : if(havesel)
103 : { // selection range
104 0 : bmx = mx = std::max(mx, (sel.o[R[d]]>>gridpower)-gx);
105 0 : bmy = my = std::max(my, (sel.o[C[d]]>>gridpower)-gy);
106 0 : bnx = nx = std::min(nx, (sel.s[R[d]]+(sel.o[R[d]]>>gridpower))-gx-1);
107 0 : bny = ny = std::min(ny, (sel.s[C[d]]+(sel.o[C[d]]>>gridpower))-gy-1);
108 : }
109 0 : bool paintme = paintbrush;
110 0 : if(havesel && mode<0) // -ve means smooth selection
111 : {
112 0 : paintme = false;
113 : }
114 : else
115 : { // brush range
116 0 : bmx = std::max(mx, brushminx);
117 0 : bmy = std::max(my, brushminy);
118 0 : bnx = std::min(nx, brushmaxx-1);
119 0 : bny = std::min(ny, brushmaxy-1);
120 : }
121 0 : nz = rootworld.mapsize()-gridsize;
122 0 : mz = 0;
123 0 : hundo.s = ivec(d,1,1,5);
124 0 : hundo.orient = sel.orient;
125 0 : hundo.grid = gridsize;
126 0 : forcenextundo();
127 :
128 0 : changes.grid = gridsize;
129 0 : changes.s = changes.o = cur;
130 0 : std::memset(map, 0, sizeof map);
131 0 : std::memset(flags, 0, sizeof flags);
132 :
133 0 : selecting = true;
134 0 : select(std::clamp(maxbrush/2-cx, bmx, bnx),
135 0 : std::clamp(maxbrush/2-cy, bmy, bny),
136 0 : dc ? gz : hws - gz);
137 0 : selecting = false;
138 0 : if(paintme)
139 : {
140 0 : paint();
141 : }
142 : else
143 : {
144 0 : smooth();
145 : }
146 0 : rippleandset(); // pull up points to cubify, and set
147 0 : changes.s.sub(changes.o).shr(gridpower).add(1);
148 0 : rootworld.changed(changes);
149 0 : }
150 : private:
151 : std::vector<int> textures;
152 : //max brush consts: number of cubes on end that can be heightmap brushed at once
153 : static constexpr int maxbrush = 64,
154 : maxbrushc = 63;
155 :
156 : std::array<std::array<int, maxbrush>, maxbrush> brush;//2d array of heights for heightmap brushs
157 : int brushx = variable("hbrushx", 0, maxbrush/2, maxbrush, &brushx, nullptr, 0), //max width for a brush
158 : brushy = variable("hbrushy", 0, maxbrush/2, maxbrush, &brushy, nullptr, 0); //max length for a brush
159 : bool paintbrush = false;
160 : int brushmaxx = 0,
161 : brushminx = maxbrush,
162 : brushmaxy = 0,
163 : brushminy = maxbrush;
164 :
165 : static constexpr int painted = 1,
166 : nothmap = 2,
167 : mapped = 16;
168 : uchar flags[maxbrush][maxbrush];
169 : cube *cmap[maxbrushc][maxbrushc][4];
170 : int mapz[maxbrushc][maxbrushc],
171 : map [maxbrush][maxbrush];
172 :
173 : selinfo changes;
174 : bool selecting; //flag used by select() for continuing to adj cubes
175 : int d, //dimension
176 : dc, //dimension coordinate
177 : dr, //dimension sign
178 : dcr, //dimension coordinate sign
179 : biasup, //if dir < 0
180 : hws, //heightmap [gridpower] world size
181 : fg; //+/- gridpower depending on dc
182 : int gx, gy, gz,
183 : mx, my, mz,
184 : nx, ny, nz,
185 : bmx, bmy,
186 : bnx, bny;
187 : uint fs; //face
188 : selinfo hundo;
189 :
190 0 : cube *getcube(ivec t, int f)
191 : {
192 0 : t[d] += dcr*f*gridsize;
193 0 : if(t[d] > nz || t[d] < mz)
194 : {
195 0 : return nullptr;
196 : }
197 0 : cube *c = &rootworld.lookupcube(t, gridsize);
198 0 : if(c->children)
199 : {
200 0 : forcemip(*c, false);
201 : }
202 0 : c->discardchildren(true);
203 0 : if(!isheightmap(sel.orient, true, *c))
204 : {
205 0 : return nullptr;
206 : }
207 : //x
208 0 : if (t.x < changes.o.x) changes.o.x = t.x;
209 0 : else if(t.x > changes.s.x) changes.s.x = t.x;
210 : //y
211 0 : if (t.y < changes.o.y) changes.o.y = t.y;
212 0 : else if(t.y > changes.s.y) changes.s.y = t.y;
213 : //z
214 0 : if (t.z < changes.o.z) changes.o.z = t.z;
215 0 : else if(t.z > changes.s.z) changes.s.z = t.z;
216 0 : return c;
217 : }
218 :
219 0 : uint getface(const cube *c, int d) const
220 : {
221 0 : return 0x0f0f0f0f & ((dc ? c->faces[d] : 0x88888888 - c->faces[d]) >> fs);
222 : }
223 :
224 0 : void pushside(cube &c, int d, int x, int y, int z) const
225 : {
226 0 : ivec a;
227 0 : getcubevector(c, d, x, y, z, a);
228 0 : a[R[d]] = 8 - a[R[d]];
229 0 : setcubevector(c, d, x, y, z, a);
230 0 : }
231 :
232 0 : void addpoint(int x, int y, int z, int v)
233 : {
234 0 : if(!(flags[x][y] & mapped))
235 : {
236 0 : map[x][y] = v + (z*8);
237 : }
238 0 : flags[x][y] |= mapped;
239 0 : }
240 :
241 0 : void select(int x, int y, int z)
242 : {
243 0 : if((nothmap & flags[x][y]) || (painted & flags[x][y]))
244 : {
245 0 : return;
246 : }
247 0 : ivec t(d, x+gx, y+gy, dc ? z : hws-z);
248 0 : t.shl(gridpower);
249 :
250 : // selections may damage; must makeundo before
251 0 : hundo.o = t;
252 0 : hundo.o[D[d]] -= dcr*gridsize*2;
253 0 : makeundo(hundo);
254 :
255 0 : cube **c = cmap[x][y];
256 0 : for(int k = 0; k < 4; ++k)
257 : {
258 0 : c[k] = nullptr;
259 : }
260 0 : c[1] = getcube(t, 0);
261 0 : if(!c[1] || !(c[1]->isempty()))
262 : { // try up
263 0 : c[2] = c[1];
264 0 : c[1] = getcube(t, 1);
265 0 : if(!c[1] || c[1]->isempty())
266 : {
267 0 : c[0] = c[1];
268 0 : c[1] = c[2];
269 0 : c[2] = nullptr;
270 : }
271 : else
272 : {
273 0 : z++;
274 0 : t[d]+=fg;
275 : }
276 : }
277 : else // drop down
278 : {
279 0 : z--;
280 0 : t[d]-= fg;
281 0 : c[0] = c[1];
282 0 : c[1] = getcube(t, 0);
283 : }
284 :
285 0 : if(!c[1] || c[1]->isempty())
286 : {
287 0 : flags[x][y] |= nothmap;
288 0 : return;
289 : }
290 0 : flags[x][y] |= painted;
291 0 : mapz [x][y] = z;
292 0 : if(!c[0])
293 : {
294 0 : c[0] = getcube(t, 1);
295 : }
296 0 : if(!c[2])
297 : {
298 0 : c[2] = getcube(t, -1);
299 : }
300 0 : c[3] = getcube(t, -2);
301 0 : c[2] = !c[2] || c[2]->isempty() ? nullptr : c[2];
302 0 : c[3] = !c[3] || c[3]->isempty() ? nullptr : c[3];
303 :
304 0 : uint face = getface(c[1], d);
305 0 : if(face == 0x08080808 && (!c[0] || !(c[0]->isempty())))
306 : {
307 0 : flags[x][y] |= nothmap;
308 0 : return;
309 : }
310 0 : if(c[1]->faces[R[d]] == facesolid) // was single
311 : {
312 0 : face += 0x08080808;
313 : }
314 : else // was pair
315 : {
316 0 : face += c[2] ? getface(c[2], d) : 0x08080808;
317 : }
318 0 : face += 0x08080808; // c[3]
319 0 : const uchar *f = reinterpret_cast<uchar*>(&face);
320 0 : addpoint(x, y, z, f[0]);
321 0 : addpoint(x+1, y, z, f[1]);
322 0 : addpoint(x, y+1, z, f[2]);
323 0 : addpoint(x+1, y+1, z, f[3]);
324 :
325 0 : if(selecting) // continue to adjacent cubes
326 : {
327 0 : if(x>bmx)
328 : {
329 0 : select(x-1, y, z);
330 : }
331 0 : if(x<bnx)
332 : {
333 0 : select(x+1, y, z);
334 : }
335 0 : if(y>bmy)
336 : {
337 0 : select(x, y-1, z);
338 : }
339 0 : if(y<bny)
340 : {
341 0 : select(x, y+1, z);
342 : }
343 : }
344 : }
345 :
346 0 : void ripple(int x, int y, int z, bool force)
347 : {
348 0 : if(force)
349 : {
350 0 : select(x, y, z);
351 : }
352 0 : if((nothmap & flags[x][y]) || !(painted & flags[x][y]))
353 : {
354 0 : return;
355 : }
356 :
357 0 : bool changed = false;
358 : std::array<int *, 4> o;
359 : int best,
360 : par,
361 0 : q = 0;
362 0 : for(int i = 0; i < 2; ++i)
363 : {
364 0 : for(int j = 0; j < 2; ++j)
365 : {
366 0 : o[i+j*2] = &map[x+i][y+j];
367 : }
368 : }
369 :
370 : #define PULL_HEIGHTMAP(I, LT, GT, M, N, A) \
371 : do \
372 : { \
373 : best = I; \
374 : for(int i = 0; i < 4; ++i) \
375 : { \
376 : if(*o[i] LT best) \
377 : { \
378 : best = *o[q = i] - M; \
379 : } \
380 : } \
381 : par = (best&(~7)) + N; \
382 : /* dual layer for extra smoothness */ \
383 : if(*o[q^3] GT par && !(*o[q^1] LT par || *o[q^2] LT par)) \
384 : { \
385 : if(*o[q^3] GT par A 8 || *o[q^1] != par || *o[q^2] != par) \
386 : { \
387 : *o[q^3] = (*o[q^3] GT par A 8 ? par A 8 : *o[q^3]); \
388 : *o[q^1] = *o[q^2] = par; \
389 : changed = true; \
390 : } \
391 : /* single layer */ \
392 : } \
393 : else { \
394 : for(int j = 0; j < 4; ++j) \
395 : { \
396 : if(*o[j] GT par) \
397 : { \
398 : *o[j] = par; \
399 : changed = true; \
400 : } \
401 : } \
402 : } \
403 : } while(0)
404 :
405 0 : if(biasup)
406 : {
407 0 : PULL_HEIGHTMAP(0, >, <, 1, 0, -);
408 : }
409 : else
410 : {
411 0 : PULL_HEIGHTMAP(rootworld.mapsize()*8, <, >, 0, 8, +);
412 : }
413 :
414 : #undef PULL_HEIGHTMAP
415 :
416 0 : cube * const * const c = cmap[x][y];
417 : int e[2][2],
418 0 : notempty = 0;
419 :
420 0 : for(int k = 0; k < 4; ++k)
421 : {
422 0 : if(c[k])
423 : {
424 0 : for(int i = 0; i < 2; ++i)
425 : {
426 0 : for(int j = 0; j < 2; ++j)
427 : {
428 : {
429 0 : e[i][j] = std::min(8, map[x+i][y+j] - (mapz[x][y]+3-k)*8);
430 0 : notempty |= e[i][j] > 0;
431 : }
432 : }
433 : }
434 0 : if(notempty)
435 : {
436 0 : c[k]->texture[sel.orient] = c[1]->texture[sel.orient];
437 0 : setcubefaces(*c[k], facesolid);
438 0 : for(int i = 0; i < 2; ++i)
439 : {
440 0 : for(int j = 0; j < 2; ++j)
441 : {
442 0 : int f = e[i][j];
443 0 : if(f<0 || (f==0 && e[1-i][j]==0 && e[i][1-j]==0))
444 : {
445 0 : f=0;
446 0 : pushside(*c[k], d, i, j, 0);
447 0 : pushside(*c[k], d, i, j, 1);
448 : }
449 0 : EDGE_SET(CUBE_EDGE(*c[k], d, i, j), dc, dc ? f : 8-f);
450 : }
451 : }
452 : }
453 : else
454 : {
455 0 : setcubefaces(*c[k], faceempty);
456 : }
457 : }
458 : }
459 0 : if(!changed)
460 : {
461 0 : return;
462 : }
463 0 : if(x>mx) ripple(x-1, y, mapz[x][y], true);
464 0 : if(x<nx) ripple(x+1, y, mapz[x][y], true);
465 0 : if(y>my) ripple(x, y-1, mapz[x][y], true);
466 0 : if(y<ny) ripple(x, y+1, mapz[x][y], true);
467 :
468 : //============================================================== DIAGONAL_RIPPLE
469 : #define DIAGONAL_RIPPLE(a,b,exp) \
470 : if(exp) { \
471 : if(flags[x a][ y] & painted) \
472 : { \
473 : ripple(x a, y b, mapz[x a][y], true); \
474 : } \
475 : else if(flags[x][y b] & painted) \
476 : { \
477 : ripple(x a, y b, mapz[x][y b], true); \
478 : } \
479 : }
480 0 : DIAGONAL_RIPPLE(-1, -1, (x>mx && y>my)); // do diagonals because adjacents
481 0 : DIAGONAL_RIPPLE(-1, +1, (x>mx && y<ny)); // won't unless changed
482 0 : DIAGONAL_RIPPLE(+1, +1, (x<nx && y<ny));
483 0 : DIAGONAL_RIPPLE(+1, -1, (x<nx && y>my));
484 : }
485 :
486 : #undef DIAGONAL_RIPPLE
487 : //==============================================================================
488 :
489 0 : void paint()
490 : {
491 0 : for(int x=bmx; x<=bnx+1; x++)
492 : {
493 0 : for(int y=bmy; y<=bny+1; y++)
494 : {
495 0 : map[x][y] -= dr * brush[x][y];
496 : }
497 : }
498 0 : }
499 :
500 0 : void smooth()
501 : {
502 : int sum, div;
503 0 : for(int x=bmx; x<=bnx-2; x++)
504 : {
505 0 : for(int y=bmy; y<=bny-2; y++)
506 : {
507 0 : sum = 0;
508 0 : div = 9;
509 0 : for(int i = 0; i < 3; ++i)
510 : {
511 0 : for(int j = 0; j < 3; ++j)
512 : {
513 0 : if(flags[x+i][y+j] & mapped)
514 : {
515 0 : sum += map[x+i][y+j];
516 : }
517 : else
518 : {
519 0 : div--;
520 : }
521 : }
522 : }
523 0 : if(div)
524 : {
525 0 : map[x+1][y+1] = sum / div;
526 : }
527 : }
528 : }
529 0 : }
530 :
531 0 : void rippleandset()
532 : {
533 0 : for(int x=bmx; x<=bnx; x++)
534 : {
535 0 : for(int y=bmy; y<=bny; y++)
536 : {
537 0 : ripple(x, y, gz, false);
538 : }
539 : }
540 0 : }
541 : } heightmapper;
542 :
543 : // free functions wrappers of member functions to bind commands to
544 : //imply existence of singleton instance of hmap
545 3 : void hmapcancel()
546 : {
547 3 : heightmapper.cancel();
548 3 : }
549 :
550 1 : void hmapselect()
551 : {
552 1 : heightmapper.hmapselect();
553 1 : }
554 :
555 1 : void clearhbrush()
556 : {
557 1 : heightmapper.clearhbrush();
558 1 : }
559 :
560 1 : void hbrushvert(const int *x, const int *y, const int *v)
561 : {
562 1 : heightmapper.hbrushvert(x, y, v);
563 1 : }
564 :
565 : //engine interface functions
566 0 : bool isheightmap(int o, bool empty, const cube &c)
567 : {
568 0 : return heightmapper.isheightmap(o, empty, c);
569 : }
570 :
571 0 : void heightmaprun(int dir, int mode)
572 : {
573 0 : heightmapper.run(dir, mode);
574 0 : }
575 :
576 1 : void initheightmapcmds()
577 : {
578 1 : addcommand("hmapcancel", reinterpret_cast<identfun>(hmapcancel), "", Id_Command);
579 1 : addcommand("hmapselect", reinterpret_cast<identfun>(hmapselect), "", Id_Command);
580 1 : addcommand("clearhbrush", reinterpret_cast<identfun>(clearhbrush), "", Id_Command);
581 1 : addcommand("hbrushvert", reinterpret_cast<identfun>(hbrushvert), "iii", Id_Command);
582 1 : }
|