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)
188 core::map<std::string, video::IImage*>::Node *n;
189 n = m_images.find(name);
191 video::IImage *oldimg = n->getValue();
196 m_images[name] = img;
198 video::IImage* get(const std::string &name)
200 core::map<std::string, video::IImage*>::Node *n;
201 n = m_images.find(name);
203 return n->getValue();
206 // Primarily fetches from cache, secondarily tries to read from filesystem
207 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
209 core::map<std::string, video::IImage*>::Node *n;
210 n = m_images.find(name);
212 n->getValue()->grab(); // Grab for caller
213 return n->getValue();
215 video::IVideoDriver* driver = device->getVideoDriver();
216 std::string path = getTexturePath(name.c_str());
218 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
219 <<name<<"\""<<std::endl;
222 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
224 video::IImage *img = driver->createImageFromFile(path.c_str());
225 // Even if could not be loaded, put as NULL
226 //m_images[name] = img;
228 m_images[name] = img;
229 img->grab(); // Grab for caller
234 core::map<std::string, video::IImage*> m_images;
241 class TextureSource : public IWritableTextureSource
244 TextureSource(IrrlichtDevice *device);
249 Now, assume a texture with the id 1 exists, and has the name
250 "stone.png^mineral1".
251 Then a random thread calls getTextureId for a texture called
252 "stone.png^mineral1^crack0".
253 ...Now, WTF should happen? Well:
254 - getTextureId strips off stuff recursively from the end until
255 the remaining part is found, or nothing is left when
256 something is stripped out
258 But it is slow to search for textures by names and modify them
260 - ContentFeatures is made to contain ids for the basic plain
262 - Crack textures can be slow by themselves, but the framework
266 - Assume a texture with the id 1 exists, and has the name
267 "stone.png^mineral1" and is specified as a part of some atlas.
268 - Now MapBlock::getNodeTile() stumbles upon a node which uses
269 texture id 1, and finds out that NODEMOD_CRACK must be applied
271 - It finds out the name of the texture with getTextureName(1),
272 appends "^crack0" to it and gets a new texture id with
273 getTextureId("stone.png^mineral1^crack0")
278 Gets a texture id from cache or
279 - if main thread, from getTextureIdDirect
280 - if other thread, adds to request queue and waits for main thread
282 u32 getTextureId(const std::string &name);
288 "stone.png^blit:mineral_coal.png"
289 "stone.png^blit:mineral_coal.png^crack1"
291 - If texture specified by name is found from cache, return the
293 - Otherwise generate the texture, add to cache and return id.
294 Recursion is used to find out the largest found part of the
295 texture and continue based on it.
297 The id 0 points to a NULL texture. It is returned in case of error.
299 u32 getTextureIdDirect(const std::string &name);
301 // Finds out the name of a cached texture.
302 std::string getTextureName(u32 id);
305 If texture specified by the name pointed by the id doesn't
306 exist, create it, then return the cached texture.
308 Can be called from any thread. If called from some other thread
309 and not found in cache, the call is queued to the main thread
312 AtlasPointer getTexture(u32 id);
314 AtlasPointer getTexture(const std::string &name)
316 return getTexture(getTextureId(name));
319 // Gets a separate texture
320 video::ITexture* getTextureRaw(const std::string &name)
322 AtlasPointer ap = getTexture(name);
326 // Update new texture pointer and texture coordinates to an
327 // AtlasPointer based on it's texture id
328 void updateAP(AtlasPointer &ap);
330 // Processes queued texture requests from other threads.
331 // Shall be called from the main thread.
334 // Insert an image into the cache without touching the filesystem.
335 // Shall be called from the main thread.
336 void insertSourceImage(const std::string &name, video::IImage *img);
338 // Rebuild images and textures from the current set of source images
339 // Shall be called from the main thread.
340 void rebuildImagesAndTextures();
342 // Build the main texture atlas which contains most of the
344 void buildMainAtlas(class IGameDef *gamedef);
348 // The id of the thread that is allowed to use irrlicht directly
349 threadid_t m_main_thread;
350 // The irrlicht device
351 IrrlichtDevice *m_device;
353 // Cache of source images
354 // This should be only accessed from the main thread
355 SourceImageCache m_sourcecache;
357 // A texture id is index in this array.
358 // The first position contains a NULL texture.
359 core::array<SourceAtlasPointer> m_atlaspointer_cache;
360 // Maps a texture name to an index in the former.
361 core::map<std::string, u32> m_name_to_id;
362 // The two former containers are behind this mutex
363 JMutex m_atlaspointer_cache_mutex;
365 // Main texture atlas. This is filled at startup and is then not touched.
366 video::IImage *m_main_atlas_image;
367 video::ITexture *m_main_atlas_texture;
369 // Queued texture fetches (to be processed by the main thread)
370 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
373 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
375 return new TextureSource(device);
378 TextureSource::TextureSource(IrrlichtDevice *device):
380 m_main_atlas_image(NULL),
381 m_main_atlas_texture(NULL)
385 m_atlaspointer_cache_mutex.Init();
387 m_main_thread = get_current_thread_id();
389 // Add a NULL AtlasPointer as the first index, named ""
390 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
391 m_name_to_id[""] = 0;
394 TextureSource::~TextureSource()
398 u32 TextureSource::getTextureId(const std::string &name)
400 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
404 See if texture already exists
406 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
407 core::map<std::string, u32>::Node *n;
408 n = m_name_to_id.find(name);
411 return n->getValue();
418 if(get_current_thread_id() == m_main_thread)
420 return getTextureIdDirect(name);
424 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
426 // We're gonna ask the result to be put into here
427 ResultQueue<std::string, u32, u8, u8> result_queue;
429 // Throw a request in
430 m_get_texture_queue.add(name, 0, 0, &result_queue);
432 infostream<<"Waiting for texture from main thread, name=\""
433 <<name<<"\""<<std::endl;
437 // Wait result for a second
438 GetResult<std::string, u32, u8, u8>
439 result = result_queue.pop_front(1000);
441 // Check that at least something worked OK
442 assert(result.key == name);
446 catch(ItemNotFoundException &e)
448 infostream<<"Waiting for texture timed out."<<std::endl;
453 infostream<<"getTextureId(): Failed"<<std::endl;
458 // Draw a progress bar on the image
459 void make_progressbar(float value, video::IImage *image);
462 Generate image based on a string like "stone.png" or "[crack0".
463 if baseimg is NULL, it is created. Otherwise stuff is made on it.
465 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
466 IrrlichtDevice *device, SourceImageCache *sourcecache);
469 Generates an image from a full string like
470 "stone.png^mineral_coal.png^[crack0".
472 This is used by buildMainAtlas().
474 video::IImage* generate_image_from_scratch(std::string name,
475 IrrlichtDevice *device, SourceImageCache *sourcecache);
478 This method generates all the textures
480 u32 TextureSource::getTextureIdDirect(const std::string &name)
482 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
484 // Empty name means texture 0
487 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
492 Calling only allowed from main thread
494 if(get_current_thread_id() != m_main_thread)
496 errorstream<<"TextureSource::getTextureIdDirect() "
497 "called not from main thread"<<std::endl;
502 See if texture already exists
505 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
507 core::map<std::string, u32>::Node *n;
508 n = m_name_to_id.find(name);
511 infostream<<"getTextureIdDirect(): \""<<name
512 <<"\" found in cache"<<std::endl;
513 return n->getValue();
517 infostream<<"getTextureIdDirect(): \""<<name
518 <<"\" NOT found in cache. Creating it."<<std::endl;
524 char separator = '^';
527 This is set to the id of the base image.
528 If left 0, there is no base image and a completely new image
531 u32 base_image_id = 0;
533 // Find last meta separator in name
534 s32 last_separator_position = -1;
535 for(s32 i=name.size()-1; i>=0; i--)
537 if(name[i] == separator)
539 last_separator_position = i;
544 If separator was found, construct the base name and make the
545 base image using a recursive call
547 std::string base_image_name;
548 if(last_separator_position != -1)
550 // Construct base name
551 base_image_name = name.substr(0, last_separator_position);
552 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
553 " to get base image of \""<<name<<"\" = \""
554 <<base_image_name<<"\""<<std::endl;*/
555 base_image_id = getTextureIdDirect(base_image_name);
558 //infostream<<"base_image_id="<<base_image_id<<std::endl;
560 video::IVideoDriver* driver = m_device->getVideoDriver();
563 video::ITexture *t = NULL;
566 An image will be built from files and then converted into a texture.
568 video::IImage *baseimg = NULL;
570 // If a base image was found, copy it to baseimg
571 if(base_image_id != 0)
573 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
575 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
577 video::IImage *image = ap.atlas_img;
581 infostream<<"getTextureIdDirect(): NULL image in "
582 <<"cache: \""<<base_image_name<<"\""
587 core::dimension2d<u32> dim = ap.intsize;
589 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
591 core::position2d<s32> pos_to(0,0);
592 core::position2d<s32> pos_from = ap.intpos;
596 v2s32(0,0), // position in target
597 core::rect<s32>(pos_from, dim) // from
600 /*infostream<<"getTextureIdDirect(): Loaded \""
601 <<base_image_name<<"\" from image cache"
607 Parse out the last part of the name of the image and act
611 std::string last_part_of_name = name.substr(last_separator_position+1);
612 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
614 // Generate image according to part of name
615 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
617 infostream<<"getTextureIdDirect(): "
618 "failed to generate \""<<last_part_of_name<<"\""
622 // If no resulting image, print a warning
625 infostream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
626 " create texture \""<<name<<"\""<<std::endl;
631 // Create texture from resulting image
632 t = driver->addTexture(name.c_str(), baseimg);
636 Add texture to caches (add NULL textures too)
639 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
641 u32 id = m_atlaspointer_cache.size();
647 core::dimension2d<u32> baseimg_dim(0,0);
649 baseimg_dim = baseimg->getDimension();
650 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
651 m_atlaspointer_cache.push_back(nap);
652 m_name_to_id.insert(name, id);
654 /*infostream<<"getTextureIdDirect(): "
655 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
660 std::string TextureSource::getTextureName(u32 id)
662 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
664 if(id >= m_atlaspointer_cache.size())
666 infostream<<"TextureSource::getTextureName(): id="<<id
667 <<" >= m_atlaspointer_cache.size()="
668 <<m_atlaspointer_cache.size()<<std::endl;
672 return m_atlaspointer_cache[id].name;
676 AtlasPointer TextureSource::getTexture(u32 id)
678 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
680 if(id >= m_atlaspointer_cache.size())
681 return AtlasPointer(0, NULL);
683 return m_atlaspointer_cache[id].a;
686 void TextureSource::updateAP(AtlasPointer &ap)
688 AtlasPointer ap2 = getTexture(ap.id);
692 void TextureSource::processQueue()
697 if(m_get_texture_queue.size() > 0)
699 GetRequest<std::string, u32, u8, u8>
700 request = m_get_texture_queue.pop();
702 infostream<<"TextureSource::processQueue(): "
703 <<"got texture request with "
704 <<"name=\""<<request.key<<"\""
707 GetResult<std::string, u32, u8, u8>
709 result.key = request.key;
710 result.callers = request.callers;
711 result.item = getTextureIdDirect(request.key);
713 request.dest->push_back(result);
717 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
719 infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
721 assert(get_current_thread_id() == m_main_thread);
723 m_sourcecache.insert(name, img);
726 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
728 video::IVideoDriver* driver = m_device->getVideoDriver();
732 video::ITexture *t = driver->addTexture(name.c_str(), img);
734 bool reuse_old_id = false;
735 u32 id = m_atlaspointer_cache.size();
736 // Check old id without fetching a texture
737 core::map<std::string, u32>::Node *n;
738 n = m_name_to_id.find(name);
739 // If it exists, we will replace the old definition
745 // Create AtlasPointer
751 core::dimension2d<u32> dim = img->getDimension();
753 // Create SourceAtlasPointer and add to containers
754 SourceAtlasPointer nap(name, ap, img, v2s32(0,0), dim);
756 m_atlaspointer_cache[id] = nap;
758 m_atlaspointer_cache.push_back(nap);
759 m_name_to_id[name] = id;
763 void TextureSource::rebuildImagesAndTextures()
765 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
767 /*// Oh well... just clear everything, they'll load sometime.
768 m_atlaspointer_cache.clear();
769 m_name_to_id.clear();*/
771 video::IVideoDriver* driver = m_device->getVideoDriver();
773 // Remove source images from textures to disable inheriting textures
774 // from existing textures
775 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
776 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
777 sap->atlas_img->drop();
778 sap->atlas_img = NULL;
782 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
783 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
785 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
786 // Create texture from resulting image
787 video::ITexture *t = NULL;
789 t = driver->addTexture(sap->name.c_str(), img);
793 sap->a.pos = v2f(0,0);
794 sap->a.size = v2f(1,1);
796 sap->atlas_img = img;
797 sap->intpos = v2s32(0,0);
798 sap->intsize = img->getDimension();
802 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
804 assert(gamedef->tsrc() == this);
805 INodeDefManager *ndef = gamedef->ndef();
807 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
809 //return; // Disable (for testing)
811 video::IVideoDriver* driver = m_device->getVideoDriver();
814 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
816 // Create an image of the right size
817 core::dimension2d<u32> atlas_dim(1024,1024);
818 video::IImage *atlas_img =
819 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
821 if(atlas_img == NULL)
823 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
824 "image; not building texture atlas."<<std::endl;
829 Grab list of stuff to include in the texture atlas from the
830 main content features
833 core::map<std::string, bool> sourcelist;
835 for(u16 j=0; j<MAX_CONTENT+1; j++)
837 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
839 const ContentFeatures &f = ndef->get(j);
840 for(std::set<std::string>::const_iterator
841 i = f.used_texturenames.begin();
842 i != f.used_texturenames.end(); i++)
844 std::string name = *i;
845 sourcelist[name] = true;
847 if(f.often_contains_mineral){
848 for(int k=1; k<MINERAL_COUNT; k++){
849 std::string mineraltexture = mineral_block_texture(k);
850 std::string fulltexture = name + "^" + mineraltexture;
851 sourcelist[fulltexture] = true;
857 infostream<<"Creating texture atlas out of textures: ";
858 for(core::map<std::string, bool>::Iterator
859 i = sourcelist.getIterator();
860 i.atEnd() == false; i++)
862 std::string name = i.getNode()->getKey();
863 infostream<<"\""<<name<<"\" ";
865 infostream<<std::endl;
867 // Padding to disallow texture bleeding
870 s32 column_width = 256;
871 s32 column_padding = 16;
874 First pass: generate almost everything
876 core::position2d<s32> pos_in_atlas(0,0);
878 pos_in_atlas.Y = padding;
880 for(core::map<std::string, bool>::Iterator
881 i = sourcelist.getIterator();
882 i.atEnd() == false; i++)
884 std::string name = i.getNode()->getKey();
886 // Generate image by name
887 video::IImage *img2 = generate_image_from_scratch(name, m_device,
891 infostream<<"TextureSource::buildMainAtlas(): Couldn't generate texture atlas: Couldn't generate image \""<<name<<"\""<<std::endl;
895 core::dimension2d<u32> dim = img2->getDimension();
897 // Don't add to atlas if image is large
898 core::dimension2d<u32> max_size_in_atlas(32,32);
899 if(dim.Width > max_size_in_atlas.Width
900 || dim.Height > max_size_in_atlas.Height)
902 infostream<<"TextureSource::buildMainAtlas(): Not adding "
903 <<"\""<<name<<"\" because image is large"<<std::endl;
907 // Wrap columns and stop making atlas if atlas is full
908 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
910 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
911 errorstream<<"TextureSource::buildMainAtlas(): "
912 <<"Atlas is full, not adding more textures."
916 pos_in_atlas.Y = padding;
917 pos_in_atlas.X += column_width + column_padding;
920 infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
921 <<"\" to texture atlas"<<std::endl;
923 // Tile it a few times in the X direction
924 u16 xwise_tiling = column_width / dim.Width;
925 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
927 for(u32 j=0; j<xwise_tiling; j++)
929 // Copy the copy to the atlas
930 img2->copyToWithAlpha(atlas_img,
931 pos_in_atlas + v2s32(j*dim.Width,0),
932 core::rect<s32>(v2s32(0,0), dim),
933 video::SColor(255,255,255,255),
937 // Copy the borders a few times to disallow texture bleeding
938 for(u32 side=0; side<2; side++) // top and bottom
939 for(s32 y0=0; y0<padding; y0++)
940 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
946 dst_y = y0 + pos_in_atlas.Y + dim.Height;
947 src_y = pos_in_atlas.Y + dim.Height - 1;
951 dst_y = -y0 + pos_in_atlas.Y-1;
952 src_y = pos_in_atlas.Y;
954 s32 x = x0 + pos_in_atlas.X;
955 video::SColor c = atlas_img->getPixel(x, src_y);
956 atlas_img->setPixel(x,dst_y,c);
962 Add texture to caches
965 bool reuse_old_id = false;
966 u32 id = m_atlaspointer_cache.size();
967 // Check old id without fetching a texture
968 core::map<std::string, u32>::Node *n;
969 n = m_name_to_id.find(name);
970 // If it exists, we will replace the old definition
974 infostream<<"TextureSource::buildMainAtlas(): "
975 <<"Replacing old AtlasPointer"<<std::endl;
978 // Create AtlasPointer
980 ap.atlas = NULL; // Set on the second pass
981 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
982 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
983 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
984 (float)dim.Width/(float)atlas_dim.Height);
985 ap.tiled = xwise_tiling;
987 // Create SourceAtlasPointer and add to containers
988 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
990 m_atlaspointer_cache[id] = nap;
992 m_atlaspointer_cache.push_back(nap);
993 m_name_to_id[name] = id;
995 // Increment position
996 pos_in_atlas.Y += dim.Height + padding * 2;
1002 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1006 Second pass: set texture pointer in generated AtlasPointers
1008 for(core::map<std::string, bool>::Iterator
1009 i = sourcelist.getIterator();
1010 i.atEnd() == false; i++)
1012 std::string name = i.getNode()->getKey();
1013 if(m_name_to_id.find(name) == NULL)
1015 u32 id = m_name_to_id[name];
1016 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1017 m_atlaspointer_cache[id].a.atlas = t;
1021 Write image to file so that it can be inspected
1023 /*std::string atlaspath = porting::path_userdata
1024 + DIR_DELIM + "generated_texture_atlas.png";
1025 infostream<<"Removing and writing texture atlas for inspection to "
1026 <<atlaspath<<std::endl;
1027 fs::RecursiveDelete(atlaspath);
1028 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1031 video::IImage* generate_image_from_scratch(std::string name,
1032 IrrlichtDevice *device, SourceImageCache *sourcecache)
1034 /*infostream<<"generate_image_from_scratch(): "
1035 "\""<<name<<"\""<<std::endl;*/
1037 video::IVideoDriver* driver = device->getVideoDriver();
1044 video::IImage *baseimg = NULL;
1046 char separator = '^';
1048 // Find last meta separator in name
1049 s32 last_separator_position = -1;
1050 for(s32 i=name.size()-1; i>=0; i--)
1052 if(name[i] == separator)
1054 last_separator_position = i;
1059 /*infostream<<"generate_image_from_scratch(): "
1060 <<"last_separator_position="<<last_separator_position
1064 If separator was found, construct the base name and make the
1065 base image using a recursive call
1067 std::string base_image_name;
1068 if(last_separator_position != -1)
1070 // Construct base name
1071 base_image_name = name.substr(0, last_separator_position);
1072 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1073 " to get base image of \""<<name<<"\" = \""
1074 <<base_image_name<<"\""<<std::endl;*/
1075 baseimg = generate_image_from_scratch(base_image_name, device,
1080 Parse out the last part of the name of the image and act
1084 std::string last_part_of_name = name.substr(last_separator_position+1);
1085 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1087 // Generate image according to part of name
1088 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1090 infostream<<"generate_image_from_scratch(): "
1091 "failed to generate \""<<last_part_of_name<<"\""
1099 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1100 IrrlichtDevice *device, SourceImageCache *sourcecache)
1102 video::IVideoDriver* driver = device->getVideoDriver();
1105 // Stuff starting with [ are special commands
1106 if(part_of_name[0] != '[')
1108 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1112 infostream<<"generate_image(): Could not load image \""
1113 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1117 infostream<<"generate_image(): Creating a dummy"
1118 <<" image for \""<<part_of_name<<"\""<<std::endl;
1120 // Just create a dummy image
1121 //core::dimension2d<u32> dim(2,2);
1122 core::dimension2d<u32> dim(1,1);
1123 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1125 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1126 image->setPixel(1,0, video::SColor(255,0,255,0));
1127 image->setPixel(0,1, video::SColor(255,0,0,255));
1128 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1129 image->setPixel(0,0, video::SColor(255,myrand()%256,
1130 myrand()%256,myrand()%256));
1131 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1132 myrand()%256,myrand()%256));
1133 image->setPixel(0,1, video::SColor(255,myrand()%256,
1134 myrand()%256,myrand()%256));
1135 image->setPixel(1,1, video::SColor(255,myrand()%256,
1136 myrand()%256,myrand()%256));*/
1139 // If base image is NULL, load as base.
1142 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1144 Copy it this way to get an alpha channel.
1145 Otherwise images with alpha cannot be blitted on
1146 images that don't have alpha in the original file.
1148 core::dimension2d<u32> dim = image->getDimension();
1149 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1150 image->copyTo(baseimg);
1153 // Else blit on base.
1156 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1157 // Size of the copied area
1158 core::dimension2d<u32> dim = image->getDimension();
1159 //core::dimension2d<u32> dim(16,16);
1160 // Position to copy the blitted to in the base image
1161 core::position2d<s32> pos_to(0,0);
1162 // Position to copy the blitted from in the blitted image
1163 core::position2d<s32> pos_from(0,0);
1165 image->copyToWithAlpha(baseimg, pos_to,
1166 core::rect<s32>(pos_from, dim),
1167 video::SColor(255,255,255,255),
1175 // A special texture modification
1177 infostream<<"generate_image(): generating special "
1178 <<"modification \""<<part_of_name<<"\""
1182 This is the simplest of all; it just adds stuff to the
1183 name so that a separate texture is created.
1185 It is used to make textures for stuff that doesn't want
1186 to implement getting the texture from a bigger texture
1189 if(part_of_name == "[forcesingle")
1194 Adds a cracking texture
1196 else if(part_of_name.substr(0,6) == "[crack")
1200 infostream<<"generate_image(): baseimg==NULL "
1201 <<"for part_of_name=\""<<part_of_name
1202 <<"\", cancelling."<<std::endl;
1206 // Crack image number
1207 u16 progression = stoi(part_of_name.substr(6));
1209 // Size of the base image
1210 core::dimension2d<u32> dim_base = baseimg->getDimension();
1215 It is an image with a number of cracking stages
1218 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1222 // Dimension of original image
1223 core::dimension2d<u32> dim_crack
1224 = img_crack->getDimension();
1225 // Count of crack stages
1226 u32 crack_count = dim_crack.Height / dim_crack.Width;
1227 // Limit progression
1228 if(progression > crack_count-1)
1229 progression = crack_count-1;
1230 // Dimension of a single scaled crack stage
1231 core::dimension2d<u32> dim_crack_scaled_single(
1235 // Dimension of scaled size
1236 core::dimension2d<u32> dim_crack_scaled(
1237 dim_crack_scaled_single.Width,
1238 dim_crack_scaled_single.Height * crack_count
1240 // Create scaled crack image
1241 video::IImage *img_crack_scaled = driver->createImage(
1242 video::ECF_A8R8G8B8, dim_crack_scaled);
1243 if(img_crack_scaled)
1245 // Scale crack image by copying
1246 img_crack->copyToScaling(img_crack_scaled);
1248 // Position to copy the crack from
1249 core::position2d<s32> pos_crack_scaled(
1251 dim_crack_scaled_single.Height * progression
1254 // This tiling does nothing currently but is useful
1255 for(u32 y0=0; y0<dim_base.Height
1256 / dim_crack_scaled_single.Height; y0++)
1257 for(u32 x0=0; x0<dim_base.Width
1258 / dim_crack_scaled_single.Width; x0++)
1260 // Position to copy the crack to in the base image
1261 core::position2d<s32> pos_base(
1262 x0*dim_crack_scaled_single.Width,
1263 y0*dim_crack_scaled_single.Height
1265 // Rectangle to copy the crack from on the scaled image
1266 core::rect<s32> rect_crack_scaled(
1268 dim_crack_scaled_single
1271 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
1273 video::SColor(255,255,255,255),
1277 img_crack_scaled->drop();
1284 [combine:WxH:X,Y=filename:X,Y=filename2
1285 Creates a bigger texture from an amount of smaller ones
1287 else if(part_of_name.substr(0,8) == "[combine")
1289 Strfnd sf(part_of_name);
1291 u32 w0 = stoi(sf.next("x"));
1292 u32 h0 = stoi(sf.next(":"));
1293 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1294 core::dimension2d<u32> dim(w0,h0);
1295 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1296 while(sf.atend() == false)
1298 u32 x = stoi(sf.next(","));
1299 u32 y = stoi(sf.next("="));
1300 std::string filename = sf.next(":");
1301 infostream<<"Adding \""<<filename
1302 <<"\" to combined ("<<x<<","<<y<<")"
1304 video::IImage *img = sourcecache->getOrLoad(filename, device);
1307 core::dimension2d<u32> dim = img->getDimension();
1308 infostream<<"Size "<<dim.Width
1309 <<"x"<<dim.Height<<std::endl;
1310 core::position2d<s32> pos_base(x, y);
1311 video::IImage *img2 =
1312 driver->createImage(video::ECF_A8R8G8B8, dim);
1315 img2->copyToWithAlpha(baseimg, pos_base,
1316 core::rect<s32>(v2s32(0,0), dim),
1317 video::SColor(255,255,255,255),
1323 infostream<<"img==NULL"<<std::endl;
1329 Adds a progress bar, 0.0 <= N <= 1.0
1331 else if(part_of_name.substr(0,12) == "[progressbar")
1335 infostream<<"generate_image(): baseimg==NULL "
1336 <<"for part_of_name=\""<<part_of_name
1337 <<"\", cancelling."<<std::endl;
1341 float value = stof(part_of_name.substr(12));
1342 make_progressbar(value, baseimg);
1345 "[noalpha:filename.png"
1346 Use an image without it's alpha channel.
1347 Used for the leaves texture when in old leaves mode, so
1348 that the transparent parts don't look completely black
1349 when simple alpha channel is used for rendering.
1351 else if(part_of_name.substr(0,8) == "[noalpha")
1355 infostream<<"generate_image(): baseimg!=NULL "
1356 <<"for part_of_name=\""<<part_of_name
1357 <<"\", cancelling."<<std::endl;
1361 std::string filename = part_of_name.substr(9);
1363 std::string path = getTexturePath(filename.c_str());
1365 infostream<<"generate_image(): Loading file \""<<filename
1368 video::IImage *image = sourcecache->getOrLoad(filename, device);
1372 infostream<<"generate_image(): Loading path \""
1373 <<path<<"\" failed"<<std::endl;
1377 core::dimension2d<u32> dim = image->getDimension();
1378 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1380 // Set alpha to full
1381 for(u32 y=0; y<dim.Height; y++)
1382 for(u32 x=0; x<dim.Width; x++)
1384 video::SColor c = image->getPixel(x,y);
1386 image->setPixel(x,y,c);
1389 image->copyTo(baseimg);
1395 "[makealpha:R,G,B:filename.png"
1396 Use an image with converting one color to transparent.
1398 else if(part_of_name.substr(0,11) == "[makealpha:")
1402 infostream<<"generate_image(): baseimg!=NULL "
1403 <<"for part_of_name=\""<<part_of_name
1404 <<"\", cancelling."<<std::endl;
1408 Strfnd sf(part_of_name.substr(11));
1409 u32 r1 = stoi(sf.next(","));
1410 u32 g1 = stoi(sf.next(","));
1411 u32 b1 = stoi(sf.next(":"));
1412 std::string filename = sf.next("");
1414 infostream<<"generate_image(): Loading file \""<<filename
1417 video::IImage *image = sourcecache->getOrLoad(filename, device);
1421 infostream<<"generate_image(): Loading file \""
1422 <<filename<<"\" failed"<<std::endl;
1426 core::dimension2d<u32> dim = image->getDimension();
1427 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1430 image->copyTo(baseimg);
1434 for(u32 y=0; y<dim.Height; y++)
1435 for(u32 x=0; x<dim.Width; x++)
1437 video::SColor c = baseimg->getPixel(x,y);
1439 u32 g = c.getGreen();
1440 u32 b = c.getBlue();
1441 if(!(r == r1 && g == g1 && b == b1))
1444 baseimg->setPixel(x,y,c);
1449 "[makealpha2:R,G,B;R2,G2,B2:filename.png"
1450 Use an image with converting two colors to transparent.
1452 else if(part_of_name.substr(0,12) == "[makealpha2:")
1456 infostream<<"generate_image(): baseimg!=NULL "
1457 <<"for part_of_name=\""<<part_of_name
1458 <<"\", cancelling."<<std::endl;
1462 Strfnd sf(part_of_name.substr(12));
1463 u32 r1 = stoi(sf.next(","));
1464 u32 g1 = stoi(sf.next(","));
1465 u32 b1 = stoi(sf.next(";"));
1466 u32 r2 = stoi(sf.next(","));
1467 u32 g2 = stoi(sf.next(","));
1468 u32 b2 = stoi(sf.next(":"));
1469 std::string filename = sf.next("");
1471 infostream<<"generate_image(): Loading filename \""<<filename
1474 video::IImage *image = sourcecache->getOrLoad(filename, device);
1478 infostream<<"generate_image(): Loading file \""
1479 <<filename<<"\" failed"<<std::endl;
1483 core::dimension2d<u32> dim = image->getDimension();
1484 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1487 image->copyTo(baseimg);
1491 for(u32 y=0; y<dim.Height; y++)
1492 for(u32 x=0; x<dim.Width; x++)
1494 video::SColor c = baseimg->getPixel(x,y);
1496 u32 g = c.getGreen();
1497 u32 b = c.getBlue();
1498 if(!(r == r1 && g == g1 && b == b1) &&
1499 !(r == r2 && g == g2 && b == b2))
1502 baseimg->setPixel(x,y,c);
1507 [inventorycube{topimage{leftimage{rightimage
1508 In every subimage, replace ^ with &.
1509 Create an "inventory cube".
1510 NOTE: This should be used only on its own.
1511 Example (a grass block (not actually used in game):
1512 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1514 else if(part_of_name.substr(0,14) == "[inventorycube")
1518 infostream<<"generate_image(): baseimg!=NULL "
1519 <<"for part_of_name=\""<<part_of_name
1520 <<"\", cancelling."<<std::endl;
1524 str_replace_char(part_of_name, '&', '^');
1525 Strfnd sf(part_of_name);
1527 std::string imagename_top = sf.next("{");
1528 std::string imagename_left = sf.next("{");
1529 std::string imagename_right = sf.next("{");
1532 // TODO: Create cube with different textures on different sides
1534 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
1536 infostream<<"generate_image(): EVDF_RENDER_TO_TARGET"
1537 " not supported. Creating fallback image"<<std::endl;
1538 baseimg = generate_image_from_scratch(
1539 imagename_top, device, sourcecache);
1545 //infostream<<"inventorycube w="<<w0<<" h="<<h0<<std::endl;
1546 core::dimension2d<u32> dim(w0,h0);
1548 // Generate images for the faces of the cube
1549 video::IImage *img_top = generate_image_from_scratch(
1550 imagename_top, device, sourcecache);
1551 video::IImage *img_left = generate_image_from_scratch(
1552 imagename_left, device, sourcecache);
1553 video::IImage *img_right = generate_image_from_scratch(
1554 imagename_right, device, sourcecache);
1555 assert(img_top && img_left && img_right);
1557 // Create textures from images
1558 // TODO: Use them all
1559 video::ITexture *texture_top = driver->addTexture(
1560 (imagename_top + "__temp__").c_str(), img_top);
1561 assert(texture_top);
1568 // Create render target texture
1569 video::ITexture *rtt = NULL;
1570 std::string rtt_name = part_of_name + "_RTT";
1571 rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str(),
1572 video::ECF_A8R8G8B8);
1575 // Set render target
1576 driver->setRenderTarget(rtt, true, true,
1577 video::SColor(0,0,0,0));
1579 // Get a scene manager
1580 scene::ISceneManager *smgr_main = device->getSceneManager();
1582 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
1587 - An unit cube is centered at 0,0,0
1588 - Camera looks at cube from Y+, Z- towards Y-, Z+
1589 NOTE: Cube has to be changed to something else because
1590 the textures cannot be set individually (or can they?)
1593 scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1,
1594 v3f(0,0,0), v3f(0, 45, 0));
1595 // Set texture of cube
1596 cube->setMaterialTexture(0, texture_top);
1597 //cube->setMaterialFlag(video::EMF_LIGHTING, false);
1598 cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false);
1599 cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
1601 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
1602 v3f(0, 1.0, -1.5), v3f(0, 0, 0));
1603 // Set orthogonal projection
1604 core::CMatrix4<f32> pm;
1605 pm.buildProjectionMatrixOrthoLH(1.65, 1.65, 0, 100);
1606 camera->setProjectionMatrix(pm, true);
1608 /*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0,
1609 v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000);
1611 smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2));
1614 driver->beginScene(true, true, video::SColor(0,0,0,0));
1618 // NOTE: The scene nodes should not be dropped, otherwise
1619 // smgr->drop() segfaults
1623 // Drop scene manager
1626 // Unset render target
1627 driver->setRenderTarget(0, true, true, 0);
1629 // Free textures of images
1630 // TODO: When all are used, free them all
1631 driver->removeTexture(texture_top);
1633 // Create image of render target
1634 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1638 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1642 image->copyTo(baseimg);
1649 infostream<<"generate_image(): Invalid "
1650 " modification: \""<<part_of_name<<"\""<<std::endl;
1657 void make_progressbar(float value, video::IImage *image)
1662 core::dimension2d<u32> size = image->getDimension();
1664 u32 barheight = size.Height/16;
1665 u32 barpad_x = size.Width/16;
1666 u32 barpad_y = size.Height/16;
1667 u32 barwidth = size.Width - barpad_x*2;
1668 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
1670 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
1672 video::SColor active(255,255,0,0);
1673 video::SColor inactive(255,0,0,0);
1674 for(u32 x0=0; x0<barwidth; x0++)
1681 u32 x = x0 + barpos.X;
1682 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
1684 image->setPixel(x,y, *c);