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^[crack:1:0".
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 "[crack:1:0".
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 static 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;*/
526 // Wait result for a second
527 GetResult<std::string, u32, u8, u8>
528 result = result_queue.pop_front(1000);
530 if (result.key == name) {
535 catch(ItemNotFoundException &e)
537 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
542 infostream<<"getTextureId(): Failed"<<std::endl;
547 // Draw an image on top of an another one, using the alpha channel of the
549 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
550 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
552 // Like blit_with_alpha, but only modifies destination pixels that
554 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
555 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
557 // Draw or overlay a crack
558 static void draw_crack(video::IImage *crack, video::IImage *dst,
559 bool use_overlay, s32 frame_count, s32 progression,
560 video::IVideoDriver *driver);
563 void brighten(video::IImage *image);
564 // Parse a transform name
565 u32 parseImageTransform(const std::string& s);
566 // Apply transform to image dimension
567 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
568 // Apply transform to image data
569 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
572 This method generates all the textures
574 u32 TextureSource::getTextureIdDirect(const std::string &name)
576 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
578 // Empty name means texture 0
581 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
586 Calling only allowed from main thread
588 if(get_current_thread_id() != m_main_thread)
590 errorstream<<"TextureSource::getTextureIdDirect() "
591 "called not from main thread"<<std::endl;
596 See if texture already exists
599 JMutexAutoLock lock(m_textureinfo_cache_mutex);
601 std::map<std::string, u32>::iterator n;
602 n = m_name_to_id.find(name);
603 if(n != m_name_to_id.end())
605 /*infostream<<"getTextureIdDirect(): \""<<name
606 <<"\" found in cache"<<std::endl;*/
611 /*infostream<<"getTextureIdDirect(): \""<<name
612 <<"\" NOT found in cache. Creating it."<<std::endl;*/
618 char separator = '^';
621 This is set to the id of the base image.
622 If left 0, there is no base image and a completely new image
625 u32 base_image_id = 0;
627 // Find last meta separator in name
628 s32 last_separator_position = -1;
629 for(s32 i=name.size()-1; i>=0; i--)
631 if(name[i] == separator)
633 last_separator_position = i;
638 If separator was found, construct the base name and make the
639 base image using a recursive call
641 std::string base_image_name;
642 if(last_separator_position != -1)
644 // Construct base name
645 base_image_name = name.substr(0, last_separator_position);
646 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
647 " to get base image of \""<<name<<"\" = \""
648 <<base_image_name<<"\""<<std::endl;*/
649 base_image_id = getTextureIdDirect(base_image_name);
652 //infostream<<"base_image_id="<<base_image_id<<std::endl;
654 video::IVideoDriver* driver = m_device->getVideoDriver();
657 video::ITexture *t = NULL;
660 An image will be built from files and then converted into a texture.
662 video::IImage *baseimg = NULL;
664 // If a base image was found, copy it to baseimg
665 if(base_image_id != 0)
667 JMutexAutoLock lock(m_textureinfo_cache_mutex);
669 TextureInfo *ti = &m_textureinfo_cache[base_image_id];
673 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
674 <<"cache: \""<<base_image_name<<"\""
679 core::dimension2d<u32> dim = ti->img->getDimension();
681 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
685 v2s32(0,0), // position in target
686 core::rect<s32>(v2s32(0,0), dim) // from
689 /*infostream<<"getTextureIdDirect(): Loaded \""
690 <<base_image_name<<"\" from image cache"
696 Parse out the last part of the name of the image and act
700 std::string last_part_of_name = name.substr(last_separator_position+1);
701 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
703 // Generate image according to part of name
704 if(!generateImage(last_part_of_name, baseimg))
706 errorstream<<"getTextureIdDirect(): "
707 "failed to generate \""<<last_part_of_name<<"\""
711 // If no resulting image, print a warning
714 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
715 " create texture \""<<name<<"\""<<std::endl;
720 // Create texture from resulting image
721 t = driver->addTexture(name.c_str(), baseimg);
725 Add texture to caches (add NULL textures too)
728 JMutexAutoLock lock(m_textureinfo_cache_mutex);
730 u32 id = m_textureinfo_cache.size();
731 TextureInfo ti(name, t, baseimg);
732 m_textureinfo_cache.push_back(ti);
733 m_name_to_id[name] = id;
735 /*infostream<<"getTextureIdDirect(): "
736 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
741 std::string TextureSource::getTextureName(u32 id)
743 JMutexAutoLock lock(m_textureinfo_cache_mutex);
745 if(id >= m_textureinfo_cache.size())
747 errorstream<<"TextureSource::getTextureName(): id="<<id
748 <<" >= m_textureinfo_cache.size()="
749 <<m_textureinfo_cache.size()<<std::endl;
753 return m_textureinfo_cache[id].name;
756 video::ITexture* TextureSource::getTexture(u32 id)
758 JMutexAutoLock lock(m_textureinfo_cache_mutex);
760 if(id >= m_textureinfo_cache.size())
763 return m_textureinfo_cache[id].texture;
766 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
768 u32 actual_id = getTextureId(name);
772 return getTexture(actual_id);
775 void TextureSource::processQueue()
780 if(!m_get_texture_queue.empty())
782 GetRequest<std::string, u32, u8, u8>
783 request = m_get_texture_queue.pop();
785 /*infostream<<"TextureSource::processQueue(): "
786 <<"got texture request with "
787 <<"name=\""<<request.key<<"\""
790 m_get_texture_queue.pushResult(request,getTextureIdDirect(request.key));
794 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
796 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
798 assert(get_current_thread_id() == m_main_thread);
800 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
801 m_source_image_existence.set(name, true);
804 void TextureSource::rebuildImagesAndTextures()
806 JMutexAutoLock lock(m_textureinfo_cache_mutex);
808 video::IVideoDriver* driver = m_device->getVideoDriver();
811 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
812 TextureInfo *ti = &m_textureinfo_cache[i];
813 video::IImage *img = generateImageFromScratch(ti->name);
814 // Create texture from resulting image
815 video::ITexture *t = NULL;
817 t = driver->addTexture(ti->name.c_str(), img);
818 video::ITexture *t_old = ti->texture;
824 m_texture_trash.push_back(t_old);
828 video::ITexture* TextureSource::generateTextureFromMesh(
829 const TextureFromMeshParams ¶ms)
831 video::IVideoDriver *driver = m_device->getVideoDriver();
834 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
836 static bool warned = false;
839 errorstream<<"TextureSource::generateTextureFromMesh(): "
840 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
846 // Create render target texture
847 video::ITexture *rtt = driver->addRenderTargetTexture(
848 params.dim, params.rtt_texture_name.c_str(),
849 video::ECF_A8R8G8B8);
852 errorstream<<"TextureSource::generateTextureFromMesh(): "
853 <<"addRenderTargetTexture returned NULL."<<std::endl;
858 driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0));
860 // Get a scene manager
861 scene::ISceneManager *smgr_main = m_device->getSceneManager();
863 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
866 scene::IMeshSceneNode* meshnode = smgr->addMeshSceneNode(params.mesh, NULL, -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
867 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
868 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
869 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
870 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
871 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
873 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
874 params.camera_position, params.camera_lookat);
875 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
876 camera->setProjectionMatrix(params.camera_projection_matrix, false);
878 smgr->setAmbientLight(params.ambient_light);
879 smgr->addLightSceneNode(0,
880 params.light_position,
882 params.light_radius);
885 driver->beginScene(true, true, video::SColor(0,0,0,0));
889 // NOTE: The scene nodes should not be dropped, otherwise
890 // smgr->drop() segfaults
894 // Drop scene manager
897 // Unset render target
898 driver->setRenderTarget(0, false, true, 0);
900 if(params.delete_texture_on_shutdown)
901 m_texture_trash.push_back(rtt);
906 video::IImage* TextureSource::generateImageFromScratch(std::string name)
908 /*infostream<<"generateImageFromScratch(): "
909 "\""<<name<<"\""<<std::endl;*/
911 video::IVideoDriver *driver = m_device->getVideoDriver();
918 video::IImage *baseimg = NULL;
920 char separator = '^';
922 // Find last meta separator in name
923 s32 last_separator_position = name.find_last_of(separator);
926 If separator was found, construct the base name and make the
927 base image using a recursive call
929 std::string base_image_name;
930 if(last_separator_position != -1)
932 // Construct base name
933 base_image_name = name.substr(0, last_separator_position);
934 baseimg = generateImageFromScratch(base_image_name);
938 Parse out the last part of the name of the image and act
942 std::string last_part_of_name = name.substr(last_separator_position+1);
944 // Generate image according to part of name
945 if(!generateImage(last_part_of_name, baseimg))
947 errorstream<<"generateImageFromScratch(): "
948 "failed to generate \""<<last_part_of_name<<"\""
956 bool TextureSource::generateImage(std::string part_of_name, video::IImage *& baseimg)
958 video::IVideoDriver* driver = m_device->getVideoDriver();
961 // Stuff starting with [ are special commands
962 if(part_of_name.size() == 0 || part_of_name[0] != '[')
964 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
968 if(part_of_name != ""){
969 errorstream<<"generateImage(): Could not load image \""
970 <<part_of_name<<"\""<<" while building texture"<<std::endl;
971 errorstream<<"generateImage(): Creating a dummy"
972 <<" image for \""<<part_of_name<<"\""<<std::endl;
975 // Just create a dummy image
976 //core::dimension2d<u32> dim(2,2);
977 core::dimension2d<u32> dim(1,1);
978 image = driver->createImage(video::ECF_A8R8G8B8, dim);
980 /*image->setPixel(0,0, video::SColor(255,255,0,0));
981 image->setPixel(1,0, video::SColor(255,0,255,0));
982 image->setPixel(0,1, video::SColor(255,0,0,255));
983 image->setPixel(1,1, video::SColor(255,255,0,255));*/
984 image->setPixel(0,0, video::SColor(255,myrand()%256,
985 myrand()%256,myrand()%256));
986 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
987 myrand()%256,myrand()%256));
988 image->setPixel(0,1, video::SColor(255,myrand()%256,
989 myrand()%256,myrand()%256));
990 image->setPixel(1,1, video::SColor(255,myrand()%256,
991 myrand()%256,myrand()%256));*/
994 // If base image is NULL, load as base.
997 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
999 Copy it this way to get an alpha channel.
1000 Otherwise images with alpha cannot be blitted on
1001 images that don't have alpha in the original file.
1003 core::dimension2d<u32> dim = image->getDimension();
1004 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1005 image->copyTo(baseimg);
1007 // Else blit on base.
1010 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1011 // Size of the copied area
1012 core::dimension2d<u32> dim = image->getDimension();
1013 //core::dimension2d<u32> dim(16,16);
1014 // Position to copy the blitted to in the base image
1015 core::position2d<s32> pos_to(0,0);
1016 // Position to copy the blitted from in the blitted image
1017 core::position2d<s32> pos_from(0,0);
1019 /*image->copyToWithAlpha(baseimg, pos_to,
1020 core::rect<s32>(pos_from, dim),
1021 video::SColor(255,255,255,255),
1023 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1030 // A special texture modification
1032 /*infostream<<"generateImage(): generating special "
1033 <<"modification \""<<part_of_name<<"\""
1039 Adds a cracking texture
1040 N = animation frame count, P = crack progression
1042 if(part_of_name.substr(0,6) == "[crack")
1046 errorstream<<"generateImage(): baseimg==NULL "
1047 <<"for part_of_name=\""<<part_of_name
1048 <<"\", cancelling."<<std::endl;
1052 // Crack image number and overlay option
1053 bool use_overlay = (part_of_name[6] == 'o');
1054 Strfnd sf(part_of_name);
1056 s32 frame_count = stoi(sf.next(":"));
1057 s32 progression = stoi(sf.next(":"));
1062 It is an image with a number of cracking stages
1065 video::IImage *img_crack = m_sourcecache.getOrLoad(
1066 "crack_anylength.png", m_device);
1068 if(img_crack && progression >= 0)
1070 draw_crack(img_crack, baseimg,
1071 use_overlay, frame_count,
1072 progression, driver);
1077 [combine:WxH:X,Y=filename:X,Y=filename2
1078 Creates a bigger texture from an amount of smaller ones
1080 else if(part_of_name.substr(0,8) == "[combine")
1082 Strfnd sf(part_of_name);
1084 u32 w0 = stoi(sf.next("x"));
1085 u32 h0 = stoi(sf.next(":"));
1086 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1087 core::dimension2d<u32> dim(w0,h0);
1090 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1091 baseimg->fill(video::SColor(0,0,0,0));
1093 while(sf.atend() == false)
1095 u32 x = stoi(sf.next(","));
1096 u32 y = stoi(sf.next("="));
1097 std::string filename = sf.next(":");
1098 infostream<<"Adding \""<<filename
1099 <<"\" to combined ("<<x<<","<<y<<")"
1101 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1104 core::dimension2d<u32> dim = img->getDimension();
1105 infostream<<"Size "<<dim.Width
1106 <<"x"<<dim.Height<<std::endl;
1107 core::position2d<s32> pos_base(x, y);
1108 video::IImage *img2 =
1109 driver->createImage(video::ECF_A8R8G8B8, dim);
1112 /*img2->copyToWithAlpha(baseimg, pos_base,
1113 core::rect<s32>(v2s32(0,0), dim),
1114 video::SColor(255,255,255,255),
1116 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1121 infostream<<"img==NULL"<<std::endl;
1128 else if(part_of_name.substr(0,9) == "[brighten")
1132 errorstream<<"generateImage(): baseimg==NULL "
1133 <<"for part_of_name=\""<<part_of_name
1134 <<"\", cancelling."<<std::endl;
1142 Make image completely opaque.
1143 Used for the leaves texture when in old leaves mode, so
1144 that the transparent parts don't look completely black
1145 when simple alpha channel is used for rendering.
1147 else if(part_of_name.substr(0,8) == "[noalpha")
1151 errorstream<<"generateImage(): baseimg==NULL "
1152 <<"for part_of_name=\""<<part_of_name
1153 <<"\", cancelling."<<std::endl;
1157 core::dimension2d<u32> dim = baseimg->getDimension();
1159 // Set alpha to full
1160 for(u32 y=0; y<dim.Height; y++)
1161 for(u32 x=0; x<dim.Width; x++)
1163 video::SColor c = baseimg->getPixel(x,y);
1165 baseimg->setPixel(x,y,c);
1170 Convert one color to transparent.
1172 else if(part_of_name.substr(0,11) == "[makealpha:")
1176 errorstream<<"generateImage(): baseimg==NULL "
1177 <<"for part_of_name=\""<<part_of_name
1178 <<"\", cancelling."<<std::endl;
1182 Strfnd sf(part_of_name.substr(11));
1183 u32 r1 = stoi(sf.next(","));
1184 u32 g1 = stoi(sf.next(","));
1185 u32 b1 = stoi(sf.next(""));
1186 std::string filename = sf.next("");
1188 core::dimension2d<u32> dim = baseimg->getDimension();
1190 /*video::IImage *oldbaseimg = baseimg;
1191 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1192 oldbaseimg->copyTo(baseimg);
1193 oldbaseimg->drop();*/
1195 // Set alpha to full
1196 for(u32 y=0; y<dim.Height; y++)
1197 for(u32 x=0; x<dim.Width; x++)
1199 video::SColor c = baseimg->getPixel(x,y);
1201 u32 g = c.getGreen();
1202 u32 b = c.getBlue();
1203 if(!(r == r1 && g == g1 && b == b1))
1206 baseimg->setPixel(x,y,c);
1211 Rotates and/or flips the image.
1213 N can be a number (between 0 and 7) or a transform name.
1214 Rotations are counter-clockwise.
1216 1 R90 rotate by 90 degrees
1217 2 R180 rotate by 180 degrees
1218 3 R270 rotate by 270 degrees
1220 5 FXR90 flip X then rotate by 90 degrees
1222 7 FYR90 flip Y then rotate by 90 degrees
1224 Note: Transform names can be concatenated to produce
1225 their product (applies the first then the second).
1226 The resulting transform will be equivalent to one of the
1227 eight existing ones, though (see: dihedral group).
1229 else if(part_of_name.substr(0,10) == "[transform")
1233 errorstream<<"generateImage(): baseimg==NULL "
1234 <<"for part_of_name=\""<<part_of_name
1235 <<"\", cancelling."<<std::endl;
1239 u32 transform = parseImageTransform(part_of_name.substr(10));
1240 core::dimension2d<u32> dim = imageTransformDimension(
1241 transform, baseimg->getDimension());
1242 video::IImage *image = driver->createImage(
1243 baseimg->getColorFormat(), dim);
1245 imageTransform(transform, baseimg, image);
1250 [inventorycube{topimage{leftimage{rightimage
1251 In every subimage, replace ^ with &.
1252 Create an "inventory cube".
1253 NOTE: This should be used only on its own.
1254 Example (a grass block (not actually used in game):
1255 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1257 else if(part_of_name.substr(0,14) == "[inventorycube")
1261 errorstream<<"generateImage(): baseimg!=NULL "
1262 <<"for part_of_name=\""<<part_of_name
1263 <<"\", cancelling."<<std::endl;
1267 str_replace_char(part_of_name, '&', '^');
1268 Strfnd sf(part_of_name);
1270 std::string imagename_top = sf.next("{");
1271 std::string imagename_left = sf.next("{");
1272 std::string imagename_right = sf.next("{");
1274 // Generate images for the faces of the cube
1275 video::IImage *img_top =
1276 generateImageFromScratch(imagename_top);
1277 video::IImage *img_left =
1278 generateImageFromScratch(imagename_left);
1279 video::IImage *img_right =
1280 generateImageFromScratch(imagename_right);
1281 assert(img_top && img_left && img_right);
1283 // Create textures from images
1284 video::ITexture *texture_top = driver->addTexture(
1285 (imagename_top + "__temp__").c_str(), img_top);
1286 video::ITexture *texture_left = driver->addTexture(
1287 (imagename_left + "__temp__").c_str(), img_left);
1288 video::ITexture *texture_right = driver->addTexture(
1289 (imagename_right + "__temp__").c_str(), img_right);
1290 assert(texture_top && texture_left && texture_right);
1298 Draw a cube mesh into a render target texture
1300 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1301 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1302 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1303 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1304 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1305 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1306 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1307 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1309 TextureFromMeshParams params;
1311 params.dim.set(64, 64);
1312 params.rtt_texture_name = part_of_name + "_RTT";
1313 // We will delete the rtt texture ourselves
1314 params.delete_texture_on_shutdown = false;
1315 params.camera_position.set(0, 1.0, -1.5);
1316 params.camera_position.rotateXZBy(45);
1317 params.camera_lookat.set(0, 0, 0);
1318 // Set orthogonal projection
1319 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1320 1.65, 1.65, 0, 100);
1322 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1323 params.light_position.set(10, 100, -50);
1324 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1325 params.light_radius = 1000;
1327 video::ITexture *rtt = generateTextureFromMesh(params);
1332 // Free textures of images
1333 driver->removeTexture(texture_top);
1334 driver->removeTexture(texture_left);
1335 driver->removeTexture(texture_right);
1339 baseimg = generateImageFromScratch(imagename_top);
1343 // Create image of render target
1344 video::IImage *image = driver->createImage(rtt, v2s32(0,0), params.dim);
1348 driver->removeTexture(rtt);
1350 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1354 image->copyTo(baseimg);
1359 [lowpart:percent:filename
1360 Adds the lower part of a texture
1362 else if(part_of_name.substr(0,9) == "[lowpart:")
1364 Strfnd sf(part_of_name);
1366 u32 percent = stoi(sf.next(":"));
1367 std::string filename = sf.next(":");
1368 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1371 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1372 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1375 core::dimension2d<u32> dim = img->getDimension();
1376 core::position2d<s32> pos_base(0, 0);
1377 video::IImage *img2 =
1378 driver->createImage(video::ECF_A8R8G8B8, dim);
1381 core::position2d<s32> clippos(0, 0);
1382 clippos.Y = dim.Height * (100-percent) / 100;
1383 core::dimension2d<u32> clipdim = dim;
1384 clipdim.Height = clipdim.Height * percent / 100 + 1;
1385 core::rect<s32> cliprect(clippos, clipdim);
1386 img2->copyToWithAlpha(baseimg, pos_base,
1387 core::rect<s32>(v2s32(0,0), dim),
1388 video::SColor(255,255,255,255),
1395 Crops a frame of a vertical animation.
1396 N = frame count, I = frame index
1398 else if(part_of_name.substr(0,15) == "[verticalframe:")
1400 Strfnd sf(part_of_name);
1402 u32 frame_count = stoi(sf.next(":"));
1403 u32 frame_index = stoi(sf.next(":"));
1405 if(baseimg == NULL){
1406 errorstream<<"generateImage(): baseimg!=NULL "
1407 <<"for part_of_name=\""<<part_of_name
1408 <<"\", cancelling."<<std::endl;
1412 v2u32 frame_size = baseimg->getDimension();
1413 frame_size.Y /= frame_count;
1415 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1418 errorstream<<"generateImage(): Could not create image "
1419 <<"for part_of_name=\""<<part_of_name
1420 <<"\", cancelling."<<std::endl;
1424 // Fill target image with transparency
1425 img->fill(video::SColor(0,0,0,0));
1427 core::dimension2d<u32> dim = frame_size;
1428 core::position2d<s32> pos_dst(0, 0);
1429 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1430 baseimg->copyToWithAlpha(img, pos_dst,
1431 core::rect<s32>(pos_src, dim),
1432 video::SColor(255,255,255,255),
1440 errorstream<<"generateImage(): Invalid "
1441 " modification: \""<<part_of_name<<"\""<<std::endl;
1449 Draw an image on top of an another one, using the alpha channel of the
1452 This exists because IImage::copyToWithAlpha() doesn't seem to always
1455 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1456 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1458 for(u32 y0=0; y0<size.Y; y0++)
1459 for(u32 x0=0; x0<size.X; x0++)
1461 s32 src_x = src_pos.X + x0;
1462 s32 src_y = src_pos.Y + y0;
1463 s32 dst_x = dst_pos.X + x0;
1464 s32 dst_y = dst_pos.Y + y0;
1465 video::SColor src_c = src->getPixel(src_x, src_y);
1466 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1467 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1468 dst->setPixel(dst_x, dst_y, dst_c);
1473 Draw an image on top of an another one, using the alpha channel of the
1474 source image; only modify fully opaque pixels in destinaion
1476 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1477 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1479 for(u32 y0=0; y0<size.Y; y0++)
1480 for(u32 x0=0; x0<size.X; x0++)
1482 s32 src_x = src_pos.X + x0;
1483 s32 src_y = src_pos.Y + y0;
1484 s32 dst_x = dst_pos.X + x0;
1485 s32 dst_y = dst_pos.Y + y0;
1486 video::SColor src_c = src->getPixel(src_x, src_y);
1487 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1488 if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1490 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1491 dst->setPixel(dst_x, dst_y, dst_c);
1496 static void draw_crack(video::IImage *crack, video::IImage *dst,
1497 bool use_overlay, s32 frame_count, s32 progression,
1498 video::IVideoDriver *driver)
1500 // Dimension of destination image
1501 core::dimension2d<u32> dim_dst = dst->getDimension();
1502 // Dimension of original image
1503 core::dimension2d<u32> dim_crack = crack->getDimension();
1504 // Count of crack stages
1505 s32 crack_count = dim_crack.Height / dim_crack.Width;
1506 // Limit frame_count
1507 if(frame_count > (s32) dim_dst.Height)
1508 frame_count = dim_dst.Height;
1511 // Limit progression
1512 if(progression > crack_count-1)
1513 progression = crack_count-1;
1514 // Dimension of a single crack stage
1515 core::dimension2d<u32> dim_crack_cropped(
1519 // Dimension of the scaled crack stage,
1520 // which is the same as the dimension of a single destination frame
1521 core::dimension2d<u32> dim_crack_scaled(
1523 dim_dst.Height / frame_count
1525 // Create cropped and scaled crack images
1526 video::IImage *crack_cropped = driver->createImage(
1527 video::ECF_A8R8G8B8, dim_crack_cropped);
1528 video::IImage *crack_scaled = driver->createImage(
1529 video::ECF_A8R8G8B8, dim_crack_scaled);
1531 if(crack_cropped && crack_scaled)
1534 v2s32 pos_crack(0, progression*dim_crack.Width);
1535 crack->copyTo(crack_cropped,
1537 core::rect<s32>(pos_crack, dim_crack_cropped));
1538 // Scale crack image by copying
1539 crack_cropped->copyToScaling(crack_scaled);
1540 // Copy or overlay crack image onto each frame
1541 for(s32 i = 0; i < frame_count; ++i)
1543 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1546 blit_with_alpha_overlay(crack_scaled, dst,
1547 v2s32(0,0), dst_pos,
1552 blit_with_alpha(crack_scaled, dst,
1553 v2s32(0,0), dst_pos,
1560 crack_scaled->drop();
1563 crack_cropped->drop();
1566 void brighten(video::IImage *image)
1571 core::dimension2d<u32> dim = image->getDimension();
1573 for(u32 y=0; y<dim.Height; y++)
1574 for(u32 x=0; x<dim.Width; x++)
1576 video::SColor c = image->getPixel(x,y);
1577 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1578 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1579 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1580 image->setPixel(x,y,c);
1584 u32 parseImageTransform(const std::string& s)
1586 int total_transform = 0;
1588 std::string transform_names[8];
1589 transform_names[0] = "i";
1590 transform_names[1] = "r90";
1591 transform_names[2] = "r180";
1592 transform_names[3] = "r270";
1593 transform_names[4] = "fx";
1594 transform_names[6] = "fy";
1596 std::size_t pos = 0;
1597 while(pos < s.size())
1600 for(int i = 0; i <= 7; ++i)
1602 const std::string &name_i = transform_names[i];
1604 if(s[pos] == ('0' + i))
1610 else if(!(name_i.empty()) &&
1611 lowercase(s.substr(pos, name_i.size())) == name_i)
1614 pos += name_i.size();
1621 // Multiply total_transform and transform in the group D4
1624 new_total = (transform + total_transform) % 4;
1626 new_total = (transform - total_transform + 8) % 4;
1627 if((transform >= 4) ^ (total_transform >= 4))
1630 total_transform = new_total;
1632 return total_transform;
1635 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1637 if(transform % 2 == 0)
1640 return core::dimension2d<u32>(dim.Height, dim.Width);
1643 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1645 if(src == NULL || dst == NULL)
1648 core::dimension2d<u32> srcdim = src->getDimension();
1649 core::dimension2d<u32> dstdim = dst->getDimension();
1651 assert(dstdim == imageTransformDimension(transform, srcdim));
1652 assert(transform >= 0 && transform <= 7);
1655 Compute the transformation from source coordinates (sx,sy)
1656 to destination coordinates (dx,dy).
1660 if(transform == 0) // identity
1661 sxn = 0, syn = 2; // sx = dx, sy = dy
1662 else if(transform == 1) // rotate by 90 degrees ccw
1663 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1664 else if(transform == 2) // rotate by 180 degrees
1665 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1666 else if(transform == 3) // rotate by 270 degrees ccw
1667 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1668 else if(transform == 4) // flip x
1669 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1670 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1671 sxn = 2, syn = 0; // sx = dy, sy = dx
1672 else if(transform == 6) // flip y
1673 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1674 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1675 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1677 for(u32 dy=0; dy<dstdim.Height; dy++)
1678 for(u32 dx=0; dx<dstdim.Width; dx++)
1680 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1681 u32 sx = entries[sxn];
1682 u32 sy = entries[syn];
1683 video::SColor c = src->getPixel(sx,sy);
1684 dst->setPixel(dx,dy,c);