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>
28 #include "mapnode.h" // For texture atlas making
29 #include "mineral.h" // For texture atlas making
30 #include "mapnode_contentfeatures.h" // For texture atlas making
33 A cache from texture name to texture path
35 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
38 Replaces the filename extension.
40 std::string image = "a/image.png"
41 replace_ext(image, "jpg")
42 -> image = "a/image.jpg"
43 Returns true on success.
45 static bool replace_ext(std::string &path, const char *ext)
49 // Find place of last dot, fail if \ or / found.
51 for(s32 i=path.size()-1; i>=0; i--)
59 if(path[i] == '\\' || path[i] == '/')
62 // If not found, return an empty string
65 // Else make the new path
66 path = path.substr(0, last_dot_i+1) + ext;
71 Find out the full path of an image by trying different filename
76 static std::string getImagePath(std::string path)
78 // A NULL-ended list of possible image extensions
79 const char *extensions[] = {
80 "png", "jpg", "bmp", "tga",
81 "pcx", "ppm", "psd", "wal", "rgb",
85 const char **ext = extensions;
87 bool r = replace_ext(path, *ext);
90 if(fs::PathExists(path))
93 while((++ext) != NULL);
99 Gets the path to a texture by first checking if the texture exists
100 in texture_path and if not, using the data path.
102 Checks all supported extensions by replacing the original extension.
104 If not found, returns "".
106 Utilizes a thread-safe cache.
108 std::string getTexturePath(const std::string &filename)
110 std::string fullpath = "";
114 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
119 Check from texture_path
121 std::string texture_path = g_settings->get("texture_path");
122 if(texture_path != "")
124 std::string testpath = texture_path + DIR_DELIM + filename;
125 // Check all filename extensions. Returns "" if not found.
126 fullpath = getImagePath(testpath);
130 Check from default data directory
134 std::string rel_path = std::string("textures")+DIR_DELIM+filename;
135 std::string testpath = porting::path_data + DIR_DELIM + rel_path;
136 // Check all filename extensions. Returns "" if not found.
137 fullpath = getImagePath(testpath);
140 // Add to cache (also an empty result is cached)
141 g_texturename_to_path_cache.set(filename, fullpath);
151 TextureSource::TextureSource(IrrlichtDevice *device):
153 m_main_atlas_image(NULL),
154 m_main_atlas_texture(NULL)
158 m_atlaspointer_cache_mutex.Init();
160 m_main_thread = get_current_thread_id();
162 // Add a NULL AtlasPointer as the first index, named ""
163 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
164 m_name_to_id[""] = 0;
166 // Build main texture atlas
167 if(g_settings->getBool("enable_texture_atlas"))
170 infostream<<"Not building texture atlas."<<std::endl;
173 TextureSource::~TextureSource()
177 void TextureSource::processQueue()
182 if(m_get_texture_queue.size() > 0)
184 GetRequest<std::string, u32, u8, u8>
185 request = m_get_texture_queue.pop();
187 infostream<<"TextureSource::processQueue(): "
188 <<"got texture request with "
189 <<"name=\""<<request.key<<"\""
192 GetResult<std::string, u32, u8, u8>
194 result.key = request.key;
195 result.callers = request.callers;
196 result.item = getTextureIdDirect(request.key);
198 request.dest->push_back(result);
202 u32 TextureSource::getTextureId(const std::string &name)
204 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
208 See if texture already exists
210 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
211 core::map<std::string, u32>::Node *n;
212 n = m_name_to_id.find(name);
215 return n->getValue();
222 if(get_current_thread_id() == m_main_thread)
224 return getTextureIdDirect(name);
228 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
230 // We're gonna ask the result to be put into here
231 ResultQueue<std::string, u32, u8, u8> result_queue;
233 // Throw a request in
234 m_get_texture_queue.add(name, 0, 0, &result_queue);
236 infostream<<"Waiting for texture from main thread, name=\""
237 <<name<<"\""<<std::endl;
241 // Wait result for a second
242 GetResult<std::string, u32, u8, u8>
243 result = result_queue.pop_front(1000);
245 // Check that at least something worked OK
246 assert(result.key == name);
250 catch(ItemNotFoundException &e)
252 infostream<<"Waiting for texture timed out."<<std::endl;
257 infostream<<"getTextureId(): Failed"<<std::endl;
262 // Draw a progress bar on the image
263 void make_progressbar(float value, video::IImage *image);
266 Generate image based on a string like "stone.png" or "[crack0".
267 if baseimg is NULL, it is created. Otherwise stuff is made on it.
269 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
270 IrrlichtDevice *device);
273 Generates an image from a full string like
274 "stone.png^mineral_coal.png^[crack0".
276 This is used by buildMainAtlas().
278 video::IImage* generate_image_from_scratch(std::string name,
279 IrrlichtDevice *device);
282 This method generates all the textures
284 u32 TextureSource::getTextureIdDirect(const std::string &name)
286 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
288 // Empty name means texture 0
291 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
296 Calling only allowed from main thread
298 if(get_current_thread_id() != m_main_thread)
300 errorstream<<"TextureSource::getTextureIdDirect() "
301 "called not from main thread"<<std::endl;
306 See if texture already exists
309 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
311 core::map<std::string, u32>::Node *n;
312 n = m_name_to_id.find(name);
315 infostream<<"getTextureIdDirect(): \""<<name
316 <<"\" found in cache"<<std::endl;
317 return n->getValue();
321 infostream<<"getTextureIdDirect(): \""<<name
322 <<"\" NOT found in cache. Creating it."<<std::endl;
328 char separator = '^';
331 This is set to the id of the base image.
332 If left 0, there is no base image and a completely new image
335 u32 base_image_id = 0;
337 // Find last meta separator in name
338 s32 last_separator_position = -1;
339 for(s32 i=name.size()-1; i>=0; i--)
341 if(name[i] == separator)
343 last_separator_position = i;
348 If separator was found, construct the base name and make the
349 base image using a recursive call
351 std::string base_image_name;
352 if(last_separator_position != -1)
354 // Construct base name
355 base_image_name = name.substr(0, last_separator_position);
356 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
357 " to get base image of \""<<name<<"\" = \""
358 <<base_image_name<<"\""<<std::endl;*/
359 base_image_id = getTextureIdDirect(base_image_name);
362 //infostream<<"base_image_id="<<base_image_id<<std::endl;
364 video::IVideoDriver* driver = m_device->getVideoDriver();
367 video::ITexture *t = NULL;
370 An image will be built from files and then converted into a texture.
372 video::IImage *baseimg = NULL;
374 // If a base image was found, copy it to baseimg
375 if(base_image_id != 0)
377 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
379 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
381 video::IImage *image = ap.atlas_img;
385 infostream<<"getTextureIdDirect(): NULL image in "
386 <<"cache: \""<<base_image_name<<"\""
391 core::dimension2d<u32> dim = ap.intsize;
393 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
395 core::position2d<s32> pos_to(0,0);
396 core::position2d<s32> pos_from = ap.intpos;
400 v2s32(0,0), // position in target
401 core::rect<s32>(pos_from, dim) // from
404 /*infostream<<"getTextureIdDirect(): Loaded \""
405 <<base_image_name<<"\" from image cache"
411 Parse out the last part of the name of the image and act
415 std::string last_part_of_name = name.substr(last_separator_position+1);
416 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
418 // Generate image according to part of name
419 if(generate_image(last_part_of_name, baseimg, m_device) == false)
421 infostream<<"getTextureIdDirect(): "
422 "failed to generate \""<<last_part_of_name<<"\""
426 // If no resulting image, print a warning
429 infostream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
430 " create texture \""<<name<<"\""<<std::endl;
435 // Create texture from resulting image
436 t = driver->addTexture(name.c_str(), baseimg);
440 Add texture to caches (add NULL textures too)
443 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
445 u32 id = m_atlaspointer_cache.size();
451 core::dimension2d<u32> baseimg_dim(0,0);
453 baseimg_dim = baseimg->getDimension();
454 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
455 m_atlaspointer_cache.push_back(nap);
456 m_name_to_id.insert(name, id);
458 /*infostream<<"getTextureIdDirect(): "
459 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
464 std::string TextureSource::getTextureName(u32 id)
466 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
468 if(id >= m_atlaspointer_cache.size())
470 infostream<<"TextureSource::getTextureName(): id="<<id
471 <<" >= m_atlaspointer_cache.size()="
472 <<m_atlaspointer_cache.size()<<std::endl;
476 return m_atlaspointer_cache[id].name;
480 AtlasPointer TextureSource::getTexture(u32 id)
482 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
484 if(id >= m_atlaspointer_cache.size())
485 return AtlasPointer(0, NULL);
487 return m_atlaspointer_cache[id].a;
490 void TextureSource::buildMainAtlas()
492 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
494 //return; // Disable (for testing)
496 video::IVideoDriver* driver = m_device->getVideoDriver();
499 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
501 // Create an image of the right size
502 core::dimension2d<u32> atlas_dim(1024,1024);
503 video::IImage *atlas_img =
504 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
506 if(atlas_img == NULL)
508 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
509 "image; not building texture atlas."<<std::endl;
514 Grab list of stuff to include in the texture atlas from the
515 main content features
518 core::map<std::string, bool> sourcelist;
520 for(u16 j=0; j<MAX_CONTENT+1; j++)
522 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
524 ContentFeatures *f = &content_features(j);
525 for(core::map<std::string, bool>::Iterator
526 i = f->used_texturenames.getIterator();
527 i.atEnd() == false; i++)
529 std::string name = i.getNode()->getKey();
530 sourcelist[name] = true;
532 if(f->often_contains_mineral){
533 for(int k=1; k<MINERAL_COUNT; k++){
534 std::string mineraltexture = mineral_block_texture(k);
535 std::string fulltexture = name + "^" + mineraltexture;
536 sourcelist[fulltexture] = true;
542 infostream<<"Creating texture atlas out of textures: ";
543 for(core::map<std::string, bool>::Iterator
544 i = sourcelist.getIterator();
545 i.atEnd() == false; i++)
547 std::string name = i.getNode()->getKey();
548 infostream<<"\""<<name<<"\" ";
550 infostream<<std::endl;
552 // Padding to disallow texture bleeding
555 s32 column_width = 256;
556 s32 column_padding = 16;
559 First pass: generate almost everything
561 core::position2d<s32> pos_in_atlas(0,0);
563 pos_in_atlas.Y = padding;
565 for(core::map<std::string, bool>::Iterator
566 i = sourcelist.getIterator();
567 i.atEnd() == false; i++)
569 std::string name = i.getNode()->getKey();
571 /*video::IImage *img = driver->createImageFromFile(
572 getTexturePath(name.c_str()).c_str());
576 core::dimension2d<u32> dim = img->getDimension();
577 // Make a copy with the right color format
578 video::IImage *img2 =
579 driver->createImage(video::ECF_A8R8G8B8, dim);
583 // Generate image by name
584 video::IImage *img2 = generate_image_from_scratch(name, m_device);
587 infostream<<"TextureSource::buildMainAtlas(): Couldn't generate texture atlas: Couldn't generate image \""<<name<<"\""<<std::endl;
591 core::dimension2d<u32> dim = img2->getDimension();
593 // Don't add to atlas if image is large
594 core::dimension2d<u32> max_size_in_atlas(32,32);
595 if(dim.Width > max_size_in_atlas.Width
596 || dim.Height > max_size_in_atlas.Height)
598 infostream<<"TextureSource::buildMainAtlas(): Not adding "
599 <<"\""<<name<<"\" because image is large"<<std::endl;
603 // Wrap columns and stop making atlas if atlas is full
604 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
606 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
607 errorstream<<"TextureSource::buildMainAtlas(): "
608 <<"Atlas is full, not adding more textures."
612 pos_in_atlas.Y = padding;
613 pos_in_atlas.X += column_width + column_padding;
616 infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
617 <<"\" to texture atlas"<<std::endl;
619 // Tile it a few times in the X direction
620 u16 xwise_tiling = column_width / dim.Width;
621 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
623 for(u32 j=0; j<xwise_tiling; j++)
625 // Copy the copy to the atlas
626 img2->copyToWithAlpha(atlas_img,
627 pos_in_atlas + v2s32(j*dim.Width,0),
628 core::rect<s32>(v2s32(0,0), dim),
629 video::SColor(255,255,255,255),
633 // Copy the borders a few times to disallow texture bleeding
634 for(u32 side=0; side<2; side++) // top and bottom
635 for(s32 y0=0; y0<padding; y0++)
636 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
642 dst_y = y0 + pos_in_atlas.Y + dim.Height;
643 src_y = pos_in_atlas.Y + dim.Height - 1;
647 dst_y = -y0 + pos_in_atlas.Y-1;
648 src_y = pos_in_atlas.Y;
650 s32 x = x0 + pos_in_atlas.X;
651 video::SColor c = atlas_img->getPixel(x, src_y);
652 atlas_img->setPixel(x,dst_y,c);
658 Add texture to caches
662 u32 id = m_atlaspointer_cache.size();
664 // Create AtlasPointer
666 ap.atlas = NULL; // Set on the second pass
667 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
668 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
669 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
670 (float)dim.Width/(float)atlas_dim.Height);
671 ap.tiled = xwise_tiling;
673 // Create SourceAtlasPointer and add to containers
674 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
675 m_atlaspointer_cache.push_back(nap);
676 m_name_to_id.insert(name, id);
678 // Increment position
679 pos_in_atlas.Y += dim.Height + padding * 2;
685 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
689 Second pass: set texture pointer in generated AtlasPointers
691 for(core::map<std::string, bool>::Iterator
692 i = sourcelist.getIterator();
693 i.atEnd() == false; i++)
695 std::string name = i.getNode()->getKey();
696 if(m_name_to_id.find(name) == NULL)
698 u32 id = m_name_to_id[name];
699 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
700 m_atlaspointer_cache[id].a.atlas = t;
704 Write image to file so that it can be inspected
706 /*std::string atlaspath = porting::path_userdata
707 + DIR_DELIM + "generated_texture_atlas.png";
708 infostream<<"Removing and writing texture atlas for inspection to "
709 <<atlaspath<<std::endl;
710 fs::RecursiveDelete(atlaspath);
711 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
714 video::IImage* generate_image_from_scratch(std::string name,
715 IrrlichtDevice *device)
717 /*infostream<<"generate_image_from_scratch(): "
718 "\""<<name<<"\""<<std::endl;*/
720 video::IVideoDriver* driver = device->getVideoDriver();
727 video::IImage *baseimg = NULL;
729 char separator = '^';
731 // Find last meta separator in name
732 s32 last_separator_position = -1;
733 for(s32 i=name.size()-1; i>=0; i--)
735 if(name[i] == separator)
737 last_separator_position = i;
742 /*infostream<<"generate_image_from_scratch(): "
743 <<"last_separator_position="<<last_separator_position
747 If separator was found, construct the base name and make the
748 base image using a recursive call
750 std::string base_image_name;
751 if(last_separator_position != -1)
753 // Construct base name
754 base_image_name = name.substr(0, last_separator_position);
755 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
756 " to get base image of \""<<name<<"\" = \""
757 <<base_image_name<<"\""<<std::endl;*/
758 baseimg = generate_image_from_scratch(base_image_name, device);
762 Parse out the last part of the name of the image and act
766 std::string last_part_of_name = name.substr(last_separator_position+1);
767 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
769 // Generate image according to part of name
770 if(generate_image(last_part_of_name, baseimg, device) == false)
772 infostream<<"generate_image_from_scratch(): "
773 "failed to generate \""<<last_part_of_name<<"\""
781 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
782 IrrlichtDevice *device)
784 video::IVideoDriver* driver = device->getVideoDriver();
787 // Stuff starting with [ are special commands
788 if(part_of_name[0] != '[')
790 // A normal texture; load it from a file
791 std::string path = getTexturePath(part_of_name.c_str());
792 /*infostream<<"generate_image(): Loading path \""<<path
795 video::IImage *image = driver->createImageFromFile(path.c_str());
799 infostream<<"generate_image(): Could not load image \""
800 <<part_of_name<<"\" from path \""<<path<<"\""
801 <<" while building texture"<<std::endl;
805 infostream<<"generate_image(): Creating a dummy"
806 <<" image for \""<<part_of_name<<"\""<<std::endl;
808 // Just create a dummy image
809 //core::dimension2d<u32> dim(2,2);
810 core::dimension2d<u32> dim(1,1);
811 image = driver->createImage(video::ECF_A8R8G8B8, dim);
813 /*image->setPixel(0,0, video::SColor(255,255,0,0));
814 image->setPixel(1,0, video::SColor(255,0,255,0));
815 image->setPixel(0,1, video::SColor(255,0,0,255));
816 image->setPixel(1,1, video::SColor(255,255,0,255));*/
817 image->setPixel(0,0, video::SColor(255,myrand()%256,
818 myrand()%256,myrand()%256));
819 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
820 myrand()%256,myrand()%256));
821 image->setPixel(0,1, video::SColor(255,myrand()%256,
822 myrand()%256,myrand()%256));
823 image->setPixel(1,1, video::SColor(255,myrand()%256,
824 myrand()%256,myrand()%256));*/
827 // If base image is NULL, load as base.
830 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
832 Copy it this way to get an alpha channel.
833 Otherwise images with alpha cannot be blitted on
834 images that don't have alpha in the original file.
836 core::dimension2d<u32> dim = image->getDimension();
837 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
838 image->copyTo(baseimg);
841 // Else blit on base.
844 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
845 // Size of the copied area
846 core::dimension2d<u32> dim = image->getDimension();
847 //core::dimension2d<u32> dim(16,16);
848 // Position to copy the blitted to in the base image
849 core::position2d<s32> pos_to(0,0);
850 // Position to copy the blitted from in the blitted image
851 core::position2d<s32> pos_from(0,0);
853 image->copyToWithAlpha(baseimg, pos_to,
854 core::rect<s32>(pos_from, dim),
855 video::SColor(255,255,255,255),
863 // A special texture modification
865 infostream<<"generate_image(): generating special "
866 <<"modification \""<<part_of_name<<"\""
870 This is the simplest of all; it just adds stuff to the
871 name so that a separate texture is created.
873 It is used to make textures for stuff that doesn't want
874 to implement getting the texture from a bigger texture
877 if(part_of_name == "[forcesingle")
882 Adds a cracking texture
884 else if(part_of_name.substr(0,6) == "[crack")
888 infostream<<"generate_image(): baseimg==NULL "
889 <<"for part_of_name=\""<<part_of_name
890 <<"\", cancelling."<<std::endl;
894 // Crack image number
895 u16 progression = stoi(part_of_name.substr(6));
897 // Size of the base image
898 core::dimension2d<u32> dim_base = baseimg->getDimension();
903 It is an image with a number of cracking stages
906 video::IImage *img_crack = driver->createImageFromFile(
907 getTexturePath("crack.png").c_str());
911 // Dimension of original image
912 core::dimension2d<u32> dim_crack
913 = img_crack->getDimension();
914 // Count of crack stages
915 u32 crack_count = dim_crack.Height / dim_crack.Width;
917 if(progression > crack_count-1)
918 progression = crack_count-1;
919 // Dimension of a single scaled crack stage
920 core::dimension2d<u32> dim_crack_scaled_single(
924 // Dimension of scaled size
925 core::dimension2d<u32> dim_crack_scaled(
926 dim_crack_scaled_single.Width,
927 dim_crack_scaled_single.Height * crack_count
929 // Create scaled crack image
930 video::IImage *img_crack_scaled = driver->createImage(
931 video::ECF_A8R8G8B8, dim_crack_scaled);
934 // Scale crack image by copying
935 img_crack->copyToScaling(img_crack_scaled);
937 // Position to copy the crack from
938 core::position2d<s32> pos_crack_scaled(
940 dim_crack_scaled_single.Height * progression
943 // This tiling does nothing currently but is useful
944 for(u32 y0=0; y0<dim_base.Height
945 / dim_crack_scaled_single.Height; y0++)
946 for(u32 x0=0; x0<dim_base.Width
947 / dim_crack_scaled_single.Width; x0++)
949 // Position to copy the crack to in the base image
950 core::position2d<s32> pos_base(
951 x0*dim_crack_scaled_single.Width,
952 y0*dim_crack_scaled_single.Height
954 // Rectangle to copy the crack from on the scaled image
955 core::rect<s32> rect_crack_scaled(
957 dim_crack_scaled_single
960 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
962 video::SColor(255,255,255,255),
966 img_crack_scaled->drop();
973 [combine:WxH:X,Y=filename:X,Y=filename2
974 Creates a bigger texture from an amount of smaller ones
976 else if(part_of_name.substr(0,8) == "[combine")
978 Strfnd sf(part_of_name);
980 u32 w0 = stoi(sf.next("x"));
981 u32 h0 = stoi(sf.next(":"));
982 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
983 core::dimension2d<u32> dim(w0,h0);
984 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
985 while(sf.atend() == false)
987 u32 x = stoi(sf.next(","));
988 u32 y = stoi(sf.next("="));
989 std::string filename = sf.next(":");
990 infostream<<"Adding \""<<filename
991 <<"\" to combined ("<<x<<","<<y<<")"
993 video::IImage *img = driver->createImageFromFile(
994 getTexturePath(filename.c_str()).c_str());
997 core::dimension2d<u32> dim = img->getDimension();
998 infostream<<"Size "<<dim.Width
999 <<"x"<<dim.Height<<std::endl;
1000 core::position2d<s32> pos_base(x, y);
1001 video::IImage *img2 =
1002 driver->createImage(video::ECF_A8R8G8B8, dim);
1005 img2->copyToWithAlpha(baseimg, pos_base,
1006 core::rect<s32>(v2s32(0,0), dim),
1007 video::SColor(255,255,255,255),
1013 infostream<<"img==NULL"<<std::endl;
1019 Adds a progress bar, 0.0 <= N <= 1.0
1021 else if(part_of_name.substr(0,12) == "[progressbar")
1025 infostream<<"generate_image(): baseimg==NULL "
1026 <<"for part_of_name=\""<<part_of_name
1027 <<"\", cancelling."<<std::endl;
1031 float value = stof(part_of_name.substr(12));
1032 make_progressbar(value, baseimg);
1035 "[noalpha:filename.png"
1036 Use an image without it's alpha channel.
1037 Used for the leaves texture when in old leaves mode, so
1038 that the transparent parts don't look completely black
1039 when simple alpha channel is used for rendering.
1041 else if(part_of_name.substr(0,8) == "[noalpha")
1045 infostream<<"generate_image(): baseimg!=NULL "
1046 <<"for part_of_name=\""<<part_of_name
1047 <<"\", cancelling."<<std::endl;
1051 std::string filename = part_of_name.substr(9);
1053 std::string path = getTexturePath(filename.c_str());
1055 infostream<<"generate_image(): Loading path \""<<path
1058 video::IImage *image = driver->createImageFromFile(path.c_str());
1062 infostream<<"generate_image(): Loading path \""
1063 <<path<<"\" failed"<<std::endl;
1067 core::dimension2d<u32> dim = image->getDimension();
1068 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1070 // Set alpha to full
1071 for(u32 y=0; y<dim.Height; y++)
1072 for(u32 x=0; x<dim.Width; x++)
1074 video::SColor c = image->getPixel(x,y);
1076 image->setPixel(x,y,c);
1079 image->copyTo(baseimg);
1085 "[makealpha:R,G,B:filename.png"
1086 Use an image with converting one color to transparent.
1088 else if(part_of_name.substr(0,11) == "[makealpha:")
1092 infostream<<"generate_image(): baseimg!=NULL "
1093 <<"for part_of_name=\""<<part_of_name
1094 <<"\", cancelling."<<std::endl;
1098 Strfnd sf(part_of_name.substr(11));
1099 u32 r1 = stoi(sf.next(","));
1100 u32 g1 = stoi(sf.next(","));
1101 u32 b1 = stoi(sf.next(":"));
1102 std::string filename = sf.next("");
1104 std::string path = getTexturePath(filename.c_str());
1106 infostream<<"generate_image(): Loading path \""<<path
1109 video::IImage *image = driver->createImageFromFile(path.c_str());
1113 infostream<<"generate_image(): Loading path \""
1114 <<path<<"\" failed"<<std::endl;
1118 core::dimension2d<u32> dim = image->getDimension();
1119 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1122 image->copyTo(baseimg);
1126 for(u32 y=0; y<dim.Height; y++)
1127 for(u32 x=0; x<dim.Width; x++)
1129 video::SColor c = baseimg->getPixel(x,y);
1131 u32 g = c.getGreen();
1132 u32 b = c.getBlue();
1133 if(!(r == r1 && g == g1 && b == b1))
1136 baseimg->setPixel(x,y,c);
1141 "[makealpha2:R,G,B;R2,G2,B2:filename.png"
1142 Use an image with converting two colors to transparent.
1144 else if(part_of_name.substr(0,12) == "[makealpha2:")
1148 infostream<<"generate_image(): baseimg!=NULL "
1149 <<"for part_of_name=\""<<part_of_name
1150 <<"\", cancelling."<<std::endl;
1154 Strfnd sf(part_of_name.substr(12));
1155 u32 r1 = stoi(sf.next(","));
1156 u32 g1 = stoi(sf.next(","));
1157 u32 b1 = stoi(sf.next(";"));
1158 u32 r2 = stoi(sf.next(","));
1159 u32 g2 = stoi(sf.next(","));
1160 u32 b2 = stoi(sf.next(":"));
1161 std::string filename = sf.next("");
1163 std::string path = getTexturePath(filename.c_str());
1165 infostream<<"generate_image(): Loading path \""<<path
1168 video::IImage *image = driver->createImageFromFile(path.c_str());
1172 infostream<<"generate_image(): Loading path \""
1173 <<path<<"\" failed"<<std::endl;
1177 core::dimension2d<u32> dim = image->getDimension();
1178 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1181 image->copyTo(baseimg);
1185 for(u32 y=0; y<dim.Height; y++)
1186 for(u32 x=0; x<dim.Width; x++)
1188 video::SColor c = baseimg->getPixel(x,y);
1190 u32 g = c.getGreen();
1191 u32 b = c.getBlue();
1192 if(!(r == r1 && g == g1 && b == b1) &&
1193 !(r == r2 && g == g2 && b == b2))
1196 baseimg->setPixel(x,y,c);
1201 [inventorycube{topimage{leftimage{rightimage
1202 In every subimage, replace ^ with &.
1203 Create an "inventory cube".
1204 NOTE: This should be used only on its own.
1205 Example (a grass block (not actually used in game):
1206 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1208 else if(part_of_name.substr(0,14) == "[inventorycube")
1212 infostream<<"generate_image(): baseimg!=NULL "
1213 <<"for part_of_name=\""<<part_of_name
1214 <<"\", cancelling."<<std::endl;
1218 str_replace_char(part_of_name, '&', '^');
1219 Strfnd sf(part_of_name);
1221 std::string imagename_top = sf.next("{");
1222 std::string imagename_left = sf.next("{");
1223 std::string imagename_right = sf.next("{");
1228 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
1230 infostream<<"generate_image(): EVDF_RENDER_TO_TARGET"
1231 " not supported. Creating fallback image"<<std::endl;
1232 baseimg = generate_image_from_scratch(
1233 imagename_top, device);
1239 //infostream<<"inventorycube w="<<w0<<" h="<<h0<<std::endl;
1240 core::dimension2d<u32> dim(w0,h0);
1242 // Generate images for the faces of the cube
1243 video::IImage *img_top = generate_image_from_scratch(
1244 imagename_top, device);
1245 video::IImage *img_left = generate_image_from_scratch(
1246 imagename_left, device);
1247 video::IImage *img_right = generate_image_from_scratch(
1248 imagename_right, device);
1249 assert(img_top && img_left && img_right);
1251 // TODO: Create textures from images
1252 video::ITexture *texture_top = driver->addTexture(
1253 (imagename_top + "__temp__").c_str(), img_top);
1254 assert(texture_top);
1261 // Create render target texture
1262 video::ITexture *rtt = NULL;
1263 std::string rtt_name = part_of_name + "_RTT";
1264 rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str(),
1265 video::ECF_A8R8G8B8);
1268 // Set render target
1269 driver->setRenderTarget(rtt, true, true,
1270 video::SColor(0,0,0,0));
1272 // Get a scene manager
1273 scene::ISceneManager *smgr_main = device->getSceneManager();
1275 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
1280 - An unit cube is centered at 0,0,0
1281 - Camera looks at cube from Y+, Z- towards Y-, Z+
1282 NOTE: Cube has to be changed to something else because
1283 the textures cannot be set individually (or can they?)
1286 scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1,
1287 v3f(0,0,0), v3f(0, 45, 0));
1288 // Set texture of cube
1289 cube->setMaterialTexture(0, texture_top);
1290 //cube->setMaterialFlag(video::EMF_LIGHTING, false);
1291 cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false);
1292 cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
1294 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
1295 v3f(0, 1.0, -1.5), v3f(0, 0, 0));
1296 // Set orthogonal projection
1297 core::CMatrix4<f32> pm;
1298 pm.buildProjectionMatrixOrthoLH(1.65, 1.65, 0, 100);
1299 camera->setProjectionMatrix(pm, true);
1301 /*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0,
1302 v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000);
1304 smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2));
1307 driver->beginScene(true, true, video::SColor(0,0,0,0));
1311 // NOTE: The scene nodes should not be dropped, otherwise
1312 // smgr->drop() segfaults
1316 // Drop scene manager
1319 // Unset render target
1320 driver->setRenderTarget(0, true, true, 0);
1322 //TODO: Free textures of images
1323 driver->removeTexture(texture_top);
1325 // Create image of render target
1326 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1330 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1334 image->copyTo(baseimg);
1341 infostream<<"generate_image(): Invalid "
1342 " modification: \""<<part_of_name<<"\""<<std::endl;
1349 void make_progressbar(float value, video::IImage *image)
1354 core::dimension2d<u32> size = image->getDimension();
1356 u32 barheight = size.Height/16;
1357 u32 barpad_x = size.Width/16;
1358 u32 barpad_y = size.Height/16;
1359 u32 barwidth = size.Width - barpad_x*2;
1360 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
1362 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
1364 video::SColor active(255,255,0,0);
1365 video::SColor inactive(255,0,0,0);
1366 for(u32 x0=0; x0<barwidth; x0++)
1373 u32 x = x0 + barpos.X;
1374 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
1376 image->setPixel(x,y, *c);