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