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