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-10-29 05:40:19 Functions: 40.9 % 22 9

            Line data    Source code
       1              : /**
       2              :  * @brief terrain-like cube pushing functionality
       3              :  *
       4              :  * to help the creation of natural terrain like geometry with cubes, heightmapping
       5              :  * allows for convenient pushing and pulling of areas of geometry
       6              :  *
       7              :  * heightmapping is merely a different way of modifying cubes, and under the hood
       8              :  * (and to other clients) behaves like bulk modification of cubes; the underlying
       9              :  * geometry is still just cubes
      10              :  *
      11              :  * for this reason, heightmapping can be done along any of the main axes, though
      12              :  * only along one at a time
      13              :  */
      14              : #include "../libprimis-headers/cube.h"
      15              : #include "../../shared/geomexts.h"
      16              : 
      17              : #include "octaedit.h"
      18              : #include "octaworld.h"
      19              : #include "world.h"
      20              : 
      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              :     private:
     151              :         std::vector<int> textures;
     152              :         //max brush consts: number of cubes on end that can be heightmap brushed at once
     153              :         static constexpr int maxbrush  = 64,
     154              :                              maxbrushc = 63;
     155              : 
     156              :         std::array<std::array<int, maxbrush>, maxbrush> brush;//2d array of heights for heightmap brushs
     157              :         int brushx = variable("hbrushx", 0, maxbrush/2, maxbrush, &brushx, nullptr, 0), //max width for a brush
     158              :             brushy = variable("hbrushy", 0, maxbrush/2, maxbrush, &brushy, nullptr, 0); //max length for a brush
     159              :         bool paintbrush = false;
     160              :         int brushmaxx = 0,
     161              :             brushminx = maxbrush,
     162              :             brushmaxy = 0,
     163              :             brushminy = maxbrush;
     164              : 
     165              :         static constexpr int painted = 1,
     166              :                              nothmap = 2,
     167              :                              mapped  = 16;
     168              :         uchar flags[maxbrush][maxbrush];
     169              :         cube *cmap[maxbrushc][maxbrushc][4];
     170              :         int  mapz[maxbrushc][maxbrushc],
     171              :              map [maxbrush][maxbrush];
     172              : 
     173              :         selinfo changes;
     174              :         bool selecting; //flag used by select() for continuing to adj cubes
     175              :         int d,   //dimension
     176              :             dc,  //dimension coordinate
     177              :             dr,  //dimension sign
     178              :             dcr, //dimension coordinate sign
     179              :             biasup, //if dir < 0
     180              :             hws, //heightmap [gridpower] world size
     181              :             fg;  //+/- gridpower depending on dc
     182              :         int gx, gy, gz,
     183              :             mx, my, mz,
     184              :             nx, ny, nz,
     185              :             bmx, bmy,
     186              :             bnx, bny;
     187              :         uint fs; //face
     188              :         selinfo hundo;
     189              : 
     190            0 :         cube *getcube(ivec t, int f)
     191              :         {
     192            0 :             t[d] += dcr*f*gridsize;
     193            0 :             if(t[d] > nz || t[d] < mz)
     194              :             {
     195            0 :                 return nullptr;
     196              :             }
     197            0 :             cube *c = &rootworld.lookupcube(t, gridsize);
     198            0 :             if(c->children)
     199              :             {
     200            0 :                 forcemip(*c, false);
     201              :             }
     202            0 :             c->discardchildren(true);
     203            0 :             if(!isheightmap(sel.orient, true, *c))
     204              :             {
     205            0 :                 return nullptr;
     206              :             }
     207              :             //x
     208            0 :             if     (t.x < changes.o.x) changes.o.x = t.x;
     209            0 :             else if(t.x > changes.s.x) changes.s.x = t.x;
     210              :             //y
     211            0 :             if     (t.y < changes.o.y) changes.o.y = t.y;
     212            0 :             else if(t.y > changes.s.y) changes.s.y = t.y;
     213              :             //z
     214            0 :             if     (t.z < changes.o.z) changes.o.z = t.z;
     215            0 :             else if(t.z > changes.s.z) changes.s.z = t.z;
     216            0 :             return c;
     217              :         }
     218              : 
     219            0 :         uint getface(const cube *c, int d) const
     220              :         {
     221            0 :             return  0x0f0f0f0f & ((dc ? c->faces[d] : 0x88888888 - c->faces[d]) >> fs);
     222              :         }
     223              : 
     224            0 :         void pushside(cube &c, int d, int x, int y, int z) const
     225              :         {
     226            0 :             ivec a;
     227            0 :             getcubevector(c, d, x, y, z, a);
     228            0 :             a[R[d]] = 8 - a[R[d]];
     229            0 :             setcubevector(c, d, x, y, z, a);
     230            0 :         }
     231              : 
     232            0 :         void addpoint(int x, int y, int z, int v)
     233              :         {
     234            0 :             if(!(flags[x][y] & mapped))
     235              :             {
     236            0 :                 map[x][y] = v + (z*8);
     237              :             }
     238            0 :             flags[x][y] |= mapped;
     239            0 :         }
     240              : 
     241            0 :         void select(int x, int y, int z)
     242              :         {
     243            0 :             if((nothmap & flags[x][y]) || (painted & flags[x][y]))
     244              :             {
     245            0 :                 return;
     246              :             }
     247            0 :             ivec t(d, x+gx, y+gy, dc ? z : hws-z);
     248            0 :             t.shl(gridpower);
     249              : 
     250              :             // selections may damage; must makeundo before
     251            0 :             hundo.o = t;
     252            0 :             hundo.o[D[d]] -= dcr*gridsize*2;
     253            0 :             makeundo(hundo);
     254              : 
     255            0 :             cube **c = cmap[x][y];
     256            0 :             for(int k = 0; k < 4; ++k)
     257              :             {
     258            0 :                 c[k] = nullptr;
     259              :             }
     260            0 :             c[1] = getcube(t, 0);
     261            0 :             if(!c[1] || !(c[1]->isempty()))
     262              :             {   // try up
     263            0 :                 c[2] = c[1];
     264            0 :                 c[1] = getcube(t, 1);
     265            0 :                 if(!c[1] || c[1]->isempty())
     266              :                 {
     267            0 :                     c[0] = c[1];
     268            0 :                     c[1] = c[2];
     269            0 :                     c[2] = nullptr;
     270              :                 }
     271              :                 else
     272              :                 {
     273            0 :                     z++;
     274            0 :                     t[d]+=fg;
     275              :                 }
     276              :             }
     277              :             else // drop down
     278              :             {
     279            0 :                 z--;
     280            0 :                 t[d]-= fg;
     281            0 :                 c[0] = c[1];
     282            0 :                 c[1] = getcube(t, 0);
     283              :             }
     284              : 
     285            0 :             if(!c[1] || c[1]->isempty())
     286              :             {
     287            0 :                 flags[x][y] |= nothmap;
     288            0 :                 return;
     289              :             }
     290            0 :             flags[x][y] |= painted;
     291            0 :             mapz [x][y]  = z;
     292            0 :             if(!c[0])
     293              :             {
     294            0 :                 c[0] = getcube(t, 1);
     295              :             }
     296            0 :             if(!c[2])
     297              :             {
     298            0 :                 c[2] = getcube(t, -1);
     299              :             }
     300            0 :             c[3] = getcube(t, -2);
     301            0 :             c[2] = !c[2] || c[2]->isempty() ? nullptr : c[2];
     302            0 :             c[3] = !c[3] || c[3]->isempty() ? nullptr : c[3];
     303              : 
     304            0 :             uint face = getface(c[1], d);
     305            0 :             if(face == 0x08080808 && (!c[0] || !(c[0]->isempty())))
     306              :             {
     307            0 :                 flags[x][y] |= nothmap;
     308            0 :                 return;
     309              :             }
     310            0 :             if(c[1]->faces[R[d]] == facesolid)   // was single
     311              :             {
     312            0 :                 face += 0x08080808;
     313              :             }
     314              :             else                                 // was pair
     315              :             {
     316            0 :                 face += c[2] ? getface(c[2], d) : 0x08080808;
     317              :             }
     318            0 :             face += 0x08080808;                  // c[3]
     319            0 :             const uchar *f = reinterpret_cast<uchar*>(&face);
     320            0 :             addpoint(x,   y,   z, f[0]);
     321            0 :             addpoint(x+1, y,   z, f[1]);
     322            0 :             addpoint(x,   y+1, z, f[2]);
     323            0 :             addpoint(x+1, y+1, z, f[3]);
     324              : 
     325            0 :             if(selecting) // continue to adjacent cubes
     326              :             {
     327            0 :                 if(x>bmx)
     328              :                 {
     329            0 :                     select(x-1, y, z);
     330              :                 }
     331            0 :                 if(x<bnx)
     332              :                 {
     333            0 :                     select(x+1, y, z);
     334              :                 }
     335            0 :                 if(y>bmy)
     336              :                 {
     337            0 :                     select(x, y-1, z);
     338              :                 }
     339            0 :                 if(y<bny)
     340              :                 {
     341            0 :                     select(x, y+1, z);
     342              :                 }
     343              :             }
     344              :         }
     345              : 
     346            0 :         void ripple(int x, int y, int z, bool force)
     347              :         {
     348            0 :             if(force)
     349              :             {
     350            0 :                 select(x, y, z);
     351              :             }
     352            0 :             if((nothmap & flags[x][y]) || !(painted & flags[x][y]))
     353              :             {
     354            0 :                 return;
     355              :             }
     356              : 
     357            0 :             bool changed = false;
     358              :             std::array<int *, 4> o;
     359              :             int best,
     360              :                 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 : }
        

Generated by: LCOV version 2.0-1