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 = name.find_last_of(separator);
1027 //if(last_separator_position == std::npos)
1028 // last_separator_position = -1;
1030 /*infostream<<"generate_image_from_scratch(): "
1031 <<"last_separator_position="<<last_separator_position
1035 If separator was found, construct the base name and make the
1036 base image using a recursive call
1038 std::string base_image_name;
1039 if(last_separator_position != -1)
1041 // Construct base name
1042 base_image_name = name.substr(0, last_separator_position);
1043 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
1044 " to get base image of \""<<name<<"\" = \""
1045 <<base_image_name<<"\""<<std::endl;*/
1046 baseimg = generate_image_from_scratch(base_image_name, device,
1051 Parse out the last part of the name of the image and act
1055 std::string last_part_of_name = name.substr(last_separator_position+1);
1056 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
1058 // Generate image according to part of name
1059 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
1061 errorstream<<"generate_image_from_scratch(): "
1062 "failed to generate \""<<last_part_of_name<<"\""
1070 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
1071 IrrlichtDevice *device, SourceImageCache *sourcecache)
1073 video::IVideoDriver* driver = device->getVideoDriver();
1076 // Stuff starting with [ are special commands
1077 if(part_of_name.size() == 0 || part_of_name[0] != '[')
1079 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
1083 if(part_of_name != ""){
1084 errorstream<<"generate_image(): Could not load image \""
1085 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1086 errorstream<<"generate_image(): Creating a dummy"
1087 <<" image for \""<<part_of_name<<"\""<<std::endl;
1090 // Just create a dummy image
1091 //core::dimension2d<u32> dim(2,2);
1092 core::dimension2d<u32> dim(1,1);
1093 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1095 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1096 image->setPixel(1,0, video::SColor(255,0,255,0));
1097 image->setPixel(0,1, video::SColor(255,0,0,255));
1098 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1099 image->setPixel(0,0, video::SColor(255,myrand()%256,
1100 myrand()%256,myrand()%256));
1101 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1102 myrand()%256,myrand()%256));
1103 image->setPixel(0,1, video::SColor(255,myrand()%256,
1104 myrand()%256,myrand()%256));
1105 image->setPixel(1,1, video::SColor(255,myrand()%256,
1106 myrand()%256,myrand()%256));*/
1109 // If base image is NULL, load as base.
1112 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1114 Copy it this way to get an alpha channel.
1115 Otherwise images with alpha cannot be blitted on
1116 images that don't have alpha in the original file.
1118 core::dimension2d<u32> dim = image->getDimension();
1119 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1120 image->copyTo(baseimg);
1123 // Else blit on base.
1126 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1127 // Size of the copied area
1128 core::dimension2d<u32> dim = image->getDimension();
1129 //core::dimension2d<u32> dim(16,16);
1130 // Position to copy the blitted to in the base image
1131 core::position2d<s32> pos_to(0,0);
1132 // Position to copy the blitted from in the blitted image
1133 core::position2d<s32> pos_from(0,0);
1135 image->copyToWithAlpha(baseimg, pos_to,
1136 core::rect<s32>(pos_from, dim),
1137 video::SColor(255,255,255,255),
1145 // A special texture modification
1147 /*infostream<<"generate_image(): generating special "
1148 <<"modification \""<<part_of_name<<"\""
1152 This is the simplest of all; it just adds stuff to the
1153 name so that a separate texture is created.
1155 It is used to make textures for stuff that doesn't want
1156 to implement getting the texture from a bigger texture
1159 if(part_of_name == "[forcesingle")
1161 // If base image is NULL, create a random color
1164 core::dimension2d<u32> dim(1,1);
1165 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1167 baseimg->setPixel(0,0, video::SColor(255,myrand()%256,
1168 myrand()%256,myrand()%256));
1173 Adds a cracking texture
1175 else if(part_of_name.substr(0,6) == "[crack")
1179 errorstream<<"generate_image(): baseimg==NULL "
1180 <<"for part_of_name=\""<<part_of_name
1181 <<"\", cancelling."<<std::endl;
1185 // Crack image number
1186 u16 progression = stoi(part_of_name.substr(6));
1188 // Size of the base image
1189 core::dimension2d<u32> dim_base = baseimg->getDimension();
1194 It is an image with a number of cracking stages
1197 video::IImage *img_crack = sourcecache->getOrLoad("crack.png", device);
1201 // Dimension of original image
1202 core::dimension2d<u32> dim_crack
1203 = img_crack->getDimension();
1204 // Count of crack stages
1205 u32 crack_count = dim_crack.Height / dim_crack.Width;
1206 // Limit progression
1207 if(progression > crack_count-1)
1208 progression = crack_count-1;
1209 // Dimension of a single scaled crack stage
1210 core::dimension2d<u32> dim_crack_scaled_single(
1214 // Dimension of scaled size
1215 core::dimension2d<u32> dim_crack_scaled(
1216 dim_crack_scaled_single.Width,
1217 dim_crack_scaled_single.Height * crack_count
1219 // Create scaled crack image
1220 video::IImage *img_crack_scaled = driver->createImage(
1221 video::ECF_A8R8G8B8, dim_crack_scaled);
1222 if(img_crack_scaled)
1224 // Scale crack image by copying
1225 img_crack->copyToScaling(img_crack_scaled);
1227 // Position to copy the crack from
1228 core::position2d<s32> pos_crack_scaled(
1230 dim_crack_scaled_single.Height * progression
1233 // This tiling does nothing currently but is useful
1234 for(u32 y0=0; y0<dim_base.Height
1235 / dim_crack_scaled_single.Height; y0++)
1236 for(u32 x0=0; x0<dim_base.Width
1237 / dim_crack_scaled_single.Width; x0++)
1239 // Position to copy the crack to in the base image
1240 core::position2d<s32> pos_base(
1241 x0*dim_crack_scaled_single.Width,
1242 y0*dim_crack_scaled_single.Height
1244 // Rectangle to copy the crack from on the scaled image
1245 core::rect<s32> rect_crack_scaled(
1247 dim_crack_scaled_single
1250 img_crack_scaled->copyToWithAlpha(baseimg, pos_base,
1252 video::SColor(255,255,255,255),
1256 img_crack_scaled->drop();
1263 [combine:WxH:X,Y=filename:X,Y=filename2
1264 Creates a bigger texture from an amount of smaller ones
1266 else if(part_of_name.substr(0,8) == "[combine")
1268 Strfnd sf(part_of_name);
1270 u32 w0 = stoi(sf.next("x"));
1271 u32 h0 = stoi(sf.next(":"));
1272 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1273 core::dimension2d<u32> dim(w0,h0);
1274 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1275 while(sf.atend() == false)
1277 u32 x = stoi(sf.next(","));
1278 u32 y = stoi(sf.next("="));
1279 std::string filename = sf.next(":");
1280 infostream<<"Adding \""<<filename
1281 <<"\" to combined ("<<x<<","<<y<<")"
1283 video::IImage *img = sourcecache->getOrLoad(filename, device);
1286 core::dimension2d<u32> dim = img->getDimension();
1287 infostream<<"Size "<<dim.Width
1288 <<"x"<<dim.Height<<std::endl;
1289 core::position2d<s32> pos_base(x, y);
1290 video::IImage *img2 =
1291 driver->createImage(video::ECF_A8R8G8B8, dim);
1294 img2->copyToWithAlpha(baseimg, pos_base,
1295 core::rect<s32>(v2s32(0,0), dim),
1296 video::SColor(255,255,255,255),
1302 infostream<<"img==NULL"<<std::endl;
1309 else if(part_of_name.substr(0,9) == "[brighten")
1313 errorstream<<"generate_image(): baseimg==NULL "
1314 <<"for part_of_name=\""<<part_of_name
1315 <<"\", cancelling."<<std::endl;
1323 Make image completely opaque.
1324 Used for the leaves texture when in old leaves mode, so
1325 that the transparent parts don't look completely black
1326 when simple alpha channel is used for rendering.
1328 else if(part_of_name.substr(0,8) == "[noalpha")
1332 errorstream<<"generate_image(): baseimg==NULL "
1333 <<"for part_of_name=\""<<part_of_name
1334 <<"\", cancelling."<<std::endl;
1338 core::dimension2d<u32> dim = baseimg->getDimension();
1340 // Set alpha to full
1341 for(u32 y=0; y<dim.Height; y++)
1342 for(u32 x=0; x<dim.Width; x++)
1344 video::SColor c = baseimg->getPixel(x,y);
1346 baseimg->setPixel(x,y,c);
1351 Convert one color to transparent.
1353 else if(part_of_name.substr(0,11) == "[makealpha:")
1357 errorstream<<"generate_image(): baseimg==NULL "
1358 <<"for part_of_name=\""<<part_of_name
1359 <<"\", cancelling."<<std::endl;
1363 Strfnd sf(part_of_name.substr(11));
1364 u32 r1 = stoi(sf.next(","));
1365 u32 g1 = stoi(sf.next(","));
1366 u32 b1 = stoi(sf.next(""));
1367 std::string filename = sf.next("");
1369 core::dimension2d<u32> dim = baseimg->getDimension();
1371 /*video::IImage *oldbaseimg = baseimg;
1372 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1373 oldbaseimg->copyTo(baseimg);
1374 oldbaseimg->drop();*/
1376 // Set alpha to full
1377 for(u32 y=0; y<dim.Height; y++)
1378 for(u32 x=0; x<dim.Width; x++)
1380 video::SColor c = baseimg->getPixel(x,y);
1382 u32 g = c.getGreen();
1383 u32 b = c.getBlue();
1384 if(!(r == r1 && g == g1 && b == b1))
1387 baseimg->setPixel(x,y,c);
1391 [inventorycube{topimage{leftimage{rightimage
1392 In every subimage, replace ^ with &.
1393 Create an "inventory cube".
1394 NOTE: This should be used only on its own.
1395 Example (a grass block (not actually used in game):
1396 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1398 else if(part_of_name.substr(0,14) == "[inventorycube")
1402 errorstream<<"generate_image(): baseimg!=NULL "
1403 <<"for part_of_name=\""<<part_of_name
1404 <<"\", cancelling."<<std::endl;
1408 str_replace_char(part_of_name, '&', '^');
1409 Strfnd sf(part_of_name);
1411 std::string imagename_top = sf.next("{");
1412 std::string imagename_left = sf.next("{");
1413 std::string imagename_right = sf.next("{");
1415 // Generate images for the faces of the cube
1416 video::IImage *img_top = generate_image_from_scratch(
1417 imagename_top, device, sourcecache);
1418 video::IImage *img_left = generate_image_from_scratch(
1419 imagename_left, device, sourcecache);
1420 video::IImage *img_right = generate_image_from_scratch(
1421 imagename_right, device, sourcecache);
1422 assert(img_top && img_left && img_right);
1424 // Create textures from images
1425 video::ITexture *texture_top = driver->addTexture(
1426 (imagename_top + "__temp__").c_str(), img_top);
1427 video::ITexture *texture_left = driver->addTexture(
1428 (imagename_left + "__temp__").c_str(), img_left);
1429 video::ITexture *texture_right = driver->addTexture(
1430 (imagename_right + "__temp__").c_str(), img_right);
1431 assert(texture_top && texture_left && texture_right);
1439 Draw a cube mesh into a render target texture
1441 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1442 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1443 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1444 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1445 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1446 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1447 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1448 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1450 core::dimension2d<u32> dim(64,64);
1451 std::string rtt_texture_name = part_of_name + "_RTT";
1453 v3f camera_position(0, 1.0, -1.5);
1454 camera_position.rotateXZBy(45);
1455 v3f camera_lookat(0, 0, 0);
1456 core::CMatrix4<f32> camera_projection_matrix;
1457 // Set orthogonal projection
1458 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1459 1.65, 1.65, 0, 100);
1461 video::SColorf ambient_light(0.2,0.2,0.2);
1462 v3f light_position(10, 100, -50);
1463 video::SColorf light_color(0.5,0.5,0.5);
1464 f32 light_radius = 1000;
1466 video::ITexture *rtt = generateTextureFromMesh(
1467 cube, device, dim, rtt_texture_name,
1470 camera_projection_matrix,
1479 // Free textures of images
1480 driver->removeTexture(texture_top);
1481 driver->removeTexture(texture_left);
1482 driver->removeTexture(texture_right);
1486 baseimg = generate_image_from_scratch(
1487 imagename_top, device, sourcecache);
1491 // Create image of render target
1492 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1495 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1499 image->copyTo(baseimg);
1505 errorstream<<"generate_image(): Invalid "
1506 " modification: \""<<part_of_name<<"\""<<std::endl;
1513 void brighten(video::IImage *image)
1518 core::dimension2d<u32> dim = image->getDimension();
1520 for(u32 y=0; y<dim.Height; y++)
1521 for(u32 x=0; x<dim.Width; x++)
1523 video::SColor c = image->getPixel(x,y);
1524 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1525 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1526 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1527 image->setPixel(x,y,c);