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

Generated by: LCOV version 2.0-1