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
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("client")
136 + DIR_DELIM + "textures" + DIR_DELIM + filename;
137 std::string testpath = porting::path_share + DIR_DELIM + rel_path;
138 // Check all filename extensions. Returns "" if not found.
139 fullpath = getImagePath(testpath);
142 // Add to cache (also an empty result is cached)
143 g_texturename_to_path_cache.set(filename, fullpath);
150 An internal variant of AtlasPointer with more data.
151 (well, more like a wrapper)
154 struct SourceAtlasPointer
158 video::IImage *atlas_img; // The source image of the atlas
159 // Integer variants of position and size
164 const std::string &name_,
165 AtlasPointer a_=AtlasPointer(0, NULL),
166 video::IImage *atlas_img_=NULL,
167 v2s32 intpos_=v2s32(0,0),
168 v2u32 intsize_=v2u32(0,0)
172 atlas_img(atlas_img_),
180 SourceImageCache: A cache used for storing source images.
183 class SourceImageCache
186 void insert(const std::string &name, video::IImage *img,
187 bool prefer_local, video::IVideoDriver *driver)
191 core::map<std::string, video::IImage*>::Node *n;
192 n = m_images.find(name);
194 video::IImage *oldimg = n->getValue();
198 // Try to use local texture instead if asked to
200 std::string path = getTexturePath(name.c_str());
202 video::IImage *img2 = driver->createImageFromFile(path.c_str());
204 m_images[name] = img2;
210 m_images[name] = img;
212 video::IImage* get(const std::string &name)
214 core::map<std::string, video::IImage*>::Node *n;
215 n = m_images.find(name);
217 return n->getValue();
220 // Primarily fetches from cache, secondarily tries to read from filesystem
221 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
223 core::map<std::string, video::IImage*>::Node *n;
224 n = m_images.find(name);
226 n->getValue()->grab(); // Grab for caller
227 return n->getValue();
229 video::IVideoDriver* driver = device->getVideoDriver();
230 std::string path = getTexturePath(name.c_str());
232 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
233 <<name<<"\""<<std::endl;
236 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
238 video::IImage *img = driver->createImageFromFile(path.c_str());
239 // Even if could not be loaded, put as NULL
240 //m_images[name] = img;
242 m_images[name] = img;
243 img->grab(); // Grab for caller
248 core::map<std::string, video::IImage*> m_images;
255 class TextureSource : public IWritableTextureSource
258 TextureSource(IrrlichtDevice *device);
263 Now, assume a texture with the id 1 exists, and has the name
264 "stone.png^mineral1".
265 Then a random thread calls getTextureId for a texture called
266 "stone.png^mineral1^crack0".
267 ...Now, WTF should happen? Well:
268 - getTextureId strips off stuff recursively from the end until
269 the remaining part is found, or nothing is left when
270 something is stripped out
272 But it is slow to search for textures by names and modify them
274 - ContentFeatures is made to contain ids for the basic plain
276 - Crack textures can be slow by themselves, but the framework
280 - Assume a texture with the id 1 exists, and has the name
281 "stone.png^mineral1" and is specified as a part of some atlas.
282 - Now getNodeTile() stumbles upon a node which uses
283 texture id 1, and determines that MATERIAL_FLAG_CRACK
284 must be applied to the tile
285 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
286 has received the current crack level 0 from the client. It
287 finds out the name of the texture with getTextureName(1),
288 appends "^crack0" to it and gets a new texture id with
289 getTextureId("stone.png^mineral1^crack0").
294 Gets a texture id from cache or
295 - if main thread, from getTextureIdDirect
296 - if other thread, adds to request queue and waits for main thread
298 u32 getTextureId(const std::string &name);
304 "stone.png^mineral_coal.png"
305 "stone.png^mineral_coal.png^crack1"
307 - If texture specified by name is found from cache, return the
309 - Otherwise generate the texture, add to cache and return id.
310 Recursion is used to find out the largest found part of the
311 texture and continue based on it.
313 The id 0 points to a NULL texture. It is returned in case of error.
315 u32 getTextureIdDirect(const std::string &name);
317 // Finds out the name of a cached texture.
318 std::string getTextureName(u32 id);
321 If texture specified by the name pointed by the id doesn't
322 exist, create it, then return the cached texture.
324 Can be called from any thread. If called from some other thread
325 and not found in cache, the call is queued to the main thread
328 AtlasPointer getTexture(u32 id);
330 AtlasPointer getTexture(const std::string &name)
332 return getTexture(getTextureId(name));
335 // Gets a separate texture
336 video::ITexture* getTextureRaw(const std::string &name)
338 AtlasPointer ap = getTexture(name + "^[forcesingle");
342 // Gets a separate texture atlas pointer
343 AtlasPointer getTextureRawAP(const AtlasPointer &ap)
345 return getTexture(getTextureName(ap.id) + "^[forcesingle");
348 // Returns a pointer to the irrlicht device
349 virtual IrrlichtDevice* getDevice()
354 // Update new texture pointer and texture coordinates to an
355 // AtlasPointer based on it's texture id
356 void updateAP(AtlasPointer &ap);
358 // Processes queued texture requests from other threads.
359 // Shall be called from the main thread.
362 // Insert an image into the cache without touching the filesystem.
363 // Shall be called from the main thread.
364 void insertSourceImage(const std::string &name, video::IImage *img);
366 // Rebuild images and textures from the current set of source images
367 // Shall be called from the main thread.
368 void rebuildImagesAndTextures();
370 // Build the main texture atlas which contains most of the
372 void buildMainAtlas(class IGameDef *gamedef);
376 // The id of the thread that is allowed to use irrlicht directly
377 threadid_t m_main_thread;
378 // The irrlicht device
379 IrrlichtDevice *m_device;
381 // Cache of source images
382 // This should be only accessed from the main thread
383 SourceImageCache m_sourcecache;
385 // A texture id is index in this array.
386 // The first position contains a NULL texture.
387 core::array<SourceAtlasPointer> m_atlaspointer_cache;
388 // Maps a texture name to an index in the former.
389 core::map<std::string, u32> m_name_to_id;
390 // The two former containers are behind this mutex
391 JMutex m_atlaspointer_cache_mutex;
393 // Main texture atlas. This is filled at startup and is then not touched.
394 video::IImage *m_main_atlas_image;
395 video::ITexture *m_main_atlas_texture;
397 // Queued texture fetches (to be processed by the main thread)
398 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
401 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
403 return new TextureSource(device);
406 TextureSource::TextureSource(IrrlichtDevice *device):
408 m_main_atlas_image(NULL),
409 m_main_atlas_texture(NULL)
413 m_atlaspointer_cache_mutex.Init();
415 m_main_thread = get_current_thread_id();
417 // Add a NULL AtlasPointer as the first index, named ""
418 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
419 m_name_to_id[""] = 0;
422 TextureSource::~TextureSource()
426 u32 TextureSource::getTextureId(const std::string &name)
428 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
432 See if texture already exists
434 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
435 core::map<std::string, u32>::Node *n;
436 n = m_name_to_id.find(name);
439 return n->getValue();
446 if(get_current_thread_id() == m_main_thread)
448 return getTextureIdDirect(name);
452 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
454 // We're gonna ask the result to be put into here
455 ResultQueue<std::string, u32, u8, u8> result_queue;
457 // Throw a request in
458 m_get_texture_queue.add(name, 0, 0, &result_queue);
460 infostream<<"Waiting for texture from main thread, name=\""
461 <<name<<"\""<<std::endl;
465 // Wait result for a second
466 GetResult<std::string, u32, u8, u8>
467 result = result_queue.pop_front(1000);
469 // Check that at least something worked OK
470 assert(result.key == name);
474 catch(ItemNotFoundException &e)
476 infostream<<"Waiting for texture timed out."<<std::endl;
481 infostream<<"getTextureId(): Failed"<<std::endl;
486 // Overlay image on top of another image (used for cracks)
487 void overlay(video::IImage *image, video::IImage *overlay);
490 void brighten(video::IImage *image);
493 Generate image based on a string like "stone.png" or "[crack0".
494 if baseimg is NULL, it is created. Otherwise stuff is made on it.
496 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
497 IrrlichtDevice *device, SourceImageCache *sourcecache);
500 Generates an image from a full string like
501 "stone.png^mineral_coal.png^[crack0".
503 This is used by buildMainAtlas().
505 video::IImage* generate_image_from_scratch(std::string name,
506 IrrlichtDevice *device, SourceImageCache *sourcecache);
509 This method generates all the textures
511 u32 TextureSource::getTextureIdDirect(const std::string &name)
513 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
515 // Empty name means texture 0
518 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
523 Calling only allowed from main thread
525 if(get_current_thread_id() != m_main_thread)
527 errorstream<<"TextureSource::getTextureIdDirect() "
528 "called not from main thread"<<std::endl;
533 See if texture already exists
536 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
538 core::map<std::string, u32>::Node *n;
539 n = m_name_to_id.find(name);
542 /*infostream<<"getTextureIdDirect(): \""<<name
543 <<"\" found in cache"<<std::endl;*/
544 return n->getValue();
548 /*infostream<<"getTextureIdDirect(): \""<<name
549 <<"\" NOT found in cache. Creating it."<<std::endl;*/
555 char separator = '^';
558 This is set to the id of the base image.
559 If left 0, there is no base image and a completely new image
562 u32 base_image_id = 0;
564 // Find last meta separator in name
565 s32 last_separator_position = -1;
566 for(s32 i=name.size()-1; i>=0; i--)
568 if(name[i] == separator)
570 last_separator_position = i;
575 If separator was found, construct the base name and make the
576 base image using a recursive call
578 std::string base_image_name;
579 if(last_separator_position != -1)
581 // Construct base name
582 base_image_name = name.substr(0, last_separator_position);
583 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
584 " to get base image of \""<<name<<"\" = \""
585 <<base_image_name<<"\""<<std::endl;*/
586 base_image_id = getTextureIdDirect(base_image_name);
589 //infostream<<"base_image_id="<<base_image_id<<std::endl;
591 video::IVideoDriver* driver = m_device->getVideoDriver();
594 video::ITexture *t = NULL;
597 An image will be built from files and then converted into a texture.
599 video::IImage *baseimg = NULL;
601 // If a base image was found, copy it to baseimg
602 if(base_image_id != 0)
604 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
606 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
608 video::IImage *image = ap.atlas_img;
612 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
613 <<"cache: \""<<base_image_name<<"\""
618 core::dimension2d<u32> dim = ap.intsize;
620 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
622 core::position2d<s32> pos_to(0,0);
623 core::position2d<s32> pos_from = ap.intpos;
627 v2s32(0,0), // position in target
628 core::rect<s32>(pos_from, dim) // from
631 /*infostream<<"getTextureIdDirect(): Loaded \""
632 <<base_image_name<<"\" from image cache"
638 Parse out the last part of the name of the image and act
642 std::string last_part_of_name = name.substr(last_separator_position+1);
643 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
645 // Generate image according to part of name
646 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
648 errorstream<<"getTextureIdDirect(): "
649 "failed to generate \""<<last_part_of_name<<"\""
653 // If no resulting image, print a warning
656 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
657 " create texture \""<<name<<"\""<<std::endl;
662 // Create texture from resulting image
663 t = driver->addTexture(name.c_str(), baseimg);
667 Add texture to caches (add NULL textures too)
670 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
672 u32 id = m_atlaspointer_cache.size();
678 core::dimension2d<u32> baseimg_dim(0,0);
680 baseimg_dim = baseimg->getDimension();
681 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
682 m_atlaspointer_cache.push_back(nap);
683 m_name_to_id.insert(name, id);
685 /*infostream<<"getTextureIdDirect(): "
686 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
691 std::string TextureSource::getTextureName(u32 id)
693 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
695 if(id >= m_atlaspointer_cache.size())
697 errorstream<<"TextureSource::getTextureName(): id="<<id
698 <<" >= m_atlaspointer_cache.size()="
699 <<m_atlaspointer_cache.size()<<std::endl;
703 return m_atlaspointer_cache[id].name;
707 AtlasPointer TextureSource::getTexture(u32 id)
709 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
711 if(id >= m_atlaspointer_cache.size())
712 return AtlasPointer(0, NULL);
714 return m_atlaspointer_cache[id].a;
717 void TextureSource::updateAP(AtlasPointer &ap)
719 AtlasPointer ap2 = getTexture(ap.id);
723 void TextureSource::processQueue()
728 if(m_get_texture_queue.size() > 0)
730 GetRequest<std::string, u32, u8, u8>
731 request = m_get_texture_queue.pop();
733 /*infostream<<"TextureSource::processQueue(): "
734 <<"got texture request with "
735 <<"name=\""<<request.key<<"\""
738 GetResult<std::string, u32, u8, u8>
740 result.key = request.key;
741 result.callers = request.callers;
742 result.item = getTextureIdDirect(request.key);
744 request.dest->push_back(result);
748 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
750 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
752 assert(get_current_thread_id() == m_main_thread);
754 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
757 void TextureSource::rebuildImagesAndTextures()
759 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
761 /*// Oh well... just clear everything, they'll load sometime.
762 m_atlaspointer_cache.clear();
763 m_name_to_id.clear();*/
765 video::IVideoDriver* driver = m_device->getVideoDriver();
767 // Remove source images from textures to disable inheriting textures
768 // from existing textures
769 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
770 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
771 sap->atlas_img->drop();
772 sap->atlas_img = NULL;
776 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
777 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
779 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
780 // Create texture from resulting image
781 video::ITexture *t = NULL;
783 t = driver->addTexture(sap->name.c_str(), img);
787 sap->a.pos = v2f(0,0);
788 sap->a.size = v2f(1,1);
790 sap->atlas_img = img;
791 sap->intpos = v2s32(0,0);
792 sap->intsize = img->getDimension();
796 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
798 assert(gamedef->tsrc() == this);
799 INodeDefManager *ndef = gamedef->ndef();
801 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
803 //return; // Disable (for testing)
805 video::IVideoDriver* driver = m_device->getVideoDriver();
808 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
810 // Create an image of the right size
811 core::dimension2d<u32> atlas_dim(1024,1024);
812 video::IImage *atlas_img =
813 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
815 if(atlas_img == NULL)
817 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
818 "image; not building texture atlas."<<std::endl;
823 Grab list of stuff to include in the texture atlas from the
824 main content features
827 core::map<std::string, bool> sourcelist;
829 for(u16 j=0; j<MAX_CONTENT+1; j++)
831 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
833 const ContentFeatures &f = ndef->get(j);
834 for(u32 i=0; i<6; i++)
836 std::string name = f.tname_tiles[i];
837 sourcelist[name] = true;
841 infostream<<"Creating texture atlas out of textures: ";
842 for(core::map<std::string, bool>::Iterator
843 i = sourcelist.getIterator();
844 i.atEnd() == false; i++)
846 std::string name = i.getNode()->getKey();
847 infostream<<"\""<<name<<"\" ";
849 infostream<<std::endl;
851 // Padding to disallow texture bleeding
854 s32 column_width = 256;
855 s32 column_padding = 16;
858 First pass: generate almost everything
860 core::position2d<s32> pos_in_atlas(0,0);
862 pos_in_atlas.Y = padding;
864 for(core::map<std::string, bool>::Iterator
865 i = sourcelist.getIterator();
866 i.atEnd() == false; i++)
868 std::string name = i.getNode()->getKey();
870 // Generate image by name
871 video::IImage *img2 = generate_image_from_scratch(name, m_device,
875 errorstream<<"TextureSource::buildMainAtlas(): "
876 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
880 core::dimension2d<u32> dim = img2->getDimension();
882 // Don't add to atlas if image is large
883 core::dimension2d<u32> max_size_in_atlas(32,32);
884 if(dim.Width > max_size_in_atlas.Width
885 || dim.Height > max_size_in_atlas.Height)
887 infostream<<"TextureSource::buildMainAtlas(): Not adding "
888 <<"\""<<name<<"\" because image is large"<<std::endl;
892 // Wrap columns and stop making atlas if atlas is full
893 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
895 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
896 errorstream<<"TextureSource::buildMainAtlas(): "
897 <<"Atlas is full, not adding more textures."
901 pos_in_atlas.Y = padding;
902 pos_in_atlas.X += column_width + column_padding;
905 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
906 <<"\" to texture atlas"<<std::endl;*/
908 // Tile it a few times in the X direction
909 u16 xwise_tiling = column_width / dim.Width;
910 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
912 for(u32 j=0; j<xwise_tiling; j++)
914 // Copy the copy to the atlas
915 /*img2->copyToWithAlpha(atlas_img,
916 pos_in_atlas + v2s32(j*dim.Width,0),
917 core::rect<s32>(v2s32(0,0), dim),
918 video::SColor(255,255,255,255),
920 img2->copyTo(atlas_img,
921 pos_in_atlas + v2s32(j*dim.Width,0),
922 core::rect<s32>(v2s32(0,0), dim),
926 // Copy the borders a few times to disallow texture bleeding
927 for(u32 side=0; side<2; side++) // top and bottom
928 for(s32 y0=0; y0<padding; y0++)
929 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
935 dst_y = y0 + pos_in_atlas.Y + dim.Height;
936 src_y = pos_in_atlas.Y + dim.Height - 1;
940 dst_y = -y0 + pos_in_atlas.Y-1;
941 src_y = pos_in_atlas.Y;
943 s32 x = x0 + pos_in_atlas.X;
944 video::SColor c = atlas_img->getPixel(x, src_y);
945 atlas_img->setPixel(x,dst_y,c);
951 Add texture to caches
954 bool reuse_old_id = false;
955 u32 id = m_atlaspointer_cache.size();
956 // Check old id without fetching a texture
957 core::map<std::string, u32>::Node *n;
958 n = m_name_to_id.find(name);
959 // If it exists, we will replace the old definition
963 /*infostream<<"TextureSource::buildMainAtlas(): "
964 <<"Replacing old AtlasPointer"<<std::endl;*/
967 // Create AtlasPointer
969 ap.atlas = NULL; // Set on the second pass
970 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
971 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
972 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
973 (float)dim.Width/(float)atlas_dim.Height);
974 ap.tiled = xwise_tiling;
976 // Create SourceAtlasPointer and add to containers
977 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
979 m_atlaspointer_cache[id] = nap;
981 m_atlaspointer_cache.push_back(nap);
982 m_name_to_id[name] = id;
984 // Increment position
985 pos_in_atlas.Y += dim.Height + padding * 2;
991 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
995 Second pass: set texture pointer in generated AtlasPointers
997 for(core::map<std::string, bool>::Iterator
998 i = sourcelist.getIterator();
999 i.atEnd() == false; i++)
1001 std::string name = i.getNode()->getKey();
1002 if(m_name_to_id.find(name) == NULL)
1004 u32 id = m_name_to_id[name];
1005 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1006 m_atlaspointer_cache[id].a.atlas = t;
1010 Write image to file so that it can be inspected
1012 /*std::string atlaspath = porting::path_user
1013 + DIR_DELIM + "generated_texture_atlas.png";
1014 infostream<<"Removing and writing texture atlas for inspection to "
1015 <<atlaspath<<std::endl;
1016 fs::RecursiveDelete(atlaspath);
1017 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1020 video::IImage* generate_image_from_scratch(std::string name,
1021 IrrlichtDevice *device, SourceImageCache *sourcecache)
1023 /*infostream<<"generate_image_from_scratch(): "
1024 "\""<<name<<"\""<<std::endl;*/
1026 video::IVideoDriver* driver = device->getVideoDriver();
1033 video::IImage *baseimg = NULL;
1035 char separator = '^';
1037 // Find last meta separator in name
1038 s32 last_separator_position = name.find_last_of(separator);
1039 //if(last_separator_position == std::npos)
1040 // last_separator_position = -1;
1042 /*infostream<<"generate_image_from_scratch(): "
1043 <<"last_separator_position="<<last_separator_position
1047 If separator was found, construct the base name and make the
1048 base image using a recursive call
1050 std::string base_image_name;
1051 if(last_separator_position != -1)
1053 // Construct base name
1054 base_image_name = name.substr(0, last_separator_position);
1055 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1056 " to get base image of \""<<name<<"\" = \""
1057 <<base_image_name<<"\""<<std::endl;*/
1058 baseimg = generate_image_from_scratch(base_image_name, device,
1063 Parse out the last part of the name of the image and act
1067 std::string last_part_of_name = name.substr(last_separator_position+1);
1068 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1070 // Generate image according to part of name
1071 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1073 errorstream<<"generate_image_from_scratch(): "
1074 "failed to generate \""<<last_part_of_name<<"\""
1082 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1083 IrrlichtDevice *device, SourceImageCache *sourcecache)
1085 video::IVideoDriver* driver = device->getVideoDriver();
1088 // Stuff starting with [ are special commands
1089 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1091 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1095 if(part_of_name != ""){
1096 errorstream<<"generate_image(): Could not load image \""
1097 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1098 errorstream<<"generate_image(): Creating a dummy"
1099 <<" image for \""<<part_of_name<<"\""<<std::endl;
1102 // Just create a dummy image
1103 //core::dimension2d<u32> dim(2,2);
1104 core::dimension2d<u32> dim(1,1);
1105 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1107 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1108 image->setPixel(1,0, video::SColor(255,0,255,0));
1109 image->setPixel(0,1, video::SColor(255,0,0,255));
1110 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1111 image->setPixel(0,0, video::SColor(255,myrand()%256,
1112 myrand()%256,myrand()%256));
1113 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1114 myrand()%256,myrand()%256));
1115 image->setPixel(0,1, video::SColor(255,myrand()%256,
1116 myrand()%256,myrand()%256));
1117 image->setPixel(1,1, video::SColor(255,myrand()%256,
1118 myrand()%256,myrand()%256));*/
1121 // If base image is NULL, load as base.
1124 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1126 Copy it this way to get an alpha channel.
1127 Otherwise images with alpha cannot be blitted on
1128 images that don't have alpha in the original file.
1130 core::dimension2d<u32> dim = image->getDimension();
1131 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1132 image->copyTo(baseimg);
1135 // Else blit on base.
1138 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1139 // Size of the copied area
1140 core::dimension2d<u32> dim = image->getDimension();
1141 //core::dimension2d<u32> dim(16,16);
1142 // Position to copy the blitted to in the base image
1143 core::position2d<s32> pos_to(0,0);
1144 // Position to copy the blitted from in the blitted image
1145 core::position2d<s32> pos_from(0,0);
1147 image->copyToWithAlpha(baseimg, pos_to,
1148 core::rect<s32>(pos_from, dim),
1149 video::SColor(255,255,255,255),
1157 // A special texture modification
1159 /*infostream<<"generate_image(): generating special "
1160 <<"modification \""<<part_of_name<<"\""
1164 This is the simplest of all; it just adds stuff to the
1165 name so that a separate texture is created.
1167 It is used to make textures for stuff that doesn't want
1168 to implement getting the texture from a bigger texture
1171 if(part_of_name == "[forcesingle")
1173 // If base image is NULL, create a random color
1176 core::dimension2d<u32> dim(1,1);
1177 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1179 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1180 myrand()%256,myrand()%256));
1185 Adds a cracking texture
1187 else if(part_of_name.substr(0,6) == "[crack")
1191 errorstream<<"generate_image(): baseimg==NULL "
1192 <<"for part_of_name=\""<<part_of_name
1193 <<"\", cancelling."<<std::endl;
1197 // Crack image number and overlay option
1198 s32 progression = 0;
1199 bool use_overlay = false;
1200 if(part_of_name.substr(6,1) == "o")
1202 progression = stoi(part_of_name.substr(7));
1207 progression = stoi(part_of_name.substr(6));
1208 use_overlay = false;
1211 // Size of the base image
1212 core::dimension2d<u32> dim_base = baseimg->getDimension();
1217 It is an image with a number of cracking stages
1220 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1222 if(img_crack && progression >= 0)
1224 // Dimension of original image
1225 core::dimension2d<u32> dim_crack
1226 = img_crack->getDimension();
1227 // Count of crack stages
1228 s32 crack_count = dim_crack.Height / dim_crack.Width;
1229 // Limit progression
1230 if(progression > crack_count-1)
1231 progression = crack_count-1;
1232 // Dimension of a single crack stage
1233 core::dimension2d<u32> dim_crack_cropped(
1237 // Create cropped and scaled crack images
1238 video::IImage *img_crack_cropped = driver->createImage(
1239 video::ECF_A8R8G8B8, dim_crack_cropped);
1240 video::IImage *img_crack_scaled = driver->createImage(
1241 video::ECF_A8R8G8B8, dim_base);
1243 if(img_crack_cropped && img_crack_scaled)
1246 v2s32 pos_crack(0, progression*dim_crack.Width);
1247 img_crack->copyTo(img_crack_cropped,
1249 core::rect<s32>(pos_crack, dim_crack_cropped));
1250 // Scale crack image by copying
1251 img_crack_cropped->copyToScaling(img_crack_scaled);
1252 // Copy or overlay crack image
1255 overlay(baseimg, img_crack_scaled);
1259 img_crack_scaled->copyToWithAlpha(
1262 core::rect<s32>(v2s32(0,0), dim_base),
1263 video::SColor(255,255,255,255));
1267 if(img_crack_scaled)
1268 img_crack_scaled->drop();
1270 if(img_crack_cropped)
1271 img_crack_cropped->drop();
1277 [combine:WxH:X,Y=filename:X,Y=filename2
1278 Creates a bigger texture from an amount of smaller ones
1280 else if(part_of_name.substr(0,8) == "[combine")
1282 Strfnd sf(part_of_name);
1284 u32 w0 = stoi(sf.next("x"));
1285 u32 h0 = stoi(sf.next(":"));
1286 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1287 core::dimension2d<u32> dim(w0,h0);
1288 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1289 while(sf.atend() == false)
1291 u32 x = stoi(sf.next(","));
1292 u32 y = stoi(sf.next("="));
1293 std::string filename = sf.next(":");
1294 infostream<<"Adding \""<<filename
1295 <<"\" to combined ("<<x<<","<<y<<")"
1297 video::IImage *img = sourcecache->getOrLoad(filename, device);
1300 core::dimension2d<u32> dim = img->getDimension();
1301 infostream<<"Size "<<dim.Width
1302 <<"x"<<dim.Height<<std::endl;
1303 core::position2d<s32> pos_base(x, y);
1304 video::IImage *img2 =
1305 driver->createImage(video::ECF_A8R8G8B8, dim);
1308 img2->copyToWithAlpha(baseimg, pos_base,
1309 core::rect<s32>(v2s32(0,0), dim),
1310 video::SColor(255,255,255,255),
1316 infostream<<"img==NULL"<<std::endl;
1323 else if(part_of_name.substr(0,9) == "[brighten")
1327 errorstream<<"generate_image(): baseimg==NULL "
1328 <<"for part_of_name=\""<<part_of_name
1329 <<"\", cancelling."<<std::endl;
1337 Make image completely opaque.
1338 Used for the leaves texture when in old leaves mode, so
1339 that the transparent parts don't look completely black
1340 when simple alpha channel is used for rendering.
1342 else if(part_of_name.substr(0,8) == "[noalpha")
1346 errorstream<<"generate_image(): baseimg==NULL "
1347 <<"for part_of_name=\""<<part_of_name
1348 <<"\", cancelling."<<std::endl;
1352 core::dimension2d<u32> dim = baseimg->getDimension();
1354 // Set alpha to full
1355 for(u32 y=0; y<dim.Height; y++)
1356 for(u32 x=0; x<dim.Width; x++)
1358 video::SColor c = baseimg->getPixel(x,y);
1360 baseimg->setPixel(x,y,c);
1365 Convert one color to transparent.
1367 else if(part_of_name.substr(0,11) == "[makealpha:")
1371 errorstream<<"generate_image(): baseimg==NULL "
1372 <<"for part_of_name=\""<<part_of_name
1373 <<"\", cancelling."<<std::endl;
1377 Strfnd sf(part_of_name.substr(11));
1378 u32 r1 = stoi(sf.next(","));
1379 u32 g1 = stoi(sf.next(","));
1380 u32 b1 = stoi(sf.next(""));
1381 std::string filename = sf.next("");
1383 core::dimension2d<u32> dim = baseimg->getDimension();
1385 /*video::IImage *oldbaseimg = baseimg;
1386 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1387 oldbaseimg->copyTo(baseimg);
1388 oldbaseimg->drop();*/
1390 // Set alpha to full
1391 for(u32 y=0; y<dim.Height; y++)
1392 for(u32 x=0; x<dim.Width; x++)
1394 video::SColor c = baseimg->getPixel(x,y);
1396 u32 g = c.getGreen();
1397 u32 b = c.getBlue();
1398 if(!(r == r1 && g == g1 && b == b1))
1401 baseimg->setPixel(x,y,c);
1405 [inventorycube{topimage{leftimage{rightimage
1406 In every subimage, replace ^ with &.
1407 Create an "inventory cube".
1408 NOTE: This should be used only on its own.
1409 Example (a grass block (not actually used in game):
1410 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1412 else if(part_of_name.substr(0,14) == "[inventorycube")
1416 errorstream<<"generate_image(): baseimg!=NULL "
1417 <<"for part_of_name=\""<<part_of_name
1418 <<"\", cancelling."<<std::endl;
1422 str_replace_char(part_of_name, '&', '^');
1423 Strfnd sf(part_of_name);
1425 std::string imagename_top = sf.next("{");
1426 std::string imagename_left = sf.next("{");
1427 std::string imagename_right = sf.next("{");
1429 // Generate images for the faces of the cube
1430 video::IImage *img_top = generate_image_from_scratch(
1431 imagename_top, device, sourcecache);
1432 video::IImage *img_left = generate_image_from_scratch(
1433 imagename_left, device, sourcecache);
1434 video::IImage *img_right = generate_image_from_scratch(
1435 imagename_right, device, sourcecache);
1436 assert(img_top && img_left && img_right);
1438 // Create textures from images
1439 video::ITexture *texture_top = driver->addTexture(
1440 (imagename_top + "__temp__").c_str(), img_top);
1441 video::ITexture *texture_left = driver->addTexture(
1442 (imagename_left + "__temp__").c_str(), img_left);
1443 video::ITexture *texture_right = driver->addTexture(
1444 (imagename_right + "__temp__").c_str(), img_right);
1445 assert(texture_top && texture_left && texture_right);
1453 Draw a cube mesh into a render target texture
1455 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1456 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1457 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1458 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1459 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1460 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1461 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1462 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1464 core::dimension2d<u32> dim(64,64);
1465 std::string rtt_texture_name = part_of_name + "_RTT";
1467 v3f camera_position(0, 1.0, -1.5);
1468 camera_position.rotateXZBy(45);
1469 v3f camera_lookat(0, 0, 0);
1470 core::CMatrix4<f32> camera_projection_matrix;
1471 // Set orthogonal projection
1472 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1473 1.65, 1.65, 0, 100);
1475 video::SColorf ambient_light(0.2,0.2,0.2);
1476 v3f light_position(10, 100, -50);
1477 video::SColorf light_color(0.5,0.5,0.5);
1478 f32 light_radius = 1000;
1480 video::ITexture *rtt = generateTextureFromMesh(
1481 cube, device, dim, rtt_texture_name,
1484 camera_projection_matrix,
1493 // Free textures of images
1494 driver->removeTexture(texture_top);
1495 driver->removeTexture(texture_left);
1496 driver->removeTexture(texture_right);
1500 baseimg = generate_image_from_scratch(
1501 imagename_top, device, sourcecache);
1505 // Create image of render target
1506 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1509 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1513 image->copyTo(baseimg);
1519 errorstream<<"generate_image(): Invalid "
1520 " modification: \""<<part_of_name<<"\""<<std::endl;
1527 void overlay(video::IImage *image, video::IImage *overlay)
1530 Copy overlay to image, taking alpha into account.
1531 Where image is transparent, don't copy from overlay.
1532 Images sizes must be identical.
1534 if(image == NULL || overlay == NULL)
1537 core::dimension2d<u32> dim = image->getDimension();
1538 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1539 assert(dim == dim_overlay);
1541 for(u32 y=0; y<dim.Height; y++)
1542 for(u32 x=0; x<dim.Width; x++)
1544 video::SColor c1 = image->getPixel(x,y);
1545 video::SColor c2 = overlay->getPixel(x,y);
1546 u32 a1 = c1.getAlpha();
1547 u32 a2 = c2.getAlpha();
1548 if(a1 == 255 && a2 != 0)
1550 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1551 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1552 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1554 image->setPixel(x,y,c1);
1558 void brighten(video::IImage *image)
1563 core::dimension2d<u32> dim = image->getDimension();
1565 for(u32 y=0; y<dim.Height; y++)
1566 for(u32 x=0; x<dim.Width; x++)
1568 video::SColor c = image->getPixel(x,y);
1569 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1570 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1571 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1572 image->setPixel(x,y,c);