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(): 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 infostream<<"getTextureIdDirect(): "
621 "failed to generate \""<<last_part_of_name<<"\""
625 // If no resulting image, print a warning
628 infostream<<"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 infostream<<"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 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
731 video::IVideoDriver* driver = m_device->getVideoDriver();
735 video::ITexture *t = driver->addTexture(name.c_str(), img);
737 bool reuse_old_id = false;
738 u32 id = m_atlaspointer_cache.size();
739 // Check old id without fetching a texture
740 core::map<std::string, u32>::Node *n;
741 n = m_name_to_id.find(name);
742 // If it exists, we will replace the old definition
748 // Create AtlasPointer
754 core::dimension2d<u32> dim = img->getDimension();
756 // Create SourceAtlasPointer and add to containers
757 SourceAtlasPointer nap(name, ap, img, v2s32(0,0), dim);
759 m_atlaspointer_cache[id] = nap;
761 m_atlaspointer_cache.push_back(nap);
762 m_name_to_id[name] = id;
766 void TextureSource::rebuildImagesAndTextures()
768 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
770 /*// Oh well... just clear everything, they'll load sometime.
771 m_atlaspointer_cache.clear();
772 m_name_to_id.clear();*/
774 video::IVideoDriver* driver = m_device->getVideoDriver();
776 // Remove source images from textures to disable inheriting textures
777 // from existing textures
778 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
779 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
780 sap->atlas_img->drop();
781 sap->atlas_img = NULL;
785 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
786 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
788 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
789 // Create texture from resulting image
790 video::ITexture *t = NULL;
792 t = driver->addTexture(sap->name.c_str(), img);
796 sap->a.pos = v2f(0,0);
797 sap->a.size = v2f(1,1);
799 sap->atlas_img = img;
800 sap->intpos = v2s32(0,0);
801 sap->intsize = img->getDimension();
805 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
807 assert(gamedef->tsrc() == this);
808 INodeDefManager *ndef = gamedef->ndef();
810 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
812 //return; // Disable (for testing)
814 video::IVideoDriver* driver = m_device->getVideoDriver();
817 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
819 // Create an image of the right size
820 core::dimension2d<u32> atlas_dim(1024,1024);
821 video::IImage *atlas_img =
822 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
824 if(atlas_img == NULL)
826 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
827 "image; not building texture atlas."<<std::endl;
832 Grab list of stuff to include in the texture atlas from the
833 main content features
836 core::map<std::string, bool> sourcelist;
838 for(u16 j=0; j<MAX_CONTENT+1; j++)
840 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
842 const ContentFeatures &f = ndef->get(j);
843 for(std::set<std::string>::const_iterator
844 i = f.used_texturenames.begin();
845 i != f.used_texturenames.end(); i++)
847 std::string name = *i;
848 sourcelist[name] = true;
850 if(f.often_contains_mineral){
851 for(int k=1; k<MINERAL_COUNT; k++){
852 std::string mineraltexture = mineral_block_texture(k);
853 std::string fulltexture = name + "^" + mineraltexture;
854 sourcelist[fulltexture] = true;
860 infostream<<"Creating texture atlas out of textures: ";
861 for(core::map<std::string, bool>::Iterator
862 i = sourcelist.getIterator();
863 i.atEnd() == false; i++)
865 std::string name = i.getNode()->getKey();
866 infostream<<"\""<<name<<"\" ";
868 infostream<<std::endl;
870 // Padding to disallow texture bleeding
873 s32 column_width = 256;
874 s32 column_padding = 16;
877 First pass: generate almost everything
879 core::position2d<s32> pos_in_atlas(0,0);
881 pos_in_atlas.Y = padding;
883 for(core::map<std::string, bool>::Iterator
884 i = sourcelist.getIterator();
885 i.atEnd() == false; i++)
887 std::string name = i.getNode()->getKey();
889 // Generate image by name
890 video::IImage *img2 = generate_image_from_scratch(name, m_device,
894 infostream<<"TextureSource::buildMainAtlas(): Couldn't generate texture atlas: Couldn't generate image \""<<name<<"\""<<std::endl;
898 core::dimension2d<u32> dim = img2->getDimension();
900 // Don't add to atlas if image is large
901 core::dimension2d<u32> max_size_in_atlas(32,32);
902 if(dim.Width > max_size_in_atlas.Width
903 || dim.Height > max_size_in_atlas.Height)
905 infostream<<"TextureSource::buildMainAtlas(): Not adding "
906 <<"\""<<name<<"\" because image is large"<<std::endl;
910 // Wrap columns and stop making atlas if atlas is full
911 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
913 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
914 errorstream<<"TextureSource::buildMainAtlas(): "
915 <<"Atlas is full, not adding more textures."
919 pos_in_atlas.Y = padding;
920 pos_in_atlas.X += column_width + column_padding;
923 infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
924 <<"\" to texture atlas"<<std::endl;
926 // Tile it a few times in the X direction
927 u16 xwise_tiling = column_width / dim.Width;
928 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
930 for(u32 j=0; j<xwise_tiling; j++)
932 // Copy the copy to the atlas
933 img2->copyToWithAlpha(atlas_img,
934 pos_in_atlas + v2s32(j*dim.Width,0),
935 core::rect<s32>(v2s32(0,0), dim),
936 video::SColor(255,255,255,255),
940 // Copy the borders a few times to disallow texture bleeding
941 for(u32 side=0; side<2; side++) // top and bottom
942 for(s32 y0=0; y0<padding; y0++)
943 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
949 dst_y = y0 + pos_in_atlas.Y + dim.Height;
950 src_y = pos_in_atlas.Y + dim.Height - 1;
954 dst_y = -y0 + pos_in_atlas.Y-1;
955 src_y = pos_in_atlas.Y;
957 s32 x = x0 + pos_in_atlas.X;
958 video::SColor c = atlas_img->getPixel(x, src_y);
959 atlas_img->setPixel(x,dst_y,c);
965 Add texture to caches
968 bool reuse_old_id = false;
969 u32 id = m_atlaspointer_cache.size();
970 // Check old id without fetching a texture
971 core::map<std::string, u32>::Node *n;
972 n = m_name_to_id.find(name);
973 // If it exists, we will replace the old definition
977 infostream<<"TextureSource::buildMainAtlas(): "
978 <<"Replacing old AtlasPointer"<<std::endl;
981 // Create AtlasPointer
983 ap.atlas = NULL; // Set on the second pass
984 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
985 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
986 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
987 (float)dim.Width/(float)atlas_dim.Height);
988 ap.tiled = xwise_tiling;
990 // Create SourceAtlasPointer and add to containers
991 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
993 m_atlaspointer_cache[id] = nap;
995 m_atlaspointer_cache.push_back(nap);
996 m_name_to_id[name] = id;
998 // Increment position
999 pos_in_atlas.Y += dim.Height + padding * 2;
1005 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1009 Second pass: set texture pointer in generated AtlasPointers
1011 for(core::map<std::string, bool>::Iterator
1012 i = sourcelist.getIterator();
1013 i.atEnd() == false; i++)
1015 std::string name = i.getNode()->getKey();
1016 if(m_name_to_id.find(name) == NULL)
1018 u32 id = m_name_to_id[name];
1019 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1020 m_atlaspointer_cache[id].a.atlas = t;
1024 Write image to file so that it can be inspected
1026 /*std::string atlaspath = porting::path_userdata
1027 + DIR_DELIM + "generated_texture_atlas.png";
1028 infostream<<"Removing and writing texture atlas for inspection to "
1029 <<atlaspath<<std::endl;
1030 fs::RecursiveDelete(atlaspath);
1031 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1034 video::IImage* generate_image_from_scratch(std::string name,
1035 IrrlichtDevice *device, SourceImageCache *sourcecache)
1037 /*infostream<<"generate_image_from_scratch(): "
1038 "\""<<name<<"\""<<std::endl;*/
1040 video::IVideoDriver* driver = device->getVideoDriver();
1047 video::IImage *baseimg = NULL;
1049 char separator = '^';
1051 // Find last meta separator in name
1052 s32 last_separator_position = -1;
1053 for(s32 i=name.size()-1; i>=0; i--)
1055 if(name[i] == separator)
1057 last_separator_position = i;
1062 /*infostream<<"generate_image_from_scratch(): "
1063 <<"last_separator_position="<<last_separator_position
1067 If separator was found, construct the base name and make the
1068 base image using a recursive call
1070 std::string base_image_name;
1071 if(last_separator_position != -1)
1073 // Construct base name
1074 base_image_name = name.substr(0, last_separator_position);
1075 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1076 " to get base image of \""<<name<<"\" = \""
1077 <<base_image_name<<"\""<<std::endl;*/
1078 baseimg = generate_image_from_scratch(base_image_name, device,
1083 Parse out the last part of the name of the image and act
1087 std::string last_part_of_name = name.substr(last_separator_position+1);
1088 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1090 // Generate image according to part of name
1091 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1093 infostream<<"generate_image_from_scratch(): "
1094 "failed to generate \""<<last_part_of_name<<"\""
1102 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1103 IrrlichtDevice *device, SourceImageCache *sourcecache)
1105 video::IVideoDriver* driver = device->getVideoDriver();
1108 // Stuff starting with [ are special commands
1109 if(part_of_name[0] != '[')
1111 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1115 infostream<<"generate_image(): Could not load image \""
1116 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1120 infostream<<"generate_image(): Creating a dummy"
1121 <<" image for \""<<part_of_name<<"\""<<std::endl;
1123 // Just create a dummy image
1124 //core::dimension2d<u32> dim(2,2);
1125 core::dimension2d<u32> dim(1,1);
1126 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1128 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1129 image->setPixel(1,0, video::SColor(255,0,255,0));
1130 image->setPixel(0,1, video::SColor(255,0,0,255));
1131 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1132 image->setPixel(0,0, video::SColor(255,myrand()%256,
1133 myrand()%256,myrand()%256));
1134 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1135 myrand()%256,myrand()%256));
1136 image->setPixel(0,1, video::SColor(255,myrand()%256,
1137 myrand()%256,myrand()%256));
1138 image->setPixel(1,1, video::SColor(255,myrand()%256,
1139 myrand()%256,myrand()%256));*/
1142 // If base image is NULL, load as base.
1145 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1147 Copy it this way to get an alpha channel.
1148 Otherwise images with alpha cannot be blitted on
1149 images that don't have alpha in the original file.
1151 core::dimension2d<u32> dim = image->getDimension();
1152 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1153 image->copyTo(baseimg);
1156 // Else blit on base.
1159 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1160 // Size of the copied area
1161 core::dimension2d<u32> dim = image->getDimension();
1162 //core::dimension2d<u32> dim(16,16);
1163 // Position to copy the blitted to in the base image
1164 core::position2d<s32> pos_to(0,0);
1165 // Position to copy the blitted from in the blitted image
1166 core::position2d<s32> pos_from(0,0);
1168 image->copyToWithAlpha(baseimg, pos_to,
1169 core::rect<s32>(pos_from, dim),
1170 video::SColor(255,255,255,255),
1178 // A special texture modification
1180 infostream<<"generate_image(): generating special "
1181 <<"modification \""<<part_of_name<<"\""
1185 This is the simplest of all; it just adds stuff to the
1186 name so that a separate texture is created.
1188 It is used to make textures for stuff that doesn't want
1189 to implement getting the texture from a bigger texture
1192 if(part_of_name == "[forcesingle")
1197 Adds a cracking texture
1199 else if(part_of_name.substr(0,6) == "[crack")
1203 infostream<<"generate_image(): baseimg==NULL "
1204 <<"for part_of_name=\""<<part_of_name
1205 <<"\", cancelling."<<std::endl;
1209 // Crack image number
1210 u16 progression = stoi(part_of_name.substr(6));
1212 // Size of the base image
1213 core::dimension2d<u32> dim_base = baseimg->getDimension();
1218 It is an image with a number of cracking stages
1221 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1225 // Dimension of original image
1226 core::dimension2d<u32> dim_crack
1227 = img_crack->getDimension();
1228 // Count of crack stages
1229 u32 crack_count = dim_crack.Height / dim_crack.Width;
1230 // Limit progression
1231 if(progression > crack_count-1)
1232 progression = crack_count-1;
1233 // Dimension of a single scaled crack stage
1234 core::dimension2d<u32> dim_crack_scaled_single(
1238 // Dimension of scaled size
1239 core::dimension2d<u32> dim_crack_scaled(
1240 dim_crack_scaled_single.Width,
1241 dim_crack_scaled_single.Height * crack_count
1243 // Create scaled crack image
1244 video::IImage *img_crack_scaled = driver->createImage(
1245 video::ECF_A8R8G8B8, dim_crack_scaled);
1246 if(img_crack_scaled)
1248 // Scale crack image by copying
1249 img_crack->copyToScaling(img_crack_scaled);
1251 // Position to copy the crack from
1252 core::position2d<s32> pos_crack_scaled(
1254 dim_crack_scaled_single.Height * progression
1257 // This tiling does nothing currently but is useful
1258 for(u32 y0=0; y0<dim_base.Height
1259 / dim_crack_scaled_single.Height; y0++)
1260 for(u32 x0=0; x0<dim_base.Width
1261 / dim_crack_scaled_single.Width; x0++)
1263 // Position to copy the crack to in the base image
1264 core::position2d<s32> pos_base(
1265 x0*dim_crack_scaled_single.Width,
1266 y0*dim_crack_scaled_single.Height
1268 // Rectangle to copy the crack from on the scaled image
1269 core::rect<s32> rect_crack_scaled(
1271 dim_crack_scaled_single
1274 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
1276 video::SColor(255,255,255,255),
1280 img_crack_scaled->drop();
1287 [combine:WxH:X,Y=filename:X,Y=filename2
1288 Creates a bigger texture from an amount of smaller ones
1290 else if(part_of_name.substr(0,8) == "[combine")
1292 Strfnd sf(part_of_name);
1294 u32 w0 = stoi(sf.next("x"));
1295 u32 h0 = stoi(sf.next(":"));
1296 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1297 core::dimension2d<u32> dim(w0,h0);
1298 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1299 while(sf.atend() == false)
1301 u32 x = stoi(sf.next(","));
1302 u32 y = stoi(sf.next("="));
1303 std::string filename = sf.next(":");
1304 infostream<<"Adding \""<<filename
1305 <<"\" to combined ("<<x<<","<<y<<")"
1307 video::IImage *img = sourcecache->getOrLoad(filename, device);
1310 core::dimension2d<u32> dim = img->getDimension();
1311 infostream<<"Size "<<dim.Width
1312 <<"x"<<dim.Height<<std::endl;
1313 core::position2d<s32> pos_base(x, y);
1314 video::IImage *img2 =
1315 driver->createImage(video::ECF_A8R8G8B8, dim);
1318 img2->copyToWithAlpha(baseimg, pos_base,
1319 core::rect<s32>(v2s32(0,0), dim),
1320 video::SColor(255,255,255,255),
1326 infostream<<"img==NULL"<<std::endl;
1332 Adds a progress bar, 0.0 <= N <= 1.0
1334 else if(part_of_name.substr(0,12) == "[progressbar")
1338 infostream<<"generate_image(): baseimg==NULL "
1339 <<"for part_of_name=\""<<part_of_name
1340 <<"\", cancelling."<<std::endl;
1344 float value = stof(part_of_name.substr(12));
1345 make_progressbar(value, baseimg);
1348 "[noalpha:filename.png"
1349 Use an image without it's alpha channel.
1350 Used for the leaves texture when in old leaves mode, so
1351 that the transparent parts don't look completely black
1352 when simple alpha channel is used for rendering.
1354 else if(part_of_name.substr(0,8) == "[noalpha")
1358 infostream<<"generate_image(): baseimg!=NULL "
1359 <<"for part_of_name=\""<<part_of_name
1360 <<"\", cancelling."<<std::endl;
1364 std::string filename = part_of_name.substr(9);
1366 std::string path = getTexturePath(filename.c_str());
1368 infostream<<"generate_image(): Loading file \""<<filename
1371 video::IImage *image = sourcecache->getOrLoad(filename, device);
1375 infostream<<"generate_image(): Loading path \""
1376 <<path<<"\" failed"<<std::endl;
1380 core::dimension2d<u32> dim = image->getDimension();
1381 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1383 // Set alpha to full
1384 for(u32 y=0; y<dim.Height; y++)
1385 for(u32 x=0; x<dim.Width; x++)
1387 video::SColor c = image->getPixel(x,y);
1389 image->setPixel(x,y,c);
1392 image->copyTo(baseimg);
1398 "[makealpha:R,G,B:filename.png"
1399 Use an image with converting one color to transparent.
1401 else if(part_of_name.substr(0,11) == "[makealpha:")
1405 infostream<<"generate_image(): baseimg!=NULL "
1406 <<"for part_of_name=\""<<part_of_name
1407 <<"\", cancelling."<<std::endl;
1411 Strfnd sf(part_of_name.substr(11));
1412 u32 r1 = stoi(sf.next(","));
1413 u32 g1 = stoi(sf.next(","));
1414 u32 b1 = stoi(sf.next(":"));
1415 std::string filename = sf.next("");
1417 infostream<<"generate_image(): Loading file \""<<filename
1420 video::IImage *image = sourcecache->getOrLoad(filename, device);
1424 infostream<<"generate_image(): Loading file \""
1425 <<filename<<"\" failed"<<std::endl;
1429 core::dimension2d<u32> dim = image->getDimension();
1430 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1433 image->copyTo(baseimg);
1437 for(u32 y=0; y<dim.Height; y++)
1438 for(u32 x=0; x<dim.Width; x++)
1440 video::SColor c = baseimg->getPixel(x,y);
1442 u32 g = c.getGreen();
1443 u32 b = c.getBlue();
1444 if(!(r == r1 && g == g1 && b == b1))
1447 baseimg->setPixel(x,y,c);
1452 "[makealpha2:R,G,B;R2,G2,B2:filename.png"
1453 Use an image with converting two colors to transparent.
1455 else if(part_of_name.substr(0,12) == "[makealpha2:")
1459 infostream<<"generate_image(): baseimg!=NULL "
1460 <<"for part_of_name=\""<<part_of_name
1461 <<"\", cancelling."<<std::endl;
1465 Strfnd sf(part_of_name.substr(12));
1466 u32 r1 = stoi(sf.next(","));
1467 u32 g1 = stoi(sf.next(","));
1468 u32 b1 = stoi(sf.next(";"));
1469 u32 r2 = stoi(sf.next(","));
1470 u32 g2 = stoi(sf.next(","));
1471 u32 b2 = stoi(sf.next(":"));
1472 std::string filename = sf.next("");
1474 infostream<<"generate_image(): Loading filename \""<<filename
1477 video::IImage *image = sourcecache->getOrLoad(filename, device);
1481 infostream<<"generate_image(): Loading file \""
1482 <<filename<<"\" failed"<<std::endl;
1486 core::dimension2d<u32> dim = image->getDimension();
1487 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1490 image->copyTo(baseimg);
1494 for(u32 y=0; y<dim.Height; y++)
1495 for(u32 x=0; x<dim.Width; x++)
1497 video::SColor c = baseimg->getPixel(x,y);
1499 u32 g = c.getGreen();
1500 u32 b = c.getBlue();
1501 if(!(r == r1 && g == g1 && b == b1) &&
1502 !(r == r2 && g == g2 && b == b2))
1505 baseimg->setPixel(x,y,c);
1510 [inventorycube{topimage{leftimage{rightimage
1511 In every subimage, replace ^ with &.
1512 Create an "inventory cube".
1513 NOTE: This should be used only on its own.
1514 Example (a grass block (not actually used in game):
1515 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1517 else if(part_of_name.substr(0,14) == "[inventorycube")
1521 infostream<<"generate_image(): baseimg!=NULL "
1522 <<"for part_of_name=\""<<part_of_name
1523 <<"\", cancelling."<<std::endl;
1527 str_replace_char(part_of_name, '&', '^');
1528 Strfnd sf(part_of_name);
1530 std::string imagename_top = sf.next("{");
1531 std::string imagename_left = sf.next("{");
1532 std::string imagename_right = sf.next("{");
1535 // TODO: Create cube with different textures on different sides
1537 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
1539 infostream<<"generate_image(): EVDF_RENDER_TO_TARGET"
1540 " not supported. Creating fallback image"<<std::endl;
1541 baseimg = generate_image_from_scratch(
1542 imagename_top, device, sourcecache);
1548 //infostream<<"inventorycube w="<<w0<<" h="<<h0<<std::endl;
1549 core::dimension2d<u32> dim(w0,h0);
1551 // Generate images for the faces of the cube
1552 video::IImage *img_top = generate_image_from_scratch(
1553 imagename_top, device, sourcecache);
1554 video::IImage *img_left = generate_image_from_scratch(
1555 imagename_left, device, sourcecache);
1556 video::IImage *img_right = generate_image_from_scratch(
1557 imagename_right, device, sourcecache);
1558 assert(img_top && img_left && img_right);
1560 // Create textures from images
1561 // TODO: Use them all
1562 video::ITexture *texture_top = driver->addTexture(
1563 (imagename_top + "__temp__").c_str(), img_top);
1564 assert(texture_top);
1571 // Create render target texture
1572 video::ITexture *rtt = NULL;
1573 std::string rtt_name = part_of_name + "_RTT";
1574 rtt = driver->addRenderTargetTexture(dim, rtt_name.c_str(),
1575 video::ECF_A8R8G8B8);
1578 // Set render target
1579 driver->setRenderTarget(rtt, true, true,
1580 video::SColor(0,0,0,0));
1582 // Get a scene manager
1583 scene::ISceneManager *smgr_main = device->getSceneManager();
1585 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
1590 - An unit cube is centered at 0,0,0
1591 - Camera looks at cube from Y+, Z- towards Y-, Z+
1592 NOTE: Cube has to be changed to something else because
1593 the textures cannot be set individually (or can they?)
1596 scene::ISceneNode* cube = smgr->addCubeSceneNode(1.0, NULL, -1,
1597 v3f(0,0,0), v3f(0, 45, 0));
1598 // Set texture of cube
1599 cube->setMaterialTexture(0, texture_top);
1600 //cube->setMaterialFlag(video::EMF_LIGHTING, false);
1601 cube->setMaterialFlag(video::EMF_ANTI_ALIASING, false);
1602 cube->setMaterialFlag(video::EMF_BILINEAR_FILTER, false);
1604 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
1605 v3f(0, 1.0, -1.5), v3f(0, 0, 0));
1606 // Set orthogonal projection
1607 core::CMatrix4<f32> pm;
1608 pm.buildProjectionMatrixOrthoLH(1.65, 1.65, 0, 100);
1609 camera->setProjectionMatrix(pm, true);
1611 /*scene::ILightSceneNode *light =*/ smgr->addLightSceneNode(0,
1612 v3f(-50, 100, 0), video::SColorf(0.5,0.5,0.5), 1000);
1614 smgr->setAmbientLight(video::SColorf(0.2,0.2,0.2));
1617 driver->beginScene(true, true, video::SColor(0,0,0,0));
1621 // NOTE: The scene nodes should not be dropped, otherwise
1622 // smgr->drop() segfaults
1626 // Drop scene manager
1629 // Unset render target
1630 driver->setRenderTarget(0, true, true, 0);
1632 // Free textures of images
1633 // TODO: When all are used, free them all
1634 driver->removeTexture(texture_top);
1636 // Create image of render target
1637 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1641 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1645 image->copyTo(baseimg);
1652 infostream<<"generate_image(): Invalid "
1653 " modification: \""<<part_of_name<<"\""<<std::endl;
1660 void make_progressbar(float value, video::IImage *image)
1665 core::dimension2d<u32> size = image->getDimension();
1667 u32 barheight = size.Height/16;
1668 u32 barpad_x = size.Width/16;
1669 u32 barpad_y = size.Height/16;
1670 u32 barwidth = size.Width - barpad_x*2;
1671 v2u32 barpos(barpad_x, size.Height - barheight - barpad_y);
1673 u32 barvalue_i = (u32)(((float)barwidth * value) + 0.5);
1675 video::SColor active(255,255,0,0);
1676 video::SColor inactive(255,0,0,0);
1677 for(u32 x0=0; x0<barwidth; x0++)
1684 u32 x = x0 + barpos.X;
1685 for(u32 y=barpos.Y; y<barpos.Y+barheight; y++)
1687 image->setPixel(x,y, *c);