LCOV - code coverage report
Current view: top level - engine/world - heightmap.cpp (source / functions) Hit Total Coverage
Test: Libprimis Test Coverage Lines: 45 265 17.0 %
Date: 2024-11-22 05:07:59 Functions: 9 22 40.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 :             if(!c.texture)
      33             :             {
      34           0 :                 return;
      35             :             }
      36           1 :             int t = c.texture[orient],
      37           1 :                 i = std::distance(textures.begin(), std::find(textures.begin(), textures.end(), t));
      38           1 :             if(i == std::distance(textures.begin(), textures.end()))
      39             :             {
      40           1 :                 textures.push_back(t);
      41             :             }
      42             :             else
      43             :             {
      44           0 :                 textures.erase(textures.begin() + i);
      45             :             }
      46             :         }
      47             : 
      48           0 :         bool isheightmap(int o, bool empty, const cube &c) const
      49             :         {
      50           0 :             return havesel ||
      51           0 :                 (empty && c.isempty()) ||
      52           0 :                 textures.empty() ||
      53           0 :                 (std::find(textures.begin(), textures.end(), c.texture[o]) != textures.end());
      54             :         }
      55             : 
      56           1 :         void clearhbrush()
      57             :         {
      58          65 :             for(auto &i : brush)
      59             :             {
      60          64 :                 i.fill(0);
      61             :             }
      62           1 :             brushmaxx = brushmaxy = 0;
      63           1 :             brushminx = brushminy = maxbrush;
      64           1 :             paintbrush = false;
      65           1 :         }
      66             : 
      67           1 :         void hbrushvert(const int *x, const int *y, const int * const v)
      68             :         {
      69             :             int x1, y1;
      70           1 :             x1 = *x + maxbrush/2 - brushx + 1; // +1 for automatic padding
      71           1 :             y1 = *y + maxbrush/2 - brushy + 1;
      72           1 :             if(x1<0 || y1<0 || x1>=maxbrush || y1>=maxbrush)
      73             :             {
      74           0 :                 return;
      75             :             }
      76           1 :             brush[x1][y1] = std::clamp(*v, 0, 8);
      77           1 :             paintbrush = paintbrush || (brush[x1][y1] > 0);
      78           1 :             brushmaxx = std::min(maxbrush-1, std::max(brushmaxx, x1+1));
      79           1 :             brushmaxy = std::min(maxbrush-1, std::max(brushmaxy, y1+1));
      80           1 :             brushminx = std::max(0,          std::min(brushminx, x1-1));
      81           1 :             brushminy = std::max(0,          std::min(brushminy, y1-1));
      82             :         }
      83             : 
      84           0 :         void run(int dir, int mode)
      85             :         {
      86           0 :             d  = DIMENSION(sel.orient);
      87           0 :             dc = DIM_COORD(sel.orient);
      88           0 :             dcr= dc ? 1 : -1;
      89           0 :             dr = dir>0 ? 1 : -1;
      90             :          //   biasup = mode == dir<0;
      91           0 :             biasup = dir < 0;
      92           0 :             int cx = (sel.corner&1 ? 0 : -1),
      93           0 :                 cy = (sel.corner&2 ? 0 : -1);
      94           0 :             hws= (rootworld.mapsize()>>gridpower);
      95           0 :             gx = (cur[R[d]] >> gridpower) + cx - maxbrush/2;
      96           0 :             gy = (cur[C[d]] >> gridpower) + cy - maxbrush/2;
      97           0 :             gz = (cur[D[d]] >> gridpower);
      98           0 :             fs = dc ? 4 : 0;
      99           0 :             fg = dc ? gridsize : -gridsize;
     100           0 :             mx = std::max(0, -gx); // ripple range
     101           0 :             my = std::max(0, -gy);
     102           0 :             nx = std::min(maxbrush-1, hws-gx) - 1;
     103           0 :             ny = std::min(maxbrush-1, hws-gy) - 1;
     104           0 :             if(havesel)
     105             :             {   // selection range
     106           0 :                 bmx = mx = std::max(mx, (sel.o[R[d]]>>gridpower)-gx);
     107           0 :                 bmy = my = std::max(my, (sel.o[C[d]]>>gridpower)-gy);
     108           0 :                 bnx = nx = std::min(nx, (sel.s[R[d]]+(sel.o[R[d]]>>gridpower))-gx-1);
     109           0 :                 bny = ny = std::min(ny, (sel.s[C[d]]+(sel.o[C[d]]>>gridpower))-gy-1);
     110             :             }
     111           0 :             bool paintme = paintbrush;
     112           0 :             if(havesel && mode<0) // -ve means smooth selection
     113             :             {
     114           0 :                 paintme = false;
     115             :             }
     116             :             else
     117             :             {   // brush range
     118           0 :                 bmx = std::max(mx, brushminx);
     119           0 :                 bmy = std::max(my, brushminy);
     120           0 :                 bnx = std::min(nx, brushmaxx-1);
     121           0 :                 bny = std::min(ny, brushmaxy-1);
     122             :             }
     123           0 :             nz = rootworld.mapsize()-gridsize;
     124           0 :             mz = 0;
     125           0 :             hundo.s = ivec(d,1,1,5);
     126           0 :             hundo.orient = sel.orient;
     127           0 :             hundo.grid = gridsize;
     128           0 :             forcenextundo();
     129             : 
     130           0 :             changes.grid = gridsize;
     131           0 :             changes.s = changes.o = cur;
     132           0 :             std::memset(map, 0, sizeof map);
     133           0 :             std::memset(flags, 0, sizeof flags);
     134             : 
     135           0 :             selecting = true;
     136           0 :             select(std::clamp(maxbrush/2-cx, bmx, bnx),
     137           0 :                    std::clamp(maxbrush/2-cy, bmy, bny),
     138           0 :                    dc ? gz : hws - gz);
     139           0 :             selecting = false;
     140           0 :             if(paintme)
     141             :             {
     142           0 :                 paint();
     143             :             }
     144             :             else
     145             :             {
     146           0 :                 smooth();
     147             :             }
     148           0 :             rippleandset();                       // pull up points to cubify, and set
     149           0 :             changes.s.sub(changes.o).shr(gridpower).add(1);
     150           0 :             rootworld.changed(changes);
     151           0 :         }
     152             :     private:
     153             :         std::vector<int> textures;
     154             :         //max brush consts: number of cubes on end that can be heightmap brushed at once
     155             :         static constexpr int maxbrush  = 64,
     156             :                              maxbrushc = 63;
     157             : 
     158             :         std::array<std::array<int, maxbrush>, maxbrush> brush;//2d array of heights for heightmap brushs
     159             :         int brushx = variable("hbrushx", 0, maxbrush/2, maxbrush, &brushx, nullptr, 0); //max width for a brush
     160             :         int brushy = variable("hbrushy", 0, maxbrush/2, maxbrush, &brushy, nullptr, 0); //max length for a brush
     161             :         bool paintbrush = 0;
     162             :         int brushmaxx = 0,
     163             :             brushminx = maxbrush,
     164             :             brushmaxy = 0,
     165             :             brushminy = maxbrush;
     166             : 
     167             :         static constexpr int painted = 1,
     168             :                              nothmap = 2,
     169             :                              mapped  = 16;
     170             :         uchar  flags[maxbrush][maxbrush];
     171             :         cube   *cmap[maxbrushc][maxbrushc][4];
     172             :         int  mapz[maxbrushc][maxbrushc],
     173             :              map [maxbrush][maxbrush];
     174             : 
     175             :         selinfo changes;
     176             :         bool selecting; //flag used by select() for continuing to adj cubes
     177             :         int d,   //dimension
     178             :             dc,  //dimension coordinate
     179             :             dr,  //dimension sign
     180             :             dcr, //dimension coordinate sign
     181             :             biasup, //if dir < 0
     182             :             hws, //heightmap [gridpower] world size
     183             :             fg;  //+/- gridpower depending on dc
     184             :         int gx, gy, gz,
     185             :             mx, my, mz,
     186             :             nx, ny, nz,
     187             :             bmx, bmy,
     188             :             bnx, bny;
     189             :         uint fs; //face
     190             :         selinfo hundo;
     191             : 
     192           0 :         cube *getcube(ivec t, int f)
     193             :         {
     194           0 :             t[d] += dcr*f*gridsize;
     195           0 :             if(t[d] > nz || t[d] < mz)
     196             :             {
     197           0 :                 return nullptr;
     198             :             }
     199           0 :             cube *c = &rootworld.lookupcube(t, gridsize);
     200           0 :             if(c->children)
     201             :             {
     202           0 :                 forcemip(*c, false);
     203             :             }
     204           0 :             c->discardchildren(true);
     205           0 :             if(!isheightmap(sel.orient, true, *c))
     206             :             {
     207           0 :                 return nullptr;
     208             :             }
     209             :             //x
     210           0 :             if     (t.x < changes.o.x) changes.o.x = t.x;
     211           0 :             else if(t.x > changes.s.x) changes.s.x = t.x;
     212             :             //y
     213           0 :             if     (t.y < changes.o.y) changes.o.y = t.y;
     214           0 :             else if(t.y > changes.s.y) changes.s.y = t.y;
     215             :             //z
     216           0 :             if     (t.z < changes.o.z) changes.o.z = t.z;
     217           0 :             else if(t.z > changes.s.z) changes.s.z = t.z;
     218           0 :             return c;
     219             :         }
     220             : 
     221           0 :         uint getface(const cube *c, int d) const
     222             :         {
     223           0 :             return  0x0f0f0f0f & ((dc ? c->faces[d] : 0x88888888 - c->faces[d]) >> fs);
     224             :         }
     225             : 
     226           0 :         void pushside(cube &c, int d, int x, int y, int z) const
     227             :         {
     228           0 :             ivec a;
     229           0 :             getcubevector(c, d, x, y, z, a);
     230           0 :             a[R[d]] = 8 - a[R[d]];
     231           0 :             setcubevector(c, d, x, y, z, a);
     232           0 :         }
     233             : 
     234           0 :         void addpoint(int x, int y, int z, int v)
     235             :         {
     236           0 :             if(!(flags[x][y] & mapped))
     237             :             {
     238           0 :                 map[x][y] = v + (z*8);
     239             :             }
     240           0 :             flags[x][y] |= mapped;
     241           0 :         }
     242             : 
     243           0 :         void select(int x, int y, int z)
     244             :         {
     245           0 :             if((nothmap & flags[x][y]) || (painted & flags[x][y]))
     246             :             {
     247           0 :                 return;
     248             :             }
     249           0 :             ivec t(d, x+gx, y+gy, dc ? z : hws-z);
     250           0 :             t.shl(gridpower);
     251             : 
     252             :             // selections may damage; must makeundo before
     253           0 :             hundo.o = t;
     254           0 :             hundo.o[D[d]] -= dcr*gridsize*2;
     255           0 :             makeundo(hundo);
     256             : 
     257           0 :             cube **c = cmap[x][y];
     258           0 :             for(int k = 0; k < 4; ++k)
     259             :             {
     260           0 :                 c[k] = nullptr;
     261             :             }
     262           0 :             c[1] = getcube(t, 0);
     263           0 :             if(!c[1] || !(c[1]->isempty()))
     264             :             {   // try up
     265           0 :                 c[2] = c[1];
     266           0 :                 c[1] = getcube(t, 1);
     267           0 :                 if(!c[1] || c[1]->isempty())
     268             :                 {
     269           0 :                     c[0] = c[1];
     270           0 :                     c[1] = c[2];
     271           0 :                     c[2] = nullptr;
     272             :                 }
     273             :                 else
     274             :                 {
     275           0 :                     z++;
     276           0 :                     t[d]+=fg;
     277             :                 }
     278             :             }
     279             :             else // drop down
     280             :             {
     281           0 :                 z--;
     282           0 :                 t[d]-= fg;
     283           0 :                 c[0] = c[1];
     284           0 :                 c[1] = getcube(t, 0);
     285             :             }
     286             : 
     287           0 :             if(!c[1] || c[1]->isempty())
     288             :             {
     289           0 :                 flags[x][y] |= nothmap;
     290           0 :                 return;
     291             :             }
     292           0 :             flags[x][y] |= painted;
     293           0 :             mapz [x][y]  = z;
     294           0 :             if(!c[0])
     295             :             {
     296           0 :                 c[0] = getcube(t, 1);
     297             :             }
     298           0 :             if(!c[2])
     299             :             {
     300           0 :                 c[2] = getcube(t, -1);
     301             :             }
     302           0 :             c[3] = getcube(t, -2);
     303           0 :             c[2] = !c[2] || c[2]->isempty() ? nullptr : c[2];
     304           0 :             c[3] = !c[3] || c[3]->isempty() ? nullptr : c[3];
     305             : 
     306           0 :             uint face = getface(c[1], d);
     307           0 :             if(face == 0x08080808 && (!c[0] || !(c[0]->isempty())))
     308             :             {
     309           0 :                 flags[x][y] |= nothmap;
     310           0 :                 return;
     311             :             }
     312           0 :             if(c[1]->faces[R[d]] == facesolid)   // was single
     313             :             {
     314           0 :                 face += 0x08080808;
     315             :             }
     316             :             else                                 // was pair
     317             :             {
     318           0 :                 face += c[2] ? getface(c[2], d) : 0x08080808;
     319             :             }
     320           0 :             face += 0x08080808;                  // c[3]
     321           0 :             uchar *f = reinterpret_cast<uchar*>(&face);
     322           0 :             addpoint(x,   y,   z, f[0]);
     323           0 :             addpoint(x+1, y,   z, f[1]);
     324           0 :             addpoint(x,   y+1, z, f[2]);
     325           0 :             addpoint(x+1, y+1, z, f[3]);
     326             : 
     327           0 :             if(selecting) // continue to adjacent cubes
     328             :             {
     329           0 :                 if(x>bmx)
     330             :                 {
     331           0 :                     select(x-1, y, z);
     332             :                 }
     333           0 :                 if(x<bnx)
     334             :                 {
     335           0 :                     select(x+1, y, z);
     336             :                 }
     337           0 :                 if(y>bmy)
     338             :                 {
     339           0 :                     select(x, y-1, z);
     340             :                 }
     341           0 :                 if(y<bny)
     342             :                 {
     343           0 :                     select(x, y+1, z);
     344             :                 }
     345             :             }
     346             :         }
     347             : 
     348           0 :         void ripple(int x, int y, int z, bool force)
     349             :         {
     350           0 :             if(force)
     351             :             {
     352           0 :                 select(x, y, z);
     353             :             }
     354           0 :             if((nothmap & flags[x][y]) || !(painted & flags[x][y]))
     355             :             {
     356           0 :                 return;
     357             :             }
     358             : 
     359           0 :             bool changed = false;
     360             :             int *o[4], best, 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 1.14