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.size() == 0 || part_of_name[0] != '[')
1087 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1091 if(part_of_name != ""){
1092 errorstream<<"generate_image(): Could not load image \""
1093 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1094 errorstream<<"generate_image(): Creating a dummy"
1095 <<" image for \""<<part_of_name<<"\""<<std::endl;
1098 // Just create a dummy image
1099 //core::dimension2d<u32> dim(2,2);
1100 core::dimension2d<u32> dim(1,1);
1101 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1103 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1104 image->setPixel(1,0, video::SColor(255,0,255,0));
1105 image->setPixel(0,1, video::SColor(255,0,0,255));
1106 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1107 image->setPixel(0,0, video::SColor(255,myrand()%256,
1108 myrand()%256,myrand()%256));
1109 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1110 myrand()%256,myrand()%256));
1111 image->setPixel(0,1, video::SColor(255,myrand()%256,
1112 myrand()%256,myrand()%256));
1113 image->setPixel(1,1, video::SColor(255,myrand()%256,
1114 myrand()%256,myrand()%256));*/
1117 // If base image is NULL, load as base.
1120 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1122 Copy it this way to get an alpha channel.
1123 Otherwise images with alpha cannot be blitted on
1124 images that don't have alpha in the original file.
1126 core::dimension2d<u32> dim = image->getDimension();
1127 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1128 image->copyTo(baseimg);
1131 // Else blit on base.
1134 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1135 // Size of the copied area
1136 core::dimension2d<u32> dim = image->getDimension();
1137 //core::dimension2d<u32> dim(16,16);
1138 // Position to copy the blitted to in the base image
1139 core::position2d<s32> pos_to(0,0);
1140 // Position to copy the blitted from in the blitted image
1141 core::position2d<s32> pos_from(0,0);
1143 image->copyToWithAlpha(baseimg, pos_to,
1144 core::rect<s32>(pos_from, dim),
1145 video::SColor(255,255,255,255),
1153 // A special texture modification
1155 /*infostream<<"generate_image(): generating special "
1156 <<"modification \""<<part_of_name<<"\""
1160 This is the simplest of all; it just adds stuff to the
1161 name so that a separate texture is created.
1163 It is used to make textures for stuff that doesn't want
1164 to implement getting the texture from a bigger texture
1167 if(part_of_name == "[forcesingle")
1172 Adds a cracking texture
1174 else if(part_of_name.substr(0,6) == "[crack")
1178 errorstream<<"generate_image(): baseimg==NULL "
1179 <<"for part_of_name=\""<<part_of_name
1180 <<"\", cancelling."<<std::endl;
1184 // Crack image number
1185 u16 progression = stoi(part_of_name.substr(6));
1187 // Size of the base image
1188 core::dimension2d<u32> dim_base = baseimg->getDimension();
1193 It is an image with a number of cracking stages
1196 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1200 // Dimension of original image
1201 core::dimension2d<u32> dim_crack
1202 = img_crack->getDimension();
1203 // Count of crack stages
1204 u32 crack_count = dim_crack.Height / dim_crack.Width;
1205 // Limit progression
1206 if(progression > crack_count-1)
1207 progression = crack_count-1;
1208 // Dimension of a single scaled crack stage
1209 core::dimension2d<u32> dim_crack_scaled_single(
1213 // Dimension of scaled size
1214 core::dimension2d<u32> dim_crack_scaled(
1215 dim_crack_scaled_single.Width,
1216 dim_crack_scaled_single.Height * crack_count
1218 // Create scaled crack image
1219 video::IImage *img_crack_scaled = driver->createImage(
1220 video::ECF_A8R8G8B8, dim_crack_scaled);
1221 if(img_crack_scaled)
1223 // Scale crack image by copying
1224 img_crack->copyToScaling(img_crack_scaled);
1226 // Position to copy the crack from
1227 core::position2d<s32> pos_crack_scaled(
1229 dim_crack_scaled_single.Height * progression
1232 // This tiling does nothing currently but is useful
1233 for(u32 y0=0; y0<dim_base.Height
1234 / dim_crack_scaled_single.Height; y0++)
1235 for(u32 x0=0; x0<dim_base.Width
1236 / dim_crack_scaled_single.Width; x0++)
1238 // Position to copy the crack to in the base image
1239 core::position2d<s32> pos_base(
1240 x0*dim_crack_scaled_single.Width,
1241 y0*dim_crack_scaled_single.Height
1243 // Rectangle to copy the crack from on the scaled image
1244 core::rect<s32> rect_crack_scaled(
1246 dim_crack_scaled_single
1249 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
1251 video::SColor(255,255,255,255),
1255 img_crack_scaled->drop();
1262 [combine:WxH:X,Y=filename:X,Y=filename2
1263 Creates a bigger texture from an amount of smaller ones
1265 else if(part_of_name.substr(0,8) == "[combine")
1267 Strfnd sf(part_of_name);
1269 u32 w0 = stoi(sf.next("x"));
1270 u32 h0 = stoi(sf.next(":"));
1271 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1272 core::dimension2d<u32> dim(w0,h0);
1273 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1274 while(sf.atend() == false)
1276 u32 x = stoi(sf.next(","));
1277 u32 y = stoi(sf.next("="));
1278 std::string filename = sf.next(":");
1279 infostream<<"Adding \""<<filename
1280 <<"\" to combined ("<<x<<","<<y<<")"
1282 video::IImage *img = sourcecache->getOrLoad(filename, device);
1285 core::dimension2d<u32> dim = img->getDimension();
1286 infostream<<"Size "<<dim.Width
1287 <<"x"<<dim.Height<<std::endl;
1288 core::position2d<s32> pos_base(x, y);
1289 video::IImage *img2 =
1290 driver->createImage(video::ECF_A8R8G8B8, dim);
1293 img2->copyToWithAlpha(baseimg, pos_base,
1294 core::rect<s32>(v2s32(0,0), dim),
1295 video::SColor(255,255,255,255),
1301 infostream<<"img==NULL"<<std::endl;
1307 Adds a progress bar, 0.0 <= N <= 1.0
1309 else if(part_of_name.substr(0,12) == "[progressbar")
1313 errorstream<<"generate_image(): baseimg==NULL "
1314 <<"for part_of_name=\""<<part_of_name
1315 <<"\", cancelling."<<std::endl;
1319 float value = stof(part_of_name.substr(12));
1320 make_progressbar(value, baseimg);
1325 else if(part_of_name.substr(0,9) == "[brighten")
1329 errorstream<<"generate_image(): baseimg==NULL "
1330 <<"for part_of_name=\""<<part_of_name
1331 <<"\", cancelling."<<std::endl;
1339 Make image completely opaque.
1340 Used for the leaves texture when in old leaves mode, so
1341 that the transparent parts don't look completely black
1342 when simple alpha channel is used for rendering.
1344 else if(part_of_name.substr(0,8) == "[noalpha")
1348 errorstream<<"generate_image(): baseimg==NULL "
1349 <<"for part_of_name=\""<<part_of_name
1350 <<"\", cancelling."<<std::endl;
1354 core::dimension2d<u32> dim = baseimg->getDimension();
1356 // Set alpha to full
1357 for(u32 y=0; y<dim.Height; y++)
1358 for(u32 x=0; x<dim.Width; x++)
1360 video::SColor c = baseimg->getPixel(x,y);
1362 baseimg->setPixel(x,y,c);
1367 Convert one color to transparent.
1369 else if(part_of_name.substr(0,11) == "[makealpha:")
1373 errorstream<<"generate_image(): baseimg==NULL "
1374 <<"for part_of_name=\""<<part_of_name
1375 <<"\", cancelling."<<std::endl;
1379 Strfnd sf(part_of_name.substr(11));
1380 u32 r1 = stoi(sf.next(","));
1381 u32 g1 = stoi(sf.next(","));
1382 u32 b1 = stoi(sf.next(""));
1383 std::string filename = sf.next("");
1385 core::dimension2d<u32> dim = baseimg->getDimension();
1387 /*video::IImage *oldbaseimg = baseimg;
1388 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1389 oldbaseimg->copyTo(baseimg);
1390 oldbaseimg->drop();*/
1392 // Set alpha to full
1393 for(u32 y=0; y<dim.Height; y++)
1394 for(u32 x=0; x<dim.Width; x++)
1396 video::SColor c = baseimg->getPixel(x,y);
1398 u32 g = c.getGreen();
1399 u32 b = c.getBlue();
1400 if(!(r == r1 && g == g1 && b == b1))
1403 baseimg->setPixel(x,y,c);
1407 [inventorycube{topimage{leftimage{rightimage
1408 In every subimage, replace ^ with &.
1409 Create an "inventory cube".
1410 NOTE: This should be used only on its own.
1411 Example (a grass block (not actually used in game):
1412 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1414 else if(part_of_name.substr(0,14) == "[inventorycube")
1418 errorstream<<"generate_image(): baseimg!=NULL "
1419 <<"for part_of_name=\""<<part_of_name
1420 <<"\", cancelling."<<std::endl;
1424 str_replace_char(part_of_name, '&', '^');
1425 Strfnd sf(part_of_name);
1427 std::string imagename_top = sf.next("{");
1428 std::string imagename_left = sf.next("{");
1429 std::string imagename_right = sf.next("{");
1432 // TODO: Create cube with different textures on different sides
1434 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
1436 errorstream<<"generate_image(): EVDF_RENDER_TO_TARGET"
1437 " not supported. Creating fallback image"<<std::endl;
1438 baseimg = generate_image_from_scratch(
1439 imagename_top, device, sourcecache);
1445 //infostream<<"inventorycube w="<<w0<<" h="<<h0<<std::endl;
1446 core::dimension2d<u32> dim(w0,h0);
1448 // Generate images for the faces of the cube
1449 video::IImage *img_top = generate_image_from_scratch(
1450 imagename_top, device, sourcecache);
1451 video::IImage *img_left = generate_image_from_scratch(
1452 imagename_left, device, sourcecache);
1453 video::IImage *img_right = generate_image_from_scratch(
1454 imagename_right, device, sourcecache);
1455 assert(img_top && img_left && img_right);
1457 // Create textures from images
1458 // TODO: Use them all
1459 video::ITexture *texture_top = driver->addTexture(
1460 (imagename_top + "__temp__").c_str(), img_top);
1461 assert(texture_top);
1468 // Create render target texture
1469 video::ITexture *rtt = NULL;
1470 std::string rtt_name = part_of_name + "_RTT";
1471 rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str(),
1472 video::ECF_A8R8G8B8);
1475 // Set render target
1476 driver->setRenderTarget(rtt, true, true,
1477 video::SColor(0,0,0,0));
1479 // Get a scene manager
1480 scene::ISceneManager *smgr_main = device->getSceneManager();
1482 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
1487 - An unit cube is centered at 0,0,0
1488 - Camera looks at cube from Y+, Z- towards Y-, Z+
1489 NOTE: Cube has to be changed to something else because
1490 the textures cannot be set individually (or can they?)
1493 scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1,
1494 v3f(0,0,0), v3f(0, 45, 0));
1495 // Set texture of cube
1496 cube->setMaterialTexture(0, texture_top);
1497 //cube->setMaterialFlag(video::EMF_LIGHTING, false);
1498 cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false);
1499 cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
1501 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
1502 v3f(0, 1.0, -1.5), v3f(0, 0, 0));
1503 // Set orthogonal projection
1504 core::CMatrix4<f32> pm;
1505 pm.buildProjectionMatrixOrthoLH(1.65, 1.65, 0, 100);
1506 camera->setProjectionMatrix(pm, true);
1508 /*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0,
1509 v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000);
1511 smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2));
1514 driver->beginScene(true, true, video::SColor(0,0,0,0));
1518 // NOTE: The scene nodes should not be dropped, otherwise
1519 // smgr->drop() segfaults
1523 // Drop scene manager
1526 // Unset render target
1527 driver->setRenderTarget(0, true, true, 0);
1529 // Free textures of images
1530 // TODO: When all are used, free them all
1531 driver->removeTexture(texture_top);
1533 // Create image of render target
1534 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1538 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1542 image->copyTo(baseimg);
1549 errorstream<<"generate_image(): Invalid "
1550 " modification: \""<<part_of_name<<"\""<<std::endl;
1557 void make_progressbar(float value, video::IImage *image)
1562 core::dimension2d<u32> size = image->getDimension();
1564 u32 barheight = size.Height/16;
1565 u32 barpad_x = size.Width/16;
1566 u32 barpad_y = size.Height/16;
1567 u32 barwidth = size.Width - barpad_x*2;
1568 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
1570 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
1572 video::SColor active(255,255,0,0);
1573 video::SColor inactive(255,0,0,0);
1574 for(u32 x0=0; x0<barwidth; x0++)
1581 u32 x = x0 + barpos.X;
1582 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
1584 image->setPixel(x,y, *c);
1589 void brighten(video::IImage *image)
1594 core::dimension2d<u32> dim = image->getDimension();
1596 for(u32 y=0; y<dim.Height; y++)
1597 for(u32 x=0; x<dim.Width; x++)
1599 video::SColor c = image->getPixel(x,y);
1600 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1601 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1602 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1603 image->setPixel(x,y,c);