LCOV - code coverage report
Current view: top level - engine/render - texture.cpp (source / functions) Coverage Total Hit
Test: Libprimis Test Coverage Lines: 20.0 % 1502 301
Test Date: 2025-12-16 06:27:13 Functions: 38.7 % 119 46

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

Generated by: LCOV version 2.0-1