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