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