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;
169 const std::string &name_,
170 video::ITexture *texture_=NULL
179 SourceImageCache: A cache used for storing source images.
182 class SourceImageCache
185 ~SourceImageCache() {
186 for(std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
187 iter != m_images.end(); iter++) {
188 iter->second->drop();
192 void insert(const std::string &name, video::IImage *img,
193 bool prefer_local, video::IVideoDriver *driver)
197 std::map<std::string, video::IImage*>::iterator n;
198 n = m_images.find(name);
199 if(n != m_images.end()){
204 video::IImage* toadd = img;
205 bool need_to_grab = true;
207 // Try to use local texture instead if asked to
209 std::string path = getTexturePath(name.c_str());
211 video::IImage *img2 = driver->createImageFromFile(path.c_str());
214 need_to_grab = false;
221 m_images[name] = toadd;
223 video::IImage* get(const std::string &name)
225 std::map<std::string, video::IImage*>::iterator n;
226 n = m_images.find(name);
227 if(n != m_images.end())
231 // Primarily fetches from cache, secondarily tries to read from filesystem
232 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
234 std::map<std::string, video::IImage*>::iterator n;
235 n = m_images.find(name);
236 if(n != m_images.end()){
237 n->second->grab(); // Grab for caller
240 video::IVideoDriver* driver = device->getVideoDriver();
241 std::string path = getTexturePath(name.c_str());
243 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
244 <<name<<"\""<<std::endl;
247 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
249 video::IImage *img = driver->createImageFromFile(path.c_str());
252 m_images[name] = img;
253 img->grab(); // Grab for caller
258 std::map<std::string, video::IImage*> m_images;
265 class TextureSource : public IWritableTextureSource
268 TextureSource(IrrlichtDevice *device);
269 virtual ~TextureSource();
273 Now, assume a texture with the id 1 exists, and has the name
274 "stone.png^mineral1".
275 Then a random thread calls getTextureId for a texture called
276 "stone.png^mineral1^crack0".
277 ...Now, WTF should happen? Well:
278 - getTextureId strips off stuff recursively from the end until
279 the remaining part is found, or nothing is left when
280 something is stripped out
282 But it is slow to search for textures by names and modify them
284 - ContentFeatures is made to contain ids for the basic plain
286 - Crack textures can be slow by themselves, but the framework
290 - Assume a texture with the id 1 exists, and has the name
291 "stone.png^mineral_coal.png".
292 - Now getNodeTile() stumbles upon a node which uses
293 texture id 1, and determines that MATERIAL_FLAG_CRACK
294 must be applied to the tile
295 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
296 has received the current crack level 0 from the client. It
297 finds out the name of the texture with getTextureName(1),
298 appends "^crack0" to it and gets a new texture id with
299 getTextureId("stone.png^mineral_coal.png^crack0").
304 Gets a texture id from cache or
305 - if main thread, from getTextureIdDirect
306 - if other thread, adds to request queue and waits for main thread
308 u32 getTextureId(const std::string &name);
314 "stone.png^mineral_coal.png"
315 "stone.png^mineral_coal.png^crack1"
317 - If texture specified by name is found from cache, return the
319 - Otherwise generate the texture, add to cache and return id.
320 Recursion is used to find out the largest found part of the
321 texture and continue based on it.
323 The id 0 points to a NULL texture. It is returned in case of error.
325 u32 getTextureIdDirect(const std::string &name);
327 // Finds out the name of a cached texture.
328 std::string getTextureName(u32 id);
331 If texture specified by the name pointed by the id doesn't
332 exist, create it, then return the cached texture.
334 Can be called from any thread. If called from some other thread
335 and not found in cache, the call is queued to the main thread
338 video::ITexture* getTexture(u32 id);
340 video::ITexture* getTexture(const std::string &name, u32 *id);
342 // Returns a pointer to the irrlicht device
343 virtual IrrlichtDevice* getDevice()
348 bool isKnownSourceImage(const std::string &name)
350 bool is_known = false;
351 bool cache_found = m_source_image_existence.get(name, &is_known);
354 // Not found in cache; find out if a local file exists
355 is_known = (getTexturePath(name) != "");
356 m_source_image_existence.set(name, is_known);
360 // Processes queued texture requests from other threads.
361 // Shall be called from the main thread.
364 // Insert an image into the cache without touching the filesystem.
365 // Shall be called from the main thread.
366 void insertSourceImage(const std::string &name, video::IImage *img);
368 // Rebuild images and textures from the current set of source images
369 // Shall be called from the main thread.
370 void rebuildImagesAndTextures();
372 // Render a mesh to a texture.
373 // Returns NULL if render-to-texture failed.
374 // Shall be called from the main thread.
375 video::ITexture* generateTextureFromMesh(
376 const TextureFromMeshParams ¶ms);
378 // Generates an image from a full string like
379 // "stone.png^mineral_coal.png^[crack:1:0".
380 // Shall be called from the main thread.
381 video::IImage* generateImageFromScratch(std::string name);
383 // Generate image based on a string like "stone.png" or "[crack:1:0".
384 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
385 // Shall be called from the main thread.
386 bool generateImage(std::string part_of_name, video::IImage *& baseimg);
390 // The id of the thread that is allowed to use irrlicht directly
391 threadid_t m_main_thread;
392 // The irrlicht device
393 IrrlichtDevice *m_device;
395 // Cache of source images
396 // This should be only accessed from the main thread
397 SourceImageCache m_sourcecache;
399 // Thread-safe cache of what source images are known (true = known)
400 MutexedMap<std::string, bool> m_source_image_existence;
402 // A texture id is index in this array.
403 // The first position contains a NULL texture.
404 std::vector<TextureInfo> m_textureinfo_cache;
405 // Maps a texture name to an index in the former.
406 std::map<std::string, u32> m_name_to_id;
407 // The two former containers are behind this mutex
408 JMutex m_textureinfo_cache_mutex;
410 // Queued texture fetches (to be processed by the main thread)
411 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
413 // Textures that have been overwritten with other ones
414 // but can't be deleted because the ITexture* might still be used
415 std::list<video::ITexture*> m_texture_trash;
417 // Cached settings needed for making textures from meshes
418 bool m_setting_trilinear_filter;
419 bool m_setting_bilinear_filter;
420 bool m_setting_anisotropic_filter;
423 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
425 return new TextureSource(device);
428 TextureSource::TextureSource(IrrlichtDevice *device):
433 m_main_thread = get_current_thread_id();
435 // Add a NULL TextureInfo as the first index, named ""
436 m_textureinfo_cache.push_back(TextureInfo(""));
437 m_name_to_id[""] = 0;
439 // Cache some settings
440 // Note: Since this is only done once, the game must be restarted
441 // for these settings to take effect
442 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
443 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
444 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
447 TextureSource::~TextureSource()
449 video::IVideoDriver* driver = m_device->getVideoDriver();
451 unsigned int textures_before = driver->getTextureCount();
453 for (std::vector<TextureInfo>::iterator iter =
454 m_textureinfo_cache.begin();
455 iter != m_textureinfo_cache.end(); iter++)
459 driver->removeTexture(iter->texture);
461 m_textureinfo_cache.clear();
463 for (std::list<video::ITexture*>::iterator iter =
464 m_texture_trash.begin(); iter != m_texture_trash.end();
467 video::ITexture *t = *iter;
469 //cleanup trashed texture
470 driver->removeTexture(t);
473 infostream << "~TextureSource() "<< textures_before << "/"
474 << driver->getTextureCount() << std::endl;
477 u32 TextureSource::getTextureId(const std::string &name)
479 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
483 See if texture already exists
485 JMutexAutoLock lock(m_textureinfo_cache_mutex);
486 std::map<std::string, u32>::iterator n;
487 n = m_name_to_id.find(name);
488 if(n != m_name_to_id.end())
497 if(get_current_thread_id() == m_main_thread)
499 return getTextureIdDirect(name);
503 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
505 // We're gonna ask the result to be put into here
506 static ResultQueue<std::string, u32, u8, u8> result_queue;
508 // Throw a request in
509 m_get_texture_queue.add(name, 0, 0, &result_queue);
511 /*infostream<<"Waiting for texture from main thread, name=\""
512 <<name<<"\""<<std::endl;*/
517 // Wait result for a second
518 GetResult<std::string, u32, u8, u8>
519 result = result_queue.pop_front(1000);
521 if (result.key == name) {
526 catch(ItemNotFoundException &e)
528 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
533 infostream<<"getTextureId(): Failed"<<std::endl;
538 // Draw an image on top of an another one, using the alpha channel of the
540 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
541 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
543 // Like blit_with_alpha, but only modifies destination pixels that
545 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
546 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
548 // Draw or overlay a crack
549 static void draw_crack(video::IImage *crack, video::IImage *dst,
550 bool use_overlay, s32 frame_count, s32 progression,
551 video::IVideoDriver *driver);
554 void brighten(video::IImage *image);
555 // Parse a transform name
556 u32 parseImageTransform(const std::string& s);
557 // Apply transform to image dimension
558 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
559 // Apply transform to image data
560 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
563 This method generates all the textures
565 u32 TextureSource::getTextureIdDirect(const std::string &name)
567 //infostream<<"getTextureIdDirect(): name=\""<<name<<"\""<<std::endl;
569 // Empty name means texture 0
572 infostream<<"getTextureIdDirect(): name is empty"<<std::endl;
577 Calling only allowed from main thread
579 if(get_current_thread_id() != m_main_thread)
581 errorstream<<"TextureSource::getTextureIdDirect() "
582 "called not from main thread"<<std::endl;
587 See if texture already exists
590 JMutexAutoLock lock(m_textureinfo_cache_mutex);
592 std::map<std::string, u32>::iterator n;
593 n = m_name_to_id.find(name);
594 if(n != m_name_to_id.end())
596 /*infostream<<"getTextureIdDirect(): \""<<name
597 <<"\" found in cache"<<std::endl;*/
602 /*infostream<<"getTextureIdDirect(): \""<<name
603 <<"\" NOT found in cache. Creating it."<<std::endl;*/
609 char separator = '^';
612 This is set to the id of the base image.
613 If left 0, there is no base image and a completely new image
616 u32 base_image_id = 0;
618 // Find last meta separator in name
619 s32 last_separator_position = -1;
620 for(s32 i=name.size()-1; i>=0; i--)
622 if(name[i] == separator)
624 last_separator_position = i;
629 If separator was found, construct the base name and make the
630 base image using a recursive call
632 std::string base_image_name;
633 if(last_separator_position != -1)
635 // Construct base name
636 base_image_name = name.substr(0, last_separator_position);
637 /*infostream<<"getTextureIdDirect(): Calling itself recursively"
638 " to get base image of \""<<name<<"\" = \""
639 <<base_image_name<<"\""<<std::endl;*/
640 base_image_id = getTextureIdDirect(base_image_name);
643 //infostream<<"base_image_id="<<base_image_id<<std::endl;
645 video::IVideoDriver* driver = m_device->getVideoDriver();
648 video::ITexture *t = NULL;
651 An image will be built from files and then converted into a texture.
653 video::IImage *baseimg = NULL;
655 // If a base image was found, copy it to baseimg
656 if(base_image_id != 0)
658 JMutexAutoLock lock(m_textureinfo_cache_mutex);
660 TextureInfo *ti = &m_textureinfo_cache[base_image_id];
662 if(ti->texture == NULL)
664 infostream<<"getTextureIdDirect(): WARNING: NULL Texture in "
665 <<"cache: \""<<base_image_name<<"\""
670 core::dimension2d<u32> dim = ti->texture->getSize();
672 baseimg = driver->createImage(ti->texture,v2s32(0,0), dim);
674 /*infostream<<"getTextureIdDirect(): Loaded \""
675 <<base_image_name<<"\" from image cache"
681 Parse out the last part of the name of the image and act
685 std::string last_part_of_name = name.substr(last_separator_position+1);
686 //infostream<<"last_part_of_name=\""<<last_part_of_name<<"\""<<std::endl;
688 // Generate image according to part of name
689 if(!generateImage(last_part_of_name, baseimg))
691 errorstream<<"getTextureIdDirect(): "
692 "failed to generate \""<<last_part_of_name<<"\""
696 // If no resulting image, print a warning
699 errorstream<<"getTextureIdDirect(): baseimg is NULL (attempted to"
700 " create texture \""<<name<<"\""<<std::endl;
705 // Create texture from resulting image
706 t = driver->addTexture(name.c_str(), baseimg);
711 Add texture to caches (add NULL textures too)
714 JMutexAutoLock lock(m_textureinfo_cache_mutex);
716 u32 id = m_textureinfo_cache.size();
717 TextureInfo ti(name, t);
718 m_textureinfo_cache.push_back(ti);
719 m_name_to_id[name] = id;
724 std::string TextureSource::getTextureName(u32 id)
726 JMutexAutoLock lock(m_textureinfo_cache_mutex);
728 if(id >= m_textureinfo_cache.size())
730 errorstream<<"TextureSource::getTextureName(): id="<<id
731 <<" >= m_textureinfo_cache.size()="
732 <<m_textureinfo_cache.size()<<std::endl;
736 return m_textureinfo_cache[id].name;
739 video::ITexture* TextureSource::getTexture(u32 id)
741 JMutexAutoLock lock(m_textureinfo_cache_mutex);
743 if(id >= m_textureinfo_cache.size())
746 return m_textureinfo_cache[id].texture;
749 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
751 u32 actual_id = getTextureId(name);
755 return getTexture(actual_id);
758 void TextureSource::processQueue()
763 //NOTE this is only thread safe for ONE consumer thread!
764 if(!m_get_texture_queue.empty())
766 GetRequest<std::string, u32, u8, u8>
767 request = m_get_texture_queue.pop();
769 /*infostream<<"TextureSource::processQueue(): "
770 <<"got texture request with "
771 <<"name=\""<<request.key<<"\""
774 m_get_texture_queue.pushResult(request,getTextureIdDirect(request.key));
778 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
780 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
782 assert(get_current_thread_id() == m_main_thread);
784 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
785 m_source_image_existence.set(name, true);
788 void TextureSource::rebuildImagesAndTextures()
790 JMutexAutoLock lock(m_textureinfo_cache_mutex);
792 video::IVideoDriver* driver = m_device->getVideoDriver();
795 for(u32 i=0; i<m_textureinfo_cache.size(); i++){
796 TextureInfo *ti = &m_textureinfo_cache[i];
797 video::IImage *img = generateImageFromScratch(ti->name);
798 // Create texture from resulting image
799 video::ITexture *t = NULL;
801 t = driver->addTexture(ti->name.c_str(), img);
804 video::ITexture *t_old = ti->texture;
809 m_texture_trash.push_back(t_old);
813 video::ITexture* TextureSource::generateTextureFromMesh(
814 const TextureFromMeshParams ¶ms)
816 video::IVideoDriver *driver = m_device->getVideoDriver();
819 if(driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
821 static bool warned = false;
824 errorstream<<"TextureSource::generateTextureFromMesh(): "
825 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
831 // Create render target texture
832 video::ITexture *rtt = driver->addRenderTargetTexture(
833 params.dim, params.rtt_texture_name.c_str(),
834 video::ECF_A8R8G8B8);
837 errorstream<<"TextureSource::generateTextureFromMesh(): "
838 <<"addRenderTargetTexture returned NULL."<<std::endl;
843 driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0));
845 // Get a scene manager
846 scene::ISceneManager *smgr_main = m_device->getSceneManager();
848 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
851 scene::IMeshSceneNode* meshnode = smgr->addMeshSceneNode(params.mesh, NULL, -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
852 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
853 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
854 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
855 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
856 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
858 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
859 params.camera_position, params.camera_lookat);
860 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
861 camera->setProjectionMatrix(params.camera_projection_matrix, false);
863 smgr->setAmbientLight(params.ambient_light);
864 smgr->addLightSceneNode(0,
865 params.light_position,
867 params.light_radius);
870 driver->beginScene(true, true, video::SColor(0,0,0,0));
874 // NOTE: The scene nodes should not be dropped, otherwise
875 // smgr->drop() segfaults
879 // Drop scene manager
882 // Unset render target
883 driver->setRenderTarget(0, false, true, 0);
885 if(params.delete_texture_on_shutdown)
886 m_texture_trash.push_back(rtt);
891 video::IImage* TextureSource::generateImageFromScratch(std::string name)
893 /*infostream<<"generateImageFromScratch(): "
894 "\""<<name<<"\""<<std::endl;*/
896 video::IVideoDriver *driver = m_device->getVideoDriver();
903 video::IImage *baseimg = NULL;
905 char separator = '^';
907 // Find last meta separator in name
908 s32 last_separator_position = name.find_last_of(separator);
911 If separator was found, construct the base name and make the
912 base image using a recursive call
914 std::string base_image_name;
915 if(last_separator_position != -1)
917 // Construct base name
918 base_image_name = name.substr(0, last_separator_position);
919 baseimg = generateImageFromScratch(base_image_name);
923 Parse out the last part of the name of the image and act
927 std::string last_part_of_name = name.substr(last_separator_position+1);
929 // Generate image according to part of name
930 if(!generateImage(last_part_of_name, baseimg))
932 errorstream<<"generateImageFromScratch(): "
933 "failed to generate \""<<last_part_of_name<<"\""
941 bool TextureSource::generateImage(std::string part_of_name, video::IImage *& baseimg)
943 video::IVideoDriver* driver = m_device->getVideoDriver();
946 // Stuff starting with [ are special commands
947 if(part_of_name.size() == 0 || part_of_name[0] != '[')
949 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
952 if (!driver->queryFeature(irr::video::EVDF_TEXTURE_NPOT)) {
953 core::dimension2d<u32> dim = image->getDimension();
956 if ((dim.Height %2 != 0) ||
957 (dim.Width %2 != 0)) {
958 infostream << "TextureSource::generateImage "
959 << part_of_name << " size npot2 x=" << dim.Width
960 << " y=" << dim.Height << std::endl;
966 if (part_of_name != "") {
967 if (part_of_name.find("_normal.png") == std::string::npos){
968 errorstream<<"generateImage(): Could not load image \""
969 <<part_of_name<<"\""<<" while building texture"<<std::endl;
970 errorstream<<"generateImage(): Creating a dummy"
971 <<" image for \""<<part_of_name<<"\""<<std::endl;
973 infostream<<"generateImage(): Could not load normal map \""
974 <<part_of_name<<"\""<<std::endl;
975 infostream<<"generateImage(): Creating a dummy"
976 <<" normal map 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 s32 frame_count = stoi(sf.next(":"));
1062 s32 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, s32 frame_count, s32 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 s32 crack_count = dim_crack.Height / dim_crack.Width;
1511 // Limit frame_count
1512 if(frame_count > (s32) dim_dst.Height)
1513 frame_count = dim_dst.Height;
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(s32 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);