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