]> git.lizzy.rs Git - dragonfireclient.git/blob - src/client/tile.cpp
Add [resize texture modifier Resizes the texture to the given dimensions.
[dragonfireclient.git] / src / client / tile.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4
5 This program is free software; you can redistribute it and/or modify
6 it under the terms of the GNU Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 GNU Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public License along
16 with this program; if not, write to the Free Software Foundation, Inc.,
17 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19
20 #include "tile.h"
21
22 #include <ICameraSceneNode.h>
23 #include "util/string.h"
24 #include "util/container.h"
25 #include "util/thread.h"
26 #include "util/numeric.h"
27 #include "irrlichttypes_extrabloated.h"
28 #include "debug.h"
29 #include "filesys.h"
30 #include "settings.h"
31 #include "mesh.h"
32 #include "log.h"
33 #include "gamedef.h"
34 #include "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, 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         /*
952                 Get the base image
953         */
954
955         const char separator = '^';
956         const char paren_open = '(';
957         const char paren_close = ')';
958
959         // Find last separator in the name
960         s32 last_separator_pos = -1;
961         u8 paren_bal = 0;
962         for (s32 i = name.size() - 1; i >= 0; i--) {
963                 switch(name[i]) {
964                 case separator:
965                         if (paren_bal == 0) {
966                                 last_separator_pos = i;
967                                 i = -1; // break out of loop
968                         }
969                         break;
970                 case paren_open:
971                         if (paren_bal == 0) {
972                                 errorstream << "generateImage(): unbalanced parentheses"
973                                                 << "(extranous '(') while generating texture \""
974                                                 << name << "\"" << std::endl;
975                                 return NULL;
976                         }
977                         paren_bal--;
978                         break;
979                 case paren_close:
980                         paren_bal++;
981                         break;
982                 default:
983                         break;
984                 }
985         }
986         if (paren_bal > 0) {
987                 errorstream << "generateImage(): unbalanced parentheses"
988                                 << "(missing matching '(') while generating texture \""
989                                 << name << "\"" << std::endl;
990                 return NULL;
991         }
992
993
994         video::IImage *baseimg = NULL;
995
996         /*
997                 If separator was found, make the base image
998                 using a recursive call.
999         */
1000         if (last_separator_pos != -1) {
1001                 baseimg = generateImage(name.substr(0, last_separator_pos));
1002         }
1003
1004
1005         video::IVideoDriver* driver = m_device->getVideoDriver();
1006         sanity_check(driver);
1007
1008         /*
1009                 Parse out the last part of the name of the image and act
1010                 according to it
1011         */
1012
1013         std::string last_part_of_name = name.substr(last_separator_pos + 1);
1014
1015         /*
1016                 If this name is enclosed in parentheses, generate it
1017                 and blit it onto the base image
1018         */
1019         if (last_part_of_name[0] == paren_open
1020                         && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
1021                 std::string name2 = last_part_of_name.substr(1,
1022                                 last_part_of_name.size() - 2);
1023                 video::IImage *tmp = generateImage(name2);
1024                 if (!tmp) {
1025                         errorstream << "generateImage(): "
1026                                 "Failed to generate \"" << name2 << "\""
1027                                 << std::endl;
1028                         return NULL;
1029                 }
1030                 core::dimension2d<u32> dim = tmp->getDimension();
1031                 if (!baseimg)
1032                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1033                 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1034                 tmp->drop();
1035         } else if (!generateImagePart(last_part_of_name, baseimg)) {
1036                 // Generate image according to part of name
1037                 errorstream << "generateImage(): "
1038                                 "Failed to generate \"" << last_part_of_name << "\""
1039                                 << std::endl;
1040         }
1041
1042         // If no resulting image, print a warning
1043         if (baseimg == NULL) {
1044                 errorstream << "generateImage(): baseimg is NULL (attempted to"
1045                                 " create texture \"" << name << "\")" << std::endl;
1046         }
1047
1048         return baseimg;
1049 }
1050
1051 #ifdef __ANDROID__
1052 #include <GLES/gl.h>
1053 /**
1054  * Check and align image to npot2 if required by hardware
1055  * @param image image to check for npot2 alignment
1056  * @param driver driver to use for image operations
1057  * @return image or copy of image aligned to npot2
1058  */
1059 video::IImage * Align2Npot2(video::IImage * image,
1060                 video::IVideoDriver* driver)
1061 {
1062         if (image == NULL) {
1063                 return image;
1064         }
1065
1066         core::dimension2d<u32> dim = image->getDimension();
1067
1068         std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1069         if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1070                 return image;
1071         }
1072
1073         unsigned int height = npot2(dim.Height);
1074         unsigned int width  = npot2(dim.Width);
1075
1076         if ((dim.Height == height) &&
1077                         (dim.Width == width)) {
1078                 return image;
1079         }
1080
1081         if (dim.Height > height) {
1082                 height *= 2;
1083         }
1084
1085         if (dim.Width > width) {
1086                 width *= 2;
1087         }
1088
1089         video::IImage *targetimage =
1090                         driver->createImage(video::ECF_A8R8G8B8,
1091                                         core::dimension2d<u32>(width, height));
1092
1093         if (targetimage != NULL) {
1094                 image->copyToScaling(targetimage);
1095         }
1096         image->drop();
1097         return targetimage;
1098 }
1099
1100 #endif
1101
1102 bool TextureSource::generateImagePart(std::string part_of_name,
1103                 video::IImage *& baseimg)
1104 {
1105         video::IVideoDriver* driver = m_device->getVideoDriver();
1106         sanity_check(driver);
1107
1108         // Stuff starting with [ are special commands
1109         if (part_of_name.size() == 0 || part_of_name[0] != '[')
1110         {
1111                 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1112 #ifdef __ANDROID__
1113                 image = Align2Npot2(image, driver);
1114 #endif
1115                 if (image == NULL) {
1116                         if (part_of_name != "") {
1117                                 if (part_of_name.find("_normal.png") == std::string::npos){
1118                                         errorstream<<"generateImage(): Could not load image \""
1119                                                 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1120                                         errorstream<<"generateImage(): Creating a dummy"
1121                                                 <<" image for \""<<part_of_name<<"\""<<std::endl;
1122                                 } else {
1123                                         infostream<<"generateImage(): Could not load normal map \""
1124                                                 <<part_of_name<<"\""<<std::endl;
1125                                         infostream<<"generateImage(): Creating a dummy"
1126                                                 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1127                                 }
1128                         }
1129
1130                         // Just create a dummy image
1131                         //core::dimension2d<u32> dim(2,2);
1132                         core::dimension2d<u32> dim(1,1);
1133                         image = driver->createImage(video::ECF_A8R8G8B8, dim);
1134                         sanity_check(image != NULL);
1135                         /*image->setPixel(0,0, video::SColor(255,255,0,0));
1136                         image->setPixel(1,0, video::SColor(255,0,255,0));
1137                         image->setPixel(0,1, video::SColor(255,0,0,255));
1138                         image->setPixel(1,1, video::SColor(255,255,0,255));*/
1139                         image->setPixel(0,0, video::SColor(255,myrand()%256,
1140                                         myrand()%256,myrand()%256));
1141                         /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1142                                         myrand()%256,myrand()%256));
1143                         image->setPixel(0,1, video::SColor(255,myrand()%256,
1144                                         myrand()%256,myrand()%256));
1145                         image->setPixel(1,1, video::SColor(255,myrand()%256,
1146                                         myrand()%256,myrand()%256));*/
1147                 }
1148
1149                 // If base image is NULL, load as base.
1150                 if (baseimg == NULL)
1151                 {
1152                         //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1153                         /*
1154                                 Copy it this way to get an alpha channel.
1155                                 Otherwise images with alpha cannot be blitted on
1156                                 images that don't have alpha in the original file.
1157                         */
1158                         core::dimension2d<u32> dim = image->getDimension();
1159                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1160                         image->copyTo(baseimg);
1161                 }
1162                 // Else blit on base.
1163                 else
1164                 {
1165                         //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1166                         // Size of the copied area
1167                         core::dimension2d<u32> dim = image->getDimension();
1168                         //core::dimension2d<u32> dim(16,16);
1169                         // Position to copy the blitted to in the base image
1170                         core::position2d<s32> pos_to(0,0);
1171                         // Position to copy the blitted from in the blitted image
1172                         core::position2d<s32> pos_from(0,0);
1173                         // Blit
1174                         /*image->copyToWithAlpha(baseimg, pos_to,
1175                                         core::rect<s32>(pos_from, dim),
1176                                         video::SColor(255,255,255,255),
1177                                         NULL);*/
1178
1179                         core::dimension2d<u32> dim_dst = baseimg->getDimension();
1180                         if (dim == dim_dst) {
1181                                 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1182                         } else if (dim.Width * dim.Height < dim_dst.Width * dim_dst.Height) {
1183                                 // Upscale overlying image
1184                                 video::IImage* scaled_image = m_device->getVideoDriver()->
1185                                         createImage(video::ECF_A8R8G8B8, dim_dst);
1186                                 image->copyToScaling(scaled_image);
1187
1188                                 blit_with_alpha(scaled_image, baseimg, pos_from, pos_to, dim_dst);
1189                                 scaled_image->drop();
1190                         } else {
1191                                 // Upscale base image
1192                                 video::IImage* scaled_base = m_device->getVideoDriver()->
1193                                         createImage(video::ECF_A8R8G8B8, dim);
1194                                 baseimg->copyToScaling(scaled_base);
1195                                 baseimg->drop();
1196                                 baseimg = scaled_base;
1197
1198                                 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1199                         }
1200                 }
1201                 //cleanup
1202                 image->drop();
1203         }
1204         else
1205         {
1206                 // A special texture modification
1207
1208                 /*infostream<<"generateImage(): generating special "
1209                                 <<"modification \""<<part_of_name<<"\""
1210                                 <<std::endl;*/
1211
1212                 /*
1213                         [crack:N:P
1214                         [cracko:N:P
1215                         Adds a cracking texture
1216                         N = animation frame count, P = crack progression
1217                 */
1218                 if (str_starts_with(part_of_name, "[crack"))
1219                 {
1220                         if (baseimg == NULL) {
1221                                 errorstream<<"generateImagePart(): baseimg == NULL "
1222                                                 <<"for part_of_name=\""<<part_of_name
1223                                                 <<"\", cancelling."<<std::endl;
1224                                 return false;
1225                         }
1226
1227                         // Crack image number and overlay option
1228                         bool use_overlay = (part_of_name[6] == 'o');
1229                         Strfnd sf(part_of_name);
1230                         sf.next(":");
1231                         s32 frame_count = stoi(sf.next(":"));
1232                         s32 progression = stoi(sf.next(":"));
1233
1234                         if (progression >= 0) {
1235                                 /*
1236                                         Load crack image.
1237
1238                                         It is an image with a number of cracking stages
1239                                         horizontally tiled.
1240                                 */
1241                                 video::IImage *img_crack = m_sourcecache.getOrLoad(
1242                                         "crack_anylength.png", m_device);
1243
1244                                 if (img_crack) {
1245                                         draw_crack(img_crack, baseimg,
1246                                                 use_overlay, frame_count,
1247                                                 progression, driver);
1248                                         img_crack->drop();
1249                                 }
1250                         }
1251                 }
1252                 /*
1253                         [combine:WxH:X,Y=filename:X,Y=filename2
1254                         Creates a bigger texture from an amount of smaller ones
1255                 */
1256                 else if (str_starts_with(part_of_name, "[combine"))
1257                 {
1258                         Strfnd sf(part_of_name);
1259                         sf.next(":");
1260                         u32 w0 = stoi(sf.next("x"));
1261                         u32 h0 = stoi(sf.next(":"));
1262                         //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1263                         core::dimension2d<u32> dim(w0,h0);
1264                         if (baseimg == NULL) {
1265                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1266                                 baseimg->fill(video::SColor(0,0,0,0));
1267                         }
1268                         while (sf.at_end() == false) {
1269                                 u32 x = stoi(sf.next(","));
1270                                 u32 y = stoi(sf.next("="));
1271                                 std::string filename = sf.next(":");
1272                                 infostream<<"Adding \""<<filename
1273                                                 <<"\" to combined ("<<x<<","<<y<<")"
1274                                                 <<std::endl;
1275                                 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1276                                 if (img) {
1277                                         core::dimension2d<u32> dim = img->getDimension();
1278                                         infostream<<"Size "<<dim.Width
1279                                                         <<"x"<<dim.Height<<std::endl;
1280                                         core::position2d<s32> pos_base(x, y);
1281                                         video::IImage *img2 =
1282                                                         driver->createImage(video::ECF_A8R8G8B8, dim);
1283                                         img->copyTo(img2);
1284                                         img->drop();
1285                                         /*img2->copyToWithAlpha(baseimg, pos_base,
1286                                                         core::rect<s32>(v2s32(0,0), dim),
1287                                                         video::SColor(255,255,255,255),
1288                                                         NULL);*/
1289                                         blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1290                                         img2->drop();
1291                                 } else {
1292                                         errorstream << "generateImagePart(): Failed to load image \""
1293                                                 << filename << "\" for [combine" << std::endl;
1294                                 }
1295                         }
1296                 }
1297                 /*
1298                         "[brighten"
1299                 */
1300                 else if (str_starts_with(part_of_name, "[brighten"))
1301                 {
1302                         if (baseimg == NULL) {
1303                                 errorstream<<"generateImagePart(): baseimg==NULL "
1304                                                 <<"for part_of_name=\""<<part_of_name
1305                                                 <<"\", cancelling."<<std::endl;
1306                                 return false;
1307                         }
1308
1309                         brighten(baseimg);
1310                 }
1311                 /*
1312                         "[noalpha"
1313                         Make image completely opaque.
1314                         Used for the leaves texture when in old leaves mode, so
1315                         that the transparent parts don't look completely black
1316                         when simple alpha channel is used for rendering.
1317                 */
1318                 else if (str_starts_with(part_of_name, "[noalpha"))
1319                 {
1320                         if (baseimg == NULL){
1321                                 errorstream<<"generateImagePart(): baseimg==NULL "
1322                                                 <<"for part_of_name=\""<<part_of_name
1323                                                 <<"\", cancelling."<<std::endl;
1324                                 return false;
1325                         }
1326
1327                         core::dimension2d<u32> dim = baseimg->getDimension();
1328
1329                         // Set alpha to full
1330                         for (u32 y=0; y<dim.Height; y++)
1331                         for (u32 x=0; x<dim.Width; x++)
1332                         {
1333                                 video::SColor c = baseimg->getPixel(x,y);
1334                                 c.setAlpha(255);
1335                                 baseimg->setPixel(x,y,c);
1336                         }
1337                 }
1338                 /*
1339                         "[makealpha:R,G,B"
1340                         Convert one color to transparent.
1341                 */
1342                 else if (str_starts_with(part_of_name, "[makealpha:"))
1343                 {
1344                         if (baseimg == NULL) {
1345                                 errorstream<<"generateImagePart(): baseimg == NULL "
1346                                                 <<"for part_of_name=\""<<part_of_name
1347                                                 <<"\", cancelling."<<std::endl;
1348                                 return false;
1349                         }
1350
1351                         Strfnd sf(part_of_name.substr(11));
1352                         u32 r1 = stoi(sf.next(","));
1353                         u32 g1 = stoi(sf.next(","));
1354                         u32 b1 = stoi(sf.next(""));
1355
1356                         core::dimension2d<u32> dim = baseimg->getDimension();
1357
1358                         /*video::IImage *oldbaseimg = baseimg;
1359                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1360                         oldbaseimg->copyTo(baseimg);
1361                         oldbaseimg->drop();*/
1362
1363                         // Set alpha to full
1364                         for (u32 y=0; y<dim.Height; y++)
1365                         for (u32 x=0; x<dim.Width; x++)
1366                         {
1367                                 video::SColor c = baseimg->getPixel(x,y);
1368                                 u32 r = c.getRed();
1369                                 u32 g = c.getGreen();
1370                                 u32 b = c.getBlue();
1371                                 if (!(r == r1 && g == g1 && b == b1))
1372                                         continue;
1373                                 c.setAlpha(0);
1374                                 baseimg->setPixel(x,y,c);
1375                         }
1376                 }
1377                 /*
1378                         "[transformN"
1379                         Rotates and/or flips the image.
1380
1381                         N can be a number (between 0 and 7) or a transform name.
1382                         Rotations are counter-clockwise.
1383                         0  I      identity
1384                         1  R90    rotate by 90 degrees
1385                         2  R180   rotate by 180 degrees
1386                         3  R270   rotate by 270 degrees
1387                         4  FX     flip X
1388                         5  FXR90  flip X then rotate by 90 degrees
1389                         6  FY     flip Y
1390                         7  FYR90  flip Y then rotate by 90 degrees
1391
1392                         Note: Transform names can be concatenated to produce
1393                         their product (applies the first then the second).
1394                         The resulting transform will be equivalent to one of the
1395                         eight existing ones, though (see: dihedral group).
1396                 */
1397                 else if (str_starts_with(part_of_name, "[transform"))
1398                 {
1399                         if (baseimg == NULL) {
1400                                 errorstream<<"generateImagePart(): baseimg == NULL "
1401                                                 <<"for part_of_name=\""<<part_of_name
1402                                                 <<"\", cancelling."<<std::endl;
1403                                 return false;
1404                         }
1405
1406                         u32 transform = parseImageTransform(part_of_name.substr(10));
1407                         core::dimension2d<u32> dim = imageTransformDimension(
1408                                         transform, baseimg->getDimension());
1409                         video::IImage *image = driver->createImage(
1410                                         baseimg->getColorFormat(), dim);
1411                         sanity_check(image != NULL);
1412                         imageTransform(transform, baseimg, image);
1413                         baseimg->drop();
1414                         baseimg = image;
1415                 }
1416                 /*
1417                         [inventorycube{topimage{leftimage{rightimage
1418                         In every subimage, replace ^ with &.
1419                         Create an "inventory cube".
1420                         NOTE: This should be used only on its own.
1421                         Example (a grass block (not actually used in game):
1422                         "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1423                 */
1424                 else if (str_starts_with(part_of_name, "[inventorycube"))
1425                 {
1426                         if (baseimg != NULL){
1427                                 errorstream<<"generateImagePart(): baseimg != NULL "
1428                                                 <<"for part_of_name=\""<<part_of_name
1429                                                 <<"\", cancelling."<<std::endl;
1430                                 return false;
1431                         }
1432
1433                         str_replace(part_of_name, '&', '^');
1434                         Strfnd sf(part_of_name);
1435                         sf.next("{");
1436                         std::string imagename_top = sf.next("{");
1437                         std::string imagename_left = sf.next("{");
1438                         std::string imagename_right = sf.next("{");
1439
1440                         // Generate images for the faces of the cube
1441                         video::IImage *img_top = generateImage(imagename_top);
1442                         video::IImage *img_left = generateImage(imagename_left);
1443                         video::IImage *img_right = generateImage(imagename_right);
1444
1445                         if (img_top == NULL || img_left == NULL || img_right == NULL) {
1446                                 errorstream << "generateImagePart(): Failed to create textures"
1447                                                 << " for inventorycube \"" << part_of_name << "\""
1448                                                 << std::endl;
1449                                 baseimg = generateImage(imagename_top);
1450                                 return true;
1451                         }
1452
1453 #ifdef __ANDROID__
1454                         assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1455                         assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1456
1457                         assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1458                         assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1459
1460                         assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1461                         assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1462 #endif
1463
1464                         // Create textures from images
1465                         video::ITexture *texture_top = driver->addTexture(
1466                                         (imagename_top + "__temp__").c_str(), img_top);
1467                         video::ITexture *texture_left = driver->addTexture(
1468                                         (imagename_left + "__temp__").c_str(), img_left);
1469                         video::ITexture *texture_right = driver->addTexture(
1470                                         (imagename_right + "__temp__").c_str(), img_right);
1471                         FATAL_ERROR_IF(!(texture_top && texture_left && texture_right), "");
1472
1473                         // Drop images
1474                         img_top->drop();
1475                         img_left->drop();
1476                         img_right->drop();
1477
1478                         /*
1479                                 Draw a cube mesh into a render target texture
1480                         */
1481                         scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1482                         setMeshColor(cube, video::SColor(255, 255, 255, 255));
1483                         cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1484                         cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1485                         cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1486                         cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1487                         cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1488                         cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1489
1490                         TextureFromMeshParams params;
1491                         params.mesh = cube;
1492                         params.dim.set(64, 64);
1493                         params.rtt_texture_name = part_of_name + "_RTT";
1494                         // We will delete the rtt texture ourselves
1495                         params.delete_texture_on_shutdown = false;
1496                         params.camera_position.set(0, 1.0, -1.5);
1497                         params.camera_position.rotateXZBy(45);
1498                         params.camera_lookat.set(0, 0, 0);
1499                         // Set orthogonal projection
1500                         params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1501                                         1.65, 1.65, 0, 100);
1502
1503                         params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1504                         params.light_position.set(10, 100, -50);
1505                         params.light_color.set(1.0, 0.5, 0.5, 0.5);
1506                         params.light_radius = 1000;
1507
1508                         video::ITexture *rtt = generateTextureFromMesh(params);
1509
1510                         // Drop mesh
1511                         cube->drop();
1512
1513                         // Free textures
1514                         driver->removeTexture(texture_top);
1515                         driver->removeTexture(texture_left);
1516                         driver->removeTexture(texture_right);
1517
1518                         if (rtt == NULL) {
1519                                 baseimg = generateImage(imagename_top);
1520                                 return true;
1521                         }
1522
1523                         // Create image of render target
1524                         video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1525                         FATAL_ERROR_IF(!image, "Could not create image of render target");
1526
1527                         // Cleanup texture
1528                         driver->removeTexture(rtt);
1529
1530                         baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1531
1532                         if (image) {
1533                                 image->copyTo(baseimg);
1534                                 image->drop();
1535                         }
1536                 }
1537                 /*
1538                         [lowpart:percent:filename
1539                         Adds the lower part of a texture
1540                 */
1541                 else if (str_starts_with(part_of_name, "[lowpart:"))
1542                 {
1543                         Strfnd sf(part_of_name);
1544                         sf.next(":");
1545                         u32 percent = stoi(sf.next(":"));
1546                         std::string filename = sf.next(":");
1547                         //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1548
1549                         if (baseimg == NULL)
1550                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1551                         video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1552                         if (img)
1553                         {
1554                                 core::dimension2d<u32> dim = img->getDimension();
1555                                 core::position2d<s32> pos_base(0, 0);
1556                                 video::IImage *img2 =
1557                                                 driver->createImage(video::ECF_A8R8G8B8, dim);
1558                                 img->copyTo(img2);
1559                                 img->drop();
1560                                 core::position2d<s32> clippos(0, 0);
1561                                 clippos.Y = dim.Height * (100-percent) / 100;
1562                                 core::dimension2d<u32> clipdim = dim;
1563                                 clipdim.Height = clipdim.Height * percent / 100 + 1;
1564                                 core::rect<s32> cliprect(clippos, clipdim);
1565                                 img2->copyToWithAlpha(baseimg, pos_base,
1566                                                 core::rect<s32>(v2s32(0,0), dim),
1567                                                 video::SColor(255,255,255,255),
1568                                                 &cliprect);
1569                                 img2->drop();
1570                         }
1571                 }
1572                 /*
1573                         [verticalframe:N:I
1574                         Crops a frame of a vertical animation.
1575                         N = frame count, I = frame index
1576                 */
1577                 else if (str_starts_with(part_of_name, "[verticalframe:"))
1578                 {
1579                         Strfnd sf(part_of_name);
1580                         sf.next(":");
1581                         u32 frame_count = stoi(sf.next(":"));
1582                         u32 frame_index = stoi(sf.next(":"));
1583
1584                         if (baseimg == NULL){
1585                                 errorstream<<"generateImagePart(): baseimg != NULL "
1586                                                 <<"for part_of_name=\""<<part_of_name
1587                                                 <<"\", cancelling."<<std::endl;
1588                                 return false;
1589                         }
1590
1591                         v2u32 frame_size = baseimg->getDimension();
1592                         frame_size.Y /= frame_count;
1593
1594                         video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1595                                         frame_size);
1596                         if (!img){
1597                                 errorstream<<"generateImagePart(): Could not create image "
1598                                                 <<"for part_of_name=\""<<part_of_name
1599                                                 <<"\", cancelling."<<std::endl;
1600                                 return false;
1601                         }
1602
1603                         // Fill target image with transparency
1604                         img->fill(video::SColor(0,0,0,0));
1605
1606                         core::dimension2d<u32> dim = frame_size;
1607                         core::position2d<s32> pos_dst(0, 0);
1608                         core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1609                         baseimg->copyToWithAlpha(img, pos_dst,
1610                                         core::rect<s32>(pos_src, dim),
1611                                         video::SColor(255,255,255,255),
1612                                         NULL);
1613                         // Replace baseimg
1614                         baseimg->drop();
1615                         baseimg = img;
1616                 }
1617                 /*
1618                         [mask:filename
1619                         Applies a mask to an image
1620                 */
1621                 else if (str_starts_with(part_of_name, "[mask:"))
1622                 {
1623                         if (baseimg == NULL) {
1624                                 errorstream << "generateImage(): baseimg == NULL "
1625                                                 << "for part_of_name=\"" << part_of_name
1626                                                 << "\", cancelling." << std::endl;
1627                                 return false;
1628                         }
1629                         Strfnd sf(part_of_name);
1630                         sf.next(":");
1631                         std::string filename = sf.next(":");
1632
1633                         video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1634                         if (img) {
1635                                 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1636                                                 img->getDimension());
1637                                 img->drop();
1638                         } else {
1639                                 errorstream << "generateImage(): Failed to load \""
1640                                                 << filename << "\".";
1641                         }
1642                 }
1643                 /*
1644                         [colorize:color
1645                         Overlays image with given color
1646                         color = color as ColorString
1647                 */
1648                 else if (str_starts_with(part_of_name, "[colorize:"))
1649                 {
1650                         Strfnd sf(part_of_name);
1651                         sf.next(":");
1652                         std::string color_str = sf.next(":");
1653                         std::string ratio_str = sf.next(":");
1654
1655                         if (baseimg == NULL) {
1656                                 errorstream << "generateImagePart(): baseimg != NULL "
1657                                                 << "for part_of_name=\"" << part_of_name
1658                                                 << "\", cancelling." << std::endl;
1659                                 return false;
1660                         }
1661
1662                         video::SColor color;
1663                         int ratio = -1;
1664                         bool keep_alpha = false;
1665
1666                         if (!parseColorString(color_str, color, false))
1667                                 return false;
1668
1669                         if (is_number(ratio_str))
1670                                 ratio = mystoi(ratio_str, 0, 255);
1671                         else if (ratio_str == "alpha")
1672                                 keep_alpha = true;
1673
1674                         apply_colorize(baseimg, v2u32(0, 0), baseimg->getDimension(), color, ratio, keep_alpha);
1675                 }
1676                 else if (str_starts_with(part_of_name, "[applyfiltersformesh"))
1677                 {
1678                         // Apply the "clean transparent" filter, if configured.
1679                         if (g_settings->getBool("texture_clean_transparent"))
1680                                 imageCleanTransparent(baseimg, 127);
1681
1682                         /* Upscale textures to user's requested minimum size.  This is a trick to make
1683                          * filters look as good on low-res textures as on high-res ones, by making
1684                          * low-res textures BECOME high-res ones.  This is helpful for worlds that
1685                          * mix high- and low-res textures, or for mods with least-common-denominator
1686                          * textures that don't have the resources to offer high-res alternatives.
1687                          */
1688                         s32 scaleto = g_settings->getS32("texture_min_size");
1689                         if (scaleto > 1) {
1690                                 const core::dimension2d<u32> dim = baseimg->getDimension();
1691
1692                                 /* Calculate scaling needed to make the shortest texture dimension
1693                                  * equal to the target minimum.  If e.g. this is a vertical frames
1694                                  * animation, the short dimension will be the real size.
1695                                  */
1696                                 u32 xscale = scaleto / dim.Width;
1697                                 u32 yscale = scaleto / dim.Height;
1698                                 u32 scale = (xscale > yscale) ? xscale : yscale;
1699
1700                                 // Never downscale; only scale up by 2x or more.
1701                                 if (scale > 1) {
1702                                         u32 w = scale * dim.Width;
1703                                         u32 h = scale * dim.Height;
1704                                         const core::dimension2d<u32> newdim = core::dimension2d<u32>(w, h);
1705                                         video::IImage *newimg = driver->createImage(
1706                                                         baseimg->getColorFormat(), newdim);
1707                                         baseimg->copyToScaling(newimg);
1708                                         baseimg->drop();
1709                                         baseimg = newimg;
1710                                 }
1711                         }
1712                 }
1713                 /*
1714                         [resize:WxH
1715                         Resizes the base image to the given dimensions
1716                 */
1717                 else if (str_starts_with(part_of_name, "[resize"))
1718                 {
1719                         if (baseimg == NULL) {
1720                                 errorstream << "generateImagePart(): baseimg == NULL "
1721                                                 << "for part_of_name=\""<< part_of_name
1722                                                 << "\", cancelling." << std::endl;
1723                                 return false;
1724                         }
1725
1726                         Strfnd sf(part_of_name);
1727                         sf.next(":");
1728                         u32 width = stoi(sf.next("x"));
1729                         u32 height = stoi(sf.next(""));
1730                         core::dimension2d<u32> dim(width, height);
1731
1732                         video::IImage* image = m_device->getVideoDriver()->
1733                                 createImage(video::ECF_A8R8G8B8, dim);
1734                         baseimg->copyToScaling(image);
1735                         baseimg->drop();
1736                         baseimg = image;
1737                 }
1738                 else
1739                 {
1740                         errorstream << "generateImagePart(): Invalid "
1741                                         " modification: \"" << part_of_name << "\"" << std::endl;
1742                 }
1743         }
1744
1745         return true;
1746 }
1747
1748 /*
1749         Draw an image on top of an another one, using the alpha channel of the
1750         source image
1751
1752         This exists because IImage::copyToWithAlpha() doesn't seem to always
1753         work.
1754 */
1755 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1756                 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1757 {
1758         for (u32 y0=0; y0<size.Y; y0++)
1759         for (u32 x0=0; x0<size.X; x0++)
1760         {
1761                 s32 src_x = src_pos.X + x0;
1762                 s32 src_y = src_pos.Y + y0;
1763                 s32 dst_x = dst_pos.X + x0;
1764                 s32 dst_y = dst_pos.Y + y0;
1765                 video::SColor src_c = src->getPixel(src_x, src_y);
1766                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1767                 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1768                 dst->setPixel(dst_x, dst_y, dst_c);
1769         }
1770 }
1771
1772 /*
1773         Draw an image on top of an another one, using the alpha channel of the
1774         source image; only modify fully opaque pixels in destinaion
1775 */
1776 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1777                 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1778 {
1779         for (u32 y0=0; y0<size.Y; y0++)
1780         for (u32 x0=0; x0<size.X; x0++)
1781         {
1782                 s32 src_x = src_pos.X + x0;
1783                 s32 src_y = src_pos.Y + y0;
1784                 s32 dst_x = dst_pos.X + x0;
1785                 s32 dst_y = dst_pos.Y + y0;
1786                 video::SColor src_c = src->getPixel(src_x, src_y);
1787                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1788                 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1789                 {
1790                         dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1791                         dst->setPixel(dst_x, dst_y, dst_c);
1792                 }
1793         }
1794 }
1795
1796 // This function has been disabled because it is currently unused.
1797 // Feel free to re-enable if you find it handy.
1798 #if 0
1799 /*
1800         Draw an image on top of an another one, using the specified ratio
1801         modify all partially-opaque pixels in the destination.
1802 */
1803 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1804                 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1805 {
1806         for (u32 y0 = 0; y0 < size.Y; y0++)
1807         for (u32 x0 = 0; x0 < size.X; x0++)
1808         {
1809                 s32 src_x = src_pos.X + x0;
1810                 s32 src_y = src_pos.Y + y0;
1811                 s32 dst_x = dst_pos.X + x0;
1812                 s32 dst_y = dst_pos.Y + y0;
1813                 video::SColor src_c = src->getPixel(src_x, src_y);
1814                 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1815                 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1816                 {
1817                         if (ratio == -1)
1818                                 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1819                         else
1820                                 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1821                         dst->setPixel(dst_x, dst_y, dst_c);
1822                 }
1823         }
1824 }
1825 #endif
1826
1827 /*
1828         Apply color to destination
1829 */
1830 static void apply_colorize(video::IImage *dst, v2u32 dst_pos, v2u32 size,
1831                 video::SColor color, int ratio, bool keep_alpha)
1832 {
1833         u32 alpha = color.getAlpha();
1834         video::SColor dst_c;
1835         if ((ratio == -1 && alpha == 255) || ratio == 255) { // full replacement of color
1836                 if (keep_alpha) { // replace the color with alpha = dest alpha * color alpha
1837                         dst_c = color;
1838                         for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1839                         for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1840                                 u32 dst_alpha = dst->getPixel(x, y).getAlpha();
1841                                 if (dst_alpha > 0) {
1842                                         dst_c.setAlpha(dst_alpha * alpha / 255);
1843                                         dst->setPixel(x, y, dst_c);
1844                                 }
1845                         }
1846                 } else { // replace the color including the alpha
1847                         for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1848                         for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++)
1849                                 if (dst->getPixel(x, y).getAlpha() > 0)
1850                                         dst->setPixel(x, y, color);
1851                 }
1852         } else {  // interpolate between the color and destination
1853                 float interp = (ratio == -1 ? color.getAlpha() / 255.0f : ratio / 255.0f);
1854                 for (u32 y = dst_pos.Y; y < dst_pos.Y + size.Y; y++)
1855                 for (u32 x = dst_pos.X; x < dst_pos.X + size.X; x++) {
1856                         dst_c = dst->getPixel(x, y);
1857                         if (dst_c.getAlpha() > 0) {
1858                                 dst_c = color.getInterpolated(dst_c, interp);
1859                                 dst->setPixel(x, y, dst_c);
1860                         }
1861                 }
1862         }
1863 }
1864
1865 /*
1866         Apply mask to destination
1867 */
1868 static void apply_mask(video::IImage *mask, video::IImage *dst,
1869                 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1870 {
1871         for (u32 y0 = 0; y0 < size.Y; y0++) {
1872                 for (u32 x0 = 0; x0 < size.X; x0++) {
1873                         s32 mask_x = x0 + mask_pos.X;
1874                         s32 mask_y = y0 + mask_pos.Y;
1875                         s32 dst_x = x0 + dst_pos.X;
1876                         s32 dst_y = y0 + dst_pos.Y;
1877                         video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1878                         video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1879                         dst_c.color &= mask_c.color;
1880                         dst->setPixel(dst_x, dst_y, dst_c);
1881                 }
1882         }
1883 }
1884
1885 static void draw_crack(video::IImage *crack, video::IImage *dst,
1886                 bool use_overlay, s32 frame_count, s32 progression,
1887                 video::IVideoDriver *driver)
1888 {
1889         // Dimension of destination image
1890         core::dimension2d<u32> dim_dst = dst->getDimension();
1891         // Dimension of original image
1892         core::dimension2d<u32> dim_crack = crack->getDimension();
1893         // Count of crack stages
1894         s32 crack_count = dim_crack.Height / dim_crack.Width;
1895         // Limit frame_count
1896         if (frame_count > (s32) dim_dst.Height)
1897                 frame_count = dim_dst.Height;
1898         if (frame_count < 1)
1899                 frame_count = 1;
1900         // Limit progression
1901         if (progression > crack_count-1)
1902                 progression = crack_count-1;
1903         // Dimension of a single crack stage
1904         core::dimension2d<u32> dim_crack_cropped(
1905                 dim_crack.Width,
1906                 dim_crack.Width
1907         );
1908         // Dimension of the scaled crack stage,
1909         // which is the same as the dimension of a single destination frame
1910         core::dimension2d<u32> dim_crack_scaled(
1911                 dim_dst.Width,
1912                 dim_dst.Height / frame_count
1913         );
1914         // Create cropped and scaled crack images
1915         video::IImage *crack_cropped = driver->createImage(
1916                         video::ECF_A8R8G8B8, dim_crack_cropped);
1917         video::IImage *crack_scaled = driver->createImage(
1918                         video::ECF_A8R8G8B8, dim_crack_scaled);
1919
1920         if (crack_cropped && crack_scaled)
1921         {
1922                 // Crop crack image
1923                 v2s32 pos_crack(0, progression*dim_crack.Width);
1924                 crack->copyTo(crack_cropped,
1925                                 v2s32(0,0),
1926                                 core::rect<s32>(pos_crack, dim_crack_cropped));
1927                 // Scale crack image by copying
1928                 crack_cropped->copyToScaling(crack_scaled);
1929                 // Copy or overlay crack image onto each frame
1930                 for (s32 i = 0; i < frame_count; ++i)
1931                 {
1932                         v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1933                         if (use_overlay)
1934                         {
1935                                 blit_with_alpha_overlay(crack_scaled, dst,
1936                                                 v2s32(0,0), dst_pos,
1937                                                 dim_crack_scaled);
1938                         }
1939                         else
1940                         {
1941                                 blit_with_alpha(crack_scaled, dst,
1942                                                 v2s32(0,0), dst_pos,
1943                                                 dim_crack_scaled);
1944                         }
1945                 }
1946         }
1947
1948         if (crack_scaled)
1949                 crack_scaled->drop();
1950
1951         if (crack_cropped)
1952                 crack_cropped->drop();
1953 }
1954
1955 void brighten(video::IImage *image)
1956 {
1957         if (image == NULL)
1958                 return;
1959
1960         core::dimension2d<u32> dim = image->getDimension();
1961
1962         for (u32 y=0; y<dim.Height; y++)
1963         for (u32 x=0; x<dim.Width; x++)
1964         {
1965                 video::SColor c = image->getPixel(x,y);
1966                 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1967                 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1968                 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1969                 image->setPixel(x,y,c);
1970         }
1971 }
1972
1973 u32 parseImageTransform(const std::string& s)
1974 {
1975         int total_transform = 0;
1976
1977         std::string transform_names[8];
1978         transform_names[0] = "i";
1979         transform_names[1] = "r90";
1980         transform_names[2] = "r180";
1981         transform_names[3] = "r270";
1982         transform_names[4] = "fx";
1983         transform_names[6] = "fy";
1984
1985         std::size_t pos = 0;
1986         while(pos < s.size())
1987         {
1988                 int transform = -1;
1989                 for (int i = 0; i <= 7; ++i)
1990                 {
1991                         const std::string &name_i = transform_names[i];
1992
1993                         if (s[pos] == ('0' + i))
1994                         {
1995                                 transform = i;
1996                                 pos++;
1997                                 break;
1998                         }
1999                         else if (!(name_i.empty()) &&
2000                                 lowercase(s.substr(pos, name_i.size())) == name_i)
2001                         {
2002                                 transform = i;
2003                                 pos += name_i.size();
2004                                 break;
2005                         }
2006                 }
2007                 if (transform < 0)
2008                         break;
2009
2010                 // Multiply total_transform and transform in the group D4
2011                 int new_total = 0;
2012                 if (transform < 4)
2013                         new_total = (transform + total_transform) % 4;
2014                 else
2015                         new_total = (transform - total_transform + 8) % 4;
2016                 if ((transform >= 4) ^ (total_transform >= 4))
2017                         new_total += 4;
2018
2019                 total_transform = new_total;
2020         }
2021         return total_transform;
2022 }
2023
2024 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
2025 {
2026         if (transform % 2 == 0)
2027                 return dim;
2028         else
2029                 return core::dimension2d<u32>(dim.Height, dim.Width);
2030 }
2031
2032 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
2033 {
2034         if (src == NULL || dst == NULL)
2035                 return;
2036
2037         core::dimension2d<u32> dstdim = dst->getDimension();
2038
2039         // Pre-conditions
2040         assert(dstdim == imageTransformDimension(transform, src->getDimension()));
2041         assert(transform <= 7);
2042
2043         /*
2044                 Compute the transformation from source coordinates (sx,sy)
2045                 to destination coordinates (dx,dy).
2046         */
2047         int sxn = 0;
2048         int syn = 2;
2049         if (transform == 0)         // identity
2050                 sxn = 0, syn = 2;  //   sx = dx, sy = dy
2051         else if (transform == 1)    // rotate by 90 degrees ccw
2052                 sxn = 3, syn = 0;  //   sx = (H-1) - dy, sy = dx
2053         else if (transform == 2)    // rotate by 180 degrees
2054                 sxn = 1, syn = 3;  //   sx = (W-1) - dx, sy = (H-1) - dy
2055         else if (transform == 3)    // rotate by 270 degrees ccw
2056                 sxn = 2, syn = 1;  //   sx = dy, sy = (W-1) - dx
2057         else if (transform == 4)    // flip x
2058                 sxn = 1, syn = 2;  //   sx = (W-1) - dx, sy = dy
2059         else if (transform == 5)    // flip x then rotate by 90 degrees ccw
2060                 sxn = 2, syn = 0;  //   sx = dy, sy = dx
2061         else if (transform == 6)    // flip y
2062                 sxn = 0, syn = 3;  //   sx = dx, sy = (H-1) - dy
2063         else if (transform == 7)    // flip y then rotate by 90 degrees ccw
2064                 sxn = 3, syn = 1;  //   sx = (H-1) - dy, sy = (W-1) - dx
2065
2066         for (u32 dy=0; dy<dstdim.Height; dy++)
2067         for (u32 dx=0; dx<dstdim.Width; dx++)
2068         {
2069                 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
2070                 u32 sx = entries[sxn];
2071                 u32 sy = entries[syn];
2072                 video::SColor c = src->getPixel(sx,sy);
2073                 dst->setPixel(dx,dy,c);
2074         }
2075 }
2076
2077 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
2078 {
2079         if (isKnownSourceImage("override_normal.png"))
2080                 return getTexture("override_normal.png");
2081         std::string fname_base = name;
2082         std::string normal_ext = "_normal.png";
2083         size_t pos = fname_base.find(".");
2084         std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
2085         if (isKnownSourceImage(fname_normal)) {
2086                 // look for image extension and replace it
2087                 size_t i = 0;
2088                 while ((i = fname_base.find(".", i)) != std::string::npos) {
2089                         fname_base.replace(i, 4, normal_ext);
2090                         i += normal_ext.length();
2091                 }
2092                 return getTexture(fname_base);
2093                 }
2094         return NULL;
2095 }
2096
2097 video::SColor TextureSource::getTextureAverageColor(const std::string &name)
2098 {
2099         video::IVideoDriver *driver = m_device->getVideoDriver();
2100         video::SColor c(0, 0, 0, 0);
2101         video::ITexture *texture = getTexture(name);
2102         video::IImage *image = driver->createImage(texture,
2103                 core::position2d<s32>(0, 0),
2104                 texture->getOriginalSize());
2105         u32 total = 0;
2106         u32 tR = 0;
2107         u32 tG = 0;
2108         u32 tB = 0;
2109         core::dimension2d<u32> dim = image->getDimension();
2110         u16 step = 1;
2111         if (dim.Width > 16)
2112                 step = dim.Width / 16;
2113         for (u16 x = 0; x < dim.Width; x += step) {
2114                 for (u16 y = 0; y < dim.Width; y += step) {
2115                         c = image->getPixel(x,y);
2116                         if (c.getAlpha() > 0) {
2117                                 total++;
2118                                 tR += c.getRed();
2119                                 tG += c.getGreen();
2120                                 tB += c.getBlue();
2121                         }
2122                 }
2123         }
2124         image->drop();
2125         if (total > 0) {
2126                 c.setRed(tR / total);
2127                 c.setGreen(tG / total);
2128                 c.setBlue(tB / total);
2129         }
2130         c.setAlpha(255);
2131         return c;
2132 }
2133
2134
2135 video::ITexture *TextureSource::getShaderFlagsTexture(bool normalmap_present)
2136 {
2137         std::string tname = "__shaderFlagsTexture";
2138         tname += normalmap_present ? "1" : "0";
2139
2140         if (isKnownSourceImage(tname)) {
2141                 return getTexture(tname);
2142         } else {
2143                 video::IVideoDriver *driver = m_device->getVideoDriver();
2144                 video::IImage *flags_image = driver->createImage(
2145                         video::ECF_A8R8G8B8, core::dimension2d<u32>(1, 1));
2146                 sanity_check(flags_image != NULL);
2147                 video::SColor c(255, normalmap_present ? 255 : 0, 0, 0);
2148                 flags_image->setPixel(0, 0, c);
2149                 insertSourceImage(tname, flags_image);
2150                 flags_image->drop();
2151                 return getTexture(tname);
2152         }
2153 }