]> git.lizzy.rs Git - dragonfireclient.git/blob - src/client/tile.cpp
03badd8177fafff716f053b26c070e93eaf2a66c
[dragonfireclient.git] / src / client / tile.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "tile.h"
21
22 #include <ICameraSceneNode.h>
23 #include "util/string.h"
24 #include "util/container.h"
25 #include "util/thread.h"
26 #include "util/numeric.h"
27 #include "irrlichttypes_extrabloated.h"
28 #include "debug.h"
29 #include "filesys.h"
30 #include "settings.h"
31 #include "mesh.h"
32 #include "log.h"
33 #include "gamedef.h"
34 #include "strfnd.h"
35 #include "util/string.h" // for parseColorString()
36 #include "imagefilters.h"
37 #include "guiscalingfilter.h"
38 #include "nodedef.h"
39
40
41 #ifdef __ANDROID__
42 #include <GLES/gl.h>
43 #endif
44
45 /*
46         A cache from texture name to texture path
47 */
48 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
49
50 /*
51         Replaces the filename extension.
52         eg:
53                 std::string image = "a/image.png"
54                 replace_ext(image, "jpg")
55                 -> image = "a/image.jpg"
56         Returns true on success.
57 */
58 static bool replace_ext(std::string &path, const char *ext)
59 {
60         if (ext == NULL)
61                 return false;
62         // Find place of last dot, fail if \ or / found.
63         s32 last_dot_i = -1;
64         for (s32 i=path.size()-1; i>=0; i--)
65         {
66                 if (path[i] == '.')
67                 {
68                         last_dot_i = i;
69                         break;
70                 }
71
72                 if (path[i] == '\\' || path[i] == '/')
73                         break;
74         }
75         // If not found, return an empty string
76         if (last_dot_i == -1)
77                 return false;
78         // Else make the new path
79         path = path.substr(0, last_dot_i+1) + ext;
80         return true;
81 }
82
83 /*
84         Find out the full path of an image by trying different filename
85         extensions.
86
87         If failed, return "".
88 */
89 std::string getImagePath(std::string path)
90 {
91         // A NULL-ended list of possible image extensions
92         const char *extensions[] = {
93                 "png", "jpg", "bmp", "tga",
94                 "pcx", "ppm", "psd", "wal", "rgb",
95                 NULL
96         };
97         // If there is no extension, add one
98         if (removeStringEnd(path, extensions) == "")
99                 path = path + ".png";
100         // Check paths until something is found to exist
101         const char **ext = extensions;
102         do{
103                 bool r = replace_ext(path, *ext);
104                 if (r == false)
105                         return "";
106                 if (fs::PathExists(path))
107                         return path;
108         }
109         while((++ext) != NULL);
110
111         return "";
112 }
113
114 /*
115         Gets the path to a texture by first checking if the texture exists
116         in texture_path and if not, using the data path.
117
118         Checks all supported extensions by replacing the original extension.
119
120         If not found, returns "".
121
122         Utilizes a thread-safe cache.
123 */
124 std::string getTexturePath(const std::string &filename)
125 {
126         std::string fullpath = "";
127         /*
128                 Check from cache
129         */
130         bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
131         if (incache)
132                 return fullpath;
133
134         /*
135                 Check from texture_path
136         */
137         std::string texture_path = g_settings->get("texture_path");
138         if (texture_path != "")
139         {
140                 std::string testpath = texture_path + DIR_DELIM + filename;
141                 // Check all filename extensions. Returns "" if not found.
142                 fullpath = getImagePath(testpath);
143         }
144
145         /*
146                 Check from default data directory
147         */
148         if (fullpath == "")
149         {
150                 std::string base_path = porting::path_share + DIR_DELIM + "textures"
151                                 + DIR_DELIM + "base" + DIR_DELIM + "pack";
152                 std::string testpath = base_path + DIR_DELIM + filename;
153                 // Check all filename extensions. Returns "" if not found.
154                 fullpath = getImagePath(testpath);
155         }
156
157         // Add to cache (also an empty result is cached)
158         g_texturename_to_path_cache.set(filename, fullpath);
159
160         // Finally return it
161         return fullpath;
162 }
163
164 void clearTextureNameCache()
165 {
166         g_texturename_to_path_cache.clear();
167 }
168
169 /*
170         Stores internal information about a texture.
171 */
172
173 struct TextureInfo
174 {
175         std::string name;
176         video::ITexture *texture;
177
178         TextureInfo(
179                         const std::string &name_,
180                         video::ITexture *texture_=NULL
181                 ):
182                 name(name_),
183                 texture(texture_)
184         {
185         }
186 };
187
188 /*
189         SourceImageCache: A cache used for storing source images.
190 */
191
192 class SourceImageCache
193 {
194 public:
195         ~SourceImageCache() {
196                 for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
197                                 iter != m_images.end(); iter++) {
198                         iter->second->drop();
199                 }
200                 m_images.clear();
201         }
202         void insert(const std::string &name, video::IImage *img,
203                         bool prefer_local, video::IVideoDriver *driver)
204         {
205                 assert(img); // Pre-condition
206                 // Remove old image
207                 std::map<std::string, video::IImage*>::iterator n;
208                 n = m_images.find(name);
209                 if (n != m_images.end()){
210                         if (n->second)
211                                 n->second->drop();
212                 }
213
214                 video::IImage* toadd = img;
215                 bool need_to_grab = true;
216
217                 // Try to use local texture instead if asked to
218                 if (prefer_local){
219                         std::string path = getTexturePath(name);
220                         if (path != ""){
221                                 video::IImage *img2 = driver->createImageFromFile(path.c_str());
222                                 if (img2){
223                                         toadd = img2;
224                                         need_to_grab = false;
225                                 }
226                         }
227                 }
228
229                 if (need_to_grab)
230                         toadd->grab();
231                 m_images[name] = toadd;
232         }
233         video::IImage* get(const std::string &name)
234         {
235                 std::map<std::string, video::IImage*>::iterator n;
236                 n = m_images.find(name);
237                 if (n != m_images.end())
238                         return n->second;
239                 return NULL;
240         }
241         // Primarily fetches from cache, secondarily tries to read from filesystem
242         video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
243         {
244                 std::map<std::string, video::IImage*>::iterator n;
245                 n = m_images.find(name);
246                 if (n != m_images.end()){
247                         n->second->grab(); // Grab for caller
248                         return n->second;
249                 }
250                 video::IVideoDriver* driver = device->getVideoDriver();
251                 std::string path = getTexturePath(name);
252                 if (path == ""){
253                         infostream<<"SourceImageCache::getOrLoad(): No path found for \""
254                                         <<name<<"\""<<std::endl;
255                         return NULL;
256                 }
257                 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
258                                 <<"\""<<std::endl;
259                 video::IImage *img = driver->createImageFromFile(path.c_str());
260
261                 if (img){
262                         m_images[name] = img;
263                         img->grab(); // Grab for caller
264                 }
265                 return img;
266         }
267 private:
268         std::map<std::string, video::IImage*> m_images;
269 };
270
271 /*
272         TextureSource
273 */
274
275 class TextureSource : public IWritableTextureSource
276 {
277 public:
278         TextureSource(IrrlichtDevice *device);
279         virtual ~TextureSource();
280
281         /*
282                 Example case:
283                 Now, assume a texture with the id 1 exists, and has the name
284                 "stone.png^mineral1".
285                 Then a random thread calls getTextureId for a texture called
286                 "stone.png^mineral1^crack0".
287                 ...Now, WTF should happen? Well:
288                 - getTextureId strips off stuff recursively from the end until
289                   the remaining part is found, or nothing is left when
290                   something is stripped out
291
292                 But it is slow to search for textures by names and modify them
293                 like that?
294                 - ContentFeatures is made to contain ids for the basic plain
295                   textures
296                 - Crack textures can be slow by themselves, but the framework
297                   must be fast.
298
299                 Example case #2:
300                 - Assume a texture with the id 1 exists, and has the name
301                   "stone.png^mineral_coal.png".
302                 - Now getNodeTile() stumbles upon a node which uses
303                   texture id 1, and determines that MATERIAL_FLAG_CRACK
304                   must be applied to the tile
305                 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
306                   has received the current crack level 0 from the client. It
307                   finds out the name of the texture with getTextureName(1),
308                   appends "^crack0" to it and gets a new texture id with
309                   getTextureId("stone.png^mineral_coal.png^crack0").
310
311         */
312
313         /*
314                 Gets a texture id from cache or
315                 - if main thread, generates the texture, adds to cache and returns id.
316                 - if other thread, adds to request queue and waits for main thread.
317
318                 The id 0 points to a NULL texture. It is returned in case of error.
319         */
320         u32 getTextureId(const std::string &name);
321
322         // Finds out the name of a cached texture.
323         std::string getTextureName(u32 id);
324
325         /*
326                 If texture specified by the name pointed by the id doesn't
327                 exist, create it, then return the cached texture.
328
329                 Can be called from any thread. If called from some other thread
330                 and not found in cache, the call is queued to the main thread
331                 for processing.
332         */
333         video::ITexture* getTexture(u32 id);
334
335         video::ITexture* getTexture(const std::string &name, u32 *id = NULL);
336
337         /*
338                 Get a texture specifically intended for mesh
339                 application, i.e. not HUD, compositing, or other 2D
340                 use.  This texture may be a different size and may
341                 have had additional filters applied.
342         */
343         video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
344
345         // Returns a pointer to the irrlicht device
346         virtual IrrlichtDevice* getDevice()
347         {
348                 return m_device;
349         }
350
351         bool isKnownSourceImage(const std::string &name)
352         {
353                 bool is_known = false;
354                 bool cache_found = m_source_image_existence.get(name, &is_known);
355                 if (cache_found)
356                         return is_known;
357                 // Not found in cache; find out if a local file exists
358                 is_known = (getTexturePath(name) != "");
359                 m_source_image_existence.set(name, is_known);
360                 return is_known;
361         }
362
363         // Processes queued texture requests from other threads.
364         // Shall be called from the main thread.
365         void processQueue();
366
367         // Insert an image into the cache without touching the filesystem.
368         // Shall be called from the main thread.
369         void insertSourceImage(const std::string &name, video::IImage *img);
370
371         // Rebuild images and textures from the current set of source images
372         // Shall be called from the main thread.
373         void rebuildImagesAndTextures();
374
375         // Render a mesh to a texture.
376         // Returns NULL if render-to-texture failed.
377         // Shall be called from the main thread.
378         video::ITexture* generateTextureFromMesh(
379                         const TextureFromMeshParams &params);
380
381         // Generates an image from a full string like
382         // "stone.png^mineral_coal.png^[crack:1:0".
383         // Shall be called from the main thread.
384         video::IImage* generateImage(const std::string &name);
385
386         video::ITexture* getNormalTexture(const std::string &name);
387         video::SColor getTextureAverageColor(const std::string &name);
388         video::ITexture *getShaderFlagsTexture(
389                 bool normamap_present, bool tileable_vertical, bool tileable_horizontal);
390
391 private:
392
393         // The id of the thread that is allowed to use irrlicht directly
394         threadid_t m_main_thread;
395         // The irrlicht device
396         IrrlichtDevice *m_device;
397
398         // Cache of source images
399         // This should be only accessed from the main thread
400         SourceImageCache m_sourcecache;
401
402         // Generate a texture
403         u32 generateTexture(const std::string &name);
404
405         // Generate image based on a string like "stone.png" or "[crack:1:0".
406         // if baseimg is NULL, it is created. Otherwise stuff is made on it.
407         bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
408
409         // Thread-safe cache of what source images are known (true = known)
410         MutexedMap<std::string, bool> m_source_image_existence;
411
412         // A texture id is index in this array.
413         // The first position contains a NULL texture.
414         std::vector<TextureInfo> m_textureinfo_cache;
415         // Maps a texture name to an index in the former.
416         std::map<std::string, u32> m_name_to_id;
417         // The two former containers are behind this mutex
418         JMutex m_textureinfo_cache_mutex;
419
420         // Queued texture fetches (to be processed by the main thread)
421         RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
422
423         // Textures that have been overwritten with other ones
424         // but can't be deleted because the ITexture* might still be used
425         std::vector<video::ITexture*> m_texture_trash;
426
427         // Cached settings needed for making textures from meshes
428         bool m_setting_trilinear_filter;
429         bool m_setting_bilinear_filter;
430         bool m_setting_anisotropic_filter;
431 };
432
433 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
434 {
435         return new TextureSource(device);
436 }
437
438 TextureSource::TextureSource(IrrlichtDevice *device):
439                 m_device(device)
440 {
441         assert(m_device); // Pre-condition
442
443         m_main_thread = get_current_thread_id();
444
445         // Add a NULL TextureInfo as the first index, named ""
446         m_textureinfo_cache.push_back(TextureInfo(""));
447         m_name_to_id[""] = 0;
448
449         // Cache some settings
450         // Note: Since this is only done once, the game must be restarted
451         // for these settings to take effect
452         m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
453         m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
454         m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
455 }
456
457 TextureSource::~TextureSource()
458 {
459         video::IVideoDriver* driver = m_device->getVideoDriver();
460
461         unsigned int textures_before = driver->getTextureCount();
462
463         for (std::vector<TextureInfo>::iterator iter =
464                         m_textureinfo_cache.begin();
465                         iter != m_textureinfo_cache.end(); iter++)
466         {
467                 //cleanup texture
468                 if (iter->texture)
469                         driver->removeTexture(iter->texture);
470         }
471         m_textureinfo_cache.clear();
472
473         for (std::vector<video::ITexture*>::iterator iter =
474                         m_texture_trash.begin(); iter != m_texture_trash.end();
475                         iter++) {
476                 video::ITexture *t = *iter;
477
478                 //cleanup trashed texture
479                 driver->removeTexture(t);
480         }
481
482         infostream << "~TextureSource() "<< textures_before << "/"
483                         << driver->getTextureCount() << std::endl;
484 }
485
486 u32 TextureSource::getTextureId(const std::string &name)
487 {
488         //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
489
490         {
491                 /*
492                         See if texture already exists
493                 */
494                 JMutexAutoLock lock(m_textureinfo_cache_mutex);
495                 std::map<std::string, u32>::iterator n;
496                 n = m_name_to_id.find(name);
497                 if (n != m_name_to_id.end())
498                 {
499                         return n->second;
500                 }
501         }
502
503         /*
504                 Get texture
505         */
506         if (get_current_thread_id() == m_main_thread)
507         {
508                 return generateTexture(name);
509         }
510         else
511         {
512                 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
513
514                 // We're gonna ask the result to be put into here
515                 static ResultQueue<std::string, u32, u8, u8> result_queue;
516
517                 // Throw a request in
518                 m_get_texture_queue.add(name, 0, 0, &result_queue);
519
520                 /*infostream<<"Waiting for texture from main thread, name=\""
521                                 <<name<<"\""<<std::endl;*/
522
523                 try
524                 {
525                         while(true) {
526                                 // Wait result for a second
527                                 GetResult<std::string, u32, u8, u8>
528                                         result = result_queue.pop_front(1000);
529
530                                 if (result.key == name) {
531                                         return result.item;
532                                 }
533                         }
534                 }
535                 catch(ItemNotFoundException &e)
536                 {
537                         errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
538                         return 0;
539                 }
540         }
541
542         infostream<<"getTextureId(): Failed"<<std::endl;
543
544         return 0;
545 }
546
547 // Draw an image on top of an another one, using the alpha channel of the
548 // source image
549 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
550                 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
551
552 // Like blit_with_alpha, but only modifies destination pixels that
553 // are fully opaque
554 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
555                 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
556
557 // Like blit_with_alpha overlay, but uses an int to calculate the ratio
558 // and modifies any destination pixels that are not fully transparent
559 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
560                 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio);
561
562 // Apply a mask to an image
563 static void apply_mask(video::IImage *mask, video::IImage *dst,
564                 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
565
566 // Draw or overlay a crack
567 static void draw_crack(video::IImage *crack, video::IImage *dst,
568                 bool use_overlay, s32 frame_count, s32 progression,
569                 video::IVideoDriver *driver);
570
571 // Brighten image
572 void brighten(video::IImage *image);
573 // Parse a transform name
574 u32 parseImageTransform(const std::string& s);
575 // Apply transform to image dimension
576 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
577 // Apply transform to image data
578 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
579
580 /*
581         This method generates all the textures
582 */
583 u32 TextureSource::generateTexture(const std::string &name)
584 {
585         //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
586
587         // Empty name means texture 0
588         if (name == "") {
589                 infostream<<"generateTexture(): name is empty"<<std::endl;
590                 return 0;
591         }
592
593         {
594                 /*
595                         See if texture already exists
596                 */
597                 JMutexAutoLock lock(m_textureinfo_cache_mutex);
598                 std::map<std::string, u32>::iterator n;
599                 n = m_name_to_id.find(name);
600                 if (n != m_name_to_id.end()) {
601                         return n->second;
602                 }
603         }
604
605         /*
606                 Calling only allowed from main thread
607         */
608         if (get_current_thread_id() != m_main_thread) {
609                 errorstream<<"TextureSource::generateTexture() "
610                                 "called not from main thread"<<std::endl;
611                 return 0;
612         }
613
614         video::IVideoDriver *driver = m_device->getVideoDriver();
615         sanity_check(driver);
616
617         video::IImage *img = generateImage(name);
618
619         video::ITexture *tex = NULL;
620
621         if (img != NULL) {
622 #ifdef __ANDROID__
623                 img = Align2Npot2(img, driver);
624 #endif
625                 // Create texture from resulting image
626                 tex = driver->addTexture(name.c_str(), img);
627                 guiScalingCache(io::path(name.c_str()), driver, img);
628                 img->drop();
629         }
630
631         /*
632                 Add texture to caches (add NULL textures too)
633         */
634
635         JMutexAutoLock lock(m_textureinfo_cache_mutex);
636
637         u32 id = m_textureinfo_cache.size();
638         TextureInfo ti(name, tex);
639         m_textureinfo_cache.push_back(ti);
640         m_name_to_id[name] = id;
641
642         return id;
643 }
644
645 std::string TextureSource::getTextureName(u32 id)
646 {
647         JMutexAutoLock lock(m_textureinfo_cache_mutex);
648
649         if (id >= m_textureinfo_cache.size())
650         {
651                 errorstream<<"TextureSource::getTextureName(): id="<<id
652                                 <<" >= m_textureinfo_cache.size()="
653                                 <<m_textureinfo_cache.size()<<std::endl;
654                 return "";
655         }
656
657         return m_textureinfo_cache[id].name;
658 }
659
660 video::ITexture* TextureSource::getTexture(u32 id)
661 {
662         JMutexAutoLock lock(m_textureinfo_cache_mutex);
663
664         if (id >= m_textureinfo_cache.size())
665                 return NULL;
666
667         return m_textureinfo_cache[id].texture;
668 }
669
670 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
671 {
672         u32 actual_id = getTextureId(name);
673         if (id){
674                 *id = actual_id;
675         }
676         return getTexture(actual_id);
677 }
678
679 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
680 {
681         return getTexture(name + "^[applyfiltersformesh", id);
682 }
683
684 void TextureSource::processQueue()
685 {
686         /*
687                 Fetch textures
688         */
689         //NOTE this is only thread safe for ONE consumer thread!
690         if (!m_get_texture_queue.empty())
691         {
692                 GetRequest<std::string, u32, u8, u8>
693                                 request = m_get_texture_queue.pop();
694
695                 /*infostream<<"TextureSource::processQueue(): "
696                                 <<"got texture request with "
697                                 <<"name=\""<<request.key<<"\""
698                                 <<std::endl;*/
699
700                 m_get_texture_queue.pushResult(request, generateTexture(request.key));
701         }
702 }
703
704 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
705 {
706         //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
707
708         sanity_check(get_current_thread_id() == m_main_thread);
709
710         m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
711         m_source_image_existence.set(name, true);
712 }
713
714 void TextureSource::rebuildImagesAndTextures()
715 {
716         JMutexAutoLock lock(m_textureinfo_cache_mutex);
717
718         video::IVideoDriver* driver = m_device->getVideoDriver();
719         sanity_check(driver);
720
721         // Recreate textures
722         for (u32 i=0; i<m_textureinfo_cache.size(); i++){
723                 TextureInfo *ti = &m_textureinfo_cache[i];
724                 video::IImage *img = generateImage(ti->name);
725 #ifdef __ANDROID__
726                 img = Align2Npot2(img, driver);
727                 sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
728                 sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
729 #endif
730                 // Create texture from resulting image
731                 video::ITexture *t = NULL;
732                 if (img) {
733                         t = driver->addTexture(ti->name.c_str(), img);
734                         guiScalingCache(io::path(ti->name.c_str()), driver, img);
735                         img->drop();
736                 }
737                 video::ITexture *t_old = ti->texture;
738                 // Replace texture
739                 ti->texture = t;
740
741                 if (t_old)
742                         m_texture_trash.push_back(t_old);
743         }
744 }
745
746 video::ITexture* TextureSource::generateTextureFromMesh(
747                 const TextureFromMeshParams &params)
748 {
749         video::IVideoDriver *driver = m_device->getVideoDriver();
750         sanity_check(driver);
751
752 #ifdef __ANDROID__
753         const GLubyte* renderstr = glGetString(GL_RENDERER);
754         std::string renderer((char*) renderstr);
755
756         // use no render to texture hack
757         if (
758                 (renderer.find("Adreno") != std::string::npos) ||
759                 (renderer.find("Mali") != std::string::npos) ||
760                 (renderer.find("Immersion") != std::string::npos) ||
761                 (renderer.find("Tegra") != std::string::npos) ||
762                 g_settings->getBool("inventory_image_hack")
763                 ) {
764                 // Get a scene manager
765                 scene::ISceneManager *smgr_main = m_device->getSceneManager();
766                 sanity_check(smgr_main);
767                 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
768                 sanity_check(smgr);
769
770                 const float scaling = 0.2;
771
772                 scene::IMeshSceneNode* meshnode =
773                                 smgr->addMeshSceneNode(params.mesh, NULL,
774                                                 -1, v3f(0,0,0), v3f(0,0,0),
775                                                 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
776                 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
777                 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
778                 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
779                 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
780                 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
781
782                 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
783                                 params.camera_position, params.camera_lookat);
784                 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
785                 camera->setProjectionMatrix(params.camera_projection_matrix, false);
786
787                 smgr->setAmbientLight(params.ambient_light);
788                 smgr->addLightSceneNode(0,
789                                 params.light_position,
790                                 params.light_color,
791                                 params.light_radius*scaling);
792
793                 core::dimension2d<u32> screen = driver->getScreenSize();
794
795                 // Render scene
796                 driver->beginScene(true, true, video::SColor(0,0,0,0));
797                 driver->clearZBuffer();
798                 smgr->drawAll();
799
800                 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
801
802                 irr::video::IImage* rawImage =
803                                 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
804
805                 u8* pixels = static_cast<u8*>(rawImage->lock());
806                 if (!pixels)
807                 {
808                         rawImage->drop();
809                         return NULL;
810                 }
811
812                 core::rect<s32> source(
813                                 screen.Width /2 - (screen.Width  * (scaling / 2)),
814                                 screen.Height/2 - (screen.Height * (scaling / 2)),
815                                 screen.Width /2 + (screen.Width  * (scaling / 2)),
816                                 screen.Height/2 + (screen.Height * (scaling / 2))
817                         );
818
819                 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
820                                 partsize.Width, partsize.Height, GL_RGBA,
821                                 GL_UNSIGNED_BYTE, pixels);
822
823                 driver->endScene();
824
825                 // Drop scene manager
826                 smgr->drop();
827
828                 unsigned int pixelcount = partsize.Width*partsize.Height;
829
830                 u8* runptr = pixels;
831                 for (unsigned int i=0; i < pixelcount; i++) {
832
833                         u8 B = *runptr;
834                         u8 G = *(runptr+1);
835                         u8 R = *(runptr+2);
836                         u8 A = *(runptr+3);
837
838                         //BGRA -> RGBA
839                         *runptr = R;
840                         runptr ++;
841                         *runptr = G;
842                         runptr ++;
843                         *runptr = B;
844                         runptr ++;
845                         *runptr = A;
846                         runptr ++;
847                 }
848
849                 video::IImage* inventory_image =
850                                 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
851
852                 rawImage->copyToScaling(inventory_image);
853                 rawImage->drop();
854
855                 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
856
857                 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
858                 inventory_image->drop();
859
860                 if (rtt == NULL) {
861                         errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
862                         return NULL;
863                 }
864
865                 driver->makeColorKeyTexture(rtt, v2s32(0,0));
866
867                 if (params.delete_texture_on_shutdown)
868                         m_texture_trash.push_back(rtt);
869
870                 return rtt;
871         }
872 #endif
873
874         if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
875         {
876                 static bool warned = false;
877                 if (!warned)
878                 {
879                         errorstream<<"TextureSource::generateTextureFromMesh(): "
880                                 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
881                         warned = true;
882                 }
883                 return NULL;
884         }
885
886         // Create render target texture
887         video::ITexture *rtt = driver->addRenderTargetTexture(
888                         params.dim, params.rtt_texture_name.c_str(),
889                         video::ECF_A8R8G8B8);
890         if (rtt == NULL)
891         {
892                 errorstream<<"TextureSource::generateTextureFromMesh(): "
893                         <<"addRenderTargetTexture returned NULL."<<std::endl;
894                 return NULL;
895         }
896
897         // Set render target
898         if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
899                 driver->removeTexture(rtt);
900                 errorstream<<"TextureSource::generateTextureFromMesh(): "
901                         <<"failed to set render target"<<std::endl;
902                 return NULL;
903         }
904
905         // Get a scene manager
906         scene::ISceneManager *smgr_main = m_device->getSceneManager();
907         assert(smgr_main);
908         scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
909         assert(smgr);
910
911         scene::IMeshSceneNode* meshnode =
912                         smgr->addMeshSceneNode(params.mesh, NULL,
913                                         -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
914         meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
915         meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
916         meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
917         meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
918         meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
919
920         scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
921                         params.camera_position, params.camera_lookat);
922         // second parameter of setProjectionMatrix (isOrthogonal) is ignored
923         camera->setProjectionMatrix(params.camera_projection_matrix, false);
924
925         smgr->setAmbientLight(params.ambient_light);
926         smgr->addLightSceneNode(0,
927                         params.light_position,
928                         params.light_color,
929                         params.light_radius);
930
931         // Render scene
932         driver->beginScene(true, true, video::SColor(0,0,0,0));
933         smgr->drawAll();
934         driver->endScene();
935
936         // Drop scene manager
937         smgr->drop();
938
939         // Unset render target
940         driver->setRenderTarget(0, false, true, 0);
941
942         if (params.delete_texture_on_shutdown)
943                 m_texture_trash.push_back(rtt);
944
945         return rtt;
946 }
947
948 video::IImage* TextureSource::generateImage(const std::string &name)
949 {
950         /*
951                 Get the base image
952         */
953
954         const char separator = '^';
955         const char paren_open = '(';
956         const char paren_close = ')';
957
958         // Find last separator in the name
959         s32 last_separator_pos = -1;
960         u8 paren_bal = 0;
961         for (s32 i = name.size() - 1; i >= 0; i--) {
962                 switch(name[i]) {
963                 case separator:
964                         if (paren_bal == 0) {
965                                 last_separator_pos = i;
966                                 i = -1; // break out of loop
967                         }
968                         break;
969                 case paren_open:
970                         if (paren_bal == 0) {
971                                 errorstream << "generateImage(): unbalanced parentheses"
972                                                 << "(extranous '(') while generating texture \""
973                                                 << name << "\"" << std::endl;
974                                 return NULL;
975                         }
976                         paren_bal--;
977                         break;
978                 case paren_close:
979                         paren_bal++;
980                         break;
981                 default:
982                         break;
983                 }
984         }
985         if (paren_bal > 0) {
986                 errorstream << "generateImage(): unbalanced parentheses"
987                                 << "(missing matching '(') while generating texture \""
988                                 << name << "\"" << std::endl;
989                 return NULL;
990         }
991
992
993         video::IImage *baseimg = NULL;
994
995         /*
996                 If separator was found, make the base image
997                 using a recursive call.
998         */
999         if (last_separator_pos != -1) {
1000                 baseimg = generateImage(name.substr(0, last_separator_pos));
1001         }
1002
1003
1004         video::IVideoDriver* driver = m_device->getVideoDriver();
1005         sanity_check(driver);
1006
1007         /*
1008                 Parse out the last part of the name of the image and act
1009                 according to it
1010         */
1011
1012         std::string last_part_of_name = name.substr(last_separator_pos + 1);
1013
1014         /* 
1015                 If this name is enclosed in parentheses, generate it
1016                 and blit it onto the base image
1017         */
1018         if (last_part_of_name[0] == paren_open
1019                         && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1020                 std::string name2 = last_part_of_name.substr(1,
1021                                 last_part_of_name.size() - 2);
1022                 video::IImage *tmp = generateImage(name2);
1023                 if (!tmp) {
1024                         errorstream << "generateImage(): "
1025                                 "Failed to generate \"" << name2 << "\""
1026                                 << std::endl;
1027                         return NULL;
1028                 }
1029                 core::dimension2d<u32> dim = tmp->getDimension();
1030                 if (!baseimg)
1031                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1032                 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1033                 tmp->drop();
1034         } else if (!generateImagePart(last_part_of_name, baseimg)) {
1035                 // Generate image according to part of name
1036                 errorstream << "generateImage(): "
1037                                 "Failed to generate \"" << last_part_of_name << "\""
1038                                 << std::endl;
1039         }
1040
1041         // If no resulting image, print a warning
1042         if (baseimg == NULL) {
1043                 errorstream << "generateImage(): baseimg is NULL (attempted to"
1044                                 " create texture \"" << name << "\")" << std::endl;
1045         }
1046
1047         return baseimg;
1048 }
1049
1050 #ifdef __ANDROID__
1051 #include <GLES/gl.h>
1052 /**
1053  * Check and align image to npot2 if required by hardware
1054  * @param image image to check for npot2 alignment
1055  * @param driver driver to use for image operations
1056  * @return image or copy of image aligned to npot2
1057  */
1058 video::IImage * Align2Npot2(video::IImage * image,
1059                 video::IVideoDriver* driver)
1060 {
1061         if (image == NULL) {
1062                 return image;
1063         }
1064
1065         core::dimension2d<u32> dim = image->getDimension();
1066
1067         std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1068         if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1069                 return image;
1070         }
1071
1072         unsigned int height = npot2(dim.Height);
1073         unsigned int width  = npot2(dim.Width);
1074
1075         if ((dim.Height == height) &&
1076                         (dim.Width == width)) {
1077                 return image;
1078         }
1079
1080         if (dim.Height > height) {
1081                 height *= 2;
1082         }
1083
1084         if (dim.Width > width) {
1085                 width *= 2;
1086         }
1087
1088         video::IImage *targetimage =
1089                         driver->createImage(video::ECF_A8R8G8B8,
1090                                         core::dimension2d<u32>(width, height));
1091
1092         if (targetimage != NULL) {
1093                 image->copyToScaling(targetimage);
1094         }
1095         image->drop();
1096         return targetimage;
1097 }
1098
1099 #endif
1100
1101 bool TextureSource::generateImagePart(std::string part_of_name,
1102                 video::IImage *& baseimg)
1103 {
1104         video::IVideoDriver* driver = m_device->getVideoDriver();
1105         sanity_check(driver);
1106
1107         // Stuff starting with [ are special commands
1108         if (part_of_name.size() == 0 || part_of_name[0] != '[')
1109         {
1110                 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1111 #ifdef __ANDROID__
1112                 image = Align2Npot2(image, driver);
1113 #endif
1114                 if (image == NULL) {
1115                         if (part_of_name != "") {
1116                                 if (part_of_name.find("_normal.png") == std::string::npos){
1117                                         errorstream<<"generateImage(): Could not load image \""
1118                                                 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1119                                         errorstream<<"generateImage(): Creating a dummy"
1120                                                 <<" image for \""<<part_of_name<<"\""<<std::endl;
1121                                 } else {
1122                                         infostream<<"generateImage(): Could not load normal map \""
1123                                                 <<part_of_name<<"\""<<std::endl;
1124                                         infostream<<"generateImage(): Creating a dummy"
1125                                                 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1126                                 }
1127                         }
1128
1129                         // Just create a dummy image
1130                         //core::dimension2d<u32> dim(2,2);
1131                         core::dimension2d<u32> dim(1,1);
1132                         image = driver->createImage(video::ECF_A8R8G8B8, dim);
1133                         sanity_check(image != NULL);
1134                         /*image->setPixel(0,0, video::SColor(255,255,0,0));
1135                         image->setPixel(1,0, video::SColor(255,0,255,0));
1136                         image->setPixel(0,1, video::SColor(255,0,0,255));
1137                         image->setPixel(1,1, video::SColor(255,255,0,255));*/
1138                         image->setPixel(0,0, video::SColor(255,myrand()%256,
1139                                         myrand()%256,myrand()%256));
1140                         /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1141                                         myrand()%256,myrand()%256));
1142                         image->setPixel(0,1, video::SColor(255,myrand()%256,
1143                                         myrand()%256,myrand()%256));
1144                         image->setPixel(1,1, video::SColor(255,myrand()%256,
1145                                         myrand()%256,myrand()%256));*/
1146                 }
1147
1148                 // If base image is NULL, load as base.
1149                 if (baseimg == NULL)
1150                 {
1151                         //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1152                         /*
1153                                 Copy it this way to get an alpha channel.
1154                                 Otherwise images with alpha cannot be blitted on
1155                                 images that don't have alpha in the original file.
1156                         */
1157                         core::dimension2d<u32> dim = image->getDimension();
1158                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1159                         image->copyTo(baseimg);
1160                 }
1161                 // Else blit on base.
1162                 else
1163                 {
1164                         //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1165                         // Size of the copied area
1166                         core::dimension2d<u32> dim = image->getDimension();
1167                         //core::dimension2d<u32> dim(16,16);
1168                         // Position to copy the blitted to in the base image
1169                         core::position2d<s32> pos_to(0,0);
1170                         // Position to copy the blitted from in the blitted image
1171                         core::position2d<s32> pos_from(0,0);
1172                         // Blit
1173                         /*image->copyToWithAlpha(baseimg, pos_to,
1174                                         core::rect<s32>(pos_from, dim),
1175                                         video::SColor(255,255,255,255),
1176                                         NULL);*/
1177                         blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1178                 }
1179                 //cleanup
1180                 image->drop();
1181         }
1182         else
1183         {
1184                 // A special texture modification
1185
1186                 /*infostream<<"generateImage(): generating special "
1187                                 <<"modification \""<<part_of_name<<"\""
1188                                 <<std::endl;*/
1189
1190                 /*
1191                         [crack:N:P
1192                         [cracko:N:P
1193                         Adds a cracking texture
1194                         N = animation frame count, P = crack progression
1195                 */
1196                 if (str_starts_with(part_of_name, "[crack"))
1197                 {
1198                         if (baseimg == NULL) {
1199                                 errorstream<<"generateImagePart(): baseimg == NULL "
1200                                                 <<"for part_of_name=\""<<part_of_name
1201                                                 <<"\", cancelling."<<std::endl;
1202                                 return false;
1203                         }
1204
1205                         // Crack image number and overlay option
1206                         bool use_overlay = (part_of_name[6] == 'o');
1207                         Strfnd sf(part_of_name);
1208                         sf.next(":");
1209                         s32 frame_count = stoi(sf.next(":"));
1210                         s32 progression = stoi(sf.next(":"));
1211
1212                         /*
1213                                 Load crack image.
1214
1215                                 It is an image with a number of cracking stages
1216                                 horizontally tiled.
1217                         */
1218                         video::IImage *img_crack = m_sourcecache.getOrLoad(
1219                                         "crack_anylength.png", m_device);
1220
1221                         if (img_crack && progression >= 0)
1222                         {
1223                                 draw_crack(img_crack, baseimg,
1224                                                 use_overlay, frame_count,
1225                                                 progression, driver);
1226                                 img_crack->drop();
1227                         }
1228                 }
1229                 /*
1230                         [combine:WxH:X,Y=filename:X,Y=filename2
1231                         Creates a bigger texture from an amount of smaller ones
1232                 */
1233                 else if (str_starts_with(part_of_name, "[combine"))
1234                 {
1235                         Strfnd sf(part_of_name);
1236                         sf.next(":");
1237                         u32 w0 = stoi(sf.next("x"));
1238                         u32 h0 = stoi(sf.next(":"));
1239                         //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1240                         core::dimension2d<u32> dim(w0,h0);
1241                         if (baseimg == NULL) {
1242                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1243                                 baseimg->fill(video::SColor(0,0,0,0));
1244                         }
1245                         while (sf.atend() == false) {
1246                                 u32 x = stoi(sf.next(","));
1247                                 u32 y = stoi(sf.next("="));
1248                                 std::string filename = sf.next(":");
1249                                 infostream<<"Adding \""<<filename
1250                                                 <<"\" to combined ("<<x<<","<<y<<")"
1251                                                 <<std::endl;
1252                                 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1253                                 if (img) {
1254                                         core::dimension2d<u32> dim = img->getDimension();
1255                                         infostream<<"Size "<<dim.Width
1256                                                         <<"x"<<dim.Height<<std::endl;
1257                                         core::position2d<s32> pos_base(x, y);
1258                                         video::IImage *img2 =
1259                                                         driver->createImage(video::ECF_A8R8G8B8, dim);
1260                                         img->copyTo(img2);
1261                                         img->drop();
1262                                         /*img2->copyToWithAlpha(baseimg, pos_base,
1263                                                         core::rect<s32>(v2s32(0,0), dim),
1264                                                         video::SColor(255,255,255,255),
1265                                                         NULL);*/
1266                                         blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1267                                         img2->drop();
1268                                 } else {
1269                                         errorstream << "generateImagePart(): Failed to load image \""
1270                                                 << filename << "\" for [combine" << std::endl;
1271                                 }
1272                         }
1273                 }
1274                 /*
1275                         "[brighten"
1276                 */
1277                 else if (str_starts_with(part_of_name, "[brighten"))
1278                 {
1279                         if (baseimg == NULL) {
1280                                 errorstream<<"generateImagePart(): baseimg==NULL "
1281                                                 <<"for part_of_name=\""<<part_of_name
1282                                                 <<"\", cancelling."<<std::endl;
1283                                 return false;
1284                         }
1285
1286                         brighten(baseimg);
1287                 }
1288                 /*
1289                         "[noalpha"
1290                         Make image completely opaque.
1291                         Used for the leaves texture when in old leaves mode, so
1292                         that the transparent parts don't look completely black
1293                         when simple alpha channel is used for rendering.
1294                 */
1295                 else if (str_starts_with(part_of_name, "[noalpha"))
1296                 {
1297                         if (baseimg == NULL){
1298                                 errorstream<<"generateImagePart(): baseimg==NULL "
1299                                                 <<"for part_of_name=\""<<part_of_name
1300                                                 <<"\", cancelling."<<std::endl;
1301                                 return false;
1302                         }
1303
1304                         core::dimension2d<u32> dim = baseimg->getDimension();
1305
1306                         // Set alpha to full
1307                         for (u32 y=0; y<dim.Height; y++)
1308                         for (u32 x=0; x<dim.Width; x++)
1309                         {
1310                                 video::SColor c = baseimg->getPixel(x,y);
1311                                 c.setAlpha(255);
1312                                 baseimg->setPixel(x,y,c);
1313                         }
1314                 }
1315                 /*
1316                         "[makealpha:R,G,B"
1317                         Convert one color to transparent.
1318                 */
1319                 else if (str_starts_with(part_of_name, "[makealpha:"))
1320                 {
1321                         if (baseimg == NULL) {
1322                                 errorstream<<"generateImagePart(): baseimg == NULL "
1323                                                 <<"for part_of_name=\""<<part_of_name
1324                                                 <<"\", cancelling."<<std::endl;
1325                                 return false;
1326                         }
1327
1328                         Strfnd sf(part_of_name.substr(11));
1329                         u32 r1 = stoi(sf.next(","));
1330                         u32 g1 = stoi(sf.next(","));
1331                         u32 b1 = stoi(sf.next(""));
1332                         std::string filename = sf.next("");
1333
1334                         core::dimension2d<u32> dim = baseimg->getDimension();
1335
1336                         /*video::IImage *oldbaseimg = baseimg;
1337                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1338                         oldbaseimg->copyTo(baseimg);
1339                         oldbaseimg->drop();*/
1340
1341                         // Set alpha to full
1342                         for (u32 y=0; y<dim.Height; y++)
1343                         for (u32 x=0; x<dim.Width; x++)
1344                         {
1345                                 video::SColor c = baseimg->getPixel(x,y);
1346                                 u32 r = c.getRed();
1347                                 u32 g = c.getGreen();
1348                                 u32 b = c.getBlue();
1349                                 if (!(r == r1 && g == g1 && b == b1))
1350                                         continue;
1351                                 c.setAlpha(0);
1352                                 baseimg->setPixel(x,y,c);
1353                         }
1354                 }
1355                 /*
1356                         "[transformN"
1357                         Rotates and/or flips the image.
1358
1359                         N can be a number (between 0 and 7) or a transform name.
1360                         Rotations are counter-clockwise.
1361                         0  I      identity
1362                         1  R90    rotate by 90 degrees
1363                         2  R180   rotate by 180 degrees
1364                         3  R270   rotate by 270 degrees
1365                         4  FX     flip X
1366                         5  FXR90  flip X then rotate by 90 degrees
1367                         6  FY     flip Y
1368                         7  FYR90  flip Y then rotate by 90 degrees
1369
1370                         Note: Transform names can be concatenated to produce
1371                         their product (applies the first then the second).
1372                         The resulting transform will be equivalent to one of the
1373                         eight existing ones, though (see: dihedral group).
1374                 */
1375                 else if (str_starts_with(part_of_name, "[transform"))
1376                 {
1377                         if (baseimg == NULL) {
1378                                 errorstream<<"generateImagePart(): baseimg == NULL "
1379                                                 <<"for part_of_name=\""<<part_of_name
1380                                                 <<"\", cancelling."<<std::endl;
1381                                 return false;
1382                         }
1383
1384                         u32 transform = parseImageTransform(part_of_name.substr(10));
1385                         core::dimension2d<u32> dim = imageTransformDimension(
1386                                         transform, baseimg->getDimension());
1387                         video::IImage *image = driver->createImage(
1388                                         baseimg->getColorFormat(), dim);
1389                         sanity_check(image != NULL);
1390                         imageTransform(transform, baseimg, image);
1391                         baseimg->drop();
1392                         baseimg = image;
1393                 }
1394                 /*
1395                         [inventorycube{topimage{leftimage{rightimage
1396                         In every subimage, replace ^ with &.
1397                         Create an "inventory cube".
1398                         NOTE: This should be used only on its own.
1399                         Example (a grass block (not actually used in game):
1400                         "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1401                 */
1402                 else if (str_starts_with(part_of_name, "[inventorycube"))
1403                 {
1404                         if (baseimg != NULL){
1405                                 errorstream<<"generateImagePart(): baseimg != NULL "
1406                                                 <<"for part_of_name=\""<<part_of_name
1407                                                 <<"\", cancelling."<<std::endl;
1408                                 return false;
1409                         }
1410
1411                         str_replace(part_of_name, '&', '^');
1412                         Strfnd sf(part_of_name);
1413                         sf.next("{");
1414                         std::string imagename_top = sf.next("{");
1415                         std::string imagename_left = sf.next("{");
1416                         std::string imagename_right = sf.next("{");
1417
1418                         // Generate images for the faces of the cube
1419                         video::IImage *img_top = generateImage(imagename_top);
1420                         video::IImage *img_left = generateImage(imagename_left);
1421                         video::IImage *img_right = generateImage(imagename_right);
1422
1423                         if (img_top == NULL || img_left == NULL || img_right == NULL) {
1424                                 errorstream << "generateImagePart(): Failed to create textures"
1425                                                 << " for inventorycube \"" << part_of_name << "\""
1426                                                 << std::endl;
1427                                 baseimg = generateImage(imagename_top);
1428                                 return true;
1429                         }
1430
1431 #ifdef __ANDROID__
1432                         assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1433                         assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1434
1435                         assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1436                         assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1437
1438                         assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1439                         assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1440 #endif
1441
1442                         // Create textures from images
1443                         video::ITexture *texture_top = driver->addTexture(
1444                                         (imagename_top + "__temp__").c_str(), img_top);
1445                         video::ITexture *texture_left = driver->addTexture(
1446                                         (imagename_left + "__temp__").c_str(), img_left);
1447                         video::ITexture *texture_right = driver->addTexture(
1448                                         (imagename_right + "__temp__").c_str(), img_right);
1449                         FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1450
1451                         // Drop images
1452                         img_top->drop();
1453                         img_left->drop();
1454                         img_right->drop();
1455
1456                         /*
1457                                 Draw a cube mesh into a render target texture
1458                         */
1459                         scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1460                         setMeshColor(cube, video::SColor(255, 255, 255, 255));
1461                         cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1462                         cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1463                         cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1464                         cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1465                         cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1466                         cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1467
1468                         TextureFromMeshParams params;
1469                         params.mesh = cube;
1470                         params.dim.set(64, 64);
1471                         params.rtt_texture_name = part_of_name + "_RTT";
1472                         // We will delete the rtt texture ourselves
1473                         params.delete_texture_on_shutdown = false;
1474                         params.camera_position.set(0, 1.0, -1.5);
1475                         params.camera_position.rotateXZBy(45);
1476                         params.camera_lookat.set(0, 0, 0);
1477                         // Set orthogonal projection
1478                         params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1479                                         1.65, 1.65, 0, 100);
1480
1481                         params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1482                         params.light_position.set(10, 100, -50);
1483                         params.light_color.set(1.0, 0.5, 0.5, 0.5);
1484                         params.light_radius = 1000;
1485
1486                         video::ITexture *rtt = generateTextureFromMesh(params);
1487
1488                         // Drop mesh
1489                         cube->drop();
1490
1491                         // Free textures
1492                         driver->removeTexture(texture_top);
1493                         driver->removeTexture(texture_left);
1494                         driver->removeTexture(texture_right);
1495
1496                         if (rtt == NULL) {
1497                                 baseimg = generateImage(imagename_top);
1498                                 return true;
1499                         }
1500
1501                         // Create image of render target
1502                         video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1503                         FATAL_ERROR_IF(!image, "Could not create image of render target");
1504
1505                         // Cleanup texture
1506                         driver->removeTexture(rtt);
1507
1508                         baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1509
1510                         if (image) {
1511                                 image->copyTo(baseimg);
1512                                 image->drop();
1513                         }
1514                 }
1515                 /*
1516                         [lowpart:percent:filename
1517                         Adds the lower part of a texture
1518                 */
1519                 else if (str_starts_with(part_of_name, "[lowpart:"))
1520                 {
1521                         Strfnd sf(part_of_name);
1522                         sf.next(":");
1523                         u32 percent = stoi(sf.next(":"));
1524                         std::string filename = sf.next(":");
1525                         //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1526
1527                         if (baseimg == NULL)
1528                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1529                         video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1530                         if (img)
1531                         {
1532                                 core::dimension2d<u32> dim = img->getDimension();
1533                                 core::position2d<s32> pos_base(0, 0);
1534                                 video::IImage *img2 =
1535                                                 driver->createImage(video::ECF_A8R8G8B8, dim);
1536                                 img->copyTo(img2);
1537                                 img->drop();
1538                                 core::position2d<s32> clippos(0, 0);
1539                                 clippos.Y = dim.Height * (100-percent) / 100;
1540                                 core::dimension2d<u32> clipdim = dim;
1541                                 clipdim.Height = clipdim.Height * percent / 100 + 1;
1542                                 core::rect<s32> cliprect(clippos, clipdim);
1543                                 img2->copyToWithAlpha(baseimg, pos_base,
1544                                                 core::rect<s32>(v2s32(0,0), dim),
1545                                                 video::SColor(255,255,255,255),
1546                                                 &cliprect);
1547                                 img2->drop();
1548                         }
1549                 }
1550                 /*
1551                         [verticalframe:N:I
1552                         Crops a frame of a vertical animation.
1553                         N = frame count, I = frame index
1554                 */
1555                 else if (str_starts_with(part_of_name, "[verticalframe:"))
1556                 {
1557                         Strfnd sf(part_of_name);
1558                         sf.next(":");
1559                         u32 frame_count = stoi(sf.next(":"));
1560                         u32 frame_index = stoi(sf.next(":"));
1561
1562                         if (baseimg == NULL){
1563                                 errorstream<<"generateImagePart(): baseimg != NULL "
1564                                                 <<"for part_of_name=\""<<part_of_name
1565                                                 <<"\", cancelling."<<std::endl;
1566                                 return false;
1567                         }
1568
1569                         v2u32 frame_size = baseimg->getDimension();
1570                         frame_size.Y /= frame_count;
1571
1572                         video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1573                                         frame_size);
1574                         if (!img){
1575                                 errorstream<<"generateImagePart(): Could not create image "
1576                                                 <<"for part_of_name=\""<<part_of_name
1577                                                 <<"\", cancelling."<<std::endl;
1578                                 return false;
1579                         }
1580
1581                         // Fill target image with transparency
1582                         img->fill(video::SColor(0,0,0,0));
1583
1584                         core::dimension2d<u32> dim = frame_size;
1585                         core::position2d<s32> pos_dst(0, 0);
1586                         core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1587                         baseimg->copyToWithAlpha(img, pos_dst,
1588                                         core::rect<s32>(pos_src, dim),
1589                                         video::SColor(255,255,255,255),
1590                                         NULL);
1591                         // Replace baseimg
1592                         baseimg->drop();
1593                         baseimg = img;
1594                 }
1595                 /*
1596                         [mask:filename
1597                         Applies a mask to an image
1598                 */
1599                 else if (str_starts_with(part_of_name, "[mask:"))
1600                 {
1601                         if (baseimg == NULL) {
1602                                 errorstream << "generateImage(): baseimg == NULL "
1603                                                 << "for part_of_name=\"" << part_of_name
1604                                                 << "\", cancelling." << std::endl;
1605                                 return false;
1606                         }
1607                         Strfnd sf(part_of_name);
1608                         sf.next(":");
1609                         std::string filename = sf.next(":");
1610
1611                         video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1612                         if (img) {
1613                                 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1614                                                 img->getDimension());
1615                         } else {
1616                                 errorstream << "generateImage(): Failed to load \""
1617                                                 << filename << "\".";
1618                         }
1619                 }
1620                 /*
1621                         [colorize:color
1622                         Overlays image with given color
1623                         color = color as ColorString
1624                 */
1625                 else if (str_starts_with(part_of_name, "[colorize:"))
1626                 {
1627                         Strfnd sf(part_of_name);
1628                         sf.next(":");
1629                         std::string color_str = sf.next(":");
1630                         std::string ratio_str = sf.next(":");
1631
1632                         if (baseimg == NULL) {
1633                                 errorstream << "generateImagePart(): baseimg != NULL "
1634                                                 << "for part_of_name=\"" << part_of_name
1635                                                 << "\", cancelling." << std::endl;
1636                                 return false;
1637                         }
1638
1639                         video::SColor color;
1640                         int ratio = -1;
1641
1642                         if (!parseColorString(color_str, color, false))
1643                                 return false;
1644
1645                         if (is_number(ratio_str))
1646                                 ratio = mystoi(ratio_str, 0, 255);
1647
1648                         core::dimension2d<u32> dim = baseimg->getDimension();
1649                         video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
1650
1651                         if (!img) {
1652                                 errorstream << "generateImagePart(): Could not create image "
1653                                                 << "for part_of_name=\"" << part_of_name
1654                                                 << "\", cancelling." << std::endl;
1655                                 return false;
1656                         }
1657
1658                         img->fill(video::SColor(color));
1659                         // Overlay the colored image
1660                         blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio);
1661                         img->drop();
1662                 }
1663                 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1664                 {
1665                         // Apply the "clean transparent" filter, if configured.
1666                         if (g_settings->getBool("texture_clean_transparent"))
1667                                 imageCleanTransparent(baseimg, 127);
1668
1669                         /* Upscale textures to user's requested minimum size.  This is a trick to make
1670                          * filters look as good on low-res textures as on high-res ones, by making
1671                          * low-res textures BECOME high-res ones.  This is helpful for worlds that
1672                          * mix high- and low-res textures, or for mods with least-common-denominator
1673                          * textures that don't have the resources to offer high-res alternatives.
1674                          */
1675                         s32 scaleto = g_settings->getS32("texture_min_size");
1676                         if (scaleto > 1) {
1677                                 const core::dimension2d<u32> dim = baseimg->getDimension();
1678
1679                                 /* Calculate scaling needed to make the shortest texture dimension
1680                                  * equal to the target minimum.  If e.g. this is a vertical frames
1681                                  * animation, the short dimension will be the real size.
1682                                  */
1683                                 u32 xscale = scaleto / dim.Width;
1684                                 u32 yscale = scaleto / dim.Height;
1685                                 u32 scale = (xscale > yscale) ? xscale : yscale;
1686
1687                                 // Never downscale; only scale up by 2x or more.
1688                                 if (scale > 1) {
1689                                         u32 w = scale * dim.Width;
1690                                         u32 h = scale * dim.Height;
1691                                         const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1692                                         video::IImage *newimg = driver->createImage(
1693                                                         baseimg->getColorFormat(), newdim);
1694                                         baseimg->copyToScaling(newimg);
1695                                         baseimg->drop();
1696                                         baseimg = newimg;
1697                                 }
1698                         }
1699                 }
1700                 else
1701                 {
1702                         errorstream << "generateImagePart(): Invalid "
1703                                         " modification: \"" << part_of_name << "\"" << std::endl;
1704                 }
1705         }
1706
1707         return true;
1708 }
1709
1710 /*
1711         Draw an image on top of an another one, using the alpha channel of the
1712         source image
1713
1714         This exists because IImage::copyToWithAlpha() doesn't seem to always
1715         work.
1716 */
1717 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1718                 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1719 {
1720         for (u32 y0=0; y0<size.Y; y0++)
1721         for (u32 x0=0; x0<size.X; x0++)
1722         {
1723                 s32 src_x = src_pos.X + x0;
1724                 s32 src_y = src_pos.Y + y0;
1725                 s32 dst_x = dst_pos.X + x0;
1726                 s32 dst_y = dst_pos.Y + y0;
1727                 video::SColor src_c = src->getPixel(src_x, src_y);
1728                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1729                 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1730                 dst->setPixel(dst_x, dst_y, dst_c);
1731         }
1732 }
1733
1734 /*
1735         Draw an image on top of an another one, using the alpha channel of the
1736         source image; only modify fully opaque pixels in destinaion
1737 */
1738 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1739                 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1740 {
1741         for (u32 y0=0; y0<size.Y; y0++)
1742         for (u32 x0=0; x0<size.X; x0++)
1743         {
1744                 s32 src_x = src_pos.X + x0;
1745                 s32 src_y = src_pos.Y + y0;
1746                 s32 dst_x = dst_pos.X + x0;
1747                 s32 dst_y = dst_pos.Y + y0;
1748                 video::SColor src_c = src->getPixel(src_x, src_y);
1749                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1750                 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1751                 {
1752                         dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1753                         dst->setPixel(dst_x, dst_y, dst_c);
1754                 }
1755         }
1756 }
1757
1758 /*
1759         Draw an image on top of an another one, using the specified ratio
1760         modify all partially-opaque pixels in the destination.
1761 */
1762 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1763                 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1764 {
1765         for (u32 y0 = 0; y0 < size.Y; y0++)
1766         for (u32 x0 = 0; x0 < size.X; x0++)
1767         {
1768                 s32 src_x = src_pos.X + x0;
1769                 s32 src_y = src_pos.Y + y0;
1770                 s32 dst_x = dst_pos.X + x0;
1771                 s32 dst_y = dst_pos.Y + y0;
1772                 video::SColor src_c = src->getPixel(src_x, src_y);
1773                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1774                 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1775                 {
1776                         if (ratio == -1)
1777                                 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1778                         else
1779                                 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1780                         dst->setPixel(dst_x, dst_y, dst_c);
1781                 }
1782         }
1783 }
1784
1785 /*
1786         Apply mask to destination
1787 */
1788 static void apply_mask(video::IImage *mask, video::IImage *dst,
1789                 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1790 {
1791         for (u32 y0 = 0; y0 < size.Y; y0++) {
1792                 for (u32 x0 = 0; x0 < size.X; x0++) {
1793                         s32 mask_x = x0 + mask_pos.X;
1794                         s32 mask_y = y0 + mask_pos.Y;
1795                         s32 dst_x = x0 + dst_pos.X;
1796                         s32 dst_y = y0 + dst_pos.Y;
1797                         video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1798                         video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1799                         dst_c.color &= mask_c.color;
1800                         dst->setPixel(dst_x, dst_y, dst_c);
1801                 }
1802         }
1803 }
1804
1805 static void draw_crack(video::IImage *crack, video::IImage *dst,
1806                 bool use_overlay, s32 frame_count, s32 progression,
1807                 video::IVideoDriver *driver)
1808 {
1809         // Dimension of destination image
1810         core::dimension2d<u32> dim_dst = dst->getDimension();
1811         // Dimension of original image
1812         core::dimension2d<u32> dim_crack = crack->getDimension();
1813         // Count of crack stages
1814         s32 crack_count = dim_crack.Height / dim_crack.Width;
1815         // Limit frame_count
1816         if (frame_count > (s32) dim_dst.Height)
1817                 frame_count = dim_dst.Height;
1818         if (frame_count < 1)
1819                 frame_count = 1;
1820         // Limit progression
1821         if (progression > crack_count-1)
1822                 progression = crack_count-1;
1823         // Dimension of a single crack stage
1824         core::dimension2d<u32> dim_crack_cropped(
1825                 dim_crack.Width,
1826                 dim_crack.Width
1827         );
1828         // Dimension of the scaled crack stage,
1829         // which is the same as the dimension of a single destination frame
1830         core::dimension2d<u32> dim_crack_scaled(
1831                 dim_dst.Width,
1832                 dim_dst.Height / frame_count
1833         );
1834         // Create cropped and scaled crack images
1835         video::IImage *crack_cropped = driver->createImage(
1836                         video::ECF_A8R8G8B8, dim_crack_cropped);
1837         video::IImage *crack_scaled = driver->createImage(
1838                         video::ECF_A8R8G8B8, dim_crack_scaled);
1839
1840         if (crack_cropped && crack_scaled)
1841         {
1842                 // Crop crack image
1843                 v2s32 pos_crack(0, progression*dim_crack.Width);
1844                 crack->copyTo(crack_cropped,
1845                                 v2s32(0,0),
1846                                 core::rect<s32>(pos_crack, dim_crack_cropped));
1847                 // Scale crack image by copying
1848                 crack_cropped->copyToScaling(crack_scaled);
1849                 // Copy or overlay crack image onto each frame
1850                 for (s32 i = 0; i < frame_count; ++i)
1851                 {
1852                         v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1853                         if (use_overlay)
1854                         {
1855                                 blit_with_alpha_overlay(crack_scaled, dst,
1856                                                 v2s32(0,0), dst_pos,
1857                                                 dim_crack_scaled);
1858                         }
1859                         else
1860                         {
1861                                 blit_with_alpha(crack_scaled, dst,
1862                                                 v2s32(0,0), dst_pos,
1863                                                 dim_crack_scaled);
1864                         }
1865                 }
1866         }
1867
1868         if (crack_scaled)
1869                 crack_scaled->drop();
1870
1871         if (crack_cropped)
1872                 crack_cropped->drop();
1873 }
1874
1875 void brighten(video::IImage *image)
1876 {
1877         if (image == NULL)
1878                 return;
1879
1880         core::dimension2d<u32> dim = image->getDimension();
1881
1882         for (u32 y=0; y<dim.Height; y++)
1883         for (u32 x=0; x<dim.Width; x++)
1884         {
1885                 video::SColor c = image->getPixel(x,y);
1886                 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1887                 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1888                 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1889                 image->setPixel(x,y,c);
1890         }
1891 }
1892
1893 u32 parseImageTransform(const std::string& s)
1894 {
1895         int total_transform = 0;
1896
1897         std::string transform_names[8];
1898         transform_names[0] = "i";
1899         transform_names[1] = "r90";
1900         transform_names[2] = "r180";
1901         transform_names[3] = "r270";
1902         transform_names[4] = "fx";
1903         transform_names[6] = "fy";
1904
1905         std::size_t pos = 0;
1906         while(pos < s.size())
1907         {
1908                 int transform = -1;
1909                 for (int i = 0; i <= 7; ++i)
1910                 {
1911                         const std::string &name_i = transform_names[i];
1912
1913                         if (s[pos] == ('0' + i))
1914                         {
1915                                 transform = i;
1916                                 pos++;
1917                                 break;
1918                         }
1919                         else if (!(name_i.empty()) &&
1920                                 lowercase(s.substr(pos, name_i.size())) == name_i)
1921                         {
1922                                 transform = i;
1923                                 pos += name_i.size();
1924                                 break;
1925                         }
1926                 }
1927                 if (transform < 0)
1928                         break;
1929
1930                 // Multiply total_transform and transform in the group D4
1931                 int new_total = 0;
1932                 if (transform < 4)
1933                         new_total = (transform + total_transform) % 4;
1934                 else
1935                         new_total = (transform - total_transform + 8) % 4;
1936                 if ((transform >= 4) ^ (total_transform >= 4))
1937                         new_total += 4;
1938
1939                 total_transform = new_total;
1940         }
1941         return total_transform;
1942 }
1943
1944 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1945 {
1946         if (transform % 2 == 0)
1947                 return dim;
1948         else
1949                 return core::dimension2d<u32>(dim.Height, dim.Width);
1950 }
1951
1952 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1953 {
1954         if (src == NULL || dst == NULL)
1955                 return;
1956
1957         core::dimension2d<u32> dstdim = dst->getDimension();
1958
1959         // Pre-conditions
1960         assert(dstdim == imageTransformDimension(transform, src->getDimension()));
1961         assert(transform <= 7);
1962
1963         /*
1964                 Compute the transformation from source coordinates (sx,sy)
1965                 to destination coordinates (dx,dy).
1966         */
1967         int sxn = 0;
1968         int syn = 2;
1969         if (transform == 0)         // identity
1970                 sxn = 0, syn = 2;  //   sx = dx, sy = dy
1971         else if (transform == 1)    // rotate by 90 degrees ccw
1972                 sxn = 3, syn = 0;  //   sx = (H-1) - dy, sy = dx
1973         else if (transform == 2)    // rotate by 180 degrees
1974                 sxn = 1, syn = 3;  //   sx = (W-1) - dx, sy = (H-1) - dy
1975         else if (transform == 3)    // rotate by 270 degrees ccw
1976                 sxn = 2, syn = 1;  //   sx = dy, sy = (W-1) - dx
1977         else if (transform == 4)    // flip x
1978                 sxn = 1, syn = 2;  //   sx = (W-1) - dx, sy = dy
1979         else if (transform == 5)    // flip x then rotate by 90 degrees ccw
1980                 sxn = 2, syn = 0;  //   sx = dy, sy = dx
1981         else if (transform == 6)    // flip y
1982                 sxn = 0, syn = 3;  //   sx = dx, sy = (H-1) - dy
1983         else if (transform == 7)    // flip y then rotate by 90 degrees ccw
1984                 sxn = 3, syn = 1;  //   sx = (H-1) - dy, sy = (W-1) - dx
1985
1986         for (u32 dy=0; dy<dstdim.Height; dy++)
1987         for (u32 dx=0; dx<dstdim.Width; dx++)
1988         {
1989                 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1990                 u32 sx = entries[sxn];
1991                 u32 sy = entries[syn];
1992                 video::SColor c = src->getPixel(sx,sy);
1993                 dst->setPixel(dx,dy,c);
1994         }
1995 }
1996
1997 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1998 {
1999         if (isKnownSourceImage("override_normal.png"))
2000                 return getTexture("override_normal.png");
2001         std::string fname_base = name;
2002         std::string normal_ext = "_normal.png";
2003         size_t pos = fname_base.find(".");
2004         std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2005         if (isKnownSourceImage(fname_normal)) {
2006                 // look for image extension and replace it
2007                 size_t i = 0;
2008                 while ((i = fname_base.find(".", i)) != std::string::npos) {
2009                         fname_base.replace(i, 4, normal_ext);
2010                         i += normal_ext.length();
2011                 }
2012                 return getTexture(fname_base);
2013                 }
2014         return NULL;
2015 }
2016
2017 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2018 {
2019         video::IVideoDriver *driver = m_device->getVideoDriver();
2020         video::SColor c(0, 0, 0, 0);
2021         video::ITexture *texture = getTexture(name);
2022         video::IImage *image = driver->createImage(texture,
2023                 core::position2d<s32>(0, 0),
2024                 texture->getOriginalSize());
2025         u32 total = 0;
2026         u32 tR = 0;
2027         u32 tG = 0;
2028         u32 tB = 0;
2029         core::dimension2d<u32> dim = image->getDimension();
2030         u16 step = 1;
2031         if (dim.Width > 16)
2032                 step = dim.Width / 16;
2033         for (u16 x = 0; x < dim.Width; x += step) {
2034                 for (u16 y = 0; y < dim.Width; y += step) {
2035                         c = image->getPixel(x,y);
2036                         if (c.getAlpha() > 0) {
2037                                 total++;
2038                                 tR += c.getRed();
2039                                 tG += c.getGreen();
2040                                 tB += c.getBlue();
2041                         }
2042                 }
2043         }
2044         image->drop();
2045         if (total > 0) {
2046                 c.setRed(tR / total);
2047                 c.setGreen(tG / total);
2048                 c.setBlue(tB / total);
2049         }
2050         c.setAlpha(255);
2051         return c;
2052 }
2053
2054
2055 video::ITexture *TextureSource::getShaderFlagsTexture(
2056         bool normalmap_present, bool tileable_vertical, bool tileable_horizontal)
2057 {
2058         std::string tname = "__shaderFlagsTexture";
2059         tname += normalmap_present ? "1" : "0";
2060         tname += tileable_horizontal ? "1" : "0";
2061         tname += tileable_vertical ? "1" : "0";
2062
2063         if (isKnownSourceImage(tname)) {
2064                 return getTexture(tname);
2065         } else {
2066                 video::IVideoDriver *driver = m_device->getVideoDriver();
2067                 video::IImage *flags_image = driver->createImage(
2068                         video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2069                 sanity_check(flags_image != NULL);
2070                 video::SColor c(
2071                         255,
2072                         normalmap_present ? 255 : 0,
2073                         tileable_horizontal ? 255 : 0,
2074                         tileable_vertical ? 255 : 0);
2075                 flags_image->setPixel(0, 0, c);
2076                 insertSourceImage(tname, flags_image);
2077                 flags_image->drop();
2078                 return getTexture(tname);
2079         }
2080 }