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
27 #include <ICameraSceneNode.h>
29 #include "mapnode.h" // For texture atlas making
30 #include "nodedef.h" // For texture atlas making
32 #include "utility_string.h"
35 A cache from texture name to texture path
37 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
40 Replaces the filename extension.
42 std::string image = "a/image.png"
43 replace_ext(image, "jpg")
44 -> image = "a/image.jpg"
45 Returns true on success.
47 static bool replace_ext(std::string &path, const char *ext)
51 // Find place of last dot, fail if \ or / found.
53 for(s32 i=path.size()-1; i>=0; i--)
61 if(path[i] == '\\' || path[i] == '/')
64 // If not found, return an empty string
67 // Else make the new path
68 path = path.substr(0, last_dot_i+1) + ext;
73 Find out the full path of an image by trying different filename
78 static std::string getImagePath(std::string path)
80 // A NULL-ended list of possible image extensions
81 const char *extensions[] = {
82 "png", "jpg", "bmp", "tga",
83 "pcx", "ppm", "psd", "wal", "rgb",
86 // If there is no extension, add one
87 if(removeStringEnd(path, extensions) == "")
89 // Check paths until something is found to exist
90 const char **ext = extensions;
92 bool r = replace_ext(path, *ext);
95 if(fs::PathExists(path))
98 while((++ext) != NULL);
104 Gets the path to a texture by first checking if the texture exists
105 in texture_path and if not, using the data path.
107 Checks all supported extensions by replacing the original extension.
109 If not found, returns "".
111 Utilizes a thread-safe cache.
113 std::string getTexturePath(const std::string &filename)
115 std::string fullpath = "";
119 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
124 Check from texture_path
126 std::string texture_path = g_settings->get("texture_path");
127 if(texture_path != "")
129 std::string testpath = texture_path + DIR_DELIM + filename;
130 // Check all filename extensions. Returns "" if not found.
131 fullpath = getImagePath(testpath);
135 Check from $user/textures/all
139 std::string texture_path = porting::path_user + DIR_DELIM
140 + "textures" + DIR_DELIM + "all";
141 std::string testpath = texture_path + DIR_DELIM + filename;
142 // Check all filename extensions. Returns "" if not found.
143 fullpath = getImagePath(testpath);
147 Check from default data directory
151 std::string base_path = porting::path_share + DIR_DELIM + "textures"
152 + DIR_DELIM + "base" + DIR_DELIM + "pack";
153 std::string testpath = base_path + DIR_DELIM + filename;
154 // Check all filename extensions. Returns "" if not found.
155 fullpath = getImagePath(testpath);
158 // Add to cache (also an empty result is cached)
159 g_texturename_to_path_cache.set(filename, fullpath);
166 An internal variant of AtlasPointer with more data.
167 (well, more like a wrapper)
170 struct SourceAtlasPointer
174 video::IImage *atlas_img; // The source image of the atlas
175 // Integer variants of position and size
180 const std::string &name_,
181 AtlasPointer a_=AtlasPointer(0, NULL),
182 video::IImage *atlas_img_=NULL,
183 v2s32 intpos_=v2s32(0,0),
184 v2u32 intsize_=v2u32(0,0)
188 atlas_img(atlas_img_),
196 SourceImageCache: A cache used for storing source images.
199 class SourceImageCache
202 void insert(const std::string &name, video::IImage *img,
203 bool prefer_local, video::IVideoDriver *driver)
207 core::map<std::string, video::IImage*>::Node *n;
208 n = m_images.find(name);
210 video::IImage *oldimg = n->getValue();
214 // Try to use local texture instead if asked to
216 std::string path = getTexturePath(name.c_str());
218 video::IImage *img2 = driver->createImageFromFile(path.c_str());
220 m_images[name] = img2;
226 m_images[name] = img;
228 video::IImage* get(const std::string &name)
230 core::map<std::string, video::IImage*>::Node *n;
231 n = m_images.find(name);
233 return n->getValue();
236 // Primarily fetches from cache, secondarily tries to read from filesystem
237 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
239 core::map<std::string, video::IImage*>::Node *n;
240 n = m_images.find(name);
242 n->getValue()->grab(); // Grab for caller
243 return n->getValue();
245 video::IVideoDriver* driver = device->getVideoDriver();
246 std::string path = getTexturePath(name.c_str());
248 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
249 <<name<<"\""<<std::endl;
252 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
254 video::IImage *img = driver->createImageFromFile(path.c_str());
255 // Even if could not be loaded, put as NULL
256 //m_images[name] = img;
258 m_images[name] = img;
259 img->grab(); // Grab for caller
264 core::map<std::string, video::IImage*> m_images;
271 class TextureSource : public IWritableTextureSource
274 TextureSource(IrrlichtDevice *device);
279 Now, assume a texture with the id 1 exists, and has the name
280 "stone.png^mineral1".
281 Then a random thread calls getTextureId for a texture called
282 "stone.png^mineral1^crack0".
283 ...Now, WTF should happen? Well:
284 - getTextureId strips off stuff recursively from the end until
285 the remaining part is found, or nothing is left when
286 something is stripped out
288 But it is slow to search for textures by names and modify them
290 - ContentFeatures is made to contain ids for the basic plain
292 - Crack textures can be slow by themselves, but the framework
296 - Assume a texture with the id 1 exists, and has the name
297 "stone.png^mineral1" and is specified as a part of some atlas.
298 - Now getNodeTile() stumbles upon a node which uses
299 texture id 1, and determines that MATERIAL_FLAG_CRACK
300 must be applied to the tile
301 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
302 has received the current crack level 0 from the client. It
303 finds out the name of the texture with getTextureName(1),
304 appends "^crack0" to it and gets a new texture id with
305 getTextureId("stone.png^mineral1^crack0").
310 Gets a texture id from cache or
311 - if main thread, from getTextureIdDirect
312 - if other thread, adds to request queue and waits for main thread
314 u32 getTextureId(const std::string &name);
320 "stone.png^mineral_coal.png"
321 "stone.png^mineral_coal.png^crack1"
323 - If texture specified by name is found from cache, return the
325 - Otherwise generate the texture, add to cache and return id.
326 Recursion is used to find out the largest found part of the
327 texture and continue based on it.
329 The id 0 points to a NULL texture. It is returned in case of error.
331 u32 getTextureIdDirect(const std::string &name);
333 // Finds out the name of a cached texture.
334 std::string getTextureName(u32 id);
337 If texture specified by the name pointed by the id doesn't
338 exist, create it, then return the cached texture.
340 Can be called from any thread. If called from some other thread
341 and not found in cache, the call is queued to the main thread
344 AtlasPointer getTexture(u32 id);
346 AtlasPointer getTexture(const std::string &name)
348 return getTexture(getTextureId(name));
351 // Gets a separate texture
352 video::ITexture* getTextureRaw(const std::string &name)
354 AtlasPointer ap = getTexture(name + "^[forcesingle");
358 // Gets a separate texture atlas pointer
359 AtlasPointer getTextureRawAP(const AtlasPointer &ap)
361 return getTexture(getTextureName(ap.id) + "^[forcesingle");
364 // Returns a pointer to the irrlicht device
365 virtual IrrlichtDevice* getDevice()
370 // Update new texture pointer and texture coordinates to an
371 // AtlasPointer based on it's texture id
372 void updateAP(AtlasPointer &ap);
374 // Processes queued texture requests from other threads.
375 // Shall be called from the main thread.
378 // Insert an image into the cache without touching the filesystem.
379 // Shall be called from the main thread.
380 void insertSourceImage(const std::string &name, video::IImage *img);
382 // Rebuild images and textures from the current set of source images
383 // Shall be called from the main thread.
384 void rebuildImagesAndTextures();
386 // Build the main texture atlas which contains most of the
388 void buildMainAtlas(class IGameDef *gamedef);
392 // The id of the thread that is allowed to use irrlicht directly
393 threadid_t m_main_thread;
394 // The irrlicht device
395 IrrlichtDevice *m_device;
397 // Cache of source images
398 // This should be only accessed from the main thread
399 SourceImageCache m_sourcecache;
401 // A texture id is index in this array.
402 // The first position contains a NULL texture.
403 core::array<SourceAtlasPointer> m_atlaspointer_cache;
404 // Maps a texture name to an index in the former.
405 core::map<std::string, u32> m_name_to_id;
406 // The two former containers are behind this mutex
407 JMutex m_atlaspointer_cache_mutex;
409 // Main texture atlas. This is filled at startup and is then not touched.
410 video::IImage *m_main_atlas_image;
411 video::ITexture *m_main_atlas_texture;
413 // Queued texture fetches (to be processed by the main thread)
414 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
417 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
419 return new TextureSource(device);
422 TextureSource::TextureSource(IrrlichtDevice *device):
424 m_main_atlas_image(NULL),
425 m_main_atlas_texture(NULL)
429 m_atlaspointer_cache_mutex.Init();
431 m_main_thread = get_current_thread_id();
433 // Add a NULL AtlasPointer as the first index, named ""
434 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
435 m_name_to_id[""] = 0;
438 TextureSource::~TextureSource()
442 u32 TextureSource::getTextureId(const std::string &name)
444 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
448 See if texture already exists
450 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
451 core::map<std::string, u32>::Node *n;
452 n = m_name_to_id.find(name);
455 return n->getValue();
462 if(get_current_thread_id() == m_main_thread)
464 return getTextureIdDirect(name);
468 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
470 // We're gonna ask the result to be put into here
471 ResultQueue<std::string, u32, u8, u8> result_queue;
473 // Throw a request in
474 m_get_texture_queue.add(name, 0, 0, &result_queue);
476 infostream<<"Waiting for texture from main thread, name=\""
477 <<name<<"\""<<std::endl;
481 // Wait result for a second
482 GetResult<std::string, u32, u8, u8>
483 result = result_queue.pop_front(1000);
485 // Check that at least something worked OK
486 assert(result.key == name);
490 catch(ItemNotFoundException &e)
492 infostream<<"Waiting for texture timed out."<<std::endl;
497 infostream<<"getTextureId(): Failed"<<std::endl;
502 // Overlay image on top of another image (used for cracks)
503 void overlay(video::IImage *image, video::IImage *overlay);
506 void brighten(video::IImage *image);
507 // Parse a transform name
508 u32 parseImageTransform(const std::string& s);
509 // Apply transform to image dimension
510 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
511 // Apply transform to image data
512 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
515 Generate image based on a string like "stone.png" or "[crack0".
516 if baseimg is NULL, it is created. Otherwise stuff is made on it.
518 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
519 IrrlichtDevice *device, SourceImageCache *sourcecache);
522 Generates an image from a full string like
523 "stone.png^mineral_coal.png^[crack0".
525 This is used by buildMainAtlas().
527 video::IImage* generate_image_from_scratch(std::string name,
528 IrrlichtDevice *device, SourceImageCache *sourcecache);
531 This method generates all the textures
533 u32 TextureSource::getTextureIdDirect(const std::string &name)
535 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
537 // Empty name means texture 0
540 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
545 Calling only allowed from main thread
547 if(get_current_thread_id() != m_main_thread)
549 errorstream<<"TextureSource::getTextureIdDirect() "
550 "called not from main thread"<<std::endl;
555 See if texture already exists
558 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
560 core::map<std::string, u32>::Node *n;
561 n = m_name_to_id.find(name);
564 /*infostream<<"getTextureIdDirect(): \""<<name
565 <<"\" found in cache"<<std::endl;*/
566 return n->getValue();
570 /*infostream<<"getTextureIdDirect(): \""<<name
571 <<"\" NOT found in cache. Creating it."<<std::endl;*/
577 char separator = '^';
580 This is set to the id of the base image.
581 If left 0, there is no base image and a completely new image
584 u32 base_image_id = 0;
586 // Find last meta separator in name
587 s32 last_separator_position = -1;
588 for(s32 i=name.size()-1; i>=0; i--)
590 if(name[i] == separator)
592 last_separator_position = i;
597 If separator was found, construct the base name and make the
598 base image using a recursive call
600 std::string base_image_name;
601 if(last_separator_position != -1)
603 // Construct base name
604 base_image_name = name.substr(0, last_separator_position);
605 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
606 " to get base image of \""<<name<<"\" = \""
607 <<base_image_name<<"\""<<std::endl;*/
608 base_image_id = getTextureIdDirect(base_image_name);
611 //infostream<<"base_image_id="<<base_image_id<<std::endl;
613 video::IVideoDriver* driver = m_device->getVideoDriver();
616 video::ITexture *t = NULL;
619 An image will be built from files and then converted into a texture.
621 video::IImage *baseimg = NULL;
623 // If a base image was found, copy it to baseimg
624 if(base_image_id != 0)
626 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
628 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
630 video::IImage *image = ap.atlas_img;
634 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
635 <<"cache: \""<<base_image_name<<"\""
640 core::dimension2d<u32> dim = ap.intsize;
642 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
644 core::position2d<s32> pos_to(0,0);
645 core::position2d<s32> pos_from = ap.intpos;
649 v2s32(0,0), // position in target
650 core::rect<s32>(pos_from, dim) // from
653 /*infostream<<"getTextureIdDirect(): Loaded \""
654 <<base_image_name<<"\" from image cache"
660 Parse out the last part of the name of the image and act
664 std::string last_part_of_name = name.substr(last_separator_position+1);
665 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
667 // Generate image according to part of name
668 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
670 errorstream<<"getTextureIdDirect(): "
671 "failed to generate \""<<last_part_of_name<<"\""
675 // If no resulting image, print a warning
678 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
679 " create texture \""<<name<<"\""<<std::endl;
684 // Create texture from resulting image
685 t = driver->addTexture(name.c_str(), baseimg);
689 Add texture to caches (add NULL textures too)
692 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
694 u32 id = m_atlaspointer_cache.size();
700 core::dimension2d<u32> baseimg_dim(0,0);
702 baseimg_dim = baseimg->getDimension();
703 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
704 m_atlaspointer_cache.push_back(nap);
705 m_name_to_id.insert(name, id);
707 /*infostream<<"getTextureIdDirect(): "
708 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
713 std::string TextureSource::getTextureName(u32 id)
715 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
717 if(id >= m_atlaspointer_cache.size())
719 errorstream<<"TextureSource::getTextureName(): id="<<id
720 <<" >= m_atlaspointer_cache.size()="
721 <<m_atlaspointer_cache.size()<<std::endl;
725 return m_atlaspointer_cache[id].name;
729 AtlasPointer TextureSource::getTexture(u32 id)
731 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
733 if(id >= m_atlaspointer_cache.size())
734 return AtlasPointer(0, NULL);
736 return m_atlaspointer_cache[id].a;
739 void TextureSource::updateAP(AtlasPointer &ap)
741 AtlasPointer ap2 = getTexture(ap.id);
745 void TextureSource::processQueue()
750 if(m_get_texture_queue.size() > 0)
752 GetRequest<std::string, u32, u8, u8>
753 request = m_get_texture_queue.pop();
755 /*infostream<<"TextureSource::processQueue(): "
756 <<"got texture request with "
757 <<"name=\""<<request.key<<"\""
760 GetResult<std::string, u32, u8, u8>
762 result.key = request.key;
763 result.callers = request.callers;
764 result.item = getTextureIdDirect(request.key);
766 request.dest->push_back(result);
770 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
772 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
774 assert(get_current_thread_id() == m_main_thread);
776 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
779 void TextureSource::rebuildImagesAndTextures()
781 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
783 /*// Oh well... just clear everything, they'll load sometime.
784 m_atlaspointer_cache.clear();
785 m_name_to_id.clear();*/
787 video::IVideoDriver* driver = m_device->getVideoDriver();
789 // Remove source images from textures to disable inheriting textures
790 // from existing textures
791 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
792 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
793 sap->atlas_img->drop();
794 sap->atlas_img = NULL;
798 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
799 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
801 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
802 // Create texture from resulting image
803 video::ITexture *t = NULL;
805 t = driver->addTexture(sap->name.c_str(), img);
809 sap->a.pos = v2f(0,0);
810 sap->a.size = v2f(1,1);
812 sap->atlas_img = img;
813 sap->intpos = v2s32(0,0);
814 sap->intsize = img->getDimension();
818 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
820 assert(gamedef->tsrc() == this);
821 INodeDefManager *ndef = gamedef->ndef();
823 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
825 //return; // Disable (for testing)
827 video::IVideoDriver* driver = m_device->getVideoDriver();
830 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
832 // Create an image of the right size
833 core::dimension2d<u32> atlas_dim(1024,1024);
834 video::IImage *atlas_img =
835 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
837 if(atlas_img == NULL)
839 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
840 "image; not building texture atlas."<<std::endl;
845 Grab list of stuff to include in the texture atlas from the
846 main content features
849 core::map<std::string, bool> sourcelist;
851 for(u16 j=0; j<MAX_CONTENT+1; j++)
853 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
855 const ContentFeatures &f = ndef->get(j);
856 for(u32 i=0; i<6; i++)
858 std::string name = f.tname_tiles[i];
859 sourcelist[name] = true;
863 infostream<<"Creating texture atlas out of textures: ";
864 for(core::map<std::string, bool>::Iterator
865 i = sourcelist.getIterator();
866 i.atEnd() == false; i++)
868 std::string name = i.getNode()->getKey();
869 infostream<<"\""<<name<<"\" ";
871 infostream<<std::endl;
873 // Padding to disallow texture bleeding
876 s32 column_width = 256;
877 s32 column_padding = 16;
880 First pass: generate almost everything
882 core::position2d<s32> pos_in_atlas(0,0);
884 pos_in_atlas.Y = padding;
886 for(core::map<std::string, bool>::Iterator
887 i = sourcelist.getIterator();
888 i.atEnd() == false; i++)
890 std::string name = i.getNode()->getKey();
892 // Generate image by name
893 video::IImage *img2 = generate_image_from_scratch(name, m_device,
897 errorstream<<"TextureSource::buildMainAtlas(): "
898 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
902 core::dimension2d<u32> dim = img2->getDimension();
904 // Don't add to atlas if image is large
905 core::dimension2d<u32> max_size_in_atlas(32,32);
906 if(dim.Width > max_size_in_atlas.Width
907 || dim.Height > max_size_in_atlas.Height)
909 infostream<<"TextureSource::buildMainAtlas(): Not adding "
910 <<"\""<<name<<"\" because image is large"<<std::endl;
914 // Wrap columns and stop making atlas if atlas is full
915 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
917 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
918 errorstream<<"TextureSource::buildMainAtlas(): "
919 <<"Atlas is full, not adding more textures."
923 pos_in_atlas.Y = padding;
924 pos_in_atlas.X += column_width + column_padding;
927 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
928 <<"\" to texture atlas"<<std::endl;*/
930 // Tile it a few times in the X direction
931 u16 xwise_tiling = column_width / dim.Width;
932 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
934 for(u32 j=0; j<xwise_tiling; j++)
936 // Copy the copy to the atlas
937 /*img2->copyToWithAlpha(atlas_img,
938 pos_in_atlas + v2s32(j*dim.Width,0),
939 core::rect<s32>(v2s32(0,0), dim),
940 video::SColor(255,255,255,255),
942 img2->copyTo(atlas_img,
943 pos_in_atlas + v2s32(j*dim.Width,0),
944 core::rect<s32>(v2s32(0,0), dim),
948 // Copy the borders a few times to disallow texture bleeding
949 for(u32 side=0; side<2; side++) // top and bottom
950 for(s32 y0=0; y0<padding; y0++)
951 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
957 dst_y = y0 + pos_in_atlas.Y + dim.Height;
958 src_y = pos_in_atlas.Y + dim.Height - 1;
962 dst_y = -y0 + pos_in_atlas.Y-1;
963 src_y = pos_in_atlas.Y;
965 s32 x = x0 + pos_in_atlas.X;
966 video::SColor c = atlas_img->getPixel(x, src_y);
967 atlas_img->setPixel(x,dst_y,c);
973 Add texture to caches
976 bool reuse_old_id = false;
977 u32 id = m_atlaspointer_cache.size();
978 // Check old id without fetching a texture
979 core::map<std::string, u32>::Node *n;
980 n = m_name_to_id.find(name);
981 // If it exists, we will replace the old definition
985 /*infostream<<"TextureSource::buildMainAtlas(): "
986 <<"Replacing old AtlasPointer"<<std::endl;*/
989 // Create AtlasPointer
991 ap.atlas = NULL; // Set on the second pass
992 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
993 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
994 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
995 (float)dim.Width/(float)atlas_dim.Height);
996 ap.tiled = xwise_tiling;
998 // Create SourceAtlasPointer and add to containers
999 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
1001 m_atlaspointer_cache[id] = nap;
1003 m_atlaspointer_cache.push_back(nap);
1004 m_name_to_id[name] = id;
1006 // Increment position
1007 pos_in_atlas.Y += dim.Height + padding * 2;
1013 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1017 Second pass: set texture pointer in generated AtlasPointers
1019 for(core::map<std::string, bool>::Iterator
1020 i = sourcelist.getIterator();
1021 i.atEnd() == false; i++)
1023 std::string name = i.getNode()->getKey();
1024 if(m_name_to_id.find(name) == NULL)
1026 u32 id = m_name_to_id[name];
1027 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1028 m_atlaspointer_cache[id].a.atlas = t;
1032 Write image to file so that it can be inspected
1034 /*std::string atlaspath = porting::path_user
1035 + DIR_DELIM + "generated_texture_atlas.png";
1036 infostream<<"Removing and writing texture atlas for inspection to "
1037 <<atlaspath<<std::endl;
1038 fs::RecursiveDelete(atlaspath);
1039 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1042 video::IImage* generate_image_from_scratch(std::string name,
1043 IrrlichtDevice *device, SourceImageCache *sourcecache)
1045 /*infostream<<"generate_image_from_scratch(): "
1046 "\""<<name<<"\""<<std::endl;*/
1048 video::IVideoDriver* driver = device->getVideoDriver();
1055 video::IImage *baseimg = NULL;
1057 char separator = '^';
1059 // Find last meta separator in name
1060 s32 last_separator_position = name.find_last_of(separator);
1061 //if(last_separator_position == std::npos)
1062 // last_separator_position = -1;
1064 /*infostream<<"generate_image_from_scratch(): "
1065 <<"last_separator_position="<<last_separator_position
1069 If separator was found, construct the base name and make the
1070 base image using a recursive call
1072 std::string base_image_name;
1073 if(last_separator_position != -1)
1075 // Construct base name
1076 base_image_name = name.substr(0, last_separator_position);
1077 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1078 " to get base image of \""<<name<<"\" = \""
1079 <<base_image_name<<"\""<<std::endl;*/
1080 baseimg = generate_image_from_scratch(base_image_name, device,
1085 Parse out the last part of the name of the image and act
1089 std::string last_part_of_name = name.substr(last_separator_position+1);
1090 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1092 // Generate image according to part of name
1093 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1095 errorstream<<"generate_image_from_scratch(): "
1096 "failed to generate \""<<last_part_of_name<<"\""
1104 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1105 IrrlichtDevice *device, SourceImageCache *sourcecache)
1107 video::IVideoDriver* driver = device->getVideoDriver();
1110 // Stuff starting with [ are special commands
1111 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1113 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1117 if(part_of_name != ""){
1118 errorstream<<"generate_image(): Could not load image \""
1119 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1120 errorstream<<"generate_image(): Creating a dummy"
1121 <<" image for \""<<part_of_name<<"\""<<std::endl;
1124 // Just create a dummy image
1125 //core::dimension2d<u32> dim(2,2);
1126 core::dimension2d<u32> dim(1,1);
1127 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1129 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1130 image->setPixel(1,0, video::SColor(255,0,255,0));
1131 image->setPixel(0,1, video::SColor(255,0,0,255));
1132 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1133 image->setPixel(0,0, video::SColor(255,myrand()%256,
1134 myrand()%256,myrand()%256));
1135 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1136 myrand()%256,myrand()%256));
1137 image->setPixel(0,1, video::SColor(255,myrand()%256,
1138 myrand()%256,myrand()%256));
1139 image->setPixel(1,1, video::SColor(255,myrand()%256,
1140 myrand()%256,myrand()%256));*/
1143 // If base image is NULL, load as base.
1146 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1148 Copy it this way to get an alpha channel.
1149 Otherwise images with alpha cannot be blitted on
1150 images that don't have alpha in the original file.
1152 core::dimension2d<u32> dim = image->getDimension();
1153 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1154 image->copyTo(baseimg);
1157 // Else blit on base.
1160 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1161 // Size of the copied area
1162 core::dimension2d<u32> dim = image->getDimension();
1163 //core::dimension2d<u32> dim(16,16);
1164 // Position to copy the blitted to in the base image
1165 core::position2d<s32> pos_to(0,0);
1166 // Position to copy the blitted from in the blitted image
1167 core::position2d<s32> pos_from(0,0);
1169 image->copyToWithAlpha(baseimg, pos_to,
1170 core::rect<s32>(pos_from, dim),
1171 video::SColor(255,255,255,255),
1179 // A special texture modification
1181 /*infostream<<"generate_image(): generating special "
1182 <<"modification \""<<part_of_name<<"\""
1186 This is the simplest of all; it just adds stuff to the
1187 name so that a separate texture is created.
1189 It is used to make textures for stuff that doesn't want
1190 to implement getting the texture from a bigger texture
1193 if(part_of_name == "[forcesingle")
1195 // If base image is NULL, create a random color
1198 core::dimension2d<u32> dim(1,1);
1199 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1201 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1202 myrand()%256,myrand()%256));
1207 Adds a cracking texture
1209 else if(part_of_name.substr(0,6) == "[crack")
1213 errorstream<<"generate_image(): baseimg==NULL "
1214 <<"for part_of_name=\""<<part_of_name
1215 <<"\", cancelling."<<std::endl;
1219 // Crack image number and overlay option
1220 s32 progression = 0;
1221 bool use_overlay = false;
1222 if(part_of_name.substr(6,1) == "o")
1224 progression = stoi(part_of_name.substr(7));
1229 progression = stoi(part_of_name.substr(6));
1230 use_overlay = false;
1233 // Size of the base image
1234 core::dimension2d<u32> dim_base = baseimg->getDimension();
1239 It is an image with a number of cracking stages
1242 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1244 if(img_crack && progression >= 0)
1246 // Dimension of original image
1247 core::dimension2d<u32> dim_crack
1248 = img_crack->getDimension();
1249 // Count of crack stages
1250 s32 crack_count = dim_crack.Height / dim_crack.Width;
1251 // Limit progression
1252 if(progression > crack_count-1)
1253 progression = crack_count-1;
1254 // Dimension of a single crack stage
1255 core::dimension2d<u32> dim_crack_cropped(
1259 // Create cropped and scaled crack images
1260 video::IImage *img_crack_cropped = driver->createImage(
1261 video::ECF_A8R8G8B8, dim_crack_cropped);
1262 video::IImage *img_crack_scaled = driver->createImage(
1263 video::ECF_A8R8G8B8, dim_base);
1265 if(img_crack_cropped && img_crack_scaled)
1268 v2s32 pos_crack(0, progression*dim_crack.Width);
1269 img_crack->copyTo(img_crack_cropped,
1271 core::rect<s32>(pos_crack, dim_crack_cropped));
1272 // Scale crack image by copying
1273 img_crack_cropped->copyToScaling(img_crack_scaled);
1274 // Copy or overlay crack image
1277 overlay(baseimg, img_crack_scaled);
1281 img_crack_scaled->copyToWithAlpha(
1284 core::rect<s32>(v2s32(0,0), dim_base),
1285 video::SColor(255,255,255,255));
1289 if(img_crack_scaled)
1290 img_crack_scaled->drop();
1292 if(img_crack_cropped)
1293 img_crack_cropped->drop();
1299 [combine:WxH:X,Y=filename:X,Y=filename2
1300 Creates a bigger texture from an amount of smaller ones
1302 else if(part_of_name.substr(0,8) == "[combine")
1304 Strfnd sf(part_of_name);
1306 u32 w0 = stoi(sf.next("x"));
1307 u32 h0 = stoi(sf.next(":"));
1308 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1309 core::dimension2d<u32> dim(w0,h0);
1310 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1311 while(sf.atend() == false)
1313 u32 x = stoi(sf.next(","));
1314 u32 y = stoi(sf.next("="));
1315 std::string filename = sf.next(":");
1316 infostream<<"Adding \""<<filename
1317 <<"\" to combined ("<<x<<","<<y<<")"
1319 video::IImage *img = sourcecache->getOrLoad(filename, device);
1322 core::dimension2d<u32> dim = img->getDimension();
1323 infostream<<"Size "<<dim.Width
1324 <<"x"<<dim.Height<<std::endl;
1325 core::position2d<s32> pos_base(x, y);
1326 video::IImage *img2 =
1327 driver->createImage(video::ECF_A8R8G8B8, dim);
1330 img2->copyToWithAlpha(baseimg, pos_base,
1331 core::rect<s32>(v2s32(0,0), dim),
1332 video::SColor(255,255,255,255),
1338 infostream<<"img==NULL"<<std::endl;
1345 else if(part_of_name.substr(0,9) == "[brighten")
1349 errorstream<<"generate_image(): baseimg==NULL "
1350 <<"for part_of_name=\""<<part_of_name
1351 <<"\", cancelling."<<std::endl;
1359 Make image completely opaque.
1360 Used for the leaves texture when in old leaves mode, so
1361 that the transparent parts don't look completely black
1362 when simple alpha channel is used for rendering.
1364 else if(part_of_name.substr(0,8) == "[noalpha")
1368 errorstream<<"generate_image(): baseimg==NULL "
1369 <<"for part_of_name=\""<<part_of_name
1370 <<"\", cancelling."<<std::endl;
1374 core::dimension2d<u32> dim = baseimg->getDimension();
1376 // Set alpha to full
1377 for(u32 y=0; y<dim.Height; y++)
1378 for(u32 x=0; x<dim.Width; x++)
1380 video::SColor c = baseimg->getPixel(x,y);
1382 baseimg->setPixel(x,y,c);
1387 Convert one color to transparent.
1389 else if(part_of_name.substr(0,11) == "[makealpha:")
1393 errorstream<<"generate_image(): baseimg==NULL "
1394 <<"for part_of_name=\""<<part_of_name
1395 <<"\", cancelling."<<std::endl;
1399 Strfnd sf(part_of_name.substr(11));
1400 u32 r1 = stoi(sf.next(","));
1401 u32 g1 = stoi(sf.next(","));
1402 u32 b1 = stoi(sf.next(""));
1403 std::string filename = sf.next("");
1405 core::dimension2d<u32> dim = baseimg->getDimension();
1407 /*video::IImage *oldbaseimg = baseimg;
1408 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1409 oldbaseimg->copyTo(baseimg);
1410 oldbaseimg->drop();*/
1412 // Set alpha to full
1413 for(u32 y=0; y<dim.Height; y++)
1414 for(u32 x=0; x<dim.Width; x++)
1416 video::SColor c = baseimg->getPixel(x,y);
1418 u32 g = c.getGreen();
1419 u32 b = c.getBlue();
1420 if(!(r == r1 && g == g1 && b == b1))
1423 baseimg->setPixel(x,y,c);
1428 Rotates and/or flips the image.
1430 N can be a number (between 0 and 7) or a transform name.
1431 Rotations are counter-clockwise.
1433 1 R90 rotate by 90 degrees
1434 2 R180 rotate by 180 degrees
1435 3 R270 rotate by 270 degrees
1437 5 FXR90 flip X then rotate by 90 degrees
1439 7 FYR90 flip Y then rotate by 90 degrees
1441 Note: Transform names can be concatenated to produce
1442 their product (applies the first then the second).
1443 The resulting transform will be equivalent to one of the
1444 eight existing ones, though (see: dihedral group).
1446 else if(part_of_name.substr(0,10) == "[transform")
1450 errorstream<<"generate_image(): baseimg==NULL "
1451 <<"for part_of_name=\""<<part_of_name
1452 <<"\", cancelling."<<std::endl;
1456 u32 transform = parseImageTransform(part_of_name.substr(10));
1457 core::dimension2d<u32> dim = imageTransformDimension(
1458 transform, baseimg->getDimension());
1459 video::IImage *image = driver->createImage(
1460 baseimg->getColorFormat(), dim);
1462 imageTransform(transform, baseimg, image);
1467 [inventorycube{topimage{leftimage{rightimage
1468 In every subimage, replace ^ with &.
1469 Create an "inventory cube".
1470 NOTE: This should be used only on its own.
1471 Example (a grass block (not actually used in game):
1472 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1474 else if(part_of_name.substr(0,14) == "[inventorycube")
1478 errorstream<<"generate_image(): baseimg!=NULL "
1479 <<"for part_of_name=\""<<part_of_name
1480 <<"\", cancelling."<<std::endl;
1484 str_replace_char(part_of_name, '&', '^');
1485 Strfnd sf(part_of_name);
1487 std::string imagename_top = sf.next("{");
1488 std::string imagename_left = sf.next("{");
1489 std::string imagename_right = sf.next("{");
1491 // Generate images for the faces of the cube
1492 video::IImage *img_top = generate_image_from_scratch(
1493 imagename_top, device, sourcecache);
1494 video::IImage *img_left = generate_image_from_scratch(
1495 imagename_left, device, sourcecache);
1496 video::IImage *img_right = generate_image_from_scratch(
1497 imagename_right, device, sourcecache);
1498 assert(img_top && img_left && img_right);
1500 // Create textures from images
1501 video::ITexture *texture_top = driver->addTexture(
1502 (imagename_top + "__temp__").c_str(), img_top);
1503 video::ITexture *texture_left = driver->addTexture(
1504 (imagename_left + "__temp__").c_str(), img_left);
1505 video::ITexture *texture_right = driver->addTexture(
1506 (imagename_right + "__temp__").c_str(), img_right);
1507 assert(texture_top && texture_left && texture_right);
1515 Draw a cube mesh into a render target texture
1517 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1518 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1519 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1520 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1521 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1522 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1523 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1524 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1526 core::dimension2d<u32> dim(64,64);
1527 std::string rtt_texture_name = part_of_name + "_RTT";
1529 v3f camera_position(0, 1.0, -1.5);
1530 camera_position.rotateXZBy(45);
1531 v3f camera_lookat(0, 0, 0);
1532 core::CMatrix4<f32> camera_projection_matrix;
1533 // Set orthogonal projection
1534 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1535 1.65, 1.65, 0, 100);
1537 video::SColorf ambient_light(0.2,0.2,0.2);
1538 v3f light_position(10, 100, -50);
1539 video::SColorf light_color(0.5,0.5,0.5);
1540 f32 light_radius = 1000;
1542 video::ITexture *rtt = generateTextureFromMesh(
1543 cube, device, dim, rtt_texture_name,
1546 camera_projection_matrix,
1555 // Free textures of images
1556 driver->removeTexture(texture_top);
1557 driver->removeTexture(texture_left);
1558 driver->removeTexture(texture_right);
1562 baseimg = generate_image_from_scratch(
1563 imagename_top, device, sourcecache);
1567 // Create image of render target
1568 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1571 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1575 image->copyTo(baseimg);
1581 errorstream<<"generate_image(): Invalid "
1582 " modification: \""<<part_of_name<<"\""<<std::endl;
1589 void overlay(video::IImage *image, video::IImage *overlay)
1592 Copy overlay to image, taking alpha into account.
1593 Where image is transparent, don't copy from overlay.
1594 Images sizes must be identical.
1596 if(image == NULL || overlay == NULL)
1599 core::dimension2d<u32> dim = image->getDimension();
1600 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1601 assert(dim == dim_overlay);
1603 for(u32 y=0; y<dim.Height; y++)
1604 for(u32 x=0; x<dim.Width; x++)
1606 video::SColor c1 = image->getPixel(x,y);
1607 video::SColor c2 = overlay->getPixel(x,y);
1608 u32 a1 = c1.getAlpha();
1609 u32 a2 = c2.getAlpha();
1610 if(a1 == 255 && a2 != 0)
1612 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1613 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1614 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1616 image->setPixel(x,y,c1);
1620 void brighten(video::IImage *image)
1625 core::dimension2d<u32> dim = image->getDimension();
1627 for(u32 y=0; y<dim.Height; y++)
1628 for(u32 x=0; x<dim.Width; x++)
1630 video::SColor c = image->getPixel(x,y);
1631 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1632 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1633 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1634 image->setPixel(x,y,c);
1638 u32 parseImageTransform(const std::string& s)
1640 int total_transform = 0;
1642 std::string transform_names[8];
1643 transform_names[0] = "i";
1644 transform_names[1] = "r90";
1645 transform_names[2] = "r180";
1646 transform_names[3] = "r270";
1647 transform_names[4] = "fx";
1648 transform_names[6] = "fy";
1650 std::size_t pos = 0;
1651 while(pos < s.size())
1654 for(int i = 0; i <= 7; ++i)
1656 const std::string &name_i = transform_names[i];
1658 if(s[pos] == ('0' + i))
1664 else if(!(name_i.empty()) &&
1665 lowercase(s.substr(pos, name_i.size())) == name_i)
1668 pos += name_i.size();
1675 // Multiply total_transform and transform in the group D4
1678 new_total = (transform + total_transform) % 4;
1680 new_total = (transform - total_transform + 8) % 4;
1681 if((transform >= 4) ^ (total_transform >= 4))
1684 total_transform = new_total;
1686 return total_transform;
1689 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1691 if(transform % 2 == 0)
1694 return core::dimension2d<u32>(dim.Height, dim.Width);
1697 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1699 if(src == NULL || dst == NULL)
1702 core::dimension2d<u32> srcdim = src->getDimension();
1703 core::dimension2d<u32> dstdim = dst->getDimension();
1705 assert(dstdim == imageTransformDimension(transform, srcdim));
1706 assert(transform >= 0 && transform <= 7);
1709 Compute the transformation from source coordinates (sx,sy)
1710 to destination coordinates (dx,dy).
1714 if(transform == 0) // identity
1715 sxn = 0, syn = 2; // sx = dx, sy = dy
1716 else if(transform == 1) // rotate by 90 degrees ccw
1717 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1718 else if(transform == 2) // rotate by 180 degrees
1719 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1720 else if(transform == 3) // rotate by 270 degrees ccw
1721 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1722 else if(transform == 4) // flip x
1723 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1724 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1725 sxn = 2, syn = 0; // sx = dy, sy = dx
1726 else if(transform == 6) // flip y
1727 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1728 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1729 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1731 for(u32 dy=0; dy<dstdim.Height; dy++)
1732 for(u32 dx=0; dx<dstdim.Width; dx++)
1734 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1735 u32 sx = entries[sxn];
1736 u32 sy = entries[syn];
1737 video::SColor c = src->getPixel(sx,sy);
1738 dst->setPixel(dx,dy,c);