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