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 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 // Draw an image on top of an another one, using the alpha channel of the
548 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
549 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
551 // Like blit_with_alpha, but only modifies destination pixels that
553 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
554 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
556 // Draw or overlay a crack
557 static void draw_crack(video::IImage *crack, video::IImage *dst,
558 bool use_overlay, u32 frame_count, u32 progression,
559 video::IVideoDriver *driver);
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<<"\""
1044 Adds a cracking texture
1045 N = animation frame count, P = crack progression
1047 if(part_of_name.substr(0,6) == "[crack")
1051 errorstream<<"generateImage(): baseimg==NULL "
1052 <<"for part_of_name=\""<<part_of_name
1053 <<"\", cancelling."<<std::endl;
1057 // Crack image number and overlay option
1058 bool use_overlay = (part_of_name[6] == 'o');
1059 Strfnd sf(part_of_name);
1061 u32 frame_count = stoi(sf.next(":"));
1062 u32 progression = stoi(sf.next(":"));
1067 It is an image with a number of cracking stages
1070 video::IImage *img_crack = m_sourcecache.getOrLoad(
1071 "crack_anylength.png", m_device);
1073 if(img_crack && progression >= 0)
1075 draw_crack(img_crack, baseimg,
1076 use_overlay, frame_count,
1077 progression, driver);
1082 [combine:WxH:X,Y=filename:X,Y=filename2
1083 Creates a bigger texture from an amount of smaller ones
1085 else if(part_of_name.substr(0,8) == "[combine")
1087 Strfnd sf(part_of_name);
1089 u32 w0 = stoi(sf.next("x"));
1090 u32 h0 = stoi(sf.next(":"));
1091 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1092 core::dimension2d<u32> dim(w0,h0);
1095 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1096 baseimg->fill(video::SColor(0,0,0,0));
1098 while(sf.atend() == false)
1100 u32 x = stoi(sf.next(","));
1101 u32 y = stoi(sf.next("="));
1102 std::string filename = sf.next(":");
1103 infostream<<"Adding \""<<filename
1104 <<"\" to combined ("<<x<<","<<y<<")"
1106 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1109 core::dimension2d<u32> dim = img->getDimension();
1110 infostream<<"Size "<<dim.Width
1111 <<"x"<<dim.Height<<std::endl;
1112 core::position2d<s32> pos_base(x, y);
1113 video::IImage *img2 =
1114 driver->createImage(video::ECF_A8R8G8B8, dim);
1117 /*img2->copyToWithAlpha(baseimg, pos_base,
1118 core::rect<s32>(v2s32(0,0), dim),
1119 video::SColor(255,255,255,255),
1121 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1126 infostream<<"img==NULL"<<std::endl;
1133 else if(part_of_name.substr(0,9) == "[brighten")
1137 errorstream<<"generateImage(): baseimg==NULL "
1138 <<"for part_of_name=\""<<part_of_name
1139 <<"\", cancelling."<<std::endl;
1147 Make image completely opaque.
1148 Used for the leaves texture when in old leaves mode, so
1149 that the transparent parts don't look completely black
1150 when simple alpha channel is used for rendering.
1152 else if(part_of_name.substr(0,8) == "[noalpha")
1156 errorstream<<"generateImage(): baseimg==NULL "
1157 <<"for part_of_name=\""<<part_of_name
1158 <<"\", cancelling."<<std::endl;
1162 core::dimension2d<u32> dim = baseimg->getDimension();
1164 // Set alpha to full
1165 for(u32 y=0; y<dim.Height; y++)
1166 for(u32 x=0; x<dim.Width; x++)
1168 video::SColor c = baseimg->getPixel(x,y);
1170 baseimg->setPixel(x,y,c);
1175 Convert one color to transparent.
1177 else if(part_of_name.substr(0,11) == "[makealpha:")
1181 errorstream<<"generateImage(): baseimg==NULL "
1182 <<"for part_of_name=\""<<part_of_name
1183 <<"\", cancelling."<<std::endl;
1187 Strfnd sf(part_of_name.substr(11));
1188 u32 r1 = stoi(sf.next(","));
1189 u32 g1 = stoi(sf.next(","));
1190 u32 b1 = stoi(sf.next(""));
1191 std::string filename = sf.next("");
1193 core::dimension2d<u32> dim = baseimg->getDimension();
1195 /*video::IImage *oldbaseimg = baseimg;
1196 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1197 oldbaseimg->copyTo(baseimg);
1198 oldbaseimg->drop();*/
1200 // Set alpha to full
1201 for(u32 y=0; y<dim.Height; y++)
1202 for(u32 x=0; x<dim.Width; x++)
1204 video::SColor c = baseimg->getPixel(x,y);
1206 u32 g = c.getGreen();
1207 u32 b = c.getBlue();
1208 if(!(r == r1 && g == g1 && b == b1))
1211 baseimg->setPixel(x,y,c);
1216 Rotates and/or flips the image.
1218 N can be a number (between 0 and 7) or a transform name.
1219 Rotations are counter-clockwise.
1221 1 R90 rotate by 90 degrees
1222 2 R180 rotate by 180 degrees
1223 3 R270 rotate by 270 degrees
1225 5 FXR90 flip X then rotate by 90 degrees
1227 7 FYR90 flip Y then rotate by 90 degrees
1229 Note: Transform names can be concatenated to produce
1230 their product (applies the first then the second).
1231 The resulting transform will be equivalent to one of the
1232 eight existing ones, though (see: dihedral group).
1234 else if(part_of_name.substr(0,10) == "[transform")
1238 errorstream<<"generateImage(): baseimg==NULL "
1239 <<"for part_of_name=\""<<part_of_name
1240 <<"\", cancelling."<<std::endl;
1244 u32 transform = parseImageTransform(part_of_name.substr(10));
1245 core::dimension2d<u32> dim = imageTransformDimension(
1246 transform, baseimg->getDimension());
1247 video::IImage *image = driver->createImage(
1248 baseimg->getColorFormat(), dim);
1250 imageTransform(transform, baseimg, image);
1255 [inventorycube{topimage{leftimage{rightimage
1256 In every subimage, replace ^ with &.
1257 Create an "inventory cube".
1258 NOTE: This should be used only on its own.
1259 Example (a grass block (not actually used in game):
1260 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1262 else if(part_of_name.substr(0,14) == "[inventorycube")
1266 errorstream<<"generateImage(): baseimg!=NULL "
1267 <<"for part_of_name=\""<<part_of_name
1268 <<"\", cancelling."<<std::endl;
1272 str_replace_char(part_of_name, '&', '^');
1273 Strfnd sf(part_of_name);
1275 std::string imagename_top = sf.next("{");
1276 std::string imagename_left = sf.next("{");
1277 std::string imagename_right = sf.next("{");
1279 // Generate images for the faces of the cube
1280 video::IImage *img_top =
1281 generateImageFromScratch(imagename_top);
1282 video::IImage *img_left =
1283 generateImageFromScratch(imagename_left);
1284 video::IImage *img_right =
1285 generateImageFromScratch(imagename_right);
1286 assert(img_top && img_left && img_right);
1288 // Create textures from images
1289 video::ITexture *texture_top = driver->addTexture(
1290 (imagename_top + "__temp__").c_str(), img_top);
1291 video::ITexture *texture_left = driver->addTexture(
1292 (imagename_left + "__temp__").c_str(), img_left);
1293 video::ITexture *texture_right = driver->addTexture(
1294 (imagename_right + "__temp__").c_str(), img_right);
1295 assert(texture_top && texture_left && texture_right);
1303 Draw a cube mesh into a render target texture
1305 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1306 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1307 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1308 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1309 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1310 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1311 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1312 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1314 TextureFromMeshParams params;
1316 params.dim.set(64, 64);
1317 params.rtt_texture_name = part_of_name + "_RTT";
1318 // We will delete the rtt texture ourselves
1319 params.delete_texture_on_shutdown = false;
1320 params.camera_position.set(0, 1.0, -1.5);
1321 params.camera_position.rotateXZBy(45);
1322 params.camera_lookat.set(0, 0, 0);
1323 // Set orthogonal projection
1324 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1325 1.65, 1.65, 0, 100);
1327 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1328 params.light_position.set(10, 100, -50);
1329 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1330 params.light_radius = 1000;
1332 video::ITexture *rtt = generateTextureFromMesh(params);
1337 // Free textures of images
1338 driver->removeTexture(texture_top);
1339 driver->removeTexture(texture_left);
1340 driver->removeTexture(texture_right);
1344 baseimg = generateImageFromScratch(imagename_top);
1348 // Create image of render target
1349 video::IImage *image = driver->createImage(rtt, v2s32(0,0), params.dim);
1353 driver->removeTexture(rtt);
1355 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1359 image->copyTo(baseimg);
1364 [lowpart:percent:filename
1365 Adds the lower part of a texture
1367 else if(part_of_name.substr(0,9) == "[lowpart:")
1369 Strfnd sf(part_of_name);
1371 u32 percent = stoi(sf.next(":"));
1372 std::string filename = sf.next(":");
1373 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1376 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1377 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1380 core::dimension2d<u32> dim = img->getDimension();
1381 core::position2d<s32> pos_base(0, 0);
1382 video::IImage *img2 =
1383 driver->createImage(video::ECF_A8R8G8B8, dim);
1386 core::position2d<s32> clippos(0, 0);
1387 clippos.Y = dim.Height * (100-percent) / 100;
1388 core::dimension2d<u32> clipdim = dim;
1389 clipdim.Height = clipdim.Height * percent / 100 + 1;
1390 core::rect<s32> cliprect(clippos, clipdim);
1391 img2->copyToWithAlpha(baseimg, pos_base,
1392 core::rect<s32>(v2s32(0,0), dim),
1393 video::SColor(255,255,255,255),
1400 Crops a frame of a vertical animation.
1401 N = frame count, I = frame index
1403 else if(part_of_name.substr(0,15) == "[verticalframe:")
1405 Strfnd sf(part_of_name);
1407 u32 frame_count = stoi(sf.next(":"));
1408 u32 frame_index = stoi(sf.next(":"));
1410 if(baseimg == NULL){
1411 errorstream<<"generateImage(): baseimg!=NULL "
1412 <<"for part_of_name=\""<<part_of_name
1413 <<"\", cancelling."<<std::endl;
1417 v2u32 frame_size = baseimg->getDimension();
1418 frame_size.Y /= frame_count;
1420 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1423 errorstream<<"generateImage(): Could not create image "
1424 <<"for part_of_name=\""<<part_of_name
1425 <<"\", cancelling."<<std::endl;
1429 // Fill target image with transparency
1430 img->fill(video::SColor(0,0,0,0));
1432 core::dimension2d<u32> dim = frame_size;
1433 core::position2d<s32> pos_dst(0, 0);
1434 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1435 baseimg->copyToWithAlpha(img, pos_dst,
1436 core::rect<s32>(pos_src, dim),
1437 video::SColor(255,255,255,255),
1445 errorstream<<"generateImage(): Invalid "
1446 " modification: \""<<part_of_name<<"\""<<std::endl;
1454 Draw an image on top of an another one, using the alpha channel of the
1457 This exists because IImage::copyToWithAlpha() doesn't seem to always
1460 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1461 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1463 for(u32 y0=0; y0<size.Y; y0++)
1464 for(u32 x0=0; x0<size.X; x0++)
1466 s32 src_x = src_pos.X + x0;
1467 s32 src_y = src_pos.Y + y0;
1468 s32 dst_x = dst_pos.X + x0;
1469 s32 dst_y = dst_pos.Y + y0;
1470 video::SColor src_c = src->getPixel(src_x, src_y);
1471 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1472 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1473 dst->setPixel(dst_x, dst_y, dst_c);
1478 Draw an image on top of an another one, using the alpha channel of the
1479 source image; only modify fully opaque pixels in destinaion
1481 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1482 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1484 for(u32 y0=0; y0<size.Y; y0++)
1485 for(u32 x0=0; x0<size.X; x0++)
1487 s32 src_x = src_pos.X + x0;
1488 s32 src_y = src_pos.Y + y0;
1489 s32 dst_x = dst_pos.X + x0;
1490 s32 dst_y = dst_pos.Y + y0;
1491 video::SColor src_c = src->getPixel(src_x, src_y);
1492 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1493 if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1495 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1496 dst->setPixel(dst_x, dst_y, dst_c);
1501 static void draw_crack(video::IImage *crack, video::IImage *dst,
1502 bool use_overlay, u32 frame_count, u32 progression,
1503 video::IVideoDriver *driver)
1505 // Dimension of destination image
1506 core::dimension2d<u32> dim_dst = dst->getDimension();
1507 // Dimension of original image
1508 core::dimension2d<u32> dim_crack = crack->getDimension();
1509 // Count of crack stages
1510 u32 crack_count = dim_crack.Height / dim_crack.Width;
1511 // Limit frame_count
1512 if(frame_count > dim_dst.Height)
1513 frame_count = dim_dst.Height;
1514 if(frame_count == 0)
1516 // Limit progression
1517 if(progression > crack_count-1)
1518 progression = crack_count-1;
1519 // Dimension of a single crack stage
1520 core::dimension2d<u32> dim_crack_cropped(
1524 // Dimension of the scaled crack stage,
1525 // which is the same as the dimension of a single destination frame
1526 core::dimension2d<u32> dim_crack_scaled(
1528 dim_dst.Height / frame_count
1530 // Create cropped and scaled crack images
1531 video::IImage *crack_cropped = driver->createImage(
1532 video::ECF_A8R8G8B8, dim_crack_cropped);
1533 video::IImage *crack_scaled = driver->createImage(
1534 video::ECF_A8R8G8B8, dim_crack_scaled);
1536 if(crack_cropped && crack_scaled)
1539 v2s32 pos_crack(0, progression*dim_crack.Width);
1540 crack->copyTo(crack_cropped,
1542 core::rect<s32>(pos_crack, dim_crack_cropped));
1543 // Scale crack image by copying
1544 crack_cropped->copyToScaling(crack_scaled);
1545 // Copy or overlay crack image onto each frame
1546 for(u32 i = 0; i < frame_count; ++i)
1548 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1551 blit_with_alpha_overlay(crack_scaled, dst,
1552 v2s32(0,0), dst_pos,
1557 blit_with_alpha(crack_scaled, dst,
1558 v2s32(0,0), dst_pos,
1565 crack_scaled->drop();
1568 crack_cropped->drop();
1571 void brighten(video::IImage *image)
1576 core::dimension2d<u32> dim = image->getDimension();
1578 for(u32 y=0; y<dim.Height; y++)
1579 for(u32 x=0; x<dim.Width; x++)
1581 video::SColor c = image->getPixel(x,y);
1582 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1583 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1584 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1585 image->setPixel(x,y,c);
1589 u32 parseImageTransform(const std::string& s)
1591 int total_transform = 0;
1593 std::string transform_names[8];
1594 transform_names[0] = "i";
1595 transform_names[1] = "r90";
1596 transform_names[2] = "r180";
1597 transform_names[3] = "r270";
1598 transform_names[4] = "fx";
1599 transform_names[6] = "fy";
1601 std::size_t pos = 0;
1602 while(pos < s.size())
1605 for(int i = 0; i <= 7; ++i)
1607 const std::string &name_i = transform_names[i];
1609 if(s[pos] == ('0' + i))
1615 else if(!(name_i.empty()) &&
1616 lowercase(s.substr(pos, name_i.size())) == name_i)
1619 pos += name_i.size();
1626 // Multiply total_transform and transform in the group D4
1629 new_total = (transform + total_transform) % 4;
1631 new_total = (transform - total_transform + 8) % 4;
1632 if((transform >= 4) ^ (total_transform >= 4))
1635 total_transform = new_total;
1637 return total_transform;
1640 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1642 if(transform % 2 == 0)
1645 return core::dimension2d<u32>(dim.Height, dim.Width);
1648 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1650 if(src == NULL || dst == NULL)
1653 core::dimension2d<u32> srcdim = src->getDimension();
1654 core::dimension2d<u32> dstdim = dst->getDimension();
1656 assert(dstdim == imageTransformDimension(transform, srcdim));
1657 assert(transform >= 0 && transform <= 7);
1660 Compute the transformation from source coordinates (sx,sy)
1661 to destination coordinates (dx,dy).
1665 if(transform == 0) // identity
1666 sxn = 0, syn = 2; // sx = dx, sy = dy
1667 else if(transform == 1) // rotate by 90 degrees ccw
1668 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1669 else if(transform == 2) // rotate by 180 degrees
1670 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1671 else if(transform == 3) // rotate by 270 degrees ccw
1672 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1673 else if(transform == 4) // flip x
1674 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1675 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1676 sxn = 2, syn = 0; // sx = dy, sy = dx
1677 else if(transform == 6) // flip y
1678 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1679 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1680 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1682 for(u32 dy=0; dy<dstdim.Height; dy++)
1683 for(u32 dx=0; dx<dstdim.Width; dx++)
1685 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1686 u32 sx = entries[sxn];
1687 u32 sy = entries[syn];
1688 video::SColor c = src->getPixel(sx,sy);
1689 dst->setPixel(dx,dy,c);