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);
465 Generate image based on a string like "stone.png" or "[crack0".
466 if baseimg is NULL, it is created. Otherwise stuff is made on it.
468 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
469 IrrlichtDevice *device, SourceImageCache *sourcecache);
472 Generates an image from a full string like
473 "stone.png^mineral_coal.png^[crack0".
475 This is used by buildMainAtlas().
477 video::IImage* generate_image_from_scratch(std::string name,
478 IrrlichtDevice *device, SourceImageCache *sourcecache);
481 This method generates all the textures
483 u32 TextureSource::getTextureIdDirect(const std::string &name)
485 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
487 // Empty name means texture 0
490 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
495 Calling only allowed from main thread
497 if(get_current_thread_id() != m_main_thread)
499 errorstream<<"TextureSource::getTextureIdDirect() "
500 "called not from main thread"<<std::endl;
505 See if texture already exists
508 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
510 core::map<std::string, u32>::Node *n;
511 n = m_name_to_id.find(name);
514 /*infostream<<"getTextureIdDirect(): \""<<name
515 <<"\" found in cache"<<std::endl;*/
516 return n->getValue();
520 /*infostream<<"getTextureIdDirect(): \""<<name
521 <<"\" NOT found in cache. Creating it."<<std::endl;*/
527 char separator = '^';
530 This is set to the id of the base image.
531 If left 0, there is no base image and a completely new image
534 u32 base_image_id = 0;
536 // Find last meta separator in name
537 s32 last_separator_position = -1;
538 for(s32 i=name.size()-1; i>=0; i--)
540 if(name[i] == separator)
542 last_separator_position = i;
547 If separator was found, construct the base name and make the
548 base image using a recursive call
550 std::string base_image_name;
551 if(last_separator_position != -1)
553 // Construct base name
554 base_image_name = name.substr(0, last_separator_position);
555 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
556 " to get base image of \""<<name<<"\" = \""
557 <<base_image_name<<"\""<<std::endl;*/
558 base_image_id = getTextureIdDirect(base_image_name);
561 //infostream<<"base_image_id="<<base_image_id<<std::endl;
563 video::IVideoDriver* driver = m_device->getVideoDriver();
566 video::ITexture *t = NULL;
569 An image will be built from files and then converted into a texture.
571 video::IImage *baseimg = NULL;
573 // If a base image was found, copy it to baseimg
574 if(base_image_id != 0)
576 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
578 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
580 video::IImage *image = ap.atlas_img;
584 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
585 <<"cache: \""<<base_image_name<<"\""
590 core::dimension2d<u32> dim = ap.intsize;
592 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
594 core::position2d<s32> pos_to(0,0);
595 core::position2d<s32> pos_from = ap.intpos;
599 v2s32(0,0), // position in target
600 core::rect<s32>(pos_from, dim) // from
603 /*infostream<<"getTextureIdDirect(): Loaded \""
604 <<base_image_name<<"\" from image cache"
610 Parse out the last part of the name of the image and act
614 std::string last_part_of_name = name.substr(last_separator_position+1);
615 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
617 // Generate image according to part of name
618 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
620 errorstream<<"getTextureIdDirect(): "
621 "failed to generate \""<<last_part_of_name<<"\""
625 // If no resulting image, print a warning
628 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
629 " create texture \""<<name<<"\""<<std::endl;
634 // Create texture from resulting image
635 t = driver->addTexture(name.c_str(), baseimg);
639 Add texture to caches (add NULL textures too)
642 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
644 u32 id = m_atlaspointer_cache.size();
650 core::dimension2d<u32> baseimg_dim(0,0);
652 baseimg_dim = baseimg->getDimension();
653 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
654 m_atlaspointer_cache.push_back(nap);
655 m_name_to_id.insert(name, id);
657 /*infostream<<"getTextureIdDirect(): "
658 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
663 std::string TextureSource::getTextureName(u32 id)
665 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
667 if(id >= m_atlaspointer_cache.size())
669 errorstream<<"TextureSource::getTextureName(): id="<<id
670 <<" >= m_atlaspointer_cache.size()="
671 <<m_atlaspointer_cache.size()<<std::endl;
675 return m_atlaspointer_cache[id].name;
679 AtlasPointer TextureSource::getTexture(u32 id)
681 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
683 if(id >= m_atlaspointer_cache.size())
684 return AtlasPointer(0, NULL);
686 return m_atlaspointer_cache[id].a;
689 void TextureSource::updateAP(AtlasPointer &ap)
691 AtlasPointer ap2 = getTexture(ap.id);
695 void TextureSource::processQueue()
700 if(m_get_texture_queue.size() > 0)
702 GetRequest<std::string, u32, u8, u8>
703 request = m_get_texture_queue.pop();
705 /*infostream<<"TextureSource::processQueue(): "
706 <<"got texture request with "
707 <<"name=\""<<request.key<<"\""
710 GetResult<std::string, u32, u8, u8>
712 result.key = request.key;
713 result.callers = request.callers;
714 result.item = getTextureIdDirect(request.key);
716 request.dest->push_back(result);
720 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
722 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
724 assert(get_current_thread_id() == m_main_thread);
726 m_sourcecache.insert(name, img, false);
729 void TextureSource::rebuildImagesAndTextures()
731 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
733 /*// Oh well... just clear everything, they'll load sometime.
734 m_atlaspointer_cache.clear();
735 m_name_to_id.clear();*/
737 video::IVideoDriver* driver = m_device->getVideoDriver();
739 // Remove source images from textures to disable inheriting textures
740 // from existing textures
741 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
742 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
743 sap->atlas_img->drop();
744 sap->atlas_img = NULL;
748 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
749 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
751 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
752 // Create texture from resulting image
753 video::ITexture *t = NULL;
755 t = driver->addTexture(sap->name.c_str(), img);
759 sap->a.pos = v2f(0,0);
760 sap->a.size = v2f(1,1);
762 sap->atlas_img = img;
763 sap->intpos = v2s32(0,0);
764 sap->intsize = img->getDimension();
768 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
770 assert(gamedef->tsrc() == this);
771 INodeDefManager *ndef = gamedef->ndef();
773 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
775 //return; // Disable (for testing)
777 video::IVideoDriver* driver = m_device->getVideoDriver();
780 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
782 // Create an image of the right size
783 core::dimension2d<u32> atlas_dim(1024,1024);
784 video::IImage *atlas_img =
785 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
787 if(atlas_img == NULL)
789 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
790 "image; not building texture atlas."<<std::endl;
795 Grab list of stuff to include in the texture atlas from the
796 main content features
799 core::map<std::string, bool> sourcelist;
801 for(u16 j=0; j<MAX_CONTENT+1; j++)
803 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
805 const ContentFeatures &f = ndef->get(j);
806 for(std::set<std::string>::const_iterator
807 i = f.used_texturenames.begin();
808 i != f.used_texturenames.end(); i++)
810 std::string name = *i;
811 sourcelist[name] = true;
813 if(f.often_contains_mineral){
814 for(int k=1; k<MINERAL_COUNT; k++){
815 std::string mineraltexture = mineral_block_texture(k);
816 std::string fulltexture = name + "^" + mineraltexture;
817 sourcelist[fulltexture] = true;
823 infostream<<"Creating texture atlas out of textures: ";
824 for(core::map<std::string, bool>::Iterator
825 i = sourcelist.getIterator();
826 i.atEnd() == false; i++)
828 std::string name = i.getNode()->getKey();
829 infostream<<"\""<<name<<"\" ";
831 infostream<<std::endl;
833 // Padding to disallow texture bleeding
836 s32 column_width = 256;
837 s32 column_padding = 16;
840 First pass: generate almost everything
842 core::position2d<s32> pos_in_atlas(0,0);
844 pos_in_atlas.Y = padding;
846 for(core::map<std::string, bool>::Iterator
847 i = sourcelist.getIterator();
848 i.atEnd() == false; i++)
850 std::string name = i.getNode()->getKey();
852 // Generate image by name
853 video::IImage *img2 = generate_image_from_scratch(name, m_device,
857 errorstream<<"TextureSource::buildMainAtlas(): "
858 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
862 core::dimension2d<u32> dim = img2->getDimension();
864 // Don't add to atlas if image is large
865 core::dimension2d<u32> max_size_in_atlas(32,32);
866 if(dim.Width > max_size_in_atlas.Width
867 || dim.Height > max_size_in_atlas.Height)
869 infostream<<"TextureSource::buildMainAtlas(): Not adding "
870 <<"\""<<name<<"\" because image is large"<<std::endl;
874 // Wrap columns and stop making atlas if atlas is full
875 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
877 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
878 errorstream<<"TextureSource::buildMainAtlas(): "
879 <<"Atlas is full, not adding more textures."
883 pos_in_atlas.Y = padding;
884 pos_in_atlas.X += column_width + column_padding;
887 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
888 <<"\" to texture atlas"<<std::endl;*/
890 // Tile it a few times in the X direction
891 u16 xwise_tiling = column_width / dim.Width;
892 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
894 for(u32 j=0; j<xwise_tiling; j++)
896 // Copy the copy to the atlas
897 img2->copyToWithAlpha(atlas_img,
898 pos_in_atlas + v2s32(j*dim.Width,0),
899 core::rect<s32>(v2s32(0,0), dim),
900 video::SColor(255,255,255,255),
904 // Copy the borders a few times to disallow texture bleeding
905 for(u32 side=0; side<2; side++) // top and bottom
906 for(s32 y0=0; y0<padding; y0++)
907 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
913 dst_y = y0 + pos_in_atlas.Y + dim.Height;
914 src_y = pos_in_atlas.Y + dim.Height - 1;
918 dst_y = -y0 + pos_in_atlas.Y-1;
919 src_y = pos_in_atlas.Y;
921 s32 x = x0 + pos_in_atlas.X;
922 video::SColor c = atlas_img->getPixel(x, src_y);
923 atlas_img->setPixel(x,dst_y,c);
929 Add texture to caches
932 bool reuse_old_id = false;
933 u32 id = m_atlaspointer_cache.size();
934 // Check old id without fetching a texture
935 core::map<std::string, u32>::Node *n;
936 n = m_name_to_id.find(name);
937 // If it exists, we will replace the old definition
941 /*infostream<<"TextureSource::buildMainAtlas(): "
942 <<"Replacing old AtlasPointer"<<std::endl;*/
945 // Create AtlasPointer
947 ap.atlas = NULL; // Set on the second pass
948 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
949 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
950 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
951 (float)dim.Width/(float)atlas_dim.Height);
952 ap.tiled = xwise_tiling;
954 // Create SourceAtlasPointer and add to containers
955 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
957 m_atlaspointer_cache[id] = nap;
959 m_atlaspointer_cache.push_back(nap);
960 m_name_to_id[name] = id;
962 // Increment position
963 pos_in_atlas.Y += dim.Height + padding * 2;
969 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
973 Second pass: set texture pointer in generated AtlasPointers
975 for(core::map<std::string, bool>::Iterator
976 i = sourcelist.getIterator();
977 i.atEnd() == false; i++)
979 std::string name = i.getNode()->getKey();
980 if(m_name_to_id.find(name) == NULL)
982 u32 id = m_name_to_id[name];
983 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
984 m_atlaspointer_cache[id].a.atlas = t;
988 Write image to file so that it can be inspected
990 /*std::string atlaspath = porting::path_userdata
991 + DIR_DELIM + "generated_texture_atlas.png";
992 infostream<<"Removing and writing texture atlas for inspection to "
993 <<atlaspath<<std::endl;
994 fs::RecursiveDelete(atlaspath);
995 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
998 video::IImage* generate_image_from_scratch(std::string name,
999 IrrlichtDevice *device, SourceImageCache *sourcecache)
1001 /*infostream<<"generate_image_from_scratch(): "
1002 "\""<<name<<"\""<<std::endl;*/
1004 video::IVideoDriver* driver = device->getVideoDriver();
1011 video::IImage *baseimg = NULL;
1013 char separator = '^';
1015 // Find last meta separator in name
1016 s32 last_separator_position = -1;
1017 for(s32 i=name.size()-1; i>=0; i--)
1019 if(name[i] == separator)
1021 last_separator_position = i;
1026 /*infostream<<"generate_image_from_scratch(): "
1027 <<"last_separator_position="<<last_separator_position
1031 If separator was found, construct the base name and make the
1032 base image using a recursive call
1034 std::string base_image_name;
1035 if(last_separator_position != -1)
1037 // Construct base name
1038 base_image_name = name.substr(0, last_separator_position);
1039 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1040 " to get base image of \""<<name<<"\" = \""
1041 <<base_image_name<<"\""<<std::endl;*/
1042 baseimg = generate_image_from_scratch(base_image_name, device,
1047 Parse out the last part of the name of the image and act
1051 std::string last_part_of_name = name.substr(last_separator_position+1);
1052 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1054 // Generate image according to part of name
1055 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1057 errorstream<<"generate_image_from_scratch(): "
1058 "failed to generate \""<<last_part_of_name<<"\""
1066 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1067 IrrlichtDevice *device, SourceImageCache *sourcecache)
1069 video::IVideoDriver* driver = device->getVideoDriver();
1072 // Stuff starting with [ are special commands
1073 if(part_of_name[0] != '[')
1075 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1079 errorstream<<"generate_image(): Could not load image \""
1080 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1081 errorstream<<"generate_image(): Creating a dummy"
1082 <<" image for \""<<part_of_name<<"\""<<std::endl;
1084 // Just create a dummy image
1085 //core::dimension2d<u32> dim(2,2);
1086 core::dimension2d<u32> dim(1,1);
1087 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1089 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1090 image->setPixel(1,0, video::SColor(255,0,255,0));
1091 image->setPixel(0,1, video::SColor(255,0,0,255));
1092 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1093 image->setPixel(0,0, video::SColor(255,myrand()%256,
1094 myrand()%256,myrand()%256));
1095 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1096 myrand()%256,myrand()%256));
1097 image->setPixel(0,1, video::SColor(255,myrand()%256,
1098 myrand()%256,myrand()%256));
1099 image->setPixel(1,1, video::SColor(255,myrand()%256,
1100 myrand()%256,myrand()%256));*/
1103 // If base image is NULL, load as base.
1106 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1108 Copy it this way to get an alpha channel.
1109 Otherwise images with alpha cannot be blitted on
1110 images that don't have alpha in the original file.
1112 core::dimension2d<u32> dim = image->getDimension();
1113 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1114 image->copyTo(baseimg);
1117 // Else blit on base.
1120 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1121 // Size of the copied area
1122 core::dimension2d<u32> dim = image->getDimension();
1123 //core::dimension2d<u32> dim(16,16);
1124 // Position to copy the blitted to in the base image
1125 core::position2d<s32> pos_to(0,0);
1126 // Position to copy the blitted from in the blitted image
1127 core::position2d<s32> pos_from(0,0);
1129 image->copyToWithAlpha(baseimg, pos_to,
1130 core::rect<s32>(pos_from, dim),
1131 video::SColor(255,255,255,255),
1139 // A special texture modification
1141 /*infostream<<"generate_image(): generating special "
1142 <<"modification \""<<part_of_name<<"\""
1146 This is the simplest of all; it just adds stuff to the
1147 name so that a separate texture is created.
1149 It is used to make textures for stuff that doesn't want
1150 to implement getting the texture from a bigger texture
1153 if(part_of_name == "[forcesingle")
1158 Adds a cracking texture
1160 else if(part_of_name.substr(0,6) == "[crack")
1164 errorstream<<"generate_image(): baseimg==NULL "
1165 <<"for part_of_name=\""<<part_of_name
1166 <<"\", cancelling."<<std::endl;
1170 // Crack image number
1171 u16 progression = stoi(part_of_name.substr(6));
1173 // Size of the base image
1174 core::dimension2d<u32> dim_base = baseimg->getDimension();
1179 It is an image with a number of cracking stages
1182 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1186 // Dimension of original image
1187 core::dimension2d<u32> dim_crack
1188 = img_crack->getDimension();
1189 // Count of crack stages
1190 u32 crack_count = dim_crack.Height / dim_crack.Width;
1191 // Limit progression
1192 if(progression > crack_count-1)
1193 progression = crack_count-1;
1194 // Dimension of a single scaled crack stage
1195 core::dimension2d<u32> dim_crack_scaled_single(
1199 // Dimension of scaled size
1200 core::dimension2d<u32> dim_crack_scaled(
1201 dim_crack_scaled_single.Width,
1202 dim_crack_scaled_single.Height * crack_count
1204 // Create scaled crack image
1205 video::IImage *img_crack_scaled = driver->createImage(
1206 video::ECF_A8R8G8B8, dim_crack_scaled);
1207 if(img_crack_scaled)
1209 // Scale crack image by copying
1210 img_crack->copyToScaling(img_crack_scaled);
1212 // Position to copy the crack from
1213 core::position2d<s32> pos_crack_scaled(
1215 dim_crack_scaled_single.Height * progression
1218 // This tiling does nothing currently but is useful
1219 for(u32 y0=0; y0<dim_base.Height
1220 / dim_crack_scaled_single.Height; y0++)
1221 for(u32 x0=0; x0<dim_base.Width
1222 / dim_crack_scaled_single.Width; x0++)
1224 // Position to copy the crack to in the base image
1225 core::position2d<s32> pos_base(
1226 x0*dim_crack_scaled_single.Width,
1227 y0*dim_crack_scaled_single.Height
1229 // Rectangle to copy the crack from on the scaled image
1230 core::rect<s32> rect_crack_scaled(
1232 dim_crack_scaled_single
1235 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
1237 video::SColor(255,255,255,255),
1241 img_crack_scaled->drop();
1248 [combine:WxH:X,Y=filename:X,Y=filename2
1249 Creates a bigger texture from an amount of smaller ones
1251 else if(part_of_name.substr(0,8) == "[combine")
1253 Strfnd sf(part_of_name);
1255 u32 w0 = stoi(sf.next("x"));
1256 u32 h0 = stoi(sf.next(":"));
1257 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1258 core::dimension2d<u32> dim(w0,h0);
1259 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1260 while(sf.atend() == false)
1262 u32 x = stoi(sf.next(","));
1263 u32 y = stoi(sf.next("="));
1264 std::string filename = sf.next(":");
1265 infostream<<"Adding \""<<filename
1266 <<"\" to combined ("<<x<<","<<y<<")"
1268 video::IImage *img = sourcecache->getOrLoad(filename, device);
1271 core::dimension2d<u32> dim = img->getDimension();
1272 infostream<<"Size "<<dim.Width
1273 <<"x"<<dim.Height<<std::endl;
1274 core::position2d<s32> pos_base(x, y);
1275 video::IImage *img2 =
1276 driver->createImage(video::ECF_A8R8G8B8, dim);
1279 img2->copyToWithAlpha(baseimg, pos_base,
1280 core::rect<s32>(v2s32(0,0), dim),
1281 video::SColor(255,255,255,255),
1287 infostream<<"img==NULL"<<std::endl;
1293 Adds a progress bar, 0.0 <= N <= 1.0
1295 else if(part_of_name.substr(0,12) == "[progressbar")
1299 errorstream<<"generate_image(): baseimg==NULL "
1300 <<"for part_of_name=\""<<part_of_name
1301 <<"\", cancelling."<<std::endl;
1305 float value = stof(part_of_name.substr(12));
1306 make_progressbar(value, baseimg);
1309 "[noalpha:filename.png"
1310 Use an image without it's alpha channel.
1311 Used for the leaves texture when in old leaves mode, so
1312 that the transparent parts don't look completely black
1313 when simple alpha channel is used for rendering.
1315 else if(part_of_name.substr(0,8) == "[noalpha")
1319 errorstream<<"generate_image(): baseimg!=NULL "
1320 <<"for part_of_name=\""<<part_of_name
1321 <<"\", cancelling."<<std::endl;
1325 std::string filename = part_of_name.substr(9);
1327 std::string path = getTexturePath(filename.c_str());
1329 /*infostream<<"generate_image(): Loading file \""<<filename
1330 <<"\""<<std::endl;*/
1332 video::IImage *image = sourcecache->getOrLoad(filename, device);
1336 infostream<<"generate_image(): Loading path \""
1337 <<path<<"\" failed"<<std::endl;
1341 core::dimension2d<u32> dim = image->getDimension();
1342 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
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 = image->getPixel(x,y);
1350 image->setPixel(x,y,c);
1353 image->copyTo(baseimg);
1359 "[makealpha:R,G,B:filename.png"
1360 Use an image with converting one color to transparent.
1362 else if(part_of_name.substr(0,11) == "[makealpha:")
1366 errorstream<<"generate_image(): baseimg!=NULL "
1367 <<"for part_of_name=\""<<part_of_name
1368 <<"\", cancelling."<<std::endl;
1372 Strfnd sf(part_of_name.substr(11));
1373 u32 r1 = stoi(sf.next(","));
1374 u32 g1 = stoi(sf.next(","));
1375 u32 b1 = stoi(sf.next(":"));
1376 std::string filename = sf.next("");
1378 /*infostream<<"generate_image(): Loading file \""<<filename
1379 <<"\""<<std::endl;*/
1381 video::IImage *image = sourcecache->getOrLoad(filename, device);
1385 errorstream<<"generate_image(): Loading file \""
1386 <<filename<<"\" failed"<<std::endl;
1390 core::dimension2d<u32> dim = image->getDimension();
1391 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1394 image->copyTo(baseimg);
1398 for(u32 y=0; y<dim.Height; y++)
1399 for(u32 x=0; x<dim.Width; x++)
1401 video::SColor c = baseimg->getPixel(x,y);
1403 u32 g = c.getGreen();
1404 u32 b = c.getBlue();
1405 if(!(r == r1 && g == g1 && b == b1))
1408 baseimg->setPixel(x,y,c);
1413 "[makealpha2:R,G,B;R2,G2,B2:filename.png"
1414 Use an image with converting two colors to transparent.
1416 else if(part_of_name.substr(0,12) == "[makealpha2:")
1420 errorstream<<"generate_image(): baseimg!=NULL "
1421 <<"for part_of_name=\""<<part_of_name
1422 <<"\", cancelling."<<std::endl;
1426 Strfnd sf(part_of_name.substr(12));
1427 u32 r1 = stoi(sf.next(","));
1428 u32 g1 = stoi(sf.next(","));
1429 u32 b1 = stoi(sf.next(";"));
1430 u32 r2 = stoi(sf.next(","));
1431 u32 g2 = stoi(sf.next(","));
1432 u32 b2 = stoi(sf.next(":"));
1433 std::string filename = sf.next("");
1435 /*infostream<<"generate_image(): Loading filename \""<<filename
1436 <<"\""<<std::endl;*/
1438 video::IImage *image = sourcecache->getOrLoad(filename, device);
1442 errorstream<<"generate_image(): Loading file \""
1443 <<filename<<"\" failed"<<std::endl;
1447 core::dimension2d<u32> dim = image->getDimension();
1448 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1451 image->copyTo(baseimg);
1455 for(u32 y=0; y<dim.Height; y++)
1456 for(u32 x=0; x<dim.Width; x++)
1458 video::SColor c = baseimg->getPixel(x,y);
1460 u32 g = c.getGreen();
1461 u32 b = c.getBlue();
1462 if(!(r == r1 && g == g1 && b == b1) &&
1463 !(r == r2 && g == g2 && b == b2))
1466 baseimg->setPixel(x,y,c);
1471 [inventorycube{topimage{leftimage{rightimage
1472 In every subimage, replace ^ with &.
1473 Create an "inventory cube".
1474 NOTE: This should be used only on its own.
1475 Example (a grass block (not actually used in game):
1476 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1478 else if(part_of_name.substr(0,14) == "[inventorycube")
1482 errorstream<<"generate_image(): baseimg!=NULL "
1483 <<"for part_of_name=\""<<part_of_name
1484 <<"\", cancelling."<<std::endl;
1488 str_replace_char(part_of_name, '&', '^');
1489 Strfnd sf(part_of_name);
1491 std::string imagename_top = sf.next("{");
1492 std::string imagename_left = sf.next("{");
1493 std::string imagename_right = sf.next("{");
1496 // TODO: Create cube with different textures on different sides
1498 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
1500 errorstream<<"generate_image(): EVDF_RENDER_TO_TARGET"
1501 " not supported. Creating fallback image"<<std::endl;
1502 baseimg = generate_image_from_scratch(
1503 imagename_top, device, sourcecache);
1509 //infostream<<"inventorycube w="<<w0<<" h="<<h0<<std::endl;
1510 core::dimension2d<u32> dim(w0,h0);
1512 // Generate images for the faces of the cube
1513 video::IImage *img_top = generate_image_from_scratch(
1514 imagename_top, device, sourcecache);
1515 video::IImage *img_left = generate_image_from_scratch(
1516 imagename_left, device, sourcecache);
1517 video::IImage *img_right = generate_image_from_scratch(
1518 imagename_right, device, sourcecache);
1519 assert(img_top && img_left && img_right);
1521 // Create textures from images
1522 // TODO: Use them all
1523 video::ITexture *texture_top = driver->addTexture(
1524 (imagename_top + "__temp__").c_str(), img_top);
1525 assert(texture_top);
1532 // Create render target texture
1533 video::ITexture *rtt = NULL;
1534 std::string rtt_name = part_of_name + "_RTT";
1535 rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str(),
1536 video::ECF_A8R8G8B8);
1539 // Set render target
1540 driver->setRenderTarget(rtt, true, true,
1541 video::SColor(0,0,0,0));
1543 // Get a scene manager
1544 scene::ISceneManager *smgr_main = device->getSceneManager();
1546 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
1551 - An unit cube is centered at 0,0,0
1552 - Camera looks at cube from Y+, Z- towards Y-, Z+
1553 NOTE: Cube has to be changed to something else because
1554 the textures cannot be set individually (or can they?)
1557 scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1,
1558 v3f(0,0,0), v3f(0, 45, 0));
1559 // Set texture of cube
1560 cube->setMaterialTexture(0, texture_top);
1561 //cube->setMaterialFlag(video::EMF_LIGHTING, false);
1562 cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false);
1563 cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
1565 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
1566 v3f(0, 1.0, -1.5), v3f(0, 0, 0));
1567 // Set orthogonal projection
1568 core::CMatrix4<f32> pm;
1569 pm.buildProjectionMatrixOrthoLH(1.65, 1.65, 0, 100);
1570 camera->setProjectionMatrix(pm, true);
1572 /*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0,
1573 v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000);
1575 smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2));
1578 driver->beginScene(true, true, video::SColor(0,0,0,0));
1582 // NOTE: The scene nodes should not be dropped, otherwise
1583 // smgr->drop() segfaults
1587 // Drop scene manager
1590 // Unset render target
1591 driver->setRenderTarget(0, true, true, 0);
1593 // Free textures of images
1594 // TODO: When all are used, free them all
1595 driver->removeTexture(texture_top);
1597 // Create image of render target
1598 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1602 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1606 image->copyTo(baseimg);
1613 errorstream<<"generate_image(): Invalid "
1614 " modification: \""<<part_of_name<<"\""<<std::endl;
1621 void make_progressbar(float value, video::IImage *image)
1626 core::dimension2d<u32> size = image->getDimension();
1628 u32 barheight = size.Height/16;
1629 u32 barpad_x = size.Width/16;
1630 u32 barpad_y = size.Height/16;
1631 u32 barwidth = size.Width - barpad_x*2;
1632 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
1634 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
1636 video::SColor active(255,255,0,0);
1637 video::SColor inactive(255,0,0,0);
1638 for(u32 x0=0; x0<barwidth; x0++)
1645 u32 x = x0 + barpos.X;
1646 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
1648 image->setPixel(x,y, *c);