Use random-generated fallback textures when real textures are not found
[oweals/minetest.git] / src / tile.cpp
1 /*
2 Minetest-c55
3 Copyright (C) 2010 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
23 TextureSource::TextureSource(IrrlichtDevice *device):
24                 m_device(device),
25                 m_main_atlas_image(NULL),
26                 m_main_atlas_texture(NULL)
27 {
28         assert(m_device);
29         
30         m_atlaspointer_cache_mutex.Init();
31         
32         m_main_thread = get_current_thread_id();
33         
34         // Add a NULL AtlasPointer as the first index, named ""
35         m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
36         m_name_to_id[""] = 0;
37
38         // Build main texture atlas
39         buildMainAtlas();
40 }
41
42 TextureSource::~TextureSource()
43 {
44 }
45
46 void TextureSource::processQueue()
47 {
48         /*
49                 Fetch textures
50         */
51         if(m_get_texture_queue.size() > 0)
52         {
53                 GetRequest<std::string, u32, u8, u8>
54                                 request = m_get_texture_queue.pop();
55
56                 dstream<<"INFO: TextureSource::processQueue(): "
57                                 <<"got texture request with "
58                                 <<"name="<<request.key
59                                 <<std::endl;
60
61                 GetResult<std::string, u32, u8, u8>
62                                 result;
63                 result.key = request.key;
64                 result.callers = request.callers;
65                 result.item = getTextureIdDirect(request.key);
66
67                 request.dest->push_back(result);
68         }
69 }
70
71 u32 TextureSource::getTextureId(const std::string &name)
72 {
73         //dstream<<"INFO: getTextureId(): name="<<name<<std::endl;
74
75         {
76                 /*
77                         See if texture already exists
78                 */
79                 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
80                 core::map<std::string, u32>::Node *n;
81                 n = m_name_to_id.find(name);
82                 if(n != NULL)
83                 {
84                         return n->getValue();
85                 }
86         }
87         
88         /*
89                 Get texture
90         */
91         if(get_current_thread_id() == m_main_thread)
92         {
93                 return getTextureIdDirect(name);
94         }
95         else
96         {
97                 dstream<<"INFO: getTextureId(): Queued: name="<<name<<std::endl;
98
99                 // We're gonna ask the result to be put into here
100                 ResultQueue<std::string, u32, u8, u8> result_queue;
101                 
102                 // Throw a request in
103                 m_get_texture_queue.add(name, 0, 0, &result_queue);
104                 
105                 dstream<<"INFO: Waiting for texture from main thread, name="
106                                 <<name<<std::endl;
107                 
108                 try
109                 {
110                         // Wait result for a second
111                         GetResult<std::string, u32, u8, u8>
112                                         result = result_queue.pop_front(1000);
113                 
114                         // Check that at least something worked OK
115                         assert(result.key == name);
116
117                         return result.item;
118                 }
119                 catch(ItemNotFoundException &e)
120                 {
121                         dstream<<"WARNING: Waiting for texture timed out."<<std::endl;
122                         return 0;
123                 }
124         }
125         
126         dstream<<"WARNING: getTextureId(): Failed"<<std::endl;
127
128         return 0;
129 }
130
131 // Draw a progress bar on the image
132 void make_progressbar(float value, video::IImage *image);
133
134 /*
135         Generate image based on a string like "stone.png" or "[crack0".
136         if baseimg is NULL, it is created. Otherwise stuff is made on it.
137 */
138 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
139                 video::IVideoDriver* driver);
140
141 /*
142         Generates an image from a full string like
143         "stone.png^mineral_coal.png^[crack0".
144
145         This is used by buildMainAtlas().
146 */
147 video::IImage* generate_image_from_scratch(std::string name,
148                 video::IVideoDriver* driver);
149
150 /*
151         This method generates all the textures
152 */
153 u32 TextureSource::getTextureIdDirect(const std::string &name)
154 {
155         dstream<<"INFO: getTextureIdDirect(): name="<<name<<std::endl;
156
157         // Empty name means texture 0
158         if(name == "")
159         {
160                 dstream<<"INFO: getTextureIdDirect(): name is empty"<<std::endl;
161                 return 0;
162         }
163         
164         /*
165                 Calling only allowed from main thread
166         */
167         if(get_current_thread_id() != m_main_thread)
168         {
169                 dstream<<"ERROR: TextureSource::getTextureIdDirect() "
170                                 "called not from main thread"<<std::endl;
171                 return 0;
172         }
173
174         /*
175                 See if texture already exists
176         */
177         {
178                 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
179
180                 core::map<std::string, u32>::Node *n;
181                 n = m_name_to_id.find(name);
182                 if(n != NULL)
183                 {
184                         dstream<<"INFO: getTextureIdDirect(): name="<<name
185                                         <<" found in cache"<<std::endl;
186                         return n->getValue();
187                 }
188         }
189
190         dstream<<"INFO: getTextureIdDirect(): name="<<name
191                         <<" NOT found in cache. Creating it."<<std::endl;
192         
193         /*
194                 Get the base image
195         */
196
197         char separator = '^';
198
199         /*
200                 This is set to the id of the base image.
201                 If left 0, there is no base image and a completely new image
202                 is made.
203         */
204         u32 base_image_id = 0;
205         
206         // Find last meta separator in name
207         s32 last_separator_position = -1;
208         for(s32 i=name.size()-1; i>=0; i--)
209         {
210                 if(name[i] == separator)
211                 {
212                         last_separator_position = i;
213                         break;
214                 }
215         }
216         /*
217                 If separator was found, construct the base name and make the
218                 base image using a recursive call
219         */
220         std::string base_image_name;
221         if(last_separator_position != -1)
222         {
223                 // Construct base name
224                 base_image_name = name.substr(0, last_separator_position);
225                 dstream<<"INFO: getTextureIdDirect(): Calling itself recursively"
226                                 " to get base image, name="<<base_image_name<<std::endl;
227                 base_image_id = getTextureIdDirect(base_image_name);
228         }
229         
230         dstream<<"base_image_id="<<base_image_id<<std::endl;
231         
232         video::IVideoDriver* driver = m_device->getVideoDriver();
233         assert(driver);
234
235         video::ITexture *t = NULL;
236
237         /*
238                 An image will be built from files and then converted into a texture.
239         */
240         video::IImage *baseimg = NULL;
241         
242         // If a base image was found, copy it to baseimg
243         if(base_image_id != 0)
244         {
245                 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
246
247                 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
248
249                 video::IImage *image = ap.atlas_img;
250                 
251                 if(image == NULL)
252                 {
253                         dstream<<"WARNING: getTextureIdDirect(): NULL image in "
254                                         <<"cache: \""<<base_image_name<<"\""
255                                         <<std::endl;
256                 }
257                 else
258                 {
259                         core::dimension2d<u32> dim = ap.intsize;
260
261                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
262
263                         core::position2d<s32> pos_to(0,0);
264                         core::position2d<s32> pos_from = ap.intpos;
265                         
266                         image->copyTo(
267                                         baseimg, // target
268                                         v2s32(0,0), // position in target
269                                         core::rect<s32>(pos_from, dim) // from
270                         );
271
272                         dstream<<"INFO: getTextureIdDirect(): Loaded \""
273                                         <<base_image_name<<"\" from image cache"
274                                         <<std::endl;
275                 }
276         }
277         
278         /*
279                 Parse out the last part of the name of the image and act
280                 according to it
281         */
282
283         std::string last_part_of_name = name.substr(last_separator_position+1);
284         dstream<<"last_part_of_name="<<last_part_of_name<<std::endl;
285
286         // Generate image according to part of name
287         if(generate_image(last_part_of_name, baseimg, driver) == false)
288         {
289                 dstream<<"INFO: getTextureIdDirect(): "
290                                 "failed to generate \""<<last_part_of_name<<"\""
291                                 <<std::endl;
292         }
293
294         // If no resulting image, print a warning
295         if(baseimg == NULL)
296         {
297                 dstream<<"WARNING: getTextureIdDirect(): baseimg is NULL (attempted to"
298                                 " create texture \""<<name<<"\""<<std::endl;
299         }
300         
301         if(baseimg != NULL)
302         {
303                 // Create texture from resulting image
304                 t = driver->addTexture(name.c_str(), baseimg);
305         }
306         
307         /*
308                 Add texture to caches (add NULL textures too)
309         */
310
311         JMutexAutoLock lock(m_atlaspointer_cache_mutex);
312         
313         u32 id = m_atlaspointer_cache.size();
314         AtlasPointer ap(id);
315         ap.atlas = t;
316         ap.pos = v2f(0,0);
317         ap.size = v2f(1,1);
318         ap.tiled = 0;
319         core::dimension2d<u32> baseimg_dim(0,0);
320         if(baseimg)
321                 baseimg_dim = baseimg->getDimension();
322         SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
323         m_atlaspointer_cache.push_back(nap);
324         m_name_to_id.insert(name, id);
325
326         dstream<<"INFO: getTextureIdDirect(): name="<<name
327                         <<": succesfully returning id="<<id<<std::endl;
328         
329         return id;
330 }
331
332 std::string TextureSource::getTextureName(u32 id)
333 {
334         JMutexAutoLock lock(m_atlaspointer_cache_mutex);
335
336         if(id >= m_atlaspointer_cache.size())
337         {
338                 dstream<<"WARNING: TextureSource::getTextureName(): id="<<id
339                                 <<" >= m_atlaspointer_cache.size()="
340                                 <<m_atlaspointer_cache.size()<<std::endl;
341                 return "";
342         }
343         
344         return m_atlaspointer_cache[id].name;
345 }
346
347
348 AtlasPointer TextureSource::getTexture(u32 id)
349 {
350         JMutexAutoLock lock(m_atlaspointer_cache_mutex);
351
352         if(id >= m_atlaspointer_cache.size())
353                 return AtlasPointer(0, NULL);
354         
355         return m_atlaspointer_cache[id].a;
356 }
357
358 void TextureSource::buildMainAtlas() 
359 {
360         dstream<<"TextureSource::buildMainAtlas()"<<std::endl;
361
362         //return; // Disable (for testing)
363         
364         video::IVideoDriver* driver = m_device->getVideoDriver();
365         assert(driver);
366
367         JMutexAutoLock lock(m_atlaspointer_cache_mutex);
368
369         // Create an image of the right size
370         core::dimension2d<u32> atlas_dim(1024,1024);
371         video::IImage *atlas_img =
372                         driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
373         assert(atlas_img);
374
375         /*
376                 A list of stuff to add. This should contain as much of the
377                 stuff shown in game as possible, to minimize texture changes.
378         */
379
380         core::array<std::string> sourcelist;
381
382         sourcelist.push_back("stone.png");
383         sourcelist.push_back("mud.png");
384         sourcelist.push_back("sand.png");
385         sourcelist.push_back("grass.png");
386         sourcelist.push_back("grass_footsteps.png");
387         sourcelist.push_back("tree.png");
388         sourcelist.push_back("tree_top.png");
389         sourcelist.push_back("water.png");
390         sourcelist.push_back("leaves.png");
391         sourcelist.push_back("mud.png^grass_side.png");
392         
393         sourcelist.push_back("stone.png^mineral_coal.png");
394         sourcelist.push_back("stone.png^mineral_iron.png");
395         sourcelist.push_back("mud.png^mineral_coal.png");
396         sourcelist.push_back("mud.png^mineral_iron.png");
397         sourcelist.push_back("sand.png^mineral_coal.png");
398         sourcelist.push_back("sand.png^mineral_iron.png");
399         
400         // Padding to disallow texture bleeding
401         s32 padding = 8;
402
403         /*
404                 First pass: generate almost everything
405         */
406         core::position2d<s32> pos_in_atlas(0,0);
407         
408         pos_in_atlas.Y += padding;
409
410         for(u32 i=0; i<sourcelist.size(); i++)
411         {
412                 std::string name = sourcelist[i];
413
414                 /*video::IImage *img = driver->createImageFromFile(
415                                 porting::getDataPath(name.c_str()).c_str());
416                 if(img == NULL)
417                         continue;
418                 
419                 core::dimension2d<u32> dim = img->getDimension();
420                 // Make a copy with the right color format
421                 video::IImage *img2 =
422                                 driver->createImage(video::ECF_A8R8G8B8, dim);
423                 img->copyTo(img2);
424                 img->drop();*/
425                 
426                 // Generate image by name
427                 video::IImage *img2 = generate_image_from_scratch(name, driver);
428                 if(img2 == NULL)
429                 {
430                         dstream<<"WARNING: TextureSource::buildMainAtlas(): Couldn't generate texture atlas: Couldn't generate image \""<<name<<"\""<<std::endl;
431                         continue;
432                 }
433
434                 core::dimension2d<u32> dim = img2->getDimension();
435                 
436                 // Tile it a few times in the X direction
437                 u16 xwise_tiling = 16;
438                 for(u32 j=0; j<xwise_tiling; j++)
439                 {
440                         // Copy the copy to the atlas
441                         img2->copyToWithAlpha(atlas_img,
442                                         pos_in_atlas + v2s32(j*dim.Width,0),
443                                         core::rect<s32>(v2s32(0,0), dim),
444                                         video::SColor(255,255,255,255),
445                                         NULL);
446                 }
447
448                 // Copy the borders a few times to disallow texture bleeding
449                 for(u32 side=0; side<2; side++) // top and bottom
450                 for(s32 y0=0; y0<padding; y0++)
451                 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
452                 {
453                         s32 dst_y;
454                         s32 src_y;
455                         if(side==0)
456                         {
457                                 dst_y = y0 + pos_in_atlas.Y + dim.Height;
458                                 src_y = pos_in_atlas.Y + dim.Height - 1;
459                         }
460                         else
461                         {
462                                 dst_y = -y0 + pos_in_atlas.Y-1;
463                                 src_y = pos_in_atlas.Y;
464                         }
465                         s32 x = x0 + pos_in_atlas.X * dim.Width;
466                         video::SColor c = atlas_img->getPixel(x, src_y);
467                         atlas_img->setPixel(x,dst_y,c);
468                 }
469
470                 img2->drop();
471
472                 /*
473                         Add texture to caches
474                 */
475                 
476                 // Get next id
477                 u32 id = m_atlaspointer_cache.size();
478
479                 // Create AtlasPointer
480                 AtlasPointer ap(id);
481                 ap.atlas = NULL; // Set on the second pass
482                 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
483                                 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
484                 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
485                                 (float)dim.Width/(float)atlas_dim.Height);
486                 ap.tiled = xwise_tiling;
487
488                 // Create SourceAtlasPointer and add to containers
489                 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
490                 m_atlaspointer_cache.push_back(nap);
491                 m_name_to_id.insert(name, id);
492                         
493                 // Increment position
494                 pos_in_atlas.Y += dim.Height + padding * 2;
495         }
496
497         /*
498                 Make texture
499         */
500         video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
501         assert(t);
502
503         /*
504                 Second pass: set texture pointer in generated AtlasPointers
505         */
506         for(u32 i=0; i<sourcelist.size(); i++)
507         {
508                 std::string name = sourcelist[i];
509                 if(m_name_to_id.find(name) == NULL)
510                         continue;
511                 u32 id = m_name_to_id[name];
512                 //dstream<<"id of name "<<name<<" is "<<id<<std::endl;
513                 m_atlaspointer_cache[id].a.atlas = t;
514         }
515
516         /*
517                 Write image to file so that it can be inspected
518         */
519         /*driver->writeImageToFile(atlas_img, 
520                         porting::getDataPath("main_atlas.png").c_str());*/
521 }
522
523 video::IImage* generate_image_from_scratch(std::string name,
524                 video::IVideoDriver* driver)
525 {
526         dstream<<"INFO: generate_image_from_scratch(): "
527                         "name="<<name<<std::endl;
528         
529         /*
530                 Get the base image
531         */
532
533         video::IImage *baseimg = NULL;
534
535         char separator = '^';
536
537         // Find last meta separator in name
538         s32 last_separator_position = -1;
539         for(s32 i=name.size()-1; i>=0; i--)
540         {
541                 if(name[i] == separator)
542                 {
543                         last_separator_position = i;
544                         break;
545                 }
546         }
547
548         /*dstream<<"INFO: generate_image_from_scratch(): "
549                         <<"last_separator_position="<<last_separator_position
550                         <<std::endl;*/
551
552         /*
553                 If separator was found, construct the base name and make the
554                 base image using a recursive call
555         */
556         std::string base_image_name;
557         if(last_separator_position != -1)
558         {
559                 // Construct base name
560                 base_image_name = name.substr(0, last_separator_position);
561                 dstream<<"INFO: generate_image_from_scratch(): Calling itself recursively"
562                                 " to get base image, name="<<base_image_name<<std::endl;
563                 baseimg = generate_image_from_scratch(base_image_name, driver);
564         }
565         
566         /*
567                 Parse out the last part of the name of the image and act
568                 according to it
569         */
570
571         std::string last_part_of_name = name.substr(last_separator_position+1);
572         dstream<<"last_part_of_name="<<last_part_of_name<<std::endl;
573         
574         // Generate image according to part of name
575         if(generate_image(last_part_of_name, baseimg, driver) == false)
576         {
577                 dstream<<"INFO: generate_image_from_scratch(): "
578                                 "failed to generate \""<<last_part_of_name<<"\""
579                                 <<std::endl;
580                 return NULL;
581         }
582         
583         return baseimg;
584 }
585
586 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
587                 video::IVideoDriver* driver)
588 {
589         // Stuff starting with [ are special commands
590         if(part_of_name[0] != '[')
591         {
592                 // A normal texture; load it from a file
593                 std::string path = porting::getDataPath(part_of_name.c_str());
594                 dstream<<"INFO: getTextureIdDirect(): Loading path \""<<path
595                                 <<"\""<<std::endl;
596                 
597                 video::IImage *image = driver->createImageFromFile(path.c_str());
598
599                 if(image == NULL)
600                 {
601                         dstream<<"WARNING: Could not load image \""<<part_of_name
602                                         <<"\" from path \""<<path<<"\""
603                                         <<" while building texture"<<std::endl;
604
605                         //return false;
606
607                         dstream<<"WARNING: Creating a dummy"<<" image for \""
608                                         <<part_of_name<<"\""<<std::endl;
609
610                         // Just create a dummy image
611                         //core::dimension2d<u32> dim(2,2);
612                         core::dimension2d<u32> dim(1,1);
613                         image = driver->createImage(video::ECF_A8R8G8B8, dim);
614                         assert(image);
615                         /*image->setPixel(0,0, video::SColor(255,255,0,0));
616                         image->setPixel(1,0, video::SColor(255,0,255,0));
617                         image->setPixel(0,1, video::SColor(255,0,0,255));
618                         image->setPixel(1,1, video::SColor(255,255,0,255));*/
619                         image->setPixel(0,0, video::SColor(255,myrand()%256,
620                                         myrand()%256,myrand()%256));
621                         /*image->setPixel(1,0, video::SColor(255,myrand()%256,
622                                         myrand()%256,myrand()%256));
623                         image->setPixel(0,1, video::SColor(255,myrand()%256,
624                                         myrand()%256,myrand()%256));
625                         image->setPixel(1,1, video::SColor(255,myrand()%256,
626                                         myrand()%256,myrand()%256));*/
627                 }
628
629                 // If base image is NULL, load as base.
630                 if(baseimg == NULL)
631                 {
632                         dstream<<"INFO: Setting "<<part_of_name<<" as base"<<std::endl;
633                         /*
634                                 Copy it this way to get an alpha channel.
635                                 Otherwise images with alpha cannot be blitted on 
636                                 images that don't have alpha in the original file.
637                         */
638                         core::dimension2d<u32> dim = image->getDimension();
639                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
640                         image->copyTo(baseimg);
641                         image->drop();
642                 }
643                 // Else blit on base.
644                 else
645                 {
646                         dstream<<"INFO: Blitting "<<part_of_name<<" on base"<<std::endl;
647                         // Size of the copied area
648                         core::dimension2d<u32> dim = image->getDimension();
649                         //core::dimension2d<u32> dim(16,16);
650                         // Position to copy the blitted to in the base image
651                         core::position2d<s32> pos_to(0,0);
652                         // Position to copy the blitted from in the blitted image
653                         core::position2d<s32> pos_from(0,0);
654                         // Blit
655                         image->copyToWithAlpha(baseimg, pos_to,
656                                         core::rect<s32>(pos_from, dim),
657                                         video::SColor(255,255,255,255),
658                                         NULL);
659                         // Drop image
660                         image->drop();
661                 }
662         }
663         else
664         {
665                 // A special texture modification
666
667                 dstream<<"INFO: getTextureIdDirect(): generating special "
668                                 <<"modification \""<<part_of_name<<"\""
669                                 <<std::endl;
670                 
671                 /*
672                         This is the simplest of all; it just adds stuff to the
673                         name so that a separate texture is created.
674
675                         It is used to make textures for stuff that doesn't want
676                         to implement getting the texture from a bigger texture
677                         atlas.
678                 */
679                 if(part_of_name == "[forcesingle")
680                 {
681                 }
682                 /*
683                         [crackN
684                         Adds a cracking texture
685                 */
686                 else if(part_of_name.substr(0,6) == "[crack")
687                 {
688                         if(baseimg == NULL)
689                         {
690                                 dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
691                                                 <<"for part_of_name="<<part_of_name
692                                                 <<", cancelling."<<std::endl;
693                                 return false;
694                         }
695
696                         u16 progression = stoi(part_of_name.substr(6));
697                         // Size of the base image
698                         core::dimension2d<u32> dim_base = baseimg->getDimension();
699                         // Crack will be drawn at this size
700                         u32 cracksize = 16;
701                         // Size of the crack image
702                         core::dimension2d<u32> dim_crack(cracksize,cracksize);
703                         // Position to copy the crack from in the crack image
704                         core::position2d<s32> pos_other(0, 16 * progression);
705
706                         video::IImage *crackimage = driver->createImageFromFile(
707                                         porting::getDataPath("crack.png").c_str());
708                 
709                         if(crackimage)
710                         {
711                                 /*crackimage->copyToWithAlpha(baseimg, v2s32(0,0),
712                                                 core::rect<s32>(pos_other, dim_base),
713                                                 video::SColor(255,255,255,255),
714                                                 NULL);*/
715
716                                 for(u32 y0=0; y0<dim_base.Height/dim_crack.Height; y0++)
717                                 for(u32 x0=0; x0<dim_base.Width/dim_crack.Width; x0++)
718                                 {
719                                         // Position to copy the crack to in the base image
720                                         core::position2d<s32> pos_base(x0*cracksize, y0*cracksize);
721                                         crackimage->copyToWithAlpha(baseimg, pos_base,
722                                                         core::rect<s32>(pos_other, dim_crack),
723                                                         video::SColor(255,255,255,255),
724                                                         NULL);
725                                 }
726
727                                 crackimage->drop();
728                         }
729                 }
730                 /*
731                         [combine:WxH:X,Y=filename:X,Y=filename2
732                         Creates a bigger texture from an amount of smaller ones
733                 */
734                 else if(part_of_name.substr(0,8) == "[combine")
735                 {
736                         Strfnd sf(part_of_name);
737                         sf.next(":");
738                         u32 w0 = stoi(sf.next("x"));
739                         u32 h0 = stoi(sf.next(":"));
740                         dstream<<"INFO: combined w="<<w0<<" h="<<h0<<std::endl;
741                         core::dimension2d<u32> dim(w0,h0);
742                         baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
743                         while(sf.atend() == false)
744                         {
745                                 u32 x = stoi(sf.next(","));
746                                 u32 y = stoi(sf.next("="));
747                                 std::string filename = sf.next(":");
748                                 dstream<<"INFO: Adding \""<<filename
749                                                 <<"\" to combined ("<<x<<","<<y<<")"
750                                                 <<std::endl;
751                                 video::IImage *img = driver->createImageFromFile(
752                                                 porting::getDataPath(filename.c_str()).c_str());
753                                 if(img)
754                                 {
755                                         core::dimension2d<u32> dim = img->getDimension();
756                                         dstream<<"INFO: Size "<<dim.Width
757                                                         <<"x"<<dim.Height<<std::endl;
758                                         core::position2d<s32> pos_base(x, y);
759                                         video::IImage *img2 =
760                                                         driver->createImage(video::ECF_A8R8G8B8, dim);
761                                         img->copyTo(img2);
762                                         img->drop();
763                                         img2->copyToWithAlpha(baseimg, pos_base,
764                                                         core::rect<s32>(v2s32(0,0), dim),
765                                                         video::SColor(255,255,255,255),
766                                                         NULL);
767                                         img2->drop();
768                                 }
769                                 else
770                                 {
771                                         dstream<<"WARNING: img==NULL"<<std::endl;
772                                 }
773                         }
774                 }
775                 /*
776                         [progressbarN
777                         Adds a progress bar, 0.0 <= N <= 1.0
778                 */
779                 else if(part_of_name.substr(0,12) == "[progressbar")
780                 {
781                         if(baseimg == NULL)
782                         {
783                                 dstream<<"WARNING: getTextureIdDirect(): baseimg==NULL "
784                                                 <<"for part_of_name="<<part_of_name
785                                                 <<", cancelling."<<std::endl;
786                                 return false;
787                         }
788
789                         float value = stof(part_of_name.substr(12));
790                         make_progressbar(value, baseimg);
791                 }
792                 /*
793                         "[noalpha:filename.png"
794                         Use an image without it's alpha channel.
795                         Used for the leaves texture when in old leaves mode, so
796                         that the transparent parts don't look completely black 
797                         when simple alpha channel is used for rendering.
798                 */
799                 else if(part_of_name.substr(0,8) == "[noalpha")
800                 {
801                         if(baseimg != NULL)
802                         {
803                                 dstream<<"WARNING: getTextureIdDirect(): baseimg!=NULL "
804                                                 <<"for part_of_name="<<part_of_name
805                                                 <<", cancelling."<<std::endl;
806                                 return false;
807                         }
808
809                         std::string filename = part_of_name.substr(9);
810
811                         std::string path = porting::getDataPath(filename.c_str());
812
813                         dstream<<"INFO: getTextureIdDirect(): Loading path \""<<path
814                                         <<"\""<<std::endl;
815                         
816                         video::IImage *image = driver->createImageFromFile(path.c_str());
817                         
818                         if(image == NULL)
819                         {
820                                 dstream<<"WARNING: getTextureIdDirect(): Loading path \""
821                                                 <<path<<"\" failed"<<std::endl;
822                         }
823                         else
824                         {
825                                 core::dimension2d<u32> dim = image->getDimension();
826                                 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
827                                 
828                                 // Set alpha to full
829                                 for(u32 y=0; y<dim.Height; y++)
830                                 for(u32 x=0; x<dim.Width; x++)
831                                 {
832                                         video::SColor c = image->getPixel(x,y);
833                                         c.setAlpha(255);
834                                         image->setPixel(x,y,c);
835                                 }
836                                 // Blit
837                                 image->copyTo(baseimg);
838
839                                 image->drop();
840                         }
841                 }
842                 /*
843                         [inventorycube{topimage{leftimage{rightimage
844                         In every subimage, replace ^ with &.
845                         Create an "inventory cube".
846                         NOTE: This should be used only on its own.
847                         Example (a grass block (not actually used in game):
848                         "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
849                 */
850                 else if(part_of_name.substr(0,14) == "[inventorycube")
851                 {
852                         if(baseimg != NULL)
853                         {
854                                 dstream<<"WARNING: getTextureIdDirect(): baseimg!=NULL "
855                                                 <<"for part_of_name="<<part_of_name
856                                                 <<", cancelling."<<std::endl;
857                                 return false;
858                         }
859
860                         // This is just a placeholder
861
862                         str_replace_char(part_of_name, '&', '^');
863                         Strfnd sf(part_of_name);
864                         sf.next("{");
865                         std::string imagename_top = sf.next("{");
866                         std::string imagename_left = sf.next("{");
867                         std::string imagename_right = sf.next("{");
868
869                         baseimg = generate_image_from_scratch(
870                                         imagename_top, driver);
871
872                         //TODO
873 #if 0
874                         if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
875                         {
876                                 dstream<<"WARNING: getTextureIdDirect(): EVDF_RENDER_TO_TARGET"
877                                                 " not supported"<<std::endl;
878                                 return false;
879                         }
880                         
881                         u32 w0 = 16;
882                         u32 h0 = 16;
883                         dstream<<"INFO: inventorycube w="<<w0<<" h="<<h0<<std::endl;
884                         core::dimension2d<u32> dim(w0,h0);
885
886                         //baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
887
888                         video::IImage *img_top = generate_image_from_scratch(
889                                         imagename_top, driver);
890                         video::IImage *img_left = generate_image_from_scratch(
891                                         imagename_left, driver);
892                         video::IImage *img_right = generate_image_from_scratch(
893                                         imagename_right, driver);
894                         
895                         // Render target texture
896                         video::ITexture *rtt = NULL;
897                         std::string rtt_name = part_of_name + "_RTT";
898                         
899                         rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str());
900                         assert(rtt);
901                         
902                         
903                         
904                         img_top->drop();
905                         img_left->drop();
906                         img_right->drop();
907                         
908                         //TODO
909                         assert(0);
910 #endif
911                 }
912                 else
913                 {
914                         dstream<<"WARNING: getTextureIdDirect(): Invalid "
915                                         " modification: \""<<part_of_name<<"\""<<std::endl;
916                 }
917         }
918
919         return true;
920 }
921
922 void make_progressbar(float value, video::IImage *image)
923 {
924         if(image == NULL)
925                 return;
926         
927         core::dimension2d<u32> size = image->getDimension();
928
929         u32 barheight = 1;
930         u32 barpad_x = 1;
931         u32 barpad_y = 1;
932         u32 barwidth = size.Width - barpad_x*2;
933         v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
934
935         u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
936
937         video::SColor active(255,255,0,0);
938         video::SColor inactive(255,0,0,0);
939         for(u32 x0=0; x0<barwidth; x0++)
940         {
941                 video::SColor *c;
942                 if(x0 < barvalue_i)
943                         c = &active;
944                 else
945                         c = &inactive;
946                 u32 x = x0 + barpos.X;
947                 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
948                 {
949                         image->setPixel(x,y, *c);
950                 }
951         }
952 }
953