3 Copyright (C) 2010-2013 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 Lesser General Public License as published by
7 the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details.
15 You should have received a copy of the GNU Lesser 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
26 #include <ICameraSceneNode.h>
29 #include "util/string.h"
30 #include "util/container.h"
31 #include "util/thread.h"
32 #include "util/numeric.h"
35 A cache from texture name to texture path
37 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
40 Replaces the filename extension.
42 std::string image = "a/image.png"
43 replace_ext(image, "jpg")
44 -> image = "a/image.jpg"
45 Returns true on success.
47 static bool replace_ext(std::string &path, const char *ext)
51 // Find place of last dot, fail if \ or / found.
53 for(s32 i=path.size()-1; i>=0; i--)
61 if(path[i] == '\\' || path[i] == '/')
64 // If not found, return an empty string
67 // Else make the new path
68 path = path.substr(0, last_dot_i+1) + ext;
73 Find out the full path of an image by trying different filename
78 std::string getImagePath(std::string path)
80 // A NULL-ended list of possible image extensions
81 const char *extensions[] = {
82 "png", "jpg", "bmp", "tga",
83 "pcx", "ppm", "psd", "wal", "rgb",
86 // If there is no extension, add one
87 if(removeStringEnd(path, extensions) == "")
89 // Check paths until something is found to exist
90 const char **ext = extensions;
92 bool r = replace_ext(path, *ext);
95 if(fs::PathExists(path))
98 while((++ext) != NULL);
104 Gets the path to a texture by first checking if the texture exists
105 in texture_path and if not, using the data path.
107 Checks all supported extensions by replacing the original extension.
109 If not found, returns "".
111 Utilizes a thread-safe cache.
113 std::string getTexturePath(const std::string &filename)
115 std::string fullpath = "";
119 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
124 Check from texture_path
126 std::string texture_path = g_settings->get("texture_path");
127 if(texture_path != "")
129 std::string testpath = texture_path + DIR_DELIM + filename;
130 // Check all filename extensions. Returns "" if not found.
131 fullpath = getImagePath(testpath);
135 Check from $user/textures/all
139 std::string texture_path = porting::path_user + DIR_DELIM
140 + "textures" + DIR_DELIM + "all";
141 std::string testpath = texture_path + DIR_DELIM + filename;
142 // Check all filename extensions. Returns "" if not found.
143 fullpath = getImagePath(testpath);
147 Check from default data directory
151 std::string base_path = porting::path_share + DIR_DELIM + "textures"
152 + DIR_DELIM + "base" + DIR_DELIM + "pack";
153 std::string testpath = base_path + DIR_DELIM + filename;
154 // Check all filename extensions. Returns "" if not found.
155 fullpath = getImagePath(testpath);
158 // Add to cache (also an empty result is cached)
159 g_texturename_to_path_cache.set(filename, fullpath);
166 Stores internal information about a texture.
172 video::ITexture *texture;
173 video::IImage *img; // The source image
176 const std::string &name_,
177 video::ITexture *texture_=NULL,
178 video::IImage *img_=NULL
188 SourceImageCache: A cache used for storing source images.
191 class SourceImageCache
194 ~SourceImageCache() {
195 for(std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
196 iter != m_images.end(); iter++) {
197 iter->second->drop();
201 void insert(const std::string &name, video::IImage *img,
202 bool prefer_local, video::IVideoDriver *driver)
206 std::map<std::string, video::IImage*>::iterator n;
207 n = m_images.find(name);
208 if(n != m_images.end()){
213 video::IImage* toadd = img;
214 bool need_to_grab = true;
216 // Try to use local texture instead if asked to
218 std::string path = getTexturePath(name.c_str());
220 video::IImage *img2 = driver->createImageFromFile(path.c_str());
223 need_to_grab = false;
230 m_images[name] = toadd;
232 video::IImage* get(const std::string &name)
234 std::map<std::string, video::IImage*>::iterator n;
235 n = m_images.find(name);
236 if(n != m_images.end())
240 // Primarily fetches from cache, secondarily tries to read from filesystem
241 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
243 std::map<std::string, video::IImage*>::iterator n;
244 n = m_images.find(name);
245 if(n != m_images.end()){
246 n->second->grab(); // Grab for caller
249 video::IVideoDriver* driver = device->getVideoDriver();
250 std::string path = getTexturePath(name.c_str());
252 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
253 <<name<<"\""<<std::endl;
256 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
258 video::IImage *img = driver->createImageFromFile(path.c_str());
261 m_images[name] = img;
262 img->grab(); // Grab for caller
267 std::map<std::string, video::IImage*> m_images;
274 class TextureSource : public IWritableTextureSource
277 TextureSource(IrrlichtDevice *device);
278 virtual ~TextureSource();
282 Now, assume a texture with the id 1 exists, and has the name
283 "stone.png^mineral1".
284 Then a random thread calls getTextureId for a texture called
285 "stone.png^mineral1^crack0".
286 ...Now, WTF should happen? Well:
287 - getTextureId strips off stuff recursively from the end until
288 the remaining part is found, or nothing is left when
289 something is stripped out
291 But it is slow to search for textures by names and modify them
293 - ContentFeatures is made to contain ids for the basic plain
295 - Crack textures can be slow by themselves, but the framework
299 - Assume a texture with the id 1 exists, and has the name
300 "stone.png^mineral_coal.png".
301 - Now getNodeTile() stumbles upon a node which uses
302 texture id 1, and determines that MATERIAL_FLAG_CRACK
303 must be applied to the tile
304 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
305 has received the current crack level 0 from the client. It
306 finds out the name of the texture with getTextureName(1),
307 appends "^crack0" to it and gets a new texture id with
308 getTextureId("stone.png^mineral_coal.png^crack0").
313 Gets a texture id from cache or
314 - if main thread, from getTextureIdDirect
315 - if other thread, adds to request queue and waits for main thread
317 u32 getTextureId(const std::string &name);
323 "stone.png^mineral_coal.png"
324 "stone.png^mineral_coal.png^crack1"
326 - If texture specified by name is found from cache, return the
328 - Otherwise generate the texture, add to cache and return id.
329 Recursion is used to find out the largest found part of the
330 texture and continue based on it.
332 The id 0 points to a NULL texture. It is returned in case of error.
334 u32 getTextureIdDirect(const std::string &name);
336 // Finds out the name of a cached texture.
337 std::string getTextureName(u32 id);
340 If texture specified by the name pointed by the id doesn't
341 exist, create it, then return the cached texture.
343 Can be called from any thread. If called from some other thread
344 and not found in cache, the call is queued to the main thread
347 video::ITexture* getTexture(u32 id);
349 video::ITexture* getTexture(const std::string &name, u32 *id);
351 // Returns a pointer to the irrlicht device
352 virtual IrrlichtDevice* getDevice()
357 bool isKnownSourceImage(const std::string &name)
359 bool is_known = false;
360 bool cache_found = m_source_image_existence.get(name, &is_known);
363 // Not found in cache; find out if a local file exists
364 is_known = (getTexturePath(name) != "");
365 m_source_image_existence.set(name, is_known);
369 // Processes queued texture requests from other threads.
370 // Shall be called from the main thread.
373 // Insert an image into the cache without touching the filesystem.
374 // Shall be called from the main thread.
375 void insertSourceImage(const std::string &name, video::IImage *img);
377 // Rebuild images and textures from the current set of source images
378 // Shall be called from the main thread.
379 void rebuildImagesAndTextures();
383 // The id of the thread that is allowed to use irrlicht directly
384 threadid_t m_main_thread;
385 // The irrlicht device
386 IrrlichtDevice *m_device;
388 // Cache of source images
389 // This should be only accessed from the main thread
390 SourceImageCache m_sourcecache;
392 // Thread-safe cache of what source images are known (true = known)
393 MutexedMap<std::string, bool> m_source_image_existence;
395 // A texture id is index in this array.
396 // The first position contains a NULL texture.
397 std::vector<TextureInfo> m_textureinfo_cache;
398 // Maps a texture name to an index in the former.
399 std::map<std::string, u32> m_name_to_id;
400 // The two former containers are behind this mutex
401 JMutex m_textureinfo_cache_mutex;
403 // Queued texture fetches (to be processed by the main thread)
404 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
406 // Textures that have been overwritten with other ones
407 // but can't be deleted because the ITexture* might still be used
408 std::list<video::ITexture*> m_texture_trash;
411 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
413 return new TextureSource(device);
416 TextureSource::TextureSource(IrrlichtDevice *device):
421 m_textureinfo_cache_mutex.Init();
423 m_main_thread = get_current_thread_id();
425 // Add a NULL TextureInfo as the first index, named ""
426 m_textureinfo_cache.push_back(TextureInfo(""));
427 m_name_to_id[""] = 0;
430 TextureSource::~TextureSource()
432 video::IVideoDriver* driver = m_device->getVideoDriver();
434 unsigned int textures_before = driver->getTextureCount();
436 for (std::vector<TextureInfo>::iterator iter =
437 m_textureinfo_cache.begin();
438 iter != m_textureinfo_cache.end(); iter++)
442 driver->removeTexture(iter->texture);
444 //cleanup source image
448 m_textureinfo_cache.clear();
450 for (std::list<video::ITexture*>::iterator iter =
451 m_texture_trash.begin(); iter != m_texture_trash.end();
454 video::ITexture *t = *iter;
456 //cleanup trashed texture
457 driver->removeTexture(t);
460 infostream << "~TextureSource() "<< textures_before << "/"
461 << driver->getTextureCount() << std::endl;
464 u32 TextureSource::getTextureId(const std::string &name)
466 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
470 See if texture already exists
472 JMutexAutoLock lock(m_textureinfo_cache_mutex);
473 std::map<std::string, u32>::iterator n;
474 n = m_name_to_id.find(name);
475 if(n != m_name_to_id.end())
484 if(get_current_thread_id() == m_main_thread)
486 return getTextureIdDirect(name);
490 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
492 // We're gonna ask the result to be put into here
493 ResultQueue<std::string, u32, u8, u8> result_queue;
495 // Throw a request in
496 m_get_texture_queue.add(name, 0, 0, &result_queue);
498 infostream<<"Waiting for texture from main thread, name=\""
499 <<name<<"\""<<std::endl;
503 // Wait result for a second
504 GetResult<std::string, u32, u8, u8>
505 result = result_queue.pop_front(1000);
507 // Check that at least something worked OK
508 assert(result.key == name);
512 catch(ItemNotFoundException &e)
514 infostream<<"Waiting for texture timed out."<<std::endl;
519 infostream<<"getTextureId(): Failed"<<std::endl;
524 // Overlay image on top of another image (used for cracks)
525 void overlay(video::IImage *image, video::IImage *overlay);
527 // Draw an image on top of an another one, using the alpha channel of the
529 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
530 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
533 void brighten(video::IImage *image);
534 // Parse a transform name
535 u32 parseImageTransform(const std::string& s);
536 // Apply transform to image dimension
537 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
538 // Apply transform to image data
539 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
542 Generate image based on a string like "stone.png" or "[crack0".
543 if baseimg is NULL, it is created. Otherwise stuff is made on it.
545 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
546 IrrlichtDevice *device, SourceImageCache *sourcecache);
549 Generates an image from a full string like
550 "stone.png^mineral_coal.png^[crack0".
552 video::IImage* generate_image_from_scratch(std::string name,
553 IrrlichtDevice *device, SourceImageCache *sourcecache);
556 This method generates all the textures
558 u32 TextureSource::getTextureIdDirect(const std::string &name)
560 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
562 // Empty name means texture 0
565 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
570 Calling only allowed from main thread
572 if(get_current_thread_id() != m_main_thread)
574 errorstream<<"TextureSource::getTextureIdDirect() "
575 "called not from main thread"<<std::endl;
580 See if texture already exists
583 JMutexAutoLock lock(m_textureinfo_cache_mutex);
585 std::map<std::string, u32>::iterator n;
586 n = m_name_to_id.find(name);
587 if(n != m_name_to_id.end())
589 /*infostream<<"getTextureIdDirect(): \""<<name
590 <<"\" found in cache"<<std::endl;*/
595 /*infostream<<"getTextureIdDirect(): \""<<name
596 <<"\" NOT found in cache. Creating it."<<std::endl;*/
602 char separator = '^';
605 This is set to the id of the base image.
606 If left 0, there is no base image and a completely new image
609 u32 base_image_id = 0;
611 // Find last meta separator in name
612 s32 last_separator_position = -1;
613 for(s32 i=name.size()-1; i>=0; i--)
615 if(name[i] == separator)
617 last_separator_position = i;
622 If separator was found, construct the base name and make the
623 base image using a recursive call
625 std::string base_image_name;
626 if(last_separator_position != -1)
628 // Construct base name
629 base_image_name = name.substr(0, last_separator_position);
630 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
631 " to get base image of \""<<name<<"\" = \""
632 <<base_image_name<<"\""<<std::endl;*/
633 base_image_id = getTextureIdDirect(base_image_name);
636 //infostream<<"base_image_id="<<base_image_id<<std::endl;
638 video::IVideoDriver* driver = m_device->getVideoDriver();
641 video::ITexture *t = NULL;
644 An image will be built from files and then converted into a texture.
646 video::IImage *baseimg = NULL;
648 // If a base image was found, copy it to baseimg
649 if(base_image_id != 0)
651 JMutexAutoLock lock(m_textureinfo_cache_mutex);
653 TextureInfo *ti = &m_textureinfo_cache[base_image_id];
657 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
658 <<"cache: \""<<base_image_name<<"\""
663 core::dimension2d<u32> dim = ti->img->getDimension();
665 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
669 v2s32(0,0), // position in target
670 core::rect<s32>(v2s32(0,0), dim) // from
673 /*infostream<<"getTextureIdDirect(): Loaded \""
674 <<base_image_name<<"\" from image cache"
680 Parse out the last part of the name of the image and act
684 std::string last_part_of_name = name.substr(last_separator_position+1);
685 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
687 // Generate image according to part of name
688 if(!generate_image(last_part_of_name, baseimg, m_device, &m_sourcecache))
690 errorstream<<"getTextureIdDirect(): "
691 "failed to generate \""<<last_part_of_name<<"\""
695 // If no resulting image, print a warning
698 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
699 " create texture \""<<name<<"\""<<std::endl;
704 // Create texture from resulting image
705 t = driver->addTexture(name.c_str(), baseimg);
709 Add texture to caches (add NULL textures too)
712 JMutexAutoLock lock(m_textureinfo_cache_mutex);
714 u32 id = m_textureinfo_cache.size();
715 TextureInfo ti(name, t, baseimg);
716 m_textureinfo_cache.push_back(ti);
717 m_name_to_id[name] = id;
719 /*infostream<<"getTextureIdDirect(): "
720 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
725 std::string TextureSource::getTextureName(u32 id)
727 JMutexAutoLock lock(m_textureinfo_cache_mutex);
729 if(id >= m_textureinfo_cache.size())
731 errorstream<<"TextureSource::getTextureName(): id="<<id
732 <<" >= m_textureinfo_cache.size()="
733 <<m_textureinfo_cache.size()<<std::endl;
737 return m_textureinfo_cache[id].name;
740 video::ITexture* TextureSource::getTexture(u32 id)
742 JMutexAutoLock lock(m_textureinfo_cache_mutex);
744 if(id >= m_textureinfo_cache.size())
747 return m_textureinfo_cache[id].texture;
750 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
752 u32 actual_id = getTextureId(name);
756 return getTexture(actual_id);
759 void TextureSource::processQueue()
764 if(!m_get_texture_queue.empty())
766 GetRequest<std::string, u32, u8, u8>
767 request = m_get_texture_queue.pop();
769 /*infostream<<"TextureSource::processQueue(): "
770 <<"got texture request with "
771 <<"name=\""<<request.key<<"\""
774 GetResult<std::string, u32, u8, u8>
776 result.key = request.key;
777 result.callers = request.callers;
778 result.item = getTextureIdDirect(request.key);
780 request.dest->push_back(result);
784 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
786 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
788 assert(get_current_thread_id() == m_main_thread);
790 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
791 m_source_image_existence.set(name, true);
794 void TextureSource::rebuildImagesAndTextures()
796 JMutexAutoLock lock(m_textureinfo_cache_mutex);
798 video::IVideoDriver* driver = m_device->getVideoDriver();
801 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
802 TextureInfo *ti = &m_textureinfo_cache[i];
804 generate_image_from_scratch(ti->name, m_device, &m_sourcecache);
805 // Create texture from resulting image
806 video::ITexture *t = NULL;
808 t = driver->addTexture(ti->name.c_str(), img);
809 video::ITexture *t_old = ti->texture;
815 m_texture_trash.push_back(t_old);
819 video::IImage* generate_image_from_scratch(std::string name,
820 IrrlichtDevice *device, SourceImageCache *sourcecache)
822 /*infostream<<"generate_image_from_scratch(): "
823 "\""<<name<<"\""<<std::endl;*/
825 video::IVideoDriver* driver = device->getVideoDriver();
832 video::IImage *baseimg = NULL;
834 char separator = '^';
836 // Find last meta separator in name
837 s32 last_separator_position = name.find_last_of(separator);
838 //if(last_separator_position == std::npos)
839 // last_separator_position = -1;
841 /*infostream<<"generate_image_from_scratch(): "
842 <<"last_separator_position="<<last_separator_position
846 If separator was found, construct the base name and make the
847 base image using a recursive call
849 std::string base_image_name;
850 if(last_separator_position != -1)
852 // Construct base name
853 base_image_name = name.substr(0, last_separator_position);
854 /*infostream<<"generate_image_from_scratch(): Calling itself recursively"
855 " to get base image of \""<<name<<"\" = \""
856 <<base_image_name<<"\""<<std::endl;*/
857 baseimg = generate_image_from_scratch(base_image_name, device,
862 Parse out the last part of the name of the image and act
866 std::string last_part_of_name = name.substr(last_separator_position+1);
867 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
869 // Generate image according to part of name
870 if(!generate_image(last_part_of_name, baseimg, device, sourcecache))
872 errorstream<<"generate_image_from_scratch(): "
873 "failed to generate \""<<last_part_of_name<<"\""
881 bool generate_image(std::string part_of_name, video::IImage *& baseimg,
882 IrrlichtDevice *device, SourceImageCache *sourcecache)
884 video::IVideoDriver* driver = device->getVideoDriver();
887 // Stuff starting with [ are special commands
888 if(part_of_name.size() == 0 || part_of_name[0] != '[')
890 video::IImage *image = sourcecache->getOrLoad(part_of_name, device);
894 if(part_of_name != ""){
895 errorstream<<"generate_image(): Could not load image \""
896 <<part_of_name<<"\""<<" while building texture"<<std::endl;
897 errorstream<<"generate_image(): Creating a dummy"
898 <<" image for \""<<part_of_name<<"\""<<std::endl;
901 // Just create a dummy image
902 //core::dimension2d<u32> dim(2,2);
903 core::dimension2d<u32> dim(1,1);
904 image = driver->createImage(video::ECF_A8R8G8B8, dim);
906 /*image->setPixel(0,0, video::SColor(255,255,0,0));
907 image->setPixel(1,0, video::SColor(255,0,255,0));
908 image->setPixel(0,1, video::SColor(255,0,0,255));
909 image->setPixel(1,1, video::SColor(255,255,0,255));*/
910 image->setPixel(0,0, video::SColor(255,myrand()%256,
911 myrand()%256,myrand()%256));
912 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
913 myrand()%256,myrand()%256));
914 image->setPixel(0,1, video::SColor(255,myrand()%256,
915 myrand()%256,myrand()%256));
916 image->setPixel(1,1, video::SColor(255,myrand()%256,
917 myrand()%256,myrand()%256));*/
920 // If base image is NULL, load as base.
923 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
925 Copy it this way to get an alpha channel.
926 Otherwise images with alpha cannot be blitted on
927 images that don't have alpha in the original file.
929 core::dimension2d<u32> dim = image->getDimension();
930 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
931 image->copyTo(baseimg);
933 // Else blit on base.
936 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
937 // Size of the copied area
938 core::dimension2d<u32> dim = image->getDimension();
939 //core::dimension2d<u32> dim(16,16);
940 // Position to copy the blitted to in the base image
941 core::position2d<s32> pos_to(0,0);
942 // Position to copy the blitted from in the blitted image
943 core::position2d<s32> pos_from(0,0);
945 /*image->copyToWithAlpha(baseimg, pos_to,
946 core::rect<s32>(pos_from, dim),
947 video::SColor(255,255,255,255),
949 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
956 // A special texture modification
958 /*infostream<<"generate_image(): generating special "
959 <<"modification \""<<part_of_name<<"\""
964 Adds a cracking texture
966 if(part_of_name.substr(0,6) == "[crack")
970 errorstream<<"generate_image(): baseimg==NULL "
971 <<"for part_of_name=\""<<part_of_name
972 <<"\", cancelling."<<std::endl;
976 // Crack image number and overlay option
978 bool use_overlay = false;
979 if(part_of_name.substr(6,1) == "o")
981 progression = stoi(part_of_name.substr(7));
986 progression = stoi(part_of_name.substr(6));
990 // Size of the base image
991 core::dimension2d<u32> dim_base = baseimg->getDimension();
996 It is an image with a number of cracking stages
999 video::IImage *img_crack = sourcecache->getOrLoad(
1000 "crack_anylength.png", device);
1002 if(img_crack && progression >= 0)
1004 // Dimension of original image
1005 core::dimension2d<u32> dim_crack
1006 = img_crack->getDimension();
1007 // Count of crack stages
1008 s32 crack_count = dim_crack.Height / dim_crack.Width;
1009 // Limit progression
1010 if(progression > crack_count-1)
1011 progression = crack_count-1;
1012 // Dimension of a single crack stage
1013 core::dimension2d<u32> dim_crack_cropped(
1017 // Create cropped and scaled crack images
1018 video::IImage *img_crack_cropped = driver->createImage(
1019 video::ECF_A8R8G8B8, dim_crack_cropped);
1020 video::IImage *img_crack_scaled = driver->createImage(
1021 video::ECF_A8R8G8B8, dim_base);
1023 if(img_crack_cropped && img_crack_scaled)
1026 v2s32 pos_crack(0, progression*dim_crack.Width);
1027 img_crack->copyTo(img_crack_cropped,
1029 core::rect<s32>(pos_crack, dim_crack_cropped));
1030 // Scale crack image by copying
1031 img_crack_cropped->copyToScaling(img_crack_scaled);
1032 // Copy or overlay crack image
1035 overlay(baseimg, img_crack_scaled);
1039 /*img_crack_scaled->copyToWithAlpha(
1042 core::rect<s32>(v2s32(0,0), dim_base),
1043 video::SColor(255,255,255,255));*/
1044 blit_with_alpha(img_crack_scaled, baseimg,
1045 v2s32(0,0), v2s32(0,0), dim_base);
1049 if(img_crack_scaled)
1050 img_crack_scaled->drop();
1052 if(img_crack_cropped)
1053 img_crack_cropped->drop();
1059 [combine:WxH:X,Y=filename:X,Y=filename2
1060 Creates a bigger texture from an amount of smaller ones
1062 else if(part_of_name.substr(0,8) == "[combine")
1064 Strfnd sf(part_of_name);
1066 u32 w0 = stoi(sf.next("x"));
1067 u32 h0 = stoi(sf.next(":"));
1068 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1069 core::dimension2d<u32> dim(w0,h0);
1072 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1073 baseimg->fill(video::SColor(0,0,0,0));
1075 while(sf.atend() == false)
1077 u32 x = stoi(sf.next(","));
1078 u32 y = stoi(sf.next("="));
1079 std::string filename = sf.next(":");
1080 infostream<<"Adding \""<<filename
1081 <<"\" to combined ("<<x<<","<<y<<")"
1083 video::IImage *img = sourcecache->getOrLoad(filename, device);
1086 core::dimension2d<u32> dim = img->getDimension();
1087 infostream<<"Size "<<dim.Width
1088 <<"x"<<dim.Height<<std::endl;
1089 core::position2d<s32> pos_base(x, y);
1090 video::IImage *img2 =
1091 driver->createImage(video::ECF_A8R8G8B8, dim);
1094 /*img2->copyToWithAlpha(baseimg, pos_base,
1095 core::rect<s32>(v2s32(0,0), dim),
1096 video::SColor(255,255,255,255),
1098 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1103 infostream<<"img==NULL"<<std::endl;
1110 else if(part_of_name.substr(0,9) == "[brighten")
1114 errorstream<<"generate_image(): baseimg==NULL "
1115 <<"for part_of_name=\""<<part_of_name
1116 <<"\", cancelling."<<std::endl;
1124 Make image completely opaque.
1125 Used for the leaves texture when in old leaves mode, so
1126 that the transparent parts don't look completely black
1127 when simple alpha channel is used for rendering.
1129 else if(part_of_name.substr(0,8) == "[noalpha")
1133 errorstream<<"generate_image(): baseimg==NULL "
1134 <<"for part_of_name=\""<<part_of_name
1135 <<"\", cancelling."<<std::endl;
1139 core::dimension2d<u32> dim = baseimg->getDimension();
1141 // Set alpha to full
1142 for(u32 y=0; y<dim.Height; y++)
1143 for(u32 x=0; x<dim.Width; x++)
1145 video::SColor c = baseimg->getPixel(x,y);
1147 baseimg->setPixel(x,y,c);
1152 Convert one color to transparent.
1154 else if(part_of_name.substr(0,11) == "[makealpha:")
1158 errorstream<<"generate_image(): baseimg==NULL "
1159 <<"for part_of_name=\""<<part_of_name
1160 <<"\", cancelling."<<std::endl;
1164 Strfnd sf(part_of_name.substr(11));
1165 u32 r1 = stoi(sf.next(","));
1166 u32 g1 = stoi(sf.next(","));
1167 u32 b1 = stoi(sf.next(""));
1168 std::string filename = sf.next("");
1170 core::dimension2d<u32> dim = baseimg->getDimension();
1172 /*video::IImage *oldbaseimg = baseimg;
1173 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1174 oldbaseimg->copyTo(baseimg);
1175 oldbaseimg->drop();*/
1177 // Set alpha to full
1178 for(u32 y=0; y<dim.Height; y++)
1179 for(u32 x=0; x<dim.Width; x++)
1181 video::SColor c = baseimg->getPixel(x,y);
1183 u32 g = c.getGreen();
1184 u32 b = c.getBlue();
1185 if(!(r == r1 && g == g1 && b == b1))
1188 baseimg->setPixel(x,y,c);
1193 Rotates and/or flips the image.
1195 N can be a number (between 0 and 7) or a transform name.
1196 Rotations are counter-clockwise.
1198 1 R90 rotate by 90 degrees
1199 2 R180 rotate by 180 degrees
1200 3 R270 rotate by 270 degrees
1202 5 FXR90 flip X then rotate by 90 degrees
1204 7 FYR90 flip Y then rotate by 90 degrees
1206 Note: Transform names can be concatenated to produce
1207 their product (applies the first then the second).
1208 The resulting transform will be equivalent to one of the
1209 eight existing ones, though (see: dihedral group).
1211 else if(part_of_name.substr(0,10) == "[transform")
1215 errorstream<<"generate_image(): baseimg==NULL "
1216 <<"for part_of_name=\""<<part_of_name
1217 <<"\", cancelling."<<std::endl;
1221 u32 transform = parseImageTransform(part_of_name.substr(10));
1222 core::dimension2d<u32> dim = imageTransformDimension(
1223 transform, baseimg->getDimension());
1224 video::IImage *image = driver->createImage(
1225 baseimg->getColorFormat(), dim);
1227 imageTransform(transform, baseimg, image);
1232 [inventorycube{topimage{leftimage{rightimage
1233 In every subimage, replace ^ with &.
1234 Create an "inventory cube".
1235 NOTE: This should be used only on its own.
1236 Example (a grass block (not actually used in game):
1237 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1239 else if(part_of_name.substr(0,14) == "[inventorycube")
1243 errorstream<<"generate_image(): baseimg!=NULL "
1244 <<"for part_of_name=\""<<part_of_name
1245 <<"\", cancelling."<<std::endl;
1249 str_replace_char(part_of_name, '&', '^');
1250 Strfnd sf(part_of_name);
1252 std::string imagename_top = sf.next("{");
1253 std::string imagename_left = sf.next("{");
1254 std::string imagename_right = sf.next("{");
1256 // Generate images for the faces of the cube
1257 video::IImage *img_top = generate_image_from_scratch(
1258 imagename_top, device, sourcecache);
1259 video::IImage *img_left = generate_image_from_scratch(
1260 imagename_left, device, sourcecache);
1261 video::IImage *img_right = generate_image_from_scratch(
1262 imagename_right, device, sourcecache);
1263 assert(img_top && img_left && img_right);
1265 // Create textures from images
1266 video::ITexture *texture_top = driver->addTexture(
1267 (imagename_top + "__temp__").c_str(), img_top);
1268 video::ITexture *texture_left = driver->addTexture(
1269 (imagename_left + "__temp__").c_str(), img_left);
1270 video::ITexture *texture_right = driver->addTexture(
1271 (imagename_right + "__temp__").c_str(), img_right);
1272 assert(texture_top && texture_left && texture_right);
1280 Draw a cube mesh into a render target texture
1282 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1283 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1284 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1285 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1286 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1287 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1288 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1289 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1291 core::dimension2d<u32> dim(64,64);
1292 std::string rtt_texture_name = part_of_name + "_RTT";
1294 v3f camera_position(0, 1.0, -1.5);
1295 camera_position.rotateXZBy(45);
1296 v3f camera_lookat(0, 0, 0);
1297 core::CMatrix4<f32> camera_projection_matrix;
1298 // Set orthogonal projection
1299 camera_projection_matrix.buildProjectionMatrixOrthoLH(
1300 1.65, 1.65, 0, 100);
1302 video::SColorf ambient_light(0.2,0.2,0.2);
1303 v3f light_position(10, 100, -50);
1304 video::SColorf light_color(0.5,0.5,0.5);
1305 f32 light_radius = 1000;
1307 video::ITexture *rtt = generateTextureFromMesh(
1308 cube, device, dim, rtt_texture_name,
1311 camera_projection_matrix,
1320 // Free textures of images
1321 driver->removeTexture(texture_top);
1322 driver->removeTexture(texture_left);
1323 driver->removeTexture(texture_right);
1327 baseimg = generate_image_from_scratch(
1328 imagename_top, device, sourcecache);
1332 // Create image of render target
1333 video::IImage *image = driver->createImage(rtt, v2s32(0,0), dim);
1337 driver->removeTexture(rtt);
1339 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1343 image->copyTo(baseimg);
1348 [lowpart:percent:filename
1349 Adds the lower part of a texture
1351 else if(part_of_name.substr(0,9) == "[lowpart:")
1353 Strfnd sf(part_of_name);
1355 u32 percent = stoi(sf.next(":"));
1356 std::string filename = sf.next(":");
1357 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1360 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1361 video::IImage *img = sourcecache->getOrLoad(filename, device);
1364 core::dimension2d<u32> dim = img->getDimension();
1365 core::position2d<s32> pos_base(0, 0);
1366 video::IImage *img2 =
1367 driver->createImage(video::ECF_A8R8G8B8, dim);
1370 core::position2d<s32> clippos(0, 0);
1371 clippos.Y = dim.Height * (100-percent) / 100;
1372 core::dimension2d<u32> clipdim = dim;
1373 clipdim.Height = clipdim.Height * percent / 100 + 1;
1374 core::rect<s32> cliprect(clippos, clipdim);
1375 img2->copyToWithAlpha(baseimg, pos_base,
1376 core::rect<s32>(v2s32(0,0), dim),
1377 video::SColor(255,255,255,255),
1384 Crops a frame of a vertical animation.
1385 N = frame count, I = frame index
1387 else if(part_of_name.substr(0,15) == "[verticalframe:")
1389 Strfnd sf(part_of_name);
1391 u32 frame_count = stoi(sf.next(":"));
1392 u32 frame_index = stoi(sf.next(":"));
1394 if(baseimg == NULL){
1395 errorstream<<"generate_image(): baseimg!=NULL "
1396 <<"for part_of_name=\""<<part_of_name
1397 <<"\", cancelling."<<std::endl;
1401 v2u32 frame_size = baseimg->getDimension();
1402 frame_size.Y /= frame_count;
1404 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1407 errorstream<<"generate_image(): Could not create image "
1408 <<"for part_of_name=\""<<part_of_name
1409 <<"\", cancelling."<<std::endl;
1413 // Fill target image with transparency
1414 img->fill(video::SColor(0,0,0,0));
1416 core::dimension2d<u32> dim = frame_size;
1417 core::position2d<s32> pos_dst(0, 0);
1418 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1419 baseimg->copyToWithAlpha(img, pos_dst,
1420 core::rect<s32>(pos_src, dim),
1421 video::SColor(255,255,255,255),
1429 errorstream<<"generate_image(): Invalid "
1430 " modification: \""<<part_of_name<<"\""<<std::endl;
1437 void overlay(video::IImage *image, video::IImage *overlay)
1440 Copy overlay to image, taking alpha into account.
1441 Where image is transparent, don't copy from overlay.
1442 Images sizes must be identical.
1444 if(image == NULL || overlay == NULL)
1447 core::dimension2d<u32> dim = image->getDimension();
1448 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1449 assert(dim == dim_overlay);
1451 for(u32 y=0; y<dim.Height; y++)
1452 for(u32 x=0; x<dim.Width; x++)
1454 video::SColor c1 = image->getPixel(x,y);
1455 video::SColor c2 = overlay->getPixel(x,y);
1456 u32 a1 = c1.getAlpha();
1457 u32 a2 = c2.getAlpha();
1458 if(a1 == 255 && a2 != 0)
1460 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1461 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1462 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1464 image->setPixel(x,y,c1);
1469 Draw an image on top of an another one, using the alpha channel of the
1472 This exists because IImage::copyToWithAlpha() doesn't seem to always
1475 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1476 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1478 for(u32 y0=0; y0<size.Y; y0++)
1479 for(u32 x0=0; x0<size.X; x0++)
1481 s32 src_x = src_pos.X + x0;
1482 s32 src_y = src_pos.Y + y0;
1483 s32 dst_x = dst_pos.X + x0;
1484 s32 dst_y = dst_pos.Y + y0;
1485 video::SColor src_c = src->getPixel(src_x, src_y);
1486 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1487 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1488 dst->setPixel(dst_x, dst_y, dst_c);
1492 void brighten(video::IImage *image)
1497 core::dimension2d<u32> dim = image->getDimension();
1499 for(u32 y=0; y<dim.Height; y++)
1500 for(u32 x=0; x<dim.Width; x++)
1502 video::SColor c = image->getPixel(x,y);
1503 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1504 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1505 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1506 image->setPixel(x,y,c);
1510 u32 parseImageTransform(const std::string& s)
1512 int total_transform = 0;
1514 std::string transform_names[8];
1515 transform_names[0] = "i";
1516 transform_names[1] = "r90";
1517 transform_names[2] = "r180";
1518 transform_names[3] = "r270";
1519 transform_names[4] = "fx";
1520 transform_names[6] = "fy";
1522 std::size_t pos = 0;
1523 while(pos < s.size())
1526 for(int i = 0; i <= 7; ++i)
1528 const std::string &name_i = transform_names[i];
1530 if(s[pos] == ('0' + i))
1536 else if(!(name_i.empty()) &&
1537 lowercase(s.substr(pos, name_i.size())) == name_i)
1540 pos += name_i.size();
1547 // Multiply total_transform and transform in the group D4
1550 new_total = (transform + total_transform) % 4;
1552 new_total = (transform - total_transform + 8) % 4;
1553 if((transform >= 4) ^ (total_transform >= 4))
1556 total_transform = new_total;
1558 return total_transform;
1561 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1563 if(transform % 2 == 0)
1566 return core::dimension2d<u32>(dim.Height, dim.Width);
1569 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1571 if(src == NULL || dst == NULL)
1574 core::dimension2d<u32> srcdim = src->getDimension();
1575 core::dimension2d<u32> dstdim = dst->getDimension();
1577 assert(dstdim == imageTransformDimension(transform, srcdim));
1578 assert(transform >= 0 && transform <= 7);
1581 Compute the transformation from source coordinates (sx,sy)
1582 to destination coordinates (dx,dy).
1586 if(transform == 0) // identity
1587 sxn = 0, syn = 2; // sx = dx, sy = dy
1588 else if(transform == 1) // rotate by 90 degrees ccw
1589 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1590 else if(transform == 2) // rotate by 180 degrees
1591 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1592 else if(transform == 3) // rotate by 270 degrees ccw
1593 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1594 else if(transform == 4) // flip x
1595 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1596 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1597 sxn = 2, syn = 0; // sx = dy, sy = dx
1598 else if(transform == 6) // flip y
1599 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1600 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1601 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1603 for(u32 dy=0; dy<dstdim.Height; dy++)
1604 for(u32 dx=0; dx<dstdim.Width; dx++)
1606 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1607 u32 sx = entries[sxn];
1608 u32 sy = entries[syn];
1609 video::SColor c = src->getPixel(sx,sy);
1610 dst->setPixel(dx,dy,c);