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