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