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
27 #include <ICameraSceneNode.h>
29 #include "mapnode.h" // For texture atlas making
30 #include "mineral.h" // For texture atlas making
31 #include "nodedef.h" // For texture atlas making
35 A cache from texture name to texture path
37 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
40 Replaces the filename extension.
42 std::string image = "a/image.png"
43 replace_ext(image, "jpg")
44 -> image = "a/image.jpg"
45 Returns true on success.
47 static bool replace_ext(std::string &path, const char *ext)
51 // Find place of last dot, fail if \ or / found.
53 for(s32 i=path.size()-1; i>=0; i--)
61 if(path[i] == '\\' || path[i] == '/')
64 // If not found, return an empty string
67 // Else make the new path
68 path = path.substr(0, last_dot_i+1) + ext;
73 Find out the full path of an image by trying different filename
78 static std::string getImagePath(std::string path)
80 // A NULL-ended list of possible image extensions
81 const char *extensions[] = {
82 "png", "jpg", "bmp", "tga",
83 "pcx", "ppm", "psd", "wal", "rgb",
87 const char **ext = extensions;
89 bool r = replace_ext(path, *ext);
92 if(fs::PathExists(path))
95 while((++ext) != NULL);
101 Gets the path to a texture by first checking if the texture exists
102 in texture_path and if not, using the data path.
104 Checks all supported extensions by replacing the original extension.
106 If not found, returns "".
108 Utilizes a thread-safe cache.
110 std::string getTexturePath(const std::string &filename)
112 std::string fullpath = "";
116 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
121 Check from texture_path
123 std::string texture_path = g_settings->get("texture_path");
124 if(texture_path != "")
126 std::string testpath = texture_path + DIR_DELIM + filename;
127 // Check all filename extensions. Returns "" if not found.
128 fullpath = getImagePath(testpath);
132 Check from default data directory
136 std::string rel_path = std::string("clienttextures")+DIR_DELIM+filename;
137 std::string testpath = porting::path_data + DIR_DELIM + rel_path;
138 // Check all filename extensions. Returns "" if not found.
139 fullpath = getImagePath(testpath);
142 // Add to cache (also an empty result is cached)
143 g_texturename_to_path_cache.set(filename, fullpath);
150 An internal variant of AtlasPointer with more data.
151 (well, more like a wrapper)
154 struct SourceAtlasPointer
158 video::IImage *atlas_img; // The source image of the atlas
159 // Integer variants of position and size
164 const std::string &name_,
165 AtlasPointer a_=AtlasPointer(0, NULL),
166 video::IImage *atlas_img_=NULL,
167 v2s32 intpos_=v2s32(0,0),
168 v2u32 intsize_=v2u32(0,0)
172 atlas_img(atlas_img_),
180 SourceImageCache: A cache used for storing source images.
183 class SourceImageCache
186 void insert(const std::string &name, video::IImage *img,
187 bool prefer_local, video::IVideoDriver *driver)
191 core::map<std::string, video::IImage*>::Node *n;
192 n = m_images.find(name);
194 video::IImage *oldimg = n->getValue();
198 // Try to use local texture instead if asked to
200 std::string path = getTexturePath(name.c_str());
202 video::IImage *img2 = driver->createImageFromFile(path.c_str());
204 m_images[name] = img2;
210 m_images[name] = img;
212 video::IImage* get(const std::string &name)
214 core::map<std::string, video::IImage*>::Node *n;
215 n = m_images.find(name);
217 return n->getValue();
220 // Primarily fetches from cache, secondarily tries to read from filesystem
221 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
223 core::map<std::string, video::IImage*>::Node *n;
224 n = m_images.find(name);
226 n->getValue()->grab(); // Grab for caller
227 return n->getValue();
229 video::IVideoDriver* driver = device->getVideoDriver();
230 std::string path = getTexturePath(name.c_str());
232 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
233 <<name<<"\""<<std::endl;
236 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
238 video::IImage *img = driver->createImageFromFile(path.c_str());
239 // Even if could not be loaded, put as NULL
240 //m_images[name] = img;
242 m_images[name] = img;
243 img->grab(); // Grab for caller
248 core::map<std::string, video::IImage*> m_images;
255 class TextureSource : public IWritableTextureSource
258 TextureSource(IrrlichtDevice *device);
263 Now, assume a texture with the id 1 exists, and has the name
264 "stone.png^mineral1".
265 Then a random thread calls getTextureId for a texture called
266 "stone.png^mineral1^crack0".
267 ...Now, WTF should happen? Well:
268 - getTextureId strips off stuff recursively from the end until
269 the remaining part is found, or nothing is left when
270 something is stripped out
272 But it is slow to search for textures by names and modify them
274 - ContentFeatures is made to contain ids for the basic plain
276 - Crack textures can be slow by themselves, but the framework
280 - Assume a texture with the id 1 exists, and has the name
281 "stone.png^mineral1" and is specified as a part of some atlas.
282 - Now MapBlock::getNodeTile() stumbles upon a node which uses
283 texture id 1, and finds out that NODEMOD_CRACK must be applied
285 - It finds out the name of the texture with getTextureName(1),
286 appends "^crack0" to it and gets a new texture id with
287 getTextureId("stone.png^mineral1^crack0")
292 Gets a texture id from cache or
293 - if main thread, from getTextureIdDirect
294 - if other thread, adds to request queue and waits for main thread
296 u32 getTextureId(const std::string &name);
302 "stone.png^blit:mineral_coal.png"
303 "stone.png^blit:mineral_coal.png^crack1"
305 - If texture specified by name is found from cache, return the
307 - Otherwise generate the texture, add to cache and return id.
308 Recursion is used to find out the largest found part of the
309 texture and continue based on it.
311 The id 0 points to a NULL texture. It is returned in case of error.
313 u32 getTextureIdDirect(const std::string &name);
315 // Finds out the name of a cached texture.
316 std::string getTextureName(u32 id);
319 If texture specified by the name pointed by the id doesn't
320 exist, create it, then return the cached texture.
322 Can be called from any thread. If called from some other thread
323 and not found in cache, the call is queued to the main thread
326 AtlasPointer getTexture(u32 id);
328 AtlasPointer getTexture(const std::string &name)
330 return getTexture(getTextureId(name));
333 // Gets a separate texture
334 video::ITexture* getTextureRaw(const std::string &name)
336 AtlasPointer ap = getTexture(name + "^[forcesingle");
340 // Returns a pointer to the irrlicht device
341 virtual IrrlichtDevice* getDevice()
346 // Update new texture pointer and texture coordinates to an
347 // AtlasPointer based on it's texture id
348 void updateAP(AtlasPointer &ap);
350 // Processes queued texture requests from other threads.
351 // Shall be called from the main thread.
354 // Insert an image into the cache without touching the filesystem.
355 // Shall be called from the main thread.
356 void insertSourceImage(const std::string &name, video::IImage *img);
358 // Rebuild images and textures from the current set of source images
359 // Shall be called from the main thread.
360 void rebuildImagesAndTextures();
362 // Build the main texture atlas which contains most of the
364 void buildMainAtlas(class IGameDef *gamedef);
368 // The id of the thread that is allowed to use irrlicht directly
369 threadid_t m_main_thread;
370 // The irrlicht device
371 IrrlichtDevice *m_device;
373 // Cache of source images
374 // This should be only accessed from the main thread
375 SourceImageCache m_sourcecache;
377 // A texture id is index in this array.
378 // The first position contains a NULL texture.
379 core::array<SourceAtlasPointer> m_atlaspointer_cache;
380 // Maps a texture name to an index in the former.
381 core::map<std::string, u32> m_name_to_id;
382 // The two former containers are behind this mutex
383 JMutex m_atlaspointer_cache_mutex;
385 // Main texture atlas. This is filled at startup and is then not touched.
386 video::IImage *m_main_atlas_image;
387 video::ITexture *m_main_atlas_texture;
389 // Queued texture fetches (to be processed by the main thread)
390 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
393 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
395 return new TextureSource(device);
398 TextureSource::TextureSource(IrrlichtDevice *device):
400 m_main_atlas_image(NULL),
401 m_main_atlas_texture(NULL)
405 m_atlaspointer_cache_mutex.Init();
407 m_main_thread = get_current_thread_id();
409 // Add a NULL AtlasPointer as the first index, named ""
410 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
411 m_name_to_id[""] = 0;
414 TextureSource::~TextureSource()
418 u32 TextureSource::getTextureId(const std::string &name)
420 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
424 See if texture already exists
426 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
427 core::map<std::string, u32>::Node *n;
428 n = m_name_to_id.find(name);
431 return n->getValue();
438 if(get_current_thread_id() == m_main_thread)
440 return getTextureIdDirect(name);
444 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
446 // We're gonna ask the result to be put into here
447 ResultQueue<std::string, u32, u8, u8> result_queue;
449 // Throw a request in
450 m_get_texture_queue.add(name, 0, 0, &result_queue);
452 infostream<<"Waiting for texture from main thread, name=\""
453 <<name<<"\""<<std::endl;
457 // Wait result for a second
458 GetResult<std::string, u32, u8, u8>
459 result = result_queue.pop_front(1000);
461 // Check that at least something worked OK
462 assert(result.key == name);
466 catch(ItemNotFoundException &e)
468 infostream<<"Waiting for texture timed out."<<std::endl;
473 infostream<<"getTextureId(): Failed"<<std::endl;
479 void brighten(video::IImage *image);
482 Generate image based on a string like "stone.png" or "[crack0".
483 if baseimg is NULL, it is created. Otherwise stuff is made on it.
485 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
486 IrrlichtDevice *device, SourceImageCache *sourcecache);
489 Generates an image from a full string like
490 "stone.png^mineral_coal.png^[crack0".
492 This is used by buildMainAtlas().
494 video::IImage* generate_image_from_scratch(std::string name,
495 IrrlichtDevice *device, SourceImageCache *sourcecache);
498 This method generates all the textures
500 u32 TextureSource::getTextureIdDirect(const std::string &name)
502 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
504 // Empty name means texture 0
507 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
512 Calling only allowed from main thread
514 if(get_current_thread_id() != m_main_thread)
516 errorstream<<"TextureSource::getTextureIdDirect() "
517 "called not from main thread"<<std::endl;
522 See if texture already exists
525 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
527 core::map<std::string, u32>::Node *n;
528 n = m_name_to_id.find(name);
531 /*infostream<<"getTextureIdDirect(): \""<<name
532 <<"\" found in cache"<<std::endl;*/
533 return n->getValue();
537 /*infostream<<"getTextureIdDirect(): \""<<name
538 <<"\" NOT found in cache. Creating it."<<std::endl;*/
544 char separator = '^';
547 This is set to the id of the base image.
548 If left 0, there is no base image and a completely new image
551 u32 base_image_id = 0;
553 // Find last meta separator in name
554 s32 last_separator_position = -1;
555 for(s32 i=name.size()-1; i>=0; i--)
557 if(name[i] == separator)
559 last_separator_position = i;
564 If separator was found, construct the base name and make the
565 base image using a recursive call
567 std::string base_image_name;
568 if(last_separator_position != -1)
570 // Construct base name
571 base_image_name = name.substr(0, last_separator_position);
572 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
573 " to get base image of \""<<name<<"\" = \""
574 <<base_image_name<<"\""<<std::endl;*/
575 base_image_id = getTextureIdDirect(base_image_name);
578 //infostream<<"base_image_id="<<base_image_id<<std::endl;
580 video::IVideoDriver* driver = m_device->getVideoDriver();
583 video::ITexture *t = NULL;
586 An image will be built from files and then converted into a texture.
588 video::IImage *baseimg = NULL;
590 // If a base image was found, copy it to baseimg
591 if(base_image_id != 0)
593 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
595 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
597 video::IImage *image = ap.atlas_img;
601 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
602 <<"cache: \""<<base_image_name<<"\""
607 core::dimension2d<u32> dim = ap.intsize;
609 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
611 core::position2d<s32> pos_to(0,0);
612 core::position2d<s32> pos_from = ap.intpos;
616 v2s32(0,0), // position in target
617 core::rect<s32>(pos_from, dim) // from
620 /*infostream<<"getTextureIdDirect(): Loaded \""
621 <<base_image_name<<"\" from image cache"
627 Parse out the last part of the name of the image and act
631 std::string last_part_of_name = name.substr(last_separator_position+1);
632 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
634 // Generate image according to part of name
635 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
637 errorstream<<"getTextureIdDirect(): "
638 "failed to generate \""<<last_part_of_name<<"\""
642 // If no resulting image, print a warning
645 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
646 " create texture \""<<name<<"\""<<std::endl;
651 // Create texture from resulting image
652 t = driver->addTexture(name.c_str(), baseimg);
656 Add texture to caches (add NULL textures too)
659 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
661 u32 id = m_atlaspointer_cache.size();
667 core::dimension2d<u32> baseimg_dim(0,0);
669 baseimg_dim = baseimg->getDimension();
670 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
671 m_atlaspointer_cache.push_back(nap);
672 m_name_to_id.insert(name, id);
674 /*infostream<<"getTextureIdDirect(): "
675 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
680 std::string TextureSource::getTextureName(u32 id)
682 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
684 if(id >= m_atlaspointer_cache.size())
686 errorstream<<"TextureSource::getTextureName(): id="<<id
687 <<" >= m_atlaspointer_cache.size()="
688 <<m_atlaspointer_cache.size()<<std::endl;
692 return m_atlaspointer_cache[id].name;
696 AtlasPointer TextureSource::getTexture(u32 id)
698 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
700 if(id >= m_atlaspointer_cache.size())
701 return AtlasPointer(0, NULL);
703 return m_atlaspointer_cache[id].a;
706 void TextureSource::updateAP(AtlasPointer &ap)
708 AtlasPointer ap2 = getTexture(ap.id);
712 void TextureSource::processQueue()
717 if(m_get_texture_queue.size() > 0)
719 GetRequest<std::string, u32, u8, u8>
720 request = m_get_texture_queue.pop();
722 /*infostream<<"TextureSource::processQueue(): "
723 <<"got texture request with "
724 <<"name=\""<<request.key<<"\""
727 GetResult<std::string, u32, u8, u8>
729 result.key = request.key;
730 result.callers = request.callers;
731 result.item = getTextureIdDirect(request.key);
733 request.dest->push_back(result);
737 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
739 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
741 assert(get_current_thread_id() == m_main_thread);
743 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
746 void TextureSource::rebuildImagesAndTextures()
748 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
750 /*// Oh well... just clear everything, they'll load sometime.
751 m_atlaspointer_cache.clear();
752 m_name_to_id.clear();*/
754 video::IVideoDriver* driver = m_device->getVideoDriver();
756 // Remove source images from textures to disable inheriting textures
757 // from existing textures
758 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
759 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
760 sap->atlas_img->drop();
761 sap->atlas_img = NULL;
765 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
766 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
768 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
769 // Create texture from resulting image
770 video::ITexture *t = NULL;
772 t = driver->addTexture(sap->name.c_str(), img);
776 sap->a.pos = v2f(0,0);
777 sap->a.size = v2f(1,1);
779 sap->atlas_img = img;
780 sap->intpos = v2s32(0,0);
781 sap->intsize = img->getDimension();
785 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
787 assert(gamedef->tsrc() == this);
788 INodeDefManager *ndef = gamedef->ndef();
790 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
792 //return; // Disable (for testing)
794 video::IVideoDriver* driver = m_device->getVideoDriver();
797 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
799 // Create an image of the right size
800 core::dimension2d<u32> atlas_dim(1024,1024);
801 video::IImage *atlas_img =
802 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
804 if(atlas_img == NULL)
806 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
807 "image; not building texture atlas."<<std::endl;
812 Grab list of stuff to include in the texture atlas from the
813 main content features
816 core::map<std::string, bool> sourcelist;
818 for(u16 j=0; j<MAX_CONTENT+1; j++)
820 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
822 const ContentFeatures &f = ndef->get(j);
823 for(u32 i=0; i<6; i++)
825 std::string name = f.tname_tiles[i];
826 sourcelist[name] = true;
828 if(f.param_type == CPT_MINERAL){
829 for(int k=1; k<MINERAL_COUNT; k++){
830 std::string mineraltexture = mineral_block_texture(k);
831 std::string fulltexture = name + "^" + mineraltexture;
832 sourcelist[fulltexture] = true;
838 infostream<<"Creating texture atlas out of textures: ";
839 for(core::map<std::string, bool>::Iterator
840 i = sourcelist.getIterator();
841 i.atEnd() == false; i++)
843 std::string name = i.getNode()->getKey();
844 infostream<<"\""<<name<<"\" ";
846 infostream<<std::endl;
848 // Padding to disallow texture bleeding
851 s32 column_width = 256;
852 s32 column_padding = 16;
855 First pass: generate almost everything
857 core::position2d<s32> pos_in_atlas(0,0);
859 pos_in_atlas.Y = padding;
861 for(core::map<std::string, bool>::Iterator
862 i = sourcelist.getIterator();
863 i.atEnd() == false; i++)
865 std::string name = i.getNode()->getKey();
867 // Generate image by name
868 video::IImage *img2 = generate_image_from_scratch(name, m_device,
872 errorstream<<"TextureSource::buildMainAtlas(): "
873 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
877 core::dimension2d<u32> dim = img2->getDimension();
879 // Don't add to atlas if image is large
880 core::dimension2d<u32> max_size_in_atlas(32,32);
881 if(dim.Width > max_size_in_atlas.Width
882 || dim.Height > max_size_in_atlas.Height)
884 infostream<<"TextureSource::buildMainAtlas(): Not adding "
885 <<"\""<<name<<"\" because image is large"<<std::endl;
889 // Wrap columns and stop making atlas if atlas is full
890 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
892 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
893 errorstream<<"TextureSource::buildMainAtlas(): "
894 <<"Atlas is full, not adding more textures."
898 pos_in_atlas.Y = padding;
899 pos_in_atlas.X += column_width + column_padding;
902 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
903 <<"\" to texture atlas"<<std::endl;*/
905 // Tile it a few times in the X direction
906 u16 xwise_tiling = column_width / dim.Width;
907 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
909 for(u32 j=0; j<xwise_tiling; j++)
911 // Copy the copy to the atlas
912 /*img2->copyToWithAlpha(atlas_img,
913 pos_in_atlas + v2s32(j*dim.Width,0),
914 core::rect<s32>(v2s32(0,0), dim),
915 video::SColor(255,255,255,255),
917 img2->copyTo(atlas_img,
918 pos_in_atlas + v2s32(j*dim.Width,0),
919 core::rect<s32>(v2s32(0,0), dim),
923 // Copy the borders a few times to disallow texture bleeding
924 for(u32 side=0; side<2; side++) // top and bottom
925 for(s32 y0=0; y0<padding; y0++)
926 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
932 dst_y = y0 + pos_in_atlas.Y + dim.Height;
933 src_y = pos_in_atlas.Y + dim.Height - 1;
937 dst_y = -y0 + pos_in_atlas.Y-1;
938 src_y = pos_in_atlas.Y;
940 s32 x = x0 + pos_in_atlas.X;
941 video::SColor c = atlas_img->getPixel(x, src_y);
942 atlas_img->setPixel(x,dst_y,c);
948 Add texture to caches
951 bool reuse_old_id = false;
952 u32 id = m_atlaspointer_cache.size();
953 // Check old id without fetching a texture
954 core::map<std::string, u32>::Node *n;
955 n = m_name_to_id.find(name);
956 // If it exists, we will replace the old definition
960 /*infostream<<"TextureSource::buildMainAtlas(): "
961 <<"Replacing old AtlasPointer"<<std::endl;*/
964 // Create AtlasPointer
966 ap.atlas = NULL; // Set on the second pass
967 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
968 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
969 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
970 (float)dim.Width/(float)atlas_dim.Height);
971 ap.tiled = xwise_tiling;
973 // Create SourceAtlasPointer and add to containers
974 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
976 m_atlaspointer_cache[id] = nap;
978 m_atlaspointer_cache.push_back(nap);
979 m_name_to_id[name] = id;
981 // Increment position
982 pos_in_atlas.Y += dim.Height + padding * 2;
988 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
992 Second pass: set texture pointer in generated AtlasPointers
994 for(core::map<std::string, bool>::Iterator
995 i = sourcelist.getIterator();
996 i.atEnd() == false; i++)
998 std::string name = i.getNode()->getKey();
999 if(m_name_to_id.find(name) == NULL)
1001 u32 id = m_name_to_id[name];
1002 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1003 m_atlaspointer_cache[id].a.atlas = t;
1007 Write image to file so that it can be inspected
1009 /*std::string atlaspath = porting::path_userdata
1010 + DIR_DELIM + "generated_texture_atlas.png";
1011 infostream<<"Removing and writing texture atlas for inspection to "
1012 <<atlaspath<<std::endl;
1013 fs::RecursiveDelete(atlaspath);
1014 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1017 video::IImage* generate_image_from_scratch(std::string name,
1018 IrrlichtDevice *device, SourceImageCache *sourcecache)
1020 /*infostream<<"generate_image_from_scratch(): "
1021 "\""<<name<<"\""<<std::endl;*/
1023 video::IVideoDriver* driver = device->getVideoDriver();
1030 video::IImage *baseimg = NULL;
1032 char separator = '^';
1034 // Find last meta separator in name
1035 s32 last_separator_position = -1;
1036 for(s32 i=name.size()-1; i>=0; i--)
1038 if(name[i] == separator)
1040 last_separator_position = i;
1045 /*infostream<<"generate_image_from_scratch(): "
1046 <<"last_separator_position="<<last_separator_position
1050 If separator was found, construct the base name and make the
1051 base image using a recursive call
1053 std::string base_image_name;
1054 if(last_separator_position != -1)
1056 // Construct base name
1057 base_image_name = name.substr(0, last_separator_position);
1058 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1059 " to get base image of \""<<name<<"\" = \""
1060 <<base_image_name<<"\""<<std::endl;*/
1061 baseimg = generate_image_from_scratch(base_image_name, device,
1066 Parse out the last part of the name of the image and act
1070 std::string last_part_of_name = name.substr(last_separator_position+1);
1071 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1073 // Generate image according to part of name
1074 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1076 errorstream<<"generate_image_from_scratch(): "
1077 "failed to generate \""<<last_part_of_name<<"\""
1085 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1086 IrrlichtDevice *device, SourceImageCache *sourcecache)
1088 video::IVideoDriver* driver = device->getVideoDriver();
1091 // Stuff starting with [ are special commands
1092 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1094 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1098 if(part_of_name != ""){
1099 errorstream<<"generate_image(): Could not load image \""
1100 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1101 errorstream<<"generate_image(): Creating a dummy"
1102 <<" image for \""<<part_of_name<<"\""<<std::endl;
1105 // Just create a dummy image
1106 //core::dimension2d<u32> dim(2,2);
1107 core::dimension2d<u32> dim(1,1);
1108 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1110 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1111 image->setPixel(1,0, video::SColor(255,0,255,0));
1112 image->setPixel(0,1, video::SColor(255,0,0,255));
1113 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1114 image->setPixel(0,0, video::SColor(255,myrand()%256,
1115 myrand()%256,myrand()%256));
1116 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1117 myrand()%256,myrand()%256));
1118 image->setPixel(0,1, video::SColor(255,myrand()%256,
1119 myrand()%256,myrand()%256));
1120 image->setPixel(1,1, video::SColor(255,myrand()%256,
1121 myrand()%256,myrand()%256));*/
1124 // If base image is NULL, load as base.
1127 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1129 Copy it this way to get an alpha channel.
1130 Otherwise images with alpha cannot be blitted on
1131 images that don't have alpha in the original file.
1133 core::dimension2d<u32> dim = image->getDimension();
1134 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1135 image->copyTo(baseimg);
1138 // Else blit on base.
1141 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1142 // Size of the copied area
1143 core::dimension2d<u32> dim = image->getDimension();
1144 //core::dimension2d<u32> dim(16,16);
1145 // Position to copy the blitted to in the base image
1146 core::position2d<s32> pos_to(0,0);
1147 // Position to copy the blitted from in the blitted image
1148 core::position2d<s32> pos_from(0,0);
1150 image->copyToWithAlpha(baseimg, pos_to,
1151 core::rect<s32>(pos_from, dim),
1152 video::SColor(255,255,255,255),
1160 // A special texture modification
1162 /*infostream<<"generate_image(): generating special "
1163 <<"modification \""<<part_of_name<<"\""
1167 This is the simplest of all; it just adds stuff to the
1168 name so that a separate texture is created.
1170 It is used to make textures for stuff that doesn't want
1171 to implement getting the texture from a bigger texture
1174 if(part_of_name == "[forcesingle")
1176 // If base image is NULL, create a random color
1179 core::dimension2d<u32> dim(1,1);
1180 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1182 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1183 myrand()%256,myrand()%256));
1188 Adds a cracking texture
1190 else if(part_of_name.substr(0,6) == "[crack")
1194 errorstream<<"generate_image(): baseimg==NULL "
1195 <<"for part_of_name=\""<<part_of_name
1196 <<"\", cancelling."<<std::endl;
1200 // Crack image number
1201 u16 progression = stoi(part_of_name.substr(6));
1203 // Size of the base image
1204 core::dimension2d<u32> dim_base = baseimg->getDimension();
1209 It is an image with a number of cracking stages
1212 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1216 // Dimension of original image
1217 core::dimension2d<u32> dim_crack
1218 = img_crack->getDimension();
1219 // Count of crack stages
1220 u32 crack_count = dim_crack.Height / dim_crack.Width;
1221 // Limit progression
1222 if(progression > crack_count-1)
1223 progression = crack_count-1;
1224 // Dimension of a single scaled crack stage
1225 core::dimension2d<u32> dim_crack_scaled_single(
1229 // Dimension of scaled size
1230 core::dimension2d<u32> dim_crack_scaled(
1231 dim_crack_scaled_single.Width,
1232 dim_crack_scaled_single.Height * crack_count
1234 // Create scaled crack image
1235 video::IImage *img_crack_scaled = driver->createImage(
1236 video::ECF_A8R8G8B8, dim_crack_scaled);
1237 if(img_crack_scaled)
1239 // Scale crack image by copying
1240 img_crack->copyToScaling(img_crack_scaled);
1242 // Position to copy the crack from
1243 core::position2d<s32> pos_crack_scaled(
1245 dim_crack_scaled_single.Height * progression
1248 // This tiling does nothing currently but is useful
1249 for(u32 y0=0; y0<dim_base.Height
1250 / dim_crack_scaled_single.Height; y0++)
1251 for(u32 x0=0; x0<dim_base.Width
1252 / dim_crack_scaled_single.Width; x0++)
1254 // Position to copy the crack to in the base image
1255 core::position2d<s32> pos_base(
1256 x0*dim_crack_scaled_single.Width,
1257 y0*dim_crack_scaled_single.Height
1259 // Rectangle to copy the crack from on the scaled image
1260 core::rect<s32> rect_crack_scaled(
1262 dim_crack_scaled_single
1265 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
1267 video::SColor(255,255,255,255),
1271 img_crack_scaled->drop();
1278 [combine:WxH:X,Y=filename:X,Y=filename2
1279 Creates a bigger texture from an amount of smaller ones
1281 else if(part_of_name.substr(0,8) == "[combine")
1283 Strfnd sf(part_of_name);
1285 u32 w0 = stoi(sf.next("x"));
1286 u32 h0 = stoi(sf.next(":"));
1287 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1288 core::dimension2d<u32> dim(w0,h0);
1289 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1290 while(sf.atend() == false)
1292 u32 x = stoi(sf.next(","));
1293 u32 y = stoi(sf.next("="));
1294 std::string filename = sf.next(":");
1295 infostream<<"Adding \""<<filename
1296 <<"\" to combined ("<<x<<","<<y<<")"
1298 video::IImage *img = sourcecache->getOrLoad(filename, device);
1301 core::dimension2d<u32> dim = img->getDimension();
1302 infostream<<"Size "<<dim.Width
1303 <<"x"<<dim.Height<<std::endl;
1304 core::position2d<s32> pos_base(x, y);
1305 video::IImage *img2 =
1306 driver->createImage(video::ECF_A8R8G8B8, dim);
1309 img2->copyToWithAlpha(baseimg, pos_base,
1310 core::rect<s32>(v2s32(0,0), dim),
1311 video::SColor(255,255,255,255),
1317 infostream<<"img==NULL"<<std::endl;
1324 else if(part_of_name.substr(0,9) == "[brighten")
1328 errorstream<<"generate_image(): baseimg==NULL "
1329 <<"for part_of_name=\""<<part_of_name
1330 <<"\", cancelling."<<std::endl;
1338 Make image completely opaque.
1339 Used for the leaves texture when in old leaves mode, so
1340 that the transparent parts don't look completely black
1341 when simple alpha channel is used for rendering.
1343 else if(part_of_name.substr(0,8) == "[noalpha")
1347 errorstream<<"generate_image(): baseimg==NULL "
1348 <<"for part_of_name=\""<<part_of_name
1349 <<"\", cancelling."<<std::endl;
1353 core::dimension2d<u32> dim = baseimg->getDimension();
1355 // Set alpha to full
1356 for(u32 y=0; y<dim.Height; y++)
1357 for(u32 x=0; x<dim.Width; x++)
1359 video::SColor c = baseimg->getPixel(x,y);
1361 baseimg->setPixel(x,y,c);
1366 Convert one color to transparent.
1368 else if(part_of_name.substr(0,11) == "[makealpha:")
1372 errorstream<<"generate_image(): baseimg==NULL "
1373 <<"for part_of_name=\""<<part_of_name
1374 <<"\", cancelling."<<std::endl;
1378 Strfnd sf(part_of_name.substr(11));
1379 u32 r1 = stoi(sf.next(","));
1380 u32 g1 = stoi(sf.next(","));
1381 u32 b1 = stoi(sf.next(""));
1382 std::string filename = sf.next("");
1384 core::dimension2d<u32> dim = baseimg->getDimension();
1386 /*video::IImage *oldbaseimg = baseimg;
1387 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1388 oldbaseimg->copyTo(baseimg);
1389 oldbaseimg->drop();*/
1391 // Set alpha to full
1392 for(u32 y=0; y<dim.Height; y++)
1393 for(u32 x=0; x<dim.Width; x++)
1395 video::SColor c = baseimg->getPixel(x,y);
1397 u32 g = c.getGreen();
1398 u32 b = c.getBlue();
1399 if(!(r == r1 && g == g1 && b == b1))
1402 baseimg->setPixel(x,y,c);
1406 [inventorycube{topimage{leftimage{rightimage
1407 In every subimage, replace ^ with &.
1408 Create an "inventory cube".
1409 NOTE: This should be used only on its own.
1410 Example (a grass block (not actually used in game):
1411 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1413 else if(part_of_name.substr(0,14) == "[inventorycube")
1417 errorstream<<"generate_image(): baseimg!=NULL "
1418 <<"for part_of_name=\""<<part_of_name
1419 <<"\", cancelling."<<std::endl;
1423 str_replace_char(part_of_name, '&', '^');
1424 Strfnd sf(part_of_name);
1426 std::string imagename_top = sf.next("{");
1427 std::string imagename_left = sf.next("{");
1428 std::string imagename_right = sf.next("{");
1430 // Generate images for the faces of the cube
1431 video::IImage *img_top = generate_image_from_scratch(
1432 imagename_top, device, sourcecache);
1433 video::IImage *img_left = generate_image_from_scratch(
1434 imagename_left, device, sourcecache);
1435 video::IImage *img_right = generate_image_from_scratch(
1436 imagename_right, device, sourcecache);
1437 assert(img_top && img_left && img_right);
1439 // Create textures from images
1440 video::ITexture *texture_top = driver->addTexture(
1441 (imagename_top + "__temp__").c_str(), img_top);
1442 video::ITexture *texture_left = driver->addTexture(
1443 (imagename_left + "__temp__").c_str(), img_left);
1444 video::ITexture *texture_right = driver->addTexture(
1445 (imagename_right + "__temp__").c_str(), img_right);
1446 assert(texture_top && texture_left && texture_right);
1454 Draw a cube mesh into a render target texture
1456 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1457 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1458 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1459 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1460 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1461 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1462 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1463 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1465 core::dimension2d<u32> dim(64,64);
1466 std::string rtt_texture_name = part_of_name + "_RTT";
1468 v3f camera_position(0, 1.0, -1.5);
1469 camera_position.rotateXZBy(45);
1470 v3f camera_lookat(0, 0, 0);
1471 core::CMatrix4<f32> camera_projection_matrix;
1472 // Set orthogonal projection
1473 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1474 1.65, 1.65, 0, 100);
1476 video::SColorf ambient_light(0.2,0.2,0.2);
1477 v3f light_position(10, 100, -50);
1478 video::SColorf light_color(0.5,0.5,0.5);
1479 f32 light_radius = 1000;
1481 video::ITexture *rtt = generateTextureFromMesh(
1482 cube, device, dim, rtt_texture_name,
1485 camera_projection_matrix,
1494 // Free textures of images
1495 driver->removeTexture(texture_top);
1496 driver->removeTexture(texture_left);
1497 driver->removeTexture(texture_right);
1501 errorstream<<"generate_image(): render to texture failed."
1502 " Creating fallback image"<<std::endl;
1503 baseimg = generate_image_from_scratch(
1504 imagename_top, device, sourcecache);
1508 // Create image of render target
1509 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1512 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1516 image->copyTo(baseimg);
1522 errorstream<<"generate_image(): Invalid "
1523 " modification: \""<<part_of_name<<"\""<<std::endl;
1530 void brighten(video::IImage *image)
1535 core::dimension2d<u32> dim = image->getDimension();
1537 for(u32 y=0; y<dim.Height; y++)
1538 for(u32 x=0; x<dim.Width; x++)
1540 video::SColor c = image->getPixel(x,y);
1541 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1542 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1543 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1544 image->setPixel(x,y,c);