mobv2
[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 <ICameraSceneNode.h>
27
28 /*
29         A cache from texture name to texture path
30 */
31 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
32
33 /*
34         Replaces the filename extension.
35         eg:
36                 std::string image = "a/image.png"
37                 replace_ext(image, "jpg")
38                 -> image = "a/image.jpg"
39         Returns true on success.
40 */
41 static bool replace_ext(std::string &path, const char *ext)
42 {
43         if(ext == NULL)
44                 return false;
45         // Find place of last dot, fail if \ or / found.
46         s32 last_dot_i = -1;
47         for(s32 i=path.size()-1; i>=0; i--)
48         {
49                 if(path[i] == '.')
50                 {
51                         last_dot_i = i;
52                         break;
53                 }
54                 
55                 if(path[i] == '\\' || path[i] == '/')
56                         break;
57         }
58         // If not found, return an empty string
59         if(last_dot_i == -1)
60                 return false;
61         // Else make the new path
62         path = path.substr(0, last_dot_i+1) + ext;
63         return true;
64 }
65
66 /*
67         Find out the full path of an image by trying different filename
68         extensions.
69
70         If failed, return "".
71 */
72 static std::string getImagePath(std::string path)
73 {
74         // A NULL-ended list of possible image extensions
75         const char *extensions[] = {
76                 "png", "jpg", "bmp", "tga",
77                 "pcx", "ppm", "psd", "wal", "rgb",
78                 NULL
79         };
80
81         const char **ext = extensions;
82         do{
83                 bool r = replace_ext(path, *ext);
84                 if(r == false)
85                         return "";
86                 if(fs::PathExists(path))
87                         return path;
88         }
89         while((++ext) != NULL);
90         
91         return "";
92 }
93
94 /*
95         Gets the path to a texture by first checking if the texture exists
96         in texture_path and if not, using the data path.
97
98         Checks all supported extensions by replacing the original extension.
99
100         If not found, returns "".
101
102         Utilizes a thread-safe cache.
103 */
104 std::string getTexturePath(const std::string &filename)
105 {
106         std::string fullpath = "";
107         /*
108                 Check from cache
109         */
110         bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
111         if(incache)
112                 return fullpath;
113         
114         /*
115                 Check from texture_path
116         */
117         std::string texture_path = g_settings->get("texture_path");
118         if(texture_path != "")
119         {
120                 std::string testpath = texture_path + '/' + filename;
121                 // Check all filename extensions. Returns "" if not found.
122                 fullpath = getImagePath(testpath);
123         }
124         
125         /*
126                 Check from default data directory
127         */
128         if(fullpath == "")
129         {
130                 std::string testpath = porting::getDataPath(filename.c_str());
131                 // Check all filename extensions. Returns "" if not found.
132                 fullpath = getImagePath(testpath);
133         }
134         
135         // Add to cache (also an empty result is cached)
136         g_texturename_to_path_cache.set(filename, fullpath);
137         
138         // Finally return it
139         return fullpath;
140 }
141
142 /*
143         TextureSource
144 */
145
146 TextureSource::TextureSource(IrrlichtDevice *device):
147                 m_device(device),
148                 m_main_atlas_image(NULL),
149                 m_main_atlas_texture(NULL)
150 {
151         assert(m_device);
152         
153         m_atlaspointer_cache_mutex.Init();
154         
155         m_main_thread = get_current_thread_id();
156         
157         // Add a NULL AtlasPointer as the first index, named ""
158         m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
159         m_name_to_id[""] = 0;
160
161         // Build main texture atlas
162         if(g_settings->getBool("enable_texture_atlas"))
163                 buildMainAtlas();
164         else
165                 dstream<<"INFO: Not building texture atlas."<<std::endl;
166 }
167
168 TextureSource::~TextureSource()
169 {
170 }
171
172 void TextureSource::processQueue()
173 {
174         /*
175                 Fetch textures
176         */
177         if(m_get_texture_queue.size() > 0)
178         {
179                 GetRequest<std::string, u32, u8, u8>
180                                 request = m_get_texture_queue.pop();
181
182                 dstream<<"INFO: TextureSource::processQueue(): "
183                                 <<"got texture request with "
184                                 <<"name=\""<<request.key<<"\""
185                                 <<std::endl;
186
187                 GetResult<std::string, u32, u8, u8>
188                                 result;
189                 result.key = request.key;
190                 result.callers = request.callers;
191                 result.item = getTextureIdDirect(request.key);
192
193                 request.dest->push_back(result);
194         }
195 }
196
197 u32 TextureSource::getTextureId(const std::string &name)
198 {
199         //dstream<<"INFO: getTextureId(): \""<<name<<"\""<<std::endl;
200
201         {
202                 /*
203                         See if texture already exists
204                 */
205                 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
206                 core::map<std::string, u32>::Node *n;
207                 n = m_name_to_id.find(name);
208                 if(n != NULL)
209                 {
210                         return n->getValue();
211                 }
212         }
213         
214         /*
215                 Get texture
216         */
217         if(get_current_thread_id() == m_main_thread)
218         {
219                 return getTextureIdDirect(name);
220         }
221         else
222         {
223                 dstream<<"INFO: getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
224
225                 // We're gonna ask the result to be put into here
226                 ResultQueue<std::string, u32, u8, u8> result_queue;
227                 
228                 // Throw a request in
229                 m_get_texture_queue.add(name, 0, 0, &result_queue);
230                 
231                 dstream<<"INFO: Waiting for texture from main thread, name=\""
232                                 <<name<<"\""<<std::endl;
233                 
234                 try
235                 {
236                         // Wait result for a second
237                         GetResult<std::string, u32, u8, u8>
238                                         result = result_queue.pop_front(1000);
239                 
240                         // Check that at least something worked OK
241                         assert(result.key == name);
242
243                         return result.item;
244                 }
245                 catch(ItemNotFoundException &e)
246                 {
247                         dstream<<"WARNING: Waiting for texture timed out."<<std::endl;
248                         return 0;
249                 }
250         }
251         
252         dstream<<"WARNING: getTextureId(): Failed"<<std::endl;
253
254         return 0;
255 }
256
257 // Draw a progress bar on the image
258 void make_progressbar(float value, video::IImage *image);
259
260 /*
261         Generate image based on a string like "stone.png" or "[crack0".
262         if baseimg is NULL, it is created. Otherwise stuff is made on it.
263 */
264 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
265                 IrrlichtDevice *device);
266
267 /*
268         Generates an image from a full string like
269         "stone.png^mineral_coal.png^[crack0".
270
271         This is used by buildMainAtlas().
272 */
273 video::IImage* generate_image_from_scratch(std::string name,
274                 IrrlichtDevice *device);
275
276 /*
277         This method generates all the textures
278 */
279 u32 TextureSource::getTextureIdDirect(const std::string &name)
280 {
281         //dstream<<"INFO: getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
282
283         // Empty name means texture 0
284         if(name == "")
285         {
286                 dstream<<"INFO: getTextureIdDirect(): name is empty"<<std::endl;
287                 return 0;
288         }
289         
290         /*
291                 Calling only allowed from main thread
292         */
293         if(get_current_thread_id() != m_main_thread)
294         {
295                 dstream<<"ERROR: TextureSource::getTextureIdDirect() "
296                                 "called not from main thread"<<std::endl;
297                 return 0;
298         }
299
300         /*
301                 See if texture already exists
302         */
303         {
304                 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
305
306                 core::map<std::string, u32>::Node *n;
307                 n = m_name_to_id.find(name);
308                 if(n != NULL)
309                 {
310                         dstream<<"INFO: getTextureIdDirect(): \""<<name
311                                         <<"\" found in cache"<<std::endl;
312                         return n->getValue();
313                 }
314         }
315
316         dstream<<"INFO: getTextureIdDirect(): \""<<name
317                         <<"\" NOT found in cache. Creating it."<<std::endl;
318         
319         /*
320                 Get the base image
321         */
322
323         char separator = '^';
324
325         /*
326                 This is set to the id of the base image.
327                 If left 0, there is no base image and a completely new image
328                 is made.
329         */
330         u32 base_image_id = 0;
331         
332         // Find last meta separator in name
333         s32 last_separator_position = -1;
334         for(s32 i=name.size()-1; i>=0; i--)
335         {
336                 if(name[i] == separator)
337                 {
338                         last_separator_position = i;
339                         break;
340                 }
341         }
342         /*
343                 If separator was found, construct the base name and make the
344                 base image using a recursive call
345         */
346         std::string base_image_name;
347         if(last_separator_position != -1)
348         {
349                 // Construct base name
350                 base_image_name = name.substr(0, last_separator_position);
351                 /*dstream<<"INFO: getTextureIdDirect(): Calling itself recursively"
352                                 " to get base image of \""<<name<<"\" = \""
353                 <<base_image_name<<"\""<<std::endl;*/
354                 base_image_id = getTextureIdDirect(base_image_name);
355         }
356         
357         //dstream<<"base_image_id="<<base_image_id<<std::endl;
358         
359         video::IVideoDriver* driver = m_device->getVideoDriver();
360         assert(driver);
361
362         video::ITexture *t = NULL;
363
364         /*
365                 An image will be built from files and then converted into a texture.
366         */
367         video::IImage *baseimg = NULL;
368         
369         // If a base image was found, copy it to baseimg
370         if(base_image_id != 0)
371         {
372                 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
373
374                 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
375
376                 video::IImage *image = ap.atlas_img;
377                 
378                 if(image == NULL)
379                 {
380                         dstream<<"WARNING: getTextureIdDirect(): NULL image in "
381                                         <<"cache: \""<<base_image_name<<"\""
382                                         <<std::endl;
383                 }
384                 else
385                 {
386                         core::dimension2d<u32> dim = ap.intsize;
387
388                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
389
390                         core::position2d<s32> pos_to(0,0);
391                         core::position2d<s32> pos_from = ap.intpos;
392                         
393                         image->copyTo(
394                                         baseimg, // target
395                                         v2s32(0,0), // position in target
396                                         core::rect<s32>(pos_from, dim) // from
397                         );
398
399                         /*dstream<<"INFO: getTextureIdDirect(): Loaded \""
400                                         <<base_image_name<<"\" from image cache"
401                                         <<std::endl;*/
402                 }
403         }
404         
405         /*
406                 Parse out the last part of the name of the image and act
407                 according to it
408         */
409
410         std::string last_part_of_name = name.substr(last_separator_position+1);
411         //dstream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
412
413         // Generate image according to part of name
414         if(generate_image(last_part_of_name, baseimg, m_device) == false)
415         {
416                 dstream<<"INFO: getTextureIdDirect(): "
417                                 "failed to generate \""<<last_part_of_name<<"\""
418                                 <<std::endl;
419         }
420
421         // If no resulting image, print a warning
422         if(baseimg == NULL)
423         {
424                 dstream<<"WARNING: getTextureIdDirect(): baseimg is NULL (attempted to"
425                                 " create texture \""<<name<<"\""<<std::endl;
426         }
427         
428         if(baseimg != NULL)
429         {
430                 // Create texture from resulting image
431                 t = driver->addTexture(name.c_str(), baseimg);
432         }
433         
434         /*
435                 Add texture to caches (add NULL textures too)
436         */
437
438         JMutexAutoLock lock(m_atlaspointer_cache_mutex);
439         
440         u32 id = m_atlaspointer_cache.size();
441         AtlasPointer ap(id);
442         ap.atlas = t;
443         ap.pos = v2f(0,0);
444         ap.size = v2f(1,1);
445         ap.tiled = 0;
446         core::dimension2d<u32> baseimg_dim(0,0);
447         if(baseimg)
448                 baseimg_dim = baseimg->getDimension();
449         SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
450         m_atlaspointer_cache.push_back(nap);
451         m_name_to_id.insert(name, id);
452
453         /*dstream<<"INFO: getTextureIdDirect(): "
454                         <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
455         
456         return id;
457 }
458
459 std::string TextureSource::getTextureName(u32 id)
460 {
461         JMutexAutoLock lock(m_atlaspointer_cache_mutex);
462
463         if(id >= m_atlaspointer_cache.size())
464         {
465                 dstream<<"WARNING: TextureSource::getTextureName(): id="<<id
466                                 <<" >= m_atlaspointer_cache.size()="
467                                 <<m_atlaspointer_cache.size()<<std::endl;
468                 return "";
469         }
470         
471         return m_atlaspointer_cache[id].name;
472 }
473
474
475 AtlasPointer TextureSource::getTexture(u32 id)
476 {
477         JMutexAutoLock lock(m_atlaspointer_cache_mutex);
478
479         if(id >= m_atlaspointer_cache.size())
480                 return AtlasPointer(0, NULL);
481         
482         return m_atlaspointer_cache[id].a;
483 }
484
485 void TextureSource::buildMainAtlas() 
486 {
487         dstream<<"TextureSource::buildMainAtlas()"<<std::endl;
488
489         //return; // Disable (for testing)
490         
491         video::IVideoDriver* driver = m_device->getVideoDriver();
492         assert(driver);
493
494         JMutexAutoLock lock(m_atlaspointer_cache_mutex);
495
496         // Create an image of the right size
497         core::dimension2d<u32> atlas_dim(1024,1024);
498         video::IImage *atlas_img =
499                         driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
500         //assert(atlas_img);
501         if(atlas_img == NULL)
502         {
503                 dstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
504                                 "image; not building texture atlas."<<std::endl;
505                 return;
506         }
507
508         /*
509                 A list of stuff to include in the texture atlas.
510
511                 It is a single-dimensional texture atlas due to the need to tile
512                 textures.
513                 
514                 It should contain as much of the stuff shown in game as possible,
515                 to minimize texture changes.
516
517                 It fills up quickly, so do not add anything that isn't contained
518                 in most MapBlocks. E.g. mese isn't suitable but stone is.
519         */
520
521         core::array<std::string> sourcelist;
522
523         sourcelist.push_back("stone.png");
524         sourcelist.push_back("mud.png");
525         sourcelist.push_back("sand.png");
526         sourcelist.push_back("grass.png");
527         sourcelist.push_back("grass_footsteps.png");
528         sourcelist.push_back("tree.png");
529         sourcelist.push_back("tree_top.png");
530         sourcelist.push_back("water.png");
531         sourcelist.push_back("leaves.png");
532         sourcelist.push_back("glass.png");
533         sourcelist.push_back("mud.png^grass_side.png");
534         sourcelist.push_back("cobble.png");
535         sourcelist.push_back("mossycobble.png");
536         sourcelist.push_back("gravel.png");
537         sourcelist.push_back("jungletree.png");
538         
539         sourcelist.push_back("stone.png^mineral_coal.png");
540         sourcelist.push_back("stone.png^mineral_iron.png");
541         
542         // Padding to disallow texture bleeding
543         s32 padding = 16;
544
545         /*
546                 First pass: generate almost everything
547         */
548         core::position2d<s32> pos_in_atlas(0,0);
549         
550         pos_in_atlas.Y += padding;
551
552         for(u32 i=0; i<sourcelist.size(); i++)
553         {
554                 std::string name = sourcelist[i];
555
556                 /*video::IImage *img = driver->createImageFromFile(
557                                 getTexturePath(name.c_str()).c_str());
558                 if(img == NULL)
559                         continue;
560                 
561                 core::dimension2d<u32> dim = img->getDimension();
562                 // Make a copy with the right color format
563                 video::IImage *img2 =
564                                 driver->createImage(video::ECF_A8R8G8B8, dim);
565                 img->copyTo(img2);
566                 img->drop();*/
567                 
568                 // Generate image by name
569                 video::IImage *img2 = generate_image_from_scratch(name, m_device);
570                 if(img2 == NULL)
571                 {
572                         dstream<<"WARNING: TextureSource::buildMainAtlas(): Couldn't generate texture atlas: Couldn't generate image \""<<name<<"\""<<std::endl;
573                         continue;
574                 }
575
576                 core::dimension2d<u32> dim = img2->getDimension();
577
578                 // Don't add to atlas if image is large
579                 core::dimension2d<u32> max_size_in_atlas(32,32);
580                 if(dim.Width > max_size_in_atlas.Width
581                 || dim.Height > max_size_in_atlas.Height)
582                 {
583                         dstream<<"INFO: TextureSource::buildMainAtlas(): Not adding "
584                                         <<"\""<<name<<"\" because image is large"<<std::endl;
585                         continue;
586                 }
587
588                 // Stop making atlas if atlas is full
589                 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
590                 {
591                         dstream<<"WARNING: TextureSource::buildMainAtlas(): "
592                                         <<"Atlas is full, not adding more textures."
593                                         <<std::endl;
594                         break;
595                 }
596                 
597         dstream<<"INFO: TextureSource::buildMainAtlas(): Adding \""<<name
598                 <<"\" to texture atlas"<<std::endl;
599
600                 // Tile it a few times in the X direction
601                 u16 xwise_tiling = 16;
602                 for(u32 j=0; j<xwise_tiling; j++)
603                 {
604                         // Copy the copy to the atlas
605                         img2->copyToWithAlpha(atlas_img,
606                                         pos_in_atlas + v2s32(j*dim.Width,0),
607                                         core::rect<s32>(v2s32(0,0), dim),
608                                         video::SColor(255,255,255,255),
609                                         NULL);
610                 }
611
612                 // Copy the borders a few times to disallow texture bleeding
613                 for(u32 side=0; side<2; side++) // top and bottom
614                 for(s32 y0=0; y0<padding; y0++)
615                 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
616                 {
617                         s32 dst_y;
618                         s32 src_y;
619                         if(side==0)
620                         {
621                                 dst_y = y0 + pos_in_atlas.Y + dim.Height;
622                                 src_y = pos_in_atlas.Y + dim.Height - 1;
623                         }
624                         else
625                         {
626                                 dst_y = -y0 + pos_in_atlas.Y-1;
627                                 src_y = pos_in_atlas.Y;
628                         }
629                         s32 x = x0 + pos_in_atlas.X * dim.Width;
630                         video::SColor c = atlas_img->getPixel(x, src_y);
631                         atlas_img->setPixel(x,dst_y,c);
632                 }
633
634                 img2->drop();
635
636                 /*
637                         Add texture to caches
638                 */
639                 
640                 // Get next id
641                 u32 id = m_atlaspointer_cache.size();
642
643                 // Create AtlasPointer
644                 AtlasPointer ap(id);
645                 ap.atlas = NULL; // Set on the second pass
646                 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
647                                 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
648                 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
649                                 (float)dim.Width/(float)atlas_dim.Height);
650                 ap.tiled = xwise_tiling;
651
652                 // Create SourceAtlasPointer and add to containers
653                 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
654                 m_atlaspointer_cache.push_back(nap);
655                 m_name_to_id.insert(name, id);
656                         
657                 // Increment position
658                 pos_in_atlas.Y += dim.Height + padding * 2;
659         }
660
661         /*
662                 Make texture
663         */
664         video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
665         assert(t);
666
667         /*
668                 Second pass: set texture pointer in generated AtlasPointers
669         */
670         for(u32 i=0; i<sourcelist.size(); i++)
671         {
672                 std::string name = sourcelist[i];
673                 if(m_name_to_id.find(name) == NULL)
674                         continue;
675                 u32 id = m_name_to_id[name];
676                 //dstream<<"id of name "<<name<<" is "<<id<<std::endl;
677                 m_atlaspointer_cache[id].a.atlas = t;
678         }
679
680         /*
681                 Write image to file so that it can be inspected
682         */
683         /*driver->writeImageToFile(atlas_img, 
684                         getTexturePath("main_atlas.png").c_str());*/
685 }
686
687 video::IImage* generate_image_from_scratch(std::string name,
688                 IrrlichtDevice *device)
689 {
690         /*dstream<<"INFO: generate_image_from_scratch(): "
691                         "\""<<name<<"\""<<std::endl;*/
692         
693         video::IVideoDriver* driver = device->getVideoDriver();
694         assert(driver);
695
696         /*
697                 Get the base image
698         */
699
700         video::IImage *baseimg = NULL;
701
702         char separator = '^';
703
704         // Find last meta separator in name
705         s32 last_separator_position = -1;
706         for(s32 i=name.size()-1; i>=0; i--)
707         {
708                 if(name[i] == separator)
709                 {
710                         last_separator_position = i;
711                         break;
712                 }
713         }
714
715         /*dstream<<"INFO: generate_image_from_scratch(): "
716                         <<"last_separator_position="<<last_separator_position
717                         <<std::endl;*/
718
719         /*
720                 If separator was found, construct the base name and make the
721                 base image using a recursive call
722         */
723         std::string base_image_name;
724         if(last_separator_position != -1)
725         {
726                 // Construct base name
727                 base_image_name = name.substr(0, last_separator_position);
728                 /*dstream<<"INFO: generate_image_from_scratch(): Calling itself recursively"
729                                 " to get base image of \""<<name<<"\" = \""
730                 <<base_image_name<<"\""<<std::endl;*/
731                 baseimg = generate_image_from_scratch(base_image_name, device);
732         }
733         
734         /*
735                 Parse out the last part of the name of the image and act
736                 according to it
737         */
738
739         std::string last_part_of_name = name.substr(last_separator_position+1);
740         //dstream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
741         
742         // Generate image according to part of name
743         if(generate_image(last_part_of_name, baseimg, device) == false)
744         {
745                 dstream<<"INFO: generate_image_from_scratch(): "
746                                 "failed to generate \""<<last_part_of_name<<"\""
747                                 <<std::endl;
748                 return NULL;
749         }
750         
751         return baseimg;
752 }
753
754 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
755                 IrrlichtDevice *device)
756 {
757         video::IVideoDriver* driver = device->getVideoDriver();
758         assert(driver);
759
760         // Stuff starting with [ are special commands
761         if(part_of_name[0] != '[')
762         {
763                 // A normal texture; load it from a file
764                 std::string path = getTexturePath(part_of_name.c_str());
765                 /*dstream<<"INFO: generate_image(): Loading path \""<<path
766                                 <<"\""<<std::endl;*/
767                 
768                 video::IImage *image = driver->createImageFromFile(path.c_str());
769
770                 if(image == NULL)
771                 {
772                         dstream<<"WARNING: generate_image(): Could not load image \""
773                     <<part_of_name<<"\" from path \""<<path<<"\""
774                                         <<" while building texture"<<std::endl;
775
776                         //return false;
777
778                         dstream<<"WARNING: generate_image(): Creating a dummy"
779                     <<" image for \""<<part_of_name<<"\""<<std::endl;
780
781                         // Just create a dummy image
782                         //core::dimension2d<u32> dim(2,2);
783                         core::dimension2d<u32> dim(1,1);
784                         image = driver->createImage(video::ECF_A8R8G8B8, dim);
785                         assert(image);
786                         /*image->setPixel(0,0, video::SColor(255,255,0,0));
787                         image->setPixel(1,0, video::SColor(255,0,255,0));
788                         image->setPixel(0,1, video::SColor(255,0,0,255));
789                         image->setPixel(1,1, video::SColor(255,255,0,255));*/
790                         image->setPixel(0,0, video::SColor(255,myrand()%256,
791                                         myrand()%256,myrand()%256));
792                         /*image->setPixel(1,0, video::SColor(255,myrand()%256,
793                                         myrand()%256,myrand()%256));
794                         image->setPixel(0,1, video::SColor(255,myrand()%256,
795                                         myrand()%256,myrand()%256));
796                         image->setPixel(1,1, video::SColor(255,myrand()%256,
797                                         myrand()%256,myrand()%256));*/
798                 }
799
800                 // If base image is NULL, load as base.
801                 if(baseimg == NULL)
802                 {
803                         //dstream<<"INFO: Setting "<<part_of_name<<" as base"<<std::endl;
804                         /*
805                                 Copy it this way to get an alpha channel.
806                                 Otherwise images with alpha cannot be blitted on 
807                                 images that don't have alpha in the original file.
808                         */
809                         core::dimension2d<u32> dim = image->getDimension();
810                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
811                         image->copyTo(baseimg);
812                         image->drop();
813                 }
814                 // Else blit on base.
815                 else
816                 {
817                         //dstream<<"INFO: Blitting "<<part_of_name<<" on base"<<std::endl;
818                         // Size of the copied area
819                         core::dimension2d<u32> dim = image->getDimension();
820                         //core::dimension2d<u32> dim(16,16);
821                         // Position to copy the blitted to in the base image
822                         core::position2d<s32> pos_to(0,0);
823                         // Position to copy the blitted from in the blitted image
824                         core::position2d<s32> pos_from(0,0);
825                         // Blit
826                         image->copyToWithAlpha(baseimg, pos_to,
827                                         core::rect<s32>(pos_from, dim),
828                                         video::SColor(255,255,255,255),
829                                         NULL);
830                         // Drop image
831                         image->drop();
832                 }
833         }
834         else
835         {
836                 // A special texture modification
837
838                 dstream<<"INFO: generate_image(): generating special "
839                                 <<"modification \""<<part_of_name<<"\""
840                                 <<std::endl;
841                 
842                 /*
843                         This is the simplest of all; it just adds stuff to the
844                         name so that a separate texture is created.
845
846                         It is used to make textures for stuff that doesn't want
847                         to implement getting the texture from a bigger texture
848                         atlas.
849                 */
850                 if(part_of_name == "[forcesingle")
851                 {
852                 }
853                 /*
854                         [crackN
855                         Adds a cracking texture
856                 */
857                 else if(part_of_name.substr(0,6) == "[crack")
858                 {
859                         if(baseimg == NULL)
860                         {
861                                 dstream<<"WARNING: generate_image(): baseimg==NULL "
862                                                 <<"for part_of_name=\""<<part_of_name
863                                                 <<"\", cancelling."<<std::endl;
864                                 return false;
865                         }
866                         
867                         // Crack image number
868                         u16 progression = stoi(part_of_name.substr(6));
869
870                         // Size of the base image
871                         core::dimension2d<u32> dim_base = baseimg->getDimension();
872                         
873                         /*
874                                 Load crack image.
875
876                                 It is an image with a number of cracking stages
877                                 horizontally tiled.
878                         */
879                         video::IImage *img_crack = driver->createImageFromFile(
880                                         getTexturePath("crack.png").c_str());
881                 
882                         if(img_crack)
883                         {
884                                 // Dimension of original image
885                                 core::dimension2d<u32> dim_crack
886                                                 = img_crack->getDimension();
887                                 // Count of crack stages
888                                 u32 crack_count = dim_crack.Height / dim_crack.Width;
889                                 // Limit progression
890                                 if(progression > crack_count-1)
891                                         progression = crack_count-1;
892                                 // Dimension of a single scaled crack stage
893                                 core::dimension2d<u32> dim_crack_scaled_single(
894                                         dim_base.Width,
895                                         dim_base.Height
896                                 );
897                                 // Dimension of scaled size
898                                 core::dimension2d<u32> dim_crack_scaled(
899                                         dim_crack_scaled_single.Width,
900                                         dim_crack_scaled_single.Height * crack_count
901                                 );
902                                 // Create scaled crack image
903                                 video::IImage *img_crack_scaled = driver->createImage(
904                                                 video::ECF_A8R8G8B8, dim_crack_scaled);
905                                 if(img_crack_scaled)
906                                 {
907                                         // Scale crack image by copying
908                                         img_crack->copyToScaling(img_crack_scaled);
909                                         
910                                         // Position to copy the crack from
911                                         core::position2d<s32> pos_crack_scaled(
912                                                 0,
913                                                 dim_crack_scaled_single.Height * progression
914                                         );
915                                         
916                                         // This tiling does nothing currently but is useful
917                                         for(u32 y0=0; y0<dim_base.Height
918                                                         / dim_crack_scaled_single.Height; y0++)
919                                         for(u32 x0=0; x0<dim_base.Width
920                                                         / dim_crack_scaled_single.Width; x0++)
921                                         {
922                                                 // Position to copy the crack to in the base image
923                                                 core::position2d<s32> pos_base(
924                                                         x0*dim_crack_scaled_single.Width,
925                                                         y0*dim_crack_scaled_single.Height
926                                                 );
927                                                 // Rectangle to copy the crack from on the scaled image
928                                                 core::rect<s32> rect_crack_scaled(
929                                                         pos_crack_scaled,
930                                                         dim_crack_scaled_single
931                                                 );
932                                                 // Copy it
933                                                 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
934                                                                 rect_crack_scaled,
935                                                                 video::SColor(255,255,255,255),
936                                                                 NULL);
937                                         }
938
939                                         img_crack_scaled->drop();
940                                 }
941                                 
942                                 img_crack->drop();
943                         }
944                 }
945                 /*
946                         [combine:WxH:X,Y=filename:X,Y=filename2
947                         Creates a bigger texture from an amount of smaller ones
948                 */
949                 else if(part_of_name.substr(0,8) == "[combine")
950                 {
951                         Strfnd sf(part_of_name);
952                         sf.next(":");
953                         u32 w0 = stoi(sf.next("x"));
954                         u32 h0 = stoi(sf.next(":"));
955                         dstream<<"INFO: combined w="<<w0<<" h="<<h0<<std::endl;
956                         core::dimension2d<u32> dim(w0,h0);
957                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
958                         while(sf.atend() == false)
959                         {
960                                 u32 x = stoi(sf.next(","));
961                                 u32 y = stoi(sf.next("="));
962                                 std::string filename = sf.next(":");
963                                 dstream<<"INFO: Adding \""<<filename
964                                                 <<"\" to combined ("<<x<<","<<y<<")"
965                                                 <<std::endl;
966                                 video::IImage *img = driver->createImageFromFile(
967                                                 getTexturePath(filename.c_str()).c_str());
968                                 if(img)
969                                 {
970                                         core::dimension2d<u32> dim = img->getDimension();
971                                         dstream<<"INFO: Size "<<dim.Width
972                                                         <<"x"<<dim.Height<<std::endl;
973                                         core::position2d<s32> pos_base(x, y);
974                                         video::IImage *img2 =
975                                                         driver->createImage(video::ECF_A8R8G8B8, dim);
976                                         img->copyTo(img2);
977                                         img->drop();
978                                         img2->copyToWithAlpha(baseimg, pos_base,
979                                                         core::rect<s32>(v2s32(0,0), dim),
980                                                         video::SColor(255,255,255,255),
981                                                         NULL);
982                                         img2->drop();
983                                 }
984                                 else
985                                 {
986                                         dstream<<"WARNING: img==NULL"<<std::endl;
987                                 }
988                         }
989                 }
990                 /*
991                         [progressbarN
992                         Adds a progress bar, 0.0 <= N <= 1.0
993                 */
994                 else if(part_of_name.substr(0,12) == "[progressbar")
995                 {
996                         if(baseimg == NULL)
997                         {
998                                 dstream<<"WARNING: generate_image(): baseimg==NULL "
999                                                 <<"for part_of_name=\""<<part_of_name
1000                                                 <<"\", cancelling."<<std::endl;
1001                                 return false;
1002                         }
1003
1004                         float value = stof(part_of_name.substr(12));
1005                         make_progressbar(value, baseimg);
1006                 }
1007                 /*
1008                         "[noalpha:filename.png"
1009                         Use an image without it's alpha channel.
1010                         Used for the leaves texture when in old leaves mode, so
1011                         that the transparent parts don't look completely black 
1012                         when simple alpha channel is used for rendering.
1013                 */
1014                 else if(part_of_name.substr(0,8) == "[noalpha")
1015                 {
1016                         if(baseimg != NULL)
1017                         {
1018                                 dstream<<"WARNING: generate_image(): baseimg!=NULL "
1019                                                 <<"for part_of_name=\""<<part_of_name
1020                                                 <<"\", cancelling."<<std::endl;
1021                                 return false;
1022                         }
1023
1024                         std::string filename = part_of_name.substr(9);
1025
1026                         std::string path = getTexturePath(filename.c_str());
1027
1028                         dstream<<"INFO: generate_image(): Loading path \""<<path
1029                                         <<"\""<<std::endl;
1030                         
1031                         video::IImage *image = driver->createImageFromFile(path.c_str());
1032                         
1033                         if(image == NULL)
1034                         {
1035                                 dstream<<"WARNING: generate_image(): Loading path \""
1036                                                 <<path<<"\" failed"<<std::endl;
1037                         }
1038                         else
1039                         {
1040                                 core::dimension2d<u32> dim = image->getDimension();
1041                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1042                                 
1043                                 // Set alpha to full
1044                                 for(u32 y=0; y<dim.Height; y++)
1045                                 for(u32 x=0; x<dim.Width; x++)
1046                                 {
1047                                         video::SColor c = image->getPixel(x,y);
1048                                         c.setAlpha(255);
1049                                         image->setPixel(x,y,c);
1050                                 }
1051                                 // Blit
1052                                 image->copyTo(baseimg);
1053
1054                                 image->drop();
1055                         }
1056                 }
1057                 /*
1058                         "[makealpha:R,G,B:filename.png"
1059                         Use an image with converting one color to transparent.
1060                 */
1061                 else if(part_of_name.substr(0,11) == "[makealpha:")
1062                 {
1063                         if(baseimg != NULL)
1064                         {
1065                                 dstream<<"WARNING: generate_image(): baseimg!=NULL "
1066                                                 <<"for part_of_name=\""<<part_of_name
1067                                                 <<"\", cancelling."<<std::endl;
1068                                 return false;
1069                         }
1070
1071                         Strfnd sf(part_of_name.substr(11));
1072                         u32 r1 = stoi(sf.next(","));
1073                         u32 g1 = stoi(sf.next(","));
1074                         u32 b1 = stoi(sf.next(":"));
1075                         std::string filename = sf.next("");
1076
1077                         std::string path = getTexturePath(filename.c_str());
1078
1079                         dstream<<"INFO: generate_image(): Loading path \""<<path
1080                                         <<"\""<<std::endl;
1081                         
1082                         video::IImage *image = driver->createImageFromFile(path.c_str());
1083                         
1084                         if(image == NULL)
1085                         {
1086                                 dstream<<"WARNING: generate_image(): Loading path \""
1087                                                 <<path<<"\" failed"<<std::endl;
1088                         }
1089                         else
1090                         {
1091                                 core::dimension2d<u32> dim = image->getDimension();
1092                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1093                                 
1094                                 for(u32 y=0; y<dim.Height; y++)
1095                                 for(u32 x=0; x<dim.Width; x++)
1096                                 {
1097                                         video::SColor c = image->getPixel(x,y);
1098                                         u32 r = c.getRed();
1099                                         u32 g = c.getGreen();
1100                                         u32 b = c.getBlue();
1101                                         if(!(r == r1 && g == g1 && b == b1))
1102                                                 continue;
1103                                         c.setAlpha(0);
1104                                         image->setPixel(x,y,c);
1105                                 }
1106                                 // Blit
1107                                 image->copyTo(baseimg);
1108
1109                                 image->drop();
1110                         }
1111                 }
1112                 /*
1113                         "[makealpha2:R,G,B;R2,G2,B2:filename.png"
1114                         Use an image with converting two colors to transparent.
1115                 */
1116                 else if(part_of_name.substr(0,12) == "[makealpha2:")
1117                 {
1118                         if(baseimg != NULL)
1119                         {
1120                                 dstream<<"WARNING: generate_image(): baseimg!=NULL "
1121                                                 <<"for part_of_name=\""<<part_of_name
1122                                                 <<"\", cancelling."<<std::endl;
1123                                 return false;
1124                         }
1125
1126                         Strfnd sf(part_of_name.substr(12));
1127                         u32 r1 = stoi(sf.next(","));
1128                         u32 g1 = stoi(sf.next(","));
1129                         u32 b1 = stoi(sf.next(";"));
1130                         u32 r2 = stoi(sf.next(","));
1131                         u32 g2 = stoi(sf.next(","));
1132                         u32 b2 = stoi(sf.next(":"));
1133                         std::string filename = sf.next("");
1134
1135                         std::string path = getTexturePath(filename.c_str());
1136
1137                         dstream<<"INFO: generate_image(): Loading path \""<<path
1138                                         <<"\""<<std::endl;
1139                         
1140                         video::IImage *image = driver->createImageFromFile(path.c_str());
1141                         
1142                         if(image == NULL)
1143                         {
1144                                 dstream<<"WARNING: generate_image(): Loading path \""
1145                                                 <<path<<"\" failed"<<std::endl;
1146                         }
1147                         else
1148                         {
1149                                 core::dimension2d<u32> dim = image->getDimension();
1150                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1151                                 
1152                                 for(u32 y=0; y<dim.Height; y++)
1153                                 for(u32 x=0; x<dim.Width; x++)
1154                                 {
1155                                         video::SColor c = image->getPixel(x,y);
1156                                         u32 r = c.getRed();
1157                                         u32 g = c.getGreen();
1158                                         u32 b = c.getBlue();
1159                                         if(!(r == r1 && g == g1 && b == b1) &&
1160                                            !(r == r2 && g == g2 && b == b2))
1161                                                 continue;
1162                                         c.setAlpha(0);
1163                                         image->setPixel(x,y,c);
1164                                 }
1165                                 // Blit
1166                                 image->copyTo(baseimg);
1167
1168                                 image->drop();
1169                         }
1170                 }
1171                 /*
1172                         [inventorycube{topimage{leftimage{rightimage
1173                         In every subimage, replace ^ with &.
1174                         Create an "inventory cube".
1175                         NOTE: This should be used only on its own.
1176                         Example (a grass block (not actually used in game):
1177                         "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1178                 */
1179                 else if(part_of_name.substr(0,14) == "[inventorycube")
1180                 {
1181                         if(baseimg != NULL)
1182                         {
1183                                 dstream<<"WARNING: generate_image(): baseimg!=NULL "
1184                                                 <<"for part_of_name=\""<<part_of_name
1185                                                 <<"\", cancelling."<<std::endl;
1186                                 return false;
1187                         }
1188
1189                         str_replace_char(part_of_name, '&', '^');
1190                         Strfnd sf(part_of_name);
1191                         sf.next("{");
1192                         std::string imagename_top = sf.next("{");
1193                         std::string imagename_left = sf.next("{");
1194                         std::string imagename_right = sf.next("{");
1195
1196 #if 1
1197                         //TODO
1198
1199                         if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
1200                         {
1201                                 dstream<<"WARNING: generate_image(): EVDF_RENDER_TO_TARGET"
1202                                                 " not supported. Creating fallback image"<<std::endl;
1203                                 baseimg = generate_image_from_scratch(
1204                                                 imagename_top, device);
1205                                 return true;
1206                         }
1207                         
1208                         u32 w0 = 64;
1209                         u32 h0 = 64;
1210                         //dstream<<"INFO: inventorycube w="<<w0<<" h="<<h0<<std::endl;
1211                         core::dimension2d<u32> dim(w0,h0);
1212                         
1213                         // Generate images for the faces of the cube
1214                         video::IImage *img_top = generate_image_from_scratch(
1215                                         imagename_top, device);
1216                         video::IImage *img_left = generate_image_from_scratch(
1217                                         imagename_left, device);
1218                         video::IImage *img_right = generate_image_from_scratch(
1219                                         imagename_right, device);
1220                         assert(img_top && img_left && img_right);
1221
1222                         // TODO: Create textures from images
1223                         video::ITexture *texture_top = driver->addTexture(
1224                                         (imagename_top + "__temp__").c_str(), img_top);
1225                         assert(texture_top);
1226                         
1227                         // Drop images
1228                         img_top->drop();
1229                         img_left->drop();
1230                         img_right->drop();
1231                         
1232                         // Create render target texture
1233                         video::ITexture *rtt = NULL;
1234                         std::string rtt_name = part_of_name + "_RTT";
1235                         rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str(),
1236                                         video::ECF_A8R8G8B8);
1237                         assert(rtt);
1238                         
1239                         // Set render target
1240                         driver->setRenderTarget(rtt, true, true,
1241                                         video::SColor(0,0,0,0));
1242                         
1243                         // Get a scene manager
1244                         scene::ISceneManager *smgr_main = device->getSceneManager();
1245                         assert(smgr_main);
1246                         scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
1247                         assert(smgr);
1248                         
1249                         /*
1250                                 Create scene:
1251                                 - An unit cube is centered at 0,0,0
1252                                 - Camera looks at cube from Y+, Z- towards Y-, Z+
1253                                 NOTE: Cube has to be changed to something else because
1254                                 the textures cannot be set individually (or can they?)
1255                         */
1256
1257                         scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1,
1258                                         v3f(0,0,0), v3f(0, 45, 0));
1259                         // Set texture of cube
1260                         cube->setMaterialTexture(0, texture_top);
1261                         //cube->setMaterialFlag(video::EMF_LIGHTING, false);
1262                         cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false);
1263                         cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
1264
1265                         scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
1266                                         v3f(0, 1.0, -1.5), v3f(0, 0, 0));
1267                         // Set orthogonal projection
1268                         core::CMatrix4<f32> pm;
1269                         pm.buildProjectionMatrixOrthoLH(1.65, 1.65, 0, 100);
1270                         camera->setProjectionMatrix(pm, true);
1271
1272                         /*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0,
1273                                         v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000);
1274
1275                         smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2));
1276
1277                         // Render scene
1278                         driver->beginScene(true, true, video::SColor(0,0,0,0));
1279                         smgr->drawAll();
1280                         driver->endScene();
1281                         
1282                         // NOTE: The scene nodes should not be dropped, otherwise
1283                         //       smgr->drop() segfaults
1284                         /*cube->drop();
1285                         camera->drop();
1286                         light->drop();*/
1287                         // Drop scene manager
1288                         smgr->drop();
1289                         
1290                         // Unset render target
1291                         driver->setRenderTarget(0, true, true, 0);
1292
1293                         //TODO: Free textures of images
1294                         driver->removeTexture(texture_top);
1295                         
1296                         // Create image of render target
1297                         video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1298
1299                         assert(image);
1300                         
1301                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1302
1303                         if(image)
1304                         {
1305                                 image->copyTo(baseimg);
1306                                 image->drop();
1307                         }
1308 #endif
1309                 }
1310                 else
1311                 {
1312                         dstream<<"WARNING: generate_image(): Invalid "
1313                                         " modification: \""<<part_of_name<<"\""<<std::endl;
1314                 }
1315         }
1316
1317         return true;
1318 }
1319
1320 void make_progressbar(float value, video::IImage *image)
1321 {
1322         if(image == NULL)
1323                 return;
1324         
1325         core::dimension2d<u32> size = image->getDimension();
1326
1327         u32 barheight = size.Height/16;
1328         u32 barpad_x = size.Width/16;
1329         u32 barpad_y = size.Height/16;
1330         u32 barwidth = size.Width - barpad_x*2;
1331         v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
1332
1333         u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
1334
1335         video::SColor active(255,255,0,0);
1336         video::SColor inactive(255,0,0,0);
1337         for(u32 x0=0; x0<barwidth; x0++)
1338         {
1339                 video::SColor *c;
1340                 if(x0 < barvalue_i)
1341                         c = &active;
1342                 else
1343                         c = &inactive;
1344                 u32 x = x0 + barpos.X;
1345                 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
1346                 {
1347                         image->setPixel(x,y, *c);
1348                 }
1349         }
1350 }
1351