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 $user/textures/all
140 std::string texture_path = porting::path_user + DIR_DELIM
141 + "textures" + DIR_DELIM + "all";
142 std::string testpath = texture_path + DIR_DELIM + filename;
143 // Check all filename extensions. Returns "" if not found.
144 fullpath = getImagePath(testpath);
148 Check from default data directory
152 std::string base_path = porting::path_share + DIR_DELIM + "textures"
153 + DIR_DELIM + "base" + DIR_DELIM + "pack";
154 std::string testpath = base_path + DIR_DELIM + filename;
155 // Check all filename extensions. Returns "" if not found.
156 fullpath = getImagePath(testpath);
159 // Add to cache (also an empty result is cached)
160 g_texturename_to_path_cache.set(filename, fullpath);
167 Stores internal information about a texture.
173 video::ITexture *texture;
174 video::IImage *img; // The source image
177 const std::string &name_,
178 video::ITexture *texture_=NULL,
179 video::IImage *img_=NULL
189 SourceImageCache: A cache used for storing source images.
192 class SourceImageCache
195 ~SourceImageCache() {
196 for(std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
197 iter != m_images.end(); iter++) {
198 iter->second->drop();
202 void insert(const std::string &name, video::IImage *img,
203 bool prefer_local, video::IVideoDriver *driver)
207 std::map<std::string, video::IImage*>::iterator n;
208 n = m_images.find(name);
209 if(n != m_images.end()){
214 video::IImage* toadd = img;
215 bool need_to_grab = true;
217 // Try to use local texture instead if asked to
219 std::string path = getTexturePath(name.c_str());
221 video::IImage *img2 = driver->createImageFromFile(path.c_str());
224 need_to_grab = false;
231 m_images[name] = toadd;
233 video::IImage* get(const std::string &name)
235 std::map<std::string, video::IImage*>::iterator n;
236 n = m_images.find(name);
237 if(n != m_images.end())
241 // Primarily fetches from cache, secondarily tries to read from filesystem
242 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *m_device)
244 std::map<std::string, video::IImage*>::iterator n;
245 n = m_images.find(name);
246 if(n != m_images.end()){
247 n->second->grab(); // Grab for caller
250 video::IVideoDriver* driver = m_device->getVideoDriver();
251 std::string path = getTexturePath(name.c_str());
253 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
254 <<name<<"\""<<std::endl;
257 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
259 video::IImage *img = driver->createImageFromFile(path.c_str());
262 m_images[name] = img;
263 img->grab(); // Grab for caller
268 std::map<std::string, video::IImage*> m_images;
275 class TextureSource : public IWritableTextureSource
278 TextureSource(IrrlichtDevice *device);
279 virtual ~TextureSource();
283 Now, assume a texture with the id 1 exists, and has the name
284 "stone.png^mineral1".
285 Then a random thread calls getTextureId for a texture called
286 "stone.png^mineral1^crack0".
287 ...Now, WTF should happen? Well:
288 - getTextureId strips off stuff recursively from the end until
289 the remaining part is found, or nothing is left when
290 something is stripped out
292 But it is slow to search for textures by names and modify them
294 - ContentFeatures is made to contain ids for the basic plain
296 - Crack textures can be slow by themselves, but the framework
300 - Assume a texture with the id 1 exists, and has the name
301 "stone.png^mineral_coal.png".
302 - Now getNodeTile() stumbles upon a node which uses
303 texture id 1, and determines that MATERIAL_FLAG_CRACK
304 must be applied to the tile
305 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
306 has received the current crack level 0 from the client. It
307 finds out the name of the texture with getTextureName(1),
308 appends "^crack0" to it and gets a new texture id with
309 getTextureId("stone.png^mineral_coal.png^crack0").
314 Gets a texture id from cache or
315 - if main thread, from getTextureIdDirect
316 - if other thread, adds to request queue and waits for main thread
318 u32 getTextureId(const std::string &name);
324 "stone.png^mineral_coal.png"
325 "stone.png^mineral_coal.png^crack1"
327 - If texture specified by name is found from cache, return the
329 - Otherwise generate the texture, add to cache and return id.
330 Recursion is used to find out the largest found part of the
331 texture and continue based on it.
333 The id 0 points to a NULL texture. It is returned in case of error.
335 u32 getTextureIdDirect(const std::string &name);
337 // Finds out the name of a cached texture.
338 std::string getTextureName(u32 id);
341 If texture specified by the name pointed by the id doesn't
342 exist, create it, then return the cached texture.
344 Can be called from any thread. If called from some other thread
345 and not found in cache, the call is queued to the main thread
348 video::ITexture* getTexture(u32 id);
350 video::ITexture* getTexture(const std::string &name, u32 *id);
352 // Returns a pointer to the irrlicht device
353 virtual IrrlichtDevice* getDevice()
358 bool isKnownSourceImage(const std::string &name)
360 bool is_known = false;
361 bool cache_found = m_source_image_existence.get(name, &is_known);
364 // Not found in cache; find out if a local file exists
365 is_known = (getTexturePath(name) != "");
366 m_source_image_existence.set(name, is_known);
370 // Processes queued texture requests from other threads.
371 // Shall be called from the main thread.
374 // Insert an image into the cache without touching the filesystem.
375 // Shall be called from the main thread.
376 void insertSourceImage(const std::string &name, video::IImage *img);
378 // Rebuild images and textures from the current set of source images
379 // Shall be called from the main thread.
380 void rebuildImagesAndTextures();
382 // Render a mesh to a texture.
383 // Returns NULL if render-to-texture failed.
384 // Shall be called from the main thread.
385 video::ITexture* generateTextureFromMesh(
386 const TextureFromMeshParams ¶ms);
388 // Generates an image from a full string like
389 // "stone.png^mineral_coal.png^[crack0".
390 // Shall be called from the main thread.
391 video::IImage* generateImageFromScratch(std::string name);
393 // Generate image based on a string like "stone.png" or "[crack0".
394 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
395 // Shall be called from the main thread.
396 bool generateImage(std::string part_of_name, video::IImage *& baseimg);
400 // The id of the thread that is allowed to use irrlicht directly
401 threadid_t m_main_thread;
402 // The irrlicht device
403 IrrlichtDevice *m_device;
405 // Cache of source images
406 // This should be only accessed from the main thread
407 SourceImageCache m_sourcecache;
409 // Thread-safe cache of what source images are known (true = known)
410 MutexedMap<std::string, bool> m_source_image_existence;
412 // A texture id is index in this array.
413 // The first position contains a NULL texture.
414 std::vector<TextureInfo> m_textureinfo_cache;
415 // Maps a texture name to an index in the former.
416 std::map<std::string, u32> m_name_to_id;
417 // The two former containers are behind this mutex
418 JMutex m_textureinfo_cache_mutex;
420 // Queued texture fetches (to be processed by the main thread)
421 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
423 // Textures that have been overwritten with other ones
424 // but can't be deleted because the ITexture* might still be used
425 std::list<video::ITexture*> m_texture_trash;
427 // Cached settings needed for making textures from meshes
428 bool m_setting_trilinear_filter;
429 bool m_setting_bilinear_filter;
430 bool m_setting_anisotropic_filter;
433 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
435 return new TextureSource(device);
438 TextureSource::TextureSource(IrrlichtDevice *device):
443 m_textureinfo_cache_mutex.Init();
445 m_main_thread = get_current_thread_id();
447 // Add a NULL TextureInfo as the first index, named ""
448 m_textureinfo_cache.push_back(TextureInfo(""));
449 m_name_to_id[""] = 0;
451 // Cache some settings
452 // Note: Since this is only done once, the game must be restarted
453 // for these settings to take effect
454 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
455 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
456 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
459 TextureSource::~TextureSource()
461 video::IVideoDriver* driver = m_device->getVideoDriver();
463 unsigned int textures_before = driver->getTextureCount();
465 for (std::vector<TextureInfo>::iterator iter =
466 m_textureinfo_cache.begin();
467 iter != m_textureinfo_cache.end(); iter++)
471 driver->removeTexture(iter->texture);
473 //cleanup source image
477 m_textureinfo_cache.clear();
479 for (std::list<video::ITexture*>::iterator iter =
480 m_texture_trash.begin(); iter != m_texture_trash.end();
483 video::ITexture *t = *iter;
485 //cleanup trashed texture
486 driver->removeTexture(t);
489 infostream << "~TextureSource() "<< textures_before << "/"
490 << driver->getTextureCount() << std::endl;
493 u32 TextureSource::getTextureId(const std::string &name)
495 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
499 See if texture already exists
501 JMutexAutoLock lock(m_textureinfo_cache_mutex);
502 std::map<std::string, u32>::iterator n;
503 n = m_name_to_id.find(name);
504 if(n != m_name_to_id.end())
513 if(get_current_thread_id() == m_main_thread)
515 return getTextureIdDirect(name);
519 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
521 // We're gonna ask the result to be put into here
522 ResultQueue<std::string, u32, u8, u8> result_queue;
524 // Throw a request in
525 m_get_texture_queue.add(name, 0, 0, &result_queue);
527 infostream<<"Waiting for texture from main thread, name=\""
528 <<name<<"\""<<std::endl;
532 // Wait result for a second
533 GetResult<std::string, u32, u8, u8>
534 result = result_queue.pop_front(1000);
536 // Check that at least something worked OK
537 assert(result.key == name);
541 catch(ItemNotFoundException &e)
543 infostream<<"Waiting for texture timed out."<<std::endl;
548 infostream<<"getTextureId(): Failed"<<std::endl;
553 // Overlay image on top of another image (used for cracks)
554 void overlay(video::IImage *image, video::IImage *overlay);
556 // Draw an image on top of an another one, using the alpha channel of the
558 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
559 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
562 void brighten(video::IImage *image);
563 // Parse a transform name
564 u32 parseImageTransform(const std::string& s);
565 // Apply transform to image dimension
566 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
567 // Apply transform to image data
568 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
571 This method generates all the textures
573 u32 TextureSource::getTextureIdDirect(const std::string &name)
575 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
577 // Empty name means texture 0
580 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
585 Calling only allowed from main thread
587 if(get_current_thread_id() != m_main_thread)
589 errorstream<<"TextureSource::getTextureIdDirect() "
590 "called not from main thread"<<std::endl;
595 See if texture already exists
598 JMutexAutoLock lock(m_textureinfo_cache_mutex);
600 std::map<std::string, u32>::iterator n;
601 n = m_name_to_id.find(name);
602 if(n != m_name_to_id.end())
604 /*infostream<<"getTextureIdDirect(): \""<<name
605 <<"\" found in cache"<<std::endl;*/
610 /*infostream<<"getTextureIdDirect(): \""<<name
611 <<"\" NOT found in cache. Creating it."<<std::endl;*/
617 char separator = '^';
620 This is set to the id of the base image.
621 If left 0, there is no base image and a completely new image
624 u32 base_image_id = 0;
626 // Find last meta separator in name
627 s32 last_separator_position = -1;
628 for(s32 i=name.size()-1; i>=0; i--)
630 if(name[i] == separator)
632 last_separator_position = i;
637 If separator was found, construct the base name and make the
638 base image using a recursive call
640 std::string base_image_name;
641 if(last_separator_position != -1)
643 // Construct base name
644 base_image_name = name.substr(0, last_separator_position);
645 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
646 " to get base image of \""<<name<<"\" = \""
647 <<base_image_name<<"\""<<std::endl;*/
648 base_image_id = getTextureIdDirect(base_image_name);
651 //infostream<<"base_image_id="<<base_image_id<<std::endl;
653 video::IVideoDriver* driver = m_device->getVideoDriver();
656 video::ITexture *t = NULL;
659 An image will be built from files and then converted into a texture.
661 video::IImage *baseimg = NULL;
663 // If a base image was found, copy it to baseimg
664 if(base_image_id != 0)
666 JMutexAutoLock lock(m_textureinfo_cache_mutex);
668 TextureInfo *ti = &m_textureinfo_cache[base_image_id];
672 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
673 <<"cache: \""<<base_image_name<<"\""
678 core::dimension2d<u32> dim = ti->img->getDimension();
680 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
684 v2s32(0,0), // position in target
685 core::rect<s32>(v2s32(0,0), dim) // from
688 /*infostream<<"getTextureIdDirect(): Loaded \""
689 <<base_image_name<<"\" from image cache"
695 Parse out the last part of the name of the image and act
699 std::string last_part_of_name = name.substr(last_separator_position+1);
700 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
702 // Generate image according to part of name
703 if(!generateImage(last_part_of_name, baseimg))
705 errorstream<<"getTextureIdDirect(): "
706 "failed to generate \""<<last_part_of_name<<"\""
710 // If no resulting image, print a warning
713 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
714 " create texture \""<<name<<"\""<<std::endl;
719 // Create texture from resulting image
720 t = driver->addTexture(name.c_str(), baseimg);
724 Add texture to caches (add NULL textures too)
727 JMutexAutoLock lock(m_textureinfo_cache_mutex);
729 u32 id = m_textureinfo_cache.size();
730 TextureInfo ti(name, t, baseimg);
731 m_textureinfo_cache.push_back(ti);
732 m_name_to_id[name] = id;
734 /*infostream<<"getTextureIdDirect(): "
735 <<"Returning id="<<id<<" for name \""<<name<<"\""<<std::endl;*/
740 std::string TextureSource::getTextureName(u32 id)
742 JMutexAutoLock lock(m_textureinfo_cache_mutex);
744 if(id >= m_textureinfo_cache.size())
746 errorstream<<"TextureSource::getTextureName(): id="<<id
747 <<" >= m_textureinfo_cache.size()="
748 <<m_textureinfo_cache.size()<<std::endl;
752 return m_textureinfo_cache[id].name;
755 video::ITexture* TextureSource::getTexture(u32 id)
757 JMutexAutoLock lock(m_textureinfo_cache_mutex);
759 if(id >= m_textureinfo_cache.size())
762 return m_textureinfo_cache[id].texture;
765 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
767 u32 actual_id = getTextureId(name);
771 return getTexture(actual_id);
774 void TextureSource::processQueue()
779 if(!m_get_texture_queue.empty())
781 GetRequest<std::string, u32, u8, u8>
782 request = m_get_texture_queue.pop();
784 /*infostream<<"TextureSource::processQueue(): "
785 <<"got texture request with "
786 <<"name=\""<<request.key<<"\""
789 GetResult<std::string, u32, u8, u8>
791 result.key = request.key;
792 result.callers = request.callers;
793 result.item = getTextureIdDirect(request.key);
795 request.dest->push_back(result);
799 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
801 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
803 assert(get_current_thread_id() == m_main_thread);
805 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
806 m_source_image_existence.set(name, true);
809 void TextureSource::rebuildImagesAndTextures()
811 JMutexAutoLock lock(m_textureinfo_cache_mutex);
813 video::IVideoDriver* driver = m_device->getVideoDriver();
816 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
817 TextureInfo *ti = &m_textureinfo_cache[i];
818 video::IImage *img = generateImageFromScratch(ti->name);
819 // Create texture from resulting image
820 video::ITexture *t = NULL;
822 t = driver->addTexture(ti->name.c_str(), img);
823 video::ITexture *t_old = ti->texture;
829 m_texture_trash.push_back(t_old);
833 video::ITexture* TextureSource::generateTextureFromMesh(
834 const TextureFromMeshParams ¶ms)
836 video::IVideoDriver *driver = m_device->getVideoDriver();
839 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
841 static bool warned = false;
844 errorstream<<"TextureSource::generateTextureFromMesh(): "
845 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
851 // Create render target texture
852 video::ITexture *rtt = driver->addRenderTargetTexture(
853 params.dim, params.rtt_texture_name.c_str(),
854 video::ECF_A8R8G8B8);
857 errorstream<<"TextureSource::generateTextureFromMesh(): "
858 <<"addRenderTargetTexture returned NULL."<<std::endl;
863 driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0));
865 // Get a scene manager
866 scene::ISceneManager *smgr_main = m_device->getSceneManager();
868 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
871 scene::IMeshSceneNode* meshnode = smgr->addMeshSceneNode(params.mesh, NULL, -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
872 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
873 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
874 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
875 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
876 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
878 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
879 params.camera_position, params.camera_lookat);
880 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
881 camera->setProjectionMatrix(params.camera_projection_matrix, false);
883 smgr->setAmbientLight(params.ambient_light);
884 smgr->addLightSceneNode(0,
885 params.light_position,
887 params.light_radius);
890 driver->beginScene(true, true, video::SColor(0,0,0,0));
894 // NOTE: The scene nodes should not be dropped, otherwise
895 // smgr->drop() segfaults
899 // Drop scene manager
902 // Unset render target
903 driver->setRenderTarget(0, false, true, 0);
905 if(params.delete_texture_on_shutdown)
906 m_texture_trash.push_back(rtt);
911 video::IImage* TextureSource::generateImageFromScratch(std::string name)
913 /*infostream<<"generateImageFromScratch(): "
914 "\""<<name<<"\""<<std::endl;*/
916 video::IVideoDriver *driver = m_device->getVideoDriver();
923 video::IImage *baseimg = NULL;
925 char separator = '^';
927 // Find last meta separator in name
928 s32 last_separator_position = name.find_last_of(separator);
931 If separator was found, construct the base name and make the
932 base image using a recursive call
934 std::string base_image_name;
935 if(last_separator_position != -1)
937 // Construct base name
938 base_image_name = name.substr(0, last_separator_position);
939 baseimg = generateImageFromScratch(base_image_name);
943 Parse out the last part of the name of the image and act
947 std::string last_part_of_name = name.substr(last_separator_position+1);
949 // Generate image according to part of name
950 if(!generateImage(last_part_of_name, baseimg))
952 errorstream<<"generateImageFromScratch(): "
953 "failed to generate \""<<last_part_of_name<<"\""
961 bool TextureSource::generateImage(std::string part_of_name, video::IImage *& baseimg)
963 video::IVideoDriver* driver = m_device->getVideoDriver();
966 // Stuff starting with [ are special commands
967 if(part_of_name.size() == 0 || part_of_name[0] != '[')
969 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
973 if(part_of_name != ""){
974 errorstream<<"generateImage(): Could not load image \""
975 <<part_of_name<<"\""<<" while building texture"<<std::endl;
976 errorstream<<"generateImage(): Creating a dummy"
977 <<" image for \""<<part_of_name<<"\""<<std::endl;
980 // Just create a dummy image
981 //core::dimension2d<u32> dim(2,2);
982 core::dimension2d<u32> dim(1,1);
983 image = driver->createImage(video::ECF_A8R8G8B8, dim);
985 /*image->setPixel(0,0, video::SColor(255,255,0,0));
986 image->setPixel(1,0, video::SColor(255,0,255,0));
987 image->setPixel(0,1, video::SColor(255,0,0,255));
988 image->setPixel(1,1, video::SColor(255,255,0,255));*/
989 image->setPixel(0,0, video::SColor(255,myrand()%256,
990 myrand()%256,myrand()%256));
991 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
992 myrand()%256,myrand()%256));
993 image->setPixel(0,1, video::SColor(255,myrand()%256,
994 myrand()%256,myrand()%256));
995 image->setPixel(1,1, video::SColor(255,myrand()%256,
996 myrand()%256,myrand()%256));*/
999 // If base image is NULL, load as base.
1002 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1004 Copy it this way to get an alpha channel.
1005 Otherwise images with alpha cannot be blitted on
1006 images that don't have alpha in the original file.
1008 core::dimension2d<u32> dim = image->getDimension();
1009 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1010 image->copyTo(baseimg);
1012 // Else blit on base.
1015 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1016 // Size of the copied area
1017 core::dimension2d<u32> dim = image->getDimension();
1018 //core::dimension2d<u32> dim(16,16);
1019 // Position to copy the blitted to in the base image
1020 core::position2d<s32> pos_to(0,0);
1021 // Position to copy the blitted from in the blitted image
1022 core::position2d<s32> pos_from(0,0);
1024 /*image->copyToWithAlpha(baseimg, pos_to,
1025 core::rect<s32>(pos_from, dim),
1026 video::SColor(255,255,255,255),
1028 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1035 // A special texture modification
1037 /*infostream<<"generateImage(): generating special "
1038 <<"modification \""<<part_of_name<<"\""
1043 Adds a cracking texture
1045 if(part_of_name.substr(0,6) == "[crack")
1049 errorstream<<"generateImage(): baseimg==NULL "
1050 <<"for part_of_name=\""<<part_of_name
1051 <<"\", cancelling."<<std::endl;
1055 // Crack image number and overlay option
1056 s32 progression = 0;
1057 bool use_overlay = false;
1058 if(part_of_name.substr(6,1) == "o")
1060 progression = stoi(part_of_name.substr(7));
1065 progression = stoi(part_of_name.substr(6));
1066 use_overlay = false;
1069 // Size of the base image
1070 core::dimension2d<u32> dim_base = baseimg->getDimension();
1075 It is an image with a number of cracking stages
1078 video::IImage *img_crack = m_sourcecache.getOrLoad(
1079 "crack_anylength.png", m_device);
1081 if(img_crack && progression >= 0)
1083 // Dimension of original image
1084 core::dimension2d<u32> dim_crack
1085 = img_crack->getDimension();
1086 // Count of crack stages
1087 s32 crack_count = dim_crack.Height / dim_crack.Width;
1088 // Limit progression
1089 if(progression > crack_count-1)
1090 progression = crack_count-1;
1091 // Dimension of a single crack stage
1092 core::dimension2d<u32> dim_crack_cropped(
1096 // Create cropped and scaled crack images
1097 video::IImage *img_crack_cropped = driver->createImage(
1098 video::ECF_A8R8G8B8, dim_crack_cropped);
1099 video::IImage *img_crack_scaled = driver->createImage(
1100 video::ECF_A8R8G8B8, dim_base);
1102 if(img_crack_cropped && img_crack_scaled)
1105 v2s32 pos_crack(0, progression*dim_crack.Width);
1106 img_crack->copyTo(img_crack_cropped,
1108 core::rect<s32>(pos_crack, dim_crack_cropped));
1109 // Scale crack image by copying
1110 img_crack_cropped->copyToScaling(img_crack_scaled);
1111 // Copy or overlay crack image
1114 overlay(baseimg, img_crack_scaled);
1118 /*img_crack_scaled->copyToWithAlpha(
1121 core::rect<s32>(v2s32(0,0), dim_base),
1122 video::SColor(255,255,255,255));*/
1123 blit_with_alpha(img_crack_scaled, baseimg,
1124 v2s32(0,0), v2s32(0,0), dim_base);
1128 if(img_crack_scaled)
1129 img_crack_scaled->drop();
1131 if(img_crack_cropped)
1132 img_crack_cropped->drop();
1138 [combine:WxH:X,Y=filename:X,Y=filename2
1139 Creates a bigger texture from an amount of smaller ones
1141 else if(part_of_name.substr(0,8) == "[combine")
1143 Strfnd sf(part_of_name);
1145 u32 w0 = stoi(sf.next("x"));
1146 u32 h0 = stoi(sf.next(":"));
1147 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1148 core::dimension2d<u32> dim(w0,h0);
1151 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1152 baseimg->fill(video::SColor(0,0,0,0));
1154 while(sf.atend() == false)
1156 u32 x = stoi(sf.next(","));
1157 u32 y = stoi(sf.next("="));
1158 std::string filename = sf.next(":");
1159 infostream<<"Adding \""<<filename
1160 <<"\" to combined ("<<x<<","<<y<<")"
1162 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1165 core::dimension2d<u32> dim = img->getDimension();
1166 infostream<<"Size "<<dim.Width
1167 <<"x"<<dim.Height<<std::endl;
1168 core::position2d<s32> pos_base(x, y);
1169 video::IImage *img2 =
1170 driver->createImage(video::ECF_A8R8G8B8, dim);
1173 /*img2->copyToWithAlpha(baseimg, pos_base,
1174 core::rect<s32>(v2s32(0,0), dim),
1175 video::SColor(255,255,255,255),
1177 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1182 infostream<<"img==NULL"<<std::endl;
1189 else if(part_of_name.substr(0,9) == "[brighten")
1193 errorstream<<"generateImage(): baseimg==NULL "
1194 <<"for part_of_name=\""<<part_of_name
1195 <<"\", cancelling."<<std::endl;
1203 Make image completely opaque.
1204 Used for the leaves texture when in old leaves mode, so
1205 that the transparent parts don't look completely black
1206 when simple alpha channel is used for rendering.
1208 else if(part_of_name.substr(0,8) == "[noalpha")
1212 errorstream<<"generateImage(): baseimg==NULL "
1213 <<"for part_of_name=\""<<part_of_name
1214 <<"\", cancelling."<<std::endl;
1218 core::dimension2d<u32> dim = baseimg->getDimension();
1220 // Set alpha to full
1221 for(u32 y=0; y<dim.Height; y++)
1222 for(u32 x=0; x<dim.Width; x++)
1224 video::SColor c = baseimg->getPixel(x,y);
1226 baseimg->setPixel(x,y,c);
1231 Convert one color to transparent.
1233 else if(part_of_name.substr(0,11) == "[makealpha:")
1237 errorstream<<"generateImage(): baseimg==NULL "
1238 <<"for part_of_name=\""<<part_of_name
1239 <<"\", cancelling."<<std::endl;
1243 Strfnd sf(part_of_name.substr(11));
1244 u32 r1 = stoi(sf.next(","));
1245 u32 g1 = stoi(sf.next(","));
1246 u32 b1 = stoi(sf.next(""));
1247 std::string filename = sf.next("");
1249 core::dimension2d<u32> dim = baseimg->getDimension();
1251 /*video::IImage *oldbaseimg = baseimg;
1252 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1253 oldbaseimg->copyTo(baseimg);
1254 oldbaseimg->drop();*/
1256 // Set alpha to full
1257 for(u32 y=0; y<dim.Height; y++)
1258 for(u32 x=0; x<dim.Width; x++)
1260 video::SColor c = baseimg->getPixel(x,y);
1262 u32 g = c.getGreen();
1263 u32 b = c.getBlue();
1264 if(!(r == r1 && g == g1 && b == b1))
1267 baseimg->setPixel(x,y,c);
1272 Rotates and/or flips the image.
1274 N can be a number (between 0 and 7) or a transform name.
1275 Rotations are counter-clockwise.
1277 1 R90 rotate by 90 degrees
1278 2 R180 rotate by 180 degrees
1279 3 R270 rotate by 270 degrees
1281 5 FXR90 flip X then rotate by 90 degrees
1283 7 FYR90 flip Y then rotate by 90 degrees
1285 Note: Transform names can be concatenated to produce
1286 their product (applies the first then the second).
1287 The resulting transform will be equivalent to one of the
1288 eight existing ones, though (see: dihedral group).
1290 else if(part_of_name.substr(0,10) == "[transform")
1294 errorstream<<"generateImage(): baseimg==NULL "
1295 <<"for part_of_name=\""<<part_of_name
1296 <<"\", cancelling."<<std::endl;
1300 u32 transform = parseImageTransform(part_of_name.substr(10));
1301 core::dimension2d<u32> dim = imageTransformDimension(
1302 transform, baseimg->getDimension());
1303 video::IImage *image = driver->createImage(
1304 baseimg->getColorFormat(), dim);
1306 imageTransform(transform, baseimg, image);
1311 [inventorycube{topimage{leftimage{rightimage
1312 In every subimage, replace ^ with &.
1313 Create an "inventory cube".
1314 NOTE: This should be used only on its own.
1315 Example (a grass block (not actually used in game):
1316 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1318 else if(part_of_name.substr(0,14) == "[inventorycube")
1322 errorstream<<"generateImage(): baseimg!=NULL "
1323 <<"for part_of_name=\""<<part_of_name
1324 <<"\", cancelling."<<std::endl;
1328 str_replace_char(part_of_name, '&', '^');
1329 Strfnd sf(part_of_name);
1331 std::string imagename_top = sf.next("{");
1332 std::string imagename_left = sf.next("{");
1333 std::string imagename_right = sf.next("{");
1335 // Generate images for the faces of the cube
1336 video::IImage *img_top =
1337 generateImageFromScratch(imagename_top);
1338 video::IImage *img_left =
1339 generateImageFromScratch(imagename_left);
1340 video::IImage *img_right =
1341 generateImageFromScratch(imagename_right);
1342 assert(img_top && img_left && img_right);
1344 // Create textures from images
1345 video::ITexture *texture_top = driver->addTexture(
1346 (imagename_top + "__temp__").c_str(), img_top);
1347 video::ITexture *texture_left = driver->addTexture(
1348 (imagename_left + "__temp__").c_str(), img_left);
1349 video::ITexture *texture_right = driver->addTexture(
1350 (imagename_right + "__temp__").c_str(), img_right);
1351 assert(texture_top && texture_left && texture_right);
1359 Draw a cube mesh into a render target texture
1361 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1362 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1363 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1364 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1365 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1366 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1367 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1368 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1370 TextureFromMeshParams params;
1372 params.dim.set(64, 64);
1373 params.rtt_texture_name = part_of_name + "_RTT";
1374 // We will delete the rtt texture ourselves
1375 params.delete_texture_on_shutdown = false;
1376 params.camera_position.set(0, 1.0, -1.5);
1377 params.camera_position.rotateXZBy(45);
1378 params.camera_lookat.set(0, 0, 0);
1379 // Set orthogonal projection
1380 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1381 1.65, 1.65, 0, 100);
1383 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1384 params.light_position.set(10, 100, -50);
1385 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1386 params.light_radius = 1000;
1388 video::ITexture *rtt = generateTextureFromMesh(params);
1393 // Free textures of images
1394 driver->removeTexture(texture_top);
1395 driver->removeTexture(texture_left);
1396 driver->removeTexture(texture_right);
1400 baseimg = generateImageFromScratch(imagename_top);
1404 // Create image of render target
1405 video::IImage *image = driver->createImage(rtt, v2s32(0,0), params.dim);
1409 driver->removeTexture(rtt);
1411 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1415 image->copyTo(baseimg);
1420 [lowpart:percent:filename
1421 Adds the lower part of a texture
1423 else if(part_of_name.substr(0,9) == "[lowpart:")
1425 Strfnd sf(part_of_name);
1427 u32 percent = stoi(sf.next(":"));
1428 std::string filename = sf.next(":");
1429 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1432 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1433 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1436 core::dimension2d<u32> dim = img->getDimension();
1437 core::position2d<s32> pos_base(0, 0);
1438 video::IImage *img2 =
1439 driver->createImage(video::ECF_A8R8G8B8, dim);
1442 core::position2d<s32> clippos(0, 0);
1443 clippos.Y = dim.Height * (100-percent) / 100;
1444 core::dimension2d<u32> clipdim = dim;
1445 clipdim.Height = clipdim.Height * percent / 100 + 1;
1446 core::rect<s32> cliprect(clippos, clipdim);
1447 img2->copyToWithAlpha(baseimg, pos_base,
1448 core::rect<s32>(v2s32(0,0), dim),
1449 video::SColor(255,255,255,255),
1456 Crops a frame of a vertical animation.
1457 N = frame count, I = frame index
1459 else if(part_of_name.substr(0,15) == "[verticalframe:")
1461 Strfnd sf(part_of_name);
1463 u32 frame_count = stoi(sf.next(":"));
1464 u32 frame_index = stoi(sf.next(":"));
1466 if(baseimg == NULL){
1467 errorstream<<"generateImage(): baseimg!=NULL "
1468 <<"for part_of_name=\""<<part_of_name
1469 <<"\", cancelling."<<std::endl;
1473 v2u32 frame_size = baseimg->getDimension();
1474 frame_size.Y /= frame_count;
1476 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1479 errorstream<<"generateImage(): Could not create image "
1480 <<"for part_of_name=\""<<part_of_name
1481 <<"\", cancelling."<<std::endl;
1485 // Fill target image with transparency
1486 img->fill(video::SColor(0,0,0,0));
1488 core::dimension2d<u32> dim = frame_size;
1489 core::position2d<s32> pos_dst(0, 0);
1490 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1491 baseimg->copyToWithAlpha(img, pos_dst,
1492 core::rect<s32>(pos_src, dim),
1493 video::SColor(255,255,255,255),
1501 errorstream<<"generateImage(): Invalid "
1502 " modification: \""<<part_of_name<<"\""<<std::endl;
1509 void overlay(video::IImage *image, video::IImage *overlay)
1512 Copy overlay to image, taking alpha into account.
1513 Where image is transparent, don't copy from overlay.
1514 Images sizes must be identical.
1516 if(image == NULL || overlay == NULL)
1519 core::dimension2d<u32> dim = image->getDimension();
1520 core::dimension2d<u32> dim_overlay = overlay->getDimension();
1521 assert(dim == dim_overlay);
1523 for(u32 y=0; y<dim.Height; y++)
1524 for(u32 x=0; x<dim.Width; x++)
1526 video::SColor c1 = image->getPixel(x,y);
1527 video::SColor c2 = overlay->getPixel(x,y);
1528 u32 a1 = c1.getAlpha();
1529 u32 a2 = c2.getAlpha();
1530 if(a1 == 255 && a2 != 0)
1532 c1.setRed((c1.getRed()*(255-a2) + c2.getRed()*a2)/255);
1533 c1.setGreen((c1.getGreen()*(255-a2) + c2.getGreen()*a2)/255);
1534 c1.setBlue((c1.getBlue()*(255-a2) + c2.getBlue()*a2)/255);
1536 image->setPixel(x,y,c1);
1541 Draw an image on top of an another one, using the alpha channel of the
1544 This exists because IImage::copyToWithAlpha() doesn't seem to always
1547 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1548 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1550 for(u32 y0=0; y0<size.Y; y0++)
1551 for(u32 x0=0; x0<size.X; x0++)
1553 s32 src_x = src_pos.X + x0;
1554 s32 src_y = src_pos.Y + y0;
1555 s32 dst_x = dst_pos.X + x0;
1556 s32 dst_y = dst_pos.Y + y0;
1557 video::SColor src_c = src->getPixel(src_x, src_y);
1558 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1559 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1560 dst->setPixel(dst_x, dst_y, dst_c);
1564 void brighten(video::IImage *image)
1569 core::dimension2d<u32> dim = image->getDimension();
1571 for(u32 y=0; y<dim.Height; y++)
1572 for(u32 x=0; x<dim.Width; x++)
1574 video::SColor c = image->getPixel(x,y);
1575 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1576 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1577 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1578 image->setPixel(x,y,c);
1582 u32 parseImageTransform(const std::string& s)
1584 int total_transform = 0;
1586 std::string transform_names[8];
1587 transform_names[0] = "i";
1588 transform_names[1] = "r90";
1589 transform_names[2] = "r180";
1590 transform_names[3] = "r270";
1591 transform_names[4] = "fx";
1592 transform_names[6] = "fy";
1594 std::size_t pos = 0;
1595 while(pos < s.size())
1598 for(int i = 0; i <= 7; ++i)
1600 const std::string &name_i = transform_names[i];
1602 if(s[pos] == ('0' + i))
1608 else if(!(name_i.empty()) &&
1609 lowercase(s.substr(pos, name_i.size())) == name_i)
1612 pos += name_i.size();
1619 // Multiply total_transform and transform in the group D4
1622 new_total = (transform + total_transform) % 4;
1624 new_total = (transform - total_transform + 8) % 4;
1625 if((transform >= 4) ^ (total_transform >= 4))
1628 total_transform = new_total;
1630 return total_transform;
1633 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1635 if(transform % 2 == 0)
1638 return core::dimension2d<u32>(dim.Height, dim.Width);
1641 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1643 if(src == NULL || dst == NULL)
1646 core::dimension2d<u32> srcdim = src->getDimension();
1647 core::dimension2d<u32> dstdim = dst->getDimension();
1649 assert(dstdim == imageTransformDimension(transform, srcdim));
1650 assert(transform >= 0 && transform <= 7);
1653 Compute the transformation from source coordinates (sx,sy)
1654 to destination coordinates (dx,dy).
1658 if(transform == 0) // identity
1659 sxn = 0, syn = 2; // sx = dx, sy = dy
1660 else if(transform == 1) // rotate by 90 degrees ccw
1661 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1662 else if(transform == 2) // rotate by 180 degrees
1663 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1664 else if(transform == 3) // rotate by 270 degrees ccw
1665 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1666 else if(transform == 4) // flip x
1667 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1668 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1669 sxn = 2, syn = 0; // sx = dy, sy = dx
1670 else if(transform == 6) // flip y
1671 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1672 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1673 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1675 for(u32 dy=0; dy<dstdim.Height; dy++)
1676 for(u32 dx=0; dx<dstdim.Width; dx++)
1678 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1679 u32 sx = entries[sxn];
1680 u32 sy = entries[syn];
1681 video::SColor c = src->getPixel(sx,sy);
1682 dst->setPixel(dx,dy,c);