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 default data directory
139 std::string base_path = porting::path_share + DIR_DELIM + "textures"
140 + DIR_DELIM + "base" + DIR_DELIM + "pack";
141 std::string testpath = base_path + DIR_DELIM + filename;
142 // Check all filename extensions. Returns "" if not found.
143 fullpath = getImagePath(testpath);
146 // Add to cache (also an empty result is cached)
147 g_texturename_to_path_cache.set(filename, fullpath);
154 An internal variant of AtlasPointer with more data.
155 (well, more like a wrapper)
158 struct SourceAtlasPointer
162 video::IImage *atlas_img; // The source image of the atlas
163 // Integer variants of position and size
168 const std::string &name_,
169 AtlasPointer a_=AtlasPointer(0, NULL),
170 video::IImage *atlas_img_=NULL,
171 v2s32 intpos_=v2s32(0,0),
172 v2u32 intsize_=v2u32(0,0)
176 atlas_img(atlas_img_),
184 SourceImageCache: A cache used for storing source images.
187 class SourceImageCache
190 void insert(const std::string &name, video::IImage *img,
191 bool prefer_local, video::IVideoDriver *driver)
195 core::map<std::string, video::IImage*>::Node *n;
196 n = m_images.find(name);
198 video::IImage *oldimg = n->getValue();
202 // Try to use local texture instead if asked to
204 std::string path = getTexturePath(name.c_str());
206 video::IImage *img2 = driver->createImageFromFile(path.c_str());
208 m_images[name] = img2;
214 m_images[name] = img;
216 video::IImage* get(const std::string &name)
218 core::map<std::string, video::IImage*>::Node *n;
219 n = m_images.find(name);
221 return n->getValue();
224 // Primarily fetches from cache, secondarily tries to read from filesystem
225 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
227 core::map<std::string, video::IImage*>::Node *n;
228 n = m_images.find(name);
230 n->getValue()->grab(); // Grab for caller
231 return n->getValue();
233 video::IVideoDriver* driver = device->getVideoDriver();
234 std::string path = getTexturePath(name.c_str());
236 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
237 <<name<<"\""<<std::endl;
240 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
242 video::IImage *img = driver->createImageFromFile(path.c_str());
243 // Even if could not be loaded, put as NULL
244 //m_images[name] = img;
246 m_images[name] = img;
247 img->grab(); // Grab for caller
252 core::map<std::string, video::IImage*> m_images;
259 class TextureSource : public IWritableTextureSource
262 TextureSource(IrrlichtDevice *device);
267 Now, assume a texture with the id 1 exists, and has the name
268 "stone.png^mineral1".
269 Then a random thread calls getTextureId for a texture called
270 "stone.png^mineral1^crack0".
271 ...Now, WTF should happen? Well:
272 - getTextureId strips off stuff recursively from the end until
273 the remaining part is found, or nothing is left when
274 something is stripped out
276 But it is slow to search for textures by names and modify them
278 - ContentFeatures is made to contain ids for the basic plain
280 - Crack textures can be slow by themselves, but the framework
284 - Assume a texture with the id 1 exists, and has the name
285 "stone.png^mineral1" and is specified as a part of some atlas.
286 - Now getNodeTile() stumbles upon a node which uses
287 texture id 1, and determines that MATERIAL_FLAG_CRACK
288 must be applied to the tile
289 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
290 has received the current crack level 0 from the client. It
291 finds out the name of the texture with getTextureName(1),
292 appends "^crack0" to it and gets a new texture id with
293 getTextureId("stone.png^mineral1^crack0").
298 Gets a texture id from cache or
299 - if main thread, from getTextureIdDirect
300 - if other thread, adds to request queue and waits for main thread
302 u32 getTextureId(const std::string &name);
308 "stone.png^mineral_coal.png"
309 "stone.png^mineral_coal.png^crack1"
311 - If texture specified by name is found from cache, return the
313 - Otherwise generate the texture, add to cache and return id.
314 Recursion is used to find out the largest found part of the
315 texture and continue based on it.
317 The id 0 points to a NULL texture. It is returned in case of error.
319 u32 getTextureIdDirect(const std::string &name);
321 // Finds out the name of a cached texture.
322 std::string getTextureName(u32 id);
325 If texture specified by the name pointed by the id doesn't
326 exist, create it, then return the cached texture.
328 Can be called from any thread. If called from some other thread
329 and not found in cache, the call is queued to the main thread
332 AtlasPointer getTexture(u32 id);
334 AtlasPointer getTexture(const std::string &name)
336 return getTexture(getTextureId(name));
339 // Gets a separate texture
340 video::ITexture* getTextureRaw(const std::string &name)
342 AtlasPointer ap = getTexture(name + "^[forcesingle");
346 // Gets a separate texture atlas pointer
347 AtlasPointer getTextureRawAP(const AtlasPointer &ap)
349 return getTexture(getTextureName(ap.id) + "^[forcesingle");
352 // Returns a pointer to the irrlicht device
353 virtual IrrlichtDevice* getDevice()
358 // Update new texture pointer and texture coordinates to an
359 // AtlasPointer based on it's texture id
360 void updateAP(AtlasPointer &ap);
362 // Processes queued texture requests from other threads.
363 // Shall be called from the main thread.
366 // Insert an image into the cache without touching the filesystem.
367 // Shall be called from the main thread.
368 void insertSourceImage(const std::string &name, video::IImage *img);
370 // Rebuild images and textures from the current set of source images
371 // Shall be called from the main thread.
372 void rebuildImagesAndTextures();
374 // Build the main texture atlas which contains most of the
376 void buildMainAtlas(class IGameDef *gamedef);
380 // The id of the thread that is allowed to use irrlicht directly
381 threadid_t m_main_thread;
382 // The irrlicht device
383 IrrlichtDevice *m_device;
385 // Cache of source images
386 // This should be only accessed from the main thread
387 SourceImageCache m_sourcecache;
389 // A texture id is index in this array.
390 // The first position contains a NULL texture.
391 core::array<SourceAtlasPointer> m_atlaspointer_cache;
392 // Maps a texture name to an index in the former.
393 core::map<std::string, u32> m_name_to_id;
394 // The two former containers are behind this mutex
395 JMutex m_atlaspointer_cache_mutex;
397 // Main texture atlas. This is filled at startup and is then not touched.
398 video::IImage *m_main_atlas_image;
399 video::ITexture *m_main_atlas_texture;
401 // Queued texture fetches (to be processed by the main thread)
402 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
405 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
407 return new TextureSource(device);
410 TextureSource::TextureSource(IrrlichtDevice *device):
412 m_main_atlas_image(NULL),
413 m_main_atlas_texture(NULL)
417 m_atlaspointer_cache_mutex.Init();
419 m_main_thread = get_current_thread_id();
421 // Add a NULL AtlasPointer as the first index, named ""
422 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
423 m_name_to_id[""] = 0;
426 TextureSource::~TextureSource()
430 u32 TextureSource::getTextureId(const std::string &name)
432 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
436 See if texture already exists
438 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
439 core::map<std::string, u32>::Node *n;
440 n = m_name_to_id.find(name);
443 return n->getValue();
450 if(get_current_thread_id() == m_main_thread)
452 return getTextureIdDirect(name);
456 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
458 // We're gonna ask the result to be put into here
459 ResultQueue<std::string, u32, u8, u8> result_queue;
461 // Throw a request in
462 m_get_texture_queue.add(name, 0, 0, &result_queue);
464 infostream<<"Waiting for texture from main thread, name=\""
465 <<name<<"\""<<std::endl;
469 // Wait result for a second
470 GetResult<std::string, u32, u8, u8>
471 result = result_queue.pop_front(1000);
473 // Check that at least something worked OK
474 assert(result.key == name);
478 catch(ItemNotFoundException &e)
480 infostream<<"Waiting for texture timed out."<<std::endl;
485 infostream<<"getTextureId(): Failed"<<std::endl;
490 // Overlay image on top of another image (used for cracks)
491 void overlay(video::IImage *image, video::IImage *overlay);
494 void brighten(video::IImage *image);
497 Generate image based on a string like "stone.png" or "[crack0".
498 if baseimg is NULL, it is created. Otherwise stuff is made on it.
500 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
501 IrrlichtDevice *device, SourceImageCache *sourcecache);
504 Generates an image from a full string like
505 "stone.png^mineral_coal.png^[crack0".
507 This is used by buildMainAtlas().
509 video::IImage* generate_image_from_scratch(std::string name,
510 IrrlichtDevice *device, SourceImageCache *sourcecache);
513 This method generates all the textures
515 u32 TextureSource::getTextureIdDirect(const std::string &name)
517 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
519 // Empty name means texture 0
522 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
527 Calling only allowed from main thread
529 if(get_current_thread_id() != m_main_thread)
531 errorstream<<"TextureSource::getTextureIdDirect() "
532 "called not from main thread"<<std::endl;
537 See if texture already exists
540 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
542 core::map<std::string, u32>::Node *n;
543 n = m_name_to_id.find(name);
546 /*infostream<<"getTextureIdDirect(): \""<<name
547 <<"\" found in cache"<<std::endl;*/
548 return n->getValue();
552 /*infostream<<"getTextureIdDirect(): \""<<name
553 <<"\" NOT found in cache. Creating it."<<std::endl;*/
559 char separator = '^';
562 This is set to the id of the base image.
563 If left 0, there is no base image and a completely new image
566 u32 base_image_id = 0;
568 // Find last meta separator in name
569 s32 last_separator_position = -1;
570 for(s32 i=name.size()-1; i>=0; i--)
572 if(name[i] == separator)
574 last_separator_position = i;
579 If separator was found, construct the base name and make the
580 base image using a recursive call
582 std::string base_image_name;
583 if(last_separator_position != -1)
585 // Construct base name
586 base_image_name = name.substr(0, last_separator_position);
587 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
588 " to get base image of \""<<name<<"\" = \""
589 <<base_image_name<<"\""<<std::endl;*/
590 base_image_id = getTextureIdDirect(base_image_name);
593 //infostream<<"base_image_id="<<base_image_id<<std::endl;
595 video::IVideoDriver* driver = m_device->getVideoDriver();
598 video::ITexture *t = NULL;
601 An image will be built from files and then converted into a texture.
603 video::IImage *baseimg = NULL;
605 // If a base image was found, copy it to baseimg
606 if(base_image_id != 0)
608 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
610 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
612 video::IImage *image = ap.atlas_img;
616 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
617 <<"cache: \""<<base_image_name<<"\""
622 core::dimension2d<u32> dim = ap.intsize;
624 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
626 core::position2d<s32> pos_to(0,0);
627 core::position2d<s32> pos_from = ap.intpos;
631 v2s32(0,0), // position in target
632 core::rect<s32>(pos_from, dim) // from
635 /*infostream<<"getTextureIdDirect(): Loaded \""
636 <<base_image_name<<"\" from image cache"
642 Parse out the last part of the name of the image and act
646 std::string last_part_of_name = name.substr(last_separator_position+1);
647 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
649 // Generate image according to part of name
650 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
652 errorstream<<"getTextureIdDirect(): "
653 "failed to generate \""<<last_part_of_name<<"\""
657 // If no resulting image, print a warning
660 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
661 " create texture \""<<name<<"\""<<std::endl;
666 // Create texture from resulting image
667 t = driver->addTexture(name.c_str(), baseimg);
671 Add texture to caches (add NULL textures too)
674 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
676 u32 id = m_atlaspointer_cache.size();
682 core::dimension2d<u32> baseimg_dim(0,0);
684 baseimg_dim = baseimg->getDimension();
685 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
686 m_atlaspointer_cache.push_back(nap);
687 m_name_to_id.insert(name, id);
689 /*infostream<<"getTextureIdDirect(): "
690 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
695 std::string TextureSource::getTextureName(u32 id)
697 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
699 if(id >= m_atlaspointer_cache.size())
701 errorstream<<"TextureSource::getTextureName(): id="<<id
702 <<" >= m_atlaspointer_cache.size()="
703 <<m_atlaspointer_cache.size()<<std::endl;
707 return m_atlaspointer_cache[id].name;
711 AtlasPointer TextureSource::getTexture(u32 id)
713 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
715 if(id >= m_atlaspointer_cache.size())
716 return AtlasPointer(0, NULL);
718 return m_atlaspointer_cache[id].a;
721 void TextureSource::updateAP(AtlasPointer &ap)
723 AtlasPointer ap2 = getTexture(ap.id);
727 void TextureSource::processQueue()
732 if(m_get_texture_queue.size() > 0)
734 GetRequest<std::string, u32, u8, u8>
735 request = m_get_texture_queue.pop();
737 /*infostream<<"TextureSource::processQueue(): "
738 <<"got texture request with "
739 <<"name=\""<<request.key<<"\""
742 GetResult<std::string, u32, u8, u8>
744 result.key = request.key;
745 result.callers = request.callers;
746 result.item = getTextureIdDirect(request.key);
748 request.dest->push_back(result);
752 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
754 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
756 assert(get_current_thread_id() == m_main_thread);
758 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
761 void TextureSource::rebuildImagesAndTextures()
763 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
765 /*// Oh well... just clear everything, they'll load sometime.
766 m_atlaspointer_cache.clear();
767 m_name_to_id.clear();*/
769 video::IVideoDriver* driver = m_device->getVideoDriver();
771 // Remove source images from textures to disable inheriting textures
772 // from existing textures
773 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
774 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
775 sap->atlas_img->drop();
776 sap->atlas_img = NULL;
780 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
781 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
783 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
784 // Create texture from resulting image
785 video::ITexture *t = NULL;
787 t = driver->addTexture(sap->name.c_str(), img);
791 sap->a.pos = v2f(0,0);
792 sap->a.size = v2f(1,1);
794 sap->atlas_img = img;
795 sap->intpos = v2s32(0,0);
796 sap->intsize = img->getDimension();
800 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
802 assert(gamedef->tsrc() == this);
803 INodeDefManager *ndef = gamedef->ndef();
805 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
807 //return; // Disable (for testing)
809 video::IVideoDriver* driver = m_device->getVideoDriver();
812 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
814 // Create an image of the right size
815 core::dimension2d<u32> atlas_dim(1024,1024);
816 video::IImage *atlas_img =
817 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
819 if(atlas_img == NULL)
821 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
822 "image; not building texture atlas."<<std::endl;
827 Grab list of stuff to include in the texture atlas from the
828 main content features
831 core::map<std::string, bool> sourcelist;
833 for(u16 j=0; j<MAX_CONTENT+1; j++)
835 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
837 const ContentFeatures &f = ndef->get(j);
838 for(u32 i=0; i<6; i++)
840 std::string name = f.tname_tiles[i];
841 sourcelist[name] = true;
845 infostream<<"Creating texture atlas out of textures: ";
846 for(core::map<std::string, bool>::Iterator
847 i = sourcelist.getIterator();
848 i.atEnd() == false; i++)
850 std::string name = i.getNode()->getKey();
851 infostream<<"\""<<name<<"\" ";
853 infostream<<std::endl;
855 // Padding to disallow texture bleeding
858 s32 column_width = 256;
859 s32 column_padding = 16;
862 First pass: generate almost everything
864 core::position2d<s32> pos_in_atlas(0,0);
866 pos_in_atlas.Y = padding;
868 for(core::map<std::string, bool>::Iterator
869 i = sourcelist.getIterator();
870 i.atEnd() == false; i++)
872 std::string name = i.getNode()->getKey();
874 // Generate image by name
875 video::IImage *img2 = generate_image_from_scratch(name, m_device,
879 errorstream<<"TextureSource::buildMainAtlas(): "
880 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
884 core::dimension2d<u32> dim = img2->getDimension();
886 // Don't add to atlas if image is large
887 core::dimension2d<u32> max_size_in_atlas(32,32);
888 if(dim.Width > max_size_in_atlas.Width
889 || dim.Height > max_size_in_atlas.Height)
891 infostream<<"TextureSource::buildMainAtlas(): Not adding "
892 <<"\""<<name<<"\" because image is large"<<std::endl;
896 // Wrap columns and stop making atlas if atlas is full
897 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
899 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
900 errorstream<<"TextureSource::buildMainAtlas(): "
901 <<"Atlas is full, not adding more textures."
905 pos_in_atlas.Y = padding;
906 pos_in_atlas.X += column_width + column_padding;
909 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
910 <<"\" to texture atlas"<<std::endl;*/
912 // Tile it a few times in the X direction
913 u16 xwise_tiling = column_width / dim.Width;
914 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
916 for(u32 j=0; j<xwise_tiling; j++)
918 // Copy the copy to the atlas
919 /*img2->copyToWithAlpha(atlas_img,
920 pos_in_atlas + v2s32(j*dim.Width,0),
921 core::rect<s32>(v2s32(0,0), dim),
922 video::SColor(255,255,255,255),
924 img2->copyTo(atlas_img,
925 pos_in_atlas + v2s32(j*dim.Width,0),
926 core::rect<s32>(v2s32(0,0), dim),
930 // Copy the borders a few times to disallow texture bleeding
931 for(u32 side=0; side<2; side++) // top and bottom
932 for(s32 y0=0; y0<padding; y0++)
933 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
939 dst_y = y0 + pos_in_atlas.Y + dim.Height;
940 src_y = pos_in_atlas.Y + dim.Height - 1;
944 dst_y = -y0 + pos_in_atlas.Y-1;
945 src_y = pos_in_atlas.Y;
947 s32 x = x0 + pos_in_atlas.X;
948 video::SColor c = atlas_img->getPixel(x, src_y);
949 atlas_img->setPixel(x,dst_y,c);
955 Add texture to caches
958 bool reuse_old_id = false;
959 u32 id = m_atlaspointer_cache.size();
960 // Check old id without fetching a texture
961 core::map<std::string, u32>::Node *n;
962 n = m_name_to_id.find(name);
963 // If it exists, we will replace the old definition
967 /*infostream<<"TextureSource::buildMainAtlas(): "
968 <<"Replacing old AtlasPointer"<<std::endl;*/
971 // Create AtlasPointer
973 ap.atlas = NULL; // Set on the second pass
974 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
975 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
976 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
977 (float)dim.Width/(float)atlas_dim.Height);
978 ap.tiled = xwise_tiling;
980 // Create SourceAtlasPointer and add to containers
981 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
983 m_atlaspointer_cache[id] = nap;
985 m_atlaspointer_cache.push_back(nap);
986 m_name_to_id[name] = id;
988 // Increment position
989 pos_in_atlas.Y += dim.Height + padding * 2;
995 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
999 Second pass: set texture pointer in generated AtlasPointers
1001 for(core::map<std::string, bool>::Iterator
1002 i = sourcelist.getIterator();
1003 i.atEnd() == false; i++)
1005 std::string name = i.getNode()->getKey();
1006 if(m_name_to_id.find(name) == NULL)
1008 u32 id = m_name_to_id[name];
1009 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
1010 m_atlaspointer_cache[id].a.atlas = t;
1014 Write image to file so that it can be inspected
1016 /*std::string atlaspath = porting::path_user
1017 + DIR_DELIM + "generated_texture_atlas.png";
1018 infostream<<"Removing and writing texture atlas for inspection to "
1019 <<atlaspath<<std::endl;
1020 fs::RecursiveDelete(atlaspath);
1021 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1024 video::IImage* generate_image_from_scratch(std::string name,
1025 IrrlichtDevice *device, SourceImageCache *sourcecache)
1027 /*infostream<<"generate_image_from_scratch(): "
1028 "\""<<name<<"\""<<std::endl;*/
1030 video::IVideoDriver* driver = device->getVideoDriver();
1037 video::IImage *baseimg = NULL;
1039 char separator = '^';
1041 // Find last meta separator in name
1042 s32 last_separator_position = name.find_last_of(separator);
1043 //if(last_separator_position == std::npos)
1044 // last_separator_position = -1;
1046 /*infostream<<"generate_image_from_scratch(): "
1047 <<"last_separator_position="<<last_separator_position
1051 If separator was found, construct the base name and make the
1052 base image using a recursive call
1054 std::string base_image_name;
1055 if(last_separator_position != -1)
1057 // Construct base name
1058 base_image_name = name.substr(0, last_separator_position);
1059 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1060 " to get base image of \""<<name<<"\" = \""
1061 <<base_image_name<<"\""<<std::endl;*/
1062 baseimg = generate_image_from_scratch(base_image_name, device,
1067 Parse out the last part of the name of the image and act
1071 std::string last_part_of_name = name.substr(last_separator_position+1);
1072 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1074 // Generate image according to part of name
1075 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1077 errorstream<<"generate_image_from_scratch(): "
1078 "failed to generate \""<<last_part_of_name<<"\""
1086 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1087 IrrlichtDevice *device, SourceImageCache *sourcecache)
1089 video::IVideoDriver* driver = device->getVideoDriver();
1092 // Stuff starting with [ are special commands
1093 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1095 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1099 if(part_of_name != ""){
1100 errorstream<<"generate_image(): Could not load image \""
1101 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1102 errorstream<<"generate_image(): Creating a dummy"
1103 <<" image for \""<<part_of_name<<"\""<<std::endl;
1106 // Just create a dummy image
1107 //core::dimension2d<u32> dim(2,2);
1108 core::dimension2d<u32> dim(1,1);
1109 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1111 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1112 image->setPixel(1,0, video::SColor(255,0,255,0));
1113 image->setPixel(0,1, video::SColor(255,0,0,255));
1114 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1115 image->setPixel(0,0, video::SColor(255,myrand()%256,
1116 myrand()%256,myrand()%256));
1117 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1118 myrand()%256,myrand()%256));
1119 image->setPixel(0,1, video::SColor(255,myrand()%256,
1120 myrand()%256,myrand()%256));
1121 image->setPixel(1,1, video::SColor(255,myrand()%256,
1122 myrand()%256,myrand()%256));*/
1125 // If base image is NULL, load as base.
1128 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1130 Copy it this way to get an alpha channel.
1131 Otherwise images with alpha cannot be blitted on
1132 images that don't have alpha in the original file.
1134 core::dimension2d<u32> dim = image->getDimension();
1135 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1136 image->copyTo(baseimg);
1139 // Else blit on base.
1142 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1143 // Size of the copied area
1144 core::dimension2d<u32> dim = image->getDimension();
1145 //core::dimension2d<u32> dim(16,16);
1146 // Position to copy the blitted to in the base image
1147 core::position2d<s32> pos_to(0,0);
1148 // Position to copy the blitted from in the blitted image
1149 core::position2d<s32> pos_from(0,0);
1151 image->copyToWithAlpha(baseimg, pos_to,
1152 core::rect<s32>(pos_from, dim),
1153 video::SColor(255,255,255,255),
1161 // A special texture modification
1163 /*infostream<<"generate_image(): generating special "
1164 <<"modification \""<<part_of_name<<"\""
1168 This is the simplest of all; it just adds stuff to the
1169 name so that a separate texture is created.
1171 It is used to make textures for stuff that doesn't want
1172 to implement getting the texture from a bigger texture
1175 if(part_of_name == "[forcesingle")
1177 // If base image is NULL, create a random color
1180 core::dimension2d<u32> dim(1,1);
1181 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1183 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1184 myrand()%256,myrand()%256));
1189 Adds a cracking texture
1191 else if(part_of_name.substr(0,6) == "[crack")
1195 errorstream<<"generate_image(): baseimg==NULL "
1196 <<"for part_of_name=\""<<part_of_name
1197 <<"\", cancelling."<<std::endl;
1201 // Crack image number and overlay option
1202 s32 progression = 0;
1203 bool use_overlay = false;
1204 if(part_of_name.substr(6,1) == "o")
1206 progression = stoi(part_of_name.substr(7));
1211 progression = stoi(part_of_name.substr(6));
1212 use_overlay = false;
1215 // Size of the base image
1216 core::dimension2d<u32> dim_base = baseimg->getDimension();
1221 It is an image with a number of cracking stages
1224 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1226 if(img_crack && progression >= 0)
1228 // Dimension of original image
1229 core::dimension2d<u32> dim_crack
1230 = img_crack->getDimension();
1231 // Count of crack stages
1232 s32 crack_count = dim_crack.Height / dim_crack.Width;
1233 // Limit progression
1234 if(progression > crack_count-1)
1235 progression = crack_count-1;
1236 // Dimension of a single crack stage
1237 core::dimension2d<u32> dim_crack_cropped(
1241 // Create cropped and scaled crack images
1242 video::IImage *img_crack_cropped = driver->createImage(
1243 video::ECF_A8R8G8B8, dim_crack_cropped);
1244 video::IImage *img_crack_scaled = driver->createImage(
1245 video::ECF_A8R8G8B8, dim_base);
1247 if(img_crack_cropped && img_crack_scaled)
1250 v2s32 pos_crack(0, progression*dim_crack.Width);
1251 img_crack->copyTo(img_crack_cropped,
1253 core::rect<s32>(pos_crack, dim_crack_cropped));
1254 // Scale crack image by copying
1255 img_crack_cropped->copyToScaling(img_crack_scaled);
1256 // Copy or overlay crack image
1259 overlay(baseimg, img_crack_scaled);
1263 img_crack_scaled->copyToWithAlpha(
1266 core::rect<s32>(v2s32(0,0), dim_base),
1267 video::SColor(255,255,255,255));
1271 if(img_crack_scaled)
1272 img_crack_scaled->drop();
1274 if(img_crack_cropped)
1275 img_crack_cropped->drop();
1281 [combine:WxH:X,Y=filename:X,Y=filename2
1282 Creates a bigger texture from an amount of smaller ones
1284 else if(part_of_name.substr(0,8) == "[combine")
1286 Strfnd sf(part_of_name);
1288 u32 w0 = stoi(sf.next("x"));
1289 u32 h0 = stoi(sf.next(":"));
1290 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1291 core::dimension2d<u32> dim(w0,h0);
1292 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1293 while(sf.atend() == false)
1295 u32 x = stoi(sf.next(","));
1296 u32 y = stoi(sf.next("="));
1297 std::string filename = sf.next(":");
1298 infostream<<"Adding \""<<filename
1299 <<"\" to combined ("<<x<<","<<y<<")"
1301 video::IImage *img = sourcecache->getOrLoad(filename, device);
1304 core::dimension2d<u32> dim = img->getDimension();
1305 infostream<<"Size "<<dim.Width
1306 <<"x"<<dim.Height<<std::endl;
1307 core::position2d<s32> pos_base(x, y);
1308 video::IImage *img2 =
1309 driver->createImage(video::ECF_A8R8G8B8, dim);
1312 img2->copyToWithAlpha(baseimg, pos_base,
1313 core::rect<s32>(v2s32(0,0), dim),
1314 video::SColor(255,255,255,255),
1320 infostream<<"img==NULL"<<std::endl;
1327 else if(part_of_name.substr(0,9) == "[brighten")
1331 errorstream<<"generate_image(): baseimg==NULL "
1332 <<"for part_of_name=\""<<part_of_name
1333 <<"\", cancelling."<<std::endl;
1341 Make image completely opaque.
1342 Used for the leaves texture when in old leaves mode, so
1343 that the transparent parts don't look completely black
1344 when simple alpha channel is used for rendering.
1346 else if(part_of_name.substr(0,8) == "[noalpha")
1350 errorstream<<"generate_image(): baseimg==NULL "
1351 <<"for part_of_name=\""<<part_of_name
1352 <<"\", cancelling."<<std::endl;
1356 core::dimension2d<u32> dim = baseimg->getDimension();
1358 // Set alpha to full
1359 for(u32 y=0; y<dim.Height; y++)
1360 for(u32 x=0; x<dim.Width; x++)
1362 video::SColor c = baseimg->getPixel(x,y);
1364 baseimg->setPixel(x,y,c);
1369 Convert one color to transparent.
1371 else if(part_of_name.substr(0,11) == "[makealpha:")
1375 errorstream<<"generate_image(): baseimg==NULL "
1376 <<"for part_of_name=\""<<part_of_name
1377 <<"\", cancelling."<<std::endl;
1381 Strfnd sf(part_of_name.substr(11));
1382 u32 r1 = stoi(sf.next(","));
1383 u32 g1 = stoi(sf.next(","));
1384 u32 b1 = stoi(sf.next(""));
1385 std::string filename = sf.next("");
1387 core::dimension2d<u32> dim = baseimg->getDimension();
1389 /*video::IImage *oldbaseimg = baseimg;
1390 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1391 oldbaseimg->copyTo(baseimg);
1392 oldbaseimg->drop();*/
1394 // Set alpha to full
1395 for(u32 y=0; y<dim.Height; y++)
1396 for(u32 x=0; x<dim.Width; x++)
1398 video::SColor c = baseimg->getPixel(x,y);
1400 u32 g = c.getGreen();
1401 u32 b = c.getBlue();
1402 if(!(r == r1 && g == g1 && b == b1))
1405 baseimg->setPixel(x,y,c);
1409 [inventorycube{topimage{leftimage{rightimage
1410 In every subimage, replace ^ with &.
1411 Create an "inventory cube".
1412 NOTE: This should be used only on its own.
1413 Example (a grass block (not actually used in game):
1414 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1416 else if(part_of_name.substr(0,14) == "[inventorycube")
1420 errorstream<<"generate_image(): baseimg!=NULL "
1421 <<"for part_of_name=\""<<part_of_name
1422 <<"\", cancelling."<<std::endl;
1426 str_replace_char(part_of_name, '&', '^');
1427 Strfnd sf(part_of_name);
1429 std::string imagename_top = sf.next("{");
1430 std::string imagename_left = sf.next("{");
1431 std::string imagename_right = sf.next("{");
1433 // Generate images for the faces of the cube
1434 video::IImage *img_top = generate_image_from_scratch(
1435 imagename_top, device, sourcecache);
1436 video::IImage *img_left = generate_image_from_scratch(
1437 imagename_left, device, sourcecache);
1438 video::IImage *img_right = generate_image_from_scratch(
1439 imagename_right, device, sourcecache);
1440 assert(img_top && img_left && img_right);
1442 // Create textures from images
1443 video::ITexture *texture_top = driver->addTexture(
1444 (imagename_top + "__temp__").c_str(), img_top);
1445 video::ITexture *texture_left = driver->addTexture(
1446 (imagename_left + "__temp__").c_str(), img_left);
1447 video::ITexture *texture_right = driver->addTexture(
1448 (imagename_right + "__temp__").c_str(), img_right);
1449 assert(texture_top && texture_left && texture_right);
1457 Draw a cube mesh into a render target texture
1459 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1460 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1461 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1462 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1463 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1464 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1465 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1466 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1468 core::dimension2d<u32> dim(64,64);
1469 std::string rtt_texture_name = part_of_name + "_RTT";
1471 v3f camera_position(0, 1.0, -1.5);
1472 camera_position.rotateXZBy(45);
1473 v3f camera_lookat(0, 0, 0);
1474 core::CMatrix4<f32> camera_projection_matrix;
1475 // Set orthogonal projection
1476 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1477 1.65, 1.65, 0, 100);
1479 video::SColorf ambient_light(0.2,0.2,0.2);
1480 v3f light_position(10, 100, -50);
1481 video::SColorf light_color(0.5,0.5,0.5);
1482 f32 light_radius = 1000;
1484 video::ITexture *rtt = generateTextureFromMesh(
1485 cube, device, dim, rtt_texture_name,
1488 camera_projection_matrix,
1497 // Free textures of images
1498 driver->removeTexture(texture_top);
1499 driver->removeTexture(texture_left);
1500 driver->removeTexture(texture_right);
1504 baseimg = generate_image_from_scratch(
1505 imagename_top, device, sourcecache);
1509 // Create image of render target
1510 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1513 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1517 image->copyTo(baseimg);
1523 errorstream<<"generate_image(): Invalid "
1524 " modification: \""<<part_of_name<<"\""<<std::endl;
1531 void overlay(video::IImage *image, video::IImage *overlay)
1534 Copy overlay to image, taking alpha into account.
1535 Where image is transparent, don't copy from overlay.
1536 Images sizes must be identical.
1538 if(image == NULL || overlay == NULL)
1541 core::dimension2d<u32> dim = image->getDimension();
1542 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1543 assert(dim == dim_overlay);
1545 for(u32 y=0; y<dim.Height; y++)
1546 for(u32 x=0; x<dim.Width; x++)
1548 video::SColor c1 = image->getPixel(x,y);
1549 video::SColor c2 = overlay->getPixel(x,y);
1550 u32 a1 = c1.getAlpha();
1551 u32 a2 = c2.getAlpha();
1552 if(a1 == 255 && a2 != 0)
1554 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1555 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1556 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1558 image->setPixel(x,y,c1);
1562 void brighten(video::IImage *image)
1567 core::dimension2d<u32> dim = image->getDimension();
1569 for(u32 y=0; y<dim.Height; y++)
1570 for(u32 x=0; x<dim.Width; x++)
1572 video::SColor c = image->getPixel(x,y);
1573 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1574 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1575 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1576 image->setPixel(x,y,c);