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("clienttextures")+DIR_DELIM+filename;
136 std::string testpath = porting::path_data + DIR_DELIM + rel_path;
137 // Check all filename extensions. Returns "" if not found.
138 fullpath = getImagePath(testpath);
141 // Add to cache (also an empty result is cached)
142 g_texturename_to_path_cache.set(filename, fullpath);
149 An internal variant of AtlasPointer with more data.
150 (well, more like a wrapper)
153 struct SourceAtlasPointer
157 video::IImage *atlas_img; // The source image of the atlas
158 // Integer variants of position and size
163 const std::string &name_,
164 AtlasPointer a_=AtlasPointer(0, NULL),
165 video::IImage *atlas_img_=NULL,
166 v2s32 intpos_=v2s32(0,0),
167 v2u32 intsize_=v2u32(0,0)
171 atlas_img(atlas_img_),
179 SourceImageCache: A cache used for storing source images.
182 class SourceImageCache
185 void insert(const std::string &name, video::IImage *img,
186 bool prefer_local, video::IVideoDriver *driver)
190 core::map<std::string, video::IImage*>::Node *n;
191 n = m_images.find(name);
193 video::IImage *oldimg = n->getValue();
197 // Try to use local texture instead if asked to
199 std::string path = getTexturePath(name.c_str());
201 video::IImage *img2 = driver->createImageFromFile(path.c_str());
203 m_images[name] = img2;
209 m_images[name] = img;
211 video::IImage* get(const std::string &name)
213 core::map<std::string, video::IImage*>::Node *n;
214 n = m_images.find(name);
216 return n->getValue();
219 // Primarily fetches from cache, secondarily tries to read from filesystem
220 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
222 core::map<std::string, video::IImage*>::Node *n;
223 n = m_images.find(name);
225 n->getValue()->grab(); // Grab for caller
226 return n->getValue();
228 video::IVideoDriver* driver = device->getVideoDriver();
229 std::string path = getTexturePath(name.c_str());
231 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
232 <<name<<"\""<<std::endl;
235 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
237 video::IImage *img = driver->createImageFromFile(path.c_str());
238 // Even if could not be loaded, put as NULL
239 //m_images[name] = img;
241 m_images[name] = img;
242 img->grab(); // Grab for caller
247 core::map<std::string, video::IImage*> m_images;
254 class TextureSource : public IWritableTextureSource
257 TextureSource(IrrlichtDevice *device);
262 Now, assume a texture with the id 1 exists, and has the name
263 "stone.png^mineral1".
264 Then a random thread calls getTextureId for a texture called
265 "stone.png^mineral1^crack0".
266 ...Now, WTF should happen? Well:
267 - getTextureId strips off stuff recursively from the end until
268 the remaining part is found, or nothing is left when
269 something is stripped out
271 But it is slow to search for textures by names and modify them
273 - ContentFeatures is made to contain ids for the basic plain
275 - Crack textures can be slow by themselves, but the framework
279 - Assume a texture with the id 1 exists, and has the name
280 "stone.png^mineral1" and is specified as a part of some atlas.
281 - Now MapBlock::getNodeTile() stumbles upon a node which uses
282 texture id 1, and finds out that NODEMOD_CRACK must be applied
284 - It finds out the name of the texture with getTextureName(1),
285 appends "^crack0" to it and gets a new texture id with
286 getTextureId("stone.png^mineral1^crack0")
291 Gets a texture id from cache or
292 - if main thread, from getTextureIdDirect
293 - if other thread, adds to request queue and waits for main thread
295 u32 getTextureId(const std::string &name);
301 "stone.png^mineral_coal.png"
302 "stone.png^mineral_coal.png^crack1"
304 - If texture specified by name is found from cache, return the
306 - Otherwise generate the texture, add to cache and return id.
307 Recursion is used to find out the largest found part of the
308 texture and continue based on it.
310 The id 0 points to a NULL texture. It is returned in case of error.
312 u32 getTextureIdDirect(const std::string &name);
314 // Finds out the name of a cached texture.
315 std::string getTextureName(u32 id);
318 If texture specified by the name pointed by the id doesn't
319 exist, create it, then return the cached texture.
321 Can be called from any thread. If called from some other thread
322 and not found in cache, the call is queued to the main thread
325 AtlasPointer getTexture(u32 id);
327 AtlasPointer getTexture(const std::string &name)
329 return getTexture(getTextureId(name));
332 // Gets a separate texture
333 video::ITexture* getTextureRaw(const std::string &name)
335 AtlasPointer ap = getTexture(name + "^[forcesingle");
339 // Returns a pointer to the irrlicht device
340 virtual IrrlichtDevice* getDevice()
345 // Update new texture pointer and texture coordinates to an
346 // AtlasPointer based on it's texture id
347 void updateAP(AtlasPointer &ap);
349 // Processes queued texture requests from other threads.
350 // Shall be called from the main thread.
353 // Insert an image into the cache without touching the filesystem.
354 // Shall be called from the main thread.
355 void insertSourceImage(const std::string &name, video::IImage *img);
357 // Rebuild images and textures from the current set of source images
358 // Shall be called from the main thread.
359 void rebuildImagesAndTextures();
361 // Build the main texture atlas which contains most of the
363 void buildMainAtlas(class IGameDef *gamedef);
367 // The id of the thread that is allowed to use irrlicht directly
368 threadid_t m_main_thread;
369 // The irrlicht device
370 IrrlichtDevice *m_device;
372 // Cache of source images
373 // This should be only accessed from the main thread
374 SourceImageCache m_sourcecache;
376 // A texture id is index in this array.
377 // The first position contains a NULL texture.
378 core::array<SourceAtlasPointer> m_atlaspointer_cache;
379 // Maps a texture name to an index in the former.
380 core::map<std::string, u32> m_name_to_id;
381 // The two former containers are behind this mutex
382 JMutex m_atlaspointer_cache_mutex;
384 // Main texture atlas. This is filled at startup and is then not touched.
385 video::IImage *m_main_atlas_image;
386 video::ITexture *m_main_atlas_texture;
388 // Queued texture fetches (to be processed by the main thread)
389 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
392 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
394 return new TextureSource(device);
397 TextureSource::TextureSource(IrrlichtDevice *device):
399 m_main_atlas_image(NULL),
400 m_main_atlas_texture(NULL)
404 m_atlaspointer_cache_mutex.Init();
406 m_main_thread = get_current_thread_id();
408 // Add a NULL AtlasPointer as the first index, named ""
409 m_atlaspointer_cache.push_back(SourceAtlasPointer(""));
410 m_name_to_id[""] = 0;
413 TextureSource::~TextureSource()
417 u32 TextureSource::getTextureId(const std::string &name)
419 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
423 See if texture already exists
425 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
426 core::map<std::string, u32>::Node *n;
427 n = m_name_to_id.find(name);
430 return n->getValue();
437 if(get_current_thread_id() == m_main_thread)
439 return getTextureIdDirect(name);
443 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
445 // We're gonna ask the result to be put into here
446 ResultQueue<std::string, u32, u8, u8> result_queue;
448 // Throw a request in
449 m_get_texture_queue.add(name, 0, 0, &result_queue);
451 infostream<<"Waiting for texture from main thread, name=\""
452 <<name<<"\""<<std::endl;
456 // Wait result for a second
457 GetResult<std::string, u32, u8, u8>
458 result = result_queue.pop_front(1000);
460 // Check that at least something worked OK
461 assert(result.key == name);
465 catch(ItemNotFoundException &e)
467 infostream<<"Waiting for texture timed out."<<std::endl;
472 infostream<<"getTextureId(): Failed"<<std::endl;
478 void brighten(video::IImage *image);
481 Generate image based on a string like "stone.png" or "[crack0".
482 if baseimg is NULL, it is created. Otherwise stuff is made on it.
484 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
485 IrrlichtDevice *device, SourceImageCache *sourcecache);
488 Generates an image from a full string like
489 "stone.png^mineral_coal.png^[crack0".
491 This is used by buildMainAtlas().
493 video::IImage* generate_image_from_scratch(std::string name,
494 IrrlichtDevice *device, SourceImageCache *sourcecache);
497 This method generates all the textures
499 u32 TextureSource::getTextureIdDirect(const std::string &name)
501 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
503 // Empty name means texture 0
506 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
511 Calling only allowed from main thread
513 if(get_current_thread_id() != m_main_thread)
515 errorstream<<"TextureSource::getTextureIdDirect() "
516 "called not from main thread"<<std::endl;
521 See if texture already exists
524 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
526 core::map<std::string, u32>::Node *n;
527 n = m_name_to_id.find(name);
530 /*infostream<<"getTextureIdDirect(): \""<<name
531 <<"\" found in cache"<<std::endl;*/
532 return n->getValue();
536 /*infostream<<"getTextureIdDirect(): \""<<name
537 <<"\" NOT found in cache. Creating it."<<std::endl;*/
543 char separator = '^';
546 This is set to the id of the base image.
547 If left 0, there is no base image and a completely new image
550 u32 base_image_id = 0;
552 // Find last meta separator in name
553 s32 last_separator_position = -1;
554 for(s32 i=name.size()-1; i>=0; i--)
556 if(name[i] == separator)
558 last_separator_position = i;
563 If separator was found, construct the base name and make the
564 base image using a recursive call
566 std::string base_image_name;
567 if(last_separator_position != -1)
569 // Construct base name
570 base_image_name = name.substr(0, last_separator_position);
571 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
572 " to get base image of \""<<name<<"\" = \""
573 <<base_image_name<<"\""<<std::endl;*/
574 base_image_id = getTextureIdDirect(base_image_name);
577 //infostream<<"base_image_id="<<base_image_id<<std::endl;
579 video::IVideoDriver* driver = m_device->getVideoDriver();
582 video::ITexture *t = NULL;
585 An image will be built from files and then converted into a texture.
587 video::IImage *baseimg = NULL;
589 // If a base image was found, copy it to baseimg
590 if(base_image_id != 0)
592 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
594 SourceAtlasPointer ap = m_atlaspointer_cache[base_image_id];
596 video::IImage *image = ap.atlas_img;
600 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
601 <<"cache: \""<<base_image_name<<"\""
606 core::dimension2d<u32> dim = ap.intsize;
608 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
610 core::position2d<s32> pos_to(0,0);
611 core::position2d<s32> pos_from = ap.intpos;
615 v2s32(0,0), // position in target
616 core::rect<s32>(pos_from, dim) // from
619 /*infostream<<"getTextureIdDirect(): Loaded \""
620 <<base_image_name<<"\" from image cache"
626 Parse out the last part of the name of the image and act
630 std::string last_part_of_name = name.substr(last_separator_position+1);
631 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
633 // Generate image according to part of name
634 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
636 errorstream<<"getTextureIdDirect(): "
637 "failed to generate \""<<last_part_of_name<<"\""
641 // If no resulting image, print a warning
644 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
645 " create texture \""<<name<<"\""<<std::endl;
650 // Create texture from resulting image
651 t = driver->addTexture(name.c_str(), baseimg);
655 Add texture to caches (add NULL textures too)
658 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
660 u32 id = m_atlaspointer_cache.size();
666 core::dimension2d<u32> baseimg_dim(0,0);
668 baseimg_dim = baseimg->getDimension();
669 SourceAtlasPointer nap(name, ap, baseimg, v2s32(0,0), baseimg_dim);
670 m_atlaspointer_cache.push_back(nap);
671 m_name_to_id.insert(name, id);
673 /*infostream<<"getTextureIdDirect(): "
674 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
679 std::string TextureSource::getTextureName(u32 id)
681 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
683 if(id >= m_atlaspointer_cache.size())
685 errorstream<<"TextureSource::getTextureName(): id="<<id
686 <<" >= m_atlaspointer_cache.size()="
687 <<m_atlaspointer_cache.size()<<std::endl;
691 return m_atlaspointer_cache[id].name;
695 AtlasPointer TextureSource::getTexture(u32 id)
697 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
699 if(id >= m_atlaspointer_cache.size())
700 return AtlasPointer(0, NULL);
702 return m_atlaspointer_cache[id].a;
705 void TextureSource::updateAP(AtlasPointer &ap)
707 AtlasPointer ap2 = getTexture(ap.id);
711 void TextureSource::processQueue()
716 if(m_get_texture_queue.size() > 0)
718 GetRequest<std::string, u32, u8, u8>
719 request = m_get_texture_queue.pop();
721 /*infostream<<"TextureSource::processQueue(): "
722 <<"got texture request with "
723 <<"name=\""<<request.key<<"\""
726 GetResult<std::string, u32, u8, u8>
728 result.key = request.key;
729 result.callers = request.callers;
730 result.item = getTextureIdDirect(request.key);
732 request.dest->push_back(result);
736 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
738 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
740 assert(get_current_thread_id() == m_main_thread);
742 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
745 void TextureSource::rebuildImagesAndTextures()
747 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
749 /*// Oh well... just clear everything, they'll load sometime.
750 m_atlaspointer_cache.clear();
751 m_name_to_id.clear();*/
753 video::IVideoDriver* driver = m_device->getVideoDriver();
755 // Remove source images from textures to disable inheriting textures
756 // from existing textures
757 /*for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
758 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
759 sap->atlas_img->drop();
760 sap->atlas_img = NULL;
764 for(u32 i=0; i<m_atlaspointer_cache.size(); i++){
765 SourceAtlasPointer *sap = &m_atlaspointer_cache[i];
767 generate_image_from_scratch(sap->name, m_device, &m_sourcecache);
768 // Create texture from resulting image
769 video::ITexture *t = NULL;
771 t = driver->addTexture(sap->name.c_str(), img);
775 sap->a.pos = v2f(0,0);
776 sap->a.size = v2f(1,1);
778 sap->atlas_img = img;
779 sap->intpos = v2s32(0,0);
780 sap->intsize = img->getDimension();
784 void TextureSource::buildMainAtlas(class IGameDef *gamedef)
786 assert(gamedef->tsrc() == this);
787 INodeDefManager *ndef = gamedef->ndef();
789 infostream<<"TextureSource::buildMainAtlas()"<<std::endl;
791 //return; // Disable (for testing)
793 video::IVideoDriver* driver = m_device->getVideoDriver();
796 JMutexAutoLock lock(m_atlaspointer_cache_mutex);
798 // Create an image of the right size
799 core::dimension2d<u32> atlas_dim(1024,1024);
800 video::IImage *atlas_img =
801 driver->createImage(video::ECF_A8R8G8B8, atlas_dim);
803 if(atlas_img == NULL)
805 errorstream<<"TextureSource::buildMainAtlas(): Failed to create atlas "
806 "image; not building texture atlas."<<std::endl;
811 Grab list of stuff to include in the texture atlas from the
812 main content features
815 core::map<std::string, bool> sourcelist;
817 for(u16 j=0; j<MAX_CONTENT+1; j++)
819 if(j == CONTENT_IGNORE || j == CONTENT_AIR)
821 const ContentFeatures &f = ndef->get(j);
822 for(u32 i=0; i<6; i++)
824 std::string name = f.tname_tiles[i];
825 sourcelist[name] = true;
829 infostream<<"Creating texture atlas out of textures: ";
830 for(core::map<std::string, bool>::Iterator
831 i = sourcelist.getIterator();
832 i.atEnd() == false; i++)
834 std::string name = i.getNode()->getKey();
835 infostream<<"\""<<name<<"\" ";
837 infostream<<std::endl;
839 // Padding to disallow texture bleeding
842 s32 column_width = 256;
843 s32 column_padding = 16;
846 First pass: generate almost everything
848 core::position2d<s32> pos_in_atlas(0,0);
850 pos_in_atlas.Y = padding;
852 for(core::map<std::string, bool>::Iterator
853 i = sourcelist.getIterator();
854 i.atEnd() == false; i++)
856 std::string name = i.getNode()->getKey();
858 // Generate image by name
859 video::IImage *img2 = generate_image_from_scratch(name, m_device,
863 errorstream<<"TextureSource::buildMainAtlas(): "
864 <<"Couldn't generate image \""<<name<<"\""<<std::endl;
868 core::dimension2d<u32> dim = img2->getDimension();
870 // Don't add to atlas if image is large
871 core::dimension2d<u32> max_size_in_atlas(32,32);
872 if(dim.Width > max_size_in_atlas.Width
873 || dim.Height > max_size_in_atlas.Height)
875 infostream<<"TextureSource::buildMainAtlas(): Not adding "
876 <<"\""<<name<<"\" because image is large"<<std::endl;
880 // Wrap columns and stop making atlas if atlas is full
881 if(pos_in_atlas.Y + dim.Height > atlas_dim.Height)
883 if(pos_in_atlas.X > (s32)atlas_dim.Width - 256 - padding){
884 errorstream<<"TextureSource::buildMainAtlas(): "
885 <<"Atlas is full, not adding more textures."
889 pos_in_atlas.Y = padding;
890 pos_in_atlas.X += column_width + column_padding;
893 /*infostream<<"TextureSource::buildMainAtlas(): Adding \""<<name
894 <<"\" to texture atlas"<<std::endl;*/
896 // Tile it a few times in the X direction
897 u16 xwise_tiling = column_width / dim.Width;
898 if(xwise_tiling > 16) // Limit to 16 (more gives no benefit)
900 for(u32 j=0; j<xwise_tiling; j++)
902 // Copy the copy to the atlas
903 /*img2->copyToWithAlpha(atlas_img,
904 pos_in_atlas + v2s32(j*dim.Width,0),
905 core::rect<s32>(v2s32(0,0), dim),
906 video::SColor(255,255,255,255),
908 img2->copyTo(atlas_img,
909 pos_in_atlas + v2s32(j*dim.Width,0),
910 core::rect<s32>(v2s32(0,0), dim),
914 // Copy the borders a few times to disallow texture bleeding
915 for(u32 side=0; side<2; side++) // top and bottom
916 for(s32 y0=0; y0<padding; y0++)
917 for(s32 x0=0; x0<(s32)xwise_tiling*(s32)dim.Width; x0++)
923 dst_y = y0 + pos_in_atlas.Y + dim.Height;
924 src_y = pos_in_atlas.Y + dim.Height - 1;
928 dst_y = -y0 + pos_in_atlas.Y-1;
929 src_y = pos_in_atlas.Y;
931 s32 x = x0 + pos_in_atlas.X;
932 video::SColor c = atlas_img->getPixel(x, src_y);
933 atlas_img->setPixel(x,dst_y,c);
939 Add texture to caches
942 bool reuse_old_id = false;
943 u32 id = m_atlaspointer_cache.size();
944 // Check old id without fetching a texture
945 core::map<std::string, u32>::Node *n;
946 n = m_name_to_id.find(name);
947 // If it exists, we will replace the old definition
951 /*infostream<<"TextureSource::buildMainAtlas(): "
952 <<"Replacing old AtlasPointer"<<std::endl;*/
955 // Create AtlasPointer
957 ap.atlas = NULL; // Set on the second pass
958 ap.pos = v2f((float)pos_in_atlas.X/(float)atlas_dim.Width,
959 (float)pos_in_atlas.Y/(float)atlas_dim.Height);
960 ap.size = v2f((float)dim.Width/(float)atlas_dim.Width,
961 (float)dim.Width/(float)atlas_dim.Height);
962 ap.tiled = xwise_tiling;
964 // Create SourceAtlasPointer and add to containers
965 SourceAtlasPointer nap(name, ap, atlas_img, pos_in_atlas, dim);
967 m_atlaspointer_cache[id] = nap;
969 m_atlaspointer_cache.push_back(nap);
970 m_name_to_id[name] = id;
972 // Increment position
973 pos_in_atlas.Y += dim.Height + padding * 2;
979 video::ITexture *t = driver->addTexture("__main_atlas__", atlas_img);
983 Second pass: set texture pointer in generated AtlasPointers
985 for(core::map<std::string, bool>::Iterator
986 i = sourcelist.getIterator();
987 i.atEnd() == false; i++)
989 std::string name = i.getNode()->getKey();
990 if(m_name_to_id.find(name) == NULL)
992 u32 id = m_name_to_id[name];
993 //infostream<<"id of name "<<name<<" is "<<id<<std::endl;
994 m_atlaspointer_cache[id].a.atlas = t;
998 Write image to file so that it can be inspected
1000 /*std::string atlaspath = porting::path_userdata
1001 + DIR_DELIM + "generated_texture_atlas.png";
1002 infostream<<"Removing and writing texture atlas for inspection to "
1003 <<atlaspath<<std::endl;
1004 fs::RecursiveDelete(atlaspath);
1005 driver->writeImageToFile(atlas_img, atlaspath.c_str());*/
1008 video::IImage* generate_image_from_scratch(std::string name,
1009 IrrlichtDevice *device, SourceImageCache *sourcecache)
1011 /*infostream<<"generate_image_from_scratch(): "
1012 "\""<<name<<"\""<<std::endl;*/
1014 video::IVideoDriver* driver = device->getVideoDriver();
1021 video::IImage *baseimg = NULL;
1023 char separator = '^';
1025 // Find last meta separator in name
1026 s32 last_separator_position = -1;
1027 for(s32 i=name.size()-1; i>=0; i--)
1029 if(name[i] == separator)
1031 last_separator_position = i;
1036 /*infostream<<"generate_image_from_scratch(): "
1037 <<"last_separator_position="<<last_separator_position
1041 If separator was found, construct the base name and make the
1042 base image using a recursive call
1044 std::string base_image_name;
1045 if(last_separator_position != -1)
1047 // Construct base name
1048 base_image_name = name.substr(0, last_separator_position);
1049 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1050 " to get base image of \""<<name<<"\" = \""
1051 <<base_image_name<<"\""<<std::endl;*/
1052 baseimg = generate_image_from_scratch(base_image_name, device,
1057 Parse out the last part of the name of the image and act
1061 std::string last_part_of_name = name.substr(last_separator_position+1);
1062 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1064 // Generate image according to part of name
1065 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1067 errorstream<<"generate_image_from_scratch(): "
1068 "failed to generate \""<<last_part_of_name<<"\""
1076 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1077 IrrlichtDevice *device, SourceImageCache *sourcecache)
1079 video::IVideoDriver* driver = device->getVideoDriver();
1082 // Stuff starting with [ are special commands
1083 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1085 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1089 if(part_of_name != ""){
1090 errorstream<<"generate_image(): Could not load image \""
1091 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1092 errorstream<<"generate_image(): Creating a dummy"
1093 <<" image for \""<<part_of_name<<"\""<<std::endl;
1096 // Just create a dummy image
1097 //core::dimension2d<u32> dim(2,2);
1098 core::dimension2d<u32> dim(1,1);
1099 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1101 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1102 image->setPixel(1,0, video::SColor(255,0,255,0));
1103 image->setPixel(0,1, video::SColor(255,0,0,255));
1104 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1105 image->setPixel(0,0, video::SColor(255,myrand()%256,
1106 myrand()%256,myrand()%256));
1107 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1108 myrand()%256,myrand()%256));
1109 image->setPixel(0,1, video::SColor(255,myrand()%256,
1110 myrand()%256,myrand()%256));
1111 image->setPixel(1,1, video::SColor(255,myrand()%256,
1112 myrand()%256,myrand()%256));*/
1115 // If base image is NULL, load as base.
1118 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1120 Copy it this way to get an alpha channel.
1121 Otherwise images with alpha cannot be blitted on
1122 images that don't have alpha in the original file.
1124 core::dimension2d<u32> dim = image->getDimension();
1125 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1126 image->copyTo(baseimg);
1129 // Else blit on base.
1132 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1133 // Size of the copied area
1134 core::dimension2d<u32> dim = image->getDimension();
1135 //core::dimension2d<u32> dim(16,16);
1136 // Position to copy the blitted to in the base image
1137 core::position2d<s32> pos_to(0,0);
1138 // Position to copy the blitted from in the blitted image
1139 core::position2d<s32> pos_from(0,0);
1141 image->copyToWithAlpha(baseimg, pos_to,
1142 core::rect<s32>(pos_from, dim),
1143 video::SColor(255,255,255,255),
1151 // A special texture modification
1153 /*infostream<<"generate_image(): generating special "
1154 <<"modification \""<<part_of_name<<"\""
1158 This is the simplest of all; it just adds stuff to the
1159 name so that a separate texture is created.
1161 It is used to make textures for stuff that doesn't want
1162 to implement getting the texture from a bigger texture
1165 if(part_of_name == "[forcesingle")
1167 // If base image is NULL, create a random color
1170 core::dimension2d<u32> dim(1,1);
1171 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1173 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1174 myrand()%256,myrand()%256));
1179 Adds a cracking texture
1181 else if(part_of_name.substr(0,6) == "[crack")
1185 errorstream<<"generate_image(): baseimg==NULL "
1186 <<"for part_of_name=\""<<part_of_name
1187 <<"\", cancelling."<<std::endl;
1191 // Crack image number
1192 u16 progression = stoi(part_of_name.substr(6));
1194 // Size of the base image
1195 core::dimension2d<u32> dim_base = baseimg->getDimension();
1200 It is an image with a number of cracking stages
1203 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1207 // Dimension of original image
1208 core::dimension2d<u32> dim_crack
1209 = img_crack->getDimension();
1210 // Count of crack stages
1211 u32 crack_count = dim_crack.Height / dim_crack.Width;
1212 // Limit progression
1213 if(progression > crack_count-1)
1214 progression = crack_count-1;
1215 // Dimension of a single scaled crack stage
1216 core::dimension2d<u32> dim_crack_scaled_single(
1220 // Dimension of scaled size
1221 core::dimension2d<u32> dim_crack_scaled(
1222 dim_crack_scaled_single.Width,
1223 dim_crack_scaled_single.Height * crack_count
1225 // Create scaled crack image
1226 video::IImage *img_crack_scaled = driver->createImage(
1227 video::ECF_A8R8G8B8, dim_crack_scaled);
1228 if(img_crack_scaled)
1230 // Scale crack image by copying
1231 img_crack->copyToScaling(img_crack_scaled);
1233 // Position to copy the crack from
1234 core::position2d<s32> pos_crack_scaled(
1236 dim_crack_scaled_single.Height * progression
1239 // This tiling does nothing currently but is useful
1240 for(u32 y0=0; y0<dim_base.Height
1241 / dim_crack_scaled_single.Height; y0++)
1242 for(u32 x0=0; x0<dim_base.Width
1243 / dim_crack_scaled_single.Width; x0++)
1245 // Position to copy the crack to in the base image
1246 core::position2d<s32> pos_base(
1247 x0*dim_crack_scaled_single.Width,
1248 y0*dim_crack_scaled_single.Height
1250 // Rectangle to copy the crack from on the scaled image
1251 core::rect<s32> rect_crack_scaled(
1253 dim_crack_scaled_single
1256 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
1258 video::SColor(255,255,255,255),
1262 img_crack_scaled->drop();
1269 [combine:WxH:X,Y=filename:X,Y=filename2
1270 Creates a bigger texture from an amount of smaller ones
1272 else if(part_of_name.substr(0,8) == "[combine")
1274 Strfnd sf(part_of_name);
1276 u32 w0 = stoi(sf.next("x"));
1277 u32 h0 = stoi(sf.next(":"));
1278 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1279 core::dimension2d<u32> dim(w0,h0);
1280 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1281 while(sf.atend() == false)
1283 u32 x = stoi(sf.next(","));
1284 u32 y = stoi(sf.next("="));
1285 std::string filename = sf.next(":");
1286 infostream<<"Adding \""<<filename
1287 <<"\" to combined ("<<x<<","<<y<<")"
1289 video::IImage *img = sourcecache->getOrLoad(filename, device);
1292 core::dimension2d<u32> dim = img->getDimension();
1293 infostream<<"Size "<<dim.Width
1294 <<"x"<<dim.Height<<std::endl;
1295 core::position2d<s32> pos_base(x, y);
1296 video::IImage *img2 =
1297 driver->createImage(video::ECF_A8R8G8B8, dim);
1300 img2->copyToWithAlpha(baseimg, pos_base,
1301 core::rect<s32>(v2s32(0,0), dim),
1302 video::SColor(255,255,255,255),
1308 infostream<<"img==NULL"<<std::endl;
1315 else if(part_of_name.substr(0,9) == "[brighten")
1319 errorstream<<"generate_image(): baseimg==NULL "
1320 <<"for part_of_name=\""<<part_of_name
1321 <<"\", cancelling."<<std::endl;
1329 Make image completely opaque.
1330 Used for the leaves texture when in old leaves mode, so
1331 that the transparent parts don't look completely black
1332 when simple alpha channel is used for rendering.
1334 else if(part_of_name.substr(0,8) == "[noalpha")
1338 errorstream<<"generate_image(): baseimg==NULL "
1339 <<"for part_of_name=\""<<part_of_name
1340 <<"\", cancelling."<<std::endl;
1344 core::dimension2d<u32> dim = baseimg->getDimension();
1346 // Set alpha to full
1347 for(u32 y=0; y<dim.Height; y++)
1348 for(u32 x=0; x<dim.Width; x++)
1350 video::SColor c = baseimg->getPixel(x,y);
1352 baseimg->setPixel(x,y,c);
1357 Convert one color to transparent.
1359 else if(part_of_name.substr(0,11) == "[makealpha:")
1363 errorstream<<"generate_image(): baseimg==NULL "
1364 <<"for part_of_name=\""<<part_of_name
1365 <<"\", cancelling."<<std::endl;
1369 Strfnd sf(part_of_name.substr(11));
1370 u32 r1 = stoi(sf.next(","));
1371 u32 g1 = stoi(sf.next(","));
1372 u32 b1 = stoi(sf.next(""));
1373 std::string filename = sf.next("");
1375 core::dimension2d<u32> dim = baseimg->getDimension();
1377 /*video::IImage *oldbaseimg = baseimg;
1378 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1379 oldbaseimg->copyTo(baseimg);
1380 oldbaseimg->drop();*/
1382 // Set alpha to full
1383 for(u32 y=0; y<dim.Height; y++)
1384 for(u32 x=0; x<dim.Width; x++)
1386 video::SColor c = baseimg->getPixel(x,y);
1388 u32 g = c.getGreen();
1389 u32 b = c.getBlue();
1390 if(!(r == r1 && g == g1 && b == b1))
1393 baseimg->setPixel(x,y,c);
1397 [inventorycube{topimage{leftimage{rightimage
1398 In every subimage, replace ^ with &.
1399 Create an "inventory cube".
1400 NOTE: This should be used only on its own.
1401 Example (a grass block (not actually used in game):
1402 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1404 else if(part_of_name.substr(0,14) == "[inventorycube")
1408 errorstream<<"generate_image(): baseimg!=NULL "
1409 <<"for part_of_name=\""<<part_of_name
1410 <<"\", cancelling."<<std::endl;
1414 str_replace_char(part_of_name, '&', '^');
1415 Strfnd sf(part_of_name);
1417 std::string imagename_top = sf.next("{");
1418 std::string imagename_left = sf.next("{");
1419 std::string imagename_right = sf.next("{");
1421 // Generate images for the faces of the cube
1422 video::IImage *img_top = generate_image_from_scratch(
1423 imagename_top, device, sourcecache);
1424 video::IImage *img_left = generate_image_from_scratch(
1425 imagename_left, device, sourcecache);
1426 video::IImage *img_right = generate_image_from_scratch(
1427 imagename_right, device, sourcecache);
1428 assert(img_top && img_left && img_right);
1430 // Create textures from images
1431 video::ITexture *texture_top = driver->addTexture(
1432 (imagename_top + "__temp__").c_str(), img_top);
1433 video::ITexture *texture_left = driver->addTexture(
1434 (imagename_left + "__temp__").c_str(), img_left);
1435 video::ITexture *texture_right = driver->addTexture(
1436 (imagename_right + "__temp__").c_str(), img_right);
1437 assert(texture_top && texture_left && texture_right);
1445 Draw a cube mesh into a render target texture
1447 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1448 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1449 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1450 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1451 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1452 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1453 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1454 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1456 core::dimension2d<u32> dim(64,64);
1457 std::string rtt_texture_name = part_of_name + "_RTT";
1459 v3f camera_position(0, 1.0, -1.5);
1460 camera_position.rotateXZBy(45);
1461 v3f camera_lookat(0, 0, 0);
1462 core::CMatrix4<f32> camera_projection_matrix;
1463 // Set orthogonal projection
1464 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1465 1.65, 1.65, 0, 100);
1467 video::SColorf ambient_light(0.2,0.2,0.2);
1468 v3f light_position(10, 100, -50);
1469 video::SColorf light_color(0.5,0.5,0.5);
1470 f32 light_radius = 1000;
1472 video::ITexture *rtt = generateTextureFromMesh(
1473 cube, device, dim, rtt_texture_name,
1476 camera_projection_matrix,
1485 // Free textures of images
1486 driver->removeTexture(texture_top);
1487 driver->removeTexture(texture_left);
1488 driver->removeTexture(texture_right);
1492 errorstream<<"generate_image(): render to texture failed."
1493 " Creating fallback image"<<std::endl;
1494 baseimg = generate_image_from_scratch(
1495 imagename_top, device, sourcecache);
1499 // Create image of render target
1500 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1503 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1507 image->copyTo(baseimg);
1513 errorstream<<"generate_image(): Invalid "
1514 " modification: \""<<part_of_name<<"\""<<std::endl;
1521 void brighten(video::IImage *image)
1526 core::dimension2d<u32> dim = image->getDimension();
1528 for(u32 y=0; y<dim.Height; y++)
1529 for(u32 x=0; x<dim.Width; x++)
1531 video::SColor c = image->getPixel(x,y);
1532 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1533 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1534 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1535 image->setPixel(x,y,c);