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