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 "nodedef.h" // For texture atlas making
34 A cache from texture name to texture path
36 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
39 Replaces the filename extension.
41 std::string image = "a/image.png"
42 replace_ext(image, "jpg")
43 -> image = "a/image.jpg"
44 Returns true on success.
46 static bool replace_ext(std::string &path, const char *ext)
50 // Find place of last dot, fail if \ or / found.
52 for(s32 i=path.size()-1; i>=0; i--)
60 if(path[i] == '\\' || path[i] == '/')
63 // If not found, return an empty string
66 // Else make the new path
67 path = path.substr(0, last_dot_i+1) + ext;
72 Find out the full path of an image by trying different filename
77 static std::string getImagePath(std::string path)
79 // A NULL-ended list of possible image extensions
80 const char *extensions[] = {
81 "png", "jpg", "bmp", "tga",
82 "pcx", "ppm", "psd", "wal", "rgb",
86 const char **ext = extensions;
88 bool r = replace_ext(path, *ext);
91 if(fs::PathExists(path))
94 while((++ext) != NULL);
100 Gets the path to a texture by first checking if the texture exists
101 in texture_path and if not, using the data path.
103 Checks all supported extensions by replacing the original extension.
105 If not found, returns "".
107 Utilizes a thread-safe cache.
109 std::string getTexturePath(const std::string &filename)
111 std::string fullpath = "";
115 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
120 Check from texture_path
122 std::string texture_path = g_settings->get("texture_path");
123 if(texture_path != "")
125 std::string testpath = texture_path + DIR_DELIM + filename;
126 // Check all filename extensions. Returns "" if not found.
127 fullpath = getImagePath(testpath);
131 Check from default data directory
135 std::string rel_path = std::string("textures")+DIR_DELIM+filename;
136 std::string testpath = porting::path_data + DIR_DELIM + rel_path;
137 // Check all filename extensions. Returns "" if not found.
138 fullpath = getImagePath(testpath);
141 // Add to cache (also an empty result is cached)
142 g_texturename_to_path_cache.set(filename, fullpath);
152 class TextureSource : public IWritableTextureSource
155 TextureSource(IrrlichtDevice *device);
160 Now, assume a texture with the id 1 exists, and has the name
161 "stone.png^mineral1".
162 Then a random thread calls getTextureId for a texture called
163 "stone.png^mineral1^crack0".
164 ...Now, WTF should happen? Well:
165 - getTextureId strips off stuff recursively from the end until
166 the remaining part is found, or nothing is left when
167 something is stripped out
169 But it is slow to search for textures by names and modify them
171 - ContentFeatures is made to contain ids for the basic plain
173 - Crack textures can be slow by themselves, but the framework
177 - Assume a texture with the id 1 exists, and has the name
178 "stone.png^mineral1" and is specified as a part of some atlas.
179 - Now MapBlock::getNodeTile() stumbles upon a node which uses
180 texture id 1, and finds out that NODEMOD_CRACK must be applied
182 - It finds out the name of the texture with getTextureName(1),
183 appends "^crack0" to it and gets a new texture id with
184 getTextureId("stone.png^mineral1^crack0")
189 Gets a texture id from cache or
190 - if main thread, from getTextureIdDirect
191 - if other thread, adds to request queue and waits for main thread
193 u32 getTextureId(const std::string &name);
199 "stone.png^blit:mineral_coal.png"
200 "stone.png^blit:mineral_coal.png^crack1"
202 - If texture specified by name is found from cache, return the
204 - Otherwise generate the texture, add to cache and return id.
205 Recursion is used to find out the largest found part of the
206 texture and continue based on it.
208 The id 0 points to a NULL texture. It is returned in case of error.
210 u32 getTextureIdDirect(const std::string &name);
213 Finds out the name of a cached texture.
215 std::string getTextureName(u32 id);
218 If texture specified by the name pointed by the id doesn't
219 exist, create it, then return the cached texture.
221 Can be called from any thread. If called from some other thread
222 and not found in cache, the call is queued to the main thread
225 AtlasPointer getTexture(u32 id);
227 AtlasPointer getTexture(const std::string &name)
229 return getTexture(getTextureId(name));
232 // Gets a separate texture
233 video::ITexture* getTextureRaw(const std::string &name)
235 AtlasPointer ap = getTexture(name);
240 Update new texture pointer and texture coordinates to an
241 AtlasPointer based on it's texture id
243 void updateAP(AtlasPointer &ap);
246 Build the main texture atlas which contains most of the
249 This is called by the constructor.
251 void buildMainAtlas(class IGameDef *gamedef);
254 Processes queued texture requests from other threads.
256 Shall be called from the main thread.
262 // The id of the thread that is allowed to use irrlicht directly
263 threadid_t m_main_thread;
264 // The irrlicht device
265 IrrlichtDevice *m_device;
267 // A texture id is index in this array.
268 // The first position contains a NULL texture.
269 core::array<SourceAtlasPointer> m_atlaspointer_cache;
270 // Maps a texture name to an index in the former.
271 core::map<std::string, u32> m_name_to_id;
272 // The two former containers are behind this mutex
273 JMutex m_atlaspointer_cache_mutex;
275 // Main texture atlas. This is filled at startup and is then not touched.
276 video::IImage *m_main_atlas_image;
277 video::ITexture *m_main_atlas_texture;
279 // Queued texture fetches (to be processed by the main thread)
280 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
283 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
285 return new TextureSource(device);
288 TextureSource::TextureSource(IrrlichtDevice *device):
290 m_main_atlas_image(NULL),
291 m_main_atlas_texture(NULL)
295 m_atlaspointer_cache_mutex.Init();
297 m_main_thread = get_current_thread_id();
299 // Add a NULL AtlasPointer as the first index, named ""
300 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
301 m_name_to_id[""] = 0;
304 TextureSource::~TextureSource()
308 void TextureSource::processQueue()
313 if(m_get_texture_queue.size() > 0)
315 GetRequest<std::string, u32, u8, u8>
316 request = m_get_texture_queue.pop();
318 infostream<<"TextureSource::processQueue(): "
319 <<"got texture request with "
320 <<"name=\""<<request.key<<"\""
323 GetResult<std::string, u32, u8, u8>
325 result.key = request.key;
326 result.callers = request.callers;
327 result.item = getTextureIdDirect(request.key);
329 request.dest->push_back(result);
333 u32 TextureSource::getTextureId(const std::string &name)
335 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
339 See if texture already exists
341 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
342 core::map<std::string, u32>::Node *n;
343 n = m_name_to_id.find(name);
346 return n->getValue();
353 if(get_current_thread_id() == m_main_thread)
355 return getTextureIdDirect(name);
359 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
361 // We're gonna ask the result to be put into here
362 ResultQueue<std::string, u32, u8, u8> result_queue;
364 // Throw a request in
365 m_get_texture_queue.add(name, 0, 0, &result_queue);
367 infostream<<"Waiting for texture from main thread, name=\""
368 <<name<<"\""<<std::endl;
372 // Wait result for a second
373 GetResult<std::string, u32, u8, u8>
374 result = result_queue.pop_front(1000);
376 // Check that at least something worked OK
377 assert(result.key == name);
381 catch(ItemNotFoundException &e)
383 infostream<<"Waiting for texture timed out."<<std::endl;
388 infostream<<"getTextureId(): Failed"<<std::endl;
393 // Draw a progress bar on the image
394 void make_progressbar(float value, video::IImage *image);
397 Generate image based on a string like "stone.png" or "[crack0".
398 if baseimg is NULL, it is created. Otherwise stuff is made on it.
400 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
401 IrrlichtDevice *device);
404 Generates an image from a full string like
405 "stone.png^mineral_coal.png^[crack0".
407 This is used by buildMainAtlas().
409 video::IImage* generate_image_from_scratch(std::string name,
410 IrrlichtDevice *device);
413 This method generates all the textures
415 u32 TextureSource::getTextureIdDirect(const std::string &name)
417 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
419 // Empty name means texture 0
422 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
427 Calling only allowed from main thread
429 if(get_current_thread_id() != m_main_thread)
431 errorstream<<"TextureSource::getTextureIdDirect() "
432 "called not from main thread"<<std::endl;
437 See if texture already exists
440 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
442 core::map<std::string, u32>::Node *n;
443 n = m_name_to_id.find(name);
446 infostream<<"getTextureIdDirect(): \""<<name
447 <<"\" found in cache"<<std::endl;
448 return n->getValue();
452 infostream<<"getTextureIdDirect(): \""<<name
453 <<"\" NOT found in cache. Creating it."<<std::endl;
459 char separator = '^';
462 This is set to the id of the base image.
463 If left 0, there is no base image and a completely new image
466 u32 base_image_id = 0;
468 // Find last meta separator in name
469 s32 last_separator_position = -1;
470 for(s32 i=name.size()-1; i>=0; i--)
472 if(name[i] == separator)
474 last_separator_position = i;
479 If separator was found, construct the base name and make the
480 base image using a recursive call
482 std::string base_image_name;
483 if(last_separator_position != -1)
485 // Construct base name
486 base_image_name = name.substr(0, last_separator_position);
487 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
488 " to get base image of \""<<name<<"\" = \""
489 <<base_image_name<<"\""<<std::endl;*/
490 base_image_id = getTextureIdDirect(base_image_name);
493 //infostream<<"base_image_id="<<base_image_id<<std::endl;
495 video::IVideoDriver* driver = m_device->getVideoDriver();
498 video::ITexture *t = NULL;
501 An image will be built from files and then converted into a texture.
503 video::IImage *baseimg = NULL;
505 // If a base image was found, copy it to baseimg
506 if(base_image_id != 0)
508 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
510 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
512 video::IImage *image = ap.atlas_img;
516 infostream<<"getTextureIdDirect(): NULL image in "
517 <<"cache: \""<<base_image_name<<"\""
522 core::dimension2d<u32> dim = ap.intsize;
524 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
526 core::position2d<s32> pos_to(0,0);
527 core::position2d<s32> pos_from = ap.intpos;
531 v2s32(0,0), // position in target
532 core::rect<s32>(pos_from, dim) // from
535 /*infostream<<"getTextureIdDirect(): Loaded \""
536 <<base_image_name<<"\" from image cache"
542 Parse out the last part of the name of the image and act
546 std::string last_part_of_name = name.substr(last_separator_position+1);
547 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
549 // Generate image according to part of name
550 if(generate_image(last_part_of_name, baseimg, m_device) == false)
552 infostream<<"getTextureIdDirect(): "
553 "failed to generate \""<<last_part_of_name<<"\""
557 // If no resulting image, print a warning
560 infostream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
561 " create texture \""<<name<<"\""<<std::endl;
566 // Create texture from resulting image
567 t = driver->addTexture(name.c_str(), baseimg);
571 Add texture to caches (add NULL textures too)
574 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
576 u32 id = m_atlaspointer_cache.size();
582 core::dimension2d<u32> baseimg_dim(0,0);
584 baseimg_dim = baseimg->getDimension();
585 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
586 m_atlaspointer_cache.push_back(nap);
587 m_name_to_id.insert(name, id);
589 /*infostream<<"getTextureIdDirect(): "
590 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
595 std::string TextureSource::getTextureName(u32 id)
597 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
599 if(id >= m_atlaspointer_cache.size())
601 infostream<<"TextureSource::getTextureName(): id="<<id
602 <<" >= m_atlaspointer_cache.size()="
603 <<m_atlaspointer_cache.size()<<std::endl;
607 return m_atlaspointer_cache[id].name;
611 AtlasPointer TextureSource::getTexture(u32 id)
613 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
615 if(id >= m_atlaspointer_cache.size())
616 return AtlasPointer(0, NULL);
618 return m_atlaspointer_cache[id].a;
621 void TextureSource::updateAP(AtlasPointer &ap)
623 AtlasPointer ap2 = getTexture(ap.id);
627 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
629 assert(gamedef->tsrc() == this);
630 INodeDefManager *ndef = gamedef->ndef();
632 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
634 //return; // Disable (for testing)
636 video::IVideoDriver* driver = m_device->getVideoDriver();
639 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
641 // Create an image of the right size
642 core::dimension2d<u32> atlas_dim(1024,1024);
643 video::IImage *atlas_img =
644 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
646 if(atlas_img == NULL)
648 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
649 "image; not building texture atlas."<<std::endl;
654 Grab list of stuff to include in the texture atlas from the
655 main content features
658 core::map<std::string, bool> sourcelist;
660 for(u16 j=0; j<MAX_CONTENT+1; j++)
662 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
664 const ContentFeatures &f = ndef->get(j);
665 for(std::set<std::string>::const_iterator
666 i = f.used_texturenames.begin();
667 i != f.used_texturenames.end(); i++)
669 std::string name = *i;
670 sourcelist[name] = true;
672 if(f.often_contains_mineral){
673 for(int k=1; k<MINERAL_COUNT; k++){
674 std::string mineraltexture = mineral_block_texture(k);
675 std::string fulltexture = name + "^" + mineraltexture;
676 sourcelist[fulltexture] = true;
682 infostream<<"Creating texture atlas out of textures: ";
683 for(core::map<std::string, bool>::Iterator
684 i = sourcelist.getIterator();
685 i.atEnd() == false; i++)
687 std::string name = i.getNode()->getKey();
688 infostream<<"\""<<name<<"\" ";
690 infostream<<std::endl;
692 // Padding to disallow texture bleeding
695 s32 column_width = 256;
696 s32 column_padding = 16;
699 First pass: generate almost everything
701 core::position2d<s32> pos_in_atlas(0,0);
703 pos_in_atlas.Y = padding;
705 for(core::map<std::string, bool>::Iterator
706 i = sourcelist.getIterator();
707 i.atEnd() == false; i++)
709 std::string name = i.getNode()->getKey();
711 /*video::IImage *img = driver->createImageFromFile(
712 getTexturePath(name.c_str()).c_str());
716 core::dimension2d<u32> dim = img->getDimension();
717 // Make a copy with the right color format
718 video::IImage *img2 =
719 driver->createImage(video::ECF_A8R8G8B8, dim);
723 // Generate image by name
724 video::IImage *img2 = generate_image_from_scratch(name, m_device);
727 infostream<<"TextureSource::buildMainAtlas(): Couldn't generate texture atlas: Couldn't generate image \""<<name<<"\""<<std::endl;
731 core::dimension2d<u32> dim = img2->getDimension();
733 // Don't add to atlas if image is large
734 core::dimension2d<u32> max_size_in_atlas(32,32);
735 if(dim.Width > max_size_in_atlas.Width
736 || dim.Height > max_size_in_atlas.Height)
738 infostream<<"TextureSource::buildMainAtlas(): Not adding "
739 <<"\""<<name<<"\" because image is large"<<std::endl;
743 // Wrap columns and stop making atlas if atlas is full
744 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
746 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
747 errorstream<<"TextureSource::buildMainAtlas(): "
748 <<"Atlas is full, not adding more textures."
752 pos_in_atlas.Y = padding;
753 pos_in_atlas.X += column_width + column_padding;
756 infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
757 <<"\" to texture atlas"<<std::endl;
759 // Tile it a few times in the X direction
760 u16 xwise_tiling = column_width / dim.Width;
761 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
763 for(u32 j=0; j<xwise_tiling; j++)
765 // Copy the copy to the atlas
766 img2->copyToWithAlpha(atlas_img,
767 pos_in_atlas + v2s32(j*dim.Width,0),
768 core::rect<s32>(v2s32(0,0), dim),
769 video::SColor(255,255,255,255),
773 // Copy the borders a few times to disallow texture bleeding
774 for(u32 side=0; side<2; side++) // top and bottom
775 for(s32 y0=0; y0<padding; y0++)
776 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
782 dst_y = y0 + pos_in_atlas.Y + dim.Height;
783 src_y = pos_in_atlas.Y + dim.Height - 1;
787 dst_y = -y0 + pos_in_atlas.Y-1;
788 src_y = pos_in_atlas.Y;
790 s32 x = x0 + pos_in_atlas.X;
791 video::SColor c = atlas_img->getPixel(x, src_y);
792 atlas_img->setPixel(x,dst_y,c);
798 Add texture to caches
801 bool reuse_old_id = false;
802 u32 id = m_atlaspointer_cache.size();
803 // Check old id without fetching a texture
804 core::map<std::string, u32>::Node *n;
805 n = m_name_to_id.find(name);
806 // If it exists, we will replace the old definition
811 infostream<<"TextureSource::buildMainAtlas(): "
812 <<"Replacing old AtlasPointer"<<std::endl;
814 // Create AtlasPointer
816 ap.atlas = NULL; // Set on the second pass
817 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
818 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
819 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
820 (float)dim.Width/(float)atlas_dim.Height);
821 ap.tiled = xwise_tiling;
823 // Create SourceAtlasPointer and add to containers
824 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
826 m_atlaspointer_cache[id] = nap;
828 m_atlaspointer_cache.push_back(nap);
829 m_name_to_id[name] = id;
831 // Increment position
832 pos_in_atlas.Y += dim.Height + padding * 2;
838 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
842 Second pass: set texture pointer in generated AtlasPointers
844 for(core::map<std::string, bool>::Iterator
845 i = sourcelist.getIterator();
846 i.atEnd() == false; i++)
848 std::string name = i.getNode()->getKey();
849 if(m_name_to_id.find(name) == NULL)
851 u32 id = m_name_to_id[name];
852 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
853 m_atlaspointer_cache[id].a.atlas = t;
857 Write image to file so that it can be inspected
859 /*std::string atlaspath = porting::path_userdata
860 + DIR_DELIM + "generated_texture_atlas.png";
861 infostream<<"Removing and writing texture atlas for inspection to "
862 <<atlaspath<<std::endl;
863 fs::RecursiveDelete(atlaspath);
864 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
867 video::IImage* generate_image_from_scratch(std::string name,
868 IrrlichtDevice *device)
870 /*infostream<<"generate_image_from_scratch(): "
871 "\""<<name<<"\""<<std::endl;*/
873 video::IVideoDriver* driver = device->getVideoDriver();
880 video::IImage *baseimg = NULL;
882 char separator = '^';
884 // Find last meta separator in name
885 s32 last_separator_position = -1;
886 for(s32 i=name.size()-1; i>=0; i--)
888 if(name[i] == separator)
890 last_separator_position = i;
895 /*infostream<<"generate_image_from_scratch(): "
896 <<"last_separator_position="<<last_separator_position
900 If separator was found, construct the base name and make the
901 base image using a recursive call
903 std::string base_image_name;
904 if(last_separator_position != -1)
906 // Construct base name
907 base_image_name = name.substr(0, last_separator_position);
908 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
909 " to get base image of \""<<name<<"\" = \""
910 <<base_image_name<<"\""<<std::endl;*/
911 baseimg = generate_image_from_scratch(base_image_name, device);
915 Parse out the last part of the name of the image and act
919 std::string last_part_of_name = name.substr(last_separator_position+1);
920 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
922 // Generate image according to part of name
923 if(generate_image(last_part_of_name, baseimg, device) == false)
925 infostream<<"generate_image_from_scratch(): "
926 "failed to generate \""<<last_part_of_name<<"\""
934 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
935 IrrlichtDevice *device)
937 video::IVideoDriver* driver = device->getVideoDriver();
940 // Stuff starting with [ are special commands
941 if(part_of_name[0] != '[')
943 // A normal texture; load it from a file
944 std::string path = getTexturePath(part_of_name.c_str());
945 /*infostream<<"generate_image(): Loading path \""<<path
948 video::IImage *image = driver->createImageFromFile(path.c_str());
952 infostream<<"generate_image(): Could not load image \""
953 <<part_of_name<<"\" from path \""<<path<<"\""
954 <<" while building texture"<<std::endl;
958 infostream<<"generate_image(): Creating a dummy"
959 <<" image for \""<<part_of_name<<"\""<<std::endl;
961 // Just create a dummy image
962 //core::dimension2d<u32> dim(2,2);
963 core::dimension2d<u32> dim(1,1);
964 image = driver->createImage(video::ECF_A8R8G8B8, dim);
966 /*image->setPixel(0,0, video::SColor(255,255,0,0));
967 image->setPixel(1,0, video::SColor(255,0,255,0));
968 image->setPixel(0,1, video::SColor(255,0,0,255));
969 image->setPixel(1,1, video::SColor(255,255,0,255));*/
970 image->setPixel(0,0, video::SColor(255,myrand()%256,
971 myrand()%256,myrand()%256));
972 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
973 myrand()%256,myrand()%256));
974 image->setPixel(0,1, video::SColor(255,myrand()%256,
975 myrand()%256,myrand()%256));
976 image->setPixel(1,1, video::SColor(255,myrand()%256,
977 myrand()%256,myrand()%256));*/
980 // If base image is NULL, load as base.
983 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
985 Copy it this way to get an alpha channel.
986 Otherwise images with alpha cannot be blitted on
987 images that don't have alpha in the original file.
989 core::dimension2d<u32> dim = image->getDimension();
990 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
991 image->copyTo(baseimg);
994 // Else blit on base.
997 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
998 // Size of the copied area
999 core::dimension2d<u32> dim = image->getDimension();
1000 //core::dimension2d<u32> dim(16,16);
1001 // Position to copy the blitted to in the base image
1002 core::position2d<s32> pos_to(0,0);
1003 // Position to copy the blitted from in the blitted image
1004 core::position2d<s32> pos_from(0,0);
1006 image->copyToWithAlpha(baseimg, pos_to,
1007 core::rect<s32>(pos_from, dim),
1008 video::SColor(255,255,255,255),
1016 // A special texture modification
1018 infostream<<"generate_image(): generating special "
1019 <<"modification \""<<part_of_name<<"\""
1023 This is the simplest of all; it just adds stuff to the
1024 name so that a separate texture is created.
1026 It is used to make textures for stuff that doesn't want
1027 to implement getting the texture from a bigger texture
1030 if(part_of_name == "[forcesingle")
1035 Adds a cracking texture
1037 else if(part_of_name.substr(0,6) == "[crack")
1041 infostream<<"generate_image(): baseimg==NULL "
1042 <<"for part_of_name=\""<<part_of_name
1043 <<"\", cancelling."<<std::endl;
1047 // Crack image number
1048 u16 progression = stoi(part_of_name.substr(6));
1050 // Size of the base image
1051 core::dimension2d<u32> dim_base = baseimg->getDimension();
1056 It is an image with a number of cracking stages
1059 video::IImage *img_crack = driver->createImageFromFile(
1060 getTexturePath("crack.png").c_str());
1064 // Dimension of original image
1065 core::dimension2d<u32> dim_crack
1066 = img_crack->getDimension();
1067 // Count of crack stages
1068 u32 crack_count = dim_crack.Height / dim_crack.Width;
1069 // Limit progression
1070 if(progression > crack_count-1)
1071 progression = crack_count-1;
1072 // Dimension of a single scaled crack stage
1073 core::dimension2d<u32> dim_crack_scaled_single(
1077 // Dimension of scaled size
1078 core::dimension2d<u32> dim_crack_scaled(
1079 dim_crack_scaled_single.Width,
1080 dim_crack_scaled_single.Height * crack_count
1082 // Create scaled crack image
1083 video::IImage *img_crack_scaled = driver->createImage(
1084 video::ECF_A8R8G8B8, dim_crack_scaled);
1085 if(img_crack_scaled)
1087 // Scale crack image by copying
1088 img_crack->copyToScaling(img_crack_scaled);
1090 // Position to copy the crack from
1091 core::position2d<s32> pos_crack_scaled(
1093 dim_crack_scaled_single.Height * progression
1096 // This tiling does nothing currently but is useful
1097 for(u32 y0=0; y0<dim_base.Height
1098 / dim_crack_scaled_single.Height; y0++)
1099 for(u32 x0=0; x0<dim_base.Width
1100 / dim_crack_scaled_single.Width; x0++)
1102 // Position to copy the crack to in the base image
1103 core::position2d<s32> pos_base(
1104 x0*dim_crack_scaled_single.Width,
1105 y0*dim_crack_scaled_single.Height
1107 // Rectangle to copy the crack from on the scaled image
1108 core::rect<s32> rect_crack_scaled(
1110 dim_crack_scaled_single
1113 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
1115 video::SColor(255,255,255,255),
1119 img_crack_scaled->drop();
1126 [combine:WxH:X,Y=filename:X,Y=filename2
1127 Creates a bigger texture from an amount of smaller ones
1129 else if(part_of_name.substr(0,8) == "[combine")
1131 Strfnd sf(part_of_name);
1133 u32 w0 = stoi(sf.next("x"));
1134 u32 h0 = stoi(sf.next(":"));
1135 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1136 core::dimension2d<u32> dim(w0,h0);
1137 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1138 while(sf.atend() == false)
1140 u32 x = stoi(sf.next(","));
1141 u32 y = stoi(sf.next("="));
1142 std::string filename = sf.next(":");
1143 infostream<<"Adding \""<<filename
1144 <<"\" to combined ("<<x<<","<<y<<")"
1146 video::IImage *img = driver->createImageFromFile(
1147 getTexturePath(filename.c_str()).c_str());
1150 core::dimension2d<u32> dim = img->getDimension();
1151 infostream<<"Size "<<dim.Width
1152 <<"x"<<dim.Height<<std::endl;
1153 core::position2d<s32> pos_base(x, y);
1154 video::IImage *img2 =
1155 driver->createImage(video::ECF_A8R8G8B8, dim);
1158 img2->copyToWithAlpha(baseimg, pos_base,
1159 core::rect<s32>(v2s32(0,0), dim),
1160 video::SColor(255,255,255,255),
1166 infostream<<"img==NULL"<<std::endl;
1172 Adds a progress bar, 0.0 <= N <= 1.0
1174 else if(part_of_name.substr(0,12) == "[progressbar")
1178 infostream<<"generate_image(): baseimg==NULL "
1179 <<"for part_of_name=\""<<part_of_name
1180 <<"\", cancelling."<<std::endl;
1184 float value = stof(part_of_name.substr(12));
1185 make_progressbar(value, baseimg);
1188 "[noalpha:filename.png"
1189 Use an image without it's alpha channel.
1190 Used for the leaves texture when in old leaves mode, so
1191 that the transparent parts don't look completely black
1192 when simple alpha channel is used for rendering.
1194 else if(part_of_name.substr(0,8) == "[noalpha")
1198 infostream<<"generate_image(): baseimg!=NULL "
1199 <<"for part_of_name=\""<<part_of_name
1200 <<"\", cancelling."<<std::endl;
1204 std::string filename = part_of_name.substr(9);
1206 std::string path = getTexturePath(filename.c_str());
1208 infostream<<"generate_image(): Loading path \""<<path
1211 video::IImage *image = driver->createImageFromFile(path.c_str());
1215 infostream<<"generate_image(): Loading path \""
1216 <<path<<"\" failed"<<std::endl;
1220 core::dimension2d<u32> dim = image->getDimension();
1221 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1223 // Set alpha to full
1224 for(u32 y=0; y<dim.Height; y++)
1225 for(u32 x=0; x<dim.Width; x++)
1227 video::SColor c = image->getPixel(x,y);
1229 image->setPixel(x,y,c);
1232 image->copyTo(baseimg);
1238 "[makealpha:R,G,B:filename.png"
1239 Use an image with converting one color to transparent.
1241 else if(part_of_name.substr(0,11) == "[makealpha:")
1245 infostream<<"generate_image(): baseimg!=NULL "
1246 <<"for part_of_name=\""<<part_of_name
1247 <<"\", cancelling."<<std::endl;
1251 Strfnd sf(part_of_name.substr(11));
1252 u32 r1 = stoi(sf.next(","));
1253 u32 g1 = stoi(sf.next(","));
1254 u32 b1 = stoi(sf.next(":"));
1255 std::string filename = sf.next("");
1257 std::string path = getTexturePath(filename.c_str());
1259 infostream<<"generate_image(): Loading path \""<<path
1262 video::IImage *image = driver->createImageFromFile(path.c_str());
1266 infostream<<"generate_image(): Loading path \""
1267 <<path<<"\" failed"<<std::endl;
1271 core::dimension2d<u32> dim = image->getDimension();
1272 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1275 image->copyTo(baseimg);
1279 for(u32 y=0; y<dim.Height; y++)
1280 for(u32 x=0; x<dim.Width; x++)
1282 video::SColor c = baseimg->getPixel(x,y);
1284 u32 g = c.getGreen();
1285 u32 b = c.getBlue();
1286 if(!(r == r1 && g == g1 && b == b1))
1289 baseimg->setPixel(x,y,c);
1294 "[makealpha2:R,G,B;R2,G2,B2:filename.png"
1295 Use an image with converting two colors to transparent.
1297 else if(part_of_name.substr(0,12) == "[makealpha2:")
1301 infostream<<"generate_image(): baseimg!=NULL "
1302 <<"for part_of_name=\""<<part_of_name
1303 <<"\", cancelling."<<std::endl;
1307 Strfnd sf(part_of_name.substr(12));
1308 u32 r1 = stoi(sf.next(","));
1309 u32 g1 = stoi(sf.next(","));
1310 u32 b1 = stoi(sf.next(";"));
1311 u32 r2 = stoi(sf.next(","));
1312 u32 g2 = stoi(sf.next(","));
1313 u32 b2 = stoi(sf.next(":"));
1314 std::string filename = sf.next("");
1316 std::string path = getTexturePath(filename.c_str());
1318 infostream<<"generate_image(): Loading path \""<<path
1321 video::IImage *image = driver->createImageFromFile(path.c_str());
1325 infostream<<"generate_image(): Loading path \""
1326 <<path<<"\" failed"<<std::endl;
1330 core::dimension2d<u32> dim = image->getDimension();
1331 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1334 image->copyTo(baseimg);
1338 for(u32 y=0; y<dim.Height; y++)
1339 for(u32 x=0; x<dim.Width; x++)
1341 video::SColor c = baseimg->getPixel(x,y);
1343 u32 g = c.getGreen();
1344 u32 b = c.getBlue();
1345 if(!(r == r1 && g == g1 && b == b1) &&
1346 !(r == r2 && g == g2 && b == b2))
1349 baseimg->setPixel(x,y,c);
1354 [inventorycube{topimage{leftimage{rightimage
1355 In every subimage, replace ^ with &.
1356 Create an "inventory cube".
1357 NOTE: This should be used only on its own.
1358 Example (a grass block (not actually used in game):
1359 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1361 else if(part_of_name.substr(0,14) == "[inventorycube")
1365 infostream<<"generate_image(): baseimg!=NULL "
1366 <<"for part_of_name=\""<<part_of_name
1367 <<"\", cancelling."<<std::endl;
1371 str_replace_char(part_of_name, '&', '^');
1372 Strfnd sf(part_of_name);
1374 std::string imagename_top = sf.next("{");
1375 std::string imagename_left = sf.next("{");
1376 std::string imagename_right = sf.next("{");
1381 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
1383 infostream<<"generate_image(): EVDF_RENDER_TO_TARGET"
1384 " not supported. Creating fallback image"<<std::endl;
1385 baseimg = generate_image_from_scratch(
1386 imagename_top, device);
1392 //infostream<<"inventorycube w="<<w0<<" h="<<h0<<std::endl;
1393 core::dimension2d<u32> dim(w0,h0);
1395 // Generate images for the faces of the cube
1396 video::IImage *img_top = generate_image_from_scratch(
1397 imagename_top, device);
1398 video::IImage *img_left = generate_image_from_scratch(
1399 imagename_left, device);
1400 video::IImage *img_right = generate_image_from_scratch(
1401 imagename_right, device);
1402 assert(img_top && img_left && img_right);
1404 // TODO: Create textures from images
1405 video::ITexture *texture_top = driver->addTexture(
1406 (imagename_top + "__temp__").c_str(), img_top);
1407 assert(texture_top);
1414 // Create render target texture
1415 video::ITexture *rtt = NULL;
1416 std::string rtt_name = part_of_name + "_RTT";
1417 rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str(),
1418 video::ECF_A8R8G8B8);
1421 // Set render target
1422 driver->setRenderTarget(rtt, true, true,
1423 video::SColor(0,0,0,0));
1425 // Get a scene manager
1426 scene::ISceneManager *smgr_main = device->getSceneManager();
1428 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
1433 - An unit cube is centered at 0,0,0
1434 - Camera looks at cube from Y+, Z- towards Y-, Z+
1435 NOTE: Cube has to be changed to something else because
1436 the textures cannot be set individually (or can they?)
1439 scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1,
1440 v3f(0,0,0), v3f(0, 45, 0));
1441 // Set texture of cube
1442 cube->setMaterialTexture(0, texture_top);
1443 //cube->setMaterialFlag(video::EMF_LIGHTING, false);
1444 cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false);
1445 cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
1447 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
1448 v3f(0, 1.0, -1.5), v3f(0, 0, 0));
1449 // Set orthogonal projection
1450 core::CMatrix4<f32> pm;
1451 pm.buildProjectionMatrixOrthoLH(1.65, 1.65, 0, 100);
1452 camera->setProjectionMatrix(pm, true);
1454 /*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0,
1455 v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000);
1457 smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2));
1460 driver->beginScene(true, true, video::SColor(0,0,0,0));
1464 // NOTE: The scene nodes should not be dropped, otherwise
1465 // smgr->drop() segfaults
1469 // Drop scene manager
1472 // Unset render target
1473 driver->setRenderTarget(0, true, true, 0);
1475 //TODO: Free textures of images
1476 driver->removeTexture(texture_top);
1478 // Create image of render target
1479 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1483 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1487 image->copyTo(baseimg);
1494 infostream<<"generate_image(): Invalid "
1495 " modification: \""<<part_of_name<<"\""<<std::endl;
1502 void make_progressbar(float value, video::IImage *image)
1507 core::dimension2d<u32> size = image->getDimension();
1509 u32 barheight = size.Height/16;
1510 u32 barpad_x = size.Width/16;
1511 u32 barpad_y = size.Height/16;
1512 u32 barwidth = size.Width - barpad_x*2;
1513 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
1515 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
1517 video::SColor active(255,255,0,0);
1518 video::SColor inactive(255,0,0,0);
1519 for(u32 x0=0; x0<barwidth; x0++)
1526 u32 x = x0 + barpos.X;
1527 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
1529 image->setPixel(x,y, *c);