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

Generated by: LCOV version 2.0-1