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