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