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("clienttextures")+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);
149 An internal variant of AtlasPointer with more data.
150 (well, more like a wrapper)
153 struct SourceAtlasPointer
157 video::IImage *atlas_img; // The source image of the atlas
158 // Integer variants of position and size
163 const std::string &name_,
164 AtlasPointer a_=AtlasPointer(0, NULL),
165 video::IImage *atlas_img_=NULL,
166 v2s32 intpos_=v2s32(0,0),
167 v2u32 intsize_=v2u32(0,0)
171 atlas_img(atlas_img_),
179 SourceImageCache: A cache used for storing source images.
182 class SourceImageCache
185 void insert(const std::string &name, video::IImage *img,
186 bool prefer_local, video::IVideoDriver *driver)
190 core::map<std::string, video::IImage*>::Node *n;
191 n = m_images.find(name);
193 video::IImage *oldimg = n->getValue();
197 // Try to use local texture instead if asked to
199 std::string path = getTexturePath(name.c_str());
201 video::IImage *img2 = driver->createImageFromFile(path.c_str());
203 m_images[name] = img2;
209 m_images[name] = img;
211 video::IImage* get(const std::string &name)
213 core::map<std::string, video::IImage*>::Node *n;
214 n = m_images.find(name);
216 return n->getValue();
219 // Primarily fetches from cache, secondarily tries to read from filesystem
220 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
222 core::map<std::string, video::IImage*>::Node *n;
223 n = m_images.find(name);
225 n->getValue()->grab(); // Grab for caller
226 return n->getValue();
228 video::IVideoDriver* driver = device->getVideoDriver();
229 std::string path = getTexturePath(name.c_str());
231 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
232 <<name<<"\""<<std::endl;
235 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
237 video::IImage *img = driver->createImageFromFile(path.c_str());
238 // Even if could not be loaded, put as NULL
239 //m_images[name] = img;
241 m_images[name] = img;
242 img->grab(); // Grab for caller
247 core::map<std::string, video::IImage*> m_images;
254 class TextureSource : public IWritableTextureSource
257 TextureSource(IrrlichtDevice *device);
262 Now, assume a texture with the id 1 exists, and has the name
263 "stone.png^mineral1".
264 Then a random thread calls getTextureId for a texture called
265 "stone.png^mineral1^crack0".
266 ...Now, WTF should happen? Well:
267 - getTextureId strips off stuff recursively from the end until
268 the remaining part is found, or nothing is left when
269 something is stripped out
271 But it is slow to search for textures by names and modify them
273 - ContentFeatures is made to contain ids for the basic plain
275 - Crack textures can be slow by themselves, but the framework
279 - Assume a texture with the id 1 exists, and has the name
280 "stone.png^mineral1" and is specified as a part of some atlas.
281 - Now MapBlock::getNodeTile() stumbles upon a node which uses
282 texture id 1, and finds out that NODEMOD_CRACK must be applied
284 - It finds out the name of the texture with getTextureName(1),
285 appends "^crack0" to it and gets a new texture id with
286 getTextureId("stone.png^mineral1^crack0")
291 Gets a texture id from cache or
292 - if main thread, from getTextureIdDirect
293 - if other thread, adds to request queue and waits for main thread
295 u32 getTextureId(const std::string &name);
301 "stone.png^blit:mineral_coal.png"
302 "stone.png^blit:mineral_coal.png^crack1"
304 - If texture specified by name is found from cache, return the
306 - Otherwise generate the texture, add to cache and return id.
307 Recursion is used to find out the largest found part of the
308 texture and continue based on it.
310 The id 0 points to a NULL texture. It is returned in case of error.
312 u32 getTextureIdDirect(const std::string &name);
314 // Finds out the name of a cached texture.
315 std::string getTextureName(u32 id);
318 If texture specified by the name pointed by the id doesn't
319 exist, create it, then return the cached texture.
321 Can be called from any thread. If called from some other thread
322 and not found in cache, the call is queued to the main thread
325 AtlasPointer getTexture(u32 id);
327 AtlasPointer getTexture(const std::string &name)
329 return getTexture(getTextureId(name));
332 // Gets a separate texture
333 video::ITexture* getTextureRaw(const std::string &name)
335 AtlasPointer ap = getTexture(name);
339 // Update new texture pointer and texture coordinates to an
340 // AtlasPointer based on it's texture id
341 void updateAP(AtlasPointer &ap);
343 // Processes queued texture requests from other threads.
344 // Shall be called from the main thread.
347 // Insert an image into the cache without touching the filesystem.
348 // Shall be called from the main thread.
349 void insertSourceImage(const std::string &name, video::IImage *img);
351 // Rebuild images and textures from the current set of source images
352 // Shall be called from the main thread.
353 void rebuildImagesAndTextures();
355 // Build the main texture atlas which contains most of the
357 void buildMainAtlas(class IGameDef *gamedef);
361 // The id of the thread that is allowed to use irrlicht directly
362 threadid_t m_main_thread;
363 // The irrlicht device
364 IrrlichtDevice *m_device;
366 // Cache of source images
367 // This should be only accessed from the main thread
368 SourceImageCache m_sourcecache;
370 // A texture id is index in this array.
371 // The first position contains a NULL texture.
372 core::array<SourceAtlasPointer> m_atlaspointer_cache;
373 // Maps a texture name to an index in the former.
374 core::map<std::string, u32> m_name_to_id;
375 // The two former containers are behind this mutex
376 JMutex m_atlaspointer_cache_mutex;
378 // Main texture atlas. This is filled at startup and is then not touched.
379 video::IImage *m_main_atlas_image;
380 video::ITexture *m_main_atlas_texture;
382 // Queued texture fetches (to be processed by the main thread)
383 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
386 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
388 return new TextureSource(device);
391 TextureSource::TextureSource(IrrlichtDevice *device):
393 m_main_atlas_image(NULL),
394 m_main_atlas_texture(NULL)
398 m_atlaspointer_cache_mutex.Init();
400 m_main_thread = get_current_thread_id();
402 // Add a NULL AtlasPointer as the first index, named ""
403 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
404 m_name_to_id[""] = 0;
407 TextureSource::~TextureSource()
411 u32 TextureSource::getTextureId(const std::string &name)
413 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
417 See if texture already exists
419 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
420 core::map<std::string, u32>::Node *n;
421 n = m_name_to_id.find(name);
424 return n->getValue();
431 if(get_current_thread_id() == m_main_thread)
433 return getTextureIdDirect(name);
437 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
439 // We're gonna ask the result to be put into here
440 ResultQueue<std::string, u32, u8, u8> result_queue;
442 // Throw a request in
443 m_get_texture_queue.add(name, 0, 0, &result_queue);
445 infostream<<"Waiting for texture from main thread, name=\""
446 <<name<<"\""<<std::endl;
450 // Wait result for a second
451 GetResult<std::string, u32, u8, u8>
452 result = result_queue.pop_front(1000);
454 // Check that at least something worked OK
455 assert(result.key == name);
459 catch(ItemNotFoundException &e)
461 infostream<<"Waiting for texture timed out."<<std::endl;
466 infostream<<"getTextureId(): Failed"<<std::endl;
471 // Draw a progress bar on the image
472 void make_progressbar(float value, video::IImage *image);
474 void brighten(video::IImage *image);
477 Generate image based on a string like "stone.png" or "[crack0".
478 if baseimg is NULL, it is created. Otherwise stuff is made on it.
480 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
481 IrrlichtDevice *device, SourceImageCache *sourcecache);
484 Generates an image from a full string like
485 "stone.png^mineral_coal.png^[crack0".
487 This is used by buildMainAtlas().
489 video::IImage* generate_image_from_scratch(std::string name,
490 IrrlichtDevice *device, SourceImageCache *sourcecache);
493 This method generates all the textures
495 u32 TextureSource::getTextureIdDirect(const std::string &name)
497 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
499 // Empty name means texture 0
502 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
507 Calling only allowed from main thread
509 if(get_current_thread_id() != m_main_thread)
511 errorstream<<"TextureSource::getTextureIdDirect() "
512 "called not from main thread"<<std::endl;
517 See if texture already exists
520 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
522 core::map<std::string, u32>::Node *n;
523 n = m_name_to_id.find(name);
526 /*infostream<<"getTextureIdDirect(): \""<<name
527 <<"\" found in cache"<<std::endl;*/
528 return n->getValue();
532 /*infostream<<"getTextureIdDirect(): \""<<name
533 <<"\" NOT found in cache. Creating it."<<std::endl;*/
539 char separator = '^';
542 This is set to the id of the base image.
543 If left 0, there is no base image and a completely new image
546 u32 base_image_id = 0;
548 // Find last meta separator in name
549 s32 last_separator_position = -1;
550 for(s32 i=name.size()-1; i>=0; i--)
552 if(name[i] == separator)
554 last_separator_position = i;
559 If separator was found, construct the base name and make the
560 base image using a recursive call
562 std::string base_image_name;
563 if(last_separator_position != -1)
565 // Construct base name
566 base_image_name = name.substr(0, last_separator_position);
567 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
568 " to get base image of \""<<name<<"\" = \""
569 <<base_image_name<<"\""<<std::endl;*/
570 base_image_id = getTextureIdDirect(base_image_name);
573 //infostream<<"base_image_id="<<base_image_id<<std::endl;
575 video::IVideoDriver* driver = m_device->getVideoDriver();
578 video::ITexture *t = NULL;
581 An image will be built from files and then converted into a texture.
583 video::IImage *baseimg = NULL;
585 // If a base image was found, copy it to baseimg
586 if(base_image_id != 0)
588 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
590 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
592 video::IImage *image = ap.atlas_img;
596 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
597 <<"cache: \""<<base_image_name<<"\""
602 core::dimension2d<u32> dim = ap.intsize;
604 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
606 core::position2d<s32> pos_to(0,0);
607 core::position2d<s32> pos_from = ap.intpos;
611 v2s32(0,0), // position in target
612 core::rect<s32>(pos_from, dim) // from
615 /*infostream<<"getTextureIdDirect(): Loaded \""
616 <<base_image_name<<"\" from image cache"
622 Parse out the last part of the name of the image and act
626 std::string last_part_of_name = name.substr(last_separator_position+1);
627 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
629 // Generate image according to part of name
630 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
632 errorstream<<"getTextureIdDirect(): "
633 "failed to generate \""<<last_part_of_name<<"\""
637 // If no resulting image, print a warning
640 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
641 " create texture \""<<name<<"\""<<std::endl;
646 // Create texture from resulting image
647 t = driver->addTexture(name.c_str(), baseimg);
651 Add texture to caches (add NULL textures too)
654 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
656 u32 id = m_atlaspointer_cache.size();
662 core::dimension2d<u32> baseimg_dim(0,0);
664 baseimg_dim = baseimg->getDimension();
665 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
666 m_atlaspointer_cache.push_back(nap);
667 m_name_to_id.insert(name, id);
669 /*infostream<<"getTextureIdDirect(): "
670 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
675 std::string TextureSource::getTextureName(u32 id)
677 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
679 if(id >= m_atlaspointer_cache.size())
681 errorstream<<"TextureSource::getTextureName(): id="<<id
682 <<" >= m_atlaspointer_cache.size()="
683 <<m_atlaspointer_cache.size()<<std::endl;
687 return m_atlaspointer_cache[id].name;
691 AtlasPointer TextureSource::getTexture(u32 id)
693 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
695 if(id >= m_atlaspointer_cache.size())
696 return AtlasPointer(0, NULL);
698 return m_atlaspointer_cache[id].a;
701 void TextureSource::updateAP(AtlasPointer &ap)
703 AtlasPointer ap2 = getTexture(ap.id);
707 void TextureSource::processQueue()
712 if(m_get_texture_queue.size() > 0)
714 GetRequest<std::string, u32, u8, u8>
715 request = m_get_texture_queue.pop();
717 /*infostream<<"TextureSource::processQueue(): "
718 <<"got texture request with "
719 <<"name=\""<<request.key<<"\""
722 GetResult<std::string, u32, u8, u8>
724 result.key = request.key;
725 result.callers = request.callers;
726 result.item = getTextureIdDirect(request.key);
728 request.dest->push_back(result);
732 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
734 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
736 assert(get_current_thread_id() == m_main_thread);
738 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
741 void TextureSource::rebuildImagesAndTextures()
743 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
745 /*// Oh well... just clear everything, they'll load sometime.
746 m_atlaspointer_cache.clear();
747 m_name_to_id.clear();*/
749 video::IVideoDriver* driver = m_device->getVideoDriver();
751 // Remove source images from textures to disable inheriting textures
752 // from existing textures
753 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
754 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
755 sap->atlas_img->drop();
756 sap->atlas_img = NULL;
760 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
761 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
763 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
764 // Create texture from resulting image
765 video::ITexture *t = NULL;
767 t = driver->addTexture(sap->name.c_str(), img);
771 sap->a.pos = v2f(0,0);
772 sap->a.size = v2f(1,1);
774 sap->atlas_img = img;
775 sap->intpos = v2s32(0,0);
776 sap->intsize = img->getDimension();
780 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
782 assert(gamedef->tsrc() == this);
783 INodeDefManager *ndef = gamedef->ndef();
785 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
787 //return; // Disable (for testing)
789 video::IVideoDriver* driver = m_device->getVideoDriver();
792 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
794 // Create an image of the right size
795 core::dimension2d<u32> atlas_dim(1024,1024);
796 video::IImage *atlas_img =
797 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
799 if(atlas_img == NULL)
801 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
802 "image; not building texture atlas."<<std::endl;
807 Grab list of stuff to include in the texture atlas from the
808 main content features
811 core::map<std::string, bool> sourcelist;
813 for(u16 j=0; j<MAX_CONTENT+1; j++)
815 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
817 const ContentFeatures &f = ndef->get(j);
818 for(std::set<std::string>::const_iterator
819 i = f.used_texturenames.begin();
820 i != f.used_texturenames.end(); i++)
822 std::string name = *i;
823 sourcelist[name] = true;
825 if(f.often_contains_mineral){
826 for(int k=1; k<MINERAL_COUNT; k++){
827 std::string mineraltexture = mineral_block_texture(k);
828 std::string fulltexture = name + "^" + mineraltexture;
829 sourcelist[fulltexture] = true;
835 infostream<<"Creating texture atlas out of textures: ";
836 for(core::map<std::string, bool>::Iterator
837 i = sourcelist.getIterator();
838 i.atEnd() == false; i++)
840 std::string name = i.getNode()->getKey();
841 infostream<<"\""<<name<<"\" ";
843 infostream<<std::endl;
845 // Padding to disallow texture bleeding
848 s32 column_width = 256;
849 s32 column_padding = 16;
852 First pass: generate almost everything
854 core::position2d<s32> pos_in_atlas(0,0);
856 pos_in_atlas.Y = padding;
858 for(core::map<std::string, bool>::Iterator
859 i = sourcelist.getIterator();
860 i.atEnd() == false; i++)
862 std::string name = i.getNode()->getKey();
864 // Generate image by name
865 video::IImage *img2 = generate_image_from_scratch(name, m_device,
869 errorstream<<"TextureSource::buildMainAtlas(): "
870 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
874 core::dimension2d<u32> dim = img2->getDimension();
876 // Don't add to atlas if image is large
877 core::dimension2d<u32> max_size_in_atlas(32,32);
878 if(dim.Width > max_size_in_atlas.Width
879 || dim.Height > max_size_in_atlas.Height)
881 infostream<<"TextureSource::buildMainAtlas(): Not adding "
882 <<"\""<<name<<"\" because image is large"<<std::endl;
886 // Wrap columns and stop making atlas if atlas is full
887 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
889 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
890 errorstream<<"TextureSource::buildMainAtlas(): "
891 <<"Atlas is full, not adding more textures."
895 pos_in_atlas.Y = padding;
896 pos_in_atlas.X += column_width + column_padding;
899 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
900 <<"\" to texture atlas"<<std::endl;*/
902 // Tile it a few times in the X direction
903 u16 xwise_tiling = column_width / dim.Width;
904 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
906 for(u32 j=0; j<xwise_tiling; j++)
908 // Copy the copy to the atlas
909 img2->copyToWithAlpha(atlas_img,
910 pos_in_atlas + v2s32(j*dim.Width,0),
911 core::rect<s32>(v2s32(0,0), dim),
912 video::SColor(255,255,255,255),
916 // Copy the borders a few times to disallow texture bleeding
917 for(u32 side=0; side<2; side++) // top and bottom
918 for(s32 y0=0; y0<padding; y0++)
919 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
925 dst_y = y0 + pos_in_atlas.Y + dim.Height;
926 src_y = pos_in_atlas.Y + dim.Height - 1;
930 dst_y = -y0 + pos_in_atlas.Y-1;
931 src_y = pos_in_atlas.Y;
933 s32 x = x0 + pos_in_atlas.X;
934 video::SColor c = atlas_img->getPixel(x, src_y);
935 atlas_img->setPixel(x,dst_y,c);
941 Add texture to caches
944 bool reuse_old_id = false;
945 u32 id = m_atlaspointer_cache.size();
946 // Check old id without fetching a texture
947 core::map<std::string, u32>::Node *n;
948 n = m_name_to_id.find(name);
949 // If it exists, we will replace the old definition
953 /*infostream<<"TextureSource::buildMainAtlas(): "
954 <<"Replacing old AtlasPointer"<<std::endl;*/
957 // Create AtlasPointer
959 ap.atlas = NULL; // Set on the second pass
960 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
961 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
962 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
963 (float)dim.Width/(float)atlas_dim.Height);
964 ap.tiled = xwise_tiling;
966 // Create SourceAtlasPointer and add to containers
967 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
969 m_atlaspointer_cache[id] = nap;
971 m_atlaspointer_cache.push_back(nap);
972 m_name_to_id[name] = id;
974 // Increment position
975 pos_in_atlas.Y += dim.Height + padding * 2;
981 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
985 Second pass: set texture pointer in generated AtlasPointers
987 for(core::map<std::string, bool>::Iterator
988 i = sourcelist.getIterator();
989 i.atEnd() == false; i++)
991 std::string name = i.getNode()->getKey();
992 if(m_name_to_id.find(name) == NULL)
994 u32 id = m_name_to_id[name];
995 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
996 m_atlaspointer_cache[id].a.atlas = t;
1000 Write image to file so that it can be inspected
1002 /*std::string atlaspath = porting::path_userdata
1003 + DIR_DELIM + "generated_texture_atlas.png";
1004 infostream<<"Removing and writing texture atlas for inspection to "
1005 <<atlaspath<<std::endl;
1006 fs::RecursiveDelete(atlaspath);
1007 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1010 video::IImage* generate_image_from_scratch(std::string name,
1011 IrrlichtDevice *device, SourceImageCache *sourcecache)
1013 /*infostream<<"generate_image_from_scratch(): "
1014 "\""<<name<<"\""<<std::endl;*/
1016 video::IVideoDriver* driver = device->getVideoDriver();
1023 video::IImage *baseimg = NULL;
1025 char separator = '^';
1027 // Find last meta separator in name
1028 s32 last_separator_position = -1;
1029 for(s32 i=name.size()-1; i>=0; i--)
1031 if(name[i] == separator)
1033 last_separator_position = i;
1038 /*infostream<<"generate_image_from_scratch(): "
1039 <<"last_separator_position="<<last_separator_position
1043 If separator was found, construct the base name and make the
1044 base image using a recursive call
1046 std::string base_image_name;
1047 if(last_separator_position != -1)
1049 // Construct base name
1050 base_image_name = name.substr(0, last_separator_position);
1051 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1052 " to get base image of \""<<name<<"\" = \""
1053 <<base_image_name<<"\""<<std::endl;*/
1054 baseimg = generate_image_from_scratch(base_image_name, device,
1059 Parse out the last part of the name of the image and act
1063 std::string last_part_of_name = name.substr(last_separator_position+1);
1064 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1066 // Generate image according to part of name
1067 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1069 errorstream<<"generate_image_from_scratch(): "
1070 "failed to generate \""<<last_part_of_name<<"\""
1078 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1079 IrrlichtDevice *device, SourceImageCache *sourcecache)
1081 video::IVideoDriver* driver = device->getVideoDriver();
1084 // Stuff starting with [ are special commands
1085 if(part_of_name[0] != '[')
1087 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1091 errorstream<<"generate_image(): Could not load image \""
1092 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1093 errorstream<<"generate_image(): Creating a dummy"
1094 <<" image for \""<<part_of_name<<"\""<<std::endl;
1096 // Just create a dummy image
1097 //core::dimension2d<u32> dim(2,2);
1098 core::dimension2d<u32> dim(1,1);
1099 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1101 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1102 image->setPixel(1,0, video::SColor(255,0,255,0));
1103 image->setPixel(0,1, video::SColor(255,0,0,255));
1104 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1105 image->setPixel(0,0, video::SColor(255,myrand()%256,
1106 myrand()%256,myrand()%256));
1107 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1108 myrand()%256,myrand()%256));
1109 image->setPixel(0,1, video::SColor(255,myrand()%256,
1110 myrand()%256,myrand()%256));
1111 image->setPixel(1,1, video::SColor(255,myrand()%256,
1112 myrand()%256,myrand()%256));*/
1115 // If base image is NULL, load as base.
1118 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1120 Copy it this way to get an alpha channel.
1121 Otherwise images with alpha cannot be blitted on
1122 images that don't have alpha in the original file.
1124 core::dimension2d<u32> dim = image->getDimension();
1125 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1126 image->copyTo(baseimg);
1129 // Else blit on base.
1132 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1133 // Size of the copied area
1134 core::dimension2d<u32> dim = image->getDimension();
1135 //core::dimension2d<u32> dim(16,16);
1136 // Position to copy the blitted to in the base image
1137 core::position2d<s32> pos_to(0,0);
1138 // Position to copy the blitted from in the blitted image
1139 core::position2d<s32> pos_from(0,0);
1141 image->copyToWithAlpha(baseimg, pos_to,
1142 core::rect<s32>(pos_from, dim),
1143 video::SColor(255,255,255,255),
1151 // A special texture modification
1153 /*infostream<<"generate_image(): generating special "
1154 <<"modification \""<<part_of_name<<"\""
1158 This is the simplest of all; it just adds stuff to the
1159 name so that a separate texture is created.
1161 It is used to make textures for stuff that doesn't want
1162 to implement getting the texture from a bigger texture
1165 if(part_of_name == "[forcesingle")
1170 Adds a cracking texture
1172 else if(part_of_name.substr(0,6) == "[crack")
1176 errorstream<<"generate_image(): baseimg==NULL "
1177 <<"for part_of_name=\""<<part_of_name
1178 <<"\", cancelling."<<std::endl;
1182 // Crack image number
1183 u16 progression = stoi(part_of_name.substr(6));
1185 // Size of the base image
1186 core::dimension2d<u32> dim_base = baseimg->getDimension();
1191 It is an image with a number of cracking stages
1194 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1198 // Dimension of original image
1199 core::dimension2d<u32> dim_crack
1200 = img_crack->getDimension();
1201 // Count of crack stages
1202 u32 crack_count = dim_crack.Height / dim_crack.Width;
1203 // Limit progression
1204 if(progression > crack_count-1)
1205 progression = crack_count-1;
1206 // Dimension of a single scaled crack stage
1207 core::dimension2d<u32> dim_crack_scaled_single(
1211 // Dimension of scaled size
1212 core::dimension2d<u32> dim_crack_scaled(
1213 dim_crack_scaled_single.Width,
1214 dim_crack_scaled_single.Height * crack_count
1216 // Create scaled crack image
1217 video::IImage *img_crack_scaled = driver->createImage(
1218 video::ECF_A8R8G8B8, dim_crack_scaled);
1219 if(img_crack_scaled)
1221 // Scale crack image by copying
1222 img_crack->copyToScaling(img_crack_scaled);
1224 // Position to copy the crack from
1225 core::position2d<s32> pos_crack_scaled(
1227 dim_crack_scaled_single.Height * progression
1230 // This tiling does nothing currently but is useful
1231 for(u32 y0=0; y0<dim_base.Height
1232 / dim_crack_scaled_single.Height; y0++)
1233 for(u32 x0=0; x0<dim_base.Width
1234 / dim_crack_scaled_single.Width; x0++)
1236 // Position to copy the crack to in the base image
1237 core::position2d<s32> pos_base(
1238 x0*dim_crack_scaled_single.Width,
1239 y0*dim_crack_scaled_single.Height
1241 // Rectangle to copy the crack from on the scaled image
1242 core::rect<s32> rect_crack_scaled(
1244 dim_crack_scaled_single
1247 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
1249 video::SColor(255,255,255,255),
1253 img_crack_scaled->drop();
1260 [combine:WxH:X,Y=filename:X,Y=filename2
1261 Creates a bigger texture from an amount of smaller ones
1263 else if(part_of_name.substr(0,8) == "[combine")
1265 Strfnd sf(part_of_name);
1267 u32 w0 = stoi(sf.next("x"));
1268 u32 h0 = stoi(sf.next(":"));
1269 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1270 core::dimension2d<u32> dim(w0,h0);
1271 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1272 while(sf.atend() == false)
1274 u32 x = stoi(sf.next(","));
1275 u32 y = stoi(sf.next("="));
1276 std::string filename = sf.next(":");
1277 infostream<<"Adding \""<<filename
1278 <<"\" to combined ("<<x<<","<<y<<")"
1280 video::IImage *img = sourcecache->getOrLoad(filename, device);
1283 core::dimension2d<u32> dim = img->getDimension();
1284 infostream<<"Size "<<dim.Width
1285 <<"x"<<dim.Height<<std::endl;
1286 core::position2d<s32> pos_base(x, y);
1287 video::IImage *img2 =
1288 driver->createImage(video::ECF_A8R8G8B8, dim);
1291 img2->copyToWithAlpha(baseimg, pos_base,
1292 core::rect<s32>(v2s32(0,0), dim),
1293 video::SColor(255,255,255,255),
1299 infostream<<"img==NULL"<<std::endl;
1305 Adds a progress bar, 0.0 <= N <= 1.0
1307 else if(part_of_name.substr(0,12) == "[progressbar")
1311 errorstream<<"generate_image(): baseimg==NULL "
1312 <<"for part_of_name=\""<<part_of_name
1313 <<"\", cancelling."<<std::endl;
1317 float value = stof(part_of_name.substr(12));
1318 make_progressbar(value, baseimg);
1323 else if(part_of_name.substr(0,9) == "[brighten")
1327 errorstream<<"generate_image(): baseimg==NULL "
1328 <<"for part_of_name=\""<<part_of_name
1329 <<"\", cancelling."<<std::endl;
1337 Make image completely opaque.
1338 Used for the leaves texture when in old leaves mode, so
1339 that the transparent parts don't look completely black
1340 when simple alpha channel is used for rendering.
1342 else if(part_of_name.substr(0,8) == "[noalpha")
1346 errorstream<<"generate_image(): baseimg==NULL "
1347 <<"for part_of_name=\""<<part_of_name
1348 <<"\", cancelling."<<std::endl;
1352 core::dimension2d<u32> dim = baseimg->getDimension();
1354 // Set alpha to full
1355 for(u32 y=0; y<dim.Height; y++)
1356 for(u32 x=0; x<dim.Width; x++)
1358 video::SColor c = baseimg->getPixel(x,y);
1360 baseimg->setPixel(x,y,c);
1365 Convert one color to transparent.
1367 else if(part_of_name.substr(0,11) == "[makealpha:")
1371 errorstream<<"generate_image(): baseimg==NULL "
1372 <<"for part_of_name=\""<<part_of_name
1373 <<"\", cancelling."<<std::endl;
1377 Strfnd sf(part_of_name.substr(11));
1378 u32 r1 = stoi(sf.next(","));
1379 u32 g1 = stoi(sf.next(","));
1380 u32 b1 = stoi(sf.next(""));
1381 std::string filename = sf.next("");
1383 core::dimension2d<u32> dim = baseimg->getDimension();
1385 /*video::IImage *oldbaseimg = baseimg;
1386 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1387 oldbaseimg->copyTo(baseimg);
1388 oldbaseimg->drop();*/
1390 // Set alpha to full
1391 for(u32 y=0; y<dim.Height; y++)
1392 for(u32 x=0; x<dim.Width; x++)
1394 video::SColor c = baseimg->getPixel(x,y);
1396 u32 g = c.getGreen();
1397 u32 b = c.getBlue();
1398 if(!(r == r1 && g == g1 && b == b1))
1401 baseimg->setPixel(x,y,c);
1405 [inventorycube{topimage{leftimage{rightimage
1406 In every subimage, replace ^ with &.
1407 Create an "inventory cube".
1408 NOTE: This should be used only on its own.
1409 Example (a grass block (not actually used in game):
1410 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1412 else if(part_of_name.substr(0,14) == "[inventorycube")
1416 errorstream<<"generate_image(): baseimg!=NULL "
1417 <<"for part_of_name=\""<<part_of_name
1418 <<"\", cancelling."<<std::endl;
1422 str_replace_char(part_of_name, '&', '^');
1423 Strfnd sf(part_of_name);
1425 std::string imagename_top = sf.next("{");
1426 std::string imagename_left = sf.next("{");
1427 std::string imagename_right = sf.next("{");
1430 // TODO: Create cube with different textures on different sides
1432 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
1434 errorstream<<"generate_image(): EVDF_RENDER_TO_TARGET"
1435 " not supported. Creating fallback image"<<std::endl;
1436 baseimg = generate_image_from_scratch(
1437 imagename_top, device, sourcecache);
1443 //infostream<<"inventorycube w="<<w0<<" h="<<h0<<std::endl;
1444 core::dimension2d<u32> dim(w0,h0);
1446 // Generate images for the faces of the cube
1447 video::IImage *img_top = generate_image_from_scratch(
1448 imagename_top, device, sourcecache);
1449 video::IImage *img_left = generate_image_from_scratch(
1450 imagename_left, device, sourcecache);
1451 video::IImage *img_right = generate_image_from_scratch(
1452 imagename_right, device, sourcecache);
1453 assert(img_top && img_left && img_right);
1455 // Create textures from images
1456 // TODO: Use them all
1457 video::ITexture *texture_top = driver->addTexture(
1458 (imagename_top + "__temp__").c_str(), img_top);
1459 assert(texture_top);
1466 // Create render target texture
1467 video::ITexture *rtt = NULL;
1468 std::string rtt_name = part_of_name + "_RTT";
1469 rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str(),
1470 video::ECF_A8R8G8B8);
1473 // Set render target
1474 driver->setRenderTarget(rtt, true, true,
1475 video::SColor(0,0,0,0));
1477 // Get a scene manager
1478 scene::ISceneManager *smgr_main = device->getSceneManager();
1480 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
1485 - An unit cube is centered at 0,0,0
1486 - Camera looks at cube from Y+, Z- towards Y-, Z+
1487 NOTE: Cube has to be changed to something else because
1488 the textures cannot be set individually (or can they?)
1491 scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1,
1492 v3f(0,0,0), v3f(0, 45, 0));
1493 // Set texture of cube
1494 cube->setMaterialTexture(0, texture_top);
1495 //cube->setMaterialFlag(video::EMF_LIGHTING, false);
1496 cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false);
1497 cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
1499 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
1500 v3f(0, 1.0, -1.5), v3f(0, 0, 0));
1501 // Set orthogonal projection
1502 core::CMatrix4<f32> pm;
1503 pm.buildProjectionMatrixOrthoLH(1.65, 1.65, 0, 100);
1504 camera->setProjectionMatrix(pm, true);
1506 /*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0,
1507 v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000);
1509 smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2));
1512 driver->beginScene(true, true, video::SColor(0,0,0,0));
1516 // NOTE: The scene nodes should not be dropped, otherwise
1517 // smgr->drop() segfaults
1521 // Drop scene manager
1524 // Unset render target
1525 driver->setRenderTarget(0, true, true, 0);
1527 // Free textures of images
1528 // TODO: When all are used, free them all
1529 driver->removeTexture(texture_top);
1531 // Create image of render target
1532 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1536 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1540 image->copyTo(baseimg);
1547 errorstream<<"generate_image(): Invalid "
1548 " modification: \""<<part_of_name<<"\""<<std::endl;
1555 void make_progressbar(float value, video::IImage *image)
1560 core::dimension2d<u32> size = image->getDimension();
1562 u32 barheight = size.Height/16;
1563 u32 barpad_x = size.Width/16;
1564 u32 barpad_y = size.Height/16;
1565 u32 barwidth = size.Width - barpad_x*2;
1566 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
1568 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
1570 video::SColor active(255,255,0,0);
1571 video::SColor inactive(255,0,0,0);
1572 for(u32 x0=0; x0<barwidth; x0++)
1579 u32 x = x0 + barpos.X;
1580 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
1582 image->setPixel(x,y, *c);
1587 void brighten(video::IImage *image)
1592 core::dimension2d<u32> dim = image->getDimension();
1594 for(u32 y=0; y<dim.Height; y++)
1595 for(u32 x=0; x<dim.Width; x++)
1597 video::SColor c = image->getPixel(x,y);
1598 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1599 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1600 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1601 image->setPixel(x,y,c);