LCOV - code coverage report
Current view: top level - engine/render - texture.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 20.1 % 1521 305
Test Date: 2026-06-16 06:16:16 Functions: 37.1 % 124 46

            Line data    Source code
       1              : /**
       2              :  * @file texture.cpp
       3              :  * @brief texture slot management
       4              :  */
       5              : 
       6              : #include "../libprimis-headers/cube.h"
       7              : #include "../../shared/geomexts.h"
       8              : #include "../../shared/glexts.h"
       9              : #include "../../shared/stream.h"
      10              : 
      11              : #include "SDL_image.h"
      12              : 
      13              : #include "imagedata.h"
      14              : #include "normal.h"
      15              : #include "octarender.h"
      16              : #include "rendergl.h"
      17              : #include "renderwindow.h"
      18              : #include "shader.h"
      19              : #include "shaderparam.h"
      20              : #include "texture.h"
      21              : 
      22              : #include "world/light.h"
      23              : #include "world/material.h"
      24              : #include "world/octaedit.h"
      25              : #include "world/octaworld.h"
      26              : #include "world/world.h"
      27              : 
      28              : #include "interface/console.h"
      29              : #include "interface/control.h"
      30              : 
      31              : extern const std::array<TexRotation, 8> texrotations =
      32              : {{
      33              :     { false, false, false }, // 0: default
      34              :     { false,  true,  true }, // 1: 90 degrees
      35              :     {  true,  true, false }, // 2: 180 degrees
      36              :     {  true, false,  true }, // 3: 270 degrees
      37              :     {  true, false, false }, // 4: flip X
      38              :     { false,  true, false }, // 5: flip Y
      39              :     { false, false,  true }, // 6: transpose
      40              :     {  true,  true,  true }, // 7: flipped transpose
      41              : }};
      42              : 
      43              : //copies every other pixel into a destination buffer
      44              : //sw,sh are source width/height
      45              : template<int BPP>
      46            3 : static void halvetexture(const uchar * RESTRICT src, uint sw, uint sh, uint stride, uchar * RESTRICT dst)
      47              : {
      48           10 :     for(const uchar *yend = &src[sh*stride]; src < yend;)
      49              :     {
      50           28 :         for(const uchar *xend = &src[sw*BPP], *xsrc = src; xsrc < xend; xsrc += 2*BPP, dst += BPP)
      51              :         {
      52           42 :             for(int i = 0; i < BPP; ++i)
      53              :             {
      54           21 :                 dst[i] = (static_cast<uint>(xsrc[i]) + static_cast<uint>(xsrc[i+BPP]) + static_cast<uint>(xsrc[stride+i]) + static_cast<uint>(xsrc[stride+i+BPP]))>>2;
      55              :             }
      56              :         }
      57            7 :         src += 2*stride;
      58              :     }
      59            3 : }
      60              : 
      61              : template<int BPP>
      62            0 : static void shifttexture(const uchar * RESTRICT src, uint sw, uint sh, uint stride, uchar * RESTRICT dst, uint dw, uint dh)
      63              : {
      64            0 :     uint wfrac = sw/dw,
      65            0 :          hfrac = sh/dh,
      66            0 :          wshift = 0,
      67            0 :          hshift = 0;
      68            0 :     while(dw<<wshift < sw)
      69              :     {
      70            0 :         wshift++;
      71              :     }
      72            0 :     while(dh<<hshift < sh)
      73              :     {
      74            0 :         hshift++;
      75              :     }
      76            0 :     uint tshift = wshift + hshift;
      77            0 :     for(const uchar *yend = &src[sh*stride]; src < yend;)
      78              :     {
      79            0 :         for(const uchar *xend = &src[sw*BPP], *xsrc = src; xsrc < xend; xsrc += wfrac*BPP, dst += BPP)
      80              :         {
      81            0 :             std::array<uint, BPP> t = {0};
      82            0 :             for(const uchar *ycur = xsrc, *xend = &ycur[wfrac*BPP], *yend = &src[hfrac*stride];
      83            0 :                 ycur < yend;
      84            0 :                 ycur += stride, xend += stride)
      85              :             {
      86              :                 //going to (xend - 1) seems to be necessary to avoid buffer overrun
      87            0 :                 for(const uchar *xcur = ycur; xcur < xend -1; xcur += BPP)
      88              :                 {
      89            0 :                     for(int i = 0; i < BPP; ++i)
      90              :                     {
      91            0 :                         t[i] += xcur[i];
      92              :                     }
      93              :                 }
      94              :             }
      95            0 :             for(int i = 0; i < BPP; ++i)
      96              :             {
      97            0 :                 dst[i] = t[i] >> tshift;
      98              :             }
      99              :         }
     100            0 :         src += hfrac*stride;
     101              :     }
     102            0 : }
     103              : 
     104              : template<size_t BPP>
     105            0 : static void scaletexture(const uchar * RESTRICT src, uint sw, uint sh, uint stride, uchar * RESTRICT dst, uint dw, uint dh)
     106              : {
     107            0 :     uint wfrac = (sw<<12)/dw,
     108            0 :          hfrac = (sh<<12)/dh,
     109            0 :          darea = dw*dh,
     110            0 :          sarea = sw*sh;
     111              :     int over, under;
     112              :     //the for loops here are merely to increment over & under vars which are used later
     113            0 :     for(over = 0; (darea>>over) > sarea; over++)
     114              :     {
     115              :         //(empty body)
     116              :     }
     117            0 :     for(under = 0; (darea<<under) < sarea; under++)
     118              :     {
     119              :         //(empty body)
     120              :     }
     121            0 :     uint cscale = std::clamp(under, over - 12, 12),
     122            0 :          ascale = std::clamp(12 + under - over, 0, 24),
     123            0 :          dscale = ascale + 12 - cscale,
     124            0 :          area = (static_cast<ullong>(darea)<<ascale)/sarea;
     125            0 :     dw *= wfrac;
     126            0 :     dh *= hfrac;
     127            0 :     for(uint y = 0; y < dh; y += hfrac)
     128              :     {
     129            0 :         const uint yn = y + hfrac - 1,
     130            0 :                    yi = y>>12, h = (yn>>12) - yi,
     131            0 :                    ylow = ((yn|(-static_cast<int>(h)>>24))&0xFFFU) + 1 - (y&0xFFFU),
     132            0 :                    yhigh = (yn&0xFFFU) + 1;
     133            0 :         const uchar *ysrc = &src[yi*stride];
     134            0 :         for(uint x = 0; x < dw; x += wfrac, dst += BPP)
     135              :         {
     136            0 :             const uint xn = x + wfrac - 1,
     137            0 :                        xi = x>>12,
     138            0 :                        w = (xn>>12) - xi,
     139            0 :                        xlow = ((w+0xFFFU)&0x1000U) - (x&0xFFFU),
     140            0 :                        xhigh = (xn&0xFFFU) + 1;
     141            0 :             const uchar *xsrc = &ysrc[xi*BPP],
     142            0 :                         *xend = &xsrc[w*BPP];
     143            0 :             std::array<uint, BPP> t = {0};
     144            0 :             for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP)
     145              :             {
     146            0 :                 for(size_t i = 0; i < BPP; ++i)
     147              :                 {
     148            0 :                     t[i] += xcur[i];
     149              :                 }
     150              :             }
     151            0 :             for(size_t i = 0; i < BPP; ++i)
     152              :             {
     153            0 :                 t[i] = (ylow*(t[i] + ((xsrc[i]*xlow + xend[i]*xhigh)>>12)))>>cscale;
     154              :             }
     155            0 :             if(h)
     156              :             {
     157            0 :                 xsrc += stride;
     158            0 :                 xend += stride;
     159            0 :                 for(uint hcur = h; --hcur; xsrc += stride, xend += stride)
     160              :                 {
     161            0 :                     std::array<uint, BPP> c = {0};
     162            0 :                     for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP)
     163            0 :                         for(size_t i = 0; i < BPP; ++i)
     164              :                         {
     165            0 :                             c[i] += xcur[i];
     166              :                         }
     167            0 :                     for(size_t i = 0; i < BPP; ++i)
     168              :                     {
     169            0 :                         t[i] += ((c[i]<<12) + xsrc[i]*xlow + xend[i]*xhigh)>>cscale;
     170              :                     }
     171              :                 }
     172            0 :                 std::array<uint, BPP> c = {0};
     173            0 :                 for(const uchar *xcur = &xsrc[BPP]; xcur < xend; xcur += BPP)
     174            0 :                     for(size_t i = 0; i < BPP; ++i)
     175              :                     {
     176            0 :                         c[i] += xcur[i];
     177              :                     }
     178            0 :                 for(size_t i = 0; i < BPP; ++i)
     179              :                 {
     180            0 :                     t[i] += (yhigh*(c[i] + ((xsrc[i]*xlow + xend[i]*xhigh)>>12)))>>cscale;
     181              :                 }
     182              :             }
     183            0 :             for(size_t i = 0; i < BPP; ++i)
     184              :             {
     185            0 :                 dst[i] = (t[i] * area)>>dscale;
     186              :             }
     187              :         }
     188              :     }
     189            0 : }
     190              : 
     191            3 : void scaletexture(const uchar * RESTRICT src, uint sw, uint sh, uint bpp, uint pitch, uchar * RESTRICT dst, uint dw, uint dh)
     192              : {
     193            3 :     if(sw == dw*2 && sh == dh*2)
     194              :     {
     195            3 :         switch(bpp)
     196              :         {
     197            3 :             case 1: return halvetexture<1>(src, sw, sh, pitch, dst);
     198            0 :             case 2: return halvetexture<2>(src, sw, sh, pitch, dst);
     199            0 :             case 3: return halvetexture<3>(src, sw, sh, pitch, dst);
     200            0 :             case 4: return halvetexture<4>(src, sw, sh, pitch, dst);
     201              :         }
     202              :     }
     203            0 :     else if(sw < dw || sh < dh || sw&(sw-1) || sh&(sh-1) || dw&(dw-1) || dh&(dh-1))
     204              :     {
     205            0 :         switch(bpp)
     206              :         {
     207            0 :             case 1: return scaletexture<1>(src, sw, sh, pitch, dst, dw, dh);
     208            0 :             case 2: return scaletexture<2>(src, sw, sh, pitch, dst, dw, dh);
     209            0 :             case 3: return scaletexture<3>(src, sw, sh, pitch, dst, dw, dh);
     210            0 :             case 4: return scaletexture<4>(src, sw, sh, pitch, dst, dw, dh);
     211              :         }
     212              :     }
     213              :     else
     214              :     {
     215            0 :         switch(bpp)
     216              :         {
     217            0 :             case 1: return shifttexture<1>(src, sw, sh, pitch, dst, dw, dh);
     218            0 :             case 2: return shifttexture<2>(src, sw, sh, pitch, dst, dw, dh);
     219            0 :             case 3: return shifttexture<3>(src, sw, sh, pitch, dst, dw, dh);
     220            0 :             case 4: return shifttexture<4>(src, sw, sh, pitch, dst, dw, dh);
     221              :         }
     222              :     }
     223              : }
     224              : 
     225              : template<int BPP>
     226            0 : static void reorienttexture(const uchar * RESTRICT src, int sw, int sh, int stride, uchar * RESTRICT dst, bool flipx, bool flipy, bool swapxy)
     227              : {
     228            0 :     int stridex = BPP,
     229            0 :         stridey = BPP;
     230            0 :     if(swapxy)
     231              :     {
     232            0 :         stridex *= sh;
     233              :     }
     234              :     else
     235              :     {
     236            0 :         stridey *= sw;
     237              :     }
     238            0 :     if(flipx)
     239              :     {
     240            0 :         dst += (sw-1)*stridex;
     241            0 :         stridex = -stridex;
     242              :     }
     243            0 :     if(flipy)
     244              :     {
     245            0 :         dst += (sh-1)*stridey;
     246            0 :         stridey = -stridey;
     247              :     }
     248            0 :     const uchar *srcrow = src;
     249            0 :     for(int i = 0; i < sh; ++i)
     250              :     {
     251              :         uchar* curdst;
     252              :         const uchar* source;
     253              :         const uchar* end;
     254            0 :         for(curdst = dst, source = srcrow, end = &srcrow[sw*BPP]; source < end;)
     255              :         {
     256            0 :             for(int k = 0; k < BPP; ++k)
     257              :             {
     258            0 :                 curdst[k] = *src++;
     259              :             }
     260            0 :             curdst += stridex;
     261              :         }
     262            0 :         srcrow += stride;
     263            0 :         dst += stridey;
     264              :     }
     265            0 : }
     266              : 
     267            0 : void reorienttexture(const uchar * RESTRICT src, int sw, int sh, int bpp, int stride, uchar * RESTRICT dst, bool flipx, bool flipy, bool swapxy)
     268              : {
     269            0 :     switch(bpp)
     270              :     {
     271            0 :         case 1: return reorienttexture<1>(src, sw, sh, stride, dst, flipx, flipy, swapxy);
     272            0 :         case 2: return reorienttexture<2>(src, sw, sh, stride, dst, flipx, flipy, swapxy);
     273            0 :         case 3: return reorienttexture<3>(src, sw, sh, stride, dst, flipx, flipy, swapxy);
     274            0 :         case 4: return reorienttexture<4>(src, sw, sh, stride, dst, flipx, flipy, swapxy);
     275              :     }
     276              : }
     277              : /*  var             min  default max  */
     278              : VAR(hwtexsize,      1,   0,      0);
     279              : VAR(hwcubetexsize,  1,   0,      0);
     280              : VAR(hwmaxaniso,     1,   0,      0);
     281              : VAR(hwtexunits,     1,   0,      0);
     282              : VAR(hwvtexunits,    1,   0,      0);
     283            0 : VARFP(maxtexsize,   0,   0,      1<<12, initwarning("texture quality",   Init_Load));
     284            0 : VARFP(reducefilter, 0,   1,      1,     initwarning("texture quality",   Init_Load));
     285            0 : VARF(trilinear,     0,   1,      1,     initwarning("texture filtering", Init_Load));
     286            0 : VARF(bilinear,      0,   1,      1,     initwarning("texture filtering", Init_Load));
     287            0 : VARFP(aniso,        0,   0,      16,    initwarning("texture filtering", Init_Load));
     288              : 
     289              : /**
     290              :  * @brief Returns number of bytes per pixel for the format passed.
     291              :  *
     292              :  * @param format a GL format enum value
     293              :  *
     294              :  * @return number of bytes, or 4 if format not listed
     295              :  */
     296            1 : static int formatsize(GLenum format)
     297              : {
     298            1 :     switch(format)
     299              :     {
     300            1 :         case GL_RED:
     301              :         {
     302            1 :             return 1;
     303              :         }
     304            0 :         case GL_RG:
     305              :         {
     306            0 :             return 2;
     307              :         }
     308            0 :         case GL_RGB:
     309              :         {
     310            0 :             return 3;
     311              :         }
     312            0 :         case GL_RGBA:
     313              :         {
     314            0 :             return 4;
     315              :         }
     316            0 :         default:
     317              :         {
     318            0 :             return 4;
     319              :         }
     320              :     }
     321              : }
     322              : 
     323            1 : static void resizetexture(int w, int h, bool mipmap, GLenum target, int compress, int &tw, int &th)
     324              : {
     325            1 :     int hwlimit = target==GL_TEXTURE_CUBE_MAP ? hwcubetexsize : hwtexsize,
     326            1 :         sizelimit = mipmap && maxtexsize ? std::min(maxtexsize, hwlimit) : hwlimit;
     327            1 :     if(compress > 0)
     328              :     {
     329            0 :         w = std::max(w/compress, 1);
     330            0 :         h = std::max(h/compress, 1);
     331              :     }
     332            1 :     w = std::min(w, sizelimit);
     333            1 :     h = std::min(h, sizelimit);
     334            1 :     tw = w;
     335            1 :     th = h;
     336            1 : }
     337              : 
     338            5 : static int texalign(int w, int bpp)
     339              : {
     340            5 :     int stride = w*bpp;
     341            5 :     if(stride&1)
     342              :     {
     343            1 :         return 1;
     344              :     }
     345            4 :     if(stride&2)
     346              :     {
     347            1 :         return 2;
     348              :     }
     349            3 :     return 4;
     350              : }
     351              : 
     352            1 : static void uploadtexture(GLenum target, GLenum internal, int tw, int th, GLenum format, GLenum type, const void *pixels, int pw, int ph, int pitch, bool mipmap)
     353              : {
     354            1 :     int bpp = formatsize(format),
     355            1 :         row = 0,
     356            1 :         rowalign = 0;
     357            1 :     if(!pitch)
     358              :     {
     359            0 :         pitch = pw*bpp;
     360              :     }
     361            1 :     uchar *buf = nullptr;
     362            1 :     if(pw!=tw || ph!=th)
     363              :     {
     364            0 :         buf = new uchar[tw*th*bpp];
     365            0 :         scaletexture(static_cast<uchar *>(const_cast<void *>(pixels)), pw, ph, bpp, pitch, buf, tw, th);
     366              :     }
     367            1 :     else if(tw*bpp != pitch)
     368              :     {
     369            0 :         row = pitch/bpp;
     370            0 :         rowalign = texalign(pitch, 1);
     371            0 :         while(rowalign > 0 && ((row*bpp + rowalign - 1)/rowalign)*rowalign != pitch)
     372              :         {
     373            0 :             rowalign >>= 1;
     374              :         }
     375            0 :         if(!rowalign)
     376              :         {
     377            0 :             row = 0;
     378            0 :             buf = new uchar[tw*th*bpp];
     379            0 :             for(int i = 0; i < th; ++i)
     380              :             {
     381            0 :                 std::memcpy(&buf[i*tw*bpp], &(const_cast<uchar *>(reinterpret_cast<const uchar *>(pixels)))[i*pitch], tw*bpp);
     382              :             }
     383              :         }
     384              :     }
     385            1 :     for(int level = 0, align = 0;; level++)
     386              :     {
     387            4 :         uchar *src = buf ? buf : const_cast<uchar *>(reinterpret_cast<const uchar *>(pixels));
     388            4 :         if(buf)
     389              :         {
     390            3 :             pitch = tw*bpp;
     391              :         }
     392            4 :         int srcalign = row > 0 ? rowalign : texalign(pitch, 1);
     393            4 :         if(align != srcalign)
     394              :         {
     395            3 :             glPixelStorei(GL_UNPACK_ALIGNMENT, align = srcalign);
     396              :         }
     397            4 :         if(row > 0)
     398              :         {
     399            0 :             glPixelStorei(GL_UNPACK_ROW_LENGTH, row);
     400              :         }
     401            4 :         if(target==GL_TEXTURE_1D)
     402              :         {
     403            0 :             glTexImage1D(target, level, internal, tw, 0, format, type, src);
     404              :         }
     405              :         else
     406              :         {
     407            4 :             glTexImage2D(target, level, internal, tw, th, 0, format, type, src);
     408              :         }
     409            4 :         if(row > 0)
     410              :         {
     411            0 :             glPixelStorei(GL_UNPACK_ROW_LENGTH, row = 0);
     412              :         }
     413            4 :         if(!mipmap || std::max(tw, th) <= 1)
     414              :         {
     415            1 :             break;
     416              :         }
     417            3 :         int srcw = tw,
     418            3 :             srch = th;
     419            3 :         if(tw > 1)
     420              :         {
     421            3 :             tw /= 2;
     422              :         }
     423            3 :         if(th > 1)
     424              :         {
     425            3 :             th /= 2;
     426              :         }
     427            3 :         if(src)
     428              :         {
     429            3 :             if(!buf)
     430              :             {
     431            1 :                 buf = new uchar[tw*th*bpp];
     432              :             }
     433            3 :             scaletexture(src, srcw, srch, bpp, pitch, buf, tw, th);
     434              :         }
     435            3 :     }
     436            1 :     if(buf)
     437              :     {
     438            1 :         delete[] buf;
     439              :     }
     440            1 : }
     441              : 
     442            0 : static void uploadcompressedtexture(GLenum target, GLenum subtarget, GLenum format, int w, int h, const uchar *data, int align, int blocksize, int levels, bool mipmap)
     443              : {
     444            0 :     int hwlimit = target==GL_TEXTURE_CUBE_MAP ? hwcubetexsize : hwtexsize,
     445            0 :         sizelimit = levels > 1 && maxtexsize ? std::min(maxtexsize, hwlimit) : hwlimit;
     446            0 :     int level = 0;
     447            0 :     for(int i = 0; i < levels; ++i)
     448              :     {
     449            0 :         int size = ((w + align-1)/align) * ((h + align-1)/align) * blocksize;
     450            0 :         if(w <= sizelimit && h <= sizelimit)
     451              :         {
     452            0 :             if(target==GL_TEXTURE_1D)
     453              :             {
     454            0 :                 glCompressedTexImage1D(subtarget, level, format, w, 0, size, data);
     455              :             }
     456              :             else
     457              :             {
     458            0 :                 glCompressedTexImage2D(subtarget, level, format, w, h, 0, size, data);
     459              :             }
     460            0 :             level++;
     461            0 :             if(!mipmap)
     462              :             {
     463            0 :                 break;
     464              :             }
     465              :         }
     466            0 :         if(std::max(w, h) <= 1)
     467              :         {
     468            0 :             break;
     469              :         }
     470            0 :         if(w > 1)
     471              :         {
     472            0 :             w /= 2;
     473              :         }
     474            0 :         if(h > 1)
     475              :         {
     476            0 :             h /= 2;
     477              :         }
     478            0 :         data += size;
     479              :     }
     480            0 : }
     481              : 
     482              : /**
     483              :  * @brief Returns that the value passed is of a cube map, or returns identity.
     484              :  *
     485              :  * Used to determine whether five other cube faces accompany this texture or whether
     486              :  * it is a texture that exists on its own.
     487              :  *
     488              :  * @param subtarget the value to check if is part of a cube map
     489              :  *
     490              :  * @return GL_TEXTURE_CUBE_MAP if this texture is a face, or subtarget
     491              :  */
     492            1 : static GLenum textarget(GLenum subtarget)
     493              : {
     494            1 :     switch(subtarget)
     495              :     {
     496            0 :         case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
     497              :         case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
     498              :         case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
     499              :         case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
     500              :         case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
     501              :         case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
     502              :         {
     503            0 :             return GL_TEXTURE_CUBE_MAP;
     504              :         }
     505              :     }
     506            1 :     return subtarget;
     507              : }
     508              : 
     509            1 : const GLint *swizzlemask(GLenum format)
     510              : {
     511              :     static constexpr std::array<GLint, 4> luminance = { GL_RED, GL_RED, GL_RED, GL_ONE },
     512              :                                           luminancealpha = { GL_RED, GL_RED, GL_RED, GL_GREEN };
     513            1 :     switch(format)
     514              :     {
     515            1 :         case GL_RED:
     516              :         {
     517            1 :             return luminance.data();
     518              :         }
     519            0 :         case GL_RG:
     520              :         {
     521            0 :             return luminancealpha.data();
     522              :         }
     523              :     }
     524            0 :     return nullptr;
     525              : }
     526              : 
     527            1 : static void setuptexparameters(int tnum, int clamp, int filter, GLenum format, GLenum target, bool swizzle)
     528              : {
     529            1 :     glBindTexture(target, tnum);
     530            1 :     glTexParameteri(target, GL_TEXTURE_WRAP_S, clamp&1 ? GL_CLAMP_TO_EDGE : (clamp&0x100 ? GL_MIRRORED_REPEAT : GL_REPEAT));
     531            1 :     if(target!=GL_TEXTURE_1D)
     532              :     {
     533            1 :         glTexParameteri(target, GL_TEXTURE_WRAP_T, clamp&2 ? GL_CLAMP_TO_EDGE : (clamp&0x200 ? GL_MIRRORED_REPEAT : GL_REPEAT));
     534              :     }
     535            1 :     if(target==GL_TEXTURE_3D)
     536              :     {
     537            0 :         glTexParameteri(target, GL_TEXTURE_WRAP_R, clamp&4 ? GL_CLAMP_TO_EDGE : (clamp&0x400 ? GL_MIRRORED_REPEAT : GL_REPEAT));
     538              :     }
     539            1 :     if(target==GL_TEXTURE_2D && std::min(aniso, hwmaxaniso) > 0 && filter > 1)
     540              :     {
     541            0 :         glTexParameteri(target, GL_TEXTURE_MAX_ANISOTROPY_EXT, std::min(aniso, hwmaxaniso));
     542              :     }
     543            1 :     glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter && bilinear ? GL_LINEAR : GL_NEAREST);
     544            2 :     glTexParameteri(target, GL_TEXTURE_MIN_FILTER,
     545              :         filter > 1 ?
     546            2 :             (trilinear ?
     547            1 :                 (bilinear ? GL_LINEAR_MIPMAP_LINEAR : GL_NEAREST_MIPMAP_LINEAR) :
     548            0 :                 (bilinear ? GL_LINEAR_MIPMAP_NEAREST : GL_NEAREST_MIPMAP_NEAREST)) :
     549            0 :             (filter && bilinear ? GL_LINEAR : GL_NEAREST));
     550            1 :     if(swizzle)
     551              :     {
     552            1 :         const GLint *mask = swizzlemask(format);
     553            1 :         if(mask)
     554              :         {
     555            1 :             glTexParameteriv(target, GL_TEXTURE_SWIZZLE_RGBA, mask);
     556              :         }
     557              :     }
     558            1 : }
     559              : 
     560            1 : static GLenum textype(GLenum &component, GLenum &format)
     561              : {
     562            1 :     GLenum type = GL_UNSIGNED_BYTE;
     563            1 :     switch(component)
     564              :     {
     565            0 :         case GL_R16F:
     566              :         case GL_R32F:
     567              :         {
     568            0 :             if(!format)
     569              :             {
     570            0 :                 format = GL_RED;
     571              :             }
     572            0 :             type = GL_FLOAT;
     573            0 :             break;
     574              :         }
     575            0 :         case GL_RG16F:
     576              :         case GL_RG32F:
     577              :         {
     578            0 :             if(!format)
     579              :             {
     580            0 :                 format = GL_RG;
     581              :             }
     582            0 :             type = GL_FLOAT;
     583            0 :             break;
     584              :         }
     585            0 :         case GL_RGB16F:
     586              :         case GL_RGB32F:
     587              :         case GL_R11F_G11F_B10F:
     588              :         {
     589            0 :             if(!format)
     590              :             {
     591            0 :                 format = GL_RGB;
     592              :             }
     593            0 :             type = GL_FLOAT;
     594            0 :             break;
     595              :         }
     596            0 :         case GL_RGBA16F:
     597              :         case GL_RGBA32F:
     598              :         {
     599            0 :             if(!format)
     600              :             {
     601            0 :                 format = GL_RGBA;
     602              :             }
     603            0 :             type = GL_FLOAT;
     604            0 :             break;
     605              :         }
     606            0 :         case GL_DEPTH_COMPONENT16:
     607              :         case GL_DEPTH_COMPONENT24:
     608              :         case GL_DEPTH_COMPONENT32:
     609              :         {
     610            0 :             if(!format)
     611              :             {
     612            0 :                 format = GL_DEPTH_COMPONENT;
     613              :             }
     614            0 :             break;
     615              :         }
     616            0 :         case GL_DEPTH_STENCIL:
     617              :         case GL_DEPTH24_STENCIL8:
     618              :         {
     619            0 :             if(!format)
     620              :             {
     621            0 :                 format = GL_DEPTH_STENCIL;
     622              :             }
     623            0 :             type = GL_UNSIGNED_INT_24_8;
     624            0 :             break;
     625              :         }
     626            0 :         case GL_R8:
     627              :         case GL_R16:
     628              :         {
     629            0 :             if(!format)
     630              :             {
     631            0 :                 format = GL_RED;
     632              :             }
     633            0 :             break;
     634              :         }
     635            0 :         case GL_RG8:
     636              :         case GL_RG16:
     637              :         {
     638            0 :             if(!format)
     639              :             {
     640            0 :                 format = GL_RG;
     641              :             }
     642            0 :             break;
     643              :         }
     644            0 :         case GL_RGB5:
     645              :         case GL_RGB8:
     646              :         case GL_RGB16:
     647              :         case GL_RGB10:
     648              :         {
     649            0 :             if(!format)
     650              :             {
     651            0 :                 format = GL_RGB;
     652              :             }
     653            0 :             break;
     654              :         }
     655            0 :         case GL_RGB5_A1:
     656              :         case GL_RGBA8:
     657              :         case GL_RGBA16:
     658              :         case GL_RGB10_A2:
     659              :         {
     660            0 :             if(!format)
     661              :             {
     662            0 :                 format = GL_RGBA;
     663              :             }
     664            0 :             break;
     665              :         }
     666            0 :         case GL_RGB8UI:
     667              :         case GL_RGB16UI:
     668              :         case GL_RGB32UI:
     669              :         case GL_RGB8I:
     670              :         case GL_RGB16I:
     671              :         case GL_RGB32I:
     672              :         {
     673            0 :             if(!format)
     674              :             {
     675            0 :                 format = GL_RGB_INTEGER;
     676              :             }
     677            0 :             break;
     678              :         }
     679            0 :         case GL_RGBA8UI:
     680              :         case GL_RGBA16UI:
     681              :         case GL_RGBA32UI:
     682              :         case GL_RGBA8I:
     683              :         case GL_RGBA16I:
     684              :         case GL_RGBA32I:
     685              :         {
     686            0 :             if(!format)
     687              :             {
     688            0 :                 format = GL_RGBA_INTEGER;
     689              :             }
     690            0 :             break;
     691              :         }
     692            0 :         case GL_R8UI:
     693              :         case GL_R16UI:
     694              :         case GL_R32UI:
     695              :         case GL_R8I:
     696              :         case GL_R16I:
     697              :         case GL_R32I:
     698              :         {
     699            0 :             if(!format)
     700              :             {
     701            0 :                 format = GL_RED_INTEGER;
     702              :             }
     703            0 :             break;
     704              :         }
     705            0 :         case GL_RG8UI:
     706              :         case GL_RG16UI:
     707              :         case GL_RG32UI:
     708              :         case GL_RG8I:
     709              :         case GL_RG16I:
     710              :         case GL_RG32I:
     711              :         {
     712            0 :             if(!format)
     713              :             {
     714            0 :                 format = GL_RG_INTEGER;
     715              :             }
     716            0 :             break;
     717              :         }
     718              :     }
     719            1 :     if(!format)
     720              :     {
     721            0 :         format = component;
     722              :     }
     723            1 :     return type;
     724              : }
     725              : 
     726            1 : void createtexture(int tnum, int w, int h, const void *pixels, int clamp, int filter, GLenum component, GLenum subtarget, int pw, int ph, int pitch, bool resize, GLenum format, bool swizzle)
     727              : {
     728            1 :     GLenum target = textarget(subtarget),
     729            1 :            type = textype(component, format);
     730            1 :     if(tnum)
     731              :     {
     732            1 :         setuptexparameters(tnum, clamp, filter, format, target, swizzle);
     733              :     }
     734            1 :     if(!pw)
     735              :     {
     736            0 :         pw = w;
     737              :     }
     738            1 :     if(!ph)
     739              :     {
     740            0 :         ph = h;
     741              :     }
     742            1 :     int tw = w,
     743            1 :         th = h;
     744            1 :     bool mipmap = filter > 1;
     745            1 :     if(resize && pixels)
     746              :     {
     747            0 :         resizetexture(w, h, mipmap, false, target, tw, th);
     748              :     }
     749            1 :     uploadtexture(subtarget, component, tw, th, format, type, pixels, pw, ph, pitch, mipmap);
     750            1 : }
     751              : 
     752            0 : static void createcompressedtexture(int tnum, int w, int h, const uchar *data, int align, int blocksize, int levels, int clamp, int filter, GLenum format, GLenum subtarget, bool swizzle = false)
     753              : {
     754            0 :     GLenum target = textarget(subtarget);
     755            0 :     if(tnum)
     756              :     {
     757            0 :         setuptexparameters(tnum, clamp, filter, format, target, swizzle);
     758              :     }
     759            0 :     uploadcompressedtexture(target, subtarget, format, w, h, data, align, blocksize, levels, filter > 1);
     760            0 : }
     761              : 
     762            0 : void create3dtexture(int tnum, int w, int h, int d, const void *pixels, int clamp, int filter, GLenum component, GLenum target, bool swizzle)
     763              : {
     764            0 :     GLenum format = GL_FALSE, type = textype(component, format);
     765            0 :     if(tnum)
     766              :     {
     767            0 :         setuptexparameters(tnum, clamp, filter, format, target, swizzle);
     768              :     }
     769            0 :     glTexImage3D(target, 0, component, w, h, d, 0, format, type, pixels);
     770            0 : }
     771              : 
     772              : std::unordered_map<std::string, Texture> textures;
     773              : 
     774              : Texture *notexture = nullptr; // used as default, ensured to be loaded
     775              : 
     776            2 : GLenum texformat(int bpp)
     777              : {
     778            2 :     switch(bpp)
     779              :     {
     780            2 :         case 1:
     781              :         {
     782            2 :             return GL_RED;
     783              :         }
     784            0 :         case 2:
     785              :         {
     786            0 :             return GL_RG;
     787              :         }
     788            0 :         case 3:
     789              :         {
     790            0 :             return GL_RGB;
     791              :         }
     792            0 :         case 4:
     793              :         {
     794            0 :             return GL_RGBA;
     795              :         }
     796            0 :         default:
     797              :         {
     798            0 :             return 0;
     799              :         }
     800              :     }
     801              : }
     802              : 
     803            1 : static bool alphaformat(GLenum format)
     804              : {
     805            1 :     switch(format)
     806              :     {
     807            0 :         case GL_RG:
     808              :         case GL_RGBA:
     809              :         {
     810            0 :             return true;
     811              :         }
     812            1 :         default:
     813              :         {
     814            1 :             return false;
     815              :         }
     816              :     }
     817              : }
     818              : 
     819            0 : bool floatformat(GLenum format)
     820              : {
     821            0 :     switch(format)
     822              :     {
     823            0 :         case GL_R16F:
     824              :         case GL_R32F:
     825              :         case GL_RG16F:
     826              :         case GL_RG32F:
     827              :         case GL_RGB16F:
     828              :         case GL_RGB32F:
     829              :         case GL_R11F_G11F_B10F:
     830              :         case GL_RGBA16F:
     831              :         case GL_RGBA32F:
     832              :         {
     833            0 :             return true;
     834              :         }
     835            0 :         default:
     836              :         {
     837            0 :             return false;
     838              :         }
     839              :     }
     840              : }
     841              : 
     842            1 : static Texture *newtexture(Texture *t, const char *rname, ImageData &s, int clamp = 0, bool mipit = true, bool canreduce = false, bool transient = false, int compress = 0)
     843              : {
     844            1 :     if(!t)
     845              :     {
     846            1 :         char *key = newstring(rname);
     847            2 :         std::unordered_map<std::string, Texture>::iterator itr = textures.insert_or_assign(key, Texture()).first;
     848            1 :         t = &(*itr).second;
     849            1 :         t->name = key;
     850              :     }
     851              : 
     852            1 :     t->clamp = clamp;
     853            1 :     t->mipmap = mipit;
     854            1 :     t->type = Texture::IMAGE;
     855            1 :     if(transient)
     856              :     {
     857            0 :         t->type |= Texture::TRANSIENT;
     858              :     }
     859            1 :     if(clamp&0x300)
     860              :     {
     861            0 :         t->type |= Texture::MIRROR;
     862              :     }
     863            1 :     if(!s.data)
     864              :     {
     865            0 :         t->type |= Texture::STUB;
     866            0 :         t->w = t->h = t->xs = t->ys = t->bpp = 0;
     867            0 :         return t;
     868              :     }
     869              : 
     870            1 :     bool swizzle = !(clamp&0x10000);
     871              :     GLenum format;
     872            1 :     format = texformat(s.depth());
     873            1 :     t->bpp = s.depth();
     874            1 :     if(alphaformat(format))
     875              :     {
     876            0 :         t->type |= Texture::ALPHA;
     877              :     }
     878            1 :     t->w = t->xs = s.width();
     879            1 :     t->h = t->ys = s.height();
     880            1 :     int filter = !canreduce || reducefilter ? (mipit ? 2 : 1) : 0;
     881            1 :     glGenTextures(1, &t->id);
     882            1 :     if(s.compressed)
     883              :     {
     884            0 :         static uchar *data = s.data;
     885            0 :         int levels = s.levels,
     886            0 :             level = 0,
     887            0 :             sizelimit = mipit && maxtexsize ? std::min(maxtexsize, hwtexsize) : hwtexsize;
     888            0 :         while(t->w > sizelimit || t->h > sizelimit)
     889              :         {
     890            0 :             data += s.calclevelsize(level++);
     891            0 :             levels--;
     892            0 :             if(t->w > 1)
     893              :             {
     894            0 :                 t->w /= 2;
     895              :             }
     896            0 :             if(t->h > 1)
     897              :             {
     898            0 :                 t->h /= 2;
     899              :             }
     900              :         }
     901            0 :         createcompressedtexture(t->id, t->w, t->h, data, s.align, s.depth(), levels, clamp, filter, s.compressed, GL_TEXTURE_2D, swizzle);
     902              :     }
     903              :     else
     904              :     {
     905            1 :         resizetexture(t->w, t->h, mipit, GL_TEXTURE_2D, compress, t->w, t->h);
     906            1 :         createtexture(t->id, t->w, t->h, s.data, clamp, filter, format, GL_TEXTURE_2D, t->xs, t->ys, s.pitch, false, format, swizzle);
     907              :     }
     908            1 :     return t;
     909              : }
     910              : 
     911            0 : static SDL_Surface *creatergbsurface(SDL_Surface *os)
     912              : {
     913            0 :     SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, os->w, os->h, 24, 0x0000ff, 0x00ff00, 0xff0000, 0);
     914            0 :     if(ns)
     915              :     {
     916            0 :         SDL_BlitSurface(os, nullptr, ns, nullptr);
     917              :     }
     918            0 :     SDL_FreeSurface(os);
     919            0 :     return ns;
     920              : }
     921              : 
     922            0 : static SDL_Surface *creatergbasurface(SDL_Surface *os)
     923              : {
     924            0 :     SDL_Surface *ns = SDL_CreateRGBSurface(SDL_SWSURFACE, os->w, os->h, 32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
     925            0 :     if(ns)
     926              :     {
     927            0 :         SDL_SetSurfaceBlendMode(os, SDL_BLENDMODE_NONE);
     928            0 :         SDL_BlitSurface(os, nullptr, ns, nullptr);
     929              :     }
     930            0 :     SDL_FreeSurface(os);
     931            0 :     return ns;
     932              : }
     933              : 
     934            1 : static bool checkgrayscale(SDL_Surface *s)
     935              : {
     936              :     // gray scale images have 256 levels, no colorkey, and the palette is a ramp
     937            1 :     if(s->format->palette)
     938              :     {
     939            1 :         if(s->format->palette->ncolors != 256 || SDL_GetColorKey(s, nullptr) >= 0)
     940              :         {
     941            0 :             return false;
     942              :         }
     943            1 :         const SDL_Color *colors = s->format->palette->colors;
     944          257 :         for(int i = 0; i < 256; ++i)
     945              :         {
     946          256 :             if(colors[i].r != i || colors[i].g != i || colors[i].b != i)
     947              :             {
     948            0 :                 return false;
     949              :             }
     950              :         }
     951              :     }
     952            1 :     return true;
     953              : }
     954              : 
     955            9 : static SDL_Surface *fixsurfaceformat(SDL_Surface *s)
     956              : {
     957            9 :     if(!s)
     958              :     {
     959            8 :         return nullptr;
     960              :     }
     961            1 :     if(!s->pixels || std::min(s->w, s->h) <= 0 || s->format->BytesPerPixel <= 0)
     962              :     {
     963            0 :         SDL_FreeSurface(s);
     964            0 :         return nullptr;
     965              :     }
     966              :     static const std::array<uint, 4> rgbmasks = {{ 0x0000ff, 0x00ff00, 0xff0000, 0 }},
     967              :                                      rgbamasks = {{ 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000 }};
     968            1 :     switch(s->format->BytesPerPixel)
     969              :     {
     970            1 :         case 1:
     971            1 :             if(!checkgrayscale(s))
     972              :             {
     973            0 :                 return SDL_GetColorKey(s, nullptr) >= 0 ? creatergbasurface(s) : creatergbsurface(s);
     974              :             }
     975            1 :             break;
     976            0 :         case 3:
     977            0 :             if(s->format->Rmask != rgbmasks[0] || s->format->Gmask != rgbmasks[1] || s->format->Bmask != rgbmasks[2])
     978              :             {
     979            0 :                 return creatergbsurface(s);
     980              :             }
     981            0 :             break;
     982            0 :         case 4:
     983            0 :             if(s->format->Rmask != rgbamasks[0] || s->format->Gmask != rgbamasks[1] || s->format->Bmask != rgbamasks[2] || s->format->Amask != rgbamasks[3])
     984              :             {
     985            0 :                 return s->format->Amask ? creatergbasurface(s) : creatergbsurface(s);
     986              :             }
     987            0 :             break;
     988              :     }
     989            1 :     return s;
     990              : }
     991              : 
     992            9 : SDL_Surface *loadsurface(const char *name)
     993              : {
     994            9 :     SDL_Surface *s = nullptr;
     995            9 :     stream *z = openzipfile(name, "rb");
     996            9 :     if(z)
     997              :     {
     998            0 :         SDL_RWops *rw = z->rwops();
     999            0 :         if(rw)
    1000              :         {
    1001            0 :             const char *ext = std::strrchr(name, '.');
    1002            0 :             if(ext)
    1003              :             {
    1004            0 :                 ++ext;
    1005              :             }
    1006            0 :             s = IMG_LoadTyped_RW(rw, 0, ext);
    1007            0 :             SDL_FreeRW(rw);
    1008              :         }
    1009            0 :         delete z;
    1010              :     }
    1011            9 :     if(!s)
    1012              :     {
    1013            9 :         s = IMG_Load(findfile(name, "rb"));
    1014              :     }
    1015            9 :     return fixsurfaceformat(s);
    1016              : }
    1017              : 
    1018            2 : const uchar * Texture::loadalphamask()
    1019              : {
    1020            2 :     if(alphamask)
    1021              :     {
    1022            0 :         return alphamask;
    1023              :     }
    1024            2 :     if(!(type&Texture::ALPHA))
    1025              :     {
    1026            2 :         return nullptr;
    1027              :     }
    1028            0 :     ImageData s;
    1029            0 :     if(!s.texturedata(name, false) || !s.data || s.compressed)
    1030              :     {
    1031            0 :         return nullptr;
    1032              :     }
    1033            0 :     alphamask = new uchar[s.height() * ((s.width()+7)/8)];
    1034            0 :     uchar *srcrow = s.data,
    1035            0 :           *dst = alphamask-1;
    1036            0 :     for(int y = 0; y < s.height(); ++y)
    1037              :     {
    1038            0 :         uchar *src = srcrow+s.depth()-1;
    1039            0 :         for(int x = 0; x < s.width(); ++x)
    1040              :         {
    1041            0 :             int offset = x%8;
    1042            0 :             if(!offset)
    1043              :             {
    1044            0 :                 *++dst = 0;
    1045              :             }
    1046            0 :             if(*src)
    1047              :             {
    1048            0 :                 *dst |= 1<<offset;
    1049              :             }
    1050            0 :             src += s.depth();
    1051              :         }
    1052            0 :         srcrow += s.pitch;
    1053              :     }
    1054            0 :     return alphamask;
    1055            0 : }
    1056              : 
    1057            0 : float Texture::ratio() const
    1058              : {
    1059            0 :     return (w / static_cast<float>(h));
    1060              : }
    1061              : 
    1062           16 : Texture *textureload(const char *name, int clamp, bool mipit, bool msg)
    1063              : {
    1064           16 :     std::string tname(name);
    1065           16 :     std::unordered_map<std::string, Texture>::iterator itr = textures.find(path(tname));
    1066           16 :     if(itr != textures.end())
    1067              :     {
    1068            7 :         return &(*itr).second;
    1069              :     }
    1070            9 :     int compress = 0;
    1071            9 :     ImageData s;
    1072            9 :     if(s.texturedata(tname.c_str(), msg, &compress, &clamp))
    1073              :     {
    1074            1 :         return newtexture(nullptr, tname.c_str(), s, clamp, mipit, false, false, compress);
    1075              :     }
    1076            8 :     return notexture;
    1077           16 : }
    1078              : 
    1079            0 : bool settexture(const char *name, int clamp)
    1080              : {
    1081            0 :     Texture *t = textureload(name, clamp, true, false);
    1082            0 :     glBindTexture(GL_TEXTURE_2D, t->id);
    1083            0 :     return t != notexture;
    1084              : }
    1085              : 
    1086              : std::vector<VSlot *> vslots;
    1087              : std::vector<Slot *> slots;
    1088              : static MatSlot materialslots[(MatFlag_Volume|MatFlag_Index)+1];
    1089              : Slot dummyslot;
    1090              : VSlot dummyvslot(&dummyslot);
    1091              : static std::vector<DecalSlot *> decalslots;
    1092              : DecalSlot dummydecalslot;
    1093              : Slot *defslot = nullptr;
    1094              : 
    1095            2 : const char *Slot::name() const
    1096              : {
    1097            2 :     return tempformatstring("slot %d", index);
    1098              : }
    1099              : 
    1100           32 : MatSlot::MatSlot() : Slot(static_cast<int>(this - materialslots)), VSlot(this) {}
    1101              : 
    1102            0 : int MatSlot::type() const
    1103              : {
    1104            0 :     return SlotType_Material;
    1105              : }
    1106              : 
    1107            0 : const char *MatSlot::name() const
    1108              : {
    1109            0 :     return tempformatstring("material slot %s", findmaterialname(Slot::index));
    1110              : }
    1111              : 
    1112            0 : VSlot &MatSlot::emptyvslot()
    1113              : {
    1114            0 :     return *this;
    1115              : }
    1116              : 
    1117            0 : int MatSlot::cancombine(int) const
    1118              : {
    1119            0 :     return -1;
    1120              : }
    1121              : 
    1122            0 : void MatSlot::reset()
    1123              : {
    1124            0 :     Slot::reset();
    1125            0 :     VSlot::reset();
    1126            0 : }
    1127              : 
    1128            0 : void MatSlot::cleanup()
    1129              : {
    1130            0 :     Slot::cleanup();
    1131            0 :     VSlot::cleanup();
    1132            0 : }
    1133              : 
    1134            2 : const char *DecalSlot::name() const
    1135              : {
    1136            2 :     return tempformatstring("decal slot %d", Slot::index);
    1137              : }
    1138              : 
    1139            1 : void texturereset(const int *n)
    1140              : {
    1141            1 :     if(!(identflags&Idf_Overridden) && !allowediting)
    1142              :     {
    1143            1 :         return;
    1144              :     }
    1145            0 :     defslot = nullptr;
    1146            0 :     resetslotshader();
    1147            0 :     int limit = std::clamp(*n, 0, static_cast<int>(slots.size()));
    1148            0 :     for(size_t i = limit; i < slots.size(); i++)
    1149              :     {
    1150            0 :         Slot *s = slots[i];
    1151            0 :         for(VSlot *vs = s->variants; vs; vs = vs->next)
    1152              :         {
    1153            0 :             vs->slot = &dummyslot;
    1154              :         }
    1155            0 :         delete s;
    1156              :     }
    1157            0 :     slots.resize(limit);
    1158            0 :     while(vslots.size())
    1159              :     {
    1160            0 :         VSlot *vs = vslots.back();
    1161            0 :         if(vs->slot != &dummyslot || vs->changed)
    1162              :         {
    1163              :             break;
    1164              :         }
    1165            0 :         delete vslots.back();
    1166            0 :         vslots.pop_back();
    1167              :     }
    1168              : }
    1169              : 
    1170            1 : void materialreset()
    1171              : {
    1172            1 :     if(!(identflags&Idf_Overridden) && !allowediting)
    1173              :     {
    1174            1 :         return;
    1175              :     }
    1176            0 :     defslot = nullptr;
    1177            0 :     for(int i = 0; i < (MatFlag_Volume|MatFlag_Index)+1; ++i)
    1178              :     {
    1179            0 :         materialslots[i].reset();
    1180              :     }
    1181              : }
    1182              : 
    1183            1 : void decalreset(const int *n)
    1184              : {
    1185            1 :     if(!(identflags&Idf_Overridden) && !allowediting)
    1186              :     {
    1187            1 :         return;
    1188              :     }
    1189            0 :     defslot = nullptr;
    1190            0 :     resetslotshader();
    1191            0 :     for(size_t i = *n; i < decalslots.size(); ++i)
    1192              :     {
    1193            0 :         delete decalslots.at(i);
    1194              :     }
    1195            0 :     decalslots.resize(*n);
    1196              : }
    1197              : 
    1198              : static int compactedvslots = 0,
    1199              :            compactvslotsprogress = 0,
    1200              :            clonedvslots = 0;
    1201              : static bool markingvslots = false;
    1202              : 
    1203            0 : void clearslots()
    1204              : {
    1205            0 :     defslot = nullptr;
    1206            0 :     resetslotshader();
    1207            0 :     for(Slot * i : slots)
    1208              :     {
    1209            0 :         delete i;
    1210              :     }
    1211            0 :     slots.clear();
    1212            0 :     for(VSlot * i : vslots)
    1213              :     {
    1214            0 :         delete i;
    1215              :     }
    1216            0 :     vslots.clear();
    1217            0 :     for(int i = 0; i < (MatFlag_Volume|MatFlag_Index)+1; ++i)
    1218              :     {
    1219            0 :         materialslots[i].reset();
    1220              :     }
    1221            0 :     decalslots.clear();
    1222            0 :     clonedvslots = 0;
    1223            0 : }
    1224              : 
    1225            0 : static void assignvslot(VSlot &vs)
    1226              : {
    1227            0 :     vs.index = compactedvslots++;
    1228            0 : }
    1229              : 
    1230            0 : void compactvslot(int &index)
    1231              : {
    1232            0 :     if(static_cast<long>(vslots.size()) > index)
    1233              :     {
    1234            0 :         VSlot &vs = *vslots[index];
    1235            0 :         if(vs.index < 0)
    1236              :         {
    1237            0 :             assignvslot(vs);
    1238              :         }
    1239            0 :         if(!markingvslots)
    1240              :         {
    1241            0 :             index = vs.index;
    1242              :         }
    1243              :     }
    1244            0 : }
    1245              : 
    1246            0 : void compactvslot(VSlot &vs)
    1247              : {
    1248            0 :     if(vs.index < 0)
    1249              :     {
    1250            0 :         assignvslot(vs);
    1251              :     }
    1252            0 : }
    1253              : 
    1254              : //n will be capped at 8
    1255            0 : void compactvslots(cube * const c, int n)
    1256              : {
    1257            0 :     if((compactvslotsprogress++&0xFFF)==0)
    1258              :     {
    1259            0 :         renderprogress(std::min(static_cast<float>(compactvslotsprogress)/allocnodes, 1.0f), markingvslots ? "marking slots..." : "compacting slots...");
    1260              :     }
    1261            0 :     for(int i = 0; i < std::min(n, 8); ++i)
    1262              :     {
    1263            0 :         if(c[i].children)
    1264              :         {
    1265            0 :             compactvslots(c[i].children->data());
    1266              :         }
    1267              :         else
    1268              :         {
    1269            0 :             for(int j = 0; j < 6; ++j)
    1270              :             {
    1271            0 :                 if(vslots.size() > c[i].texture[j])
    1272              :                 {
    1273            0 :                     VSlot &vs = *vslots[c[i].texture[j]];
    1274            0 :                     if(vs.index < 0)
    1275              :                     {
    1276            0 :                         assignvslot(vs);
    1277              :                     }
    1278            0 :                     if(!markingvslots)
    1279              :                     {
    1280            0 :                         c[i].texture[j] = vs.index;
    1281              :                     }
    1282              :                 }
    1283              :             }
    1284              :         }
    1285              :     }
    1286            0 : }
    1287              : 
    1288            1 : int cubeworld::compactvslots(bool cull)
    1289              : {
    1290            1 :     if(!worldroot)
    1291              :     {
    1292            1 :         conoutf(Console_Error, "no cube to compact");
    1293            1 :         return 0;
    1294              :     }
    1295            0 :     defslot = nullptr;
    1296            0 :     clonedvslots = 0;
    1297            0 :     markingvslots = cull;
    1298            0 :     compactedvslots = 0;
    1299            0 :     compactvslotsprogress = 0;
    1300            0 :     for(size_t i = 0; i < vslots.size(); i++)
    1301              :     {
    1302            0 :         vslots[i]->index = -1;
    1303              :     }
    1304            0 :     if(cull)
    1305              :     {
    1306            0 :         uint numdefaults = std::min(static_cast<uint>(Default_NumDefaults), static_cast<uint>(slots.size()));
    1307            0 :         for(uint i = 0; i < numdefaults; ++i)
    1308              :         {
    1309            0 :             slots[i]->variants->index = compactedvslots++;
    1310              :         }
    1311              :     }
    1312              :     else
    1313              :     {
    1314            0 :         for(const Slot *i : slots)
    1315              :         {
    1316            0 :             i->variants->index = compactedvslots++;
    1317              :         }
    1318            0 :         for(const VSlot *i : vslots)
    1319              :         {
    1320            0 :             if(!i->changed && i->index < 0)
    1321              :             {
    1322            0 :                 markingvslots = true;
    1323            0 :                 break;
    1324              :             }
    1325              :         }
    1326              :     }
    1327            0 :     ::compactvslots(worldroot->data());
    1328            0 :     int total = compactedvslots;
    1329            0 :     compacteditvslots();
    1330            0 :     for(VSlot *i : vslots)
    1331              :     {
    1332            0 :         if(i->changed)
    1333              :         {
    1334            0 :             continue;
    1335              :         }
    1336            0 :         while(i->next)
    1337              :         {
    1338            0 :             if(i->next->index < 0)
    1339              :             {
    1340            0 :                 i->next = i->next->next;
    1341              :             }
    1342              :             else
    1343              :             {
    1344            0 :                 i = i->next;
    1345              :             }
    1346              :         }
    1347              :     }
    1348            0 :     if(markingvslots)
    1349              :     {
    1350            0 :         markingvslots = false;
    1351            0 :         compactedvslots = 0;
    1352            0 :         compactvslotsprogress = 0;
    1353            0 :         int lastdiscard = 0;
    1354            0 :         for(size_t i = 0; i < vslots.size(); i++)
    1355              :         {
    1356            0 :             VSlot &vs = *vslots[i];
    1357            0 :             if(vs.changed || (vs.index < 0 && !vs.next))
    1358              :             {
    1359            0 :                 vs.index = -1;
    1360              :             }
    1361              :             else
    1362              :             {
    1363            0 :                 if(!cull)
    1364              :                 {
    1365            0 :                     while(lastdiscard < static_cast<int>(i))
    1366              :                     {
    1367            0 :                         VSlot &ds = *vslots[lastdiscard++];
    1368            0 :                         if(!ds.changed && ds.index < 0)
    1369              :                         {
    1370            0 :                             ds.index = compactedvslots++;
    1371              :                         }
    1372              :                     }
    1373              :                 }
    1374            0 :                 vs.index = compactedvslots++;
    1375              :             }
    1376              :         }
    1377            0 :         ::compactvslots(worldroot->data());
    1378            0 :         total = compactedvslots;
    1379            0 :         compacteditvslots();
    1380              :     }
    1381            0 :     compactmruvslots();
    1382            0 :     if(cull)
    1383              :     {
    1384            0 :         for(int i = static_cast<int>(slots.size()); --i >=0;) //note reverse iteration
    1385              :         {
    1386            0 :             if(slots[i]->variants->index < 0)
    1387              :             {
    1388            0 :                 delete slots.at(i);
    1389            0 :                 slots.erase(slots.begin() + i);
    1390              :             }
    1391              :         }
    1392            0 :         for(size_t i = 0; i < slots.size(); i++)
    1393              :         {
    1394            0 :             slots[i]->index = i;
    1395              :         }
    1396              :     }
    1397            0 :     for(size_t i = 0; i < vslots.size(); i++)
    1398              :     {
    1399            0 :         while(vslots[i]->index >= 0 && vslots[i]->index != static_cast<int>(i))
    1400              :         {
    1401            0 :             std::swap(vslots[i], vslots[vslots[i]->index]);
    1402              :         }
    1403              :     }
    1404            0 :     for(size_t i = compactedvslots; i < vslots.size(); i++)
    1405              :     {
    1406            0 :         delete vslots[i];
    1407              :     }
    1408            0 :     vslots.resize(compactedvslots);
    1409            0 :     return total;
    1410              : }
    1411              : 
    1412            0 : static void clampvslotoffset(VSlot &dst, Slot *slot = nullptr)
    1413              : {
    1414            0 :     if(!slot)
    1415              :     {
    1416            0 :         slot = dst.slot;
    1417              :     }
    1418            0 :     if(slot && slot->sts.size())
    1419              :     {
    1420            0 :         if(!slot->loaded)
    1421              :         {
    1422            0 :             slot->load();
    1423              :         }
    1424            0 :         const Texture *t = slot->sts[0].t;
    1425            0 :         int xs = t->xs,
    1426            0 :             ys = t->ys;
    1427            0 :         if(t->type & Texture::MIRROR)
    1428              :         {
    1429            0 :             xs *= 2;
    1430            0 :             ys *= 2;
    1431              :         }
    1432            0 :         if(texrotations[dst.rotation].swapxy)
    1433              :         {
    1434            0 :             std::swap(xs, ys);
    1435              :         }
    1436            0 :         dst.offset.x() %= xs;
    1437            0 :         if(dst.offset.x() < 0)
    1438              :         {
    1439            0 :             dst.offset.x() += xs;
    1440              :         }
    1441            0 :         dst.offset.y() %= ys;
    1442            0 :         if(dst.offset.y() < 0)
    1443              :         {
    1444            0 :             dst.offset.y() += ys;
    1445              :         }
    1446              :     }
    1447              :     else
    1448              :     {
    1449            0 :         dst.offset.max(0);
    1450              :     }
    1451            0 : }
    1452              : 
    1453            0 : static void propagatevslot(VSlot &dst, const VSlot &src, int diff, bool edit = false)
    1454              : {
    1455            0 :     if(diff & (1 << VSlot_ShParam))
    1456              :     {
    1457            0 :         for(size_t i = 0; i < src.params.size(); i++)
    1458              :         {
    1459            0 :             dst.params.push_back(src.params[i]);
    1460              :         }
    1461              :     }
    1462            0 :     if(diff & (1 << VSlot_Scale))
    1463              :     {
    1464            0 :         dst.scale = src.scale;
    1465              :     }
    1466            0 :     if(diff & (1 << VSlot_Rotation))
    1467              :     {
    1468            0 :         dst.rotation = src.rotation;
    1469            0 :         if(edit && !dst.offset.iszero())
    1470              :         {
    1471            0 :             clampvslotoffset(dst);
    1472              :         }
    1473              :     }
    1474            0 :     if(diff & (1 << VSlot_Angle))
    1475              :     {
    1476            0 :         dst.angle = src.angle;
    1477              :     }
    1478            0 :     if(diff & (1 << VSlot_Offset))
    1479              :     {
    1480            0 :         dst.offset = src.offset;
    1481            0 :         if(edit)
    1482              :         {
    1483            0 :             clampvslotoffset(dst);
    1484              :         }
    1485              :     }
    1486            0 :     if(diff & (1 << VSlot_Scroll))
    1487              :     {
    1488            0 :         dst.scroll = src.scroll;
    1489              :     }
    1490            0 :     if(diff & (1 << VSlot_Alpha))
    1491              :     {
    1492            0 :         dst.alphafront = src.alphafront;
    1493            0 :         dst.alphaback = src.alphaback;
    1494              :     }
    1495            0 :     if(diff & (1 << VSlot_Color))
    1496              :     {
    1497            0 :         dst.colorscale = src.colorscale;
    1498              :     }
    1499            0 :     if(diff & (1 << VSlot_Refract))
    1500              :     {
    1501            0 :         dst.refractscale = src.refractscale;
    1502            0 :         dst.refractcolor = src.refractcolor;
    1503              :     }
    1504            0 : }
    1505              : 
    1506            0 : static void propagatevslot(const VSlot *root, int changed)
    1507              : {
    1508            0 :     for(VSlot *vs = root->next; vs; vs = vs->next)
    1509              :     {
    1510            0 :         int diff = changed & ~vs->changed;
    1511            0 :         if(diff)
    1512              :         {
    1513            0 :             propagatevslot(*vs, *root, diff);
    1514              :         }
    1515              :     }
    1516            0 : }
    1517              : 
    1518            0 : static void mergevslot(VSlot &dst, const VSlot &src, int diff, Slot *slot = nullptr)
    1519              : {
    1520            0 :     if(diff & (1 << VSlot_ShParam))
    1521              :     {
    1522            0 :         for(size_t i = 0; i < src.params.size(); i++)
    1523              :         {
    1524            0 :             const SlotShaderParam &sp = src.params[i];
    1525            0 :             for(size_t j = 0; j < dst.params.size(); j++)
    1526              :             {
    1527            0 :                 SlotShaderParam &dp = dst.params[j];
    1528            0 :                 if(sp.name == dp.name)
    1529              :                 {
    1530            0 :                     std::memcpy(dp.val, sp.val, sizeof(dp.val));
    1531            0 :                     goto nextparam; //bail out of loop
    1532              :                 }
    1533              :             }
    1534            0 :             dst.params.push_back(sp);
    1535            0 :         nextparam:;
    1536              :         }
    1537              :     }
    1538            0 :     if(diff & (1 << VSlot_Scale))
    1539              :     {
    1540            0 :         dst.scale = std::clamp(dst.scale*src.scale, 1/8.0f, 8.0f);
    1541              :     }
    1542            0 :     if(diff & (1 << VSlot_Rotation))
    1543              :     {
    1544            0 :         dst.rotation = std::clamp(dst.rotation + src.rotation, 0, 7);
    1545            0 :         if(!dst.offset.iszero())
    1546              :         {
    1547            0 :             clampvslotoffset(dst, slot);
    1548              :         }
    1549              :     }
    1550            0 :     if(diff & (1 << VSlot_Angle))
    1551              :     {
    1552            0 :         dst.angle.add(src.angle);
    1553              :     }
    1554            0 :     if(diff & (1 << VSlot_Offset))
    1555              :     {
    1556            0 :         dst.offset.add(src.offset);
    1557            0 :         clampvslotoffset(dst, slot);
    1558              :     }
    1559            0 :     if(diff & (1 << VSlot_Scroll))
    1560              :     {
    1561            0 :         dst.scroll.add(src.scroll);
    1562              :     }
    1563            0 :     if(diff & (1 << VSlot_Alpha))
    1564              :     {
    1565            0 :         dst.alphafront = src.alphafront;
    1566            0 :         dst.alphaback = src.alphaback;
    1567              :     }
    1568            0 :     if(diff & (1 << VSlot_Color))
    1569              :     {
    1570            0 :         dst.colorscale.mul(src.colorscale);
    1571              :     }
    1572            0 :     if(diff & (1 << VSlot_Refract))
    1573              :     {
    1574            0 :         dst.refractscale *= src.refractscale;
    1575            0 :         dst.refractcolor.mul(src.refractcolor);
    1576              :     }
    1577            0 : }
    1578              : 
    1579            0 : void mergevslot(VSlot &dst, const VSlot &src, const VSlot &delta)
    1580              : {
    1581            0 :     dst.changed = src.changed | delta.changed;
    1582            0 :     propagatevslot(dst, src, (1 << VSlot_Num) - 1);
    1583            0 :     mergevslot(dst, delta, delta.changed, src.slot);
    1584            0 : }
    1585              : 
    1586            0 : static VSlot *reassignvslot(Slot &owner, VSlot *vs)
    1587              : {
    1588            0 :     vs->reset();
    1589            0 :     owner.variants = vs;
    1590            0 :     while(vs)
    1591              :     {
    1592            0 :         vs->slot = &owner;
    1593            0 :         vs->linked = false;
    1594            0 :         vs = vs->next;
    1595              :     }
    1596            0 :     return owner.variants;
    1597              : }
    1598              : 
    1599            0 : static VSlot *emptyvslot(Slot &owner)
    1600              : {
    1601            0 :     int offset = 0;
    1602            0 :     for(int i = static_cast<int>(slots.size()); --i >=0;) //note reverse iteration
    1603              :     {
    1604            0 :         if(slots[i]->variants)
    1605              :         {
    1606            0 :             offset = slots[i]->variants->index + 1;
    1607            0 :             break;
    1608              :         }
    1609              :     }
    1610            0 :     for(size_t i = offset; i < vslots.size(); i++)
    1611              :     {
    1612            0 :         if(!vslots[i]->changed)
    1613              :         {
    1614            0 :             return reassignvslot(owner, vslots[i]);
    1615              :         }
    1616              :     }
    1617            0 :     vslots.push_back(new VSlot(&owner, vslots.size()));
    1618            0 :     return vslots.back();
    1619              : }
    1620              : 
    1621            0 : static bool comparevslot(const VSlot &dst, const VSlot &src, int diff)
    1622              : {
    1623            0 :     if(diff & (1 << VSlot_ShParam))
    1624              :     {
    1625            0 :         if(src.params.size() != dst.params.size())
    1626              :         {
    1627            0 :             return false;
    1628              :         }
    1629            0 :         for(size_t i = 0; i < src.params.size(); i++)
    1630              :         {
    1631            0 :             const SlotShaderParam &sp = src.params[i], &dp = dst.params[i];
    1632            0 :             if(sp.name != dp.name || std::memcmp(sp.val, dp.val, sizeof(sp.val)))
    1633              :             {
    1634            0 :                 return false;
    1635              :             }
    1636              :         }
    1637              :     }
    1638            0 :     if(diff & (1 << VSlot_Scale)   && dst.scale != src.scale)       return false;
    1639            0 :     if(diff & (1 << VSlot_Rotation)&& dst.rotation != src.rotation) return false;
    1640            0 :     if(diff & (1 << VSlot_Angle)   && dst.angle != src.angle)       return false;
    1641            0 :     if(diff & (1 << VSlot_Offset)  && dst.offset != src.offset)     return false;
    1642            0 :     if(diff & (1 << VSlot_Scroll)  && dst.scroll != src.scroll)     return false;
    1643            0 :     if(diff & (1 << VSlot_Alpha)   && (dst.alphafront != src.alphafront || dst.alphaback != src.alphaback)) return false;
    1644            0 :     if(diff & (1 << VSlot_Color)   && dst.colorscale != src.colorscale) return false;
    1645            0 :     if(diff & (1 << VSlot_Refract) && (dst.refractscale != src.refractscale || dst.refractcolor != src.refractcolor)) return false;
    1646            0 :     return true;
    1647              : }
    1648              : 
    1649            0 : void packvslot(std::vector<uchar> &buf, const VSlot &src)
    1650              : {
    1651            0 :     if(src.changed & (1 << VSlot_ShParam))
    1652              :     {
    1653            0 :         for(size_t i = 0; i < src.params.size(); i++)
    1654              :         {
    1655            0 :             const SlotShaderParam &p = src.params[i];
    1656            0 :             buf.push_back(VSlot_ShParam);
    1657            0 :             sendstring(p.name, buf);
    1658            0 :             for(int j = 0; j < 4; ++j)
    1659              :             {
    1660            0 :                 putfloat(buf, p.val[j]);
    1661              :             }
    1662              :         }
    1663              :     }
    1664            0 :     if(src.changed & (1 << VSlot_Scale))
    1665              :     {
    1666            0 :         buf.push_back(VSlot_Scale);
    1667            0 :         putfloat(buf, src.scale);
    1668              :     }
    1669            0 :     if(src.changed & (1 << VSlot_Rotation))
    1670              :     {
    1671            0 :         buf.push_back(VSlot_Rotation);
    1672            0 :         putint(buf, src.rotation);
    1673              :     }
    1674            0 :     if(src.changed & (1 << VSlot_Angle))
    1675              :     {
    1676            0 :         buf.push_back(VSlot_Angle);
    1677            0 :         putfloat(buf, src.angle.x);
    1678            0 :         putfloat(buf, src.angle.y);
    1679            0 :         putfloat(buf, src.angle.z);
    1680              :     }
    1681            0 :     if(src.changed & (1 << VSlot_Offset))
    1682              :     {
    1683            0 :         buf.push_back(VSlot_Offset);
    1684            0 :         putint(buf, src.offset.x());
    1685            0 :         putint(buf, src.offset.y());
    1686              :     }
    1687            0 :     if(src.changed & (1 << VSlot_Scroll))
    1688              :     {
    1689            0 :         buf.push_back(VSlot_Scroll);
    1690            0 :         putfloat(buf, src.scroll.x);
    1691            0 :         putfloat(buf, src.scroll.y);
    1692              :     }
    1693            0 :     if(src.changed & (1 << VSlot_Alpha))
    1694              :     {
    1695            0 :         buf.push_back(VSlot_Alpha);
    1696            0 :         putfloat(buf, src.alphafront);
    1697            0 :         putfloat(buf, src.alphaback);
    1698              :     }
    1699            0 :     if(src.changed & (1 << VSlot_Color))
    1700              :     {
    1701            0 :         buf.push_back(VSlot_Color);
    1702            0 :         putfloat(buf, src.colorscale.r());
    1703            0 :         putfloat(buf, src.colorscale.g());
    1704            0 :         putfloat(buf, src.colorscale.b());
    1705              :     }
    1706            0 :     if(src.changed & (1 << VSlot_Refract))
    1707              :     {
    1708            0 :         buf.push_back(VSlot_Refract);
    1709            0 :         putfloat(buf, src.refractscale);
    1710            0 :         putfloat(buf, src.refractcolor.r());
    1711            0 :         putfloat(buf, src.refractcolor.g());
    1712            0 :         putfloat(buf, src.refractcolor.b());
    1713              :     }
    1714            0 :     buf.push_back(0xFF);
    1715            0 : }
    1716              : 
    1717              : //used in iengine.h
    1718            0 : void packvslot(std::vector<uchar> &buf, int index)
    1719              : {
    1720            0 :     if(static_cast<long>(vslots.size()) > index)
    1721              :     {
    1722            0 :         packvslot(buf, *vslots[index]);
    1723              :     }
    1724              :     else
    1725              :     {
    1726            0 :         buf.push_back(0xFF);
    1727              :     }
    1728            0 : }
    1729              : 
    1730              : //used in iengine.h
    1731            0 : void packvslot(std::vector<uchar> &buf, const VSlot *vs)
    1732              : {
    1733            0 :     if(vs)
    1734              :     {
    1735            0 :         packvslot(buf, *vs);
    1736              :     }
    1737              :     else
    1738              :     {
    1739            0 :         buf.push_back(0xFF);
    1740              :     }
    1741            0 : }
    1742              : 
    1743            0 : bool unpackvslot(ucharbuf &buf, VSlot &dst, bool delta)
    1744              : {
    1745            0 :     while(buf.remaining())
    1746              :     {
    1747            0 :         int changed = buf.get();
    1748            0 :         if(changed >= 0x80)
    1749              :         {
    1750            0 :             break;
    1751              :         }
    1752            0 :         switch(changed)
    1753              :         {
    1754            0 :             case VSlot_ShParam:
    1755              :             {
    1756              :                 string name;
    1757            0 :                 getstring(name, buf);
    1758            0 :                 SlotShaderParam p = { name[0] ? getshaderparamname(name) : nullptr, SIZE_MAX, 0, { 0, 0, 0, 0 } };
    1759            0 :                 for(int i = 0; i < 4; ++i)
    1760              :                 {
    1761            0 :                     p.val[i] = getfloat(buf);
    1762              :                 }
    1763            0 :                 if(p.name)
    1764              :                 {
    1765            0 :                     dst.params.push_back(p);
    1766              :                 }
    1767            0 :                 break;
    1768              :             }
    1769            0 :             case VSlot_Scale:
    1770              :             {
    1771            0 :                 dst.scale = getfloat(buf);
    1772            0 :                 if(dst.scale <= 0)
    1773              :                 {
    1774            0 :                     dst.scale = 1;
    1775              :                 }
    1776            0 :                 else if(!delta)
    1777              :                 {
    1778            0 :                     dst.scale = std::clamp(dst.scale, 1/8.0f, 8.0f);
    1779              :                 }
    1780            0 :                 break;
    1781              :             }
    1782            0 :             case VSlot_Rotation:
    1783            0 :                 dst.rotation = getint(buf);
    1784            0 :                 if(!delta)
    1785              :                 {
    1786            0 :                     dst.rotation = std::clamp(dst.rotation, 0, 7);
    1787              :                 }
    1788            0 :                 break;
    1789            0 :             case VSlot_Angle:
    1790              :             {
    1791            0 :                 dst.angle.x = getfloat(buf);
    1792            0 :                 dst.angle.y = getfloat(buf);
    1793            0 :                 dst.angle.z = getfloat(buf);
    1794            0 :                 break;
    1795              :             }
    1796            0 :             case VSlot_Offset:
    1797              :             {
    1798            0 :                 dst.offset.x() = getint(buf);
    1799            0 :                 dst.offset.y() = getint(buf);
    1800            0 :                 if(!delta)
    1801              :                 {
    1802            0 :                     dst.offset.max(0);
    1803              :                 }
    1804            0 :                 break;
    1805              :             }
    1806            0 :             case VSlot_Scroll:
    1807              :             {
    1808            0 :                 dst.scroll.x = getfloat(buf);
    1809            0 :                 dst.scroll.y = getfloat(buf);
    1810            0 :                 break;
    1811              :             }
    1812            0 :             case VSlot_Alpha:
    1813              :             {
    1814            0 :                 dst.alphafront = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1815            0 :                 dst.alphaback = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1816            0 :                 break;
    1817              :             }
    1818            0 :             case VSlot_Color:
    1819              :             {
    1820            0 :                 dst.colorscale.r() = std::clamp(getfloat(buf), 0.0f, 2.0f);
    1821            0 :                 dst.colorscale.g() = std::clamp(getfloat(buf), 0.0f, 2.0f);
    1822            0 :                 dst.colorscale.b() = std::clamp(getfloat(buf), 0.0f, 2.0f);
    1823            0 :                 break;
    1824              :             }
    1825            0 :             case VSlot_Refract:
    1826              :             {
    1827            0 :                 dst.refractscale = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1828            0 :                 dst.refractcolor.r() = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1829            0 :                 dst.refractcolor.g() = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1830            0 :                 dst.refractcolor.b() = std::clamp(getfloat(buf), 0.0f, 1.0f);
    1831            0 :                 break;
    1832              :             }
    1833            0 :             default:
    1834              :             {
    1835            0 :                 return false;
    1836              :             }
    1837              :         }
    1838            0 :         dst.changed |= 1<<changed;
    1839              :     }
    1840            0 :     if(buf.overread())
    1841              :     {
    1842            0 :         return false;
    1843              :     }
    1844            0 :     return true;
    1845              : }
    1846              : 
    1847            0 : VSlot *findvslot(const Slot &slot, const VSlot &src, const VSlot &delta)
    1848              : {
    1849            0 :     for(VSlot *dst = slot.variants; dst; dst = dst->next)
    1850              :     {
    1851            0 :         if((!dst->changed || dst->changed == (src.changed | delta.changed)) &&
    1852            0 :            comparevslot(*dst, src, src.changed & ~delta.changed) &&
    1853            0 :            comparevslot(*dst, delta, delta.changed))
    1854              :         {
    1855            0 :             return dst;
    1856              :         }
    1857              :     }
    1858            0 :     return nullptr;
    1859              : }
    1860              : 
    1861            0 : static VSlot *clonevslot(const VSlot &src, const VSlot &delta)
    1862              : {
    1863            0 :     vslots.push_back(new VSlot(src.slot, vslots.size()));
    1864            0 :     VSlot *dst = vslots.back();
    1865            0 :     dst->changed = src.changed | delta.changed;
    1866            0 :     propagatevslot(*dst, src, ((1 << VSlot_Num) - 1) & ~delta.changed);
    1867            0 :     propagatevslot(*dst, delta, delta.changed, true);
    1868            0 :     return dst;
    1869              : }
    1870              : 
    1871              : static VARP(autocompactvslots, 0, 256, 0x10000);
    1872              : 
    1873            0 : VSlot *editvslot(const VSlot &src, const VSlot &delta)
    1874              : {
    1875            0 :     VSlot *exists = findvslot(*src.slot, src, delta);
    1876            0 :     if(exists)
    1877              :     {
    1878            0 :         return exists;
    1879              :     }
    1880            0 :     if(vslots.size()>=0x10000)
    1881              :     {
    1882            0 :         ::rootworld.compactvslots();
    1883            0 :         rootworld.allchanged();
    1884            0 :         if(vslots.size()>=0x10000)
    1885              :         {
    1886            0 :             return nullptr;
    1887              :         }
    1888              :     }
    1889            0 :     if(autocompactvslots && ++clonedvslots >= autocompactvslots)
    1890              :     {
    1891            0 :         ::rootworld.compactvslots();
    1892            0 :         rootworld.allchanged();
    1893              :     }
    1894            0 :     return clonevslot(src, delta);
    1895              : }
    1896              : 
    1897            0 : static void fixinsidefaces(std::array<cube, 8> &c, const ivec &o, int size, int tex)
    1898              : {
    1899            0 :     for(int i = 0; i < 8; ++i)
    1900              :     {
    1901            0 :         ivec co(i, o, size);
    1902            0 :         if(c[i].children)
    1903              :         {
    1904            0 :             fixinsidefaces(*(c[i].children), co, size>>1, tex);
    1905              :         }
    1906              :         else
    1907              :         {
    1908            0 :             for(int j = 0; j < 6; ++j)
    1909              :             {
    1910            0 :                 if(!visibletris(c[i], j, co, size))
    1911              :                 {
    1912            0 :                     c[i].texture[j] = tex;
    1913              :                 }
    1914              :             }
    1915              :         }
    1916              :     }
    1917            0 : }
    1918              : 
    1919            1 : int findslottex(const char *name)
    1920              : {
    1921              : 
    1922              :     const struct SlotTex final
    1923              :     {
    1924              :         const char *name;
    1925              :         int id;
    1926            1 :     } slottexs[] =
    1927              :     {
    1928              :         {"0", Tex_Diffuse},
    1929              :         {"1", Tex_Unknown},
    1930              : 
    1931              :         {"c", Tex_Diffuse},
    1932              :         {"u", Tex_Unknown},
    1933              :         {"n", Tex_Normal},
    1934              :         {"g", Tex_Glow},
    1935              :         {"s", Tex_Spec},
    1936              :         {"z", Tex_Depth},
    1937              :         {"a", Tex_Alpha}
    1938              :     };
    1939              : 
    1940           10 :     for(int i = 0; i < static_cast<int>(sizeof(slottexs)/sizeof(SlotTex)); ++i)
    1941              :     {
    1942            9 :         if(!std::strcmp(slottexs[i].name, name))
    1943              :         {
    1944            0 :             return slottexs[i].id;
    1945              :         }
    1946              :     }
    1947            1 :     return -1;
    1948              : }
    1949              : 
    1950            1 : static void texture(const char *type, const char *name, const int *rot, const int *xoffset, const int *yoffset, const float *scale)
    1951              : {
    1952            1 :     int tnum = findslottex(type), matslot;
    1953            1 :     if(tnum == Tex_Diffuse)
    1954              :     {
    1955            0 :         if(slots.size() >= 0x10000)
    1956              :         {
    1957            0 :             return;
    1958              :         }
    1959            0 :         slots.push_back(new Slot(slots.size()));
    1960            0 :         defslot = slots.back();
    1961              :     }
    1962            1 :     else if(!std::strcmp(type, "decal"))
    1963              :     {
    1964            0 :         if(decalslots.size() >= 0x10000)
    1965              :         {
    1966            0 :             return;
    1967              :         }
    1968            0 :         tnum = Tex_Diffuse;
    1969            0 :         decalslots.push_back(new DecalSlot(decalslots.size()));
    1970            0 :         defslot = decalslots.back();
    1971              :     }
    1972            1 :     else if((matslot = findmaterial(type)) >= 0)
    1973              :     {
    1974            0 :         tnum = Tex_Diffuse;
    1975            0 :         defslot = &materialslots[matslot];
    1976            0 :         defslot->reset();
    1977              :     }
    1978            1 :     else if(!defslot)
    1979              :     {
    1980            1 :         return;
    1981              :     }
    1982            0 :     else if(tnum < 0)
    1983              :     {
    1984            0 :         tnum = Tex_Unknown;
    1985              :     }
    1986            0 :     Slot &s = *defslot;
    1987            0 :     s.loaded = false;
    1988            0 :     s.texmask |= 1<<tnum;
    1989            0 :     if(s.sts.size()>=8)
    1990              :     {
    1991            0 :         conoutf(Console_Warn, "warning: too many textures in %s", s.name());
    1992              :     }
    1993            0 :     s.sts.emplace_back();
    1994            0 :     Slot::Tex &st = s.sts.back();
    1995            0 :     st.type = tnum;
    1996            0 :     copystring(st.name, name);
    1997            0 :     path(st.name);
    1998            0 :     if(tnum == Tex_Diffuse)
    1999              :     {
    2000            0 :         setslotshader(s);
    2001            0 :         VSlot &vs = s.emptyvslot();
    2002            0 :         vs.rotation = std::clamp(*rot, 0, 7);
    2003            0 :         vs.offset = ivec2(*xoffset, *yoffset).max(0);
    2004            0 :         vs.scale = *scale <= 0 ? 1 : *scale;
    2005            0 :         propagatevslot(&vs, (1 << VSlot_Num) - 1);
    2006              :     }
    2007              : }
    2008              : 
    2009            1 : static void texgrass(const char *name)
    2010              : {
    2011            1 :     if(!defslot)
    2012              :     {
    2013            1 :         return;
    2014              :     }
    2015            0 :     Slot &s = *defslot;
    2016            0 :     delete[] s.grass;
    2017            0 :     s.grass = name[0] ? newstring(makerelpath("media/texture", name)) : nullptr;
    2018              : }
    2019              : 
    2020            1 : static void texscroll(const float *scrollS, const float *scrollT)
    2021              : {
    2022            1 :     if(!defslot)
    2023              :     {
    2024            1 :         return;
    2025              :     }
    2026            0 :     Slot &s = *defslot;
    2027            0 :     s.variants->scroll = vec2(*scrollS/1000.0f, *scrollT/1000.0f);
    2028            0 :     propagatevslot(s.variants, 1 << VSlot_Scroll);
    2029              : }
    2030              : 
    2031            1 : static void texoffset_(const int *xoffset, const int *yoffset)
    2032              : {
    2033            1 :     if(!defslot)
    2034              :     {
    2035            1 :         return;
    2036              :     }
    2037            0 :     Slot &s = *defslot;
    2038            0 :     s.variants->offset = ivec2(*xoffset, *yoffset).max(0);
    2039            0 :     propagatevslot(s.variants, 1 << VSlot_Offset);
    2040              : }
    2041              : 
    2042            1 : static void texrotate_(const int *rot)
    2043              : {
    2044            1 :     if(!defslot)
    2045              :     {
    2046            1 :         return;
    2047              :     }
    2048            0 :     Slot &s = *defslot;
    2049            0 :     s.variants->rotation = std::clamp(*rot, 0, 7);
    2050            0 :     propagatevslot(s.variants, 1 << VSlot_Rotation);
    2051              : }
    2052              : 
    2053            1 : static void texangle_(const float *a)
    2054              : {
    2055            1 :     if(!defslot)
    2056              :     {
    2057            1 :         return;
    2058              :     }
    2059            0 :     Slot &s = *defslot;
    2060            0 :     s.variants->angle = vec(*a, std::sin(*a/RAD), std::cos(*a/RAD));
    2061            0 :     propagatevslot(s.variants, 1 << VSlot_Angle);
    2062              : }
    2063              : 
    2064            1 : static void texscale(const float *scale)
    2065              : {
    2066            1 :     if(!defslot)
    2067              :     {
    2068            1 :         return;
    2069              :     }
    2070            0 :     Slot &s = *defslot;
    2071            0 :     s.variants->scale = *scale <= 0 ? 1 : *scale;
    2072            0 :     propagatevslot(s.variants, 1 << VSlot_Scale);
    2073              : }
    2074              : 
    2075            1 : static void texalpha(const float *front, const float *back)
    2076              : {
    2077            1 :     if(!defslot)
    2078              :     {
    2079            1 :         return;
    2080              :     }
    2081            0 :     Slot &s = *defslot;
    2082            0 :     s.variants->alphafront = std::clamp(*front, 0.0f, 1.0f);
    2083            0 :     s.variants->alphaback = std::clamp(*back, 0.0f, 1.0f);
    2084            0 :     propagatevslot(s.variants, 1 << VSlot_Alpha);
    2085              : }
    2086              : 
    2087            1 : static void texcolor(const float *r, const float *g, const float *b)
    2088              : {
    2089            1 :     if(!defslot)
    2090              :     {
    2091            1 :         return;
    2092              :     }
    2093            0 :     Slot &s = *defslot;
    2094            0 :     s.variants->colorscale = vec(std::clamp(*r, 0.0f, 2.0f), std::clamp(*g, 0.0f, 2.0f), std::clamp(*b, 0.0f, 2.0f));
    2095            0 :     propagatevslot(s.variants, 1 << VSlot_Color);
    2096              : }
    2097              : 
    2098            1 : static void texrefract(const float *k, const float *r, const float *g, const float *b)
    2099              : {
    2100            1 :     if(!defslot)
    2101              :     {
    2102            1 :         return;
    2103              :     }
    2104            0 :     Slot &s = *defslot;
    2105            0 :     s.variants->refractscale = std::clamp(*k, 0.0f, 1.0f);
    2106            0 :     if(s.variants->refractscale > 0 && (*r > 0 || *g > 0 || *b > 0))
    2107              :     {
    2108            0 :         s.variants->refractcolor = vec(std::clamp(*r, 0.0f, 1.0f), std::clamp(*g, 0.0f, 1.0f), std::clamp(*b, 0.0f, 1.0f));
    2109              :     }
    2110              :     else
    2111              :     {
    2112            0 :         s.variants->refractcolor = vec(1, 1, 1);
    2113              :     }
    2114            0 :     propagatevslot(s.variants, 1 << VSlot_Refract);
    2115              : }
    2116              : 
    2117            1 : static void texsmooth(const int *id, const int *angle)
    2118              : {
    2119            1 :     if(!defslot)
    2120              :     {
    2121            1 :         return;
    2122              :     }
    2123            0 :     Slot &s = *defslot;
    2124            0 :     s.smooth = smoothangle(*id, *angle);
    2125              : }
    2126              : 
    2127            1 : static void decaldepth(const float *depth, const float *fade)
    2128              : {
    2129            1 :     if(!defslot || defslot->type() != Slot::SlotType_Decal)
    2130              :     {
    2131            1 :         return;
    2132              :     }
    2133            0 :     DecalSlot &s = *static_cast<DecalSlot *>(defslot);
    2134            0 :     s.depth = std::clamp(*depth, 1e-3f, 1e3f);
    2135            0 :     s.fade = std::clamp(*fade, 0.0f, s.depth);
    2136              : }
    2137              : 
    2138            0 : int DecalSlot::cancombine(int type) const
    2139              : {
    2140            0 :     switch(type)
    2141              :     {
    2142            0 :         case Tex_Glow:
    2143              :         {
    2144            0 :             return Tex_Spec;
    2145              :         }
    2146            0 :         case Tex_Normal:
    2147              :         {
    2148            0 :             return texmask&(1 << Tex_Depth) ? Tex_Depth : (texmask & (1 << Tex_Glow) ? -1 : Tex_Spec);
    2149              :         }
    2150            0 :         default:
    2151              :         {
    2152            0 :             return -1;
    2153              :         }
    2154              :     }
    2155              : }
    2156              : 
    2157            0 : bool DecalSlot::shouldpremul(int type) const
    2158              : {
    2159            0 :     switch(type)
    2160              :     {
    2161            0 :         case Tex_Diffuse:
    2162              :         {
    2163            0 :             return true;
    2164              :         }
    2165            0 :         default:
    2166              :         {
    2167            0 :             return false;
    2168              :         }
    2169              :     }
    2170              : }
    2171              : 
    2172            0 : static void addname(std::vector<char> &key, Slot &slot, Slot::Tex &t, bool combined = false, const char *prefix = nullptr)
    2173              : {
    2174            0 :     if(combined)
    2175              :     {
    2176            0 :         key.push_back('&');
    2177              :     }
    2178            0 :     if(prefix)
    2179              :     {
    2180            0 :         while(*prefix)
    2181              :         {
    2182            0 :             key.push_back(*prefix++);
    2183              :         }
    2184              :     }
    2185            0 :     DEF_FORMAT_STRING(tname, "%s/%s", slot.texturedir(), t.name);
    2186            0 :     for(const char *s = path(tname); *s; key.push_back(*s++))
    2187              :     {
    2188              :         //(empty body)
    2189              :     }
    2190            0 : }
    2191              : 
    2192              : // Slot object
    2193              : 
    2194            0 : VSlot &Slot::emptyvslot()
    2195              : {
    2196            0 :     return *::emptyvslot(*this);
    2197              : }
    2198              : 
    2199            0 : int Slot::findtextype(int type, int last) const
    2200              : {
    2201            0 :     for(size_t i = last+1; i<sts.size(); i++)
    2202              :     {
    2203            0 :         if((type&(1<<sts[i].type)) && sts[i].combined<0)
    2204              :         {
    2205            0 :             return i;
    2206              :         }
    2207              :     }
    2208            0 :     return -1;
    2209              : }
    2210              : 
    2211            0 : int Slot::cancombine(int type) const
    2212              : {
    2213            0 :     switch(type)
    2214              :     {
    2215            0 :         case Tex_Diffuse:
    2216              :         {
    2217            0 :             return texmask&((1 << Tex_Spec) | (1 << Tex_Normal)) ? Tex_Spec : Tex_Alpha;
    2218              :         }
    2219            0 :         case Tex_Normal:
    2220              :         {
    2221            0 :             return texmask&(1 << Tex_Depth) ? Tex_Depth : Tex_Alpha;
    2222              :         }
    2223            0 :         default:
    2224              :         {
    2225            0 :             return -1;
    2226              :         }
    2227              :     }
    2228              : }
    2229              : 
    2230            0 : void Slot::load(int index, Slot::Tex &t)
    2231              : {
    2232            0 :     std::vector<char> key;
    2233            0 :     addname(key, *this, t, false, shouldpremul(t.type) ? "<premul>" : nullptr);
    2234            0 :     Slot::Tex *combine = nullptr;
    2235            0 :     for(size_t i = 0; i < sts.size(); i++)
    2236              :     {
    2237            0 :         Slot::Tex &c = sts[i];
    2238            0 :         if(c.combined == index)
    2239              :         {
    2240            0 :             combine = &c;
    2241            0 :             addname(key, *this, c, true);
    2242            0 :             break;
    2243              :         }
    2244              :     }
    2245            0 :     key.push_back('\0');
    2246            0 :     std::unordered_map<std::string, Texture>::iterator itr = textures.find(key.data());
    2247            0 :     if(itr != textures.end())
    2248              :     {
    2249            0 :         t.t = &(*itr).second;
    2250            0 :         return;
    2251              :     }
    2252            0 :     t.t = nullptr;
    2253            0 :     int compress = 0,
    2254            0 :         wrap = 0;
    2255            0 :     ImageData ts;
    2256            0 :     if(!ts.texturedata(*this, t, true, &compress, &wrap))
    2257              :     {
    2258            0 :         t.t = notexture;
    2259            0 :         return;
    2260              :     }
    2261            0 :     if(!ts.compressed)
    2262              :     {
    2263            0 :         switch(t.type)
    2264              :         {
    2265            0 :             case Tex_Spec:
    2266              :             {
    2267            0 :                 if(ts.depth() > 1)
    2268              :                 {
    2269            0 :                     ts.collapsespec();
    2270              :                 }
    2271            0 :                 break;
    2272              :             }
    2273            0 :             case Tex_Glow:
    2274              :             case Tex_Diffuse:
    2275              :             case Tex_Normal:
    2276            0 :                 if(combine)
    2277              :                 {
    2278            0 :                     ImageData cs;
    2279            0 :                     if(cs.texturedata(*this, *combine))
    2280              :                     {
    2281            0 :                         if(cs.width()!=ts.width() || cs.height()!=ts.height())
    2282              :                         {
    2283            0 :                             cs.scaleimage(ts.width(), ts.height());
    2284              :                         }
    2285            0 :                         switch(combine->type)
    2286              :                         {
    2287            0 :                             case Tex_Spec:
    2288              :                             {
    2289            0 :                                 ts.mergespec(cs);
    2290            0 :                                 break;
    2291              :                             }
    2292            0 :                             case Tex_Depth:
    2293              :                             {
    2294            0 :                                 ts.mergedepth(cs);
    2295            0 :                                 break;
    2296              :                             }
    2297            0 :                             case Tex_Alpha:
    2298              :                             {
    2299            0 :                                 ts.mergealpha(cs);
    2300            0 :                                 break;
    2301              :                             }
    2302              :                         }
    2303              :                     }
    2304            0 :                 }
    2305            0 :                 if(ts.depth() < 3)
    2306              :                 {
    2307            0 :                     ts.swizzleimage();
    2308              :                 }
    2309            0 :                 break;
    2310              :         }
    2311              :     }
    2312            0 :     if(!ts.compressed && shouldpremul(t.type))
    2313              :     {
    2314            0 :         ts.texpremul();
    2315              :     }
    2316            0 :     t.t = newtexture(nullptr, key.data(), ts, wrap, true, true, true, compress);
    2317            0 : }
    2318              : 
    2319            0 : void Slot::load()
    2320              : {
    2321            0 :     linkslotshader(*this);
    2322            0 :     for(size_t i = 0; i < sts.size(); i++)
    2323              :     {
    2324            0 :         Slot::Tex &t = sts[i];
    2325            0 :         if(t.combined >= 0)
    2326              :         {
    2327            0 :             continue;
    2328              :         }
    2329            0 :         int combine = cancombine(t.type);
    2330            0 :         if(combine >= 0 && (combine = findtextype(1<<combine)) >= 0)
    2331              :         {
    2332            0 :             Slot::Tex &c = sts[combine];
    2333            0 :             c.combined = i;
    2334              :         }
    2335              :     }
    2336            0 :     for(size_t i = 0; i < sts.size(); i++)
    2337              :     {
    2338            0 :         Slot::Tex &t = sts[i];
    2339            0 :         if(t.combined >= 0)
    2340              :         {
    2341            0 :             continue;
    2342              :         }
    2343            0 :         switch(t.type)
    2344              :         {
    2345              :             default:
    2346              :             {
    2347            0 :                 load(i, t);
    2348            0 :                 break;
    2349              :             }
    2350              :         }
    2351              :     }
    2352            0 :     loaded = true;
    2353            0 : }
    2354              : 
    2355              : // VSlot
    2356              : 
    2357           40 : void VSlot::addvariant(Slot *parent)
    2358              : {
    2359           40 :     if(!parent->variants)
    2360              :     {
    2361           40 :         parent->variants = this;
    2362              :     }
    2363              :     else
    2364              :     {
    2365            0 :         VSlot *prev = parent->variants;
    2366            0 :         while(prev->next)
    2367              :         {
    2368            0 :             prev = prev->next;
    2369              :         }
    2370            0 :         prev->next = this;
    2371              :     }
    2372           40 : }
    2373              : 
    2374            0 : bool VSlot::isdynamic() const
    2375              : {
    2376            0 :     return !scroll.iszero() || slot->shader->isdynamic();
    2377              : }
    2378              : 
    2379              : // end of Slot/VSlot
    2380            0 : MatSlot &lookupmaterialslot(int index, bool load)
    2381              : {
    2382            0 :     MatSlot &s = materialslots[index];
    2383            0 :     if(load && !s.linked)
    2384              :     {
    2385            0 :         if(!s.loaded)
    2386              :         {
    2387            0 :             s.load();
    2388              :         }
    2389            0 :         linkvslotshader(s);
    2390            0 :         s.linked = true;
    2391              :     }
    2392            0 :     return s;
    2393              : }
    2394              : 
    2395            0 : Slot &lookupslot(int index, bool load)
    2396              : {
    2397            0 :     Slot &s = (static_cast<long>(slots.size()) > index) ? *slots[index] : ((slots.size() > Default_Geom) ? *slots[Default_Geom] : dummyslot);
    2398            0 :     if(!s.loaded && load)
    2399              :     {
    2400            0 :         s.load();
    2401              :     }
    2402            0 :     return s;
    2403              : }
    2404              : 
    2405            0 : VSlot &lookupvslot(int index, bool load)
    2406              : {
    2407            0 :     VSlot &s = (static_cast<long>(vslots.size()) > index) && vslots[index]->slot ? *vslots[index] : ((slots.size() > Default_Geom) && slots[Default_Geom]->variants ? *slots[Default_Geom]->variants : dummyvslot);
    2408            0 :     if(load && !s.linked)
    2409              :     {
    2410            0 :         if(!s.slot->loaded)
    2411              :         {
    2412            0 :             s.slot->load();
    2413              :         }
    2414            0 :         linkvslotshader(s);
    2415            0 :         s.linked = true;
    2416              :     }
    2417            0 :     return s;
    2418              : }
    2419              : 
    2420            0 : DecalSlot &lookupdecalslot(int index, bool load)
    2421              : {
    2422            0 :     DecalSlot &s = (static_cast<int>(decalslots.size()) > index) ? *decalslots[index] : dummydecalslot;
    2423            0 :     if(load && !s.linked)
    2424              :     {
    2425            0 :         if(!s.loaded)
    2426              :         {
    2427            0 :             s.load();
    2428              :         }
    2429            0 :         linkvslotshader(s);
    2430            0 :         s.linked = true;
    2431              :     }
    2432            0 :     return s;
    2433              : }
    2434              : 
    2435            0 : void linkslotshaders()
    2436              : {
    2437            0 :     for(Slot * const &i : slots)
    2438              :     {
    2439            0 :         if(i->loaded)
    2440              :         {
    2441            0 :             linkslotshader(*i);
    2442              :         }
    2443              :     }
    2444            0 :     for(VSlot * const &i : vslots)
    2445              :     {
    2446            0 :         if(i->linked)
    2447              :         {
    2448            0 :             linkvslotshader(*i);
    2449              :         }
    2450              :     }
    2451            0 :     for(size_t i = 0; i < (MatFlag_Volume|MatFlag_Index)+1; ++i)
    2452              :     {
    2453            0 :         if(materialslots[i].loaded)
    2454              :         {
    2455            0 :             linkslotshader(materialslots[i]);
    2456            0 :             linkvslotshader(materialslots[i]);
    2457              :         }
    2458              :     }
    2459            0 :     for(DecalSlot * const &i : decalslots)
    2460              :     {
    2461            0 :         if(i->loaded)
    2462              :         {
    2463            0 :             linkslotshader(*i);
    2464            0 :             linkvslotshader(*i);
    2465              :         }
    2466              :     }
    2467            0 : }
    2468              : 
    2469            0 : static void blitthumbnail(ImageData &d, ImageData &s, int x, int y)
    2470              : {
    2471            0 :     d.forcergbimage();
    2472            0 :     s.forcergbimage();
    2473            0 :     uchar *dstrow = &d.data[d.pitch*y + d.depth()*x],
    2474            0 :           *srcrow = s.data;
    2475            0 :     for(int h = 0; h < s.height(); ++h)
    2476              :     {
    2477            0 :         for(uchar *dst = dstrow, *src = srcrow, *end = &srcrow[s.width()*s.depth()]; src < end; dst += d.depth(), src += s.depth())
    2478              :         {
    2479            0 :             for(int k = 0; k < 3; ++k)
    2480              :             {
    2481            0 :                 dst[k] = src[k];
    2482              :             }
    2483              :         }
    2484            0 :         dstrow += d.pitch;
    2485            0 :         srcrow += s.pitch;
    2486              :     }
    2487            0 : }
    2488              : 
    2489            1 : Texture *Slot::loadthumbnail()
    2490              : {
    2491            1 :     if(thumbnail)
    2492              :     {
    2493            0 :         return thumbnail;
    2494              :     }
    2495            1 :     if(!variants)
    2496              :     {
    2497            1 :         thumbnail = notexture;
    2498            1 :         return thumbnail;
    2499              :     }
    2500            0 :     VSlot &vslot = *variants;
    2501            0 :     linkslotshader(*this, false);
    2502            0 :     linkvslotshader(vslot, false);
    2503            0 :     std::vector<char> name;
    2504            0 :     if(vslot.colorscale == vec(1, 1, 1))
    2505              :     {
    2506            0 :         addname(name, *this, sts[0], false, "<thumbnail>");
    2507              :     }
    2508              :     else
    2509              :     {
    2510            0 :         DEF_FORMAT_STRING(prefix, "<thumbnail:%.2f/%.2f/%.2f>", vslot.colorscale.x, vslot.colorscale.y, vslot.colorscale.z);
    2511            0 :         addname(name, *this, sts[0], false, prefix);
    2512              :     }
    2513            0 :     int glow = -1;
    2514            0 :     if(texmask&(1 << Tex_Glow))
    2515              :     {
    2516            0 :         for(size_t j = 0; j < sts.size(); j++)
    2517              :         {
    2518            0 :             if(sts[j].type == Tex_Glow)
    2519              :             {
    2520            0 :                 glow = j;
    2521            0 :                 break;
    2522              :             }
    2523              :         }
    2524            0 :         if(glow >= 0)
    2525              :         {
    2526            0 :             DEF_FORMAT_STRING(prefix, "<glow:%.2f/%.2f/%.2f>", vslot.glowcolor.x, vslot.glowcolor.y, vslot.glowcolor.z);
    2527            0 :             addname(name, *this, sts[glow], true, prefix);
    2528              :         }
    2529              :     }
    2530            0 :     name.push_back('\0');
    2531            0 :     std::unordered_map<std::string, Texture>::iterator itr = textures.find(path(name.data()));
    2532            0 :     if(itr != textures.end())
    2533              :     {
    2534            0 :         thumbnail = &(*itr).second;
    2535            0 :         return &(*itr).second;
    2536              :     }
    2537              :     else
    2538              :     {
    2539            0 :         std::unordered_map<std::string, Texture>::iterator insert = textures.insert( { std::string(name.data()), Texture() } ).first;
    2540            0 :         Texture *t = &(*insert).second;
    2541            0 :         ImageData s, g, l, d;
    2542            0 :         s.texturedata(*this, sts[0], false);
    2543            0 :         if(glow >= 0)
    2544              :         {
    2545            0 :             g.texturedata(*this, sts[glow], false);
    2546              :         }
    2547            0 :         if(!s.data)
    2548              :         {
    2549            0 :             t = thumbnail = notexture;
    2550              :         }
    2551              :         else
    2552              :         {
    2553            0 :             if(vslot.colorscale != vec(1, 1, 1))
    2554              :             {
    2555            0 :                 s.texmad(vslot.colorscale, vec(0, 0, 0));
    2556              :             }
    2557            0 :             int xs = s.width(),
    2558            0 :                 ys = s.height();
    2559            0 :             if(s.width() > 128 || s.height() > 128)
    2560              :             {
    2561            0 :                 s.scaleimage(std::min(s.width(), 128), std::min(s.height(), 128));
    2562              :             }
    2563            0 :             if(g.data)
    2564              :             {
    2565            0 :                 if(g.width() != s.width() || g.height() != s.height())
    2566              :                 {
    2567            0 :                     g.scaleimage(s.width(), s.height());
    2568              :                 }
    2569            0 :                 s.addglow(g, vslot.glowcolor);
    2570              :             }
    2571            0 :             if(l.data)
    2572              :             {
    2573            0 :                 if(l.width() != s.width()/2 || l.height() != s.height()/2)
    2574              :                 {
    2575            0 :                     l.scaleimage(s.width()/2, s.height()/2);
    2576              :                 }
    2577            0 :                 blitthumbnail(s, l, s.width()-l.width(), s.height()-l.height());
    2578              :             }
    2579            0 :             if(d.data)
    2580              :             {
    2581            0 :                 if(vslot.colorscale != vec(1, 1, 1))
    2582              :                 {
    2583            0 :                     d.texmad(vslot.colorscale, vec(0, 0, 0));
    2584              :                 }
    2585            0 :                 if(d.width() != s.width()/2 || d.height() != s.height()/2)
    2586              :                 {
    2587            0 :                     d.scaleimage(s.width()/2, s.height()/2);
    2588              :                 }
    2589            0 :                 blitthumbnail(s, d, 0, 0);
    2590              :             }
    2591            0 :             if(s.depth() < 3)
    2592              :             {
    2593            0 :                 s.forcergbimage();
    2594              :             }
    2595            0 :             t = newtexture(nullptr, name.data(), s, 0, false, false, true);
    2596            0 :             t->xs = xs;
    2597            0 :             t->ys = ys;
    2598            0 :             thumbnail = t;
    2599              :         }
    2600            0 :         return t;
    2601            0 :     }
    2602            0 : }
    2603              : 
    2604              : // environment mapped reflections
    2605              : 
    2606            0 : void cleanuptextures()
    2607              : {
    2608            0 :     for(Slot * const &i : slots)
    2609              :     {
    2610            0 :         i->cleanup();
    2611              :     }
    2612            0 :     for(VSlot * const &i : vslots)
    2613              :     {
    2614            0 :         i->cleanup();
    2615              :     }
    2616            0 :     for(size_t i = 0; i < (MatFlag_Volume|MatFlag_Index)+1; ++i)
    2617              :     {
    2618            0 :         materialslots[i].cleanup();
    2619              :     }
    2620            0 :     for(DecalSlot * const &i : decalslots)
    2621              :     {
    2622            0 :         i->cleanup();
    2623              :     }
    2624            0 :     for(std::unordered_map<std::string, Texture>::iterator itr = textures.begin(); itr != textures.end(); ++itr)
    2625              :     {
    2626            0 :         Texture &t = (*itr).second;
    2627            0 :         delete[] t.alphamask;
    2628            0 :         t.alphamask = nullptr;
    2629            0 :         if(t.id)
    2630              :         {
    2631            0 :             glDeleteTextures(1, &t.id);
    2632            0 :             t.id = 0;
    2633              :         }
    2634            0 :         if(t.type&Texture::TRANSIENT)
    2635              :         {
    2636            0 :             itr = textures.erase(itr);
    2637              :         }
    2638              :     }
    2639            0 : }
    2640              : 
    2641            0 : bool reloadtexture(const char *name)
    2642              : {
    2643            0 :     std::unordered_map<std::string, Texture>::iterator itr = textures.find(path(std::string(name)));
    2644            0 :     if(itr != textures.end())
    2645              :     {
    2646            0 :         return (*itr).second.reload();
    2647              :     }
    2648            0 :     return true;
    2649              : }
    2650              : 
    2651            0 : bool Texture::reload()
    2652              : {
    2653            0 :     if(id)
    2654              :     {
    2655            0 :         return true;
    2656              :     }
    2657            0 :     switch(type&TYPE)
    2658              :     {
    2659            0 :         case IMAGE:
    2660              :         {
    2661            0 :             int compress = 0;
    2662            0 :             ImageData s;
    2663            0 :             if(!s.texturedata(name, true, &compress) || !newtexture(this, nullptr, s, clamp, mipmap, false, false, compress))
    2664              :             {
    2665            0 :                 return false;
    2666              :             }
    2667            0 :             break;
    2668            0 :         }
    2669              :     }
    2670            0 :     return true;
    2671              : }
    2672              : 
    2673            1 : void reloadtex(const char *name)
    2674              : {
    2675            1 :     std::unordered_map<std::string, Texture>::iterator itr = textures.find(path(std::string(name)));
    2676            1 :     if(itr == textures.end())
    2677              :     {
    2678            1 :         conoutf(Console_Error, "texture %s is not loaded", name);
    2679            1 :         return;
    2680              :     }
    2681            0 :     Texture *t = &(*itr).second;
    2682            0 :     if(t->type&Texture::TRANSIENT)
    2683              :     {
    2684            0 :         conoutf(Console_Error, "can't reload transient texture %s", name);
    2685            0 :         return;
    2686              :     }
    2687            0 :     delete[] t->alphamask;
    2688            0 :     t->alphamask = nullptr;
    2689            0 :     Texture oldtex = *t;
    2690            0 :     t->id = 0;
    2691            0 :     if(!t->reload())
    2692              :     {
    2693            0 :         if(t->id)
    2694              :         {
    2695            0 :             glDeleteTextures(1, &t->id);
    2696              :         }
    2697            0 :         *t = oldtex;
    2698            0 :         conoutf(Console_Error, "failed to reload texture %s", name);
    2699              :     }
    2700              : }
    2701              : 
    2702            0 : void reloadtextures()
    2703              : {
    2704            0 :     int reloaded = 0;
    2705            0 :     for(auto &[k, t] : textures)
    2706              :     {
    2707            0 :         loadprogress = static_cast<float>(++reloaded)/textures.size();
    2708            0 :         t.reload();
    2709              :     }
    2710            0 :     loadprogress = 0;
    2711            0 : }
    2712              : 
    2713            0 : static void writepngchunk(stream *f, const char *type, const uchar *data = nullptr, uint len = 0)
    2714              : {
    2715            0 :     f->putbig<uint>(len);
    2716            0 :     f->write(type, 4);
    2717            0 :     f->write(data, len);
    2718              : 
    2719            0 :     uint crc = crc32(0, Z_NULL, 0);
    2720            0 :     crc = crc32(crc, reinterpret_cast<const Bytef *>(type), 4);
    2721            0 :     if(data)
    2722              :     {
    2723            0 :         crc = crc32(crc, data, len);
    2724              :     }
    2725            0 :     f->putbig<uint>(crc);
    2726            0 : }
    2727              : 
    2728              : static VARP(compresspng, 0, 9, 9);
    2729              : 
    2730            0 : static void flushzip(z_stream &z, uchar *buf, const uint &buflen, uint &len, stream *f, uint &crc)
    2731              : {
    2732            0 :     int flush = buflen- z.avail_out;
    2733            0 :     crc = crc32(crc, buf, flush);
    2734            0 :     len += flush;
    2735            0 :     f->write(buf, flush);
    2736            0 :     z.next_out = static_cast<Bytef *>(buf);
    2737            0 :     z.avail_out = buflen;
    2738            0 : }
    2739              : 
    2740            1 : static void savepng(const char *filename, const ImageData &image, bool flip)
    2741              : {
    2742            1 :     if(!image.height() || !image.width())
    2743              :     {
    2744            1 :         conoutf(Console_Error, "cannot save 0-size png");
    2745            1 :         return;
    2746              :     }
    2747            0 :     uchar ctype = 0;
    2748            0 :     switch(image.depth())
    2749              :     {
    2750            0 :         case 1:
    2751              :         {
    2752            0 :             ctype = 0;
    2753            0 :             break;
    2754              :         }
    2755            0 :         case 2:
    2756              :         {
    2757            0 :             ctype = 4;
    2758            0 :             break;
    2759              :         }
    2760            0 :         case 3:
    2761              :         {
    2762            0 :             ctype = 2;
    2763            0 :             break;
    2764              :         }
    2765            0 :         case 4:
    2766              :         {
    2767            0 :             ctype = 6;
    2768            0 :             break;
    2769              :         }
    2770            0 :         default:
    2771              :         {
    2772            0 :             conoutf(Console_Error, "failed saving png to %s", filename);
    2773            0 :             return;
    2774              :         }
    2775              :     }
    2776            0 :     stream *f = openfile(filename, "wb");
    2777            0 :     if(!f)
    2778              :     {
    2779            0 :         conoutf(Console_Error, "could not write to %s", filename);
    2780            0 :         return;
    2781              :     }
    2782            0 :     std::array<uchar, 8> signature = {{ 137, 80, 78, 71, 13, 10, 26, 10 }};
    2783            0 :     f->write(signature.data(), signature.size());
    2784              :     struct PngIHdr
    2785              :     {
    2786              :         uint width,
    2787              :              height;
    2788              :         uchar bitdepth,
    2789              :               colortype,
    2790              :               compress,
    2791              :               filter,
    2792              :               interlace;
    2793              :     };
    2794            0 :     PngIHdr ihdr =
    2795              :     {
    2796            0 :         static_cast<uint>(endianswap(image.width())),
    2797            0 :         static_cast<uint>(endianswap(image.height())),
    2798              :         8,
    2799              :         ctype,
    2800              :         0,
    2801              :         0,
    2802              :         0
    2803            0 :     };
    2804            0 :     writepngchunk(f, "IHDR", reinterpret_cast<uchar *>(&ihdr), 13);
    2805            0 :     stream::offset idat = f->tell();
    2806            0 :     uint len = 0;
    2807            0 :     f->write("\0\0\0\0IDAT", 8);
    2808            0 :     uint crc = crc32(0, Z_NULL, 0);
    2809            0 :     crc = crc32(crc, reinterpret_cast<const Bytef *>("IDAT"), 4);
    2810              :     z_stream z;
    2811            0 :     z.zalloc = nullptr;
    2812            0 :     z.zfree = nullptr;
    2813            0 :     z.opaque = nullptr;
    2814            0 :     if(deflateInit(&z, compresspng) != Z_OK)
    2815              :     {
    2816            0 :         goto error; //goto is beneath FLUSHZ macro
    2817              :     }
    2818              :     std::array<uchar, 1<<12> buf;
    2819            0 :     z.next_out = static_cast<Bytef *>(buf.data());
    2820            0 :     z.avail_out = buf.size();
    2821            0 :     for(int i = 0; i < image.height(); ++i)
    2822              :     {
    2823            0 :         uchar filter = 0;
    2824            0 :         for(int j = 0; j < 2; ++j)
    2825              :         {
    2826            0 :             z.next_in = j ? static_cast<Bytef *>(image.data) + (flip ? image.height()-i-1 : i)*image.pitch : static_cast<Bytef *>(&filter);
    2827            0 :             z.avail_in = j ? image.width()*image.depth() : 1;
    2828            0 :             while(z.avail_in > 0)
    2829              :             {
    2830            0 :                 if(deflate(&z, Z_NO_FLUSH) != Z_OK)
    2831              :                 {
    2832            0 :                     goto cleanuperror; //goto is beneath FLUSHZ macro
    2833              :                 }
    2834            0 :                 flushzip(z, buf.data(), buf.size(), len, f, crc);
    2835              :             }
    2836              :         }
    2837              :     }
    2838              : 
    2839              :     for(;;)
    2840              :     {
    2841            0 :         int err = deflate(&z, Z_FINISH);
    2842            0 :         if(err != Z_OK && err != Z_STREAM_END)
    2843              :         {
    2844            0 :             goto cleanuperror;
    2845              :         }
    2846            0 :         flushzip(z, buf.data(), buf.size(), len, f, crc);
    2847            0 :         if(err == Z_STREAM_END)
    2848              :         {
    2849            0 :             break;
    2850              :         }
    2851            0 :     }
    2852            0 :     deflateEnd(&z);
    2853              : 
    2854            0 :     f->seek(idat, SEEK_SET);
    2855            0 :     f->putbig<uint>(len);
    2856            0 :     f->seek(0, SEEK_END);
    2857            0 :     f->putbig<uint>(crc);
    2858            0 :     writepngchunk(f, "IEND");
    2859            0 :     delete f;
    2860            0 :     return;
    2861              : 
    2862            0 : cleanuperror:
    2863            0 :     deflateEnd(&z);
    2864              : 
    2865            0 : error:
    2866            0 :     delete f;
    2867            0 :     conoutf(Console_Error, "failed saving png to %s", filename);
    2868              : }
    2869              : 
    2870              : static SVARP(screenshotdir, "screenshot");
    2871              : 
    2872            1 : void screenshot(const char *filename)
    2873              : {
    2874              :     static string buf;
    2875            1 :     int dirlen = 0;
    2876            1 :     copystring(buf, screenshotdir);
    2877            1 :     if(screenshotdir[0])
    2878              :     {
    2879            1 :         dirlen = std::strlen(buf);
    2880            1 :         if(buf[dirlen] != '/' && buf[dirlen] != '\\' && dirlen+1 < static_cast<int>(sizeof(buf)))
    2881              :         {
    2882            1 :             buf[dirlen++] = '/';
    2883            1 :             buf[dirlen] = '\0';
    2884              :         }
    2885            1 :         const char *dir = findfile(buf, "w");
    2886            1 :         if(!fileexists(dir, "w"))
    2887              :         {
    2888            1 :             createdir(dir);
    2889              :         }
    2890              :     }
    2891            1 :     if(filename[0])
    2892              :     {
    2893            0 :         concatstring(buf, filename);
    2894              :     }
    2895              :     else
    2896              :     {
    2897              :         string sstime;
    2898            1 :         time_t t = std::time(nullptr);
    2899            1 :         size_t len = std::strftime(sstime, sizeof(sstime), "%Y-%m-%d_%H.%M.%S.png", std::localtime(&t));
    2900            1 :         sstime[std::min(len, sizeof(sstime)-1)] = '\0';
    2901            1 :         concatstring(buf, sstime);
    2902           24 :         for(char *s = &buf[dirlen]; *s; s++)
    2903              :         {
    2904           23 :             if(iscubespace(*s) || *s == '/' || *s == '\\')
    2905              :             {
    2906            0 :                 *s = '-';
    2907              :             }
    2908              :         }
    2909              :     }
    2910              : 
    2911            1 :     ImageData image(hudw(), hudh(), 3);
    2912            1 :     glPixelStorei(GL_PACK_ALIGNMENT, texalign(hudw(), 3));
    2913            1 :     glReadPixels(0, 0, hudw(), hudh(), GL_RGB, GL_UNSIGNED_BYTE, image.data);
    2914            1 :     savepng(path(buf), image, true);
    2915            1 : }
    2916              : 
    2917              : //used in libprimis api, avoids having to provide entire Shader iface
    2918            0 : void setldrnotexture()
    2919              : {
    2920            0 :     ldrnotextureshader->set();
    2921            0 : }
    2922              : 
    2923            1 : void inittexturecmds()
    2924              : {
    2925            1 :     addcommand("texturereset", reinterpret_cast<identfun>(texturereset), "i", Id_Command);
    2926            1 :     addcommand("materialreset", reinterpret_cast<identfun>(materialreset), "", Id_Command);
    2927            1 :     addcommand("decalreset", reinterpret_cast<identfun>(decalreset), "i", Id_Command);
    2928            1 :     addcommand("compactvslots", reinterpret_cast<identfun>(+[](const int *cull)
    2929              :     {
    2930            1 :         multiplayerwarn();
    2931            1 :         rootworld.compactvslots(*cull!=0);
    2932            1 :         rootworld.allchanged();
    2933            1 :     }), "i", Id_Command);
    2934            1 :     addcommand("texture", reinterpret_cast<identfun>(texture), "ssiiif", Id_Command);
    2935            1 :     addcommand("texgrass", reinterpret_cast<identfun>(texgrass), "s", Id_Command);
    2936            1 :     addcommand("texscroll", reinterpret_cast<identfun>(texscroll), "ff", Id_Command);
    2937            1 :     addcommand("texoffset", reinterpret_cast<identfun>(texoffset_), "ii", Id_Command);
    2938            1 :     addcommand("texrotate", reinterpret_cast<identfun>(texrotate_), "i", Id_Command);
    2939            1 :     addcommand("texangle", reinterpret_cast<identfun>(texangle_), "f", Id_Command);
    2940            1 :     addcommand("texscale", reinterpret_cast<identfun>(texscale), "f", Id_Command);
    2941            1 :     addcommand("texalpha", reinterpret_cast<identfun>(texalpha), "ff", Id_Command);
    2942            1 :     addcommand("texcolor", reinterpret_cast<identfun>(texcolor), "fff", Id_Command);
    2943            1 :     addcommand("texrefract", reinterpret_cast<identfun>(texrefract), "ffff", Id_Command);
    2944            1 :     addcommand("texsmooth", reinterpret_cast<identfun>(texsmooth), "ib", Id_Command);
    2945            1 :     addcommand("decaldepth", reinterpret_cast<identfun>(decaldepth), "ff", Id_Command);
    2946            1 :     addcommand("reloadtex", reinterpret_cast<identfun>(reloadtex), "s", Id_Command);
    2947            1 :     addcommand("screenshot", reinterpret_cast<identfun>(screenshot), "s", Id_Command);
    2948            1 : }
    2949              : 
        

Generated by: LCOV version 2.0-1