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