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> max_dim = driver->getMaxTextureSize();
834 core::dimension2d<u32> atlas_dim(2048,2048);
835 atlas_dim.Width = MYMIN(atlas_dim.Width, max_dim.Width);
836 atlas_dim.Height = MYMIN(atlas_dim.Height, max_dim.Height);
837 video::IImage *atlas_img =
838 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
840 if(atlas_img == NULL)
842 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
843 "image; not building texture atlas."<<std::endl;
848 Grab list of stuff to include in the texture atlas from the
849 main content features
852 core::map<std::string, bool> sourcelist;
854 for(u16 j=0; j<MAX_CONTENT+1; j++)
856 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
858 const ContentFeatures &f = ndef->get(j);
859 for(u32 i=0; i<6; i++)
861 std::string name = f.tname_tiles[i];
862 sourcelist[name] = true;
866 infostream<<"Creating texture atlas out of textures: ";
867 for(core::map<std::string, bool>::Iterator
868 i = sourcelist.getIterator();
869 i.atEnd() == false; i++)
871 std::string name = i.getNode()->getKey();
872 infostream<<"\""<<name<<"\" ";
874 infostream<<std::endl;
876 // Padding to disallow texture bleeding
877 // (16 needed if mipmapping is used; otherwise less will work too)
879 s32 column_padding = 16;
880 s32 column_width = 256; // Space for 16 pieces of 16x16 textures
883 First pass: generate almost everything
885 core::position2d<s32> pos_in_atlas(0,0);
887 pos_in_atlas.X = column_padding;
888 pos_in_atlas.Y = padding;
890 for(core::map<std::string, bool>::Iterator
891 i = sourcelist.getIterator();
892 i.atEnd() == false; i++)
894 std::string name = i.getNode()->getKey();
896 // Generate image by name
897 video::IImage *img2 = generate_image_from_scratch(name, m_device,
901 errorstream<<"TextureSource::buildMainAtlas(): "
902 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
906 core::dimension2d<u32> dim = img2->getDimension();
908 // Don't add to atlas if image is too large
909 core::dimension2d<u32> max_size_in_atlas(64,64);
910 if(dim.Width > max_size_in_atlas.Width
911 || dim.Height > max_size_in_atlas.Height)
913 infostream<<"TextureSource::buildMainAtlas(): Not adding "
914 <<"\""<<name<<"\" because image is large"<<std::endl;
918 // Wrap columns and stop making atlas if atlas is full
919 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
921 if(pos_in_atlas.X > (s32)atlas_dim.Width - column_width - column_padding){
922 errorstream<<"TextureSource::buildMainAtlas(): "
923 <<"Atlas is full, not adding more textures."
927 pos_in_atlas.Y = padding;
928 pos_in_atlas.X += column_width + column_padding*2;
931 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
932 <<"\" to texture atlas"<<std::endl;*/
934 // Tile it a few times in the X direction
935 u16 xwise_tiling = column_width / dim.Width;
936 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
938 for(u32 j=0; j<xwise_tiling; j++)
940 // Copy the copy to the atlas
941 /*img2->copyToWithAlpha(atlas_img,
942 pos_in_atlas + v2s32(j*dim.Width,0),
943 core::rect<s32>(v2s32(0,0), dim),
944 video::SColor(255,255,255,255),
946 img2->copyTo(atlas_img,
947 pos_in_atlas + v2s32(j*dim.Width,0),
948 core::rect<s32>(v2s32(0,0), dim),
952 // Copy the borders a few times to disallow texture bleeding
953 for(u32 side=0; side<2; side++) // top and bottom
954 for(s32 y0=0; y0<padding; y0++)
955 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
961 dst_y = y0 + pos_in_atlas.Y + dim.Height;
962 src_y = pos_in_atlas.Y + dim.Height - 1;
966 dst_y = -y0 + pos_in_atlas.Y-1;
967 src_y = pos_in_atlas.Y;
969 s32 x = x0 + pos_in_atlas.X;
970 video::SColor c = atlas_img->getPixel(x, src_y);
971 atlas_img->setPixel(x,dst_y,c);
974 for(u32 side=0; side<2; side++) // left and right
975 for(s32 x0=0; x0<column_padding; x0++)
976 for(s32 y0=-padding; y0<(s32)dim.Height+padding; y0++)
982 dst_x = x0 + pos_in_atlas.X + dim.Width*xwise_tiling;
983 src_x = pos_in_atlas.X + dim.Width*xwise_tiling - 1;
987 dst_x = -x0 + pos_in_atlas.X-1;
988 src_x = pos_in_atlas.X;
990 s32 y = y0 + pos_in_atlas.Y;
991 s32 src_y = MYMAX(pos_in_atlas.Y, MYMIN(pos_in_atlas.Y + dim.Height - 1, y));
993 video::SColor c = atlas_img->getPixel(src_x, src_y);
994 atlas_img->setPixel(dst_x,dst_y,c);
1000 Add texture to caches
1003 bool reuse_old_id = false;
1004 u32 id = m_atlaspointer_cache.size();
1005 // Check old id without fetching a texture
1006 core::map<std::string, u32>::Node *n;
1007 n = m_name_to_id.find(name);
1008 // If it exists, we will replace the old definition
1011 reuse_old_id = true;
1012 /*infostream<<"TextureSource::buildMainAtlas(): "
1013 <<"Replacing old AtlasPointer"<<std::endl;*/
1016 // Create AtlasPointer
1017 AtlasPointer ap(id);
1018 ap.atlas = NULL; // Set on the second pass
1019 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
1020 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
1021 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
1022 (float)dim.Width/(float)atlas_dim.Height);
1023 ap.tiled = xwise_tiling;
1025 // Create SourceAtlasPointer and add to containers
1026 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
1028 m_atlaspointer_cache[id] = nap;
1030 m_atlaspointer_cache.push_back(nap);
1031 m_name_to_id[name] = id;
1033 // Increment position
1034 pos_in_atlas.Y += dim.Height + padding * 2;
1040 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
1044 Second pass: set texture pointer in generated AtlasPointers
1046 for(core::map<std::string, bool>::Iterator
1047 i = sourcelist.getIterator();
1048 i.atEnd() == false; i++)
1050 std::string name = i.getNode()->getKey();
1051 if(m_name_to_id.find(name) == NULL)
1053 u32 id = m_name_to_id[name];
1054 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1055 m_atlaspointer_cache[id].a.atlas = t;
1059 Write image to file so that it can be inspected
1061 /*std::string atlaspath = porting::path_user
1062 + DIR_DELIM + "generated_texture_atlas.png";
1063 infostream<<"Removing and writing texture atlas for inspection to "
1064 <<atlaspath<<std::endl;
1065 fs::RecursiveDelete(atlaspath);
1066 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1069 video::IImage* generate_image_from_scratch(std::string name,
1070 IrrlichtDevice *device, SourceImageCache *sourcecache)
1072 /*infostream<<"generate_image_from_scratch(): "
1073 "\""<<name<<"\""<<std::endl;*/
1075 video::IVideoDriver* driver = device->getVideoDriver();
1082 video::IImage *baseimg = NULL;
1084 char separator = '^';
1086 // Find last meta separator in name
1087 s32 last_separator_position = name.find_last_of(separator);
1088 //if(last_separator_position == std::npos)
1089 // last_separator_position = -1;
1091 /*infostream<<"generate_image_from_scratch(): "
1092 <<"last_separator_position="<<last_separator_position
1096 If separator was found, construct the base name and make the
1097 base image using a recursive call
1099 std::string base_image_name;
1100 if(last_separator_position != -1)
1102 // Construct base name
1103 base_image_name = name.substr(0, last_separator_position);
1104 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1105 " to get base image of \""<<name<<"\" = \""
1106 <<base_image_name<<"\""<<std::endl;*/
1107 baseimg = generate_image_from_scratch(base_image_name, device,
1112 Parse out the last part of the name of the image and act
1116 std::string last_part_of_name = name.substr(last_separator_position+1);
1117 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1119 // Generate image according to part of name
1120 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1122 errorstream<<"generate_image_from_scratch(): "
1123 "failed to generate \""<<last_part_of_name<<"\""
1131 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1132 IrrlichtDevice *device, SourceImageCache *sourcecache)
1134 video::IVideoDriver* driver = device->getVideoDriver();
1137 // Stuff starting with [ are special commands
1138 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1140 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1144 if(part_of_name != ""){
1145 errorstream<<"generate_image(): Could not load image \""
1146 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1147 errorstream<<"generate_image(): Creating a dummy"
1148 <<" image for \""<<part_of_name<<"\""<<std::endl;
1151 // Just create a dummy image
1152 //core::dimension2d<u32> dim(2,2);
1153 core::dimension2d<u32> dim(1,1);
1154 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1156 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1157 image->setPixel(1,0, video::SColor(255,0,255,0));
1158 image->setPixel(0,1, video::SColor(255,0,0,255));
1159 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1160 image->setPixel(0,0, video::SColor(255,myrand()%256,
1161 myrand()%256,myrand()%256));
1162 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1163 myrand()%256,myrand()%256));
1164 image->setPixel(0,1, video::SColor(255,myrand()%256,
1165 myrand()%256,myrand()%256));
1166 image->setPixel(1,1, video::SColor(255,myrand()%256,
1167 myrand()%256,myrand()%256));*/
1170 // If base image is NULL, load as base.
1173 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1175 Copy it this way to get an alpha channel.
1176 Otherwise images with alpha cannot be blitted on
1177 images that don't have alpha in the original file.
1179 core::dimension2d<u32> dim = image->getDimension();
1180 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1181 image->copyTo(baseimg);
1184 // Else blit on base.
1187 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1188 // Size of the copied area
1189 core::dimension2d<u32> dim = image->getDimension();
1190 //core::dimension2d<u32> dim(16,16);
1191 // Position to copy the blitted to in the base image
1192 core::position2d<s32> pos_to(0,0);
1193 // Position to copy the blitted from in the blitted image
1194 core::position2d<s32> pos_from(0,0);
1196 image->copyToWithAlpha(baseimg, pos_to,
1197 core::rect<s32>(pos_from, dim),
1198 video::SColor(255,255,255,255),
1206 // A special texture modification
1208 /*infostream<<"generate_image(): generating special "
1209 <<"modification \""<<part_of_name<<"\""
1213 This is the simplest of all; it just adds stuff to the
1214 name so that a separate texture is created.
1216 It is used to make textures for stuff that doesn't want
1217 to implement getting the texture from a bigger texture
1220 if(part_of_name == "[forcesingle")
1222 // If base image is NULL, create a random color
1225 core::dimension2d<u32> dim(1,1);
1226 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1228 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1229 myrand()%256,myrand()%256));
1234 Adds a cracking texture
1236 else if(part_of_name.substr(0,6) == "[crack")
1240 errorstream<<"generate_image(): baseimg==NULL "
1241 <<"for part_of_name=\""<<part_of_name
1242 <<"\", cancelling."<<std::endl;
1246 // Crack image number and overlay option
1247 s32 progression = 0;
1248 bool use_overlay = false;
1249 if(part_of_name.substr(6,1) == "o")
1251 progression = stoi(part_of_name.substr(7));
1256 progression = stoi(part_of_name.substr(6));
1257 use_overlay = false;
1260 // Size of the base image
1261 core::dimension2d<u32> dim_base = baseimg->getDimension();
1266 It is an image with a number of cracking stages
1269 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1271 if(img_crack && progression >= 0)
1273 // Dimension of original image
1274 core::dimension2d<u32> dim_crack
1275 = img_crack->getDimension();
1276 // Count of crack stages
1277 s32 crack_count = dim_crack.Height / dim_crack.Width;
1278 // Limit progression
1279 if(progression > crack_count-1)
1280 progression = crack_count-1;
1281 // Dimension of a single crack stage
1282 core::dimension2d<u32> dim_crack_cropped(
1286 // Create cropped and scaled crack images
1287 video::IImage *img_crack_cropped = driver->createImage(
1288 video::ECF_A8R8G8B8, dim_crack_cropped);
1289 video::IImage *img_crack_scaled = driver->createImage(
1290 video::ECF_A8R8G8B8, dim_base);
1292 if(img_crack_cropped && img_crack_scaled)
1295 v2s32 pos_crack(0, progression*dim_crack.Width);
1296 img_crack->copyTo(img_crack_cropped,
1298 core::rect<s32>(pos_crack, dim_crack_cropped));
1299 // Scale crack image by copying
1300 img_crack_cropped->copyToScaling(img_crack_scaled);
1301 // Copy or overlay crack image
1304 overlay(baseimg, img_crack_scaled);
1308 img_crack_scaled->copyToWithAlpha(
1311 core::rect<s32>(v2s32(0,0), dim_base),
1312 video::SColor(255,255,255,255));
1316 if(img_crack_scaled)
1317 img_crack_scaled->drop();
1319 if(img_crack_cropped)
1320 img_crack_cropped->drop();
1326 [combine:WxH:X,Y=filename:X,Y=filename2
1327 Creates a bigger texture from an amount of smaller ones
1329 else if(part_of_name.substr(0,8) == "[combine")
1331 Strfnd sf(part_of_name);
1333 u32 w0 = stoi(sf.next("x"));
1334 u32 h0 = stoi(sf.next(":"));
1335 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1336 core::dimension2d<u32> dim(w0,h0);
1337 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1338 while(sf.atend() == false)
1340 u32 x = stoi(sf.next(","));
1341 u32 y = stoi(sf.next("="));
1342 std::string filename = sf.next(":");
1343 infostream<<"Adding \""<<filename
1344 <<"\" to combined ("<<x<<","<<y<<")"
1346 video::IImage *img = sourcecache->getOrLoad(filename, device);
1349 core::dimension2d<u32> dim = img->getDimension();
1350 infostream<<"Size "<<dim.Width
1351 <<"x"<<dim.Height<<std::endl;
1352 core::position2d<s32> pos_base(x, y);
1353 video::IImage *img2 =
1354 driver->createImage(video::ECF_A8R8G8B8, dim);
1357 img2->copyToWithAlpha(baseimg, pos_base,
1358 core::rect<s32>(v2s32(0,0), dim),
1359 video::SColor(255,255,255,255),
1365 infostream<<"img==NULL"<<std::endl;
1372 else if(part_of_name.substr(0,9) == "[brighten")
1376 errorstream<<"generate_image(): baseimg==NULL "
1377 <<"for part_of_name=\""<<part_of_name
1378 <<"\", cancelling."<<std::endl;
1386 Make image completely opaque.
1387 Used for the leaves texture when in old leaves mode, so
1388 that the transparent parts don't look completely black
1389 when simple alpha channel is used for rendering.
1391 else if(part_of_name.substr(0,8) == "[noalpha")
1395 errorstream<<"generate_image(): baseimg==NULL "
1396 <<"for part_of_name=\""<<part_of_name
1397 <<"\", cancelling."<<std::endl;
1401 core::dimension2d<u32> dim = baseimg->getDimension();
1403 // Set alpha to full
1404 for(u32 y=0; y<dim.Height; y++)
1405 for(u32 x=0; x<dim.Width; x++)
1407 video::SColor c = baseimg->getPixel(x,y);
1409 baseimg->setPixel(x,y,c);
1414 Convert one color to transparent.
1416 else if(part_of_name.substr(0,11) == "[makealpha:")
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(11));
1427 u32 r1 = stoi(sf.next(","));
1428 u32 g1 = stoi(sf.next(","));
1429 u32 b1 = stoi(sf.next(""));
1430 std::string filename = sf.next("");
1432 core::dimension2d<u32> dim = baseimg->getDimension();
1434 /*video::IImage *oldbaseimg = baseimg;
1435 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1436 oldbaseimg->copyTo(baseimg);
1437 oldbaseimg->drop();*/
1439 // Set alpha to full
1440 for(u32 y=0; y<dim.Height; y++)
1441 for(u32 x=0; x<dim.Width; x++)
1443 video::SColor c = baseimg->getPixel(x,y);
1445 u32 g = c.getGreen();
1446 u32 b = c.getBlue();
1447 if(!(r == r1 && g == g1 && b == b1))
1450 baseimg->setPixel(x,y,c);
1455 Rotates and/or flips the image.
1457 N can be a number (between 0 and 7) or a transform name.
1458 Rotations are counter-clockwise.
1460 1 R90 rotate by 90 degrees
1461 2 R180 rotate by 180 degrees
1462 3 R270 rotate by 270 degrees
1464 5 FXR90 flip X then rotate by 90 degrees
1466 7 FYR90 flip Y then rotate by 90 degrees
1468 Note: Transform names can be concatenated to produce
1469 their product (applies the first then the second).
1470 The resulting transform will be equivalent to one of the
1471 eight existing ones, though (see: dihedral group).
1473 else if(part_of_name.substr(0,10) == "[transform")
1477 errorstream<<"generate_image(): baseimg==NULL "
1478 <<"for part_of_name=\""<<part_of_name
1479 <<"\", cancelling."<<std::endl;
1483 u32 transform = parseImageTransform(part_of_name.substr(10));
1484 core::dimension2d<u32> dim = imageTransformDimension(
1485 transform, baseimg->getDimension());
1486 video::IImage *image = driver->createImage(
1487 baseimg->getColorFormat(), dim);
1489 imageTransform(transform, baseimg, image);
1494 [inventorycube{topimage{leftimage{rightimage
1495 In every subimage, replace ^ with &.
1496 Create an "inventory cube".
1497 NOTE: This should be used only on its own.
1498 Example (a grass block (not actually used in game):
1499 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1501 else if(part_of_name.substr(0,14) == "[inventorycube")
1505 errorstream<<"generate_image(): baseimg!=NULL "
1506 <<"for part_of_name=\""<<part_of_name
1507 <<"\", cancelling."<<std::endl;
1511 str_replace_char(part_of_name, '&', '^');
1512 Strfnd sf(part_of_name);
1514 std::string imagename_top = sf.next("{");
1515 std::string imagename_left = sf.next("{");
1516 std::string imagename_right = sf.next("{");
1518 // Generate images for the faces of the cube
1519 video::IImage *img_top = generate_image_from_scratch(
1520 imagename_top, device, sourcecache);
1521 video::IImage *img_left = generate_image_from_scratch(
1522 imagename_left, device, sourcecache);
1523 video::IImage *img_right = generate_image_from_scratch(
1524 imagename_right, device, sourcecache);
1525 assert(img_top && img_left && img_right);
1527 // Create textures from images
1528 video::ITexture *texture_top = driver->addTexture(
1529 (imagename_top + "__temp__").c_str(), img_top);
1530 video::ITexture *texture_left = driver->addTexture(
1531 (imagename_left + "__temp__").c_str(), img_left);
1532 video::ITexture *texture_right = driver->addTexture(
1533 (imagename_right + "__temp__").c_str(), img_right);
1534 assert(texture_top && texture_left && texture_right);
1542 Draw a cube mesh into a render target texture
1544 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1545 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1546 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1547 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1548 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1549 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1550 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1551 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1553 core::dimension2d<u32> dim(64,64);
1554 std::string rtt_texture_name = part_of_name + "_RTT";
1556 v3f camera_position(0, 1.0, -1.5);
1557 camera_position.rotateXZBy(45);
1558 v3f camera_lookat(0, 0, 0);
1559 core::CMatrix4<f32> camera_projection_matrix;
1560 // Set orthogonal projection
1561 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1562 1.65, 1.65, 0, 100);
1564 video::SColorf ambient_light(0.2,0.2,0.2);
1565 v3f light_position(10, 100, -50);
1566 video::SColorf light_color(0.5,0.5,0.5);
1567 f32 light_radius = 1000;
1569 video::ITexture *rtt = generateTextureFromMesh(
1570 cube, device, dim, rtt_texture_name,
1573 camera_projection_matrix,
1582 // Free textures of images
1583 driver->removeTexture(texture_top);
1584 driver->removeTexture(texture_left);
1585 driver->removeTexture(texture_right);
1589 baseimg = generate_image_from_scratch(
1590 imagename_top, device, sourcecache);
1594 // Create image of render target
1595 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1598 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1602 image->copyTo(baseimg);
1608 errorstream<<"generate_image(): Invalid "
1609 " modification: \""<<part_of_name<<"\""<<std::endl;
1616 void overlay(video::IImage *image, video::IImage *overlay)
1619 Copy overlay to image, taking alpha into account.
1620 Where image is transparent, don't copy from overlay.
1621 Images sizes must be identical.
1623 if(image == NULL || overlay == NULL)
1626 core::dimension2d<u32> dim = image->getDimension();
1627 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1628 assert(dim == dim_overlay);
1630 for(u32 y=0; y<dim.Height; y++)
1631 for(u32 x=0; x<dim.Width; x++)
1633 video::SColor c1 = image->getPixel(x,y);
1634 video::SColor c2 = overlay->getPixel(x,y);
1635 u32 a1 = c1.getAlpha();
1636 u32 a2 = c2.getAlpha();
1637 if(a1 == 255 && a2 != 0)
1639 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1640 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1641 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1643 image->setPixel(x,y,c1);
1647 void brighten(video::IImage *image)
1652 core::dimension2d<u32> dim = image->getDimension();
1654 for(u32 y=0; y<dim.Height; y++)
1655 for(u32 x=0; x<dim.Width; x++)
1657 video::SColor c = image->getPixel(x,y);
1658 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1659 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1660 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1661 image->setPixel(x,y,c);
1665 u32 parseImageTransform(const std::string& s)
1667 int total_transform = 0;
1669 std::string transform_names[8];
1670 transform_names[0] = "i";
1671 transform_names[1] = "r90";
1672 transform_names[2] = "r180";
1673 transform_names[3] = "r270";
1674 transform_names[4] = "fx";
1675 transform_names[6] = "fy";
1677 std::size_t pos = 0;
1678 while(pos < s.size())
1681 for(int i = 0; i <= 7; ++i)
1683 const std::string &name_i = transform_names[i];
1685 if(s[pos] == ('0' + i))
1691 else if(!(name_i.empty()) &&
1692 lowercase(s.substr(pos, name_i.size())) == name_i)
1695 pos += name_i.size();
1702 // Multiply total_transform and transform in the group D4
1705 new_total = (transform + total_transform) % 4;
1707 new_total = (transform - total_transform + 8) % 4;
1708 if((transform >= 4) ^ (total_transform >= 4))
1711 total_transform = new_total;
1713 return total_transform;
1716 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1718 if(transform % 2 == 0)
1721 return core::dimension2d<u32>(dim.Height, dim.Width);
1724 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1726 if(src == NULL || dst == NULL)
1729 core::dimension2d<u32> srcdim = src->getDimension();
1730 core::dimension2d<u32> dstdim = dst->getDimension();
1732 assert(dstdim == imageTransformDimension(transform, srcdim));
1733 assert(transform >= 0 && transform <= 7);
1736 Compute the transformation from source coordinates (sx,sy)
1737 to destination coordinates (dx,dy).
1741 if(transform == 0) // identity
1742 sxn = 0, syn = 2; // sx = dx, sy = dy
1743 else if(transform == 1) // rotate by 90 degrees ccw
1744 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1745 else if(transform == 2) // rotate by 180 degrees
1746 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1747 else if(transform == 3) // rotate by 270 degrees ccw
1748 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1749 else if(transform == 4) // flip x
1750 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1751 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1752 sxn = 2, syn = 0; // sx = dy, sy = dx
1753 else if(transform == 6) // flip y
1754 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1755 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1756 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1758 for(u32 dy=0; dy<dstdim.Height; dy++)
1759 for(u32 dx=0; dx<dstdim.Width; dx++)
1761 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1762 u32 sx = entries[sxn];
1763 u32 sy = entries[syn];
1764 video::SColor c = src->getPixel(sx,sy);
1765 dst->setPixel(dx,dy,c);