[CSM] Add `on_dignode` callback (#5140)
[oweals/minetest.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 "util/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         video::IImage* generateImage(const std::string &name);
382
383         video::ITexture* getNormalTexture(const std::string &name);
384         video::SColor getTextureAverageColor(const std::string &name);
385         video::ITexture *getShaderFlagsTexture(bool normamap_present);
386
387 private:
388
389         // The id of the thread that is allowed to use irrlicht directly
390         threadid_t m_main_thread;
391         // The irrlicht device
392         IrrlichtDevice *m_device;
393
394         // Cache of source images
395         // This should be only accessed from the main thread
396         SourceImageCache m_sourcecache;
397
398         // Generate a texture
399         u32 generateTexture(const std::string &name);
400
401         // Generate image based on a string like "stone.png" or "[crack:1:0".
402         // if baseimg is NULL, it is created. Otherwise stuff is made on it.
403         bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
404
405         // Thread-safe cache of what source images are known (true = known)
406         MutexedMap<std::string, bool> m_source_image_existence;
407
408         // A texture id is index in this array.
409         // The first position contains a NULL texture.
410         std::vector<TextureInfo> m_textureinfo_cache;
411         // Maps a texture name to an index in the former.
412         std::map<std::string, u32> m_name_to_id;
413         // The two former containers are behind this mutex
414         Mutex m_textureinfo_cache_mutex;
415
416         // Queued texture fetches (to be processed by the main thread)
417         RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
418
419         // Textures that have been overwritten with other ones
420         // but can't be deleted because the ITexture* might still be used
421         std::vector<video::ITexture*> m_texture_trash;
422
423         // Cached settings needed for making textures from meshes
424         bool m_setting_trilinear_filter;
425         bool m_setting_bilinear_filter;
426         bool m_setting_anisotropic_filter;
427 };
428
429 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
430 {
431         return new TextureSource(device);
432 }
433
434 TextureSource::TextureSource(IrrlichtDevice *device):
435                 m_device(device)
436 {
437         assert(m_device); // Pre-condition
438
439         m_main_thread = thr_get_current_thread_id();
440
441         // Add a NULL TextureInfo as the first index, named ""
442         m_textureinfo_cache.push_back(TextureInfo(""));
443         m_name_to_id[""] = 0;
444
445         // Cache some settings
446         // Note: Since this is only done once, the game must be restarted
447         // for these settings to take effect
448         m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
449         m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
450         m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
451 }
452
453 TextureSource::~TextureSource()
454 {
455         video::IVideoDriver* driver = m_device->getVideoDriver();
456
457         unsigned int textures_before = driver->getTextureCount();
458
459         for (std::vector<TextureInfo>::iterator iter =
460                         m_textureinfo_cache.begin();
461                         iter != m_textureinfo_cache.end(); ++iter)
462         {
463                 //cleanup texture
464                 if (iter->texture)
465                         driver->removeTexture(iter->texture);
466         }
467         m_textureinfo_cache.clear();
468
469         for (std::vector<video::ITexture*>::iterator iter =
470                         m_texture_trash.begin(); iter != m_texture_trash.end();
471                         ++iter) {
472                 video::ITexture *t = *iter;
473
474                 //cleanup trashed texture
475                 driver->removeTexture(t);
476         }
477
478         infostream << "~TextureSource() "<< textures_before << "/"
479                         << driver->getTextureCount() << std::endl;
480 }
481
482 u32 TextureSource::getTextureId(const std::string &name)
483 {
484         //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
485
486         {
487                 /*
488                         See if texture already exists
489                 */
490                 MutexAutoLock lock(m_textureinfo_cache_mutex);
491                 std::map<std::string, u32>::iterator n;
492                 n = m_name_to_id.find(name);
493                 if (n != m_name_to_id.end())
494                 {
495                         return n->second;
496                 }
497         }
498
499         /*
500                 Get texture
501         */
502         if (thr_is_current_thread(m_main_thread))
503         {
504                 return generateTexture(name);
505         }
506         else
507         {
508                 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
509
510                 // We're gonna ask the result to be put into here
511                 static ResultQueue<std::string, u32, u8, u8> result_queue;
512
513                 // Throw a request in
514                 m_get_texture_queue.add(name, 0, 0, &result_queue);
515
516                 /*infostream<<"Waiting for texture from main thread, name=\""
517                                 <<name<<"\""<<std::endl;*/
518
519                 try
520                 {
521                         while(true) {
522                                 // Wait result for a second
523                                 GetResult<std::string, u32, u8, u8>
524                                         result = result_queue.pop_front(1000);
525
526                                 if (result.key == name) {
527                                         return result.item;
528                                 }
529                         }
530                 }
531                 catch(ItemNotFoundException &e)
532                 {
533                         errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
534                         return 0;
535                 }
536         }
537
538         infostream<<"getTextureId(): Failed"<<std::endl;
539
540         return 0;
541 }
542
543 // Draw an image on top of an another one, using the alpha channel of the
544 // source image
545 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
546                 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
547
548 // Like blit_with_alpha, but only modifies destination pixels that
549 // are fully opaque
550 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
551                 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
552
553 // Apply a color to an image.  Uses an int (0-255) to calculate the ratio.
554 // If the ratio is 255 or -1 and keep_alpha is true, then it multiples the
555 // color alpha with the destination alpha.
556 // Otherwise, any pixels that are not fully transparent get the color alpha.
557 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
558                 const video::SColor &color, int ratio, bool keep_alpha);
559
560 // paint a texture using the given color
561 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
562                 const video::SColor &color);
563
564 // Apply a mask to an image
565 static void apply_mask(video::IImage *mask, video::IImage *dst,
566                 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
567
568 // Draw or overlay a crack
569 static void draw_crack(video::IImage *crack, video::IImage *dst,
570                 bool use_overlay, s32 frame_count, s32 progression,
571                 video::IVideoDriver *driver);
572
573 // Brighten image
574 void brighten(video::IImage *image);
575 // Parse a transform name
576 u32 parseImageTransform(const std::string& s);
577 // Apply transform to image dimension
578 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
579 // Apply transform to image data
580 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
581
582 /*
583         This method generates all the textures
584 */
585 u32 TextureSource::generateTexture(const std::string &name)
586 {
587         //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
588
589         // Empty name means texture 0
590         if (name == "") {
591                 infostream<<"generateTexture(): name is empty"<<std::endl;
592                 return 0;
593         }
594
595         {
596                 /*
597                         See if texture already exists
598                 */
599                 MutexAutoLock lock(m_textureinfo_cache_mutex);
600                 std::map<std::string, u32>::iterator n;
601                 n = m_name_to_id.find(name);
602                 if (n != m_name_to_id.end()) {
603                         return n->second;
604                 }
605         }
606
607         /*
608                 Calling only allowed from main thread
609         */
610         if (!thr_is_current_thread(m_main_thread)) {
611                 errorstream<<"TextureSource::generateTexture() "
612                                 "called not from main thread"<<std::endl;
613                 return 0;
614         }
615
616         video::IVideoDriver *driver = m_device->getVideoDriver();
617         sanity_check(driver);
618
619         video::IImage *img = generateImage(name);
620
621         video::ITexture *tex = NULL;
622
623         if (img != NULL) {
624 #ifdef __ANDROID__
625                 img = Align2Npot2(img, driver);
626 #endif
627                 // Create texture from resulting image
628                 tex = driver->addTexture(name.c_str(), img);
629                 guiScalingCache(io::path(name.c_str()), driver, img);
630                 img->drop();
631         }
632
633         /*
634                 Add texture to caches (add NULL textures too)
635         */
636
637         MutexAutoLock lock(m_textureinfo_cache_mutex);
638
639         u32 id = m_textureinfo_cache.size();
640         TextureInfo ti(name, tex);
641         m_textureinfo_cache.push_back(ti);
642         m_name_to_id[name] = id;
643
644         return id;
645 }
646
647 std::string TextureSource::getTextureName(u32 id)
648 {
649         MutexAutoLock lock(m_textureinfo_cache_mutex);
650
651         if (id >= m_textureinfo_cache.size())
652         {
653                 errorstream<<"TextureSource::getTextureName(): id="<<id
654                                 <<" >= m_textureinfo_cache.size()="
655                                 <<m_textureinfo_cache.size()<<std::endl;
656                 return "";
657         }
658
659         return m_textureinfo_cache[id].name;
660 }
661
662 video::ITexture* TextureSource::getTexture(u32 id)
663 {
664         MutexAutoLock lock(m_textureinfo_cache_mutex);
665
666         if (id >= m_textureinfo_cache.size())
667                 return NULL;
668
669         return m_textureinfo_cache[id].texture;
670 }
671
672 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
673 {
674         u32 actual_id = getTextureId(name);
675         if (id){
676                 *id = actual_id;
677         }
678         return getTexture(actual_id);
679 }
680
681 video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *id)
682 {
683         return getTexture(name + "^[applyfiltersformesh", id);
684 }
685
686 void TextureSource::processQueue()
687 {
688         /*
689                 Fetch textures
690         */
691         //NOTE this is only thread safe for ONE consumer thread!
692         if (!m_get_texture_queue.empty())
693         {
694                 GetRequest<std::string, u32, u8, u8>
695                                 request = m_get_texture_queue.pop();
696
697                 /*infostream<<"TextureSource::processQueue(): "
698                                 <<"got texture request with "
699                                 <<"name=\""<<request.key<<"\""
700                                 <<std::endl;*/
701
702                 m_get_texture_queue.pushResult(request, generateTexture(request.key));
703         }
704 }
705
706 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
707 {
708         //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
709
710         sanity_check(thr_is_current_thread(m_main_thread));
711
712         m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
713         m_source_image_existence.set(name, true);
714 }
715
716 void TextureSource::rebuildImagesAndTextures()
717 {
718         MutexAutoLock lock(m_textureinfo_cache_mutex);
719
720         video::IVideoDriver* driver = m_device->getVideoDriver();
721         sanity_check(driver);
722
723         // Recreate textures
724         for (u32 i=0; i<m_textureinfo_cache.size(); i++){
725                 TextureInfo *ti = &m_textureinfo_cache[i];
726                 video::IImage *img = generateImage(ti->name);
727 #ifdef __ANDROID__
728                 img = Align2Npot2(img, driver);
729                 sanity_check(img->getDimension().Height == npot2(img->getDimension().Height));
730                 sanity_check(img->getDimension().Width == npot2(img->getDimension().Width));
731 #endif
732                 // Create texture from resulting image
733                 video::ITexture *t = NULL;
734                 if (img) {
735                         t = driver->addTexture(ti->name.c_str(), img);
736                         guiScalingCache(io::path(ti->name.c_str()), driver, img);
737                         img->drop();
738                 }
739                 video::ITexture *t_old = ti->texture;
740                 // Replace texture
741                 ti->texture = t;
742
743                 if (t_old)
744                         m_texture_trash.push_back(t_old);
745         }
746 }
747
748 video::ITexture* TextureSource::generateTextureFromMesh(
749                 const TextureFromMeshParams &params)
750 {
751         video::IVideoDriver *driver = m_device->getVideoDriver();
752         sanity_check(driver);
753
754 #ifdef __ANDROID__
755         const GLubyte* renderstr = glGetString(GL_RENDERER);
756         std::string renderer((char*) renderstr);
757
758         // use no render to texture hack
759         if (
760                 (renderer.find("Adreno") != std::string::npos) ||
761                 (renderer.find("Mali") != std::string::npos) ||
762                 (renderer.find("Immersion") != std::string::npos) ||
763                 (renderer.find("Tegra") != std::string::npos) ||
764                 g_settings->getBool("inventory_image_hack")
765                 ) {
766                 // Get a scene manager
767                 scene::ISceneManager *smgr_main = m_device->getSceneManager();
768                 sanity_check(smgr_main);
769                 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
770                 sanity_check(smgr);
771
772                 const float scaling = 0.2;
773
774                 scene::IMeshSceneNode* meshnode =
775                                 smgr->addMeshSceneNode(params.mesh, NULL,
776                                                 -1, v3f(0,0,0), v3f(0,0,0),
777                                                 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
778                 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
779                 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
780                 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
781                 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
782                 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
783
784                 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
785                                 params.camera_position, params.camera_lookat);
786                 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
787                 camera->setProjectionMatrix(params.camera_projection_matrix, false);
788
789                 smgr->setAmbientLight(params.ambient_light);
790                 smgr->addLightSceneNode(0,
791                                 params.light_position,
792                                 params.light_color,
793                                 params.light_radius*scaling);
794
795                 core::dimension2d<u32> screen = driver->getScreenSize();
796
797                 // Render scene
798                 driver->beginScene(true, true, video::SColor(0,0,0,0));
799                 driver->clearZBuffer();
800                 smgr->drawAll();
801
802                 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
803
804                 irr::video::IImage* rawImage =
805                                 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
806
807                 u8* pixels = static_cast<u8*>(rawImage->lock());
808                 if (!pixels)
809                 {
810                         rawImage->drop();
811                         return NULL;
812                 }
813
814                 core::rect<s32> source(
815                                 screen.Width /2 - (screen.Width  * (scaling / 2)),
816                                 screen.Height/2 - (screen.Height * (scaling / 2)),
817                                 screen.Width /2 + (screen.Width  * (scaling / 2)),
818                                 screen.Height/2 + (screen.Height * (scaling / 2))
819                         );
820
821                 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
822                                 partsize.Width, partsize.Height, GL_RGBA,
823                                 GL_UNSIGNED_BYTE, pixels);
824
825                 driver->endScene();
826
827                 // Drop scene manager
828                 smgr->drop();
829
830                 unsigned int pixelcount = partsize.Width*partsize.Height;
831
832                 u8* runptr = pixels;
833                 for (unsigned int i=0; i < pixelcount; i++) {
834
835                         u8 B = *runptr;
836                         u8 G = *(runptr+1);
837                         u8 R = *(runptr+2);
838                         u8 A = *(runptr+3);
839
840                         //BGRA -> RGBA
841                         *runptr = R;
842                         runptr ++;
843                         *runptr = G;
844                         runptr ++;
845                         *runptr = B;
846                         runptr ++;
847                         *runptr = A;
848                         runptr ++;
849                 }
850
851                 video::IImage* inventory_image =
852                                 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
853
854                 rawImage->copyToScaling(inventory_image);
855                 rawImage->drop();
856
857                 guiScalingCache(io::path(params.rtt_texture_name.c_str()), driver, inventory_image);
858
859                 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
860                 inventory_image->drop();
861
862                 if (rtt == NULL) {
863                         errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
864                         return NULL;
865                 }
866
867                 driver->makeColorKeyTexture(rtt, v2s32(0,0));
868
869                 if (params.delete_texture_on_shutdown)
870                         m_texture_trash.push_back(rtt);
871
872                 return rtt;
873         }
874 #endif
875
876         if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
877         {
878                 static bool warned = false;
879                 if (!warned)
880                 {
881                         errorstream<<"TextureSource::generateTextureFromMesh(): "
882                                 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
883                         warned = true;
884                 }
885                 return NULL;
886         }
887
888         // Create render target texture
889         video::ITexture *rtt = driver->addRenderTargetTexture(
890                         params.dim, params.rtt_texture_name.c_str(),
891                         video::ECF_A8R8G8B8);
892         if (rtt == NULL)
893         {
894                 errorstream<<"TextureSource::generateTextureFromMesh(): "
895                         <<"addRenderTargetTexture returned NULL."<<std::endl;
896                 return NULL;
897         }
898
899         // Set render target
900         if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
901                 driver->removeTexture(rtt);
902                 errorstream<<"TextureSource::generateTextureFromMesh(): "
903                         <<"failed to set render target"<<std::endl;
904                 return NULL;
905         }
906
907         // Get a scene manager
908         scene::ISceneManager *smgr_main = m_device->getSceneManager();
909         assert(smgr_main);
910         scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
911         assert(smgr);
912
913         scene::IMeshSceneNode* meshnode =
914                         smgr->addMeshSceneNode(params.mesh, NULL,
915                                         -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
916         meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
917         meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
918         meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
919         meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
920         meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
921
922         scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
923                         params.camera_position, params.camera_lookat);
924         // second parameter of setProjectionMatrix (isOrthogonal) is ignored
925         camera->setProjectionMatrix(params.camera_projection_matrix, false);
926
927         smgr->setAmbientLight(params.ambient_light);
928         smgr->addLightSceneNode(0,
929                         params.light_position,
930                         params.light_color,
931                         params.light_radius);
932
933         // Render scene
934         driver->beginScene(true, true, video::SColor(0,0,0,0));
935         smgr->drawAll();
936         driver->endScene();
937
938         // Drop scene manager
939         smgr->drop();
940
941         // Unset render target
942         driver->setRenderTarget(0, false, true, video::SColor(0,0,0,0));
943
944         if (params.delete_texture_on_shutdown)
945                 m_texture_trash.push_back(rtt);
946
947         return rtt;
948 }
949
950 video::IImage* TextureSource::generateImage(const std::string &name)
951 {
952         // Get the base image
953
954         const char separator = '^';
955         const char escape = '\\';
956         const char paren_open = '(';
957         const char paren_close = ')';
958
959         // Find last separator in the name
960         s32 last_separator_pos = -1;
961         u8 paren_bal = 0;
962         for (s32 i = name.size() - 1; i >= 0; i--) {
963                 if (i > 0 && name[i-1] == escape)
964                         continue;
965                 switch (name[i]) {
966                 case separator:
967                         if (paren_bal == 0) {
968                                 last_separator_pos = i;
969                                 i = -1; // break out of loop
970                         }
971                         break;
972                 case paren_open:
973                         if (paren_bal == 0) {
974                                 errorstream << "generateImage(): unbalanced parentheses"
975                                                 << "(extranous '(') while generating texture \""
976                                                 << name << "\"" << std::endl;
977                                 return NULL;
978                         }
979                         paren_bal--;
980                         break;
981                 case paren_close:
982                         paren_bal++;
983                         break;
984                 default:
985                         break;
986                 }
987         }
988         if (paren_bal > 0) {
989                 errorstream << "generateImage(): unbalanced parentheses"
990                                 << "(missing matching '(') while generating texture \""
991                                 << name << "\"" << std::endl;
992                 return NULL;
993         }
994
995
996         video::IImage *baseimg = NULL;
997
998         /*
999                 If separator was found, make the base image
1000                 using a recursive call.
1001         */
1002         if (last_separator_pos != -1) {
1003                 baseimg = generateImage(name.substr(0, last_separator_pos));
1004         }
1005
1006
1007         video::IVideoDriver* driver = m_device->getVideoDriver();
1008         sanity_check(driver);
1009
1010         /*
1011                 Parse out the last part of the name of the image and act
1012                 according to it
1013         */
1014
1015         std::string last_part_of_name = name.substr(last_separator_pos + 1);
1016
1017         /*
1018                 If this name is enclosed in parentheses, generate it
1019                 and blit it onto the base image
1020         */
1021         if (last_part_of_name[0] == paren_open
1022                         && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1023                 std::string name2 = last_part_of_name.substr(1,
1024                                 last_part_of_name.size() - 2);
1025                 video::IImage *tmp = generateImage(name2);
1026                 if (!tmp) {
1027                         errorstream << "generateImage(): "
1028                                 "Failed to generate \"" << name2 << "\""
1029                                 << std::endl;
1030                         return NULL;
1031                 }
1032                 core::dimension2d<u32> dim = tmp->getDimension();
1033                 if (baseimg) {
1034                         blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1035                         tmp->drop();
1036                 } else {
1037                         baseimg = tmp;
1038                 }
1039         } else if (!generateImagePart(last_part_of_name, baseimg)) {
1040                 // Generate image according to part of name
1041                 errorstream << "generateImage(): "
1042                                 "Failed to generate \"" << last_part_of_name << "\""
1043                                 << std::endl;
1044         }
1045
1046         // If no resulting image, print a warning
1047         if (baseimg == NULL) {
1048                 errorstream << "generateImage(): baseimg is NULL (attempted to"
1049                                 " create texture \"" << name << "\")" << std::endl;
1050         }
1051
1052         return baseimg;
1053 }
1054
1055 #ifdef __ANDROID__
1056 #include <GLES/gl.h>
1057 /**
1058  * Check and align image to npot2 if required by hardware
1059  * @param image image to check for npot2 alignment
1060  * @param driver driver to use for image operations
1061  * @return image or copy of image aligned to npot2
1062  */
1063 video::IImage * Align2Npot2(video::IImage * image,
1064                 video::IVideoDriver* driver)
1065 {
1066         if (image == NULL) {
1067                 return image;
1068         }
1069
1070         core::dimension2d<u32> dim = image->getDimension();
1071
1072         std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1073         if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1074                 return image;
1075         }
1076
1077         unsigned int height = npot2(dim.Height);
1078         unsigned int width  = npot2(dim.Width);
1079
1080         if ((dim.Height == height) &&
1081                         (dim.Width == width)) {
1082                 return image;
1083         }
1084
1085         if (dim.Height > height) {
1086                 height *= 2;
1087         }
1088
1089         if (dim.Width > width) {
1090                 width *= 2;
1091         }
1092
1093         video::IImage *targetimage =
1094                         driver->createImage(video::ECF_A8R8G8B8,
1095                                         core::dimension2d<u32>(width, height));
1096
1097         if (targetimage != NULL) {
1098                 image->copyToScaling(targetimage);
1099         }
1100         image->drop();
1101         return targetimage;
1102 }
1103
1104 #endif
1105
1106 static std::string unescape_string(const std::string &str, const char esc = '\\')
1107 {
1108         std::string out;
1109         size_t pos = 0, cpos;
1110         out.reserve(str.size());
1111         while (1) {
1112                 cpos = str.find_first_of(esc, pos);
1113                 if (cpos == std::string::npos) {
1114                         out += str.substr(pos);
1115                         break;
1116                 }
1117                 out += str.substr(pos, cpos - pos) + str[cpos + 1];
1118                 pos = cpos + 2;
1119         }
1120         return out;
1121 }
1122
1123 bool TextureSource::generateImagePart(std::string part_of_name,
1124                 video::IImage *& baseimg)
1125 {
1126         const char escape = '\\'; // same as in generateImage()
1127         video::IVideoDriver* driver = m_device->getVideoDriver();
1128         sanity_check(driver);
1129
1130         // Stuff starting with [ are special commands
1131         if (part_of_name.size() == 0 || part_of_name[0] != '[')
1132         {
1133                 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1134 #ifdef __ANDROID__
1135                 image = Align2Npot2(image, driver);
1136 #endif
1137                 if (image == NULL) {
1138                         if (part_of_name != "") {
1139                                 if (part_of_name.find("_normal.png") == std::string::npos){
1140                                         errorstream<<"generateImage(): Could not load image \""
1141                                                 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1142                                         errorstream<<"generateImage(): Creating a dummy"
1143                                                 <<" image for \""<<part_of_name<<"\""<<std::endl;
1144                                 } else {
1145                                         infostream<<"generateImage(): Could not load normal map \""
1146                                                 <<part_of_name<<"\""<<std::endl;
1147                                         infostream<<"generateImage(): Creating a dummy"
1148                                                 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1149                                 }
1150                         }
1151
1152                         // Just create a dummy image
1153                         //core::dimension2d<u32> dim(2,2);
1154                         core::dimension2d<u32> dim(1,1);
1155                         image = driver->createImage(video::ECF_A8R8G8B8, dim);
1156                         sanity_check(image != NULL);
1157                         /*image->setPixel(0,0, video::SColor(255,255,0,0));
1158                         image->setPixel(1,0, video::SColor(255,0,255,0));
1159                         image->setPixel(0,1, video::SColor(255,0,0,255));
1160                         image->setPixel(1,1, video::SColor(255,255,0,255));*/
1161                         image->setPixel(0,0, video::SColor(255,myrand()%256,
1162                                         myrand()%256,myrand()%256));
1163                         /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1164                                         myrand()%256,myrand()%256));
1165                         image->setPixel(0,1, video::SColor(255,myrand()%256,
1166                                         myrand()%256,myrand()%256));
1167                         image->setPixel(1,1, video::SColor(255,myrand()%256,
1168                                         myrand()%256,myrand()%256));*/
1169                 }
1170
1171                 // If base image is NULL, load as base.
1172                 if (baseimg == NULL)
1173                 {
1174                         //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1175                         /*
1176                                 Copy it this way to get an alpha channel.
1177                                 Otherwise images with alpha cannot be blitted on
1178                                 images that don't have alpha in the original file.
1179                         */
1180                         core::dimension2d<u32> dim = image->getDimension();
1181                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1182                         image->copyTo(baseimg);
1183                 }
1184                 // Else blit on base.
1185                 else
1186                 {
1187                         //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1188                         // Size of the copied area
1189                         core::dimension2d<u32> dim = image->getDimension();
1190                         //core::dimension2d<u32> dim(16,16);
1191                         // Position to copy the blitted to in the base image
1192                         core::position2d<s32> pos_to(0,0);
1193                         // Position to copy the blitted from in the blitted image
1194                         core::position2d<s32> pos_from(0,0);
1195                         // Blit
1196                         /*image->copyToWithAlpha(baseimg, pos_to,
1197                                         core::rect<s32>(pos_from, dim),
1198                                         video::SColor(255,255,255,255),
1199                                         NULL);*/
1200
1201                         core::dimension2d<u32> dim_dst = baseimg->getDimension();
1202                         if (dim == dim_dst) {
1203                                 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1204                         } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1205                                 // Upscale overlying image
1206                                 video::IImage* scaled_image = m_device->getVideoDriver()->
1207                                         createImage(video::ECF_A8R8G8B8, dim_dst);
1208                                 image->copyToScaling(scaled_image);
1209
1210                                 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1211                                 scaled_image->drop();
1212                         } else {
1213                                 // Upscale base image
1214                                 video::IImage* scaled_base = m_device->getVideoDriver()->
1215                                         createImage(video::ECF_A8R8G8B8, dim);
1216                                 baseimg->copyToScaling(scaled_base);
1217                                 baseimg->drop();
1218                                 baseimg = scaled_base;
1219
1220                                 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1221                         }
1222                 }
1223                 //cleanup
1224                 image->drop();
1225         }
1226         else
1227         {
1228                 // A special texture modification
1229
1230                 /*infostream<<"generateImage(): generating special "
1231                                 <<"modification \""<<part_of_name<<"\""
1232                                 <<std::endl;*/
1233
1234                 /*
1235                         [crack:N:P
1236                         [cracko:N:P
1237                         Adds a cracking texture
1238                         N = animation frame count, P = crack progression
1239                 */
1240                 if (str_starts_with(part_of_name, "[crack"))
1241                 {
1242                         if (baseimg == NULL) {
1243                                 errorstream<<"generateImagePart(): baseimg == NULL "
1244                                                 <<"for part_of_name=\""<<part_of_name
1245                                                 <<"\", cancelling."<<std::endl;
1246                                 return false;
1247                         }
1248
1249                         // Crack image number and overlay option
1250                         bool use_overlay = (part_of_name[6] == 'o');
1251                         Strfnd sf(part_of_name);
1252                         sf.next(":");
1253                         s32 frame_count = stoi(sf.next(":"));
1254                         s32 progression = stoi(sf.next(":"));
1255
1256                         if (progression >= 0) {
1257                                 /*
1258                                         Load crack image.
1259
1260                                         It is an image with a number of cracking stages
1261                                         horizontally tiled.
1262                                 */
1263                                 video::IImage *img_crack = m_sourcecache.getOrLoad(
1264                                         "crack_anylength.png", m_device);
1265
1266                                 if (img_crack) {
1267                                         draw_crack(img_crack, baseimg,
1268                                                 use_overlay, frame_count,
1269                                                 progression, driver);
1270                                         img_crack->drop();
1271                                 }
1272                         }
1273                 }
1274                 /*
1275                         [combine:WxH:X,Y=filename:X,Y=filename2
1276                         Creates a bigger texture from any amount of smaller ones
1277                 */
1278                 else if (str_starts_with(part_of_name, "[combine"))
1279                 {
1280                         Strfnd sf(part_of_name);
1281                         sf.next(":");
1282                         u32 w0 = stoi(sf.next("x"));
1283                         u32 h0 = stoi(sf.next(":"));
1284                         core::dimension2d<u32> dim(w0,h0);
1285                         if (baseimg == NULL) {
1286                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1287                                 baseimg->fill(video::SColor(0,0,0,0));
1288                         }
1289                         while (sf.at_end() == false) {
1290                                 u32 x = stoi(sf.next(","));
1291                                 u32 y = stoi(sf.next("="));
1292                                 std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1293                                 infostream<<"Adding \""<<filename
1294                                                 <<"\" to combined ("<<x<<","<<y<<")"
1295                                                 <<std::endl;
1296                                 video::IImage *img = generateImage(filename);
1297                                 if (img) {
1298                                         core::dimension2d<u32> dim = img->getDimension();
1299                                         infostream<<"Size "<<dim.Width
1300                                                         <<"x"<<dim.Height<<std::endl;
1301                                         core::position2d<s32> pos_base(x, y);
1302                                         video::IImage *img2 =
1303                                                         driver->createImage(video::ECF_A8R8G8B8, dim);
1304                                         img->copyTo(img2);
1305                                         img->drop();
1306                                         /*img2->copyToWithAlpha(baseimg, pos_base,
1307                                                         core::rect<s32>(v2s32(0,0), dim),
1308                                                         video::SColor(255,255,255,255),
1309                                                         NULL);*/
1310                                         blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1311                                         img2->drop();
1312                                 } else {
1313                                         errorstream << "generateImagePart(): Failed to load image \""
1314                                                 << filename << "\" for [combine" << std::endl;
1315                                 }
1316                         }
1317                 }
1318                 /*
1319                         [brighten
1320                 */
1321                 else if (str_starts_with(part_of_name, "[brighten"))
1322                 {
1323                         if (baseimg == NULL) {
1324                                 errorstream<<"generateImagePart(): baseimg==NULL "
1325                                                 <<"for part_of_name=\""<<part_of_name
1326                                                 <<"\", cancelling."<<std::endl;
1327                                 return false;
1328                         }
1329
1330                         brighten(baseimg);
1331                 }
1332                 /*
1333                         [noalpha
1334                         Make image completely opaque.
1335                         Used for the leaves texture when in old leaves mode, so
1336                         that the transparent parts don't look completely black
1337                         when simple alpha channel is used for rendering.
1338                 */
1339                 else if (str_starts_with(part_of_name, "[noalpha"))
1340                 {
1341                         if (baseimg == NULL){
1342                                 errorstream<<"generateImagePart(): baseimg==NULL "
1343                                                 <<"for part_of_name=\""<<part_of_name
1344                                                 <<"\", cancelling."<<std::endl;
1345                                 return false;
1346                         }
1347
1348                         core::dimension2d<u32> dim = baseimg->getDimension();
1349
1350                         // Set alpha to full
1351                         for (u32 y=0; y<dim.Height; y++)
1352                         for (u32 x=0; x<dim.Width; x++)
1353                         {
1354                                 video::SColor c = baseimg->getPixel(x,y);
1355                                 c.setAlpha(255);
1356                                 baseimg->setPixel(x,y,c);
1357                         }
1358                 }
1359                 /*
1360                         [makealpha:R,G,B
1361                         Convert one color to transparent.
1362                 */
1363                 else if (str_starts_with(part_of_name, "[makealpha:"))
1364                 {
1365                         if (baseimg == NULL) {
1366                                 errorstream<<"generateImagePart(): baseimg == NULL "
1367                                                 <<"for part_of_name=\""<<part_of_name
1368                                                 <<"\", cancelling."<<std::endl;
1369                                 return false;
1370                         }
1371
1372                         Strfnd sf(part_of_name.substr(11));
1373                         u32 r1 = stoi(sf.next(","));
1374                         u32 g1 = stoi(sf.next(","));
1375                         u32 b1 = stoi(sf.next(""));
1376
1377                         core::dimension2d<u32> dim = baseimg->getDimension();
1378
1379                         /*video::IImage *oldbaseimg = baseimg;
1380                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1381                         oldbaseimg->copyTo(baseimg);
1382                         oldbaseimg->drop();*/
1383
1384                         // Set alpha to full
1385                         for (u32 y=0; y<dim.Height; y++)
1386                         for (u32 x=0; x<dim.Width; x++)
1387                         {
1388                                 video::SColor c = baseimg->getPixel(x,y);
1389                                 u32 r = c.getRed();
1390                                 u32 g = c.getGreen();
1391                                 u32 b = c.getBlue();
1392                                 if (!(r == r1 && g == g1 && b == b1))
1393                                         continue;
1394                                 c.setAlpha(0);
1395                                 baseimg->setPixel(x,y,c);
1396                         }
1397                 }
1398                 /*
1399                         [transformN
1400                         Rotates and/or flips the image.
1401
1402                         N can be a number (between 0 and 7) or a transform name.
1403                         Rotations are counter-clockwise.
1404                         0  I      identity
1405                         1  R90    rotate by 90 degrees
1406                         2  R180   rotate by 180 degrees
1407                         3  R270   rotate by 270 degrees
1408                         4  FX     flip X
1409                         5  FXR90  flip X then rotate by 90 degrees
1410                         6  FY     flip Y
1411                         7  FYR90  flip Y then rotate by 90 degrees
1412
1413                         Note: Transform names can be concatenated to produce
1414                         their product (applies the first then the second).
1415                         The resulting transform will be equivalent to one of the
1416                         eight existing ones, though (see: dihedral group).
1417                 */
1418                 else if (str_starts_with(part_of_name, "[transform"))
1419                 {
1420                         if (baseimg == NULL) {
1421                                 errorstream<<"generateImagePart(): baseimg == NULL "
1422                                                 <<"for part_of_name=\""<<part_of_name
1423                                                 <<"\", cancelling."<<std::endl;
1424                                 return false;
1425                         }
1426
1427                         u32 transform = parseImageTransform(part_of_name.substr(10));
1428                         core::dimension2d<u32> dim = imageTransformDimension(
1429                                         transform, baseimg->getDimension());
1430                         video::IImage *image = driver->createImage(
1431                                         baseimg->getColorFormat(), dim);
1432                         sanity_check(image != NULL);
1433                         imageTransform(transform, baseimg, image);
1434                         baseimg->drop();
1435                         baseimg = image;
1436                 }
1437                 /*
1438                         [inventorycube{topimage{leftimage{rightimage
1439                         In every subimage, replace ^ with &.
1440                         Create an "inventory cube".
1441                         NOTE: This should be used only on its own.
1442                         Example (a grass block (not actually used in game):
1443                         "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1444                 */
1445                 else if (str_starts_with(part_of_name, "[inventorycube"))
1446                 {
1447                         if (baseimg != NULL){
1448                                 errorstream<<"generateImagePart(): baseimg != NULL "
1449                                                 <<"for part_of_name=\""<<part_of_name
1450                                                 <<"\", cancelling."<<std::endl;
1451                                 return false;
1452                         }
1453
1454                         str_replace(part_of_name, '&', '^');
1455                         Strfnd sf(part_of_name);
1456                         sf.next("{");
1457                         std::string imagename_top = sf.next("{");
1458                         std::string imagename_left = sf.next("{");
1459                         std::string imagename_right = sf.next("{");
1460
1461                         // Generate images for the faces of the cube
1462                         video::IImage *img_top = generateImage(imagename_top);
1463                         video::IImage *img_left = generateImage(imagename_left);
1464                         video::IImage *img_right = generateImage(imagename_right);
1465
1466                         if (img_top == NULL || img_left == NULL || img_right == NULL) {
1467                                 errorstream << "generateImagePart(): Failed to create textures"
1468                                                 << " for inventorycube \"" << part_of_name << "\""
1469                                                 << std::endl;
1470                                 baseimg = generateImage(imagename_top);
1471                                 return true;
1472                         }
1473
1474 #ifdef __ANDROID__
1475                         assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1476                         assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1477
1478                         assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1479                         assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1480
1481                         assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1482                         assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1483 #endif
1484
1485                         // Create textures from images
1486                         video::ITexture *texture_top = driver->addTexture(
1487                                         (imagename_top + "__temp__").c_str(), img_top);
1488                         video::ITexture *texture_left = driver->addTexture(
1489                                         (imagename_left + "__temp__").c_str(), img_left);
1490                         video::ITexture *texture_right = driver->addTexture(
1491                                         (imagename_right + "__temp__").c_str(), img_right);
1492                         FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1493
1494                         // Drop images
1495                         img_top->drop();
1496                         img_left->drop();
1497                         img_right->drop();
1498
1499                         /*
1500                                 Draw a cube mesh into a render target texture
1501                         */
1502                         scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1503                         setMeshColor(cube, video::SColor(255, 255, 255, 255));
1504                         cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1505                         cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1506                         cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1507                         cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1508                         cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1509                         cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1510
1511                         TextureFromMeshParams params;
1512                         params.mesh = cube;
1513                         params.dim.set(64, 64);
1514                         params.rtt_texture_name = part_of_name + "_RTT";
1515                         // We will delete the rtt texture ourselves
1516                         params.delete_texture_on_shutdown = false;
1517                         params.camera_position.set(0, 1.0, -1.5);
1518                         params.camera_position.rotateXZBy(45);
1519                         params.camera_lookat.set(0, 0, 0);
1520                         // Set orthogonal projection
1521                         params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1522                                         1.65, 1.65, 0, 100);
1523
1524                         params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1525                         params.light_position.set(10, 100, -50);
1526                         params.light_color.set(1.0, 0.5, 0.5, 0.5);
1527                         params.light_radius = 1000;
1528
1529                         video::ITexture *rtt = generateTextureFromMesh(params);
1530
1531                         // Drop mesh
1532                         cube->drop();
1533
1534                         // Free textures
1535                         driver->removeTexture(texture_top);
1536                         driver->removeTexture(texture_left);
1537                         driver->removeTexture(texture_right);
1538
1539                         if (rtt == NULL) {
1540                                 baseimg = generateImage(imagename_top);
1541                                 return true;
1542                         }
1543
1544                         // Create image of render target
1545                         video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1546                         FATAL_ERROR_IF(!image, "Could not create image of render target");
1547
1548                         // Cleanup texture
1549                         driver->removeTexture(rtt);
1550
1551                         baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1552
1553                         if (image) {
1554                                 image->copyTo(baseimg);
1555                                 image->drop();
1556                         }
1557                 }
1558                 /*
1559                         [lowpart:percent:filename
1560                         Adds the lower part of a texture
1561                 */
1562                 else if (str_starts_with(part_of_name, "[lowpart:"))
1563                 {
1564                         Strfnd sf(part_of_name);
1565                         sf.next(":");
1566                         u32 percent = stoi(sf.next(":"));
1567                         std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1568
1569                         if (baseimg == NULL)
1570                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1571                         video::IImage *img = generateImage(filename);
1572                         if (img)
1573                         {
1574                                 core::dimension2d<u32> dim = img->getDimension();
1575                                 core::position2d<s32> pos_base(0, 0);
1576                                 video::IImage *img2 =
1577                                                 driver->createImage(video::ECF_A8R8G8B8, dim);
1578                                 img->copyTo(img2);
1579                                 img->drop();
1580                                 core::position2d<s32> clippos(0, 0);
1581                                 clippos.Y = dim.Height * (100-percent) / 100;
1582                                 core::dimension2d<u32> clipdim = dim;
1583                                 clipdim.Height = clipdim.Height * percent / 100 + 1;
1584                                 core::rect<s32> cliprect(clippos, clipdim);
1585                                 img2->copyToWithAlpha(baseimg, pos_base,
1586                                                 core::rect<s32>(v2s32(0,0), dim),
1587                                                 video::SColor(255,255,255,255),
1588                                                 &cliprect);
1589                                 img2->drop();
1590                         }
1591                 }
1592                 /*
1593                         [verticalframe:N:I
1594                         Crops a frame of a vertical animation.
1595                         N = frame count, I = frame index
1596                 */
1597                 else if (str_starts_with(part_of_name, "[verticalframe:"))
1598                 {
1599                         Strfnd sf(part_of_name);
1600                         sf.next(":");
1601                         u32 frame_count = stoi(sf.next(":"));
1602                         u32 frame_index = stoi(sf.next(":"));
1603
1604                         if (baseimg == NULL){
1605                                 errorstream<<"generateImagePart(): baseimg != NULL "
1606                                                 <<"for part_of_name=\""<<part_of_name
1607                                                 <<"\", cancelling."<<std::endl;
1608                                 return false;
1609                         }
1610
1611                         v2u32 frame_size = baseimg->getDimension();
1612                         frame_size.Y /= frame_count;
1613
1614                         video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1615                                         frame_size);
1616                         if (!img){
1617                                 errorstream<<"generateImagePart(): Could not create image "
1618                                                 <<"for part_of_name=\""<<part_of_name
1619                                                 <<"\", cancelling."<<std::endl;
1620                                 return false;
1621                         }
1622
1623                         // Fill target image with transparency
1624                         img->fill(video::SColor(0,0,0,0));
1625
1626                         core::dimension2d<u32> dim = frame_size;
1627                         core::position2d<s32> pos_dst(0, 0);
1628                         core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1629                         baseimg->copyToWithAlpha(img, pos_dst,
1630                                         core::rect<s32>(pos_src, dim),
1631                                         video::SColor(255,255,255,255),
1632                                         NULL);
1633                         // Replace baseimg
1634                         baseimg->drop();
1635                         baseimg = img;
1636                 }
1637                 /*
1638                         [mask:filename
1639                         Applies a mask to an image
1640                 */
1641                 else if (str_starts_with(part_of_name, "[mask:"))
1642                 {
1643                         if (baseimg == NULL) {
1644                                 errorstream << "generateImage(): baseimg == NULL "
1645                                                 << "for part_of_name=\"" << part_of_name
1646                                                 << "\", cancelling." << std::endl;
1647                                 return false;
1648                         }
1649                         Strfnd sf(part_of_name);
1650                         sf.next(":");
1651                         std::string filename = unescape_string(sf.next_esc(":", escape), escape);
1652
1653                         video::IImage *img = generateImage(filename);
1654                         if (img) {
1655                                 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1656                                                 img->getDimension());
1657                                 img->drop();
1658                         } else {
1659                                 errorstream << "generateImage(): Failed to load \""
1660                                                 << filename << "\".";
1661                         }
1662                 }
1663                 /*
1664                 [multiply:color
1665                         multiplys a given color to any pixel of an image
1666                         color = color as ColorString
1667                 */
1668                 else if (str_starts_with(part_of_name, "[multiply:")) {
1669                         Strfnd sf(part_of_name);
1670                         sf.next(":");
1671                         std::string color_str = sf.next(":");
1672
1673                         if (baseimg == NULL) {
1674                                 errorstream << "generateImagePart(): baseimg != NULL "
1675                                                 << "for part_of_name=\"" << part_of_name
1676                                                 << "\", cancelling." << std::endl;
1677                                 return false;
1678                         }
1679
1680                         video::SColor color;
1681
1682                         if (!parseColorString(color_str, color, false))
1683                                 return false;
1684
1685                         apply_multiplication(baseimg, v2u32(0, 0), baseimg->getDimension(), color);
1686                 }
1687                 /*
1688                         [colorize:color
1689                         Overlays image with given color
1690                         color = color as ColorString
1691                 */
1692                 else if (str_starts_with(part_of_name, "[colorize:"))
1693                 {
1694                         Strfnd sf(part_of_name);
1695                         sf.next(":");
1696                         std::string color_str = sf.next(":");
1697                         std::string ratio_str = sf.next(":");
1698
1699                         if (baseimg == NULL) {
1700                                 errorstream << "generateImagePart(): baseimg != NULL "
1701                                                 << "for part_of_name=\"" << part_of_name
1702                                                 << "\", cancelling." << std::endl;
1703                                 return false;
1704                         }
1705
1706                         video::SColor color;
1707                         int ratio = -1;
1708                         bool keep_alpha = false;
1709
1710                         if (!parseColorString(color_str, color, false))
1711                                 return false;
1712
1713                         if (is_number(ratio_str))
1714                                 ratio = mystoi(ratio_str, 0, 255);
1715                         else if (ratio_str == "alpha")
1716                                 keep_alpha = true;
1717
1718                         apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1719                 }
1720                 /*
1721                         [applyfiltersformesh
1722                         Internal modifier
1723                 */
1724                 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1725                 {
1726                         // Apply the "clean transparent" filter, if configured.
1727                         if (g_settings->getBool("texture_clean_transparent"))
1728                                 imageCleanTransparent(baseimg, 127);
1729
1730                         /* Upscale textures to user's requested minimum size.  This is a trick to make
1731                          * filters look as good on low-res textures as on high-res ones, by making
1732                          * low-res textures BECOME high-res ones.  This is helpful for worlds that
1733                          * mix high- and low-res textures, or for mods with least-common-denominator
1734                          * textures that don't have the resources to offer high-res alternatives.
1735                          */
1736                         s32 scaleto = g_settings->getS32("texture_min_size");
1737                         if (scaleto > 1) {
1738                                 const core::dimension2d<u32> dim = baseimg->getDimension();
1739
1740                                 /* Calculate scaling needed to make the shortest texture dimension
1741                                  * equal to the target minimum.  If e.g. this is a vertical frames
1742                                  * animation, the short dimension will be the real size.
1743                                  */
1744                                 if ((dim.Width == 0) || (dim.Height == 0)) {
1745                                         errorstream << "generateImagePart(): Illegal 0 dimension "
1746                                                 << "for part_of_name=\""<< part_of_name
1747                                                 << "\", cancelling." << std::endl;
1748                                         return false;
1749                                 }
1750                                 u32 xscale = scaleto / dim.Width;
1751                                 u32 yscale = scaleto / dim.Height;
1752                                 u32 scale = (xscale > yscale) ? xscale : yscale;
1753
1754                                 // Never downscale; only scale up by 2x or more.
1755                                 if (scale > 1) {
1756                                         u32 w = scale * dim.Width;
1757                                         u32 h = scale * dim.Height;
1758                                         const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1759                                         video::IImage *newimg = driver->createImage(
1760                                                         baseimg->getColorFormat(), newdim);
1761                                         baseimg->copyToScaling(newimg);
1762                                         baseimg->drop();
1763                                         baseimg = newimg;
1764                                 }
1765                         }
1766                 }
1767                 /*
1768                         [resize:WxH
1769                         Resizes the base image to the given dimensions
1770                 */
1771                 else if (str_starts_with(part_of_name, "[resize"))
1772                 {
1773                         if (baseimg == NULL) {
1774                                 errorstream << "generateImagePart(): baseimg == NULL "
1775                                                 << "for part_of_name=\""<< part_of_name
1776                                                 << "\", cancelling." << std::endl;
1777                                 return false;
1778                         }
1779
1780                         Strfnd sf(part_of_name);
1781                         sf.next(":");
1782                         u32 width = stoi(sf.next("x"));
1783                         u32 height = stoi(sf.next(""));
1784                         core::dimension2d<u32> dim(width, height);
1785
1786                         video::IImage* image = m_device->getVideoDriver()->
1787                                 createImage(video::ECF_A8R8G8B8, dim);
1788                         baseimg->copyToScaling(image);
1789                         baseimg->drop();
1790                         baseimg = image;
1791                 }
1792                 /*
1793                         [opacity:R
1794                         Makes the base image transparent according to the given ratio.
1795                         R must be between 0 and 255.
1796                         0 means totally transparent.
1797                         255 means totally opaque.
1798                 */
1799                 else if (str_starts_with(part_of_name, "[opacity:")) {
1800                         if (baseimg == NULL) {
1801                                 errorstream << "generateImagePart(): baseimg == NULL "
1802                                                 << "for part_of_name=\"" << part_of_name
1803                                                 << "\", cancelling." << std::endl;
1804                                 return false;
1805                         }
1806
1807                         Strfnd sf(part_of_name);
1808                         sf.next(":");
1809
1810                         u32 ratio = mystoi(sf.next(""), 0, 255);
1811
1812                         core::dimension2d<u32> dim = baseimg->getDimension();
1813
1814                         for (u32 y = 0; y < dim.Height; y++)
1815                         for (u32 x = 0; x < dim.Width; x++)
1816                         {
1817                                 video::SColor c = baseimg->getPixel(x, y);
1818                                 c.setAlpha(floor((c.getAlpha() * ratio) / 255 + 0.5));
1819                                 baseimg->setPixel(x, y, c);
1820                         }
1821                 }
1822                 /*
1823                         [invert:mode
1824                         Inverts the given channels of the base image.
1825                         Mode may contain the characters "r", "g", "b", "a".
1826                         Only the channels that are mentioned in the mode string
1827                         will be inverted.
1828                 */
1829                 else if (str_starts_with(part_of_name, "[invert:")) {
1830                         if (baseimg == NULL) {
1831                                 errorstream << "generateImagePart(): baseimg == NULL "
1832                                                 << "for part_of_name=\"" << part_of_name
1833                                                 << "\", cancelling." << std::endl;
1834                                 return false;
1835                         }
1836
1837                         Strfnd sf(part_of_name);
1838                         sf.next(":");
1839
1840                         std::string mode = sf.next("");
1841                         u32 mask = 0;
1842                         if (mode.find("a") != std::string::npos)
1843                                 mask |= 0xff000000UL;
1844                         if (mode.find("r") != std::string::npos)
1845                                 mask |= 0x00ff0000UL;
1846                         if (mode.find("g") != std::string::npos)
1847                                 mask |= 0x0000ff00UL;
1848                         if (mode.find("b") != std::string::npos)
1849                                 mask |= 0x000000ffUL;
1850
1851                         core::dimension2d<u32> dim = baseimg->getDimension();
1852
1853                         for (u32 y = 0; y < dim.Height; y++)
1854                         for (u32 x = 0; x < dim.Width; x++)
1855                         {
1856                                 video::SColor c = baseimg->getPixel(x, y);
1857                                 c.color ^= mask;        
1858                                 baseimg->setPixel(x, y, c);
1859                         }
1860                 }
1861                 /*
1862                         [sheet:WxH:X,Y
1863                         Retrieves a tile at position X,Y (in tiles)
1864                         from the base image it assumes to be a
1865                         tilesheet with dimensions W,H (in tiles).
1866                 */
1867                 else if (part_of_name.substr(0,7) == "[sheet:") {
1868                         if (baseimg == NULL) {
1869                                 errorstream << "generateImagePart(): baseimg != NULL "
1870                                                 << "for part_of_name=\"" << part_of_name
1871                                                 << "\", cancelling." << std::endl;
1872                                 return false;
1873                         }
1874
1875                         Strfnd sf(part_of_name);
1876                         sf.next(":");
1877                         u32 w0 = stoi(sf.next("x"));
1878                         u32 h0 = stoi(sf.next(":"));
1879                         u32 x0 = stoi(sf.next(","));
1880                         u32 y0 = stoi(sf.next(":"));
1881
1882                         core::dimension2d<u32> img_dim = baseimg->getDimension();
1883                         core::dimension2d<u32> tile_dim(v2u32(img_dim) / v2u32(w0, h0));
1884
1885                         video::IImage *img = driver->createImage(
1886                                         video::ECF_A8R8G8B8, tile_dim);
1887                         if (!img) {
1888                                 errorstream << "generateImagePart(): Could not create image "
1889                                                 << "for part_of_name=\"" << part_of_name
1890                                                 << "\", cancelling." << std::endl;
1891                                 return false;
1892                         }
1893
1894                         img->fill(video::SColor(0,0,0,0));
1895                         v2u32 vdim(tile_dim);
1896                         core::rect<s32> rect(v2s32(x0 * vdim.X, y0 * vdim.Y), tile_dim);
1897                         baseimg->copyToWithAlpha(img, v2s32(0), rect,
1898                                         video::SColor(255,255,255,255), NULL);
1899
1900                         // Replace baseimg
1901                         baseimg->drop();
1902                         baseimg = img;
1903                 }
1904                 else
1905                 {
1906                         errorstream << "generateImagePart(): Invalid "
1907                                         " modification: \"" << part_of_name << "\"" << std::endl;
1908                 }
1909         }
1910
1911         return true;
1912 }
1913
1914 /*
1915         Draw an image on top of an another one, using the alpha channel of the
1916         source image
1917
1918         This exists because IImage::copyToWithAlpha() doesn't seem to always
1919         work.
1920 */
1921 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1922                 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1923 {
1924         for (u32 y0=0; y0<size.Y; y0++)
1925         for (u32 x0=0; x0<size.X; x0++)
1926         {
1927                 s32 src_x = src_pos.X + x0;
1928                 s32 src_y = src_pos.Y + y0;
1929                 s32 dst_x = dst_pos.X + x0;
1930                 s32 dst_y = dst_pos.Y + y0;
1931                 video::SColor src_c = src->getPixel(src_x, src_y);
1932                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1933                 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1934                 dst->setPixel(dst_x, dst_y, dst_c);
1935         }
1936 }
1937
1938 /*
1939         Draw an image on top of an another one, using the alpha channel of the
1940         source image; only modify fully opaque pixels in destinaion
1941 */
1942 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1943                 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1944 {
1945         for (u32 y0=0; y0<size.Y; y0++)
1946         for (u32 x0=0; x0<size.X; x0++)
1947         {
1948                 s32 src_x = src_pos.X + x0;
1949                 s32 src_y = src_pos.Y + y0;
1950                 s32 dst_x = dst_pos.X + x0;
1951                 s32 dst_y = dst_pos.Y + y0;
1952                 video::SColor src_c = src->getPixel(src_x, src_y);
1953                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1954                 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1955                 {
1956                         dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1957                         dst->setPixel(dst_x, dst_y, dst_c);
1958                 }
1959         }
1960 }
1961
1962 // This function has been disabled because it is currently unused.
1963 // Feel free to re-enable if you find it handy.
1964 #if 0
1965 /*
1966         Draw an image on top of an another one, using the specified ratio
1967         modify all partially-opaque pixels in the destination.
1968 */
1969 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1970                 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1971 {
1972         for (u32 y0 = 0; y0 < size.Y; y0++)
1973         for (u32 x0 = 0; x0 < size.X; x0++)
1974         {
1975                 s32 src_x = src_pos.X + x0;
1976                 s32 src_y = src_pos.Y + y0;
1977                 s32 dst_x = dst_pos.X + x0;
1978                 s32 dst_y = dst_pos.Y + y0;
1979                 video::SColor src_c = src->getPixel(src_x, src_y);
1980                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1981                 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1982                 {
1983                         if (ratio == -1)
1984                                 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1985                         else
1986                                 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1987                         dst->setPixel(dst_x, dst_y, dst_c);
1988                 }
1989         }
1990 }
1991 #endif
1992
1993 /*
1994         Apply color to destination
1995 */
1996 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1997                 const video::SColor &color, int ratio, bool keep_alpha)
1998 {
1999         u32 alpha = color.getAlpha();
2000         video::SColor dst_c;
2001         if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
2002                 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
2003                         dst_c = color;
2004                         for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2005                         for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2006                                 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
2007                                 if (dst_alpha > 0) {
2008                                         dst_c.setAlpha(dst_alpha * alpha / 255);
2009                                         dst->setPixel(x, y, dst_c);
2010                                 }
2011                         }
2012                 } else { // replace the color including the alpha
2013                         for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2014                         for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
2015                                 if (dst->getPixel(x, y).getAlpha() > 0)
2016                                         dst->setPixel(x, y, color);
2017                 }
2018         } else {  // interpolate between the color and destination
2019                 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
2020                 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2021                 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2022                         dst_c = dst->getPixel(x, y);
2023                         if (dst_c.getAlpha() > 0) {
2024                                 dst_c = color.getInterpolated(dst_c, interp);
2025                                 dst->setPixel(x, y, dst_c);
2026                         }
2027                 }
2028         }
2029 }
2030
2031 /*
2032         Apply color to destination
2033 */
2034 static void apply_multiplication(video::IImage *dst, v2u32 dst_pos, v2u32 size,
2035                 const video::SColor &color)
2036 {
2037         video::SColor dst_c;
2038
2039         for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
2040         for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
2041                 dst_c = dst->getPixel(x, y);
2042                 dst_c.set(
2043                                 dst_c.getAlpha(),
2044                                 (dst_c.getRed() * color.getRed()) / 255,
2045                                 (dst_c.getGreen() * color.getGreen()) / 255,
2046                                 (dst_c.getBlue() * color.getBlue()) / 255
2047                                 );
2048                 dst->setPixel(x, y, dst_c);
2049         }
2050 }
2051
2052 /*
2053         Apply mask to destination
2054 */
2055 static void apply_mask(video::IImage *mask, video::IImage *dst,
2056                 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
2057 {
2058         for (u32 y0 = 0; y0 < size.Y; y0++) {
2059                 for (u32 x0 = 0; x0 < size.X; x0++) {
2060                         s32 mask_x = x0 + mask_pos.X;
2061                         s32 mask_y = y0 + mask_pos.Y;
2062                         s32 dst_x = x0 + dst_pos.X;
2063                         s32 dst_y = y0 + dst_pos.Y;
2064                         video::SColor mask_c = mask->getPixel(mask_x, mask_y);
2065                         video::SColor dst_c = dst->getPixel(dst_x, dst_y);
2066                         dst_c.color &= mask_c.color;
2067                         dst->setPixel(dst_x, dst_y, dst_c);
2068                 }
2069         }
2070 }
2071
2072 static void draw_crack(video::IImage *crack, video::IImage *dst,
2073                 bool use_overlay, s32 frame_count, s32 progression,
2074                 video::IVideoDriver *driver)
2075 {
2076         // Dimension of destination image
2077         core::dimension2d<u32> dim_dst = dst->getDimension();
2078         // Dimension of original image
2079         core::dimension2d<u32> dim_crack = crack->getDimension();
2080         // Count of crack stages
2081         s32 crack_count = dim_crack.Height / dim_crack.Width;
2082         // Limit frame_count
2083         if (frame_count > (s32) dim_dst.Height)
2084                 frame_count = dim_dst.Height;
2085         if (frame_count < 1)
2086                 frame_count = 1;
2087         // Limit progression
2088         if (progression > crack_count-1)
2089                 progression = crack_count-1;
2090         // Dimension of a single crack stage
2091         core::dimension2d<u32> dim_crack_cropped(
2092                 dim_crack.Width,
2093                 dim_crack.Width
2094         );
2095         // Dimension of the scaled crack stage,
2096         // which is the same as the dimension of a single destination frame
2097         core::dimension2d<u32> dim_crack_scaled(
2098                 dim_dst.Width,
2099                 dim_dst.Height / frame_count
2100         );
2101         // Create cropped and scaled crack images
2102         video::IImage *crack_cropped = driver->createImage(
2103                         video::ECF_A8R8G8B8, dim_crack_cropped);
2104         video::IImage *crack_scaled = driver->createImage(
2105                         video::ECF_A8R8G8B8, dim_crack_scaled);
2106
2107         if (crack_cropped && crack_scaled)
2108         {
2109                 // Crop crack image
2110                 v2s32 pos_crack(0, progression*dim_crack.Width);
2111                 crack->copyTo(crack_cropped,
2112                                 v2s32(0,0),
2113                                 core::rect<s32>(pos_crack, dim_crack_cropped));
2114                 // Scale crack image by copying
2115                 crack_cropped->copyToScaling(crack_scaled);
2116                 // Copy or overlay crack image onto each frame
2117                 for (s32 i = 0; i < frame_count; ++i)
2118                 {
2119                         v2s32 dst_pos(0, dim_crack_scaled.Height * i);
2120                         if (use_overlay)
2121                         {
2122                                 blit_with_alpha_overlay(crack_scaled, dst,
2123                                                 v2s32(0,0), dst_pos,
2124                                                 dim_crack_scaled);
2125                         }
2126                         else
2127                         {
2128                                 blit_with_alpha(crack_scaled, dst,
2129                                                 v2s32(0,0), dst_pos,
2130                                                 dim_crack_scaled);
2131                         }
2132                 }
2133         }
2134
2135         if (crack_scaled)
2136                 crack_scaled->drop();
2137
2138         if (crack_cropped)
2139                 crack_cropped->drop();
2140 }
2141
2142 void brighten(video::IImage *image)
2143 {
2144         if (image == NULL)
2145                 return;
2146
2147         core::dimension2d<u32> dim = image->getDimension();
2148
2149         for (u32 y=0; y<dim.Height; y++)
2150         for (u32 x=0; x<dim.Width; x++)
2151         {
2152                 video::SColor c = image->getPixel(x,y);
2153                 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
2154                 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
2155                 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
2156                 image->setPixel(x,y,c);
2157         }
2158 }
2159
2160 u32 parseImageTransform(const std::string& s)
2161 {
2162         int total_transform = 0;
2163
2164         std::string transform_names[8];
2165         transform_names[0] = "i";
2166         transform_names[1] = "r90";
2167         transform_names[2] = "r180";
2168         transform_names[3] = "r270";
2169         transform_names[4] = "fx";
2170         transform_names[6] = "fy";
2171
2172         std::size_t pos = 0;
2173         while(pos < s.size())
2174         {
2175                 int transform = -1;
2176                 for (int i = 0; i <= 7; ++i)
2177                 {
2178                         const std::string &name_i = transform_names[i];
2179
2180                         if (s[pos] == ('0' + i))
2181                         {
2182                                 transform = i;
2183                                 pos++;
2184                                 break;
2185                         }
2186                         else if (!(name_i.empty()) &&
2187                                 lowercase(s.substr(pos, name_i.size())) == name_i)
2188                         {
2189                                 transform = i;
2190                                 pos += name_i.size();
2191                                 break;
2192                         }
2193                 }
2194                 if (transform < 0)
2195                         break;
2196
2197                 // Multiply total_transform and transform in the group D4
2198                 int new_total = 0;
2199                 if (transform < 4)
2200                         new_total = (transform + total_transform) % 4;
2201                 else
2202                         new_total = (transform - total_transform + 8) % 4;
2203                 if ((transform >= 4) ^ (total_transform >= 4))
2204                         new_total += 4;
2205
2206                 total_transform = new_total;
2207         }
2208         return total_transform;
2209 }
2210
2211 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2212 {
2213         if (transform % 2 == 0)
2214                 return dim;
2215         else
2216                 return core::dimension2d<u32>(dim.Height, dim.Width);
2217 }
2218
2219 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2220 {
2221         if (src == NULL || dst == NULL)
2222                 return;
2223
2224         core::dimension2d<u32> dstdim = dst->getDimension();
2225
2226         // Pre-conditions
2227         assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2228         assert(transform <= 7);
2229
2230         /*
2231                 Compute the transformation from source coordinates (sx,sy)
2232                 to destination coordinates (dx,dy).
2233         */
2234         int sxn = 0;
2235         int syn = 2;
2236         if (transform == 0)         // identity
2237                 sxn = 0, syn = 2;  //   sx = dx, sy = dy
2238         else if (transform == 1)    // rotate by 90 degrees ccw
2239                 sxn = 3, syn = 0;  //   sx = (H-1) - dy, sy = dx
2240         else if (transform == 2)    // rotate by 180 degrees
2241                 sxn = 1, syn = 3;  //   sx = (W-1) - dx, sy = (H-1) - dy
2242         else if (transform == 3)    // rotate by 270 degrees ccw
2243                 sxn = 2, syn = 1;  //   sx = dy, sy = (W-1) - dx
2244         else if (transform == 4)    // flip x
2245                 sxn = 1, syn = 2;  //   sx = (W-1) - dx, sy = dy
2246         else if (transform == 5)    // flip x then rotate by 90 degrees ccw
2247                 sxn = 2, syn = 0;  //   sx = dy, sy = dx
2248         else if (transform == 6)    // flip y
2249                 sxn = 0, syn = 3;  //   sx = dx, sy = (H-1) - dy
2250         else if (transform == 7)    // flip y then rotate by 90 degrees ccw
2251                 sxn = 3, syn = 1;  //   sx = (H-1) - dy, sy = (W-1) - dx
2252
2253         for (u32 dy=0; dy<dstdim.Height; dy++)
2254         for (u32 dx=0; dx<dstdim.Width; dx++)
2255         {
2256                 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2257                 u32 sx = entries[sxn];
2258                 u32 sy = entries[syn];
2259                 video::SColor c = src->getPixel(sx,sy);
2260                 dst->setPixel(dx,dy,c);
2261         }
2262 }
2263
2264 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2265 {
2266         if (isKnownSourceImage("override_normal.png"))
2267                 return getTexture("override_normal.png");
2268         std::string fname_base = name;
2269         std::string normal_ext = "_normal.png";
2270         size_t pos = fname_base.find(".");
2271         std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2272         if (isKnownSourceImage(fname_normal)) {
2273                 // look for image extension and replace it
2274                 size_t i = 0;
2275                 while ((i = fname_base.find(".", i)) != std::string::npos) {
2276                         fname_base.replace(i, 4, normal_ext);
2277                         i += normal_ext.length();
2278                 }
2279                 return getTexture(fname_base);
2280                 }
2281         return NULL;
2282 }
2283
2284 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2285 {
2286         video::IVideoDriver *driver = m_device->getVideoDriver();
2287         video::SColor c(0, 0, 0, 0);
2288         video::ITexture *texture = getTexture(name);
2289         video::IImage *image = driver->createImage(texture,
2290                 core::position2d<s32>(0, 0),
2291                 texture->getOriginalSize());
2292         u32 total = 0;
2293         u32 tR = 0;
2294         u32 tG = 0;
2295         u32 tB = 0;
2296         core::dimension2d<u32> dim = image->getDimension();
2297         u16 step = 1;
2298         if (dim.Width > 16)
2299                 step = dim.Width / 16;
2300         for (u16 x = 0; x < dim.Width; x += step) {
2301                 for (u16 y = 0; y < dim.Width; y += step) {
2302                         c = image->getPixel(x,y);
2303                         if (c.getAlpha() > 0) {
2304                                 total++;
2305                                 tR += c.getRed();
2306                                 tG += c.getGreen();
2307                                 tB += c.getBlue();
2308                         }
2309                 }
2310         }
2311         image->drop();
2312         if (total > 0) {
2313                 c.setRed(tR / total);
2314                 c.setGreen(tG / total);
2315                 c.setBlue(tB / total);
2316         }
2317         c.setAlpha(255);
2318         return c;
2319 }
2320
2321
2322 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2323 {
2324         std::string tname = "__shaderFlagsTexture";
2325         tname += normalmap_present ? "1" : "0";
2326
2327         if (isKnownSourceImage(tname)) {
2328                 return getTexture(tname);
2329         } else {
2330                 video::IVideoDriver *driver = m_device->getVideoDriver();
2331                 video::IImage *flags_image = driver->createImage(
2332                         video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2333                 sanity_check(flags_image != NULL);
2334                 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2335                 flags_image->setPixel(0, 0, c);
2336                 insertSourceImage(tname, flags_image);
2337                 flags_image->drop();
2338                 return getTexture(tname);
2339         }
2340 }