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