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