3 Copyright (C) 2010-2011 celeron55, Perttu Ahola <celeron55@gmail.com>
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.
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.
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.
22 #include "main.h" // for g_settings
26 #include <ICameraSceneNode.h>
30 A cache from texture name to texture path
32 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
35 Replaces the filename extension.
37 std::string image = "a/image.png"
38 replace_ext(image, "jpg")
39 -> image = "a/image.jpg"
40 Returns true on success.
42 static bool replace_ext(std::string &path, const char *ext)
46 // Find place of last dot, fail if \ or / found.
48 for(s32 i=path.size()-1; i>=0; i--)
56 if(path[i] == '\\' || path[i] == '/')
59 // If not found, return an empty string
62 // Else make the new path
63 path = path.substr(0, last_dot_i+1) + ext;
68 Find out the full path of an image by trying different filename
73 static std::string getImagePath(std::string path)
75 // A NULL-ended list of possible image extensions
76 const char *extensions[] = {
77 "png", "jpg", "bmp", "tga",
78 "pcx", "ppm", "psd", "wal", "rgb",
82 const char **ext = extensions;
84 bool r = replace_ext(path, *ext);
87 if(fs::PathExists(path))
90 while((++ext) != NULL);
96 Gets the path to a texture by first checking if the texture exists
97 in texture_path and if not, using the data path.
99 Checks all supported extensions by replacing the original extension.
101 If not found, returns "".
103 Utilizes a thread-safe cache.
105 std::string getTexturePath(const std::string &filename)
107 std::string fullpath = "";
111 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
116 Check from texture_path
118 std::string texture_path = g_settings->get("texture_path");
119 if(texture_path != "")
121 std::string testpath = texture_path + DIR_DELIM + filename;
122 // Check all filename extensions. Returns "" if not found.
123 fullpath = getImagePath(testpath);
127 Check from default data directory
131 std::string testpath = porting::getDataPath(filename.c_str());
132 // Check all filename extensions. Returns "" if not found.
133 fullpath = getImagePath(testpath);
136 // Add to cache (also an empty result is cached)
137 g_texturename_to_path_cache.set(filename, fullpath);
147 TextureSource::TextureSource(IrrlichtDevice *device):
149 m_main_atlas_image(NULL),
150 m_main_atlas_texture(NULL)
154 m_atlaspointer_cache_mutex.Init();
156 m_main_thread = get_current_thread_id();
158 // Add a NULL AtlasPointer as the first index, named ""
159 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
160 m_name_to_id[""] = 0;
162 // Build main texture atlas
163 if(g_settings->getBool("enable_texture_atlas"))
166 infostream<<"Not building texture atlas."<<std::endl;
169 TextureSource::~TextureSource()
173 void TextureSource::processQueue()
178 if(m_get_texture_queue.size() > 0)
180 GetRequest<std::string, u32, u8, u8>
181 request = m_get_texture_queue.pop();
183 infostream<<"TextureSource::processQueue(): "
184 <<"got texture request with "
185 <<"name=\""<<request.key<<"\""
188 GetResult<std::string, u32, u8, u8>
190 result.key = request.key;
191 result.callers = request.callers;
192 result.item = getTextureIdDirect(request.key);
194 request.dest->push_back(result);
198 u32 TextureSource::getTextureId(const std::string &name)
200 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
204 See if texture already exists
206 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
207 core::map<std::string, u32>::Node *n;
208 n = m_name_to_id.find(name);
211 return n->getValue();
218 if(get_current_thread_id() == m_main_thread)
220 return getTextureIdDirect(name);
224 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
226 // We're gonna ask the result to be put into here
227 ResultQueue<std::string, u32, u8, u8> result_queue;
229 // Throw a request in
230 m_get_texture_queue.add(name, 0, 0, &result_queue);
232 infostream<<"Waiting for texture from main thread, name=\""
233 <<name<<"\""<<std::endl;
237 // Wait result for a second
238 GetResult<std::string, u32, u8, u8>
239 result = result_queue.pop_front(1000);
241 // Check that at least something worked OK
242 assert(result.key == name);
246 catch(ItemNotFoundException &e)
248 infostream<<"Waiting for texture timed out."<<std::endl;
253 infostream<<"getTextureId(): Failed"<<std::endl;
258 // Draw a progress bar on the image
259 void make_progressbar(float value, video::IImage *image);
262 Generate image based on a string like "stone.png" or "[crack0".
263 if baseimg is NULL, it is created. Otherwise stuff is made on it.
265 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
266 IrrlichtDevice *device);
269 Generates an image from a full string like
270 "stone.png^mineral_coal.png^[crack0".
272 This is used by buildMainAtlas().
274 video::IImage* generate_image_from_scratch(std::string name,
275 IrrlichtDevice *device);
278 This method generates all the textures
280 u32 TextureSource::getTextureIdDirect(const std::string &name)
282 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
284 // Empty name means texture 0
287 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
292 Calling only allowed from main thread
294 if(get_current_thread_id() != m_main_thread)
296 errorstream<<"TextureSource::getTextureIdDirect() "
297 "called not from main thread"<<std::endl;
302 See if texture already exists
305 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
307 core::map<std::string, u32>::Node *n;
308 n = m_name_to_id.find(name);
311 infostream<<"getTextureIdDirect(): \""<<name
312 <<"\" found in cache"<<std::endl;
313 return n->getValue();
317 infostream<<"getTextureIdDirect(): \""<<name
318 <<"\" NOT found in cache. Creating it."<<std::endl;
324 char separator = '^';
327 This is set to the id of the base image.
328 If left 0, there is no base image and a completely new image
331 u32 base_image_id = 0;
333 // Find last meta separator in name
334 s32 last_separator_position = -1;
335 for(s32 i=name.size()-1; i>=0; i--)
337 if(name[i] == separator)
339 last_separator_position = i;
344 If separator was found, construct the base name and make the
345 base image using a recursive call
347 std::string base_image_name;
348 if(last_separator_position != -1)
350 // Construct base name
351 base_image_name = name.substr(0, last_separator_position);
352 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
353 " to get base image of \""<<name<<"\" = \""
354 <<base_image_name<<"\""<<std::endl;*/
355 base_image_id = getTextureIdDirect(base_image_name);
358 //infostream<<"base_image_id="<<base_image_id<<std::endl;
360 video::IVideoDriver* driver = m_device->getVideoDriver();
363 video::ITexture *t = NULL;
366 An image will be built from files and then converted into a texture.
368 video::IImage *baseimg = NULL;
370 // If a base image was found, copy it to baseimg
371 if(base_image_id != 0)
373 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
375 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
377 video::IImage *image = ap.atlas_img;
381 infostream<<"getTextureIdDirect(): NULL image in "
382 <<"cache: \""<<base_image_name<<"\""
387 core::dimension2d<u32> dim = ap.intsize;
389 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
391 core::position2d<s32> pos_to(0,0);
392 core::position2d<s32> pos_from = ap.intpos;
396 v2s32(0,0), // position in target
397 core::rect<s32>(pos_from, dim) // from
400 /*infostream<<"getTextureIdDirect(): Loaded \""
401 <<base_image_name<<"\" from image cache"
407 Parse out the last part of the name of the image and act
411 std::string last_part_of_name = name.substr(last_separator_position+1);
412 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
414 // Generate image according to part of name
415 if(generate_image(last_part_of_name, baseimg, m_device) == false)
417 infostream<<"getTextureIdDirect(): "
418 "failed to generate \""<<last_part_of_name<<"\""
422 // If no resulting image, print a warning
425 infostream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
426 " create texture \""<<name<<"\""<<std::endl;
431 // Create texture from resulting image
432 t = driver->addTexture(name.c_str(), baseimg);
436 Add texture to caches (add NULL textures too)
439 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
441 u32 id = m_atlaspointer_cache.size();
447 core::dimension2d<u32> baseimg_dim(0,0);
449 baseimg_dim = baseimg->getDimension();
450 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
451 m_atlaspointer_cache.push_back(nap);
452 m_name_to_id.insert(name, id);
454 /*infostream<<"getTextureIdDirect(): "
455 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
460 std::string TextureSource::getTextureName(u32 id)
462 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
464 if(id >= m_atlaspointer_cache.size())
466 infostream<<"TextureSource::getTextureName(): id="<<id
467 <<" >= m_atlaspointer_cache.size()="
468 <<m_atlaspointer_cache.size()<<std::endl;
472 return m_atlaspointer_cache[id].name;
476 AtlasPointer TextureSource::getTexture(u32 id)
478 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
480 if(id >= m_atlaspointer_cache.size())
481 return AtlasPointer(0, NULL);
483 return m_atlaspointer_cache[id].a;
486 void TextureSource::buildMainAtlas()
488 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
490 //return; // Disable (for testing)
492 video::IVideoDriver* driver = m_device->getVideoDriver();
495 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
497 // Create an image of the right size
498 core::dimension2d<u32> atlas_dim(1024,1024);
499 video::IImage *atlas_img =
500 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
502 if(atlas_img == NULL)
504 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
505 "image; not building texture atlas."<<std::endl;
510 A list of stuff to include in the texture atlas.
512 It is a single-dimensional texture atlas due to the need to tile
515 It should contain as much of the stuff shown in game as possible,
516 to minimize texture changes.
518 It fills up quickly, so do not add anything that isn't contained
519 in most MapBlocks. E.g. mese isn't suitable but stone is.
522 core::array<std::string> sourcelist;
524 sourcelist.push_back("stone.png");
525 sourcelist.push_back("mud.png");
526 sourcelist.push_back("sand.png");
527 sourcelist.push_back("grass.png");
528 sourcelist.push_back("grass_footsteps.png");
529 sourcelist.push_back("tree.png");
530 sourcelist.push_back("tree_top.png");
531 sourcelist.push_back("water.png");
532 sourcelist.push_back("leaves.png");
533 sourcelist.push_back("glass.png");
534 sourcelist.push_back("mud.png^grass_side.png");
535 sourcelist.push_back("cobble.png");
536 sourcelist.push_back("mossycobble.png");
537 sourcelist.push_back("gravel.png");
538 sourcelist.push_back("jungletree.png");
540 sourcelist.push_back("stone.png^mineral_coal.png");
541 sourcelist.push_back("stone.png^mineral_iron.png");
543 // Padding to disallow texture bleeding
547 First pass: generate almost everything
549 core::position2d<s32> pos_in_atlas(0,0);
551 pos_in_atlas.Y += padding;
553 for(u32 i=0; i<sourcelist.size(); i++)
555 std::string name = sourcelist[i];
557 /*video::IImage *img = driver->createImageFromFile(
558 getTexturePath(name.c_str()).c_str());
562 core::dimension2d<u32> dim = img->getDimension();
563 // Make a copy with the right color format
564 video::IImage *img2 =
565 driver->createImage(video::ECF_A8R8G8B8, dim);
569 // Generate image by name
570 video::IImage *img2 = generate_image_from_scratch(name, m_device);
573 infostream<<"TextureSource::buildMainAtlas(): Couldn't generate texture atlas: Couldn't generate image \""<<name<<"\""<<std::endl;
577 core::dimension2d<u32> dim = img2->getDimension();
579 // Don't add to atlas if image is large
580 core::dimension2d<u32> max_size_in_atlas(32,32);
581 if(dim.Width > max_size_in_atlas.Width
582 || dim.Height > max_size_in_atlas.Height)
584 infostream<<"TextureSource::buildMainAtlas(): Not adding "
585 <<"\""<<name<<"\" because image is large"<<std::endl;
589 // Stop making atlas if atlas is full
590 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
592 infostream<<"TextureSource::buildMainAtlas(): "
593 <<"Atlas is full, not adding more textures."
598 infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
599 <<"\" to texture atlas"<<std::endl;
601 // Tile it a few times in the X direction
602 u16 xwise_tiling = 16;
603 for(u32 j=0; j<xwise_tiling; j++)
605 // Copy the copy to the atlas
606 img2->copyToWithAlpha(atlas_img,
607 pos_in_atlas + v2s32(j*dim.Width,0),
608 core::rect<s32>(v2s32(0,0), dim),
609 video::SColor(255,255,255,255),
613 // Copy the borders a few times to disallow texture bleeding
614 for(u32 side=0; side<2; side++) // top and bottom
615 for(s32 y0=0; y0<padding; y0++)
616 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
622 dst_y = y0 + pos_in_atlas.Y + dim.Height;
623 src_y = pos_in_atlas.Y + dim.Height - 1;
627 dst_y = -y0 + pos_in_atlas.Y-1;
628 src_y = pos_in_atlas.Y;
630 s32 x = x0 + pos_in_atlas.X * dim.Width;
631 video::SColor c = atlas_img->getPixel(x, src_y);
632 atlas_img->setPixel(x,dst_y,c);
638 Add texture to caches
642 u32 id = m_atlaspointer_cache.size();
644 // Create AtlasPointer
646 ap.atlas = NULL; // Set on the second pass
647 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
648 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
649 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
650 (float)dim.Width/(float)atlas_dim.Height);
651 ap.tiled = xwise_tiling;
653 // Create SourceAtlasPointer and add to containers
654 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
655 m_atlaspointer_cache.push_back(nap);
656 m_name_to_id.insert(name, id);
658 // Increment position
659 pos_in_atlas.Y += dim.Height + padding * 2;
665 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
669 Second pass: set texture pointer in generated AtlasPointers
671 for(u32 i=0; i<sourcelist.size(); i++)
673 std::string name = sourcelist[i];
674 if(m_name_to_id.find(name) == NULL)
676 u32 id = m_name_to_id[name];
677 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
678 m_atlaspointer_cache[id].a.atlas = t;
682 Write image to file so that it can be inspected
684 /*driver->writeImageToFile(atlas_img,
685 getTexturePath("main_atlas.png").c_str());*/
688 video::IImage* generate_image_from_scratch(std::string name,
689 IrrlichtDevice *device)
691 /*infostream<<"generate_image_from_scratch(): "
692 "\""<<name<<"\""<<std::endl;*/
694 video::IVideoDriver* driver = device->getVideoDriver();
701 video::IImage *baseimg = NULL;
703 char separator = '^';
705 // Find last meta separator in name
706 s32 last_separator_position = -1;
707 for(s32 i=name.size()-1; i>=0; i--)
709 if(name[i] == separator)
711 last_separator_position = i;
716 /*infostream<<"generate_image_from_scratch(): "
717 <<"last_separator_position="<<last_separator_position
721 If separator was found, construct the base name and make the
722 base image using a recursive call
724 std::string base_image_name;
725 if(last_separator_position != -1)
727 // Construct base name
728 base_image_name = name.substr(0, last_separator_position);
729 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
730 " to get base image of \""<<name<<"\" = \""
731 <<base_image_name<<"\""<<std::endl;*/
732 baseimg = generate_image_from_scratch(base_image_name, device);
736 Parse out the last part of the name of the image and act
740 std::string last_part_of_name = name.substr(last_separator_position+1);
741 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
743 // Generate image according to part of name
744 if(generate_image(last_part_of_name, baseimg, device) == false)
746 infostream<<"generate_image_from_scratch(): "
747 "failed to generate \""<<last_part_of_name<<"\""
755 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
756 IrrlichtDevice *device)
758 video::IVideoDriver* driver = device->getVideoDriver();
761 // Stuff starting with [ are special commands
762 if(part_of_name[0] != '[')
764 // A normal texture; load it from a file
765 std::string path = getTexturePath(part_of_name.c_str());
766 /*infostream<<"generate_image(): Loading path \""<<path
769 video::IImage *image = driver->createImageFromFile(path.c_str());
773 infostream<<"generate_image(): Could not load image \""
774 <<part_of_name<<"\" from path \""<<path<<"\""
775 <<" while building texture"<<std::endl;
779 infostream<<"generate_image(): Creating a dummy"
780 <<" image for \""<<part_of_name<<"\""<<std::endl;
782 // Just create a dummy image
783 //core::dimension2d<u32> dim(2,2);
784 core::dimension2d<u32> dim(1,1);
785 image = driver->createImage(video::ECF_A8R8G8B8, dim);
787 /*image->setPixel(0,0, video::SColor(255,255,0,0));
788 image->setPixel(1,0, video::SColor(255,0,255,0));
789 image->setPixel(0,1, video::SColor(255,0,0,255));
790 image->setPixel(1,1, video::SColor(255,255,0,255));*/
791 image->setPixel(0,0, video::SColor(255,myrand()%256,
792 myrand()%256,myrand()%256));
793 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
794 myrand()%256,myrand()%256));
795 image->setPixel(0,1, video::SColor(255,myrand()%256,
796 myrand()%256,myrand()%256));
797 image->setPixel(1,1, video::SColor(255,myrand()%256,
798 myrand()%256,myrand()%256));*/
801 // If base image is NULL, load as base.
804 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
806 Copy it this way to get an alpha channel.
807 Otherwise images with alpha cannot be blitted on
808 images that don't have alpha in the original file.
810 core::dimension2d<u32> dim = image->getDimension();
811 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
812 image->copyTo(baseimg);
815 // Else blit on base.
818 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
819 // Size of the copied area
820 core::dimension2d<u32> dim = image->getDimension();
821 //core::dimension2d<u32> dim(16,16);
822 // Position to copy the blitted to in the base image
823 core::position2d<s32> pos_to(0,0);
824 // Position to copy the blitted from in the blitted image
825 core::position2d<s32> pos_from(0,0);
827 image->copyToWithAlpha(baseimg, pos_to,
828 core::rect<s32>(pos_from, dim),
829 video::SColor(255,255,255,255),
837 // A special texture modification
839 infostream<<"generate_image(): generating special "
840 <<"modification \""<<part_of_name<<"\""
844 This is the simplest of all; it just adds stuff to the
845 name so that a separate texture is created.
847 It is used to make textures for stuff that doesn't want
848 to implement getting the texture from a bigger texture
851 if(part_of_name == "[forcesingle")
856 Adds a cracking texture
858 else if(part_of_name.substr(0,6) == "[crack")
862 infostream<<"generate_image(): baseimg==NULL "
863 <<"for part_of_name=\""<<part_of_name
864 <<"\", cancelling."<<std::endl;
868 // Crack image number
869 u16 progression = stoi(part_of_name.substr(6));
871 // Size of the base image
872 core::dimension2d<u32> dim_base = baseimg->getDimension();
877 It is an image with a number of cracking stages
880 video::IImage *img_crack = driver->createImageFromFile(
881 getTexturePath("crack.png").c_str());
885 // Dimension of original image
886 core::dimension2d<u32> dim_crack
887 = img_crack->getDimension();
888 // Count of crack stages
889 u32 crack_count = dim_crack.Height / dim_crack.Width;
891 if(progression > crack_count-1)
892 progression = crack_count-1;
893 // Dimension of a single scaled crack stage
894 core::dimension2d<u32> dim_crack_scaled_single(
898 // Dimension of scaled size
899 core::dimension2d<u32> dim_crack_scaled(
900 dim_crack_scaled_single.Width,
901 dim_crack_scaled_single.Height * crack_count
903 // Create scaled crack image
904 video::IImage *img_crack_scaled = driver->createImage(
905 video::ECF_A8R8G8B8, dim_crack_scaled);
908 // Scale crack image by copying
909 img_crack->copyToScaling(img_crack_scaled);
911 // Position to copy the crack from
912 core::position2d<s32> pos_crack_scaled(
914 dim_crack_scaled_single.Height * progression
917 // This tiling does nothing currently but is useful
918 for(u32 y0=0; y0<dim_base.Height
919 / dim_crack_scaled_single.Height; y0++)
920 for(u32 x0=0; x0<dim_base.Width
921 / dim_crack_scaled_single.Width; x0++)
923 // Position to copy the crack to in the base image
924 core::position2d<s32> pos_base(
925 x0*dim_crack_scaled_single.Width,
926 y0*dim_crack_scaled_single.Height
928 // Rectangle to copy the crack from on the scaled image
929 core::rect<s32> rect_crack_scaled(
931 dim_crack_scaled_single
934 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
936 video::SColor(255,255,255,255),
940 img_crack_scaled->drop();
947 [combine:WxH:X,Y=filename:X,Y=filename2
948 Creates a bigger texture from an amount of smaller ones
950 else if(part_of_name.substr(0,8) == "[combine")
952 Strfnd sf(part_of_name);
954 u32 w0 = stoi(sf.next("x"));
955 u32 h0 = stoi(sf.next(":"));
956 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
957 core::dimension2d<u32> dim(w0,h0);
958 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
959 while(sf.atend() == false)
961 u32 x = stoi(sf.next(","));
962 u32 y = stoi(sf.next("="));
963 std::string filename = sf.next(":");
964 infostream<<"Adding \""<<filename
965 <<"\" to combined ("<<x<<","<<y<<")"
967 video::IImage *img = driver->createImageFromFile(
968 getTexturePath(filename.c_str()).c_str());
971 core::dimension2d<u32> dim = img->getDimension();
972 infostream<<"Size "<<dim.Width
973 <<"x"<<dim.Height<<std::endl;
974 core::position2d<s32> pos_base(x, y);
975 video::IImage *img2 =
976 driver->createImage(video::ECF_A8R8G8B8, dim);
979 img2->copyToWithAlpha(baseimg, pos_base,
980 core::rect<s32>(v2s32(0,0), dim),
981 video::SColor(255,255,255,255),
987 infostream<<"img==NULL"<<std::endl;
993 Adds a progress bar, 0.0 <= N <= 1.0
995 else if(part_of_name.substr(0,12) == "[progressbar")
999 infostream<<"generate_image(): baseimg==NULL "
1000 <<"for part_of_name=\""<<part_of_name
1001 <<"\", cancelling."<<std::endl;
1005 float value = stof(part_of_name.substr(12));
1006 make_progressbar(value, baseimg);
1009 "[noalpha:filename.png"
1010 Use an image without it's alpha channel.
1011 Used for the leaves texture when in old leaves mode, so
1012 that the transparent parts don't look completely black
1013 when simple alpha channel is used for rendering.
1015 else if(part_of_name.substr(0,8) == "[noalpha")
1019 infostream<<"generate_image(): baseimg!=NULL "
1020 <<"for part_of_name=\""<<part_of_name
1021 <<"\", cancelling."<<std::endl;
1025 std::string filename = part_of_name.substr(9);
1027 std::string path = getTexturePath(filename.c_str());
1029 infostream<<"generate_image(): Loading path \""<<path
1032 video::IImage *image = driver->createImageFromFile(path.c_str());
1036 infostream<<"generate_image(): Loading path \""
1037 <<path<<"\" failed"<<std::endl;
1041 core::dimension2d<u32> dim = image->getDimension();
1042 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1044 // Set alpha to full
1045 for(u32 y=0; y<dim.Height; y++)
1046 for(u32 x=0; x<dim.Width; x++)
1048 video::SColor c = image->getPixel(x,y);
1050 image->setPixel(x,y,c);
1053 image->copyTo(baseimg);
1059 "[makealpha:R,G,B:filename.png"
1060 Use an image with converting one color to transparent.
1062 else if(part_of_name.substr(0,11) == "[makealpha:")
1066 infostream<<"generate_image(): baseimg!=NULL "
1067 <<"for part_of_name=\""<<part_of_name
1068 <<"\", cancelling."<<std::endl;
1072 Strfnd sf(part_of_name.substr(11));
1073 u32 r1 = stoi(sf.next(","));
1074 u32 g1 = stoi(sf.next(","));
1075 u32 b1 = stoi(sf.next(":"));
1076 std::string filename = sf.next("");
1078 std::string path = getTexturePath(filename.c_str());
1080 infostream<<"generate_image(): Loading path \""<<path
1083 video::IImage *image = driver->createImageFromFile(path.c_str());
1087 infostream<<"generate_image(): Loading path \""
1088 <<path<<"\" failed"<<std::endl;
1092 core::dimension2d<u32> dim = image->getDimension();
1093 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1096 image->copyTo(baseimg);
1100 for(u32 y=0; y<dim.Height; y++)
1101 for(u32 x=0; x<dim.Width; x++)
1103 video::SColor c = baseimg->getPixel(x,y);
1105 u32 g = c.getGreen();
1106 u32 b = c.getBlue();
1107 if(!(r == r1 && g == g1 && b == b1))
1110 baseimg->setPixel(x,y,c);
1115 "[makealpha2:R,G,B;R2,G2,B2:filename.png"
1116 Use an image with converting two colors to transparent.
1118 else if(part_of_name.substr(0,12) == "[makealpha2:")
1122 infostream<<"generate_image(): baseimg!=NULL "
1123 <<"for part_of_name=\""<<part_of_name
1124 <<"\", cancelling."<<std::endl;
1128 Strfnd sf(part_of_name.substr(12));
1129 u32 r1 = stoi(sf.next(","));
1130 u32 g1 = stoi(sf.next(","));
1131 u32 b1 = stoi(sf.next(";"));
1132 u32 r2 = stoi(sf.next(","));
1133 u32 g2 = stoi(sf.next(","));
1134 u32 b2 = stoi(sf.next(":"));
1135 std::string filename = sf.next("");
1137 std::string path = getTexturePath(filename.c_str());
1139 infostream<<"generate_image(): Loading path \""<<path
1142 video::IImage *image = driver->createImageFromFile(path.c_str());
1146 infostream<<"generate_image(): Loading path \""
1147 <<path<<"\" failed"<<std::endl;
1151 core::dimension2d<u32> dim = image->getDimension();
1152 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1155 image->copyTo(baseimg);
1159 for(u32 y=0; y<dim.Height; y++)
1160 for(u32 x=0; x<dim.Width; x++)
1162 video::SColor c = baseimg->getPixel(x,y);
1164 u32 g = c.getGreen();
1165 u32 b = c.getBlue();
1166 if(!(r == r1 && g == g1 && b == b1) &&
1167 !(r == r2 && g == g2 && b == b2))
1170 baseimg->setPixel(x,y,c);
1175 [inventorycube{topimage{leftimage{rightimage
1176 In every subimage, replace ^ with &.
1177 Create an "inventory cube".
1178 NOTE: This should be used only on its own.
1179 Example (a grass block (not actually used in game):
1180 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1182 else if(part_of_name.substr(0,14) == "[inventorycube")
1186 infostream<<"generate_image(): baseimg!=NULL "
1187 <<"for part_of_name=\""<<part_of_name
1188 <<"\", cancelling."<<std::endl;
1192 str_replace_char(part_of_name, '&', '^');
1193 Strfnd sf(part_of_name);
1195 std::string imagename_top = sf.next("{");
1196 std::string imagename_left = sf.next("{");
1197 std::string imagename_right = sf.next("{");
1202 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
1204 infostream<<"generate_image(): EVDF_RENDER_TO_TARGET"
1205 " not supported. Creating fallback image"<<std::endl;
1206 baseimg = generate_image_from_scratch(
1207 imagename_top, device);
1213 //infostream<<"inventorycube w="<<w0<<" h="<<h0<<std::endl;
1214 core::dimension2d<u32> dim(w0,h0);
1216 // Generate images for the faces of the cube
1217 video::IImage *img_top = generate_image_from_scratch(
1218 imagename_top, device);
1219 video::IImage *img_left = generate_image_from_scratch(
1220 imagename_left, device);
1221 video::IImage *img_right = generate_image_from_scratch(
1222 imagename_right, device);
1223 assert(img_top && img_left && img_right);
1225 // TODO: Create textures from images
1226 video::ITexture *texture_top = driver->addTexture(
1227 (imagename_top + "__temp__").c_str(), img_top);
1228 assert(texture_top);
1235 // Create render target texture
1236 video::ITexture *rtt = NULL;
1237 std::string rtt_name = part_of_name + "_RTT";
1238 rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str(),
1239 video::ECF_A8R8G8B8);
1242 // Set render target
1243 driver->setRenderTarget(rtt, true, true,
1244 video::SColor(0,0,0,0));
1246 // Get a scene manager
1247 scene::ISceneManager *smgr_main = device->getSceneManager();
1249 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
1254 - An unit cube is centered at 0,0,0
1255 - Camera looks at cube from Y+, Z- towards Y-, Z+
1256 NOTE: Cube has to be changed to something else because
1257 the textures cannot be set individually (or can they?)
1260 scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1,
1261 v3f(0,0,0), v3f(0, 45, 0));
1262 // Set texture of cube
1263 cube->setMaterialTexture(0, texture_top);
1264 //cube->setMaterialFlag(video::EMF_LIGHTING, false);
1265 cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false);
1266 cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
1268 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
1269 v3f(0, 1.0, -1.5), v3f(0, 0, 0));
1270 // Set orthogonal projection
1271 core::CMatrix4<f32> pm;
1272 pm.buildProjectionMatrixOrthoLH(1.65, 1.65, 0, 100);
1273 camera->setProjectionMatrix(pm, true);
1275 /*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0,
1276 v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000);
1278 smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2));
1281 driver->beginScene(true, true, video::SColor(0,0,0,0));
1285 // NOTE: The scene nodes should not be dropped, otherwise
1286 // smgr->drop() segfaults
1290 // Drop scene manager
1293 // Unset render target
1294 driver->setRenderTarget(0, true, true, 0);
1296 //TODO: Free textures of images
1297 driver->removeTexture(texture_top);
1299 // Create image of render target
1300 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1304 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1308 image->copyTo(baseimg);
1315 infostream<<"generate_image(): Invalid "
1316 " modification: \""<<part_of_name<<"\""<<std::endl;
1323 void make_progressbar(float value, video::IImage *image)
1328 core::dimension2d<u32> size = image->getDimension();
1330 u32 barheight = size.Height/16;
1331 u32 barpad_x = size.Width/16;
1332 u32 barpad_y = size.Height/16;
1333 u32 barwidth = size.Width - barpad_x*2;
1334 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
1336 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
1338 video::SColor active(255,255,0,0);
1339 video::SColor inactive(255,0,0,0);
1340 for(u32 x0=0; x0<barwidth; x0++)
1347 u32 x = x0 + barpos.X;
1348 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
1350 image->setPixel(x,y, *c);