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 : if(!c.texture)
33 : {
34 0 : return;
35 : }
36 1 : int t = c.texture[orient],
37 1 : i = std::distance(textures.begin(), std::find(textures.begin(), textures.end(), t));
38 1 : if(i == std::distance(textures.begin(), textures.end()))
39 : {
40 1 : textures.push_back(t);
41 : }
42 : else
43 : {
44 0 : textures.erase(textures.begin() + i);
45 : }
46 : }
47 :
48 0 : bool isheightmap(int o, bool empty, const cube &c) const
49 : {
50 0 : return havesel ||
51 0 : (empty && c.isempty()) ||
52 0 : textures.empty() ||
53 0 : (std::find(textures.begin(), textures.end(), c.texture[o]) != textures.end());
54 : }
55 :
56 1 : void clearhbrush()
57 : {
58 65 : for(auto &i : brush)
59 : {
60 64 : i.fill(0);
61 : }
62 1 : brushmaxx = brushmaxy = 0;
63 1 : brushminx = brushminy = maxbrush;
64 1 : paintbrush = false;
65 1 : }
66 :
67 1 : void hbrushvert(const int *x, const int *y, const int * const v)
68 : {
69 : int x1, y1;
70 1 : x1 = *x + maxbrush/2 - brushx + 1; // +1 for automatic padding
71 1 : y1 = *y + maxbrush/2 - brushy + 1;
72 1 : if(x1<0 || y1<0 || x1>=maxbrush || y1>=maxbrush)
73 : {
74 0 : return;
75 : }
76 1 : brush[x1][y1] = std::clamp(*v, 0, 8);
77 1 : paintbrush = paintbrush || (brush[x1][y1] > 0);
78 1 : brushmaxx = std::min(maxbrush-1, std::max(brushmaxx, x1+1));
79 1 : brushmaxy = std::min(maxbrush-1, std::max(brushmaxy, y1+1));
80 1 : brushminx = std::max(0, std::min(brushminx, x1-1));
81 1 : brushminy = std::max(0, std::min(brushminy, y1-1));
82 : }
83 :
84 0 : void run(int dir, int mode)
85 : {
86 0 : d = DIMENSION(sel.orient);
87 0 : dc = DIM_COORD(sel.orient);
88 0 : dcr= dc ? 1 : -1;
89 0 : dr = dir>0 ? 1 : -1;
90 : // biasup = mode == dir<0;
91 0 : biasup = dir < 0;
92 0 : int cx = (sel.corner&1 ? 0 : -1),
93 0 : cy = (sel.corner&2 ? 0 : -1);
94 0 : hws= (rootworld.mapsize()>>gridpower);
95 0 : gx = (cur[R[d]] >> gridpower) + cx - maxbrush/2;
96 0 : gy = (cur[C[d]] >> gridpower) + cy - maxbrush/2;
97 0 : gz = (cur[D[d]] >> gridpower);
98 0 : fs = dc ? 4 : 0;
99 0 : fg = dc ? gridsize : -gridsize;
100 0 : mx = std::max(0, -gx); // ripple range
101 0 : my = std::max(0, -gy);
102 0 : nx = std::min(maxbrush-1, hws-gx) - 1;
103 0 : ny = std::min(maxbrush-1, hws-gy) - 1;
104 0 : if(havesel)
105 : { // selection range
106 0 : bmx = mx = std::max(mx, (sel.o[R[d]]>>gridpower)-gx);
107 0 : bmy = my = std::max(my, (sel.o[C[d]]>>gridpower)-gy);
108 0 : bnx = nx = std::min(nx, (sel.s[R[d]]+(sel.o[R[d]]>>gridpower))-gx-1);
109 0 : bny = ny = std::min(ny, (sel.s[C[d]]+(sel.o[C[d]]>>gridpower))-gy-1);
110 : }
111 0 : bool paintme = paintbrush;
112 0 : if(havesel && mode<0) // -ve means smooth selection
113 : {
114 0 : paintme = false;
115 : }
116 : else
117 : { // brush range
118 0 : bmx = std::max(mx, brushminx);
119 0 : bmy = std::max(my, brushminy);
120 0 : bnx = std::min(nx, brushmaxx-1);
121 0 : bny = std::min(ny, brushmaxy-1);
122 : }
123 0 : nz = rootworld.mapsize()-gridsize;
124 0 : mz = 0;
125 0 : hundo.s = ivec(d,1,1,5);
126 0 : hundo.orient = sel.orient;
127 0 : hundo.grid = gridsize;
128 0 : forcenextundo();
129 :
130 0 : changes.grid = gridsize;
131 0 : changes.s = changes.o = cur;
132 0 : std::memset(map, 0, sizeof map);
133 0 : std::memset(flags, 0, sizeof flags);
134 :
135 0 : selecting = true;
136 0 : select(std::clamp(maxbrush/2-cx, bmx, bnx),
137 0 : std::clamp(maxbrush/2-cy, bmy, bny),
138 0 : dc ? gz : hws - gz);
139 0 : selecting = false;
140 0 : if(paintme)
141 : {
142 0 : paint();
143 : }
144 : else
145 : {
146 0 : smooth();
147 : }
148 0 : rippleandset(); // pull up points to cubify, and set
149 0 : changes.s.sub(changes.o).shr(gridpower).add(1);
150 0 : rootworld.changed(changes);
151 0 : }
152 : private:
153 : std::vector<int> textures;
154 : //max brush consts: number of cubes on end that can be heightmap brushed at once
155 : static constexpr int maxbrush = 64,
156 : maxbrushc = 63;
157 :
158 : std::array<std::array<int, maxbrush>, maxbrush> brush;//2d array of heights for heightmap brushs
159 : int brushx = variable("hbrushx", 0, maxbrush/2, maxbrush, &brushx, nullptr, 0); //max width for a brush
160 : int brushy = variable("hbrushy", 0, maxbrush/2, maxbrush, &brushy, nullptr, 0); //max length for a brush
161 : bool paintbrush = 0;
162 : int brushmaxx = 0,
163 : brushminx = maxbrush,
164 : brushmaxy = 0,
165 : brushminy = maxbrush;
166 :
167 : static constexpr int painted = 1,
168 : nothmap = 2,
169 : mapped = 16;
170 : uchar flags[maxbrush][maxbrush];
171 : cube *cmap[maxbrushc][maxbrushc][4];
172 : int mapz[maxbrushc][maxbrushc],
173 : map [maxbrush][maxbrush];
174 :
175 : selinfo changes;
176 : bool selecting; //flag used by select() for continuing to adj cubes
177 : int d, //dimension
178 : dc, //dimension coordinate
179 : dr, //dimension sign
180 : dcr, //dimension coordinate sign
181 : biasup, //if dir < 0
182 : hws, //heightmap [gridpower] world size
183 : fg; //+/- gridpower depending on dc
184 : int gx, gy, gz,
185 : mx, my, mz,
186 : nx, ny, nz,
187 : bmx, bmy,
188 : bnx, bny;
189 : uint fs; //face
190 : selinfo hundo;
191 :
192 0 : cube *getcube(ivec t, int f)
193 : {
194 0 : t[d] += dcr*f*gridsize;
195 0 : if(t[d] > nz || t[d] < mz)
196 : {
197 0 : return nullptr;
198 : }
199 0 : cube *c = &rootworld.lookupcube(t, gridsize);
200 0 : if(c->children)
201 : {
202 0 : forcemip(*c, false);
203 : }
204 0 : c->discardchildren(true);
205 0 : if(!isheightmap(sel.orient, true, *c))
206 : {
207 0 : return nullptr;
208 : }
209 : //x
210 0 : if (t.x < changes.o.x) changes.o.x = t.x;
211 0 : else if(t.x > changes.s.x) changes.s.x = t.x;
212 : //y
213 0 : if (t.y < changes.o.y) changes.o.y = t.y;
214 0 : else if(t.y > changes.s.y) changes.s.y = t.y;
215 : //z
216 0 : if (t.z < changes.o.z) changes.o.z = t.z;
217 0 : else if(t.z > changes.s.z) changes.s.z = t.z;
218 0 : return c;
219 : }
220 :
221 0 : uint getface(const cube *c, int d) const
222 : {
223 0 : return 0x0f0f0f0f & ((dc ? c->faces[d] : 0x88888888 - c->faces[d]) >> fs);
224 : }
225 :
226 0 : void pushside(cube &c, int d, int x, int y, int z) const
227 : {
228 0 : ivec a;
229 0 : getcubevector(c, d, x, y, z, a);
230 0 : a[R[d]] = 8 - a[R[d]];
231 0 : setcubevector(c, d, x, y, z, a);
232 0 : }
233 :
234 0 : void addpoint(int x, int y, int z, int v)
235 : {
236 0 : if(!(flags[x][y] & mapped))
237 : {
238 0 : map[x][y] = v + (z*8);
239 : }
240 0 : flags[x][y] |= mapped;
241 0 : }
242 :
243 0 : void select(int x, int y, int z)
244 : {
245 0 : if((nothmap & flags[x][y]) || (painted & flags[x][y]))
246 : {
247 0 : return;
248 : }
249 0 : ivec t(d, x+gx, y+gy, dc ? z : hws-z);
250 0 : t.shl(gridpower);
251 :
252 : // selections may damage; must makeundo before
253 0 : hundo.o = t;
254 0 : hundo.o[D[d]] -= dcr*gridsize*2;
255 0 : makeundo(hundo);
256 :
257 0 : cube **c = cmap[x][y];
258 0 : for(int k = 0; k < 4; ++k)
259 : {
260 0 : c[k] = nullptr;
261 : }
262 0 : c[1] = getcube(t, 0);
263 0 : if(!c[1] || !(c[1]->isempty()))
264 : { // try up
265 0 : c[2] = c[1];
266 0 : c[1] = getcube(t, 1);
267 0 : if(!c[1] || c[1]->isempty())
268 : {
269 0 : c[0] = c[1];
270 0 : c[1] = c[2];
271 0 : c[2] = nullptr;
272 : }
273 : else
274 : {
275 0 : z++;
276 0 : t[d]+=fg;
277 : }
278 : }
279 : else // drop down
280 : {
281 0 : z--;
282 0 : t[d]-= fg;
283 0 : c[0] = c[1];
284 0 : c[1] = getcube(t, 0);
285 : }
286 :
287 0 : if(!c[1] || c[1]->isempty())
288 : {
289 0 : flags[x][y] |= nothmap;
290 0 : return;
291 : }
292 0 : flags[x][y] |= painted;
293 0 : mapz [x][y] = z;
294 0 : if(!c[0])
295 : {
296 0 : c[0] = getcube(t, 1);
297 : }
298 0 : if(!c[2])
299 : {
300 0 : c[2] = getcube(t, -1);
301 : }
302 0 : c[3] = getcube(t, -2);
303 0 : c[2] = !c[2] || c[2]->isempty() ? nullptr : c[2];
304 0 : c[3] = !c[3] || c[3]->isempty() ? nullptr : c[3];
305 :
306 0 : uint face = getface(c[1], d);
307 0 : if(face == 0x08080808 && (!c[0] || !(c[0]->isempty())))
308 : {
309 0 : flags[x][y] |= nothmap;
310 0 : return;
311 : }
312 0 : if(c[1]->faces[R[d]] == facesolid) // was single
313 : {
314 0 : face += 0x08080808;
315 : }
316 : else // was pair
317 : {
318 0 : face += c[2] ? getface(c[2], d) : 0x08080808;
319 : }
320 0 : face += 0x08080808; // c[3]
321 0 : uchar *f = reinterpret_cast<uchar*>(&face);
322 0 : addpoint(x, y, z, f[0]);
323 0 : addpoint(x+1, y, z, f[1]);
324 0 : addpoint(x, y+1, z, f[2]);
325 0 : addpoint(x+1, y+1, z, f[3]);
326 :
327 0 : if(selecting) // continue to adjacent cubes
328 : {
329 0 : if(x>bmx)
330 : {
331 0 : select(x-1, y, z);
332 : }
333 0 : if(x<bnx)
334 : {
335 0 : select(x+1, y, z);
336 : }
337 0 : if(y>bmy)
338 : {
339 0 : select(x, y-1, z);
340 : }
341 0 : if(y<bny)
342 : {
343 0 : select(x, y+1, z);
344 : }
345 : }
346 : }
347 :
348 0 : void ripple(int x, int y, int z, bool force)
349 : {
350 0 : if(force)
351 : {
352 0 : select(x, y, z);
353 : }
354 0 : if((nothmap & flags[x][y]) || !(painted & flags[x][y]))
355 : {
356 0 : return;
357 : }
358 :
359 0 : bool changed = false;
360 : int *o[4], best, 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 : }
|