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.
22 #include <ICameraSceneNode.h>
23 #include "util/string.h"
24 #include "util/container.h"
25 #include "util/thread.h"
26 #include "util/numeric.h"
27 #include "irrlichttypes_extrabloated.h"
29 #include "main.h" // for g_settings
36 #include "util/string.h" // for parseColorString()
43 A cache from texture name to texture path
45 MutexedMap<std::string, std::string> g_texturename_to_path_cache;
48 Replaces the filename extension.
50 std::string image = "a/image.png"
51 replace_ext(image, "jpg")
52 -> image = "a/image.jpg"
53 Returns true on success.
55 static bool replace_ext(std::string &path, const char *ext)
59 // Find place of last dot, fail if \ or / found.
61 for (s32 i=path.size()-1; i>=0; i--)
69 if (path[i] == '\\' || path[i] == '/')
72 // If not found, return an empty string
75 // Else make the new path
76 path = path.substr(0, last_dot_i+1) + ext;
81 Find out the full path of an image by trying different filename
86 std::string getImagePath(std::string path)
88 // A NULL-ended list of possible image extensions
89 const char *extensions[] = {
90 "png", "jpg", "bmp", "tga",
91 "pcx", "ppm", "psd", "wal", "rgb",
94 // If there is no extension, add one
95 if (removeStringEnd(path, extensions) == "")
97 // Check paths until something is found to exist
98 const char **ext = extensions;
100 bool r = replace_ext(path, *ext);
103 if (fs::PathExists(path))
106 while((++ext) != NULL);
112 Gets the path to a texture by first checking if the texture exists
113 in texture_path and if not, using the data path.
115 Checks all supported extensions by replacing the original extension.
117 If not found, returns "".
119 Utilizes a thread-safe cache.
121 std::string getTexturePath(const std::string &filename)
123 std::string fullpath = "";
127 bool incache = g_texturename_to_path_cache.get(filename, &fullpath);
132 Check from texture_path
134 std::string texture_path = g_settings->get("texture_path");
135 if (texture_path != "")
137 std::string testpath = texture_path + DIR_DELIM + filename;
138 // Check all filename extensions. Returns "" if not found.
139 fullpath = getImagePath(testpath);
143 Check from default data directory
147 std::string base_path = porting::path_share + DIR_DELIM + "textures"
148 + DIR_DELIM + "base" + DIR_DELIM + "pack";
149 std::string testpath = base_path + DIR_DELIM + filename;
150 // Check all filename extensions. Returns "" if not found.
151 fullpath = getImagePath(testpath);
154 // Add to cache (also an empty result is cached)
155 g_texturename_to_path_cache.set(filename, fullpath);
161 void clearTextureNameCache()
163 g_texturename_to_path_cache.clear();
167 Stores internal information about a texture.
173 video::ITexture *texture;
176 const std::string &name_,
177 video::ITexture *texture_=NULL
186 SourceImageCache: A cache used for storing source images.
189 class SourceImageCache
192 ~SourceImageCache() {
193 for (std::map<std::string, video::IImage*>::iterator iter = m_images.begin();
194 iter != m_images.end(); iter++) {
195 iter->second->drop();
199 void insert(const std::string &name, video::IImage *img,
200 bool prefer_local, video::IVideoDriver *driver)
204 std::map<std::string, video::IImage*>::iterator n;
205 n = m_images.find(name);
206 if (n != m_images.end()){
211 video::IImage* toadd = img;
212 bool need_to_grab = true;
214 // Try to use local texture instead if asked to
216 std::string path = getTexturePath(name);
218 video::IImage *img2 = driver->createImageFromFile(path.c_str());
221 need_to_grab = false;
228 m_images[name] = toadd;
230 video::IImage* get(const std::string &name)
232 std::map<std::string, video::IImage*>::iterator n;
233 n = m_images.find(name);
234 if (n != m_images.end())
238 // Primarily fetches from cache, secondarily tries to read from filesystem
239 video::IImage* getOrLoad(const std::string &name, IrrlichtDevice *device)
241 std::map<std::string, video::IImage*>::iterator n;
242 n = m_images.find(name);
243 if (n != m_images.end()){
244 n->second->grab(); // Grab for caller
247 video::IVideoDriver* driver = device->getVideoDriver();
248 std::string path = getTexturePath(name);
250 infostream<<"SourceImageCache::getOrLoad(): No path found for \""
251 <<name<<"\""<<std::endl;
254 infostream<<"SourceImageCache::getOrLoad(): Loading path \""<<path
256 video::IImage *img = driver->createImageFromFile(path.c_str());
259 m_images[name] = img;
260 img->grab(); // Grab for caller
265 std::map<std::string, video::IImage*> m_images;
272 class TextureSource : public IWritableTextureSource
275 TextureSource(IrrlichtDevice *device);
276 virtual ~TextureSource();
280 Now, assume a texture with the id 1 exists, and has the name
281 "stone.png^mineral1".
282 Then a random thread calls getTextureId for a texture called
283 "stone.png^mineral1^crack0".
284 ...Now, WTF should happen? Well:
285 - getTextureId strips off stuff recursively from the end until
286 the remaining part is found, or nothing is left when
287 something is stripped out
289 But it is slow to search for textures by names and modify them
291 - ContentFeatures is made to contain ids for the basic plain
293 - Crack textures can be slow by themselves, but the framework
297 - Assume a texture with the id 1 exists, and has the name
298 "stone.png^mineral_coal.png".
299 - Now getNodeTile() stumbles upon a node which uses
300 texture id 1, and determines that MATERIAL_FLAG_CRACK
301 must be applied to the tile
302 - MapBlockMesh::animate() finds the MATERIAL_FLAG_CRACK and
303 has received the current crack level 0 from the client. It
304 finds out the name of the texture with getTextureName(1),
305 appends "^crack0" to it and gets a new texture id with
306 getTextureId("stone.png^mineral_coal.png^crack0").
311 Gets a texture id from cache or
312 - if main thread, generates the texture, adds to cache and returns id.
313 - if other thread, adds to request queue and waits for main thread.
315 The id 0 points to a NULL texture. It is returned in case of error.
317 u32 getTextureId(const std::string &name);
319 // Finds out the name of a cached texture.
320 std::string getTextureName(u32 id);
323 If texture specified by the name pointed by the id doesn't
324 exist, create it, then return the cached texture.
326 Can be called from any thread. If called from some other thread
327 and not found in cache, the call is queued to the main thread
330 video::ITexture* getTexture(u32 id);
332 video::ITexture* getTexture(const std::string &name, u32 *id);
334 // Returns a pointer to the irrlicht device
335 virtual IrrlichtDevice* getDevice()
340 bool isKnownSourceImage(const std::string &name)
342 bool is_known = false;
343 bool cache_found = m_source_image_existence.get(name, &is_known);
346 // Not found in cache; find out if a local file exists
347 is_known = (getTexturePath(name) != "");
348 m_source_image_existence.set(name, is_known);
352 // Processes queued texture requests from other threads.
353 // Shall be called from the main thread.
356 // Insert an image into the cache without touching the filesystem.
357 // Shall be called from the main thread.
358 void insertSourceImage(const std::string &name, video::IImage *img);
360 // Rebuild images and textures from the current set of source images
361 // Shall be called from the main thread.
362 void rebuildImagesAndTextures();
364 // Render a mesh to a texture.
365 // Returns NULL if render-to-texture failed.
366 // Shall be called from the main thread.
367 video::ITexture* generateTextureFromMesh(
368 const TextureFromMeshParams ¶ms);
370 // Generates an image from a full string like
371 // "stone.png^mineral_coal.png^[crack:1:0".
372 // Shall be called from the main thread.
373 video::IImage* generateImage(const std::string &name);
375 video::ITexture* getNormalTexture(const std::string &name);
378 // The id of the thread that is allowed to use irrlicht directly
379 threadid_t m_main_thread;
380 // The irrlicht device
381 IrrlichtDevice *m_device;
383 // Cache of source images
384 // This should be only accessed from the main thread
385 SourceImageCache m_sourcecache;
387 // Generate a texture
388 u32 generateTexture(const std::string &name);
390 // Generate image based on a string like "stone.png" or "[crack:1:0".
391 // if baseimg is NULL, it is created. Otherwise stuff is made on it.
392 bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
394 // Thread-safe cache of what source images are known (true = known)
395 MutexedMap<std::string, bool> m_source_image_existence;
397 // A texture id is index in this array.
398 // The first position contains a NULL texture.
399 std::vector<TextureInfo> m_textureinfo_cache;
400 // Maps a texture name to an index in the former.
401 std::map<std::string, u32> m_name_to_id;
402 // The two former containers are behind this mutex
403 JMutex m_textureinfo_cache_mutex;
405 // Queued texture fetches (to be processed by the main thread)
406 RequestQueue<std::string, u32, u8, u8> m_get_texture_queue;
408 // Textures that have been overwritten with other ones
409 // but can't be deleted because the ITexture* might still be used
410 std::list<video::ITexture*> m_texture_trash;
412 // Cached settings needed for making textures from meshes
413 bool m_setting_trilinear_filter;
414 bool m_setting_bilinear_filter;
415 bool m_setting_anisotropic_filter;
418 IWritableTextureSource* createTextureSource(IrrlichtDevice *device)
420 return new TextureSource(device);
423 TextureSource::TextureSource(IrrlichtDevice *device):
428 m_main_thread = get_current_thread_id();
430 // Add a NULL TextureInfo as the first index, named ""
431 m_textureinfo_cache.push_back(TextureInfo(""));
432 m_name_to_id[""] = 0;
434 // Cache some settings
435 // Note: Since this is only done once, the game must be restarted
436 // for these settings to take effect
437 m_setting_trilinear_filter = g_settings->getBool("trilinear_filter");
438 m_setting_bilinear_filter = g_settings->getBool("bilinear_filter");
439 m_setting_anisotropic_filter = g_settings->getBool("anisotropic_filter");
442 TextureSource::~TextureSource()
444 video::IVideoDriver* driver = m_device->getVideoDriver();
446 unsigned int textures_before = driver->getTextureCount();
448 for (std::vector<TextureInfo>::iterator iter =
449 m_textureinfo_cache.begin();
450 iter != m_textureinfo_cache.end(); iter++)
454 driver->removeTexture(iter->texture);
456 m_textureinfo_cache.clear();
458 for (std::list<video::ITexture*>::iterator iter =
459 m_texture_trash.begin(); iter != m_texture_trash.end();
462 video::ITexture *t = *iter;
464 //cleanup trashed texture
465 driver->removeTexture(t);
468 infostream << "~TextureSource() "<< textures_before << "/"
469 << driver->getTextureCount() << std::endl;
472 u32 TextureSource::getTextureId(const std::string &name)
474 //infostream<<"getTextureId(): \""<<name<<"\""<<std::endl;
478 See if texture already exists
480 JMutexAutoLock lock(m_textureinfo_cache_mutex);
481 std::map<std::string, u32>::iterator n;
482 n = m_name_to_id.find(name);
483 if (n != m_name_to_id.end())
492 if (get_current_thread_id() == m_main_thread)
494 return generateTexture(name);
498 infostream<<"getTextureId(): Queued: name=\""<<name<<"\""<<std::endl;
500 // We're gonna ask the result to be put into here
501 static ResultQueue<std::string, u32, u8, u8> result_queue;
503 // Throw a request in
504 m_get_texture_queue.add(name, 0, 0, &result_queue);
506 /*infostream<<"Waiting for texture from main thread, name=\""
507 <<name<<"\""<<std::endl;*/
512 // Wait result for a second
513 GetResult<std::string, u32, u8, u8>
514 result = result_queue.pop_front(1000);
516 if (result.key == name) {
521 catch(ItemNotFoundException &e)
523 errorstream<<"Waiting for texture " << name << " timed out."<<std::endl;
528 infostream<<"getTextureId(): Failed"<<std::endl;
533 // Draw an image on top of an another one, using the alpha channel of the
535 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
536 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
538 // Like blit_with_alpha, but only modifies destination pixels that
540 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
541 v2s32 src_pos, v2s32 dst_pos, v2u32 size);
543 // Like blit_with_alpha overlay, but uses an int to calculate the ratio
544 // and modifies any destination pixels that are not fully transparent
545 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
546 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio);
548 // Apply a mask to an image
549 static void apply_mask(video::IImage *mask, video::IImage *dst,
550 v2s32 mask_pos, v2s32 dst_pos, v2u32 size);
552 // Draw or overlay a crack
553 static void draw_crack(video::IImage *crack, video::IImage *dst,
554 bool use_overlay, s32 frame_count, s32 progression,
555 video::IVideoDriver *driver);
558 void brighten(video::IImage *image);
559 // Parse a transform name
560 u32 parseImageTransform(const std::string& s);
561 // Apply transform to image dimension
562 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim);
563 // Apply transform to image data
564 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst);
567 This method generates all the textures
569 u32 TextureSource::generateTexture(const std::string &name)
571 //infostream << "generateTexture(): name=\"" << name << "\"" << std::endl;
573 // Empty name means texture 0
575 infostream<<"generateTexture(): name is empty"<<std::endl;
581 See if texture already exists
583 JMutexAutoLock lock(m_textureinfo_cache_mutex);
584 std::map<std::string, u32>::iterator n;
585 n = m_name_to_id.find(name);
586 if (n != m_name_to_id.end()) {
592 Calling only allowed from main thread
594 if (get_current_thread_id() != m_main_thread) {
595 errorstream<<"TextureSource::generateTexture() "
596 "called not from main thread"<<std::endl;
600 video::IVideoDriver *driver = m_device->getVideoDriver();
603 video::IImage *img = generateImage(name);
605 video::ITexture *tex = NULL;
609 img = Align2Npot2(img, driver);
611 // Create texture from resulting image
612 tex = driver->addTexture(name.c_str(), img);
617 Add texture to caches (add NULL textures too)
620 JMutexAutoLock lock(m_textureinfo_cache_mutex);
622 u32 id = m_textureinfo_cache.size();
623 TextureInfo ti(name, tex);
624 m_textureinfo_cache.push_back(ti);
625 m_name_to_id[name] = id;
630 std::string TextureSource::getTextureName(u32 id)
632 JMutexAutoLock lock(m_textureinfo_cache_mutex);
634 if (id >= m_textureinfo_cache.size())
636 errorstream<<"TextureSource::getTextureName(): id="<<id
637 <<" >= m_textureinfo_cache.size()="
638 <<m_textureinfo_cache.size()<<std::endl;
642 return m_textureinfo_cache[id].name;
645 video::ITexture* TextureSource::getTexture(u32 id)
647 JMutexAutoLock lock(m_textureinfo_cache_mutex);
649 if (id >= m_textureinfo_cache.size())
652 return m_textureinfo_cache[id].texture;
655 video::ITexture* TextureSource::getTexture(const std::string &name, u32 *id)
657 u32 actual_id = getTextureId(name);
661 return getTexture(actual_id);
664 void TextureSource::processQueue()
669 //NOTE this is only thread safe for ONE consumer thread!
670 if (!m_get_texture_queue.empty())
672 GetRequest<std::string, u32, u8, u8>
673 request = m_get_texture_queue.pop();
675 /*infostream<<"TextureSource::processQueue(): "
676 <<"got texture request with "
677 <<"name=\""<<request.key<<"\""
680 m_get_texture_queue.pushResult(request, generateTexture(request.key));
684 void TextureSource::insertSourceImage(const std::string &name, video::IImage *img)
686 //infostream<<"TextureSource::insertSourceImage(): name="<<name<<std::endl;
688 assert(get_current_thread_id() == m_main_thread);
690 m_sourcecache.insert(name, img, true, m_device->getVideoDriver());
691 m_source_image_existence.set(name, true);
694 void TextureSource::rebuildImagesAndTextures()
696 JMutexAutoLock lock(m_textureinfo_cache_mutex);
698 video::IVideoDriver* driver = m_device->getVideoDriver();
702 for (u32 i=0; i<m_textureinfo_cache.size(); i++){
703 TextureInfo *ti = &m_textureinfo_cache[i];
704 video::IImage *img = generateImage(ti->name);
706 img = Align2Npot2(img, driver);
707 assert(img->getDimension().Height == npot2(img->getDimension().Height));
708 assert(img->getDimension().Width == npot2(img->getDimension().Width));
710 // Create texture from resulting image
711 video::ITexture *t = NULL;
713 t = driver->addTexture(ti->name.c_str(), img);
716 video::ITexture *t_old = ti->texture;
721 m_texture_trash.push_back(t_old);
725 video::ITexture* TextureSource::generateTextureFromMesh(
726 const TextureFromMeshParams ¶ms)
728 video::IVideoDriver *driver = m_device->getVideoDriver();
732 const GLubyte* renderstr = glGetString(GL_RENDERER);
733 std::string renderer((char*) renderstr);
735 // use no render to texture hack
737 (renderer.find("Adreno") != std::string::npos) ||
738 (renderer.find("Mali") != std::string::npos) ||
739 (renderer.find("Immersion") != std::string::npos) ||
740 (renderer.find("Tegra") != std::string::npos) ||
741 g_settings->getBool("inventory_image_hack")
743 // Get a scene manager
744 scene::ISceneManager *smgr_main = m_device->getSceneManager();
746 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
749 const float scaling = 0.2;
751 scene::IMeshSceneNode* meshnode =
752 smgr->addMeshSceneNode(params.mesh, NULL,
753 -1, v3f(0,0,0), v3f(0,0,0),
754 v3f(1.0 * scaling,1.0 * scaling,1.0 * scaling), true);
755 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
756 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
757 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
758 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
759 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
761 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
762 params.camera_position, params.camera_lookat);
763 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
764 camera->setProjectionMatrix(params.camera_projection_matrix, false);
766 smgr->setAmbientLight(params.ambient_light);
767 smgr->addLightSceneNode(0,
768 params.light_position,
770 params.light_radius*scaling);
772 core::dimension2d<u32> screen = driver->getScreenSize();
775 driver->beginScene(true, true, video::SColor(0,0,0,0));
776 driver->clearZBuffer();
779 core::dimension2d<u32> partsize(screen.Width * scaling,screen.Height * scaling);
781 irr::video::IImage* rawImage =
782 driver->createImage(irr::video::ECF_A8R8G8B8, partsize);
784 u8* pixels = static_cast<u8*>(rawImage->lock());
791 core::rect<s32> source(
792 screen.Width /2 - (screen.Width * (scaling / 2)),
793 screen.Height/2 - (screen.Height * (scaling / 2)),
794 screen.Width /2 + (screen.Width * (scaling / 2)),
795 screen.Height/2 + (screen.Height * (scaling / 2))
798 glReadPixels(source.UpperLeftCorner.X, source.UpperLeftCorner.Y,
799 partsize.Width, partsize.Height, GL_RGBA,
800 GL_UNSIGNED_BYTE, pixels);
804 // Drop scene manager
807 unsigned int pixelcount = partsize.Width*partsize.Height;
810 for (unsigned int i=0; i < pixelcount; i++) {
828 video::IImage* inventory_image =
829 driver->createImage(irr::video::ECF_A8R8G8B8, params.dim);
831 rawImage->copyToScaling(inventory_image);
834 video::ITexture *rtt = driver->addTexture(params.rtt_texture_name.c_str(), inventory_image);
835 inventory_image->drop();
838 errorstream << "TextureSource::generateTextureFromMesh(): failed to recreate texture from image: " << params.rtt_texture_name << std::endl;
842 driver->makeColorKeyTexture(rtt, v2s32(0,0));
844 if (params.delete_texture_on_shutdown)
845 m_texture_trash.push_back(rtt);
851 if (driver->queryFeature(video::EVDF_RENDER_TO_TARGET) == false)
853 static bool warned = false;
856 errorstream<<"TextureSource::generateTextureFromMesh(): "
857 <<"EVDF_RENDER_TO_TARGET not supported."<<std::endl;
863 // Create render target texture
864 video::ITexture *rtt = driver->addRenderTargetTexture(
865 params.dim, params.rtt_texture_name.c_str(),
866 video::ECF_A8R8G8B8);
869 errorstream<<"TextureSource::generateTextureFromMesh(): "
870 <<"addRenderTargetTexture returned NULL."<<std::endl;
875 if (!driver->setRenderTarget(rtt, false, true, video::SColor(0,0,0,0))) {
876 driver->removeTexture(rtt);
877 errorstream<<"TextureSource::generateTextureFromMesh(): "
878 <<"failed to set render target"<<std::endl;
882 // Get a scene manager
883 scene::ISceneManager *smgr_main = m_device->getSceneManager();
885 scene::ISceneManager *smgr = smgr_main->createNewSceneManager();
888 scene::IMeshSceneNode* meshnode =
889 smgr->addMeshSceneNode(params.mesh, NULL,
890 -1, v3f(0,0,0), v3f(0,0,0), v3f(1,1,1), true);
891 meshnode->setMaterialFlag(video::EMF_LIGHTING, true);
892 meshnode->setMaterialFlag(video::EMF_ANTI_ALIASING, true);
893 meshnode->setMaterialFlag(video::EMF_TRILINEAR_FILTER, m_setting_trilinear_filter);
894 meshnode->setMaterialFlag(video::EMF_BILINEAR_FILTER, m_setting_bilinear_filter);
895 meshnode->setMaterialFlag(video::EMF_ANISOTROPIC_FILTER, m_setting_anisotropic_filter);
897 scene::ICameraSceneNode* camera = smgr->addCameraSceneNode(0,
898 params.camera_position, params.camera_lookat);
899 // second parameter of setProjectionMatrix (isOrthogonal) is ignored
900 camera->setProjectionMatrix(params.camera_projection_matrix, false);
902 smgr->setAmbientLight(params.ambient_light);
903 smgr->addLightSceneNode(0,
904 params.light_position,
906 params.light_radius);
909 driver->beginScene(true, true, video::SColor(0,0,0,0));
913 // Drop scene manager
916 // Unset render target
917 driver->setRenderTarget(0, false, true, 0);
919 if (params.delete_texture_on_shutdown)
920 m_texture_trash.push_back(rtt);
925 video::IImage* TextureSource::generateImage(const std::string &name)
931 const char separator = '^';
932 const char paren_open = '(';
933 const char paren_close = ')';
935 // Find last separator in the name
936 s32 last_separator_pos = -1;
938 for (s32 i = name.size() - 1; i >= 0; i--) {
941 if (paren_bal == 0) {
942 last_separator_pos = i;
943 i = -1; // break out of loop
947 if (paren_bal == 0) {
948 errorstream << "generateImage(): unbalanced parentheses"
949 << "(extranous '(') while generating texture \""
950 << name << "\"" << std::endl;
963 errorstream << "generateImage(): unbalanced parentheses"
964 << "(missing matching '(') while generating texture \""
965 << name << "\"" << std::endl;
970 video::IImage *baseimg = NULL;
973 If separator was found, make the base image
974 using a recursive call.
976 if (last_separator_pos != -1) {
977 baseimg = generateImage(name.substr(0, last_separator_pos));
981 video::IVideoDriver* driver = m_device->getVideoDriver();
985 Parse out the last part of the name of the image and act
989 std::string last_part_of_name = name.substr(last_separator_pos + 1);
992 If this name is enclosed in parentheses, generate it
993 and blit it onto the base image
995 if (last_part_of_name[0] == paren_open
996 && last_part_of_name[last_part_of_name.size() - 1] == paren_close) {
997 std::string name2 = last_part_of_name.substr(1,
998 last_part_of_name.size() - 2);
999 video::IImage *tmp = generateImage(name2);
1001 errorstream << "generateImage(): "
1002 "Failed to generate \"" << name2 << "\""
1006 core::dimension2d<u32> dim = tmp->getDimension();
1008 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1009 blit_with_alpha(tmp, baseimg, v2s32(0, 0), v2s32(0, 0), dim);
1011 } else if (!generateImagePart(last_part_of_name, baseimg)) {
1012 // Generate image according to part of name
1013 errorstream << "generateImage(): "
1014 "Failed to generate \"" << last_part_of_name << "\""
1018 // If no resulting image, print a warning
1019 if (baseimg == NULL) {
1020 errorstream << "generateImage(): baseimg is NULL (attempted to"
1021 " create texture \"" << name << "\")" << std::endl;
1028 #include <GLES/gl.h>
1030 * Check and align image to npot2 if required by hardware
1031 * @param image image to check for npot2 alignment
1032 * @param driver driver to use for image operations
1033 * @return image or copy of image aligned to npot2
1035 video::IImage * Align2Npot2(video::IImage * image,
1036 video::IVideoDriver* driver)
1038 if (image == NULL) {
1042 core::dimension2d<u32> dim = image->getDimension();
1044 std::string extensions = (char*) glGetString(GL_EXTENSIONS);
1045 if (extensions.find("GL_OES_texture_npot") != std::string::npos) {
1049 unsigned int height = npot2(dim.Height);
1050 unsigned int width = npot2(dim.Width);
1052 if ((dim.Height == height) &&
1053 (dim.Width == width)) {
1057 if (dim.Height > height) {
1061 if (dim.Width > width) {
1065 video::IImage *targetimage =
1066 driver->createImage(video::ECF_A8R8G8B8,
1067 core::dimension2d<u32>(width, height));
1069 if (targetimage != NULL) {
1070 image->copyToScaling(targetimage);
1078 bool TextureSource::generateImagePart(std::string part_of_name,
1079 video::IImage *& baseimg)
1081 video::IVideoDriver* driver = m_device->getVideoDriver();
1084 // Stuff starting with [ are special commands
1085 if (part_of_name.size() == 0 || part_of_name[0] != '[')
1087 video::IImage *image = m_sourcecache.getOrLoad(part_of_name, m_device);
1089 image = Align2Npot2(image, driver);
1091 if (image == NULL) {
1092 if (part_of_name != "") {
1093 if (part_of_name.find("_normal.png") == std::string::npos){
1094 errorstream<<"generateImage(): Could not load image \""
1095 <<part_of_name<<"\""<<" while building texture"<<std::endl;
1096 errorstream<<"generateImage(): Creating a dummy"
1097 <<" image for \""<<part_of_name<<"\""<<std::endl;
1099 infostream<<"generateImage(): Could not load normal map \""
1100 <<part_of_name<<"\""<<std::endl;
1101 infostream<<"generateImage(): Creating a dummy"
1102 <<" normal map for \""<<part_of_name<<"\""<<std::endl;
1106 // Just create a dummy image
1107 //core::dimension2d<u32> dim(2,2);
1108 core::dimension2d<u32> dim(1,1);
1109 image = driver->createImage(video::ECF_A8R8G8B8, dim);
1111 /*image->setPixel(0,0, video::SColor(255,255,0,0));
1112 image->setPixel(1,0, video::SColor(255,0,255,0));
1113 image->setPixel(0,1, video::SColor(255,0,0,255));
1114 image->setPixel(1,1, video::SColor(255,255,0,255));*/
1115 image->setPixel(0,0, video::SColor(255,myrand()%256,
1116 myrand()%256,myrand()%256));
1117 /*image->setPixel(1,0, video::SColor(255,myrand()%256,
1118 myrand()%256,myrand()%256));
1119 image->setPixel(0,1, video::SColor(255,myrand()%256,
1120 myrand()%256,myrand()%256));
1121 image->setPixel(1,1, video::SColor(255,myrand()%256,
1122 myrand()%256,myrand()%256));*/
1125 // If base image is NULL, load as base.
1126 if (baseimg == NULL)
1128 //infostream<<"Setting "<<part_of_name<<" as base"<<std::endl;
1130 Copy it this way to get an alpha channel.
1131 Otherwise images with alpha cannot be blitted on
1132 images that don't have alpha in the original file.
1134 core::dimension2d<u32> dim = image->getDimension();
1135 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1136 image->copyTo(baseimg);
1138 // Else blit on base.
1141 //infostream<<"Blitting "<<part_of_name<<" on base"<<std::endl;
1142 // Size of the copied area
1143 core::dimension2d<u32> dim = image->getDimension();
1144 //core::dimension2d<u32> dim(16,16);
1145 // Position to copy the blitted to in the base image
1146 core::position2d<s32> pos_to(0,0);
1147 // Position to copy the blitted from in the blitted image
1148 core::position2d<s32> pos_from(0,0);
1150 /*image->copyToWithAlpha(baseimg, pos_to,
1151 core::rect<s32>(pos_from, dim),
1152 video::SColor(255,255,255,255),
1154 blit_with_alpha(image, baseimg, pos_from, pos_to, dim);
1161 // A special texture modification
1163 /*infostream<<"generateImage(): generating special "
1164 <<"modification \""<<part_of_name<<"\""
1170 Adds a cracking texture
1171 N = animation frame count, P = crack progression
1173 if (part_of_name.substr(0,6) == "[crack")
1175 if (baseimg == NULL) {
1176 errorstream<<"generateImagePart(): baseimg == NULL "
1177 <<"for part_of_name=\""<<part_of_name
1178 <<"\", cancelling."<<std::endl;
1182 // Crack image number and overlay option
1183 bool use_overlay = (part_of_name[6] == 'o');
1184 Strfnd sf(part_of_name);
1186 s32 frame_count = stoi(sf.next(":"));
1187 s32 progression = stoi(sf.next(":"));
1192 It is an image with a number of cracking stages
1195 video::IImage *img_crack = m_sourcecache.getOrLoad(
1196 "crack_anylength.png", m_device);
1198 if (img_crack && progression >= 0)
1200 draw_crack(img_crack, baseimg,
1201 use_overlay, frame_count,
1202 progression, driver);
1207 [combine:WxH:X,Y=filename:X,Y=filename2
1208 Creates a bigger texture from an amount of smaller ones
1210 else if (part_of_name.substr(0,8) == "[combine")
1212 Strfnd sf(part_of_name);
1214 u32 w0 = stoi(sf.next("x"));
1215 u32 h0 = stoi(sf.next(":"));
1216 //infostream<<"combined w="<<w0<<" h="<<h0<<std::endl;
1217 core::dimension2d<u32> dim(w0,h0);
1218 if (baseimg == NULL) {
1219 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1220 baseimg->fill(video::SColor(0,0,0,0));
1222 while (sf.atend() == false) {
1223 u32 x = stoi(sf.next(","));
1224 u32 y = stoi(sf.next("="));
1225 std::string filename = sf.next(":");
1226 infostream<<"Adding \""<<filename
1227 <<"\" to combined ("<<x<<","<<y<<")"
1229 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1231 core::dimension2d<u32> dim = img->getDimension();
1232 infostream<<"Size "<<dim.Width
1233 <<"x"<<dim.Height<<std::endl;
1234 core::position2d<s32> pos_base(x, y);
1235 video::IImage *img2 =
1236 driver->createImage(video::ECF_A8R8G8B8, dim);
1239 /*img2->copyToWithAlpha(baseimg, pos_base,
1240 core::rect<s32>(v2s32(0,0), dim),
1241 video::SColor(255,255,255,255),
1243 blit_with_alpha(img2, baseimg, v2s32(0,0), pos_base, dim);
1246 errorstream << "generateImagePart(): Failed to load image \""
1247 << filename << "\" for [combine" << std::endl;
1254 else if (part_of_name.substr(0,9) == "[brighten")
1256 if (baseimg == NULL) {
1257 errorstream<<"generateImagePart(): baseimg==NULL "
1258 <<"for part_of_name=\""<<part_of_name
1259 <<"\", cancelling."<<std::endl;
1267 Make image completely opaque.
1268 Used for the leaves texture when in old leaves mode, so
1269 that the transparent parts don't look completely black
1270 when simple alpha channel is used for rendering.
1272 else if (part_of_name.substr(0,8) == "[noalpha")
1274 if (baseimg == NULL){
1275 errorstream<<"generateImagePart(): baseimg==NULL "
1276 <<"for part_of_name=\""<<part_of_name
1277 <<"\", cancelling."<<std::endl;
1281 core::dimension2d<u32> dim = baseimg->getDimension();
1283 // Set alpha to full
1284 for (u32 y=0; y<dim.Height; y++)
1285 for (u32 x=0; x<dim.Width; x++)
1287 video::SColor c = baseimg->getPixel(x,y);
1289 baseimg->setPixel(x,y,c);
1294 Convert one color to transparent.
1296 else if (part_of_name.substr(0,11) == "[makealpha:")
1298 if (baseimg == NULL) {
1299 errorstream<<"generateImagePart(): baseimg == NULL "
1300 <<"for part_of_name=\""<<part_of_name
1301 <<"\", cancelling."<<std::endl;
1305 Strfnd sf(part_of_name.substr(11));
1306 u32 r1 = stoi(sf.next(","));
1307 u32 g1 = stoi(sf.next(","));
1308 u32 b1 = stoi(sf.next(""));
1309 std::string filename = sf.next("");
1311 core::dimension2d<u32> dim = baseimg->getDimension();
1313 /*video::IImage *oldbaseimg = baseimg;
1314 baseimg = driver->createImage(video::ECF_A8R8G8B8, dim);
1315 oldbaseimg->copyTo(baseimg);
1316 oldbaseimg->drop();*/
1318 // Set alpha to full
1319 for (u32 y=0; y<dim.Height; y++)
1320 for (u32 x=0; x<dim.Width; x++)
1322 video::SColor c = baseimg->getPixel(x,y);
1324 u32 g = c.getGreen();
1325 u32 b = c.getBlue();
1326 if (!(r == r1 && g == g1 && b == b1))
1329 baseimg->setPixel(x,y,c);
1334 Rotates and/or flips the image.
1336 N can be a number (between 0 and 7) or a transform name.
1337 Rotations are counter-clockwise.
1339 1 R90 rotate by 90 degrees
1340 2 R180 rotate by 180 degrees
1341 3 R270 rotate by 270 degrees
1343 5 FXR90 flip X then rotate by 90 degrees
1345 7 FYR90 flip Y then rotate by 90 degrees
1347 Note: Transform names can be concatenated to produce
1348 their product (applies the first then the second).
1349 The resulting transform will be equivalent to one of the
1350 eight existing ones, though (see: dihedral group).
1352 else if (part_of_name.substr(0,10) == "[transform")
1354 if (baseimg == NULL) {
1355 errorstream<<"generateImagePart(): baseimg == NULL "
1356 <<"for part_of_name=\""<<part_of_name
1357 <<"\", cancelling."<<std::endl;
1361 u32 transform = parseImageTransform(part_of_name.substr(10));
1362 core::dimension2d<u32> dim = imageTransformDimension(
1363 transform, baseimg->getDimension());
1364 video::IImage *image = driver->createImage(
1365 baseimg->getColorFormat(), dim);
1367 imageTransform(transform, baseimg, image);
1372 [inventorycube{topimage{leftimage{rightimage
1373 In every subimage, replace ^ with &.
1374 Create an "inventory cube".
1375 NOTE: This should be used only on its own.
1376 Example (a grass block (not actually used in game):
1377 "[inventorycube{grass.png{mud.png&grass_side.png{mud.png&grass_side.png"
1379 else if (part_of_name.substr(0,14) == "[inventorycube")
1381 if (baseimg != NULL){
1382 errorstream<<"generateImagePart(): baseimg != NULL "
1383 <<"for part_of_name=\""<<part_of_name
1384 <<"\", cancelling."<<std::endl;
1388 str_replace(part_of_name, '&', '^');
1389 Strfnd sf(part_of_name);
1391 std::string imagename_top = sf.next("{");
1392 std::string imagename_left = sf.next("{");
1393 std::string imagename_right = sf.next("{");
1395 // Generate images for the faces of the cube
1396 video::IImage *img_top = generateImage(imagename_top);
1397 video::IImage *img_left = generateImage(imagename_left);
1398 video::IImage *img_right = generateImage(imagename_right);
1400 if (img_top == NULL || img_left == NULL || img_right == NULL) {
1401 errorstream << "generateImagePart(): Failed to create textures"
1402 << " for inventorycube \"" << part_of_name << "\""
1404 baseimg = generateImage(imagename_top);
1409 assert(img_top->getDimension().Height == npot2(img_top->getDimension().Height));
1410 assert(img_top->getDimension().Width == npot2(img_top->getDimension().Width));
1412 assert(img_left->getDimension().Height == npot2(img_left->getDimension().Height));
1413 assert(img_left->getDimension().Width == npot2(img_left->getDimension().Width));
1415 assert(img_right->getDimension().Height == npot2(img_right->getDimension().Height));
1416 assert(img_right->getDimension().Width == npot2(img_right->getDimension().Width));
1419 // Create textures from images
1420 video::ITexture *texture_top = driver->addTexture(
1421 (imagename_top + "__temp__").c_str(), img_top);
1422 video::ITexture *texture_left = driver->addTexture(
1423 (imagename_left + "__temp__").c_str(), img_left);
1424 video::ITexture *texture_right = driver->addTexture(
1425 (imagename_right + "__temp__").c_str(), img_right);
1426 assert(texture_top && texture_left && texture_right);
1434 Draw a cube mesh into a render target texture
1436 scene::IMesh* cube = createCubeMesh(v3f(1, 1, 1));
1437 setMeshColor(cube, video::SColor(255, 255, 255, 255));
1438 cube->getMeshBuffer(0)->getMaterial().setTexture(0, texture_top);
1439 cube->getMeshBuffer(1)->getMaterial().setTexture(0, texture_top);
1440 cube->getMeshBuffer(2)->getMaterial().setTexture(0, texture_right);
1441 cube->getMeshBuffer(3)->getMaterial().setTexture(0, texture_right);
1442 cube->getMeshBuffer(4)->getMaterial().setTexture(0, texture_left);
1443 cube->getMeshBuffer(5)->getMaterial().setTexture(0, texture_left);
1445 TextureFromMeshParams params;
1447 params.dim.set(64, 64);
1448 params.rtt_texture_name = part_of_name + "_RTT";
1449 // We will delete the rtt texture ourselves
1450 params.delete_texture_on_shutdown = false;
1451 params.camera_position.set(0, 1.0, -1.5);
1452 params.camera_position.rotateXZBy(45);
1453 params.camera_lookat.set(0, 0, 0);
1454 // Set orthogonal projection
1455 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(
1456 1.65, 1.65, 0, 100);
1458 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
1459 params.light_position.set(10, 100, -50);
1460 params.light_color.set(1.0, 0.5, 0.5, 0.5);
1461 params.light_radius = 1000;
1463 video::ITexture *rtt = generateTextureFromMesh(params);
1469 driver->removeTexture(texture_top);
1470 driver->removeTexture(texture_left);
1471 driver->removeTexture(texture_right);
1474 baseimg = generateImage(imagename_top);
1478 // Create image of render target
1479 video::IImage *image = driver->createImage(rtt, v2s32(0, 0), params.dim);
1483 driver->removeTexture(rtt);
1485 baseimg = driver->createImage(video::ECF_A8R8G8B8, params.dim);
1488 image->copyTo(baseimg);
1493 [lowpart:percent:filename
1494 Adds the lower part of a texture
1496 else if (part_of_name.substr(0,9) == "[lowpart:")
1498 Strfnd sf(part_of_name);
1500 u32 percent = stoi(sf.next(":"));
1501 std::string filename = sf.next(":");
1502 //infostream<<"power part "<<percent<<"%% of "<<filename<<std::endl;
1504 if (baseimg == NULL)
1505 baseimg = driver->createImage(video::ECF_A8R8G8B8, v2u32(16,16));
1506 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1509 core::dimension2d<u32> dim = img->getDimension();
1510 core::position2d<s32> pos_base(0, 0);
1511 video::IImage *img2 =
1512 driver->createImage(video::ECF_A8R8G8B8, dim);
1515 core::position2d<s32> clippos(0, 0);
1516 clippos.Y = dim.Height * (100-percent) / 100;
1517 core::dimension2d<u32> clipdim = dim;
1518 clipdim.Height = clipdim.Height * percent / 100 + 1;
1519 core::rect<s32> cliprect(clippos, clipdim);
1520 img2->copyToWithAlpha(baseimg, pos_base,
1521 core::rect<s32>(v2s32(0,0), dim),
1522 video::SColor(255,255,255,255),
1529 Crops a frame of a vertical animation.
1530 N = frame count, I = frame index
1532 else if (part_of_name.substr(0,15) == "[verticalframe:")
1534 Strfnd sf(part_of_name);
1536 u32 frame_count = stoi(sf.next(":"));
1537 u32 frame_index = stoi(sf.next(":"));
1539 if (baseimg == NULL){
1540 errorstream<<"generateImagePart(): baseimg != NULL "
1541 <<"for part_of_name=\""<<part_of_name
1542 <<"\", cancelling."<<std::endl;
1546 v2u32 frame_size = baseimg->getDimension();
1547 frame_size.Y /= frame_count;
1549 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8,
1552 errorstream<<"generateImagePart(): Could not create image "
1553 <<"for part_of_name=\""<<part_of_name
1554 <<"\", cancelling."<<std::endl;
1558 // Fill target image with transparency
1559 img->fill(video::SColor(0,0,0,0));
1561 core::dimension2d<u32> dim = frame_size;
1562 core::position2d<s32> pos_dst(0, 0);
1563 core::position2d<s32> pos_src(0, frame_index * frame_size.Y);
1564 baseimg->copyToWithAlpha(img, pos_dst,
1565 core::rect<s32>(pos_src, dim),
1566 video::SColor(255,255,255,255),
1574 Applies a mask to an image
1576 else if (part_of_name.substr(0,6) == "[mask:")
1578 if (baseimg == NULL) {
1579 errorstream << "generateImage(): baseimg == NULL "
1580 << "for part_of_name=\"" << part_of_name
1581 << "\", cancelling." << std::endl;
1584 Strfnd sf(part_of_name);
1586 std::string filename = sf.next(":");
1588 video::IImage *img = m_sourcecache.getOrLoad(filename, m_device);
1590 apply_mask(img, baseimg, v2s32(0, 0), v2s32(0, 0),
1591 img->getDimension());
1593 errorstream << "generateImage(): Failed to load \""
1594 << filename << "\".";
1599 Overlays image with given color
1600 color = color as ColorString
1602 else if (part_of_name.substr(0,10) == "[colorize:") {
1603 Strfnd sf(part_of_name);
1605 std::string color_str = sf.next(":");
1606 std::string ratio_str = sf.next(":");
1608 if (baseimg == NULL) {
1609 errorstream << "generateImagePart(): baseimg != NULL "
1610 << "for part_of_name=\"" << part_of_name
1611 << "\", cancelling." << std::endl;
1615 video::SColor color;
1618 if (!parseColorString(color_str, color, false))
1621 if (is_number(ratio_str))
1622 ratio = mystoi(ratio_str, 0, 255);
1624 core::dimension2d<u32> dim = baseimg->getDimension();
1625 video::IImage *img = driver->createImage(video::ECF_A8R8G8B8, dim);
1628 errorstream << "generateImagePart(): Could not create image "
1629 << "for part_of_name=\"" << part_of_name
1630 << "\", cancelling." << std::endl;
1634 img->fill(video::SColor(color));
1635 // Overlay the colored image
1636 blit_with_interpolate_overlay(img, baseimg, v2s32(0,0), v2s32(0,0), dim, ratio);
1641 errorstream << "generateImagePart(): Invalid "
1642 " modification: \"" << part_of_name << "\"" << std::endl;
1650 Draw an image on top of an another one, using the alpha channel of the
1653 This exists because IImage::copyToWithAlpha() doesn't seem to always
1656 static void blit_with_alpha(video::IImage *src, video::IImage *dst,
1657 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1659 for (u32 y0=0; y0<size.Y; y0++)
1660 for (u32 x0=0; x0<size.X; x0++)
1662 s32 src_x = src_pos.X + x0;
1663 s32 src_y = src_pos.Y + y0;
1664 s32 dst_x = dst_pos.X + x0;
1665 s32 dst_y = dst_pos.Y + y0;
1666 video::SColor src_c = src->getPixel(src_x, src_y);
1667 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1668 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1669 dst->setPixel(dst_x, dst_y, dst_c);
1674 Draw an image on top of an another one, using the alpha channel of the
1675 source image; only modify fully opaque pixels in destinaion
1677 static void blit_with_alpha_overlay(video::IImage *src, video::IImage *dst,
1678 v2s32 src_pos, v2s32 dst_pos, v2u32 size)
1680 for (u32 y0=0; y0<size.Y; y0++)
1681 for (u32 x0=0; x0<size.X; x0++)
1683 s32 src_x = src_pos.X + x0;
1684 s32 src_y = src_pos.Y + y0;
1685 s32 dst_x = dst_pos.X + x0;
1686 s32 dst_y = dst_pos.Y + y0;
1687 video::SColor src_c = src->getPixel(src_x, src_y);
1688 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1689 if (dst_c.getAlpha() == 255 && src_c.getAlpha() != 0)
1691 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1692 dst->setPixel(dst_x, dst_y, dst_c);
1698 Draw an image on top of an another one, using the specified ratio
1699 modify all partially-opaque pixels in the destination.
1701 static void blit_with_interpolate_overlay(video::IImage *src, video::IImage *dst,
1702 v2s32 src_pos, v2s32 dst_pos, v2u32 size, int ratio)
1704 for (u32 y0 = 0; y0 < size.Y; y0++)
1705 for (u32 x0 = 0; x0 < size.X; x0++)
1707 s32 src_x = src_pos.X + x0;
1708 s32 src_y = src_pos.Y + y0;
1709 s32 dst_x = dst_pos.X + x0;
1710 s32 dst_y = dst_pos.Y + y0;
1711 video::SColor src_c = src->getPixel(src_x, src_y);
1712 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1713 if (dst_c.getAlpha() > 0 && src_c.getAlpha() != 0)
1716 dst_c = src_c.getInterpolated(dst_c, (float)src_c.getAlpha()/255.0f);
1718 dst_c = src_c.getInterpolated(dst_c, (float)ratio/255.0f);
1719 dst->setPixel(dst_x, dst_y, dst_c);
1725 Apply mask to destination
1727 static void apply_mask(video::IImage *mask, video::IImage *dst,
1728 v2s32 mask_pos, v2s32 dst_pos, v2u32 size)
1730 for (u32 y0 = 0; y0 < size.Y; y0++) {
1731 for (u32 x0 = 0; x0 < size.X; x0++) {
1732 s32 mask_x = x0 + mask_pos.X;
1733 s32 mask_y = y0 + mask_pos.Y;
1734 s32 dst_x = x0 + dst_pos.X;
1735 s32 dst_y = y0 + dst_pos.Y;
1736 video::SColor mask_c = mask->getPixel(mask_x, mask_y);
1737 video::SColor dst_c = dst->getPixel(dst_x, dst_y);
1738 dst_c.color &= mask_c.color;
1739 dst->setPixel(dst_x, dst_y, dst_c);
1744 static void draw_crack(video::IImage *crack, video::IImage *dst,
1745 bool use_overlay, s32 frame_count, s32 progression,
1746 video::IVideoDriver *driver)
1748 // Dimension of destination image
1749 core::dimension2d<u32> dim_dst = dst->getDimension();
1750 // Dimension of original image
1751 core::dimension2d<u32> dim_crack = crack->getDimension();
1752 // Count of crack stages
1753 s32 crack_count = dim_crack.Height / dim_crack.Width;
1754 // Limit frame_count
1755 if (frame_count > (s32) dim_dst.Height)
1756 frame_count = dim_dst.Height;
1757 if (frame_count < 1)
1759 // Limit progression
1760 if (progression > crack_count-1)
1761 progression = crack_count-1;
1762 // Dimension of a single crack stage
1763 core::dimension2d<u32> dim_crack_cropped(
1767 // Dimension of the scaled crack stage,
1768 // which is the same as the dimension of a single destination frame
1769 core::dimension2d<u32> dim_crack_scaled(
1771 dim_dst.Height / frame_count
1773 // Create cropped and scaled crack images
1774 video::IImage *crack_cropped = driver->createImage(
1775 video::ECF_A8R8G8B8, dim_crack_cropped);
1776 video::IImage *crack_scaled = driver->createImage(
1777 video::ECF_A8R8G8B8, dim_crack_scaled);
1779 if (crack_cropped && crack_scaled)
1782 v2s32 pos_crack(0, progression*dim_crack.Width);
1783 crack->copyTo(crack_cropped,
1785 core::rect<s32>(pos_crack, dim_crack_cropped));
1786 // Scale crack image by copying
1787 crack_cropped->copyToScaling(crack_scaled);
1788 // Copy or overlay crack image onto each frame
1789 for (s32 i = 0; i < frame_count; ++i)
1791 v2s32 dst_pos(0, dim_crack_scaled.Height * i);
1794 blit_with_alpha_overlay(crack_scaled, dst,
1795 v2s32(0,0), dst_pos,
1800 blit_with_alpha(crack_scaled, dst,
1801 v2s32(0,0), dst_pos,
1808 crack_scaled->drop();
1811 crack_cropped->drop();
1814 void brighten(video::IImage *image)
1819 core::dimension2d<u32> dim = image->getDimension();
1821 for (u32 y=0; y<dim.Height; y++)
1822 for (u32 x=0; x<dim.Width; x++)
1824 video::SColor c = image->getPixel(x,y);
1825 c.setRed(0.5 * 255 + 0.5 * (float)c.getRed());
1826 c.setGreen(0.5 * 255 + 0.5 * (float)c.getGreen());
1827 c.setBlue(0.5 * 255 + 0.5 * (float)c.getBlue());
1828 image->setPixel(x,y,c);
1832 u32 parseImageTransform(const std::string& s)
1834 int total_transform = 0;
1836 std::string transform_names[8];
1837 transform_names[0] = "i";
1838 transform_names[1] = "r90";
1839 transform_names[2] = "r180";
1840 transform_names[3] = "r270";
1841 transform_names[4] = "fx";
1842 transform_names[6] = "fy";
1844 std::size_t pos = 0;
1845 while(pos < s.size())
1848 for (int i = 0; i <= 7; ++i)
1850 const std::string &name_i = transform_names[i];
1852 if (s[pos] == ('0' + i))
1858 else if (!(name_i.empty()) &&
1859 lowercase(s.substr(pos, name_i.size())) == name_i)
1862 pos += name_i.size();
1869 // Multiply total_transform and transform in the group D4
1872 new_total = (transform + total_transform) % 4;
1874 new_total = (transform - total_transform + 8) % 4;
1875 if ((transform >= 4) ^ (total_transform >= 4))
1878 total_transform = new_total;
1880 return total_transform;
1883 core::dimension2d<u32> imageTransformDimension(u32 transform, core::dimension2d<u32> dim)
1885 if (transform % 2 == 0)
1888 return core::dimension2d<u32>(dim.Height, dim.Width);
1891 void imageTransform(u32 transform, video::IImage *src, video::IImage *dst)
1893 if (src == NULL || dst == NULL)
1896 core::dimension2d<u32> srcdim = src->getDimension();
1897 core::dimension2d<u32> dstdim = dst->getDimension();
1899 assert(dstdim == imageTransformDimension(transform, srcdim));
1900 assert(transform <= 7);
1903 Compute the transformation from source coordinates (sx,sy)
1904 to destination coordinates (dx,dy).
1908 if (transform == 0) // identity
1909 sxn = 0, syn = 2; // sx = dx, sy = dy
1910 else if (transform == 1) // rotate by 90 degrees ccw
1911 sxn = 3, syn = 0; // sx = (H-1) - dy, sy = dx
1912 else if (transform == 2) // rotate by 180 degrees
1913 sxn = 1, syn = 3; // sx = (W-1) - dx, sy = (H-1) - dy
1914 else if (transform == 3) // rotate by 270 degrees ccw
1915 sxn = 2, syn = 1; // sx = dy, sy = (W-1) - dx
1916 else if (transform == 4) // flip x
1917 sxn = 1, syn = 2; // sx = (W-1) - dx, sy = dy
1918 else if (transform == 5) // flip x then rotate by 90 degrees ccw
1919 sxn = 2, syn = 0; // sx = dy, sy = dx
1920 else if (transform == 6) // flip y
1921 sxn = 0, syn = 3; // sx = dx, sy = (H-1) - dy
1922 else if (transform == 7) // flip y then rotate by 90 degrees ccw
1923 sxn = 3, syn = 1; // sx = (H-1) - dy, sy = (W-1) - dx
1925 for (u32 dy=0; dy<dstdim.Height; dy++)
1926 for (u32 dx=0; dx<dstdim.Width; dx++)
1928 u32 entries[4] = {dx, dstdim.Width-1-dx, dy, dstdim.Height-1-dy};
1929 u32 sx = entries[sxn];
1930 u32 sy = entries[syn];
1931 video::SColor c = src->getPixel(sx,sy);
1932 dst->setPixel(dx,dy,c);
1936 video::ITexture* TextureSource::getNormalTexture(const std::string &name)
1939 if (isKnownSourceImage("override_normal.png"))
1940 return getTexture("override_normal.png", &id);
1941 std::string fname_base = name;
1942 std::string normal_ext = "_normal.png";
1943 size_t pos = fname_base.find(".");
1944 std::string fname_normal = fname_base.substr(0, pos) + normal_ext;
1945 if (isKnownSourceImage(fname_normal)) {
1946 // look for image extension and replace it
1948 while ((i = fname_base.find(".", i)) != std::string::npos) {
1949 fname_base.replace(i, 4, normal_ext);
1950 i += normal_ext.length();
1952 return getTexture(fname_base, &id);