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