LCOV - code coverage report
Current view: top level - engine/world - heightmap.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 17.0 % 264 45
Test Date: 2025-04-10 07:01:40 Functions: 40.9 % 22 9

            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 : }
        

Generated by: LCOV version 2.0-1