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