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 MapBlock::getNodeTile() stumbles upon a node which uses
283 texture id 1, and finds out that NODEMOD_CRACK must be applied
285 - It finds out the name of the texture with getTextureName(1),
286 appends "^crack0" to it and gets a new texture id with
287 getTextureId("stone.png^mineral1^crack0")
292 Gets a texture id from cache or
293 - if main thread, from getTextureIdDirect
294 - if other thread, adds to request queue and waits for main thread
296 u32 getTextureId(const std::string &name);
302 "stone.png^mineral_coal.png"
303 "stone.png^mineral_coal.png^crack1"
305 - If texture specified by name is found from cache, return the
307 - Otherwise generate the texture, add to cache and return id.
308 Recursion is used to find out the largest found part of the
309 texture and continue based on it.
311 The id 0 points to a NULL texture. It is returned in case of error.
313 u32 getTextureIdDirect(const std::string &name);
315 // Finds out the name of a cached texture.
316 std::string getTextureName(u32 id);
319 If texture specified by the name pointed by the id doesn't
320 exist, create it, then return the cached texture.
322 Can be called from any thread. If called from some other thread
323 and not found in cache, the call is queued to the main thread
326 AtlasPointer getTexture(u32 id);
328 AtlasPointer getTexture(const std::string &name)
330 return getTexture(getTextureId(name));
333 // Gets a separate texture
334 video::ITexture* getTextureRaw(const std::string &name)
336 AtlasPointer ap = getTexture(name + "^[forcesingle");
340 // Returns a pointer to the irrlicht device
341 virtual IrrlichtDevice* getDevice()
346 // Update new texture pointer and texture coordinates to an
347 // AtlasPointer based on it's texture id
348 void updateAP(AtlasPointer &ap);
350 // Processes queued texture requests from other threads.
351 // Shall be called from the main thread.
354 // Insert an image into the cache without touching the filesystem.
355 // Shall be called from the main thread.
356 void insertSourceImage(const std::string &name, video::IImage *img);
358 // Rebuild images and textures from the current set of source images
359 // Shall be called from the main thread.
360 void rebuildImagesAndTextures();
362 // Build the main texture atlas which contains most of the
364 void buildMainAtlas(class IGameDef *gamedef);
368 // The id of the thread that is allowed to use irrlicht directly
369 threadid_t m_main_thread;
370 // The irrlicht device
371 IrrlichtDevice *m_device;
373 // Cache of source images
374 // This should be only accessed from the main thread
375 SourceImageCache m_sourcecache;
377 // A texture id is index in this array.
378 // The first position contains a NULL texture.
379 core::array<SourceAtlasPointer> m_atlaspointer_cache;
380 // Maps a texture name to an index in the former.
381 core::map<std::string, u32> m_name_to_id;
382 // The two former containers are behind this mutex
383 JMutex m_atlaspointer_cache_mutex;
385 // Main texture atlas. This is filled at startup and is then not touched.
386 video::IImage *m_main_atlas_image;
387 video::ITexture *m_main_atlas_texture;
389 // Queued texture fetches (to be processed by the main thread)
390 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
393 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
395 return new TextureSource(device);
398 TextureSource::TextureSource(IrrlichtDevice *device):
400 m_main_atlas_image(NULL),
401 m_main_atlas_texture(NULL)
405 m_atlaspointer_cache_mutex.Init();
407 m_main_thread = get_current_thread_id();
409 // Add a NULL AtlasPointer as the first index, named ""
410 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
411 m_name_to_id[""] = 0;
414 TextureSource::~TextureSource()
418 u32 TextureSource::getTextureId(const std::string &name)
420 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
424 See if texture already exists
426 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
427 core::map<std::string, u32>::Node *n;
428 n = m_name_to_id.find(name);
431 return n->getValue();
438 if(get_current_thread_id() == m_main_thread)
440 return getTextureIdDirect(name);
444 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
446 // We're gonna ask the result to be put into here
447 ResultQueue<std::string, u32, u8, u8> result_queue;
449 // Throw a request in
450 m_get_texture_queue.add(name, 0, 0, &result_queue);
452 infostream<<"Waiting for texture from main thread, name=\""
453 <<name<<"\""<<std::endl;
457 // Wait result for a second
458 GetResult<std::string, u32, u8, u8>
459 result = result_queue.pop_front(1000);
461 // Check that at least something worked OK
462 assert(result.key == name);
466 catch(ItemNotFoundException &e)
468 infostream<<"Waiting for texture timed out."<<std::endl;
473 infostream<<"getTextureId(): Failed"<<std::endl;
479 void brighten(video::IImage *image);
482 Generate image based on a string like "stone.png" or "[crack0".
483 if baseimg is NULL, it is created. Otherwise stuff is made on it.
485 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
486 IrrlichtDevice *device, SourceImageCache *sourcecache);
489 Generates an image from a full string like
490 "stone.png^mineral_coal.png^[crack0".
492 This is used by buildMainAtlas().
494 video::IImage* generate_image_from_scratch(std::string name,
495 IrrlichtDevice *device, SourceImageCache *sourcecache);
498 This method generates all the textures
500 u32 TextureSource::getTextureIdDirect(const std::string &name)
502 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
504 // Empty name means texture 0
507 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
512 Calling only allowed from main thread
514 if(get_current_thread_id() != m_main_thread)
516 errorstream<<"TextureSource::getTextureIdDirect() "
517 "called not from main thread"<<std::endl;
522 See if texture already exists
525 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
527 core::map<std::string, u32>::Node *n;
528 n = m_name_to_id.find(name);
531 /*infostream<<"getTextureIdDirect(): \""<<name
532 <<"\" found in cache"<<std::endl;*/
533 return n->getValue();
537 /*infostream<<"getTextureIdDirect(): \""<<name
538 <<"\" NOT found in cache. Creating it."<<std::endl;*/
544 char separator = '^';
547 This is set to the id of the base image.
548 If left 0, there is no base image and a completely new image
551 u32 base_image_id = 0;
553 // Find last meta separator in name
554 s32 last_separator_position = -1;
555 for(s32 i=name.size()-1; i>=0; i--)
557 if(name[i] == separator)
559 last_separator_position = i;
564 If separator was found, construct the base name and make the
565 base image using a recursive call
567 std::string base_image_name;
568 if(last_separator_position != -1)
570 // Construct base name
571 base_image_name = name.substr(0, last_separator_position);
572 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
573 " to get base image of \""<<name<<"\" = \""
574 <<base_image_name<<"\""<<std::endl;*/
575 base_image_id = getTextureIdDirect(base_image_name);
578 //infostream<<"base_image_id="<<base_image_id<<std::endl;
580 video::IVideoDriver* driver = m_device->getVideoDriver();
583 video::ITexture *t = NULL;
586 An image will be built from files and then converted into a texture.
588 video::IImage *baseimg = NULL;
590 // If a base image was found, copy it to baseimg
591 if(base_image_id != 0)
593 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
595 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
597 video::IImage *image = ap.atlas_img;
601 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
602 <<"cache: \""<<base_image_name<<"\""
607 core::dimension2d<u32> dim = ap.intsize;
609 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
611 core::position2d<s32> pos_to(0,0);
612 core::position2d<s32> pos_from = ap.intpos;
616 v2s32(0,0), // position in target
617 core::rect<s32>(pos_from, dim) // from
620 /*infostream<<"getTextureIdDirect(): Loaded \""
621 <<base_image_name<<"\" from image cache"
627 Parse out the last part of the name of the image and act
631 std::string last_part_of_name = name.substr(last_separator_position+1);
632 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
634 // Generate image according to part of name
635 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
637 errorstream<<"getTextureIdDirect(): "
638 "failed to generate \""<<last_part_of_name<<"\""
642 // If no resulting image, print a warning
645 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
646 " create texture \""<<name<<"\""<<std::endl;
651 // Create texture from resulting image
652 t = driver->addTexture(name.c_str(), baseimg);
656 Add texture to caches (add NULL textures too)
659 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
661 u32 id = m_atlaspointer_cache.size();
667 core::dimension2d<u32> baseimg_dim(0,0);
669 baseimg_dim = baseimg->getDimension();
670 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
671 m_atlaspointer_cache.push_back(nap);
672 m_name_to_id.insert(name, id);
674 /*infostream<<"getTextureIdDirect(): "
675 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
680 std::string TextureSource::getTextureName(u32 id)
682 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
684 if(id >= m_atlaspointer_cache.size())
686 errorstream<<"TextureSource::getTextureName(): id="<<id
687 <<" >= m_atlaspointer_cache.size()="
688 <<m_atlaspointer_cache.size()<<std::endl;
692 return m_atlaspointer_cache[id].name;
696 AtlasPointer TextureSource::getTexture(u32 id)
698 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
700 if(id >= m_atlaspointer_cache.size())
701 return AtlasPointer(0, NULL);
703 return m_atlaspointer_cache[id].a;
706 void TextureSource::updateAP(AtlasPointer &ap)
708 AtlasPointer ap2 = getTexture(ap.id);
712 void TextureSource::processQueue()
717 if(m_get_texture_queue.size() > 0)
719 GetRequest<std::string, u32, u8, u8>
720 request = m_get_texture_queue.pop();
722 /*infostream<<"TextureSource::processQueue(): "
723 <<"got texture request with "
724 <<"name=\""<<request.key<<"\""
727 GetResult<std::string, u32, u8, u8>
729 result.key = request.key;
730 result.callers = request.callers;
731 result.item = getTextureIdDirect(request.key);
733 request.dest->push_back(result);
737 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
739 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
741 assert(get_current_thread_id() == m_main_thread);
743 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
746 void TextureSource::rebuildImagesAndTextures()
748 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
750 /*// Oh well... just clear everything, they'll load sometime.
751 m_atlaspointer_cache.clear();
752 m_name_to_id.clear();*/
754 video::IVideoDriver* driver = m_device->getVideoDriver();
756 // Remove source images from textures to disable inheriting textures
757 // from existing textures
758 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
759 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
760 sap->atlas_img->drop();
761 sap->atlas_img = NULL;
765 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
766 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
768 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
769 // Create texture from resulting image
770 video::ITexture *t = NULL;
772 t = driver->addTexture(sap->name.c_str(), img);
776 sap->a.pos = v2f(0,0);
777 sap->a.size = v2f(1,1);
779 sap->atlas_img = img;
780 sap->intpos = v2s32(0,0);
781 sap->intsize = img->getDimension();
785 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
787 assert(gamedef->tsrc() == this);
788 INodeDefManager *ndef = gamedef->ndef();
790 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
792 //return; // Disable (for testing)
794 video::IVideoDriver* driver = m_device->getVideoDriver();
797 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
799 // Create an image of the right size
800 core::dimension2d<u32> atlas_dim(1024,1024);
801 video::IImage *atlas_img =
802 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
804 if(atlas_img == NULL)
806 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
807 "image; not building texture atlas."<<std::endl;
812 Grab list of stuff to include in the texture atlas from the
813 main content features
816 core::map<std::string, bool> sourcelist;
818 for(u16 j=0; j<MAX_CONTENT+1; j++)
820 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
822 const ContentFeatures &f = ndef->get(j);
823 for(u32 i=0; i<6; i++)
825 std::string name = f.tname_tiles[i];
826 sourcelist[name] = true;
830 infostream<<"Creating texture atlas out of textures: ";
831 for(core::map<std::string, bool>::Iterator
832 i = sourcelist.getIterator();
833 i.atEnd() == false; i++)
835 std::string name = i.getNode()->getKey();
836 infostream<<"\""<<name<<"\" ";
838 infostream<<std::endl;
840 // Padding to disallow texture bleeding
843 s32 column_width = 256;
844 s32 column_padding = 16;
847 First pass: generate almost everything
849 core::position2d<s32> pos_in_atlas(0,0);
851 pos_in_atlas.Y = padding;
853 for(core::map<std::string, bool>::Iterator
854 i = sourcelist.getIterator();
855 i.atEnd() == false; i++)
857 std::string name = i.getNode()->getKey();
859 // Generate image by name
860 video::IImage *img2 = generate_image_from_scratch(name, m_device,
864 errorstream<<"TextureSource::buildMainAtlas(): "
865 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
869 core::dimension2d<u32> dim = img2->getDimension();
871 // Don't add to atlas if image is large
872 core::dimension2d<u32> max_size_in_atlas(32,32);
873 if(dim.Width > max_size_in_atlas.Width
874 || dim.Height > max_size_in_atlas.Height)
876 infostream<<"TextureSource::buildMainAtlas(): Not adding "
877 <<"\""<<name<<"\" because image is large"<<std::endl;
881 // Wrap columns and stop making atlas if atlas is full
882 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
884 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
885 errorstream<<"TextureSource::buildMainAtlas(): "
886 <<"Atlas is full, not adding more textures."
890 pos_in_atlas.Y = padding;
891 pos_in_atlas.X += column_width + column_padding;
894 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
895 <<"\" to texture atlas"<<std::endl;*/
897 // Tile it a few times in the X direction
898 u16 xwise_tiling = column_width / dim.Width;
899 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
901 for(u32 j=0; j<xwise_tiling; j++)
903 // Copy the copy to the atlas
904 /*img2->copyToWithAlpha(atlas_img,
905 pos_in_atlas + v2s32(j*dim.Width,0),
906 core::rect<s32>(v2s32(0,0), dim),
907 video::SColor(255,255,255,255),
909 img2->copyTo(atlas_img,
910 pos_in_atlas + v2s32(j*dim.Width,0),
911 core::rect<s32>(v2s32(0,0), dim),
915 // Copy the borders a few times to disallow texture bleeding
916 for(u32 side=0; side<2; side++) // top and bottom
917 for(s32 y0=0; y0<padding; y0++)
918 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
924 dst_y = y0 + pos_in_atlas.Y + dim.Height;
925 src_y = pos_in_atlas.Y + dim.Height - 1;
929 dst_y = -y0 + pos_in_atlas.Y-1;
930 src_y = pos_in_atlas.Y;
932 s32 x = x0 + pos_in_atlas.X;
933 video::SColor c = atlas_img->getPixel(x, src_y);
934 atlas_img->setPixel(x,dst_y,c);
940 Add texture to caches
943 bool reuse_old_id = false;
944 u32 id = m_atlaspointer_cache.size();
945 // Check old id without fetching a texture
946 core::map<std::string, u32>::Node *n;
947 n = m_name_to_id.find(name);
948 // If it exists, we will replace the old definition
952 /*infostream<<"TextureSource::buildMainAtlas(): "
953 <<"Replacing old AtlasPointer"<<std::endl;*/
956 // Create AtlasPointer
958 ap.atlas = NULL; // Set on the second pass
959 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
960 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
961 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
962 (float)dim.Width/(float)atlas_dim.Height);
963 ap.tiled = xwise_tiling;
965 // Create SourceAtlasPointer and add to containers
966 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
968 m_atlaspointer_cache[id] = nap;
970 m_atlaspointer_cache.push_back(nap);
971 m_name_to_id[name] = id;
973 // Increment position
974 pos_in_atlas.Y += dim.Height + padding * 2;
980 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
984 Second pass: set texture pointer in generated AtlasPointers
986 for(core::map<std::string, bool>::Iterator
987 i = sourcelist.getIterator();
988 i.atEnd() == false; i++)
990 std::string name = i.getNode()->getKey();
991 if(m_name_to_id.find(name) == NULL)
993 u32 id = m_name_to_id[name];
994 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
995 m_atlaspointer_cache[id].a.atlas = t;
999 Write image to file so that it can be inspected
1001 /*std::string atlaspath = porting::path_user
1002 + DIR_DELIM + "generated_texture_atlas.png";
1003 infostream<<"Removing and writing texture atlas for inspection to "
1004 <<atlaspath<<std::endl;
1005 fs::RecursiveDelete(atlaspath);
1006 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1009 video::IImage* generate_image_from_scratch(std::string name,
1010 IrrlichtDevice *device, SourceImageCache *sourcecache)
1012 /*infostream<<"generate_image_from_scratch(): "
1013 "\""<<name<<"\""<<std::endl;*/
1015 video::IVideoDriver* driver = device->getVideoDriver();
1022 video::IImage *baseimg = NULL;
1024 char separator = '^';
1026 // Find last meta separator in name
1027 s32 last_separator_position = name.find_last_of(separator);
1028 //if(last_separator_position == std::npos)
1029 // last_separator_position = -1;
1031 /*infostream<<"generate_image_from_scratch(): "
1032 <<"last_separator_position="<<last_separator_position
1036 If separator was found, construct the base name and make the
1037 base image using a recursive call
1039 std::string base_image_name;
1040 if(last_separator_position != -1)
1042 // Construct base name
1043 base_image_name = name.substr(0, last_separator_position);
1044 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1045 " to get base image of \""<<name<<"\" = \""
1046 <<base_image_name<<"\""<<std::endl;*/
1047 baseimg = generate_image_from_scratch(base_image_name, device,
1052 Parse out the last part of the name of the image and act
1056 std::string last_part_of_name = name.substr(last_separator_position+1);
1057 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1059 // Generate image according to part of name
1060 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1062 errorstream<<"generate_image_from_scratch(): "
1063 "failed to generate \""<<last_part_of_name<<"\""
1071 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1072 IrrlichtDevice *device, SourceImageCache *sourcecache)
1074 video::IVideoDriver* driver = device->getVideoDriver();
1077 // Stuff starting with [ are special commands
1078 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1080 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1084 if(part_of_name != ""){
1085 errorstream<<"generate_image(): Could not load image \""
1086 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1087 errorstream<<"generate_image(): Creating a dummy"
1088 <<" image for \""<<part_of_name<<"\""<<std::endl;
1091 // Just create a dummy image
1092 //core::dimension2d<u32> dim(2,2);
1093 core::dimension2d<u32> dim(1,1);
1094 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1096 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1097 image->setPixel(1,0, video::SColor(255,0,255,0));
1098 image->setPixel(0,1, video::SColor(255,0,0,255));
1099 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1100 image->setPixel(0,0, video::SColor(255,myrand()%256,
1101 myrand()%256,myrand()%256));
1102 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1103 myrand()%256,myrand()%256));
1104 image->setPixel(0,1, video::SColor(255,myrand()%256,
1105 myrand()%256,myrand()%256));
1106 image->setPixel(1,1, video::SColor(255,myrand()%256,
1107 myrand()%256,myrand()%256));*/
1110 // If base image is NULL, load as base.
1113 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1115 Copy it this way to get an alpha channel.
1116 Otherwise images with alpha cannot be blitted on
1117 images that don't have alpha in the original file.
1119 core::dimension2d<u32> dim = image->getDimension();
1120 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1121 image->copyTo(baseimg);
1124 // Else blit on base.
1127 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1128 // Size of the copied area
1129 core::dimension2d<u32> dim = image->getDimension();
1130 //core::dimension2d<u32> dim(16,16);
1131 // Position to copy the blitted to in the base image
1132 core::position2d<s32> pos_to(0,0);
1133 // Position to copy the blitted from in the blitted image
1134 core::position2d<s32> pos_from(0,0);
1136 image->copyToWithAlpha(baseimg, pos_to,
1137 core::rect<s32>(pos_from, dim),
1138 video::SColor(255,255,255,255),
1146 // A special texture modification
1148 /*infostream<<"generate_image(): generating special "
1149 <<"modification \""<<part_of_name<<"\""
1153 This is the simplest of all; it just adds stuff to the
1154 name so that a separate texture is created.
1156 It is used to make textures for stuff that doesn't want
1157 to implement getting the texture from a bigger texture
1160 if(part_of_name == "[forcesingle")
1162 // If base image is NULL, create a random color
1165 core::dimension2d<u32> dim(1,1);
1166 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1168 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1169 myrand()%256,myrand()%256));
1174 Adds a cracking texture
1176 else if(part_of_name.substr(0,6) == "[crack")
1180 errorstream<<"generate_image(): baseimg==NULL "
1181 <<"for part_of_name=\""<<part_of_name
1182 <<"\", cancelling."<<std::endl;
1186 // Crack image number
1187 u16 progression = stoi(part_of_name.substr(6));
1189 // Size of the base image
1190 core::dimension2d<u32> dim_base = baseimg->getDimension();
1195 It is an image with a number of cracking stages
1198 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1202 // Dimension of original image
1203 core::dimension2d<u32> dim_crack
1204 = img_crack->getDimension();
1205 // Count of crack stages
1206 u32 crack_count = dim_crack.Height / dim_crack.Width;
1207 // Limit progression
1208 if(progression > crack_count-1)
1209 progression = crack_count-1;
1210 // Dimension of a single scaled crack stage
1211 core::dimension2d<u32> dim_crack_scaled_single(
1215 // Dimension of scaled size
1216 core::dimension2d<u32> dim_crack_scaled(
1217 dim_crack_scaled_single.Width,
1218 dim_crack_scaled_single.Height * crack_count
1220 // Create scaled crack image
1221 video::IImage *img_crack_scaled = driver->createImage(
1222 video::ECF_A8R8G8B8, dim_crack_scaled);
1223 if(img_crack_scaled)
1225 // Scale crack image by copying
1226 img_crack->copyToScaling(img_crack_scaled);
1228 // Position to copy the crack from
1229 core::position2d<s32> pos_crack_scaled(
1231 dim_crack_scaled_single.Height * progression
1234 // This tiling does nothing currently but is useful
1235 for(u32 y0=0; y0<dim_base.Height
1236 / dim_crack_scaled_single.Height; y0++)
1237 for(u32 x0=0; x0<dim_base.Width
1238 / dim_crack_scaled_single.Width; x0++)
1240 // Position to copy the crack to in the base image
1241 core::position2d<s32> pos_base(
1242 x0*dim_crack_scaled_single.Width,
1243 y0*dim_crack_scaled_single.Height
1245 // Rectangle to copy the crack from on the scaled image
1246 core::rect<s32> rect_crack_scaled(
1248 dim_crack_scaled_single
1251 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
1253 video::SColor(255,255,255,255),
1257 img_crack_scaled->drop();
1264 [combine:WxH:X,Y=filename:X,Y=filename2
1265 Creates a bigger texture from an amount of smaller ones
1267 else if(part_of_name.substr(0,8) == "[combine")
1269 Strfnd sf(part_of_name);
1271 u32 w0 = stoi(sf.next("x"));
1272 u32 h0 = stoi(sf.next(":"));
1273 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1274 core::dimension2d<u32> dim(w0,h0);
1275 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1276 while(sf.atend() == false)
1278 u32 x = stoi(sf.next(","));
1279 u32 y = stoi(sf.next("="));
1280 std::string filename = sf.next(":");
1281 infostream<<"Adding \""<<filename
1282 <<"\" to combined ("<<x<<","<<y<<")"
1284 video::IImage *img = sourcecache->getOrLoad(filename, device);
1287 core::dimension2d<u32> dim = img->getDimension();
1288 infostream<<"Size "<<dim.Width
1289 <<"x"<<dim.Height<<std::endl;
1290 core::position2d<s32> pos_base(x, y);
1291 video::IImage *img2 =
1292 driver->createImage(video::ECF_A8R8G8B8, dim);
1295 img2->copyToWithAlpha(baseimg, pos_base,
1296 core::rect<s32>(v2s32(0,0), dim),
1297 video::SColor(255,255,255,255),
1303 infostream<<"img==NULL"<<std::endl;
1310 else if(part_of_name.substr(0,9) == "[brighten")
1314 errorstream<<"generate_image(): baseimg==NULL "
1315 <<"for part_of_name=\""<<part_of_name
1316 <<"\", cancelling."<<std::endl;
1324 Make image completely opaque.
1325 Used for the leaves texture when in old leaves mode, so
1326 that the transparent parts don't look completely black
1327 when simple alpha channel is used for rendering.
1329 else if(part_of_name.substr(0,8) == "[noalpha")
1333 errorstream<<"generate_image(): baseimg==NULL "
1334 <<"for part_of_name=\""<<part_of_name
1335 <<"\", cancelling."<<std::endl;
1339 core::dimension2d<u32> dim = baseimg->getDimension();
1341 // Set alpha to full
1342 for(u32 y=0; y<dim.Height; y++)
1343 for(u32 x=0; x<dim.Width; x++)
1345 video::SColor c = baseimg->getPixel(x,y);
1347 baseimg->setPixel(x,y,c);
1352 Convert one color to transparent.
1354 else if(part_of_name.substr(0,11) == "[makealpha:")
1358 errorstream<<"generate_image(): baseimg==NULL "
1359 <<"for part_of_name=\""<<part_of_name
1360 <<"\", cancelling."<<std::endl;
1364 Strfnd sf(part_of_name.substr(11));
1365 u32 r1 = stoi(sf.next(","));
1366 u32 g1 = stoi(sf.next(","));
1367 u32 b1 = stoi(sf.next(""));
1368 std::string filename = sf.next("");
1370 core::dimension2d<u32> dim = baseimg->getDimension();
1372 /*video::IImage *oldbaseimg = baseimg;
1373 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1374 oldbaseimg->copyTo(baseimg);
1375 oldbaseimg->drop();*/
1377 // Set alpha to full
1378 for(u32 y=0; y<dim.Height; y++)
1379 for(u32 x=0; x<dim.Width; x++)
1381 video::SColor c = baseimg->getPixel(x,y);
1383 u32 g = c.getGreen();
1384 u32 b = c.getBlue();
1385 if(!(r == r1 && g == g1 && b == b1))
1388 baseimg->setPixel(x,y,c);
1392 [inventorycube{topimage{leftimage{rightimage
1393 In every subimage, replace ^ with &.
1394 Create an "inventory cube".
1395 NOTE: This should be used only on its own.
1396 Example (a grass block (not actually used in game):
1397 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1399 else if(part_of_name.substr(0,14) == "[inventorycube")
1403 errorstream<<"generate_image(): baseimg!=NULL "
1404 <<"for part_of_name=\""<<part_of_name
1405 <<"\", cancelling."<<std::endl;
1409 str_replace_char(part_of_name, '&', '^');
1410 Strfnd sf(part_of_name);
1412 std::string imagename_top = sf.next("{");
1413 std::string imagename_left = sf.next("{");
1414 std::string imagename_right = sf.next("{");
1416 // Generate images for the faces of the cube
1417 video::IImage *img_top = generate_image_from_scratch(
1418 imagename_top, device, sourcecache);
1419 video::IImage *img_left = generate_image_from_scratch(
1420 imagename_left, device, sourcecache);
1421 video::IImage *img_right = generate_image_from_scratch(
1422 imagename_right, device, sourcecache);
1423 assert(img_top && img_left && img_right);
1425 // Create textures from images
1426 video::ITexture *texture_top = driver->addTexture(
1427 (imagename_top + "__temp__").c_str(), img_top);
1428 video::ITexture *texture_left = driver->addTexture(
1429 (imagename_left + "__temp__").c_str(), img_left);
1430 video::ITexture *texture_right = driver->addTexture(
1431 (imagename_right + "__temp__").c_str(), img_right);
1432 assert(texture_top && texture_left && texture_right);
1440 Draw a cube mesh into a render target texture
1442 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1443 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1444 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1445 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1446 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1447 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1448 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1449 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1451 core::dimension2d<u32> dim(64,64);
1452 std::string rtt_texture_name = part_of_name + "_RTT";
1454 v3f camera_position(0, 1.0, -1.5);
1455 camera_position.rotateXZBy(45);
1456 v3f camera_lookat(0, 0, 0);
1457 core::CMatrix4<f32> camera_projection_matrix;
1458 // Set orthogonal projection
1459 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1460 1.65, 1.65, 0, 100);
1462 video::SColorf ambient_light(0.2,0.2,0.2);
1463 v3f light_position(10, 100, -50);
1464 video::SColorf light_color(0.5,0.5,0.5);
1465 f32 light_radius = 1000;
1467 video::ITexture *rtt = generateTextureFromMesh(
1468 cube, device, dim, rtt_texture_name,
1471 camera_projection_matrix,
1480 // Free textures of images
1481 driver->removeTexture(texture_top);
1482 driver->removeTexture(texture_left);
1483 driver->removeTexture(texture_right);
1487 baseimg = generate_image_from_scratch(
1488 imagename_top, device, sourcecache);
1492 // Create image of render target
1493 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1496 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1500 image->copyTo(baseimg);
1506 errorstream<<"generate_image(): Invalid "
1507 " modification: \""<<part_of_name<<"\""<<std::endl;
1514 void brighten(video::IImage *image)
1519 core::dimension2d<u32> dim = image->getDimension();
1521 for(u32 y=0; y<dim.Height; y++)
1522 for(u32 x=0; x<dim.Width; x++)
1524 video::SColor c = image->getPixel(x,y);
1525 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1526 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1527 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1528 image->setPixel(x,y,c);