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_main_thread = get_current_thread_id();
438 // Add a NULL TextureInfo as the first index, named ""
439 m_textureinfo_cache.push_back(TextureInfo(""));
440 m_name_to_id[""] = 0;
442 // Cache some settings
443 // Note: Since this is only done once, the game must be restarted
444 // for these settings to take effect
445 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
446 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
447 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
450 TextureSource::~TextureSource()
452 video::IVideoDriver* driver = m_device->getVideoDriver();
454 unsigned int textures_before = driver->getTextureCount();
456 for (std::vector<TextureInfo>::iterator iter =
457 m_textureinfo_cache.begin();
458 iter != m_textureinfo_cache.end(); iter++)
462 driver->removeTexture(iter->texture);
464 //cleanup source image
468 m_textureinfo_cache.clear();
470 for (std::list<video::ITexture*>::iterator iter =
471 m_texture_trash.begin(); iter != m_texture_trash.end();
474 video::ITexture *t = *iter;
476 //cleanup trashed texture
477 driver->removeTexture(t);
480 infostream << "~TextureSource() "<< textures_before << "/"
481 << driver->getTextureCount() << std::endl;
484 u32 TextureSource::getTextureId(const std::string &name)
486 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
490 See if texture already exists
492 JMutexAutoLock lock(m_textureinfo_cache_mutex);
493 std::map<std::string, u32>::iterator n;
494 n = m_name_to_id.find(name);
495 if(n != m_name_to_id.end())
504 if(get_current_thread_id() == m_main_thread)
506 return getTextureIdDirect(name);
510 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
512 // We're gonna ask the result to be put into here
513 static ResultQueue<std::string, u32, u8, u8> result_queue;
515 // Throw a request in
516 m_get_texture_queue.add(name, 0, 0, &result_queue);
518 /*infostream<<"Waiting for texture from main thread, name=\""
519 <<name<<"\""<<std::endl;*/
524 // Wait result for a second
525 GetResult<std::string, u32, u8, u8>
526 result = result_queue.pop_front(1000);
528 if (result.key == name) {
533 catch(ItemNotFoundException &e)
535 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
540 infostream<<"getTextureId(): Failed"<<std::endl;
545 // Draw an image on top of an another one, using the alpha channel of the
547 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
548 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
550 // Like blit_with_alpha, but only modifies destination pixels that
552 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
553 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
555 // Draw or overlay a crack
556 static void draw_crack(video::IImage *crack, video::IImage *dst,
557 bool use_overlay, s32 frame_count, s32 progression,
558 video::IVideoDriver *driver);
561 void brighten(video::IImage *image);
562 // Parse a transform name
563 u32 parseImageTransform(const std::string& s);
564 // Apply transform to image dimension
565 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
566 // Apply transform to image data
567 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
570 This method generates all the textures
572 u32 TextureSource::getTextureIdDirect(const std::string &name)
574 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
576 // Empty name means texture 0
579 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
584 Calling only allowed from main thread
586 if(get_current_thread_id() != m_main_thread)
588 errorstream<<"TextureSource::getTextureIdDirect() "
589 "called not from main thread"<<std::endl;
594 See if texture already exists
597 JMutexAutoLock lock(m_textureinfo_cache_mutex);
599 std::map<std::string, u32>::iterator n;
600 n = m_name_to_id.find(name);
601 if(n != m_name_to_id.end())
603 /*infostream<<"getTextureIdDirect(): \""<<name
604 <<"\" found in cache"<<std::endl;*/
609 /*infostream<<"getTextureIdDirect(): \""<<name
610 <<"\" NOT found in cache. Creating it."<<std::endl;*/
616 char separator = '^';
619 This is set to the id of the base image.
620 If left 0, there is no base image and a completely new image
623 u32 base_image_id = 0;
625 // Find last meta separator in name
626 s32 last_separator_position = -1;
627 for(s32 i=name.size()-1; i>=0; i--)
629 if(name[i] == separator)
631 last_separator_position = i;
636 If separator was found, construct the base name and make the
637 base image using a recursive call
639 std::string base_image_name;
640 if(last_separator_position != -1)
642 // Construct base name
643 base_image_name = name.substr(0, last_separator_position);
644 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
645 " to get base image of \""<<name<<"\" = \""
646 <<base_image_name<<"\""<<std::endl;*/
647 base_image_id = getTextureIdDirect(base_image_name);
650 //infostream<<"base_image_id="<<base_image_id<<std::endl;
652 video::IVideoDriver* driver = m_device->getVideoDriver();
655 video::ITexture *t = NULL;
658 An image will be built from files and then converted into a texture.
660 video::IImage *baseimg = NULL;
662 // If a base image was found, copy it to baseimg
663 if(base_image_id != 0)
665 JMutexAutoLock lock(m_textureinfo_cache_mutex);
667 TextureInfo *ti = &m_textureinfo_cache[base_image_id];
671 infostream<<"getTextureIdDirect(): WARNING: NULL image in "
672 <<"cache: \""<<base_image_name<<"\""
677 core::dimension2d<u32> dim = ti->img->getDimension();
679 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
683 v2s32(0,0), // position in target
684 core::rect<s32>(v2s32(0,0), dim) // from
687 /*infostream<<"getTextureIdDirect(): Loaded \""
688 <<base_image_name<<"\" from image cache"
694 Parse out the last part of the name of the image and act
698 std::string last_part_of_name = name.substr(last_separator_position+1);
699 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
701 // Generate image according to part of name
702 if(!generateImage(last_part_of_name, baseimg))
704 errorstream<<"getTextureIdDirect(): "
705 "failed to generate \""<<last_part_of_name<<"\""
709 // If no resulting image, print a warning
712 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
713 " create texture \""<<name<<"\""<<std::endl;
718 // Create texture from resulting image
719 t = driver->addTexture(name.c_str(), baseimg);
723 Add texture to caches (add NULL textures too)
726 JMutexAutoLock lock(m_textureinfo_cache_mutex);
728 u32 id = m_textureinfo_cache.size();
729 TextureInfo ti(name, t, baseimg);
730 m_textureinfo_cache.push_back(ti);
731 m_name_to_id[name] = id;
736 std::string TextureSource::getTextureName(u32 id)
738 JMutexAutoLock lock(m_textureinfo_cache_mutex);
740 if(id >= m_textureinfo_cache.size())
742 errorstream<<"TextureSource::getTextureName(): id="<<id
743 <<" >= m_textureinfo_cache.size()="
744 <<m_textureinfo_cache.size()<<std::endl;
748 return m_textureinfo_cache[id].name;
751 video::ITexture* TextureSource::getTexture(u32 id)
753 JMutexAutoLock lock(m_textureinfo_cache_mutex);
755 if(id >= m_textureinfo_cache.size())
758 return m_textureinfo_cache[id].texture;
761 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
763 u32 actual_id = getTextureId(name);
767 return getTexture(actual_id);
770 void TextureSource::processQueue()
775 //NOTE this is only thread safe for ONE consumer thread!
776 if(!m_get_texture_queue.empty())
778 GetRequest<std::string, u32, u8, u8>
779 request = m_get_texture_queue.pop();
781 /*infostream<<"TextureSource::processQueue(): "
782 <<"got texture request with "
783 <<"name=\""<<request.key<<"\""
786 m_get_texture_queue.pushResult(request,getTextureIdDirect(request.key));
790 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
792 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
794 assert(get_current_thread_id() == m_main_thread);
796 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
797 m_source_image_existence.set(name, true);
800 void TextureSource::rebuildImagesAndTextures()
802 JMutexAutoLock lock(m_textureinfo_cache_mutex);
804 video::IVideoDriver* driver = m_device->getVideoDriver();
807 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
808 TextureInfo *ti = &m_textureinfo_cache[i];
809 video::IImage *img = generateImageFromScratch(ti->name);
810 // Create texture from resulting image
811 video::ITexture *t = NULL;
813 t = driver->addTexture(ti->name.c_str(), img);
814 video::ITexture *t_old = ti->texture;
820 m_texture_trash.push_back(t_old);
824 video::ITexture* TextureSource::generateTextureFromMesh(
825 const TextureFromMeshParams ¶ms)
827 video::IVideoDriver *driver = m_device->getVideoDriver();
830 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
832 static bool warned = false;
835 errorstream<<"TextureSource::generateTextureFromMesh(): "
836 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
842 // Create render target texture
843 video::ITexture *rtt = driver->addRenderTargetTexture(
844 params.dim, params.rtt_texture_name.c_str(),
845 video::ECF_A8R8G8B8);
848 errorstream<<"TextureSource::generateTextureFromMesh(): "
849 <<"addRenderTargetTexture returned NULL."<<std::endl;
854 driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0));
856 // Get a scene manager
857 scene::ISceneManager *smgr_main = m_device->getSceneManager();
859 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
862 scene::IMeshSceneNode* meshnode = smgr->addMeshSceneNode(params.mesh, NULL, -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
863 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
864 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
865 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
866 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
867 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
869 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
870 params.camera_position, params.camera_lookat);
871 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
872 camera->setProjectionMatrix(params.camera_projection_matrix, false);
874 smgr->setAmbientLight(params.ambient_light);
875 smgr->addLightSceneNode(0,
876 params.light_position,
878 params.light_radius);
881 driver->beginScene(true, true, video::SColor(0,0,0,0));
885 // NOTE: The scene nodes should not be dropped, otherwise
886 // smgr->drop() segfaults
890 // Drop scene manager
893 // Unset render target
894 driver->setRenderTarget(0, false, true, 0);
896 if(params.delete_texture_on_shutdown)
897 m_texture_trash.push_back(rtt);
902 video::IImage* TextureSource::generateImageFromScratch(std::string name)
904 /*infostream<<"generateImageFromScratch(): "
905 "\""<<name<<"\""<<std::endl;*/
907 video::IVideoDriver *driver = m_device->getVideoDriver();
914 video::IImage *baseimg = NULL;
916 char separator = '^';
918 // Find last meta separator in name
919 s32 last_separator_position = name.find_last_of(separator);
922 If separator was found, construct the base name and make the
923 base image using a recursive call
925 std::string base_image_name;
926 if(last_separator_position != -1)
928 // Construct base name
929 base_image_name = name.substr(0, last_separator_position);
930 baseimg = generateImageFromScratch(base_image_name);
934 Parse out the last part of the name of the image and act
938 std::string last_part_of_name = name.substr(last_separator_position+1);
940 // Generate image according to part of name
941 if(!generateImage(last_part_of_name, baseimg))
943 errorstream<<"generateImageFromScratch(): "
944 "failed to generate \""<<last_part_of_name<<"\""
952 bool TextureSource::generateImage(std::string part_of_name, video::IImage *& baseimg)
954 video::IVideoDriver* driver = m_device->getVideoDriver();
957 // Stuff starting with [ are special commands
958 if(part_of_name.size() == 0 || part_of_name[0] != '[')
960 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
963 if (!driver->queryFeature(irr::video::EVDF_TEXTURE_NPOT)) {
964 core::dimension2d<u32> dim = image->getDimension();
967 if ((dim.Height %2 != 0) ||
968 (dim.Width %2 != 0)) {
969 infostream << "TextureSource::generateImage "
970 << part_of_name << " size npot2 x=" << dim.Width
971 << " y=" << dim.Height << std::endl;
977 if (part_of_name != "") {
978 if (part_of_name.find("_normal.png") == std::string::npos){
979 errorstream<<"generateImage(): Could not load image \""
980 <<part_of_name<<"\""<<" while building texture"<<std::endl;
981 errorstream<<"generateImage(): Creating a dummy"
982 <<" image for \""<<part_of_name<<"\""<<std::endl;
984 infostream<<"generateImage(): Could not load normal map \""
985 <<part_of_name<<"\""<<std::endl;
986 infostream<<"generateImage(): Creating a dummy"
987 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
991 // Just create a dummy image
992 //core::dimension2d<u32> dim(2,2);
993 core::dimension2d<u32> dim(1,1);
994 image = driver->createImage(video::ECF_A8R8G8B8, dim);
996 /*image->setPixel(0,0, video::SColor(255,255,0,0));
997 image->setPixel(1,0, video::SColor(255,0,255,0));
998 image->setPixel(0,1, video::SColor(255,0,0,255));
999 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1000 image->setPixel(0,0, video::SColor(255,myrand()%256,
1001 myrand()%256,myrand()%256));
1002 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1003 myrand()%256,myrand()%256));
1004 image->setPixel(0,1, video::SColor(255,myrand()%256,
1005 myrand()%256,myrand()%256));
1006 image->setPixel(1,1, video::SColor(255,myrand()%256,
1007 myrand()%256,myrand()%256));*/
1010 // If base image is NULL, load as base.
1013 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1015 Copy it this way to get an alpha channel.
1016 Otherwise images with alpha cannot be blitted on
1017 images that don't have alpha in the original file.
1019 core::dimension2d<u32> dim = image->getDimension();
1020 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1021 image->copyTo(baseimg);
1023 // Else blit on base.
1026 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1027 // Size of the copied area
1028 core::dimension2d<u32> dim = image->getDimension();
1029 //core::dimension2d<u32> dim(16,16);
1030 // Position to copy the blitted to in the base image
1031 core::position2d<s32> pos_to(0,0);
1032 // Position to copy the blitted from in the blitted image
1033 core::position2d<s32> pos_from(0,0);
1035 /*image->copyToWithAlpha(baseimg, pos_to,
1036 core::rect<s32>(pos_from, dim),
1037 video::SColor(255,255,255,255),
1039 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1046 // A special texture modification
1048 /*infostream<<"generateImage(): generating special "
1049 <<"modification \""<<part_of_name<<"\""
1055 Adds a cracking texture
1056 N = animation frame count, P = crack progression
1058 if(part_of_name.substr(0,6) == "[crack")
1062 errorstream<<"generateImage(): baseimg==NULL "
1063 <<"for part_of_name=\""<<part_of_name
1064 <<"\", cancelling."<<std::endl;
1068 // Crack image number and overlay option
1069 bool use_overlay = (part_of_name[6] == 'o');
1070 Strfnd sf(part_of_name);
1072 s32 frame_count = stoi(sf.next(":"));
1073 s32 progression = stoi(sf.next(":"));
1078 It is an image with a number of cracking stages
1081 video::IImage *img_crack = m_sourcecache.getOrLoad(
1082 "crack_anylength.png", m_device);
1084 if(img_crack && progression >= 0)
1086 draw_crack(img_crack, baseimg,
1087 use_overlay, frame_count,
1088 progression, driver);
1093 [combine:WxH:X,Y=filename:X,Y=filename2
1094 Creates a bigger texture from an amount of smaller ones
1096 else if(part_of_name.substr(0,8) == "[combine")
1098 Strfnd sf(part_of_name);
1100 u32 w0 = stoi(sf.next("x"));
1101 u32 h0 = stoi(sf.next(":"));
1102 infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1103 core::dimension2d<u32> dim(w0,h0);
1106 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1107 baseimg->fill(video::SColor(0,0,0,0));
1109 while(sf.atend() == false)
1111 u32 x = stoi(sf.next(","));
1112 u32 y = stoi(sf.next("="));
1113 std::string filename = sf.next(":");
1114 infostream<<"Adding \""<<filename
1115 <<"\" to combined ("<<x<<","<<y<<")"
1117 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1120 core::dimension2d<u32> dim = img->getDimension();
1121 infostream<<"Size "<<dim.Width
1122 <<"x"<<dim.Height<<std::endl;
1123 core::position2d<s32> pos_base(x, y);
1124 video::IImage *img2 =
1125 driver->createImage(video::ECF_A8R8G8B8, dim);
1128 /*img2->copyToWithAlpha(baseimg, pos_base,
1129 core::rect<s32>(v2s32(0,0), dim),
1130 video::SColor(255,255,255,255),
1132 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1137 infostream<<"img==NULL"<<std::endl;
1144 else if(part_of_name.substr(0,9) == "[brighten")
1148 errorstream<<"generateImage(): baseimg==NULL "
1149 <<"for part_of_name=\""<<part_of_name
1150 <<"\", cancelling."<<std::endl;
1158 Make image completely opaque.
1159 Used for the leaves texture when in old leaves mode, so
1160 that the transparent parts don't look completely black
1161 when simple alpha channel is used for rendering.
1163 else if(part_of_name.substr(0,8) == "[noalpha")
1167 errorstream<<"generateImage(): baseimg==NULL "
1168 <<"for part_of_name=\""<<part_of_name
1169 <<"\", cancelling."<<std::endl;
1173 core::dimension2d<u32> dim = baseimg->getDimension();
1175 // Set alpha to full
1176 for(u32 y=0; y<dim.Height; y++)
1177 for(u32 x=0; x<dim.Width; x++)
1179 video::SColor c = baseimg->getPixel(x,y);
1181 baseimg->setPixel(x,y,c);
1186 Convert one color to transparent.
1188 else if(part_of_name.substr(0,11) == "[makealpha:")
1192 errorstream<<"generateImage(): baseimg==NULL "
1193 <<"for part_of_name=\""<<part_of_name
1194 <<"\", cancelling."<<std::endl;
1198 Strfnd sf(part_of_name.substr(11));
1199 u32 r1 = stoi(sf.next(","));
1200 u32 g1 = stoi(sf.next(","));
1201 u32 b1 = stoi(sf.next(""));
1202 std::string filename = sf.next("");
1204 core::dimension2d<u32> dim = baseimg->getDimension();
1206 /*video::IImage *oldbaseimg = baseimg;
1207 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1208 oldbaseimg->copyTo(baseimg);
1209 oldbaseimg->drop();*/
1211 // Set alpha to full
1212 for(u32 y=0; y<dim.Height; y++)
1213 for(u32 x=0; x<dim.Width; x++)
1215 video::SColor c = baseimg->getPixel(x,y);
1217 u32 g = c.getGreen();
1218 u32 b = c.getBlue();
1219 if(!(r == r1 && g == g1 && b == b1))
1222 baseimg->setPixel(x,y,c);
1227 Rotates and/or flips the image.
1229 N can be a number (between 0 and 7) or a transform name.
1230 Rotations are counter-clockwise.
1232 1 R90 rotate by 90 degrees
1233 2 R180 rotate by 180 degrees
1234 3 R270 rotate by 270 degrees
1236 5 FXR90 flip X then rotate by 90 degrees
1238 7 FYR90 flip Y then rotate by 90 degrees
1240 Note: Transform names can be concatenated to produce
1241 their product (applies the first then the second).
1242 The resulting transform will be equivalent to one of the
1243 eight existing ones, though (see: dihedral group).
1245 else if(part_of_name.substr(0,10) == "[transform")
1249 errorstream<<"generateImage(): baseimg==NULL "
1250 <<"for part_of_name=\""<<part_of_name
1251 <<"\", cancelling."<<std::endl;
1255 u32 transform = parseImageTransform(part_of_name.substr(10));
1256 core::dimension2d<u32> dim = imageTransformDimension(
1257 transform, baseimg->getDimension());
1258 video::IImage *image = driver->createImage(
1259 baseimg->getColorFormat(), dim);
1261 imageTransform(transform, baseimg, image);
1266 [inventorycube{topimage{leftimage{rightimage
1267 In every subimage, replace ^ with &.
1268 Create an "inventory cube".
1269 NOTE: This should be used only on its own.
1270 Example (a grass block (not actually used in game):
1271 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1273 else if(part_of_name.substr(0,14) == "[inventorycube")
1277 errorstream<<"generateImage(): baseimg!=NULL "
1278 <<"for part_of_name=\""<<part_of_name
1279 <<"\", cancelling."<<std::endl;
1283 str_replace_char(part_of_name, '&', '^');
1284 Strfnd sf(part_of_name);
1286 std::string imagename_top = sf.next("{");
1287 std::string imagename_left = sf.next("{");
1288 std::string imagename_right = sf.next("{");
1290 // Generate images for the faces of the cube
1291 video::IImage *img_top =
1292 generateImageFromScratch(imagename_top);
1293 video::IImage *img_left =
1294 generateImageFromScratch(imagename_left);
1295 video::IImage *img_right =
1296 generateImageFromScratch(imagename_right);
1297 assert(img_top && img_left && img_right);
1299 // Create textures from images
1300 video::ITexture *texture_top = driver->addTexture(
1301 (imagename_top + "__temp__").c_str(), img_top);
1302 video::ITexture *texture_left = driver->addTexture(
1303 (imagename_left + "__temp__").c_str(), img_left);
1304 video::ITexture *texture_right = driver->addTexture(
1305 (imagename_right + "__temp__").c_str(), img_right);
1306 assert(texture_top && texture_left && texture_right);
1314 Draw a cube mesh into a render target texture
1316 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1317 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1318 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1319 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1320 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1321 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1322 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1323 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1325 TextureFromMeshParams params;
1327 params.dim.set(64, 64);
1328 params.rtt_texture_name = part_of_name + "_RTT";
1329 // We will delete the rtt texture ourselves
1330 params.delete_texture_on_shutdown = false;
1331 params.camera_position.set(0, 1.0, -1.5);
1332 params.camera_position.rotateXZBy(45);
1333 params.camera_lookat.set(0, 0, 0);
1334 // Set orthogonal projection
1335 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1336 1.65, 1.65, 0, 100);
1338 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1339 params.light_position.set(10, 100, -50);
1340 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1341 params.light_radius = 1000;
1343 video::ITexture *rtt = generateTextureFromMesh(params);
1348 // Free textures of images
1349 driver->removeTexture(texture_top);
1350 driver->removeTexture(texture_left);
1351 driver->removeTexture(texture_right);
1355 baseimg = generateImageFromScratch(imagename_top);
1359 // Create image of render target
1360 video::IImage *image = driver->createImage(rtt, v2s32(0,0), params.dim);
1364 driver->removeTexture(rtt);
1366 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1370 image->copyTo(baseimg);
1375 [lowpart:percent:filename
1376 Adds the lower part of a texture
1378 else if(part_of_name.substr(0,9) == "[lowpart:")
1380 Strfnd sf(part_of_name);
1382 u32 percent = stoi(sf.next(":"));
1383 std::string filename = sf.next(":");
1384 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1387 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1388 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1391 core::dimension2d<u32> dim = img->getDimension();
1392 core::position2d<s32> pos_base(0, 0);
1393 video::IImage *img2 =
1394 driver->createImage(video::ECF_A8R8G8B8, dim);
1397 core::position2d<s32> clippos(0, 0);
1398 clippos.Y = dim.Height * (100-percent) / 100;
1399 core::dimension2d<u32> clipdim = dim;
1400 clipdim.Height = clipdim.Height * percent / 100 + 1;
1401 core::rect<s32> cliprect(clippos, clipdim);
1402 img2->copyToWithAlpha(baseimg, pos_base,
1403 core::rect<s32>(v2s32(0,0), dim),
1404 video::SColor(255,255,255,255),
1411 Crops a frame of a vertical animation.
1412 N = frame count, I = frame index
1414 else if(part_of_name.substr(0,15) == "[verticalframe:")
1416 Strfnd sf(part_of_name);
1418 u32 frame_count = stoi(sf.next(":"));
1419 u32 frame_index = stoi(sf.next(":"));
1421 if(baseimg == NULL){
1422 errorstream<<"generateImage(): baseimg!=NULL "
1423 <<"for part_of_name=\""<<part_of_name
1424 <<"\", cancelling."<<std::endl;
1428 v2u32 frame_size = baseimg->getDimension();
1429 frame_size.Y /= frame_count;
1431 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1434 errorstream<<"generateImage(): Could not create image "
1435 <<"for part_of_name=\""<<part_of_name
1436 <<"\", cancelling."<<std::endl;
1440 // Fill target image with transparency
1441 img->fill(video::SColor(0,0,0,0));
1443 core::dimension2d<u32> dim = frame_size;
1444 core::position2d<s32> pos_dst(0, 0);
1445 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1446 baseimg->copyToWithAlpha(img, pos_dst,
1447 core::rect<s32>(pos_src, dim),
1448 video::SColor(255,255,255,255),
1456 errorstream<<"generateImage(): Invalid "
1457 " modification: \""<<part_of_name<<"\""<<std::endl;
1465 Draw an image on top of an another one, using the alpha channel of the
1468 This exists because IImage::copyToWithAlpha() doesn't seem to always
1471 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1472 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1474 for(u32 y0=0; y0<size.Y; y0++)
1475 for(u32 x0=0; x0<size.X; x0++)
1477 s32 src_x = src_pos.X + x0;
1478 s32 src_y = src_pos.Y + y0;
1479 s32 dst_x = dst_pos.X + x0;
1480 s32 dst_y = dst_pos.Y + y0;
1481 video::SColor src_c = src->getPixel(src_x, src_y);
1482 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1483 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1484 dst->setPixel(dst_x, dst_y, dst_c);
1489 Draw an image on top of an another one, using the alpha channel of the
1490 source image; only modify fully opaque pixels in destinaion
1492 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1493 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1495 for(u32 y0=0; y0<size.Y; y0++)
1496 for(u32 x0=0; x0<size.X; x0++)
1498 s32 src_x = src_pos.X + x0;
1499 s32 src_y = src_pos.Y + y0;
1500 s32 dst_x = dst_pos.X + x0;
1501 s32 dst_y = dst_pos.Y + y0;
1502 video::SColor src_c = src->getPixel(src_x, src_y);
1503 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1504 if(dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1506 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1507 dst->setPixel(dst_x, dst_y, dst_c);
1512 static void draw_crack(video::IImage *crack, video::IImage *dst,
1513 bool use_overlay, s32 frame_count, s32 progression,
1514 video::IVideoDriver *driver)
1516 // Dimension of destination image
1517 core::dimension2d<u32> dim_dst = dst->getDimension();
1518 // Dimension of original image
1519 core::dimension2d<u32> dim_crack = crack->getDimension();
1520 // Count of crack stages
1521 s32 crack_count = dim_crack.Height / dim_crack.Width;
1522 // Limit frame_count
1523 if(frame_count > (s32) dim_dst.Height)
1524 frame_count = dim_dst.Height;
1527 // Limit progression
1528 if(progression > crack_count-1)
1529 progression = crack_count-1;
1530 // Dimension of a single crack stage
1531 core::dimension2d<u32> dim_crack_cropped(
1535 // Dimension of the scaled crack stage,
1536 // which is the same as the dimension of a single destination frame
1537 core::dimension2d<u32> dim_crack_scaled(
1539 dim_dst.Height / frame_count
1541 // Create cropped and scaled crack images
1542 video::IImage *crack_cropped = driver->createImage(
1543 video::ECF_A8R8G8B8, dim_crack_cropped);
1544 video::IImage *crack_scaled = driver->createImage(
1545 video::ECF_A8R8G8B8, dim_crack_scaled);
1547 if(crack_cropped && crack_scaled)
1550 v2s32 pos_crack(0, progression*dim_crack.Width);
1551 crack->copyTo(crack_cropped,
1553 core::rect<s32>(pos_crack, dim_crack_cropped));
1554 // Scale crack image by copying
1555 crack_cropped->copyToScaling(crack_scaled);
1556 // Copy or overlay crack image onto each frame
1557 for(s32 i = 0; i < frame_count; ++i)
1559 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1562 blit_with_alpha_overlay(crack_scaled, dst,
1563 v2s32(0,0), dst_pos,
1568 blit_with_alpha(crack_scaled, dst,
1569 v2s32(0,0), dst_pos,
1576 crack_scaled->drop();
1579 crack_cropped->drop();
1582 void brighten(video::IImage *image)
1587 core::dimension2d<u32> dim = image->getDimension();
1589 for(u32 y=0; y<dim.Height; y++)
1590 for(u32 x=0; x<dim.Width; x++)
1592 video::SColor c = image->getPixel(x,y);
1593 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1594 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1595 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1596 image->setPixel(x,y,c);
1600 u32 parseImageTransform(const std::string& s)
1602 int total_transform = 0;
1604 std::string transform_names[8];
1605 transform_names[0] = "i";
1606 transform_names[1] = "r90";
1607 transform_names[2] = "r180";
1608 transform_names[3] = "r270";
1609 transform_names[4] = "fx";
1610 transform_names[6] = "fy";
1612 std::size_t pos = 0;
1613 while(pos < s.size())
1616 for(int i = 0; i <= 7; ++i)
1618 const std::string &name_i = transform_names[i];
1620 if(s[pos] == ('0' + i))
1626 else if(!(name_i.empty()) &&
1627 lowercase(s.substr(pos, name_i.size())) == name_i)
1630 pos += name_i.size();
1637 // Multiply total_transform and transform in the group D4
1640 new_total = (transform + total_transform) % 4;
1642 new_total = (transform - total_transform + 8) % 4;
1643 if((transform >= 4) ^ (total_transform >= 4))
1646 total_transform = new_total;
1648 return total_transform;
1651 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1653 if(transform % 2 == 0)
1656 return core::dimension2d<u32>(dim.Height, dim.Width);
1659 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1661 if(src == NULL || dst == NULL)
1664 core::dimension2d<u32> srcdim = src->getDimension();
1665 core::dimension2d<u32> dstdim = dst->getDimension();
1667 assert(dstdim == imageTransformDimension(transform, srcdim));
1668 assert(transform >= 0 && transform <= 7);
1671 Compute the transformation from source coordinates (sx,sy)
1672 to destination coordinates (dx,dy).
1676 if(transform == 0) // identity
1677 sxn = 0, syn = 2; // sx = dx, sy = dy
1678 else if(transform == 1) // rotate by 90 degrees ccw
1679 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1680 else if(transform == 2) // rotate by 180 degrees
1681 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1682 else if(transform == 3) // rotate by 270 degrees ccw
1683 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1684 else if(transform == 4) // flip x
1685 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1686 else if(transform == 5) // flip x then rotate by 90 degrees ccw
1687 sxn = 2, syn = 0; // sx = dy, sy = dx
1688 else if(transform == 6) // flip y
1689 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1690 else if(transform == 7) // flip y then rotate by 90 degrees ccw
1691 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1693 for(u32 dy=0; dy<dstdim.Height; dy++)
1694 for(u32 dx=0; dx<dstdim.Width; dx++)
1696 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1697 u32 sx = entries[sxn];
1698 u32 sy = entries[syn];
1699 video::SColor c = src->getPixel(sx,sy);
1700 dst->setPixel(dx,dy,c);