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: 2026-06-16 06:16:16 Functions: 40.9 % 22 9

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

Generated by: LCOV version 2.0-1