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