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