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