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.
21 #include "irrlichttypes_extrabloated.h"
23 #include "main.h" // for g_settings
27 #include <ICameraSceneNode.h>
30 #include "util/string.h"
31 #include "util/container.h"
32 #include "util/thread.h"
33 #include "util/numeric.h"
36 A cache from texture name to texture path
38 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
41 Replaces the filename extension.
43 std::string image = "a/image.png"
44 replace_ext(image, "jpg")
45 -> image = "a/image.jpg"
46 Returns true on success.
48 static bool replace_ext(std::string &path, const char *ext)
52 // Find place of last dot, fail if \ or / found.
54 for(s32 i=path.size()-1; i>=0; i--)
62 if(path[i] == '\\' || path[i] == '/')
65 // If not found, return an empty string
68 // Else make the new path
69 path = path.substr(0, last_dot_i+1) + ext;
74 Find out the full path of an image by trying different filename
79 std::string getImagePath(std::string path)
81 // A NULL-ended list of possible image extensions
82 const char *extensions[] = {
83 "png", "jpg", "bmp", "tga",
84 "pcx", "ppm", "psd", "wal", "rgb",
87 // If there is no extension, add one
88 if(removeStringEnd(path, extensions) == "")
90 // Check paths until something is found to exist
91 const char **ext = extensions;
93 bool r = replace_ext(path, *ext);
96 if(fs::PathExists(path))
99 while((++ext) != NULL);
105 Gets the path to a texture by first checking if the texture exists
106 in texture_path and if not, using the data path.
108 Checks all supported extensions by replacing the original extension.
110 If not found, returns "".
112 Utilizes a thread-safe cache.
114 std::string getTexturePath(const std::string &filename)
116 std::string fullpath = "";
120 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
125 Check from texture_path
127 std::string texture_path = g_settings->get("texture_path");
128 if(texture_path != "")
130 std::string testpath = texture_path + DIR_DELIM + filename;
131 // Check all filename extensions. Returns "" if not found.
132 fullpath = getImagePath(testpath);
136 Check from default data directory
140 std::string base_path = porting::path_share + DIR_DELIM + "textures"
141 + DIR_DELIM + "base" + DIR_DELIM + "pack";
142 std::string testpath = base_path + DIR_DELIM + filename;
143 // Check all filename extensions. Returns "" if not found.
144 fullpath = getImagePath(testpath);
147 // Add to cache (also an empty result is cached)
148 g_texturename_to_path_cache.set(filename, fullpath);
154 void clearTextureNameCache()
156 g_texturename_to_path_cache.clear();
160 Stores internal information about a texture.
166 video::ITexture *texture;
167 video::IImage *img; // The source image
170 const std::string &name_,
171 video::ITexture *texture_=NULL,
172 video::IImage *img_=NULL
182 SourceImageCache: A cache used for storing source images.
185 class SourceImageCache
188 ~SourceImageCache() {
189 for(std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
190 iter != m_images.end(); iter++) {
191 iter->second->drop();
195 void insert(const std::string &name, video::IImage *img,
196 bool prefer_local, video::IVideoDriver *driver)
200 std::map<std::string, video::IImage*>::iterator n;
201 n = m_images.find(name);
202 if(n != m_images.end()){
207 video::IImage* toadd = img;
208 bool need_to_grab = true;
210 // Try to use local texture instead if asked to
212 std::string path = getTexturePath(name.c_str());
214 video::IImage *img2 = driver->createImageFromFile(path.c_str());
217 need_to_grab = false;
224 m_images[name] = toadd;
226 video::IImage* get(const std::string &name)
228 std::map<std::string, video::IImage*>::iterator n;
229 n = m_images.find(name);
230 if(n != m_images.end())
234 // Primarily fetches from cache, secondarily tries to read from filesystem
235 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
237 std::map<std::string, video::IImage*>::iterator n;
238 n = m_images.find(name);
239 if(n != m_images.end()){
240 n->second->grab(); // Grab for caller
243 video::IVideoDriver* driver = device->getVideoDriver();
244 std::string path = getTexturePath(name.c_str());
246 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
247 <<name<<"\""<<std::endl;
250 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
252 video::IImage *img = driver->createImageFromFile(path.c_str());
255 m_images[name] = img;
256 img->grab(); // Grab for caller
261 std::map<std::string, video::IImage*> m_images;
268 class TextureSource : public IWritableTextureSource
271 TextureSource(IrrlichtDevice *device);
272 virtual ~TextureSource();
276 Now, assume a texture with the id 1 exists, and has the name
277 "stone.png^mineral1".
278 Then a random thread calls getTextureId for a texture called
279 "stone.png^mineral1^crack0".
280 ...Now, WTF should happen? Well:
281 - getTextureId strips off stuff recursively from the end until
282 the remaining part is found, or nothing is left when
283 something is stripped out
285 But it is slow to search for textures by names and modify them
287 - ContentFeatures is made to contain ids for the basic plain
289 - Crack textures can be slow by themselves, but the framework
293 - Assume a texture with the id 1 exists, and has the name
294 "stone.png^mineral_coal.png".
295 - Now getNodeTile() stumbles upon a node which uses
296 texture id 1, and determines that MATERIAL_FLAG_CRACK
297 must be applied to the tile
298 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
299 has received the current crack level 0 from the client. It
300 finds out the name of the texture with getTextureName(1),
301 appends "^crack0" to it and gets a new texture id with
302 getTextureId("stone.png^mineral_coal.png^crack0").
307 Gets a texture id from cache or
308 - if main thread, from getTextureIdDirect
309 - if other thread, adds to request queue and waits for main thread
311 u32 getTextureId(const std::string &name);
317 "stone.png^mineral_coal.png"
318 "stone.png^mineral_coal.png^crack1"
320 - If texture specified by name is found from cache, return the
322 - Otherwise generate the texture, add to cache and return id.
323 Recursion is used to find out the largest found part of the
324 texture and continue based on it.
326 The id 0 points to a NULL texture. It is returned in case of error.
328 u32 getTextureIdDirect(const std::string &name);
330 // Finds out the name of a cached texture.
331 std::string getTextureName(u32 id);
334 If texture specified by the name pointed by the id doesn't
335 exist, create it, then return the cached texture.
337 Can be called from any thread. If called from some other thread
338 and not found in cache, the call is queued to the main thread
341 video::ITexture* getTexture(u32 id);
343 video::ITexture* getTexture(const std::string &name, u32 *id);
345 // Returns a pointer to the irrlicht device
346 virtual IrrlichtDevice* getDevice()
351 bool isKnownSourceImage(const std::string &name)
353 bool is_known = false;
354 bool cache_found = m_source_image_existence.get(name, &is_known);
357 // Not found in cache; find out if a local file exists
358 is_known = (getTexturePath(name) != "");
359 m_source_image_existence.set(name, is_known);
363 // Processes queued texture requests from other threads.
364 // Shall be called from the main thread.
367 // Insert an image into the cache without touching the filesystem.
368 // Shall be called from the main thread.
369 void insertSourceImage(const std::string &name, video::IImage *img);
371 // Rebuild images and textures from the current set of source images
372 // Shall be called from the main thread.
373 void rebuildImagesAndTextures();
375 // Render a mesh to a texture.
376 // Returns NULL if render-to-texture failed.
377 // Shall be called from the main thread.
378 video::ITexture* generateTextureFromMesh(
379 const TextureFromMeshParams ¶ms);
381 // Generates an image from a full string like
382 // "stone.png^mineral_coal.png^[crack0".
383 // Shall be called from the main thread.
384 video::IImage* generateImageFromScratch(std::string name);
386 // Generate image based on a string like "stone.png" or "[crack0".
387 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
388 // Shall be called from the main thread.
389 bool generateImage(std::string part_of_name, video::IImage *& baseimg);
393 // The id of the thread that is allowed to use irrlicht directly
394 threadid_t m_main_thread;
395 // The irrlicht device
396 IrrlichtDevice *m_device;
398 // Cache of source images
399 // This should be only accessed from the main thread
400 SourceImageCache m_sourcecache;
402 // Thread-safe cache of what source images are known (true = known)
403 MutexedMap<std::string, bool> m_source_image_existence;
405 // A texture id is index in this array.
406 // The first position contains a NULL texture.
407 std::vector<TextureInfo> m_textureinfo_cache;
408 // Maps a texture name to an index in the former.
409 std::map<std::string, u32> m_name_to_id;
410 // The two former containers are behind this mutex
411 JMutex m_textureinfo_cache_mutex;
413 // Queued texture fetches (to be processed by the main thread)
414 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
416 // Textures that have been overwritten with other ones
417 // but can't be deleted because the ITexture* might still be used
418 std::list<video::ITexture*> m_texture_trash;
420 // Cached settings needed for making textures from meshes
421 bool m_setting_trilinear_filter;
422 bool m_setting_bilinear_filter;
423 bool m_setting_anisotropic_filter;
426 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
428 return new TextureSource(device);
431 TextureSource::TextureSource(IrrlichtDevice *device):
436 m_textureinfo_cache_mutex.Init();
438 m_main_thread = get_current_thread_id();
440 // Add a NULL TextureInfo as the first index, named ""
441 m_textureinfo_cache.push_back(TextureInfo(""));
442 m_name_to_id[""] = 0;
444 // Cache some settings
445 // Note: Since this is only done once, the game must be restarted
446 // for these settings to take effect
447 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
448 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
449 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
452 TextureSource::~TextureSource()
454 video::IVideoDriver* driver = m_device->getVideoDriver();
456 unsigned int textures_before = driver->getTextureCount();
458 for (std::vector<TextureInfo>::iterator iter =
459 m_textureinfo_cache.begin();
460 iter != m_textureinfo_cache.end(); iter++)
464 driver->removeTexture(iter->texture);
466 //cleanup source image
470 m_textureinfo_cache.clear();
472 for (std::list<video::ITexture*>::iterator iter =
473 m_texture_trash.begin(); iter != m_texture_trash.end();
476 video::ITexture *t = *iter;
478 //cleanup trashed texture
479 driver->removeTexture(t);
482 infostream << "~TextureSource() "<< textures_before << "/"
483 << driver->getTextureCount() << std::endl;
486 u32 TextureSource::getTextureId(const std::string &name)
488 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
492 See if texture already exists
494 JMutexAutoLock lock(m_textureinfo_cache_mutex);
495 std::map<std::string, u32>::iterator n;
496 n = m_name_to_id.find(name);
497 if(n != m_name_to_id.end())
506 if(get_current_thread_id() == m_main_thread)
508 return getTextureIdDirect(name);
512 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
514 // We're gonna ask the result to be put into here
515 ResultQueue<std::string, u32, u8, u8> result_queue;
517 // Throw a request in
518 m_get_texture_queue.add(name, 0, 0, &result_queue);
520 infostream<<"Waiting for texture from main thread, name=\""
521 <<name<<"\""<<std::endl;
525 // Wait result for a second
526 GetResult<std::string, u32, u8, u8>
527 result = result_queue.pop_front(1000);
529 // Check that at least something worked OK
530 assert(result.key == name);
534 catch(ItemNotFoundException &e)
536 infostream<<"Waiting for texture timed out."<<std::endl;
541 infostream<<"getTextureId(): Failed"<<std::endl;
546 // Overlay image on top of another image (used for cracks)
547 void overlay(video::IImage *image, video::IImage *overlay);
549 // Draw an image on top of an another one, using the alpha channel of the
551 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
552 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
555 void brighten(video::IImage *image);
556 // Parse a transform name
557 u32 parseImageTransform(const std::string& s);
558 // Apply transform to image dimension
559 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
560 // Apply transform to image data
561 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
564 This method generates all the textures
566 u32 TextureSource::getTextureIdDirect(const std::string &name)
568 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
570 // Empty name means texture 0
573 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
578 Calling only allowed from main thread
580 if(get_current_thread_id() != m_main_thread)
582 errorstream<<"TextureSource::getTextureIdDirect() "
583 "called not from main thread"<<std::endl;
588 See if texture already exists
591 JMutexAutoLock lock(m_textureinfo_cache_mutex);
593 std::map<std::string, u32>::iterator n;
594 n = m_name_to_id.find(name);
595 if(n != m_name_to_id.end())
597 /*infostream<<"getTextureIdDirect(): \""<<name
598 <<"\" found in cache"<<std::endl;*/
603 /*infostream<<"getTextureIdDirect(): \""<<name
604 <<"\" NOT found in cache. Creating it."<<std::endl;*/
610 char separator = '^';
613 This is set to the id of the base image.
614 If left 0, there is no base image and a completely new image
617 u32 base_image_id = 0;
619 // Find last meta separator in name
620 s32 last_separator_position = -1;
621 for(s32 i=name.size()-1; i>=0; i--)
623 if(name[i] == separator)
625 last_separator_position = i;
630 If separator was found, construct the base name and make the
631 base image using a recursive call
633 std::string base_image_name;
634 if(last_separator_position != -1)
636 // Construct base name
637 base_image_name = name.substr(0, last_separator_position);
638 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
639 " to get base image of \""<<name<<"\" = \""
640 <<base_image_name<<"\""<<std::endl;*/
641 base_image_id = getTextureIdDirect(base_image_name);
644 //infostream<<"base_image_id="<<base_image_id<<std::endl;
646 video::IVideoDriver* driver = m_device->getVideoDriver();
649 video::ITexture *t = NULL;
652 An image will be built from files and then converted into a texture.
654 video::IImage *baseimg = NULL;
656 // If a base image was found, copy it to baseimg
657 if(base_image_id != 0)
659 JMutexAutoLock lock(m_textureinfo_cache_mutex);
661 TextureInfo *ti = &m_textureinfo_cache[base_image_id];
665 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
666 <<"cache: \""<<base_image_name<<"\""
671 core::dimension2d<u32> dim = ti->img->getDimension();
673 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
677 v2s32(0,0), // position in target
678 core::rect<s32>(v2s32(0,0), dim) // from
681 /*infostream<<"getTextureIdDirect(): Loaded \""
682 <<base_image_name<<"\" from image cache"
688 Parse out the last part of the name of the image and act
692 std::string last_part_of_name = name.substr(last_separator_position+1);
693 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
695 // Generate image according to part of name
696 if(!generateImage(last_part_of_name, baseimg))
698 errorstream<<"getTextureIdDirect(): "
699 "failed to generate \""<<last_part_of_name<<"\""
703 // If no resulting image, print a warning
706 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
707 " create texture \""<<name<<"\""<<std::endl;
712 // Create texture from resulting image
713 t = driver->addTexture(name.c_str(), baseimg);
717 Add texture to caches (add NULL textures too)
720 JMutexAutoLock lock(m_textureinfo_cache_mutex);
722 u32 id = m_textureinfo_cache.size();
723 TextureInfo ti(name, t, baseimg);
724 m_textureinfo_cache.push_back(ti);
725 m_name_to_id[name] = id;
727 /*infostream<<"getTextureIdDirect(): "
728 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
733 std::string TextureSource::getTextureName(u32 id)
735 JMutexAutoLock lock(m_textureinfo_cache_mutex);
737 if(id >= m_textureinfo_cache.size())
739 errorstream<<"TextureSource::getTextureName(): id="<<id
740 <<" >= m_textureinfo_cache.size()="
741 <<m_textureinfo_cache.size()<<std::endl;
745 return m_textureinfo_cache[id].name;
748 video::ITexture* TextureSource::getTexture(u32 id)
750 JMutexAutoLock lock(m_textureinfo_cache_mutex);
752 if(id >= m_textureinfo_cache.size())
755 return m_textureinfo_cache[id].texture;
758 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
760 u32 actual_id = getTextureId(name);
764 return getTexture(actual_id);
767 void TextureSource::processQueue()
772 if(!m_get_texture_queue.empty())
774 GetRequest<std::string, u32, u8, u8>
775 request = m_get_texture_queue.pop();
777 /*infostream<<"TextureSource::processQueue(): "
778 <<"got texture request with "
779 <<"name=\""<<request.key<<"\""
782 GetResult<std::string, u32, u8, u8>
784 result.key = request.key;
785 result.callers = request.callers;
786 result.item = getTextureIdDirect(request.key);
788 request.dest->push_back(result);
792 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
794 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
796 assert(get_current_thread_id() == m_main_thread);
798 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
799 m_source_image_existence.set(name, true);
802 void TextureSource::rebuildImagesAndTextures()
804 JMutexAutoLock lock(m_textureinfo_cache_mutex);
806 video::IVideoDriver* driver = m_device->getVideoDriver();
809 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
810 TextureInfo *ti = &m_textureinfo_cache[i];
811 video::IImage *img = generateImageFromScratch(ti->name);
812 // Create texture from resulting image
813 video::ITexture *t = NULL;
815 t = driver->addTexture(ti->name.c_str(), img);
816 video::ITexture *t_old = ti->texture;
822 m_texture_trash.push_back(t_old);
826 video::ITexture* TextureSource::generateTextureFromMesh(
827 const TextureFromMeshParams ¶ms)
829 video::IVideoDriver *driver = m_device->getVideoDriver();
832 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
834 static bool warned = false;
837 errorstream<<"TextureSource::generateTextureFromMesh(): "
838 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
844 // Create render target texture
845 video::ITexture *rtt = driver->addRenderTargetTexture(
846 params.dim, params.rtt_texture_name.c_str(),
847 video::ECF_A8R8G8B8);
850 errorstream<<"TextureSource::generateTextureFromMesh(): "
851 <<"addRenderTargetTexture returned NULL."<<std::endl;
856 driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0));
858 // Get a scene manager
859 scene::ISceneManager *smgr_main = m_device->getSceneManager();
861 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
864 scene::IMeshSceneNode* meshnode = smgr->addMeshSceneNode(params.mesh, NULL, -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
865 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
866 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
867 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
868 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
869 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
871 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
872 params.camera_position, params.camera_lookat);
873 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
874 camera->setProjectionMatrix(params.camera_projection_matrix, false);
876 smgr->setAmbientLight(params.ambient_light);
877 smgr->addLightSceneNode(0,
878 params.light_position,
880 params.light_radius);
883 driver->beginScene(true, true, video::SColor(0,0,0,0));
887 // NOTE: The scene nodes should not be dropped, otherwise
888 // smgr->drop() segfaults
892 // Drop scene manager
895 // Unset render target
896 driver->setRenderTarget(0, false, true, 0);
898 if(params.delete_texture_on_shutdown)
899 m_texture_trash.push_back(rtt);
904 video::IImage* TextureSource::generateImageFromScratch(std::string name)
906 /*infostream<<"generateImageFromScratch(): "
907 "\""<<name<<"\""<<std::endl;*/
909 video::IVideoDriver *driver = m_device->getVideoDriver();
916 video::IImage *baseimg = NULL;
918 char separator = '^';
920 // Find last meta separator in name
921 s32 last_separator_position = name.find_last_of(separator);
924 If separator was found, construct the base name and make the
925 base image using a recursive call
927 std::string base_image_name;
928 if(last_separator_position != -1)
930 // Construct base name
931 base_image_name = name.substr(0, last_separator_position);
932 baseimg = generateImageFromScratch(base_image_name);
936 Parse out the last part of the name of the image and act
940 std::string last_part_of_name = name.substr(last_separator_position+1);
942 // Generate image according to part of name
943 if(!generateImage(last_part_of_name, baseimg))
945 errorstream<<"generateImageFromScratch(): "
946 "failed to generate \""<<last_part_of_name<<"\""
954 bool TextureSource::generateImage(std::string part_of_name, video::IImage *& baseimg)
956 video::IVideoDriver* driver = m_device->getVideoDriver();
959 // Stuff starting with [ are special commands
960 if(part_of_name.size() == 0 || part_of_name[0] != '[')
962 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
966 if(part_of_name != ""){
967 errorstream<<"generateImage(): Could not load image \""
968 <<part_of_name<<"\""<<" while building texture"<<std::endl;
969 errorstream<<"generateImage(): Creating a dummy"
970 <<" image for \""<<part_of_name<<"\""<<std::endl;
973 // Just create a dummy image
974 //core::dimension2d<u32> dim(2,2);
975 core::dimension2d<u32> dim(1,1);
976 image = driver->createImage(video::ECF_A8R8G8B8, dim);
978 /*image->setPixel(0,0, video::SColor(255,255,0,0));
979 image->setPixel(1,0, video::SColor(255,0,255,0));
980 image->setPixel(0,1, video::SColor(255,0,0,255));
981 image->setPixel(1,1, video::SColor(255,255,0,255));*/
982 image->setPixel(0,0, video::SColor(255,myrand()%256,
983 myrand()%256,myrand()%256));
984 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
985 myrand()%256,myrand()%256));
986 image->setPixel(0,1, video::SColor(255,myrand()%256,
987 myrand()%256,myrand()%256));
988 image->setPixel(1,1, video::SColor(255,myrand()%256,
989 myrand()%256,myrand()%256));*/
992 // If base image is NULL, load as base.
995 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
997 Copy it this way to get an alpha channel.
998 Otherwise images with alpha cannot be blitted on
999 images that don't have alpha in the original file.
1001 core::dimension2d<u32> dim = image->getDimension();
1002 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1003 image->copyTo(baseimg);
1005 // Else blit on base.
1008 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1009 // Size of the copied area
1010 core::dimension2d<u32> dim = image->getDimension();
1011 //core::dimension2d<u32> dim(16,16);
1012 // Position to copy the blitted to in the base image
1013 core::position2d<s32> pos_to(0,0);
1014 // Position to copy the blitted from in the blitted image
1015 core::position2d<s32> pos_from(0,0);
1017 /*image->copyToWithAlpha(baseimg, pos_to,
1018 core::rect<s32>(pos_from, dim),
1019 video::SColor(255,255,255,255),
1021 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1028 // A special texture modification
1030 /*infostream<<"generateImage(): generating special "
1031 <<"modification \""<<part_of_name<<"\""
1036 Adds a cracking texture
1038 if(part_of_name.substr(0,6) == "[crack")
1042 errorstream<<"generateImage(): baseimg==NULL "
1043 <<"for part_of_name=\""<<part_of_name
1044 <<"\", cancelling."<<std::endl;
1048 // Crack image number and overlay option
1049 s32 progression = 0;
1050 bool use_overlay = false;
1051 if(part_of_name.substr(6,1) == "o")
1053 progression = stoi(part_of_name.substr(7));
1058 progression = stoi(part_of_name.substr(6));
1059 use_overlay = false;
1062 // Size of the base image
1063 core::dimension2d<u32> dim_base = baseimg->getDimension();
1068 It is an image with a number of cracking stages
1071 video::IImage *img_crack = m_sourcecache.getOrLoad(
1072 "crack_anylength.png", m_device);
1074 if(img_crack && progression >= 0)
1076 // Dimension of original image
1077 core::dimension2d<u32> dim_crack
1078 = img_crack->getDimension();
1079 // Count of crack stages
1080 s32 crack_count = dim_crack.Height / dim_crack.Width;
1081 // Limit progression
1082 if(progression > crack_count-1)
1083 progression = crack_count-1;
1084 // Dimension of a single crack stage
1085 core::dimension2d<u32> dim_crack_cropped(
1089 // Create cropped and scaled crack images
1090 video::IImage *img_crack_cropped = driver->createImage(
1091 video::ECF_A8R8G8B8, dim_crack_cropped);
1092 video::IImage *img_crack_scaled = driver->createImage(
1093 video::ECF_A8R8G8B8, dim_base);
1095 if(img_crack_cropped && img_crack_scaled)
1098 v2s32 pos_crack(0, progression*dim_crack.Width);
1099 img_crack->copyTo(img_crack_cropped,
1101 core::rect<s32>(pos_crack, dim_crack_cropped));
1102 // Scale crack image by copying
1103 img_crack_cropped->copyToScaling(img_crack_scaled);
1104 // Copy or overlay crack image
1107 overlay(baseimg, img_crack_scaled);
1111 /*img_crack_scaled->copyToWithAlpha(
1114 core::rect<s32>(v2s32(0,0), dim_base),
1115 video::SColor(255,255,255,255));*/
1116 blit_with_alpha(img_crack_scaled, baseimg,
1117 v2s32(0,0), v2s32(0,0), dim_base);
1121 if(img_crack_scaled)
1122 img_crack_scaled->drop();
1124 if(img_crack_cropped)
1125 img_crack_cropped->drop();
1131 [combine:WxH:X,Y=filename:X,Y=filename2
1132 Creates a bigger texture from an amount of smaller ones
1134 else if(part_of_name.substr(0,8) == "[combine")
1136 Strfnd sf(part_of_name);
1138 u32 w0 = stoi(sf.next("x"));
1139 u32 h0 = stoi(sf.next(":"));
1140 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1141 core::dimension2d<u32> dim(w0,h0);
1144 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1145 baseimg->fill(video::SColor(0,0,0,0));
1147 while(sf.atend() == false)
1149 u32 x = stoi(sf.next(","));
1150 u32 y = stoi(sf.next("="));
1151 std::string filename = sf.next(":");
1152 infostream<<"Adding \""<<filename
1153 <<"\" to combined ("<<x<<","<<y<<")"
1155 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1158 core::dimension2d<u32> dim = img->getDimension();
1159 infostream<<"Size "<<dim.Width
1160 <<"x"<<dim.Height<<std::endl;
1161 core::position2d<s32> pos_base(x, y);
1162 video::IImage *img2 =
1163 driver->createImage(video::ECF_A8R8G8B8, dim);
1166 /*img2->copyToWithAlpha(baseimg, pos_base,
1167 core::rect<s32>(v2s32(0,0), dim),
1168 video::SColor(255,255,255,255),
1170 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1175 infostream<<"img==NULL"<<std::endl;
1182 else if(part_of_name.substr(0,9) == "[brighten")
1186 errorstream<<"generateImage(): baseimg==NULL "
1187 <<"for part_of_name=\""<<part_of_name
1188 <<"\", cancelling."<<std::endl;
1196 Make image completely opaque.
1197 Used for the leaves texture when in old leaves mode, so
1198 that the transparent parts don't look completely black
1199 when simple alpha channel is used for rendering.
1201 else if(part_of_name.substr(0,8) == "[noalpha")
1205 errorstream<<"generateImage(): baseimg==NULL "
1206 <<"for part_of_name=\""<<part_of_name
1207 <<"\", cancelling."<<std::endl;
1211 core::dimension2d<u32> dim = baseimg->getDimension();
1213 // Set alpha to full
1214 for(u32 y=0; y<dim.Height; y++)
1215 for(u32 x=0; x<dim.Width; x++)
1217 video::SColor c = baseimg->getPixel(x,y);
1219 baseimg->setPixel(x,y,c);
1224 Convert one color to transparent.
1226 else if(part_of_name.substr(0,11) == "[makealpha:")
1230 errorstream<<"generateImage(): baseimg==NULL "
1231 <<"for part_of_name=\""<<part_of_name
1232 <<"\", cancelling."<<std::endl;
1236 Strfnd sf(part_of_name.substr(11));
1237 u32 r1 = stoi(sf.next(","));
1238 u32 g1 = stoi(sf.next(","));
1239 u32 b1 = stoi(sf.next(""));
1240 std::string filename = sf.next("");
1242 core::dimension2d<u32> dim = baseimg->getDimension();
1244 /*video::IImage *oldbaseimg = baseimg;
1245 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1246 oldbaseimg->copyTo(baseimg);
1247 oldbaseimg->drop();*/
1249 // Set alpha to full
1250 for(u32 y=0; y<dim.Height; y++)
1251 for(u32 x=0; x<dim.Width; x++)
1253 video::SColor c = baseimg->getPixel(x,y);
1255 u32 g = c.getGreen();
1256 u32 b = c.getBlue();
1257 if(!(r == r1 && g == g1 && b == b1))
1260 baseimg->setPixel(x,y,c);
1265 Rotates and/or flips the image.
1267 N can be a number (between 0 and 7) or a transform name.
1268 Rotations are counter-clockwise.
1270 1 R90 rotate by 90 degrees
1271 2 R180 rotate by 180 degrees
1272 3 R270 rotate by 270 degrees
1274 5 FXR90 flip X then rotate by 90 degrees
1276 7 FYR90 flip Y then rotate by 90 degrees
1278 Note: Transform names can be concatenated to produce
1279 their product (applies the first then the second).
1280 The resulting transform will be equivalent to one of the
1281 eight existing ones, though (see: dihedral group).
1283 else if(part_of_name.substr(0,10) == "[transform")
1287 errorstream<<"generateImage(): baseimg==NULL "
1288 <<"for part_of_name=\""<<part_of_name
1289 <<"\", cancelling."<<std::endl;
1293 u32 transform = parseImageTransform(part_of_name.substr(10));
1294 core::dimension2d<u32> dim = imageTransformDimension(
1295 transform, baseimg->getDimension());
1296 video::IImage *image = driver->createImage(
1297 baseimg->getColorFormat(), dim);
1299 imageTransform(transform, baseimg, image);
1304 [inventorycube{topimage{leftimage{rightimage
1305 In every subimage, replace ^ with &.
1306 Create an "inventory cube".
1307 NOTE: This should be used only on its own.
1308 Example (a grass block (not actually used in game):
1309 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1311 else if(part_of_name.substr(0,14) == "[inventorycube")
1315 errorstream<<"generateImage(): baseimg!=NULL "
1316 <<"for part_of_name=\""<<part_of_name
1317 <<"\", cancelling."<<std::endl;
1321 str_replace_char(part_of_name, '&', '^');
1322 Strfnd sf(part_of_name);
1324 std::string imagename_top = sf.next("{");
1325 std::string imagename_left = sf.next("{");
1326 std::string imagename_right = sf.next("{");
1328 // Generate images for the faces of the cube
1329 video::IImage *img_top =
1330 generateImageFromScratch(imagename_top);
1331 video::IImage *img_left =
1332 generateImageFromScratch(imagename_left);
1333 video::IImage *img_right =
1334 generateImageFromScratch(imagename_right);
1335 assert(img_top && img_left && img_right);
1337 // Create textures from images
1338 video::ITexture *texture_top = driver->addTexture(
1339 (imagename_top + "__temp__").c_str(), img_top);
1340 video::ITexture *texture_left = driver->addTexture(
1341 (imagename_left + "__temp__").c_str(), img_left);
1342 video::ITexture *texture_right = driver->addTexture(
1343 (imagename_right + "__temp__").c_str(), img_right);
1344 assert(texture_top && texture_left && texture_right);
1352 Draw a cube mesh into a render target texture
1354 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1355 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1356 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1357 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1358 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1359 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1360 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1361 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1363 TextureFromMeshParams params;
1365 params.dim.set(64, 64);
1366 params.rtt_texture_name = part_of_name + "_RTT";
1367 // We will delete the rtt texture ourselves
1368 params.delete_texture_on_shutdown = false;
1369 params.camera_position.set(0, 1.0, -1.5);
1370 params.camera_position.rotateXZBy(45);
1371 params.camera_lookat.set(0, 0, 0);
1372 // Set orthogonal projection
1373 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1374 1.65, 1.65, 0, 100);
1376 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1377 params.light_position.set(10, 100, -50);
1378 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1379 params.light_radius = 1000;
1381 video::ITexture *rtt = generateTextureFromMesh(params);
1386 // Free textures of images
1387 driver->removeTexture(texture_top);
1388 driver->removeTexture(texture_left);
1389 driver->removeTexture(texture_right);
1393 baseimg = generateImageFromScratch(imagename_top);
1397 // Create image of render target
1398 video::IImage *image = driver->createImage(rtt, v2s32(0,0), params.dim);
1402 driver->removeTexture(rtt);
1404 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1408 image->copyTo(baseimg);
1413 [lowpart:percent:filename
1414 Adds the lower part of a texture
1416 else if(part_of_name.substr(0,9) == "[lowpart:")
1418 Strfnd sf(part_of_name);
1420 u32 percent = stoi(sf.next(":"));
1421 std::string filename = sf.next(":");
1422 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1425 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1426 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1429 core::dimension2d<u32> dim = img->getDimension();
1430 core::position2d<s32> pos_base(0, 0);
1431 video::IImage *img2 =
1432 driver->createImage(video::ECF_A8R8G8B8, dim);
1435 core::position2d<s32> clippos(0, 0);
1436 clippos.Y = dim.Height * (100-percent) / 100;
1437 core::dimension2d<u32> clipdim = dim;
1438 clipdim.Height = clipdim.Height * percent / 100 + 1;
1439 core::rect<s32> cliprect(clippos, clipdim);
1440 img2->copyToWithAlpha(baseimg, pos_base,
1441 core::rect<s32>(v2s32(0,0), dim),
1442 video::SColor(255,255,255,255),
1449 Crops a frame of a vertical animation.
1450 N = frame count, I = frame index
1452 else if(part_of_name.substr(0,15) == "[verticalframe:")
1454 Strfnd sf(part_of_name);
1456 u32 frame_count = stoi(sf.next(":"));
1457 u32 frame_index = stoi(sf.next(":"));
1459 if(baseimg == NULL){
1460 errorstream<<"generateImage(): baseimg!=NULL "
1461 <<"for part_of_name=\""<<part_of_name
1462 <<"\", cancelling."<<std::endl;
1466 v2u32 frame_size = baseimg->getDimension();
1467 frame_size.Y /= frame_count;
1469 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1472 errorstream<<"generateImage(): Could not create image "
1473 <<"for part_of_name=\""<<part_of_name
1474 <<"\", cancelling."<<std::endl;
1478 // Fill target image with transparency
1479 img->fill(video::SColor(0,0,0,0));
1481 core::dimension2d<u32> dim = frame_size;
1482 core::position2d<s32> pos_dst(0, 0);
1483 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1484 baseimg->copyToWithAlpha(img, pos_dst,
1485 core::rect<s32>(pos_src, dim),
1486 video::SColor(255,255,255,255),
1494 errorstream<<"generateImage(): Invalid "
1495 " modification: \""<<part_of_name<<"\""<<std::endl;
1502 void overlay(video::IImage *image, video::IImage *overlay)
1505 Copy overlay to image, taking alpha into account.
1506 Where image is transparent, don't copy from overlay.
1507 Images sizes must be identical.
1509 if(image == NULL || overlay == NULL)
1512 core::dimension2d<u32> dim = image->getDimension();
1513 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1514 assert(dim == dim_overlay);
1516 for(u32 y=0; y<dim.Height; y++)
1517 for(u32 x=0; x<dim.Width; x++)
1519 video::SColor c1 = image->getPixel(x,y);
1520 video::SColor c2 = overlay->getPixel(x,y);
1521 u32 a1 = c1.getAlpha();
1522 u32 a2 = c2.getAlpha();
1523 if(a1 == 255 && a2 != 0)
1525 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1526 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1527 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1529 image->setPixel(x,y,c1);
1534 Draw an image on top of an another one, using the alpha channel of the
1537 This exists because IImage::copyToWithAlpha() doesn't seem to always
1540 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1541 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1543 for(u32 y0=0; y0<size.Y; y0++)
1544 for(u32 x0=0; x0<size.X; x0++)
1546 s32 src_x = src_pos.X + x0;
1547 s32 src_y = src_pos.Y + y0;
1548 s32 dst_x = dst_pos.X + x0;
1549 s32 dst_y = dst_pos.Y + y0;
1550 video::SColor src_c = src->getPixel(src_x, src_y);
1551 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1552 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1553 dst->setPixel(dst_x, dst_y, dst_c);
1557 void brighten(video::IImage *image)
1562 core::dimension2d<u32> dim = image->getDimension();
1564 for(u32 y=0; y<dim.Height; y++)
1565 for(u32 x=0; x<dim.Width; x++)
1567 video::SColor c = image->getPixel(x,y);
1568 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1569 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1570 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1571 image->setPixel(x,y,c);
1575 u32 parseImageTransform(const std::string& s)
1577 int total_transform = 0;
1579 std::string transform_names[8];
1580 transform_names[0] = "i";
1581 transform_names[1] = "r90";
1582 transform_names[2] = "r180";
1583 transform_names[3] = "r270";
1584 transform_names[4] = "fx";
1585 transform_names[6] = "fy";
1587 std::size_t pos = 0;
1588 while(pos < s.size())
1591 for(int i = 0; i <= 7; ++i)
1593 const std::string &name_i = transform_names[i];
1595 if(s[pos] == ('0' + i))
1601 else if(!(name_i.empty()) &&
1602 lowercase(s.substr(pos, name_i.size())) == name_i)
1605 pos += name_i.size();
1612 // Multiply total_transform and transform in the group D4
1615 new_total = (transform + total_transform) % 4;
1617 new_total = (transform - total_transform + 8) % 4;
1618 if((transform >= 4) ^ (total_transform >= 4))
1621 total_transform = new_total;
1623 return total_transform;
1626 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1628 if(transform % 2 == 0)
1631 return core::dimension2d<u32>(dim.Height, dim.Width);
1634 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1636 if(src == NULL || dst == NULL)
1639 core::dimension2d<u32> srcdim = src->getDimension();
1640 core::dimension2d<u32> dstdim = dst->getDimension();
1642 assert(dstdim == imageTransformDimension(transform, srcdim));
1643 assert(transform >= 0 && transform <= 7);
1646 Compute the transformation from source coordinates (sx,sy)
1647 to destination coordinates (dx,dy).
1651 if(transform == 0) // identity
1652 sxn = 0, syn = 2; // sx = dx, sy = dy
1653 else if(transform == 1) // rotate by 90 degrees ccw
1654 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1655 else if(transform == 2) // rotate by 180 degrees
1656 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1657 else if(transform == 3) // rotate by 270 degrees ccw
1658 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1659 else if(transform == 4) // flip x
1660 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1661 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1662 sxn = 2, syn = 0; // sx = dy, sy = dx
1663 else if(transform == 6) // flip y
1664 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1665 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1666 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1668 for(u32 dy=0; dy<dstdim.Height; dy++)
1669 for(u32 dx=0; dx<dstdim.Width; dx++)
1671 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1672 u32 sx = entries[sxn];
1673 u32 sy = entries[syn];
1674 video::SColor c = src->getPixel(sx,sy);
1675 dst->setPixel(dx,dy,c);