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,
189 core::map<std::string, video::IImage*>::Node *n;
190 n = m_images.find(name);
194 video::IImage *oldimg = n->getValue();
199 m_images[name] = img;
201 video::IImage* get(const std::string &name)
203 core::map<std::string, video::IImage*>::Node *n;
204 n = m_images.find(name);
206 return n->getValue();
209 // Primarily fetches from cache, secondarily tries to read from filesystem
210 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
212 core::map<std::string, video::IImage*>::Node *n;
213 n = m_images.find(name);
215 n->getValue()->grab(); // Grab for caller
216 return n->getValue();
218 video::IVideoDriver* driver = device->getVideoDriver();
219 std::string path = getTexturePath(name.c_str());
221 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
222 <<name<<"\""<<std::endl;
225 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
227 video::IImage *img = driver->createImageFromFile(path.c_str());
228 // Even if could not be loaded, put as NULL
229 //m_images[name] = img;
231 m_images[name] = img;
232 img->grab(); // Grab for caller
237 core::map<std::string, video::IImage*> m_images;
244 class TextureSource : public IWritableTextureSource
247 TextureSource(IrrlichtDevice *device);
252 Now, assume a texture with the id 1 exists, and has the name
253 "stone.png^mineral1".
254 Then a random thread calls getTextureId for a texture called
255 "stone.png^mineral1^crack0".
256 ...Now, WTF should happen? Well:
257 - getTextureId strips off stuff recursively from the end until
258 the remaining part is found, or nothing is left when
259 something is stripped out
261 But it is slow to search for textures by names and modify them
263 - ContentFeatures is made to contain ids for the basic plain
265 - Crack textures can be slow by themselves, but the framework
269 - Assume a texture with the id 1 exists, and has the name
270 "stone.png^mineral1" and is specified as a part of some atlas.
271 - Now MapBlock::getNodeTile() stumbles upon a node which uses
272 texture id 1, and finds out that NODEMOD_CRACK must be applied
274 - It finds out the name of the texture with getTextureName(1),
275 appends "^crack0" to it and gets a new texture id with
276 getTextureId("stone.png^mineral1^crack0")
281 Gets a texture id from cache or
282 - if main thread, from getTextureIdDirect
283 - if other thread, adds to request queue and waits for main thread
285 u32 getTextureId(const std::string &name);
291 "stone.png^blit:mineral_coal.png"
292 "stone.png^blit:mineral_coal.png^crack1"
294 - If texture specified by name is found from cache, return the
296 - Otherwise generate the texture, add to cache and return id.
297 Recursion is used to find out the largest found part of the
298 texture and continue based on it.
300 The id 0 points to a NULL texture. It is returned in case of error.
302 u32 getTextureIdDirect(const std::string &name);
304 // Finds out the name of a cached texture.
305 std::string getTextureName(u32 id);
308 If texture specified by the name pointed by the id doesn't
309 exist, create it, then return the cached texture.
311 Can be called from any thread. If called from some other thread
312 and not found in cache, the call is queued to the main thread
315 AtlasPointer getTexture(u32 id);
317 AtlasPointer getTexture(const std::string &name)
319 return getTexture(getTextureId(name));
322 // Gets a separate texture
323 video::ITexture* getTextureRaw(const std::string &name)
325 AtlasPointer ap = getTexture(name);
329 // Update new texture pointer and texture coordinates to an
330 // AtlasPointer based on it's texture id
331 void updateAP(AtlasPointer &ap);
333 // Processes queued texture requests from other threads.
334 // Shall be called from the main thread.
337 // Insert an image into the cache without touching the filesystem.
338 // Shall be called from the main thread.
339 void insertSourceImage(const std::string &name, video::IImage *img);
341 // Rebuild images and textures from the current set of source images
342 // Shall be called from the main thread.
343 void rebuildImagesAndTextures();
345 // Build the main texture atlas which contains most of the
347 void buildMainAtlas(class IGameDef *gamedef);
351 // The id of the thread that is allowed to use irrlicht directly
352 threadid_t m_main_thread;
353 // The irrlicht device
354 IrrlichtDevice *m_device;
356 // Cache of source images
357 // This should be only accessed from the main thread
358 SourceImageCache m_sourcecache;
360 // A texture id is index in this array.
361 // The first position contains a NULL texture.
362 core::array<SourceAtlasPointer> m_atlaspointer_cache;
363 // Maps a texture name to an index in the former.
364 core::map<std::string, u32> m_name_to_id;
365 // The two former containers are behind this mutex
366 JMutex m_atlaspointer_cache_mutex;
368 // Main texture atlas. This is filled at startup and is then not touched.
369 video::IImage *m_main_atlas_image;
370 video::ITexture *m_main_atlas_texture;
372 // Queued texture fetches (to be processed by the main thread)
373 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
376 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
378 return new TextureSource(device);
381 TextureSource::TextureSource(IrrlichtDevice *device):
383 m_main_atlas_image(NULL),
384 m_main_atlas_texture(NULL)
388 m_atlaspointer_cache_mutex.Init();
390 m_main_thread = get_current_thread_id();
392 // Add a NULL AtlasPointer as the first index, named ""
393 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
394 m_name_to_id[""] = 0;
397 TextureSource::~TextureSource()
401 u32 TextureSource::getTextureId(const std::string &name)
403 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
407 See if texture already exists
409 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
410 core::map<std::string, u32>::Node *n;
411 n = m_name_to_id.find(name);
414 return n->getValue();
421 if(get_current_thread_id() == m_main_thread)
423 return getTextureIdDirect(name);
427 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
429 // We're gonna ask the result to be put into here
430 ResultQueue<std::string, u32, u8, u8> result_queue;
432 // Throw a request in
433 m_get_texture_queue.add(name, 0, 0, &result_queue);
435 infostream<<"Waiting for texture from main thread, name=\""
436 <<name<<"\""<<std::endl;
440 // Wait result for a second
441 GetResult<std::string, u32, u8, u8>
442 result = result_queue.pop_front(1000);
444 // Check that at least something worked OK
445 assert(result.key == name);
449 catch(ItemNotFoundException &e)
451 infostream<<"Waiting for texture timed out."<<std::endl;
456 infostream<<"getTextureId(): Failed"<<std::endl;
461 // Draw a progress bar on the image
462 void make_progressbar(float value, video::IImage *image);
464 void brighten(video::IImage *image);
467 Generate image based on a string like "stone.png" or "[crack0".
468 if baseimg is NULL, it is created. Otherwise stuff is made on it.
470 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
471 IrrlichtDevice *device, SourceImageCache *sourcecache);
474 Generates an image from a full string like
475 "stone.png^mineral_coal.png^[crack0".
477 This is used by buildMainAtlas().
479 video::IImage* generate_image_from_scratch(std::string name,
480 IrrlichtDevice *device, SourceImageCache *sourcecache);
483 This method generates all the textures
485 u32 TextureSource::getTextureIdDirect(const std::string &name)
487 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
489 // Empty name means texture 0
492 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
497 Calling only allowed from main thread
499 if(get_current_thread_id() != m_main_thread)
501 errorstream<<"TextureSource::getTextureIdDirect() "
502 "called not from main thread"<<std::endl;
507 See if texture already exists
510 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
512 core::map<std::string, u32>::Node *n;
513 n = m_name_to_id.find(name);
516 /*infostream<<"getTextureIdDirect(): \""<<name
517 <<"\" found in cache"<<std::endl;*/
518 return n->getValue();
522 /*infostream<<"getTextureIdDirect(): \""<<name
523 <<"\" NOT found in cache. Creating it."<<std::endl;*/
529 char separator = '^';
532 This is set to the id of the base image.
533 If left 0, there is no base image and a completely new image
536 u32 base_image_id = 0;
538 // Find last meta separator in name
539 s32 last_separator_position = -1;
540 for(s32 i=name.size()-1; i>=0; i--)
542 if(name[i] == separator)
544 last_separator_position = i;
549 If separator was found, construct the base name and make the
550 base image using a recursive call
552 std::string base_image_name;
553 if(last_separator_position != -1)
555 // Construct base name
556 base_image_name = name.substr(0, last_separator_position);
557 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
558 " to get base image of \""<<name<<"\" = \""
559 <<base_image_name<<"\""<<std::endl;*/
560 base_image_id = getTextureIdDirect(base_image_name);
563 //infostream<<"base_image_id="<<base_image_id<<std::endl;
565 video::IVideoDriver* driver = m_device->getVideoDriver();
568 video::ITexture *t = NULL;
571 An image will be built from files and then converted into a texture.
573 video::IImage *baseimg = NULL;
575 // If a base image was found, copy it to baseimg
576 if(base_image_id != 0)
578 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
580 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
582 video::IImage *image = ap.atlas_img;
586 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
587 <<"cache: \""<<base_image_name<<"\""
592 core::dimension2d<u32> dim = ap.intsize;
594 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
596 core::position2d<s32> pos_to(0,0);
597 core::position2d<s32> pos_from = ap.intpos;
601 v2s32(0,0), // position in target
602 core::rect<s32>(pos_from, dim) // from
605 /*infostream<<"getTextureIdDirect(): Loaded \""
606 <<base_image_name<<"\" from image cache"
612 Parse out the last part of the name of the image and act
616 std::string last_part_of_name = name.substr(last_separator_position+1);
617 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
619 // Generate image according to part of name
620 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
622 errorstream<<"getTextureIdDirect(): "
623 "failed to generate \""<<last_part_of_name<<"\""
627 // If no resulting image, print a warning
630 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
631 " create texture \""<<name<<"\""<<std::endl;
636 // Create texture from resulting image
637 t = driver->addTexture(name.c_str(), baseimg);
641 Add texture to caches (add NULL textures too)
644 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
646 u32 id = m_atlaspointer_cache.size();
652 core::dimension2d<u32> baseimg_dim(0,0);
654 baseimg_dim = baseimg->getDimension();
655 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
656 m_atlaspointer_cache.push_back(nap);
657 m_name_to_id.insert(name, id);
659 /*infostream<<"getTextureIdDirect(): "
660 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
665 std::string TextureSource::getTextureName(u32 id)
667 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
669 if(id >= m_atlaspointer_cache.size())
671 errorstream<<"TextureSource::getTextureName(): id="<<id
672 <<" >= m_atlaspointer_cache.size()="
673 <<m_atlaspointer_cache.size()<<std::endl;
677 return m_atlaspointer_cache[id].name;
681 AtlasPointer TextureSource::getTexture(u32 id)
683 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
685 if(id >= m_atlaspointer_cache.size())
686 return AtlasPointer(0, NULL);
688 return m_atlaspointer_cache[id].a;
691 void TextureSource::updateAP(AtlasPointer &ap)
693 AtlasPointer ap2 = getTexture(ap.id);
697 void TextureSource::processQueue()
702 if(m_get_texture_queue.size() > 0)
704 GetRequest<std::string, u32, u8, u8>
705 request = m_get_texture_queue.pop();
707 /*infostream<<"TextureSource::processQueue(): "
708 <<"got texture request with "
709 <<"name=\""<<request.key<<"\""
712 GetResult<std::string, u32, u8, u8>
714 result.key = request.key;
715 result.callers = request.callers;
716 result.item = getTextureIdDirect(request.key);
718 request.dest->push_back(result);
722 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
724 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
726 assert(get_current_thread_id() == m_main_thread);
728 m_sourcecache.insert(name, img, false);
731 void TextureSource::rebuildImagesAndTextures()
733 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
735 /*// Oh well... just clear everything, they'll load sometime.
736 m_atlaspointer_cache.clear();
737 m_name_to_id.clear();*/
739 video::IVideoDriver* driver = m_device->getVideoDriver();
741 // Remove source images from textures to disable inheriting textures
742 // from existing textures
743 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
744 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
745 sap->atlas_img->drop();
746 sap->atlas_img = NULL;
750 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
751 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
753 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
754 // Create texture from resulting image
755 video::ITexture *t = NULL;
757 t = driver->addTexture(sap->name.c_str(), img);
761 sap->a.pos = v2f(0,0);
762 sap->a.size = v2f(1,1);
764 sap->atlas_img = img;
765 sap->intpos = v2s32(0,0);
766 sap->intsize = img->getDimension();
770 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
772 assert(gamedef->tsrc() == this);
773 INodeDefManager *ndef = gamedef->ndef();
775 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
777 //return; // Disable (for testing)
779 video::IVideoDriver* driver = m_device->getVideoDriver();
782 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
784 // Create an image of the right size
785 core::dimension2d<u32> atlas_dim(1024,1024);
786 video::IImage *atlas_img =
787 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
789 if(atlas_img == NULL)
791 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
792 "image; not building texture atlas."<<std::endl;
797 Grab list of stuff to include in the texture atlas from the
798 main content features
801 core::map<std::string, bool> sourcelist;
803 for(u16 j=0; j<MAX_CONTENT+1; j++)
805 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
807 const ContentFeatures &f = ndef->get(j);
808 for(std::set<std::string>::const_iterator
809 i = f.used_texturenames.begin();
810 i != f.used_texturenames.end(); i++)
812 std::string name = *i;
813 sourcelist[name] = true;
815 if(f.often_contains_mineral){
816 for(int k=1; k<MINERAL_COUNT; k++){
817 std::string mineraltexture = mineral_block_texture(k);
818 std::string fulltexture = name + "^" + mineraltexture;
819 sourcelist[fulltexture] = true;
825 infostream<<"Creating texture atlas out of textures: ";
826 for(core::map<std::string, bool>::Iterator
827 i = sourcelist.getIterator();
828 i.atEnd() == false; i++)
830 std::string name = i.getNode()->getKey();
831 infostream<<"\""<<name<<"\" ";
833 infostream<<std::endl;
835 // Padding to disallow texture bleeding
838 s32 column_width = 256;
839 s32 column_padding = 16;
842 First pass: generate almost everything
844 core::position2d<s32> pos_in_atlas(0,0);
846 pos_in_atlas.Y = padding;
848 for(core::map<std::string, bool>::Iterator
849 i = sourcelist.getIterator();
850 i.atEnd() == false; i++)
852 std::string name = i.getNode()->getKey();
854 // Generate image by name
855 video::IImage *img2 = generate_image_from_scratch(name, m_device,
859 errorstream<<"TextureSource::buildMainAtlas(): "
860 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
864 core::dimension2d<u32> dim = img2->getDimension();
866 // Don't add to atlas if image is large
867 core::dimension2d<u32> max_size_in_atlas(32,32);
868 if(dim.Width > max_size_in_atlas.Width
869 || dim.Height > max_size_in_atlas.Height)
871 infostream<<"TextureSource::buildMainAtlas(): Not adding "
872 <<"\""<<name<<"\" because image is large"<<std::endl;
876 // Wrap columns and stop making atlas if atlas is full
877 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
879 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
880 errorstream<<"TextureSource::buildMainAtlas(): "
881 <<"Atlas is full, not adding more textures."
885 pos_in_atlas.Y = padding;
886 pos_in_atlas.X += column_width + column_padding;
889 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
890 <<"\" to texture atlas"<<std::endl;*/
892 // Tile it a few times in the X direction
893 u16 xwise_tiling = column_width / dim.Width;
894 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
896 for(u32 j=0; j<xwise_tiling; j++)
898 // Copy the copy to the atlas
899 img2->copyToWithAlpha(atlas_img,
900 pos_in_atlas + v2s32(j*dim.Width,0),
901 core::rect<s32>(v2s32(0,0), dim),
902 video::SColor(255,255,255,255),
906 // Copy the borders a few times to disallow texture bleeding
907 for(u32 side=0; side<2; side++) // top and bottom
908 for(s32 y0=0; y0<padding; y0++)
909 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
915 dst_y = y0 + pos_in_atlas.Y + dim.Height;
916 src_y = pos_in_atlas.Y + dim.Height - 1;
920 dst_y = -y0 + pos_in_atlas.Y-1;
921 src_y = pos_in_atlas.Y;
923 s32 x = x0 + pos_in_atlas.X;
924 video::SColor c = atlas_img->getPixel(x, src_y);
925 atlas_img->setPixel(x,dst_y,c);
931 Add texture to caches
934 bool reuse_old_id = false;
935 u32 id = m_atlaspointer_cache.size();
936 // Check old id without fetching a texture
937 core::map<std::string, u32>::Node *n;
938 n = m_name_to_id.find(name);
939 // If it exists, we will replace the old definition
943 /*infostream<<"TextureSource::buildMainAtlas(): "
944 <<"Replacing old AtlasPointer"<<std::endl;*/
947 // Create AtlasPointer
949 ap.atlas = NULL; // Set on the second pass
950 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
951 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
952 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
953 (float)dim.Width/(float)atlas_dim.Height);
954 ap.tiled = xwise_tiling;
956 // Create SourceAtlasPointer and add to containers
957 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
959 m_atlaspointer_cache[id] = nap;
961 m_atlaspointer_cache.push_back(nap);
962 m_name_to_id[name] = id;
964 // Increment position
965 pos_in_atlas.Y += dim.Height + padding * 2;
971 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
975 Second pass: set texture pointer in generated AtlasPointers
977 for(core::map<std::string, bool>::Iterator
978 i = sourcelist.getIterator();
979 i.atEnd() == false; i++)
981 std::string name = i.getNode()->getKey();
982 if(m_name_to_id.find(name) == NULL)
984 u32 id = m_name_to_id[name];
985 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
986 m_atlaspointer_cache[id].a.atlas = t;
990 Write image to file so that it can be inspected
992 /*std::string atlaspath = porting::path_userdata
993 + DIR_DELIM + "generated_texture_atlas.png";
994 infostream<<"Removing and writing texture atlas for inspection to "
995 <<atlaspath<<std::endl;
996 fs::RecursiveDelete(atlaspath);
997 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1000 video::IImage* generate_image_from_scratch(std::string name,
1001 IrrlichtDevice *device, SourceImageCache *sourcecache)
1003 /*infostream<<"generate_image_from_scratch(): "
1004 "\""<<name<<"\""<<std::endl;*/
1006 video::IVideoDriver* driver = device->getVideoDriver();
1013 video::IImage *baseimg = NULL;
1015 char separator = '^';
1017 // Find last meta separator in name
1018 s32 last_separator_position = -1;
1019 for(s32 i=name.size()-1; i>=0; i--)
1021 if(name[i] == separator)
1023 last_separator_position = i;
1028 /*infostream<<"generate_image_from_scratch(): "
1029 <<"last_separator_position="<<last_separator_position
1033 If separator was found, construct the base name and make the
1034 base image using a recursive call
1036 std::string base_image_name;
1037 if(last_separator_position != -1)
1039 // Construct base name
1040 base_image_name = name.substr(0, last_separator_position);
1041 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1042 " to get base image of \""<<name<<"\" = \""
1043 <<base_image_name<<"\""<<std::endl;*/
1044 baseimg = generate_image_from_scratch(base_image_name, device,
1049 Parse out the last part of the name of the image and act
1053 std::string last_part_of_name = name.substr(last_separator_position+1);
1054 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1056 // Generate image according to part of name
1057 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1059 errorstream<<"generate_image_from_scratch(): "
1060 "failed to generate \""<<last_part_of_name<<"\""
1068 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1069 IrrlichtDevice *device, SourceImageCache *sourcecache)
1071 video::IVideoDriver* driver = device->getVideoDriver();
1074 // Stuff starting with [ are special commands
1075 if(part_of_name[0] != '[')
1077 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1081 errorstream<<"generate_image(): Could not load image \""
1082 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1083 errorstream<<"generate_image(): Creating a dummy"
1084 <<" image for \""<<part_of_name<<"\""<<std::endl;
1086 // Just create a dummy image
1087 //core::dimension2d<u32> dim(2,2);
1088 core::dimension2d<u32> dim(1,1);
1089 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1091 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1092 image->setPixel(1,0, video::SColor(255,0,255,0));
1093 image->setPixel(0,1, video::SColor(255,0,0,255));
1094 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1095 image->setPixel(0,0, video::SColor(255,myrand()%256,
1096 myrand()%256,myrand()%256));
1097 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1098 myrand()%256,myrand()%256));
1099 image->setPixel(0,1, video::SColor(255,myrand()%256,
1100 myrand()%256,myrand()%256));
1101 image->setPixel(1,1, video::SColor(255,myrand()%256,
1102 myrand()%256,myrand()%256));*/
1105 // If base image is NULL, load as base.
1108 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1110 Copy it this way to get an alpha channel.
1111 Otherwise images with alpha cannot be blitted on
1112 images that don't have alpha in the original file.
1114 core::dimension2d<u32> dim = image->getDimension();
1115 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1116 image->copyTo(baseimg);
1119 // Else blit on base.
1122 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1123 // Size of the copied area
1124 core::dimension2d<u32> dim = image->getDimension();
1125 //core::dimension2d<u32> dim(16,16);
1126 // Position to copy the blitted to in the base image
1127 core::position2d<s32> pos_to(0,0);
1128 // Position to copy the blitted from in the blitted image
1129 core::position2d<s32> pos_from(0,0);
1131 image->copyToWithAlpha(baseimg, pos_to,
1132 core::rect<s32>(pos_from, dim),
1133 video::SColor(255,255,255,255),
1141 // A special texture modification
1143 /*infostream<<"generate_image(): generating special "
1144 <<"modification \""<<part_of_name<<"\""
1148 This is the simplest of all; it just adds stuff to the
1149 name so that a separate texture is created.
1151 It is used to make textures for stuff that doesn't want
1152 to implement getting the texture from a bigger texture
1155 if(part_of_name == "[forcesingle")
1160 Adds a cracking texture
1162 else if(part_of_name.substr(0,6) == "[crack")
1166 errorstream<<"generate_image(): baseimg==NULL "
1167 <<"for part_of_name=\""<<part_of_name
1168 <<"\", cancelling."<<std::endl;
1172 // Crack image number
1173 u16 progression = stoi(part_of_name.substr(6));
1175 // Size of the base image
1176 core::dimension2d<u32> dim_base = baseimg->getDimension();
1181 It is an image with a number of cracking stages
1184 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1188 // Dimension of original image
1189 core::dimension2d<u32> dim_crack
1190 = img_crack->getDimension();
1191 // Count of crack stages
1192 u32 crack_count = dim_crack.Height / dim_crack.Width;
1193 // Limit progression
1194 if(progression > crack_count-1)
1195 progression = crack_count-1;
1196 // Dimension of a single scaled crack stage
1197 core::dimension2d<u32> dim_crack_scaled_single(
1201 // Dimension of scaled size
1202 core::dimension2d<u32> dim_crack_scaled(
1203 dim_crack_scaled_single.Width,
1204 dim_crack_scaled_single.Height * crack_count
1206 // Create scaled crack image
1207 video::IImage *img_crack_scaled = driver->createImage(
1208 video::ECF_A8R8G8B8, dim_crack_scaled);
1209 if(img_crack_scaled)
1211 // Scale crack image by copying
1212 img_crack->copyToScaling(img_crack_scaled);
1214 // Position to copy the crack from
1215 core::position2d<s32> pos_crack_scaled(
1217 dim_crack_scaled_single.Height * progression
1220 // This tiling does nothing currently but is useful
1221 for(u32 y0=0; y0<dim_base.Height
1222 / dim_crack_scaled_single.Height; y0++)
1223 for(u32 x0=0; x0<dim_base.Width
1224 / dim_crack_scaled_single.Width; x0++)
1226 // Position to copy the crack to in the base image
1227 core::position2d<s32> pos_base(
1228 x0*dim_crack_scaled_single.Width,
1229 y0*dim_crack_scaled_single.Height
1231 // Rectangle to copy the crack from on the scaled image
1232 core::rect<s32> rect_crack_scaled(
1234 dim_crack_scaled_single
1237 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
1239 video::SColor(255,255,255,255),
1243 img_crack_scaled->drop();
1250 [combine:WxH:X,Y=filename:X,Y=filename2
1251 Creates a bigger texture from an amount of smaller ones
1253 else if(part_of_name.substr(0,8) == "[combine")
1255 Strfnd sf(part_of_name);
1257 u32 w0 = stoi(sf.next("x"));
1258 u32 h0 = stoi(sf.next(":"));
1259 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1260 core::dimension2d<u32> dim(w0,h0);
1261 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1262 while(sf.atend() == false)
1264 u32 x = stoi(sf.next(","));
1265 u32 y = stoi(sf.next("="));
1266 std::string filename = sf.next(":");
1267 infostream<<"Adding \""<<filename
1268 <<"\" to combined ("<<x<<","<<y<<")"
1270 video::IImage *img = sourcecache->getOrLoad(filename, device);
1273 core::dimension2d<u32> dim = img->getDimension();
1274 infostream<<"Size "<<dim.Width
1275 <<"x"<<dim.Height<<std::endl;
1276 core::position2d<s32> pos_base(x, y);
1277 video::IImage *img2 =
1278 driver->createImage(video::ECF_A8R8G8B8, dim);
1281 img2->copyToWithAlpha(baseimg, pos_base,
1282 core::rect<s32>(v2s32(0,0), dim),
1283 video::SColor(255,255,255,255),
1289 infostream<<"img==NULL"<<std::endl;
1295 Adds a progress bar, 0.0 <= N <= 1.0
1297 else if(part_of_name.substr(0,12) == "[progressbar")
1301 errorstream<<"generate_image(): baseimg==NULL "
1302 <<"for part_of_name=\""<<part_of_name
1303 <<"\", cancelling."<<std::endl;
1307 float value = stof(part_of_name.substr(12));
1308 make_progressbar(value, baseimg);
1313 else if(part_of_name.substr(0,9) == "[brighten")
1317 errorstream<<"generate_image(): baseimg==NULL "
1318 <<"for part_of_name=\""<<part_of_name
1319 <<"\", cancelling."<<std::endl;
1327 Make image completely opaque.
1328 Used for the leaves texture when in old leaves mode, so
1329 that the transparent parts don't look completely black
1330 when simple alpha channel is used for rendering.
1332 else if(part_of_name.substr(0,8) == "[noalpha")
1336 errorstream<<"generate_image(): baseimg==NULL "
1337 <<"for part_of_name=\""<<part_of_name
1338 <<"\", cancelling."<<std::endl;
1342 core::dimension2d<u32> dim = baseimg->getDimension();
1344 // Set alpha to full
1345 for(u32 y=0; y<dim.Height; y++)
1346 for(u32 x=0; x<dim.Width; x++)
1348 video::SColor c = baseimg->getPixel(x,y);
1350 baseimg->setPixel(x,y,c);
1354 "[makealpha:R,G,B:filename.png"
1355 Use an image with converting one color to transparent.
1357 else if(part_of_name.substr(0,11) == "[makealpha:")
1361 errorstream<<"generate_image(): baseimg!=NULL "
1362 <<"for part_of_name=\""<<part_of_name
1363 <<"\", cancelling."<<std::endl;
1367 Strfnd sf(part_of_name.substr(11));
1368 u32 r1 = stoi(sf.next(","));
1369 u32 g1 = stoi(sf.next(","));
1370 u32 b1 = stoi(sf.next(":"));
1371 std::string filename = sf.next("");
1373 /*infostream<<"generate_image(): Loading file \""<<filename
1374 <<"\""<<std::endl;*/
1376 video::IImage *image = sourcecache->getOrLoad(filename, device);
1380 errorstream<<"generate_image(): Loading file \""
1381 <<filename<<"\" failed"<<std::endl;
1385 core::dimension2d<u32> dim = image->getDimension();
1386 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1389 image->copyTo(baseimg);
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);
1408 "[makealpha2:R,G,B;R2,G2,B2:filename.png"
1409 Use an image with converting two colors to transparent.
1411 else if(part_of_name.substr(0,12) == "[makealpha2:")
1415 errorstream<<"generate_image(): baseimg!=NULL "
1416 <<"for part_of_name=\""<<part_of_name
1417 <<"\", cancelling."<<std::endl;
1421 Strfnd sf(part_of_name.substr(12));
1422 u32 r1 = stoi(sf.next(","));
1423 u32 g1 = stoi(sf.next(","));
1424 u32 b1 = stoi(sf.next(";"));
1425 u32 r2 = stoi(sf.next(","));
1426 u32 g2 = stoi(sf.next(","));
1427 u32 b2 = stoi(sf.next(":"));
1428 std::string filename = sf.next("");
1430 /*infostream<<"generate_image(): Loading filename \""<<filename
1431 <<"\""<<std::endl;*/
1433 video::IImage *image = sourcecache->getOrLoad(filename, device);
1437 errorstream<<"generate_image(): Loading file \""
1438 <<filename<<"\" failed"<<std::endl;
1442 core::dimension2d<u32> dim = image->getDimension();
1443 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1446 image->copyTo(baseimg);
1450 for(u32 y=0; y<dim.Height; y++)
1451 for(u32 x=0; x<dim.Width; x++)
1453 video::SColor c = baseimg->getPixel(x,y);
1455 u32 g = c.getGreen();
1456 u32 b = c.getBlue();
1457 if(!(r == r1 && g == g1 && b == b1) &&
1458 !(r == r2 && g == g2 && b == b2))
1461 baseimg->setPixel(x,y,c);
1466 [inventorycube{topimage{leftimage{rightimage
1467 In every subimage, replace ^ with &.
1468 Create an "inventory cube".
1469 NOTE: This should be used only on its own.
1470 Example (a grass block (not actually used in game):
1471 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1473 else if(part_of_name.substr(0,14) == "[inventorycube")
1477 errorstream<<"generate_image(): baseimg!=NULL "
1478 <<"for part_of_name=\""<<part_of_name
1479 <<"\", cancelling."<<std::endl;
1483 str_replace_char(part_of_name, '&', '^');
1484 Strfnd sf(part_of_name);
1486 std::string imagename_top = sf.next("{");
1487 std::string imagename_left = sf.next("{");
1488 std::string imagename_right = sf.next("{");
1491 // TODO: Create cube with different textures on different sides
1493 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
1495 errorstream<<"generate_image(): EVDF_RENDER_TO_TARGET"
1496 " not supported. Creating fallback image"<<std::endl;
1497 baseimg = generate_image_from_scratch(
1498 imagename_top, device, sourcecache);
1504 //infostream<<"inventorycube w="<<w0<<" h="<<h0<<std::endl;
1505 core::dimension2d<u32> dim(w0,h0);
1507 // Generate images for the faces of the cube
1508 video::IImage *img_top = generate_image_from_scratch(
1509 imagename_top, device, sourcecache);
1510 video::IImage *img_left = generate_image_from_scratch(
1511 imagename_left, device, sourcecache);
1512 video::IImage *img_right = generate_image_from_scratch(
1513 imagename_right, device, sourcecache);
1514 assert(img_top && img_left && img_right);
1516 // Create textures from images
1517 // TODO: Use them all
1518 video::ITexture *texture_top = driver->addTexture(
1519 (imagename_top + "__temp__").c_str(), img_top);
1520 assert(texture_top);
1527 // Create render target texture
1528 video::ITexture *rtt = NULL;
1529 std::string rtt_name = part_of_name + "_RTT";
1530 rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str(),
1531 video::ECF_A8R8G8B8);
1534 // Set render target
1535 driver->setRenderTarget(rtt, true, true,
1536 video::SColor(0,0,0,0));
1538 // Get a scene manager
1539 scene::ISceneManager *smgr_main = device->getSceneManager();
1541 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
1546 - An unit cube is centered at 0,0,0
1547 - Camera looks at cube from Y+, Z- towards Y-, Z+
1548 NOTE: Cube has to be changed to something else because
1549 the textures cannot be set individually (or can they?)
1552 scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1,
1553 v3f(0,0,0), v3f(0, 45, 0));
1554 // Set texture of cube
1555 cube->setMaterialTexture(0, texture_top);
1556 //cube->setMaterialFlag(video::EMF_LIGHTING, false);
1557 cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false);
1558 cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
1560 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
1561 v3f(0, 1.0, -1.5), v3f(0, 0, 0));
1562 // Set orthogonal projection
1563 core::CMatrix4<f32> pm;
1564 pm.buildProjectionMatrixOrthoLH(1.65, 1.65, 0, 100);
1565 camera->setProjectionMatrix(pm, true);
1567 /*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0,
1568 v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000);
1570 smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2));
1573 driver->beginScene(true, true, video::SColor(0,0,0,0));
1577 // NOTE: The scene nodes should not be dropped, otherwise
1578 // smgr->drop() segfaults
1582 // Drop scene manager
1585 // Unset render target
1586 driver->setRenderTarget(0, true, true, 0);
1588 // Free textures of images
1589 // TODO: When all are used, free them all
1590 driver->removeTexture(texture_top);
1592 // Create image of render target
1593 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1597 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1601 image->copyTo(baseimg);
1608 errorstream<<"generate_image(): Invalid "
1609 " modification: \""<<part_of_name<<"\""<<std::endl;
1616 void make_progressbar(float value, video::IImage *image)
1621 core::dimension2d<u32> size = image->getDimension();
1623 u32 barheight = size.Height/16;
1624 u32 barpad_x = size.Width/16;
1625 u32 barpad_y = size.Height/16;
1626 u32 barwidth = size.Width - barpad_x*2;
1627 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
1629 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
1631 video::SColor active(255,255,0,0);
1632 video::SColor inactive(255,0,0,0);
1633 for(u32 x0=0; x0<barwidth; x0++)
1640 u32 x = x0 + barpos.X;
1641 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
1643 image->setPixel(x,y, *c);
1648 void brighten(video::IImage *image)
1653 core::dimension2d<u32> dim = image->getDimension();
1655 for(u32 y=0; y<dim.Height; y++)
1656 for(u32 x=0; x<dim.Width; x++)
1658 video::SColor c = image->getPixel(x,y);
1659 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1660 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1661 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1662 image->setPixel(x,y,c);